From bf54589069f35de576c02b6ce3ee6a93fd51a359 Mon Sep 17 00:00:00 2001 From: Aelita4 Date: Sun, 22 Dec 2024 14:40:50 +0100 Subject: [PATCH] Add battle support --- src/lib/classes/managers/FleetManager.ts | 151 +++++++++++++++++++++-- 1 file changed, 140 insertions(+), 11 deletions(-) diff --git a/src/lib/classes/managers/FleetManager.ts b/src/lib/classes/managers/FleetManager.ts index 731e0a2..e122292 100644 --- a/src/lib/classes/managers/FleetManager.ts +++ b/src/lib/classes/managers/FleetManager.ts @@ -8,6 +8,7 @@ import { sendMail } from "../../db/mails"; import { Sector } from "./LocationManager"; import { getRandomInRange, weightedRandom } from "../../utils/math"; import { getAllShips } from "../../db/ships"; +import FleetShip from "../FleetShip"; export type Fleet = { id: ObjectId, @@ -21,6 +22,13 @@ export type Fleet = { cargo: Array<{ id: string, amount: number }> } +export type BattleFleet = { + id: string, + hitpoints: number, + attack: number, + defense: number +} + export default class FleetManager { data: Fleet; @@ -94,7 +102,7 @@ export default class FleetManager { } else { switch(this.data.mission) { case 'ATTACK': - return false; + return await this.battleResults([{ id: 'fighter', amount: 100 }]); case 'TRANSPORT': await (this.data.destination as Planet | SystemManager).resources.updateAmount(this.data.cargo); await (this.data.destination as Planet | SystemManager).resources.sync(); @@ -146,16 +154,134 @@ export default class FleetManager { await this.sync(); } - private async sendMail(description: string) { + private async sendMail(title: string, description: string) { await sendMail( null, this.data.source instanceof SystemManager ? this.data.source.data.ownedBy.id : this.data.source.system.data.ownedBy.id, this.data.arrivalTime, - "Expedition Results", + title, description ); } + async battleResults(enemyFleet: { id: string, amount: number }[]) { + const allShips = await getAllShips(); + + const playerStats = this.data.ships.reduce((acc, ship) => { + const dbShip = allShips.find(s => s.id === ship.id); + if(!dbShip) return acc; + acc.attack += dbShip.structure.attack * ship.amount; + acc.defense += dbShip.structure.defense * ship.amount; + acc.hitpoints += dbShip.structure.hitpoints * ship.amount; + return acc; + }, { attack: 0, defense: 0, hitpoints: 0 }); + + const enemyStats = enemyFleet.reduce((acc, ship) => { + const dbShip = allShips.find(s => s.id === ship.id); + if(!dbShip) return acc; + acc.attack += dbShip.structure.attack * ship.amount; + acc.defense += dbShip.structure.defense * ship.amount; + acc.hitpoints += dbShip.structure.hitpoints * ship.amount; + return acc; + }, { attack: 0, defense: 0, hitpoints: 0 }); + + const playerShipsStructure: BattleFleet[] = []; + for(const playerShip of this.data.ships) { + for(let i = 0; i < playerShip.amount; i++) playerShipsStructure.push({ + id: playerShip.id, + hitpoints: allShips.find(s => s.id === playerShip.id)?.structure.hitpoints ?? 0, + attack: allShips.find(s => s.id === playerShip.id)?.structure.attack ?? 0, + defense: allShips.find(s => s.id === playerShip.id)?.structure.defense ?? 0 + }); + } + + const enemyShipsStructure: BattleFleet[] = []; + for(const enemyShip of enemyFleet) { + for(let i = 0; i < enemyShip.amount; i++) enemyShipsStructure.push({ + id: enemyShip.id, + hitpoints: allShips.find(s => s.id === enemyShip.id)?.structure.hitpoints ?? 0, + attack: allShips.find(s => s.id === enemyShip.id)?.structure.attack ?? 0, + defense: allShips.find(s => s.id === enemyShip.id)?.structure.defense ?? 0 + }); + } + + roundLoop: for(let i = 0; i < 3; i++) { + for(const playerShip of playerShipsStructure) { + const enemyShip = enemyShipsStructure[Math.floor(Math.random() * enemyShipsStructure.length)]; + + if(!enemyShip) break roundLoop; + const typeCount = playerShipsStructure.filter(s => s.id === playerShip.id).length; + const additionalShipAttack = typeCount > 1 ? Math.floor(Math.random() * typeCount) * playerShip.attack : 0; + const playerDamage = Math.max(0, (playerShip.attack + additionalShipAttack) - enemyShip.defense); + enemyShip.hitpoints -= playerDamage; + } + + for(const enemyShip of enemyShipsStructure) { + const playerShip = playerShipsStructure[Math.floor(Math.random() * playerShipsStructure.length)]; + + if(!playerShip) break roundLoop; + const typeCount = enemyShipsStructure.filter(s => s.id === enemyShip.id).length; + const additionalShipAttack = typeCount > 1 ? Math.floor(Math.random() * typeCount) * enemyShip.attack : 0; + const enemyDamage = Math.max(0, (enemyShip.attack + additionalShipAttack) - playerShip.defense); + playerShip.hitpoints -= enemyDamage; + } + + playerShipsStructure.forEach((ship, index) => { + if(ship.hitpoints <= 0) playerShipsStructure.splice(index, 1); + }); + + enemyShipsStructure.forEach((ship, index) => { + if(ship.hitpoints <= 0) enemyShipsStructure.splice(index, 1); + }); + } + + const playerBalance = playerStats.defense - enemyStats.attack; + const enemyBalance = enemyStats.defense - playerStats.attack; + + const playerShipsLeft = playerShipsStructure.reduce((acc, ship) => { acc[ship.id] = (acc[ship.id] ?? 0) + 1; return acc }, {} as { [key: string]: number }); + const enemyShipsLeft = enemyShipsStructure.reduce((acc, ship) => { acc[ship.id] = (acc[ship.id] ?? 0) + 1; return acc }, {} as { [key: string]: number }); + + const resourcesStolen: { id: string, amount: number }[] = []; + + const previousShips = JSON.parse(JSON.stringify(this.data.ships)) as Array<{ id: string, amount: number }>; + if(playerShipsStructure.length > 0) { + this.data.ships = Object.keys(playerShipsLeft).map(id => { return { id, amount: playerShipsLeft[id] } }); + if(playerBalance > enemyBalance) { + const enemyResources = await (this.data.destination as Planet | SystemManager).resources; + await enemyResources.calculateCurrentAvailableResources(); + let cargoSpaceFree = this.data.ships.reduce((acc, ship) => { + const dbShip = allShips.find(s => s.id === ship.id); + if(!dbShip) return acc; + return acc + dbShip.capacity.solid * ship.amount; + }, 0); + + for(const res of enemyResources.resources) { + if(cargoSpaceFree <= 0) break; + const amount = Math.min(Math.floor(Math.random() * res.amount), cargoSpaceFree); + cargoSpaceFree -= amount; + this.data.cargo.push({ id: res.id, amount }); + resourcesStolen.push({ id: res.id, amount }); + enemyResources.setAmount([{ id: res.id, amount: res.amount - amount }]); + }; + } + + await this.initiateReturn(); + } else this.data.ships = []; + + await this.sendMail( + `Battle Results (${playerBalance > enemyBalance ? "Victory" : playerBalance < enemyBalance ? "Defeat" : "Draw"})`, + `Player ships:\n${previousShips.map(ship => `${ship.amount} ${ship.id}`).join(', ')}\n + Enemy ships:\n${enemyFleet.map(ship => `${ship.amount} ${ship.id}`).join(', ')}\n\n + Player stats: ${playerStats.hitpoints} HP, ${playerStats.attack} ATK, ${playerStats.defense} DEF\n + Enemy stats: ${enemyStats.hitpoints} HP, ${enemyStats.attack} ATK, ${enemyStats.defense} DEF\n\n + Player ships left:\n${Object.keys(playerShipsLeft).map(key => `${key} - ${playerShipsLeft[key]}\n`)}\n + Enemy ships left:\n${Object.keys(enemyShipsLeft).map(key => `${key} - ${enemyShipsLeft[key]}\n`)}\n + ${playerBalance > enemyBalance ? `Resources stolen:\n${resourcesStolen.map(res => `${res.id} - ${res.amount}\n`)}` : ""}` + ); + + return !(playerShipsStructure.length > 0); + } + async expeditionResults() { const expeditionRandom = Math.random(); //TODO: make use of "expedition" from DBSector @@ -169,7 +295,7 @@ export default class FleetManager { if(expeditionRandom < 0.02) { // 2% chance; lost all ships, black hole this.data.ships = []; - await this.sendMail(`Your expedition to ${(this.data.destination as Sector).name} sector encountered a black hole. All ships were lost.`); + await this.sendMail("Expedition Results", `Your expedition to ${(this.data.destination as Sector).name} sector encountered a black hole. All ships were lost.`); return; } @@ -184,7 +310,7 @@ export default class FleetManager { else ship.amount += s.amount; } - await this.sendMail(`Your expedition to ${(this.data.destination as Sector).name} sector encountered abandoned shipyard. Following ships were added to the fleet:\n${ships.map(s => `${s.id} - ${s.amount}`).join(', ')}`); + await this.sendMail("Expedition Results", `Your expedition to ${(this.data.destination as Sector).name} sector encountered abandoned shipyard. Following ships were added to the fleet:\n${ships.map(s => `${s.id} - ${s.amount}`).join(', ')}`); await this.initiateReturn(); return; } @@ -216,16 +342,19 @@ export default class FleetManager { else resource.amount += res.amount; } - await this.sendMail(`Your expedition to ${(this.data.destination as Sector).name} sector encountered resource-rich asteroid. Following resources were added to the cargo inventory:\n${resources.map(r => `${r.id} - ${r.amount}`).join('\n')}`); + await this.sendMail("Expedition Results", `Your expedition to ${(this.data.destination as Sector).name} sector encountered resource-rich asteroid. Following resources were added to the cargo inventory:\n${resources.map(r => `${r.id} - ${r.amount}`).join('\n')}`); await this.initiateReturn(); return; } if(expeditionRandom < 0.35) { // 15% chance; pirates/aliens - //TODO: implement fight mechanic + const pirates: { id: string, amount: number }[] = [ + { id: 'fighter', amount: getRandomInRange(0, 100) + valueAdded }, + { id: 'transporter', amount: getRandomInRange(0, 100) + valueAdded } + ]; - await this.sendMail(`Your expedition to ${(this.data.destination as Sector).name} sector encountered drunk pirates. After attempting to communicate with them, they attacked your fleet.`) - await this.initiateReturn(); + await this.sendMail("Expedition Results", `Your expedition to ${(this.data.destination as Sector).name} sector encountered drunk pirates. After attempting to communicate with them, they attacked your fleet.`) + await this.battleResults(pirates); return; } @@ -273,12 +402,12 @@ export default class FleetManager { else resource.amount += res.amount; } - await this.sendMail(`Your expedition to ${(this.data.destination as Sector).name} sector encountered resource-rich rouge planet. Your fleet could not extract all resources. Following resources were added to the cargo inventory:\n${addedResources.map(r => `${r.id} - ${r.amount}`).join('\n')}`); + await this.sendMail("Expedition Results", `Your expedition to ${(this.data.destination as Sector).name} sector encountered resource-rich rouge planet. Your fleet could not extract all resources. Following resources were added to the cargo inventory:\n${addedResources.map(r => `${r.id} - ${r.amount}`).join('\n')}`); await this.initiateReturn(); return; } - await this.sendMail(`Your expedition to ${(this.data.destination as Sector).name} sector scanned the sector for a long time, yet it haven't found anything.`); + await this.sendMail("Expedition Results", `Your expedition to ${(this.data.destination as Sector).name} sector scanned the sector for a long time, yet it haven't found anything.`); await this.initiateReturn(); return; }