1 import {FightRound} from '../shared/fight';
2 import { clearFight, loadMonster, getMonsterList, saveFightState, loadMonsterFromFight } from './monster';
3 import { Player, expToLevel, maxHp, totalDefence, maxVigor } from '../shared/player';
4 import { clearTravelPlan } from './map';
5 import { updatePlayer } from './player';
6 import { getEquippedItems, updateAp } from './inventory';
7 import { EquippedItemDetails } from '../shared/equipped';
8 import { EquipmentSlot } from '../shared/inventory';
9 import { MonsterWithFaction, MonsterForFight, Fight } from '../shared/monsters';
10 import { getPlayerSkillsAsObject, updatePlayerSkills } from './skills';
11 import { SkillID, Skills } from '../shared/skills';
12 import { Request, Response } from 'express';
13 import * as Alert from './views/alert';
14 import { addEvent } from './events';
16 export async function blockPlayerInFight(req: Request, 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: Fight, data: {action: 'attack' | 'cast' | 'flee'}) {
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 defence: totalDefence(equippedItems, player),
70 const equipment: Map<EquipmentSlot, EquippedItemDetails> = new Map<EquipmentSlot, EquippedItemDetails>();
71 const weapons: EquippedItemDetails[] = [];
72 let anyDamageSpells: boolean = false;
73 equippedItems.forEach(item => {
74 if(item.type === 'ARMOUR') {
75 equipment.set(item.equipment_slot, item);
77 else if(item.type === 'WEAPON') {
80 else if(item.type === 'SPELL') {
81 if(item.affectedSkills.includes('destruction_magic')) {
82 anyDamageSpells = true;
87 boost.strength += item.boosts.strength;
88 boost.constitution += item.boosts.constitution;
89 boost.dexterity += item.boosts.dexterity;
90 boost.intelligence += item.boosts.intelligence;
92 if(item.type === 'SPELL' && item.affectedSkills.includes('restoration_magic')) {
93 boost.hp += item.boosts.damage;
96 boost.damage += item.boosts.damage;
100 // @TODO implement flee based on dex + vigor
101 if(data.action === 'flee') {
102 roundData.roundDetails.push(`You managed to escape from the ${monster.name}!`)
103 roundData.winner = 'monster';
104 await clearFight(player.id);
106 return { roundData, monsters: [], player };
109 const attackType = data.action === 'attack' ? 'physical' : 'magical';
110 const primaryStat = attackType === 'physical' ? player.strength : player.intelligence;
111 const boostStat = attackType === 'physical' ? boost.strength : boost.intelligence;
113 const playerDamage = Math.floor(((primaryStat + boostStat) * 1.3) + boost.damage);
114 const skillsUsed: Record<SkillID | any, number> = {};
115 let hpHealAfterMasteries: number = -1;
116 let playerDamageAfterMasteries: number = 0;
118 weapons.forEach(item => {
119 item.affectedSkills.forEach(id => {
120 if(id === 'restoration_magic') {
121 if(hpHealAfterMasteries < 0) {
122 hpHealAfterMasteries = 0;
124 const skill = Skills.get(id);
126 const playerSkill = playerSkills.get(id);
128 hpHealAfterMasteries += skill.effect(playerSkill);
133 const skill = Skills.get(id);
135 const playerSkill = playerSkills.get(id);
137 playerDamageAfterMasteries += playerDamage * skill.effect(playerSkill);
142 if(!skillsUsed[id]) {
149 await updatePlayerSkills(player.id, playerSkills, skillsUsed);
151 const playerFinalDamage = (data.action === 'cast' && !anyDamageSpells) ? 0 : Math.floor(playerDamage + playerDamageAfterMasteries);
152 const playerFinalHeal = Math.floor(boost.hp + hpHealAfterMasteries);
154 let monsterTakesDamage = playerFinalDamage - monster.defence;
155 if(monsterTakesDamage < 0) {
156 monsterTakesDamage = 0;
158 roundData.roundDetails.push(`You dealt ${monsterTakesDamage} damage to the ${monster.name}!`);
160 monster.hp -= monsterTakesDamage;
162 if(monster.hp <= 0) {
163 roundData.monster.hp = 0;
164 roundData.winner = 'player';
166 addEvent('MONSTER_KILLED', player.id, {
167 monster_id: roundData.monster.ref_id,
168 monster_name: roundData.monster.name,
169 level: roundData.monster.level,
170 fightTrigger: roundData.monster.fight_trigger
173 const expGained = exponentialExp(monster.exp, monster.level, player.level);
175 roundData.rewards.exp = expGained;
176 roundData.rewards.gold = monster.gold;
178 player.gold += monster.gold;
179 player.exp += expGained;
181 if(player.exp >= expToLevel(player.level + 1)) {
182 player.exp -= expToLevel(player.level + 1)
184 addEvent('LEVEL_UP', player.id, {
185 from_level: player.level-1,
186 to_level: player.level
188 roundData.rewards.levelIncrease = true;
189 let statPointsGained = 1;
191 if(player.profession !== 'Wanderer') {
192 statPointsGained = 2;
195 player.stat_points += statPointsGained;
197 roundData.roundDetails.push(`You gained ${statPointsGained} stat points!`);
199 player.hp = maxHp(player.constitution, player.level);
200 player.vigor = maxVigor(player.constitution, player.level);
202 // get the monster location if it was an EXPLORED fight
203 if(roundData.fightTrigger === 'explore') {
204 const rawMonster = await loadMonster(monster.ref_id);
205 const monsterList = await getMonsterList(rawMonster.location_id);
206 potentialMonsters = monsterList.map(monster => {
210 minLevel: monster.minLevel,
211 maxLevel: monster.maxLevel,
213 maxHp: monster.maxHp,
214 fight_trigger: 'explore'
220 if(player.vigor < 0) {
224 const unequippedItems = await updateAp(player.id, 1, equippedItems.map(i => i.item_id));
225 await clearFight(player.id);
226 await updatePlayer(player);
228 if(unequippedItems.length) {
229 unequippedItems.forEach(i => {
230 roundData.roundDetails.push(`Your ${i.name} was too damaged and was unequipped!`);
234 return { roundData, monsters: potentialMonsters, player };
237 let monsterDamage = (monster.strength*2) - boost.defence;
238 if(monsterDamage < 0) {
242 roundData.roundDetails.push(`The ${monster.name} hit you for ${monsterDamage} damage`);
243 player.hp -= monsterDamage;
245 if(playerFinalHeal > 0) {
246 player.hp += playerFinalHeal;
247 if(player.hp > maxHp(player.constitution, player.level)) {
248 player.hp = maxHp(player.constitution, player.level);
250 roundData.roundDetails.push(`You healed for ${playerFinalHeal} HP`);
256 roundData.winner = 'monster';
258 roundData.roundDetails.push(`You were killed by the ${monster.name}`);
260 await clearFight(player.id);
261 const unequippedItems = await updateAp(player.id, 5, equippedItems.map(i => i.item_id));
262 await updatePlayer(player);
263 await clearTravelPlan(player.id);
265 if(unequippedItems.length) {
266 unequippedItems.forEach(i => {
267 roundData.roundDetails.push(`Your ${i.name} was too damaged and was unequipped!`);
271 return { roundData, monsters: [], player};
274 await updatePlayer(player);
275 await saveFightState(player.id, monster);
277 return { roundData, monsters: [], player};