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; }