AstroCol/src/lib/classes/managers/FleetManager.ts

723 lines
40 KiB
TypeScript

import { ObjectId } from "mongodb";
import MissionType from "../../../types/MissionType";
import DBFleet from "../../../types/db/DBFleet";
import { getAllDefenses } from "../../db/defenses";
import { updateFleet } from "../../db/fleet";
import { sendMail } from "../../db/mails";
import { getAllShips } from "../../db/ships";
import { addPlanetToExistingSpyReport, addSpyReport, getSpyReportBySystemId, updatePlanetSpyReport, updateSystemSpyReportWithoutPlanets } from "../../db/spyReports";
import getDistanceBetween from "../../utils/getDistanceBetween";
import { getRandomInRange, weightedRandom } from "../../utils/math";
import { Sector } from "./LocationManager";
import { Planet } from "./PlanetManager";
import SystemManager, { System } from "./SystemManager";
import DBSystem from "../../../types/db/DBSystem";
import { addPlanetToSystem, createSystem, getSystemById } from "../../db/systems";
import DBPlanet from "../../../types/db/DBPlanet";
import { createPlanet } from "../../db/planets";
import PlanetResourceManager from "./PlanetResourceManager";
import BuildingManager from "./BuildingManager";
import PlanetShipManager from "./PlanetShipManager";
import PlanetDefenseManager from "./PlanetDefenseManager";
import PlanetEnergyManager from "./PlanetEnergyManager";
import { addSystemToSector } from "../../db/sectors";
export type Fleet = {
id: ObjectId,
source: Planet | SystemManager,
destination: Planet | SystemManager | Sector,
departureTime: Date,
arrivalTime: Date,
returning: boolean,
mission: MissionType,
ships: Array<{ id: string, amount: number }>,
cargo: Array<{ id: string, amount: number }>,
additionalData?: string
}
export type BattleFleet = {
id: string,
hitpoints: number,
attack: number,
defense: number
}
export default class FleetManager {
data: Fleet;
constructor(id: ObjectId, source: Planet | SystemManager, destination: Planet | SystemManager | Sector, departureTime: Date, arrivalTime: Date, returning: boolean, mission: MissionType, ships: Array<{ id: string, amount: number }>, cargo: Array<{ id: string, amount: number }>, additionalData?: string) {
this.data = {
id,
source,
destination,
departureTime,
arrivalTime,
returning,
mission,
ships,
cargo,
additionalData
}
}
isSourcePlanet(): this is { data: { source: Planet } } {
return 'resources' in this.data.source;
}
isSourceSystem(): this is { data: { source: SystemManager } } {
return 'structures' in this.data.source;
}
isDestinationPlanet(): this is { data: { destination: Planet } } {
return 'resources' in this.data.destination;
}
isDestinationSystem(): this is { data: { destination: SystemManager } } {
return 'structures' in this.data.destination;
}
getSourceOwner() {
if(this.isSourcePlanet()) return this.data.source.system.data.ownedBy;
if(this.isSourceSystem()) return this.data.source.data.ownedBy;
}
getDestinationOwner() {
if(this.isDestinationPlanet()) return this.data.destination.system.data.ownedBy;
if(this.isDestinationSystem()) return this.data.destination.data.ownedBy;
}
async checkStatus(): Promise<{ finished: boolean, fleet: Fleet }> {
if(this.data.arrivalTime.getTime() < Date.now()) {
const finished = await this.finish();
return { finished, fleet: this.data };
}
return { finished: false, fleet: this.data };
}
async finish() {
if(this.data.returning) {
for(const ship of this.data.ships) {
this.data.source.ships.addShips(ship.id, ship.amount);
}
await this.data.source.resources.updateAmount(this.data.cargo);
await this.data.source.ships.sync();
await this.data.source.resources.sync();
await sendMail(
null,
this.data.source instanceof SystemManager ? this.data.source.data.ownedBy.id : this.data.source.system.data.ownedBy.id,
this.data.arrivalTime,
"Fleet Returned",
`Your fleet from ${this.data.destination instanceof SystemManager ? `${this.data.destination.data.name} system` : `${this.data.destination.name} ${"expedition" in this.data.destination ? "sector" : "planet"}`} has returned.\n
Ships: ${this.data.ships.map(ship => `${ship.amount} ${ship.id}`).join(', ')}\n
Cargo: ${this.data.cargo.length > 0 ? this.data.cargo.map(cargo => `${cargo.amount} ${cargo.id}`).join(', ') : 'None'}`
);
return true;
} else {
switch(this.data.mission) {
case 'ATTACK':
if(!("expedition" in this.data.destination)) {
const enemyShips = this.data.destination.ships.ships;
return await this.battleResults(enemyShips.map(ship => { return { id: ship.data.id, amount: ship.amount } }), this.data.destination.defenses.defenses.map(defense => { return { id: defense.data.id, amount: defense.amount } }));
} else {
throw new Error("Cannot attack sector.");
}
case 'TRANSPORT':
await (this.data.destination as Planet | SystemManager).resources.updateAmount(this.data.cargo);
await (this.data.destination as Planet | SystemManager).resources.sync();
const cargo = JSON.parse(JSON.stringify(this.data.cargo)) as Array<{ id: string, amount: number }>;
this.data.cargo = [];
const arrived = new Date(this.data.arrivalTime);
await this.initiateReturn();
await sendMail(
null,
this.data.source instanceof SystemManager ? this.data.source.data.ownedBy.id : this.data.source.system.data.ownedBy.id,
arrived,
"Fleet Arrived",
`Your fleet has arrived at ${this.data.destination instanceof SystemManager ? `${this.data.destination.data.name} system` : `planet ${this.data.destination.name}`}.\n
Ships: ${this.data.ships.map(ship => `${ship.amount} ${ship.id}`).join(', ')}\n
Cargo delivered: ${cargo.length > 0 ? cargo.map(cargo => `${cargo.amount} ${cargo.id}`).join(', ') : 'None'}\n
Fleet will return at ${this.data.arrivalTime}`
);
return false;
case 'TRANSFER':
await (this.data.destination as Planet | SystemManager).resources.updateAmount(this.data.cargo);
await (this.data.destination as Planet | SystemManager).resources.sync();
for(const ship of this.data.ships) {
(this.data.destination as Planet | SystemManager).ships.addShips(ship.id, ship.amount);
}
await (this.data.destination as Planet | SystemManager).ships.sync();
await sendMail(
null,
this.data.source instanceof SystemManager ? this.data.source.data.ownedBy.id : this.data.source.system.data.ownedBy.id,
this.data.arrivalTime,
"Fleet Arrived",
`Your fleet has arrived at ${this.data.destination instanceof SystemManager ? `${this.data.destination.data.name} system` : `planet ${this.data.destination.name}`}.\n
Ships: ${this.data.ships.map(ship => `${ship.amount} ${ship.id}`).join(', ')}\n
Cargo delivered: ${this.data.cargo.length > 0 ? this.data.cargo.map(cargo => `${cargo.amount} ${cargo.id}`).join(', ') : 'None'}\n
Ships will stay at the destination.`
);
return true;
case 'EXPEDITION':
await this.expeditionResults();
return false;
case 'MINE':
const system = this.data.destination as SystemManager;
const asteroid = system.asteroids.asteroids.find(a => a.id.equals(this.data.additionalData ?? ""));
if(!asteroid) throw new Error("Asteroid not found.");
for(const res of asteroid.resources) {
const resource = this.data.cargo.find(r => r.id === res.id);
if(!resource) this.data.cargo.push(res);
else resource.amount += res.amount;
}
await this.initiateReturn();
await sendMail(
null,
this.data.source instanceof SystemManager ? this.data.source.data.ownedBy.id : this.data.source.system.data.ownedBy.id,
this.data.arrivalTime,
"Asteroid mined",
`Your fleet has arrived at AS-${this.data.additionalData} asteroid.\n
Following resources were added to the cargo inventory:\n${asteroid.resources.map(r => `${r.id} - ${r.amount}`).join('\n')}\n
Fleet will return at ${this.data.arrivalTime}`
);
system.asteroids.asteroids = system.asteroids.asteroids.filter(a => !a.id.equals(asteroid.id));
await system.asteroids.sync();
return false;
case 'SPY':
if(this.data.destination instanceof SystemManager) {
const existing = await getSpyReportBySystemId(this.data.destination.data._id);
if(existing) {
await updateSystemSpyReportWithoutPlanets(
existing._id,
this.data.destination.resources.resources,
this.data.destination.structures.structures.map(structure => { return { id: structure.data.id, level: structure.level } }),
this.data.destination.ships.ships.map(ship => { return { id: ship.data.id, amount: ship.amount } }),
this.data.destination.defenses.defenses.map(defense => { return { id: defense.data.id, amount: defense.amount } })
);
} else {
const gatheredData = {
systemId: this.data.destination.data._id,
resources: this.data.destination.resources.resources.map(res => { return { id: res.id, amount: res.amount } }),
structures: this.data.destination.structures.structures.map(structure => { return { id: structure.data.id, level: structure.level } }),
ships: this.data.destination.ships.ships.map(ship => { return { id: ship.data.id, amount: ship.amount } }),
defense: this.data.destination.defenses.defenses.map(defense => { return { id: defense.data.id, amount: defense.amount } }),
planets: []
}
await addSpyReport(
this.data.source instanceof SystemManager ? this.data.source.data.ownedBy.id : this.data.source.system.data.ownedBy.id,
this.data.destination.data.ownedBy.id,
new Date(),
[],
gatheredData
);
}
} else if("fields" in this.data.destination) {
const existing = await getSpyReportBySystemId(this.data.destination.system.data._id);
if(!existing) {
throw new Error("Impossible to spy on planet without system report.");
}
const gatheredData = {
resources: this.data.destination.resources.resources.map(res => { return { id: res.id, amount: res.amount } }),
buildings: this.data.destination.buildings.buildings.map(building => { return { id: building.data.id, level: building.level } }),
ships: this.data.destination.ships.ships.map(ship => { return { id: ship.data.id, amount: ship.amount } }),
defense: this.data.destination.defenses.defenses.map(defense => { return { id: defense.data.id, amount: defense.amount } })
}
const planetId = this.data.destination._id;
if(existing.planets.find(p => p.id.equals(planetId))) {
await updatePlanetSpyReport(existing._id, planetId, gatheredData);
} else {
await addPlanetToExistingSpyReport(existing._id, gatheredData);
}
}
await sendMail(
null,
this.data.source instanceof SystemManager ? this.data.source.data.ownedBy.id : this.data.source.system.data.ownedBy.id,
this.data.arrivalTime,
"Spy report",
`Your fleet has arrived at ${this.data.destination instanceof SystemManager ? `${this.data.destination.data.name} system` : `planet ${this.data.destination.name}`}.\n
Following information was gathered:\n\n
${this.data.destination instanceof SystemManager ? `Research:\n${this.data.destination.data.ownedBy.research.research.map(research => `${research.id} - ${research.level}`).join('\n')}\n` : ""}
Resources:\n${(this.data.destination as Planet | SystemManager).resources.resources.map(res => `${res.amount} ${res.id}`).join('\n')}\n
${this.data.destination instanceof SystemManager ? `Structures:\n${(this.data.destination as SystemManager).structures.structures.map(structure => `${structure.level} ${structure.data.id}`).join('\n')}\n` : `Buildings:\n${(this.data.destination as Planet).buildings.buildings.map(building => `${building.level} ${building.data.id}`).join('\n')}\n`}
Ships:\n${(this.data.destination as Planet | SystemManager).ships.ships.map(ship => `${ship.amount} ${ship.data.id}`).join('\n')}\n
Defense:\n${(this.data.destination as Planet | SystemManager).defenses.defenses.map(defense => `${defense.amount} ${defense.data.id}`).join('\n')}\n
${this.data.destination instanceof SystemManager ? `Planets:\n${(this.data.destination as SystemManager).planets.map(planet => `${planet.name}`).join('\n')}` : ""}
Fleet will return at ${this.data.arrivalTime}`
);
await sendMail(
null,
this.data.destination instanceof SystemManager ? this.data.destination.data.ownedBy.id : (this.data.destination as Planet).system.data.ownedBy.id,
this.data.arrivalTime,
"Spy probe detected",
`Your systems have detected enemy spy probe at ${this.data.destination instanceof SystemManager ? `${this.data.destination.data.name} system` : `planet ${this.data.destination.name}`}.\n
Probe was identified to belong to ${this.data.source instanceof SystemManager ? this.data.source.data.ownedBy.username : this.data.source.system.data.ownedBy.username}.`
);
await this.initiateReturn();
return false;
case 'COLONIZE':
const destination = this.data.destination as Sector | SystemManager;
if(destination instanceof SystemManager) {
if(!destination.data.ownedBy.id.equals(this.data.source instanceof SystemManager ? this.data.source.data.ownedBy.id : this.data.source.system.data.ownedBy.id)) {
throw new Error("Cannot colonize system owned by other player.");
}
const planetData: DBPlanet = {
_id: new ObjectId(),
name: `Planet 1`,
owner: destination.data.ownedBy.id,
fields: 40,
resources: [],
buildings: [],
ships: [],
defenses: []
}
await createPlanet(planetData);
await addPlanetToSystem(destination.data._id, planetData._id);
const planetObject: Planet = {
_id: planetData._id,
system: destination,
name: planetData.name,
fields: planetData.fields,
//@ts-ignore
resources: null,
//@ts-ignore
buildings: null,
//@ts-ignore
ships: null,
//@ts-ignore
defenses: null,
//@ts-ignore
energy: null
}
planetObject.resources = await new PlanetResourceManager(planetObject).init(planetData.resources);
planetObject.buildings = await new BuildingManager(planetObject).init(planetData.buildings);
planetObject.ships = await new PlanetShipManager(planetObject).init(planetData.ships);
planetObject.defenses = await new PlanetDefenseManager(planetObject).init(planetData.defenses);
planetObject.energy = new PlanetEnergyManager(planetObject).update();
for(const ship of this.data.ships) {
if(ship.id === "colonizing-ship") continue;
planetObject.ships.addShips(ship.id, ship.amount);
}
planetObject.resources.setAmount(this.data.cargo);
destination.planets.push(planetObject);
await sendMail(
null,
this.data.source instanceof SystemManager ? this.data.source.data.ownedBy.id : this.data.source.system.data.ownedBy.id,
this.data.arrivalTime,
"New planet colonized",
`Your fleet has found a suitable planet for colonization in ${destination.data.name} system.\n
All the ships and resources have been used to establish a new colony.`
);
} else {
const systemData: DBSystem = {
_id: new ObjectId(),
name: `System ${destination.systems.length + 1}`,
ownedBy: this.data.source instanceof SystemManager ? this.data.source.data.ownedBy.id : this.data.source.system.data.ownedBy.id,
planets: [],
structures: [],
resources: [],
ships: [],
defenses: [],
asteroids: []
}
const planetData: DBPlanet = {
_id: new ObjectId(),
name: `Planet 1`,
owner: systemData.ownedBy,
fields: 40,
resources: [],
buildings: [],
ships: [],
defenses: []
}
systemData.planets.push(planetData._id);
await createSystem(systemData);
await createPlanet(planetData);
await addPlanetToSystem(systemData._id, planetData._id);
await addSystemToSector(destination._id, systemData._id);
const systemObject: System = {
_id: systemData._id,
sector: destination,
name: systemData.name,
ownedBy: this.data.source instanceof SystemManager ? this.data.source.data.ownedBy : this.data.source.system.data.ownedBy,
//@ts-ignore
structures: null,
//@ts-ignore
resources: null,
//@ts-ignore
ships: null,
//@ts-ignore
defenses: null,
planets: [],
asteroids: []
}
const systemManager = await new SystemManager(systemObject).fillData(systemData);
const planet = systemManager.planets.find(p => p._id.equals(planetData._id));
if(!planet) throw new Error("FATAL: Planet was not created properly in system manager.");
for(const ship of this.data.ships) {
if(ship.id === "colonizing-ship") continue;
planet.ships.addShips(ship.id, ship.amount);
}
planet.resources.setAmount(this.data.cargo);
destination.systems.push(systemManager);
await sendMail(
null,
this.data.source instanceof SystemManager ? this.data.source.data.ownedBy.id : this.data.source.system.data.ownedBy.id,
this.data.arrivalTime,
"New system colonized",
`Your fleet has settled on a planet ${planet.name} in a new system ${systemManager.data.name} in ${destination.name} sector.\n
All the ships and resources have been used to establish a new colony.`
);
}
return true;
}
}
}
async initiateReturn(isByPlayer: boolean = false) {
this.data.returning = true;
const elapsedTime = Date.now() - this.data.departureTime.getTime();
this.data.departureTime = isByPlayer ? new Date() : new Date(this.data.arrivalTime);
const travelTime = getDistanceBetween(this.data.destination, this.data.source);
this.data.arrivalTime = new Date(this.data.departureTime.getTime() + (isByPlayer ? elapsedTime : 1000 * travelTime));
await this.sync();
}
private async sendMail(user: ObjectId, title: string, description: string) {
await sendMail(
null,
user,
this.data.arrivalTime,
title,
description
);
}
async battleResults(enemyFleet: { id: string, amount: number }[], enemyDefenses: { id: string, amount: number }[] = []) {
const allShips = await getAllShips();
const allDefenses = await getAllDefenses();
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 enemyDefensesStats = enemyDefenses.reduce((acc, defense) => {
const dbDefense = allDefenses.find(d => d.id === defense.id);
if(!dbDefense) return acc;
acc.defense += dbDefense.structure.defense * defense.amount;
acc.hitpoints += dbDefense.structure.hitpoints * defense.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;
}, enemyDefensesStats);
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
});
}
if(playerStats.attack > enemyStats.defense * 2 && playerStats.defense > enemyStats.attack * 2) {
enemyShipsStructure.forEach((_, index) => enemyShipsStructure.splice(index, 1));
} else if(enemyStats.attack > playerStats.defense * 2 && enemyStats.defense > playerStats.attack * 2) {
playerShipsStructure.forEach((_, index) => playerShipsStructure.splice(index, 1));
} else {
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(
this.data.source instanceof SystemManager ? this.data.source.data.ownedBy.id : this.data.source.system.data.ownedBy.id,
`Battle Results (${playerBalance > enemyBalance ? "Victory" : playerBalance < enemyBalance ? "Defeat" : "Draw"})`,
`Results of battle at ${(this.data.destination instanceof SystemManager ? this.data.destination.data.name : this.data.destination.name)}:\n
Player ships:\n${previousShips.map(ship => `${ship.amount} ${ship.id}`).join('\n')}\n
Enemy ships:\n${enemyFleet.map(ship => `${ship.amount} ${ship.id}`).join('\n')}\n
Enemy defenses:\n${enemyDefenses.map(defense => `${defense.amount} ${defense.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]}`).join('\n')}\n
Enemy ships left:\n${Object.keys(enemyShipsLeft).map(key => `${key} - ${enemyShipsLeft[key]}`).join('\n')}\n
${playerBalance > enemyBalance ? `Resources stolen:\n${resourcesStolen.map(res => `${res.id} - ${res.amount}`).join('\n')}` : ""}`
);
if(!("expedition" in this.data.destination)) {
await this.sendMail(
this.data.destination instanceof SystemManager ? this.data.destination.data.ownedBy.id : this.data.destination.system.data.ownedBy.id,
`Battle Results (${playerBalance < enemyBalance ? "Victory" : playerBalance > enemyBalance ? "Defeat" : "Draw"})`,
`Results of battle at ${(this.data.destination instanceof SystemManager ? this.data.destination.data.name : this.data.destination.name)}:\n
Enemy ships:\n${previousShips.map(ship => `${ship.amount} ${ship.id}`).join('\n')}\n
Player ships:\n${enemyFleet.map(ship => `${ship.amount} ${ship.id}`).join('\n')}\n
Player defenses:\n${enemyDefenses.map(defense => `${defense.amount} ${defense.id}`).join('\n')}\n
Enemy stats: ${playerStats.hitpoints} HP, ${playerStats.attack} ATK, ${playerStats.defense} DEF\n
Player stats: ${enemyStats.hitpoints} HP, ${enemyStats.attack} ATK, ${enemyStats.defense} DEF\n\n
Enemy ships left:\n${Object.keys(playerShipsLeft).map(key => `${key} - ${playerShipsLeft[key]}`).join('\n')}\n
Player ships left:\n${Object.keys(enemyShipsLeft).map(key => `${key} - ${enemyShipsLeft[key]}`).join('\n')}\n
${playerBalance > enemyBalance ? `Resources stolen:\n${resourcesStolen.map(res => `${res.id} - ${res.amount}`).join('\n')}` : ""}`
);
}
return !(playerShipsStructure.length > 0);
}
async expeditionResults() {
const expeditionRandom = Math.random(); //TODO: make use of "expedition" from DBSector
const allShips = await getAllShips();
const shipCount = this.data.ships.reduce((acc, ship) => acc += ship.amount, 0);
const totalShipCapacity = this.data.ships.reduce((acc, ship) => acc + (allShips.find(s => s.id === ship.id)?.capacity.solid ?? 0) * ship.amount, 0);
const currentCargo = this.data.cargo.reduce((acc, res) => acc + res.amount, 0);
const expeditionShipPositiveRandom = weightedRandom(shipCount > 100_000 ? 100_000 : shipCount);
const valueAdded = Math.floor(Math.pow(Math.E * expeditionShipPositiveRandom, 4 * Math.PI) + 10)
if(expeditionRandom < 0.02) { // 2% chance; lost all ships, black hole
this.data.ships = [];
await this.sendMail(this.data.source instanceof SystemManager ? this.data.source.data.ownedBy.id : this.data.source.system.data.ownedBy.id, "Expedition Results", `Your expedition to ${(this.data.destination as Sector).name} sector encountered a black hole. All ships were lost.`);
return;
}
if(expeditionRandom < 0.1) { // 8% chance; found ships
const ships: { id: string, amount: number }[] = [];
ships.push({ id: 'transporter', amount: getRandomInRange(0, 20) + Math.floor(valueAdded ) });
ships.push({ id: 'fighter', amount: getRandomInRange(0, 20) + Math.floor(valueAdded ) });
for(const s of ships) {
const ship = this.data.ships.find((sh => sh.id === s.id));
if(!ship) this.data.ships.push(s);
else ship.amount += s.amount;
}
await this.sendMail(this.data.source instanceof SystemManager ? this.data.source.data.ownedBy.id : this.data.source.system.data.ownedBy.id, "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;
}
if(expeditionRandom < 0.2) { // 10% chance; found resources
let totalCapacityUsed = 0;
let resources = [];
const resToAdd = valueAdded * 3 + currentCargo > totalShipCapacity ? totalShipCapacity / 3 : valueAdded;
do {
resources = [{
id: "coal",
amount: getRandomInRange(0, 10000) + resToAdd
}, {
id: "iron",
amount: getRandomInRange(0, 10000) + resToAdd
}, {
id: "gold",
amount: getRandomInRange(0, 10000) + resToAdd
}];
totalCapacityUsed = resources.reduce((acc, res) => acc + res.amount, 0);
} while((totalCapacityUsed + currentCargo) > totalShipCapacity);
for(const res of resources) {
const resource = this.data.cargo.find(r => r.id === res.id);
if(!resource) this.data.cargo.push(res);
else resource.amount += res.amount;
}
await this.sendMail(this.data.source instanceof SystemManager ? this.data.source.data.ownedBy.id : this.data.source.system.data.ownedBy.id, "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
const pirates: { id: string, amount: number }[] = [
{ id: 'fighter', amount: getRandomInRange(0, 100) + valueAdded },
{ id: 'transporter', amount: getRandomInRange(0, 100) + valueAdded }
];
await this.sendMail(this.data.source instanceof SystemManager ? this.data.source.data.ownedBy.id : this.data.source.system.data.ownedBy.id, "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;
}
if(expeditionRandom < 0.36) { // 1% chance; rich rouge planet
const oneResourceMax = totalShipCapacity / 3;
let currentCargo = this.data.cargo.reduce((acc, res) => {
return acc + res.amount;
}, 0);
const addedResources: { id: string, amount: number }[] = [{
id: "coal",
amount: 0
}, {
id: "iron",
amount: 0
}, {
id: "gold",
amount: 0
}];
while(currentCargo < totalShipCapacity) {
if((addedResources.find(r => r.id === "coal")?.amount ?? 0) < oneResourceMax) {
(addedResources.find(r => r.id === "coal") as { amount: number }).amount++;
currentCargo++;
if(currentCargo >= totalShipCapacity) break;
}
if((addedResources.find(r => r.id === "iron")?.amount ?? 0) < oneResourceMax) {
(addedResources.find(r => r.id === "iron") as { amount: number }).amount++;
currentCargo++;
if(currentCargo >= totalShipCapacity) break;
}
if((addedResources.find(r => r.id === "gold")?.amount ?? 0) < oneResourceMax) {
(addedResources.find(r => r.id === "gold") as { amount: number }).amount++;
currentCargo++;
if(currentCargo >= totalShipCapacity) break;
}
}
for(const res of addedResources) {
const resource = this.data.cargo.find(r => r.id === res.id);
if(!resource) this.data.cargo.push(res);
else resource.amount += res.amount;
}
await this.sendMail(this.data.source instanceof SystemManager ? this.data.source.data.ownedBy.id : this.data.source.system.data.ownedBy.id, "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(this.data.source instanceof SystemManager ? this.data.source.data.ownedBy.id : this.data.source.system.data.ownedBy.id, "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;
}
async sync() {
const source = this.data.source instanceof SystemManager ? this.data.source.data._id : this.data.source._id;
const destination = this.data.destination instanceof SystemManager ? this.data.destination.data._id : this.data.destination._id;
const data: DBFleet = {
_id: this.data.id,
source,
destination,
departureTime: this.data.departureTime,
arrivalTime: this.data.arrivalTime,
returning: this.data.returning,
mission: this.data.mission,
ships: this.data.ships,
cargo: this.data.cargo,
additionalData: this.data.additionalData
}
await updateFleet(data);
}
}