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
This commit is contained in:
Dev 2023-07-09 14:45:06 +01:00
parent cad5dcd679
commit ad6b94d8a9
8 changed files with 208 additions and 33 deletions

View File

@ -14,6 +14,121 @@
"exploration": "62825ef60e88d037dc1eb42c" "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": [{ "repeatableQuests": [{
"name": "Daily", "name": "Daily",
"side": "Pmc", "side": "Pmc",

View File

@ -218,14 +218,16 @@
], ],
"events": [{ "events": [{
"name": "halloween", "name": "halloween",
"type": "HALLOWEEN",
"startDay": "24", "startDay": "24",
"startMonth": "10", "startMonth": "10",
"endDay": "4", "endDay": "4",
"endMonth": "11" "endMonth": "11"
}, { }, {
"name": "christmas", "name": "christmas",
"startDay": "16", "type": "CHRISTMAS",
"startMonth": "12", "startDay": "7",
"startMonth": "7",
"endDay": "31", "endDay": "31",
"endMonth": "12" "endMonth": "12"
} }

View File

@ -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 protected validateQuestAssortUnlocksExist(): void
{ {
const db = this.databaseServer.getTables(); const db = this.databaseServer.getTables();
@ -176,7 +177,8 @@ export class GameController
// Does assort key exist in trader assort file // Does assort key exist in trader assort file
if (!traderAssorts.loyal_level_items[assortKey]) 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}));
} }
} }
} }

View File

@ -1,5 +1,6 @@
import { inject, injectable } from "tsyringe"; import { inject, injectable } from "tsyringe";
import { SeasonalEventType } from "@spt-aki/models/enums/SeasonalEventType";
import { DialogueHelper } from "../helpers/DialogueHelper"; import { DialogueHelper } from "../helpers/DialogueHelper";
import { ItemHelper } from "../helpers/ItemHelper"; import { ItemHelper } from "../helpers/ItemHelper";
import { ProfileHelper } from "../helpers/ProfileHelper"; import { ProfileHelper } from "../helpers/ProfileHelper";
@ -26,6 +27,7 @@ import { DatabaseServer } from "../servers/DatabaseServer";
import { LocaleService } from "../services/LocaleService"; import { LocaleService } from "../services/LocaleService";
import { LocalisationService } from "../services/LocalisationService"; import { LocalisationService } from "../services/LocalisationService";
import { PlayerService } from "../services/PlayerService"; import { PlayerService } from "../services/PlayerService";
import { SeasonalEventService } from "../services/SeasonalEventService";
import { HttpResponseUtil } from "../utils/HttpResponseUtil"; import { HttpResponseUtil } from "../utils/HttpResponseUtil";
import { TimeUtil } from "../utils/TimeUtil"; import { TimeUtil } from "../utils/TimeUtil";
@ -47,6 +49,7 @@ export class QuestController
@inject("QuestConditionHelper") protected questConditionHelper: QuestConditionHelper, @inject("QuestConditionHelper") protected questConditionHelper: QuestConditionHelper,
@inject("PlayerService") protected playerService: PlayerService, @inject("PlayerService") protected playerService: PlayerService,
@inject("LocaleService") protected localeService: LocaleService, @inject("LocaleService") protected localeService: LocaleService,
@inject("SeasonalEventService") protected seasonalEventService: SeasonalEventService,
@inject("LocalisationService") protected localisationService: LocalisationService, @inject("LocalisationService") protected localisationService: LocalisationService,
@inject("ConfigServer") protected configServer: ConfigServer @inject("ConfigServer") protected configServer: ConfigServer
) )
@ -54,7 +57,6 @@ export class QuestController
this.questConfig = this.configServer.getConfig(ConfigTypes.QUEST); this.questConfig = this.configServer.getConfig(ConfigTypes.QUEST);
} }
/** /**
* Get all quests visible to player * Get all quests visible to player
* Exclude quests with incomplete preconditions (level/loyalty) * Exclude quests with incomplete preconditions (level/loyalty)
@ -66,21 +68,42 @@ export class QuestController
const quests: IQuest[] = []; const quests: IQuest[] = [];
const allQuests = this.questHelper.getQuestsFromDb(); const allQuests = this.questHelper.getQuestsFromDb();
const profile: IPmcData = this.profileHelper.getPmcProfile(sessionID); const profile: IPmcData = this.profileHelper.getPmcProfile(sessionID);
const isChristmasEventActive = this.seasonalEventService.christmasEventEnabled();
const isHalloweenEventActive = this.seasonalEventService.halloweenEventEnabled();
for (const quest of allQuests) 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)) if (profile.Quests.some(x => x.qid === quest._id))
{ {
quests.push(quest); quests.push(quest);
continue; continue;
} }
// Filter out bear quests for usec and vice versa
if (this.questIsForOtherSide(profile.Info.Side, quest._id)) if (this.questIsForOtherSide(profile.Info.Side, quest._id))
{ {
continue; 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 // Don't add quests that have a level higher than the user's
const levelConditions = this.questConditionHelper.getLevelConditions(quest.conditions.AvailableForStart); const levelConditions = this.questConditionHelper.getLevelConditions(quest.conditions.AvailableForStart);
if (levelConditions.length) if (levelConditions.length)

View File

@ -0,0 +1,6 @@
export enum SeasonalEventType
{
NONE = "None",
CHRISTMAS = "Christmas",
HALLOWEEN = "Halloween"
}

View File

@ -1,4 +1,5 @@
import { MinMax } from "../../../models/common/MinMax"; import { MinMax } from "../../../models/common/MinMax";
import { SeasonalEventType } from "../../../models/enums/SeasonalEventType";
import { ELocationName } from "../../enums/ELocationName"; import { ELocationName } from "../../enums/ELocationName";
import { IBaseConfig } from "./IBaseConfig"; import { IBaseConfig } from "./IBaseConfig";
@ -7,6 +8,9 @@ export interface IQuestConfig extends IBaseConfig
kind: "aki-quest" kind: "aki-quest"
redeemTime: number redeemTime: number
questTemplateIds: IPlayerTypeQuestIds questTemplateIds: IPlayerTypeQuestIds
/** Show non-seasonal quests be shown to player */
showNonSeasonalEventQuests: boolean
eventQuests: Record<string, IEventQuestData>
repeatableQuests: IRepeatableQuestConfig[] repeatableQuests: IRepeatableQuestConfig[]
locationIdMap: Record<string, string> locationIdMap: Record<string, string>
bearOnlyQuests: string[] bearOnlyQuests: string[]
@ -26,6 +30,15 @@ export interface IQuestTypeIds
Exploration: string Exploration: string
} }
export interface IEventQuestData
{
name: string
season: SeasonalEventType
startTimestamp: number
endTimestamp: number
yearly: boolean
}
export interface IRepeatableQuestConfig export interface IRepeatableQuestConfig
{ {
name: string name: string

View File

@ -1,3 +1,4 @@
import { SeasonalEventType } from "../../../models/enums/SeasonalEventType";
import { IBaseConfig } from "./IBaseConfig"; import { IBaseConfig } from "./IBaseConfig";
export interface ISeasonalEventConfig extends IBaseConfig export interface ISeasonalEventConfig extends IBaseConfig
@ -13,6 +14,7 @@ export interface ISeasonalEventConfig extends IBaseConfig
export interface ISeasonalEvent export interface ISeasonalEvent
{ {
name: string name: string
type: SeasonalEventType
startDay: number startDay: number
startMonth: number startMonth: number
endDay: number endDay: number

View File

@ -4,6 +4,8 @@ import { BotHelper } from "../helpers/BotHelper";
import { Config } from "../models/eft/common/IGlobals"; import { Config } from "../models/eft/common/IGlobals";
import { Inventory } from "../models/eft/common/tables/IBotType"; import { Inventory } from "../models/eft/common/tables/IBotType";
import { ConfigTypes } from "../models/enums/ConfigTypes"; 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 { ISeasonalEvent, ISeasonalEventConfig } from "../models/spt/config/ISeasonalEventConfig";
import { ILocationData } from "../models/spt/server/ILocations"; import { ILocationData } from "../models/spt/server/ILocations";
import { ILogger } from "../models/spt/utils/ILogger"; import { ILogger } from "../models/spt/utils/ILogger";
@ -15,6 +17,7 @@ import { LocalisationService } from "./LocalisationService";
export class SeasonalEventService export class SeasonalEventService
{ {
protected seasonalEventConfig: ISeasonalEventConfig; protected seasonalEventConfig: ISeasonalEventConfig;
protected questConfig: IQuestConfig;
constructor( constructor(
@inject("WinstonLogger") protected logger: ILogger, @inject("WinstonLogger") protected logger: ILogger,
@ -25,15 +28,7 @@ export class SeasonalEventService
) )
{ {
this.seasonalEventConfig = this.configServer.getConfig(ConfigTypes.SEASONAL_EVENT); this.seasonalEventConfig = this.configServer.getConfig(ConfigTypes.SEASONAL_EVENT);
} this.questConfig = this.configServer.getConfig(ConfigTypes.QUEST);
protected get events(): Record<string, string>
{
return {
"None": "None",
"Christmas": "Christmas",
"Halloween": "Halloween"
};
} }
protected get christmasEventItems(): string[] protected get christmasEventItems(): string[]
@ -144,26 +139,26 @@ export class SeasonalEventService
*/ */
public seasonalEventEnabled(): boolean public seasonalEventEnabled(): boolean
{ {
return this.databaseServer.getTables().globals.config.EventType.includes(this.events.Christmas) || return this.databaseServer.getTables().globals.config.EventType.includes(SeasonalEventType.CHRISTMAS) ||
this.databaseServer.getTables().globals.config.EventType.includes(this.events.Halloween); 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 * @returns true if active
*/ */
public christmasEventEnabled(): boolean 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 * @returns true if active
*/ */
public halloweenEventEnabled(): boolean 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 * @param eventName Name of event to get gear changes for
* @returns bots with equipment changes * @returns bots with equipment changes
*/ */
protected getEventBotGear(eventName: string): Record<string, Record<string, Record<string, number>>> protected getEventBotGear(eventType: SeasonalEventType): Record<string, Record<string, Record<string, number>>>
{ {
return this.seasonalEventConfig.eventGear[eventName.toLowerCase()]; return this.seasonalEventConfig.eventGear[eventType.toLowerCase()];
} }
/** /**
@ -194,6 +189,23 @@ export class SeasonalEventService
return this.seasonalEventConfig.events; 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 * 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 if (currentDate >= eventStartDate
&& currentDate <= eventEndDate) && currentDate <= eventEndDate)
{ {
this.updateGlobalEvents(globalConfig, event.name); this.updateGlobalEvents(globalConfig, event.type);
} }
} }
} }
@ -258,29 +270,29 @@ export class SeasonalEventService
* @param globalConfig globals.json * @param globalConfig globals.json
* @param eventName Name of the event to enable. e.g. Christmas * @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 = globalConfig.EventType.filter(x => x !== "None");
globalConfig.EventType.push("Halloween"); globalConfig.EventType.push("Halloween");
globalConfig.EventType.push("HalloweenIllumination"); globalConfig.EventType.push("HalloweenIllumination");
globalConfig.Health.ProfileHealthSettings.DefaultStimulatorBuff = "Buffs_Halloween"; globalConfig.Health.ProfileHealthSettings.DefaultStimulatorBuff = "Buffs_Halloween";
this.addEventGearToBots("halloween"); this.addEventGearToBots(eventType);
this.addPumpkinsToScavBackpacks(); this.addPumpkinsToScavBackpacks();
break; break;
case "christmas": case SeasonalEventType.CHRISTMAS.toLowerCase():
globalConfig.EventType = globalConfig.EventType.filter(x => x !== "None"); globalConfig.EventType = globalConfig.EventType.filter(x => x !== "None");
globalConfig.EventType.push("Christmas"); globalConfig.EventType.push("Christmas");
this.addEventGearToBots("christmas"); this.addEventGearToBots(eventType);
this.addGifterBotToMaps(); this.addGifterBotToMaps();
this.addLootItemsToGifterDropItemsList(); this.addLootItemsToGifterDropItemsList();
this.enableDancingTree(); this.enableDancingTree();
break; break;
default: default:
// Likely a mod event // Likely a mod event
this.addEventGearToBots(eventName.toLowerCase()); this.addEventGearToBots(eventType);
break; break;
} }
} }
@ -301,12 +313,12 @@ export class SeasonalEventService
* Read in data from seasonalEvents.json and add found equipment items to bots * 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 * @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) 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; return;
} }