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:
parent
a15a28e460
commit
2abf216a07
@ -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": {
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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 */
|
||||
|
@ -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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user