2024-06-20 11:47:21 +02:00
import { RequestHandler } from "express" ;
2025-05-01 13:53:10 -07:00
import { getAccountForRequest } from "@/src/services/loginService" ;
2025-01-25 13:12:49 +01:00
import { Inventory , TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel" ;
2024-05-15 21:55:59 +02:00
import { config } from "@/src/services/configService" ;
2024-07-03 12:31:35 +02:00
import allDialogue from "@/static/fixed_responses/allDialogue.json" ;
2023-12-14 17:34:15 +01:00
import { ILoadoutDatabase } from "@/src/types/saveLoadoutTypes" ;
2025-01-20 12:19:32 +01:00
import { IInventoryClient , IShipInventory , equipmentKeys } from "@/src/types/inventoryTypes/inventoryTypes" ;
2025-07-04 16:49:25 -07:00
import { IPolarity , ArtifactPolarity } from "@/src/types/inventoryTypes/commonInventoryTypes" ;
2025-06-22 06:37:17 -07:00
import { ExportCustoms , ExportFlavour , ExportResources , ExportVirtuals } from "warframe-public-export-plus" ;
2025-04-05 06:52:35 -07:00
import { applyCheatsToInfestedFoundry , handleSubsumeCompletion } from "@/src/services/infestedFoundryService" ;
2025-04-22 09:59:30 -07:00
import {
2025-07-01 07:45:41 -07:00
addEmailItem ,
2025-04-22 09:59:30 -07:00
addMiscItems ,
allDailyAffiliationKeys ,
2025-07-06 07:58:40 +02:00
checkCalendarAutoAdvance ,
2025-04-22 09:59:30 -07:00
cleanupInventory ,
2025-04-28 14:00:22 -07:00
createLibraryDailyTask ,
2025-06-30 13:28:27 -07:00
getCalendarProgress
2025-04-22 09:59:30 -07:00
} from "@/src/services/inventoryService" ;
2025-03-15 06:39:54 -07:00
import { logger } from "@/src/utils/logger" ;
2025-06-22 06:37:17 -07:00
import { addString , catBreadHash } from "@/src/helpers/stringHelpers" ;
2025-04-28 14:00:22 -07:00
import { Types } from "mongoose" ;
2025-05-17 23:29:23 -07:00
import { getNemesisManifest } from "@/src/helpers/nemesisHelpers" ;
2025-05-06 07:37:30 -07:00
import { getPersonalRooms } from "@/src/services/personalRoomsService" ;
import { IPersonalRoomsClient } from "@/src/types/personalRoomsTypes" ;
2025-05-08 04:51:03 -07:00
import { Ship } from "@/src/models/shipModel" ;
2025-06-25 08:04:03 -07:00
import { toLegacyOid , toOid , version_compare } from "@/src/helpers/inventoryHelpers" ;
import { Inbox } from "@/src/models/inboxModel" ;
2025-06-28 09:39:41 -07:00
import { unixTimesInMs } from "@/src/constants/timeConstants" ;
import { DailyDeal } from "@/src/models/worldStateModel" ;
2025-07-04 16:49:25 -07:00
import { EquipmentFeatures } from "@/src/types/equipmentTypes" ;
import { generateRewardSeed } from "@/src/services/rngService" ;
2025-07-06 07:58:40 +02:00
import { getWorldState } from "@/src/services/worldStateService" ;
2024-12-29 21:11:36 +01:00
export const inventoryController : RequestHandler = async ( request , response ) = > {
2025-05-01 13:53:10 -07:00
const account = await getAccountForRequest ( request ) ;
2023-06-04 03:06:22 +02:00
2025-05-01 13:53:10 -07:00
const inventory = await Inventory . findOne ( { accountOwnerId : account._id } ) ;
2023-06-04 03:06:22 +02:00
if ( ! inventory ) {
response . status ( 400 ) . json ( { error : "inventory was undefined" } ) ;
return ;
}
2024-12-22 00:44:49 +01:00
// Handle daily reset
2025-03-15 06:39:54 -07:00
if ( ! inventory . NextRefill || Date . now ( ) >= inventory . NextRefill . getTime ( ) ) {
2025-06-28 09:39:41 -07:00
const today = Math . trunc ( Date . now ( ) / 86400000 ) ;
2025-01-17 07:02:19 +01:00
for ( const key of allDailyAffiliationKeys ) {
inventory [ key ] = 16000 + inventory . PlayerLevel * 500 ;
}
2025-01-05 05:17:56 +01:00
inventory . DailyFocus = 250000 + inventory . PlayerLevel * 5000 ;
2025-03-27 12:57:44 -07:00
inventory . GiftsRemaining = Math . max ( 8 , inventory . PlayerLevel ) ;
2025-04-04 15:16:46 -07:00
inventory . TradesRemaining = inventory . PlayerLevel ;
2025-02-25 17:31:52 -08:00
inventory . LibraryAvailableDailyTaskInfo = createLibraryDailyTask ( ) ;
2025-03-15 06:39:54 -07:00
if ( inventory . NextRefill ) {
2025-06-28 09:39:41 -07:00
const lastLoginDay = Math . trunc ( inventory . NextRefill . getTime ( ) / 86400000 ) - 1 ;
const daysPassed = today - lastLoginDay ;
2025-03-15 06:39:54 -07:00
if ( config . noArgonCrystalDecay ) {
inventory . FoundToday = undefined ;
} else {
for ( let i = 0 ; i != daysPassed ; ++ i ) {
const numArgonCrystals =
inventory . MiscItems . find ( x = > x . ItemType == "/Lotus/Types/Items/MiscItems/ArgonCrystal" )
? . ItemCount ? ? 0 ;
if ( numArgonCrystals == 0 ) {
break ;
}
2025-04-03 10:38:11 -07:00
const numStableArgonCrystals = Math . min (
numArgonCrystals ,
2025-03-15 06:39:54 -07:00
inventory . FoundToday ? . find ( x = > x . ItemType == "/Lotus/Types/Items/MiscItems/ArgonCrystal" )
2025-04-03 10:38:11 -07:00
? . ItemCount ? ? 0
) ;
2025-03-15 06:39:54 -07:00
const numDecayingArgonCrystals = numArgonCrystals - numStableArgonCrystals ;
const numDecayingArgonCrystalsToRemove = Math . ceil ( numDecayingArgonCrystals / 2 ) ;
logger . debug ( ` ticking argon crystals for day ${ i + 1 } of ${ daysPassed } ` , {
numArgonCrystals ,
numStableArgonCrystals ,
numDecayingArgonCrystals ,
numDecayingArgonCrystalsToRemove
} ) ;
// Remove half of owned decaying argon crystals
addMiscItems ( inventory , [
{
ItemType : "/Lotus/Types/Items/MiscItems/ArgonCrystal" ,
ItemCount : numDecayingArgonCrystalsToRemove * - 1
}
] ) ;
// All stable argon crystals are now decaying
inventory . FoundToday = undefined ;
}
}
2025-06-28 09:39:41 -07:00
if ( inventory . UsedDailyDeals . length != 0 ) {
if ( daysPassed == 1 ) {
const todayAt0Utc = today * 86400000 ;
const darvoIndex = Math . trunc ( ( todayAt0Utc - 25200000 ) / ( 26 * unixTimesInMs . hour ) ) ;
const darvoStart = darvoIndex * ( 26 * unixTimesInMs . hour ) + 25200000 ;
const darvoOid =
( ( darvoStart / 1000 ) & 0xffffffff ) . toString ( 16 ) . padStart ( 8 , "0" ) + "adc51a72f7324d95" ;
const deal = await DailyDeal . findById ( darvoOid ) ;
if ( deal ) {
inventory . UsedDailyDeals = inventory . UsedDailyDeals . filter ( x = > x == deal . StoreItem ) ; // keep only the deal that came into this new day with us
} else {
inventory . UsedDailyDeals = [ ] ;
}
} else {
inventory . UsedDailyDeals = [ ] ;
}
}
2025-03-15 06:39:54 -07:00
}
2025-07-06 07:58:40 +02:00
// TODO: Setup CalendarProgress as part of 1999 mission completion?
const previousYearIteration = inventory . CalendarProgress ? . Iteration ;
// We need to do the following to ensure the in-game calendar does not break:
getCalendarProgress ( inventory ) ; // Keep the CalendarProgress up-to-date (at least for the current year iteration) (https://onlyg.it/OpenWF/SpaceNinjaServer/issues/2364)
checkCalendarAutoAdvance ( inventory , getWorldState ( ) . KnownCalendarSeasons [ 0 ] ) ; // Skip birthday events for characters if we do not have them unlocked yet (https://onlyg.it/OpenWF/SpaceNinjaServer/issues/2424)
// also handle sending of kiss cinematic at year rollover
if (
inventory . CalendarProgress ! . Iteration != previousYearIteration &&
inventory . DialogueHistory &&
inventory . DialogueHistory . Dialogues
) {
let kalymos = false ;
for ( const { dialogueName , kissEmail } of [
{
dialogueName : "/Lotus/Types/Gameplay/1999Wf/Dialogue/ArthurDialogue_rom.dialogue" ,
kissEmail : "/Lotus/Types/Items/EmailItems/ArthurKissEmailItem"
} ,
{
dialogueName : "/Lotus/Types/Gameplay/1999Wf/Dialogue/EleanorDialogue_rom.dialogue" ,
kissEmail : "/Lotus/Types/Items/EmailItems/EleanorKissEmailItem"
} ,
{
dialogueName : "/Lotus/Types/Gameplay/1999Wf/Dialogue/LettieDialogue_rom.dialogue" ,
kissEmail : "/Lotus/Types/Items/EmailItems/LettieKissEmailItem"
} ,
{
dialogueName : "/Lotus/Types/Gameplay/1999Wf/Dialogue/JabirDialogue_rom.dialogue" ,
kissEmail : "/Lotus/Types/Items/EmailItems/AmirKissEmailItem"
} ,
{
dialogueName : "/Lotus/Types/Gameplay/1999Wf/Dialogue/AoiDialogue_rom.dialogue" ,
kissEmail : "/Lotus/Types/Items/EmailItems/AoiKissEmailItem"
} ,
{
dialogueName : "/Lotus/Types/Gameplay/1999Wf/Dialogue/QuincyDialogue_rom.dialogue" ,
kissEmail : "/Lotus/Types/Items/EmailItems/QuincyKissEmailItem"
}
] ) {
const dialogue = inventory . DialogueHistory . Dialogues . find ( x = > x . DialogueName == dialogueName ) ;
if ( dialogue ) {
if ( dialogue . Rank == 7 ) {
await addEmailItem ( inventory , kissEmail ) ;
kalymos = false ;
break ;
2025-07-01 07:45:41 -07:00
}
2025-07-06 07:58:40 +02:00
if ( dialogue . Rank == 6 ) {
kalymos = true ;
2025-07-01 07:45:41 -07:00
}
}
2025-07-06 07:58:40 +02:00
}
if ( kalymos ) {
await addEmailItem ( inventory , "/Lotus/Types/Items/EmailItems/KalymosKissEmailItem" ) ;
2025-07-01 07:45:41 -07:00
}
2025-06-30 13:28:27 -07:00
}
2025-04-22 09:59:30 -07:00
cleanupInventory ( inventory ) ;
2025-06-28 09:39:41 -07:00
inventory . NextRefill = new Date ( ( today + 1 ) * 86400000 ) ; // tomorrow at 0 UTC
2025-04-28 14:00:22 -07:00
//await inventory.save();
2024-12-22 00:44:49 +01:00
}
2025-01-03 05:22:56 +01:00
if (
inventory . InfestedFoundry &&
inventory . InfestedFoundry . AbilityOverrideUnlockCooldown &&
new Date ( ) >= inventory . InfestedFoundry . AbilityOverrideUnlockCooldown
) {
2025-01-03 22:17:34 +01:00
handleSubsumeCompletion ( inventory ) ;
2025-04-28 14:00:22 -07:00
//await inventory.save();
2025-01-03 05:22:56 +01:00
}
2025-04-28 14:00:22 -07:00
if ( inventory . LastInventorySync ) {
const lastSyncDuviriMood = Math . trunc ( inventory . LastInventorySync . getTimestamp ( ) . getTime ( ) / 7200000 ) ;
const currentDuviriMood = Math . trunc ( Date . now ( ) / 7200000 ) ;
if ( lastSyncDuviriMood != currentDuviriMood ) {
logger . debug ( ` refreshing duviri seed ` ) ;
2025-04-30 13:28:01 -07:00
if ( ! inventory . DuviriInfo ) {
inventory . DuviriInfo = {
Seed : generateRewardSeed ( ) ,
NumCompletions : 0
} ;
} else {
inventory . DuviriInfo . Seed = generateRewardSeed ( ) ;
}
2025-04-28 14:00:22 -07:00
}
}
inventory . LastInventorySync = new Types . ObjectId ( ) ;
await inventory . save ( ) ;
2025-05-01 13:53:10 -07:00
response . json (
await getInventoryResponse ( inventory , "xpBasedLevelCapDisabled" in request . query , account . BuildLabel )
) ;
2025-01-25 13:12:49 +01:00
} ;
export const getInventoryResponse = async (
inventory : TInventoryDatabaseDocument ,
2025-05-01 13:53:10 -07:00
xpBasedLevelCapDisabled : boolean ,
buildLabel : string | undefined
2025-01-25 13:12:49 +01:00
) : Promise < IInventoryClient > = > {
2025-06-25 08:04:03 -07:00
const [ inventoryWithLoadOutPresets , ships , latestMessage ] = await Promise . all ( [
2025-05-08 04:51:03 -07:00
inventory . populate < { LoadOutPresets : ILoadoutDatabase } > ( "LoadOutPresets" ) ,
2025-06-25 08:04:03 -07:00
Ship . find ( { ShipOwnerId : inventory.accountOwnerId } ) ,
Inbox . findOne ( { ownerId : inventory.accountOwnerId } , "_id" ) . sort ( { date : - 1 } )
2025-05-08 04:51:03 -07:00
] ) ;
const inventoryResponse = inventoryWithLoadOutPresets . toJSON < IInventoryClient > ( ) ;
inventoryResponse . Ships = ships . map ( x = > x . toJSON < IShipInventory > ( ) ) ;
2023-06-04 03:06:22 +02:00
2025-06-25 08:04:03 -07:00
// In case mission inventory update added an inbox message, we need to send the Mailbox part so the client knows to refresh it.
if ( latestMessage ) {
inventoryResponse . Mailbox = {
LastInboxId : toOid ( latestMessage . _id )
} ;
}
2024-12-22 00:34:19 +01:00
if ( config . infiniteCredits ) {
2024-05-02 23:37:51 +02:00
inventoryResponse . RegularCredits = 999999999 ;
2024-12-22 00:34:19 +01:00
}
if ( config . infinitePlatinum ) {
2025-03-09 11:15:33 -07:00
inventoryResponse . PremiumCreditsFree = 0 ;
2024-05-02 23:37:51 +02:00
inventoryResponse . PremiumCredits = 999999999 ;
}
2025-01-06 05:36:39 +01:00
if ( config . infiniteEndo ) {
inventoryResponse . FusionPoints = 999999999 ;
}
if ( config . infiniteRegalAya ) {
inventoryResponse . PrimeTokens = 999999999 ;
}
2024-05-02 23:37:51 +02:00
2024-07-03 12:31:35 +02:00
if ( config . skipAllDialogue ) {
inventoryResponse . TauntHistory = [
{
node : "TreasureTutorial" ,
state : "TS_COMPLETED"
}
] ;
for ( const str of allDialogue ) {
addString ( inventoryResponse . NodeIntrosCompleted , str ) ;
}
}
2024-06-18 23:10:26 +02:00
if ( config . unlockAllShipDecorations ) {
inventoryResponse . ShipDecorations = [ ] ;
for ( const [ uniqueName , item ] of Object . entries ( ExportResources ) ) {
if ( item . productCategory == "ShipDecorations" ) {
2025-04-16 06:28:52 -07:00
inventoryResponse . ShipDecorations . push ( { ItemType : uniqueName , ItemCount : 999_999 } ) ;
2024-06-18 23:10:26 +02:00
}
}
}
if ( config . unlockAllFlavourItems ) {
inventoryResponse . FlavourItems = [ ] ;
for ( const uniqueName in ExportFlavour ) {
inventoryResponse . FlavourItems . push ( { ItemType : uniqueName } ) ;
}
}
2024-02-18 13:58:43 +01:00
2024-05-29 22:08:41 +02:00
if ( config . unlockAllSkins ) {
2025-01-20 12:19:32 +01:00
const missingWeaponSkins = new Set ( Object . keys ( ExportCustoms ) ) ;
inventoryResponse . WeaponSkins . forEach ( x = > missingWeaponSkins . delete ( x . ItemType ) ) ;
for ( const uniqueName of missingWeaponSkins ) {
2024-05-29 22:08:41 +02:00
inventoryResponse . WeaponSkins . push ( {
ItemId : {
2025-01-12 02:42:27 +01:00
$oid : "ca70ca70ca70ca70" + catBreadHash ( uniqueName ) . toString ( 16 ) . padStart ( 8 , "0" )
2024-05-29 22:08:41 +02:00
} ,
2024-06-18 23:10:26 +02:00
ItemType : uniqueName
2024-05-29 22:08:41 +02:00
} ) ;
}
}
2024-12-29 21:11:36 +01:00
if ( config . unlockAllCapturaScenes ) {
for ( const uniqueName of Object . keys ( ExportResources ) ) {
if ( resourceInheritsFrom ( uniqueName , "/Lotus/Types/Items/MiscItems/PhotoboothTile" ) ) {
inventoryResponse . MiscItems . push ( {
ItemType : uniqueName ,
ItemCount : 1
} ) ;
}
}
}
2024-06-16 12:24:15 +02:00
if ( typeof config . spoofMasteryRank === "number" && config . spoofMasteryRank >= 0 ) {
2024-05-28 13:28:35 +02:00
inventoryResponse . PlayerLevel = config . spoofMasteryRank ;
2025-01-24 21:09:34 +01:00
if ( ! xpBasedLevelCapDisabled ) {
2024-06-16 12:24:15 +02:00
// This client has not been patched to accept any mastery rank, need to fake the XP.
inventoryResponse . XPInfo = [ ] ;
let numFrames = getExpRequiredForMr ( Math . min ( config . spoofMasteryRank , 5030 ) ) / 6000 ;
while ( numFrames -- > 0 ) {
inventoryResponse . XPInfo . push ( {
ItemType : "/Lotus/Powersuits/Mag/Mag" ,
XP : 1_600_000
} ) ;
}
2024-05-28 13:28:35 +02:00
}
}
2024-06-22 23:22:38 +02:00
if ( config . universalPolarityEverywhere ) {
const Polarity : IPolarity [ ] = [ ] ;
2025-04-14 07:14:50 -07:00
// 12 is needed for necramechs. 15 is needed for plexus/crewshipharness.
for ( let i = 0 ; i != 15 ; ++ i ) {
2024-06-22 23:22:38 +02:00
Polarity . push ( {
Slot : i ,
Value : ArtifactPolarity.Any
} ) ;
}
for ( const key of equipmentKeys ) {
if ( key in inventoryResponse ) {
for ( const equipment of inventoryResponse [ key ] ) {
equipment . Polarity = Polarity ;
}
}
}
}
2025-01-15 05:20:30 +01:00
if ( config . unlockDoubleCapacityPotatoesEverywhere ) {
for ( const key of equipmentKeys ) {
if ( key in inventoryResponse ) {
for ( const equipment of inventoryResponse [ key ] ) {
equipment . Features ? ? = 0 ;
equipment . Features |= EquipmentFeatures . DOUBLE_CAPACITY ;
}
}
}
}
if ( config . unlockExilusEverywhere ) {
for ( const key of equipmentKeys ) {
if ( key in inventoryResponse ) {
for ( const equipment of inventoryResponse [ key ] ) {
equipment . Features ? ? = 0 ;
equipment . Features |= EquipmentFeatures . UTILITY_SLOT ;
}
}
}
}
if ( config . unlockArcanesEverywhere ) {
for ( const key of equipmentKeys ) {
if ( key in inventoryResponse ) {
for ( const equipment of inventoryResponse [ key ] ) {
equipment . Features ? ? = 0 ;
equipment . Features |= EquipmentFeatures . ARCANE_SLOT ;
}
}
}
}
2025-01-17 07:02:19 +01:00
if ( config . noDailyStandingLimits ) {
2025-03-16 04:33:12 -07:00
const spoofedDailyAffiliation = Math . max ( 999 _999 , 16000 + inventoryResponse . PlayerLevel * 500 ) ;
2025-01-17 07:02:19 +01:00
for ( const key of allDailyAffiliationKeys ) {
2025-03-16 04:33:12 -07:00
inventoryResponse [ key ] = spoofedDailyAffiliation ;
2025-01-17 07:02:19 +01:00
}
}
2025-04-16 06:30:22 -07:00
if ( config . noDailyFocusLimit ) {
inventoryResponse . DailyFocus = Math . max ( 999 _999 , 250000 + inventoryResponse . PlayerLevel * 5000 ) ;
}
2025-02-22 11:09:17 -08:00
if ( inventoryResponse . InfestedFoundry ) {
applyCheatsToInfestedFoundry ( inventoryResponse . InfestedFoundry ) ;
}
2025-03-15 03:21:26 -07:00
// Set 2FA enabled so trading post can be used
inventoryResponse . HWIDProtectEnabled = true ;
2025-05-09 00:20:54 -07:00
if ( buildLabel ) {
// Fix nemesis for older versions
2025-05-17 23:29:23 -07:00
if (
inventoryResponse . Nemesis &&
2025-06-06 13:13:36 -07:00
version_compare ( buildLabel , getNemesisManifest ( inventoryResponse . Nemesis . manifest ) . minBuild ) < 0
2025-05-17 23:29:23 -07:00
) {
2025-05-09 00:20:54 -07:00
inventoryResponse . Nemesis = undefined ;
}
if ( version_compare ( buildLabel , "2018.02.22.14.34" ) < 0 ) {
const personalRoomsDb = await getPersonalRooms ( inventory . accountOwnerId . toString ( ) ) ;
const personalRooms = personalRoomsDb . toJSON < IPersonalRoomsClient > ( ) ;
inventoryResponse . Ship = personalRooms . Ship ;
2025-05-01 13:53:10 -07:00
2025-05-09 00:20:54 -07:00
if ( version_compare ( buildLabel , "2016.12.21.19.13" ) <= 0 ) {
// U19.5 and below use $id instead of $oid
for ( const category of equipmentKeys ) {
for ( const item of inventoryResponse [ category ] ) {
toLegacyOid ( item . ItemId ) ;
}
}
for ( const upgrade of inventoryResponse . Upgrades ) {
toLegacyOid ( upgrade . ItemId ) ;
}
if ( inventoryResponse . BrandedSuits ) {
for ( const id of inventoryResponse . BrandedSuits ) {
toLegacyOid ( id ) ;
}
}
}
}
2025-05-06 07:37:30 -07:00
}
2025-06-20 04:41:13 -07:00
if ( config . unlockAllProfitTakerStages ) {
inventoryResponse . CompletedJobChains ? ? = [ ] ;
const EudicoHeists = inventoryResponse . CompletedJobChains . find ( x = > x . LocationTag == "EudicoHeists" ) ;
if ( EudicoHeists ) {
EudicoHeists . Jobs = allEudicoHeistJobs ;
} else {
inventoryResponse . CompletedJobChains . push ( {
LocationTag : "EudicoHeists" ,
Jobs : allEudicoHeistJobs
} ) ;
}
}
2025-06-23 04:54:54 -07:00
if ( config . unlockAllSimarisResearchEntries ) {
inventoryResponse . LibraryPersonalTarget = undefined ;
inventoryResponse . LibraryPersonalProgress = [
"/Lotus/Types/Game/Library/Targets/Research1Target" ,
"/Lotus/Types/Game/Library/Targets/Research2Target" ,
"/Lotus/Types/Game/Library/Targets/Research3Target" ,
"/Lotus/Types/Game/Library/Targets/Research4Target" ,
"/Lotus/Types/Game/Library/Targets/Research5Target" ,
"/Lotus/Types/Game/Library/Targets/Research6Target" ,
"/Lotus/Types/Game/Library/Targets/Research7Target"
] . map ( type = > ( { TargetType : type , Scans : 10 , Completed : true } ) ) ;
}
2025-01-25 13:12:49 +01:00
return inventoryResponse ;
2023-05-19 15:22:48 -03:00
} ;
2025-06-20 04:41:13 -07:00
const allEudicoHeistJobs = [
"/Lotus/Types/Gameplay/Venus/Jobs/Heists/HeistProfitTakerBountyOne" ,
"/Lotus/Types/Gameplay/Venus/Jobs/Heists/HeistProfitTakerBountyTwo" ,
"/Lotus/Types/Gameplay/Venus/Jobs/Heists/HeistProfitTakerBountyThree" ,
"/Lotus/Types/Gameplay/Venus/Jobs/Heists/HeistProfitTakerBountyFour"
] ;
2024-05-28 13:28:35 +02:00
const getExpRequiredForMr = ( rank : number ) : number = > {
if ( rank <= 30 ) {
return 2500 * rank * rank ;
}
return 2 _250_000 + 147 _500 * ( rank - 30 ) ;
} ;
2024-12-29 21:11:36 +01:00
const resourceInheritsFrom = ( resourceName : string , targetName : string ) : boolean = > {
let parentName = resourceGetParent ( resourceName ) ;
for ( ; parentName != undefined ; parentName = resourceGetParent ( parentName ) ) {
if ( parentName == targetName ) {
return true ;
}
}
return false ;
} ;
const resourceGetParent = ( resourceName : string ) : string | undefined = > {
if ( resourceName in ExportResources ) {
return ExportResources [ resourceName ] . parentName ;
}
2025-03-15 03:24:39 -07:00
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
2024-12-29 21:11:36 +01:00
return ExportVirtuals [ resourceName ] ? . parentName ;
} ;