feat: display base damage and level-based boost
authorxangelo <me@xangelo.ca>
Thu, 17 Oct 2024 20:21:32 +0000 (16:21 -0400)
committerxangelo <me@xangelo.ca>
Fri, 18 Oct 2024 18:51:45 +0000 (14:51 -0400)
Weapon and spell damage has two components:
- A static value, that the item comes with
- A "dynamic" value based on your skills

The total damage that it deals is a combination of those two values.
This change displays the static value AND the amount you gain based on
your skills.

src/server/fight.ts
src/server/routes/inventory.ts
src/server/views/components/stats.ts [new file with mode: 0644]
src/server/views/inventory.ts
src/server/views/stores.ts
src/shared/inventory.ts
src/shared/player.ts
src/shared/stats.ts

index 93e9b3c5e768b84e50ffb1c5ebe0e39eb12d99ee..3a97a9df6ca7761ae134b1542f4061440df65727 100644 (file)
@@ -1,6 +1,6 @@
 import {FightRound} from '../shared/fight';
 import { clearFight, loadMonster, getMonsterList, saveFightState, loadMonsterFromFight } from './monster';
-import { Player, expToLevel, maxHp, totalDefence, maxVigor } from '../shared/player';
+import { Player, expToLevel, maxHp, totalDefence, maxVigor, baseDamage } from '../shared/player';
 import { clearTravelPlan } from './map';
 import { updatePlayer } from './player';
 import { getEquippedItems, updateAp } from './inventory';
@@ -111,7 +111,7 @@ export async function fightRound(player: Player, monster: Fight,  data: {action:
   const primaryStat = attackType === 'physical' ? player.strength : player.intelligence;
   const boostStat = attackType === 'physical' ? boost.strength : boost.intelligence;
 
-  const playerDamage = Math.floor(((primaryStat + boostStat) * 1.3) + boost.damage);
+  const playerDamage = baseDamage(primaryStat, boostStat, boost.damage);
   const skillsUsed: Record<SkillID | any, number> = {};
   let hpHealAfterMasteries: number = -1;
   let playerDamageAfterMasteries: number = 0;
index cf88bdf35ed49e636e48cf3d4f3447e1a7b92fa4..31c9bb11eeb17b3004c0b84466210040e0465da0 100644 (file)
@@ -23,7 +23,7 @@ inventoryRouter.get('/player/inventory', authEndpoint, async (req: Request, res:
     getPlayersItems(req.player.id)
   ]);
 
-  res.send(renderInventoryPage(inventory, items));
+  res.send(renderInventoryPage(req.player, inventory, items));
 
 });
 
@@ -38,7 +38,7 @@ inventoryRouter.post('/player/unequip/:item_id', authEndpoint, blockPlayerInFigh
     getPlayersItems(req.player.id)
   ]);
 
-  res.send(renderInventoryPage(inventory, items, item.type) + renderPlayerBar(req.player));
+  res.send(renderInventoryPage(req.player, inventory, items, item.type) + renderPlayerBar(req.player));
 });
 
 inventoryRouter.post('/player/equip/:item_id/:slot', authEndpoint, blockPlayerInFight, blockPlayerInDungeon, async (req: Request, res: Response) => {
@@ -90,7 +90,7 @@ inventoryRouter.post('/player/equip/:item_id/:slot', authEndpoint, blockPlayerIn
     getPlayersItems(req.player.id)
   ]);
 
-  res.send(renderInventoryPage(inventory, items, inventoryItem.type) + renderPlayerBar(req.player));
+  res.send(renderInventoryPage(req.player, inventory, items, inventoryItem.type) + renderPlayerBar(req.player));
 });
 
 inventoryRouter.put('/item/:item_id', authEndpoint, async (req: Request, res: Response) => {
@@ -129,7 +129,7 @@ inventoryRouter.put('/item/:item_id', authEndpoint, async (req: Request, res: Re
   res.send(
     [
       renderPlayerBar(req.player),
-      renderInventoryPage(inventory, items, 'ITEMS'),
+      renderInventoryPage(req.player, inventory, items, 'ITEMS'),
       Alert.SuccessAlert(`You used the ${item.name}`)
     ].join("")
   );
diff --git a/src/server/views/components/stats.ts b/src/server/views/components/stats.ts
new file mode 100644 (file)
index 0000000..2e313fd
--- /dev/null
@@ -0,0 +1,21 @@
+import { EquippedItemDetails } from "../../../shared/equipped";
+import { ItemStatBoostAbbr, ShopEquipment } from "../../../shared/inventory";
+import { baseDamage, Player } from "../../../shared/player";
+
+export function renderStatBoostWithPlayerIncrease(player: Player, name: ItemStatBoostAbbr, item: ShopEquipment | EquippedItemDetails) {
+  const val = item.boosts.damage;
+  let valSign: string = '';
+  if(typeof item.boosts.damage === 'number') {
+    valSign = item.boosts.damage > 0 ? '+' : '-';
+  }
+
+  const primaryStat = item.type === 'WEAPON' ? player.strength : player.intelligence;
+  const boostStat = item.type === 'WEAPON' ? item.boosts.strength : item.boosts.intelligence;
+
+  const effectiveDamage = baseDamage(primaryStat, boostStat, item.boosts.damage);
+  const diff = effectiveDamage - val;
+
+  // calculate any stat boost from the player
+  return `<span class="requirement-title">${name}</span>: 
+<span class="requirement-value tooltip ${typeof val === 'number' ? (val > 0 ? "success": "error") : ""}" title="${val}(item) + ${diff}(from stats)">${valSign}${effectiveDamage}</span>`;
+}
index 88e3c8c338342180b9e93d84d5b45adf2970d3af..d9b8c435de68f7f19e9d13a3f28b3f20d251b468 100644 (file)
@@ -3,6 +3,8 @@ import { EquippedItemDetails } from "../../shared/equipped";
 import { PlayerItem } from "../../shared/items";
 import { capitalize } from "lodash";
 import { ProgressBar } from "./components/progress-bar";
+import { Player } from "../../shared/player";
+import { renderStatBoostWithPlayerIncrease } from "./components/stats";
 
 function icon(icon_name?: string): string {
   const placeholder = 'https://placehold.co/64x64/af936c/6d5f4d';
@@ -83,7 +85,7 @@ function renderStatBoost(name: string, val: number | string): string {
   return `<span class="requirement-title">${name}</span>: <span class="requirement-value ${typeof val === 'number' ? (val > 0 ? "success": "error") : ""}">${valSign}${val}</span>`;
 }
 
-function renderInventoryItem(item: EquippedItemDetails , action: (item: EquippedItemDetails) => string): string {
+function renderInventoryItem(player: Player, item: EquippedItemDetails , action: (item: EquippedItemDetails) => string): string {
   return `<div class="store-list">
     <div class="inventory-icon" style="background-image: url('${icon(item.icon)}')">
     </div>
@@ -103,7 +105,7 @@ function renderInventoryItem(item: EquippedItemDetails , action: (item: Equipped
         ${item.boosts.constitution ? renderStatBoost('CON', item.boosts.constitution) : ''}
         ${item.boosts.dexterity ? renderStatBoost('DEX', item.boosts.dexterity) : ''}
         ${item.boosts.intelligence ? renderStatBoost('INT', item.boosts.intelligence) : ''}
-        ${item.boosts.damage ? renderStatBoost('DMG', item.boosts.damage) : ''}
+        ${item.boosts.damage ? renderStatBoostWithPlayerIncrease(player, item.affectedSkills.includes('restoration_magic') ? 'HP' : 'DMG', item) : ''}
         ${item.boosts.damage_mitigation ? renderStatBoost('MIT', item.boosts.damage_mitigation.toString())+'%' : ''}
         ${ProgressBar(item.currentAp, item.maxAp, `dur-${item.item_id}`, {
           startingColor: '#7be67b',
@@ -120,11 +122,11 @@ function renderInventoryItem(item: EquippedItemDetails , action: (item: Equipped
     </div>`;
 }
 
-function renderInventorySection(inventory: EquippedItemDetails[]): string {
+function renderInventorySection(player: Player, inventory: EquippedItemDetails[]): string {
   const used_slots = inventory.filter(i => i.is_equipped).map(i => i.equipment_slot);
 
   return inventory.map(item => {
-    return renderInventoryItem(item, item => {
+    return renderInventoryItem(player, item, item => {
       if(item.is_equipped) {
         return `<button type="button" class="unequip-item red" hx-post="/player/unequip/${item.item_id}">Unequip</button>`;
       }
@@ -155,7 +157,7 @@ function renderInventorySection(inventory: EquippedItemDetails[]): string {
 }
 
 
-export function renderInventoryPage(inventory: EquippedItemDetails[], items: PlayerItem[], requestedSlot: string = 'ARMOUR') {
+export function renderInventoryPage(player: Player, inventory: EquippedItemDetails[], items: PlayerItem[], requestedSlot: string = 'ARMOUR') {
   const sectionedInventory: {ARMOUR: EquippedItemDetails[], WEAPON: EquippedItemDetails[], SPELL: EquippedItemDetails[], ITEMS: PlayerItem[]} = {
     ARMOUR: [],
     WEAPON: [],
@@ -185,7 +187,7 @@ export function renderInventoryPage(inventory: EquippedItemDetails[], items: Pla
       <div class="inventory-listing listing">
         ${Object.keys(sectionedInventory).map((type) => {
           return `<div class="filter-result inventory-${type} ${type === requestedSlot ? 'active': 'hidden'}" data-filter="${type}" id="filter_${type}">
-          ${type === 'ITEMS' ? renderInventoryItems(sectionedInventory[type]) : renderInventorySection(sectionedInventory[type])}
+          ${type === 'ITEMS' ? renderInventoryItems(sectionedInventory[type]) : renderInventorySection(player, sectionedInventory[type])}
           </div>`;
         }).join("")}
       </div>
index 10617031c2c4ea6ef93108b8aee74cfd59dec482..dcbdcaf9d579bb7802be0b2b9a7c21241d8a8b7e 100644 (file)
@@ -1,10 +1,11 @@
-import { ShopEquipment } from "../../shared/inventory";
+import { ItemStatBoostAbbr, ShopEquipment } from "../../shared/inventory";
 import { ShopItem, Item } from "../../shared/items";
 import { capitalize, merge } from "lodash";
 import { Player } from "../../shared/player";
 import { LocationWithCity } from "shared/map";
 import { ProgressBar } from "./components/progress-bar";
 import { BackToTown } from "./components/button";
+import { renderStatBoostWithPlayerIncrease } from "./components/stats";
 
 type RenderStatOptions = {
   unsigned: boolean
@@ -23,7 +24,7 @@ function renderStat(title: string, display: string, val: any, options?: RenderSt
   return `<span title="${title}"><span class="requirement-title">${display}</span>: <span class="requirement-value ${opts.unsigned ? '' : (val > 0 ? "success": "error")}">${opts.unsigned ? val : `${valSign}${val}`}</span></span>`;
 }
 
-function renderStatBoost(name: string, val: number | string, title?: string): string {
+function renderStatBoost(name: ItemStatBoostAbbr, val: number | string, title?: string): string {
   let valSign: string = '';
   if(typeof val === 'number') {
     valSign = val > 0 ? '+' : '-';
@@ -74,7 +75,7 @@ export function renderEquipmentDetails(item: ShopEquipment, player: Player): str
       ${item.boosts.constitution ? renderStatBoost('CON', item.boosts.constitution) : ''}
       ${item.boosts.dexterity ? renderStatBoost('DEX', item.boosts.dexterity) : ''}
       ${item.boosts.intelligence ? renderStatBoost('INT', item.boosts.intelligence) : ''}
-      ${item.boosts.damage ? renderStatBoost(item.affectedSkills.includes('restoration_magic') ? 'HP' : 'DMG', item.boosts.damage) : ''}
+      ${item.boosts.damage ? renderStatBoostWithPlayerIncrease(player, item.affectedSkills.includes('restoration_magic') ? 'HP' : 'DMG', item) : ''}
       ${item.boosts.damage_mitigation ? renderStatBoost('MIT', item.boosts.damage_mitigation.toString())+'%' : ''}
       ${renderStat(isSpell ? 'Uses': 'Durability', isSpell ? 'Uses': 'DUR', item.maxAp, { unsigned: true })}
       </div>
@@ -144,7 +145,7 @@ export async function renderStore(equipment: ShopEquipment[], items: (ShopItem &
     </div>
   </div>
 ${BackToTown()}
-</div>`;
+</div>`
 
   return html;
 }
index 9084cc82d7ea473e7bc609dbaf3c7d4341ca4f1b..f78d65b6e30984273451fccb17f10e9a1d13b15b 100644 (file)
@@ -9,6 +9,15 @@ export type WeaponEquipmentSlot = 'LEFT_HAND' | 'RIGHT_HAND' | 'TWO_HANDED' | 'A
 
 export type EquipmentSlot = ArmourEquipmentSlot | WeaponEquipmentSlot;
 
+export type ItemStatBoostAbbr = 'DEF' | 
+'DMG' | 
+'STR' |
+'CON' |
+'DEX' | 
+'INT' | 
+'HP' | 
+'MIT';
+
 
 export type InventoryItem = {
   item_id: string;
index 339396dce76a04bc2a36dfadb2d869d689eee287..d9aac734ee109881e458aaf6cb76b7669a3b0900 100644 (file)
@@ -69,6 +69,9 @@ export function totalDefence(equippedItems: EquippedItemDetails[], player: Playe
   }
 }
 
+export function baseDamage(statValue: number, boostStat: number, damage: number): number {
+  return Math.floor(((statValue + boostStat) * 1.3) + damage);
+}
 
 export type StatDisplay = {
   id: Stat,
index a553bd4e0c86407491d7cd79967b0439f3a598a8..14ae4fd03beabd21ebfd6677c9dd5f5152c9e84e 100644 (file)
@@ -4,3 +4,5 @@ export enum Stat {
   dexterity = 'dexterity',
   intelligence = 'intelligence'
 };
+
+export type STAT_ABR = 'STR' | 'CON' | 'DEX' | 'INT';