fix: migrate item usage to htmx
authorxangelo <me@xangelo.ca>
Sat, 5 Aug 2023 09:08:36 +0000 (05:08 -0400)
committerxangelo <me@xangelo.ca>
Sat, 5 Aug 2023 09:08:36 +0000 (05:08 -0400)
src/server/api.ts
src/server/map.ts
src/server/views/inventory.ts
src/server/views/monster-selector.ts
src/server/views/player-bar.ts
src/server/views/stores.ts

index 46d4204ebc4de43b39310b43db733ab317b752e2..41f60325179a9b8f3c42952a45b8a7bec8592280 100644 (file)
@@ -9,13 +9,13 @@ import http from 'http';
 import { Server, Socket } from 'socket.io';
 import { logger } from './lib/logger';
 import { loadPlayer, createPlayer, updatePlayer, movePlayer } from './player';
-import * as _ from 'lodash';
+import { random, sample } from 'lodash';
 import {broadcastMessage, Message} from '../shared/message';
 import {expToLevel, maxHp, Player} from '../shared/player';
 import {clearFight, createFight, getMonsterList, getRandomMonster, loadMonster, loadMonsterFromFight, loadMonsterWithFaction, saveFightState} from './monster';
 import {FightRound} from '../shared/fight';
 import {addInventoryItem, deleteInventoryItem, getEquippedItems, getInventory, getInventoryItem, updateAp} from './inventory';
-import { getItemFromPlayer, getItemFromShop, getPlayersItems, getShopItems, givePlayerItem } from './items';
+import { getItemFromPlayer, getItemFromShop, getPlayersItems, getShopItems, givePlayerItem, updateItemCount } from './items';
 import {FightTrigger, Monster, MonsterForFight, MonsterWithFaction} from '../shared/monsters';
 import {getShopEquipment, getShopItem, listShopItems } from './shopEquipment';
 import {EquippedItemDetails} from '../shared/equipped';
@@ -45,7 +45,7 @@ import { createAllCitiesAndLocations } from '../../seeds/cities';
 import { createShopItems } from '../../seeds/shop_items';
 import { Item, PlayerItem, ShopItem } from 'shared/items';
 import { equip, unequip } from './equipment';
-import { random, sample } from 'lodash';
+import { HealthPotionSmall } from '../shared/items/health_potion';
 
 dotenv();
 
@@ -256,7 +256,7 @@ async function fightRound(player: Player, monster: MonsterWithFaction,  data: {a
     roundData.winner = 'monster';
     await clearFight(player.id);
 
-    return { roundData, monsters: [] };
+    return { roundData, monsters: [], player };
   }
 
   const attackType = data.action === 'attack' ? 'physical' : 'magical';
@@ -369,7 +369,7 @@ async function fightRound(player: Player, monster: MonsterWithFaction,  data: {a
 
     await clearFight(player.id);
     await updatePlayer(player);
-    return { roundData, monsters: potentialMonsters };
+    return { roundData, monsters: potentialMonsters, player };
   }
 
   roundData.roundDetails.push(`The ${monster.name} targeted your ${target}!`);
@@ -419,13 +419,13 @@ async function fightRound(player: Player, monster: MonsterWithFaction,  data: {a
     await updatePlayer(player);
     await clearTravelPlan(player.id);
 
-    return { roundData, monsters: []};
+    return { roundData, monsters: [], player};
   }
 
   await updatePlayer(player);
   await saveFightState(player.id, monster);
 
-  return { roundData, monsters: []};
+  return { roundData, monsters: [], player};
 };
 
 app.use(healerRouter);
@@ -761,6 +761,57 @@ app.get('/location/:location_id/items/:item_id/overview', authEndpoint, async (r
   res.send(html);
 });
 
+app.put('/item/:item_id', authEndpoint, async (req: Request, res: Response) => {
+  const authToken = req.headers['x-authtoken'].toString();
+  const player: Player = await loadPlayer(authToken)
+  if(!player) {
+    logger.log(`Couldnt find player with id ${authToken}`);
+    return res.sendStatus(400);
+  }
+
+  const item: PlayerItem = await getItemFromPlayer(player.id, parseInt(req.params.item_id));
+
+  if(!item) {
+    console.log(`Can't find item [${req.params.item_id}]`);
+    return;
+  }
+
+  if(item.amount < 1) {
+    res.send(Alert.ErrorAlert(`You dont have enough ${item.name}`));
+    return;
+  }
+
+  item.amount -= 1;
+
+  switch(item.effect_name) {
+    case 'heal_small':
+      const hpGain = HealthPotionSmall.effect(player);
+
+      player.hp += hpGain;
+
+      if(player.hp > maxHp(player.constitution, player.level)) {
+        player.hp = maxHp(player.constitution, player.level);
+      }
+    break;
+  }
+
+  await updateItemCount(player.id, item.item_id, -1);
+  await updatePlayer(player);
+
+  const inventory = await getInventory(player.id);
+  const equippedItems = inventory.filter(i => i.is_equipped);
+  const items = await getPlayersItems(player.id);
+
+  res.send(
+    [
+      renderPlayerBar(player, equippedItems),
+      renderInventoryPage(inventory, items, 'ITEMS'),
+      Alert.SuccessAlert(`You used the ${item.name}`)
+    ].join("")
+  );
+
+});
+
 app.get('/modal/items/:item_id', authEndpoint, async (req: Request, res: Response) => {
   const authToken = req.headers['x-authtoken'].toString();
   const player: Player = await loadPlayer(authToken)
@@ -777,21 +828,24 @@ app.get('/modal/items/:item_id', authEndpoint, async (req: Request, res: Respons
   }
 
   let html = `
-<div class="item-modal-overview">
-<div class="icon">
-<img src="/assets/img/icons/items/${item.icon_name}" title="${item.name}" alt="${item.name}"> 
-</div>
-<div>
-<h4>${item.name}</h4>
-<p>${item.description}</p>
-</div>
-</div>
-<div class="actions">
-<button class="emit-event close-modal" data-event="item:use:${item.effect_name}" data-args="${item.item_id}">Use</button>
-</div>
+<dialog>
+  <div class="item-modal-overview">
+    <div class="icon">
+      <img src="/assets/img/icons/items/${item.icon_name}" title="${item.name}" alt="${item.name}"> 
+    </div>
+    <div>
+      <h4>${item.name}</h4>
+      <p>${item.description}</p>
+    </div>
+  </div>
+  <div class="actions">
+    <button hx-put="/item/${item.item_id}" formmethod="dialog" value="cancel" hx-target="#inventory">Use</button>
+    <button class="close-modal" formmethod="dialog" value="cancel">Cancel</button>
+  </div>
+</dialog>
 `;
 
-  return res.json({ description: html });
+  res.send(html);
 });
 
 app.get('/city/stores/city:stores/:location_id', authEndpoint, async (req: Request, res: Response) => {
@@ -885,7 +939,7 @@ app.post('/fight/turn', authEndpoint, async (req: Request, res: Response) => {
   );
 
   if(fightData.monsters.length && monster.fight_trigger === 'explore') {
-    html += renderMonsterSelector(fightData.monsters);
+    html += renderMonsterSelector(fightData.monsters, monster.ref_id);
   }
 
   let travelSection = '';
@@ -897,7 +951,7 @@ app.post('/fight/turn', authEndpoint, async (req: Request, res: Response) => {
   }
 
   const equippedItems = await getEquippedItems(player.id);
-  const playerBar = renderPlayerBar(player, equippedItems);
+  const playerBar = renderPlayerBar(fightData.player, equippedItems);
 
   res.send(html + travelSection + playerBar);
 });
index d255c9740862d10f1eb630d8177876b0805ced82..54f0de8a6becc57f82f27f1f758f318e5ed6b857 100644 (file)
@@ -85,7 +85,7 @@ export async function stepForward(player_id: string): Promise<Travel> {
 }
 
 export async function clearTravelPlan(player_id: string): Promise<Travel> {
-  return completeTravel(player_id);
+  return db('travel').where({player_id}).delete();
 }
 
 export async function completeTravel(player_id: string): Promise<Travel> {
index 9616ad86c17c35754f83f339d9f31cdb76260662..08fce8e07027836a8d543d47efbe598b76b7df6a 100644 (file)
@@ -52,7 +52,7 @@ ${map.LEGS ? map.LEGS.name : 'LEGS'}
 function renderInventoryItems(items: PlayerItem[]): string {
   return items.map(item => {
     return `
-<div class="player-item trigger-modal" data-endpoint="/modal/items/${item.item_id}">
+<div class="player-item" hx-get="/modal/items/${item.item_id}" hx-target="#modal-wrapper">
 <img src="/assets/img/icons/items/${item.icon_name}" title="${item.name}">
 <span class="amount">${item.amount.toLocaleString()}</span>
 </div>`;
@@ -159,7 +159,7 @@ export function renderInventoryPage(inventory: EquippedItemDetails[], items: Pla
   });
 
   const html = `
-  <div id="inventory-page" hx-target="#inventory" hx-swap="innerHTML">
+  <div id="inventory-page" hx-target="section#inventory" hx-swap="true">
     <div id="character-summary">
       ${renderEquipmentPlacementGrid(inventory)}
     </div>
index 370de3bff96008ffb439fb30aa1c3dd7b558b028..8acb82f549cde7305111949298f2a22132da3c61 100644 (file)
@@ -1,11 +1,11 @@
 import { Monster, MonsterForFight } from "../../shared/monsters";
 
-export function renderMonsterSelector(monsters: Monster[] | MonsterForFight[]): string {
+export function renderMonsterSelector(monsters: Monster[] | MonsterForFight[], activeMonsterId: number = 0): string {
   let html = `<form id="fight-selector" hx-post="/fight" hx-target="#explore">
   <input type="hidden" name="fightTrigger" value="explore">
   <select id="monsterId" name="monsterId">
   ${monsters.map((monster) => {
-    return `<option value="${monster.id}">${monster.name}</option>`;
+      return `<option value="${monster.id}" ${monster.id === activeMonsterId ? 'selected': ''}>${monster.name}</option>`;
   }).join("\n")}
   </select> <button type="submit">Fight</button></form>`;
 
index 13b5b0b8c31de6c0ab044c9c913f534f3a92d71e..effbbdcbbef23918a0fb82c304960b16209cfab8 100644 (file)
@@ -61,7 +61,7 @@ export function renderPlayerBar(player: Player, inventory: EquippedItemDetails[]
       </div>
       <div id="ap-bar">${calcAp(inventory)}</div>
       ${progressBar(player.hp, maxHp(player.constitution, player.level), 'hp-bar', '#ff7070')}
-      ${progressBar(player.hp, expToLevel(player.level + 1), 'exp-bar', '#5997f9')}
+      ${progressBar(player.exp, expToLevel(player.level + 1), 'exp-bar', '#5997f9')}
     </div>
   `;
 
index c3cf881c43cf8f686f74ba4a6a5f742f2c4aefbf..6c237d1bd38bdcc9a1d45ca8597810449a5fa909 100644 (file)
@@ -118,7 +118,7 @@ export async function renderStore(equipment: ShopEquipment[], items: (ShopItem &
   });
 
   let html = `<div class="shop-inventory-listing filter-container">
-    <nav class="filter" id="shop-inventory-listing">${nav.join(" ")}</nav><div class="inventory-listing listing">
+    <nav class="filter" id="shop-inventory-listing">${nav.join("")}</nav><div class="inventory-listing listing">
       ${finalListing.join("\n")}
     </div>
   </div>`;