Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Pro1 challenges, h6 & general mastery improvements #502

Merged
merged 20 commits into from
Aug 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
15 changes: 3 additions & 12 deletions components/2016/legacyProfileRouter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import { Router } from "express"
import { controller } from "../controller"
import { json as jsonMiddleware } from "body-parser"
import { uuidRegex } from "../utils"
import { compileRuntimeChallenge } from "../candle/challengeHelpers"
import { compileRuntimeChallengeOnly } from "../candle/challengeHelpers"
import { GetChallengeProgressionBody } from "../types/gameSchemas"

const legacyProfileRouter = Router()
Expand Down Expand Up @@ -74,16 +74,8 @@ legacyProfileRouter.post(
),
)
.flat()
.map(
(challengeData) =>
compileRuntimeChallenge(
challengeData,
controller.challengeService.getPersistentChallengeProgression(
req.jwt.unique_name,
challengeData.Id,
req.gameVersion,
),
).Challenge,
.map((challengeData) =>
compileRuntimeChallengeOnly(challengeData),
RDIL marked this conversation as resolved.
Show resolved Hide resolved
),
)

Expand Down Expand Up @@ -155,7 +147,6 @@ legacyProfileRouter.post(
MustBeSaved: progression.MustBeSaved,
})
}
// TODO: HELP! Please DM rdil if you see this

res.json(challenges)
},
Expand Down
106 changes: 88 additions & 18 deletions components/candle/challengeHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import {
ChallengeProgressionData,
CompiledChallengeIngameData,
CompiledChallengeRuntimeData,
InclusionData,
MissionManifest,
Expand All @@ -27,43 +28,85 @@ import { SavedChallengeGroup } from "../types/challenges"
import { controller } from "../controller"
import { gameDifficulty, isSniperLocation } from "../utils"

/**
* Change a registry challenge to the runtime format (for GetActiveChallenges).
* @param challenge The challenge.
* @returns The runtime challenge.
* @see {@link compileRuntimeChallenge} for the modern variant with progression data.
*/
export function compileRuntimeChallengeOnly(
challenge: RegistryChallenge,
): CompiledChallengeIngameData {
return {
Id: challenge.Id,
GroupId: challenge.inGroup,
Name: challenge.Name,
Type: challenge.RuntimeType || "contract",
Description: challenge.Description,
ImageName: challenge.ImageName,
InclusionData: challenge.InclusionData || undefined,
Definition: challenge.Definition,
Tags: challenge.Tags,
Drops: challenge.Drops,
LastModified: "2021-01-06T23:00:32.0117635", // this is a lie 👍
PlayableSince: null,
PlayableUntil: null,
Xp: challenge.Rewards.MasteryXP || 0,
XpModifier: challenge.XpModifier || {},
}
}

/**
* Change a registry challenge to the runtime format (for GetActiveChallengesAndProgression).
* @param challenge The challenge.
* @param progression The progression data.
* @returns The runtime challenge (including progression data).
* @see {@link compileRuntimeChallengeOnly} for when you only need the challenge data.
*/
export function compileRuntimeChallenge(
challenge: RegistryChallenge,
progression: ChallengeProgressionData,
): CompiledChallengeRuntimeData {
return {
// GetActiveChallengesAndProgression
Challenge: {
Id: challenge.Id,
GroupId: challenge.inGroup,
Name: challenge.Name,
Type: challenge.RuntimeType || "contract",
Description: challenge.Description,
ImageName: challenge.ImageName,
InclusionData: challenge.InclusionData || undefined,
Definition: challenge.Definition,
Tags: challenge.Tags,
Drops: challenge.Drops,
LastModified: "2021-01-06T23:00:32.0117635", // this is a lie 👍
PlayableSince: null,
PlayableUntil: null,
Xp: challenge.Rewards.MasteryXP || 0,
XpModifier: challenge.XpModifier || {},
},
Challenge: compileRuntimeChallengeOnly(challenge),
Progression: progression,
}
}

/**
* How to handle filtering of challenges.
*/
export enum ChallengeFilterType {
/** Note that this option will include global elusive and escalations challenges. */
None = "None",
/** A single contract's challenges. */
Contract = "Contract",
/** Only used for the CAREER -> CHALLENGES page */
Contracts = "Contracts",
/** Only used for the location page, and when calculating location completion */
ParentLocation = "ParentLocation",
}

/**
* How to handle filtering of pro1 challenges.
* Works in conjunction with {@link ChallengeFilterType}, but only if the
* challenge is tagged as pro1 and the challenge filter is met.
*/
export enum Pro1FilterType {
/**
* Only include pro1 challenges.
*/
Only = "Only",
/**
* Include both pro1 and non-pro1 challenges.
*/
Ignore = "Ignore",
/**
* Exclude pro1 challenges.
*/
Exclude = "Exclude",
}

export type ChallengeFilterOptions =
| {
type: ChallengeFilterType.None
Expand All @@ -74,15 +117,18 @@ export type ChallengeFilterOptions =
locationId: string
isFeatured?: boolean
difficulty: number
pro1Filter: Pro1FilterType
}
| {
type: ChallengeFilterType.Contracts
contractIds: string[]
locationId: string
pro1Filter: Pro1FilterType
}
| {
type: ChallengeFilterType.ParentLocation
parent: string
pro1Filter: Pro1FilterType
}

/**
Expand Down Expand Up @@ -127,6 +173,7 @@ export function isChallengeForDifficulty(
* @param locationId The sublocation ID of the challenge.
* @param difficulty The upper bound on the difficulty of the challenges to return.
* @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.
* @returns A boolean value, denoting the result.
*/
Expand All @@ -135,6 +182,7 @@ function isChallengeInContract(
locationId: string,
RDIL marked this conversation as resolved.
Show resolved Hide resolved
difficulty: number,
challenge: RegistryChallenge,
pro1Filter: Pro1FilterType,
forCareer = false,
): boolean {
if (!contractId || !locationId) {
Expand Down Expand Up @@ -193,6 +241,14 @@ function isChallengeInContract(
challenge.LocationId === locationId ||
challenge.LocationId === challenge.ParentLocationId

const isPro1 = challenge.Tags.includes("pro1")

if (isPro1 && pro1Filter === Pro1FilterType.Exclude) {
return false
} else if (!isPro1 && pro1Filter === Pro1FilterType.Only) {
return false
}

return (
isForContract ||
isForContractType ||
Expand All @@ -213,6 +269,7 @@ export function filterChallenge(
options.locationId,
options.difficulty,
challenge,
options.pro1Filter,
)
}
case ChallengeFilterType.Contracts: {
Expand All @@ -223,6 +280,7 @@ export function filterChallenge(
options.locationId,
gameDifficulty.master, // Get challenges of all difficulties
challenge,
options.pro1Filter,
true,
),
)
Expand Down Expand Up @@ -255,12 +313,24 @@ export function filterChallenge(
}

if (challenge.Tags.includes("escalation")) {
if (options.pro1Filter === Pro1FilterType.Only) {
return false
}

return (
!isSniperLocation(options.parent) &&
"LOCATION_PARENT_SNUG" !== options.parent
)
}

const isPro1 = challenge.Tags.includes("pro1")

if (isPro1 && options.pro1Filter === Pro1FilterType.Exclude) {
return false
} else if (!isPro1 && options.pro1Filter === Pro1FilterType.Only) {
return false
}

return true
}
}
Expand Down
26 changes: 22 additions & 4 deletions components/candle/challengeService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ import {
filterChallenge,
inclusionDataCheck,
mergeSavedChallengeGroups,
Pro1FilterType,
} from "./challengeHelpers"
import assert from "assert"
import { getVersionedConfig } from "../configSwizzleManager"
Expand Down Expand Up @@ -109,7 +110,7 @@ export type GlobalChallengeGroup = {
*
* This option is useful when challenge group has to be active for all locations,
* but doesn't have any location-specific challenges.
* However it's advised to create location-specific challenges instead,
* However, it's advised to create location-specific challenges instead,
* as this option will apply the challenge group to all locations and missions,
* including Freelancer and Sniper modes.
*
Expand Down Expand Up @@ -350,8 +351,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 Expand Up @@ -596,6 +597,14 @@ export class ChallengeService extends ChallengeRegistry {
return (progression?.Completed && !progression.Ticked) || false
}

/**
* Get the persistent challenge progression data for a challenge.
* WARNING: slow! Use sparingly.
* @param userId The user's ID.
* @param challengeId The challenge ID.
* @param gameVersion The game version.
* @returns The challenge progression data.
*/
getPersistentChallengeProgression(
userId: string,
challengeId: string,
Expand Down Expand Up @@ -665,7 +674,7 @@ export class ChallengeService extends ChallengeRegistry {
location: string,
challenges: [string, RegistryChallenge[]][],
gameVersion: GameVersion,
) {
): void {
const groups = this.groups[gameVersion].get(location)?.keys() ?? []

for (const groupId of groups) {
Expand Down Expand Up @@ -849,6 +858,10 @@ export class ChallengeService extends ChallengeRegistry {
? "LOCATION_ICA_FACILITY_SHIP"
: contract.Metadata.Location,
isFeatured: contractGroup.Metadata.Type === "featured",
pro1Filter:
contract.Metadata.Difficulty === "pro1"
? Pro1FilterType.Only
: Pro1FilterType.Exclude,
difficulty,
},
levelParentLocation,
Expand Down Expand Up @@ -890,6 +903,7 @@ export class ChallengeService extends ChallengeRegistry {
type: ChallengeFilterType.Contracts,
contractIds: contracts,
locationId: child,
pro1Filter: Pro1FilterType.Exclude,
},
parent,
gameVersion,
Expand Down Expand Up @@ -1249,6 +1263,7 @@ export class ChallengeService extends ChallengeRegistry {
locationParentId: string,
gameVersion: GameVersion,
userId: string,
isPro1: boolean,
): CompiledChallengeTreeCategory[] {
const locationsData = getVersionedConfig<PeacockLocationsData>(
"LocationsData",
Expand All @@ -1270,6 +1285,9 @@ export class ChallengeService extends ChallengeRegistry {
{
type: ChallengeFilterType.ParentLocation,
parent: locationParentId,
pro1Filter: isPro1
? Pro1FilterType.Only
: Pro1FilterType.Exclude,
},
locationParentId,
gameVersion,
Expand Down
Loading