--- /dev/null
+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);
+ });
+}
+
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,
- 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,
return {
id: monster.id,
name: monster.name,
- level: monster.level,
+ level: monster.minLevel,
hp: monster.hp,
maxHp: monster.maxHp,
fight_trigger: 'explore'
import { Fight, Monster, MonsterWithFaction, MonsterForList, FightTrigger } from '../shared/monsters';
import { TimePeriod, TimeManager } from '../shared/time';
import { LocationWithCity } from 'shared/map';
+import { max, random } from 'lodash';
const time = new TimeManager();
.where({ location_id })
.whereIn('time_period', timePeriod)
.from<Monster>('monsters')
- .orderBy('level');
+ .orderBy('minLevel');
return res;
}
}
export async function createFight(playerId: string, monster: Monster, fightTrigger: FightTrigger): Promise<Fight> {
+ const chosenLevel = random(monster.minLevel, monster.maxLevel);
+ // 30% boost per level difference
+ const modifier = Math.pow(Math.E, (chosenLevel - monster.minLevel)/monster.maxLevel);
+
const res = await db('fight').insert({
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,
+ strength: Math.floor(monster.strength * modifier),
+ constitution: Math.floor(monster.constitution * modifier),
+ dexterity: Math.floor(monster.dexterity * modifier),
+ intelligence: Math.floor(monster.intelligence * modifier),
+ exp: Math.floor(monster.exp * modifier),
+ level: chosenLevel,
+ gold: Math.floor(monster.gold * modifier),
+ hp: Math.floor(monster.hp * modifier),
+ defence: Math.floor(monster.defence * modifier),
+ maxHp: Math.floor(monster.maxHp * modifier),
ref_id: monster.id,
fight_trigger: fightTrigger
}).returning<Fight[]>('*');
<img id="avatar" src="https://via.placeholder.com/64x64">
</div>
<div id="defender-stat-bars">
- <div id="defender-name">${monster.name}</div>
+ <div id="defender-name">${monster.name}, 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>
<img id="avatar" src="https://via.placeholder.com/64x64">
</div>
<div id="defender-stat-bars">
- <div id="defender-name">${monster.name}</div>
+ <div id="defender-name">${monster.name}, 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 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.level, monster?.maxLevel ?? monster.level + 3];
+ 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>
dexterity: number;
intelligence: number;
constitution: number;
- level: number;
+ minLevel: number;
+ maxLevel: number;
gold: number;
exp: number;
hp: number;
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'> & {
+ id: string;
+ player_id: string;
+ level: number;
+ ref_id: number;
+ fight_trigger: FightTrigger;
};
export type MonsterWithFaction = Fight & {