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"
}
},
"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",

View File

@ -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"
}

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

View File

@ -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)

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 { 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<string, IEventQuestData>
repeatableQuests: IRepeatableQuestConfig[]
locationIdMap: Record<string, string>
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
{

View File

@ -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

View File

@ -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<string, string>
{
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<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;
}
/**
* 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;
}