3.10.3-dev #43

Merged
chomp merged 3 commits from 3.10.3-dev into master 2024-12-15 06:52:52 -05:00
617 changed files with 7393 additions and 589 deletions

View File

@ -1,4 +1,4 @@
# Mod examples for v3.10.2
# Mod examples for v3.10.3
A collection of example mods that perform typical actions in SPT

View File

@ -3,11 +3,13 @@ import { OnUpdate } from "@spt/di/OnUpdate";
import { ICoreConfig } from "@spt/models/spt/config/ICoreConfig";
import { ConfigServer } from "@spt/servers/ConfigServer";
import { SaveServer } from "@spt/servers/SaveServer";
import { BackupService } from "@spt/services/BackupService";
export declare class SaveCallbacks implements OnLoad, OnUpdate {
protected saveServer: SaveServer;
protected configServer: ConfigServer;
protected backupService: BackupService;
protected coreConfig: ICoreConfig;
constructor(saveServer: SaveServer, configServer: ConfigServer);
constructor(saveServer: SaveServer, configServer: ConfigServer, backupService: BackupService);
onLoad(): Promise<void>;
getRoute(): string;
onUpdate(secondsSinceLastRun: number): Promise<boolean>;

View File

@ -1,5 +1,8 @@
import { IDialogueChatBot } from "@spt/helpers/Dialogue/IDialogueChatBot";
import { DialogueHelper } from "@spt/helpers/DialogueHelper";
import { NotificationSendHelper } from "@spt/helpers/NotificationSendHelper";
import { ProfileHelper } from "@spt/helpers/ProfileHelper";
import { IDeleteFriendRequest } from "@spt/models/eft/dialog/IDeleteFriendRequest";
import { IFriendRequestData } from "@spt/models/eft/dialog/IFriendRequestData";
import { IFriendRequestSendResponse } from "@spt/models/eft/dialog/IFriendRequestSendResponse";
import { IGetAllAttachmentsResponse } from "@spt/models/eft/dialog/IGetAllAttachmentsResponse";
@ -20,11 +23,13 @@ export declare class DialogueController {
protected saveServer: SaveServer;
protected timeUtil: TimeUtil;
protected dialogueHelper: DialogueHelper;
protected notificationSendHelper: NotificationSendHelper;
protected profileHelper: ProfileHelper;
protected mailSendService: MailSendService;
protected localisationService: LocalisationService;
protected configServer: ConfigServer;
protected dialogueChatBots: IDialogueChatBot[];
constructor(logger: ILogger, saveServer: SaveServer, timeUtil: TimeUtil, dialogueHelper: DialogueHelper, mailSendService: MailSendService, localisationService: LocalisationService, configServer: ConfigServer, dialogueChatBots: IDialogueChatBot[]);
constructor(logger: ILogger, saveServer: SaveServer, timeUtil: TimeUtil, dialogueHelper: DialogueHelper, notificationSendHelper: NotificationSendHelper, profileHelper: ProfileHelper, mailSendService: MailSendService, localisationService: LocalisationService, configServer: ConfigServer, dialogueChatBots: IDialogueChatBot[]);
registerChatBot(chatBot: IDialogueChatBot): void;
/** Handle onUpdate spt event */
update(): void;
@ -151,4 +156,6 @@ export declare class DialogueController {
protected messageHasExpired(message: IMessage): boolean;
/** Handle client/friend/request/send */
sendFriendRequest(sessionID: string, request: IFriendRequestData): IFriendRequestSendResponse;
/** Handle client/friend/delete */
deleteFriend(sessionID: string, request: IDeleteFriendRequest): void;
}

View File

@ -72,6 +72,7 @@ export declare class QuestController {
*/
protected addTaskConditionCountersToProfile(questConditions: IQuestCondition[], pmcData: IPmcData, questId: string): void;
/**
* TODO - Move this code into RepeatableQuestController
* Handle the client accepting a repeatable quest and starting it
* Send starting rewards if any to player and
* Send start notification if any to player
@ -81,7 +82,6 @@ export declare class QuestController {
* @returns IItemEventRouterResponse
*/
acceptRepeatableQuest(pmcData: IPmcData, acceptedQuest: IAcceptQuestRequestData, sessionID: string): IItemEventRouterResponse;
protected createAcceptedQuestClientResponse(sessionID: string, pmcData: IPmcData, repeatableQuestProfile: IRepeatableQuest): IItemEventRouterResponse;
/**
* Look for an accepted quest inside player profile, return matching
* @param pmcData Profile to search through

View File

@ -147,6 +147,18 @@ export declare class RepeatableQuestController {
* @returns IItemEventRouterResponse
*/
changeRepeatableQuest(pmcData: IPmcData, changeRequest: IRepeatableQuestChangeRequest, sessionID: string): IItemEventRouterResponse;
/**
* Remove the provided quest from pmc and scav character profiles
* @param fullProfile Profile to remove quest from
* @param questToReplaceId Quest id to remove from profile
*/
protected removeQuestFromProfile(fullProfile: ISptProfile, questToReplaceId: string): void;
/**
* Clean up the repeatables `changeRequirement` dictionary of expired data
* @param repeatablesOfTypeInProfile The repeatables that have the replaced and new quest
* @param replacedQuestId Id of the replaced quest
*/
protected cleanUpRepeatableChangeRequirements(repeatablesOfTypeInProfile: IPmcDataRepeatableQuest, replacedQuestId: string): void;
/**
* Find a repeatable (daily/weekly/scav) from a players profile by its id
* @param questId Id of quest to find
@ -154,7 +166,7 @@ export declare class RepeatableQuestController {
* @returns IGetRepeatableByIdResult
*/
protected getRepeatableById(questId: string, pmcData: IPmcData): IGetRepeatableByIdResult;
protected attemptToGenerateRepeatableQuest(pmcData: IPmcData, questTypePool: IQuestTypePool, repeatableConfig: IRepeatableQuestConfig): IRepeatableQuest;
protected attemptToGenerateRepeatableQuest(sessionId: string, pmcData: IPmcData, questTypePool: IQuestTypePool, repeatableConfig: IRepeatableQuestConfig): IRepeatableQuest;
/**
* Some accounts have access to free repeatable quest refreshes
* Track the usage of them inside players profile

View File

@ -33,13 +33,14 @@ export declare class RepeatableQuestGenerator {
/**
* This method is called by /GetClientRepeatableQuests/ and creates one element of quest type format (see assets/database/templates/repeatableQuests.json).
* It randomly draws a quest type (currently Elimination, Completion or Exploration) as well as a trader who is providing the quest
* @param sessionId Session id
* @param pmcLevel Player's level for requested items and reward generation
* @param pmcTraderInfo Players traper standing/rep levels
* @param questTypePool Possible quest types pool
* @param repeatableConfig Repeatable quest config
* @returns IRepeatableQuest
*/
generateRepeatableQuest(pmcLevel: number, pmcTraderInfo: Record<string, ITraderInfo>, questTypePool: IQuestTypePool, repeatableConfig: IRepeatableQuestConfig): IRepeatableQuest;
generateRepeatableQuest(sessionId: string, pmcLevel: number, pmcTraderInfo: Record<string, ITraderInfo>, questTypePool: IQuestTypePool, repeatableConfig: IRepeatableQuestConfig): IRepeatableQuest;
/**
* Generate a randomised Elimination quest
* @param pmcLevel Player's level for requested items and reward generation
@ -48,7 +49,7 @@ export declare class RepeatableQuestGenerator {
* @param repeatableConfig The configuration for the repeatably kind (daily, weekly) as configured in QuestConfig for the requestd quest
* @returns Object of quest type format for "Elimination" (see assets/database/templates/repeatableQuests.json)
*/
protected generateEliminationQuest(pmcLevel: number, traderId: string, questTypePool: IQuestTypePool, repeatableConfig: IRepeatableQuestConfig): IRepeatableQuest;
protected generateEliminationQuest(sessionid: string, pmcLevel: number, traderId: string, questTypePool: IQuestTypePool, repeatableConfig: IRepeatableQuestConfig): IRepeatableQuest;
/**
* Get a number of kills neded to complete elimination quest
* @param targetKey Target type desired e.g. anyPmc/bossBully/Savage
@ -83,7 +84,7 @@ export declare class RepeatableQuestGenerator {
* @param {object} repeatableConfig The configuration for the repeatably kind (daily, weekly) as configured in QuestConfig for the requestd quest
* @returns {object} object of quest type format for "Completion" (see assets/database/templates/repeatableQuests.json)
*/
protected generateCompletionQuest(pmcLevel: number, traderId: string, repeatableConfig: IRepeatableQuestConfig): IRepeatableQuest;
protected generateCompletionQuest(sessionId: string, pmcLevel: number, traderId: string, repeatableConfig: IRepeatableQuestConfig): IRepeatableQuest;
/**
* A repeatable quest, besides some more or less static components, exists of reward and condition (see assets/database/templates/repeatableQuests.json)
* This is a helper method for GenerateCompletionQuest to create a completion condition (of which a completion quest theoretically can have many)
@ -102,7 +103,7 @@ export declare class RepeatableQuestGenerator {
* @param {object} repeatableConfig The configuration for the repeatably kind (daily, weekly) as configured in QuestConfig for the requestd quest
* @returns {object} object of quest type format for "Exploration" (see assets/database/templates/repeatableQuests.json)
*/
protected generateExplorationQuest(pmcLevel: number, traderId: string, questTypePool: IQuestTypePool, repeatableConfig: IRepeatableQuestConfig): IRepeatableQuest;
protected generateExplorationQuest(sessionId: string, pmcLevel: number, traderId: string, questTypePool: IQuestTypePool, repeatableConfig: IRepeatableQuestConfig): IRepeatableQuest;
/**
* Filter a maps exits to just those for the desired side
* @param locationKey Map id (e.g. factory4_day)
@ -110,7 +111,7 @@ export declare class RepeatableQuestGenerator {
* @returns Array of Exit objects
*/
protected getLocationExitsForSide(locationKey: string, playerSide: string): IExit[];
protected generatePickupQuest(pmcLevel: number, traderId: string, questTypePool: IQuestTypePool, repeatableConfig: IRepeatableQuestConfig): IRepeatableQuest;
protected generatePickupQuest(sessionId: string, pmcLevel: number, traderId: string, questTypePool: IQuestTypePool, repeatableConfig: IRepeatableQuestConfig): IRepeatableQuest;
/**
* Convert a location into an quest code can read (e.g. factory4_day into 55f2d3fd4bdc2d5f408b4567)
* @param locationKey e.g factory4_day
@ -135,5 +136,5 @@ export declare class RepeatableQuestGenerator {
* @returns {object} Object which contains the base elements for repeatable quests of the requests type
* (needs to be filled with reward and conditions by called to make a valid quest)
*/
protected generateRepeatableTemplate(type: string, traderId: string, side: string): IRepeatableQuest;
protected generateRepeatableTemplate(type: string, traderId: string, side: string, sessionId: string): IRepeatableQuest;
}

View File

@ -12,6 +12,7 @@ import { DatabaseService } from "@spt/services/DatabaseService";
import { ItemFilterService } from "@spt/services/ItemFilterService";
import { LocalisationService } from "@spt/services/LocalisationService";
import { SeasonalEventService } from "@spt/services/SeasonalEventService";
import { HashUtil } from "@spt/utils/HashUtil";
import { MathUtil } from "@spt/utils/MathUtil";
import { ObjectId } from "@spt/utils/ObjectId";
import { RandomUtil } from "@spt/utils/RandomUtil";
@ -19,6 +20,7 @@ import { ICloner } from "@spt/utils/cloners/ICloner";
export declare class RepeatableQuestRewardGenerator {
protected logger: ILogger;
protected randomUtil: RandomUtil;
protected hashUtil: HashUtil;
protected mathUtil: MathUtil;
protected databaseService: DatabaseService;
protected itemHelper: ItemHelper;
@ -31,7 +33,7 @@ export declare class RepeatableQuestRewardGenerator {
protected configServer: ConfigServer;
protected cloner: ICloner;
protected questConfig: IQuestConfig;
constructor(logger: ILogger, randomUtil: RandomUtil, mathUtil: MathUtil, databaseService: DatabaseService, itemHelper: ItemHelper, presetHelper: PresetHelper, handbookHelper: HandbookHelper, localisationService: LocalisationService, objectId: ObjectId, itemFilterService: ItemFilterService, seasonalEventService: SeasonalEventService, configServer: ConfigServer, cloner: ICloner);
constructor(logger: ILogger, randomUtil: RandomUtil, hashUtil: HashUtil, mathUtil: MathUtil, databaseService: DatabaseService, itemHelper: ItemHelper, presetHelper: PresetHelper, handbookHelper: HandbookHelper, localisationService: LocalisationService, objectId: ObjectId, itemFilterService: ItemFilterService, seasonalEventService: SeasonalEventService, configServer: ConfigServer, cloner: ICloner);
/**
* Generate the reward for a mission. A reward can consist of:
* - Experience
@ -127,7 +129,7 @@ export declare class RepeatableQuestRewardGenerator {
* @param preset Optional array of preset items
* @returns {object} Object of "Reward"-item-type
*/
protected generateItemReward(tpl: string, count: number, index: number): IQuestReward;
protected generateItemReward(tpl: string, count: number, index: number, foundInRaid?: boolean): IQuestReward;
/**
* Helper to create a reward item structured as required by the client
*
@ -137,7 +139,7 @@ export declare class RepeatableQuestRewardGenerator {
* @param preset Optional array of preset items
* @returns {object} Object of "Reward"-item-type
*/
protected generatePresetReward(tpl: string, count: number, index: number, preset?: IItem[]): IQuestReward;
protected generatePresetReward(tpl: string, count: number, index: number, preset?: IItem[], foundInRaid?: boolean): IQuestReward;
/**
* Picks rewardable items from items.json
* This means they must:

View File

@ -2,6 +2,7 @@ import { ItemHelper } from "@spt/helpers/ItemHelper";
import { IPmcData } from "@spt/models/eft/common/IPmcData";
import { Common, ICounterKeyValue, IStats } from "@spt/models/eft/common/tables/IBotBase";
import { IItem } from "@spt/models/eft/common/tables/IItem";
import { ISearchFriendResponse } from "@spt/models/eft/profile/ISearchFriendResponse";
import { ISptProfile } from "@spt/models/eft/profile/ISptProfile";
import { IValidateNicknameRequestData } from "@spt/models/eft/profile/IValidateNicknameRequestData";
import { BonusType } from "@spt/models/enums/BonusType";
@ -90,6 +91,24 @@ export declare class ProfileHelper {
* @returns ISptProfile object
*/
getFullProfile(sessionID: string): ISptProfile | undefined;
/**
* Get full representation of a players profile JSON by the account ID, or undefined if not found
* @param accountId Account ID to find
* @returns
*/
getFullProfileByAccountId(accountID: string): ISptProfile | undefined;
/**
* Retrieve a ChatRoomMember formatted profile for the given session ID
* @param sessionID The session ID to return the profile for
* @returns
*/
getChatRoomMemberFromSessionId(sessionID: string): ISearchFriendResponse | undefined;
/**
* Retrieve a ChatRoomMember formatted profile for the given PMC profile data
* @param pmcProfile The PMC profile data to format into a ChatRoomMember structure
* @returns
*/
getChatRoomMemberFromPmcProfile(pmcProfile: IPmcData): ISearchFriendResponse;
/**
* Get a PMC profile by its session id
* @param sessionID Profile id to return

View File

@ -11,23 +11,36 @@ export interface ILocationBase {
Banners: IBanner[];
BossLocationSpawn: IBossLocationSpawn[];
BotAssault: number;
/** Weighting on how likely a bot will be Easy difficulty */
BotEasy: number;
/** Weighting on how likely a bot will be Hard difficulty */
BotHard: number;
/** Weighting on how likely a bot will be Impossible difficulty */
BotImpossible: number;
BotLocationModifier: IBotLocationModifier;
BotMarksman: number;
/** Maximum Number of bots that are currently alive/loading/delayed */
BotMax: number;
/** Is not used in 33420 */
BotMaxPlayer: number;
/** Is not used in 33420 */
BotMaxTimePlayer: number;
/** Does not even exist in the client in 33420 */
BotMaxPvE: number;
/** Weighting on how likely a bot will be Normal difficulty */
BotNormal: number;
/** How many bot slots that need to be open before trying to spawn new bots. */
BotSpawnCountStep: number;
/** How often to check if bots are spawn-able. In seconds */
BotSpawnPeriodCheck: number;
/** The bot spawn will toggle on and off in intervals of Off(Min/Max) and On(Min/Max) */
BotSpawnTimeOffMax: number;
BotSpawnTimeOffMin: number;
BotSpawnTimeOnMax: number;
BotSpawnTimeOnMin: number;
/** How soon bots will be allowed to spawn */
BotStart: number;
/** After this long bots will no longer spawn */
BotStop: number;
Description: string;
DisabledForScav: boolean;

View File

@ -33,6 +33,11 @@ export interface IQuest {
changeQuestMessageText: string;
/** "Pmc" or "Scav" */
side: string;
acceptanceAndFinishingSource: string;
progressSource: string;
rankingModes: string[];
gameModes: string[];
arenaLocations: string[];
/** Status of quest to player */
sptStatus?: QuestStatus;
}
@ -148,8 +153,10 @@ export interface IQuestReward {
loyaltyLevel?: number;
/** Hideout area id */
traderId?: string;
isEncoded?: boolean;
unknown?: boolean;
findInRaid?: boolean;
gameMode?: string[];
/** Game editions whitelisted to get reward */
availableInGameEditions?: string[];
/** Game editions blacklisted from getting reward */

View File

@ -3,6 +3,12 @@ export interface IRepeatableQuest extends IQuest {
changeCost: IChangeCost[];
changeStandingCost: number;
sptRepatableGroupName: string;
acceptanceAndFinishingSource: string;
progressSource: string;
rankingModes: string[];
gameModes: string[];
arenaLocations: string[];
questStatus: IRepeatableQuestStatus;
}
export interface IRepeatableQuestDatabase {
templates: IRepeatableTemplates;
@ -10,6 +16,14 @@ export interface IRepeatableQuestDatabase {
data: IOptions;
samples: ISampleQuests[];
}
export interface IRepeatableQuestStatus {
id: string;
uid: string;
qid: string;
startTime: number;
status: number;
statusTimers: any;
}
export interface IRepeatableTemplates {
Elimination: IQuest;
Completion: IQuest;

View File

@ -8,4 +8,5 @@ export interface Info {
Side: string;
Level: number;
MemberCategory: number;
SelectedMemberCategory: number;
}

View File

@ -19,6 +19,8 @@ export interface ISptProfile {
traderPurchases?: Record<string, Record<string, ITraderPurchaseData>>;
/** Achievements earned by player */
achievements: Record<string, number>;
/** List of friend profile IDs */
friends: string[];
}
export declare class ITraderPurchaseData {
count: number;

View File

@ -0,0 +1,5 @@
import { IWsNotificationEvent } from "@spt/models/eft/ws/IWsNotificationEvent";
import { ISearchFriendResponse } from "../profile/ISearchFriendResponse";
export interface IWsFriendsListAccept extends IWsNotificationEvent {
profile: ISearchFriendResponse;
}

View File

@ -1,4 +1,4 @@
export interface IWsNotificationEvent {
type: string;
eventId: string;
eventId?: string;
}

View File

@ -1,5 +1,6 @@
export declare enum ConfigTypes {
AIRDROP = "spt-airdrop",
BACKUP = "spt-backup",
BOT = "spt-bot",
PMC = "spt-pmc",
CORE = "spt-core",

View File

@ -0,0 +1,12 @@
import { IBaseConfig } from "@spt/models/spt/config/IBaseConfig";
export interface IBackupConfig extends IBaseConfig {
kind: "spt-backup";
enabled: boolean;
maxBackups: number;
directory: string;
backupInterval: IBackupConfigInterval;
}
export interface IBackupConfigInterval {
enabled: boolean;
intervalMinutes: number;
}

View File

@ -7,6 +7,8 @@ export interface IItemConfig extends IBaseConfig {
lootableItemBlacklist: string[];
/** items that should not be given as rewards */
rewardItemBlacklist: string[];
/** Item base types that should not be given as rewards */
rewardItemTypeBlacklist: string[];
/** Items that can only be found on bosses */
bossItems: string[];
handbookPriceOverride: Record<string, IHandbookPriceOverride>;

View File

@ -0,0 +1,98 @@
import { PreSptModLoader } from "@spt/loaders/PreSptModLoader";
import { IBackupConfig } from "@spt/models/spt/config/IBackupConfig";
import { ILogger } from "@spt/models/spt/utils/ILogger";
import { ConfigServer } from "@spt/servers/ConfigServer";
export declare class BackupService {
protected logger: ILogger;
protected preSptModLoader: PreSptModLoader;
protected configServer: ConfigServer;
protected backupConfig: IBackupConfig;
protected readonly activeServerMods: string[];
protected readonly profileDir = "./user/profiles";
constructor(logger: ILogger, preSptModLoader: PreSptModLoader, configServer: ConfigServer);
/**
* Initializes the backup process.
*
* This method orchestrates the profile backup service. Handles copying profiles to a backup directory and cleaning
* up old backups if the number exceeds the configured maximum.
*
* @returns A promise that resolves when the backup process is complete.
*/
init(): Promise<void>;
/**
* Fetches the names of all JSON files in the profile directory.
*
* This method normalizes the profile directory path and reads all files within it. It then filters the files to
* include only those with a `.json` extension and returns their names.
*
* @returns A promise that resolves to an array of JSON file names.
*/
protected fetchProfileFiles(): Promise<string[]>;
/**
* Check to see if the backup service is enabled via the config.
*
* @returns True if enabled, false otherwise.
*/
protected isEnabled(): boolean;
/**
* Generates the target directory path for the backup. The directory path is constructed using the `directory` from
* the configuration and the current backup date.
*
* @returns The target directory path for the backup.
*/
protected generateBackupTargetDir(): string;
/**
* Generates a formatted backup date string in the format `YYYY-MM-DD_hh-mm-ss`.
*
* @returns The formatted backup date string.
*/
protected generateBackupDate(): string;
/**
* Cleans up old backups in the backup directory.
*
* This method reads the backup directory, and sorts backups by modification time. If the number of backups exceeds
* the configured maximum, it deletes the oldest backups.
*
* @returns A promise that resolves when the cleanup is complete.
*/
protected cleanBackups(): Promise<void>;
/**
* Retrieves and sorts the backup file paths from the specified directory.
*
* @param dir - The directory to search for backup files.
* @returns A promise that resolves to an array of sorted backup file paths.
*/
private getBackupPaths;
/**
* Compares two backup folder names based on their extracted dates.
*
* @param a - The name of the first backup folder.
* @param b - The name of the second backup folder.
* @returns The difference in time between the two dates in milliseconds, or `null` if either date is invalid.
*/
private compareBackupDates;
/**
* Extracts a date from a folder name string formatted as `YYYY-MM-DD_hh-mm-ss`.
*
* @param folderName - The name of the folder from which to extract the date.
* @returns A Date object if the folder name is in the correct format, otherwise null.
*/
private extractDateFromFolderName;
/**
* Removes excess backups from the backup directory.
*
* @param backups - An array of backup file names to be removed.
* @returns A promise that resolves when all specified backups have been removed.
*/
private removeExcessBackups;
/**
* Start the backup interval if enabled in the configuration.
*/
protected startBackupInterval(): void;
/**
* Get an array of active server mod details.
*
* @returns An array of mod names.
*/
protected getActiveServerMods(): string[];
}

View File

@ -54,6 +54,16 @@ export declare class CircleOfCultistService {
* @returns IItemEventRouterResponse
*/
startSacrifice(sessionId: string, pmcData: IPmcData, request: IHideoutCircleOfCultistProductionStartRequestData): IItemEventRouterResponse;
/**
* Attempt to add all rewards to cultist circle, if they dont fit remove one and try again until they fit
* @param sessionId Session id
* @param pmcData Player profile
* @param rewards Rewards to send to player
* @param containerGrid Cultist grid to add rewards to
* @param cultistCircleStashId Stash id
* @param output Client output
*/
protected addRewardsToCircleContainer(sessionId: string, pmcData: IPmcData, rewards: IItem[][], containerGrid: number[][], cultistCircleStashId: string, output: IItemEventRouterResponse): void;
/**
* Create a map of the possible direct rewards, keyed by the items needed to be sacrificed
* @param directRewards Direct rewards array from hideout config
@ -156,7 +166,7 @@ export declare class CircleOfCultistService {
* @param itemRewardBlacklist Items not to add to pool
* @param rewardPool Pool to add items to
*/
protected addTaskItemRequirementsToRewardPool(pmcData: IPmcData, itemRewardBlacklist: string[], rewardPool: Set<string>): void;
protected addTaskItemRequirementsToRewardPool(pmcData: IPmcData, itemRewardBlacklist: Set<string>, rewardPool: Set<string>): void;
/**
* Adds items the player needs to complete hideout crafts/upgrades to the reward pool
* @param hideoutDbData Hideout area data
@ -164,7 +174,7 @@ export declare class CircleOfCultistService {
* @param itemRewardBlacklist Items not to add to pool
* @param rewardPool Pool to add items to
*/
protected addHideoutUpgradeRequirementsToRewardPool(hideoutDbData: IHideout, pmcData: IPmcData, itemRewardBlacklist: string[], rewardPool: Set<string>): void;
protected addHideoutUpgradeRequirementsToRewardPool(hideoutDbData: IHideout, pmcData: IPmcData, itemRewardBlacklist: Set<string>, rewardPool: Set<string>): void;
/**
* Get all active hideout areas
* @param areas Hideout areas to iterate over
@ -174,11 +184,11 @@ export declare class CircleOfCultistService {
/**
* Get array of random reward items
* @param rewardPool Reward pool to add to
* @param itemRewardBlacklist Reward Blacklist
* @param itemRewardBlacklist Item tpls to ignore
* @param itemsShouldBeHighValue Should these items meet the valuable threshold
* @returns rewardPool
* @returns Set of item tpls
*/
protected generateRandomisedItemsAndAddToRewardPool(rewardPool: Set<string>, itemRewardBlacklist: string[], itemsShouldBeHighValue: boolean): Set<string>;
protected generateRandomisedItemsAndAddToRewardPool(rewardPool: Set<string>, itemRewardBlacklist: Set<string>, itemsShouldBeHighValue: boolean): Set<string>;
/**
* Iterate over passed in hideout requirements and return the Item
* @param requirements Requirements to iterate over

View File

@ -36,6 +36,11 @@ export declare class ItemFilterService {
* @returns string array of item tpls
*/
getItemRewardBlacklist(): string[];
/**
* Get an array of item types that should never be given as a reward to player
* @returns string array of item base ids
*/
getItemRewardBaseTypeBlacklist(): string[];
/**
* Return every template id blacklisted in config/item.json
* @returns string array of blacklisted tempalte ids

View File

@ -4,10 +4,10 @@ import { ICoreConfig } from "@spt/models/spt/config/ICoreConfig";
import { ILogger } from "@spt/models/spt/utils/ILogger";
import { ConfigServer } from "@spt/servers/ConfigServer";
import { HttpServer } from "@spt/servers/HttpServer";
import { DatabaseService } from "@spt/services/DatabaseService";
import { LocalisationService } from "@spt/services/LocalisationService";
import { EncodingUtil } from "@spt/utils/EncodingUtil";
import { TimeUtil } from "@spt/utils/TimeUtil";
import { DatabaseService } from "@spt/services/DatabaseService";
export declare class App {
protected logger: ILogger;
protected timeUtil: TimeUtil;

View File

@ -106,6 +106,11 @@ export declare class RandomUtil {
protected cloner: ICloner;
protected logger: ILogger;
constructor(cloner: ICloner, logger: ILogger);
/**
* The IEEE-754 standard for double-precision floating-point numbers limits the number of digits (including both
* integer + fractional parts) to about 1517 significant digits. 15 is a safe upper bound, so we'll use that.
*/
private static readonly MAX_SIGNIFICANT_DIGITS;
/**
* Generates a secure random number between 0 (inclusive) and 1 (exclusive).
*
@ -116,6 +121,16 @@ export declare class RandomUtil {
* @returns A secure random number between 0 (inclusive) and 1 (exclusive).
*/
private getSecureRandomNumber;
/**
* Determines the number of decimal places in a number.
*
* @param num - The number to analyze.
* @returns The number of decimal places, or 0 if none exist.
* @remarks There is a mathematical way to determine this, but it's not as simple as it seams due to floating point
* precision issues. This method is a simple workaround that converts the number to a string and splits it.
* It's not the most efficient but it *is* the most reliable and easy to understand. Come at me.
*/
private getNumberPrecision;
/**
* Generates a random integer between the specified minimum and maximum values, inclusive.
*
@ -173,7 +188,7 @@ export declare class RandomUtil {
/**
* Returns a random string from the provided array of strings.
*
* This method is separate from getArrayValue so we can use a generic inferance with getArrayValue.
* This method is separate from getArrayValue so we can use a generic inference with getArrayValue.
*
* @param arr - The array of strings to select a random value from.
* @returns A randomly selected string from the array.
@ -225,12 +240,27 @@ export declare class RandomUtil {
getNormallyDistributedRandomNumber(mean: number, sigma: number, attempt?: number): number;
/**
* Generates a random integer between the specified range.
* Low and high parameters are floored to integers.
*
* TODO: v3.11 - This method should not accept non-integer numbers.
*
* @param low - The lower bound of the range (inclusive).
* @param high - The upper bound of the range (exclusive). If not provided, the range will be from 0 to `low`.
* @returns A random integer within the specified range.
*/
randInt(low: number, high?: number): number;
/**
* Generates a random number between two given values with optional precision.
*
* @param value1 - The first value to determine the range.
* @param value2 - The second value to determine the range. If not provided, 0 is used.
* @param precision - The number of decimal places to round the result to. Must be a positive integer between 0
* and MAX_PRECISION, inclusive. If not provided, precision is determined by the input values.
* @returns A random floating-point number between `value1` and `value2` (inclusive) with the specified precision.
* @throws Will throw an error if `precision` is not a positive integer, if `value1` or `value2` are not finite
* numbers, or if the precision exceeds the maximum allowed for the given values.
*/
randNum(value1: number, value2?: number, precision?: number | null): number;
/**
* Draws a specified number of random elements from a given list.
*

View File

@ -3,11 +3,13 @@ import { OnUpdate } from "@spt/di/OnUpdate";
import { ICoreConfig } from "@spt/models/spt/config/ICoreConfig";
import { ConfigServer } from "@spt/servers/ConfigServer";
import { SaveServer } from "@spt/servers/SaveServer";
import { BackupService } from "@spt/services/BackupService";
export declare class SaveCallbacks implements OnLoad, OnUpdate {
protected saveServer: SaveServer;
protected configServer: ConfigServer;
protected backupService: BackupService;
protected coreConfig: ICoreConfig;
constructor(saveServer: SaveServer, configServer: ConfigServer);
constructor(saveServer: SaveServer, configServer: ConfigServer, backupService: BackupService);
onLoad(): Promise<void>;
getRoute(): string;
onUpdate(secondsSinceLastRun: number): Promise<boolean>;

View File

@ -1,5 +1,8 @@
import { IDialogueChatBot } from "@spt/helpers/Dialogue/IDialogueChatBot";
import { DialogueHelper } from "@spt/helpers/DialogueHelper";
import { NotificationSendHelper } from "@spt/helpers/NotificationSendHelper";
import { ProfileHelper } from "@spt/helpers/ProfileHelper";
import { IDeleteFriendRequest } from "@spt/models/eft/dialog/IDeleteFriendRequest";
import { IFriendRequestData } from "@spt/models/eft/dialog/IFriendRequestData";
import { IFriendRequestSendResponse } from "@spt/models/eft/dialog/IFriendRequestSendResponse";
import { IGetAllAttachmentsResponse } from "@spt/models/eft/dialog/IGetAllAttachmentsResponse";
@ -20,11 +23,13 @@ export declare class DialogueController {
protected saveServer: SaveServer;
protected timeUtil: TimeUtil;
protected dialogueHelper: DialogueHelper;
protected notificationSendHelper: NotificationSendHelper;
protected profileHelper: ProfileHelper;
protected mailSendService: MailSendService;
protected localisationService: LocalisationService;
protected configServer: ConfigServer;
protected dialogueChatBots: IDialogueChatBot[];
constructor(logger: ILogger, saveServer: SaveServer, timeUtil: TimeUtil, dialogueHelper: DialogueHelper, mailSendService: MailSendService, localisationService: LocalisationService, configServer: ConfigServer, dialogueChatBots: IDialogueChatBot[]);
constructor(logger: ILogger, saveServer: SaveServer, timeUtil: TimeUtil, dialogueHelper: DialogueHelper, notificationSendHelper: NotificationSendHelper, profileHelper: ProfileHelper, mailSendService: MailSendService, localisationService: LocalisationService, configServer: ConfigServer, dialogueChatBots: IDialogueChatBot[]);
registerChatBot(chatBot: IDialogueChatBot): void;
/** Handle onUpdate spt event */
update(): void;
@ -151,4 +156,6 @@ export declare class DialogueController {
protected messageHasExpired(message: IMessage): boolean;
/** Handle client/friend/request/send */
sendFriendRequest(sessionID: string, request: IFriendRequestData): IFriendRequestSendResponse;
/** Handle client/friend/delete */
deleteFriend(sessionID: string, request: IDeleteFriendRequest): void;
}

View File

@ -72,6 +72,7 @@ export declare class QuestController {
*/
protected addTaskConditionCountersToProfile(questConditions: IQuestCondition[], pmcData: IPmcData, questId: string): void;
/**
* TODO - Move this code into RepeatableQuestController
* Handle the client accepting a repeatable quest and starting it
* Send starting rewards if any to player and
* Send start notification if any to player
@ -81,7 +82,6 @@ export declare class QuestController {
* @returns IItemEventRouterResponse
*/
acceptRepeatableQuest(pmcData: IPmcData, acceptedQuest: IAcceptQuestRequestData, sessionID: string): IItemEventRouterResponse;
protected createAcceptedQuestClientResponse(sessionID: string, pmcData: IPmcData, repeatableQuestProfile: IRepeatableQuest): IItemEventRouterResponse;
/**
* Look for an accepted quest inside player profile, return matching
* @param pmcData Profile to search through

View File

@ -147,6 +147,18 @@ export declare class RepeatableQuestController {
* @returns IItemEventRouterResponse
*/
changeRepeatableQuest(pmcData: IPmcData, changeRequest: IRepeatableQuestChangeRequest, sessionID: string): IItemEventRouterResponse;
/**
* Remove the provided quest from pmc and scav character profiles
* @param fullProfile Profile to remove quest from
* @param questToReplaceId Quest id to remove from profile
*/
protected removeQuestFromProfile(fullProfile: ISptProfile, questToReplaceId: string): void;
/**
* Clean up the repeatables `changeRequirement` dictionary of expired data
* @param repeatablesOfTypeInProfile The repeatables that have the replaced and new quest
* @param replacedQuestId Id of the replaced quest
*/
protected cleanUpRepeatableChangeRequirements(repeatablesOfTypeInProfile: IPmcDataRepeatableQuest, replacedQuestId: string): void;
/**
* Find a repeatable (daily/weekly/scav) from a players profile by its id
* @param questId Id of quest to find
@ -154,7 +166,7 @@ export declare class RepeatableQuestController {
* @returns IGetRepeatableByIdResult
*/
protected getRepeatableById(questId: string, pmcData: IPmcData): IGetRepeatableByIdResult;
protected attemptToGenerateRepeatableQuest(pmcData: IPmcData, questTypePool: IQuestTypePool, repeatableConfig: IRepeatableQuestConfig): IRepeatableQuest;
protected attemptToGenerateRepeatableQuest(sessionId: string, pmcData: IPmcData, questTypePool: IQuestTypePool, repeatableConfig: IRepeatableQuestConfig): IRepeatableQuest;
/**
* Some accounts have access to free repeatable quest refreshes
* Track the usage of them inside players profile

View File

@ -33,13 +33,14 @@ export declare class RepeatableQuestGenerator {
/**
* This method is called by /GetClientRepeatableQuests/ and creates one element of quest type format (see assets/database/templates/repeatableQuests.json).
* It randomly draws a quest type (currently Elimination, Completion or Exploration) as well as a trader who is providing the quest
* @param sessionId Session id
* @param pmcLevel Player's level for requested items and reward generation
* @param pmcTraderInfo Players traper standing/rep levels
* @param questTypePool Possible quest types pool
* @param repeatableConfig Repeatable quest config
* @returns IRepeatableQuest
*/
generateRepeatableQuest(pmcLevel: number, pmcTraderInfo: Record<string, ITraderInfo>, questTypePool: IQuestTypePool, repeatableConfig: IRepeatableQuestConfig): IRepeatableQuest;
generateRepeatableQuest(sessionId: string, pmcLevel: number, pmcTraderInfo: Record<string, ITraderInfo>, questTypePool: IQuestTypePool, repeatableConfig: IRepeatableQuestConfig): IRepeatableQuest;
/**
* Generate a randomised Elimination quest
* @param pmcLevel Player's level for requested items and reward generation
@ -48,7 +49,7 @@ export declare class RepeatableQuestGenerator {
* @param repeatableConfig The configuration for the repeatably kind (daily, weekly) as configured in QuestConfig for the requestd quest
* @returns Object of quest type format for "Elimination" (see assets/database/templates/repeatableQuests.json)
*/
protected generateEliminationQuest(pmcLevel: number, traderId: string, questTypePool: IQuestTypePool, repeatableConfig: IRepeatableQuestConfig): IRepeatableQuest;
protected generateEliminationQuest(sessionid: string, pmcLevel: number, traderId: string, questTypePool: IQuestTypePool, repeatableConfig: IRepeatableQuestConfig): IRepeatableQuest;
/**
* Get a number of kills neded to complete elimination quest
* @param targetKey Target type desired e.g. anyPmc/bossBully/Savage
@ -83,7 +84,7 @@ export declare class RepeatableQuestGenerator {
* @param {object} repeatableConfig The configuration for the repeatably kind (daily, weekly) as configured in QuestConfig for the requestd quest
* @returns {object} object of quest type format for "Completion" (see assets/database/templates/repeatableQuests.json)
*/
protected generateCompletionQuest(pmcLevel: number, traderId: string, repeatableConfig: IRepeatableQuestConfig): IRepeatableQuest;
protected generateCompletionQuest(sessionId: string, pmcLevel: number, traderId: string, repeatableConfig: IRepeatableQuestConfig): IRepeatableQuest;
/**
* A repeatable quest, besides some more or less static components, exists of reward and condition (see assets/database/templates/repeatableQuests.json)
* This is a helper method for GenerateCompletionQuest to create a completion condition (of which a completion quest theoretically can have many)
@ -102,7 +103,7 @@ export declare class RepeatableQuestGenerator {
* @param {object} repeatableConfig The configuration for the repeatably kind (daily, weekly) as configured in QuestConfig for the requestd quest
* @returns {object} object of quest type format for "Exploration" (see assets/database/templates/repeatableQuests.json)
*/
protected generateExplorationQuest(pmcLevel: number, traderId: string, questTypePool: IQuestTypePool, repeatableConfig: IRepeatableQuestConfig): IRepeatableQuest;
protected generateExplorationQuest(sessionId: string, pmcLevel: number, traderId: string, questTypePool: IQuestTypePool, repeatableConfig: IRepeatableQuestConfig): IRepeatableQuest;
/**
* Filter a maps exits to just those for the desired side
* @param locationKey Map id (e.g. factory4_day)
@ -110,7 +111,7 @@ export declare class RepeatableQuestGenerator {
* @returns Array of Exit objects
*/
protected getLocationExitsForSide(locationKey: string, playerSide: string): IExit[];
protected generatePickupQuest(pmcLevel: number, traderId: string, questTypePool: IQuestTypePool, repeatableConfig: IRepeatableQuestConfig): IRepeatableQuest;
protected generatePickupQuest(sessionId: string, pmcLevel: number, traderId: string, questTypePool: IQuestTypePool, repeatableConfig: IRepeatableQuestConfig): IRepeatableQuest;
/**
* Convert a location into an quest code can read (e.g. factory4_day into 55f2d3fd4bdc2d5f408b4567)
* @param locationKey e.g factory4_day
@ -135,5 +136,5 @@ export declare class RepeatableQuestGenerator {
* @returns {object} Object which contains the base elements for repeatable quests of the requests type
* (needs to be filled with reward and conditions by called to make a valid quest)
*/
protected generateRepeatableTemplate(type: string, traderId: string, side: string): IRepeatableQuest;
protected generateRepeatableTemplate(type: string, traderId: string, side: string, sessionId: string): IRepeatableQuest;
}

View File

@ -12,6 +12,7 @@ import { DatabaseService } from "@spt/services/DatabaseService";
import { ItemFilterService } from "@spt/services/ItemFilterService";
import { LocalisationService } from "@spt/services/LocalisationService";
import { SeasonalEventService } from "@spt/services/SeasonalEventService";
import { HashUtil } from "@spt/utils/HashUtil";
import { MathUtil } from "@spt/utils/MathUtil";
import { ObjectId } from "@spt/utils/ObjectId";
import { RandomUtil } from "@spt/utils/RandomUtil";
@ -19,6 +20,7 @@ import { ICloner } from "@spt/utils/cloners/ICloner";
export declare class RepeatableQuestRewardGenerator {
protected logger: ILogger;
protected randomUtil: RandomUtil;
protected hashUtil: HashUtil;
protected mathUtil: MathUtil;
protected databaseService: DatabaseService;
protected itemHelper: ItemHelper;
@ -31,7 +33,7 @@ export declare class RepeatableQuestRewardGenerator {
protected configServer: ConfigServer;
protected cloner: ICloner;
protected questConfig: IQuestConfig;
constructor(logger: ILogger, randomUtil: RandomUtil, mathUtil: MathUtil, databaseService: DatabaseService, itemHelper: ItemHelper, presetHelper: PresetHelper, handbookHelper: HandbookHelper, localisationService: LocalisationService, objectId: ObjectId, itemFilterService: ItemFilterService, seasonalEventService: SeasonalEventService, configServer: ConfigServer, cloner: ICloner);
constructor(logger: ILogger, randomUtil: RandomUtil, hashUtil: HashUtil, mathUtil: MathUtil, databaseService: DatabaseService, itemHelper: ItemHelper, presetHelper: PresetHelper, handbookHelper: HandbookHelper, localisationService: LocalisationService, objectId: ObjectId, itemFilterService: ItemFilterService, seasonalEventService: SeasonalEventService, configServer: ConfigServer, cloner: ICloner);
/**
* Generate the reward for a mission. A reward can consist of:
* - Experience
@ -127,7 +129,7 @@ export declare class RepeatableQuestRewardGenerator {
* @param preset Optional array of preset items
* @returns {object} Object of "Reward"-item-type
*/
protected generateItemReward(tpl: string, count: number, index: number): IQuestReward;
protected generateItemReward(tpl: string, count: number, index: number, foundInRaid?: boolean): IQuestReward;
/**
* Helper to create a reward item structured as required by the client
*
@ -137,7 +139,7 @@ export declare class RepeatableQuestRewardGenerator {
* @param preset Optional array of preset items
* @returns {object} Object of "Reward"-item-type
*/
protected generatePresetReward(tpl: string, count: number, index: number, preset?: IItem[]): IQuestReward;
protected generatePresetReward(tpl: string, count: number, index: number, preset?: IItem[], foundInRaid?: boolean): IQuestReward;
/**
* Picks rewardable items from items.json
* This means they must:

View File

@ -2,6 +2,7 @@ import { ItemHelper } from "@spt/helpers/ItemHelper";
import { IPmcData } from "@spt/models/eft/common/IPmcData";
import { Common, ICounterKeyValue, IStats } from "@spt/models/eft/common/tables/IBotBase";
import { IItem } from "@spt/models/eft/common/tables/IItem";
import { ISearchFriendResponse } from "@spt/models/eft/profile/ISearchFriendResponse";
import { ISptProfile } from "@spt/models/eft/profile/ISptProfile";
import { IValidateNicknameRequestData } from "@spt/models/eft/profile/IValidateNicknameRequestData";
import { BonusType } from "@spt/models/enums/BonusType";
@ -90,6 +91,24 @@ export declare class ProfileHelper {
* @returns ISptProfile object
*/
getFullProfile(sessionID: string): ISptProfile | undefined;
/**
* Get full representation of a players profile JSON by the account ID, or undefined if not found
* @param accountId Account ID to find
* @returns
*/
getFullProfileByAccountId(accountID: string): ISptProfile | undefined;
/**
* Retrieve a ChatRoomMember formatted profile for the given session ID
* @param sessionID The session ID to return the profile for
* @returns
*/
getChatRoomMemberFromSessionId(sessionID: string): ISearchFriendResponse | undefined;
/**
* Retrieve a ChatRoomMember formatted profile for the given PMC profile data
* @param pmcProfile The PMC profile data to format into a ChatRoomMember structure
* @returns
*/
getChatRoomMemberFromPmcProfile(pmcProfile: IPmcData): ISearchFriendResponse;
/**
* Get a PMC profile by its session id
* @param sessionID Profile id to return

View File

@ -11,23 +11,36 @@ export interface ILocationBase {
Banners: IBanner[];
BossLocationSpawn: IBossLocationSpawn[];
BotAssault: number;
/** Weighting on how likely a bot will be Easy difficulty */
BotEasy: number;
/** Weighting on how likely a bot will be Hard difficulty */
BotHard: number;
/** Weighting on how likely a bot will be Impossible difficulty */
BotImpossible: number;
BotLocationModifier: IBotLocationModifier;
BotMarksman: number;
/** Maximum Number of bots that are currently alive/loading/delayed */
BotMax: number;
/** Is not used in 33420 */
BotMaxPlayer: number;
/** Is not used in 33420 */
BotMaxTimePlayer: number;
/** Does not even exist in the client in 33420 */
BotMaxPvE: number;
/** Weighting on how likely a bot will be Normal difficulty */
BotNormal: number;
/** How many bot slots that need to be open before trying to spawn new bots. */
BotSpawnCountStep: number;
/** How often to check if bots are spawn-able. In seconds */
BotSpawnPeriodCheck: number;
/** The bot spawn will toggle on and off in intervals of Off(Min/Max) and On(Min/Max) */
BotSpawnTimeOffMax: number;
BotSpawnTimeOffMin: number;
BotSpawnTimeOnMax: number;
BotSpawnTimeOnMin: number;
/** How soon bots will be allowed to spawn */
BotStart: number;
/** After this long bots will no longer spawn */
BotStop: number;
Description: string;
DisabledForScav: boolean;

View File

@ -33,6 +33,11 @@ export interface IQuest {
changeQuestMessageText: string;
/** "Pmc" or "Scav" */
side: string;
acceptanceAndFinishingSource: string;
progressSource: string;
rankingModes: string[];
gameModes: string[];
arenaLocations: string[];
/** Status of quest to player */
sptStatus?: QuestStatus;
}
@ -148,8 +153,10 @@ export interface IQuestReward {
loyaltyLevel?: number;
/** Hideout area id */
traderId?: string;
isEncoded?: boolean;
unknown?: boolean;
findInRaid?: boolean;
gameMode?: string[];
/** Game editions whitelisted to get reward */
availableInGameEditions?: string[];
/** Game editions blacklisted from getting reward */

View File

@ -3,6 +3,12 @@ export interface IRepeatableQuest extends IQuest {
changeCost: IChangeCost[];
changeStandingCost: number;
sptRepatableGroupName: string;
acceptanceAndFinishingSource: string;
progressSource: string;
rankingModes: string[];
gameModes: string[];
arenaLocations: string[];
questStatus: IRepeatableQuestStatus;
}
export interface IRepeatableQuestDatabase {
templates: IRepeatableTemplates;
@ -10,6 +16,14 @@ export interface IRepeatableQuestDatabase {
data: IOptions;
samples: ISampleQuests[];
}
export interface IRepeatableQuestStatus {
id: string;
uid: string;
qid: string;
startTime: number;
status: number;
statusTimers: any;
}
export interface IRepeatableTemplates {
Elimination: IQuest;
Completion: IQuest;

View File

@ -8,4 +8,5 @@ export interface Info {
Side: string;
Level: number;
MemberCategory: number;
SelectedMemberCategory: number;
}

View File

@ -19,6 +19,8 @@ export interface ISptProfile {
traderPurchases?: Record<string, Record<string, ITraderPurchaseData>>;
/** Achievements earned by player */
achievements: Record<string, number>;
/** List of friend profile IDs */
friends: string[];
}
export declare class ITraderPurchaseData {
count: number;

View File

@ -0,0 +1,5 @@
import { IWsNotificationEvent } from "@spt/models/eft/ws/IWsNotificationEvent";
import { ISearchFriendResponse } from "../profile/ISearchFriendResponse";
export interface IWsFriendsListAccept extends IWsNotificationEvent {
profile: ISearchFriendResponse;
}

View File

@ -1,4 +1,4 @@
export interface IWsNotificationEvent {
type: string;
eventId: string;
eventId?: string;
}

View File

@ -1,5 +1,6 @@
export declare enum ConfigTypes {
AIRDROP = "spt-airdrop",
BACKUP = "spt-backup",
BOT = "spt-bot",
PMC = "spt-pmc",
CORE = "spt-core",

View File

@ -0,0 +1,12 @@
import { IBaseConfig } from "@spt/models/spt/config/IBaseConfig";
export interface IBackupConfig extends IBaseConfig {
kind: "spt-backup";
enabled: boolean;
maxBackups: number;
directory: string;
backupInterval: IBackupConfigInterval;
}
export interface IBackupConfigInterval {
enabled: boolean;
intervalMinutes: number;
}

View File

@ -7,6 +7,8 @@ export interface IItemConfig extends IBaseConfig {
lootableItemBlacklist: string[];
/** items that should not be given as rewards */
rewardItemBlacklist: string[];
/** Item base types that should not be given as rewards */
rewardItemTypeBlacklist: string[];
/** Items that can only be found on bosses */
bossItems: string[];
handbookPriceOverride: Record<string, IHandbookPriceOverride>;

View File

@ -0,0 +1,98 @@
import { PreSptModLoader } from "@spt/loaders/PreSptModLoader";
import { IBackupConfig } from "@spt/models/spt/config/IBackupConfig";
import { ILogger } from "@spt/models/spt/utils/ILogger";
import { ConfigServer } from "@spt/servers/ConfigServer";
export declare class BackupService {
protected logger: ILogger;
protected preSptModLoader: PreSptModLoader;
protected configServer: ConfigServer;
protected backupConfig: IBackupConfig;
protected readonly activeServerMods: string[];
protected readonly profileDir = "./user/profiles";
constructor(logger: ILogger, preSptModLoader: PreSptModLoader, configServer: ConfigServer);
/**
* Initializes the backup process.
*
* This method orchestrates the profile backup service. Handles copying profiles to a backup directory and cleaning
* up old backups if the number exceeds the configured maximum.
*
* @returns A promise that resolves when the backup process is complete.
*/
init(): Promise<void>;
/**
* Fetches the names of all JSON files in the profile directory.
*
* This method normalizes the profile directory path and reads all files within it. It then filters the files to
* include only those with a `.json` extension and returns their names.
*
* @returns A promise that resolves to an array of JSON file names.
*/
protected fetchProfileFiles(): Promise<string[]>;
/**
* Check to see if the backup service is enabled via the config.
*
* @returns True if enabled, false otherwise.
*/
protected isEnabled(): boolean;
/**
* Generates the target directory path for the backup. The directory path is constructed using the `directory` from
* the configuration and the current backup date.
*
* @returns The target directory path for the backup.
*/
protected generateBackupTargetDir(): string;
/**
* Generates a formatted backup date string in the format `YYYY-MM-DD_hh-mm-ss`.
*
* @returns The formatted backup date string.
*/
protected generateBackupDate(): string;
/**
* Cleans up old backups in the backup directory.
*
* This method reads the backup directory, and sorts backups by modification time. If the number of backups exceeds
* the configured maximum, it deletes the oldest backups.
*
* @returns A promise that resolves when the cleanup is complete.
*/
protected cleanBackups(): Promise<void>;
/**
* Retrieves and sorts the backup file paths from the specified directory.
*
* @param dir - The directory to search for backup files.
* @returns A promise that resolves to an array of sorted backup file paths.
*/
private getBackupPaths;
/**
* Compares two backup folder names based on their extracted dates.
*
* @param a - The name of the first backup folder.
* @param b - The name of the second backup folder.
* @returns The difference in time between the two dates in milliseconds, or `null` if either date is invalid.
*/
private compareBackupDates;
/**
* Extracts a date from a folder name string formatted as `YYYY-MM-DD_hh-mm-ss`.
*
* @param folderName - The name of the folder from which to extract the date.
* @returns A Date object if the folder name is in the correct format, otherwise null.
*/
private extractDateFromFolderName;
/**
* Removes excess backups from the backup directory.
*
* @param backups - An array of backup file names to be removed.
* @returns A promise that resolves when all specified backups have been removed.
*/
private removeExcessBackups;
/**
* Start the backup interval if enabled in the configuration.
*/
protected startBackupInterval(): void;
/**
* Get an array of active server mod details.
*
* @returns An array of mod names.
*/
protected getActiveServerMods(): string[];
}

View File

@ -54,6 +54,16 @@ export declare class CircleOfCultistService {
* @returns IItemEventRouterResponse
*/
startSacrifice(sessionId: string, pmcData: IPmcData, request: IHideoutCircleOfCultistProductionStartRequestData): IItemEventRouterResponse;
/**
* Attempt to add all rewards to cultist circle, if they dont fit remove one and try again until they fit
* @param sessionId Session id
* @param pmcData Player profile
* @param rewards Rewards to send to player
* @param containerGrid Cultist grid to add rewards to
* @param cultistCircleStashId Stash id
* @param output Client output
*/
protected addRewardsToCircleContainer(sessionId: string, pmcData: IPmcData, rewards: IItem[][], containerGrid: number[][], cultistCircleStashId: string, output: IItemEventRouterResponse): void;
/**
* Create a map of the possible direct rewards, keyed by the items needed to be sacrificed
* @param directRewards Direct rewards array from hideout config
@ -156,7 +166,7 @@ export declare class CircleOfCultistService {
* @param itemRewardBlacklist Items not to add to pool
* @param rewardPool Pool to add items to
*/
protected addTaskItemRequirementsToRewardPool(pmcData: IPmcData, itemRewardBlacklist: string[], rewardPool: Set<string>): void;
protected addTaskItemRequirementsToRewardPool(pmcData: IPmcData, itemRewardBlacklist: Set<string>, rewardPool: Set<string>): void;
/**
* Adds items the player needs to complete hideout crafts/upgrades to the reward pool
* @param hideoutDbData Hideout area data
@ -164,7 +174,7 @@ export declare class CircleOfCultistService {
* @param itemRewardBlacklist Items not to add to pool
* @param rewardPool Pool to add items to
*/
protected addHideoutUpgradeRequirementsToRewardPool(hideoutDbData: IHideout, pmcData: IPmcData, itemRewardBlacklist: string[], rewardPool: Set<string>): void;
protected addHideoutUpgradeRequirementsToRewardPool(hideoutDbData: IHideout, pmcData: IPmcData, itemRewardBlacklist: Set<string>, rewardPool: Set<string>): void;
/**
* Get all active hideout areas
* @param areas Hideout areas to iterate over
@ -174,11 +184,11 @@ export declare class CircleOfCultistService {
/**
* Get array of random reward items
* @param rewardPool Reward pool to add to
* @param itemRewardBlacklist Reward Blacklist
* @param itemRewardBlacklist Item tpls to ignore
* @param itemsShouldBeHighValue Should these items meet the valuable threshold
* @returns rewardPool
* @returns Set of item tpls
*/
protected generateRandomisedItemsAndAddToRewardPool(rewardPool: Set<string>, itemRewardBlacklist: string[], itemsShouldBeHighValue: boolean): Set<string>;
protected generateRandomisedItemsAndAddToRewardPool(rewardPool: Set<string>, itemRewardBlacklist: Set<string>, itemsShouldBeHighValue: boolean): Set<string>;
/**
* Iterate over passed in hideout requirements and return the Item
* @param requirements Requirements to iterate over

View File

@ -36,6 +36,11 @@ export declare class ItemFilterService {
* @returns string array of item tpls
*/
getItemRewardBlacklist(): string[];
/**
* Get an array of item types that should never be given as a reward to player
* @returns string array of item base ids
*/
getItemRewardBaseTypeBlacklist(): string[];
/**
* Return every template id blacklisted in config/item.json
* @returns string array of blacklisted tempalte ids

View File

@ -4,10 +4,10 @@ import { ICoreConfig } from "@spt/models/spt/config/ICoreConfig";
import { ILogger } from "@spt/models/spt/utils/ILogger";
import { ConfigServer } from "@spt/servers/ConfigServer";
import { HttpServer } from "@spt/servers/HttpServer";
import { DatabaseService } from "@spt/services/DatabaseService";
import { LocalisationService } from "@spt/services/LocalisationService";
import { EncodingUtil } from "@spt/utils/EncodingUtil";
import { TimeUtil } from "@spt/utils/TimeUtil";
import { DatabaseService } from "@spt/services/DatabaseService";
export declare class App {
protected logger: ILogger;
protected timeUtil: TimeUtil;

View File

@ -106,6 +106,11 @@ export declare class RandomUtil {
protected cloner: ICloner;
protected logger: ILogger;
constructor(cloner: ICloner, logger: ILogger);
/**
* The IEEE-754 standard for double-precision floating-point numbers limits the number of digits (including both
* integer + fractional parts) to about 1517 significant digits. 15 is a safe upper bound, so we'll use that.
*/
private static readonly MAX_SIGNIFICANT_DIGITS;
/**
* Generates a secure random number between 0 (inclusive) and 1 (exclusive).
*
@ -116,6 +121,16 @@ export declare class RandomUtil {
* @returns A secure random number between 0 (inclusive) and 1 (exclusive).
*/
private getSecureRandomNumber;
/**
* Determines the number of decimal places in a number.
*
* @param num - The number to analyze.
* @returns The number of decimal places, or 0 if none exist.
* @remarks There is a mathematical way to determine this, but it's not as simple as it seams due to floating point
* precision issues. This method is a simple workaround that converts the number to a string and splits it.
* It's not the most efficient but it *is* the most reliable and easy to understand. Come at me.
*/
private getNumberPrecision;
/**
* Generates a random integer between the specified minimum and maximum values, inclusive.
*
@ -173,7 +188,7 @@ export declare class RandomUtil {
/**
* Returns a random string from the provided array of strings.
*
* This method is separate from getArrayValue so we can use a generic inferance with getArrayValue.
* This method is separate from getArrayValue so we can use a generic inference with getArrayValue.
*
* @param arr - The array of strings to select a random value from.
* @returns A randomly selected string from the array.
@ -225,12 +240,27 @@ export declare class RandomUtil {
getNormallyDistributedRandomNumber(mean: number, sigma: number, attempt?: number): number;
/**
* Generates a random integer between the specified range.
* Low and high parameters are floored to integers.
*
* TODO: v3.11 - This method should not accept non-integer numbers.
*
* @param low - The lower bound of the range (inclusive).
* @param high - The upper bound of the range (exclusive). If not provided, the range will be from 0 to `low`.
* @returns A random integer within the specified range.
*/
randInt(low: number, high?: number): number;
/**
* Generates a random number between two given values with optional precision.
*
* @param value1 - The first value to determine the range.
* @param value2 - The second value to determine the range. If not provided, 0 is used.
* @param precision - The number of decimal places to round the result to. Must be a positive integer between 0
* and MAX_PRECISION, inclusive. If not provided, precision is determined by the input values.
* @returns A random floating-point number between `value1` and `value2` (inclusive) with the specified precision.
* @throws Will throw an error if `precision` is not a positive integer, if `value1` or `value2` are not finite
* numbers, or if the precision exceeds the maximum allowed for the given values.
*/
randNum(value1: number, value2?: number, precision?: number | null): number;
/**
* Draws a specified number of random elements from a given list.
*

View File

@ -3,11 +3,13 @@ import { OnUpdate } from "@spt/di/OnUpdate";
import { ICoreConfig } from "@spt/models/spt/config/ICoreConfig";
import { ConfigServer } from "@spt/servers/ConfigServer";
import { SaveServer } from "@spt/servers/SaveServer";
import { BackupService } from "@spt/services/BackupService";
export declare class SaveCallbacks implements OnLoad, OnUpdate {
protected saveServer: SaveServer;
protected configServer: ConfigServer;
protected backupService: BackupService;
protected coreConfig: ICoreConfig;
constructor(saveServer: SaveServer, configServer: ConfigServer);
constructor(saveServer: SaveServer, configServer: ConfigServer, backupService: BackupService);
onLoad(): Promise<void>;
getRoute(): string;
onUpdate(secondsSinceLastRun: number): Promise<boolean>;

View File

@ -1,5 +1,8 @@
import { IDialogueChatBot } from "@spt/helpers/Dialogue/IDialogueChatBot";
import { DialogueHelper } from "@spt/helpers/DialogueHelper";
import { NotificationSendHelper } from "@spt/helpers/NotificationSendHelper";
import { ProfileHelper } from "@spt/helpers/ProfileHelper";
import { IDeleteFriendRequest } from "@spt/models/eft/dialog/IDeleteFriendRequest";
import { IFriendRequestData } from "@spt/models/eft/dialog/IFriendRequestData";
import { IFriendRequestSendResponse } from "@spt/models/eft/dialog/IFriendRequestSendResponse";
import { IGetAllAttachmentsResponse } from "@spt/models/eft/dialog/IGetAllAttachmentsResponse";
@ -20,11 +23,13 @@ export declare class DialogueController {
protected saveServer: SaveServer;
protected timeUtil: TimeUtil;
protected dialogueHelper: DialogueHelper;
protected notificationSendHelper: NotificationSendHelper;
protected profileHelper: ProfileHelper;
protected mailSendService: MailSendService;
protected localisationService: LocalisationService;
protected configServer: ConfigServer;
protected dialogueChatBots: IDialogueChatBot[];
constructor(logger: ILogger, saveServer: SaveServer, timeUtil: TimeUtil, dialogueHelper: DialogueHelper, mailSendService: MailSendService, localisationService: LocalisationService, configServer: ConfigServer, dialogueChatBots: IDialogueChatBot[]);
constructor(logger: ILogger, saveServer: SaveServer, timeUtil: TimeUtil, dialogueHelper: DialogueHelper, notificationSendHelper: NotificationSendHelper, profileHelper: ProfileHelper, mailSendService: MailSendService, localisationService: LocalisationService, configServer: ConfigServer, dialogueChatBots: IDialogueChatBot[]);
registerChatBot(chatBot: IDialogueChatBot): void;
/** Handle onUpdate spt event */
update(): void;
@ -151,4 +156,6 @@ export declare class DialogueController {
protected messageHasExpired(message: IMessage): boolean;
/** Handle client/friend/request/send */
sendFriendRequest(sessionID: string, request: IFriendRequestData): IFriendRequestSendResponse;
/** Handle client/friend/delete */
deleteFriend(sessionID: string, request: IDeleteFriendRequest): void;
}

View File

@ -72,6 +72,7 @@ export declare class QuestController {
*/
protected addTaskConditionCountersToProfile(questConditions: IQuestCondition[], pmcData: IPmcData, questId: string): void;
/**
* TODO - Move this code into RepeatableQuestController
* Handle the client accepting a repeatable quest and starting it
* Send starting rewards if any to player and
* Send start notification if any to player
@ -81,7 +82,6 @@ export declare class QuestController {
* @returns IItemEventRouterResponse
*/
acceptRepeatableQuest(pmcData: IPmcData, acceptedQuest: IAcceptQuestRequestData, sessionID: string): IItemEventRouterResponse;
protected createAcceptedQuestClientResponse(sessionID: string, pmcData: IPmcData, repeatableQuestProfile: IRepeatableQuest): IItemEventRouterResponse;
/**
* Look for an accepted quest inside player profile, return matching
* @param pmcData Profile to search through

View File

@ -147,6 +147,18 @@ export declare class RepeatableQuestController {
* @returns IItemEventRouterResponse
*/
changeRepeatableQuest(pmcData: IPmcData, changeRequest: IRepeatableQuestChangeRequest, sessionID: string): IItemEventRouterResponse;
/**
* Remove the provided quest from pmc and scav character profiles
* @param fullProfile Profile to remove quest from
* @param questToReplaceId Quest id to remove from profile
*/
protected removeQuestFromProfile(fullProfile: ISptProfile, questToReplaceId: string): void;
/**
* Clean up the repeatables `changeRequirement` dictionary of expired data
* @param repeatablesOfTypeInProfile The repeatables that have the replaced and new quest
* @param replacedQuestId Id of the replaced quest
*/
protected cleanUpRepeatableChangeRequirements(repeatablesOfTypeInProfile: IPmcDataRepeatableQuest, replacedQuestId: string): void;
/**
* Find a repeatable (daily/weekly/scav) from a players profile by its id
* @param questId Id of quest to find
@ -154,7 +166,7 @@ export declare class RepeatableQuestController {
* @returns IGetRepeatableByIdResult
*/
protected getRepeatableById(questId: string, pmcData: IPmcData): IGetRepeatableByIdResult;
protected attemptToGenerateRepeatableQuest(pmcData: IPmcData, questTypePool: IQuestTypePool, repeatableConfig: IRepeatableQuestConfig): IRepeatableQuest;
protected attemptToGenerateRepeatableQuest(sessionId: string, pmcData: IPmcData, questTypePool: IQuestTypePool, repeatableConfig: IRepeatableQuestConfig): IRepeatableQuest;
/**
* Some accounts have access to free repeatable quest refreshes
* Track the usage of them inside players profile

View File

@ -33,13 +33,14 @@ export declare class RepeatableQuestGenerator {
/**
* This method is called by /GetClientRepeatableQuests/ and creates one element of quest type format (see assets/database/templates/repeatableQuests.json).
* It randomly draws a quest type (currently Elimination, Completion or Exploration) as well as a trader who is providing the quest
* @param sessionId Session id
* @param pmcLevel Player's level for requested items and reward generation
* @param pmcTraderInfo Players traper standing/rep levels
* @param questTypePool Possible quest types pool
* @param repeatableConfig Repeatable quest config
* @returns IRepeatableQuest
*/
generateRepeatableQuest(pmcLevel: number, pmcTraderInfo: Record<string, ITraderInfo>, questTypePool: IQuestTypePool, repeatableConfig: IRepeatableQuestConfig): IRepeatableQuest;
generateRepeatableQuest(sessionId: string, pmcLevel: number, pmcTraderInfo: Record<string, ITraderInfo>, questTypePool: IQuestTypePool, repeatableConfig: IRepeatableQuestConfig): IRepeatableQuest;
/**
* Generate a randomised Elimination quest
* @param pmcLevel Player's level for requested items and reward generation
@ -48,7 +49,7 @@ export declare class RepeatableQuestGenerator {
* @param repeatableConfig The configuration for the repeatably kind (daily, weekly) as configured in QuestConfig for the requestd quest
* @returns Object of quest type format for "Elimination" (see assets/database/templates/repeatableQuests.json)
*/
protected generateEliminationQuest(pmcLevel: number, traderId: string, questTypePool: IQuestTypePool, repeatableConfig: IRepeatableQuestConfig): IRepeatableQuest;
protected generateEliminationQuest(sessionid: string, pmcLevel: number, traderId: string, questTypePool: IQuestTypePool, repeatableConfig: IRepeatableQuestConfig): IRepeatableQuest;
/**
* Get a number of kills neded to complete elimination quest
* @param targetKey Target type desired e.g. anyPmc/bossBully/Savage
@ -83,7 +84,7 @@ export declare class RepeatableQuestGenerator {
* @param {object} repeatableConfig The configuration for the repeatably kind (daily, weekly) as configured in QuestConfig for the requestd quest
* @returns {object} object of quest type format for "Completion" (see assets/database/templates/repeatableQuests.json)
*/
protected generateCompletionQuest(pmcLevel: number, traderId: string, repeatableConfig: IRepeatableQuestConfig): IRepeatableQuest;
protected generateCompletionQuest(sessionId: string, pmcLevel: number, traderId: string, repeatableConfig: IRepeatableQuestConfig): IRepeatableQuest;
/**
* A repeatable quest, besides some more or less static components, exists of reward and condition (see assets/database/templates/repeatableQuests.json)
* This is a helper method for GenerateCompletionQuest to create a completion condition (of which a completion quest theoretically can have many)
@ -102,7 +103,7 @@ export declare class RepeatableQuestGenerator {
* @param {object} repeatableConfig The configuration for the repeatably kind (daily, weekly) as configured in QuestConfig for the requestd quest
* @returns {object} object of quest type format for "Exploration" (see assets/database/templates/repeatableQuests.json)
*/
protected generateExplorationQuest(pmcLevel: number, traderId: string, questTypePool: IQuestTypePool, repeatableConfig: IRepeatableQuestConfig): IRepeatableQuest;
protected generateExplorationQuest(sessionId: string, pmcLevel: number, traderId: string, questTypePool: IQuestTypePool, repeatableConfig: IRepeatableQuestConfig): IRepeatableQuest;
/**
* Filter a maps exits to just those for the desired side
* @param locationKey Map id (e.g. factory4_day)
@ -110,7 +111,7 @@ export declare class RepeatableQuestGenerator {
* @returns Array of Exit objects
*/
protected getLocationExitsForSide(locationKey: string, playerSide: string): IExit[];
protected generatePickupQuest(pmcLevel: number, traderId: string, questTypePool: IQuestTypePool, repeatableConfig: IRepeatableQuestConfig): IRepeatableQuest;
protected generatePickupQuest(sessionId: string, pmcLevel: number, traderId: string, questTypePool: IQuestTypePool, repeatableConfig: IRepeatableQuestConfig): IRepeatableQuest;
/**
* Convert a location into an quest code can read (e.g. factory4_day into 55f2d3fd4bdc2d5f408b4567)
* @param locationKey e.g factory4_day
@ -135,5 +136,5 @@ export declare class RepeatableQuestGenerator {
* @returns {object} Object which contains the base elements for repeatable quests of the requests type
* (needs to be filled with reward and conditions by called to make a valid quest)
*/
protected generateRepeatableTemplate(type: string, traderId: string, side: string): IRepeatableQuest;
protected generateRepeatableTemplate(type: string, traderId: string, side: string, sessionId: string): IRepeatableQuest;
}

View File

@ -12,6 +12,7 @@ import { DatabaseService } from "@spt/services/DatabaseService";
import { ItemFilterService } from "@spt/services/ItemFilterService";
import { LocalisationService } from "@spt/services/LocalisationService";
import { SeasonalEventService } from "@spt/services/SeasonalEventService";
import { HashUtil } from "@spt/utils/HashUtil";
import { MathUtil } from "@spt/utils/MathUtil";
import { ObjectId } from "@spt/utils/ObjectId";
import { RandomUtil } from "@spt/utils/RandomUtil";
@ -19,6 +20,7 @@ import { ICloner } from "@spt/utils/cloners/ICloner";
export declare class RepeatableQuestRewardGenerator {
protected logger: ILogger;
protected randomUtil: RandomUtil;
protected hashUtil: HashUtil;
protected mathUtil: MathUtil;
protected databaseService: DatabaseService;
protected itemHelper: ItemHelper;
@ -31,7 +33,7 @@ export declare class RepeatableQuestRewardGenerator {
protected configServer: ConfigServer;
protected cloner: ICloner;
protected questConfig: IQuestConfig;
constructor(logger: ILogger, randomUtil: RandomUtil, mathUtil: MathUtil, databaseService: DatabaseService, itemHelper: ItemHelper, presetHelper: PresetHelper, handbookHelper: HandbookHelper, localisationService: LocalisationService, objectId: ObjectId, itemFilterService: ItemFilterService, seasonalEventService: SeasonalEventService, configServer: ConfigServer, cloner: ICloner);
constructor(logger: ILogger, randomUtil: RandomUtil, hashUtil: HashUtil, mathUtil: MathUtil, databaseService: DatabaseService, itemHelper: ItemHelper, presetHelper: PresetHelper, handbookHelper: HandbookHelper, localisationService: LocalisationService, objectId: ObjectId, itemFilterService: ItemFilterService, seasonalEventService: SeasonalEventService, configServer: ConfigServer, cloner: ICloner);
/**
* Generate the reward for a mission. A reward can consist of:
* - Experience
@ -127,7 +129,7 @@ export declare class RepeatableQuestRewardGenerator {
* @param preset Optional array of preset items
* @returns {object} Object of "Reward"-item-type
*/
protected generateItemReward(tpl: string, count: number, index: number): IQuestReward;
protected generateItemReward(tpl: string, count: number, index: number, foundInRaid?: boolean): IQuestReward;
/**
* Helper to create a reward item structured as required by the client
*
@ -137,7 +139,7 @@ export declare class RepeatableQuestRewardGenerator {
* @param preset Optional array of preset items
* @returns {object} Object of "Reward"-item-type
*/
protected generatePresetReward(tpl: string, count: number, index: number, preset?: IItem[]): IQuestReward;
protected generatePresetReward(tpl: string, count: number, index: number, preset?: IItem[], foundInRaid?: boolean): IQuestReward;
/**
* Picks rewardable items from items.json
* This means they must:

View File

@ -2,6 +2,7 @@ import { ItemHelper } from "@spt/helpers/ItemHelper";
import { IPmcData } from "@spt/models/eft/common/IPmcData";
import { Common, ICounterKeyValue, IStats } from "@spt/models/eft/common/tables/IBotBase";
import { IItem } from "@spt/models/eft/common/tables/IItem";
import { ISearchFriendResponse } from "@spt/models/eft/profile/ISearchFriendResponse";
import { ISptProfile } from "@spt/models/eft/profile/ISptProfile";
import { IValidateNicknameRequestData } from "@spt/models/eft/profile/IValidateNicknameRequestData";
import { BonusType } from "@spt/models/enums/BonusType";
@ -90,6 +91,24 @@ export declare class ProfileHelper {
* @returns ISptProfile object
*/
getFullProfile(sessionID: string): ISptProfile | undefined;
/**
* Get full representation of a players profile JSON by the account ID, or undefined if not found
* @param accountId Account ID to find
* @returns
*/
getFullProfileByAccountId(accountID: string): ISptProfile | undefined;
/**
* Retrieve a ChatRoomMember formatted profile for the given session ID
* @param sessionID The session ID to return the profile for
* @returns
*/
getChatRoomMemberFromSessionId(sessionID: string): ISearchFriendResponse | undefined;
/**
* Retrieve a ChatRoomMember formatted profile for the given PMC profile data
* @param pmcProfile The PMC profile data to format into a ChatRoomMember structure
* @returns
*/
getChatRoomMemberFromPmcProfile(pmcProfile: IPmcData): ISearchFriendResponse;
/**
* Get a PMC profile by its session id
* @param sessionID Profile id to return

View File

@ -11,23 +11,36 @@ export interface ILocationBase {
Banners: IBanner[];
BossLocationSpawn: IBossLocationSpawn[];
BotAssault: number;
/** Weighting on how likely a bot will be Easy difficulty */
BotEasy: number;
/** Weighting on how likely a bot will be Hard difficulty */
BotHard: number;
/** Weighting on how likely a bot will be Impossible difficulty */
BotImpossible: number;
BotLocationModifier: IBotLocationModifier;
BotMarksman: number;
/** Maximum Number of bots that are currently alive/loading/delayed */
BotMax: number;
/** Is not used in 33420 */
BotMaxPlayer: number;
/** Is not used in 33420 */
BotMaxTimePlayer: number;
/** Does not even exist in the client in 33420 */
BotMaxPvE: number;
/** Weighting on how likely a bot will be Normal difficulty */
BotNormal: number;
/** How many bot slots that need to be open before trying to spawn new bots. */
BotSpawnCountStep: number;
/** How often to check if bots are spawn-able. In seconds */
BotSpawnPeriodCheck: number;
/** The bot spawn will toggle on and off in intervals of Off(Min/Max) and On(Min/Max) */
BotSpawnTimeOffMax: number;
BotSpawnTimeOffMin: number;
BotSpawnTimeOnMax: number;
BotSpawnTimeOnMin: number;
/** How soon bots will be allowed to spawn */
BotStart: number;
/** After this long bots will no longer spawn */
BotStop: number;
Description: string;
DisabledForScav: boolean;

View File

@ -33,6 +33,11 @@ export interface IQuest {
changeQuestMessageText: string;
/** "Pmc" or "Scav" */
side: string;
acceptanceAndFinishingSource: string;
progressSource: string;
rankingModes: string[];
gameModes: string[];
arenaLocations: string[];
/** Status of quest to player */
sptStatus?: QuestStatus;
}
@ -148,8 +153,10 @@ export interface IQuestReward {
loyaltyLevel?: number;
/** Hideout area id */
traderId?: string;
isEncoded?: boolean;
unknown?: boolean;
findInRaid?: boolean;
gameMode?: string[];
/** Game editions whitelisted to get reward */
availableInGameEditions?: string[];
/** Game editions blacklisted from getting reward */

View File

@ -3,6 +3,12 @@ export interface IRepeatableQuest extends IQuest {
changeCost: IChangeCost[];
changeStandingCost: number;
sptRepatableGroupName: string;
acceptanceAndFinishingSource: string;
progressSource: string;
rankingModes: string[];
gameModes: string[];
arenaLocations: string[];
questStatus: IRepeatableQuestStatus;
}
export interface IRepeatableQuestDatabase {
templates: IRepeatableTemplates;
@ -10,6 +16,14 @@ export interface IRepeatableQuestDatabase {
data: IOptions;
samples: ISampleQuests[];
}
export interface IRepeatableQuestStatus {
id: string;
uid: string;
qid: string;
startTime: number;
status: number;
statusTimers: any;
}
export interface IRepeatableTemplates {
Elimination: IQuest;
Completion: IQuest;

View File

@ -8,4 +8,5 @@ export interface Info {
Side: string;
Level: number;
MemberCategory: number;
SelectedMemberCategory: number;
}

View File

@ -19,6 +19,8 @@ export interface ISptProfile {
traderPurchases?: Record<string, Record<string, ITraderPurchaseData>>;
/** Achievements earned by player */
achievements: Record<string, number>;
/** List of friend profile IDs */
friends: string[];
}
export declare class ITraderPurchaseData {
count: number;

View File

@ -0,0 +1,5 @@
import { IWsNotificationEvent } from "@spt/models/eft/ws/IWsNotificationEvent";
import { ISearchFriendResponse } from "../profile/ISearchFriendResponse";
export interface IWsFriendsListAccept extends IWsNotificationEvent {
profile: ISearchFriendResponse;
}

View File

@ -1,4 +1,4 @@
export interface IWsNotificationEvent {
type: string;
eventId: string;
eventId?: string;
}

View File

@ -1,5 +1,6 @@
export declare enum ConfigTypes {
AIRDROP = "spt-airdrop",
BACKUP = "spt-backup",
BOT = "spt-bot",
PMC = "spt-pmc",
CORE = "spt-core",

View File

@ -0,0 +1,12 @@
import { IBaseConfig } from "@spt/models/spt/config/IBaseConfig";
export interface IBackupConfig extends IBaseConfig {
kind: "spt-backup";
enabled: boolean;
maxBackups: number;
directory: string;
backupInterval: IBackupConfigInterval;
}
export interface IBackupConfigInterval {
enabled: boolean;
intervalMinutes: number;
}

View File

@ -7,6 +7,8 @@ export interface IItemConfig extends IBaseConfig {
lootableItemBlacklist: string[];
/** items that should not be given as rewards */
rewardItemBlacklist: string[];
/** Item base types that should not be given as rewards */
rewardItemTypeBlacklist: string[];
/** Items that can only be found on bosses */
bossItems: string[];
handbookPriceOverride: Record<string, IHandbookPriceOverride>;

View File

@ -0,0 +1,98 @@
import { PreSptModLoader } from "@spt/loaders/PreSptModLoader";
import { IBackupConfig } from "@spt/models/spt/config/IBackupConfig";
import { ILogger } from "@spt/models/spt/utils/ILogger";
import { ConfigServer } from "@spt/servers/ConfigServer";
export declare class BackupService {
protected logger: ILogger;
protected preSptModLoader: PreSptModLoader;
protected configServer: ConfigServer;
protected backupConfig: IBackupConfig;
protected readonly activeServerMods: string[];
protected readonly profileDir = "./user/profiles";
constructor(logger: ILogger, preSptModLoader: PreSptModLoader, configServer: ConfigServer);
/**
* Initializes the backup process.
*
* This method orchestrates the profile backup service. Handles copying profiles to a backup directory and cleaning
* up old backups if the number exceeds the configured maximum.
*
* @returns A promise that resolves when the backup process is complete.
*/
init(): Promise<void>;
/**
* Fetches the names of all JSON files in the profile directory.
*
* This method normalizes the profile directory path and reads all files within it. It then filters the files to
* include only those with a `.json` extension and returns their names.
*
* @returns A promise that resolves to an array of JSON file names.
*/
protected fetchProfileFiles(): Promise<string[]>;
/**
* Check to see if the backup service is enabled via the config.
*
* @returns True if enabled, false otherwise.
*/
protected isEnabled(): boolean;
/**
* Generates the target directory path for the backup. The directory path is constructed using the `directory` from
* the configuration and the current backup date.
*
* @returns The target directory path for the backup.
*/
protected generateBackupTargetDir(): string;
/**
* Generates a formatted backup date string in the format `YYYY-MM-DD_hh-mm-ss`.
*
* @returns The formatted backup date string.
*/
protected generateBackupDate(): string;
/**
* Cleans up old backups in the backup directory.
*
* This method reads the backup directory, and sorts backups by modification time. If the number of backups exceeds
* the configured maximum, it deletes the oldest backups.
*
* @returns A promise that resolves when the cleanup is complete.
*/
protected cleanBackups(): Promise<void>;
/**
* Retrieves and sorts the backup file paths from the specified directory.
*
* @param dir - The directory to search for backup files.
* @returns A promise that resolves to an array of sorted backup file paths.
*/
private getBackupPaths;
/**
* Compares two backup folder names based on their extracted dates.
*
* @param a - The name of the first backup folder.
* @param b - The name of the second backup folder.
* @returns The difference in time between the two dates in milliseconds, or `null` if either date is invalid.
*/
private compareBackupDates;
/**
* Extracts a date from a folder name string formatted as `YYYY-MM-DD_hh-mm-ss`.
*
* @param folderName - The name of the folder from which to extract the date.
* @returns A Date object if the folder name is in the correct format, otherwise null.
*/
private extractDateFromFolderName;
/**
* Removes excess backups from the backup directory.
*
* @param backups - An array of backup file names to be removed.
* @returns A promise that resolves when all specified backups have been removed.
*/
private removeExcessBackups;
/**
* Start the backup interval if enabled in the configuration.
*/
protected startBackupInterval(): void;
/**
* Get an array of active server mod details.
*
* @returns An array of mod names.
*/
protected getActiveServerMods(): string[];
}

View File

@ -54,6 +54,16 @@ export declare class CircleOfCultistService {
* @returns IItemEventRouterResponse
*/
startSacrifice(sessionId: string, pmcData: IPmcData, request: IHideoutCircleOfCultistProductionStartRequestData): IItemEventRouterResponse;
/**
* Attempt to add all rewards to cultist circle, if they dont fit remove one and try again until they fit
* @param sessionId Session id
* @param pmcData Player profile
* @param rewards Rewards to send to player
* @param containerGrid Cultist grid to add rewards to
* @param cultistCircleStashId Stash id
* @param output Client output
*/
protected addRewardsToCircleContainer(sessionId: string, pmcData: IPmcData, rewards: IItem[][], containerGrid: number[][], cultistCircleStashId: string, output: IItemEventRouterResponse): void;
/**
* Create a map of the possible direct rewards, keyed by the items needed to be sacrificed
* @param directRewards Direct rewards array from hideout config
@ -156,7 +166,7 @@ export declare class CircleOfCultistService {
* @param itemRewardBlacklist Items not to add to pool
* @param rewardPool Pool to add items to
*/
protected addTaskItemRequirementsToRewardPool(pmcData: IPmcData, itemRewardBlacklist: string[], rewardPool: Set<string>): void;
protected addTaskItemRequirementsToRewardPool(pmcData: IPmcData, itemRewardBlacklist: Set<string>, rewardPool: Set<string>): void;
/**
* Adds items the player needs to complete hideout crafts/upgrades to the reward pool
* @param hideoutDbData Hideout area data
@ -164,7 +174,7 @@ export declare class CircleOfCultistService {
* @param itemRewardBlacklist Items not to add to pool
* @param rewardPool Pool to add items to
*/
protected addHideoutUpgradeRequirementsToRewardPool(hideoutDbData: IHideout, pmcData: IPmcData, itemRewardBlacklist: string[], rewardPool: Set<string>): void;
protected addHideoutUpgradeRequirementsToRewardPool(hideoutDbData: IHideout, pmcData: IPmcData, itemRewardBlacklist: Set<string>, rewardPool: Set<string>): void;
/**
* Get all active hideout areas
* @param areas Hideout areas to iterate over
@ -174,11 +184,11 @@ export declare class CircleOfCultistService {
/**
* Get array of random reward items
* @param rewardPool Reward pool to add to
* @param itemRewardBlacklist Reward Blacklist
* @param itemRewardBlacklist Item tpls to ignore
* @param itemsShouldBeHighValue Should these items meet the valuable threshold
* @returns rewardPool
* @returns Set of item tpls
*/
protected generateRandomisedItemsAndAddToRewardPool(rewardPool: Set<string>, itemRewardBlacklist: string[], itemsShouldBeHighValue: boolean): Set<string>;
protected generateRandomisedItemsAndAddToRewardPool(rewardPool: Set<string>, itemRewardBlacklist: Set<string>, itemsShouldBeHighValue: boolean): Set<string>;
/**
* Iterate over passed in hideout requirements and return the Item
* @param requirements Requirements to iterate over

View File

@ -36,6 +36,11 @@ export declare class ItemFilterService {
* @returns string array of item tpls
*/
getItemRewardBlacklist(): string[];
/**
* Get an array of item types that should never be given as a reward to player
* @returns string array of item base ids
*/
getItemRewardBaseTypeBlacklist(): string[];
/**
* Return every template id blacklisted in config/item.json
* @returns string array of blacklisted tempalte ids

View File

@ -4,10 +4,10 @@ import { ICoreConfig } from "@spt/models/spt/config/ICoreConfig";
import { ILogger } from "@spt/models/spt/utils/ILogger";
import { ConfigServer } from "@spt/servers/ConfigServer";
import { HttpServer } from "@spt/servers/HttpServer";
import { DatabaseService } from "@spt/services/DatabaseService";
import { LocalisationService } from "@spt/services/LocalisationService";
import { EncodingUtil } from "@spt/utils/EncodingUtil";
import { TimeUtil } from "@spt/utils/TimeUtil";
import { DatabaseService } from "@spt/services/DatabaseService";
export declare class App {
protected logger: ILogger;
protected timeUtil: TimeUtil;

View File

@ -106,6 +106,11 @@ export declare class RandomUtil {
protected cloner: ICloner;
protected logger: ILogger;
constructor(cloner: ICloner, logger: ILogger);
/**
* The IEEE-754 standard for double-precision floating-point numbers limits the number of digits (including both
* integer + fractional parts) to about 1517 significant digits. 15 is a safe upper bound, so we'll use that.
*/
private static readonly MAX_SIGNIFICANT_DIGITS;
/**
* Generates a secure random number between 0 (inclusive) and 1 (exclusive).
*
@ -116,6 +121,16 @@ export declare class RandomUtil {
* @returns A secure random number between 0 (inclusive) and 1 (exclusive).
*/
private getSecureRandomNumber;
/**
* Determines the number of decimal places in a number.
*
* @param num - The number to analyze.
* @returns The number of decimal places, or 0 if none exist.
* @remarks There is a mathematical way to determine this, but it's not as simple as it seams due to floating point
* precision issues. This method is a simple workaround that converts the number to a string and splits it.
* It's not the most efficient but it *is* the most reliable and easy to understand. Come at me.
*/
private getNumberPrecision;
/**
* Generates a random integer between the specified minimum and maximum values, inclusive.
*
@ -173,7 +188,7 @@ export declare class RandomUtil {
/**
* Returns a random string from the provided array of strings.
*
* This method is separate from getArrayValue so we can use a generic inferance with getArrayValue.
* This method is separate from getArrayValue so we can use a generic inference with getArrayValue.
*
* @param arr - The array of strings to select a random value from.
* @returns A randomly selected string from the array.
@ -225,12 +240,27 @@ export declare class RandomUtil {
getNormallyDistributedRandomNumber(mean: number, sigma: number, attempt?: number): number;
/**
* Generates a random integer between the specified range.
* Low and high parameters are floored to integers.
*
* TODO: v3.11 - This method should not accept non-integer numbers.
*
* @param low - The lower bound of the range (inclusive).
* @param high - The upper bound of the range (exclusive). If not provided, the range will be from 0 to `low`.
* @returns A random integer within the specified range.
*/
randInt(low: number, high?: number): number;
/**
* Generates a random number between two given values with optional precision.
*
* @param value1 - The first value to determine the range.
* @param value2 - The second value to determine the range. If not provided, 0 is used.
* @param precision - The number of decimal places to round the result to. Must be a positive integer between 0
* and MAX_PRECISION, inclusive. If not provided, precision is determined by the input values.
* @returns A random floating-point number between `value1` and `value2` (inclusive) with the specified precision.
* @throws Will throw an error if `precision` is not a positive integer, if `value1` or `value2` are not finite
* numbers, or if the precision exceeds the maximum allowed for the given values.
*/
randNum(value1: number, value2?: number, precision?: number | null): number;
/**
* Draws a specified number of random elements from a given list.
*

View File

@ -3,11 +3,13 @@ import { OnUpdate } from "@spt/di/OnUpdate";
import { ICoreConfig } from "@spt/models/spt/config/ICoreConfig";
import { ConfigServer } from "@spt/servers/ConfigServer";
import { SaveServer } from "@spt/servers/SaveServer";
import { BackupService } from "@spt/services/BackupService";
export declare class SaveCallbacks implements OnLoad, OnUpdate {
protected saveServer: SaveServer;
protected configServer: ConfigServer;
protected backupService: BackupService;
protected coreConfig: ICoreConfig;
constructor(saveServer: SaveServer, configServer: ConfigServer);
constructor(saveServer: SaveServer, configServer: ConfigServer, backupService: BackupService);
onLoad(): Promise<void>;
getRoute(): string;
onUpdate(secondsSinceLastRun: number): Promise<boolean>;

View File

@ -1,5 +1,8 @@
import { IDialogueChatBot } from "@spt/helpers/Dialogue/IDialogueChatBot";
import { DialogueHelper } from "@spt/helpers/DialogueHelper";
import { NotificationSendHelper } from "@spt/helpers/NotificationSendHelper";
import { ProfileHelper } from "@spt/helpers/ProfileHelper";
import { IDeleteFriendRequest } from "@spt/models/eft/dialog/IDeleteFriendRequest";
import { IFriendRequestData } from "@spt/models/eft/dialog/IFriendRequestData";
import { IFriendRequestSendResponse } from "@spt/models/eft/dialog/IFriendRequestSendResponse";
import { IGetAllAttachmentsResponse } from "@spt/models/eft/dialog/IGetAllAttachmentsResponse";
@ -20,11 +23,13 @@ export declare class DialogueController {
protected saveServer: SaveServer;
protected timeUtil: TimeUtil;
protected dialogueHelper: DialogueHelper;
protected notificationSendHelper: NotificationSendHelper;
protected profileHelper: ProfileHelper;
protected mailSendService: MailSendService;
protected localisationService: LocalisationService;
protected configServer: ConfigServer;
protected dialogueChatBots: IDialogueChatBot[];
constructor(logger: ILogger, saveServer: SaveServer, timeUtil: TimeUtil, dialogueHelper: DialogueHelper, mailSendService: MailSendService, localisationService: LocalisationService, configServer: ConfigServer, dialogueChatBots: IDialogueChatBot[]);
constructor(logger: ILogger, saveServer: SaveServer, timeUtil: TimeUtil, dialogueHelper: DialogueHelper, notificationSendHelper: NotificationSendHelper, profileHelper: ProfileHelper, mailSendService: MailSendService, localisationService: LocalisationService, configServer: ConfigServer, dialogueChatBots: IDialogueChatBot[]);
registerChatBot(chatBot: IDialogueChatBot): void;
/** Handle onUpdate spt event */
update(): void;
@ -151,4 +156,6 @@ export declare class DialogueController {
protected messageHasExpired(message: IMessage): boolean;
/** Handle client/friend/request/send */
sendFriendRequest(sessionID: string, request: IFriendRequestData): IFriendRequestSendResponse;
/** Handle client/friend/delete */
deleteFriend(sessionID: string, request: IDeleteFriendRequest): void;
}

View File

@ -72,6 +72,7 @@ export declare class QuestController {
*/
protected addTaskConditionCountersToProfile(questConditions: IQuestCondition[], pmcData: IPmcData, questId: string): void;
/**
* TODO - Move this code into RepeatableQuestController
* Handle the client accepting a repeatable quest and starting it
* Send starting rewards if any to player and
* Send start notification if any to player
@ -81,7 +82,6 @@ export declare class QuestController {
* @returns IItemEventRouterResponse
*/
acceptRepeatableQuest(pmcData: IPmcData, acceptedQuest: IAcceptQuestRequestData, sessionID: string): IItemEventRouterResponse;
protected createAcceptedQuestClientResponse(sessionID: string, pmcData: IPmcData, repeatableQuestProfile: IRepeatableQuest): IItemEventRouterResponse;
/**
* Look for an accepted quest inside player profile, return matching
* @param pmcData Profile to search through

View File

@ -147,6 +147,18 @@ export declare class RepeatableQuestController {
* @returns IItemEventRouterResponse
*/
changeRepeatableQuest(pmcData: IPmcData, changeRequest: IRepeatableQuestChangeRequest, sessionID: string): IItemEventRouterResponse;
/**
* Remove the provided quest from pmc and scav character profiles
* @param fullProfile Profile to remove quest from
* @param questToReplaceId Quest id to remove from profile
*/
protected removeQuestFromProfile(fullProfile: ISptProfile, questToReplaceId: string): void;
/**
* Clean up the repeatables `changeRequirement` dictionary of expired data
* @param repeatablesOfTypeInProfile The repeatables that have the replaced and new quest
* @param replacedQuestId Id of the replaced quest
*/
protected cleanUpRepeatableChangeRequirements(repeatablesOfTypeInProfile: IPmcDataRepeatableQuest, replacedQuestId: string): void;
/**
* Find a repeatable (daily/weekly/scav) from a players profile by its id
* @param questId Id of quest to find
@ -154,7 +166,7 @@ export declare class RepeatableQuestController {
* @returns IGetRepeatableByIdResult
*/
protected getRepeatableById(questId: string, pmcData: IPmcData): IGetRepeatableByIdResult;
protected attemptToGenerateRepeatableQuest(pmcData: IPmcData, questTypePool: IQuestTypePool, repeatableConfig: IRepeatableQuestConfig): IRepeatableQuest;
protected attemptToGenerateRepeatableQuest(sessionId: string, pmcData: IPmcData, questTypePool: IQuestTypePool, repeatableConfig: IRepeatableQuestConfig): IRepeatableQuest;
/**
* Some accounts have access to free repeatable quest refreshes
* Track the usage of them inside players profile

View File

@ -33,13 +33,14 @@ export declare class RepeatableQuestGenerator {
/**
* This method is called by /GetClientRepeatableQuests/ and creates one element of quest type format (see assets/database/templates/repeatableQuests.json).
* It randomly draws a quest type (currently Elimination, Completion or Exploration) as well as a trader who is providing the quest
* @param sessionId Session id
* @param pmcLevel Player's level for requested items and reward generation
* @param pmcTraderInfo Players traper standing/rep levels
* @param questTypePool Possible quest types pool
* @param repeatableConfig Repeatable quest config
* @returns IRepeatableQuest
*/
generateRepeatableQuest(pmcLevel: number, pmcTraderInfo: Record<string, ITraderInfo>, questTypePool: IQuestTypePool, repeatableConfig: IRepeatableQuestConfig): IRepeatableQuest;
generateRepeatableQuest(sessionId: string, pmcLevel: number, pmcTraderInfo: Record<string, ITraderInfo>, questTypePool: IQuestTypePool, repeatableConfig: IRepeatableQuestConfig): IRepeatableQuest;
/**
* Generate a randomised Elimination quest
* @param pmcLevel Player's level for requested items and reward generation
@ -48,7 +49,7 @@ export declare class RepeatableQuestGenerator {
* @param repeatableConfig The configuration for the repeatably kind (daily, weekly) as configured in QuestConfig for the requestd quest
* @returns Object of quest type format for "Elimination" (see assets/database/templates/repeatableQuests.json)
*/
protected generateEliminationQuest(pmcLevel: number, traderId: string, questTypePool: IQuestTypePool, repeatableConfig: IRepeatableQuestConfig): IRepeatableQuest;
protected generateEliminationQuest(sessionid: string, pmcLevel: number, traderId: string, questTypePool: IQuestTypePool, repeatableConfig: IRepeatableQuestConfig): IRepeatableQuest;
/**
* Get a number of kills neded to complete elimination quest
* @param targetKey Target type desired e.g. anyPmc/bossBully/Savage
@ -83,7 +84,7 @@ export declare class RepeatableQuestGenerator {
* @param {object} repeatableConfig The configuration for the repeatably kind (daily, weekly) as configured in QuestConfig for the requestd quest
* @returns {object} object of quest type format for "Completion" (see assets/database/templates/repeatableQuests.json)
*/
protected generateCompletionQuest(pmcLevel: number, traderId: string, repeatableConfig: IRepeatableQuestConfig): IRepeatableQuest;
protected generateCompletionQuest(sessionId: string, pmcLevel: number, traderId: string, repeatableConfig: IRepeatableQuestConfig): IRepeatableQuest;
/**
* A repeatable quest, besides some more or less static components, exists of reward and condition (see assets/database/templates/repeatableQuests.json)
* This is a helper method for GenerateCompletionQuest to create a completion condition (of which a completion quest theoretically can have many)
@ -102,7 +103,7 @@ export declare class RepeatableQuestGenerator {
* @param {object} repeatableConfig The configuration for the repeatably kind (daily, weekly) as configured in QuestConfig for the requestd quest
* @returns {object} object of quest type format for "Exploration" (see assets/database/templates/repeatableQuests.json)
*/
protected generateExplorationQuest(pmcLevel: number, traderId: string, questTypePool: IQuestTypePool, repeatableConfig: IRepeatableQuestConfig): IRepeatableQuest;
protected generateExplorationQuest(sessionId: string, pmcLevel: number, traderId: string, questTypePool: IQuestTypePool, repeatableConfig: IRepeatableQuestConfig): IRepeatableQuest;
/**
* Filter a maps exits to just those for the desired side
* @param locationKey Map id (e.g. factory4_day)
@ -110,7 +111,7 @@ export declare class RepeatableQuestGenerator {
* @returns Array of Exit objects
*/
protected getLocationExitsForSide(locationKey: string, playerSide: string): IExit[];
protected generatePickupQuest(pmcLevel: number, traderId: string, questTypePool: IQuestTypePool, repeatableConfig: IRepeatableQuestConfig): IRepeatableQuest;
protected generatePickupQuest(sessionId: string, pmcLevel: number, traderId: string, questTypePool: IQuestTypePool, repeatableConfig: IRepeatableQuestConfig): IRepeatableQuest;
/**
* Convert a location into an quest code can read (e.g. factory4_day into 55f2d3fd4bdc2d5f408b4567)
* @param locationKey e.g factory4_day
@ -135,5 +136,5 @@ export declare class RepeatableQuestGenerator {
* @returns {object} Object which contains the base elements for repeatable quests of the requests type
* (needs to be filled with reward and conditions by called to make a valid quest)
*/
protected generateRepeatableTemplate(type: string, traderId: string, side: string): IRepeatableQuest;
protected generateRepeatableTemplate(type: string, traderId: string, side: string, sessionId: string): IRepeatableQuest;
}

View File

@ -12,6 +12,7 @@ import { DatabaseService } from "@spt/services/DatabaseService";
import { ItemFilterService } from "@spt/services/ItemFilterService";
import { LocalisationService } from "@spt/services/LocalisationService";
import { SeasonalEventService } from "@spt/services/SeasonalEventService";
import { HashUtil } from "@spt/utils/HashUtil";
import { MathUtil } from "@spt/utils/MathUtil";
import { ObjectId } from "@spt/utils/ObjectId";
import { RandomUtil } from "@spt/utils/RandomUtil";
@ -19,6 +20,7 @@ import { ICloner } from "@spt/utils/cloners/ICloner";
export declare class RepeatableQuestRewardGenerator {
protected logger: ILogger;
protected randomUtil: RandomUtil;
protected hashUtil: HashUtil;
protected mathUtil: MathUtil;
protected databaseService: DatabaseService;
protected itemHelper: ItemHelper;
@ -31,7 +33,7 @@ export declare class RepeatableQuestRewardGenerator {
protected configServer: ConfigServer;
protected cloner: ICloner;
protected questConfig: IQuestConfig;
constructor(logger: ILogger, randomUtil: RandomUtil, mathUtil: MathUtil, databaseService: DatabaseService, itemHelper: ItemHelper, presetHelper: PresetHelper, handbookHelper: HandbookHelper, localisationService: LocalisationService, objectId: ObjectId, itemFilterService: ItemFilterService, seasonalEventService: SeasonalEventService, configServer: ConfigServer, cloner: ICloner);
constructor(logger: ILogger, randomUtil: RandomUtil, hashUtil: HashUtil, mathUtil: MathUtil, databaseService: DatabaseService, itemHelper: ItemHelper, presetHelper: PresetHelper, handbookHelper: HandbookHelper, localisationService: LocalisationService, objectId: ObjectId, itemFilterService: ItemFilterService, seasonalEventService: SeasonalEventService, configServer: ConfigServer, cloner: ICloner);
/**
* Generate the reward for a mission. A reward can consist of:
* - Experience
@ -127,7 +129,7 @@ export declare class RepeatableQuestRewardGenerator {
* @param preset Optional array of preset items
* @returns {object} Object of "Reward"-item-type
*/
protected generateItemReward(tpl: string, count: number, index: number): IQuestReward;
protected generateItemReward(tpl: string, count: number, index: number, foundInRaid?: boolean): IQuestReward;
/**
* Helper to create a reward item structured as required by the client
*
@ -137,7 +139,7 @@ export declare class RepeatableQuestRewardGenerator {
* @param preset Optional array of preset items
* @returns {object} Object of "Reward"-item-type
*/
protected generatePresetReward(tpl: string, count: number, index: number, preset?: IItem[]): IQuestReward;
protected generatePresetReward(tpl: string, count: number, index: number, preset?: IItem[], foundInRaid?: boolean): IQuestReward;
/**
* Picks rewardable items from items.json
* This means they must:

View File

@ -2,6 +2,7 @@ import { ItemHelper } from "@spt/helpers/ItemHelper";
import { IPmcData } from "@spt/models/eft/common/IPmcData";
import { Common, ICounterKeyValue, IStats } from "@spt/models/eft/common/tables/IBotBase";
import { IItem } from "@spt/models/eft/common/tables/IItem";
import { ISearchFriendResponse } from "@spt/models/eft/profile/ISearchFriendResponse";
import { ISptProfile } from "@spt/models/eft/profile/ISptProfile";
import { IValidateNicknameRequestData } from "@spt/models/eft/profile/IValidateNicknameRequestData";
import { BonusType } from "@spt/models/enums/BonusType";
@ -90,6 +91,24 @@ export declare class ProfileHelper {
* @returns ISptProfile object
*/
getFullProfile(sessionID: string): ISptProfile | undefined;
/**
* Get full representation of a players profile JSON by the account ID, or undefined if not found
* @param accountId Account ID to find
* @returns
*/
getFullProfileByAccountId(accountID: string): ISptProfile | undefined;
/**
* Retrieve a ChatRoomMember formatted profile for the given session ID
* @param sessionID The session ID to return the profile for
* @returns
*/
getChatRoomMemberFromSessionId(sessionID: string): ISearchFriendResponse | undefined;
/**
* Retrieve a ChatRoomMember formatted profile for the given PMC profile data
* @param pmcProfile The PMC profile data to format into a ChatRoomMember structure
* @returns
*/
getChatRoomMemberFromPmcProfile(pmcProfile: IPmcData): ISearchFriendResponse;
/**
* Get a PMC profile by its session id
* @param sessionID Profile id to return

View File

@ -11,23 +11,36 @@ export interface ILocationBase {
Banners: IBanner[];
BossLocationSpawn: IBossLocationSpawn[];
BotAssault: number;
/** Weighting on how likely a bot will be Easy difficulty */
BotEasy: number;
/** Weighting on how likely a bot will be Hard difficulty */
BotHard: number;
/** Weighting on how likely a bot will be Impossible difficulty */
BotImpossible: number;
BotLocationModifier: IBotLocationModifier;
BotMarksman: number;
/** Maximum Number of bots that are currently alive/loading/delayed */
BotMax: number;
/** Is not used in 33420 */
BotMaxPlayer: number;
/** Is not used in 33420 */
BotMaxTimePlayer: number;
/** Does not even exist in the client in 33420 */
BotMaxPvE: number;
/** Weighting on how likely a bot will be Normal difficulty */
BotNormal: number;
/** How many bot slots that need to be open before trying to spawn new bots. */
BotSpawnCountStep: number;
/** How often to check if bots are spawn-able. In seconds */
BotSpawnPeriodCheck: number;
/** The bot spawn will toggle on and off in intervals of Off(Min/Max) and On(Min/Max) */
BotSpawnTimeOffMax: number;
BotSpawnTimeOffMin: number;
BotSpawnTimeOnMax: number;
BotSpawnTimeOnMin: number;
/** How soon bots will be allowed to spawn */
BotStart: number;
/** After this long bots will no longer spawn */
BotStop: number;
Description: string;
DisabledForScav: boolean;

View File

@ -33,6 +33,11 @@ export interface IQuest {
changeQuestMessageText: string;
/** "Pmc" or "Scav" */
side: string;
acceptanceAndFinishingSource: string;
progressSource: string;
rankingModes: string[];
gameModes: string[];
arenaLocations: string[];
/** Status of quest to player */
sptStatus?: QuestStatus;
}
@ -148,8 +153,10 @@ export interface IQuestReward {
loyaltyLevel?: number;
/** Hideout area id */
traderId?: string;
isEncoded?: boolean;
unknown?: boolean;
findInRaid?: boolean;
gameMode?: string[];
/** Game editions whitelisted to get reward */
availableInGameEditions?: string[];
/** Game editions blacklisted from getting reward */

View File

@ -3,6 +3,12 @@ export interface IRepeatableQuest extends IQuest {
changeCost: IChangeCost[];
changeStandingCost: number;
sptRepatableGroupName: string;
acceptanceAndFinishingSource: string;
progressSource: string;
rankingModes: string[];
gameModes: string[];
arenaLocations: string[];
questStatus: IRepeatableQuestStatus;
}
export interface IRepeatableQuestDatabase {
templates: IRepeatableTemplates;
@ -10,6 +16,14 @@ export interface IRepeatableQuestDatabase {
data: IOptions;
samples: ISampleQuests[];
}
export interface IRepeatableQuestStatus {
id: string;
uid: string;
qid: string;
startTime: number;
status: number;
statusTimers: any;
}
export interface IRepeatableTemplates {
Elimination: IQuest;
Completion: IQuest;

View File

@ -8,4 +8,5 @@ export interface Info {
Side: string;
Level: number;
MemberCategory: number;
SelectedMemberCategory: number;
}

View File

@ -19,6 +19,8 @@ export interface ISptProfile {
traderPurchases?: Record<string, Record<string, ITraderPurchaseData>>;
/** Achievements earned by player */
achievements: Record<string, number>;
/** List of friend profile IDs */
friends: string[];
}
export declare class ITraderPurchaseData {
count: number;

View File

@ -0,0 +1,5 @@
import { IWsNotificationEvent } from "@spt/models/eft/ws/IWsNotificationEvent";
import { ISearchFriendResponse } from "../profile/ISearchFriendResponse";
export interface IWsFriendsListAccept extends IWsNotificationEvent {
profile: ISearchFriendResponse;
}

View File

@ -1,4 +1,4 @@
export interface IWsNotificationEvent {
type: string;
eventId: string;
eventId?: string;
}

View File

@ -1,5 +1,6 @@
export declare enum ConfigTypes {
AIRDROP = "spt-airdrop",
BACKUP = "spt-backup",
BOT = "spt-bot",
PMC = "spt-pmc",
CORE = "spt-core",

View File

@ -0,0 +1,12 @@
import { IBaseConfig } from "@spt/models/spt/config/IBaseConfig";
export interface IBackupConfig extends IBaseConfig {
kind: "spt-backup";
enabled: boolean;
maxBackups: number;
directory: string;
backupInterval: IBackupConfigInterval;
}
export interface IBackupConfigInterval {
enabled: boolean;
intervalMinutes: number;
}

View File

@ -7,6 +7,8 @@ export interface IItemConfig extends IBaseConfig {
lootableItemBlacklist: string[];
/** items that should not be given as rewards */
rewardItemBlacklist: string[];
/** Item base types that should not be given as rewards */
rewardItemTypeBlacklist: string[];
/** Items that can only be found on bosses */
bossItems: string[];
handbookPriceOverride: Record<string, IHandbookPriceOverride>;

View File

@ -0,0 +1,98 @@
import { PreSptModLoader } from "@spt/loaders/PreSptModLoader";
import { IBackupConfig } from "@spt/models/spt/config/IBackupConfig";
import { ILogger } from "@spt/models/spt/utils/ILogger";
import { ConfigServer } from "@spt/servers/ConfigServer";
export declare class BackupService {
protected logger: ILogger;
protected preSptModLoader: PreSptModLoader;
protected configServer: ConfigServer;
protected backupConfig: IBackupConfig;
protected readonly activeServerMods: string[];
protected readonly profileDir = "./user/profiles";
constructor(logger: ILogger, preSptModLoader: PreSptModLoader, configServer: ConfigServer);
/**
* Initializes the backup process.
*
* This method orchestrates the profile backup service. Handles copying profiles to a backup directory and cleaning
* up old backups if the number exceeds the configured maximum.
*
* @returns A promise that resolves when the backup process is complete.
*/
init(): Promise<void>;
/**
* Fetches the names of all JSON files in the profile directory.
*
* This method normalizes the profile directory path and reads all files within it. It then filters the files to
* include only those with a `.json` extension and returns their names.
*
* @returns A promise that resolves to an array of JSON file names.
*/
protected fetchProfileFiles(): Promise<string[]>;
/**
* Check to see if the backup service is enabled via the config.
*
* @returns True if enabled, false otherwise.
*/
protected isEnabled(): boolean;
/**
* Generates the target directory path for the backup. The directory path is constructed using the `directory` from
* the configuration and the current backup date.
*
* @returns The target directory path for the backup.
*/
protected generateBackupTargetDir(): string;
/**
* Generates a formatted backup date string in the format `YYYY-MM-DD_hh-mm-ss`.
*
* @returns The formatted backup date string.
*/
protected generateBackupDate(): string;
/**
* Cleans up old backups in the backup directory.
*
* This method reads the backup directory, and sorts backups by modification time. If the number of backups exceeds
* the configured maximum, it deletes the oldest backups.
*
* @returns A promise that resolves when the cleanup is complete.
*/
protected cleanBackups(): Promise<void>;
/**
* Retrieves and sorts the backup file paths from the specified directory.
*
* @param dir - The directory to search for backup files.
* @returns A promise that resolves to an array of sorted backup file paths.
*/
private getBackupPaths;
/**
* Compares two backup folder names based on their extracted dates.
*
* @param a - The name of the first backup folder.
* @param b - The name of the second backup folder.
* @returns The difference in time between the two dates in milliseconds, or `null` if either date is invalid.
*/
private compareBackupDates;
/**
* Extracts a date from a folder name string formatted as `YYYY-MM-DD_hh-mm-ss`.
*
* @param folderName - The name of the folder from which to extract the date.
* @returns A Date object if the folder name is in the correct format, otherwise null.
*/
private extractDateFromFolderName;
/**
* Removes excess backups from the backup directory.
*
* @param backups - An array of backup file names to be removed.
* @returns A promise that resolves when all specified backups have been removed.
*/
private removeExcessBackups;
/**
* Start the backup interval if enabled in the configuration.
*/
protected startBackupInterval(): void;
/**
* Get an array of active server mod details.
*
* @returns An array of mod names.
*/
protected getActiveServerMods(): string[];
}

View File

@ -54,6 +54,16 @@ export declare class CircleOfCultistService {
* @returns IItemEventRouterResponse
*/
startSacrifice(sessionId: string, pmcData: IPmcData, request: IHideoutCircleOfCultistProductionStartRequestData): IItemEventRouterResponse;
/**
* Attempt to add all rewards to cultist circle, if they dont fit remove one and try again until they fit
* @param sessionId Session id
* @param pmcData Player profile
* @param rewards Rewards to send to player
* @param containerGrid Cultist grid to add rewards to
* @param cultistCircleStashId Stash id
* @param output Client output
*/
protected addRewardsToCircleContainer(sessionId: string, pmcData: IPmcData, rewards: IItem[][], containerGrid: number[][], cultistCircleStashId: string, output: IItemEventRouterResponse): void;
/**
* Create a map of the possible direct rewards, keyed by the items needed to be sacrificed
* @param directRewards Direct rewards array from hideout config
@ -156,7 +166,7 @@ export declare class CircleOfCultistService {
* @param itemRewardBlacklist Items not to add to pool
* @param rewardPool Pool to add items to
*/
protected addTaskItemRequirementsToRewardPool(pmcData: IPmcData, itemRewardBlacklist: string[], rewardPool: Set<string>): void;
protected addTaskItemRequirementsToRewardPool(pmcData: IPmcData, itemRewardBlacklist: Set<string>, rewardPool: Set<string>): void;
/**
* Adds items the player needs to complete hideout crafts/upgrades to the reward pool
* @param hideoutDbData Hideout area data
@ -164,7 +174,7 @@ export declare class CircleOfCultistService {
* @param itemRewardBlacklist Items not to add to pool
* @param rewardPool Pool to add items to
*/
protected addHideoutUpgradeRequirementsToRewardPool(hideoutDbData: IHideout, pmcData: IPmcData, itemRewardBlacklist: string[], rewardPool: Set<string>): void;
protected addHideoutUpgradeRequirementsToRewardPool(hideoutDbData: IHideout, pmcData: IPmcData, itemRewardBlacklist: Set<string>, rewardPool: Set<string>): void;
/**
* Get all active hideout areas
* @param areas Hideout areas to iterate over
@ -174,11 +184,11 @@ export declare class CircleOfCultistService {
/**
* Get array of random reward items
* @param rewardPool Reward pool to add to
* @param itemRewardBlacklist Reward Blacklist
* @param itemRewardBlacklist Item tpls to ignore
* @param itemsShouldBeHighValue Should these items meet the valuable threshold
* @returns rewardPool
* @returns Set of item tpls
*/
protected generateRandomisedItemsAndAddToRewardPool(rewardPool: Set<string>, itemRewardBlacklist: string[], itemsShouldBeHighValue: boolean): Set<string>;
protected generateRandomisedItemsAndAddToRewardPool(rewardPool: Set<string>, itemRewardBlacklist: Set<string>, itemsShouldBeHighValue: boolean): Set<string>;
/**
* Iterate over passed in hideout requirements and return the Item
* @param requirements Requirements to iterate over

View File

@ -36,6 +36,11 @@ export declare class ItemFilterService {
* @returns string array of item tpls
*/
getItemRewardBlacklist(): string[];
/**
* Get an array of item types that should never be given as a reward to player
* @returns string array of item base ids
*/
getItemRewardBaseTypeBlacklist(): string[];
/**
* Return every template id blacklisted in config/item.json
* @returns string array of blacklisted tempalte ids

View File

@ -4,10 +4,10 @@ import { ICoreConfig } from "@spt/models/spt/config/ICoreConfig";
import { ILogger } from "@spt/models/spt/utils/ILogger";
import { ConfigServer } from "@spt/servers/ConfigServer";
import { HttpServer } from "@spt/servers/HttpServer";
import { DatabaseService } from "@spt/services/DatabaseService";
import { LocalisationService } from "@spt/services/LocalisationService";
import { EncodingUtil } from "@spt/utils/EncodingUtil";
import { TimeUtil } from "@spt/utils/TimeUtil";
import { DatabaseService } from "@spt/services/DatabaseService";
export declare class App {
protected logger: ILogger;
protected timeUtil: TimeUtil;

View File

@ -106,6 +106,11 @@ export declare class RandomUtil {
protected cloner: ICloner;
protected logger: ILogger;
constructor(cloner: ICloner, logger: ILogger);
/**
* The IEEE-754 standard for double-precision floating-point numbers limits the number of digits (including both
* integer + fractional parts) to about 1517 significant digits. 15 is a safe upper bound, so we'll use that.
*/
private static readonly MAX_SIGNIFICANT_DIGITS;
/**
* Generates a secure random number between 0 (inclusive) and 1 (exclusive).
*
@ -116,6 +121,16 @@ export declare class RandomUtil {
* @returns A secure random number between 0 (inclusive) and 1 (exclusive).
*/
private getSecureRandomNumber;
/**
* Determines the number of decimal places in a number.
*
* @param num - The number to analyze.
* @returns The number of decimal places, or 0 if none exist.
* @remarks There is a mathematical way to determine this, but it's not as simple as it seams due to floating point
* precision issues. This method is a simple workaround that converts the number to a string and splits it.
* It's not the most efficient but it *is* the most reliable and easy to understand. Come at me.
*/
private getNumberPrecision;
/**
* Generates a random integer between the specified minimum and maximum values, inclusive.
*
@ -173,7 +188,7 @@ export declare class RandomUtil {
/**
* Returns a random string from the provided array of strings.
*
* This method is separate from getArrayValue so we can use a generic inferance with getArrayValue.
* This method is separate from getArrayValue so we can use a generic inference with getArrayValue.
*
* @param arr - The array of strings to select a random value from.
* @returns A randomly selected string from the array.
@ -225,12 +240,27 @@ export declare class RandomUtil {
getNormallyDistributedRandomNumber(mean: number, sigma: number, attempt?: number): number;
/**
* Generates a random integer between the specified range.
* Low and high parameters are floored to integers.
*
* TODO: v3.11 - This method should not accept non-integer numbers.
*
* @param low - The lower bound of the range (inclusive).
* @param high - The upper bound of the range (exclusive). If not provided, the range will be from 0 to `low`.
* @returns A random integer within the specified range.
*/
randInt(low: number, high?: number): number;
/**
* Generates a random number between two given values with optional precision.
*
* @param value1 - The first value to determine the range.
* @param value2 - The second value to determine the range. If not provided, 0 is used.
* @param precision - The number of decimal places to round the result to. Must be a positive integer between 0
* and MAX_PRECISION, inclusive. If not provided, precision is determined by the input values.
* @returns A random floating-point number between `value1` and `value2` (inclusive) with the specified precision.
* @throws Will throw an error if `precision` is not a positive integer, if `value1` or `value2` are not finite
* numbers, or if the precision exceeds the maximum allowed for the given values.
*/
randNum(value1: number, value2?: number, precision?: number | null): number;
/**
* Draws a specified number of random elements from a given list.
*

View File

@ -3,11 +3,13 @@ import { OnUpdate } from "@spt/di/OnUpdate";
import { ICoreConfig } from "@spt/models/spt/config/ICoreConfig";
import { ConfigServer } from "@spt/servers/ConfigServer";
import { SaveServer } from "@spt/servers/SaveServer";
import { BackupService } from "@spt/services/BackupService";
export declare class SaveCallbacks implements OnLoad, OnUpdate {
protected saveServer: SaveServer;
protected configServer: ConfigServer;
protected backupService: BackupService;
protected coreConfig: ICoreConfig;
constructor(saveServer: SaveServer, configServer: ConfigServer);
constructor(saveServer: SaveServer, configServer: ConfigServer, backupService: BackupService);
onLoad(): Promise<void>;
getRoute(): string;
onUpdate(secondsSinceLastRun: number): Promise<boolean>;

View File

@ -1,5 +1,8 @@
import { IDialogueChatBot } from "@spt/helpers/Dialogue/IDialogueChatBot";
import { DialogueHelper } from "@spt/helpers/DialogueHelper";
import { NotificationSendHelper } from "@spt/helpers/NotificationSendHelper";
import { ProfileHelper } from "@spt/helpers/ProfileHelper";
import { IDeleteFriendRequest } from "@spt/models/eft/dialog/IDeleteFriendRequest";
import { IFriendRequestData } from "@spt/models/eft/dialog/IFriendRequestData";
import { IFriendRequestSendResponse } from "@spt/models/eft/dialog/IFriendRequestSendResponse";
import { IGetAllAttachmentsResponse } from "@spt/models/eft/dialog/IGetAllAttachmentsResponse";
@ -20,11 +23,13 @@ export declare class DialogueController {
protected saveServer: SaveServer;
protected timeUtil: TimeUtil;
protected dialogueHelper: DialogueHelper;
protected notificationSendHelper: NotificationSendHelper;
protected profileHelper: ProfileHelper;
protected mailSendService: MailSendService;
protected localisationService: LocalisationService;
protected configServer: ConfigServer;
protected dialogueChatBots: IDialogueChatBot[];
constructor(logger: ILogger, saveServer: SaveServer, timeUtil: TimeUtil, dialogueHelper: DialogueHelper, mailSendService: MailSendService, localisationService: LocalisationService, configServer: ConfigServer, dialogueChatBots: IDialogueChatBot[]);
constructor(logger: ILogger, saveServer: SaveServer, timeUtil: TimeUtil, dialogueHelper: DialogueHelper, notificationSendHelper: NotificationSendHelper, profileHelper: ProfileHelper, mailSendService: MailSendService, localisationService: LocalisationService, configServer: ConfigServer, dialogueChatBots: IDialogueChatBot[]);
registerChatBot(chatBot: IDialogueChatBot): void;
/** Handle onUpdate spt event */
update(): void;
@ -151,4 +156,6 @@ export declare class DialogueController {
protected messageHasExpired(message: IMessage): boolean;
/** Handle client/friend/request/send */
sendFriendRequest(sessionID: string, request: IFriendRequestData): IFriendRequestSendResponse;
/** Handle client/friend/delete */
deleteFriend(sessionID: string, request: IDeleteFriendRequest): void;
}

View File

@ -72,6 +72,7 @@ export declare class QuestController {
*/
protected addTaskConditionCountersToProfile(questConditions: IQuestCondition[], pmcData: IPmcData, questId: string): void;
/**
* TODO - Move this code into RepeatableQuestController
* Handle the client accepting a repeatable quest and starting it
* Send starting rewards if any to player and
* Send start notification if any to player
@ -81,7 +82,6 @@ export declare class QuestController {
* @returns IItemEventRouterResponse
*/
acceptRepeatableQuest(pmcData: IPmcData, acceptedQuest: IAcceptQuestRequestData, sessionID: string): IItemEventRouterResponse;
protected createAcceptedQuestClientResponse(sessionID: string, pmcData: IPmcData, repeatableQuestProfile: IRepeatableQuest): IItemEventRouterResponse;
/**
* Look for an accepted quest inside player profile, return matching
* @param pmcData Profile to search through

View File

@ -147,6 +147,18 @@ export declare class RepeatableQuestController {
* @returns IItemEventRouterResponse
*/
changeRepeatableQuest(pmcData: IPmcData, changeRequest: IRepeatableQuestChangeRequest, sessionID: string): IItemEventRouterResponse;
/**
* Remove the provided quest from pmc and scav character profiles
* @param fullProfile Profile to remove quest from
* @param questToReplaceId Quest id to remove from profile
*/
protected removeQuestFromProfile(fullProfile: ISptProfile, questToReplaceId: string): void;
/**
* Clean up the repeatables `changeRequirement` dictionary of expired data
* @param repeatablesOfTypeInProfile The repeatables that have the replaced and new quest
* @param replacedQuestId Id of the replaced quest
*/
protected cleanUpRepeatableChangeRequirements(repeatablesOfTypeInProfile: IPmcDataRepeatableQuest, replacedQuestId: string): void;
/**
* Find a repeatable (daily/weekly/scav) from a players profile by its id
* @param questId Id of quest to find
@ -154,7 +166,7 @@ export declare class RepeatableQuestController {
* @returns IGetRepeatableByIdResult
*/
protected getRepeatableById(questId: string, pmcData: IPmcData): IGetRepeatableByIdResult;
protected attemptToGenerateRepeatableQuest(pmcData: IPmcData, questTypePool: IQuestTypePool, repeatableConfig: IRepeatableQuestConfig): IRepeatableQuest;
protected attemptToGenerateRepeatableQuest(sessionId: string, pmcData: IPmcData, questTypePool: IQuestTypePool, repeatableConfig: IRepeatableQuestConfig): IRepeatableQuest;
/**
* Some accounts have access to free repeatable quest refreshes
* Track the usage of them inside players profile

View File

@ -33,13 +33,14 @@ export declare class RepeatableQuestGenerator {
/**
* This method is called by /GetClientRepeatableQuests/ and creates one element of quest type format (see assets/database/templates/repeatableQuests.json).
* It randomly draws a quest type (currently Elimination, Completion or Exploration) as well as a trader who is providing the quest
* @param sessionId Session id
* @param pmcLevel Player's level for requested items and reward generation
* @param pmcTraderInfo Players traper standing/rep levels
* @param questTypePool Possible quest types pool
* @param repeatableConfig Repeatable quest config
* @returns IRepeatableQuest
*/
generateRepeatableQuest(pmcLevel: number, pmcTraderInfo: Record<string, ITraderInfo>, questTypePool: IQuestTypePool, repeatableConfig: IRepeatableQuestConfig): IRepeatableQuest;
generateRepeatableQuest(sessionId: string, pmcLevel: number, pmcTraderInfo: Record<string, ITraderInfo>, questTypePool: IQuestTypePool, repeatableConfig: IRepeatableQuestConfig): IRepeatableQuest;
/**
* Generate a randomised Elimination quest
* @param pmcLevel Player's level for requested items and reward generation
@ -48,7 +49,7 @@ export declare class RepeatableQuestGenerator {
* @param repeatableConfig The configuration for the repeatably kind (daily, weekly) as configured in QuestConfig for the requestd quest
* @returns Object of quest type format for "Elimination" (see assets/database/templates/repeatableQuests.json)
*/
protected generateEliminationQuest(pmcLevel: number, traderId: string, questTypePool: IQuestTypePool, repeatableConfig: IRepeatableQuestConfig): IRepeatableQuest;
protected generateEliminationQuest(sessionid: string, pmcLevel: number, traderId: string, questTypePool: IQuestTypePool, repeatableConfig: IRepeatableQuestConfig): IRepeatableQuest;
/**
* Get a number of kills neded to complete elimination quest
* @param targetKey Target type desired e.g. anyPmc/bossBully/Savage
@ -83,7 +84,7 @@ export declare class RepeatableQuestGenerator {
* @param {object} repeatableConfig The configuration for the repeatably kind (daily, weekly) as configured in QuestConfig for the requestd quest
* @returns {object} object of quest type format for "Completion" (see assets/database/templates/repeatableQuests.json)
*/
protected generateCompletionQuest(pmcLevel: number, traderId: string, repeatableConfig: IRepeatableQuestConfig): IRepeatableQuest;
protected generateCompletionQuest(sessionId: string, pmcLevel: number, traderId: string, repeatableConfig: IRepeatableQuestConfig): IRepeatableQuest;
/**
* A repeatable quest, besides some more or less static components, exists of reward and condition (see assets/database/templates/repeatableQuests.json)
* This is a helper method for GenerateCompletionQuest to create a completion condition (of which a completion quest theoretically can have many)
@ -102,7 +103,7 @@ export declare class RepeatableQuestGenerator {
* @param {object} repeatableConfig The configuration for the repeatably kind (daily, weekly) as configured in QuestConfig for the requestd quest
* @returns {object} object of quest type format for "Exploration" (see assets/database/templates/repeatableQuests.json)
*/
protected generateExplorationQuest(pmcLevel: number, traderId: string, questTypePool: IQuestTypePool, repeatableConfig: IRepeatableQuestConfig): IRepeatableQuest;
protected generateExplorationQuest(sessionId: string, pmcLevel: number, traderId: string, questTypePool: IQuestTypePool, repeatableConfig: IRepeatableQuestConfig): IRepeatableQuest;
/**
* Filter a maps exits to just those for the desired side
* @param locationKey Map id (e.g. factory4_day)
@ -110,7 +111,7 @@ export declare class RepeatableQuestGenerator {
* @returns Array of Exit objects
*/
protected getLocationExitsForSide(locationKey: string, playerSide: string): IExit[];
protected generatePickupQuest(pmcLevel: number, traderId: string, questTypePool: IQuestTypePool, repeatableConfig: IRepeatableQuestConfig): IRepeatableQuest;
protected generatePickupQuest(sessionId: string, pmcLevel: number, traderId: string, questTypePool: IQuestTypePool, repeatableConfig: IRepeatableQuestConfig): IRepeatableQuest;
/**
* Convert a location into an quest code can read (e.g. factory4_day into 55f2d3fd4bdc2d5f408b4567)
* @param locationKey e.g factory4_day
@ -135,5 +136,5 @@ export declare class RepeatableQuestGenerator {
* @returns {object} Object which contains the base elements for repeatable quests of the requests type
* (needs to be filled with reward and conditions by called to make a valid quest)
*/
protected generateRepeatableTemplate(type: string, traderId: string, side: string): IRepeatableQuest;
protected generateRepeatableTemplate(type: string, traderId: string, side: string, sessionId: string): IRepeatableQuest;
}

View File

@ -12,6 +12,7 @@ import { DatabaseService } from "@spt/services/DatabaseService";
import { ItemFilterService } from "@spt/services/ItemFilterService";
import { LocalisationService } from "@spt/services/LocalisationService";
import { SeasonalEventService } from "@spt/services/SeasonalEventService";
import { HashUtil } from "@spt/utils/HashUtil";
import { MathUtil } from "@spt/utils/MathUtil";
import { ObjectId } from "@spt/utils/ObjectId";
import { RandomUtil } from "@spt/utils/RandomUtil";
@ -19,6 +20,7 @@ import { ICloner } from "@spt/utils/cloners/ICloner";
export declare class RepeatableQuestRewardGenerator {
protected logger: ILogger;
protected randomUtil: RandomUtil;
protected hashUtil: HashUtil;
protected mathUtil: MathUtil;
protected databaseService: DatabaseService;
protected itemHelper: ItemHelper;
@ -31,7 +33,7 @@ export declare class RepeatableQuestRewardGenerator {
protected configServer: ConfigServer;
protected cloner: ICloner;
protected questConfig: IQuestConfig;
constructor(logger: ILogger, randomUtil: RandomUtil, mathUtil: MathUtil, databaseService: DatabaseService, itemHelper: ItemHelper, presetHelper: PresetHelper, handbookHelper: HandbookHelper, localisationService: LocalisationService, objectId: ObjectId, itemFilterService: ItemFilterService, seasonalEventService: SeasonalEventService, configServer: ConfigServer, cloner: ICloner);
constructor(logger: ILogger, randomUtil: RandomUtil, hashUtil: HashUtil, mathUtil: MathUtil, databaseService: DatabaseService, itemHelper: ItemHelper, presetHelper: PresetHelper, handbookHelper: HandbookHelper, localisationService: LocalisationService, objectId: ObjectId, itemFilterService: ItemFilterService, seasonalEventService: SeasonalEventService, configServer: ConfigServer, cloner: ICloner);
/**
* Generate the reward for a mission. A reward can consist of:
* - Experience
@ -127,7 +129,7 @@ export declare class RepeatableQuestRewardGenerator {
* @param preset Optional array of preset items
* @returns {object} Object of "Reward"-item-type
*/
protected generateItemReward(tpl: string, count: number, index: number): IQuestReward;
protected generateItemReward(tpl: string, count: number, index: number, foundInRaid?: boolean): IQuestReward;
/**
* Helper to create a reward item structured as required by the client
*
@ -137,7 +139,7 @@ export declare class RepeatableQuestRewardGenerator {
* @param preset Optional array of preset items
* @returns {object} Object of "Reward"-item-type
*/
protected generatePresetReward(tpl: string, count: number, index: number, preset?: IItem[]): IQuestReward;
protected generatePresetReward(tpl: string, count: number, index: number, preset?: IItem[], foundInRaid?: boolean): IQuestReward;
/**
* Picks rewardable items from items.json
* This means they must:

View File

@ -2,6 +2,7 @@ import { ItemHelper } from "@spt/helpers/ItemHelper";
import { IPmcData } from "@spt/models/eft/common/IPmcData";
import { Common, ICounterKeyValue, IStats } from "@spt/models/eft/common/tables/IBotBase";
import { IItem } from "@spt/models/eft/common/tables/IItem";
import { ISearchFriendResponse } from "@spt/models/eft/profile/ISearchFriendResponse";
import { ISptProfile } from "@spt/models/eft/profile/ISptProfile";
import { IValidateNicknameRequestData } from "@spt/models/eft/profile/IValidateNicknameRequestData";
import { BonusType } from "@spt/models/enums/BonusType";
@ -90,6 +91,24 @@ export declare class ProfileHelper {
* @returns ISptProfile object
*/
getFullProfile(sessionID: string): ISptProfile | undefined;
/**
* Get full representation of a players profile JSON by the account ID, or undefined if not found
* @param accountId Account ID to find
* @returns
*/
getFullProfileByAccountId(accountID: string): ISptProfile | undefined;
/**
* Retrieve a ChatRoomMember formatted profile for the given session ID
* @param sessionID The session ID to return the profile for
* @returns
*/
getChatRoomMemberFromSessionId(sessionID: string): ISearchFriendResponse | undefined;
/**
* Retrieve a ChatRoomMember formatted profile for the given PMC profile data
* @param pmcProfile The PMC profile data to format into a ChatRoomMember structure
* @returns
*/
getChatRoomMemberFromPmcProfile(pmcProfile: IPmcData): ISearchFriendResponse;
/**
* Get a PMC profile by its session id
* @param sessionID Profile id to return

View File

@ -11,23 +11,36 @@ export interface ILocationBase {
Banners: IBanner[];
BossLocationSpawn: IBossLocationSpawn[];
BotAssault: number;
/** Weighting on how likely a bot will be Easy difficulty */
BotEasy: number;
/** Weighting on how likely a bot will be Hard difficulty */
BotHard: number;
/** Weighting on how likely a bot will be Impossible difficulty */
BotImpossible: number;
BotLocationModifier: IBotLocationModifier;
BotMarksman: number;
/** Maximum Number of bots that are currently alive/loading/delayed */
BotMax: number;
/** Is not used in 33420 */
BotMaxPlayer: number;
/** Is not used in 33420 */
BotMaxTimePlayer: number;
/** Does not even exist in the client in 33420 */
BotMaxPvE: number;
/** Weighting on how likely a bot will be Normal difficulty */
BotNormal: number;
/** How many bot slots that need to be open before trying to spawn new bots. */
BotSpawnCountStep: number;
/** How often to check if bots are spawn-able. In seconds */
BotSpawnPeriodCheck: number;
/** The bot spawn will toggle on and off in intervals of Off(Min/Max) and On(Min/Max) */
BotSpawnTimeOffMax: number;
BotSpawnTimeOffMin: number;
BotSpawnTimeOnMax: number;
BotSpawnTimeOnMin: number;
/** How soon bots will be allowed to spawn */
BotStart: number;
/** After this long bots will no longer spawn */
BotStop: number;
Description: string;
DisabledForScav: boolean;

View File

@ -33,6 +33,11 @@ export interface IQuest {
changeQuestMessageText: string;
/** "Pmc" or "Scav" */
side: string;
acceptanceAndFinishingSource: string;
progressSource: string;
rankingModes: string[];
gameModes: string[];
arenaLocations: string[];
/** Status of quest to player */
sptStatus?: QuestStatus;
}
@ -148,8 +153,10 @@ export interface IQuestReward {
loyaltyLevel?: number;
/** Hideout area id */
traderId?: string;
isEncoded?: boolean;
unknown?: boolean;
findInRaid?: boolean;
gameMode?: string[];
/** Game editions whitelisted to get reward */
availableInGameEditions?: string[];
/** Game editions blacklisted from getting reward */

View File

@ -3,6 +3,12 @@ export interface IRepeatableQuest extends IQuest {
changeCost: IChangeCost[];
changeStandingCost: number;
sptRepatableGroupName: string;
acceptanceAndFinishingSource: string;
progressSource: string;
rankingModes: string[];
gameModes: string[];
arenaLocations: string[];
questStatus: IRepeatableQuestStatus;
}
export interface IRepeatableQuestDatabase {
templates: IRepeatableTemplates;
@ -10,6 +16,14 @@ export interface IRepeatableQuestDatabase {
data: IOptions;
samples: ISampleQuests[];
}
export interface IRepeatableQuestStatus {
id: string;
uid: string;
qid: string;
startTime: number;
status: number;
statusTimers: any;
}
export interface IRepeatableTemplates {
Elimination: IQuest;
Completion: IQuest;

View File

@ -8,4 +8,5 @@ export interface Info {
Side: string;
Level: number;
MemberCategory: number;
SelectedMemberCategory: number;
}

Some files were not shown because too many files have changed in this diff Show More