Updated server handling of quests/repeatable quests to match 0.14 changes - this will break profiles

This commit is contained in:
Dev 2024-01-05 19:52:21 +00:00
parent 3979e6ef61
commit 9dbd3d1acf
9 changed files with 316 additions and 572 deletions

View File

@ -19,39 +19,51 @@
"conditions": { "conditions": {
"AvailableForStart": [], "AvailableForStart": [],
"AvailableForFinish": [{ "AvailableForFinish": [{
"_props": { "id": "618c1de4d4cd91439f3de4ae",
"id": "618c1de4d4cd91439f3de4ae", "index": 0,
"parentId": "", "dynamicLocale": true,
"dynamicLocale": true, "visibilityConditions": [],
"index": 0, "globalQuestCounterId": "",
"visibilityConditions": [], "parentId": "",
"value": 1, "value": 1,
"type": "Elimination", "type": "Elimination",
"oneSessionOnly": false, "oneSessionOnly": false,
"doNotResetIfCounterCompleted": false, "doNotResetIfCounterCompleted": false,
"counter": { "counter": {
"id": "618c1de4d4cd91439f3de4ac", "id": "618c1de4d4cd91439f3de4ac",
"conditions": [{ "conditions": [{
"_props": { "id": "618c1de4d4cd91439f3de4ad",
"target": "Savage", "dynamicLocale": true,
"value": 1, "target": "Savage",
"savageRole": [ "compareMethod": ">=",
"bossBully" "value": 1,
], "weapon": [],
"id": "618c1de4d4cd91439f3de4ad", "distance": {
"dynamicLocale": true "value": 0,
}, "compareMethod": ">="
"_parent": "Kills" },
} "weaponModsInclusive": [],
] "weaponModsExclusive": [],
} "enemyEquipmentInclusive": [],
}, "enemyEquipmentExclusive": [],
"_parent": "CounterCreator", "weaponCaliber": [],
"dynamicLocale": true "savageRole": [],
} "bodyPart": [],
], "daytime": {
"from": 0,
"to": 0
},
"enemyHealthEffects": [],
"resetOnSessionEnd": false,
"conditionType": "Kills"
}
]
},
"conditionType": "CounterCreator"
}],
"Fail": [] "Fail": []
}, },
"side": "Pmc",
"name": "{templateId} name {traderId}", "name": "{templateId} name {traderId}",
"note": "{templateId} note {traderId}", "note": "{templateId} note {traderId}",
"description": "{templateId} description {traderId} 0", "description": "{templateId} description {traderId} 0",
@ -91,6 +103,7 @@
"AvailableForFinish": [], "AvailableForFinish": [],
"Fail": [] "Fail": []
}, },
"side": "Pmc",
"name": "{templateId} name {traderId}", "name": "{templateId} name {traderId}",
"note": "{templateId} note {traderId}", "note": "{templateId} note {traderId}",
"description": "{templateId} description {traderId} 0", "description": "{templateId} description {traderId} 0",
@ -110,7 +123,7 @@
"changeStandingCost": 0 "changeStandingCost": 0
}, },
"Exploration": { "Exploration": {
"_id": null, "_id": "65947c6afb90e7fcb40f8d684",
"traderId": "54cb50c76803fa8b248b4571", "traderId": "54cb50c76803fa8b248b4571",
"location": null, "location": null,
"image": "/files/quest/icon/616d993bc8c5ad2ab30ff6ba.jpg", "image": "/files/quest/icon/616d993bc8c5ad2ab30ff6ba.jpg",
@ -128,44 +141,40 @@
"conditions": { "conditions": {
"AvailableForStart": [], "AvailableForStart": [],
"AvailableForFinish": [{ "AvailableForFinish": [{
"_props": { "id": "618c1de4d4cd91439f3de4a5",
"id": "618c1de4d4cd91439f3de4a5", "index": 0,
"parentId": "", "dynamicLocale": true,
"dynamicLocale": true, "visibilityConditions": [],
"index": 0, "globalQuestCounterId": "",
"visibilityConditions": [], "parentId": "",
"value": 1, "value": 1,
"type": "Completion", "type": "Completion",
"oneSessionOnly": false, "oneSessionOnly": false,
"doNotResetIfCounterCompleted": false, "completeInSeconds": 0,
"counter": { "doNotResetIfCounterCompleted": false,
"id": "618c1de4d4cd91439f3de4a4", "counter": {
"conditions": [{ "id": "618c1de4d4cd91439f3de4a4",
"_props": { "conditions": [{
"status": [ "status": [
"Survived" "Survived"
], ],
"id": "618c1de4d4cd91439f3de4a3", "id": "618c1de4d4cd91439f3de4a3",
"dynamicLocale": true "dynamicLocale": true,
}, "conditionType": "ExitStatus"
"_parent": "ExitStatus" }, {
}, { "target": [],
"_props": { "id": "618c1de4d4cd91439f3de4a2",
"target": [], "dynamicLocale": true,
"id": "618c1de4d4cd91439f3de4a2", "conditionType": "Location"
"dynamicLocale": true }
}, ]
"_parent": "Location" },
} "conditionType": "CounterCreator"
]
}
},
"_parent": "CounterCreator",
"dynamicLocale": true
} }
], ],
"Fail": [] "Fail": []
}, },
"side": "Pmc",
"name": "{templateId} name {traderId}", "name": "{templateId} name {traderId}",
"note": "{templateId} note {traderId}", "note": "{templateId} note {traderId}",
"description": "{templateId} description {traderId} 0", "description": "{templateId} description {traderId} 0",
@ -203,73 +212,60 @@
"conditions": { "conditions": {
"AvailableForStart": [], "AvailableForStart": [],
"AvailableForFinish": [{ "AvailableForFinish": [{
"_props": { "id": "64cfb3818db9f48b3f0b0a6f",
"id": "64cfb3818db9f48b3f0b0a6f", "parentId": "",
"parentId": "", "dynamicLocale": false,
"dynamicLocale": false, "index": 0,
"index": 0, "visibilityConditions": [],
"visibilityConditions": [], "globalQuestCounterId": null,
"globalQuestCounterId": null, "target": ["5b47574386f77428ca22b336"],
"target": ["5b47574386f77428ca22b336"], "value": 7,
"value": 7, "minDurability": 0,
"minDurability": 0, "maxDurability": 100,
"maxDurability": 100, "dogtagLevel": 0,
"dogtagLevel": 0, "onlyFoundInRaid": false,
"onlyFoundInRaid": false, "isEncoded": false,
"isEncoded": false, "countInRaid": true,
"countInRaid": true "conditionType": "FindItem"
},
"_parent": "FindItem",
"dynamicLocale": false
}, { }, {
"_props": { "id": "64cfb3818db9f48b3f0b0a74",
"id": "64cfb3818db9f48b3f0b0a74", "parentId": "",
"parentId": "", "dynamicLocale": true,
"dynamicLocale": true, "index": 0,
"index": 0, "visibilityConditions": [],
"visibilityConditions": [], "globalQuestCounterId": null,
"globalQuestCounterId": null, "value": 1,
"value": 1, "type": "PickUp",
"type": "PickUp", "completeInSeconds": 0,
"completeInSeconds": 0, "oneSessionOnly": true,
"oneSessionOnly": true, "doNotResetIfCounterCompleted": false,
"doNotResetIfCounterCompleted": false, "counter": {
"counter": { "id": "64cfb3818db9f48b3f0b0a73",
"id": "64cfb3818db9f48b3f0b0a73", "conditions": [{
"conditions": [{ "target": ["any"],
"_props": { "id": "64cfb3818db9f48b3f0b0a70",
"target": ["any"], "dynamicLocale": true,
"id": "64cfb3818db9f48b3f0b0a70", "conditionType": "Location"
"dynamicLocale": true }, {
}, "status": ["Survived"],
"_parent": "Location" "id": "64cfb3818db9f48b3f0b0a71",
}, { "dynamicLocale": true,
"_props": { "conditionType": "ExitStatus"
"status": ["Survived"], }, {
"id": "64cfb3818db9f48b3f0b0a71", "equipmentInclusive": [["5b47574386f77428ca22b336"]],
"dynamicLocale": true "IncludeNotEquippedItems": true,
}, "id": "64cfb3818db9f48b3f0b0a72",
"_parent": "ExitStatus" "dynamicLocale": true,
}, { "conditionType": "Equipment"
"_props": { }
"equipmentInclusive": [["5b47574386f77428ca22b336"]], ]
"IncludeNotEquippedItems": true, },
"id": "64cfb3818db9f48b3f0b0a72", "conditionType": "CounterCreator"
"dynamicLocale": true
},
"_parent": "Equipment"
}
]
}
},
"_parent": "CounterCreator",
"dynamicLocale": true
} }
], ],
"Fail": [] "Fail": []
}, },
"side": "Scav", "side": "Scav",
"questStatus": {},
"name": "{templateId} name {traderId}", "name": "{templateId} name {traderId}",
"note": "{templateId} note {traderId}", "note": "{templateId} note {traderId}",
"description": "{templateId} description {traderId} 0", "description": "{templateId} description {traderId} 0",

View File

@ -9,7 +9,7 @@ import { TraderHelper } from "@spt-aki/helpers/TraderHelper";
import { IPmcData } from "@spt-aki/models/eft/common/IPmcData"; import { IPmcData } from "@spt-aki/models/eft/common/IPmcData";
import { IQuestStatus } from "@spt-aki/models/eft/common/tables/IBotBase"; import { IQuestStatus } from "@spt-aki/models/eft/common/tables/IBotBase";
import { Item } from "@spt-aki/models/eft/common/tables/IItem"; 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 { IPmcDataRepeatableQuest, IRepeatableQuest } from "@spt-aki/models/eft/common/tables/IRepeatableQuests";
import { IItemEventRouterResponse } from "@spt-aki/models/eft/itemEvent/IItemEventRouterResponse"; import { IItemEventRouterResponse } from "@spt-aki/models/eft/itemEvent/IItemEventRouterResponse";
import { IAcceptQuestRequestData } from "@spt-aki/models/eft/quests/IAcceptQuestRequestData"; import { IAcceptQuestRequestData } from "@spt-aki/models/eft/quests/IAcceptQuestRequestData";
@ -135,7 +135,7 @@ export class QuestController
for (const conditionToFulfil of questRequirements) for (const conditionToFulfil of questRequirements)
{ {
// If the previous quest isn't in the user profile, it hasn't been completed or started // 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) if (!prerequisiteQuest)
{ {
haveCompletedPreviousQuest = false; haveCompletedPreviousQuest = false;
@ -609,7 +609,7 @@ export class QuestController
sessionID: string, sessionID: string,
pmcData: IPmcData, pmcData: IPmcData,
completedQuestId: string, completedQuestId: string,
questRewards: Reward[], questRewards: Item[],
): void ): void
{ {
const quest = this.questHelper.getQuestFromDb(completedQuestId, pmcData); 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) // If quest has prereq of completed quest + availableAfter value > 0 (quest has wait time)
const nextQuestWaitCondition = quest.conditions.AvailableForStart.find((x) => const nextQuestWaitCondition = quest.conditions.AvailableForStart.find((x) =>
x.target === completedQuestId && x.availableAfter > 0 x.target.includes(completedQuestId) && x.availableAfter > 0
); );
if (nextQuestWaitCondition) if (nextQuestWaitCondition)
{ {
@ -687,7 +687,7 @@ export class QuestController
return false; 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; let handedInCount = 0;
// Decrement number of items handed in // Decrement number of items handed in
let handoverRequirements: AvailableForConditions; let handoverRequirements: IQuestCondition;
for (const condition of quest.conditions.AvailableForFinish) for (const condition of quest.conditions.AvailableForFinish)
{ {
if ( if (
@ -897,7 +897,7 @@ export class QuestController
protected showQuestItemHandoverMatchError( protected showQuestItemHandoverMatchError(
handoverQuestRequest: IHandoverQuestRequestData, handoverQuestRequest: IHandoverQuestRequestData,
itemHandedOver: Item, itemHandedOver: Item,
handoverRequirements: AvailableForConditions, handoverRequirements: IQuestCondition,
output: IItemEventRouterResponse, output: IItemEventRouterResponse,
): IItemEventRouterResponse ): IItemEventRouterResponse
{ {

View File

@ -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) for (const quest of currentRepeatableQuestType.activeQuests)
{ {
currentRepeatableQuestType.changeRequirement[quest._id] = { currentRepeatableQuestType.changeRequirement[quest._id] = {

View File

@ -9,24 +9,13 @@ import { RepeatableQuestHelper } from "@spt-aki/helpers/RepeatableQuestHelper";
import { Exit, ILocationBase } from "@spt-aki/models/eft/common/ILocationBase"; import { Exit, ILocationBase } from "@spt-aki/models/eft/common/ILocationBase";
import { TraderInfo } from "@spt-aki/models/eft/common/tables/IBotBase"; import { TraderInfo } from "@spt-aki/models/eft/common/tables/IBotBase";
import { Item } from "@spt-aki/models/eft/common/tables/IItem"; import { Item } from "@spt-aki/models/eft/common/tables/IItem";
import { import { IQuestCondition, IQuestConditionCounterCondition, IQuestReward, IQuestRewards } from "@spt-aki/models/eft/common/tables/IQuest";
ICompletion, import { IRepeatableQuest } from "@spt-aki/models/eft/common/tables/IRepeatableQuests";
ICompletionAvailableFor,
IElimination,
IEliminationCondition,
IEquipmentConditionProps,
IExploration,
IExplorationCondition,
IKillConditionProps,
IPickup,
IRepeatableQuest,
IReward,
IRewards,
} from "@spt-aki/models/eft/common/tables/IRepeatableQuests";
import { ITemplateItem } from "@spt-aki/models/eft/common/tables/ITemplateItem"; import { ITemplateItem } from "@spt-aki/models/eft/common/tables/ITemplateItem";
import { BaseClasses } from "@spt-aki/models/enums/BaseClasses"; import { BaseClasses } from "@spt-aki/models/enums/BaseClasses";
import { ConfigTypes } from "@spt-aki/models/enums/ConfigTypes"; import { ConfigTypes } from "@spt-aki/models/enums/ConfigTypes";
import { Money } from "@spt-aki/models/enums/Money"; 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 { Traders } from "@spt-aki/models/enums/Traders";
import { import {
IBaseQuestConfig, IBaseQuestConfig,
@ -136,7 +125,7 @@ export class RepeatableQuestGenerator
traderId: string, traderId: string,
questTypePool: IQuestTypePool, questTypePool: IQuestTypePool,
repeatableConfig: IRepeatableQuestConfig, repeatableConfig: IRepeatableQuestConfig,
): IElimination ): IRepeatableQuest
{ {
const eliminationConfig = this.repeatableQuestHelper.getEliminationConfigByPmcLevel(pmcLevel, repeatableConfig); const eliminationConfig = this.repeatableQuestHelper.getEliminationConfigByPmcLevel(pmcLevel, repeatableConfig);
const locationsConfig = repeatableConfig.locations; 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 // 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 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 // ASSUMPTION: All fence quests are for scavs
if (traderId === Traders.FENCE) if (traderId === Traders.FENCE)
@ -346,17 +335,17 @@ export class RepeatableQuestGenerator
} }
const availableForFinishCondition = quest.conditions.AvailableForFinish[0]; const availableForFinishCondition = quest.conditions.AvailableForFinish[0];
availableForFinishCondition._props.counter.id = this.objectId.generate(); availableForFinishCondition.counter.id = this.objectId.generate();
availableForFinishCondition._props.counter.conditions = []; availableForFinishCondition.counter.conditions = [];
// Only add specific location condition if specific map selected // Only add specific location condition if specific map selected
if (locationKey !== "any") if (locationKey !== "any")
{ {
availableForFinishCondition._props.counter.conditions.push( availableForFinishCondition.counter.conditions.push(
this.generateEliminationLocation(locationsConfig[locationKey]), this.generateEliminationLocation(locationsConfig[locationKey]),
); );
} }
availableForFinishCondition._props.counter.conditions.push( availableForFinishCondition.counter.conditions.push(
this.generateEliminationCondition( this.generateEliminationCondition(
targetKey, targetKey,
bodyPartsToClient, bodyPartsToClient,
@ -365,8 +354,8 @@ export class RepeatableQuestGenerator
allowedWeaponsCategory, allowedWeaponsCategory,
), ),
); );
availableForFinishCondition._props.value = desiredKillCount; availableForFinishCondition.value = desiredKillCount;
availableForFinishCondition._props.id = this.objectId.generate(); availableForFinishCondition.id = this.objectId.generate();
quest.location = this.getQuestLocationByMapId(locationKey); quest.location = this.getQuestLocationByMapId(locationKey);
quest.rewards = this.generateReward( quest.rewards = this.generateReward(
@ -413,11 +402,13 @@ export class RepeatableQuestGenerator
* @param {string} location the location on which to fulfill the elimination quest * @param {string} location the location on which to fulfill the elimination quest
* @returns {IEliminationCondition} object of "Elimination"-location-subcondition * @returns {IEliminationCondition} object of "Elimination"-location-subcondition
*/ */
protected generateEliminationLocation(location: string[]): IEliminationCondition protected generateEliminationLocation(location: string[]): IQuestConditionCounterCondition
{ {
const propsObject: IEliminationCondition = { const propsObject: IQuestConditionCounterCondition = {
_props: { target: location, id: this.objectId.generate(), dynamicLocale: true }, id: this.objectId.generate(),
_parent: "Location", dynamicLocale: true,
target: location,
conditionType: "Location"
}; };
return propsObject; return propsObject;
@ -438,13 +429,14 @@ export class RepeatableQuestGenerator
distance: number, distance: number,
allowedWeapon: string, allowedWeapon: string,
allowedWeaponCategory: string, allowedWeaponCategory: string,
): IEliminationCondition ): IQuestConditionCounterCondition
{ {
const killConditionProps: IKillConditionProps = { const killConditionProps: IQuestConditionCounterCondition = {
target: target, target: target,
value: 1, value: 1,
id: this.objectId.generate(), id: this.objectId.generate(),
dynamicLocale: true, dynamicLocale: true,
conditionType: "Kills",
}; };
if (target.startsWith("boss")) if (target.startsWith("boss"))
@ -474,10 +466,11 @@ export class RepeatableQuestGenerator
// Has specific weapon category requirement // Has specific weapon category requirement
if (allowedWeaponCategory?.length > 0) 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, pmcLevel: number,
traderId: string, traderId: string,
repeatableConfig: IRepeatableQuestConfig, repeatableConfig: IRepeatableQuestConfig,
): ICompletion ): IRepeatableQuest
{ {
const completionConfig = repeatableConfig.questConfig.Completion; const completionConfig = repeatableConfig.questConfig.Completion;
const levelsConfig = repeatableConfig.rewardScaling.levels; const levelsConfig = repeatableConfig.rewardScaling.levels;
@ -500,7 +493,7 @@ export class RepeatableQuestGenerator
const distinctItemsToRetrieveCount = this.randomUtil.getInt(1, completionConfig.uniqueItemCount); 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" // 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); 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 * @param {integer} value amount of items of this specific type to request
* @returns {object} object of "Completion"-condition * @returns {object} object of "Completion"-condition
*/ */
protected generateCompletionAvailableForFinish(itemTpl: string, value: number): ICompletionAvailableFor protected generateCompletionAvailableForFinish(itemTpl: string, value: number): IQuestCondition
{ {
let minDurability = 0; let minDurability = 0;
let onlyFoundInRaid = true; let onlyFoundInRaid = true;
@ -650,21 +643,18 @@ export class RepeatableQuestGenerator
} }
return { return {
_props: { id: this.objectId.generate(),
id: this.objectId.generate(), parentId: "",
parentId: "",
dynamicLocale: true,
index: 0,
visibilityConditions: [],
target: [itemTpl],
value: value,
minDurability: minDurability,
maxDurability: 100,
dogtagLevel: 0,
onlyFoundInRaid: onlyFoundInRaid,
},
_parent: "HandoverItem",
dynamicLocale: true, 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, traderId: string,
questTypePool: IQuestTypePool, questTypePool: IQuestTypePool,
repeatableConfig: IRepeatableQuestConfig, repeatableConfig: IRepeatableQuestConfig,
): IExploration ): IRepeatableQuest
{ {
const explorationConfig = repeatableConfig.questConfig.Exploration; const explorationConfig = repeatableConfig.questConfig.Exploration;
const requiresSpecificExtract = Math.random() < repeatableConfig.questConfig.Exploration.specificExits.probability; const requiresSpecificExtract = Math.random() < repeatableConfig.questConfig.Exploration.specificExits.probability;
@ -705,24 +695,27 @@ export class RepeatableQuestGenerator
// Different max extract count when specific extract needed // Different max extract count when specific extract needed
const numExtracts = this.randomUtil.randInt(1, requiresSpecificExtract ? explorationConfig.maxExtractsWithSpecificExit : explorationConfig.maxExtracts + 1); 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 = { const exitStatusCondition: IQuestConditionCounterCondition = {
_parent: "ExitStatus", conditionType: "ExitStatus",
_props: { id: this.objectId.generate(), dynamicLocale: true, status: ["Survived"] }, id: this.objectId.generate(),
dynamicLocale: true,
status: ["Survived"],
}; };
const locationCondition: IExplorationCondition = { const locationCondition: IQuestConditionCounterCondition = {
_parent: "Location", conditionType: "Location",
_props: { id: this.objectId.generate(), dynamicLocale: true, target: locationTarget }, id: this.objectId.generate(),
dynamicLocale: true,
target: locationTarget,
}; };
quest.conditions.AvailableForFinish[0]._props.counter.id = this.objectId.generate(); quest.conditions.AvailableForFinish[0].counter.id = this.objectId.generate();
quest.conditions.AvailableForFinish[0]._props.counter.conditions = [exitStatusCondition, locationCondition]; quest.conditions.AvailableForFinish[0].counter.conditions = [exitStatusCondition, locationCondition];
quest.conditions.AvailableForFinish[0]._props.value = numExtracts; quest.conditions.AvailableForFinish[0].value = numExtracts;
quest.conditions.AvailableForFinish[0]._props.id = this.objectId.generate(); quest.conditions.AvailableForFinish[0].id = this.objectId.generate();
quest.location = this.getQuestLocationByMapId(locationKey); quest.location = this.getQuestLocationByMapId(locationKey);
if (requiresSpecificExtract) if (requiresSpecificExtract)
{ {
// Filter by whitelist, it's also possible that the field "PassageRequirement" does not exist (e.g. Shoreline) // 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 exit = this.randomUtil.drawRandomFromList(possibleExists, 1)[0];
const exitCondition = this.generateExplorationExitCondition(exit); 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 // Difficulty for exploration goes from 1 extract to maxExtracts
@ -753,11 +746,11 @@ export class RepeatableQuestGenerator
traderId: string, traderId: string,
questTypePool: IQuestTypePool, questTypePool: IQuestTypePool,
repeatableConfig: IRepeatableQuestConfig, repeatableConfig: IRepeatableQuestConfig,
): IPickup ): IRepeatableQuest
{ {
const pickupConfig = repeatableConfig.questConfig.Pickup; 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 itemTypeToFetchWithCount = this.randomUtil.getArrayValue(pickupConfig.ItemTypeToFetchWithMaxCount);
const itemCountToFetch = this.randomUtil.randInt( const itemCountToFetch = this.randomUtil.randInt(
@ -768,18 +761,18 @@ export class RepeatableQuestGenerator
// const locationKey: string = this.randomUtil.drawRandomFromDict(questTypePool.pool.Pickup.locations)[0]; // const locationKey: string = this.randomUtil.drawRandomFromDict(questTypePool.pool.Pickup.locations)[0];
// const locationTarget = questTypePool.pool.Pickup.locations[locationKey]; // const locationTarget = questTypePool.pool.Pickup.locations[locationKey];
const findCondition = quest.conditions.AvailableForFinish.find((x) => x._parent === "FindItem"); const findCondition = quest.conditions.AvailableForFinish.find((x) => x.conditionType === "FindItem");
findCondition._props.target = [itemTypeToFetchWithCount.itemType]; findCondition.target = [itemTypeToFetchWithCount.itemType];
findCondition._props.value = itemCountToFetch; 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"); // const locationCondition = counterCreatorCondition._props.counter.conditions.find(x => x._parent === "Location");
// (locationCondition._props as ILocationConditionProps).target = [...locationTarget]; // (locationCondition._props as ILocationConditionProps).target = [...locationTarget];
const equipmentCondition = counterCreatorCondition._props.counter.conditions.find((x) => const equipmentCondition = counterCreatorCondition.counter.conditions.find((x) =>
x._parent === "Equipment" x.conditionType === "Equipment"
); );
(equipmentCondition._props as IEquipmentConditionProps).equipmentInclusive = [[ equipmentCondition.equipmentInclusive = [[
itemTypeToFetchWithCount.itemType, itemTypeToFetchWithCount.itemType,
]]; ]];
@ -806,11 +799,13 @@ export class RepeatableQuestGenerator
* @param {string} exit The exit name to generate the condition for * @param {string} exit The exit name to generate the condition for
* @returns {object} Exit condition * @returns {object} Exit condition
*/ */
protected generateExplorationExitCondition(exit: Exit): IExplorationCondition protected generateExplorationExitCondition(exit: Exit): IQuestConditionCounterCondition
{ {
return { return {
_parent: "ExitName", conditionType: "ExitName",
_props: { exitName: exit.Name, id: this.objectId.generate(), dynamicLocale: true }, exitName: exit.Name,
id: this.objectId.generate(),
dynamicLocale: true,
}; };
} }
@ -840,7 +835,7 @@ export class RepeatableQuestGenerator
traderId: string, traderId: string,
repeatableConfig: IRepeatableQuestConfig, repeatableConfig: IRepeatableQuestConfig,
questConfig: IBaseQuestConfig, questConfig: IBaseQuestConfig,
): IRewards ): IQuestRewards
{ {
// difficulty could go from 0.2 ... -> for lowest diffuculty receive 0.2*nominal reward // difficulty could go from 0.2 ... -> for lowest diffuculty receive 0.2*nominal reward
const levelsConfig = repeatableConfig.rewardScaling.levels; const levelsConfig = repeatableConfig.rewardScaling.levels;
@ -883,7 +878,7 @@ export class RepeatableQuestGenerator
let roublesBudget = rewardRoubles; let roublesBudget = rewardRoubles;
let rewardItemPool = this.chooseRewardItemsWithinBudget(repeatableConfig, roublesBudget, traderId); let rewardItemPool = this.chooseRewardItemsWithinBudget(repeatableConfig, roublesBudget, traderId);
const rewards: IRewards = { const rewards: IQuestRewards = {
Started: [], Started: [],
Success: [], Success: [],
Fail: [], Fail: [],
@ -893,7 +888,7 @@ export class RepeatableQuestGenerator
// Add xp reward // Add xp reward
if (rewardXP > 0) if (rewardXP > 0)
{ {
rewards.Success.push({ value: rewardXP, type: "Experience", index: rewardIndex }); rewards.Success.push({ value: rewardXP, type: QuestRewardType.EXPERIENCE, index: rewardIndex });
rewardIndex++; rewardIndex++;
} }
@ -987,7 +982,11 @@ export class RepeatableQuestGenerator
// Add rep reward to rewards array // Add rep reward to rewards array
if (rewardReputation > 0) 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); rewards.Success.push(reward);
rewardIndex++; rewardIndex++;
} }
@ -995,10 +994,10 @@ export class RepeatableQuestGenerator
// Chance of adding skill reward // Chance of adding skill reward
if (this.randomUtil.getChance100(skillRewardChance * 100)) if (this.randomUtil.getChance100(skillRewardChance * 100))
{ {
const reward: IReward = { const reward: IQuestReward = {
target: this.randomUtil.getArrayValue(questConfig.possibleSkillRewards), target: this.randomUtil.getArrayValue(questConfig.possibleSkillRewards),
value: skillPointReward, value: skillPointReward,
type: "Skill", type: QuestRewardType.SKILL,
index: rewardIndex, index: rewardIndex,
}; };
rewards.Success.push(reward); rewards.Success.push(reward);
@ -1028,10 +1027,13 @@ export class RepeatableQuestGenerator
protected getRandomisedRewardItemStackSizeByPrice(item: ITemplateItem): number protected getRandomisedRewardItemStackSizeByPrice(item: ITemplateItem): number
{ {
const rewardItemPrice = this.itemHelper.getStaticItemPrice(item._id); const rewardItemPrice = this.itemHelper.getStaticItemPrice(item._id);
if (rewardItemPrice < 3000) { if (rewardItemPrice < 3000)
{
return this.randomUtil.getArrayValue([2, 3, 4]); return this.randomUtil.getArrayValue([2, 3, 4]);
} }
else if (rewardItemPrice < 10000) {
if (rewardItemPrice < 10000)
{
return this.randomUtil.getArrayValue([2, 3]); 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 * @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 * @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 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) if (preset)
{ {

View File

@ -1,47 +1,47 @@
import { injectable } from "tsyringe"; 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() @injectable()
export class QuestConditionHelper export class QuestConditionHelper
{ {
public getQuestConditions( public getQuestConditions(
q: AvailableForConditions[], q: IQuestCondition[],
furtherFilter: (a: AvailableForConditions) => AvailableForConditions[] = null, furtherFilter: (a: IQuestCondition) => IQuestCondition[] = null,
): AvailableForConditions[] ): IQuestCondition[]
{ {
return this.filterConditions(q, "Quest", furtherFilter); return this.filterConditions(q, "Quest", furtherFilter);
} }
public getLevelConditions( public getLevelConditions(
q: AvailableForConditions[], q: IQuestCondition[],
furtherFilter: (a: AvailableForConditions) => AvailableForConditions[] = null, furtherFilter: (a: IQuestCondition) => IQuestCondition[] = null,
): AvailableForConditions[] ): IQuestCondition[]
{ {
return this.filterConditions(q, "Level", furtherFilter); return this.filterConditions(q, "Level", furtherFilter);
} }
public getLoyaltyConditions( public getLoyaltyConditions(
q: AvailableForConditions[], q: IQuestCondition[],
furtherFilter: (a: AvailableForConditions) => AvailableForConditions[] = null, furtherFilter: (a: IQuestCondition) => IQuestCondition[] = null,
): AvailableForConditions[] ): IQuestCondition[]
{ {
return this.filterConditions(q, "TraderLoyalty", furtherFilter); return this.filterConditions(q, "TraderLoyalty", furtherFilter);
} }
public getStandingConditions( public getStandingConditions(
q: AvailableForConditions[], q: IQuestCondition[],
furtherFilter: (a: AvailableForConditions) => AvailableForConditions[] = null, furtherFilter: (a: IQuestCondition) => IQuestCondition[] = null,
): AvailableForConditions[] ): IQuestCondition[]
{ {
return this.filterConditions(q, "TraderStanding", furtherFilter); return this.filterConditions(q, "TraderStanding", furtherFilter);
} }
protected filterConditions( protected filterConditions(
q: AvailableForConditions[], q: IQuestCondition[],
questType: string, questType: string,
furtherFilter: (a: AvailableForConditions) => AvailableForConditions[] = null, furtherFilter: (a: IQuestCondition) => IQuestCondition[] = null,
): AvailableForConditions[] ): IQuestCondition[]
{ {
const filteredQuests = q.filter((c) => const filteredQuests = q.filter((c) =>
{ {

View File

@ -10,7 +10,7 @@ import { TraderHelper } from "@spt-aki/helpers/TraderHelper";
import { IPmcData } from "@spt-aki/models/eft/common/IPmcData"; import { IPmcData } from "@spt-aki/models/eft/common/IPmcData";
import { Common, IQuestStatus } from "@spt-aki/models/eft/common/tables/IBotBase"; import { Common, IQuestStatus } from "@spt-aki/models/eft/common/tables/IBotBase";
import { Item } from "@spt-aki/models/eft/common/tables/IItem"; 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 { IItemEventRouterResponse } from "@spt-aki/models/eft/itemEvent/IItemEventRouterResponse";
import { IAcceptQuestRequestData } from "@spt-aki/models/eft/quests/IAcceptQuestRequestData"; import { IAcceptQuestRequestData } from "@spt-aki/models/eft/quests/IAcceptQuestRequestData";
import { IFailQuestRequestData } from "@spt-aki/models/eft/quests/IFailQuestRequestData"; import { IFailQuestRequestData } from "@spt-aki/models/eft/quests/IFailQuestRequestData";
@ -78,7 +78,7 @@ export class QuestHelper
* @param condition Quest condition * @param condition Quest condition
* @returns true if player level is greater than or equal to quest * @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") if (condition.conditionType === "Level")
{ {
@ -198,7 +198,7 @@ export class QuestHelper
* @param profile Player profile * @param profile Player profile
* @returns true if loyalty is high enough to fulfill quest requirement * @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 requiredLoyaltyLevel = Number(questProperties.value);
const trader = profile.TradersInfo[<string>questProperties.target]; const trader = profile.TradersInfo[<string>questProperties.target];
@ -216,7 +216,7 @@ export class QuestHelper
* @param profile Player profile * @param profile Player profile
* @returns true if standing is high enough to fulfill quest requirement * @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 requiredStanding = Number(questProperties.value);
const trader = profile.TradersInfo[<string>questProperties.target]; const trader = profile.TradersInfo[<string>questProperties.target];
@ -253,17 +253,17 @@ export class QuestHelper
} }
/** /**
* take reward item from quest and set FiR status + fix stack sizes + fix mod Ids * Take reward item from quest and set FiR status + fix stack sizes + fix mod Ids
* @param reward Reward item to fix * @param questReward Reward item to fix
* @returns Fixed rewards * @returns Fixed rewards
*/ */
protected processReward(reward: Reward): Reward[] protected processReward(questReward: IQuestReward): Item[]
{ {
let rewardItems: Reward[] = []; let rewardItems: Item[] = [];
let targets: Item[] = []; let targets: Item[] = [];
const mods: Item[] = []; const mods: Item[] = [];
for (const item of reward.items) for (const item of questReward.items)
{ {
// reward items are granted Found in Raid status // reward items are granted Found in Raid status
if (!item.upd) if (!item.upd)
@ -274,7 +274,7 @@ export class QuestHelper
item.upd.SpawnedInSession = true; item.upd.SpawnedInSession = true;
// separate base item and mods, fix stacks // separate base item and mods, fix stacks
if (item._id === reward.target) if (item._id === questReward.target)
{ {
if ( if (
(item.parentId !== undefined) && (item.parentId === "hideout") (item.parentId !== undefined) && (item.parentId === "hideout")
@ -311,7 +311,7 @@ export class QuestHelper
items.push(this.jsonUtil.clone(mod)); items.push(this.jsonUtil.clone(mod));
} }
rewardItems = rewardItems.concat(<Reward[]>this.ragfairServerHelper.reparentPresets(target, items)); rewardItems = rewardItems.concat(this.ragfairServerHelper.reparentPresets(target, items));
} }
return rewardItems; return rewardItems;
@ -323,11 +323,13 @@ export class QuestHelper
* @param status Quest status that holds the items (Started, Success, Fail) * @param status Quest status that holds the items (Started, Success, Fail)
* @returns array of items with the correct maxStack * @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 // 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) => const questRewards = quest.rewards[QuestStatus[status]].flatMap((reward: IQuestReward) =>
reward.type === "Item" ? this.processReward(reward) : [] reward.type === "Item"
? this.processReward(reward)
: []
); );
return questRewards; return questRewards;
@ -410,7 +412,7 @@ export class QuestHelper
const acceptedQuestCondition = quest.conditions.AvailableForStart.find((x) => const acceptedQuestCondition = quest.conditions.AvailableForStart.find((x) =>
{ {
return x.conditionType === "Quest" return x.conditionType === "Quest"
&& x.target === startedQuestId && x.target.includes(startedQuestId)
&& x.status[0] === QuestStatus.Started; && x.status[0] === QuestStatus.Started;
}); });
@ -466,7 +468,7 @@ export class QuestHelper
const acceptedQuestCondition = q.conditions.AvailableForStart.find((c) => const acceptedQuestCondition = q.conditions.AvailableForStart.find((c) =>
{ {
return c.conditionType === "Quest" return c.conditionType === "Quest"
&& c.target === failedQuestId && c.target.includes(failedQuestId)
&& c.status[0] === QuestStatus.Fail; && c.status[0] === QuestStatus.Fail;
}); });
@ -495,7 +497,7 @@ export class QuestHelper
*/ */
public applyMoneyBoost(quest: IQuest, multiplier: number, questStatus: QuestStatus): IQuest 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) for (const reward of rewards)
{ {
if (reward.type === "Item") if (reward.type === "Item")
@ -786,7 +788,7 @@ export class QuestHelper
state: QuestStatus, state: QuestStatus,
sessionId: string, sessionId: string,
questResponse: IItemEventRouterResponse, questResponse: IItemEventRouterResponse,
): Reward[] ): Item[]
{ {
// Repeatable quest base data is always in PMCProfile, `profileData` may be scav profile // Repeatable quest base data is always in PMCProfile, `profileData` may be scav profile
// TODO: consider moving repeatable quest data to profile-agnostic location // TODO: consider moving repeatable quest data to profile-agnostic location
@ -809,7 +811,7 @@ export class QuestHelper
// e.g. 'Success' or 'AvailableForFinish' // e.g. 'Success' or 'AvailableForFinish'
const questStateAsString = QuestStatus[state]; const questStateAsString = QuestStatus[state];
for (const reward of <Reward[]>questDetails.rewards[questStateAsString]) for (const reward of <IQuestReward[]>questDetails.rewards[questStateAsString])
{ {
switch (reward.type) switch (reward.type)
{ {
@ -873,7 +875,7 @@ export class QuestHelper
*/ */
protected findAndAddHideoutProductionIdToProfile( protected findAndAddHideoutProductionIdToProfile(
pmcData: IPmcData, pmcData: IPmcData,
craftUnlockReward: Reward, craftUnlockReward: IQuestReward,
questDetails: IQuest, questDetails: IQuest,
sessionID: string, sessionID: string,
response: IItemEventRouterResponse, response: IItemEventRouterResponse,

View File

@ -1,12 +1,12 @@
import { IQuestConditions, IRewards } from "./IQuest" import { IQuestConditionTypes, IQuestRewards } from "./IQuest"
export interface IAchievement export interface IAchievement
{ {
id: string id: string
imageUrl: string imageUrl: string
assetPath: string assetPath: string
rewards: IRewards rewards: IQuestRewards
conditions: IQuestConditions conditions: IQuestConditionTypes
instantComplete: boolean instantComplete: boolean
showNotificationsInGame: boolean showNotificationsInGame: boolean
showProgress: boolean showProgress: boolean

View File

@ -9,7 +9,7 @@ export interface IQuest
QuestName?: string; QuestName?: string;
_id: string; _id: string;
canShowNotificationsInGame: boolean; canShowNotificationsInGame: boolean;
conditions: IQuestConditions; conditions: IQuestConditionTypes;
description: string; description: string;
failMessageText: string; failMessageText: string;
name: string; name: string;
@ -26,8 +26,11 @@ export interface IQuest
secretQuest: boolean; secretQuest: boolean;
startedMessageText: string; startedMessageText: string;
successMessageText: string; successMessageText: string;
acceptPlayerMessage: string;
declinePlayerMessage: string;
completePlayerMessage: string;
templateId: string; templateId: string;
rewards: IRewards; rewards: IQuestRewards;
/** Becomes 'AppearStatus' inside client */ /** Becomes 'AppearStatus' inside client */
status: string | number; status: string | number;
KeyQuest: boolean; KeyQuest: boolean;
@ -38,26 +41,27 @@ export interface IQuest
sptStatus?: QuestStatus; sptStatus?: QuestStatus;
} }
export interface IQuestConditions export interface IQuestConditionTypes
{ {
Started: AvailableForConditions[]; Started: IQuestCondition[];
AvailableForFinish: AvailableForConditions[]; AvailableForFinish: IQuestCondition[];
AvailableForStart: AvailableForConditions[]; AvailableForStart: IQuestCondition[];
Success: AvailableForConditions[]; Success: IQuestCondition[];
Fail: AvailableForConditions[]; Fail: IQuestCondition[];
} }
export interface AvailableForConditions export interface IQuestCondition
{ {
id: string; id: string;
index: number; index?: number;
parentId: string; compareMethod?: string
isEncoded: boolean;
dynamicLocale: boolean; dynamicLocale: boolean;
value?: string | number;
compareMethod?: string;
visibilityConditions?: VisibilityCondition[]; 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[]; status?: QuestStatus[];
availableAfter?: number; availableAfter?: number;
dispersion?: number; dispersion?: number;
@ -67,42 +71,42 @@ export interface AvailableForConditions
dogtagLevel?: number; dogtagLevel?: number;
maxDurability?: number; maxDurability?: number;
minDurability?: number; minDurability?: number;
counter?: AvailableForCounter; counter?: IQuestConditionCounter;
plantTime?: number; plantTime?: number;
zoneId?: string; zoneId?: string;
type?: boolean;
countInRaid?: boolean; countInRaid?: boolean;
globalQuestCounterId?: string;
completeInSeconds?: number completeInSeconds?: number
conditionType?: string isEncoded?: boolean;
conditionType?: string;
} }
export interface AvailableForCounter export interface IQuestConditionCounter
{ {
id: string; id: string;
conditions: CounterCondition[]; conditions: IQuestConditionCounterCondition[];
} }
export interface CounterCondition export interface IQuestConditionCounterCondition
{ {
id: string; id: string;
completeInSeconds: number
dynamicLocale: boolean dynamicLocale: boolean
target?: string[] | string; // TODO: some objects have an array and some are just strings, thanks bsg very cool
completeInSeconds?: number
energy?: IValueCompare energy?: IValueCompare
exitName?: string;
hydration?: IValueCompare hydration?: IValueCompare
time?: IValueCompare time?: IValueCompare
target: string[] | string; // TODO: some objects have an array and some are just strings, thanks bsg very cool
compareMethod?: string; compareMethod?: string;
value?: string; value?: number;
weapon?: string[]; weapon?: string[];
distance: ICounterConditionDistance distance?: ICounterConditionDistance
equipmentInclusive?: string[][]; equipmentInclusive?: string[][];
weaponModsInclusive?: string[][]; weaponModsInclusive?: string[][];
weaponModsExclusive?: string[][]; weaponModsExclusive?: string[][];
enemyEquipmentInclusive?: string[][]; enemyEquipmentInclusive?: string[][];
enemyEquipmentExclusive?: string[][]; enemyEquipmentExclusive?: string[][];
weaponCaliber?: string[] weaponCaliber?: string[]
savageRole: string[] savageRole?: string[]
status?: string[]; status?: string[];
bodyPart?: string[]; bodyPart?: string[];
daytime?: IDaytimeCounter; daytime?: IDaytimeCounter;
@ -138,26 +142,28 @@ export interface IDaytimeCounter
export interface VisibilityCondition export interface VisibilityCondition
{ {
id: string; id: string;
value: number; target: string
dynamicLocale: boolean; value?: number;
dynamicLocale?: boolean;
oneSessionOnly: boolean; oneSessionOnly: boolean;
conditionType: string;
} }
export interface IRewards export interface IQuestRewards
{ {
AvailableForStart: Reward[]; AvailableForStart?: IQuestReward[];
AvailableForFinish: Reward[]; AvailableForFinish?: IQuestReward[];
Started: Reward[]; Started?: IQuestReward[];
Success: Reward[]; Success?: IQuestReward[];
Fail: Reward[]; Fail?: IQuestReward[];
FailRestartable: Reward[]; FailRestartable?: IQuestReward[];
Expired: Reward[]; Expired?: IQuestReward[];
} }
export interface Reward extends Item export interface IQuestReward
{ {
value?: string | number; value?: string | number;
id: string; id?: string;
type: QuestRewardType; type: QuestRewardType;
index: number; index: number;
target?: string; target?: string;

View File

@ -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; changeCost: IChangeCost[]
type: string; changeStandingCost: number;
value: number; sptRepatableGroupName: string
target?: string;
items?: Item[];
} }
export interface IRepeatableQuestDatabase export interface IRepeatableQuestDatabase
{ {
templates: ITemplates; templates: IRepeatableTemplates;
rewards: IRewardOptions; rewards: IRewardOptions;
data: IOptions; data: IOptions;
samples: ISampleQuests[]; samples: ISampleQuests[];
} }
export interface ITemplates export interface IRepeatableTemplates
{ {
Elimination: IRepeatableQuest; Elimination: IQuest;
Completion: IRepeatableQuest; Completion: IQuest;
Exploration: IRepeatableQuest; Exploration: IQuest;
} }
export interface IPmcDataRepeatableQuest export interface IPmcDataRepeatableQuest
@ -31,11 +29,9 @@ export interface IPmcDataRepeatableQuest
activeQuests: IRepeatableQuest[]; activeQuests: IRepeatableQuest[];
inactiveQuests: IRepeatableQuest[]; inactiveQuests: IRepeatableQuest[];
endTime: number; endTime: number;
changeRequirement: TChangeRequirementRecord; // what it costs to reset <QuestId, ChangeRequirement> redundant to change requirements within the IRepeatableQuest changeRequirement: Record<string, IChangeRequirement>; // What it costs to reset <QuestId, ChangeRequirement> redundant to change requirements within IRepeatableQuest
} }
export type TChangeRequirementRecord = Record<string, IChangeRequirement>;
export interface IChangeRequirement export interface IChangeRequirement
{ {
changeCost: IChangeCost[]; changeCost: IChangeCost[];
@ -48,264 +44,6 @@ export interface IChangeCost
count: number; // amount of item needed to reset 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 // Config Options
export interface IRewardOptions export interface IRewardOptions
@ -348,8 +86,8 @@ export interface ISampleQuests
instantComplete: boolean; instantComplete: boolean;
secretQuest: boolean; secretQuest: boolean;
canShowNotificationsInGame: boolean; canShowNotificationsInGame: boolean;
rewards: IRewards; rewards: IQuestRewards;
conditions: IConditions; conditions: IQuestConditionTypes;
name: string; name: string;
note: string; note: string;
description: string; description: string;