Complete Remake of the Modpack

This commit is contained in:
2025-05-16 00:38:39 -04:00
parent db58ebbefb
commit a57e637d5f
10423 changed files with 13308 additions and 2630183 deletions
@@ -1,6 +1,6 @@
{
"debugLogs": false,
"preventSameMapRotation": true,
"preventSameMapRotation": false,
"goonsSpawnChance": 30,
"rotationInterval": 180
}
@@ -1,7 +1,7 @@
{
"name": "Dynamic Goons",
"version": "1.1.0",
"sptVersion": "~3.10",
"version": "1.2.0",
"sptVersion": "~3.11",
"loadBefore": [],
"loadAfter": [],
"incompatibilities": [],
@@ -24,7 +24,7 @@
"typescript": "5.4",
"winston": "3.12"
},
"author": "inory",
"author": "inory, lillian",
"contributors": [],
"license": "MIT"
}
@@ -1,21 +1,18 @@
import { inject, injectAll, injectable } from "tsyringe";
import { inject, injectable } from "tsyringe";
import { AbstractDialogueChatBot } from "@spt/helpers/Dialogue/AbstractDialogueChatBot";
import { IChatCommand } from "@spt/helpers/Dialogue/Commando/IChatCommand";
import { IUserDialogInfo } from "@spt/models/eft/profile/ISptProfile";
import { MemberCategory } from "@spt/models/enums/MemberCategory";
import { ILogger } from "@spt/models/spt/utils/ILogger";
import { MailSendService } from "@spt/services/MailSendService";
import type { MailSendService } from "@spt/services/MailSendService";
import type { IUserDialogInfo } from "@spt/models/eft/profile/IUserDialogInfo";
import type { IDialogueChatBot } from "@spt/helpers/Dialogue/IDialogueChatBot";
import type { ISendMessageRequest } from "@spt/models/eft/dialog/ISendMessageRequest";
import type { ChatLocationService } from "../services/ChatLocationService";
@injectable()
export class GoonsTracker extends AbstractDialogueChatBot {
export class GoonsTracker implements IDialogueChatBot {
constructor(
@inject("WinstonLogger") logger: ILogger,
@inject("MailSendService") mailSendService: MailSendService,
@injectAll("TrackerCommands") chatCommands: IChatCommand[]
) {
super(logger, mailSendService, chatCommands);
}
@inject("MailSendService") protected mailSendService: MailSendService,
@inject("ChatLocationService") private locationService: ChatLocationService
) {}
public getChatBot(): IUserDialogInfo {
return {
@@ -31,10 +28,52 @@ export class GoonsTracker extends AbstractDialogueChatBot {
};
}
protected getUnrecognizedCommandMessage(): string {
return (
"Unrecognized command, please type goons track to receive details of The Goon's location. " +
"Or type goons rotation if you need an explanation for their rotation mechanic."
);
public handleMessage(sessionId: string, request: ISendMessageRequest): string
{
if (request.text === "goons track") {
try {
const locationData = this.locationService.getLocationData();
const responseMessage =
`Location: ${locationData.location}\n` +
`Last Seen: ${locationData.timeSinceLastSeen} minutes ago\n` +
`Rotation Chance: ${locationData.rotationChance.toFixed(2)}%\n` +
`${locationData.dateLastSeen}`;
this.mailSendService.sendUserMessageToPlayer(
sessionId,
this.getChatBot(),
responseMessage
);
} catch (error) {
console.error("Error in handle:", error.message);
this.mailSendService.sendUserMessageToPlayer(
sessionId,
this.getChatBot(),
"Error retrieving location data. Please try again later."
);
}
} else if (request.text === "goons rotation") {
const rotationExplanation =
"The Goons stay on a map for a variable amount of time. " +
"As time passes, the chance of them switching to a new " +
"map increases. They will rotate to a new map at the end of a raid based on this chance. " +
"Use goons track to see where they are currently and their rotation chance.";
this.mailSendService.sendUserMessageToPlayer(
sessionId,
this.getChatBot(),
rotationExplanation
);
} else {
this.mailSendService.sendUserMessageToPlayer(
sessionId,
this.getChatBot(),
"Unrecognized command, please type 'goons track' to receive details of The Goon's location. " +
"Or type 'goons rotation' if you need an explanation for their rotation mechanic."
);
}
return request.dialogId;
}
}
@@ -1,76 +0,0 @@
import { inject, injectable } from "tsyringe";
import { IChatCommand } from "@spt/helpers/Dialogue/Commando/IChatCommand";
import { ISendMessageRequest } from "@spt/models/eft/dialog/ISendMessageRequest";
import { IUserDialogInfo } from "@spt/models/eft/profile/ISptProfile";
import { MailSendService } from "@spt/services/MailSendService";
import { ChatLocationService } from "../services/ChatLocationService";
@injectable()
export class TrackerCommands implements IChatCommand {
constructor(
@inject("MailSendService") protected mailSendService: MailSendService,
@inject("ChatLocationService") private locationService: ChatLocationService
) {}
public getCommandPrefix(): string {
return "goons";
}
public getCommandHelp(command: string): string {
if (command === "track") {
return "Usage: goons track - Get current rotation information.";
} else if (command === "rotation") {
return "Usage: goons rotation - Learn about the rotation mechanics.";
}
}
public getCommands(): Set<string> {
return new Set<string>(["track", "rotation"]);
}
public handle(
command: string,
commandHandler: IUserDialogInfo,
sessionId: string,
request: ISendMessageRequest
): string {
if (command === "track") {
try {
const locationData = this.locationService.getLocationData();
const responseMessage =
`Location: ${locationData.location}\n` +
`Last Seen: ${locationData.timeSinceLastSeen} minutes ago\n` +
`Rotation: ${locationData.rotationChance.toFixed(2)}%\n` +
`${locationData.dateLastSeen}`;
this.mailSendService.sendUserMessageToPlayer(
sessionId,
commandHandler,
responseMessage
);
} catch (error) {
console.error("Error in handle:", error.message);
this.mailSendService.sendUserMessageToPlayer(
sessionId,
commandHandler,
"Error retrieving location data. Please try again later."
);
}
} else if (command === "rotation") {
const rotationExplanation =
"The Goons stay on a map for a variable amount of time. " +
"As time passes, the chance of them switching to a new " +
"map increases. They will rotate to a new map at the end of a raid based on this chance. " +
"Use goons track to see where they are currently and their rotation chance.";
this.mailSendService.sendUserMessageToPlayer(
sessionId,
commandHandler,
rotationExplanation
);
}
return request.dialogId;
}
}
@@ -1,6 +1,6 @@
{
"nextUpdateTime": 1736820722143,
"selectedMap": "lighthouse",
"nextUpdateTime": 1747378790984,
"selectedMap": "woods",
"lastRotationInterval": 180,
"lastUpdateTime": 1736809922143
"lastUpdateTime": 1747367990984
}
@@ -1,3 +1,4 @@
// biome-ignore lint/style/useNodejsImportProtocol: <explanation>
import * as path from "path";
import type { DependencyContainer } from "tsyringe";
import type { ILogger } from "@spt/models/spt/utils/ILogger";
@@ -6,15 +7,17 @@ import type { IPreSptLoadMod } from "@spt/models/external/IPreSptLoadMod";
import type { DatabaseServer } from "@spt/servers/DatabaseServer";
import type { IDatabaseTables } from "@spt/models/spt/server/IDatabaseTables";
import type { StaticRouterModService } from "@spt/services/mod/staticRouter/StaticRouterModService";
import type { LocationCallbacks } from "@spt/callbacks/LocationCallback";
import { ILocations } from "@spt/models/spt/server/ILocations";
import { DialogueController } from "@spt/controllers/DialogueController";
import type { LocationCallbacks } from "@spt/callbacks/LocationCallbacks";
import type { ILocations } from "@spt/models/spt/server/ILocations";
import type { DialogueController } from "@spt/controllers/DialogueController";
import { TrackerCommands } from "./chatbot/TrackerCommands";
import { GoonsTracker } from "./chatbot/GoonsTracker";
import { ChatLocationService } from "./services/ChatLocationService";
import { RotationService } from "./services/RotationService";
import { AddBossToMaps } from "./services/AddGoonsToMaps";
import type { ConfigServer } from "@spt/servers/ConfigServer";
import { ConfigTypes } from "@spt/models/enums/ConfigTypes";
import type { ICoreConfig } from "@spt/models/spt/config/ICoreConfig";
class Mod implements IPostDBLoadMod, IPreSptLoadMod {
private logger: ILogger;
@@ -28,7 +31,7 @@ class Mod implements IPostDBLoadMod, IPreSptLoadMod {
private modConfig = require("../config/config.json");
private mapConfig = path.resolve(__dirname, "../config/mapConfig.json");
private zonesConfigPath = path.resolve(__dirname, "../src/db/mapZones.json");
private async postDBLoad(container: DependencyContainer): Promise<void> {
public async postDBLoad(container: DependencyContainer): Promise<void> {
this.databaseServer = container.resolve<DatabaseServer>("DatabaseServer");
this.tables = this.databaseServer.getTables();
this.maps = this.tables.locations;
@@ -39,19 +42,24 @@ class Mod implements IPostDBLoadMod, IPreSptLoadMod {
useClass: ChatLocationService,
});
container.register<TrackerCommands>("TrackerCommands", TrackerCommands);
container.register<GoonsTracker>("GoonsTracker", GoonsTracker);
const goonsTrackBot = container.resolve<GoonsTracker>("GoonsTracker");
container
.resolve<DialogueController>("DialogueController")
.registerChatBot(container.resolve<GoonsTracker>("GoonsTracker"));
.registerChatBot(goonsTrackBot);
const coreConfig = container.resolve<ConfigServer>("ConfigServer").getConfig<ICoreConfig>(ConfigTypes.CORE);
const goonsTrackBotInfo = goonsTrackBot.getChatBot();
coreConfig.features.chatbotFeatures.ids[goonsTrackBotInfo.Info.Nickname] = goonsTrackBotInfo._id;
coreConfig.features.chatbotFeatures.enabledBots[goonsTrackBotInfo._id] = true;
this.addBossToAllMaps = new AddBossToMaps(
this.logger,
this.zonesConfigPath,
this.modConfig
);
this.addBossToAllMaps.addBossToMaps(this.maps);
this.addBossToAllMaps.addBossToMaps(this.maps.base.locations);
this.rotationService = new RotationService(
this.logger,
@@ -85,6 +93,7 @@ class Mod implements IPostDBLoadMod, IPreSptLoadMod {
url: "/client/locations",
action: async (
url: string,
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
info: any,
sessionId: string,
output: string
@@ -1,8 +1,8 @@
import * as fs from "fs";
import * as fs from "node:fs";
import { inject, injectable } from "tsyringe";
import { ILogger } from "@spt/models/spt/utils/ILogger";
import { ILocation } from "@spt/models/eft/common/ILocation";
import { IBossLocationSpawn } from "@spt/models/eft/common/ILocationBase";
import type { ILogger } from "@spt/models/spt/utils/ILogger";
import type { ILocation } from "@spt/models/eft/common/ILocation";
import type { IBossLocationSpawn } from "@spt/models/eft/common/ILocationBase";
@injectable()
export class AddBossToMaps {
@@ -1,6 +1,6 @@
import { injectable } from "tsyringe";
import * as fs from "fs";
import * as path from "path";
import * as fs from "node:fs";
import * as path from "node:path";
import { rotationChanceCalculator } from "./RotationChanceCalculator";
@injectable()
@@ -19,8 +19,7 @@ export class ChatLocationService {
const parsedData = JSON.parse(data);
if (
parsedData &&
parsedData.selectedMap &&
parsedData?.selectedMap &&
typeof parsedData.lastUpdateTime === "number"
) {
const locationMap: { [key: string]: string } = {
@@ -67,9 +66,8 @@ export class ChatLocationService {
rotationChance,
dateLastSeen,
};
} else {
throw new Error("Invalid data format in the JSON file.");
}
throw new Error("Invalid data format in the JSON file.");
} catch (error) {
console.error("Error reading or parsing the JSON file:", error.message);
throw new Error(
@@ -1,4 +1,4 @@
import { ILogger } from "@spt/models/spt/utils/ILogger";
import type { ILogger } from "@spt/models/spt/utils/ILogger";
export function rotationChanceCalculator(
remainingTime: number,
@@ -15,9 +15,9 @@ export function rotationChanceCalculator(
const factor = remainingTime / maxTime;
const steepFactor = Math.pow(factor, 0.3); // Adjust the rotation chance curve, smaller exponent, slower the initial rise
const steepFactor = factor ** 0.3; // Adjust the rotation chance curve, smaller exponent, slower the initial rise
const chance = maxChance * (1 - Math.pow(steepFactor, 1));
const chance = maxChance * (1 - steepFactor ** 1);
if (debugLogs && logger) {
logger.info(
@@ -1,17 +1,21 @@
import * as fs from "fs";
import { ILogger } from "@spt/models/spt/utils/ILogger";
import * as fs from "node:fs";
import type { ILogger } from "@spt/models/spt/utils/ILogger";
import { inject, injectable } from "tsyringe";
import { rotationChanceCalculator } from "../services/RotationChanceCalculator";
@injectable()
export class RotationService {
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
private modConfig: any;
private rotationData: string;
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
private mapConfig: any;
constructor(
@inject("Logger") private logger: ILogger,
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
modConfig: any,
rotationDataFilePath: string,
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
mapConfig: any
) {
this.modConfig = modConfig;
@@ -38,6 +42,7 @@ export class RotationService {
}
public async handleRotationChance(
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
rotationData: any,
currentTime: number,
rotationInterval: number
@@ -61,7 +66,7 @@ export class RotationService {
if (rotationData.lastRotationInterval !== this.modConfig.rotationInterval) {
if (this.modConfig.debugLogs) {
this.logger.info(
`[Dynamic Goons] Rotation interval changed. Rotating now.`
"[Dynamic Goons] Rotation interval changed. Rotating now."
);
}
await this.selectRandomMapAndSave(rotationInterval);
@@ -69,7 +74,7 @@ export class RotationService {
if (randomRoll <= rotationChance) {
if (this.modConfig.debugLogs) {
this.logger.info(`[Dynamic Goons] Rotation triggered. Rotating now.`);
this.logger.info("[Dynamic Goons] Rotation triggered. Rotating now.");
}
await this.selectRandomMapAndSave(rotationInterval);
}
@@ -82,7 +87,7 @@ export class RotationService {
if (remainingTime <= 0) return maxChance;
const factor = Math.min(Math.max(remainingTime / maxTime, 0), 1);
const chance = maxChance * (1 - Math.pow(factor, 2));
const chance = maxChance * (1 - factor ** 2);
if (this.modConfig.debugLogs) {
this.logger.info(
@@ -150,7 +155,7 @@ export class RotationService {
);
if (this.modConfig.debugLogs) {
this.logger.info(`[Dynamic Goons] Rotation data saved successfully.`);
this.logger.info("[Dynamic Goons] Rotation data saved successfully.");
}
} catch (error) {
this.logger.error(
@@ -159,6 +164,7 @@ export class RotationService {
}
}
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
public async readRotationData(): Promise<any> {
try {
const data = await fs.promises.readFile(this.rotationData, "utf8");