0
0
mirror of https://github.com/sp-tarkov/server.git synced 2025-02-12 16:10:43 -05:00

Using live data, improved emulation accuracy of repeatable quest system

This commit is contained in:
Chomp 2024-12-09 23:23:31 +00:00
parent a15a28e460
commit 2abf216a07
7 changed files with 199 additions and 90 deletions

View File

@ -28,7 +28,10 @@
"value": 1,
"type": "Elimination",
"oneSessionOnly": false,
"completeInSeconds": 0,
"doNotResetIfCounterCompleted": false,
"isResetOnConditionFailed": false,
"isNecessary": false,
"counter": {
"id": "618c1de4d4cd91439f3de4ac",
"conditions": [{
@ -59,7 +62,7 @@
}
]
},
"conditionType": "CounterCreator"
"conditionType": "CounterCreator"
}],
"Fail": []
},
@ -74,13 +77,26 @@
"acceptPlayerMessage": "{templateId} acceptPlayerMessage {traderId}",
"declinePlayerMessage": "{templateId} declinePlayerMessage {traderId}",
"completePlayerMessage": "{templateId} completePlayerMessage {traderId}",
"templateId": "{templateId}",
"status": 0,
"acceptanceAndFinishingSource": "eft",
"progressSource": "eft",
"rankingModes": [],
"gameModes": [],
"arenaLocations": [],
"changeCost": [{
"templateId": "5449016a4bdc2d6f028b456f",
"count": 5000
}
],
"changeStandingCost": 0
"changeStandingCost": 0,
"questStatus": {
"id": "mongoId",
"uid": "playerId",
"qid": "questId",
"startTime": 0,
"status": 1,
"statusTimers": {}
}
},
"Completion": {
"_id": "61943a75eb60e11b7965cdbf4",
@ -114,13 +130,26 @@
"acceptPlayerMessage": "{templateId} acceptPlayerMessage {traderId}",
"declinePlayerMessage": "{templateId} declinePlayerMessage {traderId}",
"completePlayerMessage": "{templateId} completePlayerMessage {traderId}",
"templateId": "{templateId}",
"status": 0,
"acceptanceAndFinishingSource": "eft",
"progressSource": "eft",
"rankingModes": [],
"gameModes": [],
"arenaLocations": [],
"changeCost": [{
"templateId": "5449016a4bdc2d6f028b456f",
"count": 5000
}
],
"changeStandingCost": 0
"changeStandingCost": 0,
"questStatus": {
"id": "mongoId",
"uid": "playerId",
"qid": "questId",
"startTime": 0,
"status": 1,
"statusTimers": {}
}
},
"Exploration": {
"_id": "65947c6afb90e7fcb40f8d684",
@ -185,13 +214,26 @@
"acceptPlayerMessage": "{templateId} acceptPlayerMessage {traderId}",
"declinePlayerMessage": "{templateId} declinePlayerMessage {traderId}",
"completePlayerMessage": "{templateId} completePlayerMessage {traderId}",
"templateId": "{templateId}",
"status": 0,
"acceptanceAndFinishingSource": "eft",
"progressSource": "eft",
"rankingModes": [],
"gameModes": [],
"arenaLocations": [],
"changeCost": [{
"templateId": "5449016a4bdc2d6f028b456f",
"count": 5000
}
],
"changeStandingCost": 0
"changeStandingCost": 0,
"questStatus": {
"id": "mongoId",
"uid": "playerId",
"qid": "questId",
"startTime": 0,
"status": 1,
"statusTimers": {}
}
},
"Pickup": {
"_id": "64cfb3818db9f48b3f0b0a759",
@ -217,7 +259,7 @@
"dynamicLocale": false,
"index": 0,
"visibilityConditions": [],
"globalQuestCounterId": null,
"globalQuestCounterId": "",
"target": ["5b47574386f77428ca22b336"],
"value": 7,
"minDurability": 0,
@ -233,7 +275,7 @@
"dynamicLocale": true,
"index": 0,
"visibilityConditions": [],
"globalQuestCounterId": null,
"globalQuestCounterId": "",
"value": 1,
"type": "PickUp",
"completeInSeconds": 0,
@ -276,13 +318,26 @@
"acceptPlayerMessage": "{templateId} acceptPlayerMessage {traderId}",
"declinePlayerMessage": "{templateId} declinePlayerMessage {traderId}",
"completePlayerMessage": "{templateId} completePlayerMessage {traderId}",
"templateId": "{templateId}",
"status": 0,
"acceptanceAndFinishingSource": "eft",
"progressSource": "eft",
"rankingModes": [],
"gameModes": [],
"arenaLocations": [],
"changeCost": [{
"templateId": "5449016a4bdc2d6f028b456f",
"count": 12000
}
],
"changeStandingCost": 0
"changeStandingCost": 0,
"questStatus": {
"id": "mongoId",
"uid": "playerId",
"qid": "questId",
"startTime": 0,
"status": 1,
"statusTimers": {}
}
}
},
"rewards": {

View File

@ -174,6 +174,7 @@ export class QuestController {
}
/**
* TODO - Move this code into RepeatableQuestController
* Handle the client accepting a repeatable quest and starting it
* Send starting rewards if any to player and
* Send start notification if any to player
@ -221,50 +222,11 @@ export class QuestController {
fullProfile.characters.scav.Quests.push(newRepeatableQuest);
}
const response = this.createAcceptedQuestClientResponse(sessionID, pmcData, repeatableQuestProfile);
const response = this.eventOutputHolder.getOutput(sessionID);
return response;
}
protected createAcceptedQuestClientResponse(
sessionID: string,
pmcData: IPmcData,
repeatableQuestProfile: IRepeatableQuest,
): IItemEventRouterResponse {
const repeatableSettings = pmcData.RepeatableQuests.find(
(quest) => quest.name === repeatableQuestProfile.sptRepatableGroupName,
);
const change = {};
change[repeatableQuestProfile._id] = repeatableSettings.changeRequirement[repeatableQuestProfile._id];
const repeatableData: IPmcDataRepeatableQuest = {
id:
repeatableSettings.id ??
this.questConfig.repeatableQuests.find(
(repeatableQuest) => repeatableQuest.name === repeatableQuestProfile.sptRepatableGroupName,
).id,
name: repeatableSettings.name,
endTime: repeatableSettings.endTime,
changeRequirement: change,
activeQuests: [repeatableQuestProfile],
inactiveQuests: [],
freeChanges: repeatableSettings.freeChanges,
freeChangesAvailable: repeatableSettings.freeChangesAvailable,
};
// Nullguard
const acceptQuestResponse = this.eventOutputHolder.getOutput(sessionID);
if (!acceptQuestResponse.profileChanges[sessionID].repeatableQuests) {
acceptQuestResponse.profileChanges[sessionID].repeatableQuests = [];
}
// Add constructed objet into response
acceptQuestResponse.profileChanges[sessionID].repeatableQuests.push(repeatableData);
return acceptQuestResponse;
}
/**
* Look for an accepted quest inside player profile, return matching
* @param pmcData Profile to search through

View File

@ -129,6 +129,7 @@ export class RepeatableQuestController {
let lifeline = 0;
while (!quest && questTypePool.types.length > 0) {
quest = this.repeatableQuestGenerator.generateRepeatableQuest(
sessionID,
pmcData.Info.Level,
pmcData.TradersInfo,
questTypePool,
@ -487,11 +488,11 @@ export class RepeatableQuestController {
const fullProfile = this.profileHelper.getFullProfile(sessionID);
// Check for existing quest in (daily/weekly/scav arrays)
const { quest: questToReplace, repeatableType: repeatablesInProfile } = this.getRepeatableById(
const { quest: questToReplace, repeatableType: repeatablesOfTypeInProfile } = this.getRepeatableById(
changeRequest.qid,
pmcData,
);
if (!repeatablesInProfile || !questToReplace) {
if (!repeatablesOfTypeInProfile || !questToReplace) {
// Unable to find quest being replaced
const message = this.localisationService.getText("quest-unable_to_find_repeatable_to_replace");
this.logger.error(message);
@ -500,25 +501,27 @@ export class RepeatableQuestController {
}
// Subtype name of quest - daily/weekly/scav
const repeatableTypeLower = repeatablesInProfile.name.toLowerCase();
const repeatableTypeLower = repeatablesOfTypeInProfile.name.toLowerCase();
// Save for later standing loss calculation
const replacedQuestTraderId = questToReplace.traderId;
// Update active quests to exclude the quest we're replacing
repeatablesInProfile.activeQuests = repeatablesInProfile.activeQuests.filter(
repeatablesOfTypeInProfile.activeQuests = repeatablesOfTypeInProfile.activeQuests.filter(
(quest) => quest._id !== changeRequest.qid,
);
// Save for later cost calculation
const previousChangeRequirement = this.cloner.clone(repeatablesInProfile.changeRequirement[changeRequest.qid]);
// Save for later cost calculations
const previousChangeRequirement = this.cloner.clone(
repeatablesOfTypeInProfile.changeRequirement[changeRequest.qid],
);
// Delete the replaced quest change requrement as we're going to replace it
delete repeatablesInProfile.changeRequirement[changeRequest.qid];
// Delete the replaced quest change requirement data as we're going to add new data below
delete repeatablesOfTypeInProfile.changeRequirement[changeRequest.qid];
// Get config for this repeatable sub-type (daily/weekly/scav)
const repeatableConfig = this.questConfig.repeatableQuests.find(
(config) => config.name === repeatablesInProfile.name,
(config) => config.name === repeatablesOfTypeInProfile.name,
);
// If the configuration dictates to replace with the same quest type, adjust the available quest types
@ -535,7 +538,12 @@ export class RepeatableQuestController {
// Generate meta-data for what type/levelrange of quests can be generated for player
const allowedQuestTypes = this.generateQuestPool(repeatableConfig, pmcData.Info.Level);
const newRepeatableQuest = this.attemptToGenerateRepeatableQuest(pmcData, allowedQuestTypes, repeatableConfig);
const newRepeatableQuest = this.attemptToGenerateRepeatableQuest(
sessionID,
pmcData,
allowedQuestTypes,
repeatableConfig,
);
if (!newRepeatableQuest) {
// Unable to find quest being replaced
const message = `Unable to generate repeatable quest of type: ${repeatableTypeLower} to replace trader: ${replacedQuestTraderId} quest ${changeRequest.qid}`;
@ -546,7 +554,7 @@ export class RepeatableQuestController {
// Add newly generated quest to daily/weekly/scav type array
newRepeatableQuest.side = repeatableConfig.side;
repeatablesInProfile.activeQuests.push(newRepeatableQuest);
repeatablesOfTypeInProfile.activeQuests.push(newRepeatableQuest);
this.logger.debug(
`Removing: ${repeatableConfig.name} quest: ${questToReplace._id} from trader: ${questToReplace.traderId} as its been replaced`,
@ -562,13 +570,17 @@ export class RepeatableQuestController {
);
// Add new quests replacement cost to profile
repeatablesInProfile.changeRequirement[newRepeatableQuest._id] = {
repeatablesOfTypeInProfile.changeRequirement[newRepeatableQuest._id] = {
changeCost: newRepeatableQuest.changeCost,
changeStandingCost: this.randomUtil.getArrayValue([0, 0.01]),
};
// Check if we should charge player for replacing quest
const isFreeToReplace = this.useFreeRefreshIfAvailable(fullProfile, repeatablesInProfile, repeatableTypeLower);
const isFreeToReplace = this.useFreeRefreshIfAvailable(
fullProfile,
repeatablesOfTypeInProfile,
repeatableTypeLower,
);
if (!isFreeToReplace) {
// Reduce standing with trader for not doing their quest
const traderOfReplacedQuest = pmcData.TradersInfo[replacedQuestTraderId];
@ -586,7 +598,7 @@ export class RepeatableQuestController {
}
// Clone data before we send it to client
const repeatableToChangeClone = this.cloner.clone(repeatablesInProfile);
const repeatableToChangeClone = this.cloner.clone(repeatablesOfTypeInProfile);
// Purge inactive repeatables
repeatableToChangeClone.inactiveQuests = [];
@ -622,6 +634,7 @@ export class RepeatableQuestController {
}
protected attemptToGenerateRepeatableQuest(
sessionId: string,
pmcData: IPmcData,
questTypePool: IQuestTypePool,
repeatableConfig: IRepeatableQuestConfig,
@ -631,6 +644,7 @@ export class RepeatableQuestController {
let attempts = 0;
while (attempts < maxAttempts && questTypePool.types.length > 0) {
newRepeatableQuest = this.repeatableQuestGenerator.generateRepeatableQuest(
sessionId,
pmcData.Info.Level,
pmcData.TradersInfo,
questTypePool,

View File

@ -50,6 +50,7 @@ export class RepeatableQuestGenerator {
/**
* This method is called by /GetClientRepeatableQuests/ and creates one element of quest type format (see assets/database/templates/repeatableQuests.json).
* It randomly draws a quest type (currently Elimination, Completion or Exploration) as well as a trader who is providing the quest
* @param sessionId Session id
* @param pmcLevel Player's level for requested items and reward generation
* @param pmcTraderInfo Players traper standing/rep levels
* @param questTypePool Possible quest types pool
@ -57,6 +58,7 @@ export class RepeatableQuestGenerator {
* @returns IRepeatableQuest
*/
public generateRepeatableQuest(
sessionId: string,
pmcLevel: number,
pmcTraderInfo: Record<string, ITraderInfo>,
questTypePool: IQuestTypePool,
@ -64,7 +66,7 @@ export class RepeatableQuestGenerator {
): IRepeatableQuest {
const questType = this.randomUtil.drawRandomFromList<string>(questTypePool.types)[0];
// get traders from whitelist and filter by quest type availability
// Get traders from whitelist and filter by quest type availability
let traders = repeatableConfig.traderWhitelist
.filter((x) => x.questTypes.includes(questType))
.map((x) => x.traderId);
@ -74,13 +76,13 @@ export class RepeatableQuestGenerator {
switch (questType) {
case "Elimination":
return this.generateEliminationQuest(pmcLevel, traderId, questTypePool, repeatableConfig);
return this.generateEliminationQuest(sessionId, pmcLevel, traderId, questTypePool, repeatableConfig);
case "Completion":
return this.generateCompletionQuest(pmcLevel, traderId, repeatableConfig);
return this.generateCompletionQuest(sessionId, pmcLevel, traderId, repeatableConfig);
case "Exploration":
return this.generateExplorationQuest(pmcLevel, traderId, questTypePool, repeatableConfig);
return this.generateExplorationQuest(sessionId, pmcLevel, traderId, questTypePool, repeatableConfig);
case "Pickup":
return this.generatePickupQuest(pmcLevel, traderId, questTypePool, repeatableConfig);
return this.generatePickupQuest(sessionId, pmcLevel, traderId, questTypePool, repeatableConfig);
default:
throw new Error(`Unknown mission type ${questType}. Should never be here!`);
}
@ -95,6 +97,7 @@ export class RepeatableQuestGenerator {
* @returns Object of quest type format for "Elimination" (see assets/database/templates/repeatableQuests.json)
*/
protected generateEliminationQuest(
sessionid: string,
pmcLevel: number,
traderId: string,
questTypePool: IQuestTypePool,
@ -297,7 +300,7 @@ export class RepeatableQuestGenerator {
// crazy maximum difficulty will lead to a higher difficulty reward gain factor than 1
const difficulty = this.mathUtil.mapToRange(curDifficulty, minDifficulty, maxDifficulty, 0.5, 2);
const quest = this.generateRepeatableTemplate("Elimination", traderId, repeatableConfig.side);
const quest = this.generateRepeatableTemplate("Elimination", traderId, repeatableConfig.side, sessionid);
// ASSUMPTION: All fence quests are for scavs
if (traderId === Traders.FENCE) {
@ -396,10 +399,13 @@ export class RepeatableQuestGenerator {
allowedWeaponCategory: string,
): IQuestConditionCounterCondition {
const killConditionProps: IQuestConditionCounterCondition = {
target: target,
value: 1,
id: this.objectId.generate(),
dynamicLocale: true,
target: target, // e,g, "AnyPmc"
value: 1,
resetOnSessionEnd: false,
enemyHealthEffects: [],
daytime: { from: 0, to: 0 },
conditionType: "Kills",
};
@ -441,6 +447,7 @@ export class RepeatableQuestGenerator {
* @returns {object} object of quest type format for "Completion" (see assets/database/templates/repeatableQuests.json)
*/
protected generateCompletionQuest(
sessionId: string,
pmcLevel: number,
traderId: string,
repeatableConfig: IRepeatableQuestConfig,
@ -449,7 +456,7 @@ export class RepeatableQuestGenerator {
const levelsConfig = repeatableConfig.rewardScaling.levels;
const roublesConfig = repeatableConfig.rewardScaling.roubles;
const quest = this.generateRepeatableTemplate("Completion", traderId, repeatableConfig.side);
const quest = this.generateRepeatableTemplate("Completion", traderId, repeatableConfig.side, sessionId);
// Filter the items.json items to items the player must retrieve to complete quest: shouldn't be a quest item or "non-existant"
const possibleItemsToRetrievePool = this.repeatableQuestRewardGenerator.getRewardableItems(
@ -626,16 +633,18 @@ export class RepeatableQuestGenerator {
return {
id: this.objectId.generate(),
index: 0,
parentId: "",
dynamicLocale: true,
index: 0,
visibilityConditions: [],
globalQuestCounterId: "",
target: [itemTpl],
value: value,
minDurability: minDurability,
maxDurability: 100,
dogtagLevel: 0,
onlyFoundInRaid: onlyFoundInRaid,
isEncoded: false,
conditionType: "HandoverItem",
};
}
@ -650,6 +659,7 @@ export class RepeatableQuestGenerator {
* @returns {object} object of quest type format for "Exploration" (see assets/database/templates/repeatableQuests.json)
*/
protected generateExplorationQuest(
sessionId: string,
pmcLevel: number,
traderId: string,
questTypePool: IQuestTypePool,
@ -679,19 +689,19 @@ export class RepeatableQuestGenerator {
: explorationConfig.maxExtracts + 1;
const numExtracts = this.randomUtil.randInt(1, exitTimesMax);
const quest = this.generateRepeatableTemplate("Exploration", traderId, repeatableConfig.side);
const quest = this.generateRepeatableTemplate("Exploration", traderId, repeatableConfig.side, sessionId);
const exitStatusCondition: IQuestConditionCounterCondition = {
conditionType: "ExitStatus",
id: this.objectId.generate(),
dynamicLocale: true,
status: ["Survived"],
conditionType: "ExitStatus",
};
const locationCondition: IQuestConditionCounterCondition = {
conditionType: "Location",
id: this.objectId.generate(),
dynamicLocale: true,
target: locationTarget,
conditionType: "Location",
};
quest.conditions.AvailableForFinish[0].counter.id = this.objectId.generate();
@ -757,6 +767,7 @@ export class RepeatableQuestGenerator {
}
protected generatePickupQuest(
sessionId: string,
pmcLevel: number,
traderId: string,
questTypePool: IQuestTypePool,
@ -764,7 +775,7 @@ export class RepeatableQuestGenerator {
): IRepeatableQuest {
const pickupConfig = repeatableConfig.questConfig.Pickup;
const quest = this.generateRepeatableTemplate("Pickup", traderId, repeatableConfig.side);
const quest = this.generateRepeatableTemplate("Pickup", traderId, repeatableConfig.side, sessionId);
const itemTypeToFetchWithCount = this.randomUtil.getArrayValue(pickupConfig.ItemTypeToFetchWithMaxCount);
const itemCountToFetch = this.randomUtil.randInt(
@ -819,7 +830,12 @@ export class RepeatableQuestGenerator {
* @returns {object} Exit condition
*/
protected generateExplorationExitCondition(exit: IExit): IQuestConditionCounterCondition {
return { conditionType: "ExitName", exitName: exit.Name, id: this.objectId.generate(), dynamicLocale: true };
return {
id: this.objectId.generate(),
dynamicLocale: true,
exitName: exit.Name,
conditionType: "ExitName",
};
}
/**
@ -833,7 +849,12 @@ export class RepeatableQuestGenerator {
* (needs to be filled with reward and conditions by called to make a valid quest)
*/
// @Incomplete: define Type for "type".
protected generateRepeatableTemplate(type: string, traderId: string, side: string): IRepeatableQuest {
protected generateRepeatableTemplate(
type: string,
traderId: string,
side: string,
sessionId: string,
): IRepeatableQuest {
const questClone = this.cloner.clone<IRepeatableQuest>(
this.databaseService.getTemplates().repeatableQuests.templates[type],
);
@ -882,6 +903,10 @@ export class RepeatableQuestGenerator {
.replace("{traderId}", desiredTraderId)
.replace("{templateId}", questClone.templateId);
questClone.questStatus.id = this.objectId.generate();
questClone.questStatus.uid = sessionId; // Needs to match user id
questClone.questStatus.qid = questClone._id; // Needs to match quest id
return questClone;
}
}

View File

@ -23,6 +23,7 @@ import { DatabaseService } from "@spt/services/DatabaseService";
import { ItemFilterService } from "@spt/services/ItemFilterService";
import { LocalisationService } from "@spt/services/LocalisationService";
import { SeasonalEventService } from "@spt/services/SeasonalEventService";
import { HashUtil } from "@spt/utils/HashUtil";
import { MathUtil } from "@spt/utils/MathUtil";
import { ObjectId } from "@spt/utils/ObjectId";
import { RandomUtil } from "@spt/utils/RandomUtil";
@ -36,6 +37,7 @@ export class RepeatableQuestRewardGenerator {
constructor(
@inject("PrimaryLogger") protected logger: ILogger,
@inject("RandomUtil") protected randomUtil: RandomUtil,
@inject("HashUtil") protected hashUtil: HashUtil,
@inject("MathUtil") protected mathUtil: MathUtil,
@inject("DatabaseService") protected databaseService: DatabaseService,
@inject("ItemHelper") protected itemHelper: ItemHelper,
@ -93,14 +95,18 @@ export class RepeatableQuestRewardGenerator {
const rewards: IQuestRewards = { Started: [], Success: [], Fail: [] };
// Start reward index to keep track
let rewardIndex = 0;
let rewardIndex = -1;
// Add xp reward
if (rewardParams.rewardXP > 0) {
rewards.Success.push({
id: this.hashUtil.generate(),
unknown: false,
gameMode: [],
availableInGameEditions: [],
index: rewardIndex,
value: rewardParams.rewardXP,
type: QuestRewardType.EXPERIENCE,
index: rewardIndex,
});
rewardIndex++;
}
@ -163,6 +169,10 @@ export class RepeatableQuestRewardGenerator {
// Add rep reward to rewards array
if (rewardParams.rewardReputation > 0) {
const reward: IQuestReward = {
id: this.hashUtil.generate(),
unknown: false,
gameMode: [],
availableInGameEditions: [],
target: traderId,
value: rewardParams.rewardReputation,
type: QuestRewardType.TRADER_STANDING,
@ -171,13 +181,17 @@ export class RepeatableQuestRewardGenerator {
rewards.Success.push(reward);
rewardIndex++;
this.logger.debug(` Adding ${rewardParams.rewardReputation} trader reputation reward`);
this.logger.debug(`Adding: ${rewardParams.rewardReputation} ${traderId} trader reputation reward`);
}
// Chance of adding skill reward
if (this.randomUtil.getChance100(rewardParams.skillRewardChance * 100)) {
const targetSkill = this.randomUtil.getArrayValue(questConfig.possibleSkillRewards);
const reward: IQuestReward = {
id: this.hashUtil.generate(),
unknown: false,
gameMode: [],
availableInGameEditions: [],
target: targetSkill,
value: rewardParams.skillPointReward,
type: QuestRewardType.SKILL,
@ -503,17 +517,23 @@ export class RepeatableQuestRewardGenerator {
* @param preset Optional array of preset items
* @returns {object} Object of "Reward"-item-type
*/
protected generateItemReward(tpl: string, count: number, index: number): IQuestReward {
protected generateItemReward(tpl: string, count: number, index: number, foundInRaid = true): IQuestReward {
const id = this.objectId.generate();
const questRewardItem: IQuestReward = {
id: this.hashUtil.generate(),
unknown: false,
gameMode: [],
availableInGameEditions: [],
index: index,
target: id,
value: count,
isEncoded: false,
findInRaid: foundInRaid,
type: QuestRewardType.ITEM,
index: index,
items: [],
};
const rootItem = { _id: id, _tpl: tpl, upd: { StackObjectsCount: count, SpawnedInSession: true } };
const rootItem = { _id: id, _tpl: tpl, upd: { StackObjectsCount: count, SpawnedInSession: foundInRaid } };
questRewardItem.items = [rootItem];
return questRewardItem;
@ -528,13 +548,25 @@ export class RepeatableQuestRewardGenerator {
* @param preset Optional array of preset items
* @returns {object} Object of "Reward"-item-type
*/
protected generatePresetReward(tpl: string, count: number, index: number, preset?: IItem[]): IQuestReward {
protected generatePresetReward(
tpl: string,
count: number,
index: number,
preset?: IItem[],
foundInRaid = true,
): IQuestReward {
const id = this.objectId.generate();
const questRewardItem: IQuestReward = {
id: this.hashUtil.generate(),
unknown: false,
gameMode: [],
availableInGameEditions: [],
index: index,
target: id,
value: count,
isEncoded: false,
findInRaid: foundInRaid,
type: QuestRewardType.ITEM,
index: index,
items: [],
};
@ -544,6 +576,10 @@ export class RepeatableQuestRewardGenerator {
this.logger.warning(`Root item of preset: ${tpl} not found`);
}
if (rootItem.upd) {
rootItem.upd.SpawnedInSession = foundInRaid;
}
questRewardItem.items = this.itemHelper.reparentItemAndChildren(rootItem, preset);
questRewardItem.target = rootItem._id; // Target property and root items id must match
@ -641,6 +677,6 @@ export class RepeatableQuestRewardGenerator {
currency === Money.EUROS ? this.handbookHelper.fromRUB(rewardRoubles, Money.EUROS) : rewardRoubles;
// Get chosen currency + amount and return
return this.generateItemReward(currency, rewardAmountToGivePlayer, rewardIndex);
return this.generateItemReward(currency, rewardAmountToGivePlayer, rewardIndex, false);
}
}

View File

@ -160,8 +160,10 @@ export interface IQuestReward {
loyaltyLevel?: number;
/** Hideout area id */
traderId?: string;
isEncoded?: boolean;
unknown?: boolean;
findInRaid?: boolean;
gameMode?: string[];
/** Game editions whitelisted to get reward */
availableInGameEditions?: string[];
/** Game editions blacklisted from getting reward */

View File

@ -4,6 +4,12 @@ export interface IRepeatableQuest extends IQuest {
changeCost: IChangeCost[];
changeStandingCost: number;
sptRepatableGroupName: string;
acceptanceAndFinishingSource: string;
progressSource: string;
rankingModes: string[];
gameModes: string[];
arenaLocations: string[];
questStatus: IRepeatableQuestStatus;
}
export interface IRepeatableQuestDatabase {
@ -13,6 +19,15 @@ export interface IRepeatableQuestDatabase {
samples: ISampleQuests[];
}
export interface IRepeatableQuestStatus {
id: string;
uid: string;
qid: string;
startTime: number;
status: number;
statusTimers: any;
}
export interface IRepeatableTemplates {
Elimination: IQuest;
Completion: IQuest;