fix: spells support durability
authorxangelo <me@xangelo.ca>
Thu, 31 Aug 2023 21:08:45 +0000 (17:08 -0400)
committerxangelo <me@xangelo.ca>
Thu, 31 Aug 2023 21:08:45 +0000 (17:08 -0400)
Spells have "Uses" which function the same as durability except they
don't take damage after a battle (like weapons).

For now you go to the "repair" store to repair this.. but eventually you
can go to the:
- Mageshop to recharge spells
- Armoury to repair armour
- Forge to repair weapons

src/server/locations/healer/index.ts
src/server/locations/recruiter.ts
src/server/views/inventory.ts
src/server/views/monster-selector.ts
src/server/views/repair.ts
src/server/views/stores.ts
src/shared/inventory.ts

index 16274a2f7b23cd47bfcb464318221dd0596372ec..83551b2a7f532659a9b5116082369f934f558a2e 100644 (file)
@@ -7,6 +7,7 @@ import { getCityDetails, getService } from '../../map';
 import { sample } from 'lodash';
 import { City, Location } from "../../../shared/map";
 import { renderPlayerBar } from "../../views/player-bar";
+import { BackToTown } from "../../views/components/button";
 
 export const router = Router();
 
@@ -123,6 +124,7 @@ router.get('/city/services/healer/:location_id', authEndpoint, async (req: AuthR
 <h3 class="location-name"><span>${service.name}</span></h3>
 <div class="service-in-town">
 ${text.join("\n")}
+${BackToTown()}
 </div>
 </div>
   `);
@@ -155,7 +157,6 @@ router.post('/city/services/healer/heal/:location_id', authEndpoint, async (req:
     await updatePlayer(req.player);
 
     text.push(`<p>${getText('heal_successful', service, city)}</p>`);
-    text.push('<p><button hx-get="/player/explore" hx-target="#explore">Back to Town</button></p>');
   }
 
   res.send(`
@@ -164,6 +165,7 @@ router.post('/city/services/healer/heal/:location_id', authEndpoint, async (req:
 <h3 class="location-name"><span>${service.name}</span></h3>
 <div class="service-in-town">
 ${text.join("\n")}
+${BackToTown()}
 </div>
 </div>
 ${renderPlayerBar(req.player)}
index 4ce985c2eb819a88620ef9cdedd497add1aeb538..96f720d656cf6ce4332acc050ca956faef0d68cc 100644 (file)
@@ -5,6 +5,7 @@ import { logger } from "../lib/logger";
 import * as Alert from "../views/alert";
 import { changeProfession } from "../player";
 import { renderPlayerBar } from "../views/player-bar";
+import { BackToTown } from "../views/components/button";
 
 function p(str: string) {
   return `<p>${str}</p>`;
@@ -69,6 +70,7 @@ router.get('/city/services/profession_recruitor/:location_id', authEndpoint, asy
     html.push(`<p>However, you should visit the ${place} in ${town} that can probably provide some guidance!</p>`);
   }
 
+  html.push(BackToTown());
   res.send(`
     <div class="city-title-wrapper"><div class="city-title">${service.city_name}</div></div>
     <div class="city-details">
index e21d1fd2e74bbcedc94459ffb968dc20e22102dc..6026775d1c91152eb01586d7a2d5ecce36b64227 100644 (file)
@@ -2,6 +2,7 @@ import { EquipmentSlot } from "shared/inventory";
 import { EquippedItemDetails } from "../../shared/equipped";
 import { PlayerItem } from "../../shared/items";
 import { capitalize } from "lodash";
+import { ProgressBar } from "./components/progress-bar";
 
 function icon(icon_name?: string): string {
   const placeholder = 'https://placehold.co/64x64/af936c/6d5f4d';
@@ -82,15 +83,6 @@ 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 generateProgressBar(current: number, max: number, color: string, displayPercent: boolean = true): string {
-  let percent = 0;
-  if(max > 0) {
-    percent = Math.floor((current / max) * 100);
-  }
-  const display = `${displayPercent? `${percent}% - `: ''}`;
-  return `<div class="progress-bar" style="background: linear-gradient(to right, ${color}, ${color} ${percent}%, transparent ${percent}%, transparent)" title="${display}${current}/${max}">${display}${current}/${max}</div>`;
-}
-
 function renderInventoryItem(item: EquippedItemDetails , action: (item: EquippedItemDetails) => string): string {
   return `<div class="store-list">
     <div class="inventory-icon" style="background-image: url('${icon(item.icon)}')">
@@ -113,7 +105,12 @@ function renderInventoryItem(item: EquippedItemDetails , action: (item: Equipped
         ${item.boosts.intelligence ? renderStatBoost('INT', item.boosts.intelligence) : ''}
         ${item.boosts.damage ? renderStatBoost('DMG', item.boosts.damage) : ''}
         ${item.boosts.damage_mitigation ? renderStatBoost('MIT', item.boosts.damage_mitigation.toString())+'%' : ''}
-        ${['SPELL'].includes(item.type) ? '': generateProgressBar(item.currentAp, item.maxAp, '#7be67b')}
+        ${ProgressBar(item.currentAp, item.maxAp, `dur-${item.item_id}`, {
+          startingColor: '#7be67b',
+          endingColor: '#7be67b',
+          displayPercent: false,
+          title: item.type === 'SPELL' ? 'Uses' : 'Durability'
+        })}
       </div>
       ${item.hasOwnProperty('id') ? `<div>${item.cost.toLocaleString()}G</div>` : ''}
       </div>
index 58c8665f480da4a72dd6c6338fc92da04b698434..e357835be3082955452dfd74686657b29a54e35a 100644 (file)
@@ -1,6 +1,7 @@
 import { max } from "lodash";
 import { LocationWithCity } from "../../shared/map";
 import { Monster, MonsterForFight } from "../../shared/monsters";
+import { BackToTown } from "./components/button";
 
 export function renderOnlyMonsterSelector(monsters: Monster[] | MonsterForFight[], activeMonsterId: number = 0, location?: LocationWithCity): string {
   let html = `
@@ -25,7 +26,11 @@ export function renderMonsterSelector(monsters: Monster[] | MonsterForFight[], a
       const range = [monster.level, monster.level + 3];
       return `<option value="${monster.id}" ${monster.id === activeMonsterId ? 'selected': ''}>${monster.name} (${range[0]} - ${range[1]})</option>`;
   }).join("\n")}
-  </select> <button type="submit" class="red">Fight</button></form></div>
+  </select> <button type="submit" class="red">Fight</button></form>
+<br><br>
+${BackToTown()}
+
+</div>
 `;
 
   return html;
index 447dc84edb2bb754641fc807d7d2313024a3da51..a049f9cad23f4b6e8ab02376d1194898448848e9 100644 (file)
@@ -5,6 +5,7 @@ import { LocationWithCity } from "../../shared/map";
 import { EquippedItemDetails } from "../../shared/equipped";
 import { ProgressBar } from "./components/progress-bar";
 import * as City from './components/city';
+import { BackToTown } from "./components/button";
 
 function renderStatBoost(name: string, val: number | string): string {
   let valSign: string = '';
@@ -42,9 +43,9 @@ export function renderEquipmentDetails(item: EquippedItemDetails, player: Player
       ${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_mitigation ? renderStatBoost('MIT', item.boosts.damage_mitigation.toString())+'%' : ''}
-      ${['WEAPON','SPELL'].includes(item.type) ? '' :  ProgressBar(item.currentAp, item.maxAp, `${item.item_id}-ap`, {
+      ${ProgressBar(item.currentAp, item.maxAp, `${item.item_id}-ap`, {
 displayPercent: false,
-title: 'Durability',
+title: item.type === 'SPELL' ? 'Uses' : 'Durability',
 startingColor: '#7be67b',
 endingColor: '#7be67b'
 })}
@@ -78,6 +79,9 @@ export function renderRepairService(equipment: EquippedItemDetails[], player: Pl
   }
 
   equipment.forEach(item => {
+    if(item.maxAp <= 0) {
+      return;
+    }
     const filter = item.type === 'ARMOUR' ? item.equipment_slot : item.type;
 
     listingTypes.add(filter);
@@ -109,6 +113,7 @@ export function renderRepairService(equipment: EquippedItemDetails[], player: Pl
       ${finalListing.join("\n")}
     </div>
   </div>
+  ${BackToTown()}
 `)}`;
 
   return html;
index be20cf07aece0951e8b30995835ff756b6f91703..10617031c2c4ea6ef93108b8aee74cfd59dec482 100644 (file)
@@ -4,6 +4,7 @@ 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";
 
 type RenderStatOptions = {
   unsigned: boolean
@@ -54,6 +55,8 @@ function renderShopItem(item: (ShopItem & Item), action: (item: (ShopItem & Item
 }
 
 export function renderEquipmentDetails(item: ShopEquipment, player: Player): string {
+  const isSpell = item.type === 'SPELL';
+
   return `
     <div class="details">
       <div class="name">${item.name}${item.equipment_slot === 'TWO_HANDED' ? ' (2H)': ''}</div>
@@ -73,7 +76,7 @@ export function renderEquipmentDetails(item: ShopEquipment, player: Player): str
       ${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_mitigation ? renderStatBoost('MIT', item.boosts.damage_mitigation.toString())+'%' : ''}
-      ${['SPELL'].includes(item.type) ? '' : renderStat('Durability', 'DUR', item.maxAp, { unsigned: true })}
+      ${renderStat(isSpell ? 'Uses': 'Durability', isSpell ? 'Uses': 'DUR', item.maxAp, { unsigned: true })}
       </div>
       ${item.hasOwnProperty('id') ? `<div class="store-cost">${item.cost.toLocaleString()}G</div>` : ''}
     </div>
@@ -140,6 +143,7 @@ export async function renderStore(equipment: ShopEquipment[], items: (ShopItem &
       ${finalListing.join("\n")}
     </div>
   </div>
+${BackToTown()}
 </div>`;
 
   return html;
index 0ed26ddef78e988274df8da97b3832ac253911d3..9084cc82d7ea473e7bc609dbaf3c7d4341ca4f1b 100644 (file)
@@ -1,5 +1,6 @@
 import {Profession} from "./profession";
 import {SkillID} from "./skills";
+import { max } from 'lodash';
 
 export type InventoryType = 'ARMOUR' | 'WEAPON' | 'SPELL';
 
@@ -52,5 +53,5 @@ export function repairCost(item: InventoryItem): number {
 
   const damageRatio = 1 - (item.currentAp / item.maxAp);
 
-  return Math.floor(totalCost * damageRatio);
+  return max([Math.floor(totalCost * damageRatio), 1]);
 }