Skip to content

Commit

Permalink
refactor: game versioned missionsInLocations and fix certain contract…
Browse files Browse the repository at this point in the history
…s when resolved

Fixes #510 #505 #504
  • Loading branch information
AnthonyFuller committed Jan 4, 2025
1 parent a9230a8 commit 3bfe2ba
Show file tree
Hide file tree
Showing 8 changed files with 757 additions and 401 deletions.
20 changes: 13 additions & 7 deletions components/candle/challengeService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -959,15 +959,21 @@ export class ChallengeService extends ChallengeRegistry {

let contracts = isSniperLocation(child)
? // @ts-expect-error This is fine - we know it will be there
this.controller.missionsInLocations.sniper[child]
: // @ts-expect-error This is fine - we know it will be there
(this.controller.missionsInLocations[child] ?? [])
this.controller.missionsInLocations[gameVersion].sniper[child]
: // @ts-expect-error This is fine - we can index this
(this.controller.missionsInLocations[gameVersion][child] ?? [])
.concat(
// @ts-expect-error This is fine - we know it will be there
this.controller.missionsInLocations.escalations[child],
// @ts-expect-error This is fine - we can index this
this.controller.missionsInLocations[gameVersion]
.escalations[child] ?? [],
)
.concat(
gameVersion === "h3"
? // @ts-expect-error This is fine - we know it will be there
this.controller.missionsInLocations[gameVersion]
.arcade[child]
: [],
)
// @ts-expect-error This is fine - we know it will be there
.concat(this.controller.missionsInLocations.arcade[child])

if (!contracts) {
contracts = []
Expand Down
14 changes: 0 additions & 14 deletions components/contracts/escalations/escalationService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,20 +27,6 @@ import type {
import { getUserData } from "../../databaseHandler"
import { log, LogLevel } from "../../loggingInterop"

/**
* Put a group id in here to hide it from the menus on 2016.
* This should only be used if:
* - The content is custom.
* - The content is on a 2016 map.
*/
export const no2016 = [
"0cceeecb-c8fe-42a4-aee4-d7b575f56a1b",
"9e0188e8-bdad-476c-b4ce-2faa5d2be56c",
"115425b1-e797-47bf-b517-410dc7507397",
"74415eca-d01e-4070-9bc9-5ef9b4e8f7d2",
"07bbf22b-d6ae-4883-bec2-122eeeb7b665",
]

/**
* An array of contract types to determine whether the escalation service
* should be used.
Expand Down
2 changes: 1 addition & 1 deletion components/contracts/hitsCategoryService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ export class HitsCategoryService {
const nEscalations: string[] = []

for (const escalations of Object.values(
missionsInLocations.escalations,
missionsInLocations[gameVersion].escalations,
)) {
for (const id of escalations) {
const contract = controller.resolveContract(
Expand Down
906 changes: 615 additions & 291 deletions components/contracts/missionsInLocation.ts

Large diffs are not rendered by default.

87 changes: 71 additions & 16 deletions components/controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -626,6 +626,47 @@ export class Controller {
return json
}

/**
* Fixes a contract based on game version.
*
* An example of this is the location for Holiday Hoarders changing in
* HITMAN 3 thus breaking the contract in standalone 2016.
*
* @param contract The contract to fix.
* @param gameVersion The game version.
* @returns The fixed contract.
* @private
*/
private fixContract(
contract: MissionManifest,
gameVersion: GameVersion,
): MissionManifest {
switch (gameVersion) {
case "h1": {
if (contract.Metadata.Location === "LOCATION_PARIS_NOEL")
contract.Metadata.Location = "LOCATION_PARIS"

break
}
case "h2": {
if (contract.Metadata.Location === "LOCATION_PARIS_NOEL")
contract.Metadata.Location = "LOCATION_PARIS"

if (contract.Metadata.Location === "LOCATION_HOKKAIDO_MAMUSHI")
contract.Metadata.Location = "LOCATION_HOKKAIDO"

// Fix The Jeffrey Consolation
if (contract.Data.Bricks)
contract.Data.Bricks = contract.Data.Bricks.filter(
(brick) =>
!brick.includes("override_constantjeff.brick"),
)
}
}

return contract
}

/**
* Get a contract by its ID.
*
Expand Down Expand Up @@ -669,6 +710,7 @@ export class Controller {
)

if (optionalPluginJson) {
// We skip fixing plugins as we assume they know what they're doing.
return fastClone(
getGroup
? this.getGroupContract(optionalPluginJson, gameVersion)
Expand All @@ -679,10 +721,13 @@ export class Controller {
const registryJson: MissionManifest | undefined = internalContracts[id]

if (registryJson) {
return fastClone(
getGroup
? this.getGroupContract(registryJson, gameVersion)
: registryJson,
return this.fixContract(
fastClone(
getGroup
? this.getGroupContract(registryJson, gameVersion)
: registryJson,
),
gameVersion,
)
}

Expand All @@ -691,10 +736,13 @@ export class Controller {
: undefined

if (openCtJson) {
return fastClone(
getGroup
? this.getGroupContract(openCtJson, gameVersion)
: openCtJson,
return this.fixContract(
fastClone(
getGroup
? this.getGroupContract(openCtJson, gameVersion)
: openCtJson,
),
gameVersion,
)
}

Expand All @@ -703,10 +751,13 @@ export class Controller {
: undefined

if (officialJson) {
return fastClone(
getGroup
? this.getGroupContract(officialJson, gameVersion)
: officialJson,
return this.fixContract(
fastClone(
getGroup
? this.getGroupContract(officialJson, gameVersion)
: officialJson,
),
gameVersion,
)
}

Expand Down Expand Up @@ -738,24 +789,28 @@ export class Controller {
*
* @param groupContract The escalation group contract, ALL levels must have the Id of this in Metadata.InGroup
* @param locationId The location of the escalation's ID.
* @param gameVersion The game version to add the escalation to.
* @param levels The escalation's levels.
*/
public addEscalation(
groupContract: MissionManifest,
locationId: string,
gameVersion: GameVersion,
...levels: MissionManifest[]
): void {
const fixedLevels = [...levels].filter(Boolean)

this.addMission(groupContract)
fixedLevels.forEach((level) => this.addMission(level))

type K = keyof typeof this.missionsInLocations.escalations
type K =
keyof (typeof this.missionsInLocations)[GameVersion]["escalations"]

// eslint-disable-next-line @typescript-eslint/no-explicit-any
this.missionsInLocations.escalations[locationId as K] ??= <any>[]
// @ts-expect-error This is fine.
this.missionsInLocations[gameVersion].escalations[locationId as K] ??=
[]

const a = this.missionsInLocations.escalations[
const a = this.missionsInLocations[gameVersion].escalations[
locationId as K
] as string[]

Expand Down
113 changes: 43 additions & 70 deletions components/menus/destinations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,6 @@ import { ChallengeFilterType, Pro1FilterType } from "../candle/challengeHelpers"
import { GetDestinationQuery } from "../types/gameSchemas"
import { createInventory } from "../inventory"
import { log, LogLevel } from "../loggingInterop"
import { no2016 } from "../contracts/escalations/escalationService"
import { missionsInLocations } from "../contracts/missionsInLocation"
import assert from "assert"
import { translateEntitlements } from "../ownership"

Expand Down Expand Up @@ -420,13 +418,18 @@ export function getDestination(
(subLocation) => subLocation.Properties.ParentLocation === LOCATION,
)

if (query.difficulty === "pro1") {
type Cast = keyof typeof controller.missionsInLocations.pro1
// I know it's redundant to check game version here, but it's just for typescript.
if (query.difficulty === "pro1" && gameVersion === "h1") {
type Cast = keyof (typeof controller.missionsInLocations)["h1"]["pro1"]

const obj: LocationMissionData = {
Location: locationData,
SubLocation: locationData,
Missions: [controller.missionsInLocations.pro1[LOCATION as Cast]]
Missions: [
controller.missionsInLocations[gameVersion].pro1[
LOCATION as Cast
],
]
.map((id) => contractIdToHitObject(id, gameVersion, userId))
.filter(Boolean) as Hit[],
SarajevoSixMissions: [],
Expand All @@ -452,46 +455,41 @@ export function getDestination(

const escalations: Hit[] = []

type ECast = keyof typeof controller.missionsInLocations.escalations
type ECast =
keyof (typeof controller.missionsInLocations)[GameVersion]["escalations"]
// every unique escalation from the sublocation
const allUniqueEscalations: string[] = [
...(gameVersion === "h1" && e.Id === "LOCATION_ICA_FACILITY"
? controller.missionsInLocations.escalations[
"LOCATION_ICA_FACILITY_SHIP"
]
: []),
...new Set<string>(
controller.missionsInLocations.escalations[e.Id as ECast] || [],
),
]
const escalationIds: string[] =
controller.missionsInLocations[gameVersion].escalations[
e.Id as ECast
] ?? []

for (const escalation of allUniqueEscalations) {
if (gameVersion === "h1" && no2016.includes(escalation)) continue

const details = contractIdToHitObject(
escalation,
gameVersion,
userId,
)
for (const id of escalationIds) {
const details = contractIdToHitObject(id, gameVersion, userId)

if (details) {
escalations.push(details)
}
}

const sniperMissions: Hit[] = []
type SCast = keyof typeof controller.missionsInLocations.sniper

for (const sniperMission of controller.missionsInLocations.sniper[
e.Id as SCast
] ?? []) {
const hit = contractIdToHitObject(
sniperMission,
gameVersion,
userId,
)
if (gameVersion !== "h1") {
type SCast = keyof (typeof controller.missionsInLocations)[Exclude<
GameVersion,
"h1"
>]["sniper"]

for (const sniperMission of controller.missionsInLocations[
gameVersion
].sniper[e.Id as SCast] ?? []) {
const hit = contractIdToHitObject(
sniperMission,
gameVersion,
userId,
)

if (hit) sniperMissions.push(hit)
if (hit) sniperMissions.push(hit)
}
}

const obj = {
Expand All @@ -508,49 +506,24 @@ export function getDestination(
}

const types = [
...((gameVersion === "h1" &&
// @ts-expect-error Hack.
missionsInLocations.sarajevo["h2016enabled"]) ||
gameVersion === "h3"
? [["sarajevo", "SarajevoSixMissions"]]
: []),
[undefined, "Missions"],
["elusive", "ElusiveMissions"],
["sarajevo", "SarajevoSixMissions"],
]

type TCast = keyof (typeof controller.missionsInLocations)[GameVersion]

for (const t of types) {
let theMissions: string[] | undefined = !t[0] // no specific type
? // @ts-expect-error Yup.
controller.missionsInLocations[e.Id]
: // @ts-expect-error Yup.
controller.missionsInLocations[t[0]][e.Id]

// edge case: ica facility in h1 was only 1 sublocation, so we merge
// these into a single array
if (
gameVersion === "h1" &&
!t[0] &&
LOCATION === "LOCATION_PARENT_ICA_FACILITY"
) {
theMissions = [
...controller.missionsInLocations
.LOCATION_ICA_FACILITY_ARRIVAL,
...controller.missionsInLocations
.LOCATION_ICA_FACILITY_SHIP,
...controller.missionsInLocations.LOCATION_ICA_FACILITY,
]
}
const theMissions: string[] | undefined = (
!t[0] // no specific type
? controller.missionsInLocations[gameVersion][e.Id as ECast]
: controller.missionsInLocations[gameVersion][
t[0] as TCast
][e.Id as ECast]
) as string[] | undefined

if (theMissions) {
for (const c of theMissions.filter(
// removes snow festival on h1
(m) =>
m &&
!(
gameVersion === "h1" &&
m === "c414a084-a7b9-43ce-b6ca-590620acd87e"
),
)) {
for (const c of theMissions) {
const mission = contractIdToHitObject(
c,
gameVersion,
Expand Down
7 changes: 5 additions & 2 deletions components/smfSupport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,9 +156,12 @@ export class SMFSupport {
const placeBefore = contractData.SMF?.destinations.placeBefore
const placeAfter = contractData.SMF?.destinations.placeAfter
// @ts-expect-error I know what I'm doing.
const inLocation = (this.controller.missionsInLocations[location] ??
const inLocation = (this.controller.missionsInLocations["h3"][
location
] ??
// @ts-expect-error I know what I'm doing.
(this.controller.missionsInLocations[location] = [])) as string[]
(this.controller.missionsInLocations["h3"][location] =
[])) as string[]

if (placeBefore) {
const index = inLocation.indexOf(placeBefore)
Expand Down
9 changes: 9 additions & 0 deletions components/types/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1615,3 +1615,12 @@ export type OfficialSublocation = {
Xp: number
ActionXp: number
}

export type MILLocations = {
[location in `LOCATION_${string}`]: string[] | string
}

export type MissionsInLocation = Record<
GameVersion,
MILLocations & { [key: string]: MILLocations | string[] }
>

0 comments on commit 3bfe2ba

Please sign in to comment.