diff --git a/project/assets/database/templates/repeatableQuests.json b/project/assets/database/templates/repeatableQuests.json index cfeac55b..37a8aa9d 100644 --- a/project/assets/database/templates/repeatableQuests.json +++ b/project/assets/database/templates/repeatableQuests.json @@ -19,39 +19,51 @@ "conditions": { "AvailableForStart": [], "AvailableForFinish": [{ - "_props": { - "id": "618c1de4d4cd91439f3de4ae", - "parentId": "", - "dynamicLocale": true, - "index": 0, - "visibilityConditions": [], - "value": 1, - "type": "Elimination", - "oneSessionOnly": false, - "doNotResetIfCounterCompleted": false, - "counter": { - "id": "618c1de4d4cd91439f3de4ac", - "conditions": [{ - "_props": { - "target": "Savage", - "value": 1, - "savageRole": [ - "bossBully" - ], - "id": "618c1de4d4cd91439f3de4ad", - "dynamicLocale": true - }, - "_parent": "Kills" - } - ] - } - }, - "_parent": "CounterCreator", - "dynamicLocale": true - } - ], + "id": "618c1de4d4cd91439f3de4ae", + "index": 0, + "dynamicLocale": true, + "visibilityConditions": [], + "globalQuestCounterId": "", + "parentId": "", + "value": 1, + "type": "Elimination", + "oneSessionOnly": false, + "doNotResetIfCounterCompleted": false, + "counter": { + "id": "618c1de4d4cd91439f3de4ac", + "conditions": [{ + "id": "618c1de4d4cd91439f3de4ad", + "dynamicLocale": true, + "target": "Savage", + "compareMethod": ">=", + "value": 1, + "weapon": [], + "distance": { + "value": 0, + "compareMethod": ">=" + }, + "weaponModsInclusive": [], + "weaponModsExclusive": [], + "enemyEquipmentInclusive": [], + "enemyEquipmentExclusive": [], + "weaponCaliber": [], + "savageRole": [], + "bodyPart": [], + "daytime": { + "from": 0, + "to": 0 + }, + "enemyHealthEffects": [], + "resetOnSessionEnd": false, + "conditionType": "Kills" + } + ] + }, + "conditionType": "CounterCreator" + }], "Fail": [] }, + "side": "Pmc", "name": "{templateId} name {traderId}", "note": "{templateId} note {traderId}", "description": "{templateId} description {traderId} 0", @@ -91,6 +103,7 @@ "AvailableForFinish": [], "Fail": [] }, + "side": "Pmc", "name": "{templateId} name {traderId}", "note": "{templateId} note {traderId}", "description": "{templateId} description {traderId} 0", @@ -110,7 +123,7 @@ "changeStandingCost": 0 }, "Exploration": { - "_id": null, + "_id": "65947c6afb90e7fcb40f8d684", "traderId": "54cb50c76803fa8b248b4571", "location": null, "image": "/files/quest/icon/616d993bc8c5ad2ab30ff6ba.jpg", @@ -128,44 +141,40 @@ "conditions": { "AvailableForStart": [], "AvailableForFinish": [{ - "_props": { - "id": "618c1de4d4cd91439f3de4a5", - "parentId": "", - "dynamicLocale": true, - "index": 0, - "visibilityConditions": [], - "value": 1, - "type": "Completion", - "oneSessionOnly": false, - "doNotResetIfCounterCompleted": false, - "counter": { - "id": "618c1de4d4cd91439f3de4a4", - "conditions": [{ - "_props": { - "status": [ - "Survived" - ], - "id": "618c1de4d4cd91439f3de4a3", - "dynamicLocale": true - }, - "_parent": "ExitStatus" - }, { - "_props": { - "target": [], - "id": "618c1de4d4cd91439f3de4a2", - "dynamicLocale": true - }, - "_parent": "Location" - } - ] - } - }, - "_parent": "CounterCreator", - "dynamicLocale": true + "id": "618c1de4d4cd91439f3de4a5", + "index": 0, + "dynamicLocale": true, + "visibilityConditions": [], + "globalQuestCounterId": "", + "parentId": "", + "value": 1, + "type": "Completion", + "oneSessionOnly": false, + "completeInSeconds": 0, + "doNotResetIfCounterCompleted": false, + "counter": { + "id": "618c1de4d4cd91439f3de4a4", + "conditions": [{ + "status": [ + "Survived" + ], + "id": "618c1de4d4cd91439f3de4a3", + "dynamicLocale": true, + "conditionType": "ExitStatus" + }, { + "target": [], + "id": "618c1de4d4cd91439f3de4a2", + "dynamicLocale": true, + "conditionType": "Location" + } + ] + }, + "conditionType": "CounterCreator" } ], "Fail": [] }, + "side": "Pmc", "name": "{templateId} name {traderId}", "note": "{templateId} note {traderId}", "description": "{templateId} description {traderId} 0", @@ -203,73 +212,60 @@ "conditions": { "AvailableForStart": [], "AvailableForFinish": [{ - "_props": { - "id": "64cfb3818db9f48b3f0b0a6f", - "parentId": "", - "dynamicLocale": false, - "index": 0, - "visibilityConditions": [], - "globalQuestCounterId": null, - "target": ["5b47574386f77428ca22b336"], - "value": 7, - "minDurability": 0, - "maxDurability": 100, - "dogtagLevel": 0, - "onlyFoundInRaid": false, - "isEncoded": false, - "countInRaid": true - }, - "_parent": "FindItem", - "dynamicLocale": false + "id": "64cfb3818db9f48b3f0b0a6f", + "parentId": "", + "dynamicLocale": false, + "index": 0, + "visibilityConditions": [], + "globalQuestCounterId": null, + "target": ["5b47574386f77428ca22b336"], + "value": 7, + "minDurability": 0, + "maxDurability": 100, + "dogtagLevel": 0, + "onlyFoundInRaid": false, + "isEncoded": false, + "countInRaid": true, + "conditionType": "FindItem" }, { - "_props": { - "id": "64cfb3818db9f48b3f0b0a74", - "parentId": "", - "dynamicLocale": true, - "index": 0, - "visibilityConditions": [], - "globalQuestCounterId": null, - "value": 1, - "type": "PickUp", - "completeInSeconds": 0, - "oneSessionOnly": true, - "doNotResetIfCounterCompleted": false, - "counter": { - "id": "64cfb3818db9f48b3f0b0a73", - "conditions": [{ - "_props": { - "target": ["any"], - "id": "64cfb3818db9f48b3f0b0a70", - "dynamicLocale": true - }, - "_parent": "Location" - }, { - "_props": { - "status": ["Survived"], - "id": "64cfb3818db9f48b3f0b0a71", - "dynamicLocale": true - }, - "_parent": "ExitStatus" - }, { - "_props": { - "equipmentInclusive": [["5b47574386f77428ca22b336"]], - "IncludeNotEquippedItems": true, - "id": "64cfb3818db9f48b3f0b0a72", - "dynamicLocale": true - }, - "_parent": "Equipment" - } - ] - } - }, - "_parent": "CounterCreator", - "dynamicLocale": true + "id": "64cfb3818db9f48b3f0b0a74", + "parentId": "", + "dynamicLocale": true, + "index": 0, + "visibilityConditions": [], + "globalQuestCounterId": null, + "value": 1, + "type": "PickUp", + "completeInSeconds": 0, + "oneSessionOnly": true, + "doNotResetIfCounterCompleted": false, + "counter": { + "id": "64cfb3818db9f48b3f0b0a73", + "conditions": [{ + "target": ["any"], + "id": "64cfb3818db9f48b3f0b0a70", + "dynamicLocale": true, + "conditionType": "Location" + }, { + "status": ["Survived"], + "id": "64cfb3818db9f48b3f0b0a71", + "dynamicLocale": true, + "conditionType": "ExitStatus" + }, { + "equipmentInclusive": [["5b47574386f77428ca22b336"]], + "IncludeNotEquippedItems": true, + "id": "64cfb3818db9f48b3f0b0a72", + "dynamicLocale": true, + "conditionType": "Equipment" + } + ] + }, + "conditionType": "CounterCreator" } ], "Fail": [] }, "side": "Scav", - "questStatus": {}, "name": "{templateId} name {traderId}", "note": "{templateId} note {traderId}", "description": "{templateId} description {traderId} 0", diff --git a/project/src/controllers/QuestController.ts b/project/src/controllers/QuestController.ts index c339d415..7aab2c81 100644 --- a/project/src/controllers/QuestController.ts +++ b/project/src/controllers/QuestController.ts @@ -9,7 +9,7 @@ import { TraderHelper } from "@spt-aki/helpers/TraderHelper"; import { IPmcData } from "@spt-aki/models/eft/common/IPmcData"; import { IQuestStatus } from "@spt-aki/models/eft/common/tables/IBotBase"; import { Item } from "@spt-aki/models/eft/common/tables/IItem"; -import { AvailableForConditions, IQuest, Reward } from "@spt-aki/models/eft/common/tables/IQuest"; +import { IQuest, IQuestCondition } from "@spt-aki/models/eft/common/tables/IQuest"; import { IPmcDataRepeatableQuest, IRepeatableQuest } from "@spt-aki/models/eft/common/tables/IRepeatableQuests"; import { IItemEventRouterResponse } from "@spt-aki/models/eft/itemEvent/IItemEventRouterResponse"; import { IAcceptQuestRequestData } from "@spt-aki/models/eft/quests/IAcceptQuestRequestData"; @@ -135,7 +135,7 @@ export class QuestController for (const conditionToFulfil of questRequirements) { // If the previous quest isn't in the user profile, it hasn't been completed or started - const prerequisiteQuest = profile.Quests.find((pq) => pq.qid === conditionToFulfil.target); + const prerequisiteQuest = profile.Quests.find((profileQuest) => conditionToFulfil.target.includes(profileQuest.qid)); if (!prerequisiteQuest) { haveCompletedPreviousQuest = false; @@ -609,7 +609,7 @@ export class QuestController sessionID: string, pmcData: IPmcData, completedQuestId: string, - questRewards: Reward[], + questRewards: Item[], ): void { const quest = this.questHelper.getQuestFromDb(completedQuestId, pmcData); @@ -637,7 +637,7 @@ export class QuestController { // If quest has prereq of completed quest + availableAfter value > 0 (quest has wait time) const nextQuestWaitCondition = quest.conditions.AvailableForStart.find((x) => - x.target === completedQuestId && x.availableAfter > 0 + x.target.includes(completedQuestId) && x.availableAfter > 0 ); if (nextQuestWaitCondition) { @@ -687,7 +687,7 @@ export class QuestController return false; } - return x.conditions.Fail.some((y) => y.target === completedQuestId); + return x.conditions.Fail.some((y) => y.target.includes(completedQuestId)); }); } @@ -760,7 +760,7 @@ export class QuestController let handedInCount = 0; // Decrement number of items handed in - let handoverRequirements: AvailableForConditions; + let handoverRequirements: IQuestCondition; for (const condition of quest.conditions.AvailableForFinish) { if ( @@ -897,7 +897,7 @@ export class QuestController protected showQuestItemHandoverMatchError( handoverQuestRequest: IHandoverQuestRequestData, itemHandedOver: Item, - handoverRequirements: AvailableForConditions, + handoverRequirements: IQuestCondition, output: IItemEventRouterResponse, ): IItemEventRouterResponse { diff --git a/project/src/controllers/RepeatableQuestController.ts b/project/src/controllers/RepeatableQuestController.ts index 5db3cf36..e4623013 100644 --- a/project/src/controllers/RepeatableQuestController.ts +++ b/project/src/controllers/RepeatableQuestController.ts @@ -182,7 +182,7 @@ export class RepeatableQuestController } } - // create stupid redundant change requirements from quest data + // Create stupid redundant change requirements from quest data for (const quest of currentRepeatableQuestType.activeQuests) { currentRepeatableQuestType.changeRequirement[quest._id] = { diff --git a/project/src/generators/RepeatableQuestGenerator.ts b/project/src/generators/RepeatableQuestGenerator.ts index 0e618084..41b83496 100644 --- a/project/src/generators/RepeatableQuestGenerator.ts +++ b/project/src/generators/RepeatableQuestGenerator.ts @@ -9,24 +9,13 @@ import { RepeatableQuestHelper } from "@spt-aki/helpers/RepeatableQuestHelper"; import { Exit, ILocationBase } from "@spt-aki/models/eft/common/ILocationBase"; import { TraderInfo } from "@spt-aki/models/eft/common/tables/IBotBase"; import { Item } from "@spt-aki/models/eft/common/tables/IItem"; -import { - ICompletion, - ICompletionAvailableFor, - IElimination, - IEliminationCondition, - IEquipmentConditionProps, - IExploration, - IExplorationCondition, - IKillConditionProps, - IPickup, - IRepeatableQuest, - IReward, - IRewards, -} from "@spt-aki/models/eft/common/tables/IRepeatableQuests"; +import { IQuestCondition, IQuestConditionCounterCondition, IQuestReward, IQuestRewards } from "@spt-aki/models/eft/common/tables/IQuest"; +import { IRepeatableQuest } from "@spt-aki/models/eft/common/tables/IRepeatableQuests"; import { ITemplateItem } from "@spt-aki/models/eft/common/tables/ITemplateItem"; import { BaseClasses } from "@spt-aki/models/enums/BaseClasses"; import { ConfigTypes } from "@spt-aki/models/enums/ConfigTypes"; import { Money } from "@spt-aki/models/enums/Money"; +import { QuestRewardType } from "@spt-aki/models/enums/QuestRewardType"; import { Traders } from "@spt-aki/models/enums/Traders"; import { IBaseQuestConfig, @@ -136,7 +125,7 @@ export class RepeatableQuestGenerator traderId: string, questTypePool: IQuestTypePool, repeatableConfig: IRepeatableQuestConfig, - ): IElimination + ): IRepeatableQuest { const eliminationConfig = this.repeatableQuestHelper.getEliminationConfigByPmcLevel(pmcLevel, repeatableConfig); const locationsConfig = repeatableConfig.locations; @@ -337,7 +326,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) as IElimination; + const quest = this.generateRepeatableTemplate("Elimination", traderId, repeatableConfig.side); // ASSUMPTION: All fence quests are for scavs if (traderId === Traders.FENCE) @@ -346,17 +335,17 @@ export class RepeatableQuestGenerator } const availableForFinishCondition = quest.conditions.AvailableForFinish[0]; - availableForFinishCondition._props.counter.id = this.objectId.generate(); - availableForFinishCondition._props.counter.conditions = []; + availableForFinishCondition.counter.id = this.objectId.generate(); + availableForFinishCondition.counter.conditions = []; // Only add specific location condition if specific map selected if (locationKey !== "any") { - availableForFinishCondition._props.counter.conditions.push( + availableForFinishCondition.counter.conditions.push( this.generateEliminationLocation(locationsConfig[locationKey]), ); } - availableForFinishCondition._props.counter.conditions.push( + availableForFinishCondition.counter.conditions.push( this.generateEliminationCondition( targetKey, bodyPartsToClient, @@ -365,8 +354,8 @@ export class RepeatableQuestGenerator allowedWeaponsCategory, ), ); - availableForFinishCondition._props.value = desiredKillCount; - availableForFinishCondition._props.id = this.objectId.generate(); + availableForFinishCondition.value = desiredKillCount; + availableForFinishCondition.id = this.objectId.generate(); quest.location = this.getQuestLocationByMapId(locationKey); quest.rewards = this.generateReward( @@ -413,11 +402,13 @@ export class RepeatableQuestGenerator * @param {string} location the location on which to fulfill the elimination quest * @returns {IEliminationCondition} object of "Elimination"-location-subcondition */ - protected generateEliminationLocation(location: string[]): IEliminationCondition + protected generateEliminationLocation(location: string[]): IQuestConditionCounterCondition { - const propsObject: IEliminationCondition = { - _props: { target: location, id: this.objectId.generate(), dynamicLocale: true }, - _parent: "Location", + const propsObject: IQuestConditionCounterCondition = { + id: this.objectId.generate(), + dynamicLocale: true, + target: location, + conditionType: "Location" }; return propsObject; @@ -438,13 +429,14 @@ export class RepeatableQuestGenerator distance: number, allowedWeapon: string, allowedWeaponCategory: string, - ): IEliminationCondition + ): IQuestConditionCounterCondition { - const killConditionProps: IKillConditionProps = { + const killConditionProps: IQuestConditionCounterCondition = { target: target, value: 1, id: this.objectId.generate(), dynamicLocale: true, + conditionType: "Kills", }; if (target.startsWith("boss")) @@ -474,10 +466,11 @@ export class RepeatableQuestGenerator // Has specific weapon category requirement if (allowedWeaponCategory?.length > 0) { - killConditionProps.weaponCategories = [allowedWeaponCategory]; + // TODO - fix - does weaponCategories exist? + //killConditionProps.weaponCategories = [allowedWeaponCategory]; } - return { _props: killConditionProps, _parent: "Kills" }; + return killConditionProps; } /** @@ -492,7 +485,7 @@ export class RepeatableQuestGenerator pmcLevel: number, traderId: string, repeatableConfig: IRepeatableQuestConfig, - ): ICompletion + ): IRepeatableQuest { const completionConfig = repeatableConfig.questConfig.Completion; const levelsConfig = repeatableConfig.rewardScaling.levels; @@ -500,7 +493,7 @@ export class RepeatableQuestGenerator const distinctItemsToRetrieveCount = this.randomUtil.getInt(1, completionConfig.uniqueItemCount); - const quest = this.generateRepeatableTemplate("Completion", traderId, repeatableConfig.side) as ICompletion; + const quest = this.generateRepeatableTemplate("Completion", traderId, repeatableConfig.side); // 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.getRewardableItems(repeatableConfig, traderId); @@ -628,7 +621,7 @@ export class RepeatableQuestGenerator * @param {integer} value amount of items of this specific type to request * @returns {object} object of "Completion"-condition */ - protected generateCompletionAvailableForFinish(itemTpl: string, value: number): ICompletionAvailableFor + protected generateCompletionAvailableForFinish(itemTpl: string, value: number): IQuestCondition { let minDurability = 0; let onlyFoundInRaid = true; @@ -650,21 +643,18 @@ export class RepeatableQuestGenerator } return { - _props: { - id: this.objectId.generate(), - parentId: "", - dynamicLocale: true, - index: 0, - visibilityConditions: [], - target: [itemTpl], - value: value, - minDurability: minDurability, - maxDurability: 100, - dogtagLevel: 0, - onlyFoundInRaid: onlyFoundInRaid, - }, - _parent: "HandoverItem", + id: this.objectId.generate(), + parentId: "", dynamicLocale: true, + index: 0, + visibilityConditions: [], + target: [itemTpl], + value: value, + minDurability: minDurability, + maxDurability: 100, + dogtagLevel: 0, + onlyFoundInRaid: onlyFoundInRaid, + conditionType: "HandoverItem", }; } @@ -682,7 +672,7 @@ export class RepeatableQuestGenerator traderId: string, questTypePool: IQuestTypePool, repeatableConfig: IRepeatableQuestConfig, - ): IExploration + ): IRepeatableQuest { const explorationConfig = repeatableConfig.questConfig.Exploration; const requiresSpecificExtract = Math.random() < repeatableConfig.questConfig.Exploration.specificExits.probability; @@ -705,24 +695,27 @@ export class RepeatableQuestGenerator // Different max extract count when specific extract needed const numExtracts = this.randomUtil.randInt(1, requiresSpecificExtract ? explorationConfig.maxExtractsWithSpecificExit : explorationConfig.maxExtracts + 1); - const quest = this.generateRepeatableTemplate("Exploration", traderId, repeatableConfig.side) as IExploration; + const quest = this.generateRepeatableTemplate("Exploration", traderId, repeatableConfig.side); - const exitStatusCondition: IExplorationCondition = { - _parent: "ExitStatus", - _props: { id: this.objectId.generate(), dynamicLocale: true, status: ["Survived"] }, + const exitStatusCondition: IQuestConditionCounterCondition = { + conditionType: "ExitStatus", + id: this.objectId.generate(), + dynamicLocale: true, + status: ["Survived"], }; - const locationCondition: IExplorationCondition = { - _parent: "Location", - _props: { id: this.objectId.generate(), dynamicLocale: true, target: locationTarget }, + const locationCondition: IQuestConditionCounterCondition = { + conditionType: "Location", + id: this.objectId.generate(), + dynamicLocale: true, + target: locationTarget, }; - - quest.conditions.AvailableForFinish[0]._props.counter.id = this.objectId.generate(); - quest.conditions.AvailableForFinish[0]._props.counter.conditions = [exitStatusCondition, locationCondition]; - quest.conditions.AvailableForFinish[0]._props.value = numExtracts; - quest.conditions.AvailableForFinish[0]._props.id = this.objectId.generate(); + + quest.conditions.AvailableForFinish[0].counter.id = this.objectId.generate(); + quest.conditions.AvailableForFinish[0].counter.conditions = [exitStatusCondition, locationCondition]; + quest.conditions.AvailableForFinish[0].value = numExtracts; + quest.conditions.AvailableForFinish[0].id = this.objectId.generate(); quest.location = this.getQuestLocationByMapId(locationKey); - if (requiresSpecificExtract) { // Filter by whitelist, it's also possible that the field "PassageRequirement" does not exist (e.g. Shoreline) @@ -737,7 +730,7 @@ export class RepeatableQuestGenerator ); const exit = this.randomUtil.drawRandomFromList(possibleExists, 1)[0]; const exitCondition = this.generateExplorationExitCondition(exit); - quest.conditions.AvailableForFinish[0]._props.counter.conditions.push(exitCondition); + quest.conditions.AvailableForFinish[0].counter.conditions.push(exitCondition); } // Difficulty for exploration goes from 1 extract to maxExtracts @@ -753,11 +746,11 @@ export class RepeatableQuestGenerator traderId: string, questTypePool: IQuestTypePool, repeatableConfig: IRepeatableQuestConfig, - ): IPickup + ): IRepeatableQuest { const pickupConfig = repeatableConfig.questConfig.Pickup; - const quest = this.generateRepeatableTemplate("Pickup", traderId, repeatableConfig.side) as IPickup; + const quest = this.generateRepeatableTemplate("Pickup", traderId, repeatableConfig.side); const itemTypeToFetchWithCount = this.randomUtil.getArrayValue(pickupConfig.ItemTypeToFetchWithMaxCount); const itemCountToFetch = this.randomUtil.randInt( @@ -768,18 +761,18 @@ export class RepeatableQuestGenerator // const locationKey: string = this.randomUtil.drawRandomFromDict(questTypePool.pool.Pickup.locations)[0]; // const locationTarget = questTypePool.pool.Pickup.locations[locationKey]; - const findCondition = quest.conditions.AvailableForFinish.find((x) => x._parent === "FindItem"); - findCondition._props.target = [itemTypeToFetchWithCount.itemType]; - findCondition._props.value = itemCountToFetch; + const findCondition = quest.conditions.AvailableForFinish.find((x) => x.conditionType === "FindItem"); + findCondition.target = [itemTypeToFetchWithCount.itemType]; + findCondition.value = itemCountToFetch; - const counterCreatorCondition = quest.conditions.AvailableForFinish.find((x) => x._parent === "CounterCreator"); + const counterCreatorCondition = quest.conditions.AvailableForFinish.find((x) => x.conditionType === "CounterCreator"); // const locationCondition = counterCreatorCondition._props.counter.conditions.find(x => x._parent === "Location"); // (locationCondition._props as ILocationConditionProps).target = [...locationTarget]; - const equipmentCondition = counterCreatorCondition._props.counter.conditions.find((x) => - x._parent === "Equipment" + const equipmentCondition = counterCreatorCondition.counter.conditions.find((x) => + x.conditionType === "Equipment" ); - (equipmentCondition._props as IEquipmentConditionProps).equipmentInclusive = [[ + equipmentCondition.equipmentInclusive = [[ itemTypeToFetchWithCount.itemType, ]]; @@ -806,11 +799,13 @@ export class RepeatableQuestGenerator * @param {string} exit The exit name to generate the condition for * @returns {object} Exit condition */ - protected generateExplorationExitCondition(exit: Exit): IExplorationCondition + protected generateExplorationExitCondition(exit: Exit): IQuestConditionCounterCondition { return { - _parent: "ExitName", - _props: { exitName: exit.Name, id: this.objectId.generate(), dynamicLocale: true }, + conditionType: "ExitName", + exitName: exit.Name, + id: this.objectId.generate(), + dynamicLocale: true, }; } @@ -840,7 +835,7 @@ export class RepeatableQuestGenerator traderId: string, repeatableConfig: IRepeatableQuestConfig, questConfig: IBaseQuestConfig, - ): IRewards + ): IQuestRewards { // difficulty could go from 0.2 ... -> for lowest diffuculty receive 0.2*nominal reward const levelsConfig = repeatableConfig.rewardScaling.levels; @@ -883,7 +878,7 @@ export class RepeatableQuestGenerator let roublesBudget = rewardRoubles; let rewardItemPool = this.chooseRewardItemsWithinBudget(repeatableConfig, roublesBudget, traderId); - const rewards: IRewards = { + const rewards: IQuestRewards = { Started: [], Success: [], Fail: [], @@ -893,7 +888,7 @@ export class RepeatableQuestGenerator // Add xp reward if (rewardXP > 0) { - rewards.Success.push({ value: rewardXP, type: "Experience", index: rewardIndex }); + rewards.Success.push({ value: rewardXP, type: QuestRewardType.EXPERIENCE, index: rewardIndex }); rewardIndex++; } @@ -987,7 +982,11 @@ export class RepeatableQuestGenerator // Add rep reward to rewards array if (rewardReputation > 0) { - const reward: IReward = { target: traderId, value: rewardReputation, type: "TraderStanding", index: rewardIndex }; + const reward: IQuestReward = { + target: traderId, + value: rewardReputation, + type: QuestRewardType.TRADER_STANDING, + index: rewardIndex }; rewards.Success.push(reward); rewardIndex++; } @@ -995,10 +994,10 @@ export class RepeatableQuestGenerator // Chance of adding skill reward if (this.randomUtil.getChance100(skillRewardChance * 100)) { - const reward: IReward = { + const reward: IQuestReward = { target: this.randomUtil.getArrayValue(questConfig.possibleSkillRewards), value: skillPointReward, - type: "Skill", + type: QuestRewardType.SKILL, index: rewardIndex, }; rewards.Success.push(reward); @@ -1028,10 +1027,13 @@ export class RepeatableQuestGenerator protected getRandomisedRewardItemStackSizeByPrice(item: ITemplateItem): number { const rewardItemPrice = this.itemHelper.getStaticItemPrice(item._id); - if (rewardItemPrice < 3000) { + if (rewardItemPrice < 3000) + { return this.randomUtil.getArrayValue([2, 3, 4]); } - else if (rewardItemPrice < 10000) { + + if (rewardItemPrice < 10000) + { return this.randomUtil.getArrayValue([2, 3]); } @@ -1082,10 +1084,10 @@ export class RepeatableQuestGenerator * @param {integer} index All rewards will be appended to a list, for unknown reasons the client wants the index * @returns {object} Object of "Reward"-item-type */ - protected generateRewardItem(tpl: string, value: number, index: number, preset: Item[] = null): IReward + protected generateRewardItem(tpl: string, value: number, index: number, preset: Item[] = null): IQuestReward { const id = this.objectId.generate(); - const rewardItem: IReward = { target: id, value: value, type: "Item", index: index }; + const rewardItem: IQuestReward = { target: id, value: value, type: QuestRewardType.ITEM, index: index }; if (preset) { diff --git a/project/src/helpers/QuestConditionHelper.ts b/project/src/helpers/QuestConditionHelper.ts index e57df6e5..0e0b2548 100644 --- a/project/src/helpers/QuestConditionHelper.ts +++ b/project/src/helpers/QuestConditionHelper.ts @@ -1,47 +1,47 @@ import { injectable } from "tsyringe"; -import { AvailableForConditions } from "@spt-aki/models/eft/common/tables/IQuest"; +import { IQuestCondition } from "@spt-aki/models/eft/common/tables/IQuest"; @injectable() export class QuestConditionHelper { public getQuestConditions( - q: AvailableForConditions[], - furtherFilter: (a: AvailableForConditions) => AvailableForConditions[] = null, - ): AvailableForConditions[] + q: IQuestCondition[], + furtherFilter: (a: IQuestCondition) => IQuestCondition[] = null, + ): IQuestCondition[] { return this.filterConditions(q, "Quest", furtherFilter); } public getLevelConditions( - q: AvailableForConditions[], - furtherFilter: (a: AvailableForConditions) => AvailableForConditions[] = null, - ): AvailableForConditions[] + q: IQuestCondition[], + furtherFilter: (a: IQuestCondition) => IQuestCondition[] = null, + ): IQuestCondition[] { return this.filterConditions(q, "Level", furtherFilter); } public getLoyaltyConditions( - q: AvailableForConditions[], - furtherFilter: (a: AvailableForConditions) => AvailableForConditions[] = null, - ): AvailableForConditions[] + q: IQuestCondition[], + furtherFilter: (a: IQuestCondition) => IQuestCondition[] = null, + ): IQuestCondition[] { return this.filterConditions(q, "TraderLoyalty", furtherFilter); } public getStandingConditions( - q: AvailableForConditions[], - furtherFilter: (a: AvailableForConditions) => AvailableForConditions[] = null, - ): AvailableForConditions[] + q: IQuestCondition[], + furtherFilter: (a: IQuestCondition) => IQuestCondition[] = null, + ): IQuestCondition[] { return this.filterConditions(q, "TraderStanding", furtherFilter); } protected filterConditions( - q: AvailableForConditions[], + q: IQuestCondition[], questType: string, - furtherFilter: (a: AvailableForConditions) => AvailableForConditions[] = null, - ): AvailableForConditions[] + furtherFilter: (a: IQuestCondition) => IQuestCondition[] = null, + ): IQuestCondition[] { const filteredQuests = q.filter((c) => { diff --git a/project/src/helpers/QuestHelper.ts b/project/src/helpers/QuestHelper.ts index 57d9bad0..36bae2df 100644 --- a/project/src/helpers/QuestHelper.ts +++ b/project/src/helpers/QuestHelper.ts @@ -10,7 +10,7 @@ import { TraderHelper } from "@spt-aki/helpers/TraderHelper"; import { IPmcData } from "@spt-aki/models/eft/common/IPmcData"; import { Common, IQuestStatus } from "@spt-aki/models/eft/common/tables/IBotBase"; import { Item } from "@spt-aki/models/eft/common/tables/IItem"; -import { AvailableForConditions, IQuest, Reward } from "@spt-aki/models/eft/common/tables/IQuest"; +import { IQuest, IQuestCondition, IQuestReward } from "@spt-aki/models/eft/common/tables/IQuest"; import { IItemEventRouterResponse } from "@spt-aki/models/eft/itemEvent/IItemEventRouterResponse"; import { IAcceptQuestRequestData } from "@spt-aki/models/eft/quests/IAcceptQuestRequestData"; import { IFailQuestRequestData } from "@spt-aki/models/eft/quests/IFailQuestRequestData"; @@ -78,7 +78,7 @@ export class QuestHelper * @param condition Quest condition * @returns true if player level is greater than or equal to quest */ - public doesPlayerLevelFulfilCondition(playerLevel: number, condition: AvailableForConditions): boolean + public doesPlayerLevelFulfilCondition(playerLevel: number, condition: IQuestCondition): boolean { if (condition.conditionType === "Level") { @@ -198,7 +198,7 @@ export class QuestHelper * @param profile Player profile * @returns true if loyalty is high enough to fulfill quest requirement */ - public traderLoyaltyLevelRequirementCheck(questProperties: AvailableForConditions, profile: IPmcData): boolean + public traderLoyaltyLevelRequirementCheck(questProperties: IQuestCondition, profile: IPmcData): boolean { const requiredLoyaltyLevel = Number(questProperties.value); const trader = profile.TradersInfo[questProperties.target]; @@ -216,7 +216,7 @@ export class QuestHelper * @param profile Player profile * @returns true if standing is high enough to fulfill quest requirement */ - public traderStandingRequirementCheck(questProperties: AvailableForConditions, profile: IPmcData): boolean + public traderStandingRequirementCheck(questProperties: IQuestCondition, profile: IPmcData): boolean { const requiredStanding = Number(questProperties.value); const trader = profile.TradersInfo[questProperties.target]; @@ -253,17 +253,17 @@ export class QuestHelper } /** - * take reward item from quest and set FiR status + fix stack sizes + fix mod Ids - * @param reward Reward item to fix + * Take reward item from quest and set FiR status + fix stack sizes + fix mod Ids + * @param questReward Reward item to fix * @returns Fixed rewards */ - protected processReward(reward: Reward): Reward[] + protected processReward(questReward: IQuestReward): Item[] { - let rewardItems: Reward[] = []; + let rewardItems: Item[] = []; let targets: Item[] = []; const mods: Item[] = []; - for (const item of reward.items) + for (const item of questReward.items) { // reward items are granted Found in Raid status if (!item.upd) @@ -274,7 +274,7 @@ export class QuestHelper item.upd.SpawnedInSession = true; // separate base item and mods, fix stacks - if (item._id === reward.target) + if (item._id === questReward.target) { if ( (item.parentId !== undefined) && (item.parentId === "hideout") @@ -311,7 +311,7 @@ export class QuestHelper items.push(this.jsonUtil.clone(mod)); } - rewardItems = rewardItems.concat(this.ragfairServerHelper.reparentPresets(target, items)); + rewardItems = rewardItems.concat(this.ragfairServerHelper.reparentPresets(target, items)); } return rewardItems; @@ -323,11 +323,13 @@ export class QuestHelper * @param status Quest status that holds the items (Started, Success, Fail) * @returns array of items with the correct maxStack */ - public getQuestRewardItems(quest: IQuest, status: QuestStatus): Reward[] + public getQuestRewardItems(quest: IQuest, status: QuestStatus): Item[] { // Iterate over all rewards with the desired status, flatten out items that have a type of Item - const questRewards = quest.rewards[QuestStatus[status]].flatMap((reward: Reward) => - reward.type === "Item" ? this.processReward(reward) : [] + const questRewards = quest.rewards[QuestStatus[status]].flatMap((reward: IQuestReward) => + reward.type === "Item" + ? this.processReward(reward) + : [] ); return questRewards; @@ -410,7 +412,7 @@ export class QuestHelper const acceptedQuestCondition = quest.conditions.AvailableForStart.find((x) => { return x.conditionType === "Quest" - && x.target === startedQuestId + && x.target.includes(startedQuestId) && x.status[0] === QuestStatus.Started; }); @@ -466,7 +468,7 @@ export class QuestHelper const acceptedQuestCondition = q.conditions.AvailableForStart.find((c) => { return c.conditionType === "Quest" - && c.target === failedQuestId + && c.target.includes(failedQuestId) && c.status[0] === QuestStatus.Fail; }); @@ -495,7 +497,7 @@ export class QuestHelper */ public applyMoneyBoost(quest: IQuest, multiplier: number, questStatus: QuestStatus): IQuest { - const rewards: Reward[] = quest.rewards?.[QuestStatus[questStatus]] ?? []; + const rewards: IQuestReward[] = quest.rewards?.[QuestStatus[questStatus]] ?? []; for (const reward of rewards) { if (reward.type === "Item") @@ -786,7 +788,7 @@ export class QuestHelper state: QuestStatus, sessionId: string, questResponse: IItemEventRouterResponse, - ): Reward[] + ): Item[] { // Repeatable quest base data is always in PMCProfile, `profileData` may be scav profile // TODO: consider moving repeatable quest data to profile-agnostic location @@ -809,7 +811,7 @@ export class QuestHelper // e.g. 'Success' or 'AvailableForFinish' const questStateAsString = QuestStatus[state]; - for (const reward of questDetails.rewards[questStateAsString]) + for (const reward of questDetails.rewards[questStateAsString]) { switch (reward.type) { @@ -873,7 +875,7 @@ export class QuestHelper */ protected findAndAddHideoutProductionIdToProfile( pmcData: IPmcData, - craftUnlockReward: Reward, + craftUnlockReward: IQuestReward, questDetails: IQuest, sessionID: string, response: IItemEventRouterResponse, diff --git a/project/src/models/eft/common/tables/IAchievement.ts b/project/src/models/eft/common/tables/IAchievement.ts index ea694ac9..f35fba82 100644 --- a/project/src/models/eft/common/tables/IAchievement.ts +++ b/project/src/models/eft/common/tables/IAchievement.ts @@ -1,12 +1,12 @@ -import { IQuestConditions, IRewards } from "./IQuest" +import { IQuestConditionTypes, IQuestRewards } from "./IQuest" export interface IAchievement { id: string imageUrl: string assetPath: string - rewards: IRewards - conditions: IQuestConditions + rewards: IQuestRewards + conditions: IQuestConditionTypes instantComplete: boolean showNotificationsInGame: boolean showProgress: boolean diff --git a/project/src/models/eft/common/tables/IQuest.ts b/project/src/models/eft/common/tables/IQuest.ts index b48d4f1e..65c6b278 100644 --- a/project/src/models/eft/common/tables/IQuest.ts +++ b/project/src/models/eft/common/tables/IQuest.ts @@ -9,7 +9,7 @@ export interface IQuest QuestName?: string; _id: string; canShowNotificationsInGame: boolean; - conditions: IQuestConditions; + conditions: IQuestConditionTypes; description: string; failMessageText: string; name: string; @@ -26,8 +26,11 @@ export interface IQuest secretQuest: boolean; startedMessageText: string; successMessageText: string; + acceptPlayerMessage: string; + declinePlayerMessage: string; + completePlayerMessage: string; templateId: string; - rewards: IRewards; + rewards: IQuestRewards; /** Becomes 'AppearStatus' inside client */ status: string | number; KeyQuest: boolean; @@ -38,26 +41,27 @@ export interface IQuest sptStatus?: QuestStatus; } -export interface IQuestConditions +export interface IQuestConditionTypes { - Started: AvailableForConditions[]; - AvailableForFinish: AvailableForConditions[]; - AvailableForStart: AvailableForConditions[]; - Success: AvailableForConditions[]; - Fail: AvailableForConditions[]; + Started: IQuestCondition[]; + AvailableForFinish: IQuestCondition[]; + AvailableForStart: IQuestCondition[]; + Success: IQuestCondition[]; + Fail: IQuestCondition[]; } -export interface AvailableForConditions +export interface IQuestCondition { id: string; - index: number; - parentId: string; - isEncoded: boolean; + index?: number; + compareMethod?: string dynamicLocale: boolean; - value?: string | number; - compareMethod?: string; visibilityConditions?: VisibilityCondition[]; - target?: string | string[]; // TODO: split each availableForX object into each type: FindItem, HandoverItem, Level, Quest, TraderLoyalty etc + globalQuestCounterId?: string + parentId?: string; + target: string[] | string; + value?: string | number; + type?: boolean; status?: QuestStatus[]; availableAfter?: number; dispersion?: number; @@ -67,42 +71,42 @@ export interface AvailableForConditions dogtagLevel?: number; maxDurability?: number; minDurability?: number; - counter?: AvailableForCounter; + counter?: IQuestConditionCounter; plantTime?: number; zoneId?: string; - type?: boolean; countInRaid?: boolean; - globalQuestCounterId?: string; completeInSeconds?: number - conditionType?: string + isEncoded?: boolean; + conditionType?: string; } -export interface AvailableForCounter +export interface IQuestConditionCounter { id: string; - conditions: CounterCondition[]; + conditions: IQuestConditionCounterCondition[]; } -export interface CounterCondition +export interface IQuestConditionCounterCondition { id: string; - completeInSeconds: number dynamicLocale: boolean + target?: string[] | string; // TODO: some objects have an array and some are just strings, thanks bsg very cool + completeInSeconds?: number energy?: IValueCompare + exitName?: string; hydration?: IValueCompare time?: IValueCompare - target: string[] | string; // TODO: some objects have an array and some are just strings, thanks bsg very cool compareMethod?: string; - value?: string; + value?: number; weapon?: string[]; - distance: ICounterConditionDistance + distance?: ICounterConditionDistance equipmentInclusive?: string[][]; weaponModsInclusive?: string[][]; weaponModsExclusive?: string[][]; enemyEquipmentInclusive?: string[][]; enemyEquipmentExclusive?: string[][]; weaponCaliber?: string[] - savageRole: string[] + savageRole?: string[] status?: string[]; bodyPart?: string[]; daytime?: IDaytimeCounter; @@ -138,26 +142,28 @@ export interface IDaytimeCounter export interface VisibilityCondition { id: string; - value: number; - dynamicLocale: boolean; + target: string + value?: number; + dynamicLocale?: boolean; oneSessionOnly: boolean; + conditionType: string; } -export interface IRewards +export interface IQuestRewards { - AvailableForStart: Reward[]; - AvailableForFinish: Reward[]; - Started: Reward[]; - Success: Reward[]; - Fail: Reward[]; - FailRestartable: Reward[]; - Expired: Reward[]; + AvailableForStart?: IQuestReward[]; + AvailableForFinish?: IQuestReward[]; + Started?: IQuestReward[]; + Success?: IQuestReward[]; + Fail?: IQuestReward[]; + FailRestartable?: IQuestReward[]; + Expired?: IQuestReward[]; } -export interface Reward extends Item +export interface IQuestReward { value?: string | number; - id: string; + id?: string; type: QuestRewardType; index: number; target?: string; diff --git a/project/src/models/eft/common/tables/IRepeatableQuests.ts b/project/src/models/eft/common/tables/IRepeatableQuests.ts index 21836a5c..58f94353 100644 --- a/project/src/models/eft/common/tables/IRepeatableQuests.ts +++ b/project/src/models/eft/common/tables/IRepeatableQuests.ts @@ -1,27 +1,25 @@ -import { Item } from "@spt-aki/models/eft/common/tables/IItem"; +import { IQuest, IQuestConditionTypes, IQuestRewards } from "./IQuest"; -export interface IReward +export interface IRepeatableQuest extends IQuest { - index: number; - type: string; - value: number; - target?: string; - items?: Item[]; + changeCost: IChangeCost[] + changeStandingCost: number; + sptRepatableGroupName: string } export interface IRepeatableQuestDatabase { - templates: ITemplates; + templates: IRepeatableTemplates; rewards: IRewardOptions; data: IOptions; samples: ISampleQuests[]; } -export interface ITemplates +export interface IRepeatableTemplates { - Elimination: IRepeatableQuest; - Completion: IRepeatableQuest; - Exploration: IRepeatableQuest; + Elimination: IQuest; + Completion: IQuest; + Exploration: IQuest; } export interface IPmcDataRepeatableQuest @@ -31,11 +29,9 @@ export interface IPmcDataRepeatableQuest activeQuests: IRepeatableQuest[]; inactiveQuests: IRepeatableQuest[]; endTime: number; - changeRequirement: TChangeRequirementRecord; // what it costs to reset redundant to change requirements within the IRepeatableQuest + changeRequirement: Record; // What it costs to reset redundant to change requirements within IRepeatableQuest } -export type TChangeRequirementRecord = Record; - export interface IChangeRequirement { changeCost: IChangeCost[]; @@ -48,264 +44,6 @@ export interface IChangeCost count: number; // amount of item needed to reset } -export interface IRepeatableQuest -{ - _id: string; - traderId: string; - location: string; - image: string; - type: string; - isKey: boolean; - restartable: boolean; - instantComplete: boolean; - secretQuest: boolean; - canShowNotificationsInGame: boolean; - rewards: IRewards; - conditions: IConditions; - side: string; - questStatus: any; - name: string; - note: string; - description: string; - successMessageText: string; - failMessageText: string; - startedMessageText: string; - changeQuestMessageText: string; - acceptPlayerMessage: string; - declinePlayerMessage: string; - completePlayerMessage: string; - templateId: string; - changeCost: IChangeCost[]; - changeStandingCost: number; - sptRepatableGroupName?: string; -} - -export interface IRewards -{ - Started: IReward[]; - Success: IReward[]; - Fail: IReward[]; -} - -export interface IConditions -{ - AvailableForStart: any[]; - AvailableForFinish: IAvailableFor[]; - Fail: any[]; -} - -export interface IAvailableFor -{ - _props: IAvailableForProps; - _parent: string; - dynamicLocale: boolean; -} - -export interface IAvailableForProps -{ - id: string; - parentId: string; - dynamicLocale: boolean; - index: number; - visibilityConditions: IVisibilityCondition[]; - value: number; -} - -export interface IVisibilityCondition -{ - id: string; - oneSessionOnly: boolean; - value: number; - index: number; - dynamicLocale: boolean; -} - -export interface IAvailableForPropsCounter extends IAvailableForProps -{ - type: string; - oneSessionOnly: boolean; - doNotResetIfCounterCompleted: boolean; - counter?: ICounter; -} - -export interface ICounter -{ - id: string; - conditions: ICondition[]; -} - -export interface ICondition -{ - _props: IConditionProps; - _parent: string; -} - -export interface IConditionProps -{ - id: string; - dynamicLocale: boolean; -} - -// Elimination -export interface IElimination extends IRepeatableQuest -{ - conditions: IEliminationConditions; -} - -export interface IEliminationConditions extends IConditions -{ - AvailableForFinish: IEliminationAvailableFor[]; -} - -export interface IEliminationAvailableFor extends IAvailableFor -{ - _props: IEliminationAvailableForProps; -} - -export interface IEliminationAvailableForProps extends IAvailableForPropsCounter -{ - counter: IEliminationCounter; -} - -export interface IEliminationCounter extends ICounter -{ - conditions: IEliminationCondition[]; -} - -export interface IEliminationCondition extends ICondition -{ - _props: ILocationConditionProps | IKillConditionProps; -} - -// Exploration -export interface IExploration extends IRepeatableQuest -{ - conditions: IExplorationConditions; -} - -export interface IExplorationConditions extends IConditions -{ - AvailableForFinish: IExplorationAvailableFor[]; -} - -export interface IExplorationAvailableFor extends IAvailableFor -{ - _props: IExplorationAvailableForProps; -} - -export interface IExplorationAvailableForProps extends IAvailableForPropsCounter -{ - counter: IExplorationCounter; -} - -export interface IExplorationCounter extends ICounter -{ - conditions: IExplorationCondition[]; -} - -export interface IExplorationCondition extends ICondition -{ - _props: ILocationConditionProps | IExitStatusConditionProps | IExitNameConditionProps; -} - -// Pickup -export interface IPickup extends IRepeatableQuest -{ - conditions: IPickupConditions; -} - -export interface IPickupConditions extends IConditions -{ - AvailableForFinish: IPickupAvailableFor[]; -} - -export interface IPickupAvailableFor extends IAvailableFor -{ - _props: IPickupAvailableForProps; -} - -export interface IPickupAvailableForProps extends IAvailableForPropsCounter -{ - target: string[]; - counter?: IPickupCounter; -} - -export interface IPickupCounter extends ICounter -{ - conditions: IPickupCondition[]; -} - -export interface IPickupCondition extends ICondition -{ - _props: IEquipmentConditionProps | ILocationConditionProps | IExitStatusConditionProps; -} - -// Completion -export interface ICompletion extends IRepeatableQuest -{ - conditions: ICompletionConditions; -} - -export interface ICompletionConditions extends IConditions -{ - AvailableForFinish: ICompletionAvailableFor[]; -} - -export interface ICompletionAvailableFor extends IAvailableFor -{ - _props: ICompletionAvailableForProps; -} -export interface ICompletionAvailableForProps extends IAvailableForProps -{ - target: string[]; - minDurability: number; - maxDurability: number; - dogtagLevel: number; - onlyFoundInRaid: boolean; -} - -// Quest Conditions - -export interface ILocationConditionProps extends IConditionProps -{ - target: string[]; - weapon?: string[]; - weaponCategories?: string[]; -} - -export interface IEquipmentConditionProps extends IConditionProps -{ - equipmentInclusive: [string[]]; - IncludeNotEquippedItems: boolean; -} - -export interface IKillConditionProps extends IConditionProps -{ - target: string; - value: number; - savageRole?: string[]; - bodyPart?: string[]; - distance?: IDistanceCheck; - weapon?: string[]; - weaponCategories?: string[]; -} - -export interface IDistanceCheck -{ - compareMethod: string; - value: number; -} - -export interface IExitStatusConditionProps extends IConditionProps -{ - status: string[]; -} - -export interface IExitNameConditionProps extends IConditionProps -{ - exitName: string; -} - // Config Options export interface IRewardOptions @@ -348,8 +86,8 @@ export interface ISampleQuests instantComplete: boolean; secretQuest: boolean; canShowNotificationsInGame: boolean; - rewards: IRewards; - conditions: IConditions; + rewards: IQuestRewards; + conditions: IQuestConditionTypes; name: string; note: string; description: string;