From 0740d4748ad637eec8caf40ad531c474f914bed4 Mon Sep 17 00:00:00 2001 From: Kyle Kemp Date: Fri, 23 Jun 2023 19:27:26 -0500 Subject: [PATCH] feat(item): can now sell items --- .../app/pages/inventory/inventory.page.html | 50 ++++++++++++++++--- .../src/app/pages/inventory/inventory.page.ts | 22 ++++++++ client/src/app/services/gameplay.service.ts | 6 +++ client/src/styles/ionic-overrides.scss | 5 ++ server/src/modules/content/content.service.ts | 6 ++- .../modules/inventory/inventory.service.ts | 17 +++++++ .../src/modules/player/gameplay.controller.ts | 9 +++- server/src/modules/player/gameplay.service.ts | 29 +++++++++++ shared/helpers/index.ts | 3 +- shared/helpers/item.ts | 42 ++++++++++++++++ 10 files changed, 178 insertions(+), 11 deletions(-) create mode 100644 shared/helpers/item.ts diff --git a/client/src/app/pages/inventory/inventory.page.html b/client/src/app/pages/inventory/inventory.page.html index b8cfb53..49c8f41 100644 --- a/client/src/app/pages/inventory/inventory.page.html +++ b/client/src/app/pages/inventory/inventory.page.html @@ -22,9 +22,15 @@ class="dark" [rows]="items" [rowHeight]="64" + [columnMode]="'force'" [sorts]="[{ prop: 'name', dir: 'asc' }]" > - + @@ -48,11 +54,10 @@ - - + + + {{ getValueForItem(row) | number }} + - + + > + + + + + + + + + + Sell + + + + + + diff --git a/client/src/app/pages/inventory/inventory.page.ts b/client/src/app/pages/inventory/inventory.page.ts index 21b8347..f97b745 100644 --- a/client/src/app/pages/inventory/inventory.page.ts +++ b/client/src/app/pages/inventory/inventory.page.ts @@ -3,6 +3,10 @@ import { IEquipment, IItem } from '@interfaces'; import { ContentService } from '@services/content.service'; import { PlayerService } from '@services/player.service'; +import { itemValue } from '@helpers/item'; +import { Store } from '@ngxs/store'; +import { GameplayService } from '@services/gameplay.service'; + @Component({ selector: 'app-inventory', templateUrl: './inventory.page.html', @@ -12,8 +16,10 @@ export class InventoryPage implements OnInit { public items: IItem[] = []; constructor( + private store: Store, public playerService: PlayerService, public contentService: ContentService, + private gameplayService: GameplayService, ) {} ngOnInit() { @@ -38,4 +44,20 @@ export class InventoryPage implements OnInit { getElementsForItem(item: IItem) { return (item as IEquipment).elements || []; } + + getValueForItem(item: IItem) { + return itemValue(item); + } + + valueComparison(vA: unknown, vB: unknown, itemA: IItem, itemB: IItem) { + return itemValue(itemB) - itemValue(itemA); + } + + sellItem(item: IItem, index: number) { + if (!item.instanceId) return; + + this.gameplayService.sellItem(item.instanceId).subscribe(() => { + this.items = this.items.filter((i) => i !== item); + }); + } } diff --git a/client/src/app/services/gameplay.service.ts b/client/src/app/services/gameplay.service.ts index f119972..085418f 100644 --- a/client/src/app/services/gameplay.service.ts +++ b/client/src/app/services/gameplay.service.ts @@ -30,4 +30,10 @@ export class GameplayService { isWaveBack, }); } + + sellItem(instanceId: string) { + return this.http.post(`${environment.apiUrl}/gameplay/sellitem`, { + instanceId, + }); + } } diff --git a/client/src/styles/ionic-overrides.scss b/client/src/styles/ionic-overrides.scss index 5077ca5..0a41938 100644 --- a/client/src/styles/ionic-overrides.scss +++ b/client/src/styles/ionic-overrides.scss @@ -27,3 +27,8 @@ ion-avatar { display: flex !important; align-items: center; } + +ion-button.icon-only { + --padding-start: 4px; + --padding-end: 4px; +} diff --git a/server/src/modules/content/content.service.ts b/server/src/modules/content/content.service.ts index e8ed8cf..e6c34c8 100644 --- a/server/src/modules/content/content.service.ts +++ b/server/src/modules/content/content.service.ts @@ -1,6 +1,6 @@ import { Injectable } from '@nestjs/common'; -import { ICollectible, IEquipment, IJob, ILocation } from '@interfaces'; +import { ICollectible, IEquipment, IItem, IJob, ILocation } from '@interfaces'; import * as fs from 'fs-extra'; import { Logger } from 'nestjs-pino'; @@ -84,4 +84,8 @@ export class ContentService { public getEquipment(equipment: string): IEquipment | undefined { return this.equipment[equipment]; } + + public getItem(item: string): IItem | undefined { + return this.equipment[item] || this.collectibles[item]; + } } diff --git a/server/src/modules/inventory/inventory.service.ts b/server/src/modules/inventory/inventory.service.ts index 115395c..433018f 100644 --- a/server/src/modules/inventory/inventory.service.ts +++ b/server/src/modules/inventory/inventory.service.ts @@ -47,6 +47,23 @@ export class InventoryService { return this.inventoryItems.find({ userId }); } + async getInventoryItemForUser( + userId: string, + instanceId: string, + ): Promise { + return this.inventoryItems.findOne({ userId, instanceId }); + } + + async removeInventoryItemForUser( + userId: string, + instanceId: string, + ): Promise { + const item = await this.getInventoryItemForUser(userId, instanceId); + if (!item) return; + + return this.em.remove(item); + } + async isInventoryFull(userId: string): Promise { const count = await this.inventoryItems.count({ userId, diff --git a/server/src/modules/player/gameplay.controller.ts b/server/src/modules/player/gameplay.controller.ts index 77e77f9..a79891b 100644 --- a/server/src/modules/player/gameplay.controller.ts +++ b/server/src/modules/player/gameplay.controller.ts @@ -67,7 +67,7 @@ export class GameplayController { @UseGuards(JwtAuthGuard) @ApiOperation({ summary: 'Explore Event: Take an item' }) @Post('takeitem') - async takeitem(@User() user) { + async takeItem(@User() user) { if (await this.inventoryService.isInventoryFull(user.userId)) { throw new BadRequestException('Inventory is full.'); } @@ -76,4 +76,11 @@ export class GameplayController { player: await this.gameplayService.takeItem(user.userId), }; } + + @UseGuards(JwtAuthGuard) + @ApiOperation({ summary: 'Sell an item' }) + @Post('sellitem') + async sellItem(@User() user, @Body('instanceId') instanceId: string) { + return await this.gameplayService.sellItem(user.userId, instanceId); + } } diff --git a/server/src/modules/player/gameplay.service.ts b/server/src/modules/player/gameplay.service.ts index 2fdc464..9e50cc1 100644 --- a/server/src/modules/player/gameplay.service.ts +++ b/server/src/modules/player/gameplay.service.ts @@ -1,3 +1,4 @@ +import { itemValue } from '@helpers/item'; import { IItem, ILocation, TrackedStat } from '@interfaces'; import { ConstantsService } from '@modules/content/constants.service'; import { ContentService } from '@modules/content/content.service'; @@ -390,4 +391,32 @@ export class GameplayService { return playerPatches; } + + async sellItem(userId: string, instanceId: string) { + if (!instanceId) throw new ForbiddenException('Item instance not found!'); + + const player = await this.playerService.getPlayerForUser(userId); + if (!player) throw new ForbiddenException('Player not found'); + + const itemRef = await this.inventoryService.getInventoryItemForUser( + userId, + instanceId, + ); + if (!itemRef) throw new ForbiddenException('Item ref not found!'); + + const item = this.contentService.getItem(itemRef.itemId); + if (!item) throw new ForbiddenException('Item existence not found!'); + + const coinsGained = itemValue(item); + await this.inventoryService.removeInventoryItemForUser(userId, instanceId); + + const playerPatches = await getPatchesAfterPropChanges( + player, + async (playerRef) => { + this.playerService.gainCoins(playerRef, coinsGained); + }, + ); + + return { player: playerPatches }; + } } diff --git a/shared/helpers/index.ts b/shared/helpers/index.ts index 6a6028a..3ebbf45 100644 --- a/shared/helpers/index.ts +++ b/shared/helpers/index.ts @@ -1 +1,2 @@ -export * from "./xp"; +export * from './item'; +export * from './xp'; diff --git a/shared/helpers/item.ts b/shared/helpers/item.ts new file mode 100644 index 0000000..3666454 --- /dev/null +++ b/shared/helpers/item.ts @@ -0,0 +1,42 @@ +import { IEquipment, IItem, Rarity, Stat } from '../interfaces/'; + +const multiplierPerRarity: Record = { + Common: 1, + Uncommon: 1.25, + Unusual: 1.75, + Rare: 2.5, + Masterful: 4, + Epic: 6.5, + Arcane: 9.5, + Divine: 11, + Unique: 13, +}; + +const valuePerStat: Record = { + health: 1, + magic: 3, + power: 5, + resistance: 2, + special: 10, + toughness: 4, +}; + +export function itemValue(item: IItem): number { + const levelRequirement = (item as IEquipment).levelRequirement ?? 1; + const itemStats = (item as IEquipment).stats ?? {}; + const multiplier = item.type === 'collectible' ? 50 : 1; + + let value = 1; + + Object.keys(itemStats).forEach((stat) => { + const statValue = + (itemStats[stat as Stat] ?? 0) * (valuePerStat[stat as Stat] ?? 0); + value += statValue; + }); + + value *= multiplier; + value *= multiplierPerRarity[item.rarity] ?? 1; + value *= Math.log(levelRequirement + 1); + + return Math.max(1, Math.floor(value)); +}