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
}
},
"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": {
"factory4_day": 13,
"factory4_night": 13,

View File

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

View File

@ -793,5 +793,192 @@
"minFillStaticMagazinePercent": 50,
"makeWishingTreeAlwaysGiveGift": 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,
"endTimestamp": 1703980800000,
"yearly": true
},
"655e427b64d09b4122018228": {
"name": "The punisher - Harvest",
"season": "None",
"startTimestamp": 1341615600000,
"endTimestamp": "",
"yearly": false
}
},
"repeatableQuests": [{

View File

@ -2511,7 +2511,8 @@
}
},
"health": {
"BodyParts": [{
"BodyParts": [
{
"Chest": {
"max": 85,
"min": 85
@ -2879,6 +2880,7 @@
"57c44b372459772d2b39b8ce": 5,
"57d14d2524597714373db789": 1,
"57dc2fa62459775949412633": 5,
"57f3c6bd24597738e730fa2f": 1,
"583990e32459771419544dd2": 4,
"5839a40f24597726f856b511": 5,
"587e02ff24597743df3deaeb": 5,
@ -4939,6 +4941,22 @@
"560d657b4bdc2da74d8b4572"
]
},
"57f3c6bd24597738e730fa2f": {
"mod_magazine": [
"57d1519e24597714373db79d"
],
"mod_muzzle": [
"57f3c7e024597738ea4ba286"
],
"mod_pistol_grip": [
"57d152ec245977144076ccdf"
]
},
"57f3c7e024597738ea4ba286": {
"mod_muzzle": [
"57f3c8cc2459773ec4480328"
]
},
"57ffa9f4245977728561e844": {
"mod_foregrip": [
"5c1bc4812e22164bef5cfde7",

View File

@ -2508,7 +2508,8 @@
}
},
"health": {
"BodyParts": [{
"BodyParts": [
{
"Chest": {
"max": 85,
"min": 85
@ -2876,6 +2877,7 @@
"57c44b372459772d2b39b8ce": 5,
"57d14d2524597714373db789": 1,
"57dc2fa62459775949412633": 5,
"57f3c6bd24597738e730fa2f": 1,
"583990e32459771419544dd2": 4,
"5839a40f24597726f856b511": 5,
"587e02ff24597743df3deaeb": 5,
@ -4936,6 +4938,22 @@
"560d657b4bdc2da74d8b4572"
]
},
"57f3c6bd24597738e730fa2f": {
"mod_magazine": [
"57d1519e24597714373db79d"
],
"mod_muzzle": [
"57f3c7e024597738ea4ba286"
],
"mod_pistol_grip": [
"57d152ec245977144076ccdf"
]
},
"57f3c7e024597738ea4ba286": {
"mod_muzzle": [
"57f3c8cc2459773ec4480328"
]
},
"57ffa9f4245977728561e844": {
"mod_foregrip": [
"5c1bc4812e22164bef5cfde7",

View File

@ -84042,5 +84042,403 @@
"templateId": "64f83bd983cfca080a362c82",
"traderId": "5a7c2eca46aef81a7ca2145d",
"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",
"upd": {
"BuyRestrictionMax": 10,
"StackObjectsCount": 1500
"StackObjectsCount": 20
}
},
{

View File

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

View File

@ -1,41 +1,47 @@
{
"fail": {},
"started": {},
"success": {
"6492e44bf4287b13040fca51": "60e71ce009d7c801eb0c0ec6",
"6507ff2a644a656aee0f7fbd": "5a27b7d686f77460d847e6a6",
"6507ff2a644a656aee0f7fc6": "5a27bbf886f774333a418eeb",
"6507ff2a644a656aee0f7fce": "5a27bb3d86f77411ea361a21",
"6507ff2a644a656aee0f800a": "5edac020218d181e29451446",
"6507ff2a644a656aee0f800e": "5c0d4e61d09282029f53920e",
"6507ff2a644a656aee0f8029": "5a27b80086f774429a5d7e20",
"6507ff2b644a656aee0f8089": "5a27ba9586f7741b543d8e85",
"6507ff2b644a656aee0f809e": "5a03173786f77451cb427172",
"6507ff2b644a656aee0f80df": "5a27b9de86f77464e5044585",
"6507ff2b644a656aee0f80e3": "5b477f7686f7744d1b23c4d2",
"6507ff2b644a656aee0f8107": "5a27bc6986f7741c7358402b",
"6507ff2b644a656aee0f8159": "5a27b75b86f7742e97191958",
"6507ff2b644a656aee0f816b": "6179aff8f57fb279792c60a1",
"6507ff2b644a656aee0f8179": "5c0d4e61d09282029f53920e",
"6507ff2b644a656aee0f817d": "5a27bc1586f7741f6d40fa2f",
"6507ff2b644a656aee0f81d3": "5a27b75b86f7742e97191958",
"6507ff2b644a656aee0f81f2": "5c0d4e61d09282029f53920e",
"6507ff2b644a656aee0f8201": "5c0d0f1886f77457b8210226",
"6507ff2b644a656aee0f8159": "5a27b75b86f7742e97191958",
"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",
"6507ff2c644a656aee0f82a2": "5edac63b930f5454f51e128b",
"6507ff2c644a656aee0f82b2": "5ac244c486f77413e12cf945",
"6507ff2c644a656aee0f830d": "6179b4d1bca27a099552e04e",
"6507ff2c644a656aee0f8335": "639872fe8871e1272b10ccf6",
"6507ff2c644a656aee0f8353": "5a27bc3686f7741c73584026",
"6507ff2d644a656aee0f835c": "5b47825886f77468074618d3",
"6507ff2d644a656aee0f838c": "63a9b229813bba58a50c9ee5",
"6507ff2d644a656aee0f83b1": "639135f286e646067c176a87",
"6507ff2d644a656aee0f843e": "5a27bc8586f7741b543d8ea4",
"6507ff2d644a656aee0f845f": "5a27bc6986f7741c7358402b",
"6507ff2d644a656aee0f848d": "5c0d4c12d09282029f539173",
"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 { IGameLogoutResponseData } from "@spt-aki/models/eft/game/IGameLogoutResponseData";
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 { IServerDetails } from "@spt-aki/models/eft/game/IServerDetails";
import { IVersionValidateRequestData } from "@spt-aki/models/eft/game/IVersionValidateRequestData";
@ -147,4 +148,14 @@ export class GameCallbacks implements OnLoad
{
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;
/**
* @examples
* Called like:
*
* const registerPlayerInfo = this.applicationContext.getLatestValue(ContextVariableType.REGISTER_PLAYER_REQUEST)
* .getValue<IRegisterPlayerRequestData>();
* const registerPlayerInfo = this.applicationContext.getLatestValue(ContextVariableType.REGISTER_PLAYER_REQUEST).getValue<IRegisterPlayerRequestData>();
*
* const activePlayerSessionId = this.applicationContext.getLatestValue(ContextVariableType.SESSION_ID)
* .getValue<string>();
* const activePlayerSessionId = this.applicationContext.getLatestValue(ContextVariableType.SESSION_ID).getValue<string>();
*
* const matchInfo = this.applicationContext.getLatestValue(ContextVariableType.MATCH_INFO)
* .getValue<IStartOfflineRaidRequestData>();
* const matchInfo = this.applicationContext.getLatestValue(ContextVariableType.RAID_CONFIGURATION).getValue<IGetRaidConfigurationRequestData>();
* @param type
* @returns
*/
@ -30,6 +27,7 @@ export class ApplicationContext
{
return this.variables.get(type)?.getTail()?.getValue();
}
return undefined;
}
@ -39,6 +37,7 @@ export class ApplicationContext
{
return this.variables.get(type).toList();
}
return undefined;
}
@ -62,4 +61,12 @@ export class ApplicationContext
list.add(new ContextVariable(value, type));
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
{
SESSION_ID = 0, // Logged in users session id
RAID_CONFIGURATION = 1, // Currently active raid information
CLIENT_START_TIMESTAMP = 2, // Timestamp when client first connected
REGISTER_PLAYER_REQUEST = 3, // When player is loading into map and loot is requested
export enum ContextVariableType {
/** Logged in users session id */
SESSION_ID = 0,
/** Currently acive raid information */
RAID_CONFIGURATION = 1,
/** 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
{
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 { IGameConfigResponse } from "@spt-aki/models/eft/game/IGameConfigResponse";
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 { IAkiProfile } from "@spt-aki/models/eft/profile/IAkiProfile";
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 { OpenZoneService } from "@spt-aki/services/OpenZoneService";
import { ProfileFixerService } from "@spt-aki/services/ProfileFixerService";
import { RaidTimeAdjustmentService } from "@spt-aki/services/RaidTimeAdjustmentService";
import { SeasonalEventService } from "@spt-aki/services/SeasonalEventService";
import { HashUtil } from "@spt-aki/utils/HashUtil";
import { JsonUtil } from "@spt-aki/utils/JsonUtil";
import { RandomUtil } from "@spt-aki/utils/RandomUtil";
import { TimeUtil } from "@spt-aki/utils/TimeUtil";
@ -56,6 +60,7 @@ export class GameController
@inject("DatabaseServer") protected databaseServer: DatabaseServer,
@inject("JsonUtil") protected jsonUtil: JsonUtil,
@inject("TimeUtil") protected timeUtil: TimeUtil,
@inject("HashUtil") protected hashUtil: HashUtil,
@inject("PreAkiModLoader") protected preAkiModLoader: PreAkiModLoader,
@inject("HttpServerHelper") protected httpServerHelper: HttpServerHelper,
@inject("RandomUtil") protected randomUtil: RandomUtil,
@ -68,6 +73,7 @@ export class GameController
@inject("SeasonalEventService") protected seasonalEventService: SeasonalEventService,
@inject("ItemBaseClassService") protected itemBaseClassService: ItemBaseClassService,
@inject("GiftService") protected giftService: GiftService,
@inject("RaidTimeAdjustmentService") protected raidTimeAdjustmentService: RaidTimeAdjustmentService,
@inject("ApplicationContext") protected applicationContext: ApplicationContext,
@inject("ConfigServer") protected configServer: ConfigServer,
)
@ -127,10 +133,21 @@ export class GameController
if (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;
this.logger.debug(`Started game with sessionId: ${sessionID} ${pmcProfile.Info?.Nickname}`);
if (this.coreConfig.fixes.fixProfileBreakingInventoryItemIssues)
{
this.fixProfileBreakingInventoryItemIssues(pmcProfile)
}
if (pmcProfile.Health)
{
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
*/
@ -464,6 +554,14 @@ export class GameController
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
*/

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

View File

@ -1,5 +1,7 @@
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 { LootGenerator } from "@spt-aki/generators/LootGenerator";
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 { IAirdropConfig } from "@spt-aki/models/spt/config/IAirdropConfig";
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 { LootRequest } from "@spt-aki/models/spt/services/LootRequest";
import { ILogger } from "@spt-aki/models/spt/utils/ILogger";
import { ConfigServer } from "@spt-aki/servers/ConfigServer";
import { DatabaseServer } from "@spt-aki/servers/DatabaseServer";
import { LocalisationService } from "@spt-aki/services/LocalisationService";
import { RaidTimeAdjustmentService } from "@spt-aki/services/RaidTimeAdjustmentService";
import { HashUtil } from "@spt-aki/utils/HashUtil";
import { JsonUtil } from "@spt-aki/utils/JsonUtil";
import { RandomUtil } from "@spt-aki/utils/RandomUtil";
@ -38,10 +42,12 @@ export class LocationController
@inject("WinstonLogger") protected logger: ILogger,
@inject("LocationGenerator") protected locationGenerator: LocationGenerator,
@inject("LocalisationService") protected localisationService: LocalisationService,
@inject("RaidTimeAdjustmentService") protected raidTimeAdjustmentService: RaidTimeAdjustmentService,
@inject("LootGenerator") protected lootGenerator: LootGenerator,
@inject("DatabaseServer") protected databaseServer: DatabaseServer,
@inject("TimeUtil") protected timeUtil: TimeUtil,
@inject("ConfigServer") protected configServer: ConfigServer,
@inject("ApplicationContext") protected applicationContext: ApplicationContext,
)
{
this.airdropConfig = this.configServer.getConfig(ConfigTypes.AIRDROP);
@ -83,10 +89,19 @@ export class LocationController
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);
// 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);
// Add dynamic loot to output loot
@ -107,6 +122,16 @@ export class LocationController
);
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;
}

View File

@ -133,7 +133,7 @@ export class QuestController
break;
}
// Prerequisite does not have its status requirement fulfilled
// Prereq does not have its status requirement fulfilled
if (!conditionToFulfil._props.status.includes(prerequisiteQuest.status))
{
haveCompletedPreviousQuest = false;
@ -289,7 +289,7 @@ export class QuestController
* @param pmcData Profile to update
* @param acceptedQuest Quest accepted
* @param sessionID Session id
* @returns client response
* @returns Client response
*/
public acceptQuest(
pmcData: IPmcData,
@ -299,30 +299,36 @@ export class QuestController
{
const acceptQuestResponse = this.eventOutputHolder.getOutput(sessionID);
const startedState = QuestStatus.Started;
const newQuest = this.questHelper.getQuestReadyForProfile(pmcData, startedState, acceptedQuest);
// 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
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
{
// Add new quest to server profile
const newQuest = this.questHelper.getQuestReadyForProfile(pmcData, QuestStatus.Started, acceptedQuest);
pmcData.Quests.push(newQuest);
}
// Create a dialog message for starting the quest.
// Note that for starting quests, the correct locale field is "description", not "startedMessageText".
const questFromDb = this.questHelper.getQuestFromDb(acceptedQuest.qid, pmcData);
// Get messageId of text to send to player as text message in game
const messageId = this.questHelper.getMessageIdForQuestStart(
questFromDb.startedMessageText,
questFromDb.description,
);
const startedQuestRewards = this.questHelper.applyQuestReward(
// Apply non-item rewards to profile + return item rewards
const startedQuestRewardItems = this.questHelper.applyQuestReward(
pmcData,
acceptedQuest.qid,
QuestStatus.Started,
@ -330,17 +336,19 @@ export class QuestController
acceptQuestResponse,
);
// Send started text + any starting reward items found above to player
this.mailSendService.sendLocalisedNpcMessageToPlayer(
sessionID,
this.traderHelper.getTraderById(questFromDb.traderId),
MessageType.QUEST_START,
messageId,
startedQuestRewards,
startedQuestRewardItems,
this.timeUtil.getHoursAsSeconds(this.questConfig.redeemTime),
);
acceptQuestResponse.profileChanges[sessionID].quests = this.questHelper
.getNewlyAccessibleQuestsWhenStartingQuest(acceptedQuest.qid, sessionID);
// Having accepted new quest, look for newly unlocked quests and inform client of them
acceptQuestResponse.profileChanges[sessionID].quests.push(...this.questHelper
.getNewlyAccessibleQuestsWhenStartingQuest(acceptedQuest.qid, sessionID));
return acceptQuestResponse;
}
@ -362,10 +370,11 @@ export class QuestController
{
const acceptQuestResponse = this.eventOutputHolder.getOutput(sessionID);
const desiredQuestState = QuestStatus.Started;
const newQuest = this.questHelper.getQuestReadyForProfile(pmcData, desiredQuestState, acceptedQuest);
pmcData.Quests.push(newQuest);
// Create and store quest status object inside player profile
const newRepeatableQuest = this.questHelper.getQuestReadyForProfile(pmcData, QuestStatus.Started, acceptedQuest);
pmcData.Quests.push(newRepeatableQuest);
// Look for the generated quest cache in profile.RepeatableQuests
const repeatableQuestProfile = this.getRepeatableQuestFromProfile(pmcData, acceptedQuest);
if (!repeatableQuestProfile)
{
@ -391,60 +400,13 @@ export class QuestController
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) =>
x.name === repeatableQuestProfile.sptRepatableGroupName
);
const change = {};
change[repeatableQuestProfile._id] = repeatableSettings.changeRequirement[repeatableQuestProfile._id];
const responseData: IPmcDataRepeatableQuest = {
@ -457,7 +419,12 @@ export class QuestController
activeQuests: [repeatableQuestProfile],
inactiveQuests: [],
};
acceptQuestResponse.profileChanges[sessionID].repeatableQuests = [responseData];
if (!acceptQuestResponse.profileChanges[sessionID].repeatableQuests)
{
acceptQuestResponse.profileChanges[sessionID].repeatableQuests = []
}
acceptQuestResponse.profileChanges[sessionID].repeatableQuests.push(responseData);
return acceptQuestResponse;
}
@ -536,7 +503,7 @@ export class QuestController
this.addTimeLockedQuestsToProfile(pmcData, [...questDelta, ...questsToFail], body.qid);
// 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
for (const currentRepeatable of pmcData.RepeatableQuests)
@ -593,12 +560,12 @@ export class QuestController
/**
* Return quests that have different statuses
* @param preQuestStatuses Quests before
* @param preQuestStatusus Quests before
* @param postQuestStatuses Quests after
* @returns QuestStatusChange array
*/
protected getQuestsWithDifferentStatuses(
preQuestStatuses: IQuestStatus[],
preQuestStatusus: IQuestStatus[],
postQuestStatuses: IQuestStatus[],
): IQuestStatus[]
{
@ -607,7 +574,7 @@ export class QuestController
for (const quest of postQuestStatuses)
{
// 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)
{
result.push(quest);
@ -659,7 +626,7 @@ export class QuestController
// Iterate over quests, look for quests with right criteria
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) =>
x._props.target === completedQuestId && x._props.availableAfter > 0
);
@ -686,7 +653,8 @@ export class QuestController
startTime: 0,
status: QuestStatus.AvailableAfter,
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,
});
@ -832,7 +800,7 @@ export class QuestController
const matchingItemInProfile = pmcData.Inventory.items.find((x) => x._id === itemHandover.id);
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(
handoverQuestRequest,
matchingItemInProfile,

View File

@ -117,21 +117,24 @@ export class RepeatableQuestController
// for (let i = 0; i < currentRepeatable.activeQuests.length; i++)
for (const activeQuest of currentRepeatableQuestType.activeQuests)
{
// check if the quest is ready to be completed, if so, don't remove it
const quest = pmcData.Quests.filter((q) => q.qid === activeQuest._id);
if (quest.length > 0)
// Keep finished quests in list so player can hand in
const quest = pmcData.Quests.find(quest => quest.qid === activeQuest._id);
if (quest)
{
if (quest[0].status === QuestStatus.AvailableForFinish)
if (quest.status === QuestStatus.AvailableForFinish)
{
questsToKeep.push(activeQuest);
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;
}
}
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.activeQuests = questsToKeep;
@ -455,7 +458,11 @@ export class RepeatableQuestController
droppedQuestTrader.standing -= changeRequirement.changeStandingCost;
// 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;
}

View File

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

View File

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

View File

@ -352,13 +352,21 @@ export class BotLootGenerator
itemsToAdd,
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++;
if (fitItemIntoContainerAttempts >= 4)
{
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;
@ -366,6 +374,7 @@ export class BotLootGenerator
}
else
{
// Reset counter
fitItemIntoContainerAttempts = 0;
}
@ -426,13 +435,18 @@ export class BotLootGenerator
isPmc,
botLevel,
);
this.botWeaponGeneratorHelper.addItemWithChildrenToEquipmentSlot(
const result = this.botWeaponGeneratorHelper.addItemWithChildrenToEquipmentSlot(
[equipmentSlot],
generatedWeapon.weapon[0]._id,
generatedWeapon.weapon[0]._tpl,
[...generatedWeapon.weapon],
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);
}
// Add cartridge to gun chamber if weapon has slot for it
// Add cartridge(s) to gun chamber(s)
if (
weaponItemTemplate._props.Chambers?.length === 1
&& weaponItemTemplate._props.Chambers[0]?._name === "patron_in_weapon"
weaponItemTemplate._props.Chambers?.length > 0
&& 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
@ -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 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
const existingItemWithSlot = weaponWithModsArray.find((x) => x.slotId === desiredSlotId);
for (const slotId of chamberSlotIds)
{
const existingItemWithSlot = weaponWithModsArray.find((x) => x.slotId === slotId);
if (!existingItemWithSlot)
{
// Not found, add fresh
// Not found, add new slot to weapon
weaponWithModsArray.push({
_id: this.hashUtil.generate(),
_tpl: ammoTpl,
parentId: weaponWithModsArray[0]._id,
slotId: desiredSlotId,
slotId: slotId,
upd: { StackObjectsCount: 1 },
});
}
else
{
// Already exists, update values
existingItemWithSlot.upd = { StackObjectsCount: 1 };
existingItemWithSlot._tpl = ammoTpl;
existingItemWithSlot.upd = { StackObjectsCount: 1 };
}
}
}
@ -612,7 +616,7 @@ export class BotWeaponGenerator
{
const possibleAmmo = this.weightedRandomHelper.getWeightedValue<string>(compatibleCartridges);
// Check compatibility
// Weapon has chamber but does not support cartridge
if (weaponTemplate._props.Chambers[0]
&& !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))
{
// Fill with cartrdiges
// Fill with cartridges
const ammoBoxTemplate = this.itemHelper.getItem(chosenTpl)[1];
const ammoBoxItem: Item[] = [{ _id: this.objectId.generate(), _tpl: chosenTpl }];
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 { AccountTypes } from "@spt-aki/models/enums/AccountTypes";
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 { Traders } from "@spt-aki/models/enums/Traders";
import { IPlayerScavConfig, KarmaLevel } from "@spt-aki/models/spt/config/IPlayerScavConfig";
@ -125,13 +126,18 @@ export class PlayerScavGenerator
_tpl: labsCard._id,
...this.botGeneratorHelper.generateExtraPropertiesForItem(labsCard),
}];
this.botWeaponGeneratorHelper.addItemWithChildrenToEquipmentSlot(
const result = this.botWeaponGeneratorHelper.addItemWithChildrenToEquipmentSlot(
["TacticalVest", "Pockets", "Backpack"],
itemsToAdd[0]._id,
labsCard._id,
itemsToAdd,
scavData.Inventory,
);
if (result !== ItemAddedResult.SUCCESS)
{
this.logger.debug(`Unable to add keycard to bot. Reason: ${ItemAddedResult[result]}`);
}
}
// 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 { BotWeaponGeneratorHelper } from "@spt-aki/helpers/BotWeaponGeneratorHelper";
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 { ItemAddedResult } from "@spt-aki/models/enums/ItemAddedResult";
import { ILogger } from "@spt-aki/models/spt/utils/ILogger";
import { LocalisationService } from "@spt-aki/services/LocalisationService";
import { RandomUtil } from "@spt-aki/utils/RandomUtil";
@injectable()
export class ExternalInventoryMagGen implements IInventoryMagGen
@ -17,6 +19,7 @@ export class ExternalInventoryMagGen implements IInventoryMagGen
@inject("ItemHelper") protected itemHelper: ItemHelper,
@inject("LocalisationService") protected localisationService: LocalisationService,
@inject("BotWeaponGeneratorHelper") protected botWeaponGeneratorHelper: BotWeaponGeneratorHelper,
@inject("RandomUtil") protected randomUtil: RandomUtil
)
{}
@ -25,6 +28,7 @@ export class ExternalInventoryMagGen implements IInventoryMagGen
return 99;
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
canHandleInventoryMagGen(inventoryMagGen: InventoryMagGen): boolean
{
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
{
// Cout of attempts to fit a magazine into bot inventory
let fitAttempts = 0;
// Magazine Db template
let magTemplate = inventoryMagGen.getMagazineTemplate();
let magazineTpl = magTemplate._id;
const weapon = inventoryMagGen.getWeaponTemplate();
const attemptedMagBlacklist: string[] = [];
const defaultMagazineTpl = this.botWeaponGeneratorHelper.getWeaponsDefaultMagazineTpl(weapon);
const randomizedMagazineCount = Number(
this.botWeaponGeneratorHelper.getRandomizedMagazineCount(inventoryMagGen.getMagCount()),
);
@ -45,7 +56,7 @@ export class ExternalInventoryMagGen implements IInventoryMagGen
magTemplate,
);
const ableToFitMagazinesIntoBotInventory = this.botWeaponGeneratorHelper.addItemWithChildrenToEquipmentSlot(
const fitsIntoInventory = this.botWeaponGeneratorHelper.addItemWithChildrenToEquipmentSlot(
[EquipmentSlots.TACTICAL_VEST, EquipmentSlots.POCKETS],
magazineWithAmmo[0]._id,
magazineTpl,
@ -53,42 +64,104 @@ export class ExternalInventoryMagGen implements IInventoryMagGen
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
// and try again. Temporary workaround to Killa spawning with no extras if he spawns with a drum mag.
// TODO: Fix this properly
if (
magazineTpl
=== this.botWeaponGeneratorHelper.getWeaponsDefaultMagazineTpl(
inventoryMagGen.getWeaponTemplate(),
)
)
// No containers to fit magazines, stop trying
break;
}
// No space for magazine and we haven't reached desired magazine count
else if (fitsIntoInventory === ItemAddedResult.NO_SPACE && i < randomizedMagazineCount)
{
// 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
break;
}
// Get default magazine tpl, reset loop counter by 1 and try again
magazineTpl = this.botWeaponGeneratorHelper.getWeaponsDefaultMagazineTpl(
inventoryMagGen.getWeaponTemplate(),
);
// Add failed magazine tpl to blacklist
attemptedMagBlacklist.push(magazineTpl);
// 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];
if (!magTemplate)
{
this.logger.error(
this.localisationService.getText("bot-unable_to_find_default_magazine_item", magazineTpl),
);
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")
{
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--;
}
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;
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
chamberBulletCount = magTemplate._props.Slots.length;
const firstSlotAmmoTpl = magTemplate._props.Cartridges[0]._props.filters[0].Filter[0];
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)
{
// underbarrel launchers can only have 1 chambered grenade
// Underbarrel launchers can only have 1 chambered grenade
chamberBulletCount = 1;
}
else
@ -74,7 +77,7 @@ export class BotWeaponGeneratorHelper
// const range = magCounts.max - magCounts.min;
// 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,
], 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,
): ItemAddedResult
{
let missingContainerCount = 0;
for (const slot of equipmentSlots)
{
// Get container to put item into
const container = inventory.items.find((i) => i.slotId === slot);
if (!container)
{
// Desired equipment container (e.g. backpack) not found
missingContainerCount++;
if (missingContainerCount === equipmentSlots.length)
{
// Bot doesnt have any containers
this.logger.debug(
`Unable to add item: ${
itemWithChildren[0]._tpl
} to: ${slot}, slot missing/bot generated without equipment`,
} to bot as it lacks the following containers: ${equipmentSlots.join(",")}`,
);
return ItemAddedResult.NO_CONTAINERS
}
continue;
}
@ -194,8 +211,12 @@ export class BotWeaponGeneratorHelper
continue;
}
// Get x/y grid size of item
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)
{
// Grid is empty, skip
@ -210,13 +231,13 @@ export class BotWeaponGeneratorHelper
continue;
}
// Get all base level items in backpack
const containerItems = inventory.items.filter((i) =>
// Get all root items in backpack
const existingContainerItems = inventory.items.filter((i) =>
i.parentId === container._id && i.slotId === slotGrid._name
);
// 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)
{
// 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);
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(
slotGrid._props.cellsH,
slotGrid._props.cellsV,
containerItems,
existingContainerItems,
container._id,
);
// Try to fit item into grid
const findSlotResult = this.containerHelper.findSlotForItem(slotGridMap, itemSize[0], itemSize[1]);
@ -257,11 +279,18 @@ export class BotWeaponGeneratorHelper
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
}
}
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
// if count is 0, it means after this move the reward array will be empty and all rewards collected
const rewardItemCount = message.items.data.filter((x) => x._id !== itemId);
// If count is 0, it means after this move occurs the reward array will be empty and all rewards collected
if (!message.items.data)
{
message.items.data = [];
}
const rewardItemCount = message.items.data?.filter((item) => item._id !== itemId);
if (rewardItemCount.length === 0)
{
message.rewardCollected = true;

View File

@ -240,8 +240,11 @@ export class InRaidHelper
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
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);
if (existingActiveQuestIds)
{
scavData.Quests = saveProgressRequest.profile.Quests.filter(x => existingActiveQuestIds.includes(x.qid));
}
this.profileFixerService.checkForAndFixScavProfileIssues(scavData);
}

View File

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

View File

@ -345,13 +345,14 @@ export class QuestHelper
acceptedQuest: IAcceptQuestRequestData,
): IQuestStatus
{
const currentTimestamp = this.timeUtil.getTimestamp();
const existingQuest = pmcData.Quests.find((q) => q.qid === acceptedQuest.qid);
if (existingQuest)
{
// Quest exists, update its status
existingQuest.startTime = this.timeUtil.getTimestamp();
existingQuest.startTime = currentTimestamp;
existingQuest.status = newState;
existingQuest.statusTimers[newState] = this.timeUtil.getTimestamp();
existingQuest.statusTimers[newState] = currentTimestamp;
existingQuest.completedConditions = [];
if (existingQuest.availableAfter)
@ -365,7 +366,7 @@ export class QuestHelper
// Quest doesn't exists, add it
const newQuest: IQuestStatus = {
qid: acceptedQuest.qid,
startTime: this.timeUtil.getTimestamp(),
startTime: currentTimestamp,
status: newState,
statusTimers: {},
};
@ -378,11 +379,11 @@ export class QuestHelper
// Quest should be put into 'pending' state
newQuest.startTime = 0;
newQuest.status = QuestStatus.AvailableAfter; // 9
newQuest.availableAfter = this.timeUtil.getTimestamp() + waitTime._props.availableAfter;
newQuest.availableAfter = currentTimestamp + waitTime._props.availableAfter;
}
else
{
newQuest.statusTimers[newState.toString()] = this.timeUtil.getTimestamp();
newQuest.statusTimers[newState.toString()] = currentTimestamp;
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
* @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",
SPECIAL_WEAPON = "5447bee84bdc2dc3278b4569",
SPEC_ITEM = "5447e0e74bdc2d3c308b4567",
SPRING_DRIVEN_CYLINDER = "627a137bf21bc425b06ab944",
KNIFE = "5447e1d04bdc2dff2f8b4567",
AMMO = "5485a8684bdc2da71d8b4567",
AMMO_BOX = "543be5cb4bdc2deb348b4568",

View File

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

View File

@ -29,6 +29,8 @@ export interface IBotConfig extends IBaseConfig
showTypeInNickname: boolean;
/** What ai brain should a normal scav use per map */
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 */
maxBotCap: Record<string, number>;
/** Chance scav has fake pscav name e.g. Scav name (player name) */

View File

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

View File

@ -38,6 +38,21 @@ export interface ILocationConfig extends IBaseConfig
allowDuplicateItemsInStaticContainers: boolean;
/** Key: map, value: loose loot ids to ignore */
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

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);
},
),
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;
}
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
if (hideoutStandStashId && stashItem?._tpl !== stageCurrentAt.container)
if (hideoutStandStashId && stashItem._tpl !== stageCurrentAt.container)
{
this.logger.debug(
`primary Stash tpl was: ${stashItem._tpl}, but should be ${stageCurrentAt.container}, updating`,
@ -248,7 +260,19 @@ export class ProfileFixerService
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
if (hideoutSecondaryStashId && stashSecondaryItem?._tpl !== stageCurrentAt.container)
{
@ -1072,7 +1096,8 @@ export class ProfileFixerService
public fixIncorrectAidValue(fullProfile: IAkiProfile): void
{
// 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.aid = this.hashUtil.generateAccountId();
@ -1082,7 +1107,7 @@ export class ProfileFixerService
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}`,
);
}

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));
}
/**
* 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>
{
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));
}
/**
* 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
* @param chancePercent value check needs to be above