From c43fd65cfab4f1260674cf126c3f6e5e64057b8a Mon Sep 17 00:00:00 2001 From: xangelo Date: Thu, 24 Aug 2023 13:53:54 -0400 Subject: [PATCH] chore: separate fight round into its own file --- src/server/api.ts | 286 ++---------------------------------------- src/server/fight.ts | 296 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 304 insertions(+), 278 deletions(-) create mode 100644 src/server/fight.ts diff --git a/src/server/api.ts b/src/server/api.ts index d9f67ab..4a176dd 100644 --- a/src/server/api.ts +++ b/src/server/api.ts @@ -13,20 +13,19 @@ import { logger } from './lib/logger'; import { loadPlayer, createPlayer, updatePlayer, movePlayer } from './player'; import { random, sample } from 'lodash'; import {broadcastMessage, Message} from '../shared/message'; -import {expToLevel, maxHp, Player} from '../shared/player'; -import {clearFight, createFight, getMonsterList, getMonsterLocation, getRandomMonster, loadMonster, loadMonsterFromFight, loadMonsterWithFaction, saveFightState} from './monster'; -import {FightRound} from '../shared/fight'; -import {addInventoryItem, deleteInventoryItem, getEquippedItems, getInventory, getInventoryItem, updateAp} from './inventory'; +import {maxHp, Player} from '../shared/player'; +import {createFight, getMonsterList, getMonsterLocation, getRandomMonster, loadMonster, loadMonsterFromFight, loadMonsterWithFaction} from './monster'; +import {addInventoryItem, getEquippedItems, getInventory, getInventoryItem} from './inventory'; import { getItemFromPlayer, getItemFromShop, getPlayersItems, getShopItems, givePlayerItem, updateItemCount } from './items'; -import {FightTrigger, Monster, MonsterForFight, MonsterWithFaction} from '../shared/monsters'; +import {FightTrigger, Monster, MonsterForFight} from '../shared/monsters'; import {getShopEquipment, listShopItems } from './shopEquipment'; -import {EquippedItemDetails} from '../shared/equipped'; -import {ArmourEquipmentSlot, EquipmentSlot} from '../shared/inventory'; +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 {db} from './lib/db'; -import { getPlayerSkills, getPlayerSkillsAsObject, updatePlayerSkills } from './skills'; -import {SkillID, Skills} from '../shared/skills'; +import { getPlayerSkills} from './skills'; + +import { fightRound, blockPlayerInFight } from './fight'; import { router as healerRouter } from './locations/healer'; import { router as professionRouter } from './locations/recruiter'; @@ -116,265 +115,6 @@ io.on('connection', async socket => { socket.emit('ready'); }); -async function fightRound(player: Player, monster: MonsterWithFaction, data: {action: 'attack' | 'cast' | 'flee', target: 'head' | 'body' | 'arms' | 'legs'}) { - const playerSkills = await getPlayerSkillsAsObject(player.id); - const roundData: FightRound = { - monster, - player, - winner: 'in-progress', - fightTrigger: monster.fight_trigger, - roundDetails: [], - rewards: { - exp: 0, - gold: 0, - levelIncrease: false - } - }; - const equippedItems = await getEquippedItems(player.id); - - // we only use this if the player successfully defeated the monster - // they were fighting, then we load the other monsters in this area - // so they can "fight again" - let potentialMonsters: MonsterForFight[] = []; - - /* - * cumulative chance of head/arms/body/legs - * 0 -> 0.2 = head - * 0.21 -> 0.4 = arms - * - * we use the factor to decide how many decimal places - * we care about - */ - const factor = 100; - const monsterTarget = [0.2, 0.4, 0.9, 1]; - const targets: ArmourEquipmentSlot[] = ['HEAD', 'CHEST', 'ARMS', 'LEGS']; - // calc weighted - const rand = Math.ceil(Math.random() * factor); - let target: ArmourEquipmentSlot = 'CHEST'; - monsterTarget.forEach((i, idx) => { - if (rand > (i * factor)) { - target = targets[idx] as ArmourEquipmentSlot; - } - }); - - const boost = { - strength: 0, - constitution: 0, - dexterity: 0, - intelligence: 0, - damage: 0, - hp: 0, - }; - - const equipment: Map = new Map(); - const weapons: EquippedItemDetails[] = []; - let anyDamageSpells: boolean = false; - equippedItems.forEach(item => { - if(item.type === 'ARMOUR') { - equipment.set(item.equipment_slot, item); - } - else if(item.type === 'WEAPON') { - weapons.push(item); - } - else if(item.type === 'SPELL') { - if(item.affectedSkills.includes('destruction_magic')) { - anyDamageSpells = true; - } - weapons.push(item); - } - - boost.strength += item.boosts.strength; - boost.constitution += item.boosts.constitution; - boost.dexterity += item.boosts.dexterity; - boost.intelligence += item.boosts.intelligence; - - if(item.type === 'SPELL' && item.affectedSkills.includes('restoration_magic')) { - boost.hp += item.boosts.damage; - } - else { - boost.damage += item.boosts.damage; - } - }); - - // if you flee'd, then we want to check your dex vs. the monsters - // but we want to give you the item/weapon boosts you need - // if not then you're going to get hit. - if(data.action === 'flee') { - roundData.roundDetails.push(`You managed to escape from the ${monster.name}!`) - roundData.winner = 'monster'; - await clearFight(player.id); - - return { roundData, monsters: [], player }; - } - - const attackType = data.action === 'attack' ? 'physical' : 'magical'; - const primaryStat = data.action === 'attack' ? player.strength : player.intelligence; - const boostStat = data.action === 'attack' ? boost.strength : boost.intelligence; - - const playerDamage = Math.floor(((primaryStat + boostStat) * 1.3) + boost.damage); - const skillsUsed: Record = {}; - let hpHealAfterMasteries: number = -1; - let playerDamageAfterMasteries: number = 0; - // apply masteries! - weapons.forEach(item => { - item.affectedSkills.forEach(id => { - if(id === 'restoration_magic') { - if(hpHealAfterMasteries < 0) { - hpHealAfterMasteries = 0; - } - hpHealAfterMasteries += Skills.get(id).effect(playerSkills.get(id)); - } - else { - playerDamageAfterMasteries += playerDamage * Skills.get(id).effect(playerSkills.get(id)); - } - - if(!skillsUsed[id]) { - skillsUsed[id] = 0; - } - skillsUsed[id]++; - }); - }); - - await updatePlayerSkills(player.id, skillsUsed); - - const playerFinalDamage = (data.action === 'cast' && !anyDamageSpells) ? 0 : Math.floor(playerDamage + playerDamageAfterMasteries); - const playerFinalHeal = Math.floor(boost.hp + hpHealAfterMasteries); - - roundData.roundDetails.push(`You targeted the monsters ${data.target.toUpperCase()} with ${attackType} damage!`); - let armourKey: string; - switch(data.target) { - case 'arms': - armourKey = 'armsAp'; - break; - case 'head': - armourKey = 'helmAp'; - break; - case 'legs': - armourKey = 'legsAp'; - break; - case 'body': - armourKey = 'chestAp'; - break; - } - - if(monster[armourKey] && monster[armourKey] > 0) { - monster[armourKey] -= playerFinalDamage; - - roundData.roundDetails.push(`You dealt ${playerFinalDamage} damage to their armour`); - if(monster[armourKey] < 0) { - roundData.roundDetails.push(`You destroyed the ${monster.name}'s armour!'`); - roundData.roundDetails.push(`You dealt ${monster[armourKey] * -1} damage to their HP`); - monster.hp += monster[armourKey]; - monster[armourKey] = 0; - } - } - else { - roundData.roundDetails.push(`You hit the ${monster.name} for ${playerFinalDamage} damage.`); - monster.hp -= playerFinalDamage; - } - - if(monster.hp <= 0) { - roundData.monster.hp = 0; - roundData.winner = 'player'; - - roundData.rewards.exp = monster.exp; - roundData.rewards.gold = monster.gold; - - player.gold += monster.gold; - player.exp += monster.exp; - - if(player.exp >= expToLevel(player.level + 1)) { - player.exp -= expToLevel(player.level + 1) - player.level++; - roundData.rewards.levelIncrease = true; - let statPointsGained = 1; - - if(player.profession !== 'Wanderer') { - statPointsGained = 2; - } - - player.stat_points += statPointsGained; - - roundData.roundDetails.push(`You gained ${statPointsGained} stat points!`); - - player.hp = maxHp(player.constitution, player.level); - } - // get the monster location if it was an EXPLORED fight - if(roundData.fightTrigger === 'explore') { - const rawMonster = await loadMonster(monster.ref_id); - const monsterList = await getMonsterList(rawMonster.location_id); - potentialMonsters = monsterList.map(monster => { - return { - id: monster.id, - name: monster.name, - level: monster.level, - hp: monster.hp, - maxHp: monster.maxHp, - fight_trigger: 'explore' - } - }); - } - - await clearFight(player.id); - await updatePlayer(player); - return { roundData, monsters: potentialMonsters, player }; - } - - roundData.roundDetails.push(`The ${monster.name} targeted your ${target}!`); - if(equipment.has(target)) { - const item = equipment.get(target); - // apply mitigation! - const mitigationPercentage = item.boosts.damage_mitigation || 0; - const damageAfterMitigation = Math.floor(monster.strength * ((100-mitigationPercentage)/100)); - - item.currentAp -= damageAfterMitigation; - - if(item.currentAp < 0) { - roundData.roundDetails.push(`Your ${item.name} amour was destroyed`); - roundData.roundDetails.push(`The ${monster.name} hit your HP for ${item.currentAp * -1} damage!`); - player.hp += item.currentAp; - item.currentAp = 0; - await deleteInventoryItem(player.id, item.item_id); - } - else { - roundData.roundDetails.push(`Your ${target} took ${damageAfterMitigation} damage!`); - await updateAp(player.id, item.item_id, item.currentAp, item.maxAp); - } - - } - else { - roundData.roundDetails.push(`The ${monster.name} hit you for ${monster.strength} damage`); - player.hp -= monster.strength; - } - - if(playerFinalHeal > 0) { - player.hp += playerFinalHeal; - if(player.hp > maxHp(player.constitution, player.level)) { - player.hp = maxHp(player.constitution, player.level); - } - roundData.roundDetails.push(`You healed for ${playerFinalHeal} HP`); - } - - // update the players inventory for this item! - - if(player.hp <= 0) { - player.hp = 0; - roundData.winner = 'monster'; - - roundData.roundDetails.push(`You were killed by the ${monster.name}`); - - await clearFight(player.id); - await updatePlayer(player); - await clearTravelPlan(player.id); - - return { roundData, monsters: [], player}; - } - - await updatePlayer(player); - await saveFightState(player.id, monster); - - return { roundData, monsters: [], player}; -}; app.use(healerRouter); app.use(professionRouter); @@ -538,16 +278,6 @@ app.post('/player/unequip/:item_id', authEndpoint, blockPlayerInFight, async (re res.send(renderInventoryPage(inventory, items, item.type) + renderPlayerBar(req.player, inventory)); }); -async function blockPlayerInFight(req: AuthRequest, res: Response, next) { - const fight = await loadMonsterFromFight(req.player.id); - if(!fight) { - next(); - return; - } - - res.send(Alert.ErrorAlert(`You are currently in a fight with a ${fight.name}`)); -} - app.get('/player/explore', authEndpoint, async (req: AuthRequest, res: Response) => { const fight = await loadMonsterFromFight(req.player.id); const travelPlan = await getTravelPlan(req.player.id); diff --git a/src/server/fight.ts b/src/server/fight.ts new file mode 100644 index 0000000..9b377e6 --- /dev/null +++ b/src/server/fight.ts @@ -0,0 +1,296 @@ +import {FightRound} from '../shared/fight'; +import { clearFight, loadMonster, getMonsterList, saveFightState, loadMonsterFromFight } from './monster'; +import { Player, expToLevel, maxHp } from '../shared/player'; +import { clearTravelPlan } from './map'; +import { updatePlayer } from './player'; +import { getEquippedItems, updateAp, deleteInventoryItem } from './inventory'; +import { EquippedItemDetails } from '../shared/equipped'; +import { ArmourEquipmentSlot, EquipmentSlot } from '../shared/inventory'; +import { MonsterWithFaction, MonsterForFight } from '../shared/monsters'; +import { getPlayerSkillsAsObject, updatePlayerSkills } from './skills'; +import { SkillID, Skills } from '../shared/skills'; +import { AuthRequest } from './auth'; +import { Response } from 'express'; +import * as Alert from './views/alert'; + +export async function blockPlayerInFight(req: AuthRequest, res: Response, next: any) { + const fight = await loadMonsterFromFight(req.player.id); + if(!fight) { + next(); + return; + } + + res.send(Alert.ErrorAlert(`You are currently in a fight with a ${fight.name}`)); +} + +export async function fightRound(player: Player, monster: MonsterWithFaction, data: {action: 'attack' | 'cast' | 'flee', target: 'head' | 'body' | 'arms' | 'legs'}) { + const playerSkills = await getPlayerSkillsAsObject(player.id); + const roundData: FightRound = { + monster, + player, + winner: 'in-progress', + fightTrigger: monster.fight_trigger, + roundDetails: [], + rewards: { + exp: 0, + gold: 0, + levelIncrease: false + } + }; + const equippedItems = await getEquippedItems(player.id); + + // we only use this if the player successfully defeated the monster + // they were fighting, then we load the other monsters in this area + // so they can "fight again" + let potentialMonsters: MonsterForFight[] = []; + + /* + * cumulative chance of head/arms/body/legs + * 0 -> 0.2 = head + * 0.21 -> 0.4 = arms + * + * we use the factor to decide how many decimal places + * we care about + */ + const factor = 100; + const monsterTarget = [0.2, 0.4, 0.9, 1]; + const targets: ArmourEquipmentSlot[] = ['HEAD', 'CHEST', 'ARMS', 'LEGS']; + // calc weighted + const rand = Math.ceil(Math.random() * factor); + let target: ArmourEquipmentSlot = 'CHEST'; + monsterTarget.forEach((i, idx) => { + if (rand > (i * factor)) { + target = targets[idx] as ArmourEquipmentSlot; + } + }); + + const boost = { + strength: 0, + constitution: 0, + dexterity: 0, + intelligence: 0, + damage: 0, + hp: 0, + }; + + const equipment: Map = new Map(); + const weapons: EquippedItemDetails[] = []; + let anyDamageSpells: boolean = false; + equippedItems.forEach(item => { + if(item.type === 'ARMOUR') { + equipment.set(item.equipment_slot, item); + } + else if(item.type === 'WEAPON') { + weapons.push(item); + } + else if(item.type === 'SPELL') { + if(item.affectedSkills.includes('destruction_magic')) { + anyDamageSpells = true; + } + weapons.push(item); + } + + boost.strength += item.boosts.strength; + boost.constitution += item.boosts.constitution; + boost.dexterity += item.boosts.dexterity; + boost.intelligence += item.boosts.intelligence; + + if(item.type === 'SPELL' && item.affectedSkills.includes('restoration_magic')) { + boost.hp += item.boosts.damage; + } + else { + boost.damage += item.boosts.damage; + } + }); + + // if you flee'd, then we want to check your dex vs. the monsters + // but we want to give you the item/weapon boosts you need + // if not then you're going to get hit. + if(data.action === 'flee') { + roundData.roundDetails.push(`You managed to escape from the ${monster.name}!`) + roundData.winner = 'monster'; + await clearFight(player.id); + + return { roundData, monsters: [], player }; + } + + const attackType = data.action === 'attack' ? 'physical' : 'magical'; + const primaryStat = data.action === 'attack' ? player.strength : player.intelligence; + const boostStat = data.action === 'attack' ? boost.strength : boost.intelligence; + + const playerDamage = Math.floor(((primaryStat + boostStat) * 1.3) + boost.damage); + const skillsUsed: Record = {}; + let hpHealAfterMasteries: number = -1; + let playerDamageAfterMasteries: number = 0; + // apply masteries! + weapons.forEach(item => { + item.affectedSkills.forEach(id => { + if(id === 'restoration_magic') { + if(hpHealAfterMasteries < 0) { + hpHealAfterMasteries = 0; + } + const skill = Skills.get(id); + if(skill) { + const playerSkill = playerSkills.get(id); + if(playerSkill) { + hpHealAfterMasteries += skill.effect(playerSkill); + } + } + } + else { + const skill = Skills.get(id); + if(skill) { + const playerSkill = playerSkills.get(id); + if(playerSkill) { + playerDamageAfterMasteries += playerDamage * skill.effect(playerSkill); + } + } + } + + if(!skillsUsed[id]) { + skillsUsed[id] = 0; + } + skillsUsed[id]++; + }); + }); + + await updatePlayerSkills(player.id, skillsUsed); + + const playerFinalDamage = (data.action === 'cast' && !anyDamageSpells) ? 0 : Math.floor(playerDamage + playerDamageAfterMasteries); + const playerFinalHeal = Math.floor(boost.hp + hpHealAfterMasteries); + + roundData.roundDetails.push(`You targeted the monsters ${data.target.toUpperCase()} with ${attackType} damage!`); + let armourKey: string; + switch(data.target) { + case 'arms': + armourKey = 'armsAp'; + break; + case 'head': + armourKey = 'helmAp'; + break; + case 'legs': + armourKey = 'legsAp'; + break; + case 'body': + armourKey = 'chestAp'; + break; + } + + if(monster[armourKey] && monster[armourKey] > 0) { + monster[armourKey] -= playerFinalDamage; + + roundData.roundDetails.push(`You dealt ${playerFinalDamage} damage to their armour`); + if(monster[armourKey] < 0) { + roundData.roundDetails.push(`You destroyed the ${monster.name}'s armour!'`); + roundData.roundDetails.push(`You dealt ${monster[armourKey] * -1} damage to their HP`); + monster.hp += monster[armourKey]; + monster[armourKey] = 0; + } + } + else { + roundData.roundDetails.push(`You hit the ${monster.name} for ${playerFinalDamage} damage.`); + monster.hp -= playerFinalDamage; + } + + if(monster.hp <= 0) { + roundData.monster.hp = 0; + roundData.winner = 'player'; + + roundData.rewards.exp = monster.exp; + roundData.rewards.gold = monster.gold; + + player.gold += monster.gold; + player.exp += monster.exp; + + if(player.exp >= expToLevel(player.level + 1)) { + player.exp -= expToLevel(player.level + 1) + player.level++; + roundData.rewards.levelIncrease = true; + let statPointsGained = 1; + + if(player.profession !== 'Wanderer') { + statPointsGained = 2; + } + + player.stat_points += statPointsGained; + + roundData.roundDetails.push(`You gained ${statPointsGained} stat points!`); + + player.hp = maxHp(player.constitution, player.level); + } + // get the monster location if it was an EXPLORED fight + if(roundData.fightTrigger === 'explore') { + const rawMonster = await loadMonster(monster.ref_id); + const monsterList = await getMonsterList(rawMonster.location_id); + potentialMonsters = monsterList.map(monster => { + return { + id: monster.id, + name: monster.name, + level: monster.level, + hp: monster.hp, + maxHp: monster.maxHp, + fight_trigger: 'explore' + } + }); + } + + await clearFight(player.id); + await updatePlayer(player); + return { roundData, monsters: potentialMonsters, player }; + } + + roundData.roundDetails.push(`The ${monster.name} targeted your ${target}!`); + const item = equipment.get(target); + if(item) { + // apply mitigation! + const mitigationPercentage = item.boosts.damage_mitigation || 0; + const damageAfterMitigation = Math.floor(monster.strength * ((100-mitigationPercentage)/100)); + + item.currentAp -= damageAfterMitigation; + + if(item.currentAp < 0) { + roundData.roundDetails.push(`Your ${item.name} amour was destroyed`); + roundData.roundDetails.push(`The ${monster.name} hit your HP for ${item.currentAp * -1} damage!`); + player.hp += item.currentAp; + item.currentAp = 0; + await deleteInventoryItem(player.id, item.item_id); + } + else { + roundData.roundDetails.push(`Your ${target} took ${damageAfterMitigation} damage!`); + await updateAp(player.id, item.item_id, item.currentAp, item.maxAp); + } + + } + else { + roundData.roundDetails.push(`The ${monster.name} hit you for ${monster.strength} damage`); + player.hp -= monster.strength; + } + + if(playerFinalHeal > 0) { + player.hp += playerFinalHeal; + if(player.hp > maxHp(player.constitution, player.level)) { + player.hp = maxHp(player.constitution, player.level); + } + roundData.roundDetails.push(`You healed for ${playerFinalHeal} HP`); + } + + // update the players inventory for this item! + + if(player.hp <= 0) { + player.hp = 0; + roundData.winner = 'monster'; + + roundData.roundDetails.push(`You were killed by the ${monster.name}`); + + await clearFight(player.id); + await updatePlayer(player); + await clearTravelPlan(player.id); + + return { roundData, monsters: [], player}; + } + + await updatePlayer(player); + await saveFightState(player.id, monster); + + return { roundData, monsters: [], player}; +}; -- 2.25.1