Merge branch 'master' of https://dev.sp-tarkov.com/SPT-AKI/Server into 3.8.0

# Conflicts:
#	project/src/context/ApplicationContext.ts
#	project/src/context/ContextVariableType.ts
#	project/src/controllers/QuestController.ts
#	project/src/di/Container.ts
#	project/src/generators/weapongen/implementations/ExternalInventoryMagGen.ts
This commit is contained in:
Dev 2023-11-30 10:20:37 +00:00
commit 2148eaafe5
46 changed files with 36045 additions and 34542 deletions

View File

@ -1526,6 +1526,58 @@
"pmcBot": 0 "pmcBot": 0
} }
}, },
"playerScavBrainType": {
"factory4_day": {
"bossKilla": 1,
"assault": 1,
"pmcBot": 1
},
"factory4_night": {
"bossKilla": 1,
"assault": 1,
"pmcBot": 1
},
"bigmap": {
"bossKilla": 1,
"assault": 1,
"pmcBot": 1
},
"laboratory": {
"bossKilla": 1,
"assault": 1,
"pmcBot": 1
},
"woods": {
"bossKilla": 1,
"assault": 1,
"pmcBot": 1
},
"interchange": {
"bossKilla": 1,
"assault": 1,
"pmcBot": 1
},
"lighthouse": {
"bossKilla": 1,
"assault": 1,
"pmcBot": 1
},
"rezervbase": {
"bossKilla": 1,
"assault": 1,
"pmcBot": 1
},
"shoreline": {
"bossKilla": 1,
"assault": 1,
"pmcBot": 1
},
"tarkovstreets": {
"bossKilla": 1,
"assault": 1,
"pmcBot": 1
}
},
"maxBotCap": { "maxBotCap": {
"factory4_day": 13, "factory4_day": 13,
"factory4_night": 13, "factory4_night": 13,

View File

@ -7,7 +7,8 @@
"sptFriendNickname": "SPT", "sptFriendNickname": "SPT",
"fixes": { "fixes": {
"fixShotgunDispersion": true, "fixShotgunDispersion": true,
"removeModItemsFromProfile": false "removeModItemsFromProfile": false,
"fixProfileBreakingInventoryItemIssues": false
}, },
"features": { "features": {
"autoInstallModDependencies": false "autoInstallModDependencies": false

View File

@ -793,5 +793,192 @@
"minFillStaticMagazinePercent": 50, "minFillStaticMagazinePercent": 50,
"makeWishingTreeAlwaysGiveGift": true, "makeWishingTreeAlwaysGiveGift": true,
"allowDuplicateItemsInStaticContainers": true, "allowDuplicateItemsInStaticContainers": true,
"looseLootBlacklist": {} "looseLootBlacklist": {},
"scavRaidTimeSettings": {
"bigmap": {
"reduceLootByPercent": true,
"minDynamicLootPercent": 50,
"minStaticLootPercent": 40,
"reducedChancePercent": 70,
"reductionPercentWeights": {
"20": 1,
"25": 2,
"30": 4,
"35": 4,
"40": 4,
"45": 4,
"50": 4,
"60": 2,
"70": 2,
"80": 1
},
"adjustWaves": true
},
"factory4_day": {
"reduceLootByPercent": true,
"minDynamicLootPercent": 50,
"minStaticLootPercent": 40,
"reducedChancePercent": 80,
"reductionPercentWeights": {
"5": 2,
"20": 3,
"25": 3,
"30": 5,
"40": 5,
"50": 5,
"60": 2,
"70": 2,
"80": 2,
"85": 1
},
"adjustWaves": true
},
"factory4_night": {
"reduceLootByPercent": true,
"minDynamicLootPercent": 50,
"minStaticLootPercent": 40,
"reducedChancePercent": 70,
"reductionPercentWeights": {
"20": 4,
"30": 3,
"40": 3,
"60": 2,
"70": 2,
"80": 1
},
"adjustWaves": true
},
"interchange": {
"reduceLootByPercent": true,
"minDynamicLootPercent": 50,
"minStaticLootPercent": 40,
"reducedChancePercent": 70,
"reductionPercentWeights": {
"20": 5,
"25": 5,
"30": 5,
"35": 5,
"40": 5,
"50": 5,
"60": 2,
"80": 1
},
"adjustWaves": true
},
"rezervbase": {
"reduceLootByPercent": true,
"minDynamicLootPercent": 50,
"minStaticLootPercent": 40,
"reducedChancePercent": 70,
"reductionPercentWeights": {
"20": 3,
"30": 3,
"40": 4,
"50": 4,
"60": 2,
"70": 1,
"80": 1
},
"adjustWaves": true
},
"laboratory": {
"reduceLootByPercent": true,
"minDynamicLootPercent": 50,
"minStaticLootPercent": 40,
"reducedChancePercent": 70,
"reductionPercentWeights": {
"20": 3,
"30": 5,
"40": 5,
"50": 5,
"60": 2,
"80": 1
},
"adjustWaves": true
},
"lighthouse": {
"reduceLootByPercent": true,
"minDynamicLootPercent": 50,
"minStaticLootPercent": 40,
"reducedChancePercent": 60,
"reductionPercentWeights": {
"20": 2,
"25": 2,
"30": 4,
"40": 4,
"50": 4,
"60": 2,
"80": 1
},
"adjustWaves": true
},
"shoreline": {
"reduceLootByPercent": true,
"minDynamicLootPercent": 50,
"minStaticLootPercent": 40,
"reducedChancePercent": 60,
"reductionPercentWeights": {
"20": 2,
"25": 3,
"30": 5,
"35": 5,
"40": 5,
"50": 5,
"60": 2,
"70": 1,
"80": 1
},
"adjustWaves": true
},
"tarkovstreets": {
"reduceLootByPercent": true,
"minDynamicLootPercent": 50,
"minStaticLootPercent": 40,
"reducedChancePercent": 70,
"reductionPercentWeights": {
"20": 2,
"30": 4,
"40": 4,
"50": 4,
"60": 4,
"70": 1,
"80": 1
},
"adjustWaves": true
},
"woods": {
"reduceLootByPercent": true,
"minDynamicLootPercent": 50,
"minStaticLootPercent": 40,
"reducedChancePercent": 60,
"reductionPercentWeights": {
"20": 3,
"30": 5,
"40": 5,
"50": 5,
"60": 1,
"70": 1,
"80": 1
},
"adjustWaves": true
},
"default": {
"reduceLootByPercent": true,
"minDynamicLootPercent": 50,
"minStaticLootPercent": 50,
"reducedChancePercent": 50,
"reductionPercentWeights": {
"10": 1,
"20": 2,
"30": 5,
"40": 5,
"50": 5,
"60": 2,
"70": 1,
"80": 1
},
"adjustWaves": true
}
}
} }

View File

@ -128,6 +128,13 @@
"startTimestamp": 1701388800000, "startTimestamp": 1701388800000,
"endTimestamp": 1703980800000, "endTimestamp": 1703980800000,
"yearly": true "yearly": true
},
"655e427b64d09b4122018228": {
"name": "The punisher - Harvest",
"season": "None",
"startTimestamp": 1341615600000,
"endTimestamp": "",
"yearly": false
} }
}, },
"repeatableQuests": [{ "repeatableQuests": [{

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -84042,5 +84042,403 @@
"templateId": "64f83bd983cfca080a362c82", "templateId": "64f83bd983cfca080a362c82",
"traderId": "5a7c2eca46aef81a7ca2145d", "traderId": "5a7c2eca46aef81a7ca2145d",
"type": "WeaponAssembly" "type": "WeaponAssembly"
},
"655e427b64d09b4122018228": {
"QuestName": "The Punisher - Harvest",
"_id": "655e427b64d09b4122018228",
"acceptPlayerMessage": "655e427b64d09b4122018228 acceptPlayerMessage",
"canShowNotificationsInGame": true,
"changeQuestMessageText": "655e427b64d09b4122018228 changeQuestMessageText",
"completePlayerMessage": "655e427b64d09b4122018228 completePlayerMessage",
"conditions": {
"AvailableForFinish": [
{
"_parent": "CounterCreator",
"_props": {
"counter": {
"conditions": [
{
"_parent": "Kills",
"_props": {
"compareMethod": ">=",
"id": "655e484b52dc506c051b4409",
"target": "AnyPmc",
"value": "1"
}
}
],
"id": "655e483da3ee7d4c56241e18"
},
"doNotResetIfCounterCompleted": false,
"dynamicLocale": false,
"id": "655e483da3ee7d4c56241e17",
"index": 0,
"oneSessionOnly": false,
"parentId": "",
"type": "Elimination",
"value": "50",
"visibilityConditions": []
},
"dynamicLocale": false
},
{
"_parent": "HandoverItem",
"_props": {
"dogtagLevel": "25",
"dynamicLocale": false,
"id": "655e49e942913d55e050376b",
"index": 1,
"isEncoded": false,
"maxDurability": 100,
"minDurability": 0,
"onlyFoundInRaid": true,
"parentId": "",
"target": [
"59f32bb586f774757e1e8442",
"59f32c3b86f77472a31742f0"
],
"value": "50",
"visibilityConditions": []
},
"dynamicLocale": false
}
],
"AvailableForStart": [],
"Fail": []
},
"description": "655e427b64d09b4122018228 description",
"failMessageText": "655e427b64d09b4122018228 failMessageText",
"image": "/files/quest/icon/59ca2e4186f77445e4732b22.jpg",
"instantComplete": false,
"isKey": false,
"location": "any",
"name": "655e427b64d09b4122018228 name",
"note": "655e427b64d09b4122018228 note",
"questStatus": {},
"restartable": false,
"rewards": {
"Fail": [],
"Started": [
{
"findInRaid": true,
"id": "655f3fd9b4f3e80ef35495e7",
"index": 0,
"items": [
{
"_id": "656629d07cac3c3b160e63e6",
"_tpl": "60a283193cb70855c43a381d",
"upd": {
"StackObjectsCount": 2
}
}
],
"target": "656629d07cac3c3b160e63e6",
"type": "Item",
"value": "2"
},
{
"findInRaid": true,
"id": "655f3fedd5e086614d342776",
"index": 1,
"items": [
{
"_id": "656629d07cac3c3b160e63e7",
"_tpl": "5f60c74e3b85f6263c145586",
"upd": {
"StackObjectsCount": 2
}
}
],
"target": "656629d07cac3c3b160e63e7",
"type": "Item",
"value": "2"
},
{
"findInRaid": true,
"id": "655f3ff8691eb93295472576",
"index": 2,
"items": [
{
"_id": "656629d07cac3c3b160e63e8",
"_tpl": "5f60c85b58eff926626a60f7",
"upd": {
"StackObjectsCount": 2
}
}
],
"target": "656629d07cac3c3b160e63e8",
"type": "Item",
"value": "2"
},
{
"findInRaid": true,
"id": "655f41f5fdc15c010d329535",
"index": 3,
"items": [
{
"_id": "656629d07cac3c3b160e63e9",
"_tpl": "5bb2475ed4351e00853264e3",
"upd": {
"Repairable": {
"Durability": 100,
"MaxDurability": 100
},
"StackObjectsCount": 2
}
},
{
"_id": "656629d07cac3c3b160e63ea",
"_tpl": "59db3a1d86f77429e05b4e92",
"parentId": "656629d07cac3c3b160e63e9",
"slotId": "mod_pistol_grip"
},
{
"_id": "656629d07cac3c3b160e63eb",
"_tpl": "5bb20d53d4351e4502010a69",
"parentId": "656629d07cac3c3b160e63e9",
"slotId": "mod_reciever"
},
{
"_id": "656629d07cac3c3b160e63ec",
"_tpl": "5bb20dadd4351e00367faeff",
"parentId": "656629d07cac3c3b160e63eb",
"slotId": "mod_barrel"
},
{
"_id": "656629d07cac3c3b160e63ed",
"_tpl": "5bb20dcad4351e3bac1212da",
"parentId": "656629d07cac3c3b160e63ec",
"slotId": "mod_gas_block"
},
{
"_id": "656629d07cac3c3b160e63ee",
"_tpl": "5cff9e5ed7ad1a09407397d4",
"parentId": "656629d07cac3c3b160e63ec",
"slotId": "mod_muzzle"
},
{
"_id": "656629d07cac3c3b160e63ef",
"_tpl": "5cff9e84d7ad1a049e54ed55",
"parentId": "656629d07cac3c3b160e63ee",
"slotId": "mod_muzzle"
},
{
"_id": "656629d07cac3c3b160e63f0",
"_tpl": "5c6d11152e2216000f2003e7",
"parentId": "656629d07cac3c3b160e63eb",
"slotId": "mod_handguard"
},
{
"_id": "656629d07cac3c3b160e63f1",
"_tpl": "57cffcd624597763133760c5",
"parentId": "656629d07cac3c3b160e63f0",
"slotId": "mod_foregrip"
},
{
"_id": "656629d07cac3c3b160e63f2",
"_tpl": "5c18b9192e2216398b5a8104",
"parentId": "656629d07cac3c3b160e63eb",
"slotId": "mod_sight_rear"
},
{
"_id": "656629d07cac3c3b160e63f3",
"_tpl": "5bb20e58d4351e00320205d7",
"parentId": "656629d07cac3c3b160e63e9",
"slotId": "mod_stock"
},
{
"_id": "656629d07cac3c3b160e63f4",
"_tpl": "58d2946386f774496974c37e",
"parentId": "656629d07cac3c3b160e63f3",
"slotId": "mod_stock_000"
},
{
"_id": "656629d07cac3c3b160e63f5",
"_tpl": "58d2912286f7744e27117493",
"parentId": "656629d07cac3c3b160e63f4",
"slotId": "mod_stock"
},
{
"_id": "656629d07cac3c3b160e63f6",
"_tpl": "5bb20dbcd4351e44f824c04e",
"parentId": "656629d07cac3c3b160e63e9",
"slotId": "mod_charge"
}
],
"target": "656629d07cac3c3b160e63e9",
"type": "Item",
"value": "2"
},
{
"findInRaid": true,
"id": "655f4214b4f3e80ef35495e8",
"index": 4,
"items": [
{
"_id": "656629d07cac3c3b160e63f7",
"_tpl": "5b44c8ea86f7742d1627baf1",
"upd": {
"StackObjectsCount": 2
}
}
],
"target": "656629d07cac3c3b160e63f7",
"type": "Item",
"value": "2"
},
{
"findInRaid": true,
"id": "655f4226f0cdb65ed6328125",
"index": 5,
"items": [
{
"_id": "656629d07cac3c3b160e63f8",
"_tpl": "6034d2d697633951dc245ea6",
"upd": {
"StackObjectsCount": 2
}
}
],
"target": "656629d07cac3c3b160e63f8",
"type": "Item",
"value": "2"
}
],
"Success": [
{
"findInRaid": true,
"id": "655f3f8bb4f3e80ef35495e6",
"index": 0,
"items": [
{
"_id": "656629d07cac3c3b160e63f9",
"_tpl": "5c0a840b86f7742ffa4f2482",
"upd": {
"StackObjectsCount": 1
}
}
],
"target": "656629d07cac3c3b160e63f9",
"type": "Item",
"unknown": false,
"value": "1"
},
{
"findInRaid": true,
"id": "655f3fb4fdc15c010d329516",
"index": 1,
"items": [
{
"_id": "656629d07cac3c3b160e63fa",
"_tpl": "5696686a4bdc2da3298b456a",
"upd": {
"StackObjectsCount": 15000
}
}
],
"target": "656629d07cac3c3b160e63fa",
"type": "Item",
"unknown": false,
"value": "15000"
},
{
"id": "655f3f6396fb284db70fa429",
"index": 2,
"items": [
{
"_id": "656629d07cac3c3b160e63fb",
"_tpl": "61962b617c6c7b169525f168"
}
],
"loyaltyLevel": 1,
"target": "656629d07cac3c3b160e63fb",
"traderId": "5935c25fb3acc3127c3d8cd9",
"type": "AssortmentUnlock",
"unknown": true
},
{
"id": "655f3f6bb4f3e80ef35495e5",
"index": 3,
"items": [
{
"_id": "656629d07cac3c3b160e63fc",
"_tpl": "601949593ae8f707c4608daa"
}
],
"loyaltyLevel": 1,
"target": "656629d07cac3c3b160e63fc",
"traderId": "5935c25fb3acc3127c3d8cd9",
"type": "AssortmentUnlock",
"unknown": true
},
{
"id": "655f3f6fd5e086614d342775",
"index": 4,
"items": [
{
"_id": "656629d07cac3c3b160e63fd",
"_tpl": "601aa3d2b2bcb34913271e6d"
}
],
"loyaltyLevel": 1,
"target": "656629d07cac3c3b160e63fd",
"traderId": "5935c25fb3acc3127c3d8cd9",
"type": "AssortmentUnlock",
"unknown": true
},
{
"id": "655f3f74691eb93295472575",
"index": 5,
"items": [
{
"_id": "656629d07cac3c3b160e63fe",
"_tpl": "5efb0c1bd79ff02a1f5e68d9"
}
],
"loyaltyLevel": 1,
"target": "656629d07cac3c3b160e63fe",
"traderId": "5935c25fb3acc3127c3d8cd9",
"type": "AssortmentUnlock",
"unknown": true
},
{
"id": "655f3f77fdc15c010d329515",
"index": 6,
"items": [
{
"_id": "656629d07cac3c3b160e63ff",
"_tpl": "5efb0da7a29a85116f6ea05f"
}
],
"loyaltyLevel": 1,
"target": "656629d07cac3c3b160e63ff",
"traderId": "5935c25fb3acc3127c3d8cd9",
"type": "AssortmentUnlock",
"unknown": true
},
{
"id": "655f3f7c96fb284db70fa42a",
"index": 7,
"items": [
{
"_id": "656629d07cac3c3b160e6400",
"_tpl": "5ba26835d4351e0035628ff5"
}
],
"loyaltyLevel": 1,
"target": "656629d07cac3c3b160e6400",
"traderId": "5935c25fb3acc3127c3d8cd9",
"type": "AssortmentUnlock",
"unknown": true
}
]
},
"secretQuest": false,
"side": "Pmc",
"startedMessageText": "655e427b64d09b4122018228 startedMessageText",
"successMessageText": "655e427b64d09b4122018228 successMessageText",
"templateId": "655e427b64d09b4122018228",
"traderId": "5935c25fb3acc3127c3d8cd9",
"type": "Elimination"
} }
} }

View File

@ -9122,7 +9122,7 @@
"slotId": "hideout", "slotId": "hideout",
"upd": { "upd": {
"BuyRestrictionMax": 10, "BuyRestrictionMax": 10,
"StackObjectsCount": 1500 "StackObjectsCount": 20
} }
}, },
{ {

View File

@ -4717,7 +4717,7 @@
} }
}, },
{ {
"_id": "652376e2f6c67195e4061382", "_id": "6492e44bf4287b13040fcbae",
"_tpl": "5e85a9f4add9fe03027d9bf1", "_tpl": "5e85a9f4add9fe03027d9bf1",
"parentId": "hideout", "parentId": "hideout",
"slotId": "hideout", "slotId": "hideout",
@ -4727,7 +4727,17 @@
} }
}, },
{ {
"_id": "64cac5c1d45ace5bc90c74a8", "_id": "6492e44bf4287b13040fccf6",
"_tpl": "5efb0da7a29a85116f6ea05f",
"parentId": "hideout",
"slotId": "hideout",
"upd": {
"StackObjectsCount": 20000,
"BuyRestrictionMax": 150
}
},
{
"_id": "64a8578f0e9876295f0f83ed",
"_tpl": "5b2388675acfc4771e1be0be", "_tpl": "5b2388675acfc4771e1be0be",
"parentId": "hideout", "parentId": "hideout",
"slotId": "hideout", "slotId": "hideout",
@ -4737,7 +4747,7 @@
} }
}, },
{ {
"_id": "64cac5c1d45ace5bc90c74a9", "_id": "64a8578f0e9876295f0f83ee",
"_tpl": "618ba27d9008e4636a67f61d", "_tpl": "618ba27d9008e4636a67f61d",
"parentId": "hideout", "parentId": "hideout",
"slotId": "hideout", "slotId": "hideout",
@ -4747,7 +4757,7 @@
} }
}, },
{ {
"_id": "64cac5c1d45ace5bc90c74aa", "_id": "64a8578f0e9876295f0f83ef",
"_tpl": "5b3b99475acfc432ff4dcbee", "_tpl": "5b3b99475acfc432ff4dcbee",
"parentId": "hideout", "parentId": "hideout",
"slotId": "hideout", "slotId": "hideout",
@ -4755,16 +4765,6 @@
"StackObjectsCount": 20, "StackObjectsCount": 20,
"BuyRestrictionMax": 2 "BuyRestrictionMax": 2
} }
},
{
"_id": "64cac5c1d45ace5bc90c74ab",
"_tpl": "5efb0da7a29a85116f6ea05f",
"parentId": "hideout",
"slotId": "hideout",
"upd": {
"StackObjectsCount": 20000,
"BuyRestrictionMax": 150
}
} }
], ],
"barter_scheme": { "barter_scheme": {
@ -7413,7 +7413,7 @@
} }
] ]
], ],
"652376e2f6c67195e4061382": [ "6492e44bf4287b13040fcbae": [
[ [
{ {
"count": 3381, "count": 3381,
@ -7421,7 +7421,15 @@
} }
] ]
], ],
"64cac5c1d45ace5bc90c74a8": [ "6492e44bf4287b13040fccf6": [
[
{
"count": 700,
"_tpl": "5449016a4bdc2d6f028b456f"
}
]
],
"64a8578f0e9876295f0f83ed": [
[ [
{ {
"count": 45000, "count": 45000,
@ -7429,7 +7437,7 @@
} }
] ]
], ],
"64cac5c1d45ace5bc90c74a9": [ "64a8578f0e9876295f0f83ee": [
[ [
{ {
"count": 115000, "count": 115000,
@ -7437,21 +7445,13 @@
} }
] ]
], ],
"64cac5c1d45ace5bc90c74aa": [ "64a8578f0e9876295f0f83ef": [
[ [
{ {
"count": 80000, "count": 80000,
"_tpl": "5449016a4bdc2d6f028b456f" "_tpl": "5449016a4bdc2d6f028b456f"
} }
] ]
],
"64cac5c1d45ace5bc90c74ab": [
[
{
"count": 700,
"_tpl": "5449016a4bdc2d6f028b456f"
}
]
] ]
}, },
"loyal_level_items": { "loyal_level_items": {
@ -7781,10 +7781,10 @@
"6507ff22644a656aee0f76dd": 3, "6507ff22644a656aee0f76dd": 3,
"6507ff22644a656aee0f76df": 3, "6507ff22644a656aee0f76df": 3,
"6507ff22644a656aee0f76e1": 1, "6507ff22644a656aee0f76e1": 1,
"652376e2f6c67195e4061382": 4, "6492e44bf4287b13040fcbae": 4,
"64cac5c1d45ace5bc90c74a8": 1, "6492e44bf4287b13040fccf6": 1,
"64cac5c1d45ace5bc90c74a9": 1, "64a8578f0e9876295f0f83ed": 1,
"64cac5c1d45ace5bc90c74aa": 1, "64a8578f0e9876295f0f83ee": 1,
"64cac5c1d45ace5bc90c74ab": 1 "64a8578f0e9876295f0f83ef": 1
} }
} }

View File

@ -1,41 +1,47 @@
{ {
"fail": {},
"started": {}, "started": {},
"success": { "success": {
"6492e44bf4287b13040fca51": "60e71ce009d7c801eb0c0ec6",
"6507ff2a644a656aee0f7fbd": "5a27b7d686f77460d847e6a6",
"6507ff2a644a656aee0f7fc6": "5a27bbf886f774333a418eeb",
"6507ff2a644a656aee0f7fce": "5a27bb3d86f77411ea361a21",
"6507ff2a644a656aee0f800a": "5edac020218d181e29451446",
"6507ff2a644a656aee0f800e": "5c0d4e61d09282029f53920e",
"6507ff2a644a656aee0f8029": "5a27b80086f774429a5d7e20",
"6507ff2b644a656aee0f8089": "5a27ba9586f7741b543d8e85",
"6507ff2b644a656aee0f809e": "5a03173786f77451cb427172", "6507ff2b644a656aee0f809e": "5a03173786f77451cb427172",
"6507ff2b644a656aee0f80df": "5a27b9de86f77464e5044585",
"6507ff2b644a656aee0f80e3": "5b477f7686f7744d1b23c4d2",
"6507ff2b644a656aee0f8107": "5a27bc6986f7741c7358402b",
"6507ff2b644a656aee0f8159": "5a27b75b86f7742e97191958",
"6507ff2b644a656aee0f816b": "6179aff8f57fb279792c60a1",
"6507ff2b644a656aee0f8179": "5c0d4e61d09282029f53920e",
"6507ff2b644a656aee0f817d": "5a27bc1586f7741f6d40fa2f",
"6507ff2b644a656aee0f81d3": "5a27b75b86f7742e97191958", "6507ff2b644a656aee0f81d3": "5a27b75b86f7742e97191958",
"6507ff2b644a656aee0f81f2": "5c0d4e61d09282029f53920e", "6507ff2b644a656aee0f8159": "5a27b75b86f7742e97191958",
"6507ff2b644a656aee0f8201": "5c0d0f1886f77457b8210226",
"6507ff2c644a656aee0f8231": "5a27b7a786f774579c3eb376", "6507ff2c644a656aee0f8231": "5a27b7a786f774579c3eb376",
"6507ff2c644a656aee0f8260": "61958c366726521dd96828ec", "6507ff2a644a656aee0f7fbd": "5a27b7d686f77460d847e6a6",
"6507ff2a644a656aee0f8029": "5a27b80086f774429a5d7e20",
"6507ff2b644a656aee0f80df": "5a27b9de86f77464e5044585",
"6507ff2b644a656aee0f8089": "5a27ba9586f7741b543d8e85",
"6507ff2a644a656aee0f7fce": "5a27bb3d86f77411ea361a21",
"6507ff2a644a656aee0f7fc6": "5a27bbf886f774333a418eeb",
"6507ff2b644a656aee0f817d": "5a27bc1586f7741f6d40fa2f",
"6507ff2c644a656aee0f8353": "5a27bc3686f7741c73584026",
"6507ff2b644a656aee0f8107": "5a27bc6986f7741c7358402b",
"6507ff2d644a656aee0f845f": "5a27bc6986f7741c7358402b",
"6507ff2d644a656aee0f843e": "5a27bc8586f7741b543d8ea4",
"6507ff2c644a656aee0f82b2": "5ac244c486f77413e12cf945",
"6507ff2b644a656aee0f80e3": "5b477f7686f7744d1b23c4d2",
"6507ff2d644a656aee0f835c": "5b47825886f77468074618d3",
"6507ff2b644a656aee0f8201": "5c0d0f1886f77457b8210226",
"6507ff2d644a656aee0f848d": "5c0d4c12d09282029f539173",
"6507ff2b644a656aee0f81f2": "5c0d4e61d09282029f53920e",
"6507ff2b644a656aee0f8179": "5c0d4e61d09282029f53920e",
"6507ff2a644a656aee0f800e": "5c0d4e61d09282029f53920e",
"6507ff2a644a656aee0f800a": "5edac020218d181e29451446",
"6507ff2c644a656aee0f8287": "5edac020218d181e29451446", "6507ff2c644a656aee0f8287": "5edac020218d181e29451446",
"6507ff2c644a656aee0f82a2": "5edac63b930f5454f51e128b", "6507ff2c644a656aee0f82a2": "5edac63b930f5454f51e128b",
"6507ff2c644a656aee0f82b2": "5ac244c486f77413e12cf945",
"6507ff2c644a656aee0f830d": "6179b4d1bca27a099552e04e",
"6507ff2c644a656aee0f8335": "639872fe8871e1272b10ccf6",
"6507ff2c644a656aee0f8353": "5a27bc3686f7741c73584026",
"6507ff2d644a656aee0f835c": "5b47825886f77468074618d3",
"6507ff2d644a656aee0f838c": "63a9b229813bba58a50c9ee5",
"6507ff2d644a656aee0f83b1": "639135f286e646067c176a87",
"6507ff2d644a656aee0f843e": "5a27bc8586f7741b543d8ea4",
"6507ff2d644a656aee0f845f": "5a27bc6986f7741c7358402b",
"6507ff2d644a656aee0f848d": "5c0d4c12d09282029f539173",
"6507ff2d644a656aee0f8493": "5edac63b930f5454f51e128b", "6507ff2d644a656aee0f8493": "5edac63b930f5454f51e128b",
"6507ff2e644a656aee0f8562": "63a9b229813bba58a50c9ee5" "6492e44bf4287b13040fca51": "60e71ce009d7c801eb0c0ec6",
} "6507ff2b644a656aee0f816b": "6179aff8f57fb279792c60a1",
"6507ff2c644a656aee0f830d": "6179b4d1bca27a099552e04e",
"6507ff2c644a656aee0f8260": "61958c366726521dd96828ec",
"6507ff2d644a656aee0f83b1": "639135f286e646067c176a87",
"6507ff2c644a656aee0f8335": "639872fe8871e1272b10ccf6",
"6507ff2e644a656aee0f8562": "63a9b229813bba58a50c9ee5",
"6507ff2d644a656aee0f838c": "63a9b229813bba58a50c9ee5",
"656629d07cac3c3b160e63fb": "655e427b64d09b4122018228",
"656629d07cac3c3b160e63fc": "655e427b64d09b4122018228",
"656629d07cac3c3b160e63fd": "655e427b64d09b4122018228",
"656629d07cac3c3b160e63fe": "655e427b64d09b4122018228",
"656629d07cac3c3b160e63ff": "655e427b64d09b4122018228",
"656629d07cac3c3b160e6400": "655e427b64d09b4122018228"
},
"fail": {}
} }

View File

@ -10,6 +10,7 @@ import { IGameEmptyCrcRequestData } from "@spt-aki/models/eft/game/IGameEmptyCrc
import { IGameKeepAliveResponse } from "@spt-aki/models/eft/game/IGameKeepAliveResponse"; import { IGameKeepAliveResponse } from "@spt-aki/models/eft/game/IGameKeepAliveResponse";
import { IGameLogoutResponseData } from "@spt-aki/models/eft/game/IGameLogoutResponseData"; import { IGameLogoutResponseData } from "@spt-aki/models/eft/game/IGameLogoutResponseData";
import { IGameStartResponse } from "@spt-aki/models/eft/game/IGameStartResponse"; import { IGameStartResponse } from "@spt-aki/models/eft/game/IGameStartResponse";
import { IGetRaidTimeRequest } from "@spt-aki/models/eft/game/IGetRaidTimeRequest";
import { IReportNicknameRequestData } from "@spt-aki/models/eft/game/IReportNicknameRequestData"; import { IReportNicknameRequestData } from "@spt-aki/models/eft/game/IReportNicknameRequestData";
import { IServerDetails } from "@spt-aki/models/eft/game/IServerDetails"; import { IServerDetails } from "@spt-aki/models/eft/game/IServerDetails";
import { IVersionValidateRequestData } from "@spt-aki/models/eft/game/IVersionValidateRequestData"; import { IVersionValidateRequestData } from "@spt-aki/models/eft/game/IVersionValidateRequestData";
@ -147,4 +148,14 @@ export class GameCallbacks implements OnLoad
{ {
return this.httpResponse.nullResponse(); return this.httpResponse.nullResponse();
} }
/**
* Handle singleplayer/settings/getRaidTime
* @returns string
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
public getRaidTime(url: string, request: IGetRaidTimeRequest, sessionID: string): any
{
return this.httpResponse.noBody(this.gameController.getRaidTime(sessionID, request));
}
} }

View File

@ -11,16 +11,13 @@ export class ApplicationContext
private static holderMaxSize = 10; private static holderMaxSize = 10;
/** /**
* @examples * Called like:
* *
* const registerPlayerInfo = this.applicationContext.getLatestValue(ContextVariableType.REGISTER_PLAYER_REQUEST) * const registerPlayerInfo = this.applicationContext.getLatestValue(ContextVariableType.REGISTER_PLAYER_REQUEST).getValue<IRegisterPlayerRequestData>();
* .getValue<IRegisterPlayerRequestData>();
* *
* const activePlayerSessionId = this.applicationContext.getLatestValue(ContextVariableType.SESSION_ID) * const activePlayerSessionId = this.applicationContext.getLatestValue(ContextVariableType.SESSION_ID).getValue<string>();
* .getValue<string>();
* *
* const matchInfo = this.applicationContext.getLatestValue(ContextVariableType.MATCH_INFO) * const matchInfo = this.applicationContext.getLatestValue(ContextVariableType.RAID_CONFIGURATION).getValue<IGetRaidConfigurationRequestData>();
* .getValue<IStartOfflineRaidRequestData>();
* @param type * @param type
* @returns * @returns
*/ */
@ -30,6 +27,7 @@ export class ApplicationContext
{ {
return this.variables.get(type)?.getTail()?.getValue(); return this.variables.get(type)?.getTail()?.getValue();
} }
return undefined; return undefined;
} }
@ -39,6 +37,7 @@ export class ApplicationContext
{ {
return this.variables.get(type).toList(); return this.variables.get(type).toList();
} }
return undefined; return undefined;
} }
@ -62,4 +61,12 @@ export class ApplicationContext
list.add(new ContextVariable(value, type)); list.add(new ContextVariable(value, type));
this.variables.set(type, list); this.variables.set(type, list);
} }
public clearValues(type: ContextVariableType): void
{
this.variables.has(type)
{
this.variables.delete(type);
}
}
} }

View File

@ -1,7 +1,11 @@
export enum ContextVariableType export enum ContextVariableType {
{ /** Logged in users session id */
SESSION_ID = 0, // Logged in users session id SESSION_ID = 0,
RAID_CONFIGURATION = 1, // Currently active raid information /** Currently acive raid information */
CLIENT_START_TIMESTAMP = 2, // Timestamp when client first connected RAID_CONFIGURATION = 1,
REGISTER_PLAYER_REQUEST = 3, // When player is loading into map and loot is requested /** Timestamp when client first connected */
CLIENT_START_TIMESTAMP = 2,
/** When player is loading into map and loot is requested */
REGISTER_PLAYER_REQUEST = 3,
RAID_ADJUSTMENTS = 4,
} }

View File

@ -290,6 +290,9 @@ export class BotController
public getAiBotBrainTypes(): any public getAiBotBrainTypes(): any
{ {
return { pmc: this.pmcConfig.pmcType, assault: this.botConfig.assaultBrainType }; return {
pmc: this.pmcConfig.pmcType,
assault: this.botConfig.assaultBrainType,
playerScav: this.botConfig.playerScavBrainType};
} }
} }

View File

@ -14,6 +14,8 @@ import { ICheckVersionResponse } from "@spt-aki/models/eft/game/ICheckVersionRes
import { ICurrentGroupResponse } from "@spt-aki/models/eft/game/ICurrentGroupResponse"; import { ICurrentGroupResponse } from "@spt-aki/models/eft/game/ICurrentGroupResponse";
import { IGameConfigResponse } from "@spt-aki/models/eft/game/IGameConfigResponse"; import { IGameConfigResponse } from "@spt-aki/models/eft/game/IGameConfigResponse";
import { IGameKeepAliveResponse } from "@spt-aki/models/eft/game/IGameKeepAliveResponse"; import { IGameKeepAliveResponse } from "@spt-aki/models/eft/game/IGameKeepAliveResponse";
import { IGetRaidTimeRequest } from "@spt-aki/models/eft/game/IGetRaidTimeRequest";
import { IGetRaidTimeResponse } from "@spt-aki/models/eft/game/IGetRaidTimeResponse";
import { IServerDetails } from "@spt-aki/models/eft/game/IServerDetails"; import { IServerDetails } from "@spt-aki/models/eft/game/IServerDetails";
import { IAkiProfile } from "@spt-aki/models/eft/profile/IAkiProfile"; import { IAkiProfile } from "@spt-aki/models/eft/profile/IAkiProfile";
import { AccountTypes } from "@spt-aki/models/enums/AccountTypes"; import { AccountTypes } from "@spt-aki/models/enums/AccountTypes";
@ -36,7 +38,9 @@ import { ItemBaseClassService } from "@spt-aki/services/ItemBaseClassService";
import { LocalisationService } from "@spt-aki/services/LocalisationService"; import { LocalisationService } from "@spt-aki/services/LocalisationService";
import { OpenZoneService } from "@spt-aki/services/OpenZoneService"; import { OpenZoneService } from "@spt-aki/services/OpenZoneService";
import { ProfileFixerService } from "@spt-aki/services/ProfileFixerService"; import { ProfileFixerService } from "@spt-aki/services/ProfileFixerService";
import { RaidTimeAdjustmentService } from "@spt-aki/services/RaidTimeAdjustmentService";
import { SeasonalEventService } from "@spt-aki/services/SeasonalEventService"; import { SeasonalEventService } from "@spt-aki/services/SeasonalEventService";
import { HashUtil } from "@spt-aki/utils/HashUtil";
import { JsonUtil } from "@spt-aki/utils/JsonUtil"; import { JsonUtil } from "@spt-aki/utils/JsonUtil";
import { RandomUtil } from "@spt-aki/utils/RandomUtil"; import { RandomUtil } from "@spt-aki/utils/RandomUtil";
import { TimeUtil } from "@spt-aki/utils/TimeUtil"; import { TimeUtil } from "@spt-aki/utils/TimeUtil";
@ -56,6 +60,7 @@ export class GameController
@inject("DatabaseServer") protected databaseServer: DatabaseServer, @inject("DatabaseServer") protected databaseServer: DatabaseServer,
@inject("JsonUtil") protected jsonUtil: JsonUtil, @inject("JsonUtil") protected jsonUtil: JsonUtil,
@inject("TimeUtil") protected timeUtil: TimeUtil, @inject("TimeUtil") protected timeUtil: TimeUtil,
@inject("HashUtil") protected hashUtil: HashUtil,
@inject("PreAkiModLoader") protected preAkiModLoader: PreAkiModLoader, @inject("PreAkiModLoader") protected preAkiModLoader: PreAkiModLoader,
@inject("HttpServerHelper") protected httpServerHelper: HttpServerHelper, @inject("HttpServerHelper") protected httpServerHelper: HttpServerHelper,
@inject("RandomUtil") protected randomUtil: RandomUtil, @inject("RandomUtil") protected randomUtil: RandomUtil,
@ -68,6 +73,7 @@ export class GameController
@inject("SeasonalEventService") protected seasonalEventService: SeasonalEventService, @inject("SeasonalEventService") protected seasonalEventService: SeasonalEventService,
@inject("ItemBaseClassService") protected itemBaseClassService: ItemBaseClassService, @inject("ItemBaseClassService") protected itemBaseClassService: ItemBaseClassService,
@inject("GiftService") protected giftService: GiftService, @inject("GiftService") protected giftService: GiftService,
@inject("RaidTimeAdjustmentService") protected raidTimeAdjustmentService: RaidTimeAdjustmentService,
@inject("ApplicationContext") protected applicationContext: ApplicationContext, @inject("ApplicationContext") protected applicationContext: ApplicationContext,
@inject("ConfigServer") protected configServer: ConfigServer, @inject("ConfigServer") protected configServer: ConfigServer,
) )
@ -127,10 +133,21 @@ export class GameController
if (sessionID) if (sessionID)
{ {
const fullProfile = this.profileHelper.getFullProfile(sessionID); const fullProfile = this.profileHelper.getFullProfile(sessionID);
if (fullProfile.info.wipe)
{
// Don't bother doing any fixes, we're resetting profile
return;
}
const pmcProfile = fullProfile.characters.pmc; const pmcProfile = fullProfile.characters.pmc;
this.logger.debug(`Started game with sessionId: ${sessionID} ${pmcProfile.Info?.Nickname}`); this.logger.debug(`Started game with sessionId: ${sessionID} ${pmcProfile.Info?.Nickname}`);
if (this.coreConfig.fixes.fixProfileBreakingInventoryItemIssues)
{
this.fixProfileBreakingInventoryItemIssues(pmcProfile)
}
if (pmcProfile.Health) if (pmcProfile.Health)
{ {
this.updateProfileHealthValues(pmcProfile); this.updateProfileHealthValues(pmcProfile);
@ -230,6 +247,79 @@ export class GameController
} }
} }
/**
* Attempt to fix common item issues that corrupt profiles
* @param pmcProfile Profile to check items of
*/
protected fixProfileBreakingInventoryItemIssues(pmcProfile: IPmcData): void
{
// Create a mapping of all inventory items, keyed by _id value
const itemMapping = pmcProfile.Inventory.items.reduce((acc, curr) =>
{
acc[curr._id] = acc[curr._id] || [];
acc[curr._id].push(curr);
return acc;
}, {});
for (const key in itemMapping)
{
// Only one item for this id, not a dupe
if (itemMapping[key].length === 1)
{
continue;
}
this.logger.warning(`${itemMapping[key].length - 1} duplicate(s) found for item: ${key}`);
const itemAJson = this.jsonUtil.serialize(itemMapping[key][0]);
const itemBJson = this.jsonUtil.serialize(itemMapping[key][1]);
if (itemAJson === itemBJson)
{
// Both items match, we can safely delete one
const indexOfItemToRemove = pmcProfile.Inventory.items.findIndex(x => x._id === key);
pmcProfile.Inventory.items.splice(indexOfItemToRemove, 1);
this.logger.warning(`Deleted duplicate item: ${key}`);
}
else
{
// Items are different, replace ID with unique value
// Only replace ID if items have no children, we dont want orphaned children
const itemsHaveChildren = pmcProfile.Inventory.items.some(x => x.parentId === key);
if (!itemsHaveChildren)
{
const itemToAdjustId = pmcProfile.Inventory.items.find(x => x._id === key);
itemToAdjustId._id = this.hashUtil.generate();
this.logger.warning(`Replace duplicate item Id: ${key} with ${itemToAdjustId._id}`);
}
}
}
// Iterate over all inventory items
for (const item of pmcProfile.Inventory.items.filter(x => x.slotId))
{
if (!item.upd)
{
// Ignore items without a upd object
continue;
}
// Check items with a tag that contains non alphanumeric characters
const regxp = /([/w"\\'])/g;
if (regxp.test(item.upd.Tag?.Name))
{
this.logger.warning(`Fixed item: ${item._id}s Tag value, removed invalid characters`);
item.upd.Tag.Name = item.upd.Tag.Name.replace(regxp, '');
}
// Check items with StackObjectsCount (null)
if (item.upd.StackObjectsCount === null)
{
this.logger.warning(`Fixed item: ${item._id}s null StackObjectsCount value, now set to 1`);
item.upd.StackObjectsCount = 1;
}
}
}
/** /**
* Out of date/incorrectly made trader mods forget this data * Out of date/incorrectly made trader mods forget this data
*/ */
@ -464,6 +554,14 @@ export class GameController
return { msg: "OK", utc_time: new Date().getTime() / 1000 }; return { msg: "OK", utc_time: new Date().getTime() / 1000 };
} }
/**
* Handle singleplayer/settings/getRaidTime
*/
public getRaidTime(sessionId: string, request: IGetRaidTimeRequest): IGetRaidTimeResponse
{
return this.raidTimeAdjustmentService.getRaidAdjustments(sessionId, request);
}
/** /**
* BSG have two values for shotgun dispersion, we make sure both have the same value * BSG have two values for shotgun dispersion, we make sure both have the same value
*/ */

View File

@ -328,7 +328,7 @@ export class HideoutController
} }
/** /**
* @param output Objet to send to client * @param output Object to send to client
* @param sessionID Session/player id * @param sessionID Session/player id
* @param areaType Hideout area that had stash added * @param areaType Hideout area that had stash added
* @param hideoutDbData Hideout area that caused addition of stash * @param hideoutDbData Hideout area that caused addition of stash
@ -1118,13 +1118,14 @@ export class HideoutController
// Add all improvements to output object // Add all improvements to output object
const improvements = hideoutDbData.stages[profileHideoutArea.level].improvements; const improvements = hideoutDbData.stages[profileHideoutArea.level].improvements;
const timestamp = this.timeUtil.getTimestamp(); const timestamp = this.timeUtil.getTimestamp();
if (!output.profileChanges[sessionId].improvements)
{
output.profileChanges[sessionId].improvements = {};
}
for (const improvement of improvements) for (const improvement of improvements)
{ {
if (!output.profileChanges[sessionId].improvements)
{
output.profileChanges[sessionId].improvements = {};
}
const improvementDetails = { const improvementDetails = {
completed: false, completed: false,
improveCompleteTimestamp: timestamp + improvement.improvementTime, improveCompleteTimestamp: timestamp + improvement.improvementTime,

View File

@ -1,5 +1,7 @@
import { inject, injectable } from "tsyringe"; import { inject, injectable } from "tsyringe";
import { ApplicationContext } from "@spt-aki/context/ApplicationContext";
import { ContextVariableType } from "@spt-aki/context/ContextVariableType";
import { LocationGenerator } from "@spt-aki/generators/LocationGenerator"; import { LocationGenerator } from "@spt-aki/generators/LocationGenerator";
import { LootGenerator } from "@spt-aki/generators/LootGenerator"; import { LootGenerator } from "@spt-aki/generators/LootGenerator";
import { WeightedRandomHelper } from "@spt-aki/helpers/WeightedRandomHelper"; import { WeightedRandomHelper } from "@spt-aki/helpers/WeightedRandomHelper";
@ -13,12 +15,14 @@ import { AirdropTypeEnum } from "@spt-aki/models/enums/AirdropType";
import { ConfigTypes } from "@spt-aki/models/enums/ConfigTypes"; import { ConfigTypes } from "@spt-aki/models/enums/ConfigTypes";
import { IAirdropConfig } from "@spt-aki/models/spt/config/IAirdropConfig"; import { IAirdropConfig } from "@spt-aki/models/spt/config/IAirdropConfig";
import { ILocationConfig } from "@spt-aki/models/spt/config/ILocationConfig"; import { ILocationConfig } from "@spt-aki/models/spt/config/ILocationConfig";
import { IRaidChanges } from "@spt-aki/models/spt/location/IRaidChanges";
import { ILocations } from "@spt-aki/models/spt/server/ILocations"; import { ILocations } from "@spt-aki/models/spt/server/ILocations";
import { LootRequest } from "@spt-aki/models/spt/services/LootRequest"; import { LootRequest } from "@spt-aki/models/spt/services/LootRequest";
import { ILogger } from "@spt-aki/models/spt/utils/ILogger"; import { ILogger } from "@spt-aki/models/spt/utils/ILogger";
import { ConfigServer } from "@spt-aki/servers/ConfigServer"; import { ConfigServer } from "@spt-aki/servers/ConfigServer";
import { DatabaseServer } from "@spt-aki/servers/DatabaseServer"; import { DatabaseServer } from "@spt-aki/servers/DatabaseServer";
import { LocalisationService } from "@spt-aki/services/LocalisationService"; import { LocalisationService } from "@spt-aki/services/LocalisationService";
import { RaidTimeAdjustmentService } from "@spt-aki/services/RaidTimeAdjustmentService";
import { HashUtil } from "@spt-aki/utils/HashUtil"; import { HashUtil } from "@spt-aki/utils/HashUtil";
import { JsonUtil } from "@spt-aki/utils/JsonUtil"; import { JsonUtil } from "@spt-aki/utils/JsonUtil";
import { RandomUtil } from "@spt-aki/utils/RandomUtil"; import { RandomUtil } from "@spt-aki/utils/RandomUtil";
@ -38,10 +42,12 @@ export class LocationController
@inject("WinstonLogger") protected logger: ILogger, @inject("WinstonLogger") protected logger: ILogger,
@inject("LocationGenerator") protected locationGenerator: LocationGenerator, @inject("LocationGenerator") protected locationGenerator: LocationGenerator,
@inject("LocalisationService") protected localisationService: LocalisationService, @inject("LocalisationService") protected localisationService: LocalisationService,
@inject("RaidTimeAdjustmentService") protected raidTimeAdjustmentService: RaidTimeAdjustmentService,
@inject("LootGenerator") protected lootGenerator: LootGenerator, @inject("LootGenerator") protected lootGenerator: LootGenerator,
@inject("DatabaseServer") protected databaseServer: DatabaseServer, @inject("DatabaseServer") protected databaseServer: DatabaseServer,
@inject("TimeUtil") protected timeUtil: TimeUtil, @inject("TimeUtil") protected timeUtil: TimeUtil,
@inject("ConfigServer") protected configServer: ConfigServer, @inject("ConfigServer") protected configServer: ConfigServer,
@inject("ApplicationContext") protected applicationContext: ApplicationContext,
) )
{ {
this.airdropConfig = this.configServer.getConfig(ConfigTypes.AIRDROP); this.airdropConfig = this.configServer.getConfig(ConfigTypes.AIRDROP);
@ -83,10 +89,19 @@ export class LocationController
return output; return output;
} }
// Check for a loot multipler adjustment in app context and apply if one is found
let locationConfigCopy: ILocationConfig;
const raidAdjustments = this.applicationContext.getLatestValue(ContextVariableType.RAID_ADJUSTMENTS)?.getValue<IRaidChanges>();
if (raidAdjustments)
{
locationConfigCopy = this.jsonUtil.clone(this.locationConfig); // Clone values so they can be used to reset originals later
this.raidTimeAdjustmentService.makeAdjustmentsToMap(raidAdjustments, output);
}
const staticAmmoDist = this.jsonUtil.clone(db.loot.staticAmmo); const staticAmmoDist = this.jsonUtil.clone(db.loot.staticAmmo);
// Create containers and add loot to them // Create containers and add loot to them
const staticLoot = this.locationGenerator.generateStaticContainers(location.base, staticAmmoDist); const staticLoot = this.locationGenerator.generateStaticContainers(output, staticAmmoDist);
output.Loot.push(...staticLoot); output.Loot.push(...staticLoot);
// Add dynamic loot to output loot // Add dynamic loot to output loot
@ -107,6 +122,16 @@ export class LocationController
); );
this.logger.success(this.localisationService.getText("location-generated_success", name)); this.logger.success(this.localisationService.getText("location-generated_success", name));
// Reset loot multipliers back to original values
if (raidAdjustments)
{
this.logger.debug("Resetting loot multipliers back to their original values");
this.locationConfig.staticLootMultiplier = locationConfigCopy.staticLootMultiplier;
this.locationConfig.looseLootMultiplier = locationConfigCopy.looseLootMultiplier;
this.applicationContext.clearValues(ContextVariableType.RAID_ADJUSTMENTS);
}
return output; return output;
} }

View File

@ -133,7 +133,7 @@ export class QuestController
break; break;
} }
// Prerequisite does not have its status requirement fulfilled // Prereq does not have its status requirement fulfilled
if (!conditionToFulfil._props.status.includes(prerequisiteQuest.status)) if (!conditionToFulfil._props.status.includes(prerequisiteQuest.status))
{ {
haveCompletedPreviousQuest = false; haveCompletedPreviousQuest = false;
@ -289,7 +289,7 @@ export class QuestController
* @param pmcData Profile to update * @param pmcData Profile to update
* @param acceptedQuest Quest accepted * @param acceptedQuest Quest accepted
* @param sessionID Session id * @param sessionID Session id
* @returns client response * @returns Client response
*/ */
public acceptQuest( public acceptQuest(
pmcData: IPmcData, pmcData: IPmcData,
@ -299,30 +299,36 @@ export class QuestController
{ {
const acceptQuestResponse = this.eventOutputHolder.getOutput(sessionID); const acceptQuestResponse = this.eventOutputHolder.getOutput(sessionID);
const startedState = QuestStatus.Started;
const newQuest = this.questHelper.getQuestReadyForProfile(pmcData, startedState, acceptedQuest);
// Does quest exist in profile // Does quest exist in profile
if (pmcData.Quests.find((x) => x.qid === acceptedQuest.qid)) // Restarting a failed quest can mean quest exists in profile
const existingQuestStatus = pmcData.Quests.find((x) => x.qid === acceptedQuest.qid)
if (existingQuestStatus)
{ {
// Update existing // Update existing
this.questHelper.updateQuestState(pmcData, QuestStatus.Started, acceptedQuest.qid); this.questHelper.resetQuestState(pmcData, QuestStatus.Started, acceptedQuest.qid);
// Need to send client an empty list of completedConditions (Unsure if this does anything)
acceptQuestResponse.profileChanges[sessionID].questsStatus.push(existingQuestStatus);
} }
else else
{ {
// Add new quest to server profile // Add new quest to server profile
const newQuest = this.questHelper.getQuestReadyForProfile(pmcData, QuestStatus.Started, acceptedQuest);
pmcData.Quests.push(newQuest); pmcData.Quests.push(newQuest);
} }
// Create a dialog message for starting the quest. // Create a dialog message for starting the quest.
// Note that for starting quests, the correct locale field is "description", not "startedMessageText". // Note that for starting quests, the correct locale field is "description", not "startedMessageText".
const questFromDb = this.questHelper.getQuestFromDb(acceptedQuest.qid, pmcData); const questFromDb = this.questHelper.getQuestFromDb(acceptedQuest.qid, pmcData);
// Get messageId of text to send to player as text message in game // Get messageId of text to send to player as text message in game
const messageId = this.questHelper.getMessageIdForQuestStart( const messageId = this.questHelper.getMessageIdForQuestStart(
questFromDb.startedMessageText, questFromDb.startedMessageText,
questFromDb.description, questFromDb.description,
); );
const startedQuestRewards = this.questHelper.applyQuestReward(
// Apply non-item rewards to profile + return item rewards
const startedQuestRewardItems = this.questHelper.applyQuestReward(
pmcData, pmcData,
acceptedQuest.qid, acceptedQuest.qid,
QuestStatus.Started, QuestStatus.Started,
@ -330,17 +336,19 @@ export class QuestController
acceptQuestResponse, acceptQuestResponse,
); );
// Send started text + any starting reward items found above to player
this.mailSendService.sendLocalisedNpcMessageToPlayer( this.mailSendService.sendLocalisedNpcMessageToPlayer(
sessionID, sessionID,
this.traderHelper.getTraderById(questFromDb.traderId), this.traderHelper.getTraderById(questFromDb.traderId),
MessageType.QUEST_START, MessageType.QUEST_START,
messageId, messageId,
startedQuestRewards, startedQuestRewardItems,
this.timeUtil.getHoursAsSeconds(this.questConfig.redeemTime), this.timeUtil.getHoursAsSeconds(this.questConfig.redeemTime),
); );
acceptQuestResponse.profileChanges[sessionID].quests = this.questHelper // Having accepted new quest, look for newly unlocked quests and inform client of them
.getNewlyAccessibleQuestsWhenStartingQuest(acceptedQuest.qid, sessionID); acceptQuestResponse.profileChanges[sessionID].quests.push(...this.questHelper
.getNewlyAccessibleQuestsWhenStartingQuest(acceptedQuest.qid, sessionID));
return acceptQuestResponse; return acceptQuestResponse;
} }
@ -362,10 +370,11 @@ export class QuestController
{ {
const acceptQuestResponse = this.eventOutputHolder.getOutput(sessionID); const acceptQuestResponse = this.eventOutputHolder.getOutput(sessionID);
const desiredQuestState = QuestStatus.Started; // Create and store quest status object inside player profile
const newQuest = this.questHelper.getQuestReadyForProfile(pmcData, desiredQuestState, acceptedQuest); const newRepeatableQuest = this.questHelper.getQuestReadyForProfile(pmcData, QuestStatus.Started, acceptedQuest);
pmcData.Quests.push(newQuest); pmcData.Quests.push(newRepeatableQuest);
// Look for the generated quest cache in profile.RepeatableQuests
const repeatableQuestProfile = this.getRepeatableQuestFromProfile(pmcData, acceptedQuest); const repeatableQuestProfile = this.getRepeatableQuestFromProfile(pmcData, acceptedQuest);
if (!repeatableQuestProfile) if (!repeatableQuestProfile)
{ {
@ -391,60 +400,13 @@ export class QuestController
fullProfile.characters.scav.Quests = []; fullProfile.characters.scav.Quests = [];
} }
fullProfile.characters.scav.Quests.push(newQuest); fullProfile.characters.scav.Quests.push(newRepeatableQuest);
} }
const locale = this.localeService.getLocaleDb();
const questStartedMessageKey = this.questHelper.getMessageIdForQuestStart(
repeatableQuestProfile.startedMessageText,
repeatableQuestProfile.description,
);
// Can be started text or description text based on above function result
let questStartedMessageText = locale[questStartedMessageKey];
// TODO: Remove this whole if statement, possibly not required?
if (!questStartedMessageText)
{
this.logger.debug(
`Unable to accept quest ${acceptedQuest.qid}, cannot find the quest started message text with id ${questStartedMessageKey}. attempting to find it in en locale instead`,
);
// For some reason non-en locales don't have repeatable quest ids, fall back to en and grab it if possible
const enLocale = this.databaseServer.getTables().locales.global.en;
questStartedMessageText = enLocale[repeatableQuestProfile.startedMessageText];
if (!questStartedMessageText)
{
this.logger.error(
this.localisationService.getText("repeatable-unable_to_accept_quest_starting_message_not_found", {
questId: acceptedQuest.qid,
messageId: questStartedMessageKey,
}),
);
return this.httpResponseUtil.appendErrorToOutput(
acceptQuestResponse,
this.localisationService.getText("repeatable-unable_to_accept_quest_see_log"),
);
}
}
const questRewards = this.questHelper.getQuestRewardItems(
<IQuest><unknown>repeatableQuestProfile,
desiredQuestState,
);
this.mailSendService.sendLocalisedNpcMessageToPlayer(
sessionID,
this.traderHelper.getTraderById(repeatableQuestProfile.traderId),
MessageType.QUEST_START,
questStartedMessageKey,
questRewards,
this.timeUtil.getHoursAsSeconds(this.questConfig.redeemTime),
);
const repeatableSettings = pmcData.RepeatableQuests.find((x) => const repeatableSettings = pmcData.RepeatableQuests.find((x) =>
x.name === repeatableQuestProfile.sptRepatableGroupName x.name === repeatableQuestProfile.sptRepatableGroupName
); );
const change = {}; const change = {};
change[repeatableQuestProfile._id] = repeatableSettings.changeRequirement[repeatableQuestProfile._id]; change[repeatableQuestProfile._id] = repeatableSettings.changeRequirement[repeatableQuestProfile._id];
const responseData: IPmcDataRepeatableQuest = { const responseData: IPmcDataRepeatableQuest = {
@ -457,7 +419,12 @@ export class QuestController
activeQuests: [repeatableQuestProfile], activeQuests: [repeatableQuestProfile],
inactiveQuests: [], inactiveQuests: [],
}; };
acceptQuestResponse.profileChanges[sessionID].repeatableQuests = [responseData];
if (!acceptQuestResponse.profileChanges[sessionID].repeatableQuests)
{
acceptQuestResponse.profileChanges[sessionID].repeatableQuests = []
}
acceptQuestResponse.profileChanges[sessionID].repeatableQuests.push(responseData);
return acceptQuestResponse; return acceptQuestResponse;
} }
@ -532,11 +499,11 @@ export class QuestController
// Add diff of quests before completion vs after for client response // Add diff of quests before completion vs after for client response
const questDelta = this.questHelper.getDeltaQuests(beforeQuests, this.getClientQuests(sessionID)); const questDelta = this.questHelper.getDeltaQuests(beforeQuests, this.getClientQuests(sessionID));
// Check newly available + failed quests for time gates and add them to profile // Check newly available + failed quests for timegates and add them to profile
this.addTimeLockedQuestsToProfile(pmcData, [...questDelta, ...questsToFail], body.qid); this.addTimeLockedQuestsToProfile(pmcData, [...questDelta, ...questsToFail], body.qid);
// Inform client of quest changes // Inform client of quest changes
completeQuestResponse.profileChanges[sessionID].quests = questDelta; completeQuestResponse.profileChanges[sessionID].quests.push(...questDelta);
// Check if it's a repeatable quest. If so, remove from Quests and repeatable.activeQuests list + move to repeatable.inactiveQuests // Check if it's a repeatable quest. If so, remove from Quests and repeatable.activeQuests list + move to repeatable.inactiveQuests
for (const currentRepeatable of pmcData.RepeatableQuests) for (const currentRepeatable of pmcData.RepeatableQuests)
@ -593,12 +560,12 @@ export class QuestController
/** /**
* Return quests that have different statuses * Return quests that have different statuses
* @param preQuestStatuses Quests before * @param preQuestStatusus Quests before
* @param postQuestStatuses Quests after * @param postQuestStatuses Quests after
* @returns QuestStatusChange array * @returns QuestStatusChange array
*/ */
protected getQuestsWithDifferentStatuses( protected getQuestsWithDifferentStatuses(
preQuestStatuses: IQuestStatus[], preQuestStatusus: IQuestStatus[],
postQuestStatuses: IQuestStatus[], postQuestStatuses: IQuestStatus[],
): IQuestStatus[] ): IQuestStatus[]
{ {
@ -607,7 +574,7 @@ export class QuestController
for (const quest of postQuestStatuses) for (const quest of postQuestStatuses)
{ {
// Add quest if status differs or quest not found // Add quest if status differs or quest not found
const preQuest = preQuestStatuses.find((x) => x.qid === quest.qid); const preQuest = preQuestStatusus.find((x) => x.qid === quest.qid);
if (!preQuest || preQuest.status !== quest.status) if (!preQuest || preQuest.status !== quest.status)
{ {
result.push(quest); result.push(quest);
@ -659,7 +626,7 @@ export class QuestController
// Iterate over quests, look for quests with right criteria // Iterate over quests, look for quests with right criteria
for (const quest of quests) for (const quest of quests)
{ {
// If quest has prerequisite of completed quest + availableAfter value > 0 (quest has wait time) // If quest has prereq of completed quest + availableAfter value > 0 (quest has wait time)
const nextQuestWaitCondition = quest.conditions.AvailableForStart.find((x) => const nextQuestWaitCondition = quest.conditions.AvailableForStart.find((x) =>
x._props.target === completedQuestId && x._props.availableAfter > 0 x._props.target === completedQuestId && x._props.availableAfter > 0
); );
@ -686,7 +653,8 @@ export class QuestController
startTime: 0, startTime: 0,
status: QuestStatus.AvailableAfter, status: QuestStatus.AvailableAfter,
statusTimers: { statusTimers: {
"9": this.timeUtil.getTimestamp(), // eslint-disable-line @typescript-eslint/naming-convention // eslint-disable-next-line @typescript-eslint/naming-convention
"9": this.timeUtil.getTimestamp(),
}, },
availableAfter: availableAfterTimestamp, availableAfter: availableAfterTimestamp,
}); });
@ -832,7 +800,7 @@ export class QuestController
const matchingItemInProfile = pmcData.Inventory.items.find((x) => x._id === itemHandover.id); const matchingItemInProfile = pmcData.Inventory.items.find((x) => x._id === itemHandover.id);
if (!handoverRequirements._props.target.includes(matchingItemInProfile._tpl)) if (!handoverRequirements._props.target.includes(matchingItemInProfile._tpl))
{ {
// Item handed in by player doesn't match what was requested // Item handed in by player doesnt match what was requested
return this.showQuestItemHandoverMatchError( return this.showQuestItemHandoverMatchError(
handoverQuestRequest, handoverQuestRequest,
matchingItemInProfile, matchingItemInProfile,

View File

@ -117,21 +117,24 @@ export class RepeatableQuestController
// for (let i = 0; i < currentRepeatable.activeQuests.length; i++) // for (let i = 0; i < currentRepeatable.activeQuests.length; i++)
for (const activeQuest of currentRepeatableQuestType.activeQuests) for (const activeQuest of currentRepeatableQuestType.activeQuests)
{ {
// check if the quest is ready to be completed, if so, don't remove it // Keep finished quests in list so player can hand in
const quest = pmcData.Quests.filter((q) => q.qid === activeQuest._id); const quest = pmcData.Quests.find(quest => quest.qid === activeQuest._id);
if (quest.length > 0) if (quest)
{ {
if (quest[0].status === QuestStatus.AvailableForFinish) if (quest.status === QuestStatus.AvailableForFinish)
{ {
questsToKeep.push(activeQuest); questsToKeep.push(activeQuest);
this.logger.debug( this.logger.debug(
`Keeping repeatable quest ${activeQuest._id} in activeQuests since it is available to AvailableForFinish`, `Keeping repeatable quest ${activeQuest._id} in activeQuests since it is available to hand in`,
); );
continue; continue;
} }
} }
this.profileFixerService.removeDanglingConditionCounters(pmcData); this.profileFixerService.removeDanglingConditionCounters(pmcData);
pmcData.Quests = pmcData.Quests.filter((q) => q.qid !== activeQuest._id);
// Remove expired quest from pmc.quest array
pmcData.Quests = pmcData.Quests.filter(quest => quest.qid !== activeQuest._id);
currentRepeatableQuestType.inactiveQuests.push(activeQuest); currentRepeatableQuestType.inactiveQuests.push(activeQuest);
} }
currentRepeatableQuestType.activeQuests = questsToKeep; currentRepeatableQuestType.activeQuests = questsToKeep;
@ -455,7 +458,11 @@ export class RepeatableQuestController
droppedQuestTrader.standing -= changeRequirement.changeStandingCost; droppedQuestTrader.standing -= changeRequirement.changeStandingCost;
// Update client output with new repeatable // Update client output with new repeatable
output.profileChanges[sessionID].repeatableQuests = [repeatableToChange]; if (!output.profileChanges[sessionID].repeatableQuests)
{
output.profileChanges[sessionID].repeatableQuests = [];
}
output.profileChanges[sessionID].repeatableQuests.push(repeatableToChange);
return output; return output;
} }

View File

@ -124,6 +124,7 @@ import { PostAkiModLoader } from "@spt-aki/loaders/PostAkiModLoader";
import { PostDBModLoader } from "@spt-aki/loaders/PostDBModLoader"; import { PostDBModLoader } from "@spt-aki/loaders/PostDBModLoader";
import { PreAkiModLoader } from "@spt-aki/loaders/PreAkiModLoader"; import { PreAkiModLoader } from "@spt-aki/loaders/PreAkiModLoader";
import { IAsyncQueue } from "@spt-aki/models/spt/utils/IAsyncQueue"; import { IAsyncQueue } from "@spt-aki/models/spt/utils/IAsyncQueue";
import { IUUidGenerator } from "@spt-aki/models/spt/utils/IUuidGenerator";
import { EventOutputHolder } from "@spt-aki/routers/EventOutputHolder"; import { EventOutputHolder } from "@spt-aki/routers/EventOutputHolder";
import { HttpRouter } from "@spt-aki/routers/HttpRouter"; import { HttpRouter } from "@spt-aki/routers/HttpRouter";
import { ImageRouter } from "@spt-aki/routers/ImageRouter"; import { ImageRouter } from "@spt-aki/routers/ImageRouter";
@ -215,6 +216,7 @@ import { RagfairOfferService } from "@spt-aki/services/RagfairOfferService";
import { RagfairPriceService } from "@spt-aki/services/RagfairPriceService"; import { RagfairPriceService } from "@spt-aki/services/RagfairPriceService";
import { RagfairRequiredItemsService } from "@spt-aki/services/RagfairRequiredItemsService"; import { RagfairRequiredItemsService } from "@spt-aki/services/RagfairRequiredItemsService";
import { RagfairTaxService } from "@spt-aki/services/RagfairTaxService"; import { RagfairTaxService } from "@spt-aki/services/RagfairTaxService";
import { RaidTimeAdjustmentService } from "@spt-aki/services/RaidTimeAdjustmentService";
import { RepairService } from "@spt-aki/services/RepairService"; import { RepairService } from "@spt-aki/services/RepairService";
import { SeasonalEventService } from "@spt-aki/services/SeasonalEventService"; import { SeasonalEventService } from "@spt-aki/services/SeasonalEventService";
import { TraderAssortService } from "@spt-aki/services/TraderAssortService"; import { TraderAssortService } from "@spt-aki/services/TraderAssortService";
@ -239,6 +241,7 @@ import { MathUtil } from "@spt-aki/utils/MathUtil";
import { ObjectId } from "@spt-aki/utils/ObjectId"; import { ObjectId } from "@spt-aki/utils/ObjectId";
import { RandomUtil } from "@spt-aki/utils/RandomUtil"; import { RandomUtil } from "@spt-aki/utils/RandomUtil";
import { TimeUtil } from "@spt-aki/utils/TimeUtil"; import { TimeUtil } from "@spt-aki/utils/TimeUtil";
import { UUidGenerator } from "@spt-aki/utils/UUidGenerator";
import { VFS } from "@spt-aki/utils/VFS"; import { VFS } from "@spt-aki/utils/VFS";
import { Watermark, WatermarkLocale } from "@spt-aki/utils/Watermark"; import { Watermark, WatermarkLocale } from "@spt-aki/utils/Watermark";
import { WinstonMainLogger } from "@spt-aki/utils/logging/WinstonMainLogger"; import { WinstonMainLogger } from "@spt-aki/utils/logging/WinstonMainLogger";
@ -255,442 +258,503 @@ export class Container
childContainer.registerType("HttpListener", "AkiHttpListener"); childContainer.registerType("HttpListener", "AkiHttpListener");
} }
public static registerTypes(con: DependencyContainer): void public static registerTypes(depContainer: DependencyContainer): void
{ {
con.register("ApplicationContext", ApplicationContext, { lifecycle: Lifecycle.Singleton }); depContainer.register("ApplicationContext", ApplicationContext, { lifecycle: Lifecycle.Singleton });
Container.registerUtils(con); Container.registerUtils(depContainer);
Container.registerRouters(con);
Container.registerGenerators(con); Container.registerRouters(depContainer);
Container.registerHelpers(con);
Container.registerLoaders(con); Container.registerGenerators(depContainer);
Container.registerCallbacks(con);
Container.registerServers(con); Container.registerHelpers(depContainer);
Container.registerServices(con);
Container.registerControllers(con); Container.registerLoaders(depContainer);
Container.registerCallbacks(depContainer);
Container.registerServers(depContainer);
Container.registerServices(depContainer);
Container.registerControllers(depContainer);
} }
public static registerListTypes(con: DependencyContainer): void public static registerListTypes(depContainer: DependencyContainer): void
{ {
con.register("OnLoadModService", { useValue: new OnLoadModService(con) }); depContainer.register("OnLoadModService", { useValue: new OnLoadModService(depContainer) });
con.register("HttpListenerModService", { useValue: new HttpListenerModService(con) }); depContainer.register("HttpListenerModService", { useValue: new HttpListenerModService(depContainer) });
con.register("OnUpdateModService", { useValue: new OnUpdateModService(con) }); depContainer.register("OnUpdateModService", { useValue: new OnUpdateModService(depContainer) });
con.register("DynamicRouterModService", { useValue: new DynamicRouterModService(con) }); depContainer.register("DynamicRouterModService", { useValue: new DynamicRouterModService(depContainer) });
con.register("StaticRouterModService", { useValue: new StaticRouterModService(con) }); depContainer.register("StaticRouterModService", { useValue: new StaticRouterModService(depContainer) });
con.registerType("OnLoad", "DatabaseImporter"); depContainer.registerType("OnLoad", "DatabaseImporter");
con.registerType("OnLoad", "PostDBModLoader"); depContainer.registerType("OnLoad", "PostDBModLoader");
con.registerType("OnLoad", "HandbookCallbacks"); depContainer.registerType("OnLoad", "HandbookCallbacks");
con.registerType("OnLoad", "HttpCallbacks"); depContainer.registerType("OnLoad", "HttpCallbacks");
con.registerType("OnLoad", "PresetCallbacks"); depContainer.registerType("OnLoad", "PresetCallbacks");
con.registerType("OnLoad", "SaveCallbacks"); depContainer.registerType("OnLoad", "SaveCallbacks");
con.registerType("OnLoad", "TraderCallbacks"); // Must occur prior to RagfairCallbacks depContainer.registerType("OnLoad", "TraderCallbacks"); // must occur prior to RagfairCallbacks
con.registerType("OnLoad", "RagfairPriceService"); depContainer.registerType("OnLoad", "RagfairPriceService");
con.registerType("OnLoad", "RagfairCallbacks"); depContainer.registerType("OnLoad", "RagfairCallbacks");
con.registerType("OnLoad", "ModCallbacks"); depContainer.registerType("OnLoad", "ModCallbacks");
con.registerType("OnLoad", "GameCallbacks"); depContainer.registerType("OnLoad", "GameCallbacks");
con.registerType("OnUpdate", "DialogueCallbacks"); depContainer.registerType("OnUpdate", "DialogueCallbacks");
con.registerType("OnUpdate", "HideoutCallbacks"); depContainer.registerType("OnUpdate", "HideoutCallbacks");
con.registerType("OnUpdate", "TraderCallbacks"); depContainer.registerType("OnUpdate", "TraderCallbacks");
con.registerType("OnUpdate", "RagfairCallbacks"); depContainer.registerType("OnUpdate", "RagfairCallbacks");
con.registerType("OnUpdate", "InsuranceCallbacks"); depContainer.registerType("OnUpdate", "InsuranceCallbacks");
con.registerType("OnUpdate", "SaveCallbacks"); depContainer.registerType("OnUpdate", "SaveCallbacks");
con.registerType("StaticRoutes", "BotStaticRouter"); depContainer.registerType("StaticRoutes", "BotStaticRouter");
con.registerType("StaticRoutes", "ClientLogStaticRouter"); depContainer.registerType("StaticRoutes", "ClientLogStaticRouter");
con.registerType("StaticRoutes", "CustomizationStaticRouter"); depContainer.registerType("StaticRoutes", "CustomizationStaticRouter");
con.registerType("StaticRoutes", "DataStaticRouter"); depContainer.registerType("StaticRoutes", "DataStaticRouter");
con.registerType("StaticRoutes", "DialogStaticRouter"); depContainer.registerType("StaticRoutes", "DialogStaticRouter");
con.registerType("StaticRoutes", "GameStaticRouter"); depContainer.registerType("StaticRoutes", "GameStaticRouter");
con.registerType("StaticRoutes", "HealthStaticRouter"); depContainer.registerType("StaticRoutes", "HealthStaticRouter");
con.registerType("StaticRoutes", "InraidStaticRouter"); depContainer.registerType("StaticRoutes", "InraidStaticRouter");
con.registerType("StaticRoutes", "InsuranceStaticRouter"); depContainer.registerType("StaticRoutes", "InsuranceStaticRouter");
con.registerType("StaticRoutes", "ItemEventStaticRouter"); depContainer.registerType("StaticRoutes", "ItemEventStaticRouter");
con.registerType("StaticRoutes", "LauncherStaticRouter"); depContainer.registerType("StaticRoutes", "LauncherStaticRouter");
con.registerType("StaticRoutes", "LocationStaticRouter"); depContainer.registerType("StaticRoutes", "LocationStaticRouter");
con.registerType("StaticRoutes", "WeatherStaticRouter"); depContainer.registerType("StaticRoutes", "WeatherStaticRouter");
con.registerType("StaticRoutes", "MatchStaticRouter"); depContainer.registerType("StaticRoutes", "MatchStaticRouter");
con.registerType("StaticRoutes", "QuestStaticRouter"); depContainer.registerType("StaticRoutes", "QuestStaticRouter");
con.registerType("StaticRoutes", "RagfairStaticRouter"); depContainer.registerType("StaticRoutes", "RagfairStaticRouter");
con.registerType("StaticRoutes", "PresetStaticRouter"); depContainer.registerType("StaticRoutes", "PresetStaticRouter");
con.registerType("StaticRoutes", "BundleStaticRouter"); depContainer.registerType("StaticRoutes", "BundleStaticRouter");
con.registerType("StaticRoutes", "NotifierStaticRouter"); depContainer.registerType("StaticRoutes", "NotifierStaticRouter");
con.registerType("StaticRoutes", "ProfileStaticRouter"); depContainer.registerType("StaticRoutes", "ProfileStaticRouter");
con.registerType("StaticRoutes", "TraderStaticRouter"); depContainer.registerType("StaticRoutes", "TraderStaticRouter");
con.registerType("DynamicRoutes", "BotDynamicRouter"); depContainer.registerType("DynamicRoutes", "BotDynamicRouter");
con.registerType("DynamicRoutes", "BundleDynamicRouter"); depContainer.registerType("DynamicRoutes", "BundleDynamicRouter");
con.registerType("DynamicRoutes", "CustomizationDynamicRouter"); depContainer.registerType("DynamicRoutes", "CustomizationDynamicRouter");
con.registerType("DynamicRoutes", "DataDynamicRouter"); depContainer.registerType("DynamicRoutes", "DataDynamicRouter");
con.registerType("DynamicRoutes", "HttpDynamicRouter"); depContainer.registerType("DynamicRoutes", "HttpDynamicRouter");
con.registerType("DynamicRoutes", "InraidDynamicRouter"); depContainer.registerType("DynamicRoutes", "InraidDynamicRouter");
con.registerType("DynamicRoutes", "LocationDynamicRouter"); depContainer.registerType("DynamicRoutes", "LocationDynamicRouter");
con.registerType("DynamicRoutes", "NotifierDynamicRouter"); depContainer.registerType("DynamicRoutes", "NotifierDynamicRouter");
con.registerType("DynamicRoutes", "TraderDynamicRouter"); depContainer.registerType("DynamicRoutes", "TraderDynamicRouter");
con.registerType("IERouters", "CustomizationItemEventRouter"); depContainer.registerType("IERouters", "CustomizationItemEventRouter");
con.registerType("IERouters", "HealthItemEventRouter"); depContainer.registerType("IERouters", "HealthItemEventRouter");
con.registerType("IERouters", "HideoutItemEventRouter"); depContainer.registerType("IERouters", "HideoutItemEventRouter");
con.registerType("IERouters", "InsuranceItemEventRouter"); depContainer.registerType("IERouters", "InsuranceItemEventRouter");
con.registerType("IERouters", "InventoryItemEventRouter"); depContainer.registerType("IERouters", "InventoryItemEventRouter");
con.registerType("IERouters", "NoteItemEventRouter"); depContainer.registerType("IERouters", "NoteItemEventRouter");
con.registerType("IERouters", "PresetBuildItemEventRouter"); depContainer.registerType("IERouters", "PresetBuildItemEventRouter");
con.registerType("IERouters", "QuestItemEventRouter"); depContainer.registerType("IERouters", "QuestItemEventRouter");
con.registerType("IERouters", "RagfairItemEventRouter"); depContainer.registerType("IERouters", "RagfairItemEventRouter");
con.registerType("IERouters", "RepairItemEventRouter"); depContainer.registerType("IERouters", "RepairItemEventRouter");
con.registerType("IERouters", "TradeItemEventRouter"); depContainer.registerType("IERouters", "TradeItemEventRouter");
con.registerType("IERouters", "WishlistItemEventRouter"); depContainer.registerType("IERouters", "WishlistItemEventRouter");
con.registerType("Serializer", "ImageSerializer"); depContainer.registerType("Serializer", "ImageSerializer");
con.registerType("Serializer", "BundleSerializer"); depContainer.registerType("Serializer", "BundleSerializer");
con.registerType("Serializer", "NotifySerializer"); depContainer.registerType("Serializer", "NotifySerializer");
con.registerType("SaveLoadRouter", "HealthSaveLoadRouter"); depContainer.registerType("SaveLoadRouter", "HealthSaveLoadRouter");
con.registerType("SaveLoadRouter", "InraidSaveLoadRouter"); depContainer.registerType("SaveLoadRouter", "InraidSaveLoadRouter");
con.registerType("SaveLoadRouter", "InsuranceSaveLoadRouter"); depContainer.registerType("SaveLoadRouter", "InsuranceSaveLoadRouter");
con.registerType("SaveLoadRouter", "ProfileSaveLoadRouter"); depContainer.registerType("SaveLoadRouter", "ProfileSaveLoadRouter");
} }
private static registerUtils(con: DependencyContainer): void private static registerUtils(depContainer: DependencyContainer): void
{ {
// Utils // Utils
con.register<App>("App", App, { lifecycle: Lifecycle.Singleton }); depContainer.register<App>("App", App, { lifecycle: Lifecycle.Singleton });
con.register<DatabaseImporter>("DatabaseImporter", DatabaseImporter, { lifecycle: Lifecycle.Singleton }); depContainer.register<DatabaseImporter>("DatabaseImporter", DatabaseImporter, {
con.register<HashUtil>("HashUtil", HashUtil, { lifecycle: Lifecycle.Singleton }); lifecycle: Lifecycle.Singleton,
con.register<ImporterUtil>("ImporterUtil", ImporterUtil, { lifecycle: Lifecycle.Singleton }); });
con.register<HttpResponseUtil>("HttpResponseUtil", HttpResponseUtil); depContainer.register<HashUtil>("HashUtil", HashUtil, { lifecycle: Lifecycle.Singleton });
con.register<EncodingUtil>("EncodingUtil", EncodingUtil, { lifecycle: Lifecycle.Singleton }); depContainer.register<ImporterUtil>("ImporterUtil", ImporterUtil, { lifecycle: Lifecycle.Singleton });
con.register<JsonUtil>("JsonUtil", JsonUtil); depContainer.register<HttpResponseUtil>("HttpResponseUtil", HttpResponseUtil);
con.register<WinstonMainLogger>("WinstonLogger", WinstonMainLogger, { lifecycle: Lifecycle.Singleton }); depContainer.register<EncodingUtil>("EncodingUtil", EncodingUtil, { lifecycle: Lifecycle.Singleton });
con.register<WinstonRequestLogger>("RequestsLogger", WinstonRequestLogger, { lifecycle: Lifecycle.Singleton }); depContainer.register<JsonUtil>("JsonUtil", JsonUtil);
con.register<MathUtil>("MathUtil", MathUtil, { lifecycle: Lifecycle.Singleton }); depContainer.register<WinstonMainLogger>("WinstonLogger", WinstonMainLogger, {
con.register<ObjectId>("ObjectId", ObjectId); lifecycle: Lifecycle.Singleton,
con.register<RandomUtil>("RandomUtil", RandomUtil, { lifecycle: Lifecycle.Singleton }); });
con.register<TimeUtil>("TimeUtil", TimeUtil, { lifecycle: Lifecycle.Singleton }); depContainer.register<WinstonRequestLogger>("RequestsLogger", WinstonRequestLogger, {
con.register<VFS>("VFS", VFS, { lifecycle: Lifecycle.Singleton }); lifecycle: Lifecycle.Singleton,
con.register<WatermarkLocale>("WatermarkLocale", WatermarkLocale, { lifecycle: Lifecycle.Singleton }); });
con.register<Watermark>("Watermark", Watermark, { lifecycle: Lifecycle.Singleton }); depContainer.register<MathUtil>("MathUtil", MathUtil, { lifecycle: Lifecycle.Singleton });
con.register<IAsyncQueue>("AsyncQueue", AsyncQueue, { lifecycle: Lifecycle.Singleton }); depContainer.register<ObjectId>("ObjectId", ObjectId);
con.register<HttpFileUtil>("HttpFileUtil", HttpFileUtil, { lifecycle: Lifecycle.Singleton }); depContainer.register<RandomUtil>("RandomUtil", RandomUtil, { lifecycle: Lifecycle.Singleton });
con.register<ModLoadOrder>("ModLoadOrder", ModLoadOrder, { lifecycle: Lifecycle.Singleton }); depContainer.register<TimeUtil>("TimeUtil", TimeUtil, { lifecycle: Lifecycle.Singleton });
con.register<ModTypeCheck>("ModTypeCheck", ModTypeCheck, { lifecycle: Lifecycle.Singleton }); depContainer.register<VFS>("VFS", VFS, { lifecycle: Lifecycle.Singleton });
depContainer.register<WatermarkLocale>("WatermarkLocale", WatermarkLocale, { lifecycle: Lifecycle.Singleton });
depContainer.register<Watermark>("Watermark", Watermark, { lifecycle: Lifecycle.Singleton });
depContainer.register<IAsyncQueue>("AsyncQueue", AsyncQueue, { lifecycle: Lifecycle.Singleton });
depContainer.register<IUUidGenerator>("UUidGenerator", UUidGenerator, { lifecycle: Lifecycle.Singleton });
depContainer.register<HttpFileUtil>("HttpFileUtil", HttpFileUtil, { lifecycle: Lifecycle.Singleton });
depContainer.register<ModLoadOrder>("ModLoadOrder", ModLoadOrder, { lifecycle: Lifecycle.Singleton });
depContainer.register<ModTypeCheck>("ModTypeCheck", ModTypeCheck, { lifecycle: Lifecycle.Singleton });
} }
private static registerRouters(con: DependencyContainer): void private static registerRouters(depContainer: DependencyContainer): void
{ {
// Routers // Routers
con.register<HttpRouter>("HttpRouter", HttpRouter, { lifecycle: Lifecycle.Singleton }); depContainer.register<HttpRouter>("HttpRouter", HttpRouter, { lifecycle: Lifecycle.Singleton });
con.register<ImageRouter>("ImageRouter", ImageRouter); depContainer.register<ImageRouter>("ImageRouter", ImageRouter);
con.register<EventOutputHolder>("EventOutputHolder", EventOutputHolder, { lifecycle: Lifecycle.Singleton }); depContainer.register<EventOutputHolder>("EventOutputHolder", EventOutputHolder, {
con.register<ItemEventRouter>("ItemEventRouter", ItemEventRouter); lifecycle: Lifecycle.Singleton,
});
depContainer.register<ItemEventRouter>("ItemEventRouter", ItemEventRouter);
// Dynamic routes // Dynamic routes
con.register<BotDynamicRouter>("BotDynamicRouter", { useClass: BotDynamicRouter }); depContainer.register<BotDynamicRouter>("BotDynamicRouter", { useClass: BotDynamicRouter });
con.register<BundleDynamicRouter>("BundleDynamicRouter", { useClass: BundleDynamicRouter }); depContainer.register<BundleDynamicRouter>("BundleDynamicRouter", { useClass: BundleDynamicRouter });
con.register<CustomizationDynamicRouter>("CustomizationDynamicRouter", { depContainer.register<CustomizationDynamicRouter>("CustomizationDynamicRouter", {
useClass: CustomizationDynamicRouter, useClass: CustomizationDynamicRouter,
}); });
con.register<DataDynamicRouter>("DataDynamicRouter", { useClass: DataDynamicRouter }); depContainer.register<DataDynamicRouter>("DataDynamicRouter", { useClass: DataDynamicRouter });
con.register<HttpDynamicRouter>("HttpDynamicRouter", { useClass: HttpDynamicRouter }); depContainer.register<HttpDynamicRouter>("HttpDynamicRouter", { useClass: HttpDynamicRouter });
con.register<InraidDynamicRouter>("InraidDynamicRouter", { useClass: InraidDynamicRouter }); depContainer.register<InraidDynamicRouter>("InraidDynamicRouter", { useClass: InraidDynamicRouter });
con.register<LocationDynamicRouter>("LocationDynamicRouter", { useClass: LocationDynamicRouter }); depContainer.register<LocationDynamicRouter>("LocationDynamicRouter", { useClass: LocationDynamicRouter });
con.register<NotifierDynamicRouter>("NotifierDynamicRouter", { useClass: NotifierDynamicRouter }); depContainer.register<NotifierDynamicRouter>("NotifierDynamicRouter", { useClass: NotifierDynamicRouter });
con.register<TraderDynamicRouter>("TraderDynamicRouter", { useClass: TraderDynamicRouter }); depContainer.register<TraderDynamicRouter>("TraderDynamicRouter", { useClass: TraderDynamicRouter });
// Item event routes // Item event routes
con.register<CustomizationItemEventRouter>("CustomizationItemEventRouter", { depContainer.register<CustomizationItemEventRouter>("CustomizationItemEventRouter", {
useClass: CustomizationItemEventRouter, useClass: CustomizationItemEventRouter,
}); });
con.register<HealthItemEventRouter>("HealthItemEventRouter", { useClass: HealthItemEventRouter }); depContainer.register<HealthItemEventRouter>("HealthItemEventRouter", { useClass: HealthItemEventRouter });
con.register<HideoutItemEventRouter>("HideoutItemEventRouter", { useClass: HideoutItemEventRouter }); depContainer.register<HideoutItemEventRouter>("HideoutItemEventRouter", { useClass: HideoutItemEventRouter });
con.register<InsuranceItemEventRouter>("InsuranceItemEventRouter", { useClass: InsuranceItemEventRouter }); depContainer.register<InsuranceItemEventRouter>("InsuranceItemEventRouter", {
con.register<InventoryItemEventRouter>("InventoryItemEventRouter", { useClass: InventoryItemEventRouter }); useClass: InsuranceItemEventRouter,
con.register<NoteItemEventRouter>("NoteItemEventRouter", { useClass: NoteItemEventRouter }); });
con.register<PresetBuildItemEventRouter>("PresetBuildItemEventRouter", { depContainer.register<InventoryItemEventRouter>("InventoryItemEventRouter", {
useClass: InventoryItemEventRouter,
});
depContainer.register<NoteItemEventRouter>("NoteItemEventRouter", { useClass: NoteItemEventRouter });
depContainer.register<PresetBuildItemEventRouter>("PresetBuildItemEventRouter", {
useClass: PresetBuildItemEventRouter, useClass: PresetBuildItemEventRouter,
}); });
con.register<QuestItemEventRouter>("QuestItemEventRouter", { useClass: QuestItemEventRouter }); depContainer.register<QuestItemEventRouter>("QuestItemEventRouter", { useClass: QuestItemEventRouter });
con.register<RagfairItemEventRouter>("RagfairItemEventRouter", { useClass: RagfairItemEventRouter }); depContainer.register<RagfairItemEventRouter>("RagfairItemEventRouter", { useClass: RagfairItemEventRouter });
con.register<RepairItemEventRouter>("RepairItemEventRouter", { useClass: RepairItemEventRouter }); depContainer.register<RepairItemEventRouter>("RepairItemEventRouter", { useClass: RepairItemEventRouter });
con.register<TradeItemEventRouter>("TradeItemEventRouter", { useClass: TradeItemEventRouter }); depContainer.register<TradeItemEventRouter>("TradeItemEventRouter", { useClass: TradeItemEventRouter });
con.register<WishlistItemEventRouter>("WishlistItemEventRouter", { useClass: WishlistItemEventRouter }); depContainer.register<WishlistItemEventRouter>("WishlistItemEventRouter", {
useClass: WishlistItemEventRouter,
});
// save load routes // save load routes
con.register<HealthSaveLoadRouter>("HealthSaveLoadRouter", { useClass: HealthSaveLoadRouter }); depContainer.register<HealthSaveLoadRouter>("HealthSaveLoadRouter", { useClass: HealthSaveLoadRouter });
con.register<InraidSaveLoadRouter>("InraidSaveLoadRouter", { useClass: InraidSaveLoadRouter }); depContainer.register<InraidSaveLoadRouter>("InraidSaveLoadRouter", { useClass: InraidSaveLoadRouter });
con.register<InsuranceSaveLoadRouter>("InsuranceSaveLoadRouter", { useClass: InsuranceSaveLoadRouter }); depContainer.register<InsuranceSaveLoadRouter>("InsuranceSaveLoadRouter", {
con.register<ProfileSaveLoadRouter>("ProfileSaveLoadRouter", { useClass: ProfileSaveLoadRouter }); useClass: InsuranceSaveLoadRouter,
});
depContainer.register<ProfileSaveLoadRouter>("ProfileSaveLoadRouter", { useClass: ProfileSaveLoadRouter });
// Route serializers // Route serializers
con.register<BundleSerializer>("BundleSerializer", { useClass: BundleSerializer }); depContainer.register<BundleSerializer>("BundleSerializer", { useClass: BundleSerializer });
con.register<ImageSerializer>("ImageSerializer", { useClass: ImageSerializer }); depContainer.register<ImageSerializer>("ImageSerializer", { useClass: ImageSerializer });
con.register<NotifySerializer>("NotifySerializer", { useClass: NotifySerializer }); depContainer.register<NotifySerializer>("NotifySerializer", { useClass: NotifySerializer });
// Static routes // Static routes
con.register<BotStaticRouter>("BotStaticRouter", { useClass: BotStaticRouter }); depContainer.register<BotStaticRouter>("BotStaticRouter", { useClass: BotStaticRouter });
con.register<BundleStaticRouter>("BundleStaticRouter", { useClass: BundleStaticRouter }); depContainer.register<BundleStaticRouter>("BundleStaticRouter", { useClass: BundleStaticRouter });
con.register<ClientLogStaticRouter>("ClientLogStaticRouter", { useClass: ClientLogStaticRouter }); depContainer.register<ClientLogStaticRouter>("ClientLogStaticRouter", { useClass: ClientLogStaticRouter });
con.register<CustomizationStaticRouter>("CustomizationStaticRouter", { useClass: CustomizationStaticRouter }); depContainer.register<CustomizationStaticRouter>("CustomizationStaticRouter", {
con.register<DataStaticRouter>("DataStaticRouter", { useClass: DataStaticRouter }); useClass: CustomizationStaticRouter,
con.register<DialogStaticRouter>("DialogStaticRouter", { useClass: DialogStaticRouter }); });
con.register<GameStaticRouter>("GameStaticRouter", { useClass: GameStaticRouter }); depContainer.register<DataStaticRouter>("DataStaticRouter", { useClass: DataStaticRouter });
con.register<HealthStaticRouter>("HealthStaticRouter", { useClass: HealthStaticRouter }); depContainer.register<DialogStaticRouter>("DialogStaticRouter", { useClass: DialogStaticRouter });
con.register<InraidStaticRouter>("InraidStaticRouter", { useClass: InraidStaticRouter }); depContainer.register<GameStaticRouter>("GameStaticRouter", { useClass: GameStaticRouter });
con.register<InsuranceStaticRouter>("InsuranceStaticRouter", { useClass: InsuranceStaticRouter }); depContainer.register<HealthStaticRouter>("HealthStaticRouter", { useClass: HealthStaticRouter });
con.register<ItemEventStaticRouter>("ItemEventStaticRouter", { useClass: ItemEventStaticRouter }); depContainer.register<InraidStaticRouter>("InraidStaticRouter", { useClass: InraidStaticRouter });
con.register<LauncherStaticRouter>("LauncherStaticRouter", { useClass: LauncherStaticRouter }); depContainer.register<InsuranceStaticRouter>("InsuranceStaticRouter", { useClass: InsuranceStaticRouter });
con.register<LocationStaticRouter>("LocationStaticRouter", { useClass: LocationStaticRouter }); depContainer.register<ItemEventStaticRouter>("ItemEventStaticRouter", { useClass: ItemEventStaticRouter });
con.register<MatchStaticRouter>("MatchStaticRouter", { useClass: MatchStaticRouter }); depContainer.register<LauncherStaticRouter>("LauncherStaticRouter", { useClass: LauncherStaticRouter });
con.register<NotifierStaticRouter>("NotifierStaticRouter", { useClass: NotifierStaticRouter }); depContainer.register<LocationStaticRouter>("LocationStaticRouter", { useClass: LocationStaticRouter });
con.register<PresetStaticRouter>("PresetStaticRouter", { useClass: PresetStaticRouter }); depContainer.register<MatchStaticRouter>("MatchStaticRouter", { useClass: MatchStaticRouter });
con.register<ProfileStaticRouter>("ProfileStaticRouter", { useClass: ProfileStaticRouter }); depContainer.register<NotifierStaticRouter>("NotifierStaticRouter", { useClass: NotifierStaticRouter });
con.register<QuestStaticRouter>("QuestStaticRouter", { useClass: QuestStaticRouter }); depContainer.register<PresetStaticRouter>("PresetStaticRouter", { useClass: PresetStaticRouter });
con.register<RagfairStaticRouter>("RagfairStaticRouter", { useClass: RagfairStaticRouter }); depContainer.register<ProfileStaticRouter>("ProfileStaticRouter", { useClass: ProfileStaticRouter });
con.register<TraderStaticRouter>("TraderStaticRouter", { useClass: TraderStaticRouter }); depContainer.register<QuestStaticRouter>("QuestStaticRouter", { useClass: QuestStaticRouter });
con.register<WeatherStaticRouter>("WeatherStaticRouter", { useClass: WeatherStaticRouter }); depContainer.register<RagfairStaticRouter>("RagfairStaticRouter", { useClass: RagfairStaticRouter });
depContainer.register<TraderStaticRouter>("TraderStaticRouter", { useClass: TraderStaticRouter });
depContainer.register<WeatherStaticRouter>("WeatherStaticRouter", { useClass: WeatherStaticRouter });
} }
private static registerGenerators(con: DependencyContainer): void private static registerGenerators(depContainer: DependencyContainer): void
{ {
// Generators // Generators
con.register<BotGenerator>("BotGenerator", BotGenerator); depContainer.register<BotGenerator>("BotGenerator", BotGenerator);
con.register<BotWeaponGenerator>("BotWeaponGenerator", BotWeaponGenerator); depContainer.register<BotWeaponGenerator>("BotWeaponGenerator", BotWeaponGenerator);
con.register<BotLootGenerator>("BotLootGenerator", BotLootGenerator); depContainer.register<BotLootGenerator>("BotLootGenerator", BotLootGenerator);
con.register<BotInventoryGenerator>("BotInventoryGenerator", BotInventoryGenerator); depContainer.register<BotInventoryGenerator>("BotInventoryGenerator", BotInventoryGenerator);
con.register<LocationGenerator>("LocationGenerator", { useClass: LocationGenerator }); depContainer.register<LocationGenerator>("LocationGenerator", { useClass: LocationGenerator });
con.register<PMCLootGenerator>("PMCLootGenerator", PMCLootGenerator, { lifecycle: Lifecycle.Singleton }); depContainer.register<PMCLootGenerator>("PMCLootGenerator", PMCLootGenerator, {
con.register<ScavCaseRewardGenerator>("ScavCaseRewardGenerator", ScavCaseRewardGenerator, {
lifecycle: Lifecycle.Singleton, lifecycle: Lifecycle.Singleton,
}); });
con.register<RagfairAssortGenerator>("RagfairAssortGenerator", { useClass: RagfairAssortGenerator }); depContainer.register<ScavCaseRewardGenerator>("ScavCaseRewardGenerator", ScavCaseRewardGenerator, {
con.register<RagfairOfferGenerator>("RagfairOfferGenerator", { useClass: RagfairOfferGenerator }); lifecycle: Lifecycle.Singleton,
con.register<WeatherGenerator>("WeatherGenerator", { useClass: WeatherGenerator }); });
con.register<PlayerScavGenerator>("PlayerScavGenerator", { useClass: PlayerScavGenerator }); depContainer.register<RagfairAssortGenerator>("RagfairAssortGenerator", { useClass: RagfairAssortGenerator });
con.register<LootGenerator>("LootGenerator", { useClass: LootGenerator }); depContainer.register<RagfairOfferGenerator>("RagfairOfferGenerator", { useClass: RagfairOfferGenerator });
con.register<FenceBaseAssortGenerator>("FenceBaseAssortGenerator", { useClass: FenceBaseAssortGenerator }); depContainer.register<WeatherGenerator>("WeatherGenerator", { useClass: WeatherGenerator });
con.register<BotLevelGenerator>("BotLevelGenerator", { useClass: BotLevelGenerator }); depContainer.register<PlayerScavGenerator>("PlayerScavGenerator", { useClass: PlayerScavGenerator });
con.register<BotEquipmentModGenerator>("BotEquipmentModGenerator", { useClass: BotEquipmentModGenerator }); depContainer.register<LootGenerator>("LootGenerator", { useClass: LootGenerator });
con.register<RepeatableQuestGenerator>("RepeatableQuestGenerator", { useClass: RepeatableQuestGenerator }); depContainer.register<FenceBaseAssortGenerator>("FenceBaseAssortGenerator", {
useClass: FenceBaseAssortGenerator,
});
depContainer.register<BotLevelGenerator>("BotLevelGenerator", { useClass: BotLevelGenerator });
depContainer.register<BotEquipmentModGenerator>("BotEquipmentModGenerator", {
useClass: BotEquipmentModGenerator,
});
depContainer.register<RepeatableQuestGenerator>("RepeatableQuestGenerator", {
useClass: RepeatableQuestGenerator,
});
con.register<BarrelInventoryMagGen>("BarrelInventoryMagGen", { useClass: BarrelInventoryMagGen }); depContainer.register<BarrelInventoryMagGen>("BarrelInventoryMagGen", { useClass: BarrelInventoryMagGen });
con.register<ExternalInventoryMagGen>("ExternalInventoryMagGen", { useClass: ExternalInventoryMagGen }); depContainer.register<ExternalInventoryMagGen>("ExternalInventoryMagGen", {
con.register<InternalMagazineInventoryMagGen>("InternalMagazineInventoryMagGen", { useClass: ExternalInventoryMagGen,
});
depContainer.register<InternalMagazineInventoryMagGen>("InternalMagazineInventoryMagGen", {
useClass: InternalMagazineInventoryMagGen, useClass: InternalMagazineInventoryMagGen,
}); });
con.register<UbglExternalMagGen>("UbglExternalMagGen", { useClass: UbglExternalMagGen }); depContainer.register<UbglExternalMagGen>("UbglExternalMagGen", { useClass: UbglExternalMagGen });
con.registerType("InventoryMagGen", "BarrelInventoryMagGen"); depContainer.registerType("InventoryMagGen", "BarrelInventoryMagGen");
con.registerType("InventoryMagGen", "ExternalInventoryMagGen"); depContainer.registerType("InventoryMagGen", "ExternalInventoryMagGen");
con.registerType("InventoryMagGen", "InternalMagazineInventoryMagGen"); depContainer.registerType("InventoryMagGen", "InternalMagazineInventoryMagGen");
con.registerType("InventoryMagGen", "UbglExternalMagGen"); depContainer.registerType("InventoryMagGen", "UbglExternalMagGen");
} }
private static registerHelpers(con: DependencyContainer): void private static registerHelpers(depContainer: DependencyContainer): void
{ {
// Helpers // Helpers
con.register<AssortHelper>("AssortHelper", { useClass: AssortHelper }); depContainer.register<AssortHelper>("AssortHelper", { useClass: AssortHelper });
con.register<BotHelper>("BotHelper", { useClass: BotHelper }); depContainer.register<BotHelper>("BotHelper", { useClass: BotHelper });
con.register<BotGeneratorHelper>("BotGeneratorHelper", { useClass: BotGeneratorHelper }); depContainer.register<BotGeneratorHelper>("BotGeneratorHelper", { useClass: BotGeneratorHelper });
con.register<ContainerHelper>("ContainerHelper", ContainerHelper); depContainer.register<ContainerHelper>("ContainerHelper", ContainerHelper);
con.register<DialogueHelper>("DialogueHelper", { useClass: DialogueHelper }); depContainer.register<DialogueHelper>("DialogueHelper", { useClass: DialogueHelper });
con.register<DurabilityLimitsHelper>("DurabilityLimitsHelper", { useClass: DurabilityLimitsHelper }); depContainer.register<DurabilityLimitsHelper>("DurabilityLimitsHelper", { useClass: DurabilityLimitsHelper });
con.register<GameEventHelper>("GameEventHelper", GameEventHelper); depContainer.register<GameEventHelper>("GameEventHelper", GameEventHelper);
con.register<HandbookHelper>("HandbookHelper", HandbookHelper, { lifecycle: Lifecycle.Singleton }); depContainer.register<HandbookHelper>("HandbookHelper", HandbookHelper, { lifecycle: Lifecycle.Singleton });
con.register<HealthHelper>("HealthHelper", { useClass: HealthHelper }); depContainer.register<HealthHelper>("HealthHelper", { useClass: HealthHelper });
con.register<HideoutHelper>("HideoutHelper", { useClass: HideoutHelper }); depContainer.register<HideoutHelper>("HideoutHelper", { useClass: HideoutHelper });
con.register<InRaidHelper>("InRaidHelper", { useClass: InRaidHelper }); depContainer.register<InRaidHelper>("InRaidHelper", { useClass: InRaidHelper });
con.register<InventoryHelper>("InventoryHelper", { useClass: InventoryHelper }); depContainer.register<InventoryHelper>("InventoryHelper", { useClass: InventoryHelper });
con.register<PaymentHelper>("PaymentHelper", PaymentHelper); depContainer.register<PaymentHelper>("PaymentHelper", PaymentHelper);
con.register<ItemHelper>("ItemHelper", { useClass: ItemHelper }); depContainer.register<ItemHelper>("ItemHelper", { useClass: ItemHelper });
con.register<PresetHelper>("PresetHelper", PresetHelper, { lifecycle: Lifecycle.Singleton }); depContainer.register<PresetHelper>("PresetHelper", PresetHelper, { lifecycle: Lifecycle.Singleton });
con.register<ProfileHelper>("ProfileHelper", { useClass: ProfileHelper }); depContainer.register<ProfileHelper>("ProfileHelper", { useClass: ProfileHelper });
con.register<QuestHelper>("QuestHelper", { useClass: QuestHelper }); depContainer.register<QuestHelper>("QuestHelper", { useClass: QuestHelper });
con.register<QuestConditionHelper>("QuestConditionHelper", QuestConditionHelper); depContainer.register<QuestConditionHelper>("QuestConditionHelper", QuestConditionHelper);
con.register<RagfairHelper>("RagfairHelper", { useClass: RagfairHelper }); depContainer.register<RagfairHelper>("RagfairHelper", { useClass: RagfairHelper });
con.register<RagfairSortHelper>("RagfairSortHelper", { useClass: RagfairSortHelper }); depContainer.register<RagfairSortHelper>("RagfairSortHelper", { useClass: RagfairSortHelper });
con.register<RagfairSellHelper>("RagfairSellHelper", { useClass: RagfairSellHelper }); depContainer.register<RagfairSellHelper>("RagfairSellHelper", { useClass: RagfairSellHelper });
con.register<RagfairOfferHelper>("RagfairOfferHelper", { useClass: RagfairOfferHelper }); depContainer.register<RagfairOfferHelper>("RagfairOfferHelper", { useClass: RagfairOfferHelper });
con.register<RagfairServerHelper>("RagfairServerHelper", { useClass: RagfairServerHelper }); depContainer.register<RagfairServerHelper>("RagfairServerHelper", { useClass: RagfairServerHelper });
con.register<RepairHelper>("RepairHelper", { useClass: RepairHelper }); depContainer.register<RepairHelper>("RepairHelper", { useClass: RepairHelper });
con.register<TraderHelper>("TraderHelper", TraderHelper); depContainer.register<TraderHelper>("TraderHelper", TraderHelper);
con.register<TraderAssortHelper>("TraderAssortHelper", TraderAssortHelper, { lifecycle: Lifecycle.Singleton }); depContainer.register<TraderAssortHelper>("TraderAssortHelper", TraderAssortHelper, {
con.register<TradeHelper>("TradeHelper", { useClass: TradeHelper }); lifecycle: Lifecycle.Singleton,
con.register<NotifierHelper>("NotifierHelper", { useClass: NotifierHelper }); });
con.register<UtilityHelper>("UtilityHelper", UtilityHelper); depContainer.register<TradeHelper>("TradeHelper", { useClass: TradeHelper });
con.register<WeightedRandomHelper>("WeightedRandomHelper", { useClass: WeightedRandomHelper }); depContainer.register<NotifierHelper>("NotifierHelper", { useClass: NotifierHelper });
con.register<HttpServerHelper>("HttpServerHelper", { useClass: HttpServerHelper }); depContainer.register<UtilityHelper>("UtilityHelper", UtilityHelper);
con.register<NotificationSendHelper>("NotificationSendHelper", { useClass: NotificationSendHelper }); depContainer.register<WeightedRandomHelper>("WeightedRandomHelper", { useClass: WeightedRandomHelper });
con.register<SecureContainerHelper>("SecureContainerHelper", { useClass: SecureContainerHelper }); depContainer.register<HttpServerHelper>("HttpServerHelper", { useClass: HttpServerHelper });
con.register<ProbabilityHelper>("ProbabilityHelper", { useClass: ProbabilityHelper }); depContainer.register<NotificationSendHelper>("NotificationSendHelper", { useClass: NotificationSendHelper });
con.register<BotWeaponGeneratorHelper>("BotWeaponGeneratorHelper", { useClass: BotWeaponGeneratorHelper }); depContainer.register<SecureContainerHelper>("SecureContainerHelper", { useClass: SecureContainerHelper });
con.register<BotDifficultyHelper>("BotDifficultyHelper", { useClass: BotDifficultyHelper }); depContainer.register<ProbabilityHelper>("ProbabilityHelper", { useClass: ProbabilityHelper });
con.register<RepeatableQuestHelper>("RepeatableQuestHelper", { useClass: RepeatableQuestHelper }); depContainer.register<BotWeaponGeneratorHelper>("BotWeaponGeneratorHelper", {
useClass: BotWeaponGeneratorHelper,
});
depContainer.register<BotDifficultyHelper>("BotDifficultyHelper", { useClass: BotDifficultyHelper });
depContainer.register<RepeatableQuestHelper>("RepeatableQuestHelper", { useClass: RepeatableQuestHelper });
} }
private static registerLoaders(con: DependencyContainer): void private static registerLoaders(depContainer: DependencyContainer): void
{ {
// Loaders // Loaders
con.register<BundleLoader>("BundleLoader", BundleLoader, { lifecycle: Lifecycle.Singleton }); depContainer.register<BundleLoader>("BundleLoader", BundleLoader, { lifecycle: Lifecycle.Singleton });
con.register<PreAkiModLoader>("PreAkiModLoader", PreAkiModLoader, { lifecycle: Lifecycle.Singleton }); depContainer.register<PreAkiModLoader>("PreAkiModLoader", PreAkiModLoader, { lifecycle: Lifecycle.Singleton });
con.register<PostAkiModLoader>("PostAkiModLoader", PostAkiModLoader, { lifecycle: Lifecycle.Singleton }); depContainer.register<PostAkiModLoader>("PostAkiModLoader", PostAkiModLoader, {
lifecycle: Lifecycle.Singleton,
});
} }
private static registerCallbacks(con: DependencyContainer): void private static registerCallbacks(depContainer: DependencyContainer): void
{ {
// Callbacks // Callbacks
con.register<BotCallbacks>("BotCallbacks", { useClass: BotCallbacks }); depContainer.register<BotCallbacks>("BotCallbacks", { useClass: BotCallbacks });
con.register<BundleCallbacks>("BundleCallbacks", { useClass: BundleCallbacks }); depContainer.register<BundleCallbacks>("BundleCallbacks", { useClass: BundleCallbacks });
con.register<ClientLogCallbacks>("ClientLogCallbacks", { useClass: ClientLogCallbacks }); depContainer.register<ClientLogCallbacks>("ClientLogCallbacks", { useClass: ClientLogCallbacks });
con.register<CustomizationCallbacks>("CustomizationCallbacks", { useClass: CustomizationCallbacks }); depContainer.register<CustomizationCallbacks>("CustomizationCallbacks", { useClass: CustomizationCallbacks });
con.register<DataCallbacks>("DataCallbacks", { useClass: DataCallbacks }); depContainer.register<DataCallbacks>("DataCallbacks", { useClass: DataCallbacks });
con.register<DialogueCallbacks>("DialogueCallbacks", { useClass: DialogueCallbacks }); depContainer.register<DialogueCallbacks>("DialogueCallbacks", { useClass: DialogueCallbacks });
con.register<GameCallbacks>("GameCallbacks", { useClass: GameCallbacks }); depContainer.register<GameCallbacks>("GameCallbacks", { useClass: GameCallbacks });
con.register<HandbookCallbacks>("HandbookCallbacks", { useClass: HandbookCallbacks }); depContainer.register<HandbookCallbacks>("HandbookCallbacks", { useClass: HandbookCallbacks });
con.register<HealthCallbacks>("HealthCallbacks", { useClass: HealthCallbacks }); depContainer.register<HealthCallbacks>("HealthCallbacks", { useClass: HealthCallbacks });
con.register<HideoutCallbacks>("HideoutCallbacks", { useClass: HideoutCallbacks }); depContainer.register<HideoutCallbacks>("HideoutCallbacks", { useClass: HideoutCallbacks });
con.register<HttpCallbacks>("HttpCallbacks", { useClass: HttpCallbacks }); depContainer.register<HttpCallbacks>("HttpCallbacks", { useClass: HttpCallbacks });
con.register<InraidCallbacks>("InraidCallbacks", { useClass: InraidCallbacks }); depContainer.register<InraidCallbacks>("InraidCallbacks", { useClass: InraidCallbacks });
con.register<InsuranceCallbacks>("InsuranceCallbacks", { useClass: InsuranceCallbacks }); depContainer.register<InsuranceCallbacks>("InsuranceCallbacks", { useClass: InsuranceCallbacks });
con.register<InventoryCallbacks>("InventoryCallbacks", { useClass: InventoryCallbacks }); depContainer.register<InventoryCallbacks>("InventoryCallbacks", { useClass: InventoryCallbacks });
con.register<ItemEventCallbacks>("ItemEventCallbacks", { useClass: ItemEventCallbacks }); depContainer.register<ItemEventCallbacks>("ItemEventCallbacks", { useClass: ItemEventCallbacks });
con.register<LauncherCallbacks>("LauncherCallbacks", { useClass: LauncherCallbacks }); depContainer.register<LauncherCallbacks>("LauncherCallbacks", { useClass: LauncherCallbacks });
con.register<LocationCallbacks>("LocationCallbacks", { useClass: LocationCallbacks }); depContainer.register<LocationCallbacks>("LocationCallbacks", { useClass: LocationCallbacks });
con.register<MatchCallbacks>("MatchCallbacks", { useClass: MatchCallbacks }); depContainer.register<MatchCallbacks>("MatchCallbacks", { useClass: MatchCallbacks });
con.register<ModCallbacks>("ModCallbacks", { useClass: ModCallbacks }); depContainer.register<ModCallbacks>("ModCallbacks", { useClass: ModCallbacks });
con.register<PostDBModLoader>("PostDBModLoader", { useClass: PostDBModLoader }); depContainer.register<PostDBModLoader>("PostDBModLoader", { useClass: PostDBModLoader });
con.register<NoteCallbacks>("NoteCallbacks", { useClass: NoteCallbacks }); depContainer.register<NoteCallbacks>("NoteCallbacks", { useClass: NoteCallbacks });
con.register<NotifierCallbacks>("NotifierCallbacks", { useClass: NotifierCallbacks }); depContainer.register<NotifierCallbacks>("NotifierCallbacks", { useClass: NotifierCallbacks });
con.register<PresetBuildCallbacks>("PresetBuildCallbacks", { useClass: PresetBuildCallbacks }); depContainer.register<PresetBuildCallbacks>("PresetBuildCallbacks", { useClass: PresetBuildCallbacks });
con.register<PresetCallbacks>("PresetCallbacks", { useClass: PresetCallbacks }); depContainer.register<PresetCallbacks>("PresetCallbacks", { useClass: PresetCallbacks });
con.register<ProfileCallbacks>("ProfileCallbacks", { useClass: ProfileCallbacks }); depContainer.register<ProfileCallbacks>("ProfileCallbacks", { useClass: ProfileCallbacks });
con.register<QuestCallbacks>("QuestCallbacks", { useClass: QuestCallbacks }); depContainer.register<QuestCallbacks>("QuestCallbacks", { useClass: QuestCallbacks });
con.register<RagfairCallbacks>("RagfairCallbacks", { useClass: RagfairCallbacks }); depContainer.register<RagfairCallbacks>("RagfairCallbacks", { useClass: RagfairCallbacks });
con.register<RepairCallbacks>("RepairCallbacks", { useClass: RepairCallbacks }); depContainer.register<RepairCallbacks>("RepairCallbacks", { useClass: RepairCallbacks });
con.register<SaveCallbacks>("SaveCallbacks", { useClass: SaveCallbacks }); depContainer.register<SaveCallbacks>("SaveCallbacks", { useClass: SaveCallbacks });
con.register<TradeCallbacks>("TradeCallbacks", { useClass: TradeCallbacks }); depContainer.register<TradeCallbacks>("TradeCallbacks", { useClass: TradeCallbacks });
con.register<TraderCallbacks>("TraderCallbacks", { useClass: TraderCallbacks }); depContainer.register<TraderCallbacks>("TraderCallbacks", { useClass: TraderCallbacks });
con.register<WeatherCallbacks>("WeatherCallbacks", { useClass: WeatherCallbacks }); depContainer.register<WeatherCallbacks>("WeatherCallbacks", { useClass: WeatherCallbacks });
con.register<WishlistCallbacks>("WishlistCallbacks", { useClass: WishlistCallbacks }); depContainer.register<WishlistCallbacks>("WishlistCallbacks", { useClass: WishlistCallbacks });
} }
private static registerServices(con: DependencyContainer): void private static registerServices(depContainer: DependencyContainer): void
{ {
// Services // Services
con.register<ImageRouteService>("ImageRouteService", ImageRouteService, { lifecycle: Lifecycle.Singleton }); depContainer.register<ImageRouteService>("ImageRouteService", ImageRouteService, {
con.register<FenceService>("FenceService", FenceService, { lifecycle: Lifecycle.Singleton });
con.register<PlayerService>("PlayerService", { useClass: PlayerService });
con.register<PaymentService>("PaymentService", { useClass: PaymentService });
con.register<InsuranceService>("InsuranceService", InsuranceService, { lifecycle: Lifecycle.Singleton });
con.register<TraderAssortService>("TraderAssortService", TraderAssortService, {
lifecycle: Lifecycle.Singleton, lifecycle: Lifecycle.Singleton,
}); });
con.register<RagfairPriceService>("RagfairPriceService", RagfairPriceService, { depContainer.register<FenceService>("FenceService", FenceService, { lifecycle: Lifecycle.Singleton });
depContainer.register<PlayerService>("PlayerService", { useClass: PlayerService });
depContainer.register<PaymentService>("PaymentService", { useClass: PaymentService });
depContainer.register<InsuranceService>("InsuranceService", InsuranceService, {
lifecycle: Lifecycle.Singleton, lifecycle: Lifecycle.Singleton,
}); });
con.register<RagfairCategoriesService>("RagfairCategoriesService", RagfairCategoriesService, { depContainer.register<TraderAssortService>("TraderAssortService", TraderAssortService, {
lifecycle: Lifecycle.Singleton,
});
con.register<RagfairOfferService>("RagfairOfferService", RagfairOfferService, {
lifecycle: Lifecycle.Singleton,
});
con.register<RagfairLinkedItemService>("RagfairLinkedItemService", RagfairLinkedItemService, {
lifecycle: Lifecycle.Singleton,
});
con.register<RagfairRequiredItemsService>("RagfairRequiredItemsService", RagfairRequiredItemsService, {
lifecycle: Lifecycle.Singleton, lifecycle: Lifecycle.Singleton,
}); });
con.register<NotificationService>("NotificationService", NotificationService, { depContainer.register<RagfairPriceService>("RagfairPriceService", RagfairPriceService, {
lifecycle: Lifecycle.Singleton, lifecycle: Lifecycle.Singleton,
}); });
con.register<MatchLocationService>("MatchLocationService", MatchLocationService, { depContainer.register<RagfairCategoriesService>("RagfairCategoriesService", RagfairCategoriesService, {
lifecycle: Lifecycle.Singleton, lifecycle: Lifecycle.Singleton,
}); });
con.register<ModCompilerService>("ModCompilerService", ModCompilerService); depContainer.register<RagfairOfferService>("RagfairOfferService", RagfairOfferService, {
con.register<HashCacheService>("HashCacheService", HashCacheService, { lifecycle: Lifecycle.Singleton });
con.register<LocaleService>("LocaleService", LocaleService, { lifecycle: Lifecycle.Singleton });
con.register<ProfileFixerService>("ProfileFixerService", ProfileFixerService);
con.register<RepairService>("RepairService", RepairService);
con.register<BotLootCacheService>("BotLootCacheService", BotLootCacheService, {
lifecycle: Lifecycle.Singleton, lifecycle: Lifecycle.Singleton,
}); });
con.register<CustomItemService>("CustomItemService", CustomItemService); depContainer.register<RagfairLinkedItemService>("RagfairLinkedItemService", RagfairLinkedItemService, {
con.register<BotEquipmentFilterService>("BotEquipmentFilterService", BotEquipmentFilterService);
con.register<ProfileSnapshotService>("ProfileSnapshotService", ProfileSnapshotService, {
lifecycle: Lifecycle.Singleton, lifecycle: Lifecycle.Singleton,
}); });
con.register<ItemFilterService>("ItemFilterService", ItemFilterService, { lifecycle: Lifecycle.Singleton }); depContainer.register<RagfairRequiredItemsService>("RagfairRequiredItemsService", RagfairRequiredItemsService, {
con.register<BotGenerationCacheService>("BotGenerationCacheService", BotGenerationCacheService, {
lifecycle: Lifecycle.Singleton, lifecycle: Lifecycle.Singleton,
}); });
con.register<LocalisationService>("LocalisationService", LocalisationService, {
depContainer.register<NotificationService>("NotificationService", NotificationService, {
lifecycle: Lifecycle.Singleton, lifecycle: Lifecycle.Singleton,
}); });
con.register<CustomLocationWaveService>("CustomLocationWaveService", CustomLocationWaveService, { depContainer.register<MatchLocationService>("MatchLocationService", MatchLocationService, {
lifecycle: Lifecycle.Singleton, lifecycle: Lifecycle.Singleton,
}); });
con.register<OpenZoneService>("OpenZoneService", OpenZoneService, { lifecycle: Lifecycle.Singleton }); depContainer.register<ModCompilerService>("ModCompilerService", ModCompilerService);
con.register<ItemBaseClassService>("ItemBaseClassService", ItemBaseClassService, { depContainer.register<HashCacheService>("HashCacheService", HashCacheService, {
lifecycle: Lifecycle.Singleton, lifecycle: Lifecycle.Singleton,
}); });
con.register<BotEquipmentModPoolService>("BotEquipmentModPoolService", BotEquipmentModPoolService, { depContainer.register<LocaleService>("LocaleService", LocaleService, { lifecycle: Lifecycle.Singleton });
depContainer.register<ProfileFixerService>("ProfileFixerService", ProfileFixerService);
depContainer.register<RepairService>("RepairService", RepairService);
depContainer.register<BotLootCacheService>("BotLootCacheService", BotLootCacheService, {
lifecycle: Lifecycle.Singleton, lifecycle: Lifecycle.Singleton,
}); });
con.register<BotWeaponModLimitService>("BotWeaponModLimitService", BotWeaponModLimitService, { depContainer.register<CustomItemService>("CustomItemService", CustomItemService);
depContainer.register<BotEquipmentFilterService>("BotEquipmentFilterService", BotEquipmentFilterService);
depContainer.register<ProfileSnapshotService>("ProfileSnapshotService", ProfileSnapshotService, {
lifecycle: Lifecycle.Singleton, lifecycle: Lifecycle.Singleton,
}); });
con.register<SeasonalEventService>("SeasonalEventService", SeasonalEventService, { depContainer.register<ItemFilterService>("ItemFilterService", ItemFilterService, {
lifecycle: Lifecycle.Singleton, lifecycle: Lifecycle.Singleton,
}); });
con.register<MatchBotDetailsCacheService>("MatchBotDetailsCacheService", MatchBotDetailsCacheService, { depContainer.register<BotGenerationCacheService>("BotGenerationCacheService", BotGenerationCacheService, {
lifecycle: Lifecycle.Singleton, lifecycle: Lifecycle.Singleton,
}); });
con.register<RagfairTaxService>("RagfairTaxService", RagfairTaxService, { lifecycle: Lifecycle.Singleton }); depContainer.register<LocalisationService>("LocalisationService", LocalisationService, {
con.register<TraderPurchasePersisterService>("TraderPurchasePersisterService", TraderPurchasePersisterService); lifecycle: Lifecycle.Singleton,
con.register<PmcChatResponseService>("PmcChatResponseService", PmcChatResponseService); });
con.register<GiftService>("GiftService", GiftService); depContainer.register<CustomLocationWaveService>("CustomLocationWaveService", CustomLocationWaveService, {
con.register<MailSendService>("MailSendService", MailSendService); lifecycle: Lifecycle.Singleton,
});
depContainer.register<OpenZoneService>("OpenZoneService", OpenZoneService, { lifecycle: Lifecycle.Singleton });
depContainer.register<ItemBaseClassService>("ItemBaseClassService", ItemBaseClassService, {
lifecycle: Lifecycle.Singleton,
});
depContainer.register<BotEquipmentModPoolService>("BotEquipmentModPoolService", BotEquipmentModPoolService, {
lifecycle: Lifecycle.Singleton,
});
depContainer.register<BotWeaponModLimitService>("BotWeaponModLimitService", BotWeaponModLimitService, {
lifecycle: Lifecycle.Singleton,
});
depContainer.register<SeasonalEventService>("SeasonalEventService", SeasonalEventService, {
lifecycle: Lifecycle.Singleton,
});
depContainer.register<MatchBotDetailsCacheService>("MatchBotDetailsCacheService", MatchBotDetailsCacheService, {
lifecycle: Lifecycle.Singleton,
});
depContainer.register<RagfairTaxService>("RagfairTaxService", RagfairTaxService, {
lifecycle: Lifecycle.Singleton,
});
depContainer.register<TraderPurchasePersisterService>(
"TraderPurchasePersisterService",
TraderPurchasePersisterService,
);
depContainer.register<PmcChatResponseService>("PmcChatResponseService", PmcChatResponseService);
depContainer.register<GiftService>("GiftService", GiftService);
depContainer.register<MailSendService>("MailSendService", MailSendService);
depContainer.register<RaidTimeAdjustmentService>("RaidTimeAdjustmentService", RaidTimeAdjustmentService);
} }
private static registerServers(con: DependencyContainer): void private static registerServers(depContainer: DependencyContainer): void
{ {
// Servers // Servers
con.register<DatabaseServer>("DatabaseServer", DatabaseServer, { lifecycle: Lifecycle.Singleton }); depContainer.register<DatabaseServer>("DatabaseServer", DatabaseServer, { lifecycle: Lifecycle.Singleton });
con.register<HttpServer>("HttpServer", HttpServer, { lifecycle: Lifecycle.Singleton }); depContainer.register<HttpServer>("HttpServer", HttpServer, { lifecycle: Lifecycle.Singleton });
con.register<WebSocketServer>("WebSocketServer", WebSocketServer, { lifecycle: Lifecycle.Singleton }); depContainer.register<WebSocketServer>("WebSocketServer", WebSocketServer, { lifecycle: Lifecycle.Singleton });
con.register<RagfairServer>("RagfairServer", RagfairServer); depContainer.register<RagfairServer>("RagfairServer", RagfairServer);
con.register<SaveServer>("SaveServer", SaveServer, { lifecycle: Lifecycle.Singleton }); depContainer.register<SaveServer>("SaveServer", SaveServer, { lifecycle: Lifecycle.Singleton });
con.register<ConfigServer>("ConfigServer", ConfigServer, { lifecycle: Lifecycle.Singleton }); depContainer.register<ConfigServer>("ConfigServer", ConfigServer, { lifecycle: Lifecycle.Singleton });
} }
private static registerControllers(con: DependencyContainer): void private static registerControllers(depContainer: DependencyContainer): void
{ {
// Controllers // Controllers
con.register<BotController>("BotController", { useClass: BotController }); depContainer.register<BotController>("BotController", { useClass: BotController });
con.register<ClientLogController>("ClientLogController", { useClass: ClientLogController }); depContainer.register<ClientLogController>("ClientLogController", { useClass: ClientLogController });
con.register<CustomizationController>("CustomizationController", { useClass: CustomizationController }); depContainer.register<CustomizationController>("CustomizationController", {
con.register<DialogueController>("DialogueController", { useClass: DialogueController }); useClass: CustomizationController,
con.register<GameController>("GameController", { useClass: GameController }); });
con.register<HandbookController>("HandbookController", { useClass: HandbookController }); depContainer.register<DialogueController>("DialogueController", { useClass: DialogueController });
con.register<HealthController>("HealthController", { useClass: HealthController }); depContainer.register<GameController>("GameController", { useClass: GameController });
con.register<HideoutController>("HideoutController", { useClass: HideoutController }); depContainer.register<HandbookController>("HandbookController", { useClass: HandbookController });
con.register<InraidController>("InraidController", { useClass: InraidController }); depContainer.register<HealthController>("HealthController", { useClass: HealthController });
con.register<InsuranceController>("InsuranceController", { useClass: InsuranceController }); depContainer.register<HideoutController>("HideoutController", { useClass: HideoutController });
con.register<InventoryController>("InventoryController", { useClass: InventoryController }); depContainer.register<InraidController>("InraidController", { useClass: InraidController });
con.register<LauncherController>("LauncherController", { useClass: LauncherController }); depContainer.register<InsuranceController>("InsuranceController", { useClass: InsuranceController });
con.register<LocationController>("LocationController", { useClass: LocationController }); depContainer.register<InventoryController>("InventoryController", { useClass: InventoryController });
con.register<MatchController>("MatchController", MatchController); depContainer.register<LauncherController>("LauncherController", { useClass: LauncherController });
con.register<NoteController>("NoteController", { useClass: NoteController }); depContainer.register<LocationController>("LocationController", { useClass: LocationController });
con.register<NotifierController>("NotifierController", { useClass: NotifierController }); depContainer.register<MatchController>("MatchController", MatchController);
con.register<PresetBuildController>("PresetBuildController", { useClass: PresetBuildController }); depContainer.register<NoteController>("NoteController", { useClass: NoteController });
con.register<PresetController>("PresetController", { useClass: PresetController }); depContainer.register<NotifierController>("NotifierController", { useClass: NotifierController });
con.register<ProfileController>("ProfileController", { useClass: ProfileController }); depContainer.register<PresetBuildController>("PresetBuildController", { useClass: PresetBuildController });
con.register<QuestController>("QuestController", { useClass: QuestController }); depContainer.register<PresetController>("PresetController", { useClass: PresetController });
con.register<RagfairController>("RagfairController", { useClass: RagfairController }); depContainer.register<ProfileController>("ProfileController", { useClass: ProfileController });
con.register<RepairController>("RepairController", { useClass: RepairController }); depContainer.register<QuestController>("QuestController", { useClass: QuestController });
con.register<RepeatableQuestController>("RepeatableQuestController", { useClass: RepeatableQuestController }); depContainer.register<RagfairController>("RagfairController", { useClass: RagfairController });
con.register<TradeController>("TradeController", { useClass: TradeController }); depContainer.register<RepairController>("RepairController", { useClass: RepairController });
con.register<TraderController>("TraderController", { useClass: TraderController }); depContainer.register<RepeatableQuestController>("RepeatableQuestController", {
con.register<WeatherController>("WeatherController", { useClass: WeatherController }); useClass: RepeatableQuestController,
con.register<WishlistController>("WishlistController", WishlistController); });
depContainer.register<TradeController>("TradeController", { useClass: TradeController });
depContainer.register<TraderController>("TraderController", { useClass: TraderController });
depContainer.register<WeatherController>("WeatherController", { useClass: WeatherController });
depContainer.register<WishlistController>("WishlistController", WishlistController);
} }
} }

View File

@ -196,7 +196,6 @@ export class BotEquipmentModGenerator
// Get pool of mods that fit weapon // Get pool of mods that fit weapon
const compatibleModsPool = modPool[parentTemplate._id]; const compatibleModsPool = modPool[parentTemplate._id];
// biome-ignore lint/complexity/useSimplifiedLogicExpression: <explanation>
if ( if (
!((parentTemplate._props.Slots.length || parentTemplate._props.Cartridges?.length) !((parentTemplate._props.Slots.length || parentTemplate._props.Cartridges?.length)
|| parentTemplate._props.Chambers?.length) || parentTemplate._props.Chambers?.length)

View File

@ -522,6 +522,7 @@ export class BotGenerator
return; return;
} }
// more color = more op
botInfo.GameVersion = this.weightedRandomHelper.getWeightedValue(this.pmcConfig.gameVersionWeight); botInfo.GameVersion = this.weightedRandomHelper.getWeightedValue(this.pmcConfig.gameVersionWeight);
botInfo.MemberCategory = Number.parseInt( botInfo.MemberCategory = Number.parseInt(
this.weightedRandomHelper.getWeightedValue(this.pmcConfig.accountTypeWeight), this.weightedRandomHelper.getWeightedValue(this.pmcConfig.accountTypeWeight),

View File

@ -352,13 +352,21 @@ export class BotLootGenerator
itemsToAdd, itemsToAdd,
inventoryToAddItemsTo, inventoryToAddItemsTo,
); );
if (itemAddedResult === ItemAddedResult.NO_SPACE) if (itemAddedResult !== ItemAddedResult.SUCCESS)
{ {
if (itemAddedResult === ItemAddedResult.NO_CONTAINERS)
{
// Bot has no container to put item in, exit
this.logger.debug(`Unable to add ${totalItemCount} items to bot as it lacks a container to include them`);
break;
}
fitItemIntoContainerAttempts++; fitItemIntoContainerAttempts++;
if (fitItemIntoContainerAttempts >= 4) if (fitItemIntoContainerAttempts >= 4)
{ {
this.logger.debug( this.logger.debug(
`Failed to place item ${i} of ${totalItemCount} item into ${botRole} container: ${equipmentSlots}, ${fitItemIntoContainerAttempts} times, skipping`, `Failed to place item ${i} of ${totalItemCount} items into ${botRole} containers: ${equipmentSlots.join(",")}. Tried ${fitItemIntoContainerAttempts} times, reason: ${ItemAddedResult[itemAddedResult]}, skipping`,
); );
break; break;
@ -366,6 +374,7 @@ export class BotLootGenerator
} }
else else
{ {
// Reset counter
fitItemIntoContainerAttempts = 0; fitItemIntoContainerAttempts = 0;
} }
@ -426,13 +435,18 @@ export class BotLootGenerator
isPmc, isPmc,
botLevel, botLevel,
); );
this.botWeaponGeneratorHelper.addItemWithChildrenToEquipmentSlot( const result = this.botWeaponGeneratorHelper.addItemWithChildrenToEquipmentSlot(
[equipmentSlot], [equipmentSlot],
generatedWeapon.weapon[0]._id, generatedWeapon.weapon[0]._id,
generatedWeapon.weapon[0]._tpl, generatedWeapon.weapon[0]._tpl,
[...generatedWeapon.weapon], [...generatedWeapon.weapon],
botInventory, botInventory,
); );
if (result !== ItemAddedResult.SUCCESS)
{
this.logger.debug(`Failed to add additional weapon ${generatedWeapon.weapon[0]._id} to bot backpack, reason: ${ItemAddedResult[result]}`);
}
} }
} }
} }

View File

@ -205,14 +205,15 @@ export class BotWeaponGenerator
this.fillExistingMagazines(weaponWithModsArray, magazine, ammoTpl); this.fillExistingMagazines(weaponWithModsArray, magazine, ammoTpl);
} }
// Add cartridge to gun chamber if weapon has slot for it // Add cartridge(s) to gun chamber(s)
if ( if (
weaponItemTemplate._props.Chambers?.length === 1 weaponItemTemplate._props.Chambers?.length > 0
&& weaponItemTemplate._props.Chambers[0]?._name === "patron_in_weapon"
&& weaponItemTemplate._props.Chambers[0]?._props?.filters[0]?.Filter?.includes(ammoTpl) && weaponItemTemplate._props.Chambers[0]?._props?.filters[0]?.Filter?.includes(ammoTpl)
) )
{ {
this.addCartridgeToChamber(weaponWithModsArray, ammoTpl, "patron_in_weapon"); // Guns have variety of possible Chamber ids, patron_in_weapon/patron_in_weapon_000/patron_in_weapon_001
const chamberSlotNames = weaponItemTemplate._props.Chambers.map(x => x._name);
this.addCartridgeToChamber(weaponWithModsArray, ammoTpl, chamberSlotNames);
} }
// Fill UBGL if found // Fill UBGL if found
@ -235,31 +236,34 @@ export class BotWeaponGenerator
} }
/** /**
* Insert a cartridge into a weapon * Insert a cartridge(s) into a weapon
* Handles all chambers - patron_in_weapon, patron_in_weapon_000 etc
* @param weaponWithModsArray Weapon and mods * @param weaponWithModsArray Weapon and mods
* @param ammoTpl Cartridge to add to weapon * @param ammoTpl Cartridge to add to weapon
* @param desiredSlotId name of slot, e.g. patron_in_weapon * @param chamberSlotIds name of slots to create or add ammo to
*/ */
protected addCartridgeToChamber(weaponWithModsArray: Item[], ammoTpl: string, desiredSlotId: string): void protected addCartridgeToChamber(weaponWithModsArray: Item[], ammoTpl: string, chamberSlotIds: string[]): void
{ {
// Check for slot first for (const slotId of chamberSlotIds)
const existingItemWithSlot = weaponWithModsArray.find((x) => x.slotId === desiredSlotId);
if (!existingItemWithSlot)
{ {
// Not found, add fresh const existingItemWithSlot = weaponWithModsArray.find((x) => x.slotId === slotId);
weaponWithModsArray.push({ if (!existingItemWithSlot)
_id: this.hashUtil.generate(), {
_tpl: ammoTpl, // Not found, add new slot to weapon
parentId: weaponWithModsArray[0]._id, weaponWithModsArray.push({
slotId: desiredSlotId, _id: this.hashUtil.generate(),
upd: { StackObjectsCount: 1 }, _tpl: ammoTpl,
}); parentId: weaponWithModsArray[0]._id,
} slotId: slotId,
else upd: { StackObjectsCount: 1 },
{ });
// Already exists, update values }
existingItemWithSlot.upd = { StackObjectsCount: 1 }; else
existingItemWithSlot._tpl = ammoTpl; {
// Already exists, update values
existingItemWithSlot._tpl = ammoTpl;
existingItemWithSlot.upd = { StackObjectsCount: 1 };
}
} }
} }
@ -612,7 +616,7 @@ export class BotWeaponGenerator
{ {
const possibleAmmo = this.weightedRandomHelper.getWeightedValue<string>(compatibleCartridges); const possibleAmmo = this.weightedRandomHelper.getWeightedValue<string>(compatibleCartridges);
// Check compatibility // Weapon has chamber but does not support cartridge
if (weaponTemplate._props.Chambers[0] if (weaponTemplate._props.Chambers[0]
&& !weaponTemplate._props.Chambers[0]._props.filters[0].Filter.includes(possibleAmmo) && !weaponTemplate._props.Chambers[0]._props.filters[0].Filter.includes(possibleAmmo)
) )

View File

@ -788,7 +788,7 @@ export class LocationGenerator
} }
else if (this.itemHelper.isOfBaseclass(chosenTpl, BaseClasses.AMMO_BOX)) else if (this.itemHelper.isOfBaseclass(chosenTpl, BaseClasses.AMMO_BOX))
{ {
// Fill with cartrdiges // Fill with cartridges
const ammoBoxTemplate = this.itemHelper.getItem(chosenTpl)[1]; const ammoBoxTemplate = this.itemHelper.getItem(chosenTpl)[1];
const ammoBoxItem: Item[] = [{ _id: this.objectId.generate(), _tpl: chosenTpl }]; const ammoBoxItem: Item[] = [{ _id: this.objectId.generate(), _tpl: chosenTpl }];
this.itemHelper.addCartridgesToAmmoBox(ammoBoxItem, ammoBoxTemplate); this.itemHelper.addCartridgesToAmmoBox(ammoBoxItem, ammoBoxTemplate);

View File

@ -12,6 +12,7 @@ import { IBotType } from "@spt-aki/models/eft/common/tables/IBotType";
import { Item } from "@spt-aki/models/eft/common/tables/IItem"; import { Item } from "@spt-aki/models/eft/common/tables/IItem";
import { AccountTypes } from "@spt-aki/models/enums/AccountTypes"; import { AccountTypes } from "@spt-aki/models/enums/AccountTypes";
import { ConfigTypes } from "@spt-aki/models/enums/ConfigTypes"; import { ConfigTypes } from "@spt-aki/models/enums/ConfigTypes";
import { ItemAddedResult } from "@spt-aki/models/enums/ItemAddedResult";
import { MemberCategory } from "@spt-aki/models/enums/MemberCategory"; import { MemberCategory } from "@spt-aki/models/enums/MemberCategory";
import { Traders } from "@spt-aki/models/enums/Traders"; import { Traders } from "@spt-aki/models/enums/Traders";
import { IPlayerScavConfig, KarmaLevel } from "@spt-aki/models/spt/config/IPlayerScavConfig"; import { IPlayerScavConfig, KarmaLevel } from "@spt-aki/models/spt/config/IPlayerScavConfig";
@ -125,13 +126,18 @@ export class PlayerScavGenerator
_tpl: labsCard._id, _tpl: labsCard._id,
...this.botGeneratorHelper.generateExtraPropertiesForItem(labsCard), ...this.botGeneratorHelper.generateExtraPropertiesForItem(labsCard),
}]; }];
this.botWeaponGeneratorHelper.addItemWithChildrenToEquipmentSlot( const result = this.botWeaponGeneratorHelper.addItemWithChildrenToEquipmentSlot(
["TacticalVest", "Pockets", "Backpack"], ["TacticalVest", "Pockets", "Backpack"],
itemsToAdd[0]._id, itemsToAdd[0]._id,
labsCard._id, labsCard._id,
itemsToAdd, itemsToAdd,
scavData.Inventory, scavData.Inventory,
); );
if (result !== ItemAddedResult.SUCCESS)
{
this.logger.debug(`Unable to add keycard to bot. Reason: ${ItemAddedResult[result]}`);
}
} }
// Remove secure container // Remove secure container

View File

@ -4,10 +4,12 @@ import { IInventoryMagGen } from "@spt-aki/generators/weapongen/IInventoryMagGen
import { InventoryMagGen } from "@spt-aki/generators/weapongen/InventoryMagGen"; import { InventoryMagGen } from "@spt-aki/generators/weapongen/InventoryMagGen";
import { BotWeaponGeneratorHelper } from "@spt-aki/helpers/BotWeaponGeneratorHelper"; import { BotWeaponGeneratorHelper } from "@spt-aki/helpers/BotWeaponGeneratorHelper";
import { ItemHelper } from "@spt-aki/helpers/ItemHelper"; import { ItemHelper } from "@spt-aki/helpers/ItemHelper";
import { ITemplateItem } from "@spt-aki/models/eft/common/tables/ITemplateItem";
import { EquipmentSlots } from "@spt-aki/models/enums/EquipmentSlots"; import { EquipmentSlots } from "@spt-aki/models/enums/EquipmentSlots";
import { ItemAddedResult } from "@spt-aki/models/enums/ItemAddedResult"; import { ItemAddedResult } from "@spt-aki/models/enums/ItemAddedResult";
import { ILogger } from "@spt-aki/models/spt/utils/ILogger"; import { ILogger } from "@spt-aki/models/spt/utils/ILogger";
import { LocalisationService } from "@spt-aki/services/LocalisationService"; import { LocalisationService } from "@spt-aki/services/LocalisationService";
import { RandomUtil } from "@spt-aki/utils/RandomUtil";
@injectable() @injectable()
export class ExternalInventoryMagGen implements IInventoryMagGen export class ExternalInventoryMagGen implements IInventoryMagGen
@ -17,6 +19,7 @@ export class ExternalInventoryMagGen implements IInventoryMagGen
@inject("ItemHelper") protected itemHelper: ItemHelper, @inject("ItemHelper") protected itemHelper: ItemHelper,
@inject("LocalisationService") protected localisationService: LocalisationService, @inject("LocalisationService") protected localisationService: LocalisationService,
@inject("BotWeaponGeneratorHelper") protected botWeaponGeneratorHelper: BotWeaponGeneratorHelper, @inject("BotWeaponGeneratorHelper") protected botWeaponGeneratorHelper: BotWeaponGeneratorHelper,
@inject("RandomUtil") protected randomUtil: RandomUtil
) )
{} {}
@ -25,6 +28,7 @@ export class ExternalInventoryMagGen implements IInventoryMagGen
return 99; return 99;
} }
// eslint-disable-next-line @typescript-eslint/no-unused-vars
canHandleInventoryMagGen(inventoryMagGen: InventoryMagGen): boolean canHandleInventoryMagGen(inventoryMagGen: InventoryMagGen): boolean
{ {
return true; // Fallback, if code reaches here it means no other implementation can handle this type of magazine return true; // Fallback, if code reaches here it means no other implementation can handle this type of magazine
@ -32,8 +36,15 @@ export class ExternalInventoryMagGen implements IInventoryMagGen
process(inventoryMagGen: InventoryMagGen): void process(inventoryMagGen: InventoryMagGen): void
{ {
// Cout of attempts to fit a magazine into bot inventory
let fitAttempts = 0;
// Magazine Db template
let magTemplate = inventoryMagGen.getMagazineTemplate(); let magTemplate = inventoryMagGen.getMagazineTemplate();
let magazineTpl = magTemplate._id; let magazineTpl = magTemplate._id;
const weapon = inventoryMagGen.getWeaponTemplate();
const attemptedMagBlacklist: string[] = [];
const defaultMagazineTpl = this.botWeaponGeneratorHelper.getWeaponsDefaultMagazineTpl(weapon);
const randomizedMagazineCount = Number( const randomizedMagazineCount = Number(
this.botWeaponGeneratorHelper.getRandomizedMagazineCount(inventoryMagGen.getMagCount()), this.botWeaponGeneratorHelper.getRandomizedMagazineCount(inventoryMagGen.getMagCount()),
); );
@ -45,7 +56,7 @@ export class ExternalInventoryMagGen implements IInventoryMagGen
magTemplate, magTemplate,
); );
const ableToFitMagazinesIntoBotInventory = this.botWeaponGeneratorHelper.addItemWithChildrenToEquipmentSlot( const fitsIntoInventory = this.botWeaponGeneratorHelper.addItemWithChildrenToEquipmentSlot(
[EquipmentSlots.TACTICAL_VEST, EquipmentSlots.POCKETS], [EquipmentSlots.TACTICAL_VEST, EquipmentSlots.POCKETS],
magazineWithAmmo[0]._id, magazineWithAmmo[0]._id,
magazineTpl, magazineTpl,
@ -53,42 +64,104 @@ export class ExternalInventoryMagGen implements IInventoryMagGen
inventoryMagGen.getPmcInventory(), inventoryMagGen.getPmcInventory(),
); );
if (ableToFitMagazinesIntoBotInventory === ItemAddedResult.NO_SPACE && i < randomizedMagazineCount) if (fitsIntoInventory === ItemAddedResult.NO_CONTAINERS)
{ {
// We were unable to fit at least the minimum amount of magazines, so we fallback to default magazine // No containers to fit magazines, stop trying
// and try again. Temporary workaround to Killa spawning with no extras if he spawns with a drum mag. break;
// TODO: Fix this properly }
if ( // No space for magazine and we haven't reached desired magazine count
magazineTpl else if (fitsIntoInventory === ItemAddedResult.NO_SPACE && i < randomizedMagazineCount)
=== this.botWeaponGeneratorHelper.getWeaponsDefaultMagazineTpl( {
inventoryMagGen.getWeaponTemplate(), // Prevent infinite loop by only allowing 5 attempts at fitting a magazine into inventory
) if (fitAttempts > 5)
) {
this.logger.debug(`Failed ${fitAttempts} times to add magazine ${magazineTpl} to bot inventory, stopping`);
break;
}
/* We were unable to fit at least the minimum amount of magazines,
* so we fallback to default magazine and try again.
* Temporary workaround to Killa spawning with no extra mags if he spawns with a drum mag */
if (magazineTpl === defaultMagazineTpl)
{ {
// We were already on default - stop here to prevent infinite looping // We were already on default - stop here to prevent infinite looping
break; break;
} }
// Get default magazine tpl, reset loop counter by 1 and try again // Add failed magazine tpl to blacklist
magazineTpl = this.botWeaponGeneratorHelper.getWeaponsDefaultMagazineTpl( attemptedMagBlacklist.push(magazineTpl);
inventoryMagGen.getWeaponTemplate(),
); // Set chosen magazine tpl to the weapons default magazine tpl and try to fit into inventory next loop
magazineTpl = defaultMagazineTpl;
magTemplate = this.itemHelper.getItem(magazineTpl)[1]; magTemplate = this.itemHelper.getItem(magazineTpl)[1];
if (!magTemplate) if (!magTemplate)
{ {
this.logger.error( this.logger.error(
this.localisationService.getText("bot-unable_to_find_default_magazine_item", magazineTpl), this.localisationService.getText("bot-unable_to_find_default_magazine_item", magazineTpl),
); );
break; break;
} }
// Edge case - some weapons (SKS) have an internal magazine as default, choose random non-internal magazine to add to bot instead
if (magTemplate._props.ReloadMagType === "InternalMagazine") if (magTemplate._props.ReloadMagType === "InternalMagazine")
{ {
break; const result = this.getRandomExternalMagazineForInternalMagazineGun(inventoryMagGen.getWeaponTemplate()._id, attemptedMagBlacklist);
if (!result?._id)
{
this.logger.debug(`Unable to add additional magazine into bot inventory for weapon: ${weapon._name}, attempted: ${fitAttempts} times`);
break;
}
magazineTpl = result._id;
magTemplate = result;
fitAttempts++;
} }
// Reduce loop counter by 1 to ensure we get full cout of desired magazines
i--; i--;
} }
if (fitsIntoInventory === ItemAddedResult.SUCCESS)
{
// Reset fit counter now it succeeded
fitAttempts = 0;
}
} }
} }
/**
* Get a random compatible external magazine for a weapon, excluses internal magazines from possible pool
* @param weaponTpl Weapon to get mag for
* @returns tpl of magazine
*/
protected getRandomExternalMagazineForInternalMagazineGun(weaponTpl: string, magazineBlacklist: string[]): ITemplateItem
{
// The mag Slot data for the weapon
const magSlot = this.itemHelper.getItem(weaponTpl)[1]._props.Slots.find(x => x._name === "mod_magazine");
if (!magSlot)
{
return null;
}
// All possible mags that fit into the weapon excluding blacklisted
const magazinePool = magSlot._props.filters[0].Filter.filter(x => !magazineBlacklist.includes(x)).map((x) => this.itemHelper.getItem(x)[1]);
if (!magazinePool)
{
return null;
}
// Non-internal magazines that fit into the weapon
const externalMagazineOnlyPool = magazinePool.filter(x => x._props.ReloadMagType !== "InternalMagazine");
if (!externalMagazineOnlyPool || externalMagazineOnlyPool?.length === 0)
{
return null;
}
// Randomly chosen external magazine
return this.randomUtil.getArrayValue(externalMagazineOnlyPool);
}
} }

View File

@ -46,12 +46,15 @@ export class BotWeaponGeneratorHelper
let chamberBulletCount = 0; let chamberBulletCount = 0;
if (this.magazineIsCylinderRelated(parentItem._name)) if (this.magazineIsCylinderRelated(parentItem._name))
{ {
// if we have a CylinderMagazine/SpringDrivenCylinder we count the number of camoras as the _max_count of the magazine is 0 const firstSlotAmmoTpl = magTemplate._props.Cartridges[0]._props.filters[0].Filter[0];
chamberBulletCount = magTemplate._props.Slots.length; const ammoMaxStackSize = this.itemHelper.getItem(firstSlotAmmoTpl)[1]?._props?.StackMaxSize ?? 1;
chamberBulletCount = (ammoMaxStackSize === 1)
? 1 // Rotating grenade launcher
: magTemplate._props.Slots.length; // Shotguns/revolvers. We count the number of camoras as the _max_count of the magazine is 0
} }
else if (parentItem._id === BaseClasses.UBGL) else if (parentItem._id === BaseClasses.UBGL)
{ {
// underbarrel launchers can only have 1 chambered grenade // Underbarrel launchers can only have 1 chambered grenade
chamberBulletCount = 1; chamberBulletCount = 1;
} }
else else
@ -74,7 +77,7 @@ export class BotWeaponGeneratorHelper
// const range = magCounts.max - magCounts.min; // const range = magCounts.max - magCounts.min;
// return this.randomUtil.getBiasedRandomNumber(magCounts.min, magCounts.max, Math.round(range * 0.75), 4); // return this.randomUtil.getBiasedRandomNumber(magCounts.min, magCounts.max, Math.round(range * 0.75), 4);
return this.weightedRandomHelper.getWeightedValue(magCounts.weights); return Number.parseInt(this.weightedRandomHelper.getWeightedValue(magCounts.weights));
} }
/** /**
@ -129,9 +132,15 @@ export class BotWeaponGeneratorHelper
ammoItem, ammoItem,
], inventory); ], inventory);
if (result === ItemAddedResult.NO_SPACE) if (result !== ItemAddedResult.SUCCESS)
{ {
this.logger.debug(`Unable to add ammo: ${ammoItem._tpl} to bot equipment`); this.logger.debug(`Unable to add ammo: ${ammoItem._tpl} to bot inventory, ${ItemAddedResult[result]}`);
if (result === ItemAddedResult.NO_SPACE || result === ItemAddedResult.NO_CONTAINERS)
{
// If there's no space for 1 stack, there's no space for the others
break;
}
} }
} }
} }
@ -164,18 +173,26 @@ export class BotWeaponGeneratorHelper
inventory: Inventory, inventory: Inventory,
): ItemAddedResult ): ItemAddedResult
{ {
let missingContainerCount = 0;
for (const slot of equipmentSlots) for (const slot of equipmentSlots)
{ {
// Get container to put item into // Get container to put item into
const container = inventory.items.find((i) => i.slotId === slot); const container = inventory.items.find((i) => i.slotId === slot);
if (!container) if (!container)
{ {
// Desired equipment container (e.g. backpack) not found missingContainerCount++;
this.logger.debug( if (missingContainerCount === equipmentSlots.length)
`Unable to add item: ${ {
itemWithChildren[0]._tpl // Bot doesnt have any containers
} to: ${slot}, slot missing/bot generated without equipment`, this.logger.debug(
); `Unable to add item: ${
itemWithChildren[0]._tpl
} to bot as it lacks the following containers: ${equipmentSlots.join(",")}`,
);
return ItemAddedResult.NO_CONTAINERS
}
continue; continue;
} }
@ -194,8 +211,12 @@ export class BotWeaponGeneratorHelper
continue; continue;
} }
// Get x/y grid size of item
const itemSize = this.inventoryHelper.getItemSize(parentTpl, parentId, itemWithChildren); const itemSize = this.inventoryHelper.getItemSize(parentTpl, parentId, itemWithChildren);
// Iterate over each grid in the container and look for a big enough space for the item to be placed in
let currentGridCount = 1;
const slotGridCount = containerTemplate[1]._props.Grids.length;
for (const slotGrid of containerTemplate[1]._props.Grids) for (const slotGrid of containerTemplate[1]._props.Grids)
{ {
// Grid is empty, skip // Grid is empty, skip
@ -210,13 +231,13 @@ export class BotWeaponGeneratorHelper
continue; continue;
} }
// Get all base level items in backpack // Get all root items in backpack
const containerItems = inventory.items.filter((i) => const existingContainerItems = inventory.items.filter((i) =>
i.parentId === container._id && i.slotId === slotGrid._name i.parentId === container._id && i.slotId === slotGrid._name
); );
// Get a copy of base level items we can iterate over // Get a copy of base level items we can iterate over
const containerItemsToCheck = containerItems.filter((x) => x.slotId === slotGrid._name); const containerItemsToCheck = existingContainerItems.filter((x) => x.slotId === slotGrid._name);
for (const item of containerItemsToCheck) for (const item of containerItemsToCheck)
{ {
// Look for children on items, insert into array if found // Look for children on items, insert into array if found
@ -224,7 +245,7 @@ export class BotWeaponGeneratorHelper
const itemWithChildren = this.itemHelper.findAndReturnChildrenAsItems(inventory.items, item._id); const itemWithChildren = this.itemHelper.findAndReturnChildrenAsItems(inventory.items, item._id);
if (itemWithChildren.length > 1) if (itemWithChildren.length > 1)
{ {
containerItems.splice(containerItems.indexOf(item), 1, ...itemWithChildren); existingContainerItems.splice(existingContainerItems.indexOf(item), 1, ...itemWithChildren);
} }
} }
@ -232,9 +253,10 @@ export class BotWeaponGeneratorHelper
const slotGridMap = this.inventoryHelper.getContainerMap( const slotGridMap = this.inventoryHelper.getContainerMap(
slotGrid._props.cellsH, slotGrid._props.cellsH,
slotGrid._props.cellsV, slotGrid._props.cellsV,
containerItems, existingContainerItems,
container._id, container._id,
); );
// Try to fit item into grid // Try to fit item into grid
const findSlotResult = this.containerHelper.findSlotForItem(slotGridMap, itemSize[0], itemSize[1]); const findSlotResult = this.containerHelper.findSlotForItem(slotGridMap, itemSize[0], itemSize[1]);
@ -257,11 +279,18 @@ export class BotWeaponGeneratorHelper
return ItemAddedResult.SUCCESS; return ItemAddedResult.SUCCESS;
} }
// If we've checked all grids in container and reached this point, there's no space for item
if (slotGridCount >= currentGridCount)
{
return ItemAddedResult.NO_SPACE;
}
currentGridCount++;
// Start loop again in next grid of container // Start loop again in next grid of container
} }
} }
return ItemAddedResult.NO_SPACE; return ItemAddedResult.UNKNOWN;
} }
/** /**

View File

@ -219,8 +219,13 @@ export class DialogueHelper
} }
// Check reward count when item being moved isn't in reward list // Check reward count when item being moved isn't in reward list
// if count is 0, it means after this move the reward array will be empty and all rewards collected // If count is 0, it means after this move occurs the reward array will be empty and all rewards collected
const rewardItemCount = message.items.data.filter((x) => x._id !== itemId); if (!message.items.data)
{
message.items.data = [];
}
const rewardItemCount = message.items.data?.filter((item) => item._id !== itemId);
if (rewardItemCount.length === 0) if (rewardItemCount.length === 0)
{ {
message.rewardCollected = true; message.rewardCollected = true;

View File

@ -240,8 +240,11 @@ export class InRaidHelper
public updateScavProfileDataPostRaid(scavData: IPmcData, saveProgressRequest: ISaveProgressRequestData, sessionId: string): void public updateScavProfileDataPostRaid(scavData: IPmcData, saveProgressRequest: ISaveProgressRequestData, sessionId: string): void
{ {
// Only copy active quests into scav profile // Progress will later to copied over to PMC profile // Only copy active quests into scav profile // Progress will later to copied over to PMC profile
const existingActiveQuestIds = scavData.Quests.filter(x => x.status !== QuestStatus.AvailableForStart).map(x => x.qid); const existingActiveQuestIds = scavData.Quests?.filter(x => x.status !== QuestStatus.AvailableForStart).map(x => x.qid);
scavData.Quests = saveProgressRequest.profile.Quests.filter(x => existingActiveQuestIds.includes(x.qid)); if (existingActiveQuestIds)
{
scavData.Quests = saveProgressRequest.profile.Quests.filter(x => existingActiveQuestIds.includes(x.qid));
}
this.profileFixerService.checkForAndFixScavProfileIssues(scavData); this.profileFixerService.checkForAndFixScavProfileIssues(scavData);
} }

View File

@ -1027,7 +1027,7 @@ class ItemHelper
minSizePercent = 0.25, minSizePercent = 0.25,
): void ): void
{ {
// Get cartrdge properties and max allowed stack size // Get cartridge properties and max allowed stack size
const cartridgeDetails = this.getItem(cartridgeTpl); const cartridgeDetails = this.getItem(cartridgeTpl);
const cartridgeMaxStackSize = cartridgeDetails[1]._props.StackMaxSize; const cartridgeMaxStackSize = cartridgeDetails[1]._props.StackMaxSize;
@ -1085,7 +1085,7 @@ class ItemHelper
* Chose a randomly weighted cartridge that fits * Chose a randomly weighted cartridge that fits
* @param caliber Desired caliber * @param caliber Desired caliber
* @param staticAmmoDist Cartridges and thier weights * @param staticAmmoDist Cartridges and thier weights
* @returns Tpl of cartrdige * @returns Tpl of cartridge
*/ */
protected drawAmmoTpl(caliber: string, staticAmmoDist: Record<string, IStaticAmmoDetails[]>): string protected drawAmmoTpl(caliber: string, staticAmmoDist: Record<string, IStaticAmmoDetails[]>): string
{ {

View File

@ -345,13 +345,14 @@ export class QuestHelper
acceptedQuest: IAcceptQuestRequestData, acceptedQuest: IAcceptQuestRequestData,
): IQuestStatus ): IQuestStatus
{ {
const currentTimestamp = this.timeUtil.getTimestamp();
const existingQuest = pmcData.Quests.find((q) => q.qid === acceptedQuest.qid); const existingQuest = pmcData.Quests.find((q) => q.qid === acceptedQuest.qid);
if (existingQuest) if (existingQuest)
{ {
// Quest exists, update its status // Quest exists, update its status
existingQuest.startTime = this.timeUtil.getTimestamp(); existingQuest.startTime = currentTimestamp;
existingQuest.status = newState; existingQuest.status = newState;
existingQuest.statusTimers[newState] = this.timeUtil.getTimestamp(); existingQuest.statusTimers[newState] = currentTimestamp;
existingQuest.completedConditions = []; existingQuest.completedConditions = [];
if (existingQuest.availableAfter) if (existingQuest.availableAfter)
@ -365,7 +366,7 @@ export class QuestHelper
// Quest doesn't exists, add it // Quest doesn't exists, add it
const newQuest: IQuestStatus = { const newQuest: IQuestStatus = {
qid: acceptedQuest.qid, qid: acceptedQuest.qid,
startTime: this.timeUtil.getTimestamp(), startTime: currentTimestamp,
status: newState, status: newState,
statusTimers: {}, statusTimers: {},
}; };
@ -378,11 +379,11 @@ export class QuestHelper
// Quest should be put into 'pending' state // Quest should be put into 'pending' state
newQuest.startTime = 0; newQuest.startTime = 0;
newQuest.status = QuestStatus.AvailableAfter; // 9 newQuest.status = QuestStatus.AvailableAfter; // 9
newQuest.availableAfter = this.timeUtil.getTimestamp() + waitTime._props.availableAfter; newQuest.availableAfter = currentTimestamp + waitTime._props.availableAfter;
} }
else else
{ {
newQuest.statusTimers[newState.toString()] = this.timeUtil.getTimestamp(); newQuest.statusTimers[newState.toString()] = currentTimestamp;
newQuest.completedConditions = []; newQuest.completedConditions = [];
} }
@ -733,6 +734,43 @@ export class QuestHelper
} }
} }
/**
* Resets a quests values back to its chosen state
* @param pmcData Profile to update
* @param newQuestState New state the quest should be in
* @param questId Id of the quest to alter the status of
*/
public resetQuestState(pmcData: IPmcData, newQuestState: QuestStatus, questId: string): void
{
const questToUpdate = pmcData.Quests.find((quest) => quest.qid === questId);
if (questToUpdate)
{
const currentTimestamp = this.timeUtil.getTimestamp();
questToUpdate.status = newQuestState;
// Only set start time when quest is being started
if (newQuestState === QuestStatus.Started)
{
questToUpdate.startTime = currentTimestamp;
}
questToUpdate.statusTimers[newQuestState] = currentTimestamp;
// Delete all status timers after applying new status
for (const statusKey in questToUpdate.statusTimers)
{
if (Number.parseInt(statusKey) > newQuestState)
{
delete questToUpdate.statusTimers[statusKey];
}
}
// Remove all completed conditions
questToUpdate.completedConditions = [];
}
}
/** /**
* Give player quest rewards - Skills/exp/trader standing/items/assort unlocks - Returns reward items player earned * Give player quest rewards - Skills/exp/trader standing/items/assort unlocks - Returns reward items player earned
* @param profileData Player profile (scav or pmc) * @param profileData Player profile (scav or pmc)

View File

@ -0,0 +1,5 @@
export interface IGetRaidTimeRequest
{
Side: string,
Location: string
}

View File

@ -0,0 +1,15 @@
export interface IGetRaidTimeResponse
{
RaidTimeMinutes: number
NewSurviveTimeSeconds: number
OriginalSurvivalTimeSeconds: number
ExitChanges: ExtractChange[]
}
export interface ExtractChange
{
Name: string
MinTime?: number
MaxTime?: number
Chance?: number
}

View File

@ -55,6 +55,7 @@ export enum BaseClasses
GRENADE_LAUNCHER = "5447bedf4bdc2d87278b4568", GRENADE_LAUNCHER = "5447bedf4bdc2d87278b4568",
SPECIAL_WEAPON = "5447bee84bdc2dc3278b4569", SPECIAL_WEAPON = "5447bee84bdc2dc3278b4569",
SPEC_ITEM = "5447e0e74bdc2d3c308b4567", SPEC_ITEM = "5447e0e74bdc2d3c308b4567",
SPRING_DRIVEN_CYLINDER = "627a137bf21bc425b06ab944",
KNIFE = "5447e1d04bdc2dff2f8b4567", KNIFE = "5447e1d04bdc2dff2f8b4567",
AMMO = "5485a8684bdc2da71d8b4567", AMMO = "5485a8684bdc2da71d8b4567",
AMMO_BOX = "543be5cb4bdc2deb348b4568", AMMO_BOX = "543be5cb4bdc2deb348b4568",

View File

@ -1,5 +1,6 @@
export enum ItemAddedResult export enum ItemAddedResult {
{ UNKNOWN = -1,
SUCCESS = 1, SUCCESS = 1,
NO_SPACE = 2, NO_SPACE = 2,
NO_CONTAINERS = 3
} }

View File

@ -29,6 +29,8 @@ export interface IBotConfig extends IBaseConfig
showTypeInNickname: boolean; showTypeInNickname: boolean;
/** What ai brain should a normal scav use per map */ /** What ai brain should a normal scav use per map */
assaultBrainType: Record<string, Record<string, number>>; assaultBrainType: Record<string, Record<string, number>>;
/** What ai brain should a player scav use per map */
playerScavBrainType: Record<string, Record<string, number>>;
/** Max number of bots that can be spawned in a raid at any one time */ /** Max number of bots that can be spawned in a raid at any one time */
maxBotCap: Record<string, number>; maxBotCap: Record<string, number>;
/** Chance scav has fake pscav name e.g. Scav name (player name) */ /** Chance scav has fake pscav name e.g. Scav name (player name) */

View File

@ -23,6 +23,8 @@ export interface IGameFixes
fixShotgunDispersion: boolean; fixShotgunDispersion: boolean;
/** Remove items added by mods when the mod no longer exists - can fix dead profiles stuck at game load*/ /** Remove items added by mods when the mod no longer exists - can fix dead profiles stuck at game load*/
removeModItemsFromProfile: boolean; removeModItemsFromProfile: boolean;
/** Fix issues that cause the game to not start due to inventory item issues */
fixProfileBreakingInventoryItemIssues: boolean;
} }
export interface IServerFeatures export interface IServerFeatures

View File

@ -38,6 +38,21 @@ export interface ILocationConfig extends IBaseConfig
allowDuplicateItemsInStaticContainers: boolean; allowDuplicateItemsInStaticContainers: boolean;
/** Key: map, value: loose loot ids to ignore */ /** Key: map, value: loose loot ids to ignore */
looseLootBlacklist: Record<string, string[]>; looseLootBlacklist: Record<string, string[]>;
/** Key: map, value: settings to control how long scav raids are*/
scavRaidTimeSettings: Record<string, IScavRaidTimeLocationSettings>;
}
export interface IScavRaidTimeLocationSettings
{
/** Should loot be reduced by same percent length of raid is reduced by */
reduceLootByPercent: boolean;
minStaticLootPercent: number;
minDynamicLootPercent: number;
/** Chance raid time is reduced */
reducedChancePercent: number;
reductionPercentWeights: Record<string, number>;
/** Should bot waves be removed / spawn times be adjusted */
adjustWaves: boolean;
} }
export interface IContainerRandomistionSettings export interface IContainerRandomistionSettings

View File

@ -0,0 +1,9 @@
export interface IRaidChanges
{
/** What percentage of dynamic loot should the map contain */
dynamicLootPercent: number
/** What percentage of static loot should the map contain */
staticLootPercent: number
/** How many seconds into the raid is the player simulated to spawn in at */
simulatedRaidStartSeconds: number
}

View File

@ -64,6 +64,14 @@ export class GameStaticRouter extends StaticRouter
return this.gameCallbacks.reportNickname(url, info, sessionID); return this.gameCallbacks.reportNickname(url, info, sessionID);
}, },
), ),
new RouteAction(
"/singleplayer/settings/getRaidTime",
// eslint-disable-next-line @typescript-eslint/no-unused-vars
(url: string, info: any, sessionID: string, output: string): any =>
{
return this.gameCallbacks.getRaidTime(url, info, sessionID);
},
)
]); ]);
} }
} }

View File

@ -237,9 +237,21 @@ export class ProfileFixerService
return; return;
} }
const stashItem = pmcProfile.Inventory.items?.find((x) => x._id === hideoutStandAreaDb._id); let stashItem = pmcProfile.Inventory.items?.find((x) => x._id === hideoutStandAreaDb._id);
if (!stashItem)
{
// Stand inventory stash item doesnt exist, add it
pmcProfile.Inventory.items.push(
{
_id: hideoutStandAreaDb._id,
_tpl: stageCurrentAt.container
}
)
stashItem = pmcProfile.Inventory.items?.find((x) => x._id === hideoutStandAreaDb._id)
}
// `hideoutAreaStashes` has value related stash inventory items tpl doesnt match what's expected // `hideoutAreaStashes` has value related stash inventory items tpl doesnt match what's expected
if (hideoutStandStashId && stashItem?._tpl !== stageCurrentAt.container) if (hideoutStandStashId && stashItem._tpl !== stageCurrentAt.container)
{ {
this.logger.debug( this.logger.debug(
`primary Stash tpl was: ${stashItem._tpl}, but should be ${stageCurrentAt.container}, updating`, `primary Stash tpl was: ${stashItem._tpl}, but should be ${stageCurrentAt.container}, updating`,
@ -248,7 +260,19 @@ export class ProfileFixerService
stashItem._tpl = stageCurrentAt.container; stashItem._tpl = stageCurrentAt.container;
} }
const stashSecondaryItem = pmcProfile.Inventory.items?.find((x) => x._id === hideoutStandSecondaryAreaDb._id); let stashSecondaryItem = pmcProfile.Inventory.items?.find((x) => x._id === hideoutStandSecondaryAreaDb._id);
if (!stashSecondaryItem)
{
// Stand inventory stash item doesnt exist, add it
pmcProfile.Inventory.items.push(
{
_id: hideoutStandSecondaryAreaDb._id,
_tpl: stageCurrentAt.container
}
)
stashSecondaryItem = pmcProfile.Inventory.items?.find((x) => x._id === hideoutStandSecondaryAreaDb._id)
}
// `hideoutAreaStashes` has value related stash inventory items tpl doesnt match what's expected // `hideoutAreaStashes` has value related stash inventory items tpl doesnt match what's expected
if (hideoutSecondaryStashId && stashSecondaryItem?._tpl !== stageCurrentAt.container) if (hideoutSecondaryStashId && stashSecondaryItem?._tpl !== stageCurrentAt.container)
{ {
@ -1072,7 +1096,8 @@ export class ProfileFixerService
public fixIncorrectAidValue(fullProfile: IAkiProfile): void public fixIncorrectAidValue(fullProfile: IAkiProfile): void
{ {
// Not a number, regenerate // Not a number, regenerate
if (Number.isNaN(fullProfile.characters.pmc.aid)) // biome-ignore lint/suspicious/noGlobalIsNan: <value can be a valid string, Number.IsNaN() would ignore it>
if (isNaN(fullProfile.characters.pmc.aid) || !fullProfile.info.aid)
{ {
fullProfile.characters.pmc.sessionId = <string><unknown>fullProfile.characters.pmc.aid; fullProfile.characters.pmc.sessionId = <string><unknown>fullProfile.characters.pmc.aid;
fullProfile.characters.pmc.aid = this.hashUtil.generateAccountId(); fullProfile.characters.pmc.aid = this.hashUtil.generateAccountId();
@ -1082,7 +1107,7 @@ export class ProfileFixerService
fullProfile.info.aid = fullProfile.characters.pmc.aid; fullProfile.info.aid = fullProfile.characters.pmc.aid;
this.logger.debug( this.logger.info(
`Migrated AccountId from: ${fullProfile.characters.pmc.sessionId} to: ${fullProfile.characters.pmc.aid}`, `Migrated AccountId from: ${fullProfile.characters.pmc.sessionId} to: ${fullProfile.characters.pmc.aid}`,
); );
} }

View File

@ -0,0 +1,240 @@
import { inject, injectable } from "tsyringe";
import { ApplicationContext } from "@spt-aki/context/ApplicationContext";
import { ContextVariableType } from "@spt-aki/context/ContextVariableType";
import { WeightedRandomHelper } from "@spt-aki/helpers/WeightedRandomHelper";
import { ILocationBase } from "@spt-aki/models/eft/common/ILocationBase";
import { IGetRaidTimeRequest } from "@spt-aki/models/eft/game/IGetRaidTimeRequest";
import { ExtractChange, IGetRaidTimeResponse } from "@spt-aki/models/eft/game/IGetRaidTimeResponse";
import { ConfigTypes } from "@spt-aki/models/enums/ConfigTypes";
import { ILocationConfig, IScavRaidTimeLocationSettings, LootMultiplier } from "@spt-aki/models/spt/config/ILocationConfig";
import { IRaidChanges } from "@spt-aki/models/spt/location/IRaidChanges";
import { ILogger } from "@spt-aki/models/spt/utils/ILogger";
import { ConfigServer } from "@spt-aki/servers/ConfigServer";
import { DatabaseServer } from "@spt-aki/servers/DatabaseServer";
import { RandomUtil } from "@spt-aki/utils/RandomUtil";
@injectable()
export class RaidTimeAdjustmentService
{
protected locationConfig: ILocationConfig;
constructor(
@inject("WinstonLogger") protected logger: ILogger,
@inject("DatabaseServer") protected databaseServer: DatabaseServer,
@inject("RandomUtil") protected randomUtil: RandomUtil,
@inject("WeightedRandomHelper") protected weightedRandomHelper: WeightedRandomHelper,
@inject("ApplicationContext") protected applicationContext: ApplicationContext,
@inject("ConfigServer") protected configServer: ConfigServer,
)
{
this.locationConfig = this.configServer.getConfig(ConfigTypes.LOCATION);
}
/**
* Make alterations to the base map data passed in
* Loot multipliers/waves/wave start times
* @param raidAdjustments Changes to process on map
* @param mapBase Map to adjust
*/
public makeAdjustmentsToMap(raidAdjustments: IRaidChanges, mapBase: ILocationBase): void
{
this.logger.debug(`Adjusting dynamic loot multipliers to ${raidAdjustments.dynamicLootPercent}% and static loot multipliers to ${raidAdjustments.staticLootPercent}% of original`);
// Change loot multipler values before they're used below
this.adjustLootMultipliers(this.locationConfig.looseLootMultiplier, raidAdjustments.dynamicLootPercent);
this.adjustLootMultipliers(this.locationConfig.staticLootMultiplier, raidAdjustments.staticLootPercent);
const mapSettings = this.getMapSettings(mapBase.Id);
if (mapSettings.adjustWaves)
{
// Make alterations to bot spawn waves now player is simulated spawning later
this.adjustWaves(mapBase, raidAdjustments)
}
}
/**
* Adjust the loot multiplier values passed in to be a % of their original value
* @param mapLootMultiplers Multiplers to adjust
* @param loosePercent Percent to change values to
*/
protected adjustLootMultipliers(mapLootMultiplers: LootMultiplier, loosePercent: number): void
{
for (const key in mapLootMultiplers)
{
mapLootMultiplers[key] = this.randomUtil.getPercentOfValue(mapLootMultiplers[key], loosePercent);
}
}
/**
* Adjust bot waves to act as if player spawned later
* @param mapBase map to adjust
* @param raidAdjustments Map adjustments
*/
protected adjustWaves(mapBase: ILocationBase, raidAdjustments: IRaidChanges): void
{
// Remove waves that spawned before the player joined
const originalWaveCount = mapBase.waves.length;
mapBase.waves = mapBase.waves.filter(x => x.time_max > raidAdjustments.simulatedRaidStartSeconds);
// Adjust wave min/max times to match new simulated start
for (const wave of mapBase.waves)
{
// Dont let time fall below 0
wave.time_min -= Math.max(raidAdjustments.simulatedRaidStartSeconds, 0);
wave.time_max -= Math.max(raidAdjustments.simulatedRaidStartSeconds, 0);
}
this.logger.debug(`Removed ${originalWaveCount - mapBase.waves.length} wave from map due to simulated raid start time of ${raidAdjustments.simulatedRaidStartSeconds / 60} minutes`);
}
/**
* Create a randomised adjustment to the raid based on map data in location.json
* @param sessionId Session id
* @param request Raid adjustment request
* @returns Response to send to client
*/
public getRaidAdjustments(sessionId: string, request: IGetRaidTimeRequest): IGetRaidTimeResponse
{
const db = this.databaseServer.getTables();
const mapBase: ILocationBase = db.locations[request.Location.toLowerCase()].base;
const baseEscapeTimeMinutes = mapBase.EscapeTimeLimit;
// Prep result object to return
const result: IGetRaidTimeResponse = {
RaidTimeMinutes: baseEscapeTimeMinutes,
ExitChanges: [],
NewSurviveTimeSeconds: null,
OriginalSurvivalTimeSeconds: db.globals.config.exp.match_end.survived_seconds_requirement
}
// Pmc raid, send default
if (request.Side.toLowerCase() === "pmc")
{
return result;
}
// We're scav adjust values
const mapSettings = this.getMapSettings(request.Location);
// Chance of reducing raid time for scav, not guaranteed
if (!this.randomUtil.getChance100(mapSettings.reducedChancePercent))
{
// Send default
return result;
}
// Get the weighted percent to reduce the raid time by
const chosenRaidReductionPercent = Number.parseInt(this.weightedRandomHelper.getWeightedValue<string>(
mapSettings.reductionPercentWeights,
));
// How many minutes raid will last
const newRaidTimeMinutes = Math.floor(this.randomUtil.reduceValueByPercent(baseEscapeTimeMinutes, chosenRaidReductionPercent));
// Time player spawns into the raid if it was online
const simulatedRaidStartTimeMinutes = baseEscapeTimeMinutes - newRaidTimeMinutes;
if (mapSettings.reduceLootByPercent)
{
// Store time reduction percent in app context so loot gen can pick it up later
this.applicationContext.addValue(ContextVariableType.RAID_ADJUSTMENTS,
{
dynamicLootPercent: Math.max(chosenRaidReductionPercent, mapSettings.minDynamicLootPercent),
staticLootPercent: Math.max(chosenRaidReductionPercent, mapSettings.minStaticLootPercent),
simulatedRaidStartSeconds: simulatedRaidStartTimeMinutes * 60
});
}
// Update result object with new time
result.RaidTimeMinutes = newRaidTimeMinutes;
this.logger.debug(`Reduced: ${request.Location} raid time by: ${chosenRaidReductionPercent}% to ${newRaidTimeMinutes} minutes`)
// Calculate how long player needs to be in raid to get a `survived` extract status
result.NewSurviveTimeSeconds = Math.max(result.OriginalSurvivalTimeSeconds - ((baseEscapeTimeMinutes - newRaidTimeMinutes) * 60), 0);
const exitAdjustments = this.getExitAdjustments(mapBase, newRaidTimeMinutes);
if (exitAdjustments)
{
result.ExitChanges.push(...exitAdjustments);
}
return result;
}
/**
* Get raid start time settings for specific map
* @param location Map Location e.g. bigmap
* @returns IScavRaidTimeLocationSettings
*/
protected getMapSettings(location: string): IScavRaidTimeLocationSettings
{
const mapSettings = this.locationConfig.scavRaidTimeSettings[location.toLowerCase()];
if (!mapSettings)
{
this.logger.warning(`Unable to find scav raid time settings for map: ${location}, using defaults`);
return this.locationConfig.scavRaidTimeSettings.default;
}
return mapSettings;
}
/**
* Adjust exit times to handle scavs entering raids part-way through
* @param mapBase Map base file player is on
* @param newRaidTimeMinutes How long raid is in minutes
* @returns List of exit changes to send to client
*/
protected getExitAdjustments(mapBase: ILocationBase, newRaidTimeMinutes: number): ExtractChange[]
{
const result = [];
// Adjust train exits only
for (const exit of mapBase.exits)
{
if (exit.PassageRequirement !== "Train")
{
continue;
}
// Prepare train adjustment object
const exitChange: ExtractChange = {
Name: exit.Name,
MinTime: null,
MaxTime: null,
Chance: null
}
// If raid is after last moment train can leave, assume train has already left, disable extract
const latestPossibleDepartureMinutes = (exit.MaxTime + exit.Count) / 60;
if (newRaidTimeMinutes < latestPossibleDepartureMinutes)
{
exitChange.Chance = 0;
this.logger.debug(`Train Exit: ${exit.Name} disabled as new raid time ${newRaidTimeMinutes} minutes is below ${latestPossibleDepartureMinutes} minutes`);
result.push(exitChange);
continue;
}
// What minute we simulate the player joining a raid at
const simulatedRaidEntryTimeMinutes = mapBase.EscapeTimeLimit - newRaidTimeMinutes;
// How many seconds to reduce extract arrival times by, negative values seem to make extract turn red in game
const reductionSeconds = simulatedRaidEntryTimeMinutes * 60;
exitChange.MinTime = exit.MinTime - reductionSeconds;
exitChange.MaxTime = exit.MaxTime - reductionSeconds;
this.logger.debug(`Train appears between: ${exitChange.MinTime} and ${exitChange.MaxTime} seconds raid time`);
result.push(exitChange);
}
return result.length > 0
? result
: null ;
}
}

View File

@ -34,6 +34,13 @@ export class HttpResponseUtil
return this.clearString(this.jsonUtil.serialize(data)); return this.clearString(this.jsonUtil.serialize(data));
} }
/**
* Game client needs server responses in a particular format
* @param data
* @param err
* @param errmsg
* @returns
*/
public getBody<T>(data: T, err = 0, errmsg = null): IGetBodyResponseData<T> public getBody<T>(data: T, err = 0, errmsg = null): IGetBodyResponseData<T>
{ {
return this.clearString(this.getUnclearedBody(data, err, errmsg)); return this.clearString(this.getUnclearedBody(data, err, errmsg));

View File

@ -231,6 +231,18 @@ export class RandomUtil
return Number.parseFloat(((percent * number) / 100).toFixed(toFixed)); return Number.parseFloat(((percent * number) / 100).toFixed(toFixed));
} }
/**
* Reduce a value by a percentage
* @param number Value to reduce
* @param percentage Percentage to reduce value by
* @returns Reduced value
*/
public reduceValueByPercent(number: number, percentage: number): number
{
const reductionAmount = number * (percentage / 100);
return number - reductionAmount;
}
/** /**
* Check if number passes a check out of 100 * Check if number passes a check out of 100
* @param chancePercent value check needs to be above * @param chancePercent value check needs to be above