mirror of
https://github.com/thepeacockproject/Peacock
synced 2025-02-16 16:34:28 +01:00
Add mastery data for sniper missions (#148)
This commit is contained in:
parent
a4114c0926
commit
6d3ef2f486
1
.gitignore
vendored
1
.gitignore
vendored
@ -39,6 +39,7 @@ resources/contracts.br
|
||||
resources/challenges
|
||||
resources/mastery
|
||||
|
||||
plugins
|
||||
/*.plugin.*
|
||||
/*Plugin.*
|
||||
/resources/dynamic_resources_h1/REPO/006D6B8D26B0F442.REPO
|
||||
|
@ -58,6 +58,7 @@ import {
|
||||
levelForXp,
|
||||
xpRequiredForEvergreenLevel,
|
||||
xpRequiredForLevel,
|
||||
isSniperLocation,
|
||||
} from "../utils"
|
||||
import {
|
||||
ChallengeFilterOptions,
|
||||
@ -411,12 +412,9 @@ export class ChallengeService extends ChallengeRegistry {
|
||||
const location = locations.children[child]
|
||||
assert.ok(location)
|
||||
|
||||
let contracts =
|
||||
child === "LOCATION_AUSTRIA" ||
|
||||
child === "LOCATION_SALTY_SEAGULL" ||
|
||||
child === "LOCATION_CAGED_FALCON"
|
||||
? this.controller.missionsInLocations.sniper[child]
|
||||
: this.controller.missionsInLocations[child]
|
||||
let contracts = isSniperLocation(child)
|
||||
? this.controller.missionsInLocations.sniper[child]
|
||||
: this.controller.missionsInLocations[child]
|
||||
if (!contracts) {
|
||||
contracts = []
|
||||
}
|
||||
|
@ -30,8 +30,9 @@ import { CompletionData, GameVersion, Unlockable } from "../types/types"
|
||||
import {
|
||||
clampValue,
|
||||
DEFAULT_MASTERY_MAXLEVEL,
|
||||
xpRequiredForEvergreenLevel,
|
||||
xpRequiredForLevel,
|
||||
XP_PER_LEVEL,
|
||||
xpRequiredForSniperLevel,
|
||||
} from "../utils"
|
||||
|
||||
export class MasteryService {
|
||||
@ -80,11 +81,67 @@ export class MasteryService {
|
||||
}
|
||||
}
|
||||
|
||||
getCompletionData(
|
||||
/**
|
||||
* 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 maxLevel The max level for this progression.
|
||||
*/
|
||||
private getCompletionData(
|
||||
userId: string,
|
||||
gameVersion: GameVersion,
|
||||
completionId: string,
|
||||
maxLevel: number,
|
||||
levelToXpRequired: (level: number) => number,
|
||||
) {
|
||||
//Get the user profile
|
||||
const userProfile = getUserData(userId, gameVersion)
|
||||
|
||||
// Generate default completion before trying to acquire it
|
||||
userProfile.Extensions.progression.Locations[completionId] ??= {
|
||||
Xp: 0,
|
||||
Level: 1,
|
||||
}
|
||||
|
||||
const completionData =
|
||||
userProfile.Extensions.progression.Locations[completionId]
|
||||
|
||||
const nextLevel: number = clampValue(
|
||||
completionData.Level + 1,
|
||||
1,
|
||||
maxLevel,
|
||||
)
|
||||
|
||||
const nextLevelXp: number = levelToXpRequired(nextLevel)
|
||||
|
||||
const thisLevelXp: number = levelToXpRequired(completionData.Level)
|
||||
|
||||
return {
|
||||
Level: completionData.Level,
|
||||
MaxLevel: maxLevel,
|
||||
XP: completionData.Xp,
|
||||
Completion:
|
||||
(completionData.Xp - thisLevelXp) / (nextLevelXp - thisLevelXp),
|
||||
XpLeft: nextLevelXp - completionData.Xp,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the completion data for a location.
|
||||
* @param locationParentId The parent Id of the location.
|
||||
* @param subLocationId The id of the sublocation.
|
||||
* @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).
|
||||
* @returns The CompletionData object.
|
||||
*/
|
||||
getLocationCompletion(
|
||||
locationParentId: string,
|
||||
subLocationId: string,
|
||||
gameVersion: GameVersion,
|
||||
userId: string,
|
||||
contractType = "mission",
|
||||
): CompletionData {
|
||||
//Get the mastery data
|
||||
const masteryData: MasteryPackage =
|
||||
@ -94,40 +151,16 @@ export class MasteryService {
|
||||
return undefined
|
||||
}
|
||||
|
||||
//Get the user profile
|
||||
const userProfile = getUserData(userId, gameVersion)
|
||||
|
||||
//Gather all required data
|
||||
const lowerCaseLocationParentId = locationParentId.toLowerCase()
|
||||
|
||||
userProfile.Extensions.progression.Locations[
|
||||
lowerCaseLocationParentId
|
||||
] ??= {
|
||||
Xp: 0,
|
||||
Level: 1,
|
||||
}
|
||||
|
||||
const locationData =
|
||||
userProfile.Extensions.progression.Locations[
|
||||
lowerCaseLocationParentId
|
||||
]
|
||||
|
||||
const maxLevel = masteryData.MaxLevel || DEFAULT_MASTERY_MAXLEVEL
|
||||
|
||||
const nextLevel: number = clampValue(
|
||||
locationData.Level + 1,
|
||||
1,
|
||||
maxLevel,
|
||||
)
|
||||
const nextLevelXp: number = xpRequiredForLevel(nextLevel)
|
||||
|
||||
return {
|
||||
Level: locationData.Level,
|
||||
MaxLevel: maxLevel,
|
||||
XP: locationData.Xp,
|
||||
Completion:
|
||||
(XP_PER_LEVEL - (nextLevelXp - locationData.Xp)) / XP_PER_LEVEL,
|
||||
XpLeft: nextLevelXp - locationData.Xp,
|
||||
...this.getCompletionData(
|
||||
userId,
|
||||
gameVersion,
|
||||
locationParentId.toLowerCase(),
|
||||
masteryData.MaxLevel || DEFAULT_MASTERY_MAXLEVEL,
|
||||
contractType === "evergreen"
|
||||
? xpRequiredForEvergreenLevel
|
||||
: xpRequiredForLevel,
|
||||
),
|
||||
Id: masteryData.Id,
|
||||
SubLocationId: subLocationId,
|
||||
HideProgression: masteryData.HideProgression || false,
|
||||
@ -136,6 +169,36 @@ export class MasteryService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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,
|
||||
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)) {
|
||||
return undefined
|
||||
@ -173,7 +236,7 @@ export class MasteryService {
|
||||
)
|
||||
|
||||
//Map all the data into a new structure
|
||||
const completionData = this.getCompletionData(
|
||||
const completionData = this.getLocationCompletion(
|
||||
locationParentId,
|
||||
locationParentId,
|
||||
gameVersion,
|
||||
|
@ -79,7 +79,11 @@ contractRoutingRouter.post(
|
||||
return
|
||||
}
|
||||
|
||||
const sniperloadouts = createSniperLoadouts(contractData)
|
||||
const sniperloadouts = createSniperLoadouts(
|
||||
req.jwt.unique_name,
|
||||
req.gameVersion,
|
||||
contractData,
|
||||
)
|
||||
const loadoutData = {
|
||||
CharacterLoadoutData:
|
||||
sniperloadouts.length !== 0 ? sniperloadouts : null,
|
||||
|
@ -87,13 +87,14 @@ export function getSubLocationByName(
|
||||
* @param subLocationId The ID of the targeted sub-location.
|
||||
* @param userId The ID of the user.
|
||||
* @param gameVersion The game's version.
|
||||
* If true, the SubLocationId property will not be set.
|
||||
* @param contractType The type of the contract, only used to distinguish evergreen from other types (default).
|
||||
* @returns The completion data object.
|
||||
*/
|
||||
export function generateCompletionData(
|
||||
subLocationId: string,
|
||||
userId: string,
|
||||
gameVersion: GameVersion,
|
||||
contractType = "mission",
|
||||
): CompletionData {
|
||||
const subLocation = getSubLocationByName(subLocationId, gameVersion)
|
||||
|
||||
@ -101,19 +102,16 @@ export function generateCompletionData(
|
||||
? subLocation.Properties?.ParentLocation
|
||||
: subLocationId
|
||||
|
||||
const completionData = controller.masteryService.getCompletionData(
|
||||
const completionData = controller.masteryService.getLocationCompletion(
|
||||
locationId,
|
||||
subLocation?.Id,
|
||||
gameVersion,
|
||||
userId,
|
||||
contractType,
|
||||
)
|
||||
|
||||
if (!completionData) {
|
||||
log(
|
||||
LogLevel.DEBUG,
|
||||
`Could not get CompletionData for location ${locationId}`,
|
||||
)
|
||||
|
||||
// Should only reach here for sniper locations.
|
||||
return {
|
||||
Level: 1,
|
||||
MaxLevel: 1,
|
||||
|
@ -19,6 +19,7 @@
|
||||
import { Response, Router } from "express"
|
||||
import {
|
||||
contractCreationTutorialId,
|
||||
DEFAULT_MASTERY_MAXLEVEL,
|
||||
gameDifficulty,
|
||||
getMaxProfileLevel,
|
||||
PEACOCKVERSTRING,
|
||||
@ -96,6 +97,7 @@ import {
|
||||
StashpointQuery,
|
||||
} from "./types/gameSchemas"
|
||||
import assert from "assert"
|
||||
import { SniperLoadoutConfig } from "./menus/sniper"
|
||||
|
||||
export const preMenuDataRouter = Router()
|
||||
const menuDataRouter = Router()
|
||||
@ -1895,7 +1897,6 @@ menuDataRouter.get(
|
||||
menuDataRouter.get(
|
||||
"/MasteryUnlockable",
|
||||
(req: RequestWithJwt<MasteryUnlockableQuery>, res) => {
|
||||
let sniperLoadouts = getConfig("SniperLoadouts", false)
|
||||
let masteryUnlockTemplate = getConfig(
|
||||
"MasteryUnlockablesTemplate",
|
||||
false,
|
||||
@ -1914,6 +1915,11 @@ menuDataRouter.get(
|
||||
}
|
||||
})()
|
||||
|
||||
let sniperLoadout = getConfig<SniperLoadoutConfig>(
|
||||
"SniperLoadouts",
|
||||
false,
|
||||
)[location][req.query.unlockableId]
|
||||
|
||||
if (req.gameVersion === "scpc") {
|
||||
masteryUnlockTemplate = JSON.parse(
|
||||
JSON.stringify(masteryUnlockTemplate).replace(
|
||||
@ -1922,36 +1928,33 @@ menuDataRouter.get(
|
||||
),
|
||||
)
|
||||
|
||||
sniperLoadouts = JSON.parse(
|
||||
JSON.stringify(sniperLoadouts).replace(/hawk\/+/g, ""),
|
||||
sniperLoadout = JSON.parse(
|
||||
JSON.stringify(sniperLoadout).replace(/hawk\/+/g, ""),
|
||||
)
|
||||
}
|
||||
|
||||
const unlockables = sniperLoadout.Unlockable
|
||||
|
||||
res.json({
|
||||
template: masteryUnlockTemplate,
|
||||
data: {
|
||||
CompletionData: generateCompletionData(
|
||||
location,
|
||||
CompletionData: controller.masteryService.getFirearmCompletion(
|
||||
req.query.unlockableId,
|
||||
sniperLoadout.MainUnlockable.Properties.Name,
|
||||
req.jwt.unique_name,
|
||||
req.gameVersion,
|
||||
),
|
||||
Drops: [
|
||||
{
|
||||
IsLevelMarker: false,
|
||||
Unlockable:
|
||||
sniperLoadouts[location][req.query.unlockableId][
|
||||
"Unlockable"
|
||||
],
|
||||
Level: 20,
|
||||
IsLocked: false,
|
||||
TypeLocaKey:
|
||||
"UI_MENU_PAGE_MASTERY_UNLOCKABLE_NAME_weapon",
|
||||
},
|
||||
],
|
||||
Unlockable:
|
||||
sniperLoadouts[location][req.query.unlockableId][
|
||||
"MainUnlockable"
|
||||
],
|
||||
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,
|
||||
},
|
||||
})
|
||||
},
|
||||
|
@ -273,7 +273,11 @@ export async function planningView(
|
||||
userCentric.Contract.Metadata.Type = "mission"
|
||||
}
|
||||
|
||||
const sniperLoadouts = createSniperLoadouts(contractData)
|
||||
const sniperLoadouts = createSniperLoadouts(
|
||||
req.jwt.unique_name,
|
||||
req.gameVersion,
|
||||
contractData,
|
||||
)
|
||||
|
||||
if (req.gameVersion === "scpc") {
|
||||
sniperLoadouts.forEach((loadout) => {
|
||||
|
@ -16,9 +16,11 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { controller } from "../controller"
|
||||
import { nilUuid } from "../utils"
|
||||
import { getConfig } from "../configSwizzleManager"
|
||||
import type {
|
||||
CompletionData,
|
||||
GameVersion,
|
||||
MissionManifest,
|
||||
SniperLoadout,
|
||||
} from "../types/types"
|
||||
@ -30,14 +32,21 @@ export type SniperLoadoutConfig = {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the sniper loadouts data.
|
||||
* Creates the sniper loadouts data for a contract. Returns loadouts for all three
|
||||
* characters because multiplayer and singleplayer share the same request.
|
||||
* (Official only returns one because multiplayer is not supported)
|
||||
*
|
||||
* @author Anthony Fuller
|
||||
* @param userId The id of the user.
|
||||
* @param gameVersion The game version.
|
||||
* @param contractData The contract's data.
|
||||
* @param loadoutData Should the output just contain loadout data in an array?
|
||||
* @returns The sniper loadouts data.
|
||||
* @returns An array containing the sniper loadouts data for all three characters
|
||||
* if the contract is a sniper mission, or an empty array otherwise.
|
||||
*/
|
||||
export function createSniperLoadouts(
|
||||
userId: string,
|
||||
gameVersion: GameVersion,
|
||||
contractData: MissionManifest,
|
||||
loadoutData = false,
|
||||
) {
|
||||
@ -59,9 +68,9 @@ export function createSniperLoadouts(
|
||||
{
|
||||
Item: {
|
||||
InstanceId: character.InstanceID,
|
||||
ProfileId:
|
||||
"00000000-0000-0000-0000-000000000000",
|
||||
Unlockable: character.Unlockable,
|
||||
ProfileId: nilUuid,
|
||||
// TODO: All mastery upgrades are unlocked. Change this when adding sniper progression.
|
||||
Unlockable: character.Unlockable[18],
|
||||
Properties: {},
|
||||
},
|
||||
ItemDetails: {
|
||||
@ -91,11 +100,10 @@ export function createSniperLoadouts(
|
||||
Page: 0,
|
||||
Recommended: {
|
||||
item: {
|
||||
InstanceId:
|
||||
"00000000-0000-0000-0000-000000000000",
|
||||
ProfileId:
|
||||
"00000000-0000-0000-0000-000000000000",
|
||||
Unlockable: character.Unlockable,
|
||||
InstanceId: nilUuid,
|
||||
ProfileId: nilUuid,
|
||||
// TODO: All mastery upgrades are unlocked. Change this when adding sniper progression.
|
||||
Unlockable: character.Unlockable[18],
|
||||
Properties: {},
|
||||
},
|
||||
type: "carriedweapon",
|
||||
@ -109,19 +117,12 @@ export function createSniperLoadouts(
|
||||
],
|
||||
LimitedLoadoutUnlockLevel: 0 as number | undefined,
|
||||
},
|
||||
// TODO(Anthony): Use the function to get these details.
|
||||
CompletionData: {
|
||||
Level: 20,
|
||||
MaxLevel: 20,
|
||||
XP: 0,
|
||||
Completion: 1,
|
||||
XpLeft: 0,
|
||||
Id: index,
|
||||
SubLocationId: "",
|
||||
HideProgression: false,
|
||||
IsLocationProgression: false,
|
||||
Name: index,
|
||||
} as CompletionData,
|
||||
CompletionData: controller.masteryService.getFirearmCompletion(
|
||||
index,
|
||||
character.MainUnlockable.Properties.Name,
|
||||
userId,
|
||||
gameVersion,
|
||||
),
|
||||
}
|
||||
|
||||
if (loadoutData) {
|
||||
|
@ -18,7 +18,6 @@
|
||||
|
||||
import type { Response } from "express"
|
||||
import {
|
||||
clampValue,
|
||||
DEFAULT_MASTERY_MAXLEVEL,
|
||||
contractTypes,
|
||||
difficultyToString,
|
||||
@ -29,7 +28,6 @@ import {
|
||||
levelForXp,
|
||||
PEACOCKVERSTRING,
|
||||
SNIPER_LEVEL_INFO,
|
||||
xpRequiredForEvergreenLevel,
|
||||
xpRequiredForLevel,
|
||||
} from "./utils"
|
||||
import { contractSessions, getCurrentState } from "./eventHandler"
|
||||
@ -602,8 +600,6 @@ export async function missionEnd(
|
||||
return
|
||||
}
|
||||
|
||||
const locationParentIdLowerCase = locationParentId.toLocaleLowerCase()
|
||||
|
||||
//Resolve all opportunities for the location
|
||||
const opportunities = contractData.Metadata.Opportunities
|
||||
const opportunityCount = opportunities ? opportunities.length : 0
|
||||
@ -647,16 +643,6 @@ export async function missionEnd(
|
||||
opportunityCount,
|
||||
)
|
||||
|
||||
//Get the location and playerprofile progression from the userdata
|
||||
if (!userData.Extensions.progression.Locations[locationParentIdLowerCase]) {
|
||||
userData.Extensions.progression.Locations[locationParentIdLowerCase] = {
|
||||
Xp: 0,
|
||||
Level: 1,
|
||||
}
|
||||
}
|
||||
|
||||
const locationProgressionData =
|
||||
userData.Extensions.progression.Locations[locationParentIdLowerCase]
|
||||
const playerProgressionData =
|
||||
userData.Extensions.progression.PlayerProfileXP
|
||||
|
||||
@ -704,10 +690,17 @@ export async function missionEnd(
|
||||
//NOTE: Official doesn't seem to make up it's mind whether or not XPGain is the same for both Mastery and Profile...
|
||||
const totalXpGain = calculateXpResult.xp + masteryXpGain
|
||||
|
||||
const completionData = generateCompletionData(
|
||||
contractData.Metadata.Location,
|
||||
req.jwt.unique_name,
|
||||
req.gameVersion,
|
||||
contractData.Metadata.Type,
|
||||
)
|
||||
|
||||
//Calculate the old location progression based on the current one and process it
|
||||
const oldLocationXp = locationProgressionData.Xp - masteryXpGain
|
||||
const oldLocationXp = completionData.XP - masteryXpGain
|
||||
let oldLocationLevel = levelForXp(oldLocationXp)
|
||||
const newLocationXp = locationProgressionData.Xp
|
||||
const newLocationXp = completionData.XP
|
||||
let newLocationLevel = levelForXp(newLocationXp)
|
||||
|
||||
const masteryData =
|
||||
@ -724,12 +717,6 @@ export async function missionEnd(
|
||||
})
|
||||
}
|
||||
|
||||
const completionData = generateCompletionData(
|
||||
contractData.Metadata.Location,
|
||||
req.jwt.unique_name,
|
||||
req.gameVersion,
|
||||
)
|
||||
|
||||
//Calculate the old playerprofile progression based on the current one and process it
|
||||
const oldPlayerProfileXp = playerProgressionData.Total - totalXpGain
|
||||
const oldPlayerProfileLevel = levelForXp(oldPlayerProfileXp)
|
||||
@ -847,27 +834,9 @@ export async function missionEnd(
|
||||
|
||||
locationLevelInfo = EVERGREEN_LEVEL_INFO
|
||||
|
||||
const currentLevelRequiredXp = xpRequiredForEvergreenLevel(
|
||||
locationProgressionData.Level,
|
||||
)
|
||||
const nextLevelRequiredXp = clampValue(
|
||||
xpRequiredForEvergreenLevel(locationProgressionData.Level + 1),
|
||||
1,
|
||||
100,
|
||||
)
|
||||
|
||||
//Override completion data for proper animations
|
||||
completionData.XP = locationProgressionData.Xp
|
||||
completionData.Level = locationProgressionData.Level
|
||||
completionData.Completion =
|
||||
(currentLevelRequiredXp - locationProgressionData.Xp) /
|
||||
(nextLevelRequiredXp - currentLevelRequiredXp)
|
||||
|
||||
//Override the location levels to trigger potential drops
|
||||
oldLocationLevel = evergreenLevelForXp(
|
||||
locationProgressionData.Xp - totalXpGain,
|
||||
)
|
||||
newLocationLevel = locationProgressionData.Level
|
||||
oldLocationLevel = evergreenLevelForXp(completionData.XP - totalXpGain)
|
||||
newLocationLevel = completionData.Level
|
||||
|
||||
//Override the silent assassin rank
|
||||
if (calculateScoreResult.silentAssassin) {
|
||||
|
@ -851,7 +851,11 @@ export interface MissionManifestMetadata {
|
||||
},
|
||||
]
|
||||
}[]
|
||||
CharacterLoadoutData?: unknown
|
||||
CharacterLoadoutData?: {
|
||||
Id: string
|
||||
Loadout: unknown
|
||||
CompletionData: CompletionData
|
||||
}[]
|
||||
SpawnSelectionType?: "random" | string
|
||||
Gamemodes?: ("versus" | string)[]
|
||||
Enginemodes?: ("singleplayer" | "multiplayer" | string)[]
|
||||
@ -1381,7 +1385,7 @@ export type SafehouseCategory = {
|
||||
export type SniperLoadout = {
|
||||
ID: string
|
||||
InstanceID: string
|
||||
Unlockable: Unlockable
|
||||
Unlockable: Unlockable[]
|
||||
MainUnlockable: Unlockable
|
||||
}
|
||||
|
||||
|
@ -189,6 +189,15 @@ export const SNIPER_LEVEL_INFO: number[] = [
|
||||
38000000, 47000000, 58000000, 70000000,
|
||||
]
|
||||
|
||||
/**
|
||||
* Get the number of xp needed to reach a level in sniper missions.
|
||||
* @param level The level in question.
|
||||
* @returns The xp, as a number.
|
||||
*/
|
||||
export function xpRequiredForSniperLevel(level: number): number {
|
||||
return SNIPER_LEVEL_INFO[level - 1]
|
||||
}
|
||||
|
||||
/**
|
||||
* Clamps the given value between a minimum and maximum value
|
||||
*/
|
||||
@ -196,6 +205,19 @@ export function clampValue(value: number, min: number, max: number) {
|
||||
return Math.max(min, Math.min(value, max))
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether a location is a sniper location. Works for both parent and child locations.
|
||||
* @param location The location ID string.
|
||||
* @returns A boolean denoting the result.
|
||||
*/
|
||||
export function isSniperLocation(location: string): boolean {
|
||||
return (
|
||||
location.includes("AUSTRIA") ||
|
||||
location.includes("SALTY") ||
|
||||
location.includes("CAGED")
|
||||
)
|
||||
}
|
||||
|
||||
export function castUserProfile(profile: UserProfile): UserProfile {
|
||||
const j = fastClone(profile)
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user