Add battle support

This commit is contained in:
Aelita4 2024-12-22 14:40:50 +01:00
parent f6a27eec59
commit bf54589069
Signed by: Aelita4
GPG Key ID: E44490C2025906C1
1 changed files with 140 additions and 11 deletions

View File

@ -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;
}