Archived
Complete Remake of the Modpack
This commit is contained in:
@@ -1,21 +1,21 @@
|
||||
[General]
|
||||
gameName=spt
|
||||
modid=0
|
||||
version=d2025.1.2.0
|
||||
version=d2025.5.14.0
|
||||
newestVersion=
|
||||
category="1,"
|
||||
category="-1,"
|
||||
nexusFileStatus=1
|
||||
installationFile=inory-dynamicgoons-1.1.0.rar
|
||||
installationFile=inory-dynamicgoons-1.1.1.rar
|
||||
repository=Nexus
|
||||
ignoredVersion=
|
||||
comments=
|
||||
notes=
|
||||
nexusDescription=
|
||||
url=
|
||||
hasCustomURL=true
|
||||
hasCustomURL=false
|
||||
lastNexusQuery=
|
||||
lastNexusUpdate=
|
||||
nexusLastModified=2024-12-16T05:56:04Z
|
||||
nexusLastModified=2025-05-14T13:42:03Z
|
||||
nexusCategory=0
|
||||
converted=false
|
||||
validated=false
|
||||
|
||||
@@ -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(
|
||||
|
||||
+3
-3
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user