update quest objects and implement get achievemetns

This commit is contained in:
Dev 2023-12-27 17:15:38 +00:00
parent 87b177586e
commit 9e55a52965
10 changed files with 85 additions and 71 deletions

View File

@ -1,3 +1,4 @@
import { AchievementController } from "@spt-aki/controllers/AchievementController";
import { IEmptyRequestData } from "@spt-aki/models/eft/common/IEmptyRequestData";
import { HttpResponseUtil } from "@spt-aki/utils/HttpResponseUtil";
import { inject, injectable } from "tsyringe";
@ -6,7 +7,7 @@ import { inject, injectable } from "tsyringe";
export class AchievementCallbacks
{
constructor(
// @inject("AchievementController") protected botController: AchievementController,
@inject("AchievementController") protected achievementController: AchievementController,
@inject("HttpResponseUtil") protected httpResponse: HttpResponseUtil,
)
{}
@ -18,8 +19,8 @@ export class AchievementCallbacks
// eslint-disable-next-line @typescript-eslint/no-unused-vars
public getAchievements(url: string, info: IEmptyRequestData, sessionID: string): any
{
throw new Error("Not implemented");
return this.httpResponse.getBody(this.achievementController.getAchievements(sessionID));
}
/**

View File

@ -6,7 +6,6 @@ import { inject, injectable } from "tsyringe";
export class BuildsCallbacks
{
constructor(
// @inject("AchievementController") protected botController: AchievementController,
@inject("HttpResponseUtil") protected httpResponse: HttpResponseUtil,
)
{}

View File

@ -135,7 +135,7 @@ export class QuestController
for (const conditionToFulfil of questRequirements)
{
// 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._props.target);
const prerequisiteQuest = profile.Quests.find((pq) => pq.qid === conditionToFulfil.target);
if (!prerequisiteQuest)
{
haveCompletedPreviousQuest = false;
@ -143,18 +143,18 @@ export class QuestController
}
// Prereq does not have its status requirement fulfilled
if (!conditionToFulfil._props.status.includes(prerequisiteQuest.status))
if (!conditionToFulfil.status.includes(prerequisiteQuest.status))
{
haveCompletedPreviousQuest = false;
break;
}
// Has a wait timer
if (conditionToFulfil._props.availableAfter > 0)
if (conditionToFulfil.availableAfter > 0)
{
// Compare current time to unlock time for previous quest
const previousQuestCompleteTime = prerequisiteQuest.statusTimers[prerequisiteQuest.status];
const unlockTime = previousQuestCompleteTime + conditionToFulfil._props.availableAfter;
const unlockTime = previousQuestCompleteTime + conditionToFulfil.availableAfter;
if (unlockTime > this.timeUtil.getTimestamp())
{
this.logger.debug(
@ -175,7 +175,7 @@ export class QuestController
let passesLoyaltyRequirements = true;
for (const condition of loyaltyRequirements)
{
if (!this.questHelper.traderLoyaltyLevelRequirementCheck(condition._props, profile))
if (!this.questHelper.traderLoyaltyLevelRequirementCheck(condition, profile))
{
passesLoyaltyRequirements = false;
break;
@ -185,7 +185,7 @@ export class QuestController
let passesStandingRequirements = true;
for (const condition of standingRequirements)
{
if (!this.questHelper.traderStandingRequirementCheck(condition._props, profile))
if (!this.questHelper.traderStandingRequirementCheck(condition, profile))
{
passesStandingRequirements = false;
break;
@ -637,13 +637,13 @@ export class QuestController
{
// If quest has prereq of completed quest + availableAfter value > 0 (quest has wait time)
const nextQuestWaitCondition = quest.conditions.AvailableForStart.find((x) =>
x._props.target === completedQuestId && x._props.availableAfter > 0
x.target === completedQuestId && x.availableAfter > 0
);
if (nextQuestWaitCondition)
{
// Now + wait time
const availableAfterTimestamp = this.timeUtil.getTimestamp()
+ nextQuestWaitCondition._props.availableAfter;
+ nextQuestWaitCondition.availableAfter;
// Update quest in profile with status of AvailableAfter
const existingQuestInProfile = pmcData.Quests.find((x) => x.qid === quest._id);
@ -687,7 +687,7 @@ export class QuestController
return false;
}
return x.conditions.Fail.some((y) => y._props.target === completedQuestId);
return x.conditions.Fail.some((y) => y.target === completedQuestId);
});
}
@ -709,7 +709,7 @@ export class QuestController
for (const questToFail of questsToFail)
{
// Skip failing a quest that has a fail status of something other than success
if (questToFail.conditions.Fail?.some((x) => x._props.status?.some((y) => y !== QuestStatus.Success)))
if (questToFail.conditions.Fail?.some((x) => x.status?.some((y) => y !== QuestStatus.Success)))
{
continue;
}
@ -764,12 +764,12 @@ export class QuestController
for (const condition of quest.conditions.AvailableForFinish)
{
if (
condition._props.id === handoverQuestRequest.conditionId
&& handoverQuestTypes.includes(condition._parent)
condition.id === handoverQuestRequest.conditionId
&& handoverQuestTypes.includes(condition.conditionType)
)
{
handedInCount = Number.parseInt(<string>condition._props.value);
isItemHandoverQuest = condition._parent === handoverQuestTypes[0];
handedInCount = Number.parseInt(<string>condition.value);
isItemHandoverQuest = condition.conditionType === handoverQuestTypes[0];
handoverRequirements = condition;
const profileCounter = (handoverQuestRequest.conditionId in pmcData.BackendCounters)
@ -807,7 +807,7 @@ export class QuestController
for (const itemHandover of handoverQuestRequest.items)
{
const matchingItemInProfile = pmcData.Inventory.items.find((item) => item._id === itemHandover.id);
if (!matchingItemInProfile || !handoverRequirements._props.target.includes(matchingItemInProfile._tpl))
if (!matchingItemInProfile || !handoverRequirements.target.includes(matchingItemInProfile._tpl))
{
// Item handed in by player doesnt match what was requested
return this.showQuestItemHandoverMatchError(
@ -904,7 +904,7 @@ export class QuestController
const errorMessage = this.localisationService.getText("quest-handover_wrong_item", {
questId: handoverQuestRequest.qid,
handedInTpl: itemHandedOver._tpl,
requiredTpl: handoverRequirements._props.target[0],
requiredTpl: handoverRequirements.target[0],
});
this.logger.error(errorMessage);

View File

@ -1,6 +1,8 @@
import { DependencyContainer, Lifecycle } from "tsyringe";
import { AchievementCallbacks } from "@spt-aki/callbacks/AchievementCallbacks";
import { BotCallbacks } from "@spt-aki/callbacks/BotCallbacks";
import { BuildsCallbacks } from "@spt-aki/callbacks/BuildsCallbacks";
import { BundleCallbacks } from "@spt-aki/callbacks/BundleCallbacks";
import { ClientLogCallbacks } from "@spt-aki/callbacks/ClientLogCallbacks";
import { CustomizationCallbacks } from "@spt-aki/callbacks/CustomizationCallbacks";
@ -33,6 +35,7 @@ import { TraderCallbacks } from "@spt-aki/callbacks/TraderCallbacks";
import { WeatherCallbacks } from "@spt-aki/callbacks/WeatherCallbacks";
import { WishlistCallbacks } from "@spt-aki/callbacks/WishlistCallbacks";
import { ApplicationContext } from "@spt-aki/context/ApplicationContext";
import { AchievementController } from "@spt-aki/controllers/AchievementController";
import { BotController } from "@spt-aki/controllers/BotController";
import { ClientLogController } from "@spt-aki/controllers/ClientLogController";
import { CustomizationController } from "@spt-aki/controllers/CustomizationController";
@ -643,6 +646,8 @@ export class Container
depContainer.register<TraderCallbacks>("TraderCallbacks", { useClass: TraderCallbacks });
depContainer.register<WeatherCallbacks>("WeatherCallbacks", { useClass: WeatherCallbacks });
depContainer.register<WishlistCallbacks>("WishlistCallbacks", { useClass: WishlistCallbacks });
depContainer.register<AchievementCallbacks>("AchievementCallbacks", { useClass: AchievementCallbacks });
depContainer.register<BuildsCallbacks>("BuildsCallbacks", { useClass: BuildsCallbacks });
}
private static registerServices(depContainer: DependencyContainer): void
@ -787,5 +792,6 @@ export class Container
depContainer.register<TraderController>("TraderController", { useClass: TraderController });
depContainer.register<WeatherController>("WeatherController", { useClass: WeatherController });
depContainer.register<WishlistController>("WishlistController", WishlistController);
depContainer.register<AchievementController>("AchievementController", AchievementController);
}
}

View File

@ -327,14 +327,14 @@ export class InRaidHelper
// Does failed quest have requirement to collect items from raid
const questDbData = this.questHelper.getQuestFromDb(postRaidQuest.qid, pmcData);
// AvailableForFinish
const matchingAffFindConditions = questDbData.conditions.AvailableForFinish.filter(x => x._parent === "FindItem");
const matchingAffFindConditions = questDbData.conditions.AvailableForFinish.filter(x => x.conditionType === "FindItem");
const itemsToCollect: string[] = [];
if (matchingAffFindConditions)
{
// Find all items the failed quest wanted
for (const condition of matchingAffFindConditions)
{
itemsToCollect.push(...condition._props.target);
itemsToCollect.push(...condition.target);
}
}
@ -381,12 +381,12 @@ export class InRaidHelper
}
// Find the time requirement in AvailableForStart array (assuming there is one as quest in locked state === its time-gated)
const afsRequirement = dbQuest.conditions.AvailableForStart.find(x => x._parent === "Quest");
if (afsRequirement && afsRequirement._props.availableAfter > 0)
const afsRequirement = dbQuest.conditions.AvailableForStart.find(x => x.conditionType === "Quest");
if (afsRequirement && afsRequirement.availableAfter > 0)
{
// Prereq quest has a wait
// Set quest as AvailableAfter and set timer
const timestamp = this.timeUtil.getTimestamp() + afsRequirement._props.availableAfter;
const timestamp = this.timeUtil.getTimestamp() + afsRequirement.availableAfter;
lockedQuest.availableAfter = timestamp;
lockedQuest.statusTimers.AvailableAfter = timestamp;
lockedQuest.status = 9;

View File

@ -45,7 +45,7 @@ export class QuestConditionHelper
{
const filteredQuests = q.filter((c) =>
{
if (c._parent === questType)
if (c.conditionType === questType)
{
if (furtherFilter)
{

View File

@ -10,7 +10,7 @@ import { TraderHelper } from "@spt-aki/helpers/TraderHelper";
import { IPmcData } from "@spt-aki/models/eft/common/IPmcData";
import { Common, IQuestStatus } from "@spt-aki/models/eft/common/tables/IBotBase";
import { Item } from "@spt-aki/models/eft/common/tables/IItem";
import { AvailableForConditions, AvailableForProps, IQuest, Reward } from "@spt-aki/models/eft/common/tables/IQuest";
import { AvailableForConditions, IQuest, Reward } from "@spt-aki/models/eft/common/tables/IQuest";
import { IItemEventRouterResponse } from "@spt-aki/models/eft/itemEvent/IItemEventRouterResponse";
import { IAcceptQuestRequestData } from "@spt-aki/models/eft/quests/IAcceptQuestRequestData";
import { IFailQuestRequestData } from "@spt-aki/models/eft/quests/IFailQuestRequestData";
@ -80,25 +80,25 @@ export class QuestHelper
*/
public doesPlayerLevelFulfilCondition(playerLevel: number, condition: AvailableForConditions): boolean
{
if (condition._parent === "Level")
if (condition.conditionType === "Level")
{
switch (condition._props.compareMethod)
switch (condition.compareMethod)
{
case ">=":
return playerLevel >= <number>condition._props.value;
return playerLevel >= <number>condition.value;
case ">":
return playerLevel > <number>condition._props.value;
return playerLevel > <number>condition.value;
case "<":
return playerLevel < <number>condition._props.value;
return playerLevel < <number>condition.value;
case "<=":
return playerLevel <= <number>condition._props.value;
return playerLevel <= <number>condition.value;
case "=":
return playerLevel === <number>condition._props.value;
return playerLevel === <number>condition.value;
default:
this.logger.error(
this.localisationService.getText(
"quest-unable_to_find_compare_condition",
condition._props.compareMethod,
condition.compareMethod,
),
);
return false;
@ -198,7 +198,7 @@ export class QuestHelper
* @param profile Player profile
* @returns true if loyalty is high enough to fulfill quest requirement
*/
public traderLoyaltyLevelRequirementCheck(questProperties: AvailableForProps, profile: IPmcData): boolean
public traderLoyaltyLevelRequirementCheck(questProperties: AvailableForConditions, profile: IPmcData): boolean
{
const requiredLoyaltyLevel = Number(questProperties.value);
const trader = profile.TradersInfo[<string>questProperties.target];
@ -216,7 +216,7 @@ export class QuestHelper
* @param profile Player profile
* @returns true if standing is high enough to fulfill quest requirement
*/
public traderStandingRequirementCheck(questProperties: AvailableForProps, profile: IPmcData): boolean
public traderStandingRequirementCheck(questProperties: AvailableForConditions, profile: IPmcData): boolean
{
const requiredStanding = Number(questProperties.value);
const trader = profile.TradersInfo[<string>questProperties.target];
@ -373,13 +373,13 @@ export class QuestHelper
// Check if quest has a prereq to be placed in a 'pending' state
const questDbData = this.getQuestFromDb(acceptedQuest.qid, pmcData);
const waitTime = questDbData.conditions.AvailableForStart.find((x) => x._props.availableAfter > 0);
const waitTime = questDbData.conditions.AvailableForStart.find((x) => x.availableAfter > 0);
if (waitTime && acceptedQuest.type !== "repeatable")
{
// Quest should be put into 'pending' state
newQuest.startTime = 0;
newQuest.status = QuestStatus.AvailableAfter; // 9
newQuest.availableAfter = currentTimestamp + waitTime._props.availableAfter;
newQuest.availableAfter = currentTimestamp + waitTime.availableAfter;
}
else
{
@ -409,9 +409,9 @@ export class QuestHelper
// e.g. Quest A passed in, quest B is looped over and has requirement of A to be started, include it
const acceptedQuestCondition = quest.conditions.AvailableForStart.find((x) =>
{
return x._parent === "Quest"
&& x._props.target === startedQuestId
&& x._props.status[0] === QuestStatus.Started;
return x.conditionType === "Quest"
&& x.target === startedQuestId
&& x.status[0] === QuestStatus.Started;
});
// Not found, skip quest
@ -425,7 +425,7 @@ export class QuestHelper
);
for (const condition of standingRequirements)
{
if (!this.traderStandingRequirementCheck(condition._props, profile))
if (!this.traderStandingRequirementCheck(condition, profile))
{
return false;
}
@ -436,7 +436,7 @@ export class QuestHelper
);
for (const condition of loyaltyRequirements)
{
if (!this.traderLoyaltyLevelRequirementCheck(condition._props, profile))
if (!this.traderLoyaltyLevelRequirementCheck(condition, profile))
{
return false;
}
@ -465,9 +465,9 @@ export class QuestHelper
{
const acceptedQuestCondition = q.conditions.AvailableForStart.find((c) =>
{
return c._parent === "Quest"
&& c._props.target === failedQuestId
&& c._props.status[0] === QuestStatus.Fail;
return c.conditionType === "Quest"
&& c.target === failedQuestId
&& c.status[0] === QuestStatus.Fail;
});
if (!acceptedQuestCondition)
@ -602,7 +602,7 @@ export class QuestHelper
public getQuestWithOnlyLevelRequirementStartCondition(quest: IQuest): IQuest
{
quest = this.jsonUtil.clone(quest);
quest.conditions.AvailableForStart = quest.conditions.AvailableForStart.filter((q) => q._parent === "Level");
quest.conditions.AvailableForStart = quest.conditions.AvailableForStart.filter((q) => q.conditionType === "Level");
return quest;
}
@ -958,11 +958,11 @@ export class QuestHelper
}
const condition = questInDb.conditions.AvailableForFinish.find((c) =>
c._parent === "FindItem" && c._props?.target?.includes(itemTpl)
c.conditionType === "FindItem" && c?.target?.includes(itemTpl)
);
if (condition)
{
result[questId] = condition._props.id;
result[questId] = condition.id;
break;
}

View File

@ -9,7 +9,7 @@ export interface IQuest
QuestName?: string;
_id: string;
canShowNotificationsInGame: boolean;
conditions: Conditions;
conditions: IQuestConditions;
description: string;
failMessageText: string;
name: string;
@ -27,7 +27,7 @@ export interface IQuest
startedMessageText: string;
successMessageText: string;
templateId: string;
rewards: Rewards;
rewards: IRewards;
/** Becomes 'AppearStatus' inside client */
status: string | number;
KeyQuest: boolean;
@ -38,7 +38,7 @@ export interface IQuest
sptStatus?: QuestStatus;
}
export interface Conditions
export interface IQuestConditions
{
Started: AvailableForConditions[];
AvailableForFinish: AvailableForConditions[];
@ -48,13 +48,6 @@ export interface Conditions
}
export interface AvailableForConditions
{
_parent: string;
_props: AvailableForProps;
dynamicLocale?: boolean;
}
export interface AvailableForProps
{
id: string;
index: number;
@ -79,7 +72,9 @@ export interface AvailableForProps
zoneId?: string;
type?: boolean;
countInRaid?: boolean;
globalQuestCounterId?: any;
globalQuestCounterId?: string;
completeInSeconds?: number
conditionType?: string
}
export interface AvailableForCounter
@ -89,26 +84,36 @@ export interface AvailableForCounter
}
export interface CounterCondition
{
_parent: string;
_props: CounterProps;
}
export interface CounterProps
{
id: string;
dynamicLocale: boolean
target: string[] | string; // TODO: some objects have an array and some are just strings, thanks bsg very cool
compareMethod?: string;
value?: string;
weapon?: string[];
distance: ICounterConditionDistance
equipmentInclusive?: string[][];
weaponModsInclusive?: string[][];
weaponModsExclusive?: string[][];
enemyEquipmentInclusive?: string[][];
enemyEquipmentExclusive?: string[][];
weaponCaliber: string[]
savageRole: any[]
status?: string[];
bodyPart?: string[];
daytime?: DaytimeCounter;
daytime?: IDaytimeCounter;
conditionType?: string
enemyHealthEffects: any[]
resetOnSessionEnd: boolean
}
export interface DaytimeCounter
export interface ICounterConditionDistance
{
value: number
compareMethod: string
}
export interface IDaytimeCounter
{
from: number;
to: number;
@ -122,7 +127,7 @@ export interface VisibilityCondition
oneSessionOnly: boolean;
}
export interface Rewards
export interface IRewards
{
AvailableForStart: Reward[];
AvailableForFinish: Reward[];

View File

@ -52,6 +52,9 @@ export interface IDatabaseTables
/** Default equipment loadouts that show on main inventory screen */
defaultEquipmentPresets: IEquipmentBuild[];
/** Achievements */
achievements: IAchievement[]
};
traders?: Record<string, ITrader>;

View File

@ -6,7 +6,7 @@ import { RouteAction, StaticRouter } from "@spt-aki/di/Router";
@injectable()
export class AchievementStaticRouter extends StaticRouter
{
constructor(@inject("AchievementStaticRouter") protected achievementCallbacks: AchievementCallbacks)
constructor(@inject("AchievementCallbacks") protected achievementCallbacks: AchievementCallbacks)
{
super([
new RouteAction(