Add expedition support
This commit is contained in:
parent
393bcb1960
commit
12f022150c
|
@ -5,11 +5,14 @@ import { updateFleet } from "../../db/fleet";
|
|||
import { Planet } from "./PlanetManager";
|
||||
import SystemManager, { System } from "./SystemManager";
|
||||
import { sendMail } from "../../db/mails";
|
||||
import { Sector } from "./LocationManager";
|
||||
import { getRandomInRange, weightedRandom } from "../../utils/math";
|
||||
import { getAllShips } from "../../db/ships";
|
||||
|
||||
export type Fleet = {
|
||||
id: ObjectId,
|
||||
source: Planet | SystemManager,
|
||||
destination: Planet | SystemManager,
|
||||
destination: Planet | SystemManager | Sector,
|
||||
departureTime: Date,
|
||||
arrivalTime: Date,
|
||||
returning: boolean,
|
||||
|
@ -21,7 +24,7 @@ export type Fleet = {
|
|||
export default class FleetManager {
|
||||
data: Fleet;
|
||||
|
||||
constructor(id: ObjectId, source: Planet | SystemManager, destination: Planet | SystemManager, departureTime: Date, arrivalTime: Date, returning: boolean, mission: MissionType, ships: Array<{ id: string, amount: number }>, cargo: Array<{ id: string, amount: number }>) {
|
||||
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 }>) {
|
||||
this.data = {
|
||||
id,
|
||||
source,
|
||||
|
@ -83,7 +86,7 @@ export default class FleetManager {
|
|||
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` : `planet ${this.data.destination.name}`} has returned.\n
|
||||
`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'}`
|
||||
);
|
||||
|
@ -93,8 +96,8 @@ export default class FleetManager {
|
|||
case 'ATTACK':
|
||||
return false;
|
||||
case 'TRANSPORT':
|
||||
await this.data.destination.resources.updateAmount(this.data.cargo);
|
||||
await this.data.destination.resources.sync();
|
||||
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);
|
||||
|
@ -111,12 +114,12 @@ export default class FleetManager {
|
|||
);
|
||||
return false;
|
||||
case 'TRANSFER':
|
||||
await this.data.destination.resources.updateAmount(this.data.cargo);
|
||||
await this.data.destination.resources.sync();
|
||||
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.ships.addShips(ship.id, ship.amount);
|
||||
(this.data.destination as Planet | SystemManager).ships.addShips(ship.id, ship.amount);
|
||||
}
|
||||
await this.data.destination.ships.sync();
|
||||
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,
|
||||
|
@ -128,6 +131,10 @@ export default class FleetManager {
|
|||
Ships will stay at the destination.`
|
||||
);
|
||||
return true;
|
||||
case 'EXPEDITION':
|
||||
await this.expeditionResults();
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -139,6 +146,143 @@ export default class FleetManager {
|
|||
await this.sync();
|
||||
}
|
||||
|
||||
private async sendMail(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",
|
||||
description
|
||||
);
|
||||
}
|
||||
|
||||
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(`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(`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(`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
|
||||
|
||||
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();
|
||||
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(`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.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;
|
||||
|
|
|
@ -246,7 +246,7 @@ class LocationManager {
|
|||
else found = fleet.data.source.system.data.ownedBy.id.equals(userId);
|
||||
|
||||
if(fleet.data.destination instanceof SystemManager) found = fleet.data.destination.data.ownedBy.id.equals(userId);
|
||||
else found = fleet.data.destination.system.data.ownedBy.id.equals(userId);
|
||||
else if(!("expedition" in fleet.data.destination)) found = fleet.data.destination.system.data.ownedBy.id.equals(userId);
|
||||
|
||||
return found;
|
||||
});
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { ObjectId } from 'mongodb';
|
||||
import DBFleet from '../../types/db/DBFleet';
|
||||
import { Fleet, Planets } from '../db/mongodb';
|
||||
import { Fleet, Planets, Systems } from '../db/mongodb';
|
||||
|
||||
export const getAllFleet = async () => {
|
||||
return (await Fleet()).find({}).toArray() as unknown as Array<DBFleet>;
|
||||
|
@ -8,6 +8,7 @@ export const getAllFleet = async () => {
|
|||
|
||||
export const getAllFleetByUser = async (userId: ObjectId) => {
|
||||
const planets = await (await Planets()).find({ owner: userId }).toArray();
|
||||
const systems = await (await Systems()).find({ ownedBy: userId }).toArray();
|
||||
|
||||
const fleets = new Map<string, DBFleet>();
|
||||
|
||||
|
@ -22,6 +23,17 @@ export const getAllFleetByUser = async (userId: ObjectId) => {
|
|||
}
|
||||
}
|
||||
|
||||
for(const system of systems) {
|
||||
const fleet = await (await Fleet()).find({ $or: [
|
||||
{ source: system._id },
|
||||
{ destination: system._id }
|
||||
] }).toArray() as unknown as DBFleet[];
|
||||
|
||||
for(const f of fleet) {
|
||||
fleets.set(f._id.toString(), f);
|
||||
}
|
||||
}
|
||||
|
||||
return Array.from(fleets.values());
|
||||
}
|
||||
|
||||
|
|
|
@ -7,17 +7,13 @@ const options = {
|
|||
};
|
||||
|
||||
const mongo = new MongoClient(uri, options);
|
||||
|
||||
export const connect = async () => {
|
||||
await mongo.connect();
|
||||
}
|
||||
await mongo.connect();
|
||||
|
||||
export const disconnect = async () => {
|
||||
mongo.close();
|
||||
}
|
||||
|
||||
export const getDB = async (dbName = config.MONGODB_DB) => {
|
||||
await connect();
|
||||
return mongo.db(dbName);
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
export function getRandomGaussian(mean: number, stdDev: number) {
|
||||
let u1 = Math.random();
|
||||
let u2 = Math.random();
|
||||
let z0 = Math.sqrt(-2.0 * Math.log(u1)) * Math.cos(2.0 * Math.PI * u2);
|
||||
return z0 * stdDev + mean;
|
||||
}
|
||||
|
||||
export function getRandomInRange(min: number, max: number) {
|
||||
const mean = 0;
|
||||
const stdDev = 3000;
|
||||
let randomNum;
|
||||
|
||||
do {
|
||||
randomNum = getRandomGaussian(mean, stdDev);
|
||||
} while(randomNum < min || randomNum > max);
|
||||
|
||||
return Math.round(randomNum);
|
||||
}
|
||||
|
||||
export function weightedRandom(weight: number) {
|
||||
if (weight < 1 || weight > 100_000) {
|
||||
throw new Error("Weight must be between 1 and 100000.");
|
||||
}
|
||||
|
||||
return 1 / (1 + Math.exp(-0.00005 * (weight - 40_000))) * 0.7 + Math.random() * 0.3;
|
||||
}
|
|
@ -50,12 +50,7 @@ export const POST: APIRoute = async({ request }) => {
|
|||
|
||||
const checkSource = checkPlanetOrSystemId(body.source, 'source');
|
||||
if(typeof checkSource.error !== "undefined") return new Response(JSON.stringify(checkSource), { status: checkSource.code });
|
||||
|
||||
const checkDestination = checkPlanetOrSystemId(body.destination, 'destination');
|
||||
if(typeof checkDestination.error !== "undefined") return new Response(JSON.stringify(checkDestination), { status: checkDestination.code });
|
||||
|
||||
const source = checkSource.planetOrSystem;
|
||||
const destination = checkDestination.planetOrSystem;
|
||||
|
||||
const shipsDB = await getAllShips();
|
||||
|
||||
|
@ -68,10 +63,21 @@ export const POST: APIRoute = async({ request }) => {
|
|||
const checkCargoBody = checkCargo(body.cargo, body.ships, source, body.mission);
|
||||
if(typeof checkCargoBody.error !== "undefined") return new Response(JSON.stringify(checkCargoBody), { status: checkCargoBody.code });
|
||||
|
||||
let dest;
|
||||
if(body.mission === "EXPEDITION") {
|
||||
const destinationSector = checkSectorId(body.destination);
|
||||
if(typeof destinationSector.error !== "undefined") return new Response(JSON.stringify(destinationSector), { status: destinationSector.code });
|
||||
dest = destinationSector.sector;
|
||||
} else {
|
||||
const checkDestination = checkPlanetOrSystemId(body.destination, 'destination');
|
||||
if(typeof checkDestination.error !== "undefined") return new Response(JSON.stringify(checkDestination), { status: checkDestination.code });
|
||||
dest = checkDestination.planetOrSystem;
|
||||
}
|
||||
|
||||
const fleetManager = new FleetManager(
|
||||
new ObjectId(),
|
||||
source,
|
||||
destination,
|
||||
dest,
|
||||
new Date(),
|
||||
new Date(Date.now() + 1000 * 30), //TODO: calculate time based on distance
|
||||
false,
|
||||
|
@ -146,6 +152,39 @@ function checkPlanetOrSystemId(id: string, type: string) {
|
|||
}
|
||||
}
|
||||
|
||||
function checkSectorId(id: string) {
|
||||
if(typeof ObjectId === "undefined") return {
|
||||
code: 400,
|
||||
message: "Bad Request",
|
||||
error: "Missing 'sector' in body"
|
||||
}
|
||||
|
||||
let idToCheck;
|
||||
try {
|
||||
idToCheck = new ObjectId(id);
|
||||
} catch(e) {
|
||||
return {
|
||||
code: 400,
|
||||
message: "Bad Request",
|
||||
error: "Invalid ID in 'sector'"
|
||||
}
|
||||
}
|
||||
|
||||
const sector = locationManager.getSector(idToCheck);
|
||||
|
||||
if(!sector) return {
|
||||
code: 404,
|
||||
message: "Not Found",
|
||||
error: "Non-existent sector provided in 'sector'"
|
||||
}
|
||||
|
||||
return {
|
||||
code: 200,
|
||||
message: "OK",
|
||||
sector
|
||||
}
|
||||
}
|
||||
|
||||
function checkShips(ships: Array<{ id: string, amount: number }>, shipsDB: Array<DBShip>, source: Planet | SystemManager) {
|
||||
if(typeof ships === "undefined") return {
|
||||
code: 400,
|
||||
|
|
|
@ -30,9 +30,15 @@ if(Astro.request.method === "POST") {
|
|||
}
|
||||
});
|
||||
|
||||
const destination = form.get('mission') === "EXPEDITION" ?
|
||||
form.get('destination-sector')?.toString() :
|
||||
form.get('toSystem') ?
|
||||
form.get('destination-system')?.toString() :
|
||||
form.get('destination-planet')?.toString();
|
||||
|
||||
const fleetData = {
|
||||
source: active instanceof SystemManager ? active.data._id : active._id,
|
||||
destination: form.get('toSystem') ? form.get('destination-system')?.toString() : form.get('destination-planet')?.toString(),
|
||||
destination,
|
||||
mission: form.get('mission')?.toString() ?? "NULL",
|
||||
ships: ships.map(ship => {
|
||||
const amount = parseInt(form.get(`ship-amount-${ship.id}`)?.toString() ?? "0");
|
||||
|
@ -149,6 +155,7 @@ const sectorsList = galaxies.map(galaxy => {
|
|||
<label for="attack"><input type="radio" name="mission" value="ATTACK" id="attack" required />Attack</label>
|
||||
<label for="transport"><input type="radio" name="mission" value="TRANSPORT" id="transport" />Transport</label>
|
||||
<label for="transfer"><input type="radio" name="mission" value="TRANSFER" id="transfer" />Transfer</label>
|
||||
<label for="expedition"><input type="radio" name="mission" value="EXPEDITION" id="expedition" />Expedition</label>
|
||||
<label><input type="checkbox" name="toSystem" />Send to system</label>
|
||||
<hr />
|
||||
<h2>Send to:</h2>
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
type MissionType = "TRANSPORT" | "ATTACK" | "TRANSFER";
|
||||
type MissionType = "TRANSPORT" | "ATTACK" | "TRANSFER" | "EXPEDITION";
|
||||
|
||||
export default MissionType;
|
Loading…
Reference in New Issue