Skip to content

Commit

Permalink
refactor: general challenge/mastery type improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
RDIL committed Aug 13, 2024
1 parent 342858d commit 3642c44
Show file tree
Hide file tree
Showing 8 changed files with 71 additions and 30 deletions.
1 change: 1 addition & 0 deletions components/2016/legacyMenuData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ legacyMenuDataRouter.get(
req.query.locationId,
req.gameVersion,
req.jwt.unique_name,
req.query.difficulty,
)

const location = getParentLocationByName(
Expand Down
4 changes: 2 additions & 2 deletions components/candle/challengeService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -350,8 +350,8 @@ export abstract class ChallengeRegistry {
* It iterates over all the challenges for the specified game version and for each challenge, it checks if there are any unlockables (Drops).
* If there are unlockables, it adds them to the accumulator object with the dropId as the key and the challenge Id as the value.
*
* @param gameVersion - The version of the game for which to retrieve the unlockables.
* @returns {Record<string, string>} - An object where each key is an unlockable's id (dropId) and the corresponding value is the id of the challenge that unlocks it.
* @param gameVersion The version of the game for which to retrieve the unlockables.
* @returns An object where each key is an unlockable's id (dropId) and the corresponding value is the id of the challenge that unlocks it.
*/
getChallengesUnlockables(gameVersion: GameVersion): Record<string, string> {
return [...this.challenges[gameVersion].values()].reduce(
Expand Down
26 changes: 22 additions & 4 deletions components/candle/masteryService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,13 @@ export class MasteryService {
)[0]
}

/**
* Returns mastery data for a location (either a parent or sub-location). For mastery data of a destination, use {@link getMasteryDataForDestination}.
* @param locationId The location's ID.
* @param gameVersion The game version.
* @param userId The user's ID.
* @returns The mastery data.
*/
getMasteryDataForLocation(
locationId: string,
gameVersion: GameVersion,
Expand All @@ -180,13 +187,14 @@ export class MasteryService {
}

/**
* Get generic completion data stored in a user's profile. Called by both `getLocationCompletion` and `getFirearmCompletion`.
* Get generic completion data stored in a user's profile.
* @param userId The id of the user.
* @param gameVersion The game version.
* @param locationParentId The location's parent ID, used for progression storage @since v7.0.0
* @param maxLevel The max level for this progression.
* @param levelToXpRequired A function to get the XP required for a level.
* @param subPackageId The subpackage id you want.
* @returns The completion data, minus any location-specific fields.
*/
private getCompletionData(
userId: string,
Expand All @@ -199,9 +207,13 @@ export class MasteryService {
// Get the user profile
const userProfile = getUserData(userId, gameVersion)

assert.ok(userProfile, `user profile ${userId} not found`)

const parent =
userProfile.Extensions.progression.Locations[locationParentId]

assert.ok(parent, `parent ${locationParentId} not found`)

const completionData: ProgressionData = subPackageId
? (parent[subPackageId as keyof typeof parent] as ProgressionData)
: (parent as ProgressionData)
Expand Down Expand Up @@ -268,10 +280,14 @@ export class MasteryService {
? getConfig<Unlockable[]>("SniperUnlockables", false).find(
(unlockable) => unlockable.Id === subPackageId,
)?.Properties.Name
: undefined
: null

if (isSniper) {
assert.ok(name, `unlockable ${subPackageId} not found`)
}

return {
...this.getCompletionData(
...this.getGenericCompletionData(
userId,
gameVersion,
locationParentId,
Expand Down Expand Up @@ -360,7 +376,9 @@ export class MasteryService {

// Add the main unlockable to the set to generate the page properly
if (isSniper) {
masteryPkg.SubPackages.forEach((pkg) => dropIdSet.add(pkg.Id))
for (const pkg of masteryPkg.SubPackages) {
dropIdSet.add(pkg.Id)
}
}

if (!dropIdSet || dropIdSet.size === 0) {
Expand Down
25 changes: 11 additions & 14 deletions components/candle/progressionService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,14 +49,14 @@ export class ProgressionService {
contractSession: ContractSession,
userProfile: UserProfile,
location: string,
sniperUnlockable?: string,
) {
subPackage?: string,
): void {
// Total XP for profile XP is the total sum of the action and mastery XP
const xp = actionXp + masteryXp

// Grants profile XP, if this is at contract end where we're adding the final
// sniper score, don't grant it to the profile, otherwise you'll get 1,000+ levels.
if (!sniperUnlockable) {
if (!subPackage) {
this.grantUserXp(xp, contractSession, userProfile)
}

Expand All @@ -67,7 +67,7 @@ export class ProgressionService {
contractSession,
userProfile,
location,
sniperUnlockable,
subPackage,
)

// Award provided drops. E.g. From challenges. Don't run this function
Expand Down Expand Up @@ -104,7 +104,7 @@ export class ProgressionService {
masteryLocationDrops: MasteryPackageDrop[],
minLevel: number,
maxLevel: number,
) {
): Unlockable[] {
const unlockableIds = masteryLocationDrops
.filter((drop) => drop.Level > minLevel && drop.Level <= maxLevel)
.map((drop) => drop.Id)
Expand Down Expand Up @@ -135,7 +135,7 @@ export class ProgressionService {
}
}

return unlockables
return unlockables.filter((u) => !!u) as Unlockable[]
}

// Grants xp and rewards to mastery progression on contract location
Expand All @@ -146,11 +146,11 @@ export class ProgressionService {
userProfile: UserProfile,
location: string,
sniperUnlockable?: string,
): boolean {
): void {
const contract = controller.resolveContract(contractSession.contractId)

if (!contract) {
return false
return
}

const subLocation = getSubLocationByName(
Expand All @@ -163,7 +163,7 @@ export class ProgressionService {
: location ?? contract.Metadata.Location

if (!parentLocationId) {
return false
return
}

// We can't grant sniper XP here as it's based on final score, so we skip updating mastery
Expand All @@ -185,6 +185,7 @@ export class ProgressionService {
const isEvergreenContract = contract.Metadata.Type === "evergreen"

if (masteryData) {
assert.ok(locationData, `location ${location} not found`)
const previousLevel = locationData.Level

locationData.Xp = clampValue(
Expand Down Expand Up @@ -248,8 +249,6 @@ export class ProgressionService {
profileData.Sublocations[contract.Metadata.Location].Xp += masteryXp
profileData.Sublocations[contract.Metadata.Location].ActionXp +=
actionXp

return true
}

// Grants xp to user profile
Expand All @@ -258,7 +257,7 @@ export class ProgressionService {
xp: number,
contractSession: ContractSession,
userProfile: UserProfile,
): boolean {
): void {
const profileData = userProfile.Extensions.progression.PlayerProfileXP

profileData.Total += xp
Expand All @@ -267,7 +266,5 @@ export class ProgressionService {
1,
getMaxProfileLevel(contractSession.gameVersion),
)

return true
}
}
5 changes: 5 additions & 0 deletions components/inventory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,11 @@ function filterUnlockedContent(
unlockableMasteryData.SubPackageId,
)

assert.ok(
locationData,
`location ${unlockableMasteryData.Location} (subPackageId: ${unlockableMasteryData.SubPackageId}) not found`,
)

const canUnlock = locationData.Level >= unlockableMasteryData.Level

if (canUnlock && unlockable.Type !== "evergreenmastery") {
Expand Down
5 changes: 5 additions & 0 deletions components/scoreHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -662,6 +662,11 @@ export async function getMissionEndData(
}
}

if (gameVersion === "h1") {
// h1 has a separate mastery track for pro1 and normal
query.masteryUnlockableId = contractData.Metadata.Difficulty ?? "normal"
}

// Resolve all opportunities for the location
const opportunities: string[] | null | undefined =
contractData.Metadata.Opportunities
Expand Down
2 changes: 1 addition & 1 deletion components/types/challenges.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export interface SavedChallenge {
LocationId: string
ParentLocationId: string
// H1 challenges do not have Type
Type?: "contract" | string
Type?: "contract" | "global" | "location" | "parentlocation" | string
RuntimeType: "Hit" | string
Xp: number
XpModifier?: unknown
Expand Down
33 changes: 24 additions & 9 deletions components/types/mastery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,17 @@

import { CompletionData, GameVersion, Unlockable } from "./types"

export interface LocationMasteryData {
export type LocationMasteryData = {
Location: Unlockable
MasteryData: MasteryData[]
}

export interface MasteryPackageDrop {
export type MasteryPackageDrop = {
Id: string
Level: number
}

interface MasterySubPackage {
export type MasterySubPackage = {
Id: string
MaxLevel?: number
Drops: MasteryPackageDrop[]
Expand All @@ -49,34 +49,49 @@ export type MasteryPackage = {
GameVersions: GameVersion[]
MaxLevel?: number
HideProgression?: boolean
} & (HasDrop | HasSubPackage)
} & (MasteryPackageDropExt | MasteryPackageSubPackageExt)

type HasDrop = {
/**
* Extends {@link MasteryPackage} with the `Drops` field.
*/
export type MasteryPackageDropExt = {
Drops: MasteryPackageDrop[]
SubPackages?: never
}

type HasSubPackage = {
/**
* Extends {@link MasteryPackage} with the `SubPackages` field.
*/
export type MasteryPackageSubPackageExt = {
Drops?: never
SubPackages: MasterySubPackage[]
}

export interface MasteryData {
export type MasteryData = {
CompletionData: CompletionData
Drops: MasteryDrop[]
Unlockable?: Unlockable
}

export interface MasteryDrop {
export type MasteryDrop = {
IsLevelMarker: boolean
Unlockable: Unlockable
Level: number
IsLocked: boolean
TypeLocaKey: string
}

export interface UnlockableMasteryData {
export type UnlockableMasteryData = {
Location: string
SubPackageId?: string
Level: number
}

export type GenericCompletionData = Omit<
CompletionData,
| "Id"
| "SubLocationId"
| "HideProgression"
| "IsLocationProgression"
| "Name"
>

0 comments on commit 3642c44

Please sign in to comment.