1 import {FightRound} from '../shared/fight';
2 import { clearFight, loadMonster, getMonsterList, saveFightState, loadMonsterFromFight } from './monster';
3 import { Player, expToLevel, maxHp } from '../shared/player';
4 import { clearTravelPlan } from './map';
5 import { updatePlayer } from './player';
6 import { getEquippedItems, updateAp, deleteInventoryItem } from './inventory';
7 import { EquippedItemDetails } from '../shared/equipped';
8 import { ArmourEquipmentSlot, EquipmentSlot } from '../shared/inventory';
9 import { MonsterWithFaction, MonsterForFight } from '../shared/monsters';
10 import { getPlayerSkillsAsObject, updatePlayerSkills } from './skills';
11 import { SkillID, Skills } from '../shared/skills';
12 import { AuthRequest } from './auth';
13 import { Response } from 'express';
14 import * as Alert from './views/alert';
16 export async function blockPlayerInFight(req: AuthRequest, res: Response, next: any) {
17 const fight = await loadMonsterFromFight(req.player.id);
23 res.send(Alert.ErrorAlert(`You are currently in a fight with a ${fight.name}`));
26 function exponentialExp(exp: number, monsterLevel: number, playerLevel: number): number {
29 if((monsterLevel+3) < playerLevel) {
30 finalExp = Math.floor(exp * Math.pow(Math.E, ((monsterLevel + 3) - playerLevel)/5));
32 else if(monsterLevel > (playerLevel + 3)) {
33 finalExp = Math.floor(exp * Math.pow(Math.E, ((playerLevel + 3) - monsterLevel)/5));
36 return Math.floor(finalExp);
39 export async function fightRound(player: Player, monster: MonsterWithFaction, data: {action: 'attack' | 'cast' | 'flee', target: 'head' | 'body' | 'arms' | 'legs'}) {
40 const playerSkills = await getPlayerSkillsAsObject(player.id);
41 const roundData: FightRound = {
44 winner: 'in-progress',
45 fightTrigger: monster.fight_trigger,
53 const equippedItems = await getEquippedItems(player.id);
55 // we only use this if the player successfully defeated the monster
56 // they were fighting, then we load the other monsters in this area
57 // so they can "fight again"
58 let potentialMonsters: MonsterForFight[] = [];
61 * cumulative chance of head/arms/body/legs
65 * we use the factor to decide how many decimal places
69 const monsterTarget = [0.2, 0.4, 0.9, 1];
70 const targets: ArmourEquipmentSlot[] = ['HEAD', 'CHEST', 'ARMS', 'LEGS'];
72 const rand = Math.ceil(Math.random() * factor);
73 let target: ArmourEquipmentSlot = 'CHEST';
74 monsterTarget.forEach((i, idx) => {
75 if (rand > (i * factor)) {
76 target = targets[idx] as ArmourEquipmentSlot;
89 const equipment: Map<EquipmentSlot, EquippedItemDetails> = new Map<EquipmentSlot, EquippedItemDetails>();
90 const weapons: EquippedItemDetails[] = [];
91 let anyDamageSpells: boolean = false;
92 equippedItems.forEach(item => {
93 if(item.type === 'ARMOUR') {
94 equipment.set(item.equipment_slot, item);
96 else if(item.type === 'WEAPON') {
99 else if(item.type === 'SPELL') {
100 if(item.affectedSkills.includes('destruction_magic')) {
101 anyDamageSpells = true;
106 boost.strength += item.boosts.strength;
107 boost.constitution += item.boosts.constitution;
108 boost.dexterity += item.boosts.dexterity;
109 boost.intelligence += item.boosts.intelligence;
111 if(item.type === 'SPELL' && item.affectedSkills.includes('restoration_magic')) {
112 boost.hp += item.boosts.damage;
115 boost.damage += item.boosts.damage;
119 // if you flee'd, then we want to check your dex vs. the monsters
120 // but we want to give you the item/weapon boosts you need
121 // if not then you're going to get hit.
122 if(data.action === 'flee') {
123 roundData.roundDetails.push(`You managed to escape from the ${monster.name}!`)
124 roundData.winner = 'monster';
125 await clearFight(player.id);
127 return { roundData, monsters: [], player };
130 const attackType = data.action === 'attack' ? 'physical' : 'magical';
131 const primaryStat = data.action === 'attack' ? player.strength : player.intelligence;
132 const boostStat = data.action === 'attack' ? boost.strength : boost.intelligence;
134 const playerDamage = Math.floor(((primaryStat + boostStat) * 1.3) + boost.damage);
135 const skillsUsed: Record<SkillID | any, number> = {};
136 let hpHealAfterMasteries: number = -1;
137 let playerDamageAfterMasteries: number = 0;
139 weapons.forEach(item => {
140 item.affectedSkills.forEach(id => {
141 if(id === 'restoration_magic') {
142 if(hpHealAfterMasteries < 0) {
143 hpHealAfterMasteries = 0;
145 const skill = Skills.get(id);
147 const playerSkill = playerSkills.get(id);
149 hpHealAfterMasteries += skill.effect(playerSkill);
154 const skill = Skills.get(id);
156 const playerSkill = playerSkills.get(id);
158 playerDamageAfterMasteries += playerDamage * skill.effect(playerSkill);
163 if(!skillsUsed[id]) {
170 await updatePlayerSkills(player.id, skillsUsed);
172 const playerFinalDamage = (data.action === 'cast' && !anyDamageSpells) ? 0 : Math.floor(playerDamage + playerDamageAfterMasteries);
173 const playerFinalHeal = Math.floor(boost.hp + hpHealAfterMasteries);
175 roundData.roundDetails.push(`You targeted the monsters ${data.target.toUpperCase()} with ${attackType} damage!`);
176 let armourKey: string;
177 switch(data.target) {
179 armourKey = 'armsAp';
182 armourKey = 'helmAp';
185 armourKey = 'legsAp';
188 armourKey = 'chestAp';
192 if(monster[armourKey] && monster[armourKey] > 0) {
193 monster[armourKey] -= playerFinalDamage;
195 roundData.roundDetails.push(`You dealt ${playerFinalDamage} damage to their armour`);
196 if(monster[armourKey] < 0) {
197 roundData.roundDetails.push(`You destroyed the ${monster.name}'s armour!'`);
198 roundData.roundDetails.push(`You dealt ${monster[armourKey] * -1} damage to their HP`);
199 monster.hp += monster[armourKey];
200 monster[armourKey] = 0;
204 roundData.roundDetails.push(`You hit the ${monster.name} for ${playerFinalDamage} damage.`);
205 monster.hp -= playerFinalDamage;
208 if(monster.hp <= 0) {
209 roundData.monster.hp = 0;
210 roundData.winner = 'player';
212 const expGained = exponentialExp(monster.exp, monster.level, player.level);
214 roundData.rewards.exp = expGained;
215 roundData.rewards.gold = monster.gold;
217 player.gold += monster.gold;
218 player.exp += expGained;
220 if(player.exp >= expToLevel(player.level + 1)) {
221 player.exp -= expToLevel(player.level + 1)
223 roundData.rewards.levelIncrease = true;
224 let statPointsGained = 1;
226 if(player.profession !== 'Wanderer') {
227 statPointsGained = 2;
230 player.stat_points += statPointsGained;
232 roundData.roundDetails.push(`You gained ${statPointsGained} stat points!`);
234 player.hp = maxHp(player.constitution, player.level);
236 // get the monster location if it was an EXPLORED fight
237 if(roundData.fightTrigger === 'explore') {
238 const rawMonster = await loadMonster(monster.ref_id);
239 const monsterList = await getMonsterList(rawMonster.location_id);
240 potentialMonsters = monsterList.map(monster => {
244 level: monster.level,
246 maxHp: monster.maxHp,
247 fight_trigger: 'explore'
252 await clearFight(player.id);
253 await updatePlayer(player);
254 return { roundData, monsters: potentialMonsters, player };
257 roundData.roundDetails.push(`The ${monster.name} targeted your ${target}!`);
258 const item = equipment.get(target);
261 const mitigationPercentage = item.boosts.damage_mitigation || 0;
262 const damageAfterMitigation = Math.floor(monster.strength * ((100-mitigationPercentage)/100));
264 item.currentAp -= damageAfterMitigation;
266 if(item.currentAp < 0) {
267 roundData.roundDetails.push(`Your ${item.name} amour was destroyed`);
268 roundData.roundDetails.push(`The ${monster.name} hit your HP for ${item.currentAp * -1} damage!`);
269 player.hp += item.currentAp;
271 await deleteInventoryItem(player.id, item.item_id);
274 roundData.roundDetails.push(`Your ${target} took ${damageAfterMitigation} damage!`);
275 await updateAp(player.id, item.item_id, item.currentAp, item.maxAp);
280 roundData.roundDetails.push(`The ${monster.name} hit you for ${monster.strength} damage`);
281 player.hp -= monster.strength;
284 if(playerFinalHeal > 0) {
285 player.hp += playerFinalHeal;
286 if(player.hp > maxHp(player.constitution, player.level)) {
287 player.hp = maxHp(player.constitution, player.level);
289 roundData.roundDetails.push(`You healed for ${playerFinalHeal} HP`);
292 // update the players inventory for this item!
296 roundData.winner = 'monster';
298 roundData.roundDetails.push(`You were killed by the ${monster.name}`);
300 await clearFight(player.id);
301 await updatePlayer(player);
302 await clearTravelPlan(player.id);
304 return { roundData, monsters: [], player};
307 await updatePlayer(player);
308 await saveFightState(player.id, monster);
310 return { roundData, monsters: [], player};