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:
parent
1cb640992f
commit
bfe82fe1e2
@ -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)
|
||||||
|
@ -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,
|
||||||
|
@ -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,
|
||||||
|
@ -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",
|
||||||
|
@ -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: {
|
||||||
|
2144
static/ChallengeLocationTemplate.json
Normal file
2144
static/ChallengeLocationTemplate.json
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user