chore(release): 0.3.3 v0.3.3
authorxangelo <me@xangelo.ca>
Wed, 6 Sep 2023 17:28:29 +0000 (13:28 -0400)
committerxangelo <me@xangelo.ca>
Wed, 6 Sep 2023 17:28:29 +0000 (13:28 -0400)
36 files changed:
CHANGELOG.md
migrations/20230901182406_monster-variance.ts [new file with mode: 0644]
package-lock.json
package.json
public/assets/css/game.css
seeds/monsters.ts
src/events/client.ts [deleted file]
src/events/equipping-items/server.ts [deleted file]
src/events/explore/server.ts [deleted file]
src/events/items/server.ts [deleted file]
src/events/profession-changing/client.ts [deleted file]
src/events/profession-changing/server.ts [deleted file]
src/events/profession-changing/shared.ts [deleted file]
src/events/server.ts [deleted file]
src/events/stat-points/server.ts [deleted file]
src/events/stores/server.ts [deleted file]
src/events/travel/client.ts [deleted file]
src/events/travel/server.ts [deleted file]
src/events/travel/shared.ts [deleted file]
src/server/api.ts
src/server/auth.ts
src/server/fight.ts
src/server/lib/logger.ts
src/server/locations/healer.ts [new file with mode: 0644]
src/server/locations/healer/index.ts [deleted file]
src/server/locations/recruiter.ts
src/server/locations/repair.ts
src/server/monster.ts
src/server/views/fight.ts
src/server/views/inventory.ts
src/server/views/monster-selector.ts
src/shared/constants.ts
src/shared/fight.ts
src/shared/monsters.ts
src/types/express/index.d.ts [new file with mode: 0644]
tsconfig.json

index 0d59abd049247519f7b1d5434655735a690c55ff..947be5b3864157382de7671d5146e664dd3c5f48 100644 (file)
@@ -2,6 +2,23 @@
 
 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.3.3](https://git.xangelo.ca/?p=risinglegends.git;a=commitdiff;h=v0.3.3;hp=v0.3.2;ds=sidebyside) (2023-09-06)
+
+
+### Features
+
+* add debug/error methods to logger 3238965
+* display equippable hand option only d7569d3
+* migrate to augmenting express.Request interface d820e11
+* monster variants b8bed23
+* remove body-part targeting c6a5f62
+* variable level monsters b5577b4
+
+
+### Bug Fixes
+
+* min/max level definitions for monsters bedec85
+
 ### [0.3.2](https://git.xangelo.ca/?p=risinglegends.git;a=commitdiff;h=v0.3.2;hp=v0.3.1;ds=sidebyside) (2023-09-01)
 
 
 ### [0.3.2](https://git.xangelo.ca/?p=risinglegends.git;a=commitdiff;h=v0.3.2;hp=v0.3.1;ds=sidebyside) (2023-09-01)
 
 
diff --git a/migrations/20230901182406_monster-variance.ts b/migrations/20230901182406_monster-variance.ts
new file mode 100644 (file)
index 0000000..35e6db0
--- /dev/null
@@ -0,0 +1,20 @@
+import { Knex } from "knex";
+
+
+export async function up(knex: Knex): Promise<void> {
+  return knex.schema.alterTable('monsters', function(table) {
+    table.dropColumn('level');
+    table.integer('minLevel').notNullable().defaultTo(1);
+    table.integer('maxLevel').notNullable().defaultTo(1);
+  });
+}
+
+
+export async function down(knex: Knex): Promise<void> {
+  return knex.schema.alterTable('monsters', function(table) {
+    table.dropColumn('minLevel');
+    table.dropColumn('maxLevel');
+    table.integer('level').notNullable().defaultTo(1);
+  });
+}
+
index 4d866447b9c4a3d7f9cc9e65b9b4eff59b945369..bf0c89334b599fd2e016c0e21cc0c78a2f3bb6af 100644 (file)
@@ -1,12 +1,12 @@
 {
   "name": "rising-legends",
 {
   "name": "rising-legends",
-  "version": "0.3.2",
+  "version": "0.3.3",
   "lockfileVersion": 2,
   "requires": true,
   "packages": {
     "": {
       "name": "rising-legends",
   "lockfileVersion": 2,
   "requires": true,
   "packages": {
     "": {
       "name": "rising-legends",
-      "version": "0.3.2",
+      "version": "0.3.3",
       "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 2dc99dbf4f35d7d4f49ffcc67891d02fd00706b7..fe8ae1261ae6aafe284add8663c12c773c1f5017 100644 (file)
@@ -1,7 +1,7 @@
 {
   "name": "rising-legends",
   "private": true,
 {
   "name": "rising-legends",
   "private": true,
-  "version": "0.3.2",
+  "version": "0.3.3",
   "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",
@@ -69,7 +69,8 @@
       "src/client/**",
       "**/client.ts",
       "public/**",
       "src/client/**",
       "**/client.ts",
       "public/**",
-      "**.test.ts"
+      "**.test.ts",
+      "migrations/**"
     ]
   },
   "volta": {
     ]
   },
   "volta": {
index da277db1e50850ccff9d65c80cb9da9e7eff7efa..898b78a43d311b0cd1a15146b291c2912b7eb7cf 100644 (file)
@@ -441,9 +441,21 @@ nav.filter-result.active {
   width: 70%;
   margin: 0 auto 1rem;
 }
   width: 70%;
   margin: 0 auto 1rem;
 }
-#defender-name {
+.monster-identifier {
   text-align: left;
 }
   text-align: left;
 }
+#defender-name {
+  font-weight: bold;
+}
+.Elder #defender-name {
+  color: #2b2b2b;
+}
+.Skittish #defender-name {
+  color: #8700ff;
+}
+.Brute #defender-name {
+  color: #a91313;
+}
 #fight-results {
   margin-top: 1rem;
 }
 #fight-results {
   margin-top: 1rem;
 }
index 44afa4f723d8542914e86542187e6b9b015d1fa1..02eecb821d279c342517f93b7f9ac9e63a20c5db 100644 (file)
@@ -50,12 +50,13 @@ export async function createMonsters(): Promise<void> {
         return {
           id: r.fields.id,
           name: r.fields.Name,
         return {
           id: r.fields.id,
           name: r.fields.Name,
-          strength: r.fields['STR_override'] || r.fields.STR,
-          constitution: r.fields['CON_override'] || r.fields.CON,
-          dexterity: r.fields['DEX_override'] || r.fields.DEX,
-          intelligence: r.fields['INT_override'] || r.fields.INT,
+          strength: r.fields.STR,
+          constitution: r.fields.CON,
+          dexterity: r.fields.DEX,
+          intelligence: r.fields.INT,
           exp: r.fields.EXP,
           exp: r.fields.EXP,
-          level: r.fields.Level,
+          minLevel: r.fields.minLevel,
+          maxLevel: r.fields.maxLevel ?? r.fields.minLevel,
           gold: r.fields.GOLD,
           hp: r.fields.HP,
           maxHp: r.fields.HP,
           gold: r.fields.GOLD,
           hp: r.fields.HP,
           maxHp: r.fields.HP,
diff --git a/src/events/client.ts b/src/events/client.ts
deleted file mode 100644 (file)
index 4d0754f..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-export * from './profession-changing/client';
-export * from './travel/client';
diff --git a/src/events/equipping-items/server.ts b/src/events/equipping-items/server.ts
deleted file mode 100644 (file)
index 7b00316..0000000
+++ /dev/null
@@ -1,108 +0,0 @@
-import {SocketEvent} from "../../server/socket-event.server";
-import { Socket } from 'socket.io';
-import {EquipmentSlot} from '../../shared/inventory';
-import {getEquippedItems, getInventory, getInventoryItem} from '../../server/inventory';
-import { equip, unequip } from '../../server/equipment';
-import { logger } from '../../server/lib/logger';
-import {EquippedItemDetails} from '../../shared/equipped';
-import { getPlayersItems } from "../../server/items";
-
-class EquipmentInSlotError extends Error {
-  code: number;
-  constructor() {
-    super('You already have something in that slot');
-    this.code = 23505;
-  }
-}
-
-
-function calcAp(inventoryItem: EquippedItemDetails[], socket: Socket) {
-  const ap: Record<any | EquipmentSlot, {currentAp: number, maxAp: number}> = {};
-  inventoryItem.forEach(item => {
-    if(item.is_equipped && item.type === 'ARMOUR') {
-      ap[item.equipment_slot] = {
-        currentAp: item.currentAp,
-        maxAp: item.maxAp
-      };
-    }
-  });
-
-  socket.emit('calc:ap', {ap});
-}
-
-export const equipItem: SocketEvent = {
-  eventName: 'equip',
-  handler: async (api, data: {id: string, slot: EquipmentSlot}) => {
-    const inventoryItem = await getInventoryItem(api.player.id, data.id);
-    const equippedItems = await getEquippedItems(api.player.id);
-    let desiredSlot: EquipmentSlot = inventoryItem.equipment_slot;
-
-    try {
-      // handes the situation where you're trying to equip an item 
-      // that can be equipped to any hand
-      if(inventoryItem.equipment_slot === 'ANY_HAND') {
-        if(data.slot === 'LEFT_HAND' || data.slot === 'RIGHT_HAND') {
-          // get the players current equipment in that slot!
-          if(equippedItems.some(v => {
-            return v.equipment_slot === data.slot || v.equipment_slot === 'TWO_HANDED';
-          })) {
-            throw new EquipmentInSlotError();
-          }
-          else {
-            desiredSlot = data.slot;
-          }
-        }
-      }
-
-      if(data.slot === 'TWO_HANDED') {
-        if(equippedItems.some(v => {
-          return v.equipment_slot === 'LEFT_HAND' || v.equipment_slot === 'RIGHT_HAND';
-        })) {
-          throw new EquipmentInSlotError();
-        }
-      }
-
-
-      await equip(api.player.id, inventoryItem, desiredSlot);
-      api.socket.emit('alert', {
-        type: 'success', 
-        text: `You equipped your ${inventoryItem.name}`
-      });
-    }
-    catch(e) {
-      if(e.code.toString() === '23505') {
-        api.socket.emit('alert', {
-          type: 'error',
-          text: 'You already have an item equipped in that slot'
-        });
-      }
-      else {
-        logger.log(e);
-      }
-    }
-
-    const inventory = await getInventory(api.player.id);
-    const items = await getPlayersItems(api.player.id);
-    api.socket.emit('inventory', {
-      inventory,
-      items
-    });
-    calcAp(inventory, api.socket);
-
-  }
-}
-
-export const unequipItem: SocketEvent = {
-  eventName: 'unequip',
-  handler: async (api, data: {id: string}) => {
-    await unequip(api.player.id, data.id);
-
-    const inventory = await getInventory(api.player.id);
-    calcAp(inventory, api.socket);
-    const items = await getPlayersItems(api.player.id);
-    api.socket.emit('inventory', {
-      inventory,
-      items
-    });
-  }
-}
diff --git a/src/events/explore/server.ts b/src/events/explore/server.ts
deleted file mode 100644 (file)
index ed12d9d..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-import {Monster, MonsterForList} from "../../shared/monsters";
-import {SocketEvent} from "../../server/socket-event.server";
-import {getMonsterList} from "../../server/monster";
-
-export const exploreInCity: SocketEvent = {
-  eventName: 'city:explore',
-  handler: async (api, data: { args: string }) => {
-    // @TODO add check to make sure player is in this town and can actually explore this area
-    const locationId = parseInt(data.args);
-
-    if(!locationId || isNaN(locationId)) {
-      return;
-    }
-
-    const monsters: Monster[] = await getMonsterList(locationId);
-    let monsterList: MonsterForList[] = monsters.map(monster => {
-      return {
-        id: monster.id,
-        name: monster.name,
-        level: monster.level
-      }
-    });
-
-    api.socket.emit('explore:fights', monsterList);
-  }
-}
diff --git a/src/events/items/server.ts b/src/events/items/server.ts
deleted file mode 100644 (file)
index 0c9023d..0000000
+++ /dev/null
@@ -1,54 +0,0 @@
-import { getItemFromPlayer, getPlayersItems, updateItemCount } from "../../server/items";
-import {API, SocketEvent} from "../../server/socket-event.server";
-import { maxHp } from "../../shared/player";
-import { updatePlayer } from "../../server/player";
-import { HealthPotionSmall } from "../../shared/items/health_potion";
-import { getInventory } from "../../server/inventory";
-
-export const smallHeal: SocketEvent = {
-  eventName: 'item:use:heal_small',
-  handler: async (api: API, data: {args: string}): Promise<any> => {
-    const itemId = parseInt(data.args);
-    const item = await getItemFromPlayer(api.player.id, itemId);
-
-    if(!item) {
-      console.log(`Can't find item [${data.args}]`);
-      return;
-    }
-
-    if(item.amount < 1) {
-      api.socket.emit('alert', {
-        type: 'error',
-        text: `You don't have enough ${item.name}`
-      });
-      return;
-    }
-
-    item.amount -= 1;
-
-    const hpGain = HealthPotionSmall.effect(api.player);
-
-    api.player.hp += hpGain;
-
-    if(api.player.hp > maxHp(api.player.constitution, api.player.level)) {
-      api.player.hp = maxHp(api.player.constitution, api.player.level);
-    }
-
-    await updateItemCount(api.player.id, item.item_id, -1);
-    await updatePlayer(api.player);
-    const inventory = await getInventory(api.player.id);
-    const items = await getPlayersItems(api.player.id);
-
-    api.socket.emit('updatePlayer', api.player);
-    api.socket.emit('inventory', {
-      inventory,
-      items
-    });
-    api.socket.emit('alert', {
-      type: 'success',
-      text: `You used the Small Health Potion`
-    });
-
-    return;
-  }
-}
diff --git a/src/events/profession-changing/client.ts b/src/events/profession-changing/client.ts
deleted file mode 100644 (file)
index 5a045c6..0000000
+++ /dev/null
@@ -1,24 +0,0 @@
-import {SocketEvent} from "../../client/socket-event.client";
-import $ from 'jquery';
-import { EVENT_NAME } from "./shared";
-
-function generalTextReplacementHandler(api, data: { text: string }) {
-  $('#map').html(data.text);
-}
-
-export const professionRecruiter: SocketEvent = new SocketEvent(EVENT_NAME, generalTextReplacementHandler);
-
-export const professionChangeWarrior: SocketEvent = new SocketEvent(
-  `${EVENT_NAME}:warrior`,
-  generalTextReplacementHandler
-);
-
-export const professionChangeMage: SocketEvent = new SocketEvent(
-  `${EVENT_NAME}:mage`,
-  generalTextReplacementHandler
-);
-
-export const professionChangeRogue: SocketEvent = new SocketEvent(
-  `${EVENT_NAME}:rogue`,
-  generalTextReplacementHandler
-);
diff --git a/src/events/profession-changing/server.ts b/src/events/profession-changing/server.ts
deleted file mode 100644 (file)
index 60882e8..0000000
+++ /dev/null
@@ -1,107 +0,0 @@
-import {API, SocketEvent} from "../../server/socket-event.server";
-import { EVENT_NAME } from './shared';
-import { changeProfession } from "../../server/player";
-import {Profession} from "shared/profession";
-import {broadcastMessage} from "../../shared/message";
-
-const MIN_LEVEL = 25;
-
-export const professionRecruiter: SocketEvent = {
-  eventName: EVENT_NAME,
-  handler: (api, data): Promise<any> => {
-
-    let text: string = '';
-
-    if(api.player.profession === 'Wanderer') {
-      text = `
-      <b>Welcome to the Professional Services at Windcross!</b>
-      <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>
-      <p>You have 3 choices laid before you.</p>
-      <p>You could become a great and mighty <b>Warrior</b>! Wielding powerful swords and maces.</p>
-      <p>You could become a powerful <b>Mage</b>! Casting spells to rain fire upon our enemies.</p>
-      <p>You could become a lithe <b>Rogue</b>! Attacking our enemies swiftly when they least expect!</p>
-      `;
-
-      if(api.player.level < MIN_LEVEL) {
-        text += `<p>Unfortunately.. you have to be at least level ${MIN_LEVEL} to take part in our training...</p>`;
-      }
-      else {
-        text += `
-        <p><b>Be Careful!</b> Once you change your profession, you will never again be a Wanderer</p>
-        <div>
-        <button class="city-emit-event" data-event="${EVENT_NAME}:warrior">Become a Warrior</button>
-        <button class="city-emit-event" data-event="${EVENT_NAME}:mage">Become a Mage</button>
-        <button class="city-emit-event" data-event="${EVENT_NAME}:rogue">Become a Rogue</button>
-        </div>
-        `
-      }
-    }
-    else {
-
-      let town = 'UNSET';
-      let place = 'UNSETPLACE';
-      switch(api.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;
-      }
-
-      text = `<p>Welcome <b>${api.player.profession}</b>!`;
-      text += `<p>Unfortunately I won't be of much help to you now that you are no longer a wanderer...</p>`;
-      text += `<p>However, you should visit the ${place} in ${town} that can probably provide some guidance!</p>`;
-    }
-
-
-    api.socket.emit('city:services:profession_recruitor_windcross', {
-      text
-    });
-
-    return;
-  }
-};
-
-async function generalFirstLevelProfessionChange(api: API, data: unknown, profession: Profession) {
-    console.log(`${api.player.username} is becoming a ${profession}!`);
-    const update = await changeProfession(api.player.id, profession);
-
-    api.player.level = update.level;
-    api.player.exp = update.exp;
-    api.player.profession = profession;
-
-    api.socket.emit('updatePlayer', api.player);
-    api.socket.emit(`${EVENT_NAME}:${profession.toLowerCase()}`, {
-      text: `Congratulations! You are now a ${profession}!`
-    });
-
-    api.io.emit('chat', broadcastMessage('server', `${api.player.username} is now a ${profession}`));
-}
-
-export const professionChangeWarrior: SocketEvent = {
-  eventName: `${EVENT_NAME}:warrior`,
-  handler: async  (api, data) => {
-    generalFirstLevelProfessionChange(api, data, 'Warrior');
-  }
-}
-
-export const professionChangeMage: SocketEvent = {
-  eventName: `${EVENT_NAME}:mage`,
-  handler: async  (api, data) => {
-    generalFirstLevelProfessionChange(api, data, 'Mage');
-  }
-}
-
-export const professionChangeRogue: SocketEvent = {
-  eventName: `${EVENT_NAME}:rogue`,
-  handler: async  (api, data) => {
-    generalFirstLevelProfessionChange(api, data, 'Rogue');
-  }
-}
diff --git a/src/events/profession-changing/shared.ts b/src/events/profession-changing/shared.ts
deleted file mode 100644 (file)
index bbd9879..0000000
+++ /dev/null
@@ -1 +0,0 @@
-export const EVENT_NAME: string = 'city:services:profession_recruitor_windcross';
diff --git a/src/events/server.ts b/src/events/server.ts
deleted file mode 100644 (file)
index 6f9811a..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-export * from './profession-changing/server';
-export * from './equipping-items/server';
-export * from './travel/server';
-export * from './explore/server';
-export * from './stores/server';
-export * from './stat-points/server';
-export * from './items/server';
diff --git a/src/events/stat-points/server.ts b/src/events/stat-points/server.ts
deleted file mode 100644 (file)
index 697959a..0000000
+++ /dev/null
@@ -1,36 +0,0 @@
-import {SocketEvent} from "../../server/socket-event.server";
-import { professionList } from "../../shared/profession";
-import { updatePlayer } from "../../server/player";
-import { Stat } from "../../shared/stats";
-
-export const spendStatPoint: SocketEvent = {
-  eventName: 'spend-stat-point',
-  handler: async (api, data: { args: any }): Promise<void> => {
-
-    if(!Stat[data.args]) {
-      api.socket.emit('alert', {
-        type: 'error',
-        text: `Invalid stat type [${data.args}]`
-      });
-      return;
-    }
-
-    const statToIncrease: Stat = data.args as Stat;
-    const costToIncrease: number = professionList[api.player.profession].classStats.includes(statToIncrease) ? 1 : 2;
-
-    if(api.player.stat_points < costToIncrease) {
-      api.socket.emit('alert', {
-        type: 'error',
-        text: 'You don\'t have enough stat points'
-      });
-    }
-    else {
-      api.player[statToIncrease] += costToIncrease;
-      api.player.stat_points -= costToIncrease;
-
-      await updatePlayer(api.player);
-      api.socket.emit('updatePlayer', api.player);
-    }
-
-  }
-}
diff --git a/src/events/stores/server.ts b/src/events/stores/server.ts
deleted file mode 100644 (file)
index 1acbd8c..0000000
+++ /dev/null
@@ -1,30 +0,0 @@
-import {SocketEvent} from "../../server/socket-event.server";
-import {listShopItems} from '../../server/shopEquipment';
-import { getShopItems } from "../../server/items";
-import { logger } from '../../server/lib/logger';
-
-export const stores: SocketEvent = {
-  eventName: 'city:stores',
-  handler: async (api, data: {args: string}) => {
-    const storeId = parseInt(data.args);
-
-    if(!storeId || isNaN(storeId)) {
-      logger.log(`Invalid store id: ${storeId}`);
-    }
-
-    const [shopEquipemnt, shopItems] = await Promise.all([
-      listShopItems({location_id: storeId}),
-      getShopItems(storeId)
-    ]);
-
-    if(shopEquipemnt && shopEquipemnt.length) {
-      api.socket.emit('city:stores', {
-        equipment: shopEquipemnt,
-        items: shopItems
-      });
-    }
-    else {
-      logger.log(`Insufficient shop items: ${shopEquipemnt.length}`);
-    }
-  }
-}
diff --git a/src/events/travel/client.ts b/src/events/travel/client.ts
deleted file mode 100644 (file)
index df11e92..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-import {SocketEvent} from "../../client/socket-event.client";
-import $ from 'jquery';
-import { TravelDTO } from './shared';
-
-export const travel: SocketEvent = new SocketEvent(
-  'city:travel',
-  (api, data) => {
-    api.events.emit('renderTravel',[data as TravelDTO]);
-  }
-);
-
-export const renderCity: SocketEvent = new SocketEvent(
-  'city:display',
-  async (api, data) => {
-    api.cache.delete('currentMapHTML');
-    api.events.emit('renderMap', [data]);
-  }
-)
diff --git a/src/events/travel/server.ts b/src/events/travel/server.ts
deleted file mode 100644 (file)
index 26b81bd..0000000
+++ /dev/null
@@ -1,123 +0,0 @@
-import {movePlayer} from "../../server/player";
-import {getCityDetails, getAllServices, getAllPaths, travel, getTravelPlan, stepForward, completeTravel} from "../../server/map";
-import {SocketEvent} from "../../server/socket-event.server";
-import { sample, random } from 'lodash';
-import { getRandomMonster } from "../../server/monster";
-import { STEP_DELAY } from "./shared";
-
-const MONSTER_ENCOUNTER_CHANCE = 30;
-
-export const explore: SocketEvent = {
-  eventName: 'city:travel',
-  handler: async (api, data: { args: string }) => {
-    if(api.player.hp <= 0) {
-      api.socket.emit('alert', {
-        type: 'error',
-        text: 'Sorry, you need some HP to start your travel'
-      });
-      return;
-    }
-
-    const destinationCity = parseInt(data.args);
-
-
-    if(!destinationCity || isNaN(destinationCity)) {
-      console.log(`Invalid destination city [${destinationCity}]`);
-      return;
-    }
-
-    try {
-      const city = await getCityDetails(destinationCity);
-
-      if(!city) {
-        console.log(`Invalid destination city [${destinationCity}]`);
-        return;
-      }
-
-      console.log(`${api.player.username} attempting travel to ${city.name}`);
-      const travelPlan = await travel(api.player, city.id);
-
-      api.socket.emit('city:travel', {
-        things: [],
-        nextAction: 0,
-        closestTown: api.player.city_id,
-        walkingText: ''
-      });
-    }
-    catch(e) {
-      console.log(e);
-    }
-  }
-}
-
-const walkingText: string[] = [
-  'You take a step forward',
-  'You keep moving'
-];
-
-export const nextStep: SocketEvent = {
-  eventName: 'travel:step',
-  handler: async (api, data: { args: string }) => {
-    const stepTimerKey = `step:${api.player.id}`;
-    const travelPlan = await getTravelPlan(api.player.id);
-
-    if(api.cache[stepTimerKey]) {
-      if(api.cache[stepTimerKey] > Date.now()) {
-        // clicked too fast
-        return;
-      }
-    }
-
-    if(!travelPlan) {
-      return;
-    }
-
-    travelPlan.current_position++;
-    if(travelPlan.current_position >= travelPlan.total_distance) {
-      const travel = await completeTravel(api.player.id);
-
-      api.player.city_id = travel.destination_id;
-      await movePlayer(travel.destination_id, api.player.id);
-
-      const [city, locations, paths] = await Promise.all([
-        getCityDetails(travel.destination_id),
-        getAllServices(travel.destination_id),
-        getAllPaths(travel.destination_id)
-      ]);
-
-      api.socket.emit('city:display', {
-        city,
-        locations,
-        paths
-      });
-
-      delete api.cache[stepTimerKey];
-    }
-    else {
-      // update existing plan..
-      // decide if they will run into anything
-      const travelPlan = await stepForward(api.player.id);
-
-      const closest: number = (travelPlan.current_position / travelPlan.total_distance) > 0.5 ? travelPlan.destination_id : travelPlan.source_id;
-
-      const chanceToSeeMonster = random(0, 100);
-      const things: any[] = [];
-      if(chanceToSeeMonster < MONSTER_ENCOUNTER_CHANCE) {
-        const monster = await getRandomMonster([closest]);
-        things.push(monster);
-      }
-
-      const nextAction = Date.now() + STEP_DELAY;
-
-      api.cache[stepTimerKey] = nextAction;
-
-      api.socket.emit('city:travel', {
-        things,
-        nextAction,
-        closestTown: closest,
-        walkingText: sample(walkingText)
-      });
-
-    }
-  }
-};
diff --git a/src/events/travel/shared.ts b/src/events/travel/shared.ts
deleted file mode 100644 (file)
index d6f2f10..0000000
+++ /dev/null
@@ -1,16 +0,0 @@
-import {City, Location, Path} from "../../shared/map"
-
-export type DTO = {
-  city: City,
-  locations: Location[],
-  paths: Path[]
-}
-
-export type TravelDTO = {
-  things: any[],
-  nextAction: number;
-  walkingText: string,
-  closestTown: number;
-}
-
-export const STEP_DELAY = 3000;
index f167ff61bf0ba9cf5a3b1bb154485e11f29ad784..80aa82af1b4649f7dacfc2d639a57d5274e0f45f 100644 (file)
@@ -22,7 +22,7 @@ import {FightTrigger, Monster, MonsterForFight} from '../shared/monsters';
 import {getShopEquipment, listShopItems } from './shopEquipment';
 import {EquipmentSlot} from '../shared/inventory';
 import { clearTravelPlan, completeTravel, getAllPaths, getAllServices, getCityDetails, getService, getTravelPlan, stepForward, travel } from './map';
 import {getShopEquipment, listShopItems } from './shopEquipment';
 import {EquipmentSlot} from '../shared/inventory';
 import { clearTravelPlan, completeTravel, getAllPaths, getAllServices, getCityDetails, getService, getTravelPlan, stepForward, travel } from './map';
-import { signup, login, authEndpoint, AuthRequest } from './auth';
+import { signup, login, authEndpoint } from './auth';
 import {db} from './lib/db';
 import { getPlayerSkills} from './skills';
 
 import {db} from './lib/db';
 import { getPlayerSkills} from './skills';
 
@@ -150,13 +150,13 @@ app.use(professionRouter);
 app.use(repairRouter);
 
 
 app.use(repairRouter);
 
 
-app.get('/chat/history', authEndpoint, async (req: AuthRequest, res: Response) => {
+app.get('/chat/history', authEndpoint, async (req: Request, res: Response) => {
   let html = chatHistory.map(renderChatMessage);
 
   res.send(html.join("\n"));
 });
 
   let html = chatHistory.map(renderChatMessage);
 
   res.send(html.join("\n"));
 });
 
-app.post('/chat', authEndpoint, async (req: AuthRequest, res: Response) => {
+app.post('/chat', authEndpoint, async (req: Request, res: Response) => {
   const msg = req.body.message.trim();
 
   if(!msg || !msg.length) {
   const msg = req.body.message.trim();
 
   if(!msg || !msg.length) {
@@ -166,24 +166,29 @@ app.post('/chat', authEndpoint, async (req: AuthRequest, res: Response) => {
 
   let message: Message;
   if(msg.startsWith('/server lmnop')) {
 
   let message: Message;
   if(msg.startsWith('/server lmnop')) {
-    if(msg === '/server lmnop refresh-monsters') {
-      await createMonsters();
-      message = broadcastMessage('server', 'Monster refresh!');
-    }
-    else if(msg === '/server lmnop refresh-cities') {
-      await createAllCitiesAndLocations();
-      message = broadcastMessage('server', 'Cities, Locations, and Paths refreshed!');
-    }
-    else if(msg === '/server lmnop refresh-shops') {
-      await createShopItems();
-      await createShopEquipment();
-      message = broadcastMessage('server', 'Refresh shop items');
-    }
-    else {
-      const str = msg.split('/server lmnop ')[1];
-      if(str) {
-        message = broadcastMessage('server', str);
+    try {
+      if(msg === '/server lmnop refresh-monsters') {
+        await createMonsters();
+        message = broadcastMessage('server', 'Monster refresh!');
+      }
+      else if(msg === '/server lmnop refresh-cities') {
+        await createAllCitiesAndLocations();
+        message = broadcastMessage('server', 'Cities, Locations, and Paths refreshed!');
+      }
+      else if(msg === '/server lmnop refresh-shops') {
+        await createShopItems();
+        await createShopEquipment();
+        message = broadcastMessage('server', 'Refresh shop items');
       }
       }
+      else {
+        const str = msg.split('/server lmnop ')[1];
+        if(str) {
+          message = broadcastMessage('server', str);
+        }
+      }
+    }
+    catch(e) {
+      io.to(cache.get(`socket:${req.player.id}`)).emit('chat', renderChatMessage(broadcastMessage('server', e.message)));
     }
   }
   else if(msg === '/online') {
     }
   }
   else if(msg === '/online') {
@@ -207,12 +212,12 @@ app.post('/chat', authEndpoint, async (req: AuthRequest, res: Response) => {
   }
 });
 
   }
 });
 
-app.get('/player', authEndpoint, async (req: AuthRequest, res: Response) => {
+app.get('/player', authEndpoint, async (req: Request, res: Response) => {
   const equipment = await getEquippedItems(req.player.id);
   res.send(renderPlayerBar(req.player) + renderProfilePage(req.player, equipment));
 });
 
   const equipment = await getEquippedItems(req.player.id);
   res.send(renderPlayerBar(req.player) + renderProfilePage(req.player, equipment));
 });
 
-app.post('/player/stat/:stat', authEndpoint, async (req: AuthRequest, res: Response) => {
+app.post('/player/stat/:stat', authEndpoint, async (req: Request, res: Response) => {
   const equipment = await getEquippedItems(req.player.id);
   const stat = req.params.stat;
   if(!['strength', 'constitution', 'dexterity', 'intelligence'].includes(stat)) {
   const equipment = await getEquippedItems(req.player.id);
   const stat = req.params.stat;
   if(!['strength', 'constitution', 'dexterity', 'intelligence'].includes(stat)) {
@@ -235,13 +240,13 @@ app.post('/player/stat/:stat', authEndpoint, async (req: AuthRequest, res: Respo
   res.send(renderPlayerBar(req.player) + renderProfilePage(req.player, equipment));
 });
 
   res.send(renderPlayerBar(req.player) + renderProfilePage(req.player, equipment));
 });
 
-app.get('/player/skills', authEndpoint, async (req: AuthRequest, res: Response) => {
+app.get('/player/skills', authEndpoint, async (req: Request, res: Response) => {
   const skills = await getPlayerSkills(req.player.id);
 
   res.send(renderSkills(skills));
 });
 
   const skills = await getPlayerSkills(req.player.id);
 
   res.send(renderSkills(skills));
 });
 
-app.get('/player/inventory', authEndpoint, async (req: AuthRequest, res: Response) => {
+app.get('/player/inventory', authEndpoint, async (req: Request, res: Response) => {
   const [inventory, items] = await Promise.all([
     getInventory(req.player.id),
     getPlayersItems(req.player.id)
   const [inventory, items] = await Promise.all([
     getInventory(req.player.id),
     getPlayersItems(req.player.id)
@@ -250,7 +255,7 @@ app.get('/player/inventory', authEndpoint, async (req: AuthRequest, res: Respons
   res.send(renderInventoryPage(inventory, items));
 });
 
   res.send(renderInventoryPage(inventory, items));
 });
 
-app.post('/player/equip/:item_id/:slot', authEndpoint, blockPlayerInFight, async (req: AuthRequest, res: Response) => {
+app.post('/player/equip/:item_id/:slot', authEndpoint, blockPlayerInFight, async (req: Request, res: Response) => {
   const inventoryItem = await getInventoryItem(req.player.id, req.params.item_id);
   const equippedItems = await getEquippedItems(req.player.id);
   const requestedSlot = req.params.slot;
   const inventoryItem = await getInventoryItem(req.player.id, req.params.item_id);
   const equippedItems = await getEquippedItems(req.player.id);
   const requestedSlot = req.params.slot;
@@ -302,7 +307,7 @@ app.post('/player/equip/:item_id/:slot', authEndpoint, blockPlayerInFight, async
   res.send(renderInventoryPage(inventory, items, inventoryItem.type) + renderPlayerBar(req.player));
 });
 
   res.send(renderInventoryPage(inventory, items, inventoryItem.type) + renderPlayerBar(req.player));
 });
 
-app.post('/player/unequip/:item_id', authEndpoint, blockPlayerInFight, async (req: AuthRequest, res: Response) => {
+app.post('/player/unequip/:item_id', authEndpoint, blockPlayerInFight, async (req: Request, res: Response) => {
   const [item, ] = await Promise.all([
     getInventoryItem(req.player.id, req.params.item_id),
     unequip(req.player.id, req.params.item_id)
   const [item, ] = await Promise.all([
     getInventoryItem(req.player.id, req.params.item_id),
     unequip(req.player.id, req.params.item_id)
@@ -316,7 +321,7 @@ app.post('/player/unequip/:item_id', authEndpoint, blockPlayerInFight, async (re
   res.send(renderInventoryPage(inventory, items, item.type) + renderPlayerBar(req.player));
 });
 
   res.send(renderInventoryPage(inventory, items, item.type) + renderPlayerBar(req.player));
 });
 
-app.get('/player/explore', authEndpoint, async (req: AuthRequest, res: Response) => {
+app.get('/player/explore', authEndpoint, async (req: Request, res: Response) => {
   const fight = await loadMonsterFromFight(req.player.id);
   const travelPlan = await getTravelPlan(req.player.id);
   let closestTown = req.player.city_id;
   const fight = await loadMonsterFromFight(req.player.id);
   const travelPlan = await getTravelPlan(req.player.id);
   let closestTown = req.player.city_id;
@@ -326,18 +331,9 @@ app.get('/player/explore', authEndpoint, async (req: AuthRequest, res: Response)
   }
 
   if(fight) {
   }
 
   if(fight) {
-    const data: MonsterForFight = {
-      id: fight.id,
-      hp: fight.hp,
-      maxHp: fight.maxHp,
-      name: fight.name,
-      level: fight.level,
-      fight_trigger: fight.fight_trigger
-    };
     const location = await getMonsterLocation(fight.ref_id);
 
     const location = await getMonsterLocation(fight.ref_id);
 
-
-    res.send(renderPlayerBar(req.player) + renderFightPreRound(data, true, location, closestTown));
+    res.send(renderPlayerBar(req.player) + renderFightPreRound(fight, true, location, closestTown));
   }
   else {
     if(travelPlan) {
   }
   else {
     if(travelPlan) {
@@ -375,7 +371,7 @@ app.get('/player/explore', authEndpoint, async (req: AuthRequest, res: Response)
 });
 
 // used to purchase equipment from a particular shop
 });
 
 // used to purchase equipment from a particular shop
-app.put('/location/:location_id/equipment/:item_id', authEndpoint, async (req: AuthRequest, res: Response) => {
+app.put('/location/:location_id/equipment/:item_id', authEndpoint, async (req: Request, res: Response) => {
   const item = await getShopEquipment(parseInt(req.params.item_id), parseInt(req.params.location_id));
 
   if(!item) {
   const item = await getShopEquipment(parseInt(req.params.item_id), parseInt(req.params.location_id));
 
   if(!item) {
@@ -397,7 +393,7 @@ app.put('/location/:location_id/equipment/:item_id', authEndpoint, async (req: A
 });
 
 // used to purchase items from a particular shop
 });
 
 // used to purchase items from a particular shop
-app.put('/location/:location_id/items/:item_id', authEndpoint, async (req: AuthRequest, res: Response) => {
+app.put('/location/:location_id/items/:item_id', authEndpoint, async (req: Request, res: Response) => {
   const item: (ShopItem & Item) = await getItemFromShop(parseInt(req.params.item_id), parseInt(req.params.location_id));
 
   if(!item) {
   const item: (ShopItem & Item) = await getItemFromShop(parseInt(req.params.item_id), parseInt(req.params.location_id));
 
   if(!item) {
@@ -421,7 +417,7 @@ app.put('/location/:location_id/items/:item_id', authEndpoint, async (req: AuthR
 // used to display equipment modals in a store, validates that 
 // the equipment is actually in this store before displaying 
 // the modal
 // used to display equipment modals in a store, validates that 
 // the equipment is actually in this store before displaying 
 // the modal
-app.get('/location/:location_id/equipment/:item_id/overview', authEndpoint, async (req: AuthRequest, res: Response) => {
+app.get('/location/:location_id/equipment/:item_id/overview', authEndpoint, async (req: Request, res: Response) => {
   const equipment = await getShopEquipment(parseInt(req.params.item_id), parseInt(req.params.location_id));
 
   if(!equipment) {
   const equipment = await getShopEquipment(parseInt(req.params.item_id), parseInt(req.params.location_id));
 
   if(!equipment) {
@@ -452,7 +448,7 @@ app.get('/location/:location_id/equipment/:item_id/overview', authEndpoint, asyn
 // used to display item modals in a store, validates that 
 // the item is actually in this store before displaying 
 // the modal
 // used to display item modals in a store, validates that 
 // the item is actually in this store before displaying 
 // the modal
-app.get('/location/:location_id/items/:item_id/overview', authEndpoint, async (req: AuthRequest, res: Response) => {
+app.get('/location/:location_id/items/:item_id/overview', authEndpoint, async (req: Request, res: Response) => {
   const item: (ShopItem & Item) = await getItemFromShop(parseInt(req.params.item_id), parseInt(req.params.location_id));
 
   if(!item) {
   const item: (ShopItem & Item) = await getItemFromShop(parseInt(req.params.item_id), parseInt(req.params.location_id));
 
   if(!item) {
@@ -481,7 +477,7 @@ app.get('/location/:location_id/items/:item_id/overview', authEndpoint, async (r
   res.send(html);
 });
 
   res.send(html);
 });
 
-app.put('/item/:item_id', authEndpoint, async (req: AuthRequest, res: Response) => {
+app.put('/item/:item_id', authEndpoint, async (req: Request, res: Response) => {
   const item: PlayerItem = await getItemFromPlayer(req.player.id, parseInt(req.params.item_id));
 
   if(!item) {
   const item: PlayerItem = await getItemFromPlayer(req.player.id, parseInt(req.params.item_id));
 
   if(!item) {
@@ -524,7 +520,7 @@ app.put('/item/:item_id', authEndpoint, async (req: AuthRequest, res: Response)
 
 });
 
 
 });
 
-app.get('/modal/items/:item_id', authEndpoint, async (req: AuthRequest, res: Response) => {
+app.get('/modal/items/:item_id', authEndpoint, async (req: Request, res: Response) => {
   const item: PlayerItem = await getItemFromPlayer(req.player.id, parseInt(req.params.item_id));
 
   if(!item) {
   const item: PlayerItem = await getItemFromPlayer(req.player.id, parseInt(req.params.item_id));
 
   if(!item) {
@@ -553,7 +549,7 @@ app.get('/modal/items/:item_id', authEndpoint, async (req: AuthRequest, res: Res
   res.send(html);
 });
 
   res.send(html);
 });
 
-app.get('/city/stores/city:stores/:location_id', authEndpoint, async (req: AuthRequest, res: Response) => {
+app.get('/city/stores/city:stores/:location_id', authEndpoint, async (req: Request, res: Response) => {
   const location = await getService(parseInt(req.params.location_id));
 
   if(!location || location.city_id !== req.player.city_id) {
   const location = await getService(parseInt(req.params.location_id));
 
   if(!location || location.city_id !== req.player.city_id) {
@@ -570,7 +566,7 @@ app.get('/city/stores/city:stores/:location_id', authEndpoint, async (req: AuthR
   res.send(html);
 });
 
   res.send(html);
 });
 
-app.get('/city/explore/city:explore/:location_id', authEndpoint, async (req: AuthRequest, res: Response) => {
+app.get('/city/explore/city:explore/:location_id', authEndpoint, async (req: Request, res: Response) => {
   const location = await getService(parseInt(req.params.location_id));
   if(!location || location.city_id !== req.player.city_id) {
 
   const location = await getService(parseInt(req.params.location_id));
   if(!location || location.city_id !== req.player.city_id) {
 
@@ -582,7 +578,7 @@ app.get('/city/explore/city:explore/:location_id', authEndpoint, async (req: Aut
   res.send(renderOnlyMonsterSelector(monsters, 0, location));
 });
 
   res.send(renderOnlyMonsterSelector(monsters, 0, location));
 });
 
-app.post('/travel', authEndpoint, async (req: AuthRequest, res: Response) => {
+app.post('/travel', authEndpoint, async (req: Request, res: Response) => {
   const destination_id = parseInt(req.body.destination_id);
 
   if(!destination_id || isNaN(destination_id)) {
   const destination_id = parseInt(req.body.destination_id);
 
   if(!destination_id || isNaN(destination_id)) {
@@ -595,7 +591,7 @@ app.post('/travel', authEndpoint, async (req: AuthRequest, res: Response) => {
   res.json(travelPlan);
 });
 
   res.json(travelPlan);
 });
 
-app.post('/fight/turn', authEndpoint, async (req: AuthRequest, res: Response) => {
+app.post('/fight/turn', authEndpoint, async (req: Request, res: Response) => {
   const fightBlockKey = `fightturn:${req.player.id}`;
 
   if(cache[fightBlockKey] && cache[fightBlockKey] > Date.now()) {
   const fightBlockKey = `fightturn:${req.player.id}`;
 
   if(cache[fightBlockKey] && cache[fightBlockKey] > Date.now()) {
@@ -605,7 +601,7 @@ app.post('/fight/turn', authEndpoint, async (req: AuthRequest, res: Response) =>
   }
 
   cache[fightBlockKey] = Date.now() + CONSTANT.FIGHT_ATTACK_DELAY;
   }
 
   cache[fightBlockKey] = Date.now() + CONSTANT.FIGHT_ATTACK_DELAY;
-  const monster = await loadMonsterWithFaction(req.player.id);
+  const monster = await loadMonsterFromFight(req.player.id);
 
   if(!monster) {
     res.send(Alert.ErrorAlert('Not in a fight'));
 
   if(!monster) {
     res.send(Alert.ErrorAlert('Not in a fight'));
@@ -613,8 +609,7 @@ app.post('/fight/turn', authEndpoint, async (req: AuthRequest, res: Response) =>
   }
 
   const fightData  = await fightRound(req.player, monster, {
   }
 
   const fightData  = await fightRound(req.player, monster, {
-    action: req.body.action,
-    target: req.body.fightTarget
+    action: req.body.action
   });
 
 
   });
 
 
@@ -646,7 +641,7 @@ app.post('/fight/turn', authEndpoint, async (req: AuthRequest, res: Response) =>
   res.send(html + travelSection + playerBar);
 });
 
   res.send(html + travelSection + playerBar);
 });
 
-app.post('/fight', fightRateLimiter, authEndpoint, async (req: AuthRequest, res: Response) => {
+app.post('/fight', fightRateLimiter, authEndpoint, async (req: Request, res: Response) => {
   if(req.player.hp <= 0) {
     logger.log(`Player didn\'t have enough hp`);
     return res.sendStatus(400);
   if(req.player.hp <= 0) {
     logger.log(`Player didn\'t have enough hp`);
     return res.sendStatus(400);
@@ -675,20 +670,10 @@ app.post('/fight', fightRateLimiter, authEndpoint, async (req: AuthRequest, res:
   const fight = await createFight(req.player.id, monster, fightTrigger);
   const location = await getService(monster.location_id);
 
   const fight = await createFight(req.player.id, monster, fightTrigger);
   const location = await getService(monster.location_id);
 
-
-  const data: MonsterForFight = {
-    id: fight.id,
-    hp: fight.hp,
-    maxHp: fight.maxHp,
-    name: fight.name,
-    level: fight.level,
-    fight_trigger: fight.fight_trigger
-  };
-
-  res.send(renderFightPreRound(data, true, location, location.city_id));
+  res.send(renderFightPreRound(fight, true, location, location.city_id));
 });
 
 });
 
-app.post('/travel/step', authEndpoint, async (req: AuthRequest, res: Response) => {
+app.post('/travel/step', authEndpoint, async (req: Request, res: Response) => {
   const stepTimerKey = `step:${req.player.id}`;
 
   const travelPlan = await getTravelPlan(req.player.id);
   const stepTimerKey = `step:${req.player.id}`;
 
   const travelPlan = await getTravelPlan(req.player.id);
@@ -755,7 +740,7 @@ app.post('/travel/step', authEndpoint, async (req: AuthRequest, res: Response) =
   }
 });
 
   }
 });
 
-app.post('/travel/return-to-source', authEndpoint, async (req: AuthRequest, res: Response) => {
+app.post('/travel/return-to-source', authEndpoint, async (req: Request, res: Response) => {
   // puts the player back in their starting town
   // doesn't matter if they don't have one
   // redirect them!
   // puts the player back in their starting town
   // doesn't matter if they don't have one
   // redirect them!
@@ -764,17 +749,9 @@ app.post('/travel/return-to-source', authEndpoint, async (req: AuthRequest, res:
   const fight = await loadMonsterFromFight(req.player.id);
   if(fight) {
     // go to the fight screen
   const fight = await loadMonsterFromFight(req.player.id);
   if(fight) {
     // go to the fight screen
-    const data: MonsterForFight = {
-      id: fight.id,
-      hp: fight.hp,
-      maxHp: fight.maxHp,
-      name: fight.name,
-      level: fight.level,
-      fight_trigger: fight.fight_trigger
-    };
     const location = await getMonsterLocation(fight.ref_id);
 
     const location = await getMonsterLocation(fight.ref_id);
 
-    res.send(renderPlayerBar(req.player) + renderFightPreRound(data, true, location, req.player.city_id));
+    res.send(renderPlayerBar(req.player) + renderFightPreRound(fight, true, location, req.player.city_id));
   }
   else {
     const [city, locations, paths] = await Promise.all([
   }
   else {
     const [city, locations, paths] = await Promise.all([
@@ -789,7 +766,7 @@ app.post('/travel/return-to-source', authEndpoint, async (req: AuthRequest, res:
 
 });
 
 
 });
 
-app.post('/travel/:destination_id', authEndpoint, async (req: AuthRequest, res: Response) => {
+app.post('/travel/:destination_id', authEndpoint, async (req: Request, res: Response) => {
 if(req.player.hp <= 0) {
     logger.log(`Player didn\'t have enough hp`);
     res.send(Alert.ErrorAlert('Sorry, you need some HP to start travelling.'));
 if(req.player.hp <= 0) {
     logger.log(`Player didn\'t have enough hp`);
     res.send(Alert.ErrorAlert('Sorry, you need some HP to start travelling.'));
@@ -814,7 +791,7 @@ if(req.player.hp <= 0) {
   }));
 });
 
   }));
 });
 
-app.get('/settings', authEndpoint, async (req: AuthRequest, res: Response) => {
+app.get('/settings', authEndpoint, async (req: Request, res: Response) => {
   let warning = '';
   let html = '';
   if(req.player.account_type === 'session') {
   let warning = '';
   let html = '';
   if(req.player.account_type === 'session') {
@@ -825,7 +802,7 @@ app.get('/settings', authEndpoint, async (req: AuthRequest, res: Response) => {
   res.send(warning + html);
 });
 
   res.send(warning + html);
 });
 
-app.post('/logout', authEndpoint, async (req: AuthRequest, res: Response) => {
+app.post('/logout', authEndpoint, async (req: Request, res: Response) => {
   // ref to get the socket id for a particular player
   cache.delete(`socket:${req.player.id}`);
   // ref to get the player object
   // ref to get the socket id for a particular player
   cache.delete(`socket:${req.player.id}`);
   // ref to get the player object
index 456d2937a7f755c174411f3c5d709061705a0f9b..9b4a3a41d38ddfbc2160e881e7cf94f78039b829 100644 (file)
@@ -6,10 +6,6 @@ import { Auth } from '../shared/auth';
 import { db } from './lib/db';
 import { Request, Response } from 'express';
 
 import { db } from './lib/db';
 import { Request, Response } from 'express';
 
-export interface AuthRequest extends Request {
-  player: Player
-}
-
 export async function signup(playerId: string, username: string, password: string): Promise<void> {
   const salt = await bcrypt.genSalt(10);
   const hash = await bcrypt.hash(password, salt);
 export async function signup(playerId: string, username: string, password: string): Promise<void> {
   const salt = await bcrypt.genSalt(10);
   const hash = await bcrypt.hash(password, salt);
@@ -63,7 +59,7 @@ export async function login(username: string, password: string): Promise<Player>
 
 }
 
 
 }
 
-export async function authEndpoint(req: AuthRequest, res: Response, next: any) {
+export async function authEndpoint(req: Request, res: Response, next: any) {
   const authToken = req.headers['x-authtoken'];
   if(!authToken) {
     console.log(`Invalid auth token ${authToken}`);
   const authToken = req.headers['x-authtoken'];
   if(!authToken) {
     console.log(`Invalid auth token ${authToken}`);
index 2c9383d19b361f22d2c36fbd40420f151c64a85c..011cc5c667dfa27a4f4f90035b94aa545a675aa5 100644 (file)
@@ -6,14 +6,13 @@ import { updatePlayer } from './player';
 import { getEquippedItems, updateAp } from './inventory';
 import { EquippedItemDetails } from '../shared/equipped';
 import { EquipmentSlot } from '../shared/inventory';
 import { getEquippedItems, updateAp } from './inventory';
 import { EquippedItemDetails } from '../shared/equipped';
 import { EquipmentSlot } from '../shared/inventory';
-import { MonsterWithFaction, MonsterForFight } from '../shared/monsters';
+import { MonsterWithFaction, MonsterForFight, Fight } from '../shared/monsters';
 import { getPlayerSkillsAsObject, updatePlayerSkills } from './skills';
 import { SkillID, Skills } from '../shared/skills';
 import { getPlayerSkillsAsObject, updatePlayerSkills } from './skills';
 import { SkillID, Skills } from '../shared/skills';
-import { AuthRequest } from './auth';
-import { Response } from 'express';
+import { Request, Response } from 'express';
 import * as Alert from './views/alert';
 
 import * as Alert from './views/alert';
 
-export async function blockPlayerInFight(req: AuthRequest, res: Response, next: any) {
+export async function blockPlayerInFight(req: Request, res: Response, next: any) {
   const fight = await loadMonsterFromFight(req.player.id);
   if(!fight) {
     next();
   const fight = await loadMonsterFromFight(req.player.id);
   if(!fight) {
     next();
@@ -36,7 +35,7 @@ function exponentialExp(exp: number, monsterLevel: number, playerLevel: number):
   return Math.floor(finalExp);
 }
 
   return Math.floor(finalExp);
 }
 
-export async function fightRound(player: Player, monster: MonsterWithFaction,  data: {action: 'attack' | 'cast' | 'flee', target: 'head' | 'body' | 'arms' | 'legs'}) {
+export async function fightRound(player: Player, monster: Fight,  data: {action: 'attack' | 'cast' | 'flee'}) {
   const playerSkills = await getPlayerSkillsAsObject(player.id);
   const roundData: FightRound = {
     monster,
   const playerSkills = await getPlayerSkillsAsObject(player.id);
   const roundData: FightRound = {
     monster,
@@ -196,7 +195,8 @@ export async function fightRound(player: Player, monster: MonsterWithFaction,  d
         return {
           id: monster.id,
           name: monster.name,
         return {
           id: monster.id,
           name: monster.name,
-          level: monster.level,
+          minLevel: monster.minLevel,
+          maxLevel: monster.maxLevel,
           hp: monster.hp,
           maxHp: monster.maxHp,
           fight_trigger: 'explore'
           hp: monster.hp,
           maxHp: monster.maxHp,
           fight_trigger: 'explore'
index c49e6bf9b96355004514bdb9a0b9f88361c9cb4f..1d0759a2bb9a6a055ba4e0d4acb6f4aaa51df286 100644 (file)
@@ -1,5 +1,11 @@
 export const logger = {
 export const logger = {
+  debug: (...args: any | any[]) => {
+    console.debug(...args);
+  },
   log: (...args: any | any[]) => {
     console.log(...args);
   log: (...args: any | any[]) => {
     console.log(...args);
-  }
+  },
+  error: (...args: any | any[]) => {
+    console.error(...args);
+  },
 }
 }
diff --git a/src/server/locations/healer.ts b/src/server/locations/healer.ts
new file mode 100644 (file)
index 0000000..3d7374b
--- /dev/null
@@ -0,0 +1,173 @@
+import { Request, Response, Router } from "express";
+import { maxHp, maxVigor } from "../../shared/player";
+import { authEndpoint } from '../auth';
+import { logger } from "../lib/logger";
+import { 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 { BackToTown } from "../views/components/button";
+
+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/healer/:location_id', authEndpoint, async (req: Request, res: Response) => {
+  const service = await getService(parseInt(req.params.location_id));
+  const city = await getCityDetails(service.city_id);
+
+  if(!service || service.city_id !== req.player.city_id) {
+    logger.log(`Invalid location: [${req.params.location_id}]`);
+    res.sendStatus(400);
+  }
+
+  const text: string[] = [];
+
+  text.push(`<p>"${getText('intro', service, city)}"</p>`);
+
+  if(req.player.hp === maxHp(req.player.constitution, req.player.level) && req.player.vigor === maxVigor(req.player.constitution, req.player.level)) {
+    text.push(`<p>You're already in peak condition!</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/healer/heal/${service.id}" hx-target="#explore">Heal for Free!</button></p>`);
+    }
+    else {
+      text.push(`<p><button type="button" hx-post="/city/services/healer/heal/${service.id}" hx-target="#explore">Heal for ${healCost}g!</button></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">
+${text.join("\n")}
+${BackToTown()}
+</div>
+</div>
+  `);
+
+  //res.send(`<div class="service-in-town">${text.join("\n")}</div>`);
+});
+
+
+
+router.post('/city/services/healer/heal/:location_id', authEndpoint, async (req: Request, res: Response) => {
+  const service = await getService(parseInt(req.params.location_id));
+  const city = await getCityDetails(service.city_id);
+
+  if(!service || service.city_id !== req.player.city_id) {
+    logger.log(`Invalid location: [${req.params.location_id}]`);
+    res.sendStatus(400);
+  }
+
+  const text: string[] = [];
+  const cost = req.player.gold <= (healCost * 2) ? 0 : healCost;
+
+  if(req.player.gold < cost) {
+    text.push(`<p>${getText('insufficient_money', service, city)}</p>`)
+  }
+  else {
+    req.player.hp = maxHp(req.player.constitution, req.player.level);
+    req.player.vigor = maxVigor(req.player.constitution, req.player.level);
+    req.player.gold -= cost;
+
+    await updatePlayer(req.player);
+
+    text.push(`<p>${getText('heal_successful', service, city)}</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">
+${text.join("\n")}
+${BackToTown()}
+</div>
+</div>
+${renderPlayerBar(req.player)}
+`);
+});
diff --git a/src/server/locations/healer/index.ts b/src/server/locations/healer/index.ts
deleted file mode 100644 (file)
index 83551b2..0000000
+++ /dev/null
@@ -1,173 +0,0 @@
-import { Response, Router } from "express";
-import { maxHp, maxVigor } from "../../../shared/player";
-import { authEndpoint, AuthRequest } from '../../auth';
-import { logger } from "../../lib/logger";
-import { 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 { BackToTown } from "../../views/components/button";
-
-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/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);
-
-  if(!service || service.city_id !== req.player.city_id) {
-    logger.log(`Invalid location: [${req.params.location_id}]`);
-    res.sendStatus(400);
-  }
-
-  const text: string[] = [];
-
-  text.push(`<p>"${getText('intro', service, city)}"</p>`);
-
-  if(req.player.hp === maxHp(req.player.constitution, req.player.level) && req.player.vigor === maxVigor(req.player.constitution, req.player.level)) {
-    text.push(`<p>You're already in peak condition!</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/healer/heal/${service.id}" hx-target="#explore">Heal for Free!</button></p>`);
-    }
-    else {
-      text.push(`<p><button type="button" hx-post="/city/services/healer/heal/${service.id}" hx-target="#explore">Heal for ${healCost}g!</button></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">
-${text.join("\n")}
-${BackToTown()}
-</div>
-</div>
-  `);
-
-  //res.send(`<div class="service-in-town">${text.join("\n")}</div>`);
-});
-
-
-
-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);
-
-  if(!service || service.city_id !== req.player.city_id) {
-    logger.log(`Invalid location: [${req.params.location_id}]`);
-    res.sendStatus(400);
-  }
-
-  const text: string[] = [];
-  const cost = req.player.gold <= (healCost * 2) ? 0 : healCost;
-
-  if(req.player.gold < cost) {
-    text.push(`<p>${getText('insufficient_money', service, city)}</p>`)
-  }
-  else {
-    req.player.hp = maxHp(req.player.constitution, req.player.level);
-    req.player.vigor = maxVigor(req.player.constitution, req.player.level);
-    req.player.gold -= cost;
-
-    await updatePlayer(req.player);
-
-    text.push(`<p>${getText('heal_successful', service, city)}</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">
-${text.join("\n")}
-${BackToTown()}
-</div>
-</div>
-${renderPlayerBar(req.player)}
-`);
-});
index 96f720d656cf6ce4332acc050ca956faef0d68cc..fa4686089b1c672b360f53728868dfe6444409cb 100644 (file)
@@ -1,6 +1,6 @@
-import { Response, Router } from "express";
+import { Request, Response, Router } from "express";
 import { getService } from "../map";
 import { getService } from "../map";
-import { authEndpoint, AuthRequest } from '../auth';
+import { authEndpoint } from '../auth';
 import { logger } from "../lib/logger";
 import * as Alert from "../views/alert";
 import { changeProfession } from "../player";
 import { logger } from "../lib/logger";
 import * as Alert from "../views/alert";
 import { changeProfession } from "../player";
@@ -15,7 +15,7 @@ export const router = Router();
 
 const MIN_LEVEL = 25;
 
 
 const MIN_LEVEL = 25;
 
-router.get('/city/services/profession_recruitor/:location_id', authEndpoint, async(req: AuthRequest, res: Response) => {
+router.get('/city/services/profession_recruitor/:location_id', authEndpoint, async(req: Request, res: Response) => {
   const service = await getService(parseInt(req.params.location_id));
 
   if(!service || service.city_id !== req.player.city_id) {
   const service = await getService(parseInt(req.params.location_id));
 
   if(!service || service.city_id !== req.player.city_id) {
@@ -80,7 +80,7 @@ router.get('/city/services/profession_recruitor/:location_id', authEndpoint, asy
   `);
 });
 
   `);
 });
 
-router.post('/city/services/profession_change/:location_id', authEndpoint, async(req: AuthRequest, res: Response) => {
+router.post('/city/services/profession_change/:location_id', authEndpoint, async(req: Request, res: Response) => {
   const service = await getService(parseInt(req.params.location_id));
 
   if(!service || service.city_id !== req.player.city_id) {
   const service = await getService(parseInt(req.params.location_id));
 
   if(!service || service.city_id !== req.player.city_id) {
index 6bb742333de4033847f0d2860805201a635aa53c..19d382c3d17cab0e187f074a9cc492df182364d1 100644 (file)
@@ -1,5 +1,5 @@
-import { Response, Router } from "express";
-import { authEndpoint, AuthRequest } from '../auth';
+import { Request, Response, Router } from "express";
+import { authEndpoint } from '../auth';
 import { logger } from "../lib/logger";
 import { getService } from "../map";
 import { getInventory, getInventoryItem, repair } from '../inventory';
 import { logger } from "../lib/logger";
 import { getService } from "../map";
 import { getInventory, getInventoryItem, repair } from '../inventory';
@@ -11,7 +11,7 @@ import { renderPlayerBar } from "../views/player-bar";
 
 export const router = Router();
 
 
 export const router = Router();
 
-router.get('/city/services/repair/:location_id', authEndpoint, async(req: AuthRequest, res: Response) => {
+router.get('/city/services/repair/:location_id', authEndpoint, async(req: Request, res: Response) => {
   const service = await getService(parseInt(req.params.location_id));
 
   if(!service || service.city_id !== req.player.city_id) {
   const service = await getService(parseInt(req.params.location_id));
 
   if(!service || service.city_id !== req.player.city_id) {
@@ -28,7 +28,7 @@ router.get('/city/services/repair/:location_id', authEndpoint, async(req: AuthRe
   res.send(renderRepairService(damaged, req.player, service));
 });
 
   res.send(renderRepairService(damaged, req.player, service));
 });
 
-router.post('/city/services/:location_id/repair/:item_id', authEndpoint, async (req: AuthRequest, res: Response) => {
+router.post('/city/services/:location_id/repair/:item_id', authEndpoint, async (req: Request, res: Response) => {
   const service = await getService(parseInt(req.params.location_id));
 
   if(!service || service.city_id !== req.player.city_id) {
   const service = await getService(parseInt(req.params.location_id));
 
   if(!service || service.city_id !== req.player.city_id) {
index 943a868205bedd89cee65471efd21111ab7c0435..a0b7aac669e7b2c400180381cfde0f3b22fb8cad 100644 (file)
@@ -1,7 +1,9 @@
 import { db } from './lib/db';
 import { db } from './lib/db';
-import { Fight, Monster, MonsterWithFaction, MonsterForList, FightTrigger } from '../shared/monsters';
+import { Fight, Monster, MonsterWithFaction, MonsterForList, FightTrigger, MonsterVariant, MonsterVariants } from '../shared/monsters';
 import { TimePeriod, TimeManager } from '../shared/time';
 import { TimePeriod, TimeManager } from '../shared/time';
-import { LocationWithCity } from 'shared/map';
+import { LocationWithCity } from '../shared/map';
+import { random, sample } from 'lodash';
+import { CHANCE_TO_FIGHT_SPECIAL } from '../shared/constants';
 
 const time = new TimeManager();
 
 
 const time = new TimeManager();
 
@@ -21,7 +23,7 @@ export async function getMonsterList(location_id: number, timePeriod: TimePeriod
                       .where({ location_id })
                       .whereIn('time_period', timePeriod)
                       .from<Monster>('monsters')
                       .where({ location_id })
                       .whereIn('time_period', timePeriod)
                       .from<Monster>('monsters')
-                      .orderBy('level');
+                      .orderBy('minLevel');
 
   return res;
 }
 
   return res;
 }
@@ -41,7 +43,8 @@ export async function loadMonsterFromFight(player_id: string): Promise<Fight> {
 export async function loadMonsterWithFaction(player_id: string): Promise<MonsterWithFaction> {
   const res = await db.raw(`
                       select 
 export async function loadMonsterWithFaction(player_id: string): Promise<MonsterWithFaction> {
   const res = await db.raw(`
                       select 
-                        f.*, fa.id as faction_id, fa.name as faction_name
+                        f.*, fa.id as faction_id, fa.name as faction_name,
+                        m.minLevel, m.maxLevel
                       from fight f
                       join monsters m on f.ref_id = m.id
                       left outer join factions fa on m.faction_id = fa.id
                       from fight f
                       join monsters m on f.ref_id = m.id
                       left outer join factions fa on m.faction_id = fa.id
@@ -62,22 +65,46 @@ export async function saveFightState(authToken: string, monster: Fight) {
 }
 
 export async function createFight(playerId: string, monster: Monster, fightTrigger: FightTrigger): Promise<Fight> {
 }
 
 export async function createFight(playerId: string, monster: Monster, fightTrigger: FightTrigger): Promise<Fight> {
-  const res = await db('fight').insert({
+  const chosenLevel = random(monster.minLevel, monster.maxLevel);
+  // 30% boost per level difference
+  const modifier = Math.pow(Math.E, (chosenLevel - monster.minLevel)/monster.maxLevel);
+  let variant: MonsterVariant = {
+    name: '',
+    display: '{{name}}',
+    strength: 1,
+    constitution: 1,
+    dexterity: 1,
+    intelligence: 1,
+    exp: 1,
+    gold: 1,
+    maxHp: 1,
+    defence: 1
+  };
+
+  if(monster.maxLevel >= 5 && random(0,100) <= CHANCE_TO_FIGHT_SPECIAL) {
+    variant = sample(MonsterVariants);
+  }
+
+  const monsterData: Omit<Fight, 'id'> = {
     player_id: playerId,
     player_id: playerId,
-    name: monster.name,
-    strength: monster.strength,
-    constitution: monster.constitution,
-    dexterity: monster.dexterity,
-    intelligence: monster.intelligence,
-    exp: monster.exp,
-    level: monster.level,
-    gold: monster.gold,
-    hp: monster.hp,
-    defence: monster.defence,
-    maxHp: monster.maxHp,
+    variant: variant.name,
+    name: variant.display.replace("{{name}}", monster.name),
+    strength: Math.floor(monster.strength * modifier * variant.strength),
+    constitution: Math.floor(monster.constitution * modifier * variant.constitution),
+    dexterity: Math.floor(monster.dexterity * modifier * variant.dexterity),
+    intelligence: Math.floor(monster.intelligence * modifier * variant.intelligence),
+    exp: Math.floor(monster.exp * modifier * variant.exp),
+    level: chosenLevel,
+    gold: Math.floor(monster.gold * modifier * variant.exp),
+    hp: Math.floor(monster.hp * modifier * variant.maxHp),
+    defence: Math.floor(monster.defence * modifier * variant.defence),
+    maxHp: Math.floor(monster.maxHp * modifier * variant.maxHp),
     ref_id: monster.id,
     fight_trigger: fightTrigger
     ref_id: monster.id,
     fight_trigger: fightTrigger
-  }).returning<Fight[]>('*');
+  }
+
+  const res = await db('fight').insert(monsterData)
+                    .returning<Fight[]>('*');
 
   return res.pop();
 }
 
   return res.pop();
 }
index 06ebcdb170d8971288de6ae765fd9fbba57e8812..c66cdbd87b2c40415a9ccb958940891457ac5190 100644 (file)
@@ -1,6 +1,6 @@
 import { FightRound } from "shared/fight";
 import { LocationWithCity } from "shared/map";
 import { FightRound } from "shared/fight";
 import { LocationWithCity } from "shared/map";
-import { MonsterForFight } from "../../shared/monsters";
+import { Fight, MonsterForFight } from "../../shared/monsters";
 import { Button, ButtonWithBlock } from "./components/button";
 
 export function renderRoundDetails(roundData: FightRound): string {
 import { Button, ButtonWithBlock } from "./components/button";
 
 export function renderRoundDetails(roundData: FightRound): string {
@@ -69,7 +69,7 @@ function AttackButton(blockTime?: number): string {
   }
 }
 
   }
 }
 
-export function renderFight(monster: MonsterForFight, results: string = '', displayFightActions: boolean = true, blockTime: number = 0) {
+export function renderFight(monster: Fight, results: string = '', displayFightActions: boolean = true, blockTime: number = 0) {
   const hpPercent = Math.floor((monster.hp / monster.maxHp) * 100);
 
   let html = `
   const hpPercent = Math.floor((monster.hp / monster.maxHp) * 100);
 
   let html = `
@@ -79,19 +79,13 @@ export function renderFight(monster: MonsterForFight, results: string = '', disp
         <img id="avatar" src="https://via.placeholder.com/64x64">
       </div>
       <div id="defender-stat-bars">
         <img id="avatar" src="https://via.placeholder.com/64x64">
       </div>
       <div id="defender-stat-bars">
-        <div id="defender-name">${monster.name}</div>
+        <div class="monster-identifier ${monster.variant}"><span id="defender-name">${monster.name}</span>, level ${monster.level}</div>
         <div class="progress-bar" id="defender-hp-bar" style="background: linear-gradient(to right, red, red ${hpPercent}%, transparent ${hpPercent}%, transparent)" title="${hpPercent}% - ${monster.hp}/${monster.maxHp}">${hpPercent}% - ${monster.hp} / ${monster.maxHp}</div>
       </div>
     </div>
     <div id="fight-actions">
       ${displayFightActions ? `
       <form hx-post="/fight/turn" hx-target="#fight-container">
         <div class="progress-bar" id="defender-hp-bar" style="background: linear-gradient(to right, red, red ${hpPercent}%, transparent ${hpPercent}%, transparent)" title="${hpPercent}% - ${monster.hp}/${monster.maxHp}">${hpPercent}% - ${monster.hp} / ${monster.maxHp}</div>
       </div>
     </div>
     <div id="fight-actions">
       ${displayFightActions ? `
       <form hx-post="/fight/turn" hx-target="#fight-container">
-        <select id="fight-target" name="fightTarget">
-          <option value="head">Head</option>
-          <option value="body">Body</option>
-          <option value="arms">Arms</option>
-          <option value="legs">Legs</option>
-        </select>
         ${AttackButton(blockTime)}
         ${CastButton(blockTime)}
         <button type="submit" class="fight-action" name="action" value="flee">Flee</button>
         ${AttackButton(blockTime)}
         ${CastButton(blockTime)}
         <button type="submit" class="fight-action" name="action" value="flee">Flee</button>
@@ -105,9 +99,7 @@ export function renderFight(monster: MonsterForFight, results: string = '', disp
   return html;
 }
 
   return html;
 }
 
-export function renderFightPreRound(monster: MonsterForFight,  displayFightActions: boolean = true, location: LocationWithCity, closestTown: number) {
-  const hpPercent = Math.floor((monster.hp / monster.maxHp) * 100);
-
+export function renderFightPreRound(monster: Fight,  displayFightActions: boolean = true, location: LocationWithCity, closestTown: number) {
   let html = `
 <section id="explore" class="tab active" style="background-image: url('/assets/img/map/${closestTown}.jpeg')" hx-swap-oob="true">
   <div class="city-title-wrapper">
   let html = `
 <section id="explore" class="tab active" style="background-image: url('/assets/img/map/${closestTown}.jpeg')" hx-swap-oob="true">
   <div class="city-title-wrapper">
@@ -116,34 +108,8 @@ export function renderFightPreRound(monster: MonsterForFight,  displayFightActio
   <div class="city-details">
     <h3 class="location-name"><span>${location.name}</span></h3>
 
   <div class="city-details">
     <h3 class="location-name"><span>${location.name}</span></h3>
 
-  <div id="fight-container">
-    <div id="defender-info">
-      <div class="avatar-container">
-        <img id="avatar" src="https://via.placeholder.com/64x64">
-      </div>
-      <div id="defender-stat-bars">
-        <div id="defender-name">${monster.name}</div>
-        <div class="progress-bar" id="defender-hp-bar" style="background: linear-gradient(to right, red, red ${hpPercent}%, transparent ${hpPercent}%, transparent)" title="${hpPercent}% - ${monster.hp}/${monster.maxHp}">${hpPercent}% - ${monster.hp} / ${monster.maxHp}</div>
-      </div>
-    </div>
-    <div id="fight-actions">
-      ${displayFightActions ? `
-      <form hx-post="/fight/turn" hx-target="#fight-container">
-        <select id="fight-target" name="fightTarget">
-          <option value="head">Head</option>
-          <option value="body">Body</option>
-          <option value="arms">Arms</option>
-          <option value="legs">Legs</option>
-        </select>
-        ${AttackButton()}
-        ${CastButton()}
-        <button type="submit" class="fight-action" name="action" value="flee">Flee</button>
-      </form>
-      `: ''}
-      </div>
-    <div id="fight-results"></div>
+    ${renderFight(monster, '', displayFightActions)}
   </div>
   </div>
-</div>
 </section>
 `;
 
 </section>
 `;
 
index 6026775d1c91152eb01586d7a2d5ecce36b64227..88e3c8c338342180b9e93d84d5b45adf2970d3af 100644 (file)
@@ -121,21 +121,30 @@ function renderInventoryItem(item: EquippedItemDetails , action: (item: Equipped
 }
 
 function renderInventorySection(inventory: EquippedItemDetails[]): string {
 }
 
 function renderInventorySection(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 => {
       if(item.is_equipped) {
         return `<button type="button" class="unequip-item red" hx-post="/player/unequip/${item.item_id}">Unequip</button>`;
       }
       else {
   return inventory.map(item => {
     return renderInventoryItem(item, item => {
       if(item.is_equipped) {
         return `<button type="button" class="unequip-item red" hx-post="/player/unequip/${item.item_id}">Unequip</button>`;
       }
       else {
-        if(item.equipment_slot === 'ANY_HAND') {
-          return `<button type="button" class="equip-item" hx-post="/player/equip/${item.item_id}/LEFT_HAND">Equip L</button>
-<button type="button" class="equip-item" hx-post="/player/equip/${item.item_id}/RIGHT_HAND">Equip R</button>`;
-        }
-        else if(item.equipment_slot === 'LEFT_HAND') {
-          return `<button type="button" class="equip-item" hx-post="/player/equip/${item.item_id}/${item.equipment_slot}">Equip Left</button>`;
-        }
-        else if(item.equipment_slot === 'RIGHT_HAND') {
-          return `<button type="button" class="equip-item" hx-post="/player/equip/${item.item_id}/${item.equipment_slot}">Equip Right</button>`;
+        if(['ANY_HAND', 'LEFT_HAND', 'RIGHT_HAND'].includes(item.equipment_slot)) {
+          const str: string[] = [
+            `<button type="button" class="equip-item" hx-post="/player/equip/${item.item_id}/LEFT_HAND">Equip L</button>`,
+            `<button type="button" class="equip-item" hx-post="/player/equip/${item.item_id}/RIGHT_HAND">Equip R</button>`
+          ];
+
+          if(used_slots.includes('LEFT_HAND') && !used_slots.includes('RIGHT_HAND')) {
+            return str[1];
+          }
+          else if(used_slots.includes('RIGHT_HAND') && !used_slots.includes('LEFT_HAND')) {
+            return str[0];
+          }
+          else if(used_slots.includes('LEFT_HAND') && used_slots.includes('RIGHT_HAND')) {
+            return "";
+          }
+          return str.join("");
         }
         else {
           return `<button type="button" class="equip-item" hx-post="/player/equip/${item.item_id}/${item.equipment_slot}">Equip</button>`;
         }
         else {
           return `<button type="button" class="equip-item" hx-post="/player/equip/${item.item_id}/${item.equipment_slot}">Equip</button>`;
index e357835be3082955452dfd74686657b29a54e35a..0fc5e5b3afa634e62e2cf415f5b25f619f059ec6 100644 (file)
@@ -22,9 +22,9 @@ export function renderMonsterSelector(monsters: Monster[] | MonsterForFight[], a
 <div class="service-in-town"><form id="fight-selector" hx-post="/fight" hx-target="#explore">
   <input type="hidden" name="fightTrigger" value="explore">
   <select id="monsterId" name="monsterId">
 <div class="service-in-town"><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: (Monster | MonsterForFight)) => {
-      const range = [monster.level, monster.level + 3];
-      return `<option value="${monster.id}" ${monster.id === activeMonsterId ? 'selected': ''}>${monster.name} (${range[0]} - ${range[1]})</option>`;
+  ${monsters.map((monster: (any)) => {
+      const range = [monster?.minLevel, monster?.maxLevel];
+      return `<option value="${monster.id}" ${monster.id === activeMonsterId ? 'selected': ''}>${monster.name} (LVL ${range[0]} - ${range[1]})</option>`;
   }).join("\n")}
   </select> <button type="submit" class="red">Fight</button></form>
 <br><br>
   }).join("\n")}
   </select> <button type="submit" class="red">Fight</button></form>
 <br><br>
index fae88bf2f54ae71561d9c8da885d7fc330921319..c95c80dd1f4a17e3b33d7bedd6a7926cf4f0a4d5 100644 (file)
@@ -1,3 +1,5 @@
 export const FIGHT_ATTACK_DELAY = 1500;
 export const STEP_DELAY = 2000;
 export const ALERT_DISPLAY_LENGTH = 3000;
 export const FIGHT_ATTACK_DELAY = 1500;
 export const STEP_DELAY = 2000;
 export const ALERT_DISPLAY_LENGTH = 3000;
+// this is displayed as a percentage out of 100
+export const CHANCE_TO_FIGHT_SPECIAL = 100;
index d0f7b97e226977c44c3b13fd7cea8756a2545856..22f0ed9b04c81bd64af6b95fd999cfc85af4535b 100644 (file)
@@ -1,4 +1,4 @@
-import {FightTrigger, MonsterWithFaction} from "./monsters"
+import {Fight, FightTrigger, MonsterWithFaction} from "./monsters"
 import {Player} from "./player"
 
 export type FightReward = {
 import {Player} from "./player"
 
 export type FightReward = {
@@ -8,7 +8,7 @@ export type FightReward = {
 }
 
 export type FightRound = {
 }
 
 export type FightRound = {
-  monster: MonsterWithFaction,
+  monster: Fight,
   player: Player,
   fightTrigger: FightTrigger,
   winner: 'player' | 'monster' | 'in-progress',
   player: Player,
   fightTrigger: FightTrigger,
   winner: 'player' | 'monster' | 'in-progress',
index c29a4777800047d5e4cc3acfb408940352410aa3..c7f8700c86560a60baad62fb2f8ad1b19970de86 100644 (file)
@@ -7,7 +7,8 @@ export type Monster = {
   dexterity: number;
   intelligence: number;
   constitution: number;
   dexterity: number;
   intelligence: number;
   constitution: number;
-  level: number;
+  minLevel: number;
+  maxLevel: number;
   gold: number;
   exp: number;
   hp: number;
   gold: number;
   exp: number;
   hp: number;
@@ -26,11 +27,13 @@ export type MonsterForList = {
 
 export type FightTrigger = 'explore' | 'travel';
 
 
 export type FightTrigger = 'explore' | 'travel';
 
-export type Fight = Omit<Monster, 'id' | 'faction_id' | 'location_id'> & { 
-  id: string,
-  player_id: string,
-  ref_id: number
-  fight_trigger: FightTrigger
+export type Fight = Omit<Monster, 'id' | 'faction_id' | 'location_id' | 'minLevel' | 'maxLevel' | 'time_period'> & { 
+  id: string;
+  player_id: string;
+  variant: string;
+  level: number;
+  ref_id: number;
+  fight_trigger: FightTrigger;
 };
 
 export type MonsterWithFaction = Fight & {
 };
 
 export type MonsterWithFaction = Fight & {
@@ -43,6 +46,59 @@ export type MonsterForFight = {
   hp: number;
   maxHp: number;
   name: string;
   hp: number;
   maxHp: number;
   name: string;
-  level: number;
+  minLevel: number;
+  maxLevel: number;
   fight_trigger: FightTrigger;
 }
   fight_trigger: FightTrigger;
 }
+
+export type MonsterVariant = {
+  name: string;
+  display: string;
+  strength: number;
+  constitution: number;
+  dexterity: number;
+  intelligence: number;
+  exp: number;
+  gold: number;
+  maxHp: number;
+  defence: number;
+};
+
+export const MonsterVariants: MonsterVariant[] = [
+  {
+    name: 'Brute',
+    display: '{{name}} Brute',
+    strength: 1,
+    constitution: 1,
+    dexterity: 0.6,
+    intelligence: 0.2,
+    exp: 4,
+    gold: 3,
+    maxHp: 2,
+    defence: 3
+  },
+  {
+    name: 'Elder',
+    display: 'Elder {{name}}',
+    strength: 0.8,
+    constitution: 1.2,
+    dexterity: 0.6,
+    intelligence: 1.6,
+    exp: 2,
+    gold: 1,
+    maxHp: 1,
+    defence: 1
+  },
+  {
+    name: 'Skittish',
+    display: 'Skittish {{name}}',
+    strength: 0.8,
+    constitution: 1.2,
+    dexterity: 0.6,
+    intelligence: 1.6,
+    exp: 1,
+    gold: 1.2,
+    maxHp: 1,
+    defence: 0.8
+  }
+];
diff --git a/src/types/express/index.d.ts b/src/types/express/index.d.ts
new file mode 100644 (file)
index 0000000..9f12cde
--- /dev/null
@@ -0,0 +1,7 @@
+import { Player } from '../../shared/player';
+
+declare module 'express-serve-static-core' {
+  interface Request {
+    player: Player
+  }
+}
index 47b69bd289823e2a3d2545dce673f133e0622532..fa4cf87e3095b149436632e588feb6e01935365e 100644 (file)
@@ -1,17 +1,18 @@
 {
 {
-    "compilerOptions": {
-        "module": "commonjs",
-        "esModuleInterop": true,
-        "target": "es6",
-        "moduleResolution": "node",
-        "removeComments": true,
-        "preserveConstEnums": true,
-        "sourceMap": true,
-        "baseUrl": "src",
-        "outDir": "dist",
-        "emitDecoratorMetadata": true,
-        "experimentalDecorators": true,
-        "resolveJsonModule": true
-    },
-    "include": ["src/server/api.ts"]
+  "compilerOptions": {
+    "module": "commonjs",
+    "esModuleInterop": true,
+    "target": "es6",
+    "moduleResolution": "node",
+    "removeComments": true,
+    "preserveConstEnums": true,
+    "sourceMap": true,
+    "baseUrl": "src",
+    "outDir": "dist",
+    "emitDecoratorMetadata": true,
+    "experimentalDecorators": true,
+    "resolveJsonModule": true,
+    "typeRoots": ["./src/types"],
+  },
+  "include": ["src/server/api.ts"]
 }
 }