mirror of
https://github.com/thepeacockproject/Peacock
synced 2025-03-01 14:43:02 +01:00
Added distinction between Mastery XP and Action XP
Added extended Profile Profile to main menu Added support for Payout objectives on the score screen Added flag for unlocking all shortcuts Added flag for unlocking all Freelancer masteries Added flag to allow Peacock to be restarted when the game is running and connected Fixed issue where playstyle wasn't show properly
This commit is contained in:
parent
ba9b799abe
commit
4031779a91
@ -1139,11 +1139,12 @@ export class ChallengeService extends ChallengeRegistry {
|
||||
}
|
||||
|
||||
//NOTE: Official will always grant XP to both Location Mastery and the Player Profile
|
||||
const totalXp =
|
||||
(challenge.Xp || 0) + (challenge.Rewards?.MasteryXP || 0)
|
||||
const actionXp = challenge.Xp || 0
|
||||
const masteryXp = challenge.Rewards?.MasteryXP || 0
|
||||
const xp = actionXp + masteryXp
|
||||
|
||||
this.grantLocationMasteryXp(totalXp, session, userData)
|
||||
this.grantUserXp(totalXp, session, userData)
|
||||
this.grantLocationMasteryXp(masteryXp, actionXp, session, userData)
|
||||
this.grantUserXp(xp, session, userData)
|
||||
|
||||
writeUserData(userId, gameVersion)
|
||||
|
||||
@ -1163,6 +1164,7 @@ export class ChallengeService extends ChallengeRegistry {
|
||||
|
||||
grantLocationMasteryXp(
|
||||
masteryXp: number,
|
||||
actionXp: number,
|
||||
contractSession: ContractSession,
|
||||
userProfile: UserProfile,
|
||||
): boolean {
|
||||
@ -1194,6 +1196,7 @@ export class ChallengeService extends ChallengeRegistry {
|
||||
|
||||
const parentLocationIdLowerCase = parentLocationId.toLocaleLowerCase()
|
||||
|
||||
//Update the Location data
|
||||
userProfile.Extensions.progression.Locations[
|
||||
parentLocationIdLowerCase
|
||||
] ??= {
|
||||
@ -1209,7 +1212,7 @@ export class ChallengeService extends ChallengeRegistry {
|
||||
const maxLevel = masteryData.MaxLevel || DEFAULT_MASTERY_MAXLEVEL
|
||||
|
||||
locationData.Xp = clampValue(
|
||||
locationData.Xp + masteryXp,
|
||||
locationData.Xp + masteryXp + actionXp,
|
||||
0,
|
||||
contract.Metadata.Type !== "evergreen"
|
||||
? xpRequiredForLevel(maxLevel)
|
||||
@ -1224,9 +1227,43 @@ export class ChallengeService extends ChallengeRegistry {
|
||||
maxLevel,
|
||||
)
|
||||
|
||||
//Update the SubLocation data
|
||||
const profileData = userProfile.Extensions.progression.PlayerProfileXP
|
||||
|
||||
let foundSubLocation = profileData.Sublocations.find(
|
||||
(e) => e.Location === parentLocationId,
|
||||
)
|
||||
|
||||
if (!foundSubLocation) {
|
||||
foundSubLocation = {
|
||||
Location: parentLocationId,
|
||||
Xp: 0,
|
||||
ActionXp: 0,
|
||||
}
|
||||
|
||||
profileData.Sublocations.push(foundSubLocation)
|
||||
}
|
||||
|
||||
foundSubLocation.Xp = clampValue(
|
||||
foundSubLocation.Xp + masteryXp,
|
||||
0,
|
||||
contract.Metadata.Type !== "evergreen"
|
||||
? xpRequiredForLevel(maxLevel)
|
||||
: xpRequiredForEvergreenLevel(maxLevel),
|
||||
)
|
||||
foundSubLocation.ActionXp += actionXp
|
||||
|
||||
//Update the EvergreenLevel with the latest Mastery Level
|
||||
if (contract.Metadata.Type === "evergreen") {
|
||||
userProfile.Extensions.CPD[contract.Metadata.CpdId][
|
||||
"EvergreenLevel"
|
||||
] = locationData.Level
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
//TODO: Combine with grantLocationMasteryXp?
|
||||
grantUserXp(
|
||||
xp: number,
|
||||
contractSession: ContractSession,
|
||||
|
@ -19,6 +19,7 @@
|
||||
import { getUserData, writeUserData } from "./databaseHandler"
|
||||
import { getConfig } from "./configSwizzleManager"
|
||||
import { ContractProgressionData } from "./types/types"
|
||||
import { getFlag } from "./flags"
|
||||
|
||||
export async function setCpd(
|
||||
data: ContractProgressionData,
|
||||
@ -51,11 +52,11 @@ export async function getCpd(
|
||||
return defaultCPD
|
||||
}
|
||||
|
||||
//NOTE: Update the EvergreenLevel with the latest Mastery Level
|
||||
//TODO: Get rid of hard-coded values
|
||||
userData.Extensions.CPD[cpdID]["EvergreenLevel"] =
|
||||
userData.Extensions.progression.Locations["location_parent_snug"]
|
||||
?.Level || 1
|
||||
//NOTE: Override the EvergreenLevel with the latest Mastery Level
|
||||
if (getFlag("gameplayUnlockAllFreelancerMasteries")) {
|
||||
//TODO: Get rid of hardcoded values
|
||||
userData.Extensions.CPD[cpdID]["EvergreenLevel"] = 100
|
||||
}
|
||||
|
||||
return userData.Extensions.CPD[cpdID]
|
||||
}
|
||||
|
@ -89,10 +89,22 @@ const defaultFlags: Flags = {
|
||||
desc: "[Development - Workspace required] Toggle loading of plugins with a .ts/.cts extension inside the /plugins folder",
|
||||
default: false,
|
||||
},
|
||||
developmentAllowRuntimeRestart: {
|
||||
desc: "[Development] When set to true, it will be possible to restart Peacock while the game is running and connected.",
|
||||
default: false,
|
||||
},
|
||||
legacyContractDownloader: {
|
||||
desc: "When set to true, the official servers will be used for contract downloading in H3, which only works for the platform you are playing on. When false, the HITMAPS servers will be used instead. Note that this option only pertains to H3. Official servers will be used for H1 and H2 regardless of the value of this option.",
|
||||
default: false,
|
||||
},
|
||||
gameplayUnlockAllShortcuts: {
|
||||
desc: "When set to true, all shortcuts will always be unlocked.",
|
||||
default: false,
|
||||
},
|
||||
gameplayUnlockAllFreelancerMasteries: {
|
||||
desc: "When set to true, all Freelancer unlocks will always be available.",
|
||||
default: false,
|
||||
},
|
||||
}
|
||||
|
||||
const OLD_FLAGS_FILE = "flags.json5"
|
||||
|
@ -357,18 +357,22 @@ app.use(
|
||||
|
||||
next()
|
||||
}),
|
||||
).use(async (req: RequestWithJwt, _res, next): Promise<void> => {
|
||||
if (!req.jwt) {
|
||||
)
|
||||
|
||||
if (getFlag("developmentAllowRuntimeRestart")) {
|
||||
app.use(async (req: RequestWithJwt, _res, next): Promise<void> => {
|
||||
if (!req.jwt) {
|
||||
next()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
//Make sure the userdata is always loaded if a proper JWT token is available
|
||||
await cheapLoadUserData(req.jwt.unique_name, req.gameVersion)
|
||||
|
||||
next()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// make sure the userdata is always loaded if a proper jwt token is available
|
||||
await cheapLoadUserData(req.jwt.unique_name, req.gameVersion)
|
||||
|
||||
next()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function generateBlobConfig(req: RequestWithJwt) {
|
||||
return {
|
||||
|
@ -1801,6 +1801,7 @@ menuDataRouter.post(
|
||||
createLoadSaveMiddleware("SaveMenuTemplate"),
|
||||
)
|
||||
|
||||
//TODO: Add statistics
|
||||
menuDataRouter.get("/PlayerProfile", (req: RequestWithJwt, res) => {
|
||||
const playerProfilePage = getConfig<PlayerProfileView>(
|
||||
"PlayerProfilePage",
|
||||
@ -1813,6 +1814,28 @@ menuDataRouter.get("/PlayerProfile", (req: RequestWithJwt, res) => {
|
||||
playerProfilePage.data.PlayerProfileXp.Level =
|
||||
userProfile.Extensions.progression.PlayerProfileXP.ProfileLevel
|
||||
|
||||
const subLocationMap = new Map(
|
||||
userProfile.Extensions.progression.PlayerProfileXP.Sublocations.map(
|
||||
(obj) => [obj.Location, obj],
|
||||
),
|
||||
)
|
||||
|
||||
playerProfilePage.data.PlayerProfileXp.Seasons.forEach((e) =>
|
||||
e.Locations.forEach((f) => {
|
||||
const subLocationData = subLocationMap.get(f.LocationId)
|
||||
|
||||
f.Xp = subLocationData?.Xp || 0
|
||||
f.ActionXp = subLocationData?.ActionXp || 0
|
||||
|
||||
if (f.LocationProgression) {
|
||||
f.LocationProgression.Level =
|
||||
userProfile.Extensions.progression.Locations[
|
||||
f.LocationId.toLocaleLowerCase()
|
||||
]?.Level || 1
|
||||
}
|
||||
}),
|
||||
)
|
||||
|
||||
res.json(playerProfilePage)
|
||||
})
|
||||
|
||||
|
@ -547,21 +547,41 @@ profileRouter.post(
|
||||
})
|
||||
}
|
||||
|
||||
const unlockAllShortcuts = getFlag("gameplayUnlockAllShortcuts")
|
||||
|
||||
for (const challenge of challenges) {
|
||||
challenge.Progression = Object.assign(
|
||||
{
|
||||
if (
|
||||
unlockAllShortcuts &&
|
||||
challenge.Challenge.Tags?.includes("shortcut")
|
||||
) {
|
||||
challenge.Progression = {
|
||||
ChallengeId: challenge.Challenge.Id,
|
||||
ProfileId: req.jwt.unique_name,
|
||||
Completed: false,
|
||||
State: {},
|
||||
ETag: `W/"datetime'${encodeURIComponent(
|
||||
new Date().toISOString(),
|
||||
)}'"`,
|
||||
CompletedAt: null,
|
||||
Completed: true,
|
||||
Ticked: true,
|
||||
State: {
|
||||
CurrentState: "Success",
|
||||
},
|
||||
// @ts-expect-error typescript hates dates
|
||||
CompletedAt: new Date(new Date() - 10).toISOString(),
|
||||
MustBeSaved: false,
|
||||
},
|
||||
challenge.Progression,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
challenge.Progression = Object.assign(
|
||||
{
|
||||
ChallengeId: challenge.Challenge.Id,
|
||||
ProfileId: req.jwt.unique_name,
|
||||
Completed: false,
|
||||
State: {},
|
||||
ETag: `W/"datetime'${encodeURIComponent(
|
||||
new Date().toISOString(),
|
||||
)}'"`,
|
||||
CompletedAt: null,
|
||||
MustBeSaved: false,
|
||||
},
|
||||
challenge.Progression,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
res.json(challenges)
|
||||
@ -582,6 +602,7 @@ profileRouter.post(
|
||||
),
|
||||
},
|
||||
LevelsDefinition: {
|
||||
//TODO: Add Evergreen LevelInfo here?
|
||||
Location: [0],
|
||||
PlayerProfile: {
|
||||
Version: 1,
|
||||
|
@ -35,6 +35,7 @@ import { getConfig } from "./configSwizzleManager"
|
||||
import { _theLastYardbirdScpc, controller } from "./controller"
|
||||
import type {
|
||||
ContractSession,
|
||||
GameChanger,
|
||||
GameVersion,
|
||||
MissionManifest,
|
||||
MissionManifestObjective,
|
||||
@ -76,6 +77,7 @@ import { MasteryData } from "./types/mastery"
|
||||
* @param session The contract session.
|
||||
* @returns The play-styles, ranked from best fit to worst fit.
|
||||
*/
|
||||
//TODO: This could use an update with more playstyles
|
||||
export function calculatePlaystyle(
|
||||
session: Partial<{ kills: Set<RatingKill> }>,
|
||||
): Playstyle[] {
|
||||
@ -728,11 +730,89 @@ export async function missionEnd(
|
||||
|
||||
const profileLevelInfoOffset = oldPlayerProfileLevel - 1
|
||||
|
||||
//Time
|
||||
const timeTotal: Seconds =
|
||||
(sessionDetails.timerEnd as number) -
|
||||
(sessionDetails.timerStart as number)
|
||||
|
||||
//Playstyle
|
||||
const calculatedPlaystyles = calculatePlaystyle(sessionDetails)
|
||||
|
||||
let playstyle =
|
||||
calculatedPlaystyles[0].Score !== 0
|
||||
? calculatedPlaystyles[0]
|
||||
: undefined
|
||||
|
||||
//Calculate score and summary
|
||||
const calculateScoreResult = calculateScore(
|
||||
req.gameVersion,
|
||||
req.query.contractSessionId,
|
||||
sessionDetails,
|
||||
contractData,
|
||||
timeTotal,
|
||||
)
|
||||
|
||||
//Evergreen
|
||||
const evergreenData: MissionEndEvergreen = <MissionEndEvergreen>{}
|
||||
const evergreenData: MissionEndEvergreen = <MissionEndEvergreen>{
|
||||
PayoutsCompleted: [],
|
||||
PayoutsFailed: [],
|
||||
}
|
||||
|
||||
if (contractData.Metadata.Type === "evergreen") {
|
||||
evergreenData.Payout = sessionDetails.evergreen.payout
|
||||
const gameChangerProperties = getConfig<Record<string, GameChanger>>(
|
||||
"EvergreenGameChangerProperties",
|
||||
true,
|
||||
)
|
||||
|
||||
let totalPayout = 0
|
||||
|
||||
//ASSUMPTION: All payout objectives have a "condition"-category objective
|
||||
//and a "secondary"-category objective with a "MyPayout" in the context.
|
||||
Object.keys(gameChangerProperties).forEach((e) => {
|
||||
const gameChanger = gameChangerProperties[e]
|
||||
|
||||
const conditionObjective = gameChanger.Objectives.find(
|
||||
(e) => e.Category === "condition",
|
||||
)
|
||||
|
||||
const secondaryObjective = gameChanger.Objectives.find(
|
||||
(e) =>
|
||||
e.Category === "secondary" &&
|
||||
e.Definition.Context["MyPayout"],
|
||||
)
|
||||
|
||||
if (
|
||||
conditionObjective &&
|
||||
secondaryObjective &&
|
||||
sessionDetails.objectiveStates.get(conditionObjective.Id) ===
|
||||
"Success"
|
||||
) {
|
||||
const payoutObjective = {
|
||||
Name: gameChanger.Name,
|
||||
Payout: parseInt(
|
||||
sessionDetails.objectiveContexts.get(
|
||||
secondaryObjective.Id,
|
||||
)["MyPayout"] || 0,
|
||||
),
|
||||
IsPrestige: gameChanger.IsPrestigeObjective || false,
|
||||
}
|
||||
|
||||
if (
|
||||
sessionDetails.objectiveStates.get(
|
||||
secondaryObjective.Id,
|
||||
) === "Success"
|
||||
) {
|
||||
totalPayout += payoutObjective.Payout
|
||||
evergreenData.PayoutsCompleted.push(payoutObjective)
|
||||
} else {
|
||||
evergreenData.PayoutsFailed.push(payoutObjective)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
logDebug("Payout", sessionDetails.evergreen.payout, totalPayout)
|
||||
|
||||
evergreenData.Payout = totalPayout
|
||||
evergreenData.EndStateEventName =
|
||||
sessionDetails.evergreen.scoringScreenEndState
|
||||
|
||||
@ -760,23 +840,17 @@ export async function missionEnd(
|
||||
)
|
||||
newLocationLevel = locationProgressionData.Level
|
||||
|
||||
//Debug
|
||||
logDebug(
|
||||
sessionDetails.failedObjectives,
|
||||
sessionDetails.completedObjectives,
|
||||
sessionDetails.objectiveContexts,
|
||||
sessionDetails.objectiveStates,
|
||||
)
|
||||
//Override the silent assassin rank
|
||||
if (calculateScoreResult.silentAssassin) {
|
||||
playstyle = {
|
||||
Id: "595f6ff1-85bf-4e4f-a9ee-76038a455648",
|
||||
Name: "UI_PLAYSTYLE_ICA_STEALTH_ASSASSIN",
|
||||
Type: "STEALTH_ASSASSIN",
|
||||
Score: 0,
|
||||
}
|
||||
}
|
||||
|
||||
evergreenData.PayoutsCompleted = [
|
||||
{
|
||||
Name: "UI_CONTRACT_EVERGREEN_ELIMINATIONPAYOUT_TITLE",
|
||||
Payout: 0,
|
||||
IsPrestige: false,
|
||||
},
|
||||
]
|
||||
|
||||
evergreenData.PayoutsFailed = []
|
||||
calculateScoreResult.silentAssassin = false
|
||||
}
|
||||
|
||||
//Drops
|
||||
@ -799,26 +873,6 @@ export async function missionEnd(
|
||||
})
|
||||
}
|
||||
|
||||
//Time
|
||||
const timeTotal: Seconds =
|
||||
(sessionDetails.timerEnd as number) -
|
||||
(sessionDetails.timerStart as number)
|
||||
|
||||
//Playstyle
|
||||
const calculatedPlaystyles = calculatePlaystyle(sessionDetails)
|
||||
|
||||
const playstyle =
|
||||
calculatedPlaystyles[0].Score !== 0 ? calculatePlaystyle[0] : undefined
|
||||
|
||||
//Calculate score and summary
|
||||
const calculateScoreResult = calculateScore(
|
||||
req.gameVersion,
|
||||
req.query.contractSessionId,
|
||||
sessionDetails,
|
||||
contractData,
|
||||
timeTotal,
|
||||
)
|
||||
|
||||
//Setup the result
|
||||
const result: MissionEndResponse = {
|
||||
MissionReward: {
|
||||
@ -975,8 +1029,6 @@ export async function missionEnd(
|
||||
}
|
||||
//#endregion
|
||||
|
||||
logDebug(result)
|
||||
|
||||
res.json({
|
||||
template:
|
||||
req.gameVersion === "scpc"
|
||||
|
@ -379,6 +379,18 @@ export interface PlayerProfileView {
|
||||
PlayerProfileXp: {
|
||||
Total: number
|
||||
Level: number
|
||||
Seasons: {
|
||||
Number: number
|
||||
Locations: {
|
||||
LocationId: string
|
||||
Xp: number
|
||||
ActionXp: number
|
||||
LocationProgression?: {
|
||||
Level: number
|
||||
MaxLevel: number
|
||||
}
|
||||
}[]
|
||||
}[]
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -425,6 +437,11 @@ export interface UserProfile {
|
||||
* The total amount of XP a user has obtained.
|
||||
*/
|
||||
Total: number
|
||||
Sublocations: {
|
||||
Location: string
|
||||
Xp: number
|
||||
ActionXp: number
|
||||
}[]
|
||||
}
|
||||
Locations: {
|
||||
[location: string]: {
|
||||
|
@ -139,7 +139,7 @@ export function getMaxProfileLevel(gameVersion: GameVersion): number {
|
||||
* Minimum level returned is 1.
|
||||
*/
|
||||
export function levelForXp(xp: number): number {
|
||||
return Math.floor(xp / XP_PER_LEVEL) + 1
|
||||
return Math.min(1, Math.floor(xp / XP_PER_LEVEL) + 1)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -150,6 +150,7 @@ export function xpRequiredForLevel(level: number): number {
|
||||
return Math.max(0, (level - 1) * XP_PER_LEVEL)
|
||||
}
|
||||
|
||||
//TODO: Determine some mathematical function
|
||||
export const EVERGREEN_LEVEL_INFO: number[] = [
|
||||
0, 5000, 10000, 17000, 24000, 31000, 38000, 45000, 52000, 61000, 70000,
|
||||
79000, 88000, 97000, 106000, 115000, 124000, 133000, 142000, 154000, 166000,
|
||||
|
@ -6,9 +6,238 @@
|
||||
"Total": 0,
|
||||
"Level": 1,
|
||||
"Seasons": [
|
||||
{
|
||||
"Number": 1,
|
||||
"Locations": [
|
||||
{
|
||||
"LocationId": "LOCATION_PARENT_ICA_FACILITY",
|
||||
"Xp": 0,
|
||||
"ActionXp": 0
|
||||
},
|
||||
{
|
||||
"LocationId": "LOCATION_PARENT_PARIS",
|
||||
"Xp": 0,
|
||||
"ActionXp": 0,
|
||||
"LocationProgression": {
|
||||
"Level": 1,
|
||||
"MaxLevel": 20
|
||||
}
|
||||
},
|
||||
{
|
||||
"LocationId": "LOCATION_PARENT_COASTALTOWN",
|
||||
"Xp": 0,
|
||||
"ActionXp": 0,
|
||||
"LocationProgression": {
|
||||
"Level": 1,
|
||||
"MaxLevel": 20
|
||||
}
|
||||
},
|
||||
{
|
||||
"LocationId": "LOCATION_PARENT_MARRAKECH",
|
||||
"Xp": 0,
|
||||
"ActionXp": 0,
|
||||
"LocationProgression": {
|
||||
"Level": 1,
|
||||
"MaxLevel": 20
|
||||
}
|
||||
},
|
||||
{
|
||||
"LocationId": "LOCATION_PARENT_BANGKOK",
|
||||
"Xp": 0,
|
||||
"ActionXp": 0,
|
||||
"LocationProgression": {
|
||||
"Level": 1,
|
||||
"MaxLevel": 20
|
||||
}
|
||||
},
|
||||
{
|
||||
"LocationId": "LOCATION_PARENT_COLORADO",
|
||||
"Xp": 0,
|
||||
"ActionXp": 0,
|
||||
"LocationProgression": {
|
||||
"Level": 1,
|
||||
"MaxLevel": 20
|
||||
}
|
||||
},
|
||||
{
|
||||
"LocationId": "LOCATION_PARENT_HOKKAIDO",
|
||||
"Xp": 0,
|
||||
"ActionXp": 0,
|
||||
"LocationProgression": {
|
||||
"Level": 1,
|
||||
"MaxLevel": 20
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Number": 2,
|
||||
"Locations": [
|
||||
{
|
||||
"LocationId": "LOCATION_PARENT_NEWZEALAND",
|
||||
"Xp": 0,
|
||||
"ActionXp": 0,
|
||||
"LocationProgression": {
|
||||
"Level": 1,
|
||||
"MaxLevel": 5
|
||||
}
|
||||
},
|
||||
{
|
||||
"LocationId": "LOCATION_PARENT_MIAMI",
|
||||
"Xp": 0,
|
||||
"ActionXp": 0,
|
||||
"LocationProgression": {
|
||||
"Level": 1,
|
||||
"MaxLevel": 20
|
||||
}
|
||||
},
|
||||
{
|
||||
"LocationId": "LOCATION_PARENT_COLOMBIA",
|
||||
"Xp": 0,
|
||||
"ActionXp": 0,
|
||||
"LocationProgression": {
|
||||
"Level": 1,
|
||||
"MaxLevel": 20
|
||||
}
|
||||
},
|
||||
{
|
||||
"LocationId": "LOCATION_PARENT_MUMBAI",
|
||||
"Xp": 0,
|
||||
"ActionXp": 0,
|
||||
"LocationProgression": {
|
||||
"Level": 1,
|
||||
"MaxLevel": 20
|
||||
}
|
||||
},
|
||||
{
|
||||
"LocationId": "LOCATION_PARENT_NORTHAMERICA",
|
||||
"Xp": 0,
|
||||
"ActionXp": 0,
|
||||
"LocationProgression": {
|
||||
"Level": 1,
|
||||
"MaxLevel": 20
|
||||
}
|
||||
},
|
||||
{
|
||||
"LocationId": "LOCATION_PARENT_NORTHSEA",
|
||||
"Xp": 0,
|
||||
"ActionXp": 0,
|
||||
"LocationProgression": {
|
||||
"Level": 1,
|
||||
"MaxLevel": 20
|
||||
}
|
||||
},
|
||||
{
|
||||
"LocationId": "LOCATION_PARENT_GREEDY",
|
||||
"Xp": 0,
|
||||
"ActionXp": 0,
|
||||
"LocationProgression": {
|
||||
"Level": 1,
|
||||
"MaxLevel": 20
|
||||
}
|
||||
},
|
||||
{
|
||||
"LocationId": "LOCATION_PARENT_OPULENT",
|
||||
"Xp": 0,
|
||||
"ActionXp": 0,
|
||||
"LocationProgression": {
|
||||
"Level": 1,
|
||||
"MaxLevel": 20
|
||||
}
|
||||
},
|
||||
{
|
||||
"LocationId": "LOCATION_PARENT_AUSTRIA",
|
||||
"Xp": 0,
|
||||
"ActionXp": 0
|
||||
},
|
||||
{
|
||||
"LocationId": "LOCATION_PARENT_SALTY",
|
||||
"Xp": 0,
|
||||
"ActionXp": 0
|
||||
},
|
||||
{
|
||||
"LocationId": "LOCATION_PARENT_CAGED",
|
||||
"Xp": 0,
|
||||
"ActionXp": 0
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Number": 3,
|
||||
"Locations": []
|
||||
"Locations": [
|
||||
{
|
||||
"LocationId": "LOCATION_PARENT_GOLDEN",
|
||||
"Xp": 0,
|
||||
"ActionXp": 0,
|
||||
"LocationProgression": {
|
||||
"Level": 1,
|
||||
"MaxLevel": 20
|
||||
}
|
||||
},
|
||||
{
|
||||
"LocationId": "LOCATION_PARENT_ANCESTRAL",
|
||||
"Xp": 0,
|
||||
"ActionXp": 0,
|
||||
"LocationProgression": {
|
||||
"Level": 1,
|
||||
"MaxLevel": 20
|
||||
}
|
||||
},
|
||||
{
|
||||
"LocationId": "LOCATION_PARENT_EDGY",
|
||||
"Xp": 0,
|
||||
"ActionXp": 0,
|
||||
"LocationProgression": {
|
||||
"Level": 1,
|
||||
"MaxLevel": 20
|
||||
}
|
||||
},
|
||||
{
|
||||
"LocationId": "LOCATION_PARENT_WET",
|
||||
"Xp": 0,
|
||||
"ActionXp": 0,
|
||||
"LocationProgression": {
|
||||
"Level": 1,
|
||||
"MaxLevel": 20
|
||||
}
|
||||
},
|
||||
{
|
||||
"LocationId": "LOCATION_PARENT_ELEGANT",
|
||||
"Xp": 0,
|
||||
"ActionXp": 0,
|
||||
"LocationProgression": {
|
||||
"Level": 1,
|
||||
"MaxLevel": 20
|
||||
}
|
||||
},
|
||||
{
|
||||
"LocationId": "LOCATION_PARENT_TRAPPED",
|
||||
"Xp": 0,
|
||||
"ActionXp": 0,
|
||||
"LocationProgression": {
|
||||
"Level": 1,
|
||||
"MaxLevel": 5
|
||||
}
|
||||
},
|
||||
{
|
||||
"LocationId": "LOCATION_PARENT_ROCKY",
|
||||
"Xp": 0,
|
||||
"ActionXp": 0,
|
||||
"LocationProgression": {
|
||||
"Level": 1,
|
||||
"MaxLevel": 20
|
||||
}
|
||||
},
|
||||
{
|
||||
"LocationId": "LOCATION_PARENT_SNUG",
|
||||
"Xp": 0,
|
||||
"ActionXp": 0,
|
||||
"LocationProgression": {
|
||||
"Level": 1,
|
||||
"MaxLevel": 100
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user