diff --git a/project/src/callbacks/AchievementCallbacks.ts b/project/src/callbacks/AchievementCallbacks.ts index d15fa0cc..ed823ecb 100644 --- a/project/src/callbacks/AchievementCallbacks.ts +++ b/project/src/callbacks/AchievementCallbacks.ts @@ -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)); } /** diff --git a/project/src/callbacks/BuildsCallbacks.ts b/project/src/callbacks/BuildsCallbacks.ts index 8c408f14..584fda38 100644 --- a/project/src/callbacks/BuildsCallbacks.ts +++ b/project/src/callbacks/BuildsCallbacks.ts @@ -6,7 +6,6 @@ import { inject, injectable } from "tsyringe"; export class BuildsCallbacks { constructor( - // @inject("AchievementController") protected botController: AchievementController, @inject("HttpResponseUtil") protected httpResponse: HttpResponseUtil, ) {} diff --git a/project/src/controllers/QuestController.ts b/project/src/controllers/QuestController.ts index 2f6b2083..de4f0212 100644 --- a/project/src/controllers/QuestController.ts +++ b/project/src/controllers/QuestController.ts @@ -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(condition._props.value); - isItemHandoverQuest = condition._parent === handoverQuestTypes[0]; + handedInCount = Number.parseInt(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); diff --git a/project/src/di/Container.ts b/project/src/di/Container.ts index 064bda55..918e3f1e 100644 --- a/project/src/di/Container.ts +++ b/project/src/di/Container.ts @@ -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", { useClass: TraderCallbacks }); depContainer.register("WeatherCallbacks", { useClass: WeatherCallbacks }); depContainer.register("WishlistCallbacks", { useClass: WishlistCallbacks }); + depContainer.register("AchievementCallbacks", { useClass: AchievementCallbacks }); + depContainer.register("BuildsCallbacks", { useClass: BuildsCallbacks }); } private static registerServices(depContainer: DependencyContainer): void @@ -787,5 +792,6 @@ export class Container depContainer.register("TraderController", { useClass: TraderController }); depContainer.register("WeatherController", { useClass: WeatherController }); depContainer.register("WishlistController", WishlistController); + depContainer.register("AchievementController", AchievementController); } } diff --git a/project/src/helpers/InRaidHelper.ts b/project/src/helpers/InRaidHelper.ts index 607a4e6d..70c87008 100644 --- a/project/src/helpers/InRaidHelper.ts +++ b/project/src/helpers/InRaidHelper.ts @@ -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; diff --git a/project/src/helpers/QuestConditionHelper.ts b/project/src/helpers/QuestConditionHelper.ts index b47ba161..e57df6e5 100644 --- a/project/src/helpers/QuestConditionHelper.ts +++ b/project/src/helpers/QuestConditionHelper.ts @@ -45,7 +45,7 @@ export class QuestConditionHelper { const filteredQuests = q.filter((c) => { - if (c._parent === questType) + if (c.conditionType === questType) { if (furtherFilter) { diff --git a/project/src/helpers/QuestHelper.ts b/project/src/helpers/QuestHelper.ts index b3e2a0b9..57d9bad0 100644 --- a/project/src/helpers/QuestHelper.ts +++ b/project/src/helpers/QuestHelper.ts @@ -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 >= condition._props.value; + return playerLevel >= condition.value; case ">": - return playerLevel > condition._props.value; + return playerLevel > condition.value; case "<": - return playerLevel < condition._props.value; + return playerLevel < condition.value; case "<=": - return playerLevel <= condition._props.value; + return playerLevel <= condition.value; case "=": - return playerLevel === condition._props.value; + return playerLevel === 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[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[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; } diff --git a/project/src/models/eft/common/tables/IQuest.ts b/project/src/models/eft/common/tables/IQuest.ts index e0d2778a..bfb213f0 100644 --- a/project/src/models/eft/common/tables/IQuest.ts +++ b/project/src/models/eft/common/tables/IQuest.ts @@ -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[]; diff --git a/project/src/models/spt/server/IDatabaseTables.ts b/project/src/models/spt/server/IDatabaseTables.ts index acd72d6e..770091b2 100644 --- a/project/src/models/spt/server/IDatabaseTables.ts +++ b/project/src/models/spt/server/IDatabaseTables.ts @@ -52,6 +52,9 @@ export interface IDatabaseTables /** Default equipment loadouts that show on main inventory screen */ defaultEquipmentPresets: IEquipmentBuild[]; + + /** Achievements */ + achievements: IAchievement[] }; traders?: Record; diff --git a/project/src/routers/static/AchievementStaticRouter.ts b/project/src/routers/static/AchievementStaticRouter.ts index b6f58364..54ae8de3 100644 --- a/project/src/routers/static/AchievementStaticRouter.ts +++ b/project/src/routers/static/AchievementStaticRouter.ts @@ -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(