fix: migrate healer to htmx
authorxangelo <me@xangelo.ca>
Thu, 3 Aug 2023 19:52:24 +0000 (15:52 -0400)
committerxangelo <me@xangelo.ca>
Thu, 3 Aug 2023 19:54:11 +0000 (15:54 -0400)
src/client/index.ts
src/events/healer/client.ts [deleted file]
src/events/healer/server.ts [deleted file]
src/server/api.ts
src/server/auth.ts
src/server/locations/healer/index.ts [new file with mode: 0644]
src/server/views/map.ts

index ce08207ce9aaf89110ce1551692dc472a8fd27cf..b460ead9f20cdf7cb2f78c6a5db6f11b9388a2f4 100644 (file)
@@ -1111,3 +1111,7 @@ function bootstrap() {
   $('nav a').first().click();
 
 }
+document.body.addEventListener('htmx:configRequest', function(evt) {
+  //@ts-ignore
+  evt.detail.headers['x-authtoken'] = authToken();
+});
diff --git a/src/events/healer/client.ts b/src/events/healer/client.ts
deleted file mode 100644 (file)
index 56a0da1..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-import { SocketEvent } from '../../client/socket-event.client';
-import $ from 'jquery';
-
-export const displayHealerDetauls: SocketEvent = new SocketEvent('city:service:healer', (api, data: {text: string}) => {
-  $('#map').html(data.text);
-});
diff --git a/src/events/healer/server.ts b/src/events/healer/server.ts
deleted file mode 100644 (file)
index df8fb8a..0000000
+++ /dev/null
@@ -1,171 +0,0 @@
-import {SocketEvent} from "../../server/socket-event.server";
-import { getCityDetails, getService } from "../../server/map";
-import { City, Location } from "../../shared/map";
-import {maxHp, Player} from '../../shared/player';
-import { updatePlayer } from "../../server/player";
-import { sample } from 'lodash';
-
-type TextSegment = 'intro' | 'insufficient_money' | 'heal_successful';
-
-type HealText = Record<TextSegment, string[]>;
-
-const healCost = 10;
-
-const defaultTexts: HealText = {
-  intro: [
-    `Welcome traveller, I am {{NAME}}, Healer of {{CITY_NAME}}`,
-    "Please come in traveller, I am {{NAME}}, Healer of {{CITY_NAME}}",
-  ],
-  insufficient_money: [
-    "Sorry friend, you don't have enough money..",
-    "Sorry, that won't be enough..",
-    "Healing is hard work.. I'm afraid that won't cover it.."
-  ],
-  heal_successful: [
-    "I hope you feel better now",
-    "Good luck on your travels!",
-    "Glad to be of service..."
-  ]
-}
-
-// overrides for specific areas
-const playerTexts: Record<number, HealText> = {
-  [8]: {
-    intro: [
-      'Welcome to Midfield traveller, I am Casim - healer in these parts',
-      'I am Casim the Healer here... how are you enjoying your stay at Midfield?'
-    ],
-    insufficient_money: [
-      'Sorry friend, you don\'t have enough money',
-      'Look.. I\'m sorry.. that won\'t be enough...'
-    ],
-    heal_successful: [
-      'Glad to help!'
-    ]
-  },
-  [16]: {
-    intro: [
-      'Ah, welcome to Wildegard, one of the few safehavens in the Akari Woods. I am Adovras, healer in these parts.',
-      'Welcome traveller, I am Adovras - healer in these parts'
-    ],
-    insufficient_money: [
-      `Sorry friend, you don't have enough money...`
-    ],
-    heal_successful: [
-      "Hope this small healing will be helpful on your journeys"
-    ]
-
-  },
-  [11]: {
-    intro: [
-      'Ah, welcome traveler - I am Uthar, healer of Davelfell',
-      'Hello, I am Uthar, healer of Davelfell',
-      'Sorry I\'m a bit busy today, I am Uthar, healer of Davelfell'
-    ],
-    insufficient_money: [
-      "Bah, don't bother me if you don't have the money",
-      "Look, I'm very busy - come back when you have the money"
-    ],
-    heal_successful: [
-      "*Fizz* *POOF* YOU'RE HEALED!"
-    ]
-  }
-}
-
-function getText(type: TextSegment, location: Location, city: City): string {
-  let selected = sample(defaultTexts[type]);
-
-  if(playerTexts[location.id]) {
-    if(playerTexts[location.id][type].length) {
-      selected = sample(playerTexts[location.id][type]);
-    }
-  }
-
-  return selected.replace("{{NAME}}", location.name).replace("{{CITY_NAME}}", city.name);
-
-}
-
-export const healer: SocketEvent = {
-  eventName: 'city:services:healer',
-  handler: async (api, data: { args: number }) => {
-    const text: string[] = [];
-    const healerId = data.args;
-    const service = await getService(healerId);
-
-    if(service.city_id !== api.player.city_id) {
-      api.socket.emit('alert', {
-        type: 'error',
-        text: `You don't seem to be in the right city...`
-      });
-      return;
-    }
-
-    const city = await getCityDetails(service.city_id);
-
-    text.push(`<p><b>${service.name}</b></p>`);
-    text.push(`<p>"${getText('intro', service, city)}"</p>`);
-
-
-    if(api.player.hp === maxHp(api.player.constitution, api.player.level)) {
-      text.push(`<p>You're already at full health?</p>`);
-    }
-    else {
-      if(api.player.gold <= (healCost * 2)) {
-        text.push(`<p>You don't seem to have too much money... I guess I can do it for free this time...</p>`);
-        text.push(`<p><button type="button" class="city-emit-event" data-event="city:services:healer:heal" data-args="${service.id}">Heal for free!</button></p>`);
-      }
-      else {
-        text.push(`<p><button type="button" class="city-emit-event" data-event="city:services:healer:heal" data-args="${service.id}">Heal for ${healCost}g!</button></p>`);
-      }
-
-    }
-
-    api.socket.emit('city:service:healer', {
-      text: text.join("\n")
-    });
-  }
-}
-
-export const heal: SocketEvent = {
-  eventName: 'city:services:healer:heal',
-  handler: async (api, data: { args: number }) => {
-    const text: string[] = [];
-    const healerId = data.args;
-    const service = await getService(healerId);
-
-    if(service.city_id !== api.player.city_id) {
-      api.socket.emit('alert', {
-        type: 'error',
-        text: `You don't seem to be in the right city...`
-      });
-      return;
-    }
-
-    const city = await getCityDetails(service.city_id);
-
-    text.push(`<p><b>${service.name}</b></p>`);
-
-    const cost = api.player.gold <= (healCost * 2) ? 0 : healCost;
-
-    if(api.player.gold < cost) {
-      text.push(`<p>${getText('insufficient_money', service, city)}</p>`)
-      api.socket.emit('city:service:healer', {
-        text: text.join("\n")
-      });
-      return;
-    }
-
-    api.player.hp = maxHp(api.player.constitution, api.player.level);
-    api.player.gold -= cost;
-
-    await updatePlayer(api.player);
-    api.socket.emit('updatePlayer', api.player);
-
-
-    text.push(`<p>${getText('heal_successful', service, city)}</p>`);
-    text.push('<p><button class="emit-event-internal" data-event="tab:explore">Back to Town</button></p>');
-    api.socket.emit('city:service:healer', {
-      text: text.join("\n")
-    });
-  }
-}
index c2469cd6a9fda5c49d350fe6872480d880df427b..71c84fe6a10869f2dace39706d0ea54d733075fa 100644 (file)
@@ -20,12 +20,14 @@ import {getShopItem, listShopItems } from './shopEquipment';
 import {EquippedItemDetails} from '../shared/equipped';
 import {ArmourEquipmentSlot, EquipmentSlot} from '../shared/inventory';
 import { clearTravelPlan, getAllPaths, getAllServices, getCityDetails, getService, getTravelPlan, travel } from './map';
-import { signup, login } from './auth';
+import { signup, login, authEndpoint } from './auth';
 import {db} from './lib/db';
 import { getPlayerSkills, getPlayerSkillsAsObject, updatePlayerSkills } from './skills';
 import {SkillID, Skills} from '../shared/skills';
 import * as EventList from '../events/server';
 
+import  { router as healerRouter } from './locations/healer';
+
 import { renderPlayerBar } from './views/player-bar'
 import { renderStore } from './views/stores';
 import { renderMap } from './views/map';
@@ -55,6 +57,11 @@ const io = new Server(server);
 const cache = new Map<string, any>();
 const chatHistory: Message[] = [];
 
+app.use((req, res, next) => {
+  console.log(req.method, req.url);
+  next();
+});
+
 function calcAp(inventoryItem: EquippedItemDetails[], socket: Socket) {
   const ap: Record<any | EquipmentSlot, {currentAp: number, maxAp: number}> = {};
   inventoryItem.forEach(item => {
@@ -520,16 +527,7 @@ io.on('connection', async socket => {
   socket.emit('ready');
 });
 
-function authEndpoint(req: Request, res: Response, next: any) {
-  const authToken = req.headers['x-authtoken'];
-  if(!authToken) {
-    logger.log(`Invalid auth token ${authToken}`);
-    res.sendStatus(400)
-  }
-  else {
-    next()
-  }
-}
+app.use(healerRouter);
 
 app.get('/player', authEndpoint, async (req: Request, res: Response) => {
   const authToken = req.headers['x-authtoken'].toString();
@@ -559,20 +557,6 @@ app.get('/player/skills', authEndpoint, async (req: Request, res: Response) => {
   res.send(renderSkills(skills));
 });
 
-app.get('/city/:id', async (req: Request, res: Response) => {
-  const id = parseInt(req.params.id);
-  if(!id || isNaN(id)) {
-    return res.sendStatus(400);
-  }
-  const [city, locations, paths] = await Promise.all([
-    getCityDetails(id),
-    getAllServices(id),
-    getAllPaths(id)
-  ]);
-
-  res.send(await renderMap({city, locations, paths}));
-});
-
 app.get('/player/inventory', authEndpoint, async (req: Request, res: Response) => {
   const authToken = req.headers['x-authtoken'].toString();
   const player: Player = await loadPlayer(authToken)
@@ -672,37 +656,42 @@ app.post('/player/unequip/:item_id', authEndpoint, async (req: Request, res: Res
   res.send(renderInventoryPage(inventory, items, item.type) + renderPlayerBar(player, inventory));
 });
 
-app.get('/state', authEndpoint, async (req: Request, res: Response) => {
+app.get('/player/explore', authEndpoint, async (req: Request, res: Response) => {
   const authToken = req.headers['x-authtoken'].toString();
   const player: Player = await loadPlayer(authToken)
-  let closestTown: number = player.city_id;
 
   if(!player) {
     logger.log(`Couldnt find player with id ${authToken}`);
     return res.sendStatus(400);
   }
 
-  const fight = await loadMonsterFromFight(player.id);
 
-  // check if the player is exploring somewhere!
-  const travelPlan = await getTravelPlan(player.id);
+  const fight = await loadMonsterFromFight(player.id);
+  let closestTown = player.city_id;
 
-  if(travelPlan) {
-    closestTown = (travelPlan.current_position / travelPlan.total_distance) > 0.5 ? travelPlan.destination_id : travelPlan.source_id;
+  if(fight) {
+    // ok lets display the fight screen!
+    console.log('in a fight!');
   }
+  else {
+    const travelPlan = await getTravelPlan(player.id);
+    if(travelPlan) {
+      // traveling!
+      closestTown = (travelPlan.current_position / travelPlan.total_distance) > 0.5 ? travelPlan.destination_id : travelPlan.source_id;
+      console.log('travel plan');
+    }
+    else {
+      // display the city info!
+      const [city, locations, paths] = await Promise.all([
+        getCityDetails(player.city_id),
+        getAllServices(player.city_id),
+        getAllPaths(player.city_id)
+      ]);
 
-  const state = {
-    fight: fight || null,
-    closestTown,
-    travel: travelPlan ? {
-      things: [],
-      closestTown,
-      walkingText: 'You keep walking...'
-    } : null
-  };
-
-  res.json(state);
+      res.send(await renderMap({city, locations, paths}, closestTown));
+    }
 
+  }
 });
 
 app.post('/player/:player_id/items/:item_id', authEndpoint, async (req: Request, res: Response) => {
@@ -823,6 +812,9 @@ app.get('/city/:city_id/location/:location_id', authEndpoint, async (req: Reques
   }
 
   switch(location.type) {
+    case 'SERVICES':
+      console.log('loading service', location);
+    break;
     case 'STORES':
       const [shopEquipment, shopItems] = await Promise.all([
         listShopItems({location_id: location.id}),
index 748c79d6ce9906a9c6830b15caddd9b0ec30af14..d82d4b64c39f06a9460a338fa1f8e3361bf61234 100644 (file)
@@ -3,6 +3,7 @@ import bcrypt from 'bcrypt';
 import { loadPlayer } from './player';
 import { Auth } from '../shared/auth';
 import { db } from './lib/db';
+import { Request, Response } from 'express';
 
 export async function signup(playerId: string, username: string, password: string): Promise<void> {
   const salt = await bcrypt.genSalt(10);
@@ -56,3 +57,14 @@ export async function login(username: string, password: string): Promise<Player>
   }
 
 }
+
+export function authEndpoint(req: Request, res: Response, next: any) {
+  const authToken = req.headers['x-authtoken'];
+  if(!authToken) {
+    console.log(`Invalid auth token ${authToken}`);
+    res.sendStatus(400)
+  }
+  else {
+    next()
+  }
+}
diff --git a/src/server/locations/healer/index.ts b/src/server/locations/healer/index.ts
new file mode 100644 (file)
index 0000000..8cee6aa
--- /dev/null
@@ -0,0 +1,171 @@
+import { Request, Response, Router } from "express";
+import { maxHp, Player } from "../../../shared/player";
+import { authEndpoint } from '../../auth';
+import { logger } from "../../lib/logger";
+import { loadPlayer, updatePlayer } from "../../player";
+import { getCityDetails, getService } from '../../map';
+import { sample } from 'lodash';
+import { City, Location } from "../../../shared/map";
+import { renderPlayerBar } from "../../views/player-bar";
+import { getEquippedItems } from "../../inventory";
+
+export const router = Router();
+
+type TextSegment = 'intro' | 'insufficient_money' | 'heal_successful';
+
+type HealText = Record<TextSegment, string[]>;
+
+const healCost = 10;
+
+const defaultTexts: HealText = {
+  intro: [
+    `Welcome traveller, I am {{NAME}}, Healer of {{CITY_NAME}}`,
+    "Please come in traveller, I am {{NAME}}, Healer of {{CITY_NAME}}",
+  ],
+  insufficient_money: [
+    "Sorry friend, you don't have enough money..",
+    "Sorry, that won't be enough..",
+    "Healing is hard work.. I'm afraid that won't cover it.."
+  ],
+  heal_successful: [
+    "I hope you feel better now",
+    "Good luck on your travels!",
+    "Glad to be of service..."
+  ]
+};
+
+// overrides for specific areas
+const playerTexts: Record<number, HealText> = {
+  [8]: {
+    intro: [
+      'Welcome to Midfield traveller, I am Casim - healer in these parts',
+      'I am Casim the Healer here... how are you enjoying your stay at Midfield?'
+    ],
+    insufficient_money: [
+      'Sorry friend, you don\'t have enough money',
+      'Look.. I\'m sorry.. that won\'t be enough...'
+    ],
+    heal_successful: [
+      'Glad to help!'
+    ]
+  },
+  [16]: {
+    intro: [
+      'Ah, welcome to Wildegard, one of the few safehavens in the Akari Woods. I am Adovras, healer in these parts.',
+      'Welcome traveller, I am Adovras - healer in these parts'
+    ],
+    insufficient_money: [
+      `Sorry friend, you don't have enough money...`
+    ],
+    heal_successful: [
+      "Hope this small healing will be helpful on your journeys"
+    ]
+
+  },
+  [11]: {
+    intro: [
+      'Ah, welcome traveler - I am Uthar, healer of Davelfell',
+      'Hello, I am Uthar, healer of Davelfell',
+      'Sorry I\'m a bit busy today, I am Uthar, healer of Davelfell'
+    ],
+    insufficient_money: [
+      "Bah, don't bother me if you don't have the money",
+      "Look, I'm very busy - come back when you have the money"
+    ],
+    heal_successful: [
+      "*Fizz* *POOF* YOU'RE HEALED!"
+    ]
+  }
+}
+
+function getText(type: TextSegment, location: Location, city: City): string {
+  let selected = sample(defaultTexts[type]);
+
+  if(playerTexts[location.id]) {
+    if(playerTexts[location.id][type].length) {
+      selected = sample(playerTexts[location.id][type]);
+    }
+  }
+
+  return selected.replace("{{NAME}}", location.name).replace("{{CITY_NAME}}", city.name);
+
+}
+
+router.get('/city/services/city:services:healer/:location_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 service = await getService(parseInt(req.params.location_id));
+  const city = await getCityDetails(service.city_id);
+
+  if(!service || service.city_id !== player.city_id) {
+    logger.log(`Invalid location: [${req.params.location_id}]`);
+    res.sendStatus(400);
+  }
+
+  const text: string[] = [];
+
+  text.push(`<p><b>${service.name}</b></p>`);
+  text.push(`<p>"${getText('intro', service, city)}"</p>`);
+
+
+  if(player.hp === maxHp(player.constitution, player.level)) {
+    text.push(`<p>You're already at full health?</p>`);
+  }
+  else {
+    if(player.gold <= (healCost * 2)) {
+      text.push(`<p>You don't seem to have too much money... I guess I can do it for free this time...</p>`);
+      text.push(`<p><button type="button" class="city-emit-event" data-event="city:services:healer:heal" data-args="${service.id}">Heal for free!</button></p>`);
+    }
+    else {
+      text.push(`<p><button type="button" hx-post="/city/services/city:services:healer:heal/${service.id}">Heal for ${healCost}g!</button></p>`);
+    }
+
+  }
+
+  res.send(`<div id="map" hx-target="#map" hx-swap-oob="true">${text.join("\n")}</div>`);
+});
+
+
+
+router.post('/city/services/city:services:healer:heal/:location_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 service = await getService(parseInt(req.params.location_id));
+  const city = await getCityDetails(service.city_id);
+
+  if(!service || service.city_id !== player.city_id) {
+    logger.log(`Invalid location: [${req.params.location_id}]`);
+    res.sendStatus(400);
+  }
+
+  const text: string[] = [];
+  text.push(`<p><b>${service.name}</b></p>`);
+
+  const cost = player.gold <= (healCost * 2) ? 0 : healCost;
+
+  if(player.gold < cost) {
+    text.push(`<p>${getText('insufficient_money', service, city)}</p>`)
+    res.send(`<div id="map" hx-target="#map" hx-swap-oob="true">${text.join("\n")}</div>`);
+  }
+  else {
+    player.hp = maxHp(player.constitution, player.level);
+    player.gold -= cost;
+
+    await updatePlayer(player);
+    const inventory = await getEquippedItems(player.id);
+
+    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(`<div id="map" hx-target="#map" hx-swap-oob="true">${text.join("\n")}</div>` + renderPlayerBar(player, inventory));
+  }
+});
index 63c3496914f47b825d38b3a41644f7e555efad32..48e4ece179402e423b6abd77ed169d8876d638a6 100644 (file)
@@ -1,6 +1,6 @@
 import { LocationType, Location, Path, City } from "../../shared/map";
 
-export async function renderMap(data: { city: City, locations: Location[], paths: Path[]}): Promise<string> {
+export async function renderMap(data: { city: City, locations: Location[], paths: Path[]}, closestTown: number): Promise<string> {
 
   if(!data) {
     console.error('oh no.. this got triggered without any city data');
@@ -13,10 +13,12 @@ export async function renderMap(data: { city: City, locations: Location[], paths
   };
 
   data.locations.forEach(l => {
-    servicesParsed[l.type].push(`<a href="#" hx-get="/city/${data.city.id}/location/${l.id}/" hx-trigger="click" id="location-${l.id}">${l.name}</a>`);
+    servicesParsed[l.type].push(`<a href="#" hx-get="/city/${l.type.toLowerCase()}/${l.event_name}/${l.id}" hx-trigger="click" id="location-${l.id}">${l.name}</a>`);
   });
 
-  let html = `<div hx-target="#map">
+  let html = `
+<section id="explore" class="tab active" style="background-image: linear-gradient(to left top, rgba(255,255,255,0) 0%,rgb(255,255,255) 100%), linear-gradient(to left, rgba(255, 255, 255, 0) 0%, rgb(255, 255, 255) 100%), url('/assets/img/map/${closestTown}.jpeg')">
+<div id="map">
 <h1>${data.city.name}</h1>
   <div class="city-details">`;
 
@@ -37,6 +39,7 @@ export async function renderMap(data: { city: City, locations: Location[], paths
       }).join("<br>")}
     </div>
   </div>
+</div>
 </div>
   `;