From ad6b94d8a9437419da9d07a9551dfb929c4d5497 Mon Sep 17 00:00:00 2001 From: Dev Date: Sun, 9 Jul 2023 14:45:06 +0100 Subject: [PATCH] Filter out event quests from data prior to sending them to player Rework season service to use enum instead of magic strings for event names Add config property to control showing/hiding non-seasonal event quests from player --- project/assets/configs/quest.json | 115 ++++++++++++++++++ project/assets/configs/seasonalevents.json | 6 +- project/src/controllers/GameController.ts | 4 +- project/src/controllers/QuestController.ts | 27 +++- project/src/models/enums/SeasonalEventType.ts | 6 + project/src/models/spt/config/IQuestConfig.ts | 13 ++ .../models/spt/config/ISeasonalEventConfig.ts | 2 + project/src/services/SeasonalEventService.ts | 68 ++++++----- 8 files changed, 208 insertions(+), 33 deletions(-) create mode 100644 project/src/models/enums/SeasonalEventType.ts diff --git a/project/assets/configs/quest.json b/project/assets/configs/quest.json index 264de1a9..fd9df0ba 100644 --- a/project/assets/configs/quest.json +++ b/project/assets/configs/quest.json @@ -14,6 +14,121 @@ "exploration": "62825ef60e88d037dc1eb42c" } }, + "showNonSeasonalEventQuests": false + "eventQuests": { + "641dbfd7f43eda9d810d7137": { + "name": "Important Patient", + "season": "None", + "startTimestamp": 1333753200000, + "endTimestamp": 1334444400000, + "yearly": false + }, + "64764abcd125ab430a14ccb5": { + "name": "Bloodhounds", + "season": "None", + "startTimestamp": 1338591600000, + "endTimestamp": 1340578800000, + "yearly": false + }, + "647710905320c660d91c15a5": { + "name": "Hint", + "season": "None", + "startTimestamp": 1338591600000, + "endTimestamp": , + "yearly": false + }, + "649567f359eab30d1b7c9585": { + "name": "Hustle", + "season": "None", + "startTimestamp": 1341270000000, + "endTimestamp": , + "yearly": false + }, + "649570491bb4d158bc4d0168": { + "name": "Tourist", + "season": "None", + "startTimestamp": 1341270000000, + "endTimestamp": "", + "yearly": false + }, + "64916da7ad4e722c106f2345": { + "name": "Failed setup", + "season": "None", + "startTimestamp": 1340665200000, + "endTimestamp": "", + "yearly": false + }, + "649af47d717cb30e7e4b5e26": { + "name": "Cocktail Tasting", + "season": "None", + "startTimestamp": 1341615600000, + "endTimestamp": "", + "yearly": false + }, + "61bb474dce7374453b45dfd2": { + "name": "Fairy Tale Showdown", + "season": "christmas", + "startTimestamp": 1701388800000, + "endTimestamp": 1703980800000, + "yearly": true + }, + "61bb474b1ab5304c3817a53a": { + "name": "Disrupting the Party", + "season": "christmas", + "startTimestamp": 1701388800000, + "endTimestamp": 1703980800000, + "yearly": true + }, + "61bb47481908c67d4249a205": { + "name": "No Gifts for You", + "season": "christmas", + "startTimestamp": 1701388800000, + "endTimestamp": 1703980800000, + "yearly": true + }, + "61bb468b8d7cac1532300ccc": { + "name": "Party Preparations", + "season": "christmas", + "startTimestamp": 1701388800000, + "endTimestamp": 1703980800000, + "yearly": true + }, + "61b9e1aaef9a1b5d6a79899a": { + "name": "Santas bag", + "season": "christmas", + "startTimestamp": 1701388800000, + "endTimestamp": 1703980800000, + "yearly": true + }, + "61bb47516b70332c062ca7b9": { + "name": "Stop the Fight!", + "season": "christmas", + "startTimestamp": 1701388800000, + "endTimestamp": 1703980800000, + "yearly": true + }, + "61bb475467f83663e155e26a": { + "name": "A Kind Snow Maiden", + "season": "christmas", + "startTimestamp": 1701388800000, + "endTimestamp": 1703980800000, + "yearly": true + }, + "61bb4756883b2c16a163870a": { + "name": "Home Comfort", + "season": "christmas", + "startTimestamp": 1701388800000, + "endTimestamp": 1703980800000, + "yearly": true + }, + "61bb47578d7cac1532300ccd": { + "name": "Bad Santa", + "season": "christmas", + "startTimestamp": 1701388800000, + "endTimestamp": 1703980800000, + "yearly": true + } + }, "repeatableQuests": [{ "name": "Daily", "side": "Pmc", diff --git a/project/assets/configs/seasonalevents.json b/project/assets/configs/seasonalevents.json index f152303a..24d9743a 100644 --- a/project/assets/configs/seasonalevents.json +++ b/project/assets/configs/seasonalevents.json @@ -218,14 +218,16 @@ ], "events": [{ "name": "halloween", + "type": "HALLOWEEN", "startDay": "24", "startMonth": "10", "endDay": "4", "endMonth": "11" }, { "name": "christmas", - "startDay": "16", - "startMonth": "12", + "type": "CHRISTMAS", + "startDay": "7", + "startMonth": "7", "endDay": "31", "endMonth": "12" } diff --git a/project/src/controllers/GameController.ts b/project/src/controllers/GameController.ts index ccdb3fa4..59c949ab 100644 --- a/project/src/controllers/GameController.ts +++ b/project/src/controllers/GameController.ts @@ -153,6 +153,7 @@ export class GameController } } + /** Check for any missing assorts inside each traders assort.json data, checking against traders qeustassort.json */ protected validateQuestAssortUnlocksExist(): void { const db = this.databaseServer.getTables(); @@ -176,7 +177,8 @@ export class GameController // Does assort key exist in trader assort file if (!traderAssorts.loyal_level_items[assortKey]) { - this.logger.warning(this.localisationService.getText("assort-missing_quest_assort_unlocks", {traderName: Object.keys(Traders)[Object.values(Traders).indexOf(traderId)], questName: quests[questKey].QuestName})); + // reverse lookup of enum key by value + this.logger.warning(this.localisationService.getText("assort-missing_quest_assort_unlock", {traderName: Object.keys(Traders)[Object.values(Traders).indexOf(traderId)], questName: quests[questKey].QuestName})); } } } diff --git a/project/src/controllers/QuestController.ts b/project/src/controllers/QuestController.ts index 92750066..d657afba 100644 --- a/project/src/controllers/QuestController.ts +++ b/project/src/controllers/QuestController.ts @@ -1,5 +1,6 @@ import { inject, injectable } from "tsyringe"; +import { SeasonalEventType } from "@spt-aki/models/enums/SeasonalEventType"; import { DialogueHelper } from "../helpers/DialogueHelper"; import { ItemHelper } from "../helpers/ItemHelper"; import { ProfileHelper } from "../helpers/ProfileHelper"; @@ -26,6 +27,7 @@ import { DatabaseServer } from "../servers/DatabaseServer"; import { LocaleService } from "../services/LocaleService"; import { LocalisationService } from "../services/LocalisationService"; import { PlayerService } from "../services/PlayerService"; +import { SeasonalEventService } from "../services/SeasonalEventService"; import { HttpResponseUtil } from "../utils/HttpResponseUtil"; import { TimeUtil } from "../utils/TimeUtil"; @@ -47,6 +49,7 @@ export class QuestController @inject("QuestConditionHelper") protected questConditionHelper: QuestConditionHelper, @inject("PlayerService") protected playerService: PlayerService, @inject("LocaleService") protected localeService: LocaleService, + @inject("SeasonalEventService") protected seasonalEventService: SeasonalEventService, @inject("LocalisationService") protected localisationService: LocalisationService, @inject("ConfigServer") protected configServer: ConfigServer ) @@ -54,7 +57,6 @@ export class QuestController this.questConfig = this.configServer.getConfig(ConfigTypes.QUEST); } - /** * Get all quests visible to player * Exclude quests with incomplete preconditions (level/loyalty) @@ -66,21 +68,42 @@ export class QuestController const quests: IQuest[] = []; const allQuests = this.questHelper.getQuestsFromDb(); const profile: IPmcData = this.profileHelper.getPmcProfile(sessionID); + const isChristmasEventActive = this.seasonalEventService.christmasEventEnabled(); + const isHalloweenEventActive = this.seasonalEventService.halloweenEventEnabled(); for (const quest of allQuests) { - // If a quest is already in the profile we need to just add it + // Player already accepted the quest, show it regardless of status if (profile.Quests.some(x => x.qid === quest._id)) { quests.push(quest); continue; } + // Filter out bear quests for usec and vice versa if (this.questIsForOtherSide(profile.Info.Side, quest._id)) { continue; } + // Not christmas + if (!isChristmasEventActive && this.seasonalEventService.isQuestRelatedToEvent(quest._id, SeasonalEventType.CHRISTMAS)) + { + continue; + } + + // Not halloween + quest is for halloween + if (!isHalloweenEventActive && this.seasonalEventService.isQuestRelatedToEvent(quest._id, SeasonalEventType.HALLOWEEN)) + { + continue; + } + + // Should event quests be shown to player + if (!this.questConfig.showNonSeasonalEventQuests && this.seasonalEventService.isQuestRelatedToEvent(quest._id, SeasonalEventType.NONE)) + { + continue; + } + // Don't add quests that have a level higher than the user's const levelConditions = this.questConditionHelper.getLevelConditions(quest.conditions.AvailableForStart); if (levelConditions.length) diff --git a/project/src/models/enums/SeasonalEventType.ts b/project/src/models/enums/SeasonalEventType.ts new file mode 100644 index 00000000..369a8261 --- /dev/null +++ b/project/src/models/enums/SeasonalEventType.ts @@ -0,0 +1,6 @@ +export enum SeasonalEventType + { + NONE = "None", + CHRISTMAS = "Christmas", + HALLOWEEN = "Halloween" +} \ No newline at end of file diff --git a/project/src/models/spt/config/IQuestConfig.ts b/project/src/models/spt/config/IQuestConfig.ts index 610d05e9..e029e7e0 100644 --- a/project/src/models/spt/config/IQuestConfig.ts +++ b/project/src/models/spt/config/IQuestConfig.ts @@ -1,4 +1,5 @@ import { MinMax } from "../../../models/common/MinMax"; +import { SeasonalEventType } from "../../../models/enums/SeasonalEventType"; import { ELocationName } from "../../enums/ELocationName"; import { IBaseConfig } from "./IBaseConfig"; @@ -7,6 +8,9 @@ export interface IQuestConfig extends IBaseConfig kind: "aki-quest" redeemTime: number questTemplateIds: IPlayerTypeQuestIds + /** Show non-seasonal quests be shown to player */ + showNonSeasonalEventQuests: boolean + eventQuests: Record repeatableQuests: IRepeatableQuestConfig[] locationIdMap: Record bearOnlyQuests: string[] @@ -25,6 +29,15 @@ export interface IQuestTypeIds Completion: string Exploration: string } + +export interface IEventQuestData +{ + name: string + season: SeasonalEventType + startTimestamp: number + endTimestamp: number + yearly: boolean +} export interface IRepeatableQuestConfig { diff --git a/project/src/models/spt/config/ISeasonalEventConfig.ts b/project/src/models/spt/config/ISeasonalEventConfig.ts index f51fd6df..0a4771d7 100644 --- a/project/src/models/spt/config/ISeasonalEventConfig.ts +++ b/project/src/models/spt/config/ISeasonalEventConfig.ts @@ -1,3 +1,4 @@ +import { SeasonalEventType } from "../../../models/enums/SeasonalEventType"; import { IBaseConfig } from "./IBaseConfig"; export interface ISeasonalEventConfig extends IBaseConfig @@ -13,6 +14,7 @@ export interface ISeasonalEventConfig extends IBaseConfig export interface ISeasonalEvent { name: string + type: SeasonalEventType startDay: number startMonth: number endDay: number diff --git a/project/src/services/SeasonalEventService.ts b/project/src/services/SeasonalEventService.ts index 2fb6ea40..4bc3bfae 100644 --- a/project/src/services/SeasonalEventService.ts +++ b/project/src/services/SeasonalEventService.ts @@ -4,6 +4,8 @@ import { BotHelper } from "../helpers/BotHelper"; import { Config } from "../models/eft/common/IGlobals"; import { Inventory } from "../models/eft/common/tables/IBotType"; import { ConfigTypes } from "../models/enums/ConfigTypes"; +import { SeasonalEventType } from "../models/enums/SeasonalEventType"; +import { IQuestConfig } from "../models/spt/config/IQuestConfig"; import { ISeasonalEvent, ISeasonalEventConfig } from "../models/spt/config/ISeasonalEventConfig"; import { ILocationData } from "../models/spt/server/ILocations"; import { ILogger } from "../models/spt/utils/ILogger"; @@ -15,6 +17,7 @@ import { LocalisationService } from "./LocalisationService"; export class SeasonalEventService { protected seasonalEventConfig: ISeasonalEventConfig; + protected questConfig: IQuestConfig; constructor( @inject("WinstonLogger") protected logger: ILogger, @@ -25,15 +28,7 @@ export class SeasonalEventService ) { this.seasonalEventConfig = this.configServer.getConfig(ConfigTypes.SEASONAL_EVENT); - } - - protected get events(): Record - { - return { - "None": "None", - "Christmas": "Christmas", - "Halloween": "Halloween" - }; + this.questConfig = this.configServer.getConfig(ConfigTypes.QUEST); } protected get christmasEventItems(): string[] @@ -144,26 +139,26 @@ export class SeasonalEventService */ public seasonalEventEnabled(): boolean { - return this.databaseServer.getTables().globals.config.EventType.includes(this.events.Christmas) || - this.databaseServer.getTables().globals.config.EventType.includes(this.events.Halloween); + return this.databaseServer.getTables().globals.config.EventType.includes(SeasonalEventType.CHRISTMAS) || + this.databaseServer.getTables().globals.config.EventType.includes(SeasonalEventType.HALLOWEEN); } /** - * is christmas event active + * Is christmas event active (Globals eventtype array contains even name) * @returns true if active */ public christmasEventEnabled(): boolean { - return this.databaseServer.getTables().globals.config.EventType.includes(this.events.Christmas); + return this.databaseServer.getTables().globals.config.EventType.includes(SeasonalEventType.CHRISTMAS); } /** - * is christmas event active + * is halloween event active (Globals eventtype array contains even name) * @returns true if active */ public halloweenEventEnabled(): boolean { - return this.databaseServer.getTables().globals.config.EventType.includes(this.events.Halloween); + return this.databaseServer.getTables().globals.config.EventType.includes(SeasonalEventType.HALLOWEEN); } /** @@ -180,9 +175,9 @@ export class SeasonalEventService * @param eventName Name of event to get gear changes for * @returns bots with equipment changes */ - protected getEventBotGear(eventName: string): Record>> + protected getEventBotGear(eventType: SeasonalEventType): Record>> { - return this.seasonalEventConfig.eventGear[eventName.toLowerCase()]; + return this.seasonalEventConfig.eventGear[eventType.toLowerCase()]; } /** @@ -194,6 +189,23 @@ export class SeasonalEventService return this.seasonalEventConfig.events; } + /** + * Look up quest in configs/quest.json + * @param questId Quest to look up + * @param event event type (Christmas/Halloween/None) + * @returns true if related + */ + public isQuestRelatedToEvent(questId: string, event: SeasonalEventType): boolean + { + const eventQuestData = this.questConfig.eventQuests[questId]; + if (eventQuestData?.season.toLowerCase() === event.toLowerCase()) + { + return true; + } + + return false; + } + /** * Check if current date falls inside any of the seasons events pased in, if so, handle them */ @@ -212,7 +224,7 @@ export class SeasonalEventService if (currentDate >= eventStartDate && currentDate <= eventEndDate) { - this.updateGlobalEvents(globalConfig, event.name); + this.updateGlobalEvents(globalConfig, event.type); } } } @@ -258,29 +270,29 @@ export class SeasonalEventService * @param globalConfig globals.json * @param eventName Name of the event to enable. e.g. Christmas */ - protected updateGlobalEvents(globalConfig: Config, eventName: string): void + protected updateGlobalEvents(globalConfig: Config, eventType: SeasonalEventType): void { - switch (eventName.toLowerCase()) + switch (eventType.toLowerCase()) { - case "halloween": + case SeasonalEventType.HALLOWEEN.toLowerCase(): globalConfig.EventType = globalConfig.EventType.filter(x => x !== "None"); globalConfig.EventType.push("Halloween"); globalConfig.EventType.push("HalloweenIllumination"); globalConfig.Health.ProfileHealthSettings.DefaultStimulatorBuff = "Buffs_Halloween"; - this.addEventGearToBots("halloween"); + this.addEventGearToBots(eventType); this.addPumpkinsToScavBackpacks(); break; - case "christmas": + case SeasonalEventType.CHRISTMAS.toLowerCase(): globalConfig.EventType = globalConfig.EventType.filter(x => x !== "None"); globalConfig.EventType.push("Christmas"); - this.addEventGearToBots("christmas"); + this.addEventGearToBots(eventType); this.addGifterBotToMaps(); this.addLootItemsToGifterDropItemsList(); this.enableDancingTree(); break; default: // Likely a mod event - this.addEventGearToBots(eventName.toLowerCase()); + this.addEventGearToBots(eventType); break; } } @@ -301,12 +313,12 @@ export class SeasonalEventService * Read in data from seasonalEvents.json and add found equipment items to bots * @param eventName Name of the event to read equipment in from config */ - protected addEventGearToBots(eventName: string): void + protected addEventGearToBots(eventType: SeasonalEventType): void { - const botGearChanges = this.getEventBotGear(eventName); + const botGearChanges = this.getEventBotGear(eventType); if (!botGearChanges) { - this.logger.warning(this.localisationService.getText("gameevent-no_gear_data", eventName)); + this.logger.warning(this.localisationService.getText("gameevent-no_gear_data", eventType)); return; }