mirror of
https://github.com/sp-tarkov/server.git
synced 2025-02-13 09:50:43 -05:00
Stored free daily quest resets in profile per-daily-type
Wired up `getClientRepeatableQuests()` to retrieve this value + reduced indentation of function Updated `changeRepeatableQuest()` to decrement free daily value from profile
This commit is contained in:
parent
671c57cefb
commit
7842167595
@ -88,99 +88,116 @@ export class RepeatableQuestController
|
|||||||
public getClientRepeatableQuests(_info: IEmptyRequestData, sessionID: string): IPmcDataRepeatableQuest[]
|
public getClientRepeatableQuests(_info: IEmptyRequestData, sessionID: string): IPmcDataRepeatableQuest[]
|
||||||
{
|
{
|
||||||
const returnData: Array<IPmcDataRepeatableQuest> = [];
|
const returnData: Array<IPmcDataRepeatableQuest> = [];
|
||||||
const pmcData = this.profileHelper.getPmcProfile(sessionID);
|
const fullProfile = this.profileHelper.getFullProfile(sessionID)!;
|
||||||
|
const pmcData = fullProfile.characters.pmc;
|
||||||
const currentTime = this.timeUtil.getTimestamp();
|
const currentTime = this.timeUtil.getTimestamp();
|
||||||
|
|
||||||
// Daily / weekly / Daily_Savage
|
// Daily / weekly / Daily_Savage
|
||||||
for (const repeatableConfig of this.questConfig.repeatableQuests)
|
for (const repeatableConfig of this.questConfig.repeatableQuests)
|
||||||
{
|
{
|
||||||
// get daily/weekly data from profile, add empty object if missing
|
// Get daily/weekly data from profile, add empty object if missing
|
||||||
const currentRepeatableQuestType = this.getRepeatableQuestSubTypeFromProfile(repeatableConfig, pmcData);
|
const currentRepeatableQuestType = this.getRepeatableQuestSubTypeFromProfile(repeatableConfig, pmcData);
|
||||||
|
const repeatableType = repeatableConfig.name.toLowerCase();
|
||||||
|
|
||||||
// Has repeatable unlocked
|
// Is PMC and can't see dailies yet, skip
|
||||||
if ((repeatableConfig.side === "Pmc" && this.playerHasDailyPmcQuestsUnlocked(pmcData, repeatableConfig))
|
if ((repeatableConfig.side === "Pmc" && !this.playerHasDailyPmcQuestsUnlocked(pmcData, repeatableConfig)))
|
||||||
|| (repeatableConfig.side === "Scav" && this.playerHasDailyScavQuestsUnlocked(pmcData))
|
|
||||||
)
|
|
||||||
{
|
{
|
||||||
// Current time is past expiry time
|
continue;
|
||||||
if (currentTime > currentRepeatableQuestType.endTime - 1)
|
|
||||||
{
|
|
||||||
// Adjust endtime to be now + new duration
|
|
||||||
currentRepeatableQuestType.endTime = currentTime + repeatableConfig.resetTime;
|
|
||||||
currentRepeatableQuestType.inactiveQuests = [];
|
|
||||||
this.logger.debug(`Generating new ${repeatableConfig.name}`);
|
|
||||||
|
|
||||||
// put old quests to inactive (this is required since only then the client makes them fail due to non-completion)
|
|
||||||
// we also need to push them to the "inactiveQuests" list since we need to remove them from offraidData.profile.Quests
|
|
||||||
// after a raid (the client seems to keep quests internally and we want to get rid of old repeatable quests)
|
|
||||||
// and remove them from the PMC's Quests and RepeatableQuests[i].activeQuests
|
|
||||||
const questsToKeep = [];
|
|
||||||
// for (let i = 0; i < currentRepeatable.activeQuests.length; i++)
|
|
||||||
for (const activeQuest of currentRepeatableQuestType.activeQuests)
|
|
||||||
{
|
|
||||||
// Keep finished quests in list so player can hand in
|
|
||||||
const quest = pmcData.Quests.find((quest) => quest.qid === activeQuest._id);
|
|
||||||
if (quest)
|
|
||||||
{
|
|
||||||
if (quest.status === QuestStatus.AvailableForFinish)
|
|
||||||
{
|
|
||||||
questsToKeep.push(activeQuest);
|
|
||||||
this.logger.debug(
|
|
||||||
`Keeping repeatable quest ${activeQuest._id} in activeQuests since it is available to hand in`,
|
|
||||||
);
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.profileFixerService.removeDanglingConditionCounters(pmcData);
|
|
||||||
|
|
||||||
// Remove expired quest from pmc.quest array
|
|
||||||
pmcData.Quests = pmcData.Quests.filter((quest) => quest.qid !== activeQuest._id);
|
|
||||||
currentRepeatableQuestType.inactiveQuests.push(activeQuest);
|
|
||||||
}
|
|
||||||
currentRepeatableQuestType.activeQuests = questsToKeep;
|
|
||||||
|
|
||||||
// introduce a dynamic quest pool to avoid duplicates
|
|
||||||
const questTypePool = this.generateQuestPool(repeatableConfig, pmcData.Info.Level);
|
|
||||||
|
|
||||||
// Add daily quests
|
|
||||||
for (let i = 0; i < this.getQuestCount(repeatableConfig, pmcData); i++)
|
|
||||||
{
|
|
||||||
let quest: IRepeatableQuest | undefined = undefined;
|
|
||||||
let lifeline = 0;
|
|
||||||
while (!quest && questTypePool.types.length > 0)
|
|
||||||
{
|
|
||||||
quest = this.repeatableQuestGenerator.generateRepeatableQuest(
|
|
||||||
pmcData.Info.Level,
|
|
||||||
pmcData.TradersInfo,
|
|
||||||
questTypePool,
|
|
||||||
repeatableConfig,
|
|
||||||
);
|
|
||||||
lifeline++;
|
|
||||||
if (lifeline > 10)
|
|
||||||
{
|
|
||||||
this.logger.debug(
|
|
||||||
"We were stuck in repeatable quest generation. This should never happen. Please report",
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// check if there are no more quest types available
|
|
||||||
if (questTypePool.types.length === 0)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
quest.side = repeatableConfig.side;
|
|
||||||
currentRepeatableQuestType.activeQuests.push(quest);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
this.logger.debug(`[Quest Check] ${repeatableConfig.name} quests are still valid.`);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Is Scav and can't see dailies yet, skip
|
||||||
|
if ((repeatableConfig.side === "Scav" && !this.playerHasDailyScavQuestsUnlocked(pmcData)))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Daily sub-type still has time left being active, skip
|
||||||
|
if (currentTime < currentRepeatableQuestType.endTime - 1)
|
||||||
|
{
|
||||||
|
this.logger.debug(`[Quest Check] ${repeatableType} quests are still valid.`);
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Current time is past expiry time
|
||||||
|
|
||||||
|
// Adjust endtime to be now + new duration
|
||||||
|
currentRepeatableQuestType.endTime = currentTime + repeatableConfig.resetTime;
|
||||||
|
currentRepeatableQuestType.inactiveQuests = [];
|
||||||
|
this.logger.debug(`Generating new ${repeatableType}`);
|
||||||
|
|
||||||
|
// Put old quests to inactive (this is required since only then the client makes them fail due to non-completion)
|
||||||
|
// we also need to push them to the "inactiveQuests" list since we need to remove them from offraidData.profile.Quests
|
||||||
|
// after a raid (the client seems to keep quests internally and we want to get rid of old repeatable quests)
|
||||||
|
// and remove them from the PMC's Quests and RepeatableQuests[i].activeQuests
|
||||||
|
const questsToKeep = [];
|
||||||
|
// for (let i = 0; i < currentRepeatable.activeQuests.length; i++)
|
||||||
|
for (const activeQuest of currentRepeatableQuestType.activeQuests)
|
||||||
|
{
|
||||||
|
// Keep finished quests in list so player can hand in
|
||||||
|
const quest = pmcData.Quests.find((quest) => quest.qid === activeQuest._id);
|
||||||
|
if (quest)
|
||||||
|
{
|
||||||
|
if (quest.status === QuestStatus.AvailableForFinish)
|
||||||
|
{
|
||||||
|
questsToKeep.push(activeQuest);
|
||||||
|
this.logger.debug(
|
||||||
|
`Keeping repeatable quest ${activeQuest._id} in activeQuests since it is available to hand in`,
|
||||||
|
);
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.profileFixerService.removeDanglingConditionCounters(pmcData);
|
||||||
|
|
||||||
|
// Remove expired quest from pmc.quest array
|
||||||
|
pmcData.Quests = pmcData.Quests.filter((quest) => quest.qid !== activeQuest._id);
|
||||||
|
currentRepeatableQuestType.inactiveQuests.push(activeQuest);
|
||||||
|
}
|
||||||
|
currentRepeatableQuestType.activeQuests = questsToKeep;
|
||||||
|
|
||||||
|
// introduce a dynamic quest pool to avoid duplicates
|
||||||
|
const questTypePool = this.generateQuestPool(repeatableConfig, pmcData.Info.Level);
|
||||||
|
|
||||||
|
// Add daily quests
|
||||||
|
for (let i = 0; i < this.getQuestCount(repeatableConfig, pmcData); i++)
|
||||||
|
{
|
||||||
|
let quest: IRepeatableQuest | undefined = undefined;
|
||||||
|
let lifeline = 0;
|
||||||
|
while (!quest && questTypePool.types.length > 0)
|
||||||
|
{
|
||||||
|
quest = this.repeatableQuestGenerator.generateRepeatableQuest(
|
||||||
|
pmcData.Info.Level,
|
||||||
|
pmcData.TradersInfo,
|
||||||
|
questTypePool,
|
||||||
|
repeatableConfig,
|
||||||
|
);
|
||||||
|
lifeline++;
|
||||||
|
if (lifeline > 10)
|
||||||
|
{
|
||||||
|
this.logger.debug(
|
||||||
|
"We were stuck in repeatable quest generation. This should never happen. Please report",
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if there are no more quest types available
|
||||||
|
if (questTypePool.types.length === 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
quest.side = repeatableConfig.side;
|
||||||
|
currentRepeatableQuestType.activeQuests.push(quest);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset players free quest count for this repeatable sub-type
|
||||||
|
if (!fullProfile.spt.freeRepeatableChangeCount)
|
||||||
|
{
|
||||||
|
fullProfile.spt.freeRepeatableChangeCount = {};
|
||||||
|
}
|
||||||
|
fullProfile.spt.freeRepeatableChangeCount[repeatableType] = repeatableConfig.freeChanges;
|
||||||
|
|
||||||
// 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)
|
||||||
{
|
{
|
||||||
@ -198,13 +215,18 @@ export class RepeatableQuestController
|
|||||||
inactiveQuests: currentRepeatableQuestType.inactiveQuests,
|
inactiveQuests: currentRepeatableQuestType.inactiveQuests,
|
||||||
changeRequirement: currentRepeatableQuestType.changeRequirement,
|
changeRequirement: currentRepeatableQuestType.changeRequirement,
|
||||||
freeChanges: currentRepeatableQuestType.freeChanges,
|
freeChanges: currentRepeatableQuestType.freeChanges,
|
||||||
freeChangesAvailable: currentRepeatableQuestType.freeChangesAvailable,
|
freeChangesAvailable: fullProfile.spt.freeRepeatableChangeCount[repeatableType],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return returnData;
|
return returnData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Does player have daily scav quests unlocked
|
||||||
|
* @param pmcData Player profile to check
|
||||||
|
* @returns True if unlocked
|
||||||
|
*/
|
||||||
protected playerHasDailyScavQuestsUnlocked(pmcData: IPmcData): boolean
|
protected playerHasDailyScavQuestsUnlocked(pmcData: IPmcData): boolean
|
||||||
{
|
{
|
||||||
return pmcData?.Hideout?.Areas
|
return pmcData?.Hideout?.Areas
|
||||||
@ -212,6 +234,12 @@ export class RepeatableQuestController
|
|||||||
?.level >= 1;
|
?.level >= 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Does player have daily pmc quests unlocked
|
||||||
|
* @param pmcData Player profile to check
|
||||||
|
* @param repeatableConfig Config of daily type to check
|
||||||
|
* @returns True if unlocked
|
||||||
|
*/
|
||||||
protected playerHasDailyPmcQuestsUnlocked(pmcData: IPmcData, repeatableConfig: IRepeatableQuestConfig): boolean
|
protected playerHasDailyPmcQuestsUnlocked(pmcData: IPmcData, repeatableConfig: IRepeatableQuestConfig): boolean
|
||||||
{
|
{
|
||||||
return pmcData.Info.Level >= repeatableConfig.minPlayerLevel;
|
return pmcData.Info.Level >= repeatableConfig.minPlayerLevel;
|
||||||
@ -450,17 +478,19 @@ export class RepeatableQuestController
|
|||||||
{
|
{
|
||||||
let repeatableToChange: IPmcDataRepeatableQuest;
|
let repeatableToChange: IPmcDataRepeatableQuest;
|
||||||
let changeRequirement: IChangeRequirement;
|
let changeRequirement: IChangeRequirement;
|
||||||
|
const fullProfile = this.profileHelper.getFullProfile(sessionID);
|
||||||
|
|
||||||
// Trader existing quest is linked to
|
// The trader existing quest is linked to
|
||||||
let replacedQuestTraderId: string;
|
let replacedQuestTraderId: string;
|
||||||
|
|
||||||
// Daily,weekly or scav daily
|
// Daily, weekly or scav repeatable type
|
||||||
for (const currentRepeatablePool of pmcData.RepeatableQuests)
|
for (const currentRepeatablePool of pmcData.RepeatableQuests)
|
||||||
{
|
{
|
||||||
// Check for existing quest in (daily/weekly/scav arrays)
|
// Check for existing quest in (daily/weekly/scav arrays)
|
||||||
const questToReplace = currentRepeatablePool.activeQuests.find((x) => x._id === changeRequest.qid);
|
const questToReplace = currentRepeatablePool.activeQuests.find((x) => x._id === changeRequest.qid);
|
||||||
if (!questToReplace)
|
if (!questToReplace)
|
||||||
{
|
{
|
||||||
|
// Not found, skip to next repeatable sub-type
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -468,23 +498,22 @@ export class RepeatableQuestController
|
|||||||
replacedQuestTraderId = questToReplace.traderId;
|
replacedQuestTraderId = questToReplace.traderId;
|
||||||
|
|
||||||
// Update active quests to exclude the quest we're replacing
|
// Update active quests to exclude the quest we're replacing
|
||||||
currentRepeatablePool.activeQuests = currentRepeatablePool.activeQuests.filter(
|
currentRepeatablePool.activeQuests = currentRepeatablePool.activeQuests
|
||||||
(x) => x._id !== changeRequest.qid,
|
.filter((quest) => quest._id !== changeRequest.qid);
|
||||||
);
|
|
||||||
|
|
||||||
// Get cost to replace existing quest
|
// Get cost to replace existing quest
|
||||||
changeRequirement = this.cloner.clone(currentRepeatablePool.changeRequirement[changeRequest.qid]);
|
changeRequirement = this.cloner.clone(currentRepeatablePool.changeRequirement[changeRequest.qid]);
|
||||||
delete currentRepeatablePool.changeRequirement[changeRequest.qid];
|
delete currentRepeatablePool.changeRequirement[changeRequest.qid];
|
||||||
// TODO: somehow we need to reduce the questPool by the currently active quests (for all repeatables)
|
// TODO: somehow we need to reduce the questPool by the currently active quests (for all repeatables)
|
||||||
|
|
||||||
const repeatableConfig = this.questConfig.repeatableQuests.find(
|
const repeatableConfig = this.questConfig.repeatableQuests
|
||||||
(x) => x.name === currentRepeatablePool.name,
|
.find((config) => config.name === currentRepeatablePool.name,
|
||||||
);
|
);
|
||||||
const questTypePool = this.generateQuestPool(repeatableConfig, pmcData.Info.Level);
|
const questTypePool = this.generateQuestPool(repeatableConfig, pmcData.Info.Level);
|
||||||
const newRepeatableQuest = this.attemptToGenerateRepeatableQuest(pmcData, questTypePool, repeatableConfig);
|
const newRepeatableQuest = this.attemptToGenerateRepeatableQuest(pmcData, questTypePool, repeatableConfig);
|
||||||
if (newRepeatableQuest)
|
if (newRepeatableQuest)
|
||||||
{
|
{
|
||||||
// Add newly generated quest to daily/weekly array
|
// Add newly generated quest to daily/weekly/scav type array
|
||||||
newRepeatableQuest.side = repeatableConfig.side;
|
newRepeatableQuest.side = repeatableConfig.side;
|
||||||
currentRepeatablePool.activeQuests.push(newRepeatableQuest);
|
currentRepeatablePool.activeQuests.push(newRepeatableQuest);
|
||||||
currentRepeatablePool.changeRequirement[newRepeatableQuest._id] = {
|
currentRepeatablePool.changeRequirement[newRepeatableQuest._id] = {
|
||||||
@ -508,12 +537,17 @@ export class RepeatableQuestController
|
|||||||
repeatableToChange = this.cloner.clone(currentRepeatablePool);
|
repeatableToChange = this.cloner.clone(currentRepeatablePool);
|
||||||
delete repeatableToChange.inactiveQuests;
|
delete repeatableToChange.inactiveQuests;
|
||||||
|
|
||||||
|
// Decrement free reset value in profile, dont let drop below 0
|
||||||
|
fullProfile.spt.freeRepeatableChangeCount[currentRepeatablePool.name.toLowerCase()]
|
||||||
|
= Math.max(0, fullProfile.spt.freeRepeatableChangeCount[currentRepeatablePool.name.toLowerCase()] - 1);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
const output = this.eventOutputHolder.getOutput(sessionID);
|
const output = this.eventOutputHolder.getOutput(sessionID);
|
||||||
if (!repeatableToChange)
|
if (!repeatableToChange)
|
||||||
{
|
{
|
||||||
|
// Unable to find quest being replaced
|
||||||
const message = this.localisationService.getText("quest-unable_to_find_repeatable_to_replace");
|
const message = this.localisationService.getText("quest-unable_to_find_repeatable_to_replace");
|
||||||
this.logger.error(message);
|
this.logger.error(message);
|
||||||
|
|
||||||
|
@ -215,6 +215,8 @@ export interface Spt
|
|||||||
receivedGifts: ReceivedGift[]
|
receivedGifts: ReceivedGift[]
|
||||||
/** item TPLs blacklisted from being sold on flea for this profile */
|
/** item TPLs blacklisted from being sold on flea for this profile */
|
||||||
blacklistedItemTpls?: string[]
|
blacklistedItemTpls?: string[]
|
||||||
|
/** key: daily type */
|
||||||
|
freeRepeatableChangeCount: Record<string, number>
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ModDetails
|
export interface ModDetails
|
||||||
|
Loading…
x
Reference in New Issue
Block a user