From 2abf216a0786e53cb02f10559f7705fa121e2e7b Mon Sep 17 00:00:00 2001 From: Chomp Date: Mon, 9 Dec 2024 23:23:31 +0000 Subject: [PATCH] Using live data, improved emulation accuracy of repeatable quest system --- .../database/templates/repeatableQuests.json | 77 ++++++++++++++++--- project/src/controllers/QuestController.ts | 42 +--------- .../controllers/RepeatableQuestController.ts | 42 ++++++---- .../generators/RepeatableQuestGenerator.ts | 57 ++++++++++---- .../RepeatableQuestRewardGenerator.ts | 54 ++++++++++--- .../src/models/eft/common/tables/IQuest.ts | 2 + .../eft/common/tables/IRepeatableQuests.ts | 15 ++++ 7 files changed, 199 insertions(+), 90 deletions(-) diff --git a/project/assets/database/templates/repeatableQuests.json b/project/assets/database/templates/repeatableQuests.json index f832b13b..e96422bb 100644 --- a/project/assets/database/templates/repeatableQuests.json +++ b/project/assets/database/templates/repeatableQuests.json @@ -28,7 +28,10 @@ "value": 1, "type": "Elimination", "oneSessionOnly": false, + "completeInSeconds": 0, "doNotResetIfCounterCompleted": false, + "isResetOnConditionFailed": false, + "isNecessary": false, "counter": { "id": "618c1de4d4cd91439f3de4ac", "conditions": [{ @@ -59,7 +62,7 @@ } ] }, - "conditionType": "CounterCreator" + "conditionType": "CounterCreator" }], "Fail": [] }, @@ -74,13 +77,26 @@ "acceptPlayerMessage": "{templateId} acceptPlayerMessage {traderId}", "declinePlayerMessage": "{templateId} declinePlayerMessage {traderId}", "completePlayerMessage": "{templateId} completePlayerMessage {traderId}", - "templateId": "{templateId}", + "status": 0, + "acceptanceAndFinishingSource": "eft", + "progressSource": "eft", + "rankingModes": [], + "gameModes": [], + "arenaLocations": [], "changeCost": [{ "templateId": "5449016a4bdc2d6f028b456f", "count": 5000 } ], - "changeStandingCost": 0 + "changeStandingCost": 0, + "questStatus": { + "id": "mongoId", + "uid": "playerId", + "qid": "questId", + "startTime": 0, + "status": 1, + "statusTimers": {} + } }, "Completion": { "_id": "61943a75eb60e11b7965cdbf4", @@ -114,13 +130,26 @@ "acceptPlayerMessage": "{templateId} acceptPlayerMessage {traderId}", "declinePlayerMessage": "{templateId} declinePlayerMessage {traderId}", "completePlayerMessage": "{templateId} completePlayerMessage {traderId}", - "templateId": "{templateId}", + "status": 0, + "acceptanceAndFinishingSource": "eft", + "progressSource": "eft", + "rankingModes": [], + "gameModes": [], + "arenaLocations": [], "changeCost": [{ "templateId": "5449016a4bdc2d6f028b456f", "count": 5000 } ], - "changeStandingCost": 0 + "changeStandingCost": 0, + "questStatus": { + "id": "mongoId", + "uid": "playerId", + "qid": "questId", + "startTime": 0, + "status": 1, + "statusTimers": {} + } }, "Exploration": { "_id": "65947c6afb90e7fcb40f8d684", @@ -185,13 +214,26 @@ "acceptPlayerMessage": "{templateId} acceptPlayerMessage {traderId}", "declinePlayerMessage": "{templateId} declinePlayerMessage {traderId}", "completePlayerMessage": "{templateId} completePlayerMessage {traderId}", - "templateId": "{templateId}", + "status": 0, + "acceptanceAndFinishingSource": "eft", + "progressSource": "eft", + "rankingModes": [], + "gameModes": [], + "arenaLocations": [], "changeCost": [{ "templateId": "5449016a4bdc2d6f028b456f", "count": 5000 } ], - "changeStandingCost": 0 + "changeStandingCost": 0, + "questStatus": { + "id": "mongoId", + "uid": "playerId", + "qid": "questId", + "startTime": 0, + "status": 1, + "statusTimers": {} + } }, "Pickup": { "_id": "64cfb3818db9f48b3f0b0a759", @@ -217,7 +259,7 @@ "dynamicLocale": false, "index": 0, "visibilityConditions": [], - "globalQuestCounterId": null, + "globalQuestCounterId": "", "target": ["5b47574386f77428ca22b336"], "value": 7, "minDurability": 0, @@ -233,7 +275,7 @@ "dynamicLocale": true, "index": 0, "visibilityConditions": [], - "globalQuestCounterId": null, + "globalQuestCounterId": "", "value": 1, "type": "PickUp", "completeInSeconds": 0, @@ -276,13 +318,26 @@ "acceptPlayerMessage": "{templateId} acceptPlayerMessage {traderId}", "declinePlayerMessage": "{templateId} declinePlayerMessage {traderId}", "completePlayerMessage": "{templateId} completePlayerMessage {traderId}", - "templateId": "{templateId}", + "status": 0, + "acceptanceAndFinishingSource": "eft", + "progressSource": "eft", + "rankingModes": [], + "gameModes": [], + "arenaLocations": [], "changeCost": [{ "templateId": "5449016a4bdc2d6f028b456f", "count": 12000 } ], - "changeStandingCost": 0 + "changeStandingCost": 0, + "questStatus": { + "id": "mongoId", + "uid": "playerId", + "qid": "questId", + "startTime": 0, + "status": 1, + "statusTimers": {} + } } }, "rewards": { diff --git a/project/src/controllers/QuestController.ts b/project/src/controllers/QuestController.ts index a7a94eff..a85c48a5 100644 --- a/project/src/controllers/QuestController.ts +++ b/project/src/controllers/QuestController.ts @@ -174,6 +174,7 @@ export class QuestController { } /** + * 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 @@ -221,50 +222,11 @@ export class QuestController { fullProfile.characters.scav.Quests.push(newRepeatableQuest); } - const response = this.createAcceptedQuestClientResponse(sessionID, pmcData, repeatableQuestProfile); + const response = this.eventOutputHolder.getOutput(sessionID); return response; } - protected createAcceptedQuestClientResponse( - sessionID: string, - pmcData: IPmcData, - repeatableQuestProfile: IRepeatableQuest, - ): IItemEventRouterResponse { - const repeatableSettings = pmcData.RepeatableQuests.find( - (quest) => quest.name === repeatableQuestProfile.sptRepatableGroupName, - ); - - const change = {}; - change[repeatableQuestProfile._id] = repeatableSettings.changeRequirement[repeatableQuestProfile._id]; - - const repeatableData: IPmcDataRepeatableQuest = { - id: - repeatableSettings.id ?? - this.questConfig.repeatableQuests.find( - (repeatableQuest) => repeatableQuest.name === repeatableQuestProfile.sptRepatableGroupName, - ).id, - name: repeatableSettings.name, - endTime: repeatableSettings.endTime, - changeRequirement: change, - activeQuests: [repeatableQuestProfile], - inactiveQuests: [], - freeChanges: repeatableSettings.freeChanges, - freeChangesAvailable: repeatableSettings.freeChangesAvailable, - }; - - // Nullguard - const acceptQuestResponse = this.eventOutputHolder.getOutput(sessionID); - if (!acceptQuestResponse.profileChanges[sessionID].repeatableQuests) { - acceptQuestResponse.profileChanges[sessionID].repeatableQuests = []; - } - - // Add constructed objet into response - acceptQuestResponse.profileChanges[sessionID].repeatableQuests.push(repeatableData); - - return acceptQuestResponse; - } - /** * Look for an accepted quest inside player profile, return matching * @param pmcData Profile to search through diff --git a/project/src/controllers/RepeatableQuestController.ts b/project/src/controllers/RepeatableQuestController.ts index aa70b6bc..208b40e1 100644 --- a/project/src/controllers/RepeatableQuestController.ts +++ b/project/src/controllers/RepeatableQuestController.ts @@ -129,6 +129,7 @@ export class RepeatableQuestController { let lifeline = 0; while (!quest && questTypePool.types.length > 0) { quest = this.repeatableQuestGenerator.generateRepeatableQuest( + sessionID, pmcData.Info.Level, pmcData.TradersInfo, questTypePool, @@ -487,11 +488,11 @@ export class RepeatableQuestController { const fullProfile = this.profileHelper.getFullProfile(sessionID); // Check for existing quest in (daily/weekly/scav arrays) - const { quest: questToReplace, repeatableType: repeatablesInProfile } = this.getRepeatableById( + const { quest: questToReplace, repeatableType: repeatablesOfTypeInProfile } = this.getRepeatableById( changeRequest.qid, pmcData, ); - if (!repeatablesInProfile || !questToReplace) { + if (!repeatablesOfTypeInProfile || !questToReplace) { // Unable to find quest being replaced const message = this.localisationService.getText("quest-unable_to_find_repeatable_to_replace"); this.logger.error(message); @@ -500,25 +501,27 @@ export class RepeatableQuestController { } // Subtype name of quest - daily/weekly/scav - const repeatableTypeLower = repeatablesInProfile.name.toLowerCase(); + const repeatableTypeLower = repeatablesOfTypeInProfile.name.toLowerCase(); // Save for later standing loss calculation const replacedQuestTraderId = questToReplace.traderId; // Update active quests to exclude the quest we're replacing - repeatablesInProfile.activeQuests = repeatablesInProfile.activeQuests.filter( + repeatablesOfTypeInProfile.activeQuests = repeatablesOfTypeInProfile.activeQuests.filter( (quest) => quest._id !== changeRequest.qid, ); - // Save for later cost calculation - const previousChangeRequirement = this.cloner.clone(repeatablesInProfile.changeRequirement[changeRequest.qid]); + // Save for later cost calculations + const previousChangeRequirement = this.cloner.clone( + repeatablesOfTypeInProfile.changeRequirement[changeRequest.qid], + ); - // Delete the replaced quest change requrement as we're going to replace it - delete repeatablesInProfile.changeRequirement[changeRequest.qid]; + // Delete the replaced quest change requirement data as we're going to add new data below + delete repeatablesOfTypeInProfile.changeRequirement[changeRequest.qid]; // Get config for this repeatable sub-type (daily/weekly/scav) const repeatableConfig = this.questConfig.repeatableQuests.find( - (config) => config.name === repeatablesInProfile.name, + (config) => config.name === repeatablesOfTypeInProfile.name, ); // If the configuration dictates to replace with the same quest type, adjust the available quest types @@ -535,7 +538,12 @@ export class RepeatableQuestController { // Generate meta-data for what type/levelrange of quests can be generated for player const allowedQuestTypes = this.generateQuestPool(repeatableConfig, pmcData.Info.Level); - const newRepeatableQuest = this.attemptToGenerateRepeatableQuest(pmcData, allowedQuestTypes, repeatableConfig); + const newRepeatableQuest = this.attemptToGenerateRepeatableQuest( + sessionID, + pmcData, + allowedQuestTypes, + repeatableConfig, + ); if (!newRepeatableQuest) { // Unable to find quest being replaced const message = `Unable to generate repeatable quest of type: ${repeatableTypeLower} to replace trader: ${replacedQuestTraderId} quest ${changeRequest.qid}`; @@ -546,7 +554,7 @@ export class RepeatableQuestController { // Add newly generated quest to daily/weekly/scav type array newRepeatableQuest.side = repeatableConfig.side; - repeatablesInProfile.activeQuests.push(newRepeatableQuest); + repeatablesOfTypeInProfile.activeQuests.push(newRepeatableQuest); this.logger.debug( `Removing: ${repeatableConfig.name} quest: ${questToReplace._id} from trader: ${questToReplace.traderId} as its been replaced`, @@ -562,13 +570,17 @@ export class RepeatableQuestController { ); // Add new quests replacement cost to profile - repeatablesInProfile.changeRequirement[newRepeatableQuest._id] = { + repeatablesOfTypeInProfile.changeRequirement[newRepeatableQuest._id] = { changeCost: newRepeatableQuest.changeCost, changeStandingCost: this.randomUtil.getArrayValue([0, 0.01]), }; // Check if we should charge player for replacing quest - const isFreeToReplace = this.useFreeRefreshIfAvailable(fullProfile, repeatablesInProfile, repeatableTypeLower); + const isFreeToReplace = this.useFreeRefreshIfAvailable( + fullProfile, + repeatablesOfTypeInProfile, + repeatableTypeLower, + ); if (!isFreeToReplace) { // Reduce standing with trader for not doing their quest const traderOfReplacedQuest = pmcData.TradersInfo[replacedQuestTraderId]; @@ -586,7 +598,7 @@ export class RepeatableQuestController { } // Clone data before we send it to client - const repeatableToChangeClone = this.cloner.clone(repeatablesInProfile); + const repeatableToChangeClone = this.cloner.clone(repeatablesOfTypeInProfile); // Purge inactive repeatables repeatableToChangeClone.inactiveQuests = []; @@ -622,6 +634,7 @@ export class RepeatableQuestController { } protected attemptToGenerateRepeatableQuest( + sessionId: string, pmcData: IPmcData, questTypePool: IQuestTypePool, repeatableConfig: IRepeatableQuestConfig, @@ -631,6 +644,7 @@ export class RepeatableQuestController { let attempts = 0; while (attempts < maxAttempts && questTypePool.types.length > 0) { newRepeatableQuest = this.repeatableQuestGenerator.generateRepeatableQuest( + sessionId, pmcData.Info.Level, pmcData.TradersInfo, questTypePool, diff --git a/project/src/generators/RepeatableQuestGenerator.ts b/project/src/generators/RepeatableQuestGenerator.ts index 3e783013..d8f1e666 100644 --- a/project/src/generators/RepeatableQuestGenerator.ts +++ b/project/src/generators/RepeatableQuestGenerator.ts @@ -50,6 +50,7 @@ export 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 @@ -57,6 +58,7 @@ export class RepeatableQuestGenerator { * @returns IRepeatableQuest */ public generateRepeatableQuest( + sessionId: string, pmcLevel: number, pmcTraderInfo: Record, questTypePool: IQuestTypePool, @@ -64,7 +66,7 @@ export class RepeatableQuestGenerator { ): IRepeatableQuest { const questType = this.randomUtil.drawRandomFromList(questTypePool.types)[0]; - // get traders from whitelist and filter by quest type availability + // Get traders from whitelist and filter by quest type availability let traders = repeatableConfig.traderWhitelist .filter((x) => x.questTypes.includes(questType)) .map((x) => x.traderId); @@ -74,13 +76,13 @@ export class RepeatableQuestGenerator { switch (questType) { case "Elimination": - return this.generateEliminationQuest(pmcLevel, traderId, questTypePool, repeatableConfig); + return this.generateEliminationQuest(sessionId, pmcLevel, traderId, questTypePool, repeatableConfig); case "Completion": - return this.generateCompletionQuest(pmcLevel, traderId, repeatableConfig); + return this.generateCompletionQuest(sessionId, pmcLevel, traderId, repeatableConfig); case "Exploration": - return this.generateExplorationQuest(pmcLevel, traderId, questTypePool, repeatableConfig); + return this.generateExplorationQuest(sessionId, pmcLevel, traderId, questTypePool, repeatableConfig); case "Pickup": - return this.generatePickupQuest(pmcLevel, traderId, questTypePool, repeatableConfig); + return this.generatePickupQuest(sessionId, pmcLevel, traderId, questTypePool, repeatableConfig); default: throw new Error(`Unknown mission type ${questType}. Should never be here!`); } @@ -95,6 +97,7 @@ export class RepeatableQuestGenerator { * @returns Object of quest type format for "Elimination" (see assets/database/templates/repeatableQuests.json) */ protected generateEliminationQuest( + sessionid: string, pmcLevel: number, traderId: string, questTypePool: IQuestTypePool, @@ -297,7 +300,7 @@ export class RepeatableQuestGenerator { // crazy maximum difficulty will lead to a higher difficulty reward gain factor than 1 const difficulty = this.mathUtil.mapToRange(curDifficulty, minDifficulty, maxDifficulty, 0.5, 2); - const quest = this.generateRepeatableTemplate("Elimination", traderId, repeatableConfig.side); + const quest = this.generateRepeatableTemplate("Elimination", traderId, repeatableConfig.side, sessionid); // ASSUMPTION: All fence quests are for scavs if (traderId === Traders.FENCE) { @@ -396,10 +399,13 @@ export class RepeatableQuestGenerator { allowedWeaponCategory: string, ): IQuestConditionCounterCondition { const killConditionProps: IQuestConditionCounterCondition = { - target: target, - value: 1, id: this.objectId.generate(), dynamicLocale: true, + target: target, // e,g, "AnyPmc" + value: 1, + resetOnSessionEnd: false, + enemyHealthEffects: [], + daytime: { from: 0, to: 0 }, conditionType: "Kills", }; @@ -441,6 +447,7 @@ export class RepeatableQuestGenerator { * @returns {object} object of quest type format for "Completion" (see assets/database/templates/repeatableQuests.json) */ protected generateCompletionQuest( + sessionId: string, pmcLevel: number, traderId: string, repeatableConfig: IRepeatableQuestConfig, @@ -449,7 +456,7 @@ export class RepeatableQuestGenerator { const levelsConfig = repeatableConfig.rewardScaling.levels; const roublesConfig = repeatableConfig.rewardScaling.roubles; - const quest = this.generateRepeatableTemplate("Completion", traderId, repeatableConfig.side); + const quest = this.generateRepeatableTemplate("Completion", traderId, repeatableConfig.side, sessionId); // Filter the items.json items to items the player must retrieve to complete quest: shouldn't be a quest item or "non-existant" const possibleItemsToRetrievePool = this.repeatableQuestRewardGenerator.getRewardableItems( @@ -626,16 +633,18 @@ export class RepeatableQuestGenerator { return { id: this.objectId.generate(), + index: 0, parentId: "", dynamicLocale: true, - index: 0, visibilityConditions: [], + globalQuestCounterId: "", target: [itemTpl], value: value, minDurability: minDurability, maxDurability: 100, dogtagLevel: 0, onlyFoundInRaid: onlyFoundInRaid, + isEncoded: false, conditionType: "HandoverItem", }; } @@ -650,6 +659,7 @@ export class RepeatableQuestGenerator { * @returns {object} object of quest type format for "Exploration" (see assets/database/templates/repeatableQuests.json) */ protected generateExplorationQuest( + sessionId: string, pmcLevel: number, traderId: string, questTypePool: IQuestTypePool, @@ -679,19 +689,19 @@ export class RepeatableQuestGenerator { : explorationConfig.maxExtracts + 1; const numExtracts = this.randomUtil.randInt(1, exitTimesMax); - const quest = this.generateRepeatableTemplate("Exploration", traderId, repeatableConfig.side); + const quest = this.generateRepeatableTemplate("Exploration", traderId, repeatableConfig.side, sessionId); const exitStatusCondition: IQuestConditionCounterCondition = { - conditionType: "ExitStatus", id: this.objectId.generate(), dynamicLocale: true, status: ["Survived"], + conditionType: "ExitStatus", }; const locationCondition: IQuestConditionCounterCondition = { - conditionType: "Location", id: this.objectId.generate(), dynamicLocale: true, target: locationTarget, + conditionType: "Location", }; quest.conditions.AvailableForFinish[0].counter.id = this.objectId.generate(); @@ -757,6 +767,7 @@ export class RepeatableQuestGenerator { } protected generatePickupQuest( + sessionId: string, pmcLevel: number, traderId: string, questTypePool: IQuestTypePool, @@ -764,7 +775,7 @@ export class RepeatableQuestGenerator { ): IRepeatableQuest { const pickupConfig = repeatableConfig.questConfig.Pickup; - const quest = this.generateRepeatableTemplate("Pickup", traderId, repeatableConfig.side); + const quest = this.generateRepeatableTemplate("Pickup", traderId, repeatableConfig.side, sessionId); const itemTypeToFetchWithCount = this.randomUtil.getArrayValue(pickupConfig.ItemTypeToFetchWithMaxCount); const itemCountToFetch = this.randomUtil.randInt( @@ -819,7 +830,12 @@ export class RepeatableQuestGenerator { * @returns {object} Exit condition */ protected generateExplorationExitCondition(exit: IExit): IQuestConditionCounterCondition { - return { conditionType: "ExitName", exitName: exit.Name, id: this.objectId.generate(), dynamicLocale: true }; + return { + id: this.objectId.generate(), + dynamicLocale: true, + exitName: exit.Name, + conditionType: "ExitName", + }; } /** @@ -833,7 +849,12 @@ export class RepeatableQuestGenerator { * (needs to be filled with reward and conditions by called to make a valid quest) */ // @Incomplete: define Type for "type". - protected generateRepeatableTemplate(type: string, traderId: string, side: string): IRepeatableQuest { + protected generateRepeatableTemplate( + type: string, + traderId: string, + side: string, + sessionId: string, + ): IRepeatableQuest { const questClone = this.cloner.clone( this.databaseService.getTemplates().repeatableQuests.templates[type], ); @@ -882,6 +903,10 @@ export class RepeatableQuestGenerator { .replace("{traderId}", desiredTraderId) .replace("{templateId}", questClone.templateId); + questClone.questStatus.id = this.objectId.generate(); + questClone.questStatus.uid = sessionId; // Needs to match user id + questClone.questStatus.qid = questClone._id; // Needs to match quest id + return questClone; } } diff --git a/project/src/generators/RepeatableQuestRewardGenerator.ts b/project/src/generators/RepeatableQuestRewardGenerator.ts index 1e2a2547..e4d15cc9 100644 --- a/project/src/generators/RepeatableQuestRewardGenerator.ts +++ b/project/src/generators/RepeatableQuestRewardGenerator.ts @@ -23,6 +23,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"; @@ -36,6 +37,7 @@ export class RepeatableQuestRewardGenerator { constructor( @inject("PrimaryLogger") protected logger: ILogger, @inject("RandomUtil") protected randomUtil: RandomUtil, + @inject("HashUtil") protected hashUtil: HashUtil, @inject("MathUtil") protected mathUtil: MathUtil, @inject("DatabaseService") protected databaseService: DatabaseService, @inject("ItemHelper") protected itemHelper: ItemHelper, @@ -93,14 +95,18 @@ export class RepeatableQuestRewardGenerator { const rewards: IQuestRewards = { Started: [], Success: [], Fail: [] }; // Start reward index to keep track - let rewardIndex = 0; + let rewardIndex = -1; // Add xp reward if (rewardParams.rewardXP > 0) { rewards.Success.push({ + id: this.hashUtil.generate(), + unknown: false, + gameMode: [], + availableInGameEditions: [], + index: rewardIndex, value: rewardParams.rewardXP, type: QuestRewardType.EXPERIENCE, - index: rewardIndex, }); rewardIndex++; } @@ -163,6 +169,10 @@ export class RepeatableQuestRewardGenerator { // Add rep reward to rewards array if (rewardParams.rewardReputation > 0) { const reward: IQuestReward = { + id: this.hashUtil.generate(), + unknown: false, + gameMode: [], + availableInGameEditions: [], target: traderId, value: rewardParams.rewardReputation, type: QuestRewardType.TRADER_STANDING, @@ -171,13 +181,17 @@ export class RepeatableQuestRewardGenerator { rewards.Success.push(reward); rewardIndex++; - this.logger.debug(` Adding ${rewardParams.rewardReputation} trader reputation reward`); + this.logger.debug(`Adding: ${rewardParams.rewardReputation} ${traderId} trader reputation reward`); } // Chance of adding skill reward if (this.randomUtil.getChance100(rewardParams.skillRewardChance * 100)) { const targetSkill = this.randomUtil.getArrayValue(questConfig.possibleSkillRewards); const reward: IQuestReward = { + id: this.hashUtil.generate(), + unknown: false, + gameMode: [], + availableInGameEditions: [], target: targetSkill, value: rewardParams.skillPointReward, type: QuestRewardType.SKILL, @@ -503,17 +517,23 @@ export 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 = true): IQuestReward { const id = this.objectId.generate(); const questRewardItem: IQuestReward = { + id: this.hashUtil.generate(), + unknown: false, + gameMode: [], + availableInGameEditions: [], + index: index, target: id, value: count, + isEncoded: false, + findInRaid: foundInRaid, type: QuestRewardType.ITEM, - index: index, items: [], }; - const rootItem = { _id: id, _tpl: tpl, upd: { StackObjectsCount: count, SpawnedInSession: true } }; + const rootItem = { _id: id, _tpl: tpl, upd: { StackObjectsCount: count, SpawnedInSession: foundInRaid } }; questRewardItem.items = [rootItem]; return questRewardItem; @@ -528,13 +548,25 @@ export 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 = true, + ): IQuestReward { const id = this.objectId.generate(); const questRewardItem: IQuestReward = { + id: this.hashUtil.generate(), + unknown: false, + gameMode: [], + availableInGameEditions: [], + index: index, target: id, value: count, + isEncoded: false, + findInRaid: foundInRaid, type: QuestRewardType.ITEM, - index: index, items: [], }; @@ -544,6 +576,10 @@ export class RepeatableQuestRewardGenerator { this.logger.warning(`Root item of preset: ${tpl} not found`); } + if (rootItem.upd) { + rootItem.upd.SpawnedInSession = foundInRaid; + } + questRewardItem.items = this.itemHelper.reparentItemAndChildren(rootItem, preset); questRewardItem.target = rootItem._id; // Target property and root items id must match @@ -641,6 +677,6 @@ export class RepeatableQuestRewardGenerator { currency === Money.EUROS ? this.handbookHelper.fromRUB(rewardRoubles, Money.EUROS) : rewardRoubles; // Get chosen currency + amount and return - return this.generateItemReward(currency, rewardAmountToGivePlayer, rewardIndex); + return this.generateItemReward(currency, rewardAmountToGivePlayer, rewardIndex, false); } } diff --git a/project/src/models/eft/common/tables/IQuest.ts b/project/src/models/eft/common/tables/IQuest.ts index 440c2a04..9201feec 100644 --- a/project/src/models/eft/common/tables/IQuest.ts +++ b/project/src/models/eft/common/tables/IQuest.ts @@ -160,8 +160,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 */ diff --git a/project/src/models/eft/common/tables/IRepeatableQuests.ts b/project/src/models/eft/common/tables/IRepeatableQuests.ts index e3eeb458..db0d7253 100644 --- a/project/src/models/eft/common/tables/IRepeatableQuests.ts +++ b/project/src/models/eft/common/tables/IRepeatableQuests.ts @@ -4,6 +4,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 { @@ -13,6 +19,15 @@ export interface IRepeatableQuestDatabase { samples: ISampleQuests[]; } +export interface IRepeatableQuestStatus { + id: string; + uid: string; + qid: string; + startTime: number; + status: number; + statusTimers: any; +} + export interface IRepeatableTemplates { Elimination: IQuest; Completion: IQuest;