Skip to content

Commit

Permalink
Using live data, improved emulation accuracy of repeatable quest system
Browse files Browse the repository at this point in the history
  • Loading branch information
Chomp committed Dec 9, 2024
1 parent a15a28e commit 2abf216
Show file tree
Hide file tree
Showing 7 changed files with 199 additions and 90 deletions.
77 changes: 66 additions & 11 deletions project/assets/database/templates/repeatableQuests.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,10 @@
"value": 1,
"type": "Elimination",
"oneSessionOnly": false,
"completeInSeconds": 0,
"doNotResetIfCounterCompleted": false,
"isResetOnConditionFailed": false,
"isNecessary": false,
"counter": {
"id": "618c1de4d4cd91439f3de4ac",
"conditions": [{
Expand Down Expand Up @@ -59,7 +62,7 @@
}
]
},
"conditionType": "CounterCreator"
"conditionType": "CounterCreator"
}],
"Fail": []
},
Expand All @@ -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",
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand All @@ -217,7 +259,7 @@
"dynamicLocale": false,
"index": 0,
"visibilityConditions": [],
"globalQuestCounterId": null,
"globalQuestCounterId": "",
"target": ["5b47574386f77428ca22b336"],
"value": 7,
"minDurability": 0,
Expand All @@ -233,7 +275,7 @@
"dynamicLocale": true,
"index": 0,
"visibilityConditions": [],
"globalQuestCounterId": null,
"globalQuestCounterId": "",
"value": 1,
"type": "PickUp",
"completeInSeconds": 0,
Expand Down Expand Up @@ -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": {
Expand Down
42 changes: 2 additions & 40 deletions project/src/controllers/QuestController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
42 changes: 28 additions & 14 deletions project/src/controllers/RepeatableQuestController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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);
Expand All @@ -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
Expand All @@ -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}`;
Expand All @@ -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`,
Expand All @@ -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];
Expand All @@ -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 = [];
Expand Down Expand Up @@ -622,6 +634,7 @@ export class RepeatableQuestController {
}

protected attemptToGenerateRepeatableQuest(
sessionId: string,
pmcData: IPmcData,
questTypePool: IQuestTypePool,
repeatableConfig: IRepeatableQuestConfig,
Expand All @@ -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,
Expand Down
Loading

0 comments on commit 2abf216

Please sign in to comment.