chore(release): 0.2.11 v0.2.11
authorxangelo <me@xangelo.ca>
Mon, 21 Aug 2023 17:28:30 +0000 (13:28 -0400)
committerxangelo <me@xangelo.ca>
Mon, 21 Aug 2023 17:28:30 +0000 (13:28 -0400)
CHANGELOG.md
package-lock.json
package.json
public/assets/css/game.css
public/index.html
src/server/api.ts
src/server/locations/healer/index.ts
src/server/locations/recruiter.ts [new file with mode: 0644]

index 3cdb4ac2d6832969a4ab2463a4518ef51166eb74..dc33716f300ea7fa3307203496ca637fc9eb284c 100644 (file)
@@ -2,6 +2,15 @@
 
 All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
 
 
 All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
 
+### [0.2.11](https://git.xangelo.ca/?p=risinglegends.git;a=commitdiff;h=v0.2.11;hp=v0.2.10;ds=sidebyside) (2023-08-21)
+
+
+### Bug Fixes
+
+* chat history clearning existing chat on load 8a7161c
+* green button colors b8755db
+* migrate recruiter to htmx 8f42a66
+
 ### [0.2.10](https://git.xangelo.ca/?p=risinglegends.git;a=commitdiff;h=v0.2.10;hp=v0.2.9;ds=sidebyside) (2023-08-18)
 
 
 ### [0.2.10](https://git.xangelo.ca/?p=risinglegends.git;a=commitdiff;h=v0.2.10;hp=v0.2.9;ds=sidebyside) (2023-08-18)
 
 
index 5a3857378ce5eca6e12a5aa685b14ced74d8de6f..3a59fa940979d4a5ac9578458e4204311a9ac44f 100644 (file)
@@ -1,12 +1,12 @@
 {
   "name": "rising-legends",
 {
   "name": "rising-legends",
-  "version": "0.2.10",
+  "version": "0.2.11",
   "lockfileVersion": 2,
   "requires": true,
   "packages": {
     "": {
       "name": "rising-legends",
   "lockfileVersion": 2,
   "requires": true,
   "packages": {
     "": {
       "name": "rising-legends",
-      "version": "0.2.10",
+      "version": "0.2.11",
       "dependencies": {
         "@honeycombio/opentelemetry-node": "^0.4.0",
         "@opentelemetry/auto-instrumentations-node": "^0.37.0",
       "dependencies": {
         "@honeycombio/opentelemetry-node": "^0.4.0",
         "@opentelemetry/auto-instrumentations-node": "^0.37.0",
index 2a9e74bc152878481e64d3c2c270fe001b25cb20..b651e635e1ed0c298623363d0e5a739d028a4fcd 100644 (file)
@@ -1,7 +1,7 @@
 {
   "name": "rising-legends",
   "private": true,
 {
   "name": "rising-legends",
   "private": true,
-  "version": "0.2.10",
+  "version": "0.2.11",
   "scripts": {
     "up": "npx prisma migrate dev --name \"init\"",
     "start": "pm2 start dist/server/api.js",
   "scripts": {
     "up": "npx prisma migrate dev --name \"init\"",
     "start": "pm2 start dist/server/api.js",
index 55a52fc9ff1022d80c50c920123c91b712211a78..69187e73a516f180a87db12ebfc4856fc8f22ef5 100644 (file)
@@ -80,10 +80,10 @@ button.red:hover {
   background: #b20b00;
 }
 button.green {
   background: #b20b00;
 }
 button.green {
-  background: #0a0;
+  background: url(data:image/png;base64,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), linear-gradient(to bottom, #41d437 0%, #0a9404 100%);
 }
 button.green:hover {
 }
 button.green:hover {
-  background: #0b0;
+  background: url(data:image/png;base64,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), linear-gradient(to bottom, #32e027 0%, #10a209 100%);
 }
 button:active {
   position: relative;
 }
 button:active {
   position: relative;
@@ -458,6 +458,9 @@ nav.filter-result.active {
 .city-details.flex > div {
   margin: 1rem;
 }
 .city-details.flex > div {
   margin: 1rem;
 }
+.service-in-town {
+  padding: 0 1rem;
+}
 h1 {
   font-size: 1.5rem;
   font-weight: bold;
 h1 {
   font-size: 1.5rem;
   font-weight: bold;
index 746e2c36d2326f54fbecc2f850948bd87b5498e8..777ee8db40e68a56b66d1b2345cad42268ee8134 100644 (file)
@@ -66,7 +66,7 @@
       </div>
 
       <section id="chat">
       </div>
 
       <section id="chat">
-        <div id="chat-messages" hx-trigger="load delay:1s" hx-get="/chat/history" hx-swap="innerHTML"></div>
+        <div id="chat-messages" hx-trigger="load delay:1s" hx-get="/chat/history" hx-swap="afterbegin"></div>
         <form id="chat-form" hx-post="/chat">
           <input type="text" id="message" name="message" autocomplete="off"><button type="submit">Send</button>
         </form>
         <form id="chat-form" hx-post="/chat">
           <input type="text" id="message" name="message" autocomplete="off"><button type="submit">Send</button>
         </form>
index 8a7eec80dc29f03b1363e194597a55b4839a6abe..baed723e2c7e50348d50aa18d36077929b718dfe 100644 (file)
@@ -27,6 +27,7 @@ import { getPlayerSkills, getPlayerSkillsAsObject, updatePlayerSkills } from './
 import {SkillID, Skills} from '../shared/skills';
 
 import  { router as healerRouter } from './locations/healer';
 import {SkillID, Skills} from '../shared/skills';
 
 import  { router as healerRouter } from './locations/healer';
+import { router as professionRouter } from './locations/recruiter';
 
 import * as Alert from './views/alert';
 import { renderPlayerBar } from './views/player-bar'
 
 import * as Alert from './views/alert';
 import { renderPlayerBar } from './views/player-bar'
@@ -374,6 +375,7 @@ async function fightRound(player: Player, monster: MonsterWithFaction,  data: {a
 };
 
 app.use(healerRouter);
 };
 
 app.use(healerRouter);
+app.use(professionRouter);
 
 
 app.get('/chat/history', authEndpoint, async (req: AuthRequest, res: Response) => {
 
 
 app.get('/chat/history', authEndpoint, async (req: AuthRequest, res: Response) => {
@@ -671,7 +673,7 @@ app.get('/location/:location_id/equipment/:item_id/overview', authEndpoint, asyn
     </div>
   </div>
   <div class="actions">
     </div>
   </div>
   <div class="actions">
-    <button hx-put="/location/${equipment.location_id}/equipment/${equipment.id}" formmethod="dialog" value="cancel">Buy</button>
+    <button hx-put="/location/${equipment.location_id}/equipment/${equipment.id}" formmethod="dialog" value="cancel" class="green">Buy</button>
     <button class="close-modal" formmethod="dialog" value="cancel">Cancel</button>
   </div>
 </dialog>
     <button class="close-modal" formmethod="dialog" value="cancel">Cancel</button>
   </div>
 </dialog>
index 3504d2aab194e81869050f202ca2a1f2f26389c2..3e658fe5c11020442b82f2fe6c084e5d5d01f7d9 100644 (file)
@@ -92,7 +92,7 @@ function getText(type: TextSegment, location: Location, city: City): string {
 
 }
 
 
 }
 
-router.get('/city/services/city:services:healer/:location_id', authEndpoint, async (req: AuthRequest, res: Response) => {
+router.get('/city/services/healer/:location_id', authEndpoint, async (req: AuthRequest, res: Response) => {
   const service = await getService(parseInt(req.params.location_id));
   const city = await getCityDetails(service.city_id);
 
   const service = await getService(parseInt(req.params.location_id));
   const city = await getCityDetails(service.city_id);
 
@@ -111,10 +111,10 @@ router.get('/city/services/city:services:healer/:location_id', authEndpoint, asy
   else {
     if(req.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>`);
   else {
     if(req.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" hx-post="/city/services/city:services:healer:heal/${service.id}" hx-target="#explore">Heal for Free!</button></p>`);
+      text.push(`<p><button type="button" hx-post="/city/services/healer/heal/${service.id}" hx-target="#explore">Heal for Free!</button></p>`);
     }
     else {
     }
     else {
-      text.push(`<p><button type="button" hx-post="/city/services/city:services:healer:heal/${service.id}" hx-target="#explore">Heal for ${healCost}g!</button></p>`);
+      text.push(`<p><button type="button" hx-post="/city/services/healer/heal/${service.id}" hx-target="#explore">Heal for ${healCost}g!</button></p>`);
     }
 
   }
     }
 
   }
@@ -134,7 +134,7 @@ ${text.join("\n")}
 
 
 
 
 
 
-router.post('/city/services/city:services:healer:heal/:location_id', authEndpoint, async (req: AuthRequest, res: Response) => {
+router.post('/city/services/healer/heal/:location_id', authEndpoint, async (req: AuthRequest, res: Response) => {
   const service = await getService(parseInt(req.params.location_id));
   const city = await getCityDetails(service.city_id);
 
   const service = await getService(parseInt(req.params.location_id));
   const city = await getCityDetails(service.city_id);
 
diff --git a/src/server/locations/recruiter.ts b/src/server/locations/recruiter.ts
new file mode 100644 (file)
index 0000000..10623d8
--- /dev/null
@@ -0,0 +1,117 @@
+import { Response, Router } from "express";
+import { getService } from "../map";
+import { authEndpoint, AuthRequest } from '../auth';
+import { logger } from "../lib/logger";
+import * as Alert from "../views/alert";
+import { changeProfession } from "../player";
+import { renderPlayerBar } from "../views/player-bar";
+import { getEquippedItems } from "../inventory";
+
+function p(str: string) {
+  return `<p>${str}</p>`;
+}
+
+export const router = Router();
+
+const MIN_LEVEL = 25;
+
+router.get('/city/services/profession_recruitor/:location_id', authEndpoint, async(req: AuthRequest, res: Response) => {
+  const service = await getService(parseInt(req.params.location_id));
+
+  if(!service || service.city_id !== req.player.city_id) {
+    logger.log(`Invalid location: [${req.params.location_id}]`);
+    res.sendStatus(400);
+  }
+
+  let html: string[] = [];
+  if(req.player.profession === 'Wanderer') {
+    html.push(`<p>Our duty is to help Wanderers such as yourself become more than they are. By helping you achieve new levels in service of the King, we can ensure that the Kingdom of Khatis continues to grow!</p>`);
+    html.push(`<p>You have 3 choices laid before you.</p>`);
+    html.push(`<p>You could become a great and mighty <b>Warrior</b>! Wielding powerful swords and maces.</p>`);
+    html.push(`<p>You could become a powerful <b>Mage</b>! Casting spells to rain fire upon our enemies.</p>`);
+    html.push(`<p>You could become a lithe <b>Rogue</b>! Attacking our enemies swiftly when they least expect!</p>`);
+
+    if(req.player.level < MIN_LEVEL) {
+      html.push(p(`Unfortunately you have to be at least level ${MIN_LEVEL} to take part in our training...`));
+    }
+    else {
+      html.push(p(`<b>Be Careful!</b> Once you change your profession, you'll never be a Wanderer again...`));
+      html.push(`
+          <div>
+          <form hx-post="/city/services/profession_change/${service.id}">
+          <button type="submit" value="warrior" name="profession">Become a Warrior</button>
+          <button type="submit" value="mage" name="profession">Become a Mage</button>
+          <button type="submit" value="rogue" name="profession">Become a Rogue</button>
+          </form>
+          </div>
+        `);
+    }
+  }
+  else {
+    let town = 'UNSET';
+    let place = 'UNSETPLACE';
+    switch(req.player.profession) {
+      case 'Warrior':
+        town = 'Stether';
+        place = 'Highbreaker Inn'
+        break;
+      case 'Mage':
+        town = 'Davelfell';
+        place = 'Mages Tower';
+        break;
+      case 'Rogue':
+        town = 'Ferbalt Gap';
+        place = 'Keepers Tavern';
+        break;
+    }
+
+    html.push(p(`Welcome <b>${req.player.profession}</b>!`));
+    html.push(`<p>Unfortunately I won't be of much help to you now that you are no longer a wanderer...</p>`);
+    html.push(`<p>However, you should visit the ${place} in ${town} that can probably provide some guidance!</p>`);
+  }
+
+  res.send(`
+    <div class="city-title-wrapper"><div class="city-title">${service.city_name}</div></div>
+    <div class="city-details">
+      <h3 class="location-name"><span>${service.name}</span></h3>
+      <div class="service-in-town" id="recruiter-target">${html.join("\n")}</div>
+    </div>
+  `);
+});
+
+router.post('/city/services/profession_change/:location_id', authEndpoint, async(req: AuthRequest, res: Response) => {
+  const service = await getService(parseInt(req.params.location_id));
+
+  if(!service || service.city_id !== req.player.city_id) {
+    logger.log(`Invalid location: [${req.params.location_id}]`);
+    res.sendStatus(400);
+  }
+
+  let update: {level: number, exp: number};
+
+  switch(req.body.profession.toLowerCase()) {
+    case 'warrior':
+      update = await changeProfession(req.player.id, 'Warrior');
+      req.player.profession = 'Warrior';
+    break;
+    case 'mage':
+      update = await changeProfession(req.player.id, 'Mage');
+      req.player.profession = 'Mage';
+    break;
+    case 'rogue':
+      update = await changeProfession(req.player.id, 'Rogue');
+      req.player.profession = 'Rogue';
+    break;
+    default:
+      res.send(Alert.ErrorAlert(`Invalid profession`));
+    break;
+  }
+
+  if(update) {
+    const equipped = await getEquippedItems(req.player.id);
+    req.player.level = update.level;
+    req.player.exp = update.exp;
+    res.send(renderPlayerBar(req.player, equipped) + `<div id="recruiter-target" class="service-in-town" hx-swap-oob="true">Congrats! You are now a ${req.player.profession}</div>`);
+  }
+
+});