1
mirror of https://github.com/thepeacockproject/Peacock synced 2024-11-29 09:15:11 +01:00

Implement the Challenges tab on the Career page (#80)

* Renders Career->Challenges page

* Support sniper levels on Career->Challenges page

* Refactor the code to get challenges for locations

* Support clicking tiles on Career->Challenges page

* Add elusives part (currently unsupported)

* Add gameVersion check to avoid issues on old games
This commit is contained in:
moonysolari 2023-01-14 02:12:08 -05:00 committed by GitHub
parent 1cb640992f
commit bfe82fe1e2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 2370 additions and 30 deletions

View File

@ -66,6 +66,7 @@ export function compileRuntimeChallenge(
export enum ChallengeFilterType { export enum ChallengeFilterType {
None = "None", None = "None",
Contract = "Contract", Contract = "Contract",
Contracts = "Contracts",
ParentLocation = "ParentLocation", ParentLocation = "ParentLocation",
} }
@ -79,11 +80,50 @@ export type ChallengeFilterOptions =
locationId: string locationId: string
locationParentId: string locationParentId: string
} }
| {
type: ChallengeFilterType.Contracts
contractIds: string[]
locationId: string
locationParentId: string
}
| { | {
type: ChallengeFilterType.ParentLocation type: ChallengeFilterType.ParentLocation
locationParentId: string locationParentId: string
} }
function isChallengeInContract(
contractId: string,
locationId: string,
locationParentId: string,
challenge: RegistryChallenge,
) {
assert.ok(contractId)
assert.ok(locationId)
assert.ok(locationParentId)
if (!challenge) {
return false
}
// is this for the current contract?
const isForContract = (challenge.InclusionData?.ContractIds || []).includes(
contractId,
)
// is this a location-wide challenge?
const isForLocation = challenge.Type === "location"
// is this for the current location?
const isCurrentLocation =
// is this challenge for the current parent location?
challenge.ParentLocationId === locationParentId &&
// and, is this challenge's location the current sub-location
// or the parent location? (yup, that can happen)
(challenge.LocationId === locationId ||
challenge.LocationId === locationParentId)
return isForContract || (isForLocation && isCurrentLocation)
}
export function filterChallenge( export function filterChallenge(
options: ChallengeFilterOptions, options: ChallengeFilterOptions,
challenge: RegistryChallenge, challenge: RegistryChallenge,
@ -92,32 +132,22 @@ export function filterChallenge(
case ChallengeFilterType.None: case ChallengeFilterType.None:
return true return true
case ChallengeFilterType.Contract: { case ChallengeFilterType.Contract: {
assert.ok(options.contractId) return isChallengeInContract(
assert.ok(options.locationId) options.contractId,
assert.ok(options.locationParentId) options.locationId,
options.locationParentId,
if (!challenge) { challenge,
return false )
} }
case ChallengeFilterType.Contracts: {
// is this for the current contract? return options.contractIds.some((contractId) =>
const isForContract = ( isChallengeInContract(
challenge.InclusionData?.ContractIds || [] contractId,
).includes(options.contractId) options.locationId,
options.locationParentId,
// is this a location-wide challenge? challenge,
const isForLocation = challenge.Type === "location" ),
)
// is this for the current location?
const isCurrentLocation =
// is this challenge for the current parent location?
challenge.ParentLocationId === options.locationParentId &&
// and, is this challenge's location the current sub-location
// or the parent location? (yup, that can happen)
(challenge.LocationId === options.locationId ||
challenge.LocationId === options.locationParentId)
return isForContract || (isForLocation && isCurrentLocation)
} }
case ChallengeFilterType.ParentLocation: case ChallengeFilterType.ParentLocation:
assert.ok(options.locationParentId) assert.ok(options.locationParentId)

View File

@ -291,6 +291,37 @@ export class ChallengeService extends ChallengeRegistry {
}) })
} }
getChallengesForLocation(
child: string,
gameVersion: GameVersion,
): GroupIndexedChallengeLists {
const locations = getVersionedConfig<PeacockLocationsData>(
"LocationsData",
gameVersion,
true,
)
const parent = locations.children[child].Properties.ParentLocation
const location = locations.children[child]
assert.ok(location)
let contracts =
child === "LOCATION_AUSTRIA" ||
child === "LOCATION_SALTY_SEAGULL" ||
child === "LOCATION_CAGED_FALCON"
? this.controller.missionsInLocations.sniper[child]
: this.controller.missionsInLocations[child]
if (!contracts) {
contracts = []
}
return this.getGroupedChallengeLists({
type: ChallengeFilterType.Contracts,
contractIds: contracts,
locationId: child,
locationParentId: parent,
})
}
startContract( startContract(
userId: string, userId: string,
sessionId: string, sessionId: string,
@ -556,6 +587,41 @@ export class ChallengeService extends ChallengeRegistry {
) )
} }
getChallengeDataForLocation(
locationId: string,
gameVersion: GameVersion,
userId: string,
): CompiledChallengeTreeCategory[] {
const locationsData = getVersionedConfig<PeacockLocationsData>(
"LocationsData",
gameVersion,
false,
)
const locationData = locationsData.children[locationId]
if (!locationData) {
log(
LogLevel.WARN,
`Failed to get location data in CSERV [${locationId}]`,
)
return []
}
const forLocation = this.getChallengesForLocation(
locationId,
gameVersion,
)
return this.reBatchIntoSwitchedData(
forLocation,
userId,
gameVersion,
locationData,
true,
)
}
private reBatchIntoSwitchedData( private reBatchIntoSwitchedData(
challengeLists: GroupIndexedChallengeLists, challengeLists: GroupIndexedChallengeLists,
userId: string, userId: string,

View File

@ -91,6 +91,7 @@ import FrankensteinMmMpTemplate from "../static/FrankensteinMmMpTemplate.json"
import FrankensteinScoreOverviewTemplate from "../static/FrankensteinScoreOverviewTemplate.json" import FrankensteinScoreOverviewTemplate from "../static/FrankensteinScoreOverviewTemplate.json"
import FrankensteinPlanningTemplate from "../static/FrankensteinPlanningTemplate.json" import FrankensteinPlanningTemplate from "../static/FrankensteinPlanningTemplate.json"
import Videos from "../static/Videos.json" import Videos from "../static/Videos.json"
import ChallengeLocationTemplate from "../static/ChallengeLocationTemplate.json"
import ContractSearchPageTemplate from "../static/ContractSearchPageTemplate.json" import ContractSearchPageTemplate from "../static/ContractSearchPageTemplate.json"
import ContractSearchResponseTemplate from "../static/ContractSearchResponseTemplate.json" import ContractSearchResponseTemplate from "../static/ContractSearchResponseTemplate.json"
import LegacyDebriefingChallengesTemplate from "../static/LegacyDebriefingChallengesTemplate.json" import LegacyDebriefingChallengesTemplate from "../static/LegacyDebriefingChallengesTemplate.json"
@ -183,6 +184,7 @@ const configs: Record<string, unknown> = {
FrankensteinPlanningTemplate, FrankensteinPlanningTemplate,
FrankensteinScoreOverviewTemplate, FrankensteinScoreOverviewTemplate,
Videos, Videos,
ChallengeLocationTemplate,
ContractSearchPageTemplate, ContractSearchPageTemplate,
ContractSearchResponseTemplate, ContractSearchResponseTemplate,
MasteryUnlockablesTemplate, MasteryUnlockablesTemplate,

View File

@ -138,6 +138,33 @@ menuDataRouter.get(
"/dashboard/Dashboard_Category_Escalation/:subscriptionId/:type/:id/:mode", "/dashboard/Dashboard_Category_Escalation/:subscriptionId/:type/:id/:mode",
dashEscalations, dashEscalations,
) )
menuDataRouter.get(
"/ChallengeLocation",
(req: RequestWithJwt<{ locationId: string }>, res) => {
const location = getVersionedConfig<PeacockLocationsData>(
"LocationsData",
req.gameVersion,
true,
).children[req.query.locationId]
res.json({
template: getVersionedConfig(
"ChallengeLocationTemplate",
req.gameVersion,
false,
),
data: {
Name: location.DisplayNameLocKey,
Location: location,
Children:
controller.challengeService.getChallengeDataForLocation(
req.query.locationId,
req.gameVersion,
req.jwt.unique_name,
),
},
})
},
)
menuDataRouter.get("/Hub", (req: RequestWithJwt, res) => { menuDataRouter.get("/Hub", (req: RequestWithJwt, res) => {
swapToBrowsingMenusStatus(req.gameVersion) swapToBrowsingMenusStatus(req.gameVersion)
@ -160,6 +187,72 @@ menuDataRouter.get("/Hub", (req: RequestWithJwt, res) => {
"d7e2607c-6916-48e2-9588-976c7d8998bb", "d7e2607c-6916-48e2-9588-976c7d8998bb",
)! )!
const locations = getVersionedConfig<PeacockLocationsData>(
"LocationsData",
req.gameVersion,
true,
)
const career = {
// TODO: Add data on elusive challenges. They are not shown on the Career->Challenges page. What the client does with this information is unclear. They are not supported by Peacock as of v5.6.2.
ELUSIVES_UNSUPPORTED:
req.gameVersion === "h3"
? {
Children: [],
Name: "UI_MENU_PAGE_PROFILE_CHALLENGES_CATEGORY_ELUSIVE",
Location:
locations.parents["LOCATION_PARENT_ICA_FACILITY"],
}
: {},
}
for (const parent in locations.parents) {
career[parent] = {
Children: [],
Location: locations.parents[parent],
Name: locations.parents[parent].DisplayNameLocKey,
}
}
for (const child in locations.children) {
if (
child === "LOCATION_ICA_FACILITY_ARRIVAL" ||
child === "LOCATION_HOKKAIDO_SHIM_MAMUSHI"
) {
continue
}
const parent = locations.children[child].Properties.ParentLocation
const location = locations.children[child]
const challenges = controller.challengeService.getChallengesForLocation(
child,
req.gameVersion,
)
const challengeCompletion =
controller.challengeService.countTotalNCompletedChallenges(
challenges,
req.jwt.unique_name,
req.gameVersion,
)
career[parent].Children.push({
IsLocked: location.Properties.IsLocked,
Name: location.DisplayNameLocKey,
Image: location.Properties.Icon,
Icon: location.Type, // should be "location" for all locations
CompletedChallengesCount:
challengeCompletion.CompletedChallengesCount,
ChallengesCount: challengeCompletion.ChallengesCount,
CategoryId: child,
Description: `UI_${child}_PRIMARY_DESC`,
Location: location,
ImageLocked: location.Properties.LockedIcon,
RequiredResources: location.Properties.RequiredResources,
IsPack: false, // should be false for all locations
CompletionData: generateCompletionData(
child,
req.jwt.unique_name,
req.gameVersion,
),
})
}
res.json({ res.json({
template: theTemplate, template: theTemplate,
data: { data: {
@ -187,7 +280,12 @@ menuDataRouter.get("/Hub", (req: RequestWithJwt, res) => {
req.gameVersion, req.gameVersion,
), ),
LocationsData: createLocationsData(req.gameVersion), LocationsData: createLocationsData(req.gameVersion),
ProfileData: {}, ProfileData: {
ChallengeData: {
Children: Object.values(career),
},
MasteryData: {},
},
StoryData: makeCampaigns(req.gameVersion, req.jwt.unique_name), StoryData: makeCampaigns(req.gameVersion, req.jwt.unique_name),
FilterData: getVersionedConfig( FilterData: getVersionedConfig(
"FilterData", "FilterData",

View File

@ -290,9 +290,9 @@ export async function missionEnd(
const opportunities = contractData.Metadata.Opportunities const opportunities = contractData.Metadata.Opportunities
const opportunityCount = opportunities ? opportunities.length : 0 const opportunityCount = opportunities ? opportunities.length : 0
const opportunityCompleted = opportunities const opportunityCompleted = opportunities
? opportunities.filter((ms) => ( ? opportunities.filter(
ms in userData.Extensions.opportunityprogression (ms) => ms in userData.Extensions.opportunityprogression,
)).length ).length
: 0 : 0
const result = { const result = {
MissionReward: { MissionReward: {

File diff suppressed because it is too large Load Diff