mirror of
https://github.com/thepeacockproject/Peacock
synced 2024-11-29 09:15:11 +01:00
feat: Add API to simplify creation of global challenges (#459)
* feat: Add API to simplify creation of global challenges * feat: Finalize global challenge API style: Renamed `globalGroups` into `globalMergeGroups` to clarify function. docs: Provided docs on new API refactor: Moved classic, elusive and H1/H2 escalations into globalMergeGroups feat: Added `challengeService.registerChallengeList` for easier challenge group registration feat: Added `challengeService.hasGroup` method to check if group was already registered.
This commit is contained in:
parent
4dbc0b39de
commit
a306cc5643
@ -12,6 +12,6 @@ Thanks to Moo for the name suggestion.
|
||||
|
||||
Challenge data reside in files in `contractdata` ending in `_CHALLENGES.json`. When building, the `packResources` function in `buildTasks.mjs` will take all such files and pack them into files in the `resources/challenges` folder.
|
||||
|
||||
Then, when the server starts, challenge data is loaded from the files in the `resources/challenges` folder into the memory, via the `_loadResources` function in `controller.ts`. It calls the `_handleChallengeResources` function that in turn calls `challengeService.registerGroup` and `challengeService.registerChallenge` to register the challenge group and the respective challenges.
|
||||
Then, when the server starts, challenge data is loaded from the files in the `resources/challenges` folder into the memory, via the `_loadResources` function in `controller.ts`. It calls the `_handleChallengeResources` function that in turn calls `challengeService.registerGroup` and `challengeService.registerChallengeList` to register the challenge group and the respective challenges.
|
||||
|
||||
These two registration functions initialize two data structures, `groups: Map<string, Map<string, SavedChallengeGroup>>`, which maps `parentLocationId`s to `Map`s of `groupId`s to `SavedChallengeGroup` objects for this group of this parent location, and `groupContents: Map<string, Map<string, Set<string>>>`, which maps `parentLocationId`s to `Map`s of `groupId`s to `Set`s of challenge Ids in this group of this parent location. All subsequent operations on challenges write to or read from these two `Map`s.
|
||||
|
@ -87,6 +87,38 @@ export type ChallengePack = {
|
||||
Icon: string
|
||||
}
|
||||
|
||||
export type GlobalChallengeGroup = {
|
||||
/**
|
||||
* ID of a challenge group that will have location and global challenges merged.
|
||||
*
|
||||
* Not necessarily should match the group it's being merged with,
|
||||
* but it's highly advised to match them.
|
||||
*/
|
||||
groupId: string
|
||||
/**
|
||||
* The global challenge group location ID from where the global challenges are merged.
|
||||
*/
|
||||
location: string
|
||||
/**
|
||||
* Which game versions are supported by this global challenge group.
|
||||
*/
|
||||
gameVersions: GameVersion[]
|
||||
/**
|
||||
* If set, this global challenge group will be visible in all locations,
|
||||
* regardless if this group has challenges in that location or not.
|
||||
*
|
||||
* 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,
|
||||
* as this option will apply the challenge group to all locations and missions,
|
||||
* including Freelancer and Sniper modes.
|
||||
*
|
||||
* This can be alleviated by providing valid InclusionData filter to challenges,
|
||||
* in order to exclude them from locations you do not wish them to be in.
|
||||
*/
|
||||
allLocations?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* A base class providing challenge registration support.
|
||||
*/
|
||||
@ -186,16 +218,56 @@ export abstract class ChallengeRegistry {
|
||||
],
|
||||
])
|
||||
|
||||
/**
|
||||
* The list of user-made challenge groups that span multiple locations
|
||||
* and should merge their global type challenges merged with location challenges.
|
||||
*
|
||||
* @see GlobalChallengeGroup fields for more information.
|
||||
*/
|
||||
public globalMergeGroups: Map<string, GlobalChallengeGroup> = new Map([
|
||||
[
|
||||
"classic",
|
||||
{
|
||||
gameVersions: ["h1", "h2", "h3", "scpc"],
|
||||
groupId: "classic",
|
||||
location: "GLOBAL_CLASSIC_CHALLENGES",
|
||||
},
|
||||
],
|
||||
[
|
||||
"elusive",
|
||||
{
|
||||
gameVersions: ["h1", "h2", "h3", "scpc"],
|
||||
groupId: "elusive",
|
||||
location: "GLOBAL_ELUSIVES_CHALLENGES",
|
||||
},
|
||||
],
|
||||
[
|
||||
// H2 & H1 have the escalation challenges in "feats"
|
||||
"feats",
|
||||
{
|
||||
gameVersions: ["h1", "h2", "scpc"],
|
||||
groupId: "feats",
|
||||
location: "GLOBAL_ESCALATION_CHALLENGES",
|
||||
},
|
||||
],
|
||||
])
|
||||
|
||||
registerChallenge(
|
||||
challenge: RegistryChallenge,
|
||||
groupId: string,
|
||||
location: string,
|
||||
gameVersion: GameVersion,
|
||||
): void {
|
||||
this.registerChallengeList([challenge], groupId, location, gameVersion)
|
||||
}
|
||||
|
||||
registerChallengeList(
|
||||
challenges: RegistryChallenge[],
|
||||
groupId: string,
|
||||
location: string,
|
||||
gameVersion: GameVersion,
|
||||
): void {
|
||||
const gameChallenges = this.groupContents[gameVersion]
|
||||
challenge.inGroup = groupId
|
||||
challenge.inLocation = location
|
||||
this.challenges[gameVersion].set(challenge.Id, challenge)
|
||||
|
||||
if (!gameChallenges.has(location)) {
|
||||
gameChallenges.set(location, new Map())
|
||||
@ -208,9 +280,14 @@ export abstract class ChallengeRegistry {
|
||||
}
|
||||
|
||||
const set = locationMap.get(groupId)!
|
||||
set.add(challenge.Id)
|
||||
|
||||
this.checkHeuristics(challenge, gameVersion)
|
||||
for (const challenge of challenges) {
|
||||
challenge.inGroup = groupId
|
||||
challenge.inLocation = location
|
||||
this.challenges[gameVersion].set(challenge.Id, challenge)
|
||||
set.add(challenge.Id)
|
||||
this.checkHeuristics(challenge, gameVersion)
|
||||
}
|
||||
}
|
||||
|
||||
registerGroup(
|
||||
@ -227,6 +304,23 @@ export abstract class ChallengeRegistry {
|
||||
gameGroups.get(location)?.set(group.CategoryId, group)
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if `groupId` is already registered for given `location` and `gameVersion`.
|
||||
*
|
||||
* @param groupId The group ID to check
|
||||
* @param location The location group belongs to
|
||||
* @param gameVersion The game version group works in
|
||||
*/
|
||||
hasGroup(
|
||||
groupId: string,
|
||||
location: string,
|
||||
gameVersion: GameVersion,
|
||||
): boolean {
|
||||
return (
|
||||
this.groups[gameVersion]?.get(location)?.get(groupId) !== undefined
|
||||
)
|
||||
}
|
||||
|
||||
getChallengeById(
|
||||
challengeId: string,
|
||||
gameVersion: GameVersion,
|
||||
@ -290,18 +384,6 @@ export abstract class ChallengeRegistry {
|
||||
|
||||
const mainGroup = gameGroups.get(location)?.get(groupId)
|
||||
|
||||
if (groupId === "feats" && gameVersion !== "h3") {
|
||||
if (!mainGroup) {
|
||||
// emergency bailout - shouldn't happen in practice
|
||||
return undefined
|
||||
}
|
||||
|
||||
return mergeSavedChallengeGroups(
|
||||
mainGroup,
|
||||
gameGroups.get("GLOBAL_ESCALATION_CHALLENGES")?.get(groupId),
|
||||
)
|
||||
}
|
||||
|
||||
if (groupId?.includes("featured")) {
|
||||
return gameGroups.get("GLOBAL_FEATURED_CHALLENGES")?.get(groupId)
|
||||
}
|
||||
@ -314,32 +396,24 @@ export abstract class ChallengeRegistry {
|
||||
return gameGroups.get("GLOBAL_ESCALATION_CHALLENGES")?.get(groupId)
|
||||
}
|
||||
|
||||
// Included by default. Filtered later.
|
||||
if (groupId === "classic" && location !== "GLOBAL_CLASSIC_CHALLENGES") {
|
||||
if (!mainGroup) {
|
||||
// emergency bailout - shouldn't happen in practice
|
||||
return undefined
|
||||
}
|
||||
// Global merge groups are included by default. Filtered later.
|
||||
|
||||
return mergeSavedChallengeGroups(
|
||||
mainGroup,
|
||||
gameGroups.get("GLOBAL_CLASSIC_CHALLENGES")?.get(groupId),
|
||||
)
|
||||
}
|
||||
const globalGroup = this.globalMergeGroups.get(groupId)
|
||||
|
||||
if (
|
||||
groupId === "elusive" &&
|
||||
location !== "GLOBAL_ELUSIVES_CHALLENGES"
|
||||
globalGroup &&
|
||||
location !== globalGroup.location &&
|
||||
globalGroup.gameVersions.includes(gameVersion)
|
||||
) {
|
||||
const globalGroupChallenges = gameGroups
|
||||
.get(globalGroup.location)
|
||||
?.get(globalGroup.groupId)
|
||||
|
||||
if (!mainGroup) {
|
||||
// emergency bailout - shouldn't happen in practice
|
||||
return undefined
|
||||
return globalGroupChallenges
|
||||
}
|
||||
|
||||
return mergeSavedChallengeGroups(
|
||||
mainGroup,
|
||||
gameGroups.get("GLOBAL_ELUSIVES_CHALLENGES")?.get(groupId),
|
||||
)
|
||||
return mergeSavedChallengeGroups(mainGroup, globalGroupChallenges)
|
||||
}
|
||||
|
||||
return mainGroup
|
||||
@ -363,15 +437,6 @@ export abstract class ChallengeRegistry {
|
||||
): Set<string> | undefined {
|
||||
const gameChalGC = this.groupContents[gameVersion]
|
||||
|
||||
if (groupId === "feats" && gameVersion !== "h3") {
|
||||
return new Set([
|
||||
...(gameChalGC.get(location)?.get(groupId) ?? []),
|
||||
...(gameChalGC
|
||||
.get("GLOBAL_ESCALATION_CHALLENGES")
|
||||
?.get(groupId) ?? []),
|
||||
])
|
||||
}
|
||||
|
||||
if (groupId?.includes("featured")) {
|
||||
return gameChalGC.get("GLOBAL_FEATURED_CHALLENGES")?.get(groupId)
|
||||
}
|
||||
@ -384,24 +449,20 @@ export abstract class ChallengeRegistry {
|
||||
return gameChalGC.get("GLOBAL_ESCALATION_CHALLENGES")?.get(groupId)
|
||||
}
|
||||
|
||||
// Included by default. Filtered later.
|
||||
if (groupId === "classic" && location !== "GLOBAL_CLASSIC_CHALLENGES") {
|
||||
return new Set([
|
||||
...(gameChalGC.get(location)?.get(groupId) ?? []),
|
||||
...(gameChalGC.get("GLOBAL_CLASSIC_CHALLENGES")?.get(groupId) ??
|
||||
[]),
|
||||
])
|
||||
}
|
||||
// Global merge groups are included by default. Filtered later.
|
||||
|
||||
const globalGroup = this.globalMergeGroups.get(groupId)
|
||||
|
||||
if (
|
||||
groupId === "elusive" &&
|
||||
location !== "GLOBAL_ELUSIVES_CHALLENGES"
|
||||
globalGroup &&
|
||||
globalGroup.location !== location &&
|
||||
globalGroup.gameVersions.includes(gameVersion)
|
||||
) {
|
||||
return new Set([
|
||||
...(gameChalGC.get(location)?.get(groupId) ?? []),
|
||||
...(gameChalGC
|
||||
.get("GLOBAL_ELUSIVES_CHALLENGES")
|
||||
?.get(groupId) ?? []),
|
||||
.get(globalGroup.location)
|
||||
?.get(globalGroup.groupId) ?? []),
|
||||
])
|
||||
}
|
||||
|
||||
@ -705,6 +766,26 @@ export class ChallengeService extends ChallengeRegistry {
|
||||
gameVersion,
|
||||
)
|
||||
}
|
||||
|
||||
// Handle merge gropus with `allLocations` flag.
|
||||
// Should apply only if location has no challenges in that group,
|
||||
// as those are merged in `getGroupContentByIdLoc` already.
|
||||
for (const globalGroup of this.globalMergeGroups.values()) {
|
||||
if (
|
||||
globalGroup.allLocations &&
|
||||
globalGroup.gameVersions.includes(gameVersion) &&
|
||||
this.groups[gameVersion]
|
||||
.get(location)
|
||||
?.get(globalGroup.groupId) === undefined
|
||||
) {
|
||||
this.getGroupedChallengesByLoc(
|
||||
filter,
|
||||
globalGroup.location,
|
||||
challenges,
|
||||
gameVersion,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// remove empty groups
|
||||
|
@ -959,14 +959,12 @@ export class Controller {
|
||||
version,
|
||||
)
|
||||
|
||||
for (const challenge of group.Challenges) {
|
||||
this.challengeService.registerChallenge(
|
||||
challenge,
|
||||
group.CategoryId,
|
||||
data.meta.Location,
|
||||
version,
|
||||
)
|
||||
}
|
||||
this.challengeService.registerChallengeList(
|
||||
group.Challenges,
|
||||
group.CategoryId,
|
||||
data.meta.Location,
|
||||
version,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user