1
mirror of https://github.com/thepeacockproject/Peacock synced 2025-02-16 16:34:28 +01:00

Merge remote-tracking branch 'remotes/origin/mv-mastery' into unittests

This commit is contained in:
Lennard Fonteijn 2023-06-11 14:32:01 +02:00
commit 29b140d29e
87 changed files with 19358 additions and 11057 deletions

View File

@ -86,7 +86,6 @@ legacyMenuDataRouter.get(
const inventory = createInventory(
req.jwt.unique_name,
req.gameVersion,
userProfile.Extensions.entP,
sublocation,
)
@ -157,7 +156,10 @@ legacyMenuDataRouter.get(
.LoadoutSlot !== "disguise")) && // => display all non-disguise items
(req.query.allowlargeitems === "true" ||
item.Unlockable.Properties
.LoadoutSlot !== "carriedweapon")
.LoadoutSlot !== "carriedweapon") &&
item.Unlockable.Type !==
"challengemultipler" &&
!item.Unlockable.Properties.InclusionData
) // not sure about this one
})
.map((item) => ({
@ -290,12 +292,11 @@ legacyMenuDataRouter.get(
},
Available: true,
},
// This is currently a copy of "normal" as pro1 is not implemented
{
Name: "pro1",
Data: {
LocationId: req.query.locationId,
...masteryData[0],
...masteryData[1],
},
Available: true,
},

View File

@ -58,7 +58,7 @@ import {
mergeSavedChallengeGroups,
} from "./challengeHelpers"
import assert from "assert"
import { getVersionedConfig } from "../configSwizzleManager"
import { getConfig, getVersionedConfig } from "../configSwizzleManager"
import { SyncHook } from "../hooksImpl"
import { getUserEscalationProgress } from "../contracts/escalations/escalationService"
@ -216,6 +216,7 @@ export abstract class ChallengeRegistry {
* Gets a challenge group by its parent location and group ID.
* @param groupId The group ID of the challenge group.
* @param location The parent location for this challenge group.
* @param gameVersion The game version.
* @returns A `SavedChallengeGroup` if such a group exists, or `undefined` if not.
*/
getGroupByIdLoc(
@ -349,6 +350,7 @@ export abstract class ChallengeRegistry {
* Parse a challenge's context listeners into the format used internally.
*
* @param challenge The challenge.
* @param Context? the current context of the challenge.
* @returns The context listener details.
*/
protected static _parseContextListeners(
@ -554,6 +556,7 @@ export class ChallengeService extends ChallengeRegistry {
*
* @param filter The filter to use.
* @param location The parent location whose challenges to get.
* @param gameVersion The game version.
* @returns A GroupIndexedChallengeLists containing the resulting challenge groups.
*/
getGroupedChallengeLists(
@ -847,8 +850,7 @@ export class ChallengeService extends ChallengeRegistry {
/**
* Upon an event, updates the context for all challenges in a contract session. Challenges not in the session are ignored.
* @param event The event to handle.
* @param sessionId The ID of the session. Unused as of v6.0.0.
* @param session The session.
* @param session The contract session the event is for.
*/
onContractEvent(
event: ClientToServerEvent,
@ -1193,11 +1195,14 @@ export class ChallengeService extends ChallengeRegistry {
userId: string,
isDestination = false,
): CompiledChallengeTreeData {
const allUnlockables = getVersionedConfig<Unlockable[]>(
"allunlockables",
gameVersion,
false,
)
const allUnlockables = [
...getVersionedConfig<Unlockable[]>(
"allunlockables",
gameVersion,
false,
),
...getConfig<Unlockable[]>("SniperUnlockables", false),
]
const drops = challenge.Drops.map((e) =>
allUnlockables.find((f) => f.Id === e),
@ -1355,6 +1360,7 @@ export class ChallengeService extends ChallengeRegistry {
/**
* Checks if the conditions to complete a challenge are met. If so, calls `onChallengeCompleted` for it.
* @param session The contract session where the challenge was completed.
* @param challengeId The id of the challenge.
* @param userData The profile of the user.
* @param parentId A parent challenge of this challenge, the completion of which might cause this challenge to complete. Pass `undefined` if such a parent is unknown or doesn't exist.
@ -1483,6 +1489,7 @@ export class ChallengeService extends ChallengeRegistry {
challenge?.Drops ?? [],
session,
userData,
gameVersion,
challenge.LocationId,
)

View File

@ -28,54 +28,119 @@ import {
MasteryDataTemplate,
MasteryDrop,
MasteryPackage,
MasteryPackageDrop,
UnlockableMasteryData,
} from "../types/mastery"
import { CompletionData, GameVersion, Unlockable } from "../types/types"
import {
clampValue,
DEFAULT_MASTERY_MAXLEVEL,
isSniperLocation,
xpRequiredForEvergreenLevel,
xpRequiredForLevel,
xpRequiredForSniperLevel,
} from "../utils"
export class MasteryService {
private masteryData: Map<string, MasteryPackage> = new Map()
private unlockableMasteryData: Map<string, UnlockableMasteryData> =
new Map()
/**
* @Key1 Game version.
* @Key2 The parent location Id.
* @Value A `MasteryPackage` object.
*/
protected masteryPackages: Map<GameVersion, Map<string, MasteryPackage>> =
new Map([
["h1", new Map()],
["h2", new Map()],
["h3", new Map()],
["scpc", new Map()],
])
/**
* @Key1 Game version.
* @Key2 Unlockable Id.
* @Value A `MasteryPackage` object.
*/
private unlockableMasteryData: Map<
string,
Map<string, UnlockableMasteryData>
> = new Map([
["h1", new Map()],
["h2", new Map()],
["h3", new Map()],
["scpc", new Map()],
])
registerMasteryData(masteryPackage: MasteryPackage) {
this.masteryData.set(masteryPackage.Id, masteryPackage)
for (const gv of masteryPackage.GameVersions) {
this.masteryPackages
.get(gv)
.set(masteryPackage.LocationId, masteryPackage)
/**
* Generates the same data in a reverse order. It could be considered redundant but this allows for
* faster access to location and level based on unlockable ID, avoiding big-O operation for `getMasteryForUnlockable`
*/
for (const drop of masteryPackage.Drops) {
this.unlockableMasteryData.set(drop.Id, {
Location: masteryPackage.Id.toLocaleLowerCase(),
Level: drop.Level,
})
/**
* Generates the same data in a reverse order. It could be considered redundant but this allows for
* faster access to location and level based on unlockable ID, avoiding big-O operation for `getMasteryForUnlockable`
*/
if (masteryPackage.SubPackages) {
for (const subPkg of masteryPackage.SubPackages) {
for (const drop of subPkg.Drops) {
this.unlockableMasteryData.get(gv).set(drop.Id, {
Location: masteryPackage.LocationId,
SubPackageId: subPkg.Id,
Level: drop.Level,
})
}
}
} else {
for (const drop of masteryPackage.Drops) {
this.unlockableMasteryData.get(gv).set(drop.Id, {
Location: masteryPackage.LocationId,
Level: drop.Level,
})
}
}
}
}
/**
* Returns mastery data for unlockable, if there's any
* @param unlockable
* @param gameVersion
* @returns { Location: string, Level: number } | undefined
*/
getMasteryForUnlockable(
unlockable: Unlockable,
gameVersion: GameVersion,
): UnlockableMasteryData | undefined {
return this.unlockableMasteryData.get(unlockable.Id)
return this.unlockableMasteryData.get(gameVersion).get(unlockable.Id)
}
getMasteryDataForDestination(
locationParentId: string,
gameVersion: GameVersion,
userId: string,
difficulty?: string,
): MasteryData[] {
return this.getMasteryData(locationParentId, gameVersion, userId)
return this.getMasteryData(
locationParentId,
gameVersion,
userId,
difficulty,
)
}
getMasteryDataForSubPackage(
locationParentId: string,
subPackageId: string,
gameVersion: GameVersion,
userId: string,
): MasteryData {
// Since we're getting a subpackage, we know there will only be one entry in this array.
// If the array is empty it will return undefined.
return this.getMasteryData(
locationParentId,
gameVersion,
userId,
subPackageId,
)[0]
}
getMasteryDataForLocation(
@ -90,7 +155,7 @@ export class MasteryService {
const masteryDataTemplate: MasteryDataTemplate =
getConfig<MasteryDataTemplate>(
"MasteryDataForLocationTemplate",
true,
false,
)
const masteryData = this.getMasteryData(
@ -112,29 +177,35 @@ export class MasteryService {
* Get generic completion data stored in a user's profile. Called by both `getLocationCompletion` and `getFirearmCompletion`.
* @param userId The id of the user.
* @param gameVersion The game version.
* @param completionId An Id used to look up completion data in the user's profile. Can be `parentLocationId` or `progressionKey`.
* @param locationParentId The location's parent ID, used for progression storage @since v7.0.0
* @param maxLevel The max level for this progression.
* @param levelToXpRequired A function to get the XP required for a level.
* @param subPackageId? The subpackage id you want.
*/
private getCompletionData(
userId: string,
gameVersion: GameVersion,
completionId: string,
locationParentId: string,
maxLevel: number,
levelToXpRequired: (level: number) => number,
subPackageId?: string,
) {
// Get the user profile
const userProfile = getUserData(userId, gameVersion)
// Generate default completion before trying to acquire it
userProfile.Extensions.progression.Locations[completionId] ??= {
// @since v7.0.0 this has been commented out as the default profile should
// have all the required properties - AF
/* userProfile.Extensions.progression.Locations[locationParentId] ??= {
Xp: 0,
Level: 1,
PreviouslySeenXp: 0,
}
} */
const completionData =
userProfile.Extensions.progression.Locations[completionId]
const completionData = subPackageId
? userProfile.Extensions.progression.Locations[locationParentId][
subPackageId
]
: userProfile.Extensions.progression.Locations[locationParentId]
const nextLevel: number = clampValue(
completionData.Level + 1,
@ -164,6 +235,7 @@ export class MasteryService {
* @param gameVersion The game version.
* @param userId The id of the user.
* @param contractType The type of the contract, only used to distinguish evergreen from other types (default).
* @param subPackageId? The id of the subpackage you want.
* @returns The CompletionData object.
*/
getLocationCompletion(
@ -172,133 +244,212 @@ export class MasteryService {
gameVersion: GameVersion,
userId: string,
contractType = "mission",
subPackageId?: string,
): CompletionData {
// Get the mastery data
const masteryData: MasteryPackage =
this.getMasteryPackage(locationParentId)
const masteryPkg = this.getMasteryPackage(locationParentId, gameVersion)
if (!masteryData) {
// We use the result from this function a bit, so we're just caching it
const isSniper = isSniperLocation(locationParentId)
if (!masteryPkg || (masteryPkg.SubPackages && !subPackageId)) {
return undefined
}
const subPackage = masteryPkg.SubPackages
? masteryPkg.SubPackages.filter((pkg) => pkg.Id === subPackageId)[0]
: undefined
if (!subPackage && subPackageId) {
return undefined
}
const name = isSniper
? getVersionedConfig<Unlockable[]>(
"SniperUnlockables",
gameVersion,
false,
).find((unlockable) => unlockable.Id === subPackageId).Properties
.Name
: undefined
return {
...this.getCompletionData(
userId,
gameVersion,
locationParentId.toLowerCase(),
masteryData.MaxLevel || DEFAULT_MASTERY_MAXLEVEL,
contractType === "evergreen"
locationParentId,
(subPackage ? subPackage.MaxLevel : masteryPkg.MaxLevel) ||
DEFAULT_MASTERY_MAXLEVEL,
contractType === "sniper"
? xpRequiredForSniperLevel
: contractType === "evergreen"
? xpRequiredForEvergreenLevel
: xpRequiredForLevel,
subPackageId,
),
Id: masteryData.Id,
SubLocationId: subLocationId,
HideProgression: masteryData.HideProgression || false,
IsLocationProgression: true,
Name: undefined,
Id: isSniper ? subPackageId : masteryPkg.LocationId,
SubLocationId: isSniper ? "" : subLocationId,
HideProgression: masteryPkg.HideProgression || false,
IsLocationProgression: !isSniper,
Name: name,
}
}
/**
* Get the completion data for a firearm. Used for sniper assassin mastery.
* @param progressionKey The Id of the progression. E.g. FIREARMS_SC_HERO_SNIPER_HM.
* @param unlockableName The name of the unlockable.
* @param userId The id of the user.
* @param gameVersion The game version.
* @returns The CompletionData object.
*/
getFirearmCompletion(
progressionKey: string,
unlockableName: string,
userId: string,
getMasteryPackage(
locationParentId: string,
gameVersion: GameVersion,
): CompletionData {
return {
...this.getCompletionData(
userId,
gameVersion,
progressionKey,
DEFAULT_MASTERY_MAXLEVEL,
xpRequiredForSniperLevel,
),
Id: progressionKey,
SubLocationId: "",
HideProgression: false,
IsLocationProgression: false,
Name: unlockableName,
}
}
getMasteryPackage(locationParentId: string): MasteryPackage {
if (!this.masteryData.has(locationParentId)) {
): MasteryPackage {
if (!this.masteryPackages.get(gameVersion).has(locationParentId)) {
return undefined
}
return this.masteryData.get(locationParentId)
return this.masteryPackages.get(gameVersion).get(locationParentId)
}
private processDrops(
curLevel: number,
drops: MasteryPackageDrop[],
unlockableMap: Map<string, Unlockable>,
): MasteryDrop[] {
return drops
.filter((drop) => {
if (!unlockableMap.has(drop.Id)) {
log(LogLevel.DEBUG, `No unlockable found for ${drop.Id}`)
return false
}
return true
})
.map((drop) => {
const unlockable: Unlockable = unlockableMap.get(drop.Id)
return {
IsLevelMarker: false,
Unlockable: unlockable,
Level: drop.Level,
IsLocked: drop.Level > curLevel,
TypeLocaKey: `UI_MENU_PAGE_MASTERY_UNLOCKABLE_NAME_${unlockable.Type}`,
}
})
}
private getMasteryData(
locationParentId: string,
gameVersion: GameVersion,
userId: string,
subPackageId?: string,
): MasteryData[] {
// Get the mastery data
const masteryData: MasteryPackage =
this.getMasteryPackage(locationParentId)
const masteryPkg: MasteryPackage = this.getMasteryPackage(
locationParentId,
gameVersion,
)
if (!masteryData || masteryData.Drops.length === 0) {
if (!masteryPkg || (!masteryPkg.Drops && !masteryPkg.SubPackages)) {
return []
}
// We use the result from this function a lot in here, so we're just "caching" it
const isSniper = isSniperLocation(locationParentId)
// Put all Ids into a set for quick lookup
const dropIdSet = new Set(masteryData.Drops.map((drop) => drop.Id))
let dropIdSet = undefined
// Get all unlockables with matching Ids
const unlockableData: Unlockable[] = getVersionedConfig<Unlockable[]>(
"allunlockables",
gameVersion,
true,
).filter((unlockable) => dropIdSet.has(unlockable.Id))
if (masteryPkg.SubPackages) {
dropIdSet = new Set(
masteryPkg.SubPackages.map((pkg) =>
subPackageId && pkg.Id !== subPackageId
? []
: pkg.Drops.map((drop) => drop.Id),
).reduce((a, e) => a.concat(e)),
)
// Put all unlockabkes in a map for quick lookup
// Add the main unlockable to the set to generate the page properly
if (isSniper) {
masteryPkg.SubPackages.forEach((pkg) => dropIdSet.add(pkg.Id))
}
if (!dropIdSet || dropIdSet.size === 0) {
log(
LogLevel.ERROR,
"Unknown subPackageId specified for location",
)
return []
}
} else {
dropIdSet = new Set(masteryPkg.Drops.map((drop) => drop.Id))
}
// Get all unlockables with matching Ids and put them in a map for quick lookup
const unlockableMap = new Map(
unlockableData.map((unlockable) => [unlockable.Id, unlockable]),
[
...getVersionedConfig<Unlockable[]>(
"allunlockables",
gameVersion,
true,
),
...(isSniper
? getConfig<Unlockable[]>("SniperUnlockables", true)
: []),
]
.filter((unlockable) => dropIdSet.has(unlockable.Id))
.map((unlockable) => [unlockable.Id, unlockable]),
)
// Map all the data into a new structure
const completionData = this.getLocationCompletion(
locationParentId,
locationParentId,
gameVersion,
userId,
locationParentId.includes("SNUG") ? "evergreen" : "mission",
)
const masteryData: MasteryData[] = []
const drops: MasteryDrop[] = masteryData.Drops.filter((drop) => {
if (!unlockableMap.has(drop.Id)) {
log(LogLevel.DEBUG, `No unlockable found for ${drop.Id}`)
if (masteryPkg.SubPackages) {
for (const subPkg of masteryPkg.SubPackages) {
if (subPackageId && subPkg.Id !== subPackageId) continue
return false
const completionData = this.getLocationCompletion(
locationParentId,
locationParentId,
gameVersion,
userId,
isSniper
? "sniper"
: locationParentId.includes("SNUG")
? "evergreen"
: "mission",
subPkg.Id,
)
masteryData.push({
CompletionData: completionData,
Drops: this.processDrops(
completionData.Level,
subPkg.Drops,
unlockableMap,
),
Unlockable: isSniper
? unlockableMap.get(subPkg.Id)
: undefined,
})
}
} else {
// All sniper locations are subpackages, so we don't need to add "sniper"
// to the contractType expression.
const completionData = this.getLocationCompletion(
locationParentId,
locationParentId,
gameVersion,
userId,
locationParentId.includes("SNUG") ? "evergreen" : "mission",
)
return true
}).map((drop) => {
const unlockable: Unlockable = unlockableMap.get(drop.Id)
return {
IsLevelMarker: false,
Unlockable: unlockable,
Level: drop.Level,
IsLocked: drop.Level > completionData.Level,
TypeLocaKey: `UI_MENU_PAGE_MASTERY_UNLOCKABLE_NAME_${unlockable.Type}`,
}
})
return [
{
masteryData.push({
CompletionData: completionData,
Drops: drops,
},
]
Drops: this.processDrops(
completionData.Level,
masteryPkg.Drops,
unlockableMap,
),
})
}
return masteryData
}
}

View File

@ -18,18 +18,21 @@
import { getSubLocationByName } from "../contracts/dataGen"
import { controller } from "../controller"
import { grantDrops, getDataForUnlockables } from "../inventory"
import type { ContractSession, UserProfile, GameVersion } from "../types/types"
import { getDataForUnlockables, grantDrops } from "../inventory"
import type { ContractSession, GameVersion, UserProfile } from "../types/types"
import {
DEFAULT_MASTERY_MAXLEVEL,
clampValue,
xpRequiredForLevel,
xpRequiredForEvergreenLevel,
levelForXp,
DEFAULT_MASTERY_MAXLEVEL,
evergreenLevelForXp,
getMaxProfileLevel,
levelForXp,
sniperLevelForXp,
xpRequiredForEvergreenLevel,
xpRequiredForLevel,
xpRequiredForSniperLevel,
} from "../utils"
import { writeUserData } from "../databaseHandler"
import { MasteryPackageDrop } from "../types/mastery"
export class ProgressionService {
// NOTE: Official will always grant XP to both Location Mastery and the Player Profile
@ -39,13 +42,18 @@ export class ProgressionService {
dropIds: string[],
contractSession: ContractSession,
userProfile: UserProfile,
gameVersion: GameVersion,
location: string,
sniperUnlockable?: string,
) {
// Total XP for profile XP is the total sum of the action and mastery XP's
// Total XP for profile XP is the total sum of the action and mastery XP
const xp = actionXp + masteryXp
// Grants profile XP
this.grantUserXp(xp, contractSession, userProfile)
// Grants profile XP, if this is at contract end where we're adding the final
// sniper score, don't grant it to the profile, otherwise you'll get 1,000+ levels.
if (!sniperUnlockable) {
this.grantUserXp(xp, contractSession, userProfile)
}
// Grants Mastery Progression and Drops
this.grantLocationMasteryXpAndRewards(
@ -53,14 +61,19 @@ export class ProgressionService {
actionXp,
contractSession,
userProfile,
gameVersion,
location,
sniperUnlockable,
)
// Award provided drops. E.g. From challenges
grantDrops(
userProfile.Id,
getDataForUnlockables(contractSession.gameVersion, dropIds),
)
// Award provided drops. E.g. From challenges. Don't run this function
// if there aren't any drops being granted.
if (dropIds.length > 0) {
grantDrops(
userProfile.Id,
getDataForUnlockables(contractSession.gameVersion, dropIds),
)
}
// Saves profile data
writeUserData(userProfile.Id, contractSession.gameVersion)
@ -70,24 +83,18 @@ export class ProgressionService {
getMasteryProgressionForLocation(
userProfile: UserProfile,
location: string,
subPkgId?: string,
) {
return (userProfile.Extensions.progression.Locations[
location.toLocaleLowerCase()
] ??= {
Xp: 0,
Level: 1,
PreviouslySeenXp: 0,
})
return subPkgId
? userProfile.Extensions.progression.Locations[location][subPkgId]
: userProfile.Extensions.progression.Locations[location]
}
// Return mastery drops from location from a level range
private getLocationMasteryDrops(
gameVersion: GameVersion,
isEvergreenContract: boolean,
masteryLocationDrops: {
Id: string
Level: number
}[],
masteryLocationDrops: MasteryPackageDrop[],
minLevel: number,
maxLevel: number,
) {
@ -125,7 +132,9 @@ export class ProgressionService {
actionXp: number,
contractSession: ContractSession,
userProfile: UserProfile,
gameVersion: GameVersion,
location: string,
sniperUnlockable?: string,
): boolean {
const contract = controller.resolveContract(contractSession.contractId)
@ -146,46 +155,69 @@ export class ProgressionService {
return false
}
const masteryData =
controller.masteryService.getMasteryPackage(parentLocationId)
const locationData = this.getMasteryProgressionForLocation(
userProfile,
parentLocationId.toLocaleLowerCase(),
)
const maxLevel = masteryData?.MaxLevel || DEFAULT_MASTERY_MAXLEVEL
const isEvergreenContract = contract.Metadata.Type === "evergreen"
if (masteryData) {
const previousLevel = locationData.Level
locationData.Xp = clampValue(
locationData.Xp + masteryXp + actionXp,
0,
isEvergreenContract
? xpRequiredForEvergreenLevel(maxLevel)
: xpRequiredForLevel(maxLevel),
// We can't grant sniper XP here as it's based on final score, so we skip updating mastery
// until we call this in mission end.
if (contract.Metadata.Type !== "sniper" || sniperUnlockable) {
const masteryData = controller.masteryService.getMasteryPackage(
parentLocationId,
gameVersion,
)
const locationData = this.getMasteryProgressionForLocation(
userProfile,
parentLocationId,
gameVersion === "h1"
? contract.Metadata.Difficulty ?? "normal"
: sniperUnlockable ?? undefined,
)
locationData.Level = clampValue(
isEvergreenContract
? evergreenLevelForXp(locationData.Xp)
: levelForXp(locationData.Xp),
1,
maxLevel,
)
const maxLevel = masteryData?.MaxLevel || DEFAULT_MASTERY_MAXLEVEL
const isEvergreenContract = contract.Metadata.Type === "evergreen"
// If mastery level has gone up, check if there are available drop rewards and award them
if (locationData.Level > previousLevel) {
const masteryLocationDrops = this.getLocationMasteryDrops(
contractSession.gameVersion,
isEvergreenContract,
masteryData.Drops,
previousLevel,
locationData.Level,
if (masteryData) {
const previousLevel = locationData.Level
locationData.Xp = clampValue(
locationData.Xp + masteryXp + actionXp,
0,
isEvergreenContract
? xpRequiredForEvergreenLevel(maxLevel)
: sniperUnlockable
? xpRequiredForSniperLevel(maxLevel)
: xpRequiredForLevel(maxLevel),
)
grantDrops(userProfile.Id, masteryLocationDrops)
locationData.Level = clampValue(
isEvergreenContract
? evergreenLevelForXp(locationData.Xp)
: sniperUnlockable
? sniperLevelForXp(locationData.Xp)
: levelForXp(locationData.Xp),
1,
maxLevel,
)
// If mastery level has gone up, check if there are available drop rewards and award them
if (locationData.Level > previousLevel) {
const masteryLocationDrops = this.getLocationMasteryDrops(
contractSession.gameVersion,
isEvergreenContract,
sniperUnlockable
? masteryData.SubPackages.find(
(pkg) => pkg.Id === sniperUnlockable,
).Drops
: masteryData.Drops,
previousLevel,
locationData.Level,
)
grantDrops(userProfile.Id, masteryLocationDrops)
}
}
// Update the EvergreenLevel with the latest Mastery Level
if (isEvergreenContract) {
userProfile.Extensions.CPD[contract.Metadata.CpdId][
"EvergreenLevel"
] = locationData.Level
}
}
@ -209,13 +241,6 @@ export class ProgressionService {
foundSubLocation.Xp += masteryXp
foundSubLocation.ActionXp += actionXp
// Update the EvergreenLevel with the latest Mastery Level
if (isEvergreenContract) {
userProfile.Extensions.CPD[contract.Metadata.CpdId][
"EvergreenLevel"
] = locationData.Level
}
return true
}

View File

@ -101,7 +101,6 @@ import ContractSearchResponseTemplate from "../static/ContractSearchResponseTemp
import LegacyDebriefingChallengesTemplate from "../static/LegacyDebriefingChallengesTemplate.json"
import DebriefingChallengesTemplate from "../static/DebriefingChallengesTemplate.json"
import MasteryUnlockablesTemplate from "../static/MasteryUnlockablesTemplate.json"
import SniperLoadouts from "../static/SniperLoadouts.json"
import Scpcallunlockables from "../static/Scpcallunlockables.json"
import DiscordRichAssetsForBricks from "../static/DiscordRichAssetsForBricks.json"
import EscalationCodenames from "../static/EscalationCodenames.json"
@ -116,6 +115,8 @@ import EvergreenGameChangerProperties from "../static/EvergreenGameChangerProper
import AreaMap from "../static/AreaMap.json"
import HitsCategoryElusiveTemplate from "../static/HitsCategoryElusiveTemplate.json"
import MissionRewardsTemplate from "../static/MissionRewardsTemplate.json"
import SniperUnlockables from "../static/SniperUnlockables.json"
import ScpcLocationsData from "../static/ScpcLocationsData.json"
import type { GameVersion } from "./types/types"
import { fastClone } from "./utils"
@ -204,8 +205,8 @@ const configs = {
ContractSearchPaginateTemplate,
ContractSearchResponseTemplate,
MasteryUnlockablesTemplate,
SniperLoadouts,
Scpcallunlockables,
ScpcLocationsData,
DiscordRichAssetsForBricks,
EscalationCodenames,
ScoreOverviewTemplate,
@ -219,6 +220,7 @@ const configs = {
AreaMap,
HitsCategoryElusiveTemplate,
MissionRewardsTemplate,
SniperUnlockables,
}
Object.keys(configs).forEach((cfg) => {

View File

@ -30,6 +30,7 @@ import {
getSession,
newSession,
registerObjectiveListener,
setupScoring,
} from "../eventHandler"
import { controller } from "../controller"
import { getConfig } from "../configSwizzleManager"
@ -222,6 +223,10 @@ contractRoutingRouter.post(
// register the objective as a tracked statemachine
registerObjectiveListener(theSession, obj)
}
if (contractData.Metadata.Modules) {
setupScoring(theSession, contractData.Metadata.Modules)
}
},
)

View File

@ -109,6 +109,7 @@ export function getParentLocationByName(
* @param userId The ID of the user.
* @param gameVersion The game's version.
* @param contractType The type of the contract, only used to distinguish evergreen from other types (default).
* @param subPackageId The sub package id you want (think of mastery).
* @returns The completion data object.
*/
export function generateCompletionData(
@ -116,8 +117,17 @@ export function generateCompletionData(
userId: string,
gameVersion: GameVersion,
contractType = "mission",
subPackageId?: string,
): CompletionData {
const subLocation = getSubLocationByName(subLocationId, gameVersion)
let difficulty = undefined
if (gameVersion === "h1") {
difficulty = getUserData(userId, gameVersion).Extensions
.gamepersistentdata.menudata.difficulty.destinations[
subLocation ? subLocation.Properties?.ParentLocation : subLocationId
]
}
const locationId = subLocation
? subLocation.Properties?.ParentLocation
@ -129,10 +139,12 @@ export function generateCompletionData(
gameVersion,
userId,
contractType,
subPackageId ? subPackageId : difficulty,
)
if (!completionData) {
// Should only reach here for sniper locations.
// Should only reach here for sniper locations with no subpackage id
// specified or the ICA Facility in H2016.
return {
Level: 1,
MaxLevel: 1,

View File

@ -1295,7 +1295,7 @@ export function contractIdToHitObject(
).parents[subLocation?.Properties?.ParentLocation]
// failed to find the location, must be from a newer game
if (!subLocation && (gameVersion === "h1" || gameVersion === "h2")) {
if (!subLocation && ["h1", "h2", "scpc"].includes(gameVersion)) {
log(
LogLevel.DEBUG,
`${contract.Metadata.Location} looks to be from a newer game, skipping (hitObj)!`,

View File

@ -151,6 +151,8 @@ export async function loadUserData(
const userProfile = castUserProfile(
JSON.parse((await readFile(path)).toString()),
gameVersion,
path,
)
asyncGuard.addLoadedProfile(`${userId}.${gameVersion}`, userProfile)

View File

@ -66,6 +66,11 @@ import picocolors from "picocolors"
import { setCpd } from "./evergreen"
import { getConfig } from "./configSwizzleManager"
import { resetUserEscalationProgress } from "./contracts/escalations/escalationService"
import {
ManifestScoringDefinition,
ManifestScoringModule,
} from "./types/scoring"
import { deepmerge } from "deepmerge-ts"
const eventRouter = Router()
@ -146,6 +151,67 @@ export function registerObjectiveListener(
session.objectiveStates.set(objective.Id, state)
}
/**
* Sets up scoring state machines.
*
* @param session The contract session.
* @param modules Array of scoring modules.
*/
export function setupScoring(
session: ContractSession,
modules: ManifestScoringModule[],
): void {
const scoring = {
Settings: {},
Context: undefined,
Definition: undefined,
State: undefined,
Timers: [],
}
for (const module of modules) {
const name = module.Type.split(".").at(-1)
if (name === "scoring") {
const definition: ManifestScoringDefinition = deepmerge(
...module.ScoringDefinitions,
)
let state = "Start"
let context = definition.Context
const immediate = handleEvent(
// @ts-expect-error Type issue
definition,
context,
{},
{
eventName: "-",
currentState: state,
timers: scoring.Timers,
},
)
if (immediate.state) {
state = immediate.state
}
if (immediate.context) {
context = immediate.context
}
scoring.Definition = definition
scoring.Context = context
scoring.State = state
} else {
scoring.Settings[name] = module
delete scoring.Settings[name]["Type"]
}
}
session.scoring = scoring
}
/**
* Enqueue a server to client event.
* It will be sent back the next time the client calls `SaveAndSynchronizeEvents4`.
@ -601,6 +667,28 @@ function saveEvents(
}
}
if (session.scoring) {
const scoringContext = session.scoring.Context
const scoringState = session.scoring.State
const val = handleEvent(
session.scoring.Definition as never,
scoringContext,
event.Value,
{
eventName: event.Name,
timestamp: event.Timestamp,
currentState: scoringState,
timers: session.scoring.Timers,
},
)
if (val.context) {
session.scoring.Context = val.context
session.scoring.State = val.state
}
}
controller.challengeService.onContractEvent(event, session)
if (event.Name.startsWith("ScoringScreenEndState_")) {
@ -647,6 +735,10 @@ function saveEvents(
case "Kill": {
const killValue = (event as KillC2SEvent).Value
if (session.firstKillTimestamp === undefined) {
session.firstKillTimestamp = event.Timestamp
}
if (session.lastKill.timestamp === event.Timestamp) {
session.lastKill.repositoryIds?.push(killValue.RepositoryId)
} else {

View File

@ -22,7 +22,7 @@ import { ContractProgressionData } from "./types/types"
import { getFlag } from "./flags"
import { EVERGREEN_LEVEL_INFO } from "./utils"
export async function setCpd(
export function setCpd(
data: ContractProgressionData,
uID: string,
cpdID: string,
@ -34,13 +34,10 @@ export async function setCpd(
...data,
}
await writeUserData(uID, "h3")
writeUserData(uID, "h3")
}
export async function getCpd(
uID: string,
cpdID: string,
): Promise<ContractProgressionData> {
export function getCpd(uID: string, cpdID: string): ContractProgressionData {
const userData = getUserData(uID, "h3")
if (!Object.keys(userData.Extensions.CPD).includes(cpdID)) {
@ -49,7 +46,7 @@ export async function getCpd(
false,
) as ContractProgressionData
await setCpd(defaultCPD, uID, cpdID)
setCpd(defaultCPD, uID, cpdID)
}
// NOTE: Override the EvergreenLevel with the latest Mastery Level

View File

@ -82,7 +82,6 @@ import * as livesplit from "./types/livesplit"
import * as mastery from "./types/mastery"
import * as score from "./types/score"
import * as scoring from "./types/scoring"
import * as sniperModules from "./types/sniperModules"
import * as types from "./types/types"
import * as escalationService from "./contracts/escalations/escalationService"
@ -267,10 +266,6 @@ export default {
"@peacockproject/core/types/mastery": { __esModule: true, ...mastery },
"@peacockproject/core/types/score": { __esModule: true, ...score },
"@peacockproject/core/types/scoring": { __esModule: true, ...scoring },
"@peacockproject/core/types/sniperModules": {
__esModule: true,
...sniperModules,
},
"@peacockproject/core/types/types": { __esModule: true, ...types },
"@peacockproject/core/contracts/escalations/escalationService": {
__esModule: true,

View File

@ -23,9 +23,6 @@ import "./generatedPeacockRequireTable"
// load flags as soon as possible
import { getFlag, loadFlags } from "./flags"
loadFlags()
import { setFlagsFromString } from "v8"
import { program } from "commander"
import express, { Request, Router } from "express"
@ -91,6 +88,8 @@ import { liveSplitManager } from "./livesplit/liveSplitManager"
import { cheapLoadUserData } from "./databaseHandler"
import { reportRouter } from "./contracts/reportRouting"
loadFlags()
// welcome to the bleeding edge
setFlagsFromString("--harmony")
@ -205,7 +204,6 @@ app.get(
}
if (req.params.audience === "scpc-prod") {
log(LogLevel.DEBUG, "Entering special mode.")
// sniper challenge is a different game/audience
config.Versions[0].Name = "scpc-prod"
config.Versions[0].GAME_VER = "7.3.0"
@ -260,62 +258,6 @@ app.get("/files/onlineconfig.json", (req, res) => {
res.send(getConfig("OnlineConfig", false))
})
app.get(
"/profiles/page//dashboard//Dashboard_Category_Sniper_Singleplayer/00000000-0000-0000-0000-000000000015/Contract/ff9f46cf-00bd-4c12-b887-eac491c3a96d",
(req: RequestWithJwt, res) => {
res.json({
template: getConfig("FrankensteinMmSpTemplate", false),
data: {
Item: {
Id: "ff9f46cf-00bd-4c12-b887-eac491c3a96d",
Type: "Contract",
Title: "UI_CONTRACT_HAWK_TITLE",
Date: new Date().toISOString(),
Data: generateUserCentric(
_theLastYardbirdScpc,
req.jwt.unique_name,
"h1",
),
},
},
})
},
)
// We handle this for now, but it's not used. For the future though.
app.get(
"/profiles/page//dashboard//Dashboard_Category_Sniper_Multiplayer/00000000-0000-0000-0000-000000000015/Contract/ff9f46cf-00bd-4c12-b887-eac491c3a96d",
(req: RequestWithJwt, res) => {
const template = getConfig("FrankensteinMmMpTemplate", false)
/* To enable multiplayer:
* Change MultiplayerNotSupported to false
* NOTE: REMOVING THIS FULLY WILL BREAK THE EDITED TEMPLATE!
*/
res.json({
template: template,
data: {
Item: {
Id: "ff9f46cf-00bd-4c12-b887-eac491c3a96d",
Type: "Contract",
Title: "UI_CONTRACT_HAWK_TITLE",
Date: new Date().toISOString(),
Disabled: true,
Data: {
...generateUserCentric(
_theLastYardbirdScpc,
req.jwt.unique_name,
"h1",
),
...{ MultiplayerNotSupported: true },
},
},
},
})
},
)
// NOTE! All routes attached after this point will be checked for a JWT or blob signature.
// If you are adding a route that does NOT require authentication, put it ABOVE this message!
@ -376,6 +318,62 @@ app.use(
}),
)
app.get(
"/profiles/page//dashboard//Dashboard_Category_Sniper_Singleplayer/00000000-0000-0000-0000-000000000015/Contract/ff9f46cf-00bd-4c12-b887-eac491c3a96d",
(req: RequestWithJwt, res) => {
res.json({
template: getConfig("FrankensteinMmSpTemplate", false),
data: {
Item: {
Id: "ff9f46cf-00bd-4c12-b887-eac491c3a96d",
Type: "Contract",
Title: "UI_CONTRACT_HAWK_TITLE",
Date: new Date().toISOString(),
Data: generateUserCentric(
_theLastYardbirdScpc,
req.jwt.unique_name,
"scpc",
),
},
},
})
},
)
// We handle this for now, but it's not used. For the future though.
app.get(
"/profiles/page//dashboard//Dashboard_Category_Sniper_Multiplayer/00000000-0000-0000-0000-000000000015/Contract/ff9f46cf-00bd-4c12-b887-eac491c3a96d",
(req: RequestWithJwt, res) => {
const template = getConfig("FrankensteinMmMpTemplate", false)
/* To enable multiplayer:
* Change MultiplayerNotSupported to false
* NOTE: REMOVING THIS FULLY WILL BREAK THE EDITED TEMPLATE!
*/
res.json({
template: template,
data: {
Item: {
Id: "ff9f46cf-00bd-4c12-b887-eac491c3a96d",
Type: "Contract",
Title: "UI_CONTRACT_HAWK_TITLE",
Date: new Date().toISOString(),
Disabled: true,
Data: {
...generateUserCentric(
_theLastYardbirdScpc,
req.jwt.unique_name,
"scpc",
),
...{ MultiplayerNotSupported: true },
},
},
},
})
},
)
if (getFlag("developmentAllowRuntimeRestart")) {
app.use(async (req: RequestWithJwt, _res, next): Promise<void> => {
if (!req.jwt) {

View File

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { getVersionedConfig } from "./configSwizzleManager"
import { getConfig, getVersionedConfig } from "./configSwizzleManager"
import type { GameVersion, Unlockable, UserProfile } from "./types/types"
import {
brokenItems,
@ -98,12 +98,14 @@ export function clearInventoryCache(): void {
* @param userProfile
* @param packagedUnlocks
* @param challengesUnlockables
* @param gameVersion
* @returns [Unlockable[], Unlockable[]]
*/
function filterUnlockedContent(
userProfile: UserProfile,
packagedUnlocks: Map<string, boolean>,
challengesUnlockables: object,
gameVersion: GameVersion,
) {
return function (
acc: [Unlockable[], Unlockable[]],
@ -141,12 +143,16 @@ function filterUnlockedContent(
// If the unlockable is mastery locked, checks if its unlocked based on user location progression
else if (
(unlockableMasteryData =
controller.masteryService.getMasteryForUnlockable(unlockable))
controller.masteryService.getMasteryForUnlockable(
unlockable,
gameVersion,
))
) {
const locationData =
controller.progressionService.getMasteryProgressionForLocation(
userProfile,
unlockableMasteryData.Location,
unlockableMasteryData.SubPackageId,
)
const canUnlock = locationData.Level >= unlockableMasteryData.Level
@ -435,14 +441,12 @@ function updateWithDefaultSuit(
* Generate a player's inventory with unlockables.
* @param profileId The profile ID of the player
* @param gameVersion The game version
* @param entP The player's entitlements
* @param sublocation The sublocation to generate the inventory for. Used to award default suits for the sublocation. Defaulted to undefined.
* @returns The player's inventory
*/
export function createInventory(
profileId: string,
gameVersion: GameVersion,
entP: string[],
sublocation = undefined,
): InventoryItem[] {
if (inventoryUserCache.has(profileId)) {
@ -458,11 +462,14 @@ export function createInventory(
const userProfile = getUserData(profileId, gameVersion)
// add all unlockables to player's inventory
const allunlockables = getVersionedConfig<Unlockable[]>(
"allunlockables",
gameVersion,
true,
).filter((u) => u.Type !== "location") // locations not in inventory
const allunlockables = [
...getVersionedConfig<Unlockable[]>(
"allunlockables",
gameVersion,
true,
),
...getConfig<Unlockable[]>("SniperUnlockables", true),
].filter((u) => u.Type !== "location") // locations not in inventory
let unlockables: Unlockable[] = allunlockables
@ -490,6 +497,7 @@ export function createInventory(
userProfile,
packagedUnlocks,
challengesUnlockables,
gameVersion,
),
[[], []],
)
@ -534,7 +542,7 @@ export function createInventory(
}
})
// filter again, this time removing legacy unlockables
.filter(filterAllowedContent(gameVersion, entP))
.filter(filterAllowedContent(gameVersion, userProfile.Extensions.entP))
for (const unlockable of filtered) {
unlockable!.ProfileId = profileId
@ -575,11 +583,14 @@ export function getDataForUnlockables(
gameVersion: GameVersion,
unlockableIds: string[],
): Unlockable[] {
return getVersionedConfig<Unlockable[]>(
"allunlockables",
gameVersion,
true,
).filter((unlockable) => unlockableIds.includes(unlockable.Id))
return [
...getVersionedConfig<Unlockable[]>(
"allunlockables",
gameVersion,
true,
),
...getConfig<Unlockable[]>("SniperUnlockables", true),
].filter((unlockable) => unlockableIds.includes(unlockable.Id))
}
export function getUnlockableById(

View File

@ -20,9 +20,9 @@ import { missionEnd } from "./scoreHandler"
import { Response, Router } from "express"
import {
contractCreationTutorialId,
DEFAULT_MASTERY_MAXLEVEL,
gameDifficulty,
getMaxProfileLevel,
isSniperLocation,
isSuit,
PEACOCKVERSTRING,
unlockOrderComparer,
@ -51,6 +51,7 @@ import type {
MissionManifest,
PeacockLocationsData,
PlayerProfileView,
ProgressionData,
RequestWithJwt,
SafehouseCategory,
SceneConfig,
@ -73,10 +74,12 @@ import random from "random"
import { getUserData } from "./databaseHandler"
import {
createMainOpportunityTile,
createMenuPageTile,
createPlayNextTile,
getSeasonId,
orderedMissions,
orderedPZMissions,
sniperMissionIds,
} from "./menus/playnext"
import { randomUUID } from "crypto"
import { planningView } from "./menus/planning"
@ -100,7 +103,6 @@ import {
StashpointQuery,
} from "./types/gameSchemas"
import assert from "assert"
import { SniperLoadoutConfig } from "./menus/sniper"
export const preMenuDataRouter = Router()
const menuDataRouter = Router()
@ -195,13 +197,10 @@ menuDataRouter.get("/Hub", (req: RequestWithJwt, res) => {
? getConfig("FrankensteinHubTemplate", false)
: getConfig("LegacyHubTemplate", false)
if (req.gameVersion === "scpc") {
req.gameVersion = "h1"
}
const contractCreationTutorial = controller.resolveContract(
contractCreationTutorialId,
)!
const contractCreationTutorial =
req.gameVersion !== "scpc"
? controller.resolveContract(contractCreationTutorialId)!
: undefined
const locations = getVersionedConfig<PeacockLocationsData>(
"LocationsData",
@ -230,6 +229,9 @@ menuDataRouter.get("/Hub", (req: RequestWithJwt, res) => {
Name: locations.parents[parent].DisplayNameLocKey,
}
// Exclude ICA Facility from showing in the Career -> Mastery page
if (parent === "LOCATION_PARENT_ICA_FACILITY") continue
if (
controller.masteryService.getMasteryDataForDestination(
parent,
@ -244,6 +246,7 @@ menuDataRouter.get("/Hub", (req: RequestWithJwt, res) => {
req.gameVersion,
req.jwt.unique_name,
parent.includes("SNUG") ? "evergreen" : "mission",
req.gameVersion === "h1" ? "normal" : undefined,
)
masteryData.push({
@ -254,9 +257,18 @@ menuDataRouter.get("/Hub", (req: RequestWithJwt, res) => {
normal: {
CompletionData: completionData,
},
// pro1 is currently a copy of normal completion as it is not implemented
pro1: {
CompletionData: completionData,
CompletionData:
controller.masteryService.getLocationCompletion(
parent,
parent,
req.gameVersion,
req.jwt.unique_name,
parent.includes("SNUG")
? "evergreen"
: "mission",
"pro1",
),
},
},
}
@ -330,12 +342,7 @@ menuDataRouter.get("/Hub", (req: RequestWithJwt, res) => {
},
},
DashboardData: [],
DestinationsData:
req.gameVersion === "h3"
? destinationsMenu(req)
: req.gameVersion === "h2"
? getConfig("H2DestinationsData", false)
: getConfig("LegacyDestinations", false),
DestinationsData: destinationsMenu(req),
CreateContractTutorial: generateUserCentric(
contractCreationTutorial,
req.jwt.unique_name,
@ -376,13 +383,7 @@ menuDataRouter.get("/Hub", (req: RequestWithJwt, res) => {
})
menuDataRouter.get("/SafehouseCategory", (req: RequestWithJwt, res) => {
const exts = getUserData(req.jwt.unique_name, req.gameVersion).Extensions
const inventory = createInventory(
req.jwt.unique_name,
req.gameVersion,
exts.entP,
)
const inventory = createInventory(req.jwt.unique_name, req.gameVersion)
const safehouseData = {
template:
@ -416,9 +417,14 @@ menuDataRouter.get("/SafehouseCategory", (req: RequestWithJwt, res) => {
item.Unlockable.Type === "location" ||
item.Unlockable.Type === "package" ||
item.Unlockable.Type === "loadoutunlock" ||
item.Unlockable.Type === "agencypickup"
item.Unlockable.Type === "difficultyunlock" ||
item.Unlockable.Type === "agencypickup" ||
item.Unlockable.Type === "challengemultiplier"
) {
continue // these types should not be displayed when not asked for
} else if (item.Unlockable.Properties.InclusionData) {
// Only sniper unlockables have inclusion data, don't show them
continue
}
if (
@ -542,8 +548,6 @@ menuDataRouter.get(
return
}
const userData = getUserData(req.jwt.unique_name, req.gameVersion)
let contractData: MissionManifest | undefined = undefined
if (req.query.contractid) {
@ -553,7 +557,6 @@ menuDataRouter.get(
const inventory = createInventory(
req.jwt.unique_name,
req.gameVersion,
userData.Extensions.entP,
getSubLocationByName(
contractData?.Metadata.Location,
req.gameVersion,
@ -607,7 +610,9 @@ menuDataRouter.get(
!item.Unlockable.Properties.IsContainer) &&
(req.query.allowlargeitems === "true" ||
item.Unlockable.Properties.LoadoutSlot !==
"carriedweapon")
"carriedweapon") &&
item.Unlockable.Type !== "challengemultiplier" &&
!item.Unlockable.Properties.InclusionData
) // not sure about this one
})
.map((item) => ({
@ -746,16 +751,7 @@ menuDataRouter.get(
),
} as CommonSelectScreenConfig
const exts = getUserData(
req.jwt.unique_name,
req.gameVersion,
).Extensions
const inventory = createInventory(
req.jwt.unique_name,
req.gameVersion,
exts.entP,
)
const inventory = createInventory(req.jwt.unique_name, req.gameVersion)
const contractData = controller.resolveContract(req.query.contractId)
@ -843,16 +839,7 @@ menuDataRouter.get(
),
}
const exts = getUserData(
req.jwt.unique_name,
req.gameVersion,
).Extensions
const inventory = createInventory(
req.jwt.unique_name,
req.gameVersion,
exts.entP,
)
const inventory = createInventory(req.jwt.unique_name, req.gameVersion)
const contractData = controller.resolveContract(req.query.contractId)
@ -966,6 +953,7 @@ menuDataRouter.get(
req.query.locationId,
req.gameVersion,
req.jwt.unique_name,
req.query.difficulty,
)
const response = {
@ -988,13 +976,24 @@ menuDataRouter.get(
),
},
MasteryData:
// No pro1 mastery, in an ideal world we'd pass normal as 0 and pro1 as 1
req.gameVersion === "h1" ? masteryData[0] : masteryData,
LOCATION !== "LOCATION_PARENT_ICA_FACILITY"
? req.gameVersion === "h1"
? masteryData[0]
: masteryData
: {},
DifficultyData: undefined,
},
}
if (req.gameVersion === "h1") {
if (
req.gameVersion === "h1" &&
LOCATION !== "LOCATION_PARENT_ICA_FACILITY"
) {
const inventory = createInventory(
req.jwt.unique_name,
req.gameVersion,
)
response.data.DifficultyData = {
AvailableDifficultyModes: [
{
@ -1003,7 +1002,11 @@ menuDataRouter.get(
},
{
Name: "pro1",
Available: true,
Available: inventory.some(
(e) =>
e.Unlockable.Id ===
locationData.Properties.DifficultyUnlock.pro1,
),
},
],
Difficulty: req.query.difficulty,
@ -1022,8 +1025,6 @@ menuDataRouter.get(
response.data.Location = locationData
if (req.query.difficulty === "pro1") {
log(LogLevel.DEBUG, "Adjusting for legacy-pro1.")
const obj = {
Location: locationData,
SubLocation: locationData,
@ -1153,7 +1154,7 @@ menuDataRouter.get(
if (theMissions !== undefined) {
;(theMissions as string[])
.filter(
// removes snow festival on h1, traditions on non-h3
// removes snow festival on h1
(m) =>
m &&
!(
@ -1360,6 +1361,10 @@ menuDataRouter.get(
)
}
if (sniperMissionIds.includes(req.query.contractId)) {
cats.push(createMenuPageTile("sniper"))
}
const pluginData = controller.hooks.getNextCampaignMission.call(
req.query.contractId,
req.gameVersion,
@ -1936,11 +1941,15 @@ menuDataRouter.get("/PlayerProfile", (req: RequestWithJwt, res) => {
f.Xp = subLocationData?.Xp || 0
f.ActionXp = subLocationData?.ActionXp || 0
if (f.LocationProgression) {
if (f.LocationProgression && !isSniperLocation(f.LocationId)) {
// We typecast below as it could be an object for subpackages.
// Checks before this ensure it isn't, but TS doesn't realise this.
f.LocationProgression.Level =
userProfile.Extensions.progression.Locations[
f.LocationId.toLocaleLowerCase()
]?.Level || 1
(
userProfile.Extensions.progression.Locations[
f.LocationId
] as ProgressionData
).Level || 1
}
}),
)
@ -2003,24 +2012,19 @@ menuDataRouter.get(
false,
)
const location = (() => {
const parentLocation = (() => {
switch (req.query.unlockableId?.split("_").slice(0, 3).join("_")) {
case "FIREARMS_SC_HERO":
return "LOCATION_AUSTRIA"
return "LOCATION_PARENT_AUSTRIA"
case "FIREARMS_SC_SEAGULL":
return "LOCATION_SALTY_SEAGULL"
return "LOCATION_PARENT_SALTY"
case "FIREARMS_SC_FALCON":
return "LOCATION_CAGED_FALCON"
return "LOCATION_PARENT_CAGED"
default:
assert.fail("fell through switch (bad query?)")
}
})()
let sniperLoadout = getConfig<SniperLoadoutConfig>(
"SniperLoadouts",
false,
)[location][req.query.unlockableId]
if (req.gameVersion === "scpc") {
masteryUnlockTemplate = JSON.parse(
JSON.stringify(masteryUnlockTemplate).replace(
@ -2029,33 +2033,21 @@ menuDataRouter.get(
),
)
sniperLoadout = JSON.parse(
JSON.stringify(sniperLoadout).replace(/hawk\/+/g, ""),
)
// Do we still need to do this? - AF
// sniperLoadout = JSON.parse(
// JSON.stringify(sniperLoadout).replace(/hawk\/+/g, ""),
// )
}
const unlockables = sniperLoadout.Unlockable
res.json({
template: masteryUnlockTemplate,
data: {
CompletionData: controller.masteryService.getFirearmCompletion(
...controller.masteryService.getMasteryDataForSubPackage(
parentLocation,
req.query.unlockableId,
sniperLoadout.MainUnlockable.Properties.Name,
req.jwt.unique_name,
req.gameVersion,
req.jwt.unique_name,
),
Drops: unlockables.map((unlockable) => ({
IsLevelMarker: false,
Unlockable: unlockable,
Level:
unlockable.Properties.UnlockOrder ??
DEFAULT_MASTERY_MAXLEVEL,
// TODO: Everything is unlocked. Change this when adding sniper progression
IsLocked: false,
TypeLocaKey: "UI_MENU_PAGE_MASTERY_UNLOCKABLE_NAME_weapon",
})),
Unlockable: sniperLoadout.MainUnlockable,
},
})
},
@ -2074,4 +2066,36 @@ menuDataRouter.get(
},
)
menuDataRouter.get(
"/GetMasteryCompletionDataForUnlockable",
(req: RequestWithJwt<{ unlockableId: string }>, res) => {
// We make this lookup table to quickly get it, there's no other quick way for it.
const unlockToLoc = {
FIREARMS_SC_HERO_SNIPER_HM: "LOCATION_PARENT_AUSTRIA",
FIREARMS_SC_HERO_SNIPER_KNIGHT: "LOCATION_PARENT_AUSTRIA",
FIREARMS_SC_HERO_SNIPER_STONE: "LOCATION_PARENT_AUSTRIA",
FIREARMS_SC_SEAGULL_HM: "LOCATION_PARENT_SALTY",
FIREARMS_SC_SEAGULL_KNIGHT: "LOCATION_PARENT_SALTY",
FIREARMS_SC_SEAGULL_STONE: "LOCATION_PARENT_SALTY",
FIREARMS_SC_FALCON_HM: "LOCATION_PARENT_CAGED",
FIREARMS_SC_FALCON_KNIGHT: "LOCATION_PARENT_CAGED",
FIREARMS_SC_FALCON_STONE: "LOCATION_PARENT_CAGED",
}
res.json({
template: null,
data: {
CompletionData: controller.masteryService.getLocationCompletion(
unlockToLoc[req.query.unlockableId],
unlockToLoc[req.query.unlockableId],
req.gameVersion,
req.jwt.unique_name,
"sniper",
req.query.unlockableId,
),
},
})
},
)
export { menuDataRouter }

View File

@ -41,6 +41,16 @@ type GameFacingDestination = {
OpportunityStatistics: OpportunityStatistics
LocationCompletionPercent: number
Location: Unlockable
// H2016 only
Data?: {
[difficulty: string]: {
ChallengeCompletion: {
ChallengesCount: number
CompletedChallengesCount: number
}
CompletionData: CompletionData
}
}
}
const missionStories = getConfig<Record<string, MissionStory>>(
"MissionStories",
@ -149,8 +159,44 @@ export function destinationsMenu(req: RequestWithJwt): GameFacingDestination[] {
req.jwt.unique_name,
req.gameVersion,
),
Data:
req.gameVersion === "h1"
? {
normal: {
ChallengeCompletion: undefined,
CompletionData: generateCompletionData(
destination,
req.jwt.unique_name,
req.gameVersion,
"mission",
"normal",
),
},
pro1: {
ChallengeCompletion: undefined,
CompletionData: generateCompletionData(
destination,
req.jwt.unique_name,
req.gameVersion,
"mission",
"pro1",
),
},
}
: undefined,
},
}
// TODO: THIS IS NOT CORRECT FOR 2016!
// There are different challenges for normal and pro1 in 2016, right now, we do not support this.
// We're just reusing this for now.
if (req.gameVersion === "h1") {
template.Data.normal.ChallengeCompletion =
template.ChallengeCompletion
template.Data.pro1.ChallengeCompletion =
template.ChallengeCompletion
}
result.push(template)
}

View File

@ -208,7 +208,6 @@ export async function planningView(
const typedInv = createInventory(
req.jwt.unique_name,
req.gameVersion,
userData.Extensions.entP,
sublocation,
)
@ -391,15 +390,18 @@ export async function planningView(
const loadoutMasteryData =
controller.masteryService.getMasteryForUnlockable(
loadoutUnlockable,
req.gameVersion,
)
const locationProgression = (loadoutMasteryData &&
userData.Extensions.progression.Locations[
loadoutMasteryData.Location
]) ?? {
Xp: 0,
Level: 1,
}
const locationProgression =
loadoutMasteryData &&
(loadoutMasteryData.SubPackageId
? userData.Extensions.progression.Locations[
loadoutMasteryData.Location
][loadoutMasteryData.SubPackageId]
: userData.Extensions.progression.Locations[
loadoutMasteryData.Location
])
if (locationProgression.Level < loadoutMasteryData.Level)
loadoutSlots = loadoutSlots.filter(

View File

@ -55,6 +55,12 @@ export const orderedPZMissions: string[] = [
"a2befcec-7799-4987-9215-6a152cb6a320",
]
export const sniperMissionIds: string[] = [
"ff9f46cf-00bd-4c12-b887-eac491c3a96d",
"00e57709-e049-44c9-a2c3-7655e19884fb",
"25b20d86-bb5a-4ebd-b6bb-81ed2779c180",
]
/**
* Gets the ID for a season.
*
@ -141,3 +147,24 @@ export function createMainOpportunityTile(contractId: string) {
})),
}
}
/**
* Generates tiles for menu pages
* @param menuPages An array of menu page IDs.
* @returns The tile object
*/
export function createMenuPageTile(...menuPages: string[]) {
// This is all based on what sniper does, not sure if any others have it.
return {
CategoryType: "MenuPage",
CategoryName: "UI_MENU_PAGE_DEBRIEFING_MAIN_MENU",
Items: menuPages.map((id) => ({
ItemType: null,
ContentType: "MenuPage",
CategoryType: "MenuPage",
Content: {
Name: id,
},
})),
}
}

View File

@ -17,19 +17,8 @@
*/
import { controller } from "../controller"
import { nilUuid } from "../utils"
import { getConfig } from "../configSwizzleManager"
import type {
GameVersion,
MissionManifest,
SniperLoadout,
} from "../types/types"
export type SniperLoadoutConfig = {
[locationId: string]: {
[firearmCharacter: string]: SniperLoadout
}
}
import type { GameVersion, MissionManifest } from "../types/types"
import { getSubLocationByName } from "../contracts/dataGen"
/**
* Creates the sniper loadouts data for a contract. Returns loadouts for all three
@ -51,87 +40,99 @@ export function createSniperLoadouts(
loadoutData = false,
) {
const sniperLoadouts = []
const parentLocation = getSubLocationByName(
contractData.Metadata.Location,
gameVersion,
).Properties.ParentLocation
// This function call is used as it gets all mastery data for the current location
// which includes all the characters we'll need.
// We map it by Id for quick lookup.
const masteryMap = new Map(
controller.masteryService
.getMasteryDataForDestination(parentLocation, gameVersion, userId)
.map((data) => [data.CompletionData.Id, data]),
)
if (contractData.Metadata.Type === "sniper") {
const sLoadouts = getConfig<SniperLoadoutConfig>("SniperLoadouts", true)
for (const charSetup of contractData.Metadata.CharacterSetup) {
for (const character of charSetup.Characters) {
// Get the mastery data for this character
const masteryData = masteryMap.get(
character.MandatoryLoadout[0],
)
for (const index in sLoadouts[contractData.Metadata.Location]) {
const character = sLoadouts[contractData.Metadata.Location][index]
const data = {
Id: character.ID,
Loadout: {
LoadoutData: [
{
SlotId: "0",
SlotName: "carriedweapon",
Items: [
{
Item: {
InstanceId: character.InstanceID,
ProfileId: nilUuid,
// TODO: All mastery upgrades are unlocked. Change this when adding sniper progression.
Unlockable: character.Unlockable[18],
// Get the unlockable that is currently unlocked
const curUnlockable =
masteryData.CompletionData.Level === 1
? masteryData.Unlockable
: masteryData.Drops[
masteryData.CompletionData.Level - 2
].Unlockable
const data = {
Id: character.Id,
Loadout: {
LoadoutData: [
{
SlotId: "0",
SlotName: "carriedweapon",
Items: [
{
Item: {
InstanceId: character.Id,
ProfileId: userId,
Unlockable: curUnlockable,
Properties: {},
},
ItemDetails: {
Capabilities: [],
StatList: Object.keys(
curUnlockable.Properties
.Gameplay,
).map((key) => {
return {
Name: key,
Ratio: curUnlockable
.Properties.Gameplay[
key
],
}
}),
PropertyTexts: [],
},
},
],
Page: 0,
Recommended: {
item: {
InstanceId: character.Id,
ProfileId: userId,
Unlockable: curUnlockable,
Properties: {},
},
ItemDetails: {
Capabilities: [],
StatList: [
{
Name: "clipsize",
Ratio: 0.2,
},
{
Name: "damage",
Ratio: 1.0,
},
{
Name: "range",
Ratio: 1.0,
},
{
Name: "rateoffire",
Ratio: 0.3,
},
],
PropertyTexts: [],
},
type: "carriedweapon",
owned: true,
},
],
Page: 0,
Recommended: {
item: {
InstanceId: nilUuid,
ProfileId: nilUuid,
// TODO: All mastery upgrades are unlocked. Change this when adding sniper progression.
Unlockable: character.Unlockable[18],
Properties: {},
},
type: "carriedweapon",
owned: true,
HasMore: false,
HasMoreLeft: false,
HasMoreRight: false,
OptionalData: {},
},
HasMore: false,
HasMoreLeft: false,
HasMoreRight: false,
OptionalData: {},
},
],
LimitedLoadoutUnlockLevel: 0 as number | undefined,
},
CompletionData: controller.masteryService.getFirearmCompletion(
index,
character.MainUnlockable.Properties.Name,
userId,
gameVersion,
),
}
],
LimitedLoadoutUnlockLevel: 0 as number | undefined,
},
CompletionData: masteryData.CompletionData,
}
if (loadoutData) {
delete data.Loadout.LimitedLoadoutUnlockLevel
sniperLoadouts.push(data.Loadout)
continue
}
if (loadoutData) {
delete data.Loadout.LimitedLoadoutUnlockLevel
sniperLoadouts.push(data.Loadout)
continue
}
sniperLoadouts.push(data)
sniperLoadouts.push(data)
}
}
}

View File

@ -93,11 +93,7 @@ export function getMultiplayerLoadoutData(
false,
)
const inventory = createInventory(
userData.Id,
gameVersion,
userData.Extensions.entP,
)
const inventory = createInventory(userData.Id, gameVersion)
let unlockable = inventory.find(
(unlockable) =>

View File

@ -21,6 +21,7 @@ import path from "path"
import {
castUserProfile,
getMaxProfileLevel,
LATEST_PROFILE_VERSION,
nilUuid,
uuidRegex,
XP_PER_LEVEL,
@ -193,14 +194,7 @@ profileRouter.post(
profileRouter.post(
"/UnlockableService/GetInventory",
(req: RequestWithJwt, res) => {
const exts = getUserData(
req.jwt.unique_name,
req.gameVersion,
).Extensions
res.json(
createInventory(req.jwt.unique_name, req.gameVersion, exts.entP),
)
res.json(createInventory(req.jwt.unique_name, req.gameVersion))
},
)
@ -256,11 +250,7 @@ profileRouter.post(
writeUserData(req.jwt.unique_name, req.gameVersion)
res.json({
Inventory: createInventory(
req.jwt.unique_name,
req.gameVersion,
userdata.Extensions.entP,
),
Inventory: createInventory(req.jwt.unique_name, req.gameVersion),
Stats: req.body.localStats,
})
},
@ -298,6 +288,7 @@ export async function resolveProfiles(
XboxLiveId: null,
PSNAccountId: null,
PSNOnlineId: null,
Version: LATEST_PROFILE_VERSION,
})
}
@ -323,6 +314,7 @@ export async function resolveProfiles(
XboxLiveId: null,
PSNAccountId: null,
PSNOnlineId: null,
Version: LATEST_PROFILE_VERSION,
})
}
@ -352,6 +344,7 @@ export async function resolveProfiles(
XboxLiveId: null,
PSNAccountId: null,
PSNOnlineId: null,
Version: LATEST_PROFILE_VERSION,
})
}
@ -391,7 +384,7 @@ export async function resolveProfiles(
let userdata: UserProfile = outcome.value
if (!fakeIds.includes(outcome?.value?.Id)) {
userdata = castUserProfile(outcome.value)
userdata = castUserProfile(outcome.value, gameVersion)
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any

View File

@ -18,16 +18,17 @@
import type { Response } from "express"
import {
DEFAULT_MASTERY_MAXLEVEL,
contractTypes,
DEFAULT_MASTERY_MAXLEVEL,
difficultyToString,
evergreenLevelForXp,
EVERGREEN_LEVEL_INFO,
evergreenLevelForXp,
handleAxiosError,
isObjectiveActive,
levelForXp,
PEACOCKVERSTRING,
SNIPER_LEVEL_INFO,
sniperLevelForXp,
xpRequiredForLevel,
} from "./utils"
import { contractSessions, getCurrentState } from "./eventHandler"
@ -61,15 +62,20 @@ import { MissionEndRequestQuery } from "./types/gameSchemas"
import { ChallengeFilterType } from "./candle/challengeHelpers"
import { getCompletionPercent } from "./menus/destinations"
import {
CalculateXpResult,
CalculateScoreResult,
MissionEndResponse,
CalculateSniperScoreResult,
CalculateXpResult,
MissionEndChallenge,
MissionEndDrop,
MissionEndEvergreen,
MissionEndChallenge,
MissionEndResponse,
} from "./types/score"
import { MasteryData } from "./types/mastery"
import { getDataForUnlockables } from "./inventory"
import {
createInventory,
getDataForUnlockables,
InventoryItem,
} from "./inventory"
import { calculatePlaystyle } from "./playStyles"
export function calculateGlobalXp(
@ -361,10 +367,159 @@ export function calculateScore(
}
}
export function calculateSniperScore(
contractSession: ContractSession,
timeTotal: Seconds,
inventory: InventoryItem[],
): [CalculateSniperScoreResult, ScoringHeadline[]] {
const timeMinutes = Math.floor(timeTotal / 60)
const timeSeconds = Math.floor(timeTotal % 60)
const timeMiliseconds = Math.floor(((timeTotal % 60) - timeSeconds) * 1000)
const bonusTimeStart =
contractSession.firstKillTimestamp ?? contractSession.timerStart
const bonusTimeEnd = contractSession.timerEnd
const bonusTimeTotal: Seconds =
(bonusTimeEnd as number) - (bonusTimeStart as number)
let timeBonus = 0
// TODO? generate this curve from contractSession.scoring.Settings["timebonus"] somehow
const scorePoints = [
[0, 50000], // 50000 bonus score at 0 secs (0 min)
[240, 40000], // 40000 bonus score at 240 secs (4 min)
[480, 35000], // 35000 bonus score at 480 secs (8 min)
[900, 0], // 0 bonus score at 900 secs (15 min)
]
let prevsecs: number, prevscore: number
for (const [secs, score] of scorePoints) {
if (bonusTimeTotal > secs) {
prevsecs = secs
prevscore = score
continue
}
// linear interpolation between current and previous scorePoints
timeBonus =
prevscore -
((prevscore - score) * (bonusTimeTotal - prevsecs)) /
(secs - prevsecs)
break
}
timeBonus = Math.floor(timeBonus)
const defaultHeadline: Partial<ScoringHeadline> = {
type: "summary",
count: "",
scoreIsFloatingType: false,
fractionNumerator: 0,
fractionDenominator: 0,
scoreTotal: 0,
}
const baseScore = contractSession.scoring.Context["TotalScore"]
const challengeMultiplier = contractSession.scoring.Settings["challenges"][
"Unlockables"
].reduce((acc, unlockable) => {
const item = inventory.find((item) => item.Unlockable.Id === unlockable)
if (item) {
return acc + item.Unlockable.Properties["Multiplier"]
}
return acc
}, 1.0)
const bulletsMissed = 0 // TODO? not sure if neccessary, the penalty is always 0 for inbuilt contracts
const bulletsMissedPenalty =
bulletsMissed *
contractSession.scoring.Settings["bulletsused"]["penalty"]
// Get SA status from global SA challenge for contracttype sniper
const silentAssassin =
contractSession.challengeContexts[
"029c4971-0ddd-47ab-a568-17b007eec04e"
].state !== "Failure"
const saBonus = silentAssassin
? contractSession.scoring.Settings["silentassassin"]["score"]
: 0
const saMultiplier = silentAssassin
? contractSession.scoring.Settings["silentassassin"]["multiplier"]
: 1.0
const subTotalScore = baseScore + timeBonus + saBonus - bulletsMissedPenalty
const totalScore = Math.round(
subTotalScore * challengeMultiplier * saMultiplier,
)
const headlines = [
{
headline: "UI_SNIPERSCORING_SUMMARY_BASESCORE",
scoreTotal: baseScore,
},
{
headline: "UI_SNIPERSCORING_SUMMARY_BULLETS_MISSED_PENALTY",
scoreTotal: bulletsMissedPenalty,
},
{
headline: "UI_SNIPERSCORING_SUMMARY_TIME_BONUS",
count: `${String(timeMinutes).padStart(2, "0")}:${String(
timeSeconds,
).padStart(2, "0")}.${String(timeMiliseconds).padStart(3, "0")}`,
scoreTotal: timeBonus,
},
{
headline: "UI_SNIPERSCORING_SUMMARY_SILENT_ASSASIN_BONUS",
scoreTotal: saBonus,
},
{
headline: "UI_SNIPERSCORING_SUMMARY_SUBTOTAL",
scoreTotal: subTotalScore,
},
{
headline: "UI_SNIPERSCORING_SUMMARY_CHALLENGE_MULTIPLIER",
scoreIsFloatingType: true,
scoreTotal: challengeMultiplier,
},
{
headline: "UI_SNIPERSCORING_SUMMARY_SILENT_ASSASIN_MULTIPLIER",
scoreIsFloatingType: true,
scoreTotal: saMultiplier,
},
{
type: "total",
headline: "UI_SNIPERSCORING_SUMMARY_TOTAL",
scoreTotal: totalScore,
},
].map((e) => {
return Object.assign(
Object.assign({}, defaultHeadline),
e,
) as ScoringHeadline
})
return [
{
FinalScore: totalScore,
BaseScore: baseScore,
TotalChallengeMultiplier: challengeMultiplier,
BulletsMissed: bulletsMissed,
BulletsMissedPenalty: bulletsMissedPenalty,
TimeTaken: timeTotal,
TimeBonus: timeBonus,
SilentAssassin: silentAssassin,
SilentAssassinBonus: saBonus,
SilentAssassinMultiplier: saMultiplier,
},
headlines,
]
}
export async function missionEnd(
req: RequestWithJwt<MissionEndRequestQuery>,
res: Response,
): Promise<void> {
// TODO: For this entire function, add support for 2016 difficulties
// Resolve the contract session
if (!req.query.contractSessionId) {
res.status(400).end()
@ -584,11 +739,12 @@ export async function missionEnd(
})
})
const completionData = generateCompletionData(
let completionData = generateCompletionData(
levelData.Metadata.Location,
req.jwt.unique_name,
req.gameVersion,
contractData.Metadata.Type,
req.query.masteryUnlockableId,
)
// Calculate the old location progression based on the current one and process it
@ -599,19 +755,30 @@ export async function missionEnd(
const newLocationXp = completionData.XP
let newLocationLevel = levelForXp(newLocationXp)
userData.Extensions.progression.Locations[
locationParentId.toLowerCase()
].PreviouslySeenXp = newLocationXp
if (!req.query.masteryUnlockableId) {
userData.Extensions.progression.Locations[
locationParentId
].PreviouslySeenXp = newLocationXp
}
writeUserData(req.jwt.unique_name, req.gameVersion)
const masteryData =
controller.masteryService.getMasteryPackage(locationParentId)
const masteryData = controller.masteryService.getMasteryPackage(
locationParentId,
req.gameVersion,
)
let maxLevel = 1
let locationLevelInfo = [0]
if (masteryData) {
maxLevel = masteryData.MaxLevel || DEFAULT_MASTERY_MAXLEVEL
maxLevel =
(req.query.masteryUnlockableId
? masteryData.SubPackages.find(
(subPkg) => subPkg.Id === req.query.masteryUnlockableId,
).MaxLevel
: masteryData.MaxLevel) || DEFAULT_MASTERY_MAXLEVEL
locationLevelInfo = Array.from({ length: maxLevel }, (_, i) => {
return xpRequiredForLevel(i + 1)
@ -758,37 +925,69 @@ export async function missionEnd(
SilentAssassin: calculateScoreResult.silentAssassin,
}
// TODO: Calculate proper Sniper XP and Score
// TODO: Move most of this to its own calculateSniperScore function
if (contractData.Metadata.Type === "sniper") {
const sniperLoadouts = getConfig("SniperLoadouts", true)
const userInventory = createInventory(
req.jwt.unique_name,
req.gameVersion,
undefined,
)
const mainUnlockableProperties =
sniperLoadouts[contractData.Metadata.Location][
req.query.masteryUnlockableId
].MainUnlockable.Properties
const [sniperScore, headlines] = calculateSniperScore(
sessionDetails,
timeTotal,
userInventory,
)
sniperChallengeScore = sniperScore
// Grant sniper mastery
controller.progressionService.grantProfileProgression(
0,
sniperScore.FinalScore,
[],
sessionDetails,
userData,
req.gameVersion,
locationParentId,
req.query.masteryUnlockableId,
)
// Update completion data with latest mastery
locationLevelInfo = SNIPER_LEVEL_INFO
oldLocationLevel = sniperLevelForXp(oldLocationXp)
// Temporarily get completion data for the unlockable
completionData = generateCompletionData(
levelData.Metadata.Location,
req.jwt.unique_name,
req.gameVersion,
"sniper", // We know the type will be sniper.
req.query.masteryUnlockableId,
)
newLocationLevel = completionData.Level
unlockableProgression = {
LevelInfo: SNIPER_LEVEL_INFO,
XP: SNIPER_LEVEL_INFO[SNIPER_LEVEL_INFO.length - 1],
Level: SNIPER_LEVEL_INFO.length,
XPGain: 0,
Id: mainUnlockableProperties.ProgressionKey,
Name: mainUnlockableProperties.Name,
Id: completionData.Id,
Level: completionData.Level,
LevelInfo: locationLevelInfo,
Name: completionData.Name,
XP: completionData.XP,
XPGain:
completionData.Level === completionData.MaxLevel
? 0
: sniperScore.FinalScore,
}
sniperChallengeScore = {
FinalScore: 112000,
BaseScore: 112000,
TotalChallengeMultiplier: 1.0,
BulletsMissed: 0,
BulletsMissedPenalty: 0,
TimeTaken: timeTotal,
TimeBonus: 0,
SilentAssassin: false,
SilentAssassinBonus: 0,
SilentAssassinMultiplier: 1.0,
}
userData.Extensions.progression.Locations[locationParentId][
req.query.masteryUnlockableId
].PreviouslySeenXp = completionData.XP
writeUserData(req.jwt.unique_name, req.gameVersion)
// Set the completion data to the location so the end screen formats properly.
completionData = generateCompletionData(
levelData.Metadata.Location,
req.jwt.unique_name,
req.gameVersion,
)
// Override the contract score
contractScore = undefined
@ -796,88 +995,26 @@ export async function missionEnd(
// Override the playstyle
playstyle = undefined
// Override the calculated score
const timeMinutes = Math.floor(timeTotal / 60)
const timeSeconds = Math.floor(timeTotal % 60)
const timeMiliseconds = Math.floor(
((timeTotal % 60) - timeSeconds) * 1000,
)
const defaultHeadline: Partial<ScoringHeadline> = {
type: "summary",
count: "",
scoreIsFloatingType: false,
fractionNumerator: 0,
fractionDenominator: 0,
scoreTotal: 0,
}
const headlines = [
{
headline: "UI_SNIPERSCORING_SUMMARY_BASESCORE",
scoreTotal: 112000,
},
{
headline: "UI_SNIPERSCORING_SUMMARY_BULLETS_MISSED_PENALTY",
scoreTotal: 0,
},
{
headline: "UI_SNIPERSCORING_SUMMARY_TIME_BONUS",
count: `${String(timeMinutes).padStart(2, "0")}:${String(
timeSeconds,
).padStart(2, "0")}.${String(timeMiliseconds).padStart(
3,
"0",
)}`,
scoreTotal: 0,
},
{
headline: "UI_SNIPERSCORING_SUMMARY_SILENT_ASSASIN_BONUS",
scoreTotal: 0,
},
{
headline: "UI_SNIPERSCORING_SUMMARY_SUBTOTAL",
scoreTotal: 112000,
},
{
headline: "UI_SNIPERSCORING_SUMMARY_CHALLENGE_MULTIPLIER",
scoreIsFloatingType: true,
scoreTotal: 1.0,
},
{
headline: "UI_SNIPERSCORING_SUMMARY_SILENT_ASSASIN_MULTIPLIER",
scoreIsFloatingType: true,
scoreTotal: 1.0,
},
{
type: "total",
headline: "UI_SNIPERSCORING_SUMMARY_TOTAL",
scoreTotal: 112000,
},
]
calculateScoreResult.stars = undefined
calculateScoreResult.scoringHeadlines = headlines.map((e) => {
return Object.assign(
Object.assign({}, defaultHeadline),
e,
) as ScoringHeadline
})
calculateScoreResult.scoringHeadlines = headlines
}
// Mastery Drops
let masteryDrops: MissionEndDrop[] = []
if (newLocationLevel - oldLocationLevel > 0) {
// We get the subpackage as it functions like getMasteryDataForDestination
// but allows us to get the specific unlockable if required.
const masteryData =
controller.masteryService.getMasteryDataForDestination(
controller.masteryService.getMasteryDataForSubPackage(
locationParentId,
req.query.masteryUnlockableId ?? undefined,
req.gameVersion,
req.jwt.unique_name,
) as MasteryData[]
) as MasteryData
if (masteryData.length > 0) {
masteryDrops = masteryData[0].Drops.filter(
if (masteryData) {
masteryDrops = masteryData.Drops.filter(
(e) =>
e.Level > oldLocationLevel && e.Level <= newLocationLevel,
).map((e) => {
@ -989,10 +1126,11 @@ export async function missionEnd(
if (
getFlag("leaderboards") === true &&
req.gameVersion !== "scpc" &&
req.gameVersion !== "h1" &&
sessionDetails.compat === true &&
contractData.Metadata.Type !== "vsrace"
contractData.Metadata.Type !== "vsrace" &&
contractData.Metadata.Type !== "evergreen" &&
// Disable sending sniper scores for now
contractData.Metadata.Type !== "sniper"
) {
try {
// update leaderboards
@ -1031,8 +1169,7 @@ export async function missionEnd(
StarCount: calculateScoreResult.stars,
},
GroupIndex: 0,
// TODO sniper scores
SniperChallengeScore: null,
SniperChallengeScore: sniperChallengeScore,
PlayStyle: result.ScoreOverview.PlayStyle || null,
Description: "UI_MENU_SCORE_CONTRACT_COMPLETED",
ContractSessionId: req.query.contractSessionId,

View File

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { CompletionData, Unlockable } from "./types"
import { CompletionData, GameVersion, Unlockable } from "./types"
export interface MasteryDataTemplate {
template: unknown
@ -26,19 +26,40 @@ export interface MasteryDataTemplate {
}
}
export interface MasteryPackage {
export interface MasteryPackageDrop {
Id: string
Level: number
}
interface MasterySubPackage {
Id: string
MaxLevel?: number
Drops: MasteryPackageDrop[]
}
/**
* @since v7.0.0
* The Id field has been renamed to LocationId to properly reflect what it is.
*
* Mastery packages may have Drops OR SubPackages, never the two.
* This is to properly support sniper mastery by integrating it into the current system
* and mastery on H2016 as it is separated by difficulty.
*
* Also, a GameVersions array has been added to support multi-version mastery.
*/
export interface MasteryPackage {
LocationId: string
GameVersions: GameVersion[]
MaxLevel?: number
HideProgression?: boolean
Drops: {
Id: string
Level: number
}[]
Drops?: MasteryPackageDrop[]
SubPackages?: MasterySubPackage[]
}
export interface MasteryData {
CompletionData: CompletionData
Drops: MasteryDrop[]
Unlockable?: Unlockable
}
export interface MasteryDrop {
@ -51,5 +72,6 @@ export interface MasteryDrop {
export interface UnlockableMasteryData {
Location: string
SubPackageId?: string
Level: number
}

View File

@ -41,6 +41,19 @@ export interface CalculateScoreResult {
scoreWithBonus: number
}
export interface CalculateSniperScoreResult {
FinalScore: number
BaseScore: number
TotalChallengeMultiplier: number
BulletsMissed: number
BulletsMissedPenalty: number
TimeTaken: number
TimeBonus: number
SilentAssassin: boolean
SilentAssassinBonus: number
SilentAssassinMultiplier: number
}
export interface MissionEndChallenge {
ChallengeId: string
ChallengeTags: string[]

View File

@ -16,6 +16,8 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { IContextListener } from "../statemachines/contextListeners"
export type Playstyle = {
Id: string
Name: string
@ -40,3 +42,30 @@ export type ScoringHeadline = {
fractionDenominator: number
scoreTotal: number
}
export type ManifestScoringModule =
| ScoringModule & {
Type: string
}
export type ManifestScoringDefinition = {
ContextListeners?: null | Record<string, IContextListener<never>>
ScoreEvents?: {
[name: string]: {
type: number
text: string
}
}
States?: Record<string, unknown>
Constants?: Record<string, unknown>
Context?: Record<string, unknown | string[] | string>
}
export type ScoringModule = {
score?: number
maxtime?: number
multiplier?: number
penalty?: number
Unlockables?: string[]
ScoringDefinitions?: ManifestScoringDefinition[]
}

View File

@ -1,99 +0,0 @@
/*
* The Peacock Project - a HITMAN server replacement.
* Copyright (C) 2021-2023 The Peacock Project Team
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
/* eslint-disable @typescript-eslint/no-explicit-any */
import { StateMachineLike } from "@peacockproject/statemachine-parser"
export type SniperScoreModules = {
Type: string
ScoringDefinitions: StateMachineLike<SniperContext, SniperConstants>[]
}
type SniperConstants = Readonly<{
Score_Synchronised_Kill: number
Score_Synchronised_Kill_Successive: number
SynchronisedKillTimerLength: number
Score_Kill_Bodyshot: number
Score_Kill_Streak: number
Score_Kill_Streak_Successive: number
Score_Kill_Headshot: number
Score_Kill_Headshot_Streak: number
Score_Kill_Headshot_Streak_Successive: number
Score_Kill_Headshot_Successive: number
Score_Kill_Multi: number
Score_Kill_Multi_Successive: number
Score_Kill_Multi_Shot: number
Score_Kill_Multi_Shot_Successive: number
Score_Kill_Moving: number
Score_Kill_Moving_Successive: number
Score_Kill_Moving_Streak: number
Score_Kill_Moving_Streak_Successive: number
Score_Kill_Explosion: number
Score_Kill_Explosion_Successive: number
Score_Kill_Civilian: number
Score_Kill_Civilian_Successive: number
Score_Kill_Accident: number
Score_Kill_Accident_Successive: number
Score_Kill_Distraction: number
Score_Kill_Distraction_Successive: number
Score_BodyHidden: number
Score_BodyHidden_Successive: number
}>
export type SniperContext = {
PlayerIds: any[]
SuccessivePlayerIds: any[]
Events: any[]
TotalScore: number
SynchronisedKill_Count: number
SynchronisedKill_Successive_CurrentScore: number
SynchronisedKill_CurrentScore: number
Targets: string[]
BulletsUsed: number
HeadShot_Successive_Count: number
HeadShot_Successive_CurrentScore: number
HeadShot_CurrentScore: number
HeadShot_Streak_Count: number
HeadShot_Streak_Count_Temp: number
HeadShot_Streak_CurrentScore: number
Moving_Successive_Count: number
Moving_Successive_CurrentScore: number
Moving_CurrentScore: number
Moving_Streak_Count: number
Moving_Streak_Count_Temp: number
Moving_Streak_CurrentScore: number
Kill_Streak_Count: number
Kill_Streak_Count_Temp: number
Kill_Streak_CurrentScore: number
Accident_Successive_Count: number
Accident_Successive_CurrentScore: number
Explosion_Successive_Count: number
Explosion_Successive_CurrentScore: number
BodyHidden_Successive_Count: number
BodyHidden_Successive_CurrentScore: number
BodyHidden_CurrentScore: number
CivilianKill_Successive_Count: number
CivilianKill_Successive_CurrentScore: number
CivilianKill_CurrentScore: number
MultiKill_CurrentScore: number
MultiKill_Shot_CurrentScore: number
ReInitialiseTimer: number
StreakTime: number
PlayerId: number
}

View File

@ -27,6 +27,8 @@ import {
} from "./challenges"
import { SessionGhostModeDetails } from "../multiplayer/multiplayerService"
import { IContextListener } from "../statemachines/contextListeners"
import { ManifestScoringModule, ScoringModule } from "./scoring"
import { Timer } from "@peacockproject/statemachine-parser"
/**
* A duration or relative point in time expressed in seconds.
@ -275,6 +277,33 @@ export interface ContractSession {
scoringScreenEndState: string
failed: boolean
}
/**
* Scoring settings, and statemachine settings.
* Currently only used for Sniper Challenge missions.
*
* Settings: Keyed by the type property in modules.
* Context: The current context of the scoring statemachine.
* Definition: The initial definition of the scoring statemachine.
* State: The current state of the scoring statemachine.
* Timers: The current timers of the scoring statemachine.
*
* @since v7.0.0
*/
scoring?: {
Settings: {
[name: string]: ScoringModule
}
Context: unknown
Definition: unknown
State: string
Timers: Timer[]
}
/**
* Timestamp of first kill.
* Used for calculating Sniper Challenge time bonus.
* @since v7.0.0
*/
firstKillTimestamp?: number
}
/**
@ -427,6 +456,12 @@ export interface ContractHistory {
IsEscalation?: boolean
}
export interface ProgressionData {
Xp: number
Level: number
PreviouslySeenXp: number
}
export interface UserProfile {
Id: string
LinkedAccounts: {
@ -478,12 +513,16 @@ export interface UserProfile {
ActionXp: number
}[]
}
/**
* If the mastery location has subpackages and not drops, it will
* be an object.
*/
Locations: {
[location: string]: {
Xp: number
Level: number
PreviouslySeenXp: number
}
[location: string]:
| ProgressionData
| {
[subPackageId: string]: ProgressionData
}
}
}
defaultloadout?: {
@ -505,6 +544,14 @@ export interface UserProfile {
MyContracts: string
MyPlaylist: string
}
menudata: {
difficulty: {
destinations: {
[locationId: string]: "normal" | "pro1"
}
}
newunlockables: string[]
}
}
opportunityprogression: {
[opportunityId: RepositoryId]: boolean
@ -521,6 +568,10 @@ export interface UserProfile {
XboxLiveId: string | null
PSNAccountId: string | null
PSNOnlineId: string | null
/**
* @since v7.0.0 user profiles are now versioned.
*/
Version: number
}
export interface RatingKill {
@ -925,6 +976,8 @@ export interface MissionManifestMetadata {
CpdId?: string
// Elusive custom property (like official's year)
Season?: number
// Used for sniper scoring
Modules?: ManifestScoringModule[]
}
export interface GroupObjectiveDisplayOrderItem {

View File

@ -21,6 +21,7 @@ import type { NextFunction, Response } from "express"
import type {
GameVersion,
MissionManifestObjective,
PeacockLocationsData,
RepositoryId,
RequestWithJwt,
ServerVersion,
@ -31,7 +32,7 @@ import axios, { AxiosError } from "axios"
import { log, LogLevel } from "./loggingInterop"
import { writeFileSync } from "fs"
import { getFlag } from "./flags"
import { getConfig } from "./configSwizzleManager"
import { getConfig, getVersionedConfig } from "./configSwizzleManager"
/**
* True if the server is being run by the launcher, false otherwise.
@ -57,6 +58,13 @@ export const versions: GameVersion[] = ["h1", "h2", "h3"]
export const contractCreationTutorialId = "d7e2607c-6916-48e2-9588-976c7d8998bb"
/**
* The latest profile version, this should be changed in conjunction with the updating mechanism.
*
* See docs/USER_PROFILES.md for more.
*/
export const LATEST_PROFILE_VERSION = 1
export async function checkForUpdates(): Promise<void> {
if (getFlag("updateChecking") === false) {
return
@ -192,6 +200,18 @@ export const SNIPER_LEVEL_INFO: number[] = [
38000000, 47000000, 58000000, 70000000,
]
export function sniperLevelForXp(xp: number): number {
for (let i = 1; i < SNIPER_LEVEL_INFO.length; i++) {
if (xp >= SNIPER_LEVEL_INFO[i]) {
continue
}
return i
}
return 1
}
/**
* Get the number of xp needed to reach a level in sniper missions.
* @param level The level in question.
@ -208,6 +228,123 @@ export function clampValue(value: number, min: number, max: number) {
return Math.max(min, Math.min(value, max))
}
/**
* Updates a user profile depending on the current version (if any).
* @param profile The userprofile to update
* @param gameVersion The game version
* @returns The updated user profile
*/
function updateUserProfile(
profile: UserProfile,
gameVersion: GameVersion,
): void {
/**
* This switch is structured such that the current profile version will return.
* thus stopping the function.
*
* As the version number is incremented, the previous version should be added
* as a case to update it to the newest version.
*/
switch (profile.Version) {
case LATEST_PROFILE_VERSION:
// This profile updated to the latest version, we're done.
return
default: {
// Check that the profile version is indeed undefined. If it isn't,
// we've forgotten to add a version to the switch.
if (profile.Version !== undefined) {
log(
LogLevel.ERROR,
`Unhandled profile version ${profile.Version}`,
)
return
}
// Profile has no version, update it to version 1, then re-run
// the function to update it to subsequent versions.
const sniperLocs = {
LOCATION_PARENT_AUSTRIA: [
"FIREARMS_SC_HERO_SNIPER_HM",
"FIREARMS_SC_HERO_SNIPER_KNIGHT",
"FIREARMS_SC_HERO_SNIPER_STONE",
],
LOCATION_PARENT_SALTY: [
"FIREARMS_SC_SEAGULL_HM",
"FIREARMS_SC_SEAGULL_KNIGHT",
"FIREARMS_SC_SEAGULL_STONE",
],
LOCATION_PARENT_CAGED: [
"FIREARMS_SC_FALCON_HM",
"FIREARMS_SC_FALCON_KNIGHT",
"FIREARMS_SC_FALCON_STONE",
],
}
// We need this to ensure all locations are added.
const allLocs = Object.keys(
getVersionedConfig<PeacockLocationsData>(
"LocationsData",
gameVersion,
false,
).parents,
).map((key) => key.toLocaleLowerCase())
profile.Extensions.progression.Locations = allLocs.reduce(
(obj, key) => {
const newKey = key.toLocaleUpperCase()
const curData =
profile.Extensions.progression.Locations[key]
if (gameVersion === "h1") {
// No sniper locations, but we add normal and pro1
obj[newKey] = {
// Data from previous profiles only contains normal and is the default.
normal: {
Xp: curData.Xp ?? 0,
Level: curData.Level ?? 1,
PreviouslySeenXp: curData.PreviouslySeenXp ?? 0,
},
pro1: {
Xp: 0,
Level: 1,
PreviouslySeenXp: 0,
},
}
} else {
// We need to update sniper locations.
obj[newKey] = sniperLocs[newKey]
? sniperLocs[newKey].reduce((obj, uId) => {
obj[uId] = {
Xp: 0,
Level: 1,
PreviouslySeenXp: 0,
}
return obj
}, {})
: {
Xp: curData.Xp ?? 0,
Level: curData.Level ?? 1,
PreviouslySeenXp:
curData.PreviouslySeenXp ?? 0,
}
}
return obj
},
{},
)
delete profile.Extensions.progression["Unlockables"]
profile.Version = 1
return updateUserProfile(profile, gameVersion)
}
}
}
/**
* Returns whether a location is a sniper location. Works for both parent and child locations.
* @param location The location ID string.
@ -221,7 +358,11 @@ export function isSniperLocation(location: string): boolean {
)
}
export function castUserProfile(profile: UserProfile): UserProfile {
export function castUserProfile(
profile: UserProfile,
gameVersion: GameVersion,
path?: string,
): UserProfile {
const j = fastClone(profile)
if (!j.Extensions || Object.keys(j.Extensions).length === 0) {
@ -311,8 +452,15 @@ export function castUserProfile(profile: UserProfile): UserProfile {
}
}
if (j.Version !== LATEST_PROFILE_VERSION) {
// This profile is not the latest version. We must update it.
log(LogLevel.DEBUG, `Profile is outdated, updating...`)
updateUserProfile(j, gameVersion)
dirty = true
}
if (dirty) {
writeFileSync(`userdata/users/${j.Id}.json`, JSON.stringify(j))
writeFileSync(path ?? `userdata/users/${j.Id}.json`, JSON.stringify(j))
log(LogLevel.INFO, "Profile successfully repaired!")
}

View File

@ -1,5 +1,6 @@
{
"Id": "LOCATION_PARENT_ROCKY",
"LocationId": "LOCATION_PARENT_ROCKY",
"GameVersions": ["h3"],
"Drops": [
{
"Id": "AGENCYPICKUP_ROCKY_SMALL_BIGCENTRALTREE",

View File

@ -1,5 +1,6 @@
{
"Id": "LOCATION_PARENT_BANGKOK",
"LocationId": "LOCATION_PARENT_BANGKOK",
"GameVersions": ["h3"],
"Drops": [
{
"Id": "STARTING_LOCATION_BANGKOK_SECURITY_HUT",

View File

@ -0,0 +1,94 @@
{
"LocationId": "LOCATION_PARENT_BANGKOK",
"GameVersions": ["h2"],
"Drops": [
{
"Id": "STARTING_LOCATION_BANGKOK_SECURITY_HUT",
"Level": 2
},
{
"Id": "PROP_MELEE_MACHETE_BLOODY",
"Level": 2
},
{
"Id": "AGENCYPICKUP_BANGKOK_GARDEN_SHED",
"Level": 3
},
{
"Id": "STARTING_LOCATION_BANGKOK_TIGER_47_SUITE",
"Level": 4
},
{
"Id": "FIREARMS_HERO_SHOTGUN_SEMIAUTO_ENRAM_HV_COVERT",
"Level": 5
},
{
"Id": "AGENCYPICKUP_BANGKOK_TIGER_PENTHOUSE",
"Level": 6
},
{
"Id": "STARTING_LOCATION_BANGKOK_BAR",
"Level": 7
},
{
"Id": "FIREARMS_HERO_SMG_TACTICAL_TAC_SMG_S",
"Level": 7
},
{
"Id": "AGENCYPICKUP_BANGKOK_TIGER_RESTAURANT_TOILET",
"Level": 8
},
{
"Id": "STARTING_LOCATION_BANGKOK_TIGER_RESTAURANT_KITCHEN",
"Level": 9
},
{
"Id": "PROP_MELEE_SYRINGE_EMETIC",
"Level": 10
},
{
"Id": "AGENCYPICKUP_BANGKOK_TIGER_BALCONY_PLANTER",
"Level": 11
},
{
"Id": "STARTING_LOCATION_BANGKOK_GARDEN",
"Level": 12
},
{
"Id": "AGENCYPICKUP_BANGKOK_STORAGE_ROOM",
"Level": 13
},
{
"Id": "AGENCYPICKUP_BANGKOK_TIGER_BASEMENT_SOUTHWING_B",
"Level": 14
},
{
"Id": "FIREARMS_HERO_SNIPER_HEAVY_JAEGER_TIGER",
"Level": 15
},
{
"Id": "STARTING_LOCATION_BANGKOK_TIGER_LINEN_ROOM",
"Level": 16
},
{
"Id": "AGENCYPICKUP_BANGKOK_TIGER_BATHROOM_TOWELS",
"Level": 17
},
{
"Id": "AGENCYPICKUP_BANGKOK_TIGER_STUDIO",
"Level": 18
},
{
"Id": "STARTING_LOCATION_BANGKOK_TIGER_CREW_ROOM",
"Level": 19
},
{
"Id": "FIREARMS_HERO_PISTOL_KRUGERMEIER",
"Level": 20
},
{
"Id": "PROP_DEVICE_ICA_MODULAR_REMOTE_MUSICDISTRACTION",
"Level": 20
}
]
}

View File

@ -0,0 +1,169 @@
{
"LocationId": "LOCATION_PARENT_BANGKOK",
"GameVersions": ["h1"],
"SubPackages": [
{
"Id": "normal",
"Drops": [
{
"Id": "STARTING_LOCATION_BANGKOK_SECURITY_HUT",
"Level": 2
},
{
"Id": "AGENCYPICKUP_BANGKOK_GARDEN_SHED",
"Level": 3
},
{
"Id": "STARTING_LOCATION_BANGKOK_TIGER_47_SUITE",
"Level": 4
},
{
"Id": "PROP_MELEE_MACHETE_BLOODY",
"Level": 5
},
{
"Id": "DIFFICULTY_UNLOCK_PRO1_BANGKOK",
"Level": 5
},
{
"Id": "AGENCYPICKUP_BANGKOK_TIGER_PENTHOUSE",
"Level": 6
},
{
"Id": "STARTING_LOCATION_BANGKOK_BAR",
"Level": 7
},
{
"Id": "AGENCYPICKUP_BANGKOK_TIGER_RESTAURANT_TOILET",
"Level": 8
},
{
"Id": "STARTING_LOCATION_BANGKOK_TIGER_RESTAURANT_KITCHEN",
"Level": 9
},
{
"Id": "FIREARMS_HERO_SHOTGUN_SEMIAUTO_012_SU",
"Level": 10
},
{
"Id": "PROP_DEVICE_ICA_PHONE_EXPLOSIVE",
"Level": 10
},
{
"Id": "AGENCYPICKUP_BANGKOK_TIGER_BALCONY_PLANTER",
"Level": 11
},
{
"Id": "STARTING_LOCATION_BANGKOK_GARDEN",
"Level": 12
},
{
"Id": "AGENCYPICKUP_BANGKOK_STORAGE_ROOM",
"Level": 13
},
{
"Id": "AGENCYPICKUP_BANGKOK_TIGER_BASEMENT_SOUTHWING_B",
"Level": 14
},
{
"Id": "FIREARMS_HERO_SNIPER_HEAVY_013_SU_SUB_SCOUT_SKIN09",
"Level": 15
},
{
"Id": "STARTING_LOCATION_BANGKOK_TIGER_LINEN_ROOM",
"Level": 16
},
{
"Id": "AGENCYPICKUP_BANGKOK_TIGER_BATHROOM_TOWELS",
"Level": 17
},
{
"Id": "AGENCYPICKUP_BANGKOK_TIGER_STUDIO",
"Level": 18
},
{
"Id": "STARTING_LOCATION_BANGKOK_TIGER_CREW_ROOM",
"Level": 19
},
{
"Id": "FIREARMS_HERO_PISTOL_KRUGERMEIER_001_SU_SKIN08",
"Level": 20
},
{
"Id": "PROP_DEVICE_ICA_MODULAR_REMOTE_MUSICDISTRACTION",
"Level": 20
}
]
},
{
"Id": "pro1",
"MaxLevel": 10,
"Drops": [
{
"Id": "PRO1_STARTING_LOCATION_BANGKOK_SECURITY_HUT",
"Level": 2
},
{
"Id": "PRO1_AGENCYPICKUP_BANGKOK_GARDEN_SHED",
"Level": 2
},
{
"Id": "PRO1_STARTING_LOCATION_BANGKOK_TIGER_47_SUITE",
"Level": 3
},
{
"Id": "PRO1_AGENCYPICKUP_BANGKOK_TIGER_PENTHOUSE",
"Level": 3
},
{
"Id": "PRO1_STARTING_LOCATION_BANGKOK_BAR",
"Level": 4
},
{
"Id": "PRO1_AGENCYPICKUP_BANGKOK_TIGER_RESTAURANT_TOILET",
"Level": 4
},
{
"Id": "PRO1_STARTING_LOCATION_BANGKOK_TIGER_RESTAURANT_KITCHEN",
"Level": 5
},
{
"Id": "PRO1_AGENCYPICKUP_BANGKOK_TIGER_BALCONY_PLANTER",
"Level": 5
},
{
"Id": "PRO1_STARTING_LOCATION_BANGKOK_GARDEN",
"Level": 6
},
{
"Id": "PRO1_AGENCYPICKUP_BANGKOK_STORAGE_ROOM",
"Level": 6
},
{
"Id": "PRO1_AGENCYPICKUP_BANGKOK_TIGER_BASEMENT_SOUTHWING_B",
"Level": 7
},
{
"Id": "PRO1_STARTING_LOCATION_BANGKOK_TIGER_LINEN_ROOM",
"Level": 7
},
{
"Id": "PRO1_AGENCYPICKUP_BANGKOK_TIGER_BATHROOM_TOWELS",
"Level": 8
},
{
"Id": "PRO1_AGENCYPICKUP_BANGKOK_TIGER_STUDIO",
"Level": 8
},
{
"Id": "PRO1_STARTING_LOCATION_BANGKOK_TIGER_CREW_ROOM",
"Level": 9
},
{
"Id": "FIREARMS_HERO_PISTOL_TACTICAL_018_FA_SU_SKIN01",
"Level": 10
}
]
}
]
}

View File

@ -1,5 +1,6 @@
{
"Id": "LOCATION_PARENT_EDGY",
"LocationId": "LOCATION_PARENT_EDGY",
"GameVersions": ["h3"],
"Drops": [
{
"Id": "AGENCYPICKUP_EDGY_LARGE_ENTRANCE",

View File

@ -1,5 +1,6 @@
{
"Id": "LOCATION_PARENT_TRAPPED",
"LocationId": "LOCATION_PARENT_TRAPPED",
"GameVersions": ["h3"],
"MaxLevel": 5,
"Drops": [
{

View File

@ -1,5 +1,6 @@
{
"Id": "LOCATION_PARENT_WET",
"LocationId": "LOCATION_PARENT_WET",
"GameVersions": ["h3"],
"Drops": [
{
"Id": "AGENCYPICKUP_WET_SMALL_ARCADE",

View File

@ -1,5 +1,6 @@
{
"Id": "LOCATION_PARENT_COLORADO",
"LocationId": "LOCATION_PARENT_COLORADO",
"GameVersions": ["h3"],
"Drops": [
{
"Id": "AGENCYPICKUP_COLORADO_WATERTOWER",

View File

@ -0,0 +1,90 @@
{
"LocationId": "LOCATION_PARENT_COLORADO",
"GameVersions": ["h2"],
"Drops": [
{
"Id": "AGENCYPICKUP_COLORADO_WATERTOWER",
"Level": 2
},
{
"Id": "FIREARMS_HERO_RIFLE_FULLAUTO_RS_15",
"Level": 2
},
{
"Id": "STARTING_LOCATION_COLORADO_ORCHARD",
"Level": 3
},
{
"Id": "AGENCYPICKUP_COLORADO_BULL_ORCHARD_BOX",
"Level": 4
},
{
"Id": "FIREARMS_HERO_RIFLE_SEMIAUTO_TAC_4_SA_STEALTH",
"Level": 5
},
{
"Id": "STARTING_LOCATION_COLORADO_BULL_COURTYARD_GARAGE",
"Level": 6
},
{
"Id": "AGENCYPICKUP_COLORADO_HAYBARN",
"Level": 7
},
{
"Id": "PROP_MELEE_EXPANDABLE_BATON",
"Level": 7
},
{
"Id": "STARTING_LOCATION_COLORADO_BULL_DEMOLITION_AREA",
"Level": 8
},
{
"Id": "AGENCYPICKUP_COLORADO_BULL_RED_BARN",
"Level": 9
},
{
"Id": "FIREARMS_HERO_PISTOL_TACTICAL_ICA_19_FA_STEALTH",
"Level": 10
},
{
"Id": "STARTING_LOCATION_COLORADO_BULL_GREENHOUSE",
"Level": 11
},
{
"Id": "AGENCYPICKUP_COLORADO_COURTYARD_OUTHOUSE",
"Level": 12
},
{
"Id": "AGENCYPICKUP_COLORADO_WESTBRIDGE",
"Level": 13
},
{
"Id": "STARTING_LOCATION_COLORADO_WEST_BRIDGE",
"Level": 14
},
{
"Id": "FIREARMS_HERO_SNIPER_MEDIUM_SIEGER_300",
"Level": 15
},
{
"Id": "STARTING_LOCATION_COLORADO_WATER_TOWER",
"Level": 16
},
{
"Id": "AGENCYPICKUP_COLORADO_BULL_ORCHARD_PARKINGAREA",
"Level": 17
},
{
"Id": "STARTING_LOCATION_COLORADO_BULL_HACKER_ROOM",
"Level": 18
},
{
"Id": "AGENCYPICKUP_COLORADO_BULL_HOUSE_LAUNDRYROOM",
"Level": 19
},
{
"Id": "PROP_DEVICE_ICA_C4_REMOTE_EXPLOSIVE",
"Level": 20
}
]
}

View File

@ -0,0 +1,169 @@
{
"LocationId": "LOCATION_PARENT_COLORADO",
"GameVersions": ["h1"],
"SubPackages": [
{
"Id": "normal",
"Drops": [
{
"Id": "AGENCYPICKUP_COLORADO_WATERTOWER",
"Level": 2
},
{
"Id": "STARTING_LOCATION_COLORADO_ORCHARD",
"Level": 3
},
{
"Id": "AGENCYPICKUP_COLORADO_BULL_ORCHARD_BOX",
"Level": 4
},
{
"Id": "PROP_MELEE_SYRINGE_EMETIC",
"Level": 5
},
{
"Id": "DIFFICULTY_UNLOCK_PRO1_COLORADO",
"Level": 5
},
{
"Id": "STARTING_LOCATION_COLORADO_BULL_COURTYARD_GARAGE",
"Level": 6
},
{
"Id": "AGENCYPICKUP_COLORADO_HAYBARN",
"Level": 7
},
{
"Id": "STARTING_LOCATION_COLORADO_BULL_DEMOLITION_AREA",
"Level": 8
},
{
"Id": "AGENCYPICKUP_COLORADO_BULL_RED_BARN",
"Level": 9
},
{
"Id": "FIREARMS_HERO_RIFLE_FULLAUTO_HEAVY_SU_001_SKIN08",
"Level": 10
},
{
"Id": "PROP_MELEE_EXPANDABLE_BATON",
"Level": 10
},
{
"Id": "STARTING_LOCATION_COLORADO_BULL_GREENHOUSE",
"Level": 11
},
{
"Id": "AGENCYPICKUP_COLORADO_COURTYARD_OUTHOUSE",
"Level": 12
},
{
"Id": "AGENCYPICKUP_COLORADO_WESTBRIDGE",
"Level": 13
},
{
"Id": "STARTING_LOCATION_COLORADO_WEST_BRIDGE",
"Level": 14
},
{
"Id": "PROP_TOOL_ELECTRICAL_KIT",
"Level": 15
},
{
"Id": "STARTING_LOCATION_COLORADO_WATER_TOWER",
"Level": 16
},
{
"Id": "AGENCYPICKUP_COLORADO_BULL_ORCHARD_PARKINGAREA",
"Level": 17
},
{
"Id": "STARTING_LOCATION_COLORADO_BULL_HACKER_ROOM",
"Level": 18
},
{
"Id": "AGENCYPICKUP_COLORADO_BULL_HOUSE_LAUNDRYROOM",
"Level": 19
},
{
"Id": "FIREARMS_HERO_RIFLE_SEMIAUTO_010_SU_ST_AIM_SKIN07",
"Level": 20
},
{
"Id": "PROP_DEVICE_ICA_C4_REMOTE_EXPLOSIVE",
"Level": 20
}
]
},
{
"Id": "pro1",
"MaxLevel": 10,
"Drops": [
{
"Id": "PRO1_AGENCYPICKUP_COLORADO_WATERTOWER",
"Level": 2
},
{
"Id": "PRO1_STARTING_LOCATION_COLORADO_ORCHARD",
"Level": 2
},
{
"Id": "PRO1_AGENCYPICKUP_COLORADO_BULL_ORCHARD_BOX",
"Level": 3
},
{
"Id": "PRO1_STARTING_LOCATION_COLORADO_BULL_COURTYARD_GARAGE",
"Level": 3
},
{
"Id": "PRO1_AGENCYPICKUP_COLORADO_HAYBARN",
"Level": 4
},
{
"Id": "PRO1_STARTING_LOCATION_COLORADO_BULL_DEMOLITION_AREA",
"Level": 4
},
{
"Id": "PRO1_AGENCYPICKUP_COLORADO_BULL_RED_BARN",
"Level": 5
},
{
"Id": "PRO1_STARTING_LOCATION_COLORADO_BULL_GREENHOUSE",
"Level": 5
},
{
"Id": "PRO1_AGENCYPICKUP_COLORADO_COURTYARD_OUTHOUSE",
"Level": 6
},
{
"Id": "PRO1_AGENCYPICKUP_COLORADO_WESTBRIDGE",
"Level": 6
},
{
"Id": "PRO1_STARTING_LOCATION_COLORADO_WEST_BRIDGE",
"Level": 7
},
{
"Id": "PRO1_STARTING_LOCATION_COLORADO_WATER_TOWER",
"Level": 7
},
{
"Id": "PRO1_AGENCYPICKUP_COLORADO_BULL_ORCHARD_PARKINGAREA",
"Level": 8
},
{
"Id": "PRO1_STARTING_LOCATION_COLORADO_BULL_HACKER_ROOM",
"Level": 8
},
{
"Id": "PRO1_AGENCYPICKUP_COLORADO_BULL_HOUSE_LAUNDRYROOM",
"Level": 9
},
{
"Id": "FIREARMS_HERO_SNIPER_MEDIUM_002_SU_AVEMARIA_SKIN12",
"Level": 10
}
]
}
]
}

View File

@ -1,5 +1,6 @@
{
"Id": "LOCATION_PARENT_ANCESTRAL",
"LocationId": "LOCATION_PARENT_ANCESTRAL",
"GameVersions": ["h3"],
"Drops": [
{
"Id": "AGENCYPICKUP_ANCESTRAL_SMALL_DELIVERIES",

View File

@ -1,5 +1,6 @@
{
"Id": "LOCATION_PARENT_GOLDEN",
"LocationId": "LOCATION_PARENT_GOLDEN",
"GameVersions": ["h3"],
"Drops": [
{
"Id": "PROP_MELEE_SHOWGLOBE",

View File

@ -1,5 +1,6 @@
{
"Id": "LOCATION_PARENT_ICA_FACILITY",
"LocationId": "LOCATION_PARENT_ICA_FACILITY",
"GameVersions": ["h1", "h2", "h3"],
"MaxLevel": 4,
"HideProgression": true,
"Drops": []

View File

@ -0,0 +1,82 @@
{
"LocationId": "LOCATION_PARENT_OPULENT",
"GameVersions": ["h2"],
"Drops": [
{
"Id": "PROP_MELEE_PIRATE_SABRE",
"Level": 2
},
{
"Id": "STARTING_LOCATION_OPULENT_STINGRAY_47HUT",
"Level": 3
},
{
"Id": "AGENCYPICKUP_STINGRAY_SMALL_LAUNDRYROOM",
"Level": 4
},
{
"Id": "PROP_DEVICE_ICA_REMOTE_GAS_EMETIC",
"Level": 5
},
{
"Id": "AGENCYPICKUP_STINGRAY_SMALL_STAFFROOM",
"Level": 6
},
{
"Id": "STARTING_LOCATION_OPULENT_STINGRAY_POOL",
"Level": 7
},
{
"Id": "AGENCYPICKUP_STINGRAY_LARGE_47HUT",
"Level": 8
},
{
"Id": "AGENCYPICKUP_STINGRAY_SMALL_SPACHANGINGROOM",
"Level": 9
},
{
"Id": "FIREARMS_PISTOL_DARTGUN_SICK",
"Level": 10
},
{
"Id": "STARTING_LOCATION_OPULENT_STINGRAY_GYM",
"Level": 11
},
{
"Id": "AGENCYPICKUP_STINGRAY_LARGE_SECURITYHUT",
"Level": 12
},
{
"Id": "AGENCYPICKUP_STINGRAY_SMALL_HIDDENINSAND",
"Level": 13
},
{
"Id": "STARTING_LOCATION_OPULENT_STINGRAY_KITCHEN",
"Level": 14
},
{
"Id": "PROP_MELEE_TREASURE_KNIFE",
"Level": 15
},
{
"Id": "AGENCYPICKUP_STINGRAY_SMALL_VILLA_BATHROOM",
"Level": 16
},
{
"Id": "AGENCYPICKUP_STINGRAY_LARGE_VILLABEACH",
"Level": 17
},
{
"Id": "STARTING_LOCATION_OPULENT_STINGRAY_VILLA_PIER",
"Level": 18
},
{
"Id": "AGENCYPICKUP_STINGRAY_SMALL_UNDERGROUND_STORAGE",
"Level": 19
},
{
"Id": "TOKEN_OUTFIT_STINGRAY_MASTERY_REWARD_SUIT",
"Level": 20
}
]
}

View File

@ -1,5 +1,6 @@
{
"Id": "LOCATION_PARENT_OPULENT",
"LocationId": "LOCATION_PARENT_OPULENT",
"GameVersions": ["h3"],
"Drops": [
{
"Id": "PROP_MELEE_PIRATE_SABRE",

View File

@ -0,0 +1,35 @@
{
"LocationId": "LOCATION_PARENT_NEWZEALAND",
"GameVersions": ["h2"],
"MaxLevel": 5,
"Drops": [
{
"Id": "STARTING_LOCATION_NEWZEALAND_BOAT",
"Level": 1
},
{
"Id": "AGENCYPICKUP_NEWZEALAND_SMALL",
"Level": 2
},
{
"Id": "PROP_DEVICE_ICA_MODULAR_PROXIMITY_EXPLOSIVE_S2",
"Level": 2
},
{
"Id": "STARTING_LOCATION_NEWZEALAND_BEACH",
"Level": 3
},
{
"Id": "AGENCYPICKUP_NEWZEALAND_LARGE",
"Level": 4
},
{
"Id": "STARTING_LOCATION_NEWZEALAND_OFFICE",
"Level": 5
},
{
"Id": "PROP_EXPLOSIVE_GRENADE_FLASH",
"Level": 5
}
]
}

View File

@ -1,5 +1,6 @@
{
"Id": "LOCATION_PARENT_NEWZEALAND",
"LocationId": "LOCATION_PARENT_NEWZEALAND",
"GameVersions": ["h3"],
"MaxLevel": 5,
"Drops": [
{

View File

@ -0,0 +1,94 @@
{
"LocationId": "LOCATION_PARENT_HOKKAIDO",
"GameVersions": ["h2"],
"Drops": [
{
"Id": "AGENCYPICKUP_HOKKAIDO_SNOWCRANE_SLEEPINGQUARTERS",
"Level": 4
},
{
"Id": "FIREARMS_HERO_PISTOL_CUSTOM5MM",
"Level": 5
},
{
"Id": "STARTING_LOCATION_HOKKAIDO_SNOWCRANE_SPA",
"Level": 6
},
{
"Id": "STARTING_LOCATION_HOKKAIDO_SNOWCRANE_NINJA",
"Level": 7
},
{
"Id": "PROP_MELEE_NINJATONFA",
"Level": 7
},
{
"Id": "AGENCYPICKUP_HOKKAIDO_SNOWCRANE_MOUNTAINPATH",
"Level": 7
},
{
"Id": "STARTING_LOCATION_HOKKAIDO_SNOWCRANE_RESTAURANT",
"Level": 8
},
{
"Id": "AGENCYPICKUP_HOKKAIDO_SNOWCRANE_OPERATIONTOILET",
"Level": 9
},
{
"Id": "PROP_MELEE_SHURIKEN",
"Level": 10
},
{
"Id": "STARTING_LOCATION_HOKKAIDO_SNOWCRANE_SLEEPINGQUARTERS",
"Level": 10
},
{
"Id": "AGENCYPICKUP_HOKKAIDO_SNOWCRANE_KITCHEN",
"Level": 11
},
{
"Id": "STARTING_LOCATION_HOKKAIDO_SNOWCRANE_KITCHEN",
"Level": 12
},
{
"Id": "AGENCYPICKUP_HOKKAIDO_SNOWCRANE_GARAGE",
"Level": 13
},
{
"Id": "STARTING_LOCATION_HOKKAIDO_SNOWCRANE_GARDEN",
"Level": 14
},
{
"Id": "PROP_EXPLOSIVE_EXPLOSIVE_COMPOUND",
"Level": 15
},
{
"Id": "AGENCYPICKUP_HOKKAIDO_SNOWCRANE_MORGUE",
"Level": 16
},
{
"Id": "STARTING_LOCATION_HOKKAIDO_SNOWCRANE_MORGUE",
"Level": 17
},
{
"Id": "AGENCYPICKUP_HOKKAIDO_SNOWCRANE_RESTAURANTRESTROOM",
"Level": 18
},
{
"Id": "STARTING_LOCATION_HOKKAIDO_SNOWCRANE_OPERATINGTHEATER",
"Level": 19
},
{
"Id": "FIREARMS_HERO_SNIPER_MEDIUM_SIEGER_300_ADVANCED",
"Level": 20
},
{
"Id": "PROP_MELEE_JAPANESE_BASEBALLBAT",
"Level": 20
},
{
"Id": "LOADOUT_UNLOCK_HOKKAIDO",
"Level": 20
}
]
}

View File

@ -1,5 +1,6 @@
{
"Id": "LOCATION_PARENT_HOKKAIDO",
"LocationId": "LOCATION_PARENT_HOKKAIDO",
"GameVersions": ["h3"],
"Drops": [
{
"Id": "PROP_MELEE_SHURIKEN",

View File

@ -0,0 +1,177 @@
{
"LocationId": "LOCATION_PARENT_HOKKAIDO",
"GameVersions": ["h1"],
"SubPackages": [
{
"Id": "normal",
"Drops": [
{
"Id": "AGENCYPICKUP_HOKKAIDO_SNOWCRANE_SLEEPINGQUARTERS",
"Level": 4
},
{
"Id": "FIREARMS_HERO_PISTOL_CUSTOM5MM_001_SU_SKIN07",
"Level": 5
},
{
"Id": "DIFFICULTY_UNLOCK_PRO1_HOKKAIDO",
"Level": 5
},
{
"Id": "STARTING_LOCATION_HOKKAIDO_SNOWCRANE_SPA",
"Level": 6
},
{
"Id": "STARTING_LOCATION_HOKKAIDO_SNOWCRANE_NINJA",
"Level": 7
},
{
"Id": "PROP_MELEE_SHURIKEN",
"Level": 7
},
{
"Id": "AGENCYPICKUP_HOKKAIDO_SNOWCRANE_MOUNTAINPATH",
"Level": 7
},
{
"Id": "STARTING_LOCATION_HOKKAIDO_SNOWCRANE_RESTAURANT",
"Level": 8
},
{
"Id": "AGENCYPICKUP_HOKKAIDO_SNOWCRANE_OPERATIONTOILET",
"Level": 9
},
{
"Id": "PROP_MELEE_NINJATONFA",
"Level": 10
},
{
"Id": "STARTING_LOCATION_HOKKAIDO_SNOWCRANE_SLEEPINGQUARTERS",
"Level": 10
},
{
"Id": "AGENCYPICKUP_HOKKAIDO_SNOWCRANE_KITCHEN",
"Level": 11
},
{
"Id": "STARTING_LOCATION_HOKKAIDO_SNOWCRANE_KITCHEN",
"Level": 12
},
{
"Id": "AGENCYPICKUP_HOKKAIDO_SNOWCRANE_GARAGE",
"Level": 13
},
{
"Id": "STARTING_LOCATION_HOKKAIDO_SNOWCRANE_GARDEN",
"Level": 14
},
{
"Id": "PROP_EXPLOSIVE_EXPLOSIVE_COMPOUND",
"Level": 15
},
{
"Id": "AGENCYPICKUP_HOKKAIDO_SNOWCRANE_MORGUE",
"Level": 16
},
{
"Id": "STARTING_LOCATION_HOKKAIDO_SNOWCRANE_MORGUE",
"Level": 17
},
{
"Id": "AGENCYPICKUP_HOKKAIDO_SNOWCRANE_RESTAURANTRESTROOM",
"Level": 18
},
{
"Id": "STARTING_LOCATION_HOKKAIDO_SNOWCRANE_OPERATINGTHEATER",
"Level": 19
},
{
"Id": "PROP_MELEE_HIDDEN_BLADE",
"Level": 20
},
{
"Id": "FIREARMS_HERO_SNIPER_MEDIUM_001_SU_SKIN11",
"Level": 20
},
{
"Id": "LOADOUT_UNLOCK_HOKKAIDO",
"Level": 20
}
]
},
{
"Id": "pro1",
"MaxLevel": 10,
"Drops": [
{
"Id": "PRO1_AGENCYPICKUP_HOKKAIDO_SNOWCRANE_SLEEPINGQUARTERS",
"Level": 2
},
{
"Id": "PRO1_STARTING_LOCATION_HOKKAIDO_SNOWCRANE_SPA",
"Level": 2
},
{
"Id": "PRO1_STARTING_LOCATION_HOKKAIDO_SNOWCRANE_NINJA",
"Level": 3
},
{
"Id": "PRO1_AGENCYPICKUP_HOKKAIDO_SNOWCRANE_MOUNTAINPATH",
"Level": 3
},
{
"Id": "PRO1_STARTING_LOCATION_HOKKAIDO_SNOWCRANE_RESTAURANT",
"Level": 4
},
{
"Id": "PRO1_AGENCYPICKUP_HOKKAIDO_SNOWCRANE_OPERATIONTOILET",
"Level": 4
},
{
"Id": "PRO1_STARTING_LOCATION_HOKKAIDO_SNOWCRANE_SLEEPINGQUARTERS",
"Level": 5
},
{
"Id": "PRO1_AGENCYPICKUP_HOKKAIDO_SNOWCRANE_KITCHEN",
"Level": 5
},
{
"Id": "PRO1_STARTING_LOCATION_HOKKAIDO_SNOWCRANE_KITCHEN",
"Level": 6
},
{
"Id": "PRO1_AGENCYPICKUP_HOKKAIDO_SNOWCRANE_GARAGE",
"Level": 6
},
{
"Id": "PRO1_STARTING_LOCATION_HOKKAIDO_SNOWCRANE_GARDEN",
"Level": 7
},
{
"Id": "PRO1_AGENCYPICKUP_HOKKAIDO_SNOWCRANE_MORGUE",
"Level": 7
},
{
"Id": "PRO1_STARTING_LOCATION_HOKKAIDO_SNOWCRANE_MORGUE",
"Level": 8
},
{
"Id": "PRO1_AGENCYPICKUP_HOKKAIDO_SNOWCRANE_RESTAURANTRESTROOM",
"Level": 8
},
{
"Id": "PRO1_STARTING_LOCATION_HOKKAIDO_SNOWCRANE_OPERATINGTHEATER",
"Level": 9
},
{
"Id": "PROP_MELEE_JAPANESE_BASEBALLBAT",
"Level": 10
},
{
"Id": "PRO1_LOADOUT_UNLOCK_HOKKAIDO",
"Level": 10
}
]
}
]
}

View File

@ -0,0 +1,94 @@
{
"LocationId": "LOCATION_PARENT_MARRAKECH",
"GameVersions": ["h2"],
"Drops": [
{
"Id": "AGENCYPICKUP_MARRAKESH_SPIDER_CONSULATE_BASEMENT",
"Level": 2
},
{
"Id": "PROP_DEVICE_ICA_MODULAR_PROXIMITY_EXPLOSIVE",
"Level": 2
},
{
"Id": "STARTING_LOCATION_MARRAKESH_SHISHA_CAFE",
"Level": 3
},
{
"Id": "AGENCYPICKUP_MARRAKESH_MECHANIC_SHOP",
"Level": 4
},
{
"Id": "FIREARMS_HERO_PISTOL_TACTICAL_ICA_19_FA",
"Level": 5
},
{
"Id": "STARTING_LOCATION_MARRAKESH_SNAIL_VENDOR",
"Level": 6
},
{
"Id": "AGENCYPICKUP_MARRAKESH_SNIPER_ROOF",
"Level": 7
},
{
"Id": "STARTING_LOCATION_MARRAKESH_SPIDER_SCHOOL_DORMITORY",
"Level": 8
},
{
"Id": "AGENCYPICKUP_MARRAKESH_CAFE_RESTROOM",
"Level": 9
},
{
"Id": "PROP_DEVICE_ICA_RUBBERDUCK_REMOTE_EXPLOSIVE",
"Level": 10
},
{
"Id": "STARTING_LOCATION_MARRAKESH_SNIPER_ROOF",
"Level": 11
},
{
"Id": "AGENCYPICKUP_MARRAKESH_HEADMASTER",
"Level": 12
},
{
"Id": "FIREARMS_HERO_SHOTGUN_SEMIAUTO_ENRAM_HV_CM",
"Level": 12
},
{
"Id": "STARTING_LOCATION_MARRAKESH_LAMPSTORE_ROOF",
"Level": 13
},
{
"Id": "AGENCYPICKUP_MARRAKESH_SPIDER_CONSULATE_TROLLEY",
"Level": 14
},
{
"Id": "PROP_DEVICE_ICA_C4_PROXIMITY_EXPLOSIVE",
"Level": 15
},
{
"Id": "STARTING_LOCATION_MARRAKESH_SPIDER_CONSULATE_CLEANING_TROLLEY",
"Level": 16
},
{
"Id": "AGENCYPICKUP_MARRAKESH_BAZAAR_CARPETSHOP",
"Level": 17
},
{
"Id": "STARTING_LOCATION_MARRAKESH_SPIDER_SCHOOL_ALLEY",
"Level": 18
},
{
"Id": "AGENCYPICKUP_MARRAKESH_SPIDER_SCHOOL_BACKENTRANCE",
"Level": 19
},
{
"Id": "FIREARMS_HERO_RIFLE_FULLAUTO_TAC_4_AR_STEALTH",
"Level": 20
},
{
"Id": "PROP_MELEE_CRYSTALBALL",
"Level": 20
}
]
}

View File

@ -0,0 +1,169 @@
{
"LocationId": "LOCATION_PARENT_MARRAKECH",
"GameVersions": ["h1"],
"SubPackages": [
{
"Id": "normal",
"Drops": [
{
"Id": "AGENCYPICKUP_MARRAKESH_SPIDER_CONSULATE_BASEMENT",
"Level": 2
},
{
"Id": "STARTING_LOCATION_MARRAKESH_SHISHA_CAFE",
"Level": 3
},
{
"Id": "AGENCYPICKUP_MARRAKESH_MECHANIC_SHOP",
"Level": 4
},
{
"Id": "PROP_MELEE_COMBAT_KNIFE",
"Level": 5
},
{
"Id": "DIFFICULTY_UNLOCK_PRO1_MARRAKECH",
"Level": 5
},
{
"Id": "STARTING_LOCATION_MARRAKESH_SNAIL_VENDOR",
"Level": 6
},
{
"Id": "AGENCYPICKUP_MARRAKESH_SNIPER_ROOF",
"Level": 7
},
{
"Id": "STARTING_LOCATION_MARRAKESH_SPIDER_SCHOOL_DORMITORY",
"Level": 8
},
{
"Id": "AGENCYPICKUP_MARRAKESH_CAFE_RESTROOM",
"Level": 9
},
{
"Id": "FIREARMS_HERO_PISTOL_TACTICAL_011_FA_SKIN01",
"Level": 10
},
{
"Id": "PROP_POISON_VIAL_SICK",
"Level": 10
},
{
"Id": "STARTING_LOCATION_MARRAKESH_SNIPER_ROOF",
"Level": 11
},
{
"Id": "AGENCYPICKUP_MARRAKESH_HEADMASTER",
"Level": 12
},
{
"Id": "STARTING_LOCATION_MARRAKESH_LAMPSTORE_ROOF",
"Level": 13
},
{
"Id": "AGENCYPICKUP_MARRAKESH_SPIDER_CONSULATE_TROLLEY",
"Level": 14
},
{
"Id": "PROP_DEVICE_ICA_RUBBERDUCK_REMOTE_EXPLOSIVE",
"Level": 15
},
{
"Id": "STARTING_LOCATION_MARRAKESH_SPIDER_CONSULATE_CLEANING_TROLLEY",
"Level": 16
},
{
"Id": "AGENCYPICKUP_MARRAKESH_BAZAAR_CARPETSHOP",
"Level": 17
},
{
"Id": "STARTING_LOCATION_MARRAKESH_SPIDER_SCHOOL_ALLEY",
"Level": 18
},
{
"Id": "AGENCYPICKUP_MARRAKESH_SPIDER_SCHOOL_BACKENTRANCE",
"Level": 19
},
{
"Id": "FIREARMS_HERO_RIFLE_FULLAUTO_015_SU_SKIN07",
"Level": 20
},
{
"Id": "PROP_DEVICE_ICA_C4_PROXIMITY_EXPLOSIVE",
"Level": 20
}
]
},
{
"Id": "pro1",
"MaxLevel": 10,
"Drops": [
{
"Id": "PRO1_AGENCYPICKUP_MARRAKESH_SPIDER_CONSULATE_BASEMENT",
"Level": 2
},
{
"Id": "PRO1_STARTING_LOCATION_MARRAKESH_SHISHA_CAFE",
"Level": 2
},
{
"Id": "PRO1_AGENCYPICKUP_MARRAKESH_MECHANIC_SHOP",
"Level": 3
},
{
"Id": "PRO1_STARTING_LOCATION_MARRAKESH_SNAIL_VENDOR",
"Level": 3
},
{
"Id": "PRO1_AGENCYPICKUP_MARRAKESH_SNIPER_ROOF",
"Level": 4
},
{
"Id": "PRO1_STARTING_LOCATION_MARRAKESH_SPIDER_SCHOOL_DORMITORY",
"Level": 4
},
{
"Id": "PRO1_AGENCYPICKUP_MARRAKESH_CAFE_RESTROOM",
"Level": 5
},
{
"Id": "PRO1_STARTING_LOCATION_MARRAKESH_SNIPER_ROOF",
"Level": 5
},
{
"Id": "PRO1_AGENCYPICKUP_MARRAKESH_HEADMASTER",
"Level": 6
},
{
"Id": "PRO1_STARTING_LOCATION_MARRAKESH_LAMPSTORE_ROOF",
"Level": 6
},
{
"Id": "PRO1_AGENCYPICKUP_MARRAKESH_SPIDER_CONSULATE_TROLLEY",
"Level": 7
},
{
"Id": "PRO1_STARTING_LOCATION_MARRAKESH_SPIDER_CONSUL_CLEANING_TROLLEY",
"Level": 7
},
{
"Id": "PRO1_AGENCYPICKUP_MARRAKESH_BAZAAR_CARPETSHOP",
"Level": 8
},
{
"Id": "PRO1_STARTING_LOCATION_MARRAKESH_SPIDER_SCHOOL_ALLEY",
"Level": 8
},
{
"Id": "PRO1_AGENCYPICKUP_MARRAKESH_SPIDER_SCHOOL_BACKENTRANCE",
"Level": 9
},
{
"Id": "PROP_MELEE_CRYSTALBALL",
"Level": 10
}
]
}
]
}

View File

@ -1,5 +1,6 @@
{
"Id": "LOCATION_PARENT_MARRAKECH",
"LocationId": "LOCATION_PARENT_MARRAKECH",
"GameVersions": ["h3"],
"Drops": [
{
"Id": "AGENCYPICKUP_MARRAKESH_SPIDER_CONSULATE_BASEMENT",

View File

@ -1,5 +1,6 @@
{
"Id": "LOCATION_PARENT_ELEGANT",
"LocationId": "LOCATION_PARENT_ELEGANT",
"GameVersions": ["h3"],
"Drops": [
{
"Id": "STARTING_LOCATION_ELEGANT_LLAMA_PARKINGLOT_SUIT",

View File

@ -0,0 +1,122 @@
{
"LocationId": "LOCATION_PARENT_MIAMI",
"GameVersions": ["h2"],
"Drops": [
{
"Id": "STARTING_LOCATION_MIAMI_FLAMINGO_EXPO_ENTRANCE",
"Level": 2
},
{
"Id": "PROP_TOOL_LOCK_PICK_S2",
"Level": 2
},
{
"Id": "LOADOUT_UNLOCK_MIAMI",
"Level": 2
},
{
"Id": "AGENCYPICKUP_MIAMI_SMALL_EXPO_RECEPTION",
"Level": 3
},
{
"Id": "STARTING_LOCATION_MIAMI_FLAMINGO_MARINA",
"Level": 4
},
{
"Id": "PROP_MELEE_FISH",
"Level": 4
},
{
"Id": "AGENCYPICKUP_MIAMI_SMALL_STANDS_TOILET",
"Level": 5
},
{
"Id": "FIREARMS_HERO_SNIPER_JAEGER_S2",
"Level": 5
},
{
"Id": "STARTING_LOCATION_MIAMI_FLAMINGO_STANDS",
"Level": 6
},
{
"Id": "AGENCYPICKUP_MIAMI_LARGE_BOAT_RENTAL",
"Level": 7
},
{
"Id": "FIREARMS_PISTOL_HWK_21_S2",
"Level": 7
},
{
"Id": "STARTING_LOCATION_MIAMI_FLAMINGO_FOOD_TRUCK",
"Level": 8
},
{
"Id": "AGENCYPICKUP_MIAMI_SMALL_PODIUM",
"Level": 8
},
{
"Id": "STARTING_LOCATION_MIAMI_FLAMINGO_HOTEL",
"Level": 9
},
{
"Id": "PROP_DEVICE_ICA_PROXIMITY_TASER",
"Level": 10
},
{
"Id": "AGENCYPICKUP_MIAMI_LARGE_TRUCK",
"Level": 11
},
{
"Id": "STARTING_LOCATION_MIAMI_FLAMINGO_VIPLOUNGE",
"Level": 12
},
{
"Id": "AGENCYPICKUP_MIAMI_LARGE_FOOD_AREA",
"Level": 13
},
{
"Id": "PROP_DEVICE_ICA_RUBBER_DUCK_PROXIMITY_EXPLOSIVE_S2",
"Level": 13
},
{
"Id": "STARTING_LOCATION_MIAMI_FLAMINGO_PODIUM",
"Level": 14
},
{
"Id": "AGENCYPICKUP_MIAMI_LARGE_STORAGE_CONTAINER",
"Level": 15
},
{
"Id": "FIREARMS_PISTOL_CONCEPT_5",
"Level": 15
},
{
"Id": "STARTING_LOCATION_MIAMI_FLAMINGO_MEDICAL_AREA",
"Level": 16
},
{
"Id": "AGENCYPICKUP_MIAMI_LARGE_STORAGE_PADDOCK",
"Level": 17
},
{
"Id": "STARTING_LOCATION_MIAMI_FLAMINGO_EXPO",
"Level": 18
},
{
"Id": "PROP_DEVICE_ICA_REMOTE_AUDIO_DISTRACTION_S2",
"Level": 18
},
{
"Id": "AGENCYPICKUP_MIAMI_LARGE_PITBUILDING_BASEMENT",
"Level": 19
},
{
"Id": "STARTING_LOCATION_MIAMI_FLAMINGO_GARAGEPIT",
"Level": 20
},
{
"Id": "PROP_EXPLOSIVE_GRENADE_CONCUSSION",
"Level": 20
}
]
}

View File

@ -1,5 +1,6 @@
{
"Id": "LOCATION_PARENT_MIAMI",
"LocationId": "LOCATION_PARENT_MIAMI",
"GameVersions": ["h3"],
"Drops": [
{
"Id": "STARTING_LOCATION_MIAMI_FLAMINGO_EXPO_ENTRANCE",

View File

@ -0,0 +1,162 @@
{
"LocationId": "LOCATION_PARENT_MUMBAI",
"GameVersions": ["h2"],
"Drops": [
{
"Id": "STARTING_LOCATION_MUMBAI_MONGOOSE_TRAIN",
"Level": 2
},
{
"Id": "AGENCYPICKUP_MUMBAI_SMALL_SHOESHOP",
"Level": 2
},
{
"Id": "FIREARMS_PISTOL_SILVERBALLER_S2",
"Level": 2
},
{
"Id": "STARTING_LOCATION_MUMBAI_MONGOOSE_LAUNDRY",
"Level": 3
},
{
"Id": "AGENCYPICKUP_MUMBAI_SMALL_NEST",
"Level": 3
},
{
"Id": "STARTING_LOCATION_MUMBAI_MONGOOSE_BOAT",
"Level": 4
},
{
"Id": "STARTING_LOCATION_MUMBAI_MONGOOSE_BARGE",
"Level": 5
},
{
"Id": "AGENCYPICKUP_MUMBAI_SMALL_FLOWER",
"Level": 5
},
{
"Id": "AGENCYPICKUP_MUMBAI_SMALL_MEAT",
"Level": 5
},
{
"Id": "FIREARMS_HERO_SHOTGUN_BARTOLI_12G_HEROVERSION",
"Level": 5
},
{
"Id": "STARTING_LOCATION_MUMBAI_MONGOOSE_SKYWALK",
"Level": 6
},
{
"Id": "AGENCYPICKUP_MUMBAI_SMALL_CLOTH",
"Level": 6
},
{
"Id": "AGENCYPICKUP_MUMBAI_SMALL_POT",
"Level": 6
},
{
"Id": "AGENCYPICKUP_MUMBAI_LARGE_BARGE",
"Level": 7
},
{
"Id": "PROP_DEVICE_ICA_MODULAR_REMOTE_EXPLOSIVE_S2",
"Level": 7
},
{
"Id": "STARTING_LOCATION_MUMBAI_MONGOOSE_TAXI",
"Level": 8
},
{
"Id": "AGENCYPICKUP_MUMBAI_SMALL_TYRE",
"Level": 8
},
{
"Id": "AGENCYPICKUP_MUMBAI_SMALL_BOAT",
"Level": 8
},
{
"Id": "STARTING_LOCATION_MUMBAI_MONGOOSE_KASHMERIAN",
"Level": 9
},
{
"Id": "AGENCYPICKUP_MUMBAI_LARGE_FORGE",
"Level": 9
},
{
"Id": "AGENCYPICKUP_MUMBAI_LARGE_ROOFTOP",
"Level": 10
},
{
"Id": "PROP_DEVICE_ICA_MICRO_PROXIMITY_EXPLOSIVE",
"Level": 10
},
{
"Id": "STARTING_LOCATION_MUMBAI_MONGOOSE_SLUMS",
"Level": 11
},
{
"Id": "AGENCYPICKUP_MUMBAI_SMALL_SEWER",
"Level": 11
},
{
"Id": "AGENCYPICKUP_MUMBAI_LARGE_TRAIN",
"Level": 12
},
{
"Id": "STARTING_LOCATION_MUMBAI_MONGOOSE_FORGE",
"Level": 13
},
{
"Id": "AGENCYPICKUP_MUMBAI_SMALL_BAG",
"Level": 13
},
{
"Id": "PROP_MELEE_SYRINGE_EMETIC_S2",
"Level": 13
},
{
"Id": "AGENCYPICKUP_MUMBAI_LARGE_LAUNDRY",
"Level": 14
},
{
"Id": "AGENCYPICKUP_MUMBAI_LARGE_HILL",
"Level": 15
},
{
"Id": "FIREARMS_HERO_SMG_MAC10_COVERT",
"Level": 15
},
{
"Id": "STARTING_LOCATION_MUMBAI_MONGOOSE_APARTMENT",
"Level": 16
},
{
"Id": "AGENCYPICKUP_MUMBAI_SMALL_KASHMERIAN",
"Level": 16
},
{
"Id": "STARTING_LOCATION_MUMBAI_MONGOOSE_HILL",
"Level": 17
},
{
"Id": "AGENCYPICKUP_MUMBAI_SMALL_APARTMENT",
"Level": 17
},
{
"Id": "AGENCYPICKUP_MUMBAI_LARGE_GARAGE",
"Level": 18
},
{
"Id": "STARTING_LOCATION_MUMBAI_MONGOOSE_TRAINYARD",
"Level": 19
},
{
"Id": "AGENCYPICKUP_MUMBAI_LARGE_TRAINYARD",
"Level": 20
},
{
"Id": "FIREARMS_SNIPER_DRUZHINA_34",
"Level": 20
}
]
}

View File

@ -1,5 +1,6 @@
{
"Id": "LOCATION_PARENT_MUMBAI",
"LocationId": "LOCATION_PARENT_MUMBAI",
"GameVersions": ["h3"],
"Drops": [
{
"Id": "STARTING_LOCATION_MUMBAI_MONGOOSE_TRAIN",

View File

@ -0,0 +1,67 @@
{
"LocationId": "LOCATION_PARENT_GREEDY",
"GameVersions": ["h2"],
"MaxLevel": 15,
"Drops": [
{
"Id": "PROP_TOOL_GOLD_BAR_SMALL",
"Level": 2
},
{
"Id": "STARTING_LOCATION_GREEDY_RACCOON_MAINTENANCE_ENTRANCE",
"Level": 3
},
{
"Id": "AGENCYPICKUP_GREEDY_SMALL_1STFLOORTOILET",
"Level": 4
},
{
"Id": "PROP_DEVICE_ICA_REMOTE_FLASH",
"Level": 5
},
{
"Id": "AGENCYPICKUP_GREEDY_LARGE_FIRSTFLOORJANITOR",
"Level": 6
},
{
"Id": "STARTING_LOCATION_GREEDY_RACCOON_AUDITHALL_ENTRANCE",
"Level": 7
},
{
"Id": "AGENCYPICKUP_GREEDY_SMALL_2NDFLOORTOILET",
"Level": 7
},
{
"Id": "AGENCYPICKUP_GREEDY_SMALL_DEPOSITBOX",
"Level": 8
},
{
"Id": "STARTING_LOCATION_GREEDY_RACCOON_DEPOSITBOXROOM_ENTRANCE",
"Level": 9
},
{
"Id": "FIREARMS_HERO_SHOTGUN_BARTOLI_12G_SAWED_OFF",
"Level": 10
},
{
"Id": "STARTING_LOCATION_GREEDY_RACCOON_INVESTMENTFLOOR_ENTRANCE",
"Level": 11
},
{
"Id": "AGENCYPICKUP_GREEDY_LARGE_ASSISTANTSROOM",
"Level": 12
},
{
"Id": "STARTING_LOCATION_GREEDY_RACCOON_GARAGE_ENTRANCE",
"Level": 13
},
{
"Id": "AGENCYPICKUP_GREEDY_LARGE_GARAGE",
"Level": 14
},
{
"Id": "TOKEN_OUTFIT_BANK_STARTING_SUIT_REWARD",
"Level": 15
}
]
}

View File

@ -1,5 +1,6 @@
{
"Id": "LOCATION_PARENT_GREEDY",
"LocationId": "LOCATION_PARENT_GREEDY",
"GameVersions": ["h3"],
"Drops": [
{
"Id": "PROP_TOOL_GOLD_BAR_SMALL",

View File

@ -0,0 +1,94 @@
{
"LocationId": "LOCATION_PARENT_PARIS",
"GameVersions": ["h2"],
"Drops": [
{
"Id": "STARTING_LOCATION_PARIS_PEACOCK_BASEMENT_KITCHEN",
"Level": 2
},
{
"Id": "PROP_POISON_VIAL_SEDATIVE",
"Level": 2
},
{
"Id": "AGENCYPICKUP_PARIS_BARGE",
"Level": 3
},
{
"Id": "STARTING_LOCATION_PARIS_PEACOCK_DRESSINGROOM",
"Level": 4
},
{
"Id": "FIREARMS_HERO_SNIPER_JAEGER",
"Level": 5
},
{
"Id": "AGENCYPICKUP_PARIS_PEACOCK_PANTRY",
"Level": 6
},
{
"Id": "STARTING_LOCATION_PARIS_PEACOCK_AVTECH",
"Level": 7
},
{
"Id": "FIREARMS_HERO_PISTOL_HWK_21",
"Level": 7
},
{
"Id": "AGENCYPICKUP_PARIS_PEACOCK_LOGISTICS_TRAILER",
"Level": 8
},
{
"Id": "STARTING_LOCATION_PARIS_PEACOCK_LOCKERROOM",
"Level": 9
},
{
"Id": "PROP_DEVICE_NAPOLEON_FIGURE_REMOTE_EXPLOSIVE",
"Level": 10
},
{
"Id": "STARTING_LOCATION_PARIS_PALACEGARDEN",
"Level": 11
},
{
"Id": "FIREARMS_HERO_SMG_TAC_SMG",
"Level": 12
},
{
"Id": "AGENCYPICKUP_PARIS_TOILET",
"Level": 13
},
{
"Id": "STARTING_LOCATION_PARIS_PEACOCK_ATTIC",
"Level": 14
},
{
"Id": "PROP_POISON_VIAL_FAST",
"Level": 15
},
{
"Id": "AGENCYPICKUP_PARIS_PEACOCK_NEWSPAPERS",
"Level": 16
},
{
"Id": "STARTING_LOCATION_PARIS_SNIPERBARGE",
"Level": 17
},
{
"Id": "AGENCYPICKUP_PARIS_ATTIC_ROOM",
"Level": 18
},
{
"Id": "STARTING_LOCATION_PARIS_PEACOCK_AUCTION",
"Level": 19
},
{
"Id": "FIREARMS_HERO_PISTOL_LIGHT_HWK_21_COVERT",
"Level": 20
},
{
"Id": "PROP_DEVICE_ICA_MODULAR_REMOTE_EXPLOSIVE",
"Level": 20
}
]
}

View File

@ -0,0 +1,161 @@
{
"LocationId": "LOCATION_PARENT_PARIS",
"GameVersions": ["h1"],
"SubPackages": [
{
"Id": "normal",
"Drops": [
{
"Id": "STARTING_LOCATION_PARIS_PEACOCK_BASEMENT_KITCHEN",
"Level": 2
},
{
"Id": "AGENCYPICKUP_PARIS_BARGE",
"Level": 3
},
{
"Id": "STARTING_LOCATION_PARIS_PEACOCK_DRESSINGROOM",
"Level": 4
},
{
"Id": "FIREARMS_HERO_SNIPER_HEAVY_008_SU_SUB_SCOUT_SKIN01",
"Level": 5
},
{
"Id": "DIFFICULTY_UNLOCK_PRO1_PARIS",
"Level": 5
},
{
"Id": "AGENCYPICKUP_PARIS_PEACOCK_PANTRY",
"Level": 6
},
{
"Id": "STARTING_LOCATION_PARIS_PEACOCK_AVTECH",
"Level": 7
},
{
"Id": "AGENCYPICKUP_PARIS_PEACOCK_LOGISTICS_TRAILER",
"Level": 8
},
{
"Id": "STARTING_LOCATION_PARIS_PEACOCK_LOCKERROOM",
"Level": 9
},
{
"Id": "FIREARMS_HERO_PISTOL_TACTICAL_012_SU_SKIN02",
"Level": 10
},
{
"Id": "PROP_POISON_VIAL_SEDATIVE",
"Level": 11
},
{
"Id": "STARTING_LOCATION_PARIS_PALACEGARDEN",
"Level": 12
},
{
"Id": "AGENCYPICKUP_PARIS_TOILET",
"Level": 13
},
{
"Id": "STARTING_LOCATION_PARIS_PEACOCK_ATTIC",
"Level": 14
},
{
"Id": "FIREARMS_HERO_SMG_TACTICAL_005_ROF_SU_SKIN01",
"Level": 15
},
{
"Id": "AGENCYPICKUP_PARIS_PEACOCK_NEWSPAPERS",
"Level": 16
},
{
"Id": "STARTING_LOCATION_PARIS_SNIPERBARGE",
"Level": 17
},
{
"Id": "AGENCYPICKUP_PARIS_ATTIC_ROOM",
"Level": 18
},
{
"Id": "STARTING_LOCATION_PARIS_PEACOCK_AUCTION",
"Level": 19
},
{
"Id": "FIREARMS_HERO_PISTOL_LIGHT_010_SU_EX_SKIN03",
"Level": 20
},
{
"Id": "PROP_DEVICE_ICA_MODULAR_REMOTE_EXPLOSIVE",
"Level": 20
}
]
},
{
"Id": "pro1",
"MaxLevel": 10,
"Drops": [
{
"Id": "PRO1_STARTING_LOCATION_PARIS_PEACOCK_BASEMENT_KITCHEN",
"Level": 2
},
{
"Id": "PRO1_AGENCYPICKUP_PARIS_BARGE",
"Level": 3
},
{
"Id": "PRO1_STARTING_LOCATION_PARIS_PEACOCK_DRESSINGROOM",
"Level": 4
},
{
"Id": "PRO1_AGENCYPICKUP_PARIS_PEACOCK_PANTRY",
"Level": 4
},
{
"Id": "PRO1_STARTING_LOCATION_PARIS_PEACOCK_AVTECH",
"Level": 5
},
{
"Id": "PRO1_AGENCYPICKUP_PARIS_PEACOCK_LOGISTICS_TRAILER",
"Level": 5
},
{
"Id": "PRO1_STARTING_LOCATION_PARIS_PEACOCK_LOCKERROOM",
"Level": 6
},
{
"Id": "PRO1_STARTING_LOCATION_PARIS_PALACEGARDEN",
"Level": 6
},
{
"Id": "PRO1_AGENCYPICKUP_PARIS_TOILET",
"Level": 7
},
{
"Id": "PRO1_STARTING_LOCATION_PARIS_PEACOCK_ATTIC",
"Level": 7
},
{
"Id": "PRO1_AGENCYPICKUP_PARIS_PEACOCK_NEWSPAPERS",
"Level": 8
},
{
"Id": "PRO1_STARTING_LOCATION_PARIS_SNIPERBARGE",
"Level": 8
},
{
"Id": "PRO1_AGENCYPICKUP_PARIS_ATTIC_ROOM",
"Level": 9
},
{
"Id": "PRO1_STARTING_LOCATION_PARIS_PEACOCK_AUCTION",
"Level": 9
},
{
"Id": "PROP_DEVICE_NAPOLEON_FIGURE_REMOTE_EXPLOSIVE",
"Level": 10
}
]
}
]
}

View File

@ -1,5 +1,6 @@
{
"Id": "LOCATION_PARENT_PARIS",
"LocationId": "LOCATION_PARENT_PARIS",
"GameVersions": ["h3"],
"Drops": [
{
"Id": "STARTING_LOCATION_PARIS_PEACOCK_BASEMENT_KITCHEN",

View File

@ -0,0 +1,98 @@
{
"LocationId": "LOCATION_PARENT_COLOMBIA",
"GameVersions": ["h2"],
"Drops": [
{
"Id": "AGENCYPICKUP_COLOMBIA_LARGE_VILLAGE_CONSTRUCTIONBUILDING",
"Level": 2
},
{
"Id": "PROP_POISON_PILLS_SEDATIVE",
"Level": 2
},
{
"Id": "STARTING_LOCATION_COLOMBIA_HIPPO_HOSTEL",
"Level": 3
},
{
"Id": "AGENCYPICKUP_COLOMBIA_SMALL_HOSTEL",
"Level": 4
},
{
"Id": "PROP_EXPLOSIVE_GRENADE_FRAG",
"Level": 5
},
{
"Id": "STARTING_LOCATION_COLOMBIA_HIPPO_VILLAGEBAR",
"Level": 6
},
{
"Id": "AGENCYPICKUP_COLOMBIA_SMALL_FISHINGVILLAGE",
"Level": 7
},
{
"Id": "PROP_POISON_PILLS_EMETIC",
"Level": 7
},
{
"Id": "STARTING_LOCATION_COLOMBIA_HIPPO_JUNGLE",
"Level": 8
},
{
"Id": "AGENCYPICKUP_COLOMBIA_SMALL_JUNGLE",
"Level": 9
},
{
"Id": "FIREARMS_HERO_RIFLE_AK47_HEROVERSION",
"Level": 10
},
{
"Id": "STARTING_LOCATION_COLOMBIA_HIPPO_CONSTRUCTIONSITE",
"Level": 11
},
{
"Id": "AGENCYPICKUP_COLOMBIA_LARGE_CONSTRUCTIONSITE",
"Level": 12
},
{
"Id": "STARTING_LOCATION_COLOMBIA_HIPPO_COCAFIELD",
"Level": 13
},
{
"Id": "FIREARMS_HERO_SMG_TAC_SMG_S2",
"Level": 13
},
{
"Id": "AGENCYPICKUP_COLOMBIA_LARGE_COCAFIELD",
"Level": 14
},
{
"Id": "PROP_DEVICE_ICA_RFID_COIN_EXPLOSIVE",
"Level": 15
},
{
"Id": "STARTING_LOCATION_COLOMBIA_HIPPO_SUBMARINECAVE",
"Level": 16
},
{
"Id": "AGENCYPICKUP_COLOMBIA_SMALL_CAVES",
"Level": 17
},
{
"Id": "STARTING_LOCATION_COLOMBIA_HIPPO_MANSION",
"Level": 18
},
{
"Id": "PROP_DEVICE_ICA_PROXIMITY_SEMTEX_BLOCK",
"Level": 18
},
{
"Id": "AGENCYPICKUP_COLOMBIA_LARGE_MANSION_WINECELLAR",
"Level": 19
},
{
"Id": "PROP_DEVICE_ICA_MICRO_AUDIO_DISTRACTION",
"Level": 20
}
]
}

View File

@ -1,5 +1,6 @@
{
"Id": "LOCATION_PARENT_COLOMBIA",
"LocationId": "LOCATION_PARENT_COLOMBIA",
"GameVersions": ["h3"],
"Drops": [
{
"Id": "AGENCYPICKUP_COLOMBIA_LARGE_VILLAGE_CONSTRUCTIONBUILDING",

View File

@ -0,0 +1,98 @@
{
"LocationId": "LOCATION_PARENT_COASTALTOWN",
"GameVersions": ["h2"],
"Drops": [
{
"Id": "STARTING_LOCATION_SAPIENZA_APARTMENT",
"Level": 2
},
{
"Id": "PROP_MELEE_SYRINGE_LETHAL",
"Level": 2
},
{
"Id": "AGENCYPICKUP_SAPIENZA_CAFEBASEMENT",
"Level": 3
},
{
"Id": "STARTING_LOCATION_SAPIENZA_OCTOPUS_MANSIONGARDEN",
"Level": 4
},
{
"Id": "FIREARMS_HERO_RIFLE_FULLAUTO_TAC_4_AUTO",
"Level": 5
},
{
"Id": "AGENCYPICKUP_SAPIENZA_OCTOPUS_MANSIONGARAGE",
"Level": 6
},
{
"Id": "STARTING_LOCATION_SAPIENZA_OCTOPUS_MANSIONKITCHEN",
"Level": 7
},
{
"Id": "PROP_MELEE_COMBAT_KNIFE",
"Level": 7
},
{
"Id": "AGENCYPICKUP_SAPIENZA_RUINSTOWER",
"Level": 8
},
{
"Id": "STARTING_LOCATION_SAPIENZA_OCTOPUS_LEMONGARDEN",
"Level": 9
},
{
"Id": "PROP_DEVICE_ICA_MODULAR_REMOTE_AUDIODISTRACTION",
"Level": 10
},
{
"Id": "STARTING_LOCATION_SAPIENZA_OCTOPUS_BIOLAB",
"Level": 11
},
{
"Id": "AGENCYPICKUP_SAPIENZA_OCTOPUS_KITCHENPANTRY",
"Level": 12
},
{
"Id": "FIREARMS_HERO_SHOTGUN_SEMIAUTO_ENRAM_HV",
"Level": 12
},
{
"Id": "STARTING_LOCATION_SAPIENZA_HARBOUR",
"Level": 13
},
{
"Id": "AGENCYPICKUP_SAPIENZA_CHURCHCONFESSIONAL",
"Level": 14
},
{
"Id": "PROP_DEVICE_ICA_MODULAR_REMOTE_BREACHCHARGE",
"Level": 15
},
{
"Id": "STARTING_LOCATION_SAPIENZA_CAFETOWER",
"Level": 16
},
{
"Id": "AGENCYPICKUP_SAPIENZA_SEWERS",
"Level": 17
},
{
"Id": "STARTING_LOCATION_SAPIENZA_RUINS",
"Level": 18
},
{
"Id": "AGENCYPICKUP_SAPIENZA_OCTOPUS_BIOLAB",
"Level": 19
},
{
"Id": "FIREARMS_HERO_SNIPER_HEAVY_JAEAGER_LANCER",
"Level": 20
},
{
"Id": "PROP_MELEE_ANTIQUE_SYRINGE_EMETIC",
"Level": 20
}
]
}

View File

@ -0,0 +1,169 @@
{
"LocationId": "LOCATION_PARENT_COASTALTOWN",
"GameVersions": ["h1"],
"SubPackages": [
{
"Id": "normal",
"Drops": [
{
"Id": "STARTING_LOCATION_SAPIENZA_APARTMENT",
"Level": 2
},
{
"Id": "AGENCYPICKUP_SAPIENZA_CAFEBASEMENT",
"Level": 3
},
{
"Id": "STARTING_LOCATION_SAPIENZA_OCTOPUS_MANSIONGARDEN",
"Level": 4
},
{
"Id": "PROP_DEVICE_ICA_MODULAR_REMOTE_BREACHCHARGE",
"Level": 5
},
{
"Id": "DIFFICULTY_UNLOCK_PRO1_COASTALTOWN",
"Level": 5
},
{
"Id": "AGENCYPICKUP_SAPIENZA_OCTOPUS_MANSIONGARAGE",
"Level": 6
},
{
"Id": "STARTING_LOCATION_SAPIENZA_OCTOPUS_MANSIONKITCHEN",
"Level": 7
},
{
"Id": "AGENCYPICKUP_SAPIENZA_RUINSTOWER",
"Level": 8
},
{
"Id": "STARTING_LOCATION_SAPIENZA_OCTOPUS_LEMONGARDEN",
"Level": 9
},
{
"Id": "PROP_DEVICE_ICA_MODULAR_REMOTE_AUDIODISTRACTION",
"Level": 10
},
{
"Id": "PROP_POISON_VIAL_FAST",
"Level": 10
},
{
"Id": "STARTING_LOCATION_SAPIENZA_OCTOPUS_BIOLAB",
"Level": 11
},
{
"Id": "AGENCYPICKUP_SAPIENZA_OCTOPUS_KITCHENPANTRY",
"Level": 12
},
{
"Id": "STARTING_LOCATION_SAPIENZA_HARBOUR",
"Level": 13
},
{
"Id": "AGENCYPICKUP_SAPIENZA_CHURCHCONFESSIONAL",
"Level": 14
},
{
"Id": "FIREARMS_HERO_SHOTGUN_SEMIAUTO_007_ST_ROF_SKIN01",
"Level": 15
},
{
"Id": "STARTING_LOCATION_SAPIENZA_CAFETOWER",
"Level": 16
},
{
"Id": "AGENCYPICKUP_SAPIENZA_SEWERS",
"Level": 17
},
{
"Id": "STARTING_LOCATION_SAPIENZA_RUINS",
"Level": 18
},
{
"Id": "AGENCYPICKUP_SAPIENZA_OCTOPUS_BIOLAB",
"Level": 19
},
{
"Id": "FIREARMS_HERO_SNIPER_HEAVY_006_PI_SKIN01",
"Level": 20
},
{
"Id": "PROP_DEVICE_ICA_RUBBERDUCK_PROXIMITY_EXPLOSIVE",
"Level": 20
}
]
},
{
"Id": "pro1",
"MaxLevel": 10,
"Drops": [
{
"Id": "PRO1_STARTING_LOCATION_SAPIENZA_APARTMENT",
"Level": 2
},
{
"Id": "PRO1_AGENCYPICKUP_SAPIENZA_CAFEBASEMENT",
"Level": 2
},
{
"Id": "PRO1_STARTING_LOCATION_SAPIENZA_OCTOPUS_MANSIONGARDEN",
"Level": 3
},
{
"Id": "PRO1_AGENCYPICKUP_SAPIENZA_OCTOPUS_MANSIONGARAGE",
"Level": 3
},
{
"Id": "PRO1_STARTING_LOCATION_SAPIENZA_OCTOPUS_MANSIONKITCHEN",
"Level": 4
},
{
"Id": "PRO1_AGENCYPICKUP_SAPIENZA_RUINSTOWER",
"Level": 4
},
{
"Id": "PRO1_STARTING_LOCATION_SAPIENZA_OCTOPUS_LEMONGARDEN",
"Level": 5
},
{
"Id": "PRO1_STARTING_LOCATION_SAPIENZA_OCTOPUS_BIOLAB",
"Level": 5
},
{
"Id": "PRO1_AGENCYPICKUP_SAPIENZA_OCTOPUS_KITCHENPANTRY",
"Level": 6
},
{
"Id": "PRO1_STARTING_LOCATION_SAPIENZA_HARBOUR",
"Level": 6
},
{
"Id": "PRO1_AGENCYPICKUP_SAPIENZA_CHURCHCONFESSIONAL",
"Level": 7
},
{
"Id": "PRO1_STARTING_LOCATION_SAPIENZA_CAFETOWER",
"Level": 7
},
{
"Id": "PRO1_AGENCYPICKUP_SAPIENZA_SEWERS",
"Level": 8
},
{
"Id": "PRO1_STARTING_LOCATION_SAPIENZA_RUINS",
"Level": 8
},
{
"Id": "PRO1_AGENCYPICKUP_SAPIENZA_OCTOPUS_BIOLAB",
"Level": 9
},
{
"Id": "PROP_MELEE_ANTIQUE_SYRINGE_EMETIC",
"Level": 10
}
]
}
]
}

View File

@ -1,5 +1,6 @@
{
"Id": "LOCATION_PARENT_COASTALTOWN",
"LocationId": "LOCATION_PARENT_COASTALTOWN",
"GameVersions": ["h3"],
"Drops": [
{
"Id": "STARTING_LOCATION_SAPIENZA_APARTMENT",

View File

@ -0,0 +1,94 @@
{
"LocationId": "LOCATION_PARENT_NORTHSEA",
"GameVersions": ["h2"],
"Drops": [
{
"Id": "PROP_MELEE_MACE",
"Level": 2
},
{
"Id": "AGENCYPICKUP_THEISLAND_LARGE_CHAPEL",
"Level": 2
},
{
"Id": "STARTING_LOCATION_THE_ISLAND_MAGPIE_EFFIGY",
"Level": 3
},
{
"Id": "AGENCYPICKUP_THEISLAND_SMALL_GATEHOUSE",
"Level": 4
},
{
"Id": "PROP_MELEE_SYRINGE_LETHAL_S2",
"Level": 5
},
{
"Id": "STARTING_LOCATION_THE_ISLAND_MAGPIE_WORKER_WAITER",
"Level": 6
},
{
"Id": "PROP_DEVICE_KEYCARD_HACKER_S2",
"Level": 7
},
{
"Id": "AGENCYPICKUP_THEISLAND_SMALL_BASEMENT",
"Level": 7
},
{
"Id": "STARTING_LOCATION_THE_ISLAND_MAGPIE_WORKER_CHEF",
"Level": 8
},
{
"Id": "AGENCYPICKUP_THEISLAND_SMALL_ARK",
"Level": 9
},
{
"Id": "FIREARMS_SNIPER_SIEGER_300_TACTICAL",
"Level": 10
},
{
"Id": "STARTING_LOCATION_THE_ISLAND_MAGPIE_WORKER_CUSTODIAN",
"Level": 11
},
{
"Id": "AGENCYPICKUP_THEISLAND_LARGE_KEEP",
"Level": 12
},
{
"Id": "STARTING_LOCATION_THE_ISLAND_MAGPIE_KEEP",
"Level": 13
},
{
"Id": "AGENCYPICKUP_THEISLAND_LARGE_CISTERN",
"Level": 14
},
{
"Id": "STARTING_LOCATION_THE_ISLAND_MAGPIE_ARKIAN_TIER2",
"Level": 15
},
{
"Id": "PROP_MELEE_LONG_SWORD",
"Level": 16
},
{
"Id": "AGENCYPICKUP_THEISLAND_LARGE_CONSTANTS_BASEMENT",
"Level": 16
},
{
"Id": "FIREARMS_HERO_SHOTGUN_ENRAM_HV_COVERT_S2",
"Level": 18
},
{
"Id": "STARTING_LOCATION_THE_ISLAND_MAGPIE_ARKIAN_TIER3",
"Level": 18
},
{
"Id": "AGENCYPICKUP_THEISLAND_SMALL_CONFERENCE_ROOM",
"Level": 19
},
{
"Id": "PROP_DEVICE_REMOTE_RUBBERDUCK_CONCUSSION",
"Level": 20
}
]
}

View File

@ -1,5 +1,6 @@
{
"Id": "LOCATION_PARENT_NORTHSEA",
"LocationId": "LOCATION_PARENT_NORTHSEA",
"GameVersions": ["h3"],
"Drops": [
{
"Id": "PROP_MELEE_MACE",

View File

@ -0,0 +1,249 @@
{
"LocationId": "LOCATION_PARENT_AUSTRIA",
"GameVersions": ["h2", "h3", "scpc"],
"SubPackages": [
{
"Id": "FIREARMS_SC_HERO_SNIPER_HM",
"Drops": [
{
"Id": "FIREARMS_SC_HERO_SNIPER_HM_LVL_2",
"Level": 2
},
{
"Id": "FIREARMS_SC_HERO_SNIPER_HM_LVL_3",
"Level": 3
},
{
"Id": "FIREARMS_SC_HERO_SNIPER_HM_LVL_4",
"Level": 4
},
{
"Id": "FIREARMS_SC_HERO_SNIPER_HM_LVL_5",
"Level": 5
},
{
"Id": "FIREARMS_SC_HERO_SNIPER_HM_LVL_6",
"Level": 6
},
{
"Id": "FIREARMS_SC_HERO_SNIPER_HM_LVL_7",
"Level": 7
},
{
"Id": "FIREARMS_SC_HERO_SNIPER_HM_LVL_8",
"Level": 8
},
{
"Id": "FIREARMS_SC_HERO_SNIPER_HM_LVL_9",
"Level": 9
},
{
"Id": "FIREARMS_SC_HERO_SNIPER_HM_LVL_10",
"Level": 10
},
{
"Id": "FIREARMS_SC_HERO_SNIPER_HM_LVL_11",
"Level": 11
},
{
"Id": "FIREARMS_SC_HERO_SNIPER_HM_LVL_12",
"Level": 12
},
{
"Id": "FIREARMS_SC_HERO_SNIPER_HM_LVL_13",
"Level": 13
},
{
"Id": "FIREARMS_SC_HERO_SNIPER_HM_LVL_14",
"Level": 14
},
{
"Id": "FIREARMS_SC_HERO_SNIPER_HM_LVL_15",
"Level": 15
},
{
"Id": "FIREARMS_SC_HERO_SNIPER_HM_LVL_16",
"Level": 16
},
{
"Id": "FIREARMS_SC_HERO_SNIPER_HM_LVL_17",
"Level": 17
},
{
"Id": "FIREARMS_SC_HERO_SNIPER_HM_LVL_18",
"Level": 18
},
{
"Id": "FIREARMS_SC_HERO_SNIPER_HM_LVL_19",
"Level": 19
},
{
"Id": "FIREARMS_SC_HERO_SNIPER_HM_LVL_20",
"Level": 20
}
]
},
{
"Id": "FIREARMS_SC_HERO_SNIPER_KNIGHT",
"Drops": [
{
"Id": "FIREARMS_SC_HERO_SNIPER_KNIGHT_LVL_2",
"Level": 2
},
{
"Id": "FIREARMS_SC_HERO_SNIPER_KNIGHT_LVL_3",
"Level": 3
},
{
"Id": "FIREARMS_SC_HERO_SNIPER_KNIGHT_LVL_4",
"Level": 4
},
{
"Id": "FIREARMS_SC_HERO_SNIPER_KNIGHT_LVL_5",
"Level": 5
},
{
"Id": "FIREARMS_SC_HERO_SNIPER_KNIGHT_LVL_6",
"Level": 6
},
{
"Id": "FIREARMS_SC_HERO_SNIPER_KNIGHT_LVL_7",
"Level": 7
},
{
"Id": "FIREARMS_SC_HERO_SNIPER_KNIGHT_LVL_8",
"Level": 8
},
{
"Id": "FIREARMS_SC_HERO_SNIPER_KNIGHT_LVL_9",
"Level": 9
},
{
"Id": "FIREARMS_SC_HERO_SNIPER_KNIGHT_LVL_10",
"Level": 10
},
{
"Id": "FIREARMS_SC_HERO_SNIPER_KNIGHT_LVL_11",
"Level": 11
},
{
"Id": "FIREARMS_SC_HERO_SNIPER_KNIGHT_LVL_12",
"Level": 12
},
{
"Id": "FIREARMS_SC_HERO_SNIPER_KNIGHT_LVL_13",
"Level": 13
},
{
"Id": "FIREARMS_SC_HERO_SNIPER_KNIGHT_LVL_14",
"Level": 14
},
{
"Id": "FIREARMS_SC_HERO_SNIPER_KNIGHT_LVL_15",
"Level": 15
},
{
"Id": "FIREARMS_SC_HERO_SNIPER_KNIGHT_LVL_16",
"Level": 16
},
{
"Id": "FIREARMS_SC_HERO_SNIPER_KNIGHT_LVL_17",
"Level": 17
},
{
"Id": "FIREARMS_SC_HERO_SNIPER_KNIGHT_LVL_18",
"Level": 18
},
{
"Id": "FIREARMS_SC_HERO_SNIPER_KNIGHT_LVL_19",
"Level": 19
},
{
"Id": "FIREARMS_SC_HERO_SNIPER_KNIGHT_LVL_20",
"Level": 20
}
]
},
{
"Id": "FIREARMS_SC_HERO_SNIPER_STONE",
"Drops": [
{
"Id": "FIREARMS_SC_HERO_SNIPER_STONE_LVL_2",
"Level": 2
},
{
"Id": "FIREARMS_SC_HERO_SNIPER_STONE_LVL_3",
"Level": 3
},
{
"Id": "FIREARMS_SC_HERO_SNIPER_STONE_LVL_4",
"Level": 4
},
{
"Id": "FIREARMS_SC_HERO_SNIPER_STONE_LVL_5",
"Level": 5
},
{
"Id": "FIREARMS_SC_HERO_SNIPER_STONE_LVL_6",
"Level": 6
},
{
"Id": "FIREARMS_SC_HERO_SNIPER_STONE_LVL_7",
"Level": 7
},
{
"Id": "FIREARMS_SC_HERO_SNIPER_STONE_LVL_8",
"Level": 8
},
{
"Id": "FIREARMS_SC_HERO_SNIPER_STONE_LVL_9",
"Level": 9
},
{
"Id": "FIREARMS_SC_HERO_SNIPER_STONE_LVL_10",
"Level": 10
},
{
"Id": "FIREARMS_SC_HERO_SNIPER_STONE_LVL_11",
"Level": 11
},
{
"Id": "FIREARMS_SC_HERO_SNIPER_STONE_LVL_12",
"Level": 12
},
{
"Id": "FIREARMS_SC_HERO_SNIPER_STONE_LVL_13",
"Level": 13
},
{
"Id": "FIREARMS_SC_HERO_SNIPER_STONE_LVL_14",
"Level": 14
},
{
"Id": "FIREARMS_SC_HERO_SNIPER_STONE_LVL_15",
"Level": 15
},
{
"Id": "FIREARMS_SC_HERO_SNIPER_STONE_LVL_16",
"Level": 16
},
{
"Id": "FIREARMS_SC_HERO_SNIPER_STONE_LVL_17",
"Level": 17
},
{
"Id": "FIREARMS_SC_HERO_SNIPER_STONE_LVL_18",
"Level": 18
},
{
"Id": "FIREARMS_SC_HERO_SNIPER_STONE_LVL_19",
"Level": 19
},
{
"Id": "FIREARMS_SC_HERO_SNIPER_STONE_LVL_20",
"Level": 20
}
]
}
]
}

View File

@ -0,0 +1,253 @@
{
"LocationId": "LOCATION_PARENT_CAGED",
"GameVersions": ["h2", "h3"],
"SubPackages": [
{
"Id": "FIREARMS_SC_FALCON_HM",
"Drops": [
{
"Id": "FIREARMS_SC_FALCON_HM_LVL_2",
"Level": 2
},
{
"Id": "FIREARMS_SC_FALCON_HM_LVL_3",
"Level": 3
},
{
"Id": "FIREARMS_SC_FALCON_HM_LVL_4",
"Level": 4
},
{
"Id": "FIREARMS_SC_FALCON_HM_LVL_5",
"Level": 5
},
{
"Id": "FIREARMS_SC_FALCON_HM_LVL_6",
"Level": 6
},
{
"Id": "FIREARMS_SC_FALCON_HM_LVL_7",
"Level": 7
},
{
"Id": "FIREARMS_SC_FALCON_HM_LVL_8",
"Level": 8
},
{
"Id": "FIREARMS_SC_FALCON_HM_LVL_9",
"Level": 9
},
{
"Id": "FIREARMS_SC_FALCON_HM_LVL_10",
"Level": 10
},
{
"Id": "FIREARMS_SC_FALCON_HM_LVL_11",
"Level": 11
},
{
"Id": "FIREARMS_SC_FALCON_HM_LVL_12",
"Level": 12
},
{
"Id": "FIREARMS_SC_FALCON_HM_LVL_13",
"Level": 13
},
{
"Id": "FIREARMS_SC_FALCON_HM_LVL_14",
"Level": 14
},
{
"Id": "FIREARMS_SC_FALCON_HM_LVL_15",
"Level": 15
},
{
"Id": "FIREARMS_SC_FALCON_HM_LVL_16",
"Level": 16
},
{
"Id": "FIREARMS_SC_FALCON_HM_LVL_17",
"Level": 17
},
{
"Id": "FIREARMS_SC_FALCON_HM_LVL_18",
"Level": 18
},
{
"Id": "FIREARMS_SC_FALCON_HM_LVL_19",
"Level": 19
},
{
"Id": "FIREARMS_SC_FALCON_HM_LVL_20",
"Level": 20
},
{
"Id": "FIREARMS_SNIPER_DRUZHINA_34_ARCTIC",
"Level": 20
}
]
},
{
"Id": "FIREARMS_SC_FALCON_KNIGHT",
"Drops": [
{
"Id": "FIREARMS_SC_FALCON_KNIGHT_LVL_2",
"Level": 2
},
{
"Id": "FIREARMS_SC_FALCON_KNIGHT_LVL_3",
"Level": 3
},
{
"Id": "FIREARMS_SC_FALCON_KNIGHT_LVL_4",
"Level": 4
},
{
"Id": "FIREARMS_SC_FALCON_KNIGHT_LVL_5",
"Level": 5
},
{
"Id": "FIREARMS_SC_FALCON_KNIGHT_LVL_6",
"Level": 6
},
{
"Id": "FIREARMS_SC_FALCON_KNIGHT_LVL_7",
"Level": 7
},
{
"Id": "FIREARMS_SC_FALCON_KNIGHT_LVL_8",
"Level": 8
},
{
"Id": "FIREARMS_SC_FALCON_KNIGHT_LVL_9",
"Level": 9
},
{
"Id": "FIREARMS_SC_FALCON_KNIGHT_LVL_10",
"Level": 10
},
{
"Id": "FIREARMS_SC_FALCON_KNIGHT_LVL_11",
"Level": 11
},
{
"Id": "FIREARMS_SC_FALCON_KNIGHT_LVL_12",
"Level": 12
},
{
"Id": "FIREARMS_SC_FALCON_KNIGHT_LVL_13",
"Level": 13
},
{
"Id": "FIREARMS_SC_FALCON_KNIGHT_LVL_14",
"Level": 14
},
{
"Id": "FIREARMS_SC_FALCON_KNIGHT_LVL_15",
"Level": 15
},
{
"Id": "FIREARMS_SC_FALCON_KNIGHT_LVL_16",
"Level": 16
},
{
"Id": "FIREARMS_SC_FALCON_KNIGHT_LVL_17",
"Level": 17
},
{
"Id": "FIREARMS_SC_FALCON_KNIGHT_LVL_18",
"Level": 18
},
{
"Id": "FIREARMS_SC_FALCON_KNIGHT_LVL_19",
"Level": 19
},
{
"Id": "FIREARMS_SC_FALCON_KNIGHT_LVL_20",
"Level": 20
}
]
},
{
"Id": "FIREARMS_SC_FALCON_STONE",
"Drops": [
{
"Id": "FIREARMS_SC_FALCON_STONE_LVL_2",
"Level": 2
},
{
"Id": "FIREARMS_SC_FALCON_STONE_LVL_3",
"Level": 3
},
{
"Id": "FIREARMS_SC_FALCON_STONE_LVL_4",
"Level": 4
},
{
"Id": "FIREARMS_SC_FALCON_STONE_LVL_5",
"Level": 5
},
{
"Id": "FIREARMS_SC_FALCON_STONE_LVL_6",
"Level": 6
},
{
"Id": "FIREARMS_SC_FALCON_STONE_LVL_7",
"Level": 7
},
{
"Id": "FIREARMS_SC_FALCON_STONE_LVL_8",
"Level": 8
},
{
"Id": "FIREARMS_SC_FALCON_STONE_LVL_9",
"Level": 9
},
{
"Id": "FIREARMS_SC_FALCON_STONE_LVL_10",
"Level": 10
},
{
"Id": "FIREARMS_SC_FALCON_STONE_LVL_11",
"Level": 11
},
{
"Id": "FIREARMS_SC_FALCON_STONE_LVL_12",
"Level": 12
},
{
"Id": "FIREARMS_SC_FALCON_STONE_LVL_13",
"Level": 13
},
{
"Id": "FIREARMS_SC_FALCON_STONE_LVL_14",
"Level": 14
},
{
"Id": "FIREARMS_SC_FALCON_STONE_LVL_15",
"Level": 15
},
{
"Id": "FIREARMS_SC_FALCON_STONE_LVL_16",
"Level": 16
},
{
"Id": "FIREARMS_SC_FALCON_STONE_LVL_17",
"Level": 17
},
{
"Id": "FIREARMS_SC_FALCON_STONE_LVL_18",
"Level": 18
},
{
"Id": "FIREARMS_SC_FALCON_STONE_LVL_19",
"Level": 19
},
{
"Id": "FIREARMS_SC_FALCON_STONE_LVL_20",
"Level": 20
}
]
}
]
}

View File

@ -0,0 +1,249 @@
{
"LocationId": "LOCATION_PARENT_SALTY",
"GameVersions": ["h2", "h3"],
"SubPackages": [
{
"Id": "FIREARMS_SC_SEAGULL_HM",
"Drops": [
{
"Id": "FIREARMS_SC_SEAGULL_HM_LVL_2",
"Level": 2
},
{
"Id": "FIREARMS_SC_SEAGULL_HM_LVL_3",
"Level": 3
},
{
"Id": "FIREARMS_SC_SEAGULL_HM_LVL_4",
"Level": 4
},
{
"Id": "FIREARMS_SC_SEAGULL_HM_LVL_5",
"Level": 5
},
{
"Id": "FIREARMS_SC_SEAGULL_HM_LVL_6",
"Level": 6
},
{
"Id": "FIREARMS_SC_SEAGULL_HM_LVL_7",
"Level": 7
},
{
"Id": "FIREARMS_SC_SEAGULL_HM_LVL_8",
"Level": 8
},
{
"Id": "FIREARMS_SC_SEAGULL_HM_LVL_9",
"Level": 9
},
{
"Id": "FIREARMS_SC_SEAGULL_HM_LVL_10",
"Level": 10
},
{
"Id": "FIREARMS_SC_SEAGULL_HM_LVL_11",
"Level": 11
},
{
"Id": "FIREARMS_SC_SEAGULL_HM_LVL_12",
"Level": 12
},
{
"Id": "FIREARMS_SC_SEAGULL_HM_LVL_13",
"Level": 13
},
{
"Id": "FIREARMS_SC_SEAGULL_HM_LVL_14",
"Level": 14
},
{
"Id": "FIREARMS_SC_SEAGULL_HM_LVL_15",
"Level": 15
},
{
"Id": "FIREARMS_SC_SEAGULL_HM_LVL_16",
"Level": 16
},
{
"Id": "FIREARMS_SC_SEAGULL_HM_LVL_17",
"Level": 17
},
{
"Id": "FIREARMS_SC_SEAGULL_HM_LVL_18",
"Level": 18
},
{
"Id": "FIREARMS_SC_SEAGULL_HM_LVL_19",
"Level": 19
},
{
"Id": "FIREARMS_SC_SEAGULL_HM_LVL_20",
"Level": 20
}
]
},
{
"Id": "FIREARMS_SC_SEAGULL_KNIGHT",
"Drops": [
{
"Id": "FIREARMS_SC_SEAGULL_KNIGHT_LVL_2",
"Level": 2
},
{
"Id": "FIREARMS_SC_SEAGULL_KNIGHT_LVL_3",
"Level": 3
},
{
"Id": "FIREARMS_SC_SEAGULL_KNIGHT_LVL_4",
"Level": 4
},
{
"Id": "FIREARMS_SC_SEAGULL_KNIGHT_LVL_5",
"Level": 5
},
{
"Id": "FIREARMS_SC_SEAGULL_KNIGHT_LVL_6",
"Level": 6
},
{
"Id": "FIREARMS_SC_SEAGULL_KNIGHT_LVL_7",
"Level": 7
},
{
"Id": "FIREARMS_SC_SEAGULL_KNIGHT_LVL_8",
"Level": 8
},
{
"Id": "FIREARMS_SC_SEAGULL_KNIGHT_LVL_9",
"Level": 9
},
{
"Id": "FIREARMS_SC_SEAGULL_KNIGHT_LVL_10",
"Level": 10
},
{
"Id": "FIREARMS_SC_SEAGULL_KNIGHT_LVL_11",
"Level": 11
},
{
"Id": "FIREARMS_SC_SEAGULL_KNIGHT_LVL_12",
"Level": 12
},
{
"Id": "FIREARMS_SC_SEAGULL_KNIGHT_LVL_13",
"Level": 13
},
{
"Id": "FIREARMS_SC_SEAGULL_KNIGHT_LVL_14",
"Level": 14
},
{
"Id": "FIREARMS_SC_SEAGULL_KNIGHT_LVL_15",
"Level": 15
},
{
"Id": "FIREARMS_SC_SEAGULL_KNIGHT_LVL_16",
"Level": 16
},
{
"Id": "FIREARMS_SC_SEAGULL_KNIGHT_LVL_17",
"Level": 17
},
{
"Id": "FIREARMS_SC_SEAGULL_KNIGHT_LVL_18",
"Level": 18
},
{
"Id": "FIREARMS_SC_SEAGULL_KNIGHT_LVL_19",
"Level": 19
},
{
"Id": "FIREARMS_SC_SEAGULL_KNIGHT_LVL_20",
"Level": 20
}
]
},
{
"Id": "FIREARMS_SC_SEAGULL_STONE",
"Drops": [
{
"Id": "FIREARMS_SC_SEAGULL_STONE_LVL_2",
"Level": 2
},
{
"Id": "FIREARMS_SC_SEAGULL_STONE_LVL_3",
"Level": 3
},
{
"Id": "FIREARMS_SC_SEAGULL_STONE_LVL_4",
"Level": 4
},
{
"Id": "FIREARMS_SC_SEAGULL_STONE_LVL_5",
"Level": 5
},
{
"Id": "FIREARMS_SC_SEAGULL_STONE_LVL_6",
"Level": 6
},
{
"Id": "FIREARMS_SC_SEAGULL_STONE_LVL_7",
"Level": 7
},
{
"Id": "FIREARMS_SC_SEAGULL_STONE_LVL_8",
"Level": 8
},
{
"Id": "FIREARMS_SC_SEAGULL_STONE_LVL_9",
"Level": 9
},
{
"Id": "FIREARMS_SC_SEAGULL_STONE_LVL_10",
"Level": 10
},
{
"Id": "FIREARMS_SC_SEAGULL_STONE_LVL_11",
"Level": 11
},
{
"Id": "FIREARMS_SC_SEAGULL_STONE_LVL_12",
"Level": 12
},
{
"Id": "FIREARMS_SC_SEAGULL_STONE_LVL_13",
"Level": 13
},
{
"Id": "FIREARMS_SC_SEAGULL_STONE_LVL_14",
"Level": 14
},
{
"Id": "FIREARMS_SC_SEAGULL_STONE_LVL_15",
"Level": 15
},
{
"Id": "FIREARMS_SC_SEAGULL_STONE_LVL_16",
"Level": 16
},
{
"Id": "FIREARMS_SC_SEAGULL_STONE_LVL_17",
"Level": 17
},
{
"Id": "FIREARMS_SC_SEAGULL_STONE_LVL_18",
"Level": 18
},
{
"Id": "FIREARMS_SC_SEAGULL_STONE_LVL_19",
"Level": 19
},
{
"Id": "FIREARMS_SC_SEAGULL_STONE_LVL_20",
"Level": 20
}
]
}
]
}

View File

@ -1,5 +1,6 @@
{
"Id": "LOCATION_PARENT_SNUG",
"LocationId": "LOCATION_PARENT_SNUG",
"GameVersions": ["h3"],
"MaxLevel": 100,
"Drops": [
{

View File

@ -0,0 +1,107 @@
{
"LocationId": "LOCATION_PARENT_NORTHAMERICA",
"GameVersions": ["h2"],
"MaxLevel": 15,
"Drops": [
{
"Id": "FIREARMS_HERO_RIFLE_TAC_4_AR_AUTO_S2",
"Level": 2
},
{
"Id": "AGENCYPICKUP_NORTHAMERICA_SMALL_CREEK_SHED",
"Level": 2
},
{
"Id": "AGENCYPICKUP_NORTHAMERICA_SMALL_GARROS_DRIVEWAY",
"Level": 3
},
{
"Id": "STARTING_LOCATION_NORTHAMERICA_SKUNK_CONSTRUCTION",
"Level": 4
},
{
"Id": "AGENCYPICKUP_NORTHAMERICA_LARGE_BENCH",
"Level": 4
},
{
"Id": "PROP_DEVICE_PROXIMITY_CONCUSSION",
"Level": 5
},
{
"Id": "AGENCYPICKUP_NORTHAMERICA_SMALL_PARK",
"Level": 5
},
{
"Id": "AGENCYPICKUP_NORTHAMERICA_SMALL_RENOVATION_HOUSE",
"Level": 6
},
{
"Id": "STARTING_LOCATION_NORTHAMERICA_SKUNK_FUMIGATION",
"Level": 7
},
{
"Id": "PROP_POISON_PILLS_LETHAL",
"Level": 7
},
{
"Id": "AGENCYPICKUP_NORTHAMERICA_LARGE_PARK_SHED",
"Level": 8
},
{
"Id": "STARTING_LOCATION_NORTHAMERICA_SKUNK_GARBAGE",
"Level": 9
},
{
"Id": "AGENCYPICKUP_NORTHAMERICA_SMALL_HELENS_GARAGE",
"Level": 9
},
{
"Id": "PROP_EXPLOSIVE_BASEBALL",
"Level": 10
},
{
"Id": "AGENCYPICKUP_NORTHAMERICA_SMALL_BATTYS_GARDEN",
"Level": 10
},
{
"Id": "STARTING_LOCATION_NORTHAMERICA_SKUNK_GARDENER",
"Level": 11
},
{
"Id": "AGENCYPICKUP_NORTHAMERICA_LARGE_VACATIONHOUSE",
"Level": 11
},
{
"Id": "AGENCYPICKUP_NORTHAMERICA_SMALL_HOUSE_FOR_SALE",
"Level": 12
},
{
"Id": "PROP_DEVICE_ICA_REMOTE_SEMTEX_BLOCK",
"Level": 13
},
{
"Id": "AGENCYPICKUP_NORTHAMERICA_LARGE_TREEHOUSE",
"Level": 13
},
{
"Id": "STARTING_LOCATION_NORTHAMERICA_SKUNK_BBQ",
"Level": 14
},
{
"Id": "AGENCYPICKUP_NORTHAMERICA_SMALL_JANUS_KITCHEN",
"Level": 14
},
{
"Id": "FIREARMS_PISTOL_RUDE_RUBY",
"Level": 15
},
{
"Id": "PROP_DEVICE_ICA_RUBBER_DUCK_REMOTE_EXPLOSIVE_S2",
"Level": 15
},
{
"Id": "AGENCYPICKUP_NORTHAMERICA_LARGE_CASSIDYS_ATTIC",
"Level": 15
}
]
}

View File

@ -1,5 +1,6 @@
{
"Id": "LOCATION_PARENT_NORTHAMERICA",
"LocationId": "LOCATION_PARENT_NORTHAMERICA",
"GameVersions": ["h3"],
"Drops": [
{
"Id": "FIREARMS_HERO_RIFLE_TAC_4_AR_AUTO_S2",

25
docs/USER_PROFILES.md Normal file
View File

@ -0,0 +1,25 @@
# User Profiles
This file serves to document the various changes made to user profiles.
The default user profile for before the versioning system can be found at the following links for
[H2/3](https://github.com/thepeacockproject/Peacock/blob/2a7f41b1c0160191ada0990542c426010b663f29/static/UserDefault.json)
and [H2016](https://github.com/thepeacockproject/Peacock/blob/2a7f41b1c0160191ada0990542c426010b663f29/static/LegacyUserDefault.json).
## Notes about the versioning system
If a version is added **ENSURE** that you accordingly update the `updateUserProfile`
function in `components/utils.ts`. There are comments in that function to guide you.
If you are unsure, ask.
## Version 1
Version 1 introduced the profile versioning system. The changes are:
- Removed unused keys (non location parents) in `u.Extensions.progression.Locations`.
- Upper-cased keys in `u.Extensions.progression.Locations` to fit with the rest of Peacock.
- Added subpackages to certain locations in `u.Extensions.progression.Locations`:
- Legacy profiles now have `normal` and `pro1`.
- Sniper locations now have their unlockables contained inside the location.
- Unused properties were removed from locations in `u.Extensions.progression.Locations`.
- `u.Extensions.progression.Unlockables` has been removed.

View File

@ -36,7 +36,7 @@
"send": "patch:send@npm:0.18.0#.yarn/patches/send-npm-0.18.0-faadf6353f.patch"
},
"dependencies": {
"@peacockproject/statemachine-parser": "^5.9.2",
"@peacockproject/statemachine-parser": "^5.9.3",
"@yarnpkg/fslib": "^3.0.0-rc.42",
"@yarnpkg/libzip": "^3.0.0-rc.42",
"atomically": "^2.0.1",
@ -44,6 +44,7 @@
"body-parser": "*",
"clipanion": "^3.2.0",
"commander": "^10.0.1",
"deepmerge-ts": "^5.1.0",
"esbuild-wasm": "^0.17.18",
"express": "patch:express@npm%3A4.18.2#~/.yarn/patches/express-npm-4.18.2-bb15ff679a.patch",
"jest-diff": "^29.5.0",

View File

@ -21,7 +21,7 @@
"reversion": "node ../versionTypedefsPackageJson.mjs"
},
"dependencies": {
"@peacockproject/statemachine-parser": "^5.9.2",
"@peacockproject/statemachine-parser": "^5.9.3",
"@types/express": "^4.17.17",
"@types/node": "^18.15.11",
"atomically": "^2.0.1",

File diff suppressed because it is too large Load Diff

View File

@ -22,67 +22,33 @@
"LastScore": 0,
"LastCompletedChallenge": "00000000-0000-0000-0000-000000000000",
"Locations": {
"location_parent_ica_facility": {
"Xp": 0,
"PreviousXp": 0,
"NextLevelXP": 0,
"LastCompletedChallenge": "00000000-0000-0000-0000-000000000000",
"PreviouslySeenStaging": null,
"LevelCompletion": 0,
"Level": 1
"LOCATION_PARENT_ICA_FACILITY": {
"normal": { "Xp": 0, "Level": 1, "PreviouslySeenXp": 0 },
"pro1": { "Xp": 0, "Level": 1, "PreviouslySeenXp": 0 }
},
"location_parent_paris": {
"Xp": 0,
"PreviousXp": 0,
"NextLevelXP": 0,
"LastCompletedChallenge": "00000000-0000-0000-0000-000000000000",
"PreviouslySeenStaging": null,
"LevelCompletion": 0,
"Level": 1
"LOCATION_PARENT_PARIS": {
"normal": { "Xp": 0, "Level": 1, "PreviouslySeenXp": 0 },
"pro1": { "Xp": 0, "Level": 1, "PreviouslySeenXp": 0 }
},
"location_parent_coastaltown": {
"Xp": 0,
"PreviousXp": 0,
"NextLevelXP": 0,
"LastCompletedChallenge": "00000000-0000-0000-0000-000000000000",
"PreviouslySeenStaging": null,
"LevelCompletion": 0,
"Level": 1
"LOCATION_PARENT_COASTALTOWN": {
"normal": { "Xp": 0, "Level": 1, "PreviouslySeenXp": 0 },
"pro1": { "Xp": 0, "Level": 1, "PreviouslySeenXp": 0 }
},
"location_parent_marrakech": {
"Xp": 0,
"PreviousXp": 0,
"NextLevelXP": 0,
"LastCompletedChallenge": "00000000-0000-0000-0000-000000000000",
"PreviouslySeenStaging": null,
"LevelCompletion": 0
"LOCATION_PARENT_MARRAKECH": {
"normal": { "Xp": 0, "Level": 1, "PreviouslySeenXp": 0 },
"pro1": { "Xp": 0, "Level": 1, "PreviouslySeenXp": 0 }
},
"location_parent_bangkok": {
"Xp": 0,
"PreviousXp": 0,
"NextLevelXP": 0,
"LastCompletedChallenge": "00000000-0000-0000-0000-000000000000",
"PreviouslySeenStaging": null,
"LevelCompletion": 0,
"Level": 1
"LOCATION_PARENT_BANGKOK": {
"normal": { "Xp": 0, "Level": 1, "PreviouslySeenXp": 0 },
"pro1": { "Xp": 0, "Level": 1, "PreviouslySeenXp": 0 }
},
"location_parent_colorado": {
"Xp": 0,
"PreviousXp": 0,
"NextLevelXP": 0,
"LastCompletedChallenge": "00000000-0000-0000-0000-000000000000",
"PreviouslySeenStaging": null,
"LevelCompletion": 0,
"Level": 1
"LOCATION_PARENT_COLORADO": {
"normal": { "Xp": 0, "Level": 1, "PreviouslySeenXp": 0 },
"pro1": { "Xp": 0, "Level": 1, "PreviouslySeenXp": 0 }
},
"location_parent_hokkaido": {
"Xp": 0,
"PreviousXp": 0,
"NextLevelXP": 0,
"LastCompletedChallenge": "00000000-0000-0000-0000-000000000000",
"PreviouslySeenStaging": null,
"LevelCompletion": 0,
"Level": 1
"LOCATION_PARENT_HOKKAIDO": {
"normal": { "Xp": 0, "Level": 1, "PreviouslySeenXp": 0 },
"pro1": { "Xp": 0, "Level": 1, "PreviouslySeenXp": 0 }
}
},
"PlayerProfileXP": {
@ -163,8 +129,7 @@
"ProfileLevel": 0,
"PreviouslySeenStaging": null
},
"TimeDropDelta": 0,
"Unlockables": {}
"TimeDropDelta": 0
},
"gamepersistentdata": {
"prologue": {
@ -208,5 +173,6 @@
"NintendoId": null,
"XboxLiveId": null,
"PSNAccountId": null,
"PSNOnlineId": null
"PSNOnlineId": null,
"Version": 1
}

View File

@ -8625,5 +8625,189 @@
]
},
"Rarity": "legendary"
},
{
"Id": "DIFFICULTY_UNLOCK_PRO1_PARIS",
"Type": "difficultyunlock",
"Subtype": "difficultyunlock",
"GameAsset": null,
"ImageId": "",
"RMTPrice": -1,
"GamePrice": -1,
"IsPurchasable": false,
"IsPublished": true,
"IsDroppable": false,
"Capabilities": [],
"Qualities": {},
"Properties": {
"Name": "UI_DIFFICULTY_UNLOCK_PRO1_PARIS",
"Description": "UI_DIFFICULTY_UNLOCK_PRO1_PARIS_DESC",
"Icon": "images/difficulty/paris_difficulty_pro.jpg",
"UnlockOrder": 20
},
"Guid": "a356b7c6-2778-49dd-9507-dadf6dad40f8",
"Rarity": null,
"DisplayNameLocKey": "UI_DIFFICULTY_UNLOCK_PRO1_PARIS_NAME"
},
{
"Id": "DIFFICULTY_UNLOCK_PRO1_COASTALTOWN",
"Type": "difficultyunlock",
"Subtype": "difficultyunlock",
"GameAsset": null,
"ImageId": "",
"RMTPrice": -1,
"GamePrice": -1,
"IsPurchasable": false,
"IsPublished": true,
"IsDroppable": false,
"Capabilities": [],
"Qualities": {},
"Properties": {
"Name": "UI_DIFFICULTY_UNLOCK_PRO1_COASTALTOWN",
"Description": "UI_DIFFICULTY_UNLOCK_PRO1_COASTALTOWN_DESC",
"Icon": "images/difficulty/coastaltown_difficulty_pro.jpg",
"UnlockOrder": 20
},
"Guid": "8e557d15-4462-4095-8b27-82aa945ed9ef",
"Rarity": null,
"DisplayNameLocKey": "UI_DIFFICULTY_UNLOCK_PRO1_COASTALTOWN_NAME"
},
{
"Id": "DIFFICULTY_UNLOCK_PRO1_MARRAKECH",
"Type": "difficultyunlock",
"Subtype": "difficultyunlock",
"GameAsset": null,
"ImageId": "",
"RMTPrice": -1,
"GamePrice": -1,
"IsPurchasable": false,
"IsPublished": true,
"IsDroppable": false,
"Capabilities": [],
"Qualities": {},
"Properties": {
"Name": "UI_DIFFICULTY_UNLOCK_PRO1_MARRAKECH",
"Description": "UI_DIFFICULTY_UNLOCK_PRO1_MARRAKECH_DESC",
"Icon": "images/difficulty/marrakech_difficulty_pro.jpg",
"UnlockOrder": 20
},
"Guid": "d958cf79-4851-434a-83a6-da0feffb9f74",
"Rarity": null,
"DisplayNameLocKey": "UI_DIFFICULTY_UNLOCK_PRO1_MARRAKECH_NAME"
},
{
"Id": "DIFFICULTY_UNLOCK_PRO1_BANGKOK",
"Type": "difficultyunlock",
"Subtype": "difficultyunlock",
"GameAsset": null,
"ImageId": "",
"RMTPrice": -1,
"GamePrice": -1,
"IsPurchasable": false,
"IsPublished": true,
"IsDroppable": false,
"Capabilities": [],
"Qualities": {},
"Properties": {
"Name": "UI_DIFFICULTY_UNLOCK_PRO1_BANGKOK",
"Description": "UI_DIFFICULTY_UNLOCK_PRO1_BANGKOK_DESC",
"Icon": "images/difficulty/bangkok_difficulty_pro.jpg",
"UnlockOrder": 20
},
"Guid": "54fd15ac-5e27-4ee1-804d-d73e979e6952",
"Rarity": null,
"DisplayNameLocKey": "UI_DIFFICULTY_UNLOCK_PRO1_BANGKOK_NAME"
},
{
"Id": "DIFFICULTY_UNLOCK_PRO1_COLORADO",
"Type": "difficultyunlock",
"Subtype": "difficultyunlock",
"GameAsset": null,
"ImageId": "",
"RMTPrice": -1,
"GamePrice": -1,
"IsPurchasable": false,
"IsPublished": true,
"IsDroppable": false,
"Capabilities": [],
"Qualities": {},
"Properties": {
"Name": "UI_DIFFICULTY_UNLOCK_PRO1_COLORADO",
"Description": "UI_DIFFICULTY_UNLOCK_PRO1_COLORADO_DESC",
"Icon": "images/difficulty/colorado_difficulty_pro.jpg",
"UnlockOrder": 20
},
"Guid": "d9571b4c-b3e6-4cc7-98ef-a0368f81c0c5",
"Rarity": null,
"DisplayNameLocKey": "UI_DIFFICULTY_UNLOCK_PRO1_COLORADO_NAME"
},
{
"Id": "DIFFICULTY_UNLOCK_PRO1_HOKKAIDO",
"Type": "difficultyunlock",
"Subtype": "difficultyunlock",
"GameAsset": null,
"ImageId": "",
"RMTPrice": -1,
"GamePrice": -1,
"IsPurchasable": false,
"IsPublished": true,
"IsDroppable": false,
"Capabilities": [],
"Qualities": {},
"Properties": {
"Name": "UI_DIFFICULTY_UNLOCK_PRO1_HOKKAIDO",
"Description": "UI_DIFFICULTY_UNLOCK_PRO1_HOKKAIDO_DESC",
"Icon": "images/difficulty/hokkaido_difficulty_pro.jpg",
"UnlockOrder": 20
},
"Guid": "6239151b-ac67-424e-b61d-83aacbb8265e",
"Rarity": null,
"DisplayNameLocKey": "UI_DIFFICULTY_UNLOCK_PRO1_HOKKAIDO_NAME"
},
{
"Id": "LOADOUT_UNLOCK_HOKKAIDO",
"Type": "loadoutunlock",
"Subtype": "loadoutunlock",
"GameAsset": null,
"ImageId": "",
"RMTPrice": -1,
"GamePrice": -1,
"IsPurchasable": false,
"IsPublished": true,
"IsDroppable": false,
"Capabilities": [],
"Qualities": {},
"Properties": {
"Name": "UI_LOADOUT_UNLOCK",
"Description": "UI_LOADOUT_UNLOCK_DESC",
"Icon": "images/unlockables/loadout_unlock.jpg",
"UnlockOrder": 20
},
"Guid": "35dc17b5-fdb6-494c-bb65-38d81f73fc29",
"Rarity": null,
"DisplayNameLocKey": "UI_LOADOUT_UNLOCK_HOKKAIDO_NAME"
},
{
"Id": "PRO1_LOADOUT_UNLOCK_HOKKAIDO",
"Type": "loadoutunlock",
"Subtype": "loadoutunlock",
"GameAsset": null,
"ImageId": "",
"RMTPrice": -1,
"GamePrice": -1,
"IsPurchasable": false,
"IsPublished": true,
"IsDroppable": false,
"Capabilities": [],
"Qualities": {},
"Properties": {
"Difficulty": "pro1",
"Name": "UI_LOADOUT_UNLOCK",
"Description": "UI_LOADOUT_UNLOCK_DESC",
"Icon": "images/unlockables/loadout_unlock.jpg"
},
"Guid": "aaf4e090-09ff-4e4c-9f4b-3a3dcb29e756",
"Rarity": null,
"DisplayNameLocKey": "UI_PRO1_LOADOUT_UNLOCK_HOKKAIDO_NAME"
}
]

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,71 @@
{
"parents": {
"LOCATION_PARENT_AUSTRIA": {
"Properties": {
"Icon": "images/locations/LOCATION_AUSTRIA/tile.jpg",
"Background": "images/locations/LOCATION_AUSTRIA/background.jpg",
"DlcImage": "images/livetile/dlc/austria_wide_logo.png",
"DlcName": "GAME_STORE_METADATA_S2_DLC13_TITLE",
"IsLocked": false,
"UpcomingContent": false,
"Order": 200,
"ProgressionKey": "LOCATION_AUSTRIA",
"HideProgression": true,
"Season": 2,
"RequiredResources": [
"[assembly:/_PRO/Scenes/Missions/Hawk/scene_hawk.entity].entitytemplate"
]
},
"Rarity": null,
"DisplayNameLocKey": "UI_LOCATION_PARENT_AUSTRIA_NAME",
"Id": "LOCATION_PARENT_AUSTRIA",
"Type": "location",
"Subtype": "location",
"GameAsset": null,
"ImageId": "",
"RMTPrice": -1,
"GamePrice": -1,
"IsPurchasable": false,
"IsPublished": true,
"IsDroppable": false,
"Capabilities": [],
"Qualities": {},
"Guid": "163f9ed3-116c-4dca-9974-7e7f1757330a"
}
},
"children": {
"LOCATION_AUSTRIA": {
"Properties": {
"ParentLocation": "LOCATION_PARENT_AUSTRIA",
"Icon": "images/locations/LOCATION_AUSTRIA/tile.jpg",
"Background": "images/locations/LOCATION_AUSTRIA/background.jpg",
"DlcImage": "images/livetile/dlc/austria_wide_logo.png",
"DlcName": "GAME_STORE_METADATA_S2_DLC13_TITLE",
"IsLocked": false,
"UpcomingContent": false,
"Order": 0,
"ProgressionKey": "LOCATION_AUSTRIA",
"HideProgression": true,
"Season": 2,
"RequiredResources": [
"[assembly:/_PRO/Scenes/Missions/Hawk/scene_hawk.entity].entitytemplate"
]
},
"Rarity": null,
"DisplayNameLocKey": "UI_LOCATION_AUSTRIA_NAME",
"Id": "LOCATION_AUSTRIA",
"Type": "location",
"Subtype": "sublocation",
"GameAsset": null,
"ImageId": "",
"RMTPrice": -1,
"GamePrice": -1,
"IsPurchasable": false,
"IsPublished": true,
"IsDroppable": false,
"Capabilities": [],
"Qualities": {},
"Guid": "176f3b0b-983b-420f-b07b-a09a58b5bd2b"
}
}
}

View File

@ -21,6 +21,29 @@
},
"Rarity": null
},
{
"Id": "TOKEN_OUTFIT_HITMANSUIT",
"Guid": "5fa043cf-fe36-457c-9e44-2fa330903e24",
"Type": "disguise",
"Subtype": "disguise",
"ImageId": "",
"RMTPrice": -1,
"GamePrice": -1,
"IsPurchasable": false,
"IsPublished": true,
"IsDroppable": false,
"Capabilities": [],
"Qualities": {},
"Properties": {
"Quality": 4,
"Rarity": "common",
"LoadoutSlot": "disguise",
"IsConsumable": false,
"RepositoryId": "4fc9396e-2619-4e66-a51e-2bd366230da7",
"OrderIndex": 10
},
"Rarity": "common"
},
{
"Id": "FIREARMS_SC_HERO_SNIPER_HM",
"Guid": "34867527-1f8b-46ef-9f08-95a2a62931a7",

File diff suppressed because it is too large Load Diff

11654
static/SniperUnlockables.json Normal file

File diff suppressed because it is too large Load Diff

View File

@ -15,187 +15,171 @@
"LastScore": 0,
"LastCompletedChallenge": "00000000-0000-0000-0000-000000000000",
"Locations": {
"location_parent_ica_facility": {
"LOCATION_PARENT_ICA_FACILITY": {
"Xp": 0,
"PreviouslySeenXp": 0,
"LastCompletedChallenge": "00000000-0000-0000-0000-000000000000",
"PreviouslySeenStaging": null,
"Level": 1
"Level": 1,
"PreviouslySeenXp": 0
},
"location_parent_paris": {
"LOCATION_PARENT_PARIS": {
"Xp": 0,
"PreviouslySeenXp": 0,
"LastCompletedChallenge": "00000000-0000-0000-0000-000000000000",
"PreviouslySeenStaging": null,
"Level": 1
"Level": 1,
"PreviouslySeenXp": 0
},
"location_parent_coastaltown": {
"LOCATION_PARENT_COASTALTOWN": {
"Xp": 0,
"PreviouslySeenXp": 0,
"LastCompletedChallenge": "00000000-0000-0000-0000-000000000000",
"PreviouslySeenStaging": null,
"Level": 1
"Level": 1,
"PreviouslySeenXp": 0
},
"location_parent_marrakech": {
"LOCATION_PARENT_MARRAKECH": {
"Xp": 0,
"PreviouslySeenXp": 0,
"LastCompletedChallenge": "00000000-0000-0000-0000-000000000000",
"PreviouslySeenStaging": null,
"Level": 1
"Level": 1,
"PreviouslySeenXp": 0
},
"location_parent_bangkok": {
"LOCATION_PARENT_BANGKOK": {
"Xp": 0,
"PreviouslySeenXp": 0,
"LastCompletedChallenge": "00000000-0000-0000-0000-000000000000",
"PreviouslySeenStaging": null,
"Level": 1
"Level": 1,
"PreviouslySeenXp": 0
},
"location_parent_colorado": {
"LOCATION_PARENT_COLORADO": {
"Xp": 0,
"PreviouslySeenXp": 0,
"LastCompletedChallenge": "00000000-0000-0000-0000-000000000000",
"PreviouslySeenStaging": null,
"Level": 1
"Level": 1,
"PreviouslySeenXp": 0
},
"location_parent_hokkaido": {
"LOCATION_PARENT_HOKKAIDO": {
"Xp": 0,
"PreviouslySeenXp": 0,
"LastCompletedChallenge": "00000000-0000-0000-0000-000000000000",
"PreviouslySeenStaging": null,
"Level": 1
"Level": 1,
"PreviouslySeenXp": 0
},
"location_parent_newzealand": {
"LOCATION_PARENT_NEWZEALAND": {
"Xp": 0,
"PreviouslySeenXp": 0,
"LastCompletedChallenge": "00000000-0000-0000-0000-000000000000",
"PreviouslySeenStaging": null,
"Level": 1
"Level": 1,
"PreviouslySeenXp": 0
},
"location_parent_miami": {
"LOCATION_PARENT_MIAMI": {
"Xp": 0,
"PreviouslySeenXp": 0,
"LastCompletedChallenge": "00000000-0000-0000-0000-000000000000",
"PreviouslySeenStaging": null,
"Level": 1
"Level": 1,
"PreviouslySeenXp": 0
},
"location_parent_colombia": {
"LOCATION_PARENT_COLOMBIA": {
"Xp": 0,
"PreviouslySeenXp": 0,
"LastCompletedChallenge": "00000000-0000-0000-0000-000000000000",
"PreviouslySeenStaging": null,
"Level": 1
"Level": 1,
"PreviouslySeenXp": 0
},
"location_parent_mumbai": {
"LOCATION_PARENT_MUMBAI": {
"Xp": 0,
"PreviouslySeenXp": 0,
"LastCompletedChallenge": "00000000-0000-0000-0000-000000000000",
"PreviouslySeenStaging": null,
"Level": 1
"Level": 1,
"PreviouslySeenXp": 0
},
"location_parent_northamerica": {
"LOCATION_PARENT_NORTHAMERICA": {
"Xp": 0,
"PreviouslySeenXp": 0,
"LastCompletedChallenge": "00000000-0000-0000-0000-000000000000",
"PreviouslySeenStaging": null,
"Level": 1
"Level": 1,
"PreviouslySeenXp": 0
},
"location_parent_northsea": {
"LOCATION_PARENT_NORTHSEA": {
"Xp": 0,
"PreviouslySeenXp": 0,
"LastCompletedChallenge": "00000000-0000-0000-0000-000000000000",
"PreviouslySeenStaging": null,
"Level": 1
"Level": 1,
"PreviouslySeenXp": 0
},
"location_parent_greedy": {
"LOCATION_PARENT_GREEDY": {
"Xp": 0,
"PreviouslySeenXp": 0,
"LastCompletedChallenge": "00000000-0000-0000-0000-000000000000",
"PreviouslySeenStaging": null,
"Level": 1
"Level": 1,
"PreviouslySeenXp": 0
},
"location_parent_opulent": {
"LOCATION_PARENT_OPULENT": {
"Xp": 0,
"PreviouslySeenXp": 0,
"LastCompletedChallenge": "00000000-0000-0000-0000-000000000000",
"PreviouslySeenStaging": null,
"Level": 1
"Level": 1,
"PreviouslySeenXp": 0
},
"location_parent_austria": {
"Xp": 0,
"PreviouslySeenXp": 0,
"LastCompletedChallenge": "00000000-0000-0000-0000-000000000000",
"PreviouslySeenStaging": null,
"Level": 1
"LOCATION_PARENT_AUSTRIA": {
"FIREARMS_SC_HERO_SNIPER_HM": {
"Xp": 0,
"Level": 1,
"PreviouslySeenXp": 0
},
"FIREARMS_SC_HERO_SNIPER_KNIGHT": {
"Xp": 0,
"Level": 1,
"PreviouslySeenXp": 0
},
"FIREARMS_SC_HERO_SNIPER_STONE": {
"Xp": 0,
"Level": 1,
"PreviouslySeenXp": 0
}
},
"location_parent_salty": {
"Xp": 0,
"PreviouslySeenXp": 0,
"LastCompletedChallenge": "00000000-0000-0000-0000-000000000000",
"PreviouslySeenStaging": null,
"Level": 1
"LOCATION_PARENT_SALTY": {
"FIREARMS_SC_SEAGULL_HM": {
"Xp": 0,
"Level": 1,
"PreviouslySeenXp": 0
},
"FIREARMS_SC_SEAGULL_KNIGHT": {
"Xp": 0,
"Level": 1,
"PreviouslySeenXp": 0
},
"FIREARMS_SC_SEAGULL_STONE": {
"Xp": 0,
"Level": 1,
"PreviouslySeenXp": 0
}
},
"location_parent_caged": {
"Xp": 0,
"PreviouslySeenXp": 0,
"LastCompletedChallenge": "00000000-0000-0000-0000-000000000000",
"PreviouslySeenStaging": null,
"Level": 1
"LOCATION_PARENT_CAGED": {
"FIREARMS_SC_FALCON_HM": {
"Xp": 0,
"Level": 1,
"PreviouslySeenXp": 0
},
"FIREARMS_SC_FALCON_KNIGHT": {
"Xp": 0,
"Level": 1,
"PreviouslySeenXp": 0
},
"FIREARMS_SC_FALCON_STONE": {
"Xp": 0,
"Level": 1,
"PreviouslySeenXp": 0
}
},
"location_parent_golden": {
"LOCATION_PARENT_GOLDEN": {
"Xp": 0,
"PreviouslySeenXp": 0,
"LastCompletedChallenge": "00000000-0000-0000-0000-000000000000",
"PreviouslySeenStaging": null,
"Level": 1
"Level": 1,
"PreviouslySeenXp": 0
},
"location_parent_ancestral": {
"LOCATION_PARENT_ANCESTRAL": {
"Xp": 0,
"PreviouslySeenXp": 0,
"LastCompletedChallenge": "00000000-0000-0000-0000-000000000000",
"PreviouslySeenStaging": null,
"Level": 1
"Level": 1,
"PreviouslySeenXp": 0
},
"location_parent_edgy": {
"LOCATION_PARENT_EDGY": {
"Xp": 0,
"PreviouslySeenXp": 0,
"LastCompletedChallenge": "00000000-0000-0000-0000-000000000000",
"PreviouslySeenStaging": null,
"Level": 1
"Level": 1,
"PreviouslySeenXp": 0
},
"location_parent_wet": {
"LOCATION_PARENT_WET": {
"Xp": 0,
"PreviouslySeenXp": 0,
"LastCompletedChallenge": "00000000-0000-0000-0000-000000000000",
"PreviouslySeenStaging": null,
"Level": 1
"Level": 1,
"PreviouslySeenXp": 0
},
"location_parent_elegant": {
"LOCATION_PARENT_ELEGANT": {
"Xp": 0,
"PreviouslySeenXp": 0,
"LastCompletedChallenge": "00000000-0000-0000-0000-000000000000",
"PreviouslySeenStaging": null,
"Level": 1
"Level": 1,
"PreviouslySeenXp": 0
},
"location_parent_trapped": {
"LOCATION_PARENT_TRAPPED": {
"Xp": 0,
"PreviouslySeenXp": 0,
"LastCompletedChallenge": "00000000-0000-0000-0000-000000000000",
"PreviouslySeenStaging": null,
"Level": 1
"Level": 1,
"PreviouslySeenXp": 0
},
"location_parent_rocky": {
"LOCATION_PARENT_ROCKY": {
"Xp": 0,
"PreviouslySeenXp": 0,
"LastCompletedChallenge": "00000000-0000-0000-0000-000000000000",
"PreviouslySeenStaging": null,
"Level": 1
"Level": 1,
"PreviouslySeenXp": 0
},
"location_parent_snug": {
"LOCATION_PARENT_SNUG": {
"Xp": 0,
"PreviouslySeenXp": 0,
"LastCompletedChallenge": "00000000-0000-0000-0000-000000000000",
"PreviouslySeenStaging": null,
"Level": 1
"Level": 1,
"PreviouslySeenXp": 0
}
},
"PlayerProfileXP": {
@ -366,27 +350,7 @@
"ProfileLevel": 0,
"PreviouslySeenStaging": null
},
"TimeDropDelta": 0,
"Unlockables": {
"firearms_sc_hero_sniper_stone": {
"Xp": 0,
"LastCompletedChallenge": "00000000-0000-0000-0000-000000000000"
},
"firearms_sc_falcon_hm": {
"Xp": 0,
"PreviouslySeenXp": 0,
"LastCompletedChallenge": "00000000-0000-0000-0000-000000000000",
"PreviouslySeenStaging": null,
"Level": 1
},
"firearms_sc_falcon_knight": {
"Xp": 0,
"PreviouslySeenXp": 0,
"LastCompletedChallenge": "00000000-0000-0000-0000-000000000000",
"PreviouslySeenStaging": null,
"Level": 1
}
}
"TimeDropDelta": 0
},
"gamepersistentdata": {
"IsFSPUser": false,
@ -431,5 +395,6 @@
"NintendoId": null,
"XboxLiveId": null,
"PSNAccountId": null,
"PSNOnlineId": null
"PSNOnlineId": null,
"Version": 1
}

View File

@ -438,7 +438,7 @@ __metadata:
version: 0.0.0-use.local
resolution: "@peacockproject/core@workspace:packaging/typedefs"
dependencies:
"@peacockproject/statemachine-parser": "npm:^5.9.2"
"@peacockproject/statemachine-parser": "npm:^5.9.3"
"@types/express": "npm:^4.17.17"
"@types/node": "npm:^18.15.11"
atomically: "npm:^2.0.1"
@ -465,7 +465,7 @@ __metadata:
dependencies:
"@mixer/parallel-prettier": "npm:^2.0.3"
"@peacockproject/eslint-plugin": "workspace:*"
"@peacockproject/statemachine-parser": "npm:^5.9.2"
"@peacockproject/statemachine-parser": "npm:^5.9.3"
"@types/body-parser": "npm:1.19.2"
"@types/express": "npm:^4.17.17"
"@types/jsonwebtoken": "npm:^9.0.2"
@ -485,6 +485,7 @@ __metadata:
body-parser: "npm:*"
clipanion: "npm:^3.2.0"
commander: "npm:^10.0.1"
deepmerge-ts: "npm:^5.1.0"
esbuild: "npm:^0.17.18"
esbuild-register: "npm:^3.4.2"
esbuild-wasm: "npm:^0.17.18"
@ -521,10 +522,10 @@ __metadata:
languageName: unknown
linkType: soft
"@peacockproject/statemachine-parser@npm:^5.9.2":
version: 5.9.2
resolution: "@peacockproject/statemachine-parser@npm:5.9.2"
checksum: 994c753990448d745170b9a2d8e5442de0075625eb5cf2d4731a609a7459598089df9160b867ee898a17ac65fec26b4f2d9be3a5b48267cc9addb71aeae4485f
"@peacockproject/statemachine-parser@npm:^5.9.3":
version: 5.9.3
resolution: "@peacockproject/statemachine-parser@npm:5.9.3"
checksum: 7332a5b7616fd9c54df1433a49f410b49e461a865b588f4bb3cc17bdf781ad054e1fb80d7d4ff312cde2af0515c0ea8b7dcaed029ecddb52f29c4a9a2ad37669
languageName: node
linkType: hard
@ -1726,6 +1727,13 @@ __metadata:
languageName: node
linkType: hard
"deepmerge-ts@npm:^5.1.0":
version: 5.1.0
resolution: "deepmerge-ts@npm:5.1.0"
checksum: 80a548a1e6a9d22565accddd8cba31a55ee84958420a4ae2c9b0feb15a1f2c6be157a75d1b363a19d294383ffdde95a168f7e000ba575afe15d7ca572fcd0609
languageName: node
linkType: hard
"defaults@npm:^1.0.3":
version: 1.0.4
resolution: "defaults@npm:1.0.4"