From 49d59db23ae17f9a97575668854c4b97f1fee6ba Mon Sep 17 00:00:00 2001 From: Reece Dunham Date: Mon, 2 Sep 2024 16:11:04 -0400 Subject: [PATCH] feat!: add game version to resolveContract Signed-off-by: Reece Dunham --- components/2016/legacyContractHandler.ts | 16 ++-- components/2016/legacyMenuData.ts | 2 + components/2016/legacyProfileRouter.ts | 5 +- components/candle/challengeHelpers.ts | 14 +++- components/candle/challengeService.ts | 46 +++++++++-- components/candle/progressionService.ts | 5 +- components/contracts/contractRouting.ts | 11 ++- components/contracts/contractsModeRouting.ts | 1 + components/contracts/dataGen.ts | 2 +- .../escalations/escalationService.ts | 2 +- components/contracts/hitsCategoryService.ts | 7 +- components/contracts/leaderboards.ts | 2 +- components/controller.ts | 82 +++++++++++++++---- components/eventHandler.ts | 18 ++-- components/livesplit/liveSplitManager.ts | 40 ++++++--- components/loggingInterop.ts | 42 +++++----- components/menuData.ts | 23 ++++-- components/menus/campaigns.ts | 12 ++- components/menus/destinations.ts | 2 + components/menus/favoriteContracts.ts | 10 ++- components/menus/hub.ts | 5 +- components/menus/planning.ts | 22 ++--- components/menus/playnext.ts | 9 +- components/menus/stashpoints.ts | 7 +- components/multiplayer/multiplayerMenuData.ts | 2 +- components/multiplayer/multiplayerService.ts | 2 +- components/profileHandler.ts | 6 +- components/scoreHandler.ts | 6 +- 28 files changed, 283 insertions(+), 118 deletions(-) diff --git a/components/2016/legacyContractHandler.ts b/components/2016/legacyContractHandler.ts index bd20d35bb..5be4907db 100644 --- a/components/2016/legacyContractHandler.ts +++ b/components/2016/legacyContractHandler.ts @@ -20,7 +20,7 @@ import { Router } from "express" import { gameDifficulty, nilUuid, ServerVer, uuidRegex } from "../utils" import { json as jsonMiddleware } from "body-parser" import { enqueueEvent, newSession } from "../eventHandler" -import { _legacyBull, controller } from "../controller" +import { controller } from "../controller" import { log, LogLevel } from "../loggingInterop" import { getConfig } from "../configSwizzleManager" import type { GameChanger, RequestWithJwt } from "../types/types" @@ -88,11 +88,10 @@ legacyContractRouter.post( return } - const contractData = - req.gameVersion === "h1" && - req.body.id === "42bac555-bbb9-429d-a8ce-f1ffdf94211c" - ? _legacyBull - : controller.resolveContract(req.body.id) + const contractData = controller.resolveContract( + req.body.id, + req.gameVersion, + ) if (!contractData) { log( @@ -193,7 +192,10 @@ legacyContractRouter.post( return } - const c = controller.resolveContract(req.body.contractId) + const c = controller.resolveContract( + req.body.contractId, + req.gameVersion, + ) if (!c) { res.status(404).end() diff --git a/components/2016/legacyMenuData.ts b/components/2016/legacyMenuData.ts index 234abb929..4b982e1e0 100644 --- a/components/2016/legacyMenuData.ts +++ b/components/2016/legacyMenuData.ts @@ -135,6 +135,7 @@ legacyMenuDataRouter.get( { type: ChallengeFilterType.ParentLocation, parent: parentLocation.Id, + gameVersion: req.gameVersion, pro1Filter: Pro1FilterType.Exclude, }, parentLocation.Id, @@ -198,6 +199,7 @@ legacyMenuDataRouter.get( { type: ChallengeFilterType.ParentLocation, parent: parentLocation.Id, + gameVersion: req.gameVersion, pro1Filter: Pro1FilterType.Only, }, parentLocation.Id, diff --git a/components/2016/legacyProfileRouter.ts b/components/2016/legacyProfileRouter.ts index fe2efd504..dced52bc9 100644 --- a/components/2016/legacyProfileRouter.ts +++ b/components/2016/legacyProfileRouter.ts @@ -49,7 +49,10 @@ legacyProfileRouter.post( false, ) - const json = controller.resolveContract(req.body.contractId) + const json = controller.resolveContract( + req.body.contractId, + req.gameVersion, + ) if (!json) { log( diff --git a/components/candle/challengeHelpers.ts b/components/candle/challengeHelpers.ts index 60e8c208d..6c89debe5 100644 --- a/components/candle/challengeHelpers.ts +++ b/components/candle/challengeHelpers.ts @@ -20,6 +20,7 @@ import { ChallengeProgressionData, CompiledChallengeIngameData, CompiledChallengeRuntimeData, + GameVersion, InclusionData, MissionManifest, RegistryChallenge, @@ -115,6 +116,7 @@ export type ChallengeFilterOptions = type: ChallengeFilterType.Contract contractId: string locationId: string + gameVersion: GameVersion isFeatured?: boolean difficulty: number pro1Filter: Pro1FilterType @@ -122,12 +124,14 @@ export type ChallengeFilterOptions = | { type: ChallengeFilterType.Contracts contractIds: string[] + gameVersion: GameVersion locationId: string pro1Filter: Pro1FilterType } | { type: ChallengeFilterType.ParentLocation parent: string + gameVersion: GameVersion pro1Filter: Pro1FilterType } @@ -172,6 +176,7 @@ export function isChallengeForDifficulty( * @param contractId The id of the contract. * @param locationId The sublocation ID of the challenge. * @param difficulty The upper bound on the difficulty of the challenges to return. + * @param gameVersion The game version. * @param challenge The challenge in question. * @param pro1Filter Settings for handling pro1 challenges. * @param forCareer Whether the result is used to decide what is shown the CAREER -> CHALLENGES page. Defaulted to false. @@ -181,6 +186,7 @@ function isChallengeInContract( contractId: string, locationId: string, difficulty: number, + gameVersion: GameVersion, challenge: RegistryChallenge, pro1Filter: Pro1FilterType, forCareer = false, @@ -197,7 +203,7 @@ function isChallengeInContract( return false } - const contract = controller.resolveContract(contractId, true) + const contract = controller.resolveContract(contractId, gameVersion, true) if (challenge.Type === "global") { return inclusionDataCheck( @@ -233,7 +239,9 @@ function isChallengeInContract( // We have to resolve the non-group contract, `contract` is the group contract const isForContractType = ( challenge.InclusionData?.ContractTypes || [] - ).includes(controller.resolveContract(contractId)!.Metadata.Type) + ).includes( + controller.resolveContract(contractId, gameVersion)!.Metadata.Type, + ) // Is this a location-wide challenge? // "location" is more widely used, but "parentlocation" is used in Ambrose and Berlin, as well as some "Discover XX" challenges. @@ -275,6 +283,7 @@ export function filterChallenge( options.contractId, options.locationId, options.difficulty, + options.gameVersion, challenge, options.pro1Filter, ) @@ -286,6 +295,7 @@ export function filterChallenge( contractId, options.locationId, gameDifficulty.master, // Get challenges of all difficulties + options.gameVersion, challenge, options.pro1Filter, true, diff --git a/components/candle/challengeService.ts b/components/candle/challengeService.ts index 6ed3839e7..eaafe6e53 100644 --- a/components/candle/challengeService.ts +++ b/components/candle/challengeService.ts @@ -833,7 +833,11 @@ export class ChallengeService extends ChallengeRegistry { difficulty = 4, ): GroupIndexedChallengeLists { const userData = getUserData(userId, gameVersion) - const contractGroup = this.controller.resolveContract(contractId, true) + const contractGroup = this.controller.resolveContract( + contractId, + gameVersion, + true, + ) if (!contractGroup) { return {} @@ -852,9 +856,17 @@ export class ChallengeService extends ChallengeRegistry { assert.ok(currentLevel, "expected current level ID in escalation") - contract = this.controller.resolveContract(currentLevel, false) + contract = this.controller.resolveContract( + currentLevel, + gameVersion, + false, + ) } else { - contract = this.controller.resolveContract(contractId, false) + contract = this.controller.resolveContract( + contractId, + gameVersion, + false, + ) } if (!contract) { @@ -878,6 +890,7 @@ export class ChallengeService extends ChallengeRegistry { gameVersion !== "h1" ? "LOCATION_ICA_FACILITY_SHIP" : contract.Metadata.Location, + gameVersion, isFeatured: contractGroup.Metadata.Type === "featured", pro1Filter: contract.Metadata.Difficulty === "pro1" @@ -924,6 +937,7 @@ export class ChallengeService extends ChallengeRegistry { type: ChallengeFilterType.Contracts, contractIds: contracts, locationId: child, + gameVersion, pro1Filter: Pro1FilterType.Exclude, }, parent, @@ -936,7 +950,11 @@ export class ChallengeService extends ChallengeRegistry { // brand new. const { gameVersion, contractId, challengeContexts } = session - const contractJson = this.controller.resolveContract(contractId, true) + const contractJson = this.controller.resolveContract( + contractId, + gameVersion, + true, + ) const challengeGroups = this.getChallengesForContract( contractId, @@ -1139,7 +1157,11 @@ export class ChallengeService extends ChallengeRegistry { ): CompiledChallengeTreeCategory[] { const userData = getUserData(userId, gameVersion) - const contractData = this.controller.resolveContract(contractId, true) + const contractData = this.controller.resolveContract( + contractId, + gameVersion, + true, + ) if (!contractData) { return [] @@ -1164,9 +1186,17 @@ export class ChallengeService extends ChallengeRegistry { return [] } - levelData = this.controller.resolveContract(order, false) + levelData = this.controller.resolveContract( + order, + gameVersion, + false, + ) } else { - levelData = this.controller.resolveContract(contractId, false) + levelData = this.controller.resolveContract( + contractId, + gameVersion, + false, + ) } if (!levelData) { @@ -1306,6 +1336,7 @@ export class ChallengeService extends ChallengeRegistry { { type: ChallengeFilterType.ParentLocation, parent: locationParentId, + gameVersion, pro1Filter: isPro1 ? Pro1FilterType.Only : Pro1FilterType.Exclude, @@ -1535,6 +1566,7 @@ export class ChallengeService extends ChallengeRegistry { if (challenge.Type === "contract") { contract = this.controller.resolveContract( challenge.InclusionData?.ContractIds?.[0] || "", + gameVersion, ) // This is so we can remove unused data and make it more like official - AF diff --git a/components/candle/progressionService.ts b/components/candle/progressionService.ts index c3f2590b4..ad93cc509 100644 --- a/components/candle/progressionService.ts +++ b/components/candle/progressionService.ts @@ -153,7 +153,10 @@ export class ProgressionService { location: string, sniperUnlockable?: string, ): void { - const contract = controller.resolveContract(contractSession.contractId) + const contract = controller.resolveContract( + contractSession.contractId, + contractSession.gameVersion, + ) if (!contract) { return diff --git a/components/contracts/contractRouting.ts b/components/contracts/contractRouting.ts index 14b1fd8d5..111cb4952 100644 --- a/components/contracts/contractRouting.ts +++ b/components/contracts/contractRouting.ts @@ -70,7 +70,10 @@ contractRoutingRouter.post( return // user sent some nasty info } - const contractData = controller.resolveContract(req.body.id) + const contractData = controller.resolveContract( + req.body.id, + req.gameVersion, + ) if (!contractData) { log( @@ -266,6 +269,7 @@ contractRoutingRouter.post( const contractData = controller.resolveContract( sessionDetails.contractId, + sessionDetails.gameVersion, ) if (!contractData) { @@ -347,7 +351,10 @@ contractRoutingRouter.post( jsonMiddleware(), // @ts-expect-error Has jwt props. (req: RequestWithJwt, res) => { - const contract = controller.resolveContract(req.body.contractId) + const contract = controller.resolveContract( + req.body.contractId, + req.gameVersion, + ) if (!contract) { res.status(400).send("contract not found") diff --git a/components/contracts/contractsModeRouting.ts b/components/contracts/contractsModeRouting.ts index c9b98287e..d3a0729f8 100644 --- a/components/contracts/contractsModeRouting.ts +++ b/components/contracts/contractsModeRouting.ts @@ -42,6 +42,7 @@ export function contractsModeHome(req: RequestWithJwt, res: Response): void { const contractCreationTutorial = controller.resolveContract( contractCreationTutorialId, + req.gameVersion, ) res.json({ diff --git a/components/contracts/dataGen.ts b/components/contracts/dataGen.ts index f2eebf3e5..e317b6bf7 100644 --- a/components/contracts/dataGen.ts +++ b/components/contracts/dataGen.ts @@ -271,7 +271,7 @@ export function generateUserCentric( uc.Data.EscalationCompletedLevels = p - 1 uc.Data.EscalationTotalLevels = getLevelCount( - controller.resolveContract(eGroupId), + controller.resolveContract(eGroupId, gameVersion), ) uc.Data.EscalationCompleted = userData.Extensions.PeacockCompletedEscalations.includes(eGroupId) diff --git a/components/contracts/escalations/escalationService.ts b/components/contracts/escalations/escalationService.ts index cfe1e0cd8..4c5d41f1f 100644 --- a/components/contracts/escalations/escalationService.ts +++ b/components/contracts/escalations/escalationService.ts @@ -133,7 +133,7 @@ export function getPlayEscalationInfo( } const totalLevelCount = getLevelCount( - controller.resolveContract(groupContractId), + controller.resolveContract(groupContractId, gameVersion), ) let nextContractId = "00000000-0000-0000-0000-000000000000" diff --git a/components/contracts/hitsCategoryService.ts b/components/contracts/hitsCategoryService.ts index fe9912bb4..297edfab8 100644 --- a/components/contracts/hitsCategoryService.ts +++ b/components/contracts/hitsCategoryService.ts @@ -152,7 +152,7 @@ export class HitsCategoryService { .for("Elusive_Target_Hits") .tap(tapName, (contracts, gameVersion) => { for (const id of orderedETs) { - const contract = controller.resolveContract(id) + const contract = controller.resolveContract(id, gameVersion) switch (gameVersion) { case "h1": @@ -243,7 +243,10 @@ export class HitsCategoryService { missionsInLocations.escalations, )) { for (const id of escalations) { - const contract = controller.resolveContract(id) + const contract = controller.resolveContract( + id, + gameVersion, + ) if (!contract) continue diff --git a/components/contracts/leaderboards.ts b/components/contracts/leaderboards.ts index 97135269b..6aabf7378 100644 --- a/components/contracts/leaderboards.ts +++ b/components/contracts/leaderboards.ts @@ -56,7 +56,7 @@ export async function getLeaderboardEntries( ): Promise { let difficulty = "unset" - const contract = controller.resolveContract(contractId) + const contract = controller.resolveContract(contractId, gameVersion) if (!contract) { return undefined diff --git a/components/controller.ts b/components/controller.ts index 10dec82a7..f4d83e97c 100644 --- a/components/controller.ts +++ b/components/controller.ts @@ -220,7 +220,7 @@ function createPeacockRequire(pluginName: string): NodeRequire { /** * Freedom Fighters for Hitman 2016 (objectives are different). */ -export const _legacyBull: MissionManifest = JSON.parse(LEGACYFF) +const _legacyBull: MissionManifest = JSON.parse(LEGACYFF) /** * Ensure a mission has the bare minimum required to work. @@ -330,9 +330,10 @@ export class Controller { ] > getContractManifest: SyncBailHook< - [contractId: string], + [contractId: string, gameVersion: GameVersion, isGroup: boolean], MissionManifest | undefined > + getContractIdsForGroupDiscovery: SyncHook<[string[]]> contributeCampaigns: SyncHook< [ campaigns: Campaign[], @@ -389,6 +390,7 @@ export class Controller { newEvent: new SyncHook(), newMetricsEvent: new SyncHook(), getContractManifest: new SyncBailHook(), + getContractIdsForGroupDiscovery: new SyncHook(), contributeCampaigns: new SyncHook(), getSearchResults: new AsyncSeriesHook(), getNextCampaignMission: new SyncBailHook(), @@ -455,6 +457,18 @@ export class Controller { ) await this._loadInternalContracts() + // in h1 (legacy), bull is not the same + this.hooks.getContractManifest.tap( + "PeacockH1Bull", + (id, gameVersion) => { + if ( + gameVersion === "h1" && + id === "42bac555-bbb9-429d-a8ce-f1ffdf94211c" + ) { + return _legacyBull + } + }, + ) this.challengeService = new ChallengeService(this) this.masteryService = new MasteryService() @@ -492,7 +506,7 @@ export class Controller { private _getETALocations(): void { for (const cId of orderedETAs) { - const contract = this.resolveContract(cId, true) + const contract = this.resolveContract(cId, "h3", true) if (!contract) { continue @@ -504,7 +518,7 @@ export class Controller { ) for (const lId of contract.Metadata.GroupDefinition.Order) { - const level = this.resolveContract(lId, false) + const level = this.resolveContract(lId, "h3", false) if (!level) { continue @@ -580,13 +594,18 @@ export class Controller { return manifest } - private getGroupContract(json: MissionManifest) { + private getGroupContract( + json: MissionManifest, + gameVersion: GameVersion, + ): MissionManifest { if (escalationTypes.includes(json.Metadata.Type)) { if (!json.Metadata.InGroup) { return json } - return this.resolveContract(json.Metadata.InGroup) ?? json + return ( + this.resolveContract(json.Metadata.InGroup, gameVersion) ?? json + ) } return json @@ -596,28 +615,48 @@ export class Controller { * Get a contract by its ID. * * Order of precedence: - * 1. Plugins ({@link addMission} or the `getMissionManifest` hook) - * 2. Peacock internal contracts storage + * 1. Plugins ({@link addMission} or the `getContractManifest` hook). + * 2. Peacock internal contracts storage. * 3. Files in the `contracts` folder. * * @param id The contract's ID. + * @param gameVersion The game version. * @param getGroup When `id` points one of the levels in a contract group, controls whether to get the group contract instead of the individual mission. Defaulted to false. WARNING: If you set this to true, what is returned is not what is pointed to by the inputted `id`. * @returns The mission manifest object, or undefined if it wasn't found. */ public resolveContract( id: string | undefined, + gameVersion: GameVersion, getGroup = false, ): MissionManifest | undefined { if (!id) { return undefined } - const optionalPluginJson = this.hooks.getContractManifest.call(id) + // no matter what, this function is so widely used that it's almost certain + // at some point, it'll be called with either a boolean or undefined as game version, + // because people haven't updated their plugins yet. + // noinspection SuspiciousTypeOfGuard + if (typeof gameVersion === "boolean" || gameVersion === undefined) { + gameVersion = "h3" + log( + LogLevel.WARN, + `Game version not specified. This plugin needs to be updated! Assuming h3.`, + "Contracts", + ) + log(LogLevel.TRACE, `No game version.`, "Contracts") + } + + const optionalPluginJson = this.hooks.getContractManifest.call( + id, + gameVersion, + getGroup, + ) if (optionalPluginJson) { return fastClone( getGroup - ? this.getGroupContract(optionalPluginJson) + ? this.getGroupContract(optionalPluginJson, gameVersion) : optionalPluginJson, ) } @@ -626,7 +665,9 @@ export class Controller { if (registryJson) { return fastClone( - getGroup ? this.getGroupContract(registryJson) : registryJson, + getGroup + ? this.getGroupContract(registryJson, gameVersion) + : registryJson, ) } @@ -636,7 +677,9 @@ export class Controller { if (openCtJson) { return fastClone( - getGroup ? this.getGroupContract(openCtJson) : openCtJson, + getGroup + ? this.getGroupContract(openCtJson, gameVersion) + : openCtJson, ) } @@ -646,7 +689,9 @@ export class Controller { if (officialJson) { return fastClone( - getGroup ? this.getGroupContract(officialJson) : officialJson, + getGroup + ? this.getGroupContract(officialJson, gameVersion) + : officialJson, ) } @@ -1137,11 +1182,15 @@ export class Controller { scanForGroups(): void { let groupCount = 0 + const discoveryIdPool: string[] = [] + this.hooks.getContractIdsForGroupDiscovery.call(discoveryIdPool) + allGroups: for (const contractId of new Set([ ...Object.keys(internalContracts), ...this.hooks.getContractManifest.allTapNames, + ...discoveryIdPool, ])) { - const contract = this.resolveContract(contractId) + const contract = this.resolveContract(contractId, "h3") if (!contract?.Metadata?.GroupDefinition) { continue @@ -1154,7 +1203,7 @@ export class Controller { const order = contract.Metadata.GroupDefinition.Order while (i + 1 <= order.length) { - const next = this.resolveContract(order[i]) + const next = this.resolveContract(order[i], "h3") if (!next) { log( @@ -1208,7 +1257,7 @@ export function contractIdToHitObject( gameVersion: GameVersion, userId: string, ): Hit | undefined { - const contract = controller.resolveContract(contractId) + const contract = controller.resolveContract(contractId, gameVersion) if (!contract) { return undefined @@ -1249,6 +1298,7 @@ export function contractIdToHitObject( { type: ChallengeFilterType.ParentLocation, parent: parentLocation?.Id, + gameVersion, pro1Filter: Pro1FilterType.Ignore, }, parentLocation?.Id, diff --git a/components/eventHandler.ts b/components/eventHandler.ts index 50a95edba..4a4a0db3a 100644 --- a/components/eventHandler.ts +++ b/components/eventHandler.ts @@ -71,6 +71,7 @@ import { ManifestScoringModule, } from "./types/scoring" import { deepmerge } from "deepmerge-ts" +import assert from "assert" const eventRouter = Router() @@ -302,12 +303,9 @@ export function newSession( ): void { const timestamp = new Date() - const contract = controller.resolveContract(contractId) + const contract = controller.resolveContract(contractId, gameVersion) - if (!contract) { - log(LogLevel.ERROR, `Failed to load ${contractId}`) - throw new Error("no ct") - } + assert.ok(contract, `Failed to load ${contractId}`) if (difficulty === 0 && contractTypes.includes(contract.Metadata.Type)) { log( @@ -546,7 +544,10 @@ function contractFailed( ): void { session.markedTargets.clear() - const json = controller.resolveContract(session.contractId)! + const json = controller.resolveContract( + session.contractId, + session.gameVersion, + )! const userData = getUserData(session.userId, session.gameVersion) let realName: string @@ -677,7 +678,10 @@ function saveEvents( session.duration = event.Timestamp session.lastUpdate = new Date() - const contract = controller.resolveContract(session.contractId) + const contract = controller.resolveContract( + session.contractId, + gameVersion, + ) const contractType = contract?.Metadata?.Type?.toLowerCase() controller.hooks.newEvent.call( diff --git a/components/livesplit/liveSplitManager.ts b/components/livesplit/liveSplitManager.ts index f1e54e14d..2b0bf7ca4 100644 --- a/components/livesplit/liveSplitManager.ts +++ b/components/livesplit/liveSplitManager.ts @@ -37,6 +37,7 @@ export class LiveSplitManager { private _inValidCampaignRun: boolean private _currentMission: string | undefined private _currentMissionTotalTime: number + private _currentMissionGameVersion: GameVersion private _campaignTotalTime: number private _completedMissions: string[] private _timeCalcEntries: LiveSplitTimeCalcEntry[] @@ -53,6 +54,7 @@ export class LiveSplitManager { this._currentMissionTotalTime = 0 this._campaignTotalTime = 0 this._raceMode = undefined + this._currentMissionGameVersion = "h3" this._liveSplitClient = new LiveSplitClient("127.0.0.1:16834") } @@ -76,11 +78,13 @@ export class LiveSplitManager { contractId: string, gameVersion: GameVersion, userId: string, - ) { + ): Promise { if (!this._checkInit()) { return } + this._currentMissionGameVersion = gameVersion + const campaign = getCampaignMissions(contractId, gameVersion, userId) if (campaign === undefined) { @@ -164,7 +168,7 @@ export class LiveSplitManager { } } - async failMission(attemptTime: Seconds) { + async failMission(attemptTime: Seconds): Promise { if (!this._checkInit()) { return } @@ -176,13 +180,18 @@ export class LiveSplitManager { ) const computedTime = this._addMissionTime(attemptTime) - this._addTimeCalcEntry(this._currentMission, computedTime, false) + this._addTimeCalcEntry( + this._currentMission, + this._currentMissionGameVersion, + computedTime, + false, + ) LiveSplitManager._logAttempt(computedTime) await this._pushGameTime() } } - async completeMission(attemptTime: Seconds) { + async completeMission(attemptTime: Seconds): Promise { LiveSplitManager._logAttempt(attemptTime) if (!this._checkInit()) { @@ -196,7 +205,12 @@ export class LiveSplitManager { ) const computedTime = this._addMissionTime(attemptTime) - this._addTimeCalcEntry(this._currentMission, computedTime, true) + this._addTimeCalcEntry( + this._currentMission, + this._currentMissionGameVersion, + computedTime, + true, + ) log( LogLevel.INFO, `Total mission time with resets: ${this._currentMissionTotalTime}`, @@ -256,7 +270,7 @@ export class LiveSplitManager { * PRIVATE METHODS */ - async init() { + async init(): Promise { if (!getFlag("liveSplit")) { this._initializationAttempted = true @@ -311,7 +325,7 @@ export class LiveSplitManager { return this._raceMode as boolean } - private async _invalidateRun(contractId: string) { + private async _invalidateRun(contractId: string): Promise { log( LogLevel.INFO, "Entered invalid mission for current campaign state, invalidating autosplitter run. Start first mission of a campaign to reset and start a new run.", @@ -379,7 +393,7 @@ export class LiveSplitManager { ].includes(startLocationId) } - private async _setGameTime(totalTime: Seconds) { + private async _setGameTime(totalTime: Seconds): Promise { // IMPORTANT to floor to int before sending to livesplit or else parsing will fail silently... const flooredTime = Math.floor(totalTime) const minutes = Math.floor(flooredTime / 60) @@ -420,8 +434,11 @@ export class LiveSplitManager { await this._setGameTime(this._campaignTotalTime) } - private _getMissionLocationName(contractId: string): string | undefined { - const contract = controller.resolveContract(contractId) + private _getMissionLocationName( + contractId: string, + gameVersion: GameVersion, + ): string | undefined { + const contract = controller.resolveContract(contractId, gameVersion) if (!contract) { return undefined @@ -436,10 +453,11 @@ export class LiveSplitManager { private _addTimeCalcEntry( contractId: string, + gameVersion: GameVersion, time: Seconds, isCompleted: boolean, ) { - const location = this._getMissionLocationName(contractId) + const location = this._getMissionLocationName(contractId, gameVersion) if (!location) return diff --git a/components/loggingInterop.ts b/components/loggingInterop.ts index 64a1da2f7..d4fe7c7ad 100644 --- a/components/loggingInterop.ts +++ b/components/loggingInterop.ts @@ -21,14 +21,6 @@ import picocolors from "picocolors" import winston from "winston" import "winston-daily-rotate-file" -const isDebug = ["*", "true", "peacock", "yes"].includes( - process.env.DEBUG || "false", -) - -const isTest = ["*", "true", "peacock", "yes"].includes( - process.env.TEST || "false", -) - /** * Represents the different log levels. */ @@ -51,7 +43,7 @@ export enum LogLevel { */ DEBUG = "debug", /** - * For outputting stacktraces. + * For outputting stack traces. */ TRACE = "trace", /** @@ -62,16 +54,13 @@ export enum LogLevel { /** * Represents the different internal log categories used by Peacock. + * @internal */ -export enum LogCategory { +export const enum LogCategory { /** * Remove the category from the log */ NONE = "none", - /** - * Set the category to the name of the calling function - */ - CALLER = "caller", /** * Used for logging HTTP request */ @@ -80,9 +69,7 @@ export enum LogCategory { const LOG_LEVEL_NONE = "none" -// NOTE: Tests run in "strict mode", so only enable CALLER by default for debug-mode. -const LOG_CATEGORY_DEFAULT = - isDebug && !isTest ? LogCategory.CALLER : LogCategory.NONE +const LOG_CATEGORY_DEFAULT = LogCategory.NONE const fileLogLevel = process.env.LOG_LEVEL_FILE || LogLevel.SILLY const consoleLogLevel = process.env.LOG_LEVEL_CONSOLE || LogLevel.SILLY @@ -163,6 +150,21 @@ export function logDebug(...args: unknown[]): void { log(LogLevel.DEBUG, JSON.stringify(args, undefined, " ")) } +function fixMessage(message: string | unknown | null | undefined): string { + switch (typeof message) { + case "string": + return message + case "object": + return JSON.stringify(message, undefined, 4) + case "undefined": + return "undefined" + case "function": + return "function" + default: + return String(message) + } +} + /** * Outputs a log message to the console. * @@ -178,11 +180,7 @@ export function log( data: string | unknown, category: LogCategory | string = LOG_CATEGORY_DEFAULT, ): void { - if (category === LogCategory.CALLER) { - category = log.caller?.name.toString() || "unknown" - } - - const message = data || "No message specified" + const message = fixMessage(data) const now = new Date() const stampParts: number[] = [ diff --git a/components/menuData.ts b/components/menuData.ts index e7d5d7642..4f124fd6c 100644 --- a/components/menuData.ts +++ b/components/menuData.ts @@ -387,7 +387,11 @@ menuDataRouter.get( } const { contractId } = s - const contractData = controller.resolveContract(contractId, true) + const contractData = controller.resolveContract( + contractId, + req.gameVersion, + true, + ) const userData = getUserData(req.jwt.unique_name, req.gameVersion) assert.ok(contractData, "contract not found") @@ -534,7 +538,10 @@ menuDataRouter.get( const inventory = createInventory(req.jwt.unique_name, req.gameVersion) - const contractData = controller.resolveContract(req.query.contractId) + const contractData = controller.resolveContract( + req.query.contractId, + req.gameVersion, + ) if (!contractData) { log( @@ -636,7 +643,10 @@ menuDataRouter.get( const inventory = createInventory(req.jwt.unique_name, req.gameVersion) - const contractData = controller.resolveContract(req.query.contractId) + const contractData = controller.resolveContract( + req.query.contractId, + req.gameVersion, + ) if (!contractData) { log(LogLevel.WARN, `Unknown contract: ${req.query.contractId}`) @@ -1066,6 +1076,7 @@ menuDataRouter.get( menuDataRouter.get("/contractsearchpage", (req: RequestWithJwt, res) => { const createContractTutorial = controller.resolveContract( contractCreationTutorialId, + req.gameVersion, ) res.json({ @@ -1121,7 +1132,7 @@ menuDataRouter.post( for (const contract of specialContracts) { const userCentric = generateUserCentric( - controller.resolveContract(contract), + controller.resolveContract(contract, req.gameVersion), req.jwt.unique_name, req.gameVersion, ) @@ -1250,7 +1261,7 @@ menuDataRouter.get("/contractcreation/create", (req: RequestWithJwt, res) => { // if for some reason the id is already in use, generate a new one // the math says this is like a one in a billion chance though, I think - while (controller.resolveContract(cUuid)) { + while (controller.resolveContract(cUuid, req.gameVersion)) { cUuid = randomUUID() } @@ -1351,7 +1362,7 @@ const createLoadSaveMiddleware = if (e && !doneContracts.includes(e)) { doneContracts.push(e) - const contract = controller.resolveContract(e) + const contract = controller.resolveContract(e, req.gameVersion) if (!contract) { log(LogLevel.WARN, `Unknown contract in L/S: ${e}`) diff --git a/components/menus/campaigns.ts b/components/menus/campaigns.ts index cd8a2349e..ce671cd64 100644 --- a/components/menus/campaigns.ts +++ b/components/menus/campaigns.ts @@ -19,12 +19,12 @@ import { contractIdToHitObject, controller } from "../controller" import type { Campaign, - GameVersion, - GenSingleMissionFunc, CampaignMission, CampaignVideo, - Video, + GameVersion, + GenSingleMissionFunc, StoryData, + Video, } from "../types/types" import { log, LogLevel } from "../loggingInterop" import { getConfig } from "../configSwizzleManager" @@ -47,7 +47,11 @@ const genSingleMissionFactory = (userId: string): GenSingleMissionFunc => { "Plugin tried to generate mission with no game version", ) - const actualContractData = controller.resolveContract(contractId, true) + const actualContractData = controller.resolveContract( + contractId, + gameVersion, + true, + ) if (!actualContractData) { log(LogLevel.ERROR, `Failed to resolve contract ${contractId}!`) diff --git a/components/menus/destinations.ts b/components/menus/destinations.ts index 7b9c2bb82..6945e8943 100644 --- a/components/menus/destinations.ts +++ b/components/menus/destinations.ts @@ -123,6 +123,7 @@ export function getDestinationCompletion( { type: ChallengeFilterType.ParentLocation, parent: parent.Id, + gameVersion, pro1Filter: Pro1FilterType.Exclude, }, parent.Id, @@ -298,6 +299,7 @@ export function createLocationsData( locData.parents[sublocation.Properties.ParentLocation] const creationContract = controller.resolveContract( sublocation.Properties.CreateContractId!, + gameVersion, ) if (!creationContract && excludeIfNoContracts) { diff --git a/components/menus/favoriteContracts.ts b/components/menus/favoriteContracts.ts index ffceb017b..be5f5fe78 100644 --- a/components/menus/favoriteContracts.ts +++ b/components/menus/favoriteContracts.ts @@ -44,7 +44,10 @@ export function withLookupDialog( return } - const contract = controller.resolveContract(req.query.contractId) + const contract = controller.resolveContract( + req.query.contractId, + req.gameVersion, + ) if (!contract) { res.status(404).send("contract does not exist!") @@ -130,7 +133,10 @@ export function directRoute(req: RequestWithJwt, res: Response): void { return } - const contract = controller.resolveContract(req.params.contractId) + const contract = controller.resolveContract( + req.params.contractId, + req.gameVersion, + ) if (!contract) { res.status(404).send("contract does not exist!") diff --git a/components/menus/hub.ts b/components/menus/hub.ts index 76a70d297..3600d78b3 100644 --- a/components/menus/hub.ts +++ b/components/menus/hub.ts @@ -89,7 +89,10 @@ export function getHubData(gameVersion: GameVersion, userId: string) { const contractCreationTutorial = gameVersion !== "scpc" - ? controller.resolveContract(contractCreationTutorialId)! + ? controller.resolveContract( + contractCreationTutorialId, + gameVersion, + )! : undefined const locations = getVersionedConfig( diff --git a/components/menus/planning.ts b/components/menus/planning.ts index 81abb6724..4a511c492 100644 --- a/components/menus/planning.ts +++ b/components/menus/planning.ts @@ -28,7 +28,7 @@ import type { UserProfile, } from "../types/types" import { log, LogLevel } from "../loggingInterop" -import { _legacyBull, controller } from "../controller" +import { controller } from "../controller" import { escalationTypes, getLevelCount, @@ -129,16 +129,7 @@ export async function getPlanningData( } } - let contractData: MissionManifest | undefined - - if ( - gameVersion === "h1" && - contractId === "42bac555-bbb9-429d-a8ce-f1ffdf94211c" - ) { - contractData = _legacyBull - } else { - contractData = controller.resolveContract(contractId) - } + let contractData = controller.resolveContract(contractId, gameVersion) if (!contractData) { return { @@ -169,7 +160,7 @@ export async function getPlanningData( // now reassign properties and continue contractId = group["1"] - contractData = controller.resolveContract(contractId) + contractData = controller.resolveContract(contractId, gameVersion) } if (!contractData) { @@ -195,7 +186,10 @@ export async function getPlanningData( contractData.Metadata.InGroup ?? contractData.Metadata.Id if (escalation) { - const groupContractData = controller.resolveContract(escalationGroupId) + const groupContractData = controller.resolveContract( + escalationGroupId, + gameVersion, + ) if (!groupContractData) { log(LogLevel.ERROR, `Not found: ${contractId}, planning esc group`) @@ -225,7 +219,7 @@ export async function getPlanningData( assert(typeof newLevelId === "string", "newLevelId is not a string") - contractData = controller.resolveContract(newLevelId) + contractData = controller.resolveContract(newLevelId, gameVersion) } } diff --git a/components/menus/playnext.ts b/components/menus/playnext.ts index b3eef71bb..95bee4aaa 100644 --- a/components/menus/playnext.ts +++ b/components/menus/playnext.ts @@ -112,7 +112,7 @@ export function createPlayNextMission( Content: { ContractId: contractId, UserCentricContract: generateUserCentric( - controller.resolveContract(contractId), + controller.resolveContract(contractId, gameVersion), userId, gameVersion, ), @@ -143,14 +143,15 @@ export type PlayNextCategory = { /** * Generates tiles for recommended mission stories given a contract ID. - * * @param contractId The contract ID. + * @param gameVersion The game's version. * @returns The tile object. */ export function createMainOpportunityTile( contractId: string, + gameVersion: GameVersion, ): PlayNextCategory { - const contractData = controller.resolveContract(contractId) + const contractData = controller.resolveContract(contractId, gameVersion) const missionStories = getConfig>( "MissionStories", @@ -233,7 +234,7 @@ export function getGamePlayNextData( ) } - cats.push(createMainOpportunityTile(contractId)) + cats.push(createMainOpportunityTile(contractId, gameVersion)) } const pzIdIndex = orderedPZMissions.indexOf(contractId) diff --git a/components/menus/stashpoints.ts b/components/menus/stashpoints.ts index 68a37bd58..ef90b6eef 100644 --- a/components/menus/stashpoints.ts +++ b/components/menus/stashpoints.ts @@ -134,7 +134,7 @@ export function getModernStashData( let contractData: MissionManifest | undefined = undefined if (query.contractid) { - contractData = controller.resolveContract(query.contractid) + contractData = controller.resolveContract(query.contractid, gameVersion) } const inventory = createInventory( @@ -264,7 +264,10 @@ export function getLegacyStashData( return undefined } - const contractData = controller.resolveContract(query.contractid) + const contractData = controller.resolveContract( + query.contractid, + gameVersion, + ) if (!contractData) { return undefined diff --git a/components/multiplayer/multiplayerMenuData.ts b/components/multiplayer/multiplayerMenuData.ts index 184f75874..6cf6209c6 100644 --- a/components/multiplayer/multiplayerMenuData.ts +++ b/components/multiplayer/multiplayerMenuData.ts @@ -134,7 +134,7 @@ multiplayerMenuDataRouter.get( Presets: presets, UserCentricContracts: [...contractIds].map((contractId) => { return generateUserCentric( - controller.resolveContract(contractId), + controller.resolveContract(contractId, req.gameVersion), req.jwt.unique_name, req.gameVersion, ) diff --git a/components/multiplayer/multiplayerService.ts b/components/multiplayer/multiplayerService.ts index 9861ef97b..befaa4b4a 100644 --- a/components/multiplayer/multiplayerService.ts +++ b/components/multiplayer/multiplayerService.ts @@ -110,7 +110,7 @@ multiplayerRouter.post( const userCentrics = contractIds .map((id) => generateUserCentric( - controller.resolveContract(id), + controller.resolveContract(id, req.gameVersion), req.jwt.unique_name, req.gameVersion, ), diff --git a/components/profileHandler.ts b/components/profileHandler.ts index 07d61f3c2..d3a5f727b 100644 --- a/components/profileHandler.ts +++ b/components/profileHandler.ts @@ -476,7 +476,11 @@ profileRouter.post( return res.status(404).send("invalid contract") } - const json = controller.resolveContract(req.body.contractId, true) + const json = controller.resolveContract( + req.body.contractId, + req.gameVersion, + true, + ) if (!json) { log( diff --git a/components/scoreHandler.ts b/components/scoreHandler.ts index 2261c1b58..da1d74702 100644 --- a/components/scoreHandler.ts +++ b/components/scoreHandler.ts @@ -637,6 +637,7 @@ export async function getMissionEndData( // Resolve contract data const contractData = controller.resolveContract( sessionDetails.contractId, + gameVersion, false, ) @@ -661,7 +662,9 @@ export async function getMissionEndData( IsEscalation: true, } - const levelCount = getLevelCount(controller.resolveContract(eGroupId)) + const levelCount = getLevelCount( + controller.resolveContract(eGroupId, gameVersion), + ) escalationCompletion: if ( userData.Extensions.PeacockEscalations[eGroupId] === levelCount @@ -754,6 +757,7 @@ export async function getMissionEndData( { type: ChallengeFilterType.ParentLocation, parent: locationParentId, + gameVersion, pro1Filter: contractData.Metadata.Difficulty === "pro1" ? Pro1FilterType.Only