Implement spying mechanic

This commit is contained in:
Aelita4 2025-01-29 21:17:32 +01:00
parent 75d936b78a
commit b21665ecae
Signed by: Aelita4
GPG Key ID: E44490C2025906C1
9 changed files with 285 additions and 3 deletions

View File

@ -1,15 +1,16 @@
import { ObjectId } from "mongodb"; import { ObjectId } from "mongodb";
import MissionType from "../../../types/MissionType"; import MissionType from "../../../types/MissionType";
import DBFleet from "../../../types/db/DBFleet"; import DBFleet from "../../../types/db/DBFleet";
import { getAllDefenses } from "../../db/defenses";
import { updateFleet } from "../../db/fleet"; import { updateFleet } from "../../db/fleet";
import { sendMail } from "../../db/mails"; import { sendMail } from "../../db/mails";
import { getAllShips } from "../../db/ships"; import { getAllShips } from "../../db/ships";
import { addPlanetToExistingSpyReport, addSpyReport, getSpyReportBySystemId, updatePlanetSpyReport, updateSystemSpyReportWithoutPlanets } from "../../db/spyReports";
import getDistanceBetween from "../../utils/getDistanceBetween"; import getDistanceBetween from "../../utils/getDistanceBetween";
import { getRandomInRange, weightedRandom } from "../../utils/math"; import { getRandomInRange, weightedRandom } from "../../utils/math";
import { Sector } from "./LocationManager"; import { Sector } from "./LocationManager";
import { Planet } from "./PlanetManager"; import { Planet } from "./PlanetManager";
import SystemManager from "./SystemManager"; import SystemManager from "./SystemManager";
import { getAllDefenses } from "../../db/defenses";
export type Fleet = { export type Fleet = {
id: ObjectId, id: ObjectId,
@ -174,6 +175,86 @@ export default class FleetManager {
system.asteroids.asteroids = system.asteroids.asteroids.filter(a => !a.id.equals(asteroid.id)); system.asteroids.asteroids = system.asteroids.asteroids.filter(a => !a.id.equals(asteroid.id));
await system.asteroids.sync(); 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; return false;
} }
} }

View File

@ -98,4 +98,9 @@ export const Lang = async (language = "en") => {
export const Mails = async() => { export const Mails = async() => {
const db = await getDB(); const db = await getDB();
return db.collection('mails'); return db.collection('mails');
}
export const SpyReports = async() => {
const db = await getDB();
return db.collection('spyReports');
} }

121
src/lib/db/spyReports.ts Normal file
View File

@ -0,0 +1,121 @@
import { ObjectId } from "mongodb";
import { SpyReports } from "./mongodb"
import DBSpyReport, { DBSpyReportPlanet } from "../../types/db/DBSpyReport";
import { addSpyReportToUser } from "./users";
export const getSpyReportById = async (id: ObjectId) => {
return await (await SpyReports()).findOne({
_id: id
}) as DBSpyReport;
}
export const getSpyReportBySystemId = async (id: ObjectId) => {
return await (await SpyReports()).findOne({
systemId: id
}) as DBSpyReport;
}
export const addSpyReport = async (
toPlayer: ObjectId,
victimId: ObjectId,
scannedAt: Date,
research: Array<{ id: string, level: number }>,
data: {
systemId: ObjectId,
resources: Array<{ id: string, amount: number }>,
structures: Array<{ id: string, level: number }>,
ships: Array<{ id: string, amount: number }>,
defense: Array<{ id: string, amount: number }>,
planets: Array<DBSpyReportPlanet>
}
) => {
const report = await (await SpyReports()).insertOne({
victimId,
scannedAt,
research,
systemId: data.systemId,
resources: data.resources,
structures: data.structures,
ships: data.ships,
defense: data.defense,
planets: data.planets
});
await addSpyReportToUser(toPlayer, report.insertedId);
}
export const updateSystemSpyReportWithoutPlanets = async (
id: ObjectId,
resources: Array<{ id: string, amount: number }>,
structures: Array<{ id: string, level: number }>,
ships: Array<{ id: string, amount: number }>,
defense: Array<{ id: string, amount: number }>
) => {
await (await SpyReports()).updateOne({
_id: id
}, {
$set: {
resources,
structures,
ships,
defense
}
});
}
export const addPlanetToExistingSpyReport = async (
id: ObjectId,
data: {
resources: Array<{ id: string, amount: number }>,
buildings: Array<{ id: string, level: number }>,
ships: Array<{ id: string, amount: number }>,
defense: Array<{ id: string, amount: number }>
}
) => {
await (await SpyReports()).updateOne({
_id: id
}, {
$push: {
planets: {
id: new ObjectId(),
scanned: true,
scannedAt: new Date(),
resources: data.resources,
buildings: data.buildings,
ships: data.ships,
defense: data.defense
}
}
});
}
export const updatePlanetSpyReport = async (
id: ObjectId,
planetId: ObjectId,
data: {
resources: Array<{ id: string, amount: number }>,
buildings: Array<{ id: string, level: number }>,
ships: Array<{ id: string, amount: number }>,
defense: Array<{ id: string, amount: number }>
}
) => {
await (await SpyReports()).updateOne({
_id: id,
"planets.id": planetId
}, {
$set: {
"planets.$.scanned": true,
"planets.$.scannedAt": new Date(),
"planets.$.resources": data.resources,
"planets.$.buildings": data.buildings,
"planets.$.ships": data.ships,
"planets.$.defense": data.defense
}
});
}
export const overwriteSpyReport = async (id: ObjectId, report: DBSpyReport) => {
await (await SpyReports()).replaceOne({
_id: id
}, report);
}

View File

@ -4,6 +4,8 @@ import { hash } from 'argon2'
import DBUser from '../../types/db/DBUser'; import DBUser from '../../types/db/DBUser';
import User from '../classes/User'; import User from '../classes/User';
import AccessToken from '../../types/AccessToken'; import AccessToken from '../../types/AccessToken';
import { getSpyReportById } from './spyReports';
import DBSpyReport from '../../types/db/DBSpyReport';
export const getAllUsers = async () => { export const getAllUsers = async () => {
const users = await Users(); const users = await Users();
@ -20,7 +22,8 @@ export const createUser = async (id: ObjectId, username: string, email: string,
createdAt: new Date(), createdAt: new Date(),
updatedAt: new Date(), updatedAt: new Date(),
research: [], research: [],
mainPlanet mainPlanet,
spyReports: []
} }
await (await Users()).insertOne(user); await (await Users()).insertOne(user);
@ -75,4 +78,24 @@ export const getUserResearch = async (user: User): Promise<Array<{ id: string, l
export const updateUserResearch = async (user: User, research: Array<{ id: string, level: number }>) => { export const updateUserResearch = async (user: User, research: Array<{ id: string, level: number }>) => {
const users = await Users(); const users = await Users();
users.updateOne({ username: user.username }, { $set: { research } }); users.updateOne({ username: user.username }, { $set: { research } });
}
export const addSpyReportToUser = async (userId: ObjectId, spyReportId: ObjectId) => {
const users = await Users();
users.updateOne({ _id: userId }, { $push: { spyReports: spyReportId } });
}
export const getUserSpyReports = async (userId: ObjectId) => {
const users = await Users();
const user = await users.findOne({ _id: userId });
if (!user) return [];
const out: DBSpyReport[] = [];
for(const spyId of user.spyReports) {
const spyReport = await getSpyReportById(spyId);
if(spyReport) out.push(spyReport);
}
return out;
} }

View File

@ -5,6 +5,7 @@ import { Planet } from '../../lib/classes/managers/PlanetManager';
import SystemManager from '../../lib/classes/managers/SystemManager'; import SystemManager from '../../lib/classes/managers/SystemManager';
import { getAllFleetByUser } from '../../lib/db/fleet'; import { getAllFleetByUser } from '../../lib/db/fleet';
import { getAllShips } from '../../lib/db/ships'; import { getAllShips } from '../../lib/db/ships';
import { getUserSpyReports } from '../../lib/db/users';
import { getName } from '../../lib/utils/langDriver'; import { getName } from '../../lib/utils/langDriver';
const { token, user, lang } = Astro.locals; const { token, user, lang } = Astro.locals;
@ -89,6 +90,8 @@ for(const f of fleet) {
} }
} }
const discoveredSectors = await getUserSpyReports(user.id);
const sectorsList = galaxies.map(galaxy => { const sectorsList = galaxies.map(galaxy => {
return { return {
_id: galaxy._id, _id: galaxy._id,
@ -98,6 +101,13 @@ const sectorsList = galaxies.map(galaxy => {
_id: sector._id, _id: sector._id,
name: sector.name, name: sector.name,
systems: sector.systems.map(system => { systems: sector.systems.map(system => {
const discovered = discoveredSectors.find(s => s.systemId.equals(system.data._id));
if(!discovered && !system.data.ownedBy.id.equals(user.id)) return {
_id: system.data._id,
name: `${system.data.name} (not discovered)`,
planets: []
}
return { return {
_id: system.data._id, _id: system.data._id,
name: system.data.name, name: system.data.name,
@ -158,6 +168,7 @@ const sectorsList = galaxies.map(galaxy => {
<label for="transport"><input type="radio" name="mission" value="TRANSPORT" id="transport" />Transport</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="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 for="expedition"><input type="radio" name="mission" value="EXPEDITION" id="expedition" />Expedition</label>
<label for="spy"><input type="radio" name="mission" value="SPY" id="spy" />Spying</label>
<label><input type="checkbox" name="toSystem" />Send to system</label> <label><input type="checkbox" name="toSystem" />Send to system</label>
<hr /> <hr />
<h2>Send to:</h2> <h2>Send to:</h2>

View File

@ -1,3 +1,3 @@
type MissionType = "TRANSPORT" | "ATTACK" | "TRANSFER" | "EXPEDITION" | "MINE"; type MissionType = "TRANSPORT" | "ATTACK" | "TRANSFER" | "EXPEDITION" | "MINE" | "SPY";
export default MissionType; export default MissionType;

16
src/types/SpyData.ts Normal file
View File

@ -0,0 +1,16 @@
export default interface SpyData {
id: string;
resources: Array<{ id: string, amount: number }>;
structures: Array<{ id: string, level: number }>;
ships: Array<{ id: string, amount: number }>;
defense: Array<{ id: string, amount: number }>;
planets: Array<{
id: string;
scanned: boolean;
scannedAt: Date;
resources: Array<{ id: string, amount: number }>;
buildings: Array<{ id: string, level: number }>;
ships: Array<{ id: string, amount: number }>;
defense: Array<{ id: string, amount: number }>;
}>;
}

View File

@ -0,0 +1,24 @@
import { ObjectId } from "mongodb";
export interface DBSpyReportPlanet {
id: ObjectId;
scanned: boolean;
scannedAt: Date;
resources: Array<{ id: string, amount: number }>;
buildings: Array<{ id: string, level: number }>;
ships: Array<{ id: string, amount: number }>;
defense: Array<{ id: string, amount: number }>;
}
export default interface DBSpyReport {
_id: ObjectId;
victimId: ObjectId;
scannedAt: Date;
research: Array<{ id: string, level: number }>;
systemId: ObjectId;
resources: Array<{ id: string, amount: number }>;
structures: Array<{ id: string, level: number }>;
ships: Array<{ id: string, amount: number }>;
defense: Array<{ id: string, amount: number }>;
planets: Array<DBSpyReportPlanet>;
}

View File

@ -10,4 +10,5 @@ export default interface DBUser {
updatedAt: Date; updatedAt: Date;
research: Array<{ id: string, level: number }>; research: Array<{ id: string, level: number }>;
mainPlanet: ObjectId; mainPlanet: ObjectId;
spyReports: Array<ObjectId>;
} }