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:
commit
29b140d29e
@ -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,
|
||||
},
|
||||
|
@ -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,
|
||||
)
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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) => {
|
||||
|
@ -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)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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)!`,
|
||||
|
@ -151,6 +151,8 @@ export async function loadUserData(
|
||||
|
||||
const userProfile = castUserProfile(
|
||||
JSON.parse((await readFile(path)).toString()),
|
||||
gameVersion,
|
||||
path,
|
||||
)
|
||||
|
||||
asyncGuard.addLoadedProfile(`${userId}.${gameVersion}`, userProfile)
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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) {
|
||||
|
@ -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(
|
||||
|
@ -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 }
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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(
|
||||
|
@ -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,
|
||||
},
|
||||
})),
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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) =>
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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[]
|
||||
|
@ -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[]
|
||||
}
|
||||
|
@ -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
|
||||
}
|
@ -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 {
|
||||
|
@ -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!")
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
{
|
||||
"Id": "LOCATION_PARENT_ROCKY",
|
||||
"LocationId": "LOCATION_PARENT_ROCKY",
|
||||
"GameVersions": ["h3"],
|
||||
"Drops": [
|
||||
{
|
||||
"Id": "AGENCYPICKUP_ROCKY_SMALL_BIGCENTRALTREE",
|
||||
|
@ -1,5 +1,6 @@
|
||||
{
|
||||
"Id": "LOCATION_PARENT_BANGKOK",
|
||||
"LocationId": "LOCATION_PARENT_BANGKOK",
|
||||
"GameVersions": ["h3"],
|
||||
"Drops": [
|
||||
{
|
||||
"Id": "STARTING_LOCATION_BANGKOK_SECURITY_HUT",
|
||||
|
94
contractdata/BANGKOK/_H2_BANGKOK_MASTERY.json
Normal file
94
contractdata/BANGKOK/_H2_BANGKOK_MASTERY.json
Normal 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
|
||||
}
|
||||
]
|
||||
}
|
169
contractdata/BANGKOK/_LEGACY_BANGKOK_MASTERY.json
Normal file
169
contractdata/BANGKOK/_LEGACY_BANGKOK_MASTERY.json
Normal 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
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
{
|
||||
"Id": "LOCATION_PARENT_EDGY",
|
||||
"LocationId": "LOCATION_PARENT_EDGY",
|
||||
"GameVersions": ["h3"],
|
||||
"Drops": [
|
||||
{
|
||||
"Id": "AGENCYPICKUP_EDGY_LARGE_ENTRANCE",
|
||||
|
@ -1,5 +1,6 @@
|
||||
{
|
||||
"Id": "LOCATION_PARENT_TRAPPED",
|
||||
"LocationId": "LOCATION_PARENT_TRAPPED",
|
||||
"GameVersions": ["h3"],
|
||||
"MaxLevel": 5,
|
||||
"Drops": [
|
||||
{
|
||||
|
@ -1,5 +1,6 @@
|
||||
{
|
||||
"Id": "LOCATION_PARENT_WET",
|
||||
"LocationId": "LOCATION_PARENT_WET",
|
||||
"GameVersions": ["h3"],
|
||||
"Drops": [
|
||||
{
|
||||
"Id": "AGENCYPICKUP_WET_SMALL_ARCADE",
|
||||
|
@ -1,5 +1,6 @@
|
||||
{
|
||||
"Id": "LOCATION_PARENT_COLORADO",
|
||||
"LocationId": "LOCATION_PARENT_COLORADO",
|
||||
"GameVersions": ["h3"],
|
||||
"Drops": [
|
||||
{
|
||||
"Id": "AGENCYPICKUP_COLORADO_WATERTOWER",
|
||||
|
90
contractdata/COLORADO/_H2_COLORADO_MASTERY.json
Normal file
90
contractdata/COLORADO/_H2_COLORADO_MASTERY.json
Normal 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
|
||||
}
|
||||
]
|
||||
}
|
169
contractdata/COLORADO/_LEGACY_COLORADO_MASTERY.json
Normal file
169
contractdata/COLORADO/_LEGACY_COLORADO_MASTERY.json
Normal 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
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
{
|
||||
"Id": "LOCATION_PARENT_ANCESTRAL",
|
||||
"LocationId": "LOCATION_PARENT_ANCESTRAL",
|
||||
"GameVersions": ["h3"],
|
||||
"Drops": [
|
||||
{
|
||||
"Id": "AGENCYPICKUP_ANCESTRAL_SMALL_DELIVERIES",
|
||||
|
@ -1,5 +1,6 @@
|
||||
{
|
||||
"Id": "LOCATION_PARENT_GOLDEN",
|
||||
"LocationId": "LOCATION_PARENT_GOLDEN",
|
||||
"GameVersions": ["h3"],
|
||||
"Drops": [
|
||||
{
|
||||
"Id": "PROP_MELEE_SHOWGLOBE",
|
||||
|
@ -1,5 +1,6 @@
|
||||
{
|
||||
"Id": "LOCATION_PARENT_ICA_FACILITY",
|
||||
"LocationId": "LOCATION_PARENT_ICA_FACILITY",
|
||||
"GameVersions": ["h1", "h2", "h3"],
|
||||
"MaxLevel": 4,
|
||||
"HideProgression": true,
|
||||
"Drops": []
|
||||
|
82
contractdata/HAVEN/_H2_HAVEN_MASTERY.json
Normal file
82
contractdata/HAVEN/_H2_HAVEN_MASTERY.json
Normal 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
|
||||
}
|
||||
]
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
{
|
||||
"Id": "LOCATION_PARENT_OPULENT",
|
||||
"LocationId": "LOCATION_PARENT_OPULENT",
|
||||
"GameVersions": ["h3"],
|
||||
"Drops": [
|
||||
{
|
||||
"Id": "PROP_MELEE_PIRATE_SABRE",
|
||||
|
35
contractdata/HAWKESBAY/_H2_HAWKESBAY_MASTERY.json
Normal file
35
contractdata/HAWKESBAY/_H2_HAWKESBAY_MASTERY.json
Normal 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
|
||||
}
|
||||
]
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
{
|
||||
"Id": "LOCATION_PARENT_NEWZEALAND",
|
||||
"LocationId": "LOCATION_PARENT_NEWZEALAND",
|
||||
"GameVersions": ["h3"],
|
||||
"MaxLevel": 5,
|
||||
"Drops": [
|
||||
{
|
||||
|
94
contractdata/HOKKAIDO/_H2_HOKKAIDO_MASTERY.json
Normal file
94
contractdata/HOKKAIDO/_H2_HOKKAIDO_MASTERY.json
Normal 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
|
||||
}
|
||||
]
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
{
|
||||
"Id": "LOCATION_PARENT_HOKKAIDO",
|
||||
"LocationId": "LOCATION_PARENT_HOKKAIDO",
|
||||
"GameVersions": ["h3"],
|
||||
"Drops": [
|
||||
{
|
||||
"Id": "PROP_MELEE_SHURIKEN",
|
||||
|
177
contractdata/HOKKAIDO/_LEGACY_HOKKAIDO_MASTERY.json
Normal file
177
contractdata/HOKKAIDO/_LEGACY_HOKKAIDO_MASTERY.json
Normal 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
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
94
contractdata/MARRAKESH/_H2_MARRAKESH_MASTERY.json
Normal file
94
contractdata/MARRAKESH/_H2_MARRAKESH_MASTERY.json
Normal 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
|
||||
}
|
||||
]
|
||||
}
|
169
contractdata/MARRAKESH/_LEGACY_MARRAKESH_MASTERY.json
Normal file
169
contractdata/MARRAKESH/_LEGACY_MARRAKESH_MASTERY.json
Normal 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
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
{
|
||||
"Id": "LOCATION_PARENT_MARRAKECH",
|
||||
"LocationId": "LOCATION_PARENT_MARRAKECH",
|
||||
"GameVersions": ["h3"],
|
||||
"Drops": [
|
||||
{
|
||||
"Id": "AGENCYPICKUP_MARRAKESH_SPIDER_CONSULATE_BASEMENT",
|
||||
|
@ -1,5 +1,6 @@
|
||||
{
|
||||
"Id": "LOCATION_PARENT_ELEGANT",
|
||||
"LocationId": "LOCATION_PARENT_ELEGANT",
|
||||
"GameVersions": ["h3"],
|
||||
"Drops": [
|
||||
{
|
||||
"Id": "STARTING_LOCATION_ELEGANT_LLAMA_PARKINGLOT_SUIT",
|
||||
|
122
contractdata/MIAMI/_H2_MIAMI_MASTERY.json
Normal file
122
contractdata/MIAMI/_H2_MIAMI_MASTERY.json
Normal 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
|
||||
}
|
||||
]
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
{
|
||||
"Id": "LOCATION_PARENT_MIAMI",
|
||||
"LocationId": "LOCATION_PARENT_MIAMI",
|
||||
"GameVersions": ["h3"],
|
||||
"Drops": [
|
||||
{
|
||||
"Id": "STARTING_LOCATION_MIAMI_FLAMINGO_EXPO_ENTRANCE",
|
||||
|
162
contractdata/MUMBAI/_H2_MUMBAI_MASTERY.json
Normal file
162
contractdata/MUMBAI/_H2_MUMBAI_MASTERY.json
Normal 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
|
||||
}
|
||||
]
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
{
|
||||
"Id": "LOCATION_PARENT_MUMBAI",
|
||||
"LocationId": "LOCATION_PARENT_MUMBAI",
|
||||
"GameVersions": ["h3"],
|
||||
"Drops": [
|
||||
{
|
||||
"Id": "STARTING_LOCATION_MUMBAI_MONGOOSE_TRAIN",
|
||||
|
67
contractdata/NEWYORK/_H2_NEWYORK_MASTERY.json
Normal file
67
contractdata/NEWYORK/_H2_NEWYORK_MASTERY.json
Normal 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
|
||||
}
|
||||
]
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
{
|
||||
"Id": "LOCATION_PARENT_GREEDY",
|
||||
"LocationId": "LOCATION_PARENT_GREEDY",
|
||||
"GameVersions": ["h3"],
|
||||
"Drops": [
|
||||
{
|
||||
"Id": "PROP_TOOL_GOLD_BAR_SMALL",
|
||||
|
94
contractdata/PARIS/_H2_PARIS_MASTERY.json
Normal file
94
contractdata/PARIS/_H2_PARIS_MASTERY.json
Normal 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
|
||||
}
|
||||
]
|
||||
}
|
161
contractdata/PARIS/_LEGACY_PARIS_MASTERY.json
Normal file
161
contractdata/PARIS/_LEGACY_PARIS_MASTERY.json
Normal 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
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
{
|
||||
"Id": "LOCATION_PARENT_PARIS",
|
||||
"LocationId": "LOCATION_PARENT_PARIS",
|
||||
"GameVersions": ["h3"],
|
||||
"Drops": [
|
||||
{
|
||||
"Id": "STARTING_LOCATION_PARIS_PEACOCK_BASEMENT_KITCHEN",
|
||||
|
98
contractdata/SANTAFORTUNA/_H2_SANTAFORTUNA_MASTERY.json
Normal file
98
contractdata/SANTAFORTUNA/_H2_SANTAFORTUNA_MASTERY.json
Normal 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
|
||||
}
|
||||
]
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
{
|
||||
"Id": "LOCATION_PARENT_COLOMBIA",
|
||||
"LocationId": "LOCATION_PARENT_COLOMBIA",
|
||||
"GameVersions": ["h3"],
|
||||
"Drops": [
|
||||
{
|
||||
"Id": "AGENCYPICKUP_COLOMBIA_LARGE_VILLAGE_CONSTRUCTIONBUILDING",
|
||||
|
98
contractdata/SAPIENZA/_H2_SAPIENZA_MASTERY.json
Normal file
98
contractdata/SAPIENZA/_H2_SAPIENZA_MASTERY.json
Normal 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
|
||||
}
|
||||
]
|
||||
}
|
169
contractdata/SAPIENZA/_LEGACY_SAPIENZA_MASTERY.json
Normal file
169
contractdata/SAPIENZA/_LEGACY_SAPIENZA_MASTERY.json
Normal 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
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
{
|
||||
"Id": "LOCATION_PARENT_COASTALTOWN",
|
||||
"LocationId": "LOCATION_PARENT_COASTALTOWN",
|
||||
"GameVersions": ["h3"],
|
||||
"Drops": [
|
||||
{
|
||||
"Id": "STARTING_LOCATION_SAPIENZA_APARTMENT",
|
||||
|
94
contractdata/SGAIL/_H2_SGAIL_MASTERY.json
Normal file
94
contractdata/SGAIL/_H2_SGAIL_MASTERY.json
Normal 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
|
||||
}
|
||||
]
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
{
|
||||
"Id": "LOCATION_PARENT_NORTHSEA",
|
||||
"LocationId": "LOCATION_PARENT_NORTHSEA",
|
||||
"GameVersions": ["h3"],
|
||||
"Drops": [
|
||||
{
|
||||
"Id": "PROP_MELEE_MACE",
|
||||
|
249
contractdata/SNIPER/_AUSTRIA_MASTERY.json
Normal file
249
contractdata/SNIPER/_AUSTRIA_MASTERY.json
Normal 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
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
253
contractdata/SNIPER/_CAGED_MASTERY.json
Normal file
253
contractdata/SNIPER/_CAGED_MASTERY.json
Normal 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
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
249
contractdata/SNIPER/_SALTY_MASTERY.json
Normal file
249
contractdata/SNIPER/_SALTY_MASTERY.json
Normal 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
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
{
|
||||
"Id": "LOCATION_PARENT_SNUG",
|
||||
"LocationId": "LOCATION_PARENT_SNUG",
|
||||
"GameVersions": ["h3"],
|
||||
"MaxLevel": 100,
|
||||
"Drops": [
|
||||
{
|
||||
|
107
contractdata/WHITTLETON/_H2_WHITTLETON_MASTERY.json
Normal file
107
contractdata/WHITTLETON/_H2_WHITTLETON_MASTERY.json
Normal 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
|
||||
}
|
||||
]
|
||||
}
|
@ -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
25
docs/USER_PROFILES.md
Normal 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.
|
@ -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",
|
||||
|
@ -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
@ -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
|
||||
}
|
||||
|
@ -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
71
static/ScpcLocationsData.json
Normal file
71
static/ScpcLocationsData.json
Normal 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"
|
||||
}
|
||||
}
|
||||
}
|
@ -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
11654
static/SniperUnlockables.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -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
|
||||
}
|
||||
|
20
yarn.lock
20
yarn.lock
@ -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"
|
||||
|
Loading…
Reference in New Issue
Block a user