mirror of
https://github.com/sp-tarkov/server.git
synced 2025-02-12 17:30:42 -05:00
Apply Biome Formatting
This is the result of running `npm run format` which applies the Biome formatting rules. Rejoice!
This commit is contained in:
parent
7c76342ee2
commit
5740774a46
@ -15,14 +15,10 @@
|
||||
"keepClassNames": true,
|
||||
"baseUrl": "./",
|
||||
"paths": {
|
||||
"@spt/*": [
|
||||
"src/*"
|
||||
]
|
||||
"@spt/*": ["src/*"]
|
||||
}
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules/"
|
||||
],
|
||||
"exclude": ["node_modules/"],
|
||||
"module": {
|
||||
"type": "commonjs",
|
||||
"strict": false,
|
||||
|
@ -104,12 +104,7 @@
|
||||
"max": 50
|
||||
}
|
||||
},
|
||||
"armorLevelWhitelist": [
|
||||
0,
|
||||
4,
|
||||
5,
|
||||
6
|
||||
],
|
||||
"armorLevelWhitelist": [0, 4, 5, 6],
|
||||
"allowBossItems": false
|
||||
},
|
||||
"weaponArmor": {
|
||||
@ -181,13 +176,7 @@
|
||||
"max": 50
|
||||
}
|
||||
},
|
||||
"armorLevelWhitelist": [
|
||||
0,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6
|
||||
],
|
||||
"armorLevelWhitelist": [0, 3, 4, 5, 6],
|
||||
"allowBossItems": false
|
||||
},
|
||||
"foodMedical": {
|
||||
@ -267,9 +256,7 @@
|
||||
"max": 50
|
||||
}
|
||||
},
|
||||
"armorLevelWhitelist": [
|
||||
0
|
||||
],
|
||||
"armorLevelWhitelist": [0],
|
||||
"allowBossItems": false
|
||||
},
|
||||
"barter": {
|
||||
@ -350,9 +337,7 @@
|
||||
"max": 50
|
||||
}
|
||||
},
|
||||
"armorLevelWhitelist": [
|
||||
0
|
||||
],
|
||||
"armorLevelWhitelist": [0],
|
||||
"allowBossItems": false
|
||||
}
|
||||
}
|
||||
|
@ -277,12 +277,9 @@
|
||||
},
|
||||
"chanceAssaultScavHasPlayerScavName": 10,
|
||||
"secureContainerAmmoStackCount": 20,
|
||||
"botRolesWithDogTags": [
|
||||
"pmcbear",
|
||||
"pmcusec"
|
||||
],
|
||||
"botRolesWithDogTags": ["pmcbear", "pmcusec"],
|
||||
"revenge": {
|
||||
"pmcBot": ["pmcBot","gifter"],
|
||||
"pmcBot": ["pmcBot", "gifter"],
|
||||
"arenaFighter": ["pmcBot", "gifter"],
|
||||
"arenaFighterEvent": ["pmcBot", "gifter"],
|
||||
"bossKnight": ["exUsec", "gifter", "bossKnight", "followerBigPipe", "followerBirdEye"],
|
||||
@ -495,9 +492,7 @@
|
||||
"lightIsActiveNightChancePercent": 90,
|
||||
"laserIsActiveChancePercent": 85,
|
||||
"forceStock": true,
|
||||
"weaponSlotIdsToMakeRequired": [
|
||||
"mod_reciever"
|
||||
]
|
||||
"weaponSlotIdsToMakeRequired": ["mod_reciever"]
|
||||
},
|
||||
"bossbully": {
|
||||
"nvgIsActiveChanceDayPercent": 10,
|
||||
@ -776,9 +771,7 @@
|
||||
"scopeLimit": 1,
|
||||
"lightLaserLimit": 1
|
||||
},
|
||||
"weaponSlotIdsToMakeRequired": [
|
||||
"mod_reciever"
|
||||
]
|
||||
"weaponSlotIdsToMakeRequired": ["mod_reciever"]
|
||||
},
|
||||
"peacemaker": {
|
||||
"nvgIsActiveChanceDayPercent": 10,
|
||||
@ -926,11 +919,7 @@
|
||||
"laserIsActiveChancePercent": 85,
|
||||
"forceOnlyArmoredRigWhenNoArmor": true,
|
||||
"filterPlatesByLevel": true,
|
||||
"weaponSlotIdsToMakeRequired": [
|
||||
"mod_reciever",
|
||||
"mod_stock",
|
||||
"mod_muzzle"
|
||||
],
|
||||
"weaponSlotIdsToMakeRequired": ["mod_reciever", "mod_stock", "mod_muzzle"],
|
||||
"randomisation": [
|
||||
{
|
||||
"levelRange": {
|
||||
@ -1148,11 +1137,7 @@
|
||||
"Scabbard": 100,
|
||||
"TacticalVest": 90
|
||||
},
|
||||
"randomisedArmorSlots": [
|
||||
"Headwear",
|
||||
"ArmorVest",
|
||||
"TacticalVest"
|
||||
],
|
||||
"randomisedArmorSlots": ["Headwear", "ArmorVest", "TacticalVest"],
|
||||
"randomisedWeaponModSlots": [
|
||||
"mod_scope",
|
||||
"mod_scope_000",
|
||||
@ -1250,11 +1235,7 @@
|
||||
"SecondPrimaryWeapon": 10,
|
||||
"FaceCover": 50
|
||||
},
|
||||
"randomisedArmorSlots": [
|
||||
"Headwear",
|
||||
"TacticalVest",
|
||||
"ArmorVest"
|
||||
],
|
||||
"randomisedArmorSlots": ["Headwear", "TacticalVest", "ArmorVest"],
|
||||
"randomisedWeaponModSlots": [
|
||||
"mod_scope",
|
||||
"mod_scope_000",
|
||||
@ -1321,11 +1302,7 @@
|
||||
"mod_muzzle_000": 95,
|
||||
"mod_muzzle_001": 95
|
||||
},
|
||||
"randomisedArmorSlots": [
|
||||
"Headwear",
|
||||
"TacticalVest",
|
||||
"ArmorVest"
|
||||
],
|
||||
"randomisedArmorSlots": ["Headwear", "TacticalVest", "ArmorVest"],
|
||||
"randomisedWeaponModSlots": [
|
||||
"mod_scope",
|
||||
"mod_scope_000",
|
||||
@ -1397,30 +1374,15 @@
|
||||
"65392f611406374f82152ba5",
|
||||
"653931da5db71d30ab1d6296"
|
||||
],
|
||||
"mod_nvg": [
|
||||
"5c11046cd174af02a012e42b"
|
||||
],
|
||||
"mod_reciever": [
|
||||
"5d4405aaa4b9361e6a4e6bd3"
|
||||
],
|
||||
"mod_stock": [
|
||||
"5cde739cd7f00c0010373bd3"
|
||||
],
|
||||
"mod_rear_sight": [
|
||||
"5a0ed824fcdbcb0176308b0d"
|
||||
],
|
||||
"mod_front_sight": [
|
||||
"5a0f096dfcdbcb0176308b15"
|
||||
]
|
||||
"mod_nvg": ["5c11046cd174af02a012e42b"],
|
||||
"mod_reciever": ["5d4405aaa4b9361e6a4e6bd3"],
|
||||
"mod_stock": ["5cde739cd7f00c0010373bd3"],
|
||||
"mod_rear_sight": ["5a0ed824fcdbcb0176308b0d"],
|
||||
"mod_front_sight": ["5a0f096dfcdbcb0176308b15"]
|
||||
},
|
||||
"cartridge": {
|
||||
"Caliber23x75": [
|
||||
"5e85a9f4add9fe03027d9bf1"
|
||||
],
|
||||
"Caliber127x55": [
|
||||
"5cadf6e5ae921500113bb973",
|
||||
"5cadf6ddae9215051e1c23b2"
|
||||
]
|
||||
"Caliber23x75": ["5e85a9f4add9fe03027d9bf1"],
|
||||
"Caliber127x55": ["5cadf6e5ae921500113bb973", "5cadf6ddae9215051e1c23b2"]
|
||||
}
|
||||
}
|
||||
],
|
||||
@ -2677,10 +2639,7 @@
|
||||
"569668774bdc2da2298b4568": 0,
|
||||
"5696686a4bdc2da3298b456a": 0
|
||||
},
|
||||
"walletTplPool": [
|
||||
"5783c43d2459774bbe137486",
|
||||
"60b0f6c058e0b0481a09ad11"
|
||||
]
|
||||
"walletTplPool": ["5783c43d2459774bbe137486", "60b0f6c058e0b0481a09ad11"]
|
||||
},
|
||||
"currencyStackSize": {
|
||||
"default": {
|
||||
@ -2735,7 +2694,14 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"lowProfileGasBlockTpls": ["61702f1b67085e45ef140b26", "5dfa3d45dfc58d14537c20b0", "5bb20dcad4351e3bac1212da", "56eabcd4d2720b66698b4574", "6065dc8a132d4d12c81fd8e3", "55d4af3a4bdc2d972f8b456f"],
|
||||
"lowProfileGasBlockTpls": [
|
||||
"61702f1b67085e45ef140b26",
|
||||
"5dfa3d45dfc58d14537c20b0",
|
||||
"5bb20dcad4351e3bac1212da",
|
||||
"56eabcd4d2720b66698b4574",
|
||||
"6065dc8a132d4d12c81fd8e3",
|
||||
"55d4af3a4bdc2d972f8b456f"
|
||||
],
|
||||
"disableLootOnBotTypes": [],
|
||||
"assaultToBossConversion": {
|
||||
"bossConvertEnabled": false,
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -13,15 +13,7 @@
|
||||
"save": {
|
||||
"loot": true
|
||||
},
|
||||
"carExtracts": [
|
||||
"Dorms V-Ex",
|
||||
"PP Exfil",
|
||||
"V-Ex_light",
|
||||
"South V-Ex",
|
||||
"E7_car",
|
||||
"Sandbox_VExit",
|
||||
"Shorl_V-Ex"
|
||||
],
|
||||
"carExtracts": ["Dorms V-Ex", "PP Exfil", "V-Ex_light", "South V-Ex", "E7_car", "Sandbox_VExit", "Shorl_V-Ex"],
|
||||
"coopExtracts": [
|
||||
"Interchange Cooperation",
|
||||
"tunnel_shared",
|
||||
|
@ -3,15 +3,8 @@
|
||||
"54cb50c76803fa8b248b4571": 75,
|
||||
"54cb57776803fa99248b456e": 85
|
||||
},
|
||||
"blacklistedEquipment": [
|
||||
"SpecialSlot1",
|
||||
"SpecialSlot2",
|
||||
"SpecialSlot3"
|
||||
],
|
||||
"slotIdsToAlwaysRemove": [
|
||||
"cartridges",
|
||||
"patron_in_weapon"
|
||||
],
|
||||
"blacklistedEquipment": ["SpecialSlot1", "SpecialSlot2", "SpecialSlot3"],
|
||||
"slotIdsToAlwaysRemove": ["cartridges", "patron_in_weapon"],
|
||||
"returnTimeOverrideSeconds": 0,
|
||||
"runIntervalSeconds": 600,
|
||||
"minAttachmentRoublePriceToBeTaken": 2000,
|
||||
|
@ -49,10 +49,7 @@
|
||||
"6241c2c2117ad530666a5108",
|
||||
"5580239d4bdc2de7118b4583"
|
||||
],
|
||||
"lootableItemBlacklist": [
|
||||
"660bbc47c38b837877075e47",
|
||||
"660bc341c38b837877075e4c"
|
||||
],
|
||||
"lootableItemBlacklist": ["660bbc47c38b837877075e47", "660bc341c38b837877075e4c"],
|
||||
"rewardItemBlacklist": [
|
||||
"58ac60eb86f77401897560ff",
|
||||
"5e997f0b86f7741ac73993e2",
|
||||
|
@ -56,9 +56,7 @@
|
||||
"Supports": [
|
||||
{
|
||||
"BossEscortType": "pmcBEAR",
|
||||
"BossEscortDifficult": [
|
||||
"normal"
|
||||
],
|
||||
"BossEscortDifficult": ["normal"],
|
||||
"BossEscortAmount": "1"
|
||||
}
|
||||
],
|
||||
@ -79,9 +77,7 @@
|
||||
"Supports": [
|
||||
{
|
||||
"BossEscortType": "pmcUSEC",
|
||||
"BossEscortDifficult": [
|
||||
"normal"
|
||||
],
|
||||
"BossEscortDifficult": ["normal"],
|
||||
"BossEscortAmount": "1"
|
||||
}
|
||||
],
|
||||
@ -104,9 +100,7 @@
|
||||
"Supports": [
|
||||
{
|
||||
"BossEscortType": "pmcBEAR",
|
||||
"BossEscortDifficult": [
|
||||
"normal"
|
||||
],
|
||||
"BossEscortDifficult": ["normal"],
|
||||
"BossEscortAmount": "2"
|
||||
}
|
||||
],
|
||||
@ -127,9 +121,7 @@
|
||||
"Supports": [
|
||||
{
|
||||
"BossEscortType": "pmcUSEC",
|
||||
"BossEscortDifficult": [
|
||||
"normal"
|
||||
],
|
||||
"BossEscortDifficult": ["normal"],
|
||||
"BossEscortAmount": "2"
|
||||
}
|
||||
],
|
||||
@ -152,9 +144,7 @@
|
||||
"Supports": [
|
||||
{
|
||||
"BossEscortType": "pmcBEAR",
|
||||
"BossEscortDifficult": [
|
||||
"normal"
|
||||
],
|
||||
"BossEscortDifficult": ["normal"],
|
||||
"BossEscortAmount": "2"
|
||||
}
|
||||
],
|
||||
@ -175,9 +165,7 @@
|
||||
"Supports": [
|
||||
{
|
||||
"BossEscortType": "pmcUSEC",
|
||||
"BossEscortDifficult": [
|
||||
"normal"
|
||||
],
|
||||
"BossEscortDifficult": ["normal"],
|
||||
"BossEscortAmount": "2"
|
||||
}
|
||||
],
|
||||
@ -198,9 +186,7 @@
|
||||
"Supports": [
|
||||
{
|
||||
"BossEscortType": "pmcBEAR",
|
||||
"BossEscortDifficult": [
|
||||
"normal"
|
||||
],
|
||||
"BossEscortDifficult": ["normal"],
|
||||
"BossEscortAmount": "2"
|
||||
}
|
||||
],
|
||||
@ -221,9 +207,7 @@
|
||||
"Supports": [
|
||||
{
|
||||
"BossEscortType": "pmcUSEC",
|
||||
"BossEscortDifficult": [
|
||||
"normal"
|
||||
],
|
||||
"BossEscortDifficult": ["normal"],
|
||||
"BossEscortAmount": "2"
|
||||
}
|
||||
],
|
||||
@ -244,9 +228,7 @@
|
||||
"Supports": [
|
||||
{
|
||||
"BossEscortType": "pmcBEAR",
|
||||
"BossEscortDifficult": [
|
||||
"normal"
|
||||
],
|
||||
"BossEscortDifficult": ["normal"],
|
||||
"BossEscortAmount": "2"
|
||||
}
|
||||
],
|
||||
@ -267,9 +249,7 @@
|
||||
"Supports": [
|
||||
{
|
||||
"BossEscortType": "pmcUSEC",
|
||||
"BossEscortDifficult": [
|
||||
"normal"
|
||||
],
|
||||
"BossEscortDifficult": ["normal"],
|
||||
"BossEscortAmount": "2"
|
||||
}
|
||||
],
|
||||
@ -292,9 +272,7 @@
|
||||
"Supports": [
|
||||
{
|
||||
"BossEscortType": "pmcBEAR",
|
||||
"BossEscortDifficult": [
|
||||
"normal"
|
||||
],
|
||||
"BossEscortDifficult": ["normal"],
|
||||
"BossEscortAmount": "1"
|
||||
}
|
||||
],
|
||||
@ -315,9 +293,7 @@
|
||||
"Supports": [
|
||||
{
|
||||
"BossEscortType": "pmcUSEC",
|
||||
"BossEscortDifficult": [
|
||||
"normal"
|
||||
],
|
||||
"BossEscortDifficult": ["normal"],
|
||||
"BossEscortAmount": "1"
|
||||
}
|
||||
],
|
||||
@ -338,9 +314,7 @@
|
||||
"Supports": [
|
||||
{
|
||||
"BossEscortType": "pmcUSEC",
|
||||
"BossEscortDifficult": [
|
||||
"normal"
|
||||
],
|
||||
"BossEscortDifficult": ["normal"],
|
||||
"BossEscortAmount": "1"
|
||||
}
|
||||
],
|
||||
@ -361,9 +335,7 @@
|
||||
"Supports": [
|
||||
{
|
||||
"BossEscortType": "pmcBEAR",
|
||||
"BossEscortDifficult": [
|
||||
"normal"
|
||||
],
|
||||
"BossEscortDifficult": ["normal"],
|
||||
"BossEscortAmount": "1"
|
||||
}
|
||||
],
|
||||
@ -384,9 +356,7 @@
|
||||
"Supports": [
|
||||
{
|
||||
"BossEscortType": "pmcUSEC",
|
||||
"BossEscortDifficult": [
|
||||
"normal"
|
||||
],
|
||||
"BossEscortDifficult": ["normal"],
|
||||
"BossEscortAmount": "1"
|
||||
}
|
||||
],
|
||||
@ -407,9 +377,7 @@
|
||||
"Supports": [
|
||||
{
|
||||
"BossEscortType": "pmcBEAR",
|
||||
"BossEscortDifficult": [
|
||||
"normal"
|
||||
],
|
||||
"BossEscortDifficult": ["normal"],
|
||||
"BossEscortAmount": "1"
|
||||
}
|
||||
],
|
||||
@ -432,9 +400,7 @@
|
||||
"Supports": [
|
||||
{
|
||||
"BossEscortType": "pmcBEAR",
|
||||
"BossEscortDifficult": [
|
||||
"normal"
|
||||
],
|
||||
"BossEscortDifficult": ["normal"],
|
||||
"BossEscortAmount": "1"
|
||||
}
|
||||
],
|
||||
@ -455,9 +421,7 @@
|
||||
"Supports": [
|
||||
{
|
||||
"BossEscortType": "pmcBEAR",
|
||||
"BossEscortDifficult": [
|
||||
"normal"
|
||||
],
|
||||
"BossEscortDifficult": ["normal"],
|
||||
"BossEscortAmount": "1"
|
||||
}
|
||||
],
|
||||
@ -478,9 +442,7 @@
|
||||
"Supports": [
|
||||
{
|
||||
"BossEscortType": "pmcBEAR",
|
||||
"BossEscortDifficult": [
|
||||
"normal"
|
||||
],
|
||||
"BossEscortDifficult": ["normal"],
|
||||
"BossEscortAmount": "0"
|
||||
}
|
||||
],
|
||||
@ -501,9 +463,7 @@
|
||||
"Supports": [
|
||||
{
|
||||
"BossEscortType": "pmcUSEC",
|
||||
"BossEscortDifficult": [
|
||||
"normal"
|
||||
],
|
||||
"BossEscortDifficult": ["normal"],
|
||||
"BossEscortAmount": "1"
|
||||
}
|
||||
],
|
||||
@ -524,9 +484,7 @@
|
||||
"Supports": [
|
||||
{
|
||||
"BossEscortType": "pmcUSEC",
|
||||
"BossEscortDifficult": [
|
||||
"normal"
|
||||
],
|
||||
"BossEscortDifficult": ["normal"],
|
||||
"BossEscortAmount": "1"
|
||||
}
|
||||
],
|
||||
@ -547,9 +505,7 @@
|
||||
"Supports": [
|
||||
{
|
||||
"BossEscortType": "pmcUSEC",
|
||||
"BossEscortDifficult": [
|
||||
"normal"
|
||||
],
|
||||
"BossEscortDifficult": ["normal"],
|
||||
"BossEscortAmount": "0"
|
||||
}
|
||||
],
|
||||
@ -572,9 +528,7 @@
|
||||
"Supports": [
|
||||
{
|
||||
"BossEscortType": "pmcBEAR",
|
||||
"BossEscortDifficult": [
|
||||
"normal"
|
||||
],
|
||||
"BossEscortDifficult": ["normal"],
|
||||
"BossEscortAmount": "1"
|
||||
}
|
||||
],
|
||||
@ -595,9 +549,7 @@
|
||||
"Supports": [
|
||||
{
|
||||
"BossEscortType": "pmcUSEC",
|
||||
"BossEscortDifficult": [
|
||||
"normal"
|
||||
],
|
||||
"BossEscortDifficult": ["normal"],
|
||||
"BossEscortAmount": "1"
|
||||
}
|
||||
],
|
||||
@ -620,9 +572,7 @@
|
||||
"Supports": [
|
||||
{
|
||||
"BossEscortType": "pmcBEAR",
|
||||
"BossEscortDifficult": [
|
||||
"normal"
|
||||
],
|
||||
"BossEscortDifficult": ["normal"],
|
||||
"BossEscortAmount": "1"
|
||||
}
|
||||
],
|
||||
@ -643,9 +593,7 @@
|
||||
"Supports": [
|
||||
{
|
||||
"BossEscortType": "pmcUSEC",
|
||||
"BossEscortDifficult": [
|
||||
"normal"
|
||||
],
|
||||
"BossEscortDifficult": ["normal"],
|
||||
"BossEscortAmount": "1"
|
||||
}
|
||||
],
|
||||
@ -668,9 +616,7 @@
|
||||
"Supports": [
|
||||
{
|
||||
"BossEscortType": "pmcBEAR",
|
||||
"BossEscortDifficult": [
|
||||
"normal"
|
||||
],
|
||||
"BossEscortDifficult": ["normal"],
|
||||
"BossEscortAmount": "1"
|
||||
}
|
||||
],
|
||||
@ -691,9 +637,7 @@
|
||||
"Supports": [
|
||||
{
|
||||
"BossEscortType": "pmcBEAR",
|
||||
"BossEscortDifficult": [
|
||||
"normal"
|
||||
],
|
||||
"BossEscortDifficult": ["normal"],
|
||||
"BossEscortAmount": "1"
|
||||
}
|
||||
],
|
||||
@ -714,9 +658,7 @@
|
||||
"Supports": [
|
||||
{
|
||||
"BossEscortType": "pmcUSEC",
|
||||
"BossEscortDifficult": [
|
||||
"normal"
|
||||
],
|
||||
"BossEscortDifficult": ["normal"],
|
||||
"BossEscortAmount": "1"
|
||||
}
|
||||
],
|
||||
@ -737,9 +679,7 @@
|
||||
"Supports": [
|
||||
{
|
||||
"BossEscortType": "pmcUSEC",
|
||||
"BossEscortDifficult": [
|
||||
"normal"
|
||||
],
|
||||
"BossEscortDifficult": ["normal"],
|
||||
"BossEscortAmount": "1"
|
||||
}
|
||||
],
|
||||
@ -762,9 +702,7 @@
|
||||
"Supports": [
|
||||
{
|
||||
"BossEscortType": "pmcBEAR",
|
||||
"BossEscortDifficult": [
|
||||
"normal"
|
||||
],
|
||||
"BossEscortDifficult": ["normal"],
|
||||
"BossEscortAmount": "2"
|
||||
}
|
||||
],
|
||||
@ -785,9 +723,7 @@
|
||||
"Supports": [
|
||||
{
|
||||
"BossEscortType": "pmcUSEC",
|
||||
"BossEscortDifficult": [
|
||||
"normal"
|
||||
],
|
||||
"BossEscortDifficult": ["normal"],
|
||||
"BossEscortAmount": "2"
|
||||
}
|
||||
],
|
||||
@ -810,9 +746,7 @@
|
||||
"Supports": [
|
||||
{
|
||||
"BossEscortType": "pmcBEAR",
|
||||
"BossEscortDifficult": [
|
||||
"normal"
|
||||
],
|
||||
"BossEscortDifficult": ["normal"],
|
||||
"BossEscortAmount": "2"
|
||||
}
|
||||
],
|
||||
@ -833,9 +767,7 @@
|
||||
"Supports": [
|
||||
{
|
||||
"BossEscortType": "pmcUSEC",
|
||||
"BossEscortDifficult": [
|
||||
"normal"
|
||||
],
|
||||
"BossEscortDifficult": ["normal"],
|
||||
"BossEscortAmount": "2"
|
||||
}
|
||||
],
|
||||
@ -1047,9 +979,7 @@
|
||||
}
|
||||
},
|
||||
"openZones": {
|
||||
"bigmap": [
|
||||
"ZoneScavBase"
|
||||
]
|
||||
"bigmap": ["ZoneScavBase"]
|
||||
},
|
||||
"forcedLootSingleSpawnById": {
|
||||
"bigmap": [
|
||||
@ -1058,9 +988,7 @@
|
||||
"64e74a3d4d49d23b2c39d319",
|
||||
"6614230055afee107f05e998"
|
||||
],
|
||||
"interchange": [
|
||||
"64e74a5ac2b4f829615ec336"
|
||||
],
|
||||
"interchange": ["64e74a5ac2b4f829615ec336"],
|
||||
"lighthouse": [
|
||||
"6331bb0d1aa9f42b804997a6",
|
||||
"6398a0861c712b1e1d4dadf1",
|
||||
@ -1068,10 +996,7 @@
|
||||
"64e74a64aac4cd0a7264ecdf",
|
||||
"661666458c2aa9cb1602503b"
|
||||
],
|
||||
"rezervbase": [
|
||||
"64e74a4baac4cd0a7264ecdd",
|
||||
"6398a072e301557ae24cec92"
|
||||
],
|
||||
"rezervbase": ["64e74a4baac4cd0a7264ecdd", "6398a072e301557ae24cec92"],
|
||||
"shoreline": [
|
||||
"64e74a534d49d23b2c39d31b",
|
||||
"661421c7c1f2f548c50ee649",
|
||||
@ -1101,14 +1026,8 @@
|
||||
"657acb2ac900be5902191ac9",
|
||||
"6582dbf0b8d7830efc45016f"
|
||||
],
|
||||
"laboratory": [
|
||||
"6398a4cfb5992f573c6562b3",
|
||||
"64e74a44c2b4f829615ec334"
|
||||
],
|
||||
"sandbox": [
|
||||
"6575a6ca8778e96ded05a802",
|
||||
"6582bd252b50c61c565828e2"
|
||||
]
|
||||
"laboratory": ["6398a4cfb5992f573c6562b3", "64e74a44c2b4f829615ec334"],
|
||||
"sandbox": ["6575a6ca8778e96ded05a802", "6582bd252b50c61c565828e2"]
|
||||
},
|
||||
"splitWaveIntoSingleSpawnsSettings": {
|
||||
"enabled": true,
|
||||
@ -1131,15 +1050,7 @@
|
||||
},
|
||||
"fixEmptyBotWavesSettings": {
|
||||
"enabled": true,
|
||||
"ignoreMaps": [
|
||||
"base",
|
||||
"develop",
|
||||
"hideout",
|
||||
"privatearea",
|
||||
"suburbs",
|
||||
"terminal",
|
||||
"town"
|
||||
]
|
||||
"ignoreMaps": ["base", "develop", "hideout", "privatearea", "suburbs", "terminal", "town"]
|
||||
},
|
||||
"fitLootIntoContainerAttempts": 3,
|
||||
"addOpenZonesToAllMaps": true,
|
||||
|
@ -856,19 +856,10 @@
|
||||
"botTypeForLoot": "assault",
|
||||
"equipmentBlacklist": {
|
||||
"Headwear": [],
|
||||
"ArmorVest": [
|
||||
"5df8a2ca86f7740bfe6df777"
|
||||
],
|
||||
"TacticalVest": [
|
||||
"572b7adb24597762ae139821"
|
||||
],
|
||||
"Backpack": [
|
||||
"56e33634d2720bd8058b456b",
|
||||
"5ab8ee7786f7742d8f33f0b9"
|
||||
],
|
||||
"FirstPrimaryWeapon": [
|
||||
"5a38e6bac4a2826c6e06d79b"
|
||||
],
|
||||
"ArmorVest": ["5df8a2ca86f7740bfe6df777"],
|
||||
"TacticalVest": ["572b7adb24597762ae139821"],
|
||||
"Backpack": ["56e33634d2720bd8058b456b", "5ab8ee7786f7742d8f33f0b9"],
|
||||
"FirstPrimaryWeapon": ["5a38e6bac4a2826c6e06d79b"],
|
||||
"Holster": [],
|
||||
"Scabbard": []
|
||||
},
|
||||
@ -955,22 +946,10 @@
|
||||
"botTypeForLoot": "assault",
|
||||
"equipmentBlacklist": {
|
||||
"Headwear": [],
|
||||
"ArmorVest": [
|
||||
"59e7635f86f7742cbf2c1095",
|
||||
"5df8a2ca86f7740bfe6df777"
|
||||
],
|
||||
"TacticalVest": [
|
||||
"5fd4c5477a8d854fa0105061",
|
||||
"572b7adb24597762ae139821"
|
||||
],
|
||||
"Backpack": [
|
||||
"56e33680d2720be2748b4576",
|
||||
"56e33634d2720bd8058b456b",
|
||||
"5ab8ee7786f7742d8f33f0b9"
|
||||
],
|
||||
"FirstPrimaryWeapon": [
|
||||
"5a38e6bac4a2826c6e06d79b"
|
||||
],
|
||||
"ArmorVest": ["59e7635f86f7742cbf2c1095", "5df8a2ca86f7740bfe6df777"],
|
||||
"TacticalVest": ["5fd4c5477a8d854fa0105061", "572b7adb24597762ae139821"],
|
||||
"Backpack": ["56e33680d2720be2748b4576", "56e33634d2720bd8058b456b", "5ab8ee7786f7742d8f33f0b9"],
|
||||
"FirstPrimaryWeapon": ["5a38e6bac4a2826c6e06d79b"],
|
||||
"Holster": [],
|
||||
"Scabbard": []
|
||||
},
|
||||
@ -1056,22 +1035,10 @@
|
||||
"botTypeForLoot": "assault",
|
||||
"equipmentBlacklist": {
|
||||
"Headwear": [],
|
||||
"ArmorVest": [
|
||||
"59e7635f86f7742cbf2c1095",
|
||||
"5df8a2ca86f7740bfe6df777"
|
||||
],
|
||||
"TacticalVest": [
|
||||
"5fd4c5477a8d854fa0105061",
|
||||
"572b7adb24597762ae139821"
|
||||
],
|
||||
"Backpack": [
|
||||
"56e33680d2720be2748b4576",
|
||||
"56e33634d2720bd8058b456b",
|
||||
"5ab8ee7786f7742d8f33f0b9"
|
||||
],
|
||||
"FirstPrimaryWeapon": [
|
||||
"5a38e6bac4a2826c6e06d79b"
|
||||
],
|
||||
"ArmorVest": ["59e7635f86f7742cbf2c1095", "5df8a2ca86f7740bfe6df777"],
|
||||
"TacticalVest": ["5fd4c5477a8d854fa0105061", "572b7adb24597762ae139821"],
|
||||
"Backpack": ["56e33680d2720be2748b4576", "56e33634d2720bd8058b456b", "5ab8ee7786f7742d8f33f0b9"],
|
||||
"FirstPrimaryWeapon": ["5a38e6bac4a2826c6e06d79b"],
|
||||
"Holster": [],
|
||||
"Scabbard": []
|
||||
},
|
||||
@ -1168,14 +1135,8 @@
|
||||
"5f60e7788adaa7100c3adb49",
|
||||
"5aa2a7e8e5b5b00016327c16"
|
||||
],
|
||||
"ArmorVest": [
|
||||
"59e7635f86f7742cbf2c1095",
|
||||
"5df8a2ca86f7740bfe6df777"
|
||||
],
|
||||
"TacticalVest": [
|
||||
"5fd4c5477a8d854fa0105061",
|
||||
"572b7adb24597762ae139821"
|
||||
],
|
||||
"ArmorVest": ["59e7635f86f7742cbf2c1095", "5df8a2ca86f7740bfe6df777"],
|
||||
"TacticalVest": ["5fd4c5477a8d854fa0105061", "572b7adb24597762ae139821"],
|
||||
"Backpack": [
|
||||
"56e33680d2720be2748b4576",
|
||||
"56e33634d2720bd8058b456b",
|
||||
@ -1183,14 +1144,8 @@
|
||||
"5ab8f04f86f774585f4237d8",
|
||||
"5f5e45cc5021ce62144be7aa"
|
||||
],
|
||||
"FirstPrimaryWeapon": [
|
||||
"5a38e6bac4a2826c6e06d79b"
|
||||
],
|
||||
"Holster": [
|
||||
"5448bd6b4bdc2dfc2f8b4569",
|
||||
"579204f224597773d619e051",
|
||||
"571a12c42459771f627b58a0"
|
||||
],
|
||||
"FirstPrimaryWeapon": ["5a38e6bac4a2826c6e06d79b"],
|
||||
"Holster": ["5448bd6b4bdc2dfc2f8b4569", "579204f224597773d619e051", "571a12c42459771f627b58a0"],
|
||||
"Scabbard": []
|
||||
},
|
||||
"modifiers": {
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -276,10 +276,7 @@
|
||||
},
|
||||
"armor": {
|
||||
"removeRemovablePlateChance": 40,
|
||||
"plateSlotIdToRemovePool": [
|
||||
"front_plate",
|
||||
"back_plate"
|
||||
]
|
||||
"plateSlotIdToRemovePool": ["front_plate", "back_plate"]
|
||||
},
|
||||
"itemPriceMultiplier": {
|
||||
"5737292724597765e5728562": 6,
|
||||
@ -314,10 +311,7 @@
|
||||
"traderItems": false,
|
||||
"armorPlate": {
|
||||
"maxProtectionLevel": 4,
|
||||
"ignoreSlots": [
|
||||
"right_side_plate",
|
||||
"left_side_plate"
|
||||
]
|
||||
"ignoreSlots": ["right_side_plate", "left_side_plate"]
|
||||
},
|
||||
"enableCustomItemCategoryList": false,
|
||||
"customItemCategoryList": []
|
||||
|
@ -12,9 +12,9 @@
|
||||
"trader": 0.6
|
||||
},
|
||||
"weaponTreatment": {
|
||||
"critSuccessChance": 0.10,
|
||||
"critSuccessChance": 0.1,
|
||||
"critSuccessAmount": 4,
|
||||
"critFailureChance": 0.10,
|
||||
"critFailureChance": 0.1,
|
||||
"critFailureAmount": 4,
|
||||
"pointGainMultiplier": 0.6
|
||||
},
|
||||
@ -163,7 +163,7 @@
|
||||
"MalfunctionProtections": {
|
||||
"valuesMinMax": {
|
||||
"min": 0.75,
|
||||
"max": 0.90
|
||||
"max": 0.9
|
||||
},
|
||||
"activeDurabilityPercentMinMax": {
|
||||
"min": 75,
|
||||
|
@ -86,9 +86,7 @@
|
||||
"ammoRewardChancePercent": 10,
|
||||
"minStackSize": 10,
|
||||
"ammoRewardBlacklist": {
|
||||
"common": [
|
||||
"59e690b686f7746c9f75e848"
|
||||
],
|
||||
"common": ["59e690b686f7746c9f75e848"],
|
||||
"rare": [],
|
||||
"superrare": []
|
||||
},
|
||||
|
@ -2,94 +2,32 @@
|
||||
"acceleration": 7,
|
||||
"weather": {
|
||||
"clouds": {
|
||||
"values": [
|
||||
-1.5,
|
||||
-1,
|
||||
0,
|
||||
0.5,
|
||||
1,
|
||||
1.5
|
||||
],
|
||||
"weights": [
|
||||
60,
|
||||
50,
|
||||
15,
|
||||
5,
|
||||
4,
|
||||
3
|
||||
]
|
||||
"values": [-1.5, -1, 0, 0.5, 1, 1.5],
|
||||
"weights": [60, 50, 15, 5, 4, 3]
|
||||
},
|
||||
"windSpeed": {
|
||||
"values": [
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"weights": [
|
||||
4,
|
||||
3,
|
||||
2,
|
||||
1
|
||||
]
|
||||
"values": [0, 1, 2, 3],
|
||||
"weights": [4, 3, 2, 1]
|
||||
},
|
||||
"windDirection": {
|
||||
"values": [
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8
|
||||
],
|
||||
"weights": [
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1
|
||||
]
|
||||
"values": [1, 2, 3, 4, 5, 6, 7, 8],
|
||||
"weights": [1, 1, 1, 1, 1, 1, 1, 1]
|
||||
},
|
||||
"windGustiness": {
|
||||
"min": 0,
|
||||
"max": 1
|
||||
},
|
||||
"rain": {
|
||||
"values": [
|
||||
1,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"weights": [
|
||||
25,
|
||||
1,
|
||||
1
|
||||
]
|
||||
"values": [1, 2, 3],
|
||||
"weights": [25, 1, 1]
|
||||
},
|
||||
"rainIntensity": {
|
||||
"min": 0,
|
||||
"max": 1
|
||||
},
|
||||
"fog": {
|
||||
"values": [
|
||||
0.002,
|
||||
0.004,
|
||||
0.008,
|
||||
0.012,
|
||||
0.087
|
||||
],
|
||||
"weights": [
|
||||
20,
|
||||
8,
|
||||
5,
|
||||
5,
|
||||
1
|
||||
]
|
||||
"values": [0.002, 0.004, 0.008, 0.012, 0.087],
|
||||
"weights": [20, 8, 5, 5, 1]
|
||||
},
|
||||
"temp": {
|
||||
"min": 0,
|
||||
|
@ -37,8 +37,7 @@ const licenseFile = "../LICENSE.md";
|
||||
/**
|
||||
* Transpile src files into Javascript with SWC
|
||||
*/
|
||||
const compile = async () =>
|
||||
{
|
||||
const compile = async () => {
|
||||
// Compile TypeScript files using SWC
|
||||
await exec("npx swc src -d obj", { stdio: "inherit" });
|
||||
|
||||
@ -46,29 +45,23 @@ const compile = async () =>
|
||||
const srcDir = path.join("obj", "src");
|
||||
const destDir = path.join("obj");
|
||||
|
||||
try
|
||||
{
|
||||
try {
|
||||
const entities = await fs.readdir(srcDir);
|
||||
for (const entity of entities)
|
||||
{
|
||||
for (const entity of entities) {
|
||||
const srcPath = path.join(srcDir, entity);
|
||||
const destPath = path.join(destDir, entity);
|
||||
await fs.move(srcPath, destPath, { overwrite: true });
|
||||
}
|
||||
// After moving all contents, remove the now-empty /obj/src directory
|
||||
await fs.remove(srcDir);
|
||||
}
|
||||
catch (error)
|
||||
{
|
||||
} catch (error) {
|
||||
console.error("An error occurred during the merge operation:", error);
|
||||
}
|
||||
};
|
||||
|
||||
// Packaging
|
||||
const fetchPackageImage = async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
const fetchPackageImage = async () => {
|
||||
try {
|
||||
const output = "./.pkg-cache/v3.5";
|
||||
const fetchedPkg = await pkgfetch.need({
|
||||
arch: targetArch,
|
||||
@ -79,18 +72,14 @@ const fetchPackageImage = async () =>
|
||||
console.log(`fetched node binary at ${fetchedPkg}`);
|
||||
const builtPkg = fetchedPkg.replace("node", "built");
|
||||
await fs.copyFile(fetchedPkg, builtPkg);
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
} catch (e) {
|
||||
console.error(`Error while fetching and patching package image: ${e.message}`);
|
||||
console.error(e.stack);
|
||||
}
|
||||
};
|
||||
|
||||
const updateBuildProperties = async () =>
|
||||
{
|
||||
if (targetPlatform !== "win32")
|
||||
{
|
||||
const updateBuildProperties = async () => {
|
||||
if (targetPlatform !== "win32") {
|
||||
// can't modify executable's resource on non-windows build
|
||||
return;
|
||||
}
|
||||
@ -110,12 +99,15 @@ const updateBuildProperties = async () =>
|
||||
|
||||
const vi = ResEdit.Resource.VersionInfo.fromEntries(res.entries)[0];
|
||||
|
||||
vi.setStringValues({ lang: 1033, codepage: 1200 }, {
|
||||
vi.setStringValues(
|
||||
{ lang: 1033, codepage: 1200 },
|
||||
{
|
||||
ProductName: manifest.author,
|
||||
FileDescription: manifest.description,
|
||||
CompanyName: manifest.name,
|
||||
LegalCopyright: manifest.license,
|
||||
});
|
||||
},
|
||||
);
|
||||
vi.removeStringValue({ lang: 1033, codepage: 1200 }, "OriginalFilename");
|
||||
vi.removeStringValue({ lang: 1033, codepage: 1200 }, "InternalName");
|
||||
vi.setFileVersion(...manifest.version.split(".").map(Number));
|
||||
@ -129,15 +121,14 @@ const updateBuildProperties = async () =>
|
||||
* Copy various asset files to the destination directory
|
||||
*/
|
||||
const copyAssets = () =>
|
||||
gulp.src(["assets/**/*.json", "assets/**/*.json5", "assets/**/*.png", "assets/**/*.jpg", "assets/**/*.ico"]).pipe(
|
||||
gulp.dest(dataDir),
|
||||
);
|
||||
gulp
|
||||
.src(["assets/**/*.json", "assets/**/*.json5", "assets/**/*.png", "assets/**/*.jpg", "assets/**/*.ico"])
|
||||
.pipe(gulp.dest(dataDir));
|
||||
|
||||
/**
|
||||
* Download pnpm executable
|
||||
*/
|
||||
const downloadPnpm = async () =>
|
||||
{
|
||||
const downloadPnpm = async () => {
|
||||
// Please ensure that the @pnpm/exe version in devDependencies is pinned to a specific version. If it's not, the
|
||||
// following task will download *all* versions that are compatible with the semver range specified.
|
||||
const pnpmVersion = manifest.devDependencies["@pnpm/exe"];
|
||||
@ -145,7 +136,9 @@ const downloadPnpm = async () =>
|
||||
const npmResult = await exec(`npm view ${pnpmPackageName}@${pnpmVersion} dist.tarball`, { stdout: "pipe" });
|
||||
const pnpmLink = npmResult.stdout.trim();
|
||||
console.log(`Downloading pnpm binary from ${pnpmLink}`);
|
||||
download(pnpmLink).pipe(decompress({ strip: 1 })).pipe(gulp.dest(path.join(dataDir, "@pnpm", "exe")));
|
||||
download(pnpmLink)
|
||||
.pipe(decompress({ strip: 1 }))
|
||||
.pipe(gulp.dest(path.join(dataDir, "@pnpm", "exe")));
|
||||
};
|
||||
|
||||
/**
|
||||
@ -156,10 +149,8 @@ const copyLicense = () => gulp.src([licenseFile]).pipe(rename("LICENSE-Server.tx
|
||||
/**
|
||||
* Writes the latest build data to the core.json and build.json configuration files.
|
||||
*/
|
||||
const writeBuildDataToJSON = async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
const writeBuildDataToJSON = async () => {
|
||||
try {
|
||||
// Fetch the latest Git commit hash
|
||||
const gitResult = await exec("git rev-parse HEAD", { stdout: "pipe" });
|
||||
|
||||
@ -180,9 +171,7 @@ const writeBuildDataToJSON = async () =>
|
||||
buildInfo.buildTime = coreParsed.buildTime;
|
||||
buildInfo.sptVersion = coreParsed.sptVersion;
|
||||
await fs.writeFile(buildJsonPath, JSON.stringify(buildInfo, null, 4));
|
||||
}
|
||||
catch (error)
|
||||
{
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to write commit hash to core.json: ${error.message}`);
|
||||
}
|
||||
};
|
||||
@ -190,8 +179,7 @@ const writeBuildDataToJSON = async () =>
|
||||
/**
|
||||
* Create a hash file for asset checks
|
||||
*/
|
||||
const createHashFile = async () =>
|
||||
{
|
||||
const createHashFile = async () => {
|
||||
const hashFileDir = path.resolve(dataDir, "checks.dat");
|
||||
const assetData = await loadRecursiveAsync("assets/");
|
||||
const assetDataString = Buffer.from(JSON.stringify(assetData), "utf-8").toString("base64");
|
||||
@ -218,18 +206,13 @@ const cleanCompiled = async () => await fs.rm("./obj", { recursive: true, force:
|
||||
* @param {string[]} files
|
||||
* @returns {Promise<string[]>}
|
||||
*/
|
||||
const getJSONFiles = async (dir, files = []) =>
|
||||
{
|
||||
const getJSONFiles = async (dir, files = []) => {
|
||||
const fileList = await fs.readdir(dir);
|
||||
for (const file of fileList)
|
||||
{
|
||||
for (const file of fileList) {
|
||||
const name = path.resolve(dir, file);
|
||||
if ((await fs.stat(name)).isDirectory())
|
||||
{
|
||||
if ((await fs.stat(name)).isDirectory()) {
|
||||
getJSONFiles(name, files);
|
||||
}
|
||||
else if (name.slice(-5) === ".json")
|
||||
{
|
||||
} else if (name.slice(-5) === ".json") {
|
||||
files.push(name);
|
||||
}
|
||||
}
|
||||
@ -239,21 +222,16 @@ const getJSONFiles = async (dir, files = []) =>
|
||||
/**
|
||||
* Goes through every json file in assets and makes sure they're valid json.
|
||||
*/
|
||||
const validateJSONs = async () =>
|
||||
{
|
||||
const validateJSONs = async () => {
|
||||
const assetsPath = path.resolve("assets");
|
||||
const jsonFileList = await getJSONFiles(assetsPath);
|
||||
let jsonFileInProcess = "";
|
||||
try
|
||||
{
|
||||
for (const jsonFile of jsonFileList)
|
||||
{
|
||||
try {
|
||||
for (const jsonFile of jsonFileList) {
|
||||
jsonFileInProcess = jsonFile;
|
||||
JSON.parse(await fs.readFile(jsonFile));
|
||||
}
|
||||
}
|
||||
catch (error)
|
||||
{
|
||||
} catch (error) {
|
||||
throw new Error(`${error.message} | ${jsonFileInProcess}`);
|
||||
}
|
||||
};
|
||||
@ -264,8 +242,7 @@ const validateJSONs = async () =>
|
||||
* @param {crypto.BinaryLike} data
|
||||
* @returns {string}
|
||||
*/
|
||||
const generateHashForData = (data) =>
|
||||
{
|
||||
const generateHashForData = (data) => {
|
||||
const hashSum = crypto.createHash("sha1");
|
||||
hashSum.update(data);
|
||||
return hashSum.digest("hex");
|
||||
@ -277,21 +254,16 @@ const generateHashForData = (data) =>
|
||||
* @param {fs.PathLike} filepath
|
||||
* @returns {}
|
||||
*/
|
||||
const loadRecursiveAsync = async (filepath) =>
|
||||
{
|
||||
const loadRecursiveAsync = async (filepath) => {
|
||||
const result = {};
|
||||
|
||||
const filesList = await fs.readdir(filepath);
|
||||
|
||||
for (const file of filesList)
|
||||
{
|
||||
for (const file of filesList) {
|
||||
const curPath = path.parse(path.join(filepath, file));
|
||||
if ((await fs.stat(path.join(curPath.dir, curPath.base))).isDirectory())
|
||||
{
|
||||
if ((await fs.stat(path.join(curPath.dir, curPath.base))).isDirectory()) {
|
||||
result[curPath.name] = loadRecursiveAsync(`${filepath}${file}/`);
|
||||
}
|
||||
else if (curPath.ext === ".json")
|
||||
{
|
||||
} else if (curPath.ext === ".json") {
|
||||
result[curPath.name] = generateHashForData(await fs.readFile(`${filepath}${file}`));
|
||||
}
|
||||
}
|
||||
@ -299,8 +271,7 @@ const loadRecursiveAsync = async (filepath) =>
|
||||
// set all loadRecursive to be executed asynchronously
|
||||
const resEntries = Object.entries(result);
|
||||
const resResolved = await Promise.all(resEntries.map((ent) => ent[1]));
|
||||
for (let resIdx = 0; resIdx < resResolved.length; resIdx++)
|
||||
{
|
||||
for (let resIdx = 0; resIdx < resResolved.length; resIdx++) {
|
||||
resEntries[resIdx][1] = resResolved[resIdx];
|
||||
}
|
||||
|
||||
@ -309,8 +280,7 @@ const loadRecursiveAsync = async (filepath) =>
|
||||
};
|
||||
|
||||
// Main Tasks Generation
|
||||
const build = (packagingType) =>
|
||||
{
|
||||
const build = (packagingType) => {
|
||||
const anonPackaging = () => packaging(entries[packagingType]);
|
||||
anonPackaging.displayName = `packaging-${packagingType}`;
|
||||
const tasks = [
|
||||
@ -327,11 +297,9 @@ const build = (packagingType) =>
|
||||
};
|
||||
|
||||
// Packaging Arguments
|
||||
const packaging = async (entry) =>
|
||||
{
|
||||
const packaging = async (entry) => {
|
||||
const target = `${nodeVersion}-${targetPlatform}-${targetArch}`;
|
||||
try
|
||||
{
|
||||
try {
|
||||
await pkg.exec([
|
||||
entry,
|
||||
"--compress",
|
||||
@ -344,9 +312,7 @@ const packaging = async (entry) =>
|
||||
pkgConfig,
|
||||
"--public",
|
||||
]);
|
||||
}
|
||||
catch (error)
|
||||
{
|
||||
} catch (error) {
|
||||
console.error(`Error occurred during packaging: ${error}`);
|
||||
}
|
||||
};
|
||||
@ -361,8 +327,7 @@ gulp.task(
|
||||
"run:debug",
|
||||
async () => await exec("ts-node-dev -r tsconfig-paths/register src/ide/TestEntry.ts", { stdio }),
|
||||
);
|
||||
gulp.task("run:profiler", async () =>
|
||||
{
|
||||
gulp.task("run:profiler", async () => {
|
||||
await cleanCompiled();
|
||||
await compile();
|
||||
await exec("node --prof --inspect --trace-warnings obj/ide/TestEntry.js", { stdio });
|
||||
|
@ -1,11 +1,6 @@
|
||||
{
|
||||
"pkg": {
|
||||
"scripts": [
|
||||
"obj/**/*.js"
|
||||
],
|
||||
"assets": [
|
||||
"obj/**/*.js.map",
|
||||
"package.json"
|
||||
]
|
||||
"scripts": ["obj/**/*.js"],
|
||||
"assets": ["obj/**/*.js.map", "package.json"]
|
||||
}
|
||||
}
|
||||
|
@ -3,23 +3,19 @@ import { ILogger } from "@spt/models/spt/utils/ILogger";
|
||||
import { AsyncQueue } from "@spt/utils/AsyncQueue";
|
||||
import { WinstonMainLogger } from "@spt/utils/logging/WinstonMainLogger";
|
||||
|
||||
export class ErrorHandler
|
||||
{
|
||||
export class ErrorHandler {
|
||||
private logger: ILogger;
|
||||
private readLine: readline.Interface;
|
||||
|
||||
constructor()
|
||||
{
|
||||
constructor() {
|
||||
this.logger = new WinstonMainLogger(new AsyncQueue());
|
||||
this.readLine = readline.createInterface({ input: process.stdin, output: process.stdout });
|
||||
}
|
||||
|
||||
public handleCriticalError(err: Error): void
|
||||
{
|
||||
public handleCriticalError(err: Error): void {
|
||||
this.logger.error("The application had a critical error and failed to run");
|
||||
this.logger.error(`Exception produced: ${err.name}`);
|
||||
if (err.stack)
|
||||
{
|
||||
if (err.stack) {
|
||||
this.logger.error(`\nStacktrace:\n${err.stack}`);
|
||||
}
|
||||
|
||||
|
@ -1,25 +1,21 @@
|
||||
import { container } from "tsyringe";
|
||||
import { Container } from "@spt/di/Container";
|
||||
import { ErrorHandler } from "@spt/ErrorHandler";
|
||||
import { Container } from "@spt/di/Container";
|
||||
import type { PreSptModLoader } from "@spt/loaders/PreSptModLoader";
|
||||
import { App } from "@spt/utils/App";
|
||||
import { Watermark } from "@spt/utils/Watermark";
|
||||
import { container } from "tsyringe";
|
||||
|
||||
export class Program
|
||||
{
|
||||
export class Program {
|
||||
private errorHandler: ErrorHandler;
|
||||
constructor()
|
||||
{
|
||||
constructor() {
|
||||
// set window properties
|
||||
process.stdout.setEncoding("utf8");
|
||||
process.title = "SPT Server";
|
||||
this.errorHandler = new ErrorHandler();
|
||||
}
|
||||
|
||||
public async start(): Promise<void>
|
||||
{
|
||||
try
|
||||
{
|
||||
public async start(): Promise<void> {
|
||||
try {
|
||||
Container.registerTypes(container);
|
||||
const childContainer = container.createChildContainer();
|
||||
const watermark = childContainer.resolve<Watermark>("Watermark");
|
||||
@ -31,9 +27,7 @@ export class Program
|
||||
|
||||
Container.registerPostLoadTypes(container, childContainer);
|
||||
childContainer.resolve<App>("App").load();
|
||||
}
|
||||
catch (err: any)
|
||||
{
|
||||
} catch (err: any) {
|
||||
this.errorHandler.handleCriticalError(err instanceof Error ? err : new Error(err));
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { inject, injectable } from "tsyringe";
|
||||
import { AchievementController } from "@spt/controllers/AchievementController";
|
||||
import { ProfileController } from "@spt/controllers/ProfileController";
|
||||
import { IEmptyRequestData } from "@spt/models/eft/common/IEmptyRequestData";
|
||||
@ -6,16 +5,15 @@ import { IGetBodyResponseData } from "@spt/models/eft/httpResponse/IGetBodyRespo
|
||||
import { ICompletedAchievementsResponse } from "@spt/models/eft/profile/ICompletedAchievementsResponse";
|
||||
import { IGetAchievementsResponse } from "@spt/models/eft/profile/IGetAchievementsResponse";
|
||||
import { HttpResponseUtil } from "@spt/utils/HttpResponseUtil";
|
||||
import { inject, injectable } from "tsyringe";
|
||||
|
||||
@injectable()
|
||||
export class AchievementCallbacks
|
||||
{
|
||||
export class AchievementCallbacks {
|
||||
constructor(
|
||||
@inject("AchievementController") protected achievementController: AchievementController,
|
||||
@inject("ProfileController") protected profileController: ProfileController,
|
||||
@inject("HttpResponseUtil") protected httpResponse: HttpResponseUtil,
|
||||
)
|
||||
{}
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Handle client/achievement/list
|
||||
@ -24,8 +22,7 @@ export class AchievementCallbacks
|
||||
url: string,
|
||||
info: IEmptyRequestData,
|
||||
sessionID: string,
|
||||
): IGetBodyResponseData<IGetAchievementsResponse>
|
||||
{
|
||||
): IGetBodyResponseData<IGetAchievementsResponse> {
|
||||
return this.httpResponse.getBody(this.achievementController.getAchievements(sessionID));
|
||||
}
|
||||
|
||||
@ -36,8 +33,7 @@ export class AchievementCallbacks
|
||||
url: string,
|
||||
info: IEmptyRequestData,
|
||||
sessionID: string,
|
||||
): IGetBodyResponseData<ICompletedAchievementsResponse>
|
||||
{
|
||||
): IGetBodyResponseData<ICompletedAchievementsResponse> {
|
||||
return this.httpResponse.getBody(this.achievementController.getAchievementStatistics(sessionID));
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { inject, injectable } from "tsyringe";
|
||||
import { BotController } from "@spt/controllers/BotController";
|
||||
import { IGenerateBotsRequestData } from "@spt/models/eft/bot/IGenerateBotsRequestData";
|
||||
import { IEmptyRequestData } from "@spt/models/eft/common/IEmptyRequestData";
|
||||
@ -6,23 +5,21 @@ import { IBotBase } from "@spt/models/eft/common/tables/IBotBase";
|
||||
import { Difficulties } from "@spt/models/eft/common/tables/IBotType";
|
||||
import { IGetBodyResponseData } from "@spt/models/eft/httpResponse/IGetBodyResponseData";
|
||||
import { HttpResponseUtil } from "@spt/utils/HttpResponseUtil";
|
||||
import { inject, injectable } from "tsyringe";
|
||||
|
||||
@injectable()
|
||||
export class BotCallbacks
|
||||
{
|
||||
export class BotCallbacks {
|
||||
constructor(
|
||||
@inject("BotController") protected botController: BotController,
|
||||
@inject("HttpResponseUtil") protected httpResponse: HttpResponseUtil,
|
||||
)
|
||||
{}
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Handle singleplayer/settings/bot/limit
|
||||
* Is called by client to define each bot roles wave limit
|
||||
* @returns string
|
||||
*/
|
||||
public getBotLimit(url: string, info: IEmptyRequestData, sessionID: string): string
|
||||
{
|
||||
public getBotLimit(url: string, info: IEmptyRequestData, sessionID: string): string {
|
||||
const splittedUrl = url.split("/");
|
||||
const type = splittedUrl[splittedUrl.length - 1];
|
||||
return this.httpResponse.noBody(this.botController.getBotPresetGenerationLimit(type));
|
||||
@ -32,13 +29,11 @@ export class BotCallbacks
|
||||
* Handle singleplayer/settings/bot/difficulty
|
||||
* @returns string
|
||||
*/
|
||||
public getBotDifficulty(url: string, info: IEmptyRequestData, sessionID: string): string
|
||||
{
|
||||
public getBotDifficulty(url: string, info: IEmptyRequestData, sessionID: string): string {
|
||||
const splittedUrl = url.split("/");
|
||||
const type = splittedUrl[splittedUrl.length - 2].toLowerCase();
|
||||
const difficulty = splittedUrl[splittedUrl.length - 1];
|
||||
if (difficulty === "core")
|
||||
{
|
||||
if (difficulty === "core") {
|
||||
return this.httpResponse.noBody(this.botController.getBotCoreDifficulty());
|
||||
}
|
||||
return this.httpResponse.noBody(this.botController.getBotDifficulty(type, difficulty));
|
||||
@ -52,8 +47,7 @@ export class BotCallbacks
|
||||
url: string,
|
||||
info: IEmptyRequestData,
|
||||
sessionID: string,
|
||||
): Record<string, Difficulties>
|
||||
{
|
||||
): Record<string, Difficulties> {
|
||||
return this.httpResponse.noBody(this.botController.getAllBotDifficulties());
|
||||
}
|
||||
|
||||
@ -65,8 +59,7 @@ export class BotCallbacks
|
||||
url: string,
|
||||
info: IGenerateBotsRequestData,
|
||||
sessionID: string,
|
||||
): Promise<IGetBodyResponseData<IBotBase[]>>
|
||||
{
|
||||
): Promise<IGetBodyResponseData<IBotBase[]>> {
|
||||
return this.httpResponse.getBody(await this.botController.generate(sessionID, info));
|
||||
}
|
||||
|
||||
@ -74,8 +67,7 @@ export class BotCallbacks
|
||||
* Handle singleplayer/settings/bot/maxCap
|
||||
* @returns string
|
||||
*/
|
||||
public getBotCap(url: string, info: any, sessionID: string): string
|
||||
{
|
||||
public getBotCap(url: string, info: any, sessionID: string): string {
|
||||
const splitUrl = url.split("/");
|
||||
const location = splitUrl[splitUrl.length - 1];
|
||||
return this.httpResponse.noBody(this.botController.getBotCap(location));
|
||||
@ -85,8 +77,7 @@ export class BotCallbacks
|
||||
* Handle singleplayer/settings/bot/getBotBehaviours
|
||||
* @returns string
|
||||
*/
|
||||
public getBotBehaviours(): string
|
||||
{
|
||||
public getBotBehaviours(): string {
|
||||
return this.httpResponse.noBody(this.botController.getAiBotBrainTypes());
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { inject, injectable } from "tsyringe";
|
||||
import { BuildController } from "@spt/controllers/BuildController";
|
||||
import { ISetMagazineRequest } from "@spt/models/eft/builds/ISetMagazineRequest";
|
||||
import { IEmptyRequestData } from "@spt/models/eft/common/IEmptyRequestData";
|
||||
@ -8,29 +7,26 @@ import { IPresetBuildActionRequestData } from "@spt/models/eft/presetBuild/IPres
|
||||
import { IRemoveBuildRequestData } from "@spt/models/eft/presetBuild/IRemoveBuildRequestData";
|
||||
import { IUserBuilds } from "@spt/models/eft/profile/ISptProfile";
|
||||
import { HttpResponseUtil } from "@spt/utils/HttpResponseUtil";
|
||||
import { inject, injectable } from "tsyringe";
|
||||
|
||||
@injectable()
|
||||
export class BuildsCallbacks
|
||||
{
|
||||
export class BuildsCallbacks {
|
||||
constructor(
|
||||
@inject("HttpResponseUtil") protected httpResponse: HttpResponseUtil,
|
||||
@inject("BuildController") protected buildController: BuildController,
|
||||
)
|
||||
{}
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Handle client/builds/list
|
||||
*/
|
||||
public getBuilds(url: string, info: IEmptyRequestData, sessionID: string): IGetBodyResponseData<IUserBuilds>
|
||||
{
|
||||
public getBuilds(url: string, info: IEmptyRequestData, sessionID: string): IGetBodyResponseData<IUserBuilds> {
|
||||
return this.httpResponse.getBody(this.buildController.getUserBuilds(sessionID));
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle client/builds/magazine/save
|
||||
*/
|
||||
public createMagazineTemplate(url: string, request: ISetMagazineRequest, sessionID: string): INullResponseData
|
||||
{
|
||||
public createMagazineTemplate(url: string, request: ISetMagazineRequest, sessionID: string): INullResponseData {
|
||||
this.buildController.createMagazineTemplate(sessionID, request);
|
||||
|
||||
return this.httpResponse.nullResponse();
|
||||
@ -39,8 +35,7 @@ export class BuildsCallbacks
|
||||
/**
|
||||
* Handle client/builds/weapon/save
|
||||
*/
|
||||
public setWeapon(url: string, info: IPresetBuildActionRequestData, sessionID: string): INullResponseData
|
||||
{
|
||||
public setWeapon(url: string, info: IPresetBuildActionRequestData, sessionID: string): INullResponseData {
|
||||
this.buildController.saveWeaponBuild(sessionID, info);
|
||||
|
||||
return this.httpResponse.nullResponse();
|
||||
@ -49,8 +44,7 @@ export class BuildsCallbacks
|
||||
/**
|
||||
* Handle client/builds/equipment/save
|
||||
*/
|
||||
public setEquipment(url: string, info: IPresetBuildActionRequestData, sessionID: string): INullResponseData
|
||||
{
|
||||
public setEquipment(url: string, info: IPresetBuildActionRequestData, sessionID: string): INullResponseData {
|
||||
this.buildController.saveEquipmentBuild(sessionID, info);
|
||||
|
||||
return this.httpResponse.nullResponse();
|
||||
@ -59,8 +53,7 @@ export class BuildsCallbacks
|
||||
/**
|
||||
* Handle client/builds/delete
|
||||
*/
|
||||
public deleteBuild(url: string, info: IRemoveBuildRequestData, sessionID: string): INullResponseData
|
||||
{
|
||||
public deleteBuild(url: string, info: IRemoveBuildRequestData, sessionID: string): INullResponseData {
|
||||
this.buildController.removeBuild(sessionID, info);
|
||||
|
||||
return this.httpResponse.nullResponse();
|
||||
|
@ -1,34 +1,30 @@
|
||||
import { inject, injectable } from "tsyringe";
|
||||
import { BundleLoader } from "@spt/loaders/BundleLoader";
|
||||
import { ConfigTypes } from "@spt/models/enums/ConfigTypes";
|
||||
import { IHttpConfig } from "@spt/models/spt/config/IHttpConfig";
|
||||
import { ConfigServer } from "@spt/servers/ConfigServer";
|
||||
import { HttpResponseUtil } from "@spt/utils/HttpResponseUtil";
|
||||
import { inject, injectable } from "tsyringe";
|
||||
|
||||
@injectable()
|
||||
export class BundleCallbacks
|
||||
{
|
||||
export class BundleCallbacks {
|
||||
protected httpConfig: IHttpConfig;
|
||||
|
||||
constructor(
|
||||
@inject("HttpResponseUtil") protected httpResponse: HttpResponseUtil,
|
||||
@inject("BundleLoader") protected bundleLoader: BundleLoader,
|
||||
@inject("ConfigServer") protected configServer: ConfigServer,
|
||||
)
|
||||
{
|
||||
) {
|
||||
this.httpConfig = this.configServer.getConfig(ConfigTypes.HTTP);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle singleplayer/bundles
|
||||
*/
|
||||
public getBundles(url: string, info: any, sessionID: string): string
|
||||
{
|
||||
public getBundles(url: string, info: any, sessionID: string): string {
|
||||
return this.httpResponse.noBody(this.bundleLoader.getBundles());
|
||||
}
|
||||
|
||||
public getBundle(url: string, info: any, sessionID: string): string
|
||||
{
|
||||
public getBundle(url: string, info: any, sessionID: string): string {
|
||||
return "BUNDLE";
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { inject, injectable } from "tsyringe";
|
||||
import { ClientLogController } from "@spt/controllers/ClientLogController";
|
||||
import { ModLoadOrder } from "@spt/loaders/ModLoadOrder";
|
||||
import { INullResponseData } from "@spt/models/eft/httpResponse/INullResponseData";
|
||||
@ -8,25 +7,23 @@ import { IClientLogRequest } from "@spt/models/spt/logging/IClientLogRequest";
|
||||
import { ConfigServer } from "@spt/servers/ConfigServer";
|
||||
import { LocalisationService } from "@spt/services/LocalisationService";
|
||||
import { HttpResponseUtil } from "@spt/utils/HttpResponseUtil";
|
||||
import { inject, injectable } from "tsyringe";
|
||||
|
||||
/** Handle client logging related events */
|
||||
@injectable()
|
||||
export class ClientLogCallbacks
|
||||
{
|
||||
export class ClientLogCallbacks {
|
||||
constructor(
|
||||
@inject("HttpResponseUtil") protected httpResponse: HttpResponseUtil,
|
||||
@inject("ClientLogController") protected clientLogController: ClientLogController,
|
||||
@inject("ConfigServer") protected configServer: ConfigServer,
|
||||
@inject("LocalisationService") protected localisationService: LocalisationService,
|
||||
@inject("ModLoadOrder") protected modLoadOrder: ModLoadOrder,
|
||||
)
|
||||
{}
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Handle /singleplayer/log
|
||||
*/
|
||||
public clientLog(url: string, info: IClientLogRequest, sessionID: string): INullResponseData
|
||||
{
|
||||
public clientLog(url: string, info: IClientLogRequest, sessionID: string): INullResponseData {
|
||||
this.clientLogController.clientLog(info);
|
||||
return this.httpResponse.nullResponse();
|
||||
}
|
||||
@ -34,8 +31,7 @@ export class ClientLogCallbacks
|
||||
/**
|
||||
* Handle /singleplayer/release
|
||||
*/
|
||||
public releaseNotes(): string
|
||||
{
|
||||
public releaseNotes(): string {
|
||||
const data: IRelease = this.configServer.getConfig<ICoreConfig>(ConfigTypes.CORE).release;
|
||||
|
||||
data.betaDisclaimerText = globalThis.G_MODS_ENABLED
|
||||
@ -62,8 +58,7 @@ export class ClientLogCallbacks
|
||||
* Handle /singleplayer/enableBSGlogging
|
||||
*/
|
||||
|
||||
public bsgLogging(): string
|
||||
{
|
||||
public bsgLogging(): string {
|
||||
const data: IBsgLogging = this.configServer.getConfig<ICoreConfig>(ConfigTypes.CORE).bsgLogging;
|
||||
return this.httpResponse.noBody(data);
|
||||
}
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { inject, injectable } from "tsyringe";
|
||||
import { CustomizationController } from "@spt/controllers/CustomizationController";
|
||||
import { IEmptyRequestData } from "@spt/models/eft/common/IEmptyRequestData";
|
||||
import { IPmcData } from "@spt/models/eft/common/IPmcData";
|
||||
@ -10,23 +9,21 @@ import { IGetBodyResponseData } from "@spt/models/eft/httpResponse/IGetBodyRespo
|
||||
import { IItemEventRouterResponse } from "@spt/models/eft/itemEvent/IItemEventRouterResponse";
|
||||
import { SaveServer } from "@spt/servers/SaveServer";
|
||||
import { HttpResponseUtil } from "@spt/utils/HttpResponseUtil";
|
||||
import { inject, injectable } from "tsyringe";
|
||||
|
||||
@injectable()
|
||||
export class CustomizationCallbacks
|
||||
{
|
||||
export class CustomizationCallbacks {
|
||||
constructor(
|
||||
@inject("CustomizationController") protected customizationController: CustomizationController,
|
||||
@inject("SaveServer") protected saveServer: SaveServer,
|
||||
@inject("HttpResponseUtil") protected httpResponse: HttpResponseUtil,
|
||||
)
|
||||
{}
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Handle client/trading/customization/storage
|
||||
* @returns IGetSuitsResponse
|
||||
*/
|
||||
public getSuits(url: string, info: IEmptyRequestData, sessionID: string): IGetBodyResponseData<IGetSuitsResponse>
|
||||
{
|
||||
public getSuits(url: string, info: IEmptyRequestData, sessionID: string): IGetBodyResponseData<IGetSuitsResponse> {
|
||||
const result: IGetSuitsResponse = { _id: sessionID, suites: this.saveServer.getProfile(sessionID).suits };
|
||||
return this.httpResponse.getBody(result);
|
||||
}
|
||||
@ -35,8 +32,7 @@ export class CustomizationCallbacks
|
||||
* Handle client/trading/customization
|
||||
* @returns ISuit[]
|
||||
*/
|
||||
public getTraderSuits(url: string, info: IEmptyRequestData, sessionID: string): IGetBodyResponseData<ISuit[]>
|
||||
{
|
||||
public getTraderSuits(url: string, info: IEmptyRequestData, sessionID: string): IGetBodyResponseData<ISuit[]> {
|
||||
const splittedUrl = url.split("/");
|
||||
const traderID = splittedUrl[splittedUrl.length - 2];
|
||||
|
||||
@ -50,16 +46,14 @@ export class CustomizationCallbacks
|
||||
pmcData: IPmcData,
|
||||
body: IWearClothingRequestData,
|
||||
sessionID: string,
|
||||
): IItemEventRouterResponse
|
||||
{
|
||||
): IItemEventRouterResponse {
|
||||
return this.customizationController.wearClothing(pmcData, body, sessionID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle CustomizationBuy event
|
||||
*/
|
||||
public buyClothing(pmcData: IPmcData, body: IBuyClothingRequestData, sessionID: string): IItemEventRouterResponse
|
||||
{
|
||||
public buyClothing(pmcData: IPmcData, body: IBuyClothingRequestData, sessionID: string): IItemEventRouterResponse {
|
||||
return this.customizationController.buyClothing(pmcData, body, sessionID);
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { inject, injectable } from "tsyringe";
|
||||
import { HideoutController } from "@spt/controllers/HideoutController";
|
||||
import { RagfairController } from "@spt/controllers/RagfairController";
|
||||
import { TraderHelper } from "@spt/helpers/TraderHelper";
|
||||
@ -17,13 +16,13 @@ import { ISettingsBase } from "@spt/models/spt/server/ISettingsBase";
|
||||
import { DatabaseService } from "@spt/services/DatabaseService";
|
||||
import { HttpResponseUtil } from "@spt/utils/HttpResponseUtil";
|
||||
import { TimeUtil } from "@spt/utils/TimeUtil";
|
||||
import { inject, injectable } from "tsyringe";
|
||||
|
||||
/**
|
||||
* Handle client requests
|
||||
*/
|
||||
@injectable()
|
||||
export class DataCallbacks
|
||||
{
|
||||
export class DataCallbacks {
|
||||
constructor(
|
||||
@inject("HttpResponseUtil") protected httpResponse: HttpResponseUtil,
|
||||
@inject("TimeUtil") protected timeUtil: TimeUtil,
|
||||
@ -31,15 +30,13 @@ export class DataCallbacks
|
||||
@inject("DatabaseService") protected databaseService: DatabaseService,
|
||||
@inject("RagfairController") protected ragfairController: RagfairController,
|
||||
@inject("HideoutController") protected hideoutController: HideoutController,
|
||||
)
|
||||
{}
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Handle client/settings
|
||||
* @returns ISettingsBase
|
||||
*/
|
||||
public getSettings(url: string, info: IEmptyRequestData, sessionID: string): IGetBodyResponseData<ISettingsBase>
|
||||
{
|
||||
public getSettings(url: string, info: IEmptyRequestData, sessionID: string): IGetBodyResponseData<ISettingsBase> {
|
||||
return this.httpResponse.getBody(this.databaseService.getSettings());
|
||||
}
|
||||
|
||||
@ -47,8 +44,7 @@ export class DataCallbacks
|
||||
* Handle client/globals
|
||||
* @returns IGlobals
|
||||
*/
|
||||
public getGlobals(url: string, info: IEmptyRequestData, sessionID: string): IGetBodyResponseData<IGlobals>
|
||||
{
|
||||
public getGlobals(url: string, info: IEmptyRequestData, sessionID: string): IGetBodyResponseData<IGlobals> {
|
||||
const globals = this.databaseService.getGlobals();
|
||||
globals.time = Date.now() / 1000;
|
||||
|
||||
@ -59,8 +55,7 @@ export class DataCallbacks
|
||||
* Handle client/items
|
||||
* @returns string
|
||||
*/
|
||||
public getTemplateItems(url: string, info: IEmptyRequestData, sessionID: string): string
|
||||
{
|
||||
public getTemplateItems(url: string, info: IEmptyRequestData, sessionID: string): string {
|
||||
return this.httpResponse.getUnclearedBody(this.databaseService.getItems());
|
||||
}
|
||||
|
||||
@ -72,8 +67,7 @@ export class DataCallbacks
|
||||
url: string,
|
||||
info: IEmptyRequestData,
|
||||
sessionID: string,
|
||||
): IGetBodyResponseData<IHandbookBase>
|
||||
{
|
||||
): IGetBodyResponseData<IHandbookBase> {
|
||||
return this.httpResponse.getBody(this.databaseService.getHandbook());
|
||||
}
|
||||
|
||||
@ -85,8 +79,7 @@ export class DataCallbacks
|
||||
url: string,
|
||||
info: IEmptyRequestData,
|
||||
sessionID: string,
|
||||
): IGetBodyResponseData<Record<string, ICustomizationItem>>
|
||||
{
|
||||
): IGetBodyResponseData<Record<string, ICustomizationItem>> {
|
||||
return this.httpResponse.getBody(this.databaseService.getTemplates().customization);
|
||||
}
|
||||
|
||||
@ -98,8 +91,7 @@ export class DataCallbacks
|
||||
url: string,
|
||||
info: IEmptyRequestData,
|
||||
sessionID: string,
|
||||
): IGetBodyResponseData<string[]>
|
||||
{
|
||||
): IGetBodyResponseData<string[]> {
|
||||
return this.httpResponse.getBody(this.databaseService.getTemplates().character);
|
||||
}
|
||||
|
||||
@ -111,8 +103,7 @@ export class DataCallbacks
|
||||
url: string,
|
||||
info: IEmptyRequestData,
|
||||
sessionID: string,
|
||||
): IGetBodyResponseData<IHideoutSettingsBase>
|
||||
{
|
||||
): IGetBodyResponseData<IHideoutSettingsBase> {
|
||||
return this.httpResponse.getBody(this.databaseService.getHideout().settings);
|
||||
}
|
||||
|
||||
@ -120,8 +111,7 @@ export class DataCallbacks
|
||||
url: string,
|
||||
info: IEmptyRequestData,
|
||||
sessionID: string,
|
||||
): IGetBodyResponseData<IHideoutArea[]>
|
||||
{
|
||||
): IGetBodyResponseData<IHideoutArea[]> {
|
||||
return this.httpResponse.getBody(this.databaseService.getHideout().areas);
|
||||
}
|
||||
|
||||
@ -129,8 +119,7 @@ export class DataCallbacks
|
||||
url: string,
|
||||
info: IEmptyRequestData,
|
||||
sessionID: string,
|
||||
): IGetBodyResponseData<IHideoutProduction[]>
|
||||
{
|
||||
): IGetBodyResponseData<IHideoutProduction[]> {
|
||||
return this.httpResponse.getBody(this.databaseService.getHideout().production);
|
||||
}
|
||||
|
||||
@ -138,8 +127,7 @@ export class DataCallbacks
|
||||
url: string,
|
||||
info: IEmptyRequestData,
|
||||
sessionID: string,
|
||||
): IGetBodyResponseData<IHideoutScavCase[]>
|
||||
{
|
||||
): IGetBodyResponseData<IHideoutScavCase[]> {
|
||||
return this.httpResponse.getBody(this.databaseService.getHideout().scavcase);
|
||||
}
|
||||
|
||||
@ -150,27 +138,23 @@ export class DataCallbacks
|
||||
url: string,
|
||||
info: IEmptyRequestData,
|
||||
sessionID: string,
|
||||
): IGetBodyResponseData<Record<string, string>>
|
||||
{
|
||||
): IGetBodyResponseData<Record<string, string>> {
|
||||
return this.httpResponse.getBody(this.databaseService.getLocales().languages);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle client/menu/locale
|
||||
*/
|
||||
public getLocalesMenu(url: string, info: IEmptyRequestData, sessionID: string): IGetBodyResponseData<string>
|
||||
{
|
||||
public getLocalesMenu(url: string, info: IEmptyRequestData, sessionID: string): IGetBodyResponseData<string> {
|
||||
const localeId = url.replace("/client/menu/locale/", "");
|
||||
const locales = this.databaseService.getLocales();
|
||||
let result = locales.menu[localeId];
|
||||
|
||||
if (result === undefined)
|
||||
{
|
||||
if (result === undefined) {
|
||||
result = locales.menu.en;
|
||||
}
|
||||
|
||||
if (result === undefined)
|
||||
throw new Error(`Unable to determine locale for request with '${localeId}'`);
|
||||
if (result === undefined) throw new Error(`Unable to determine locale for request with '${localeId}'`);
|
||||
|
||||
return this.httpResponse.getBody(result);
|
||||
}
|
||||
@ -178,14 +162,12 @@ export class DataCallbacks
|
||||
/**
|
||||
* Handle client/locale
|
||||
*/
|
||||
public getLocalesGlobal(url: string, info: IEmptyRequestData, sessionID: string): string
|
||||
{
|
||||
public getLocalesGlobal(url: string, info: IEmptyRequestData, sessionID: string): string {
|
||||
const localeId = url.replace("/client/locale/", "");
|
||||
const locales = this.databaseService.getLocales();
|
||||
let result = locales.global[localeId];
|
||||
|
||||
if (result === undefined)
|
||||
{
|
||||
if (result === undefined) {
|
||||
result = locales.global["en"];
|
||||
}
|
||||
|
||||
@ -195,8 +177,7 @@ export class DataCallbacks
|
||||
/**
|
||||
* Handle client/hideout/qte/list
|
||||
*/
|
||||
public getQteList(url: string, info: IEmptyRequestData, sessionID: string): string
|
||||
{
|
||||
public getQteList(url: string, info: IEmptyRequestData, sessionID: string): string {
|
||||
return this.httpResponse.getUnclearedBody(this.hideoutController.getQteList(sessionID));
|
||||
}
|
||||
|
||||
@ -209,16 +190,14 @@ export class DataCallbacks
|
||||
url: string,
|
||||
info: IEmptyRequestData,
|
||||
sessionID: string,
|
||||
): IGetBodyResponseData<IGetItemPricesResponse>
|
||||
{
|
||||
): IGetBodyResponseData<IGetItemPricesResponse> {
|
||||
const traderId = url.replace("/client/items/prices/", "");
|
||||
|
||||
// All traders share same item prices, unknown how to tell what items are shown for each trader
|
||||
// Shown items listed are likely linked to traders items_buy/category array
|
||||
const handbookPrices = this.ragfairController.getStaticPrices();
|
||||
|
||||
const response: IGetItemPricesResponse
|
||||
= {
|
||||
const response: IGetItemPricesResponse = {
|
||||
supplyNextTime: this.traderHelper.getNextUpdateTimestamp(traderId),
|
||||
prices: handbookPrices,
|
||||
currencyCourses: {
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { inject, injectable } from "tsyringe";
|
||||
import { DialogueController } from "@spt/controllers/DialogueController";
|
||||
import { OnUpdate } from "@spt/di/OnUpdate";
|
||||
import { IEmptyRequestData } from "@spt/models/eft/common/IEmptyRequestData";
|
||||
@ -36,17 +35,16 @@ import { DialogueInfo } from "@spt/models/eft/profile/ISptProfile";
|
||||
import { HashUtil } from "@spt/utils/HashUtil";
|
||||
import { HttpResponseUtil } from "@spt/utils/HttpResponseUtil";
|
||||
import { TimeUtil } from "@spt/utils/TimeUtil";
|
||||
import { inject, injectable } from "tsyringe";
|
||||
|
||||
@injectable()
|
||||
export class DialogueCallbacks implements OnUpdate
|
||||
{
|
||||
export class DialogueCallbacks implements OnUpdate {
|
||||
constructor(
|
||||
@inject("HashUtil") protected hashUtil: HashUtil,
|
||||
@inject("TimeUtil") protected timeUtil: TimeUtil,
|
||||
@inject("HttpResponseUtil") protected httpResponse: HttpResponseUtil,
|
||||
@inject("DialogueController") protected dialogueController: DialogueController,
|
||||
)
|
||||
{}
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Handle client/friend/list
|
||||
@ -56,8 +54,7 @@ export class DialogueCallbacks implements OnUpdate
|
||||
url: string,
|
||||
info: IEmptyRequestData,
|
||||
sessionID: string,
|
||||
): IGetBodyResponseData<IGetFriendListDataResponse>
|
||||
{
|
||||
): IGetBodyResponseData<IGetFriendListDataResponse> {
|
||||
return this.httpResponse.getBody(this.dialogueController.getFriendList(sessionID));
|
||||
}
|
||||
|
||||
@ -69,8 +66,7 @@ export class DialogueCallbacks implements OnUpdate
|
||||
url: string,
|
||||
info: IGetChatServerListRequestData,
|
||||
sessionID: string,
|
||||
): IGetBodyResponseData<IChatServer[]>
|
||||
{
|
||||
): IGetBodyResponseData<IChatServer[]> {
|
||||
const chatServer: IChatServer = {
|
||||
_id: this.hashUtil.generate(),
|
||||
RegistrationId: 20,
|
||||
@ -91,8 +87,7 @@ export class DialogueCallbacks implements OnUpdate
|
||||
url: string,
|
||||
info: IGetMailDialogListRequestData,
|
||||
sessionID: string,
|
||||
): IGetBodyResponseData<DialogueInfo[]>
|
||||
{
|
||||
): IGetBodyResponseData<DialogueInfo[]> {
|
||||
return this.httpResponse.getBody(this.dialogueController.generateDialogueList(sessionID), 0, undefined, false);
|
||||
}
|
||||
|
||||
@ -101,8 +96,7 @@ export class DialogueCallbacks implements OnUpdate
|
||||
url: string,
|
||||
info: IGetMailDialogViewRequestData,
|
||||
sessionID: string,
|
||||
): IGetBodyResponseData<IGetMailDialogViewResponseData>
|
||||
{
|
||||
): IGetBodyResponseData<IGetMailDialogViewResponseData> {
|
||||
return this.httpResponse.getBody(
|
||||
this.dialogueController.generateDialogueView(info, sessionID),
|
||||
0,
|
||||
@ -116,35 +110,30 @@ export class DialogueCallbacks implements OnUpdate
|
||||
url: string,
|
||||
info: IGetMailDialogInfoRequestData,
|
||||
sessionID: string,
|
||||
): IGetBodyResponseData<DialogueInfo>
|
||||
{
|
||||
): IGetBodyResponseData<DialogueInfo> {
|
||||
return this.httpResponse.getBody(this.dialogueController.getDialogueInfo(info.dialogId, sessionID));
|
||||
}
|
||||
|
||||
/** Handle client/mail/dialog/remove */
|
||||
public removeDialog(url: string, info: IRemoveDialogRequestData, sessionID: string): IGetBodyResponseData<any[]>
|
||||
{
|
||||
public removeDialog(url: string, info: IRemoveDialogRequestData, sessionID: string): IGetBodyResponseData<any[]> {
|
||||
this.dialogueController.removeDialogue(info.dialogId, sessionID);
|
||||
return this.httpResponse.emptyArrayResponse();
|
||||
}
|
||||
|
||||
/** Handle client/mail/dialog/pin */
|
||||
public pinDialog(url: string, info: IPinDialogRequestData, sessionID: string): IGetBodyResponseData<any[]>
|
||||
{
|
||||
public pinDialog(url: string, info: IPinDialogRequestData, sessionID: string): IGetBodyResponseData<any[]> {
|
||||
this.dialogueController.setDialoguePin(info.dialogId, true, sessionID);
|
||||
return this.httpResponse.emptyArrayResponse();
|
||||
}
|
||||
|
||||
/** Handle client/mail/dialog/unpin */
|
||||
public unpinDialog(url: string, info: IPinDialogRequestData, sessionID: string): IGetBodyResponseData<any[]>
|
||||
{
|
||||
public unpinDialog(url: string, info: IPinDialogRequestData, sessionID: string): IGetBodyResponseData<any[]> {
|
||||
this.dialogueController.setDialoguePin(info.dialogId, false, sessionID);
|
||||
return this.httpResponse.emptyArrayResponse();
|
||||
}
|
||||
|
||||
/** Handle client/mail/dialog/read */
|
||||
public setRead(url: string, info: ISetDialogReadRequestData, sessionID: string): IGetBodyResponseData<any[]>
|
||||
{
|
||||
public setRead(url: string, info: ISetDialogReadRequestData, sessionID: string): IGetBodyResponseData<any[]> {
|
||||
this.dialogueController.setRead(info.dialogs, sessionID);
|
||||
return this.httpResponse.emptyArrayResponse();
|
||||
}
|
||||
@ -157,28 +146,24 @@ export class DialogueCallbacks implements OnUpdate
|
||||
url: string,
|
||||
info: IGetAllAttachmentsRequestData,
|
||||
sessionID: string,
|
||||
): IGetBodyResponseData<IGetAllAttachmentsResponse | undefined>
|
||||
{
|
||||
): IGetBodyResponseData<IGetAllAttachmentsResponse | undefined> {
|
||||
return this.httpResponse.getBody(this.dialogueController.getAllAttachments(info.dialogId, sessionID));
|
||||
}
|
||||
|
||||
/** Handle client/mail/msg/send */
|
||||
public sendMessage(url: string, request: ISendMessageRequest, sessionID: string): IGetBodyResponseData<string>
|
||||
{
|
||||
public sendMessage(url: string, request: ISendMessageRequest, sessionID: string): IGetBodyResponseData<string> {
|
||||
return this.httpResponse.getBody(this.dialogueController.sendMessage(sessionID, request));
|
||||
}
|
||||
|
||||
/** Handle client/friend/request/list/outbox */
|
||||
public listOutbox(url: string, info: IEmptyRequestData, sessionID: string): IGetBodyResponseData<any[]>
|
||||
{
|
||||
public listOutbox(url: string, info: IEmptyRequestData, sessionID: string): IGetBodyResponseData<any[]> {
|
||||
return this.httpResponse.getBody([]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle client/friend/request/list/inbox
|
||||
*/
|
||||
public listInbox(url: string, info: IEmptyRequestData, sessionID: string): IGetBodyResponseData<any[]>
|
||||
{
|
||||
public listInbox(url: string, info: IEmptyRequestData, sessionID: string): IGetBodyResponseData<any[]> {
|
||||
return this.httpResponse.getBody([]);
|
||||
}
|
||||
|
||||
@ -189,16 +174,14 @@ export class DialogueCallbacks implements OnUpdate
|
||||
url: string,
|
||||
request: IFriendRequestData,
|
||||
sessionID: string,
|
||||
): IGetBodyResponseData<IFriendRequestSendResponse>
|
||||
{
|
||||
): IGetBodyResponseData<IFriendRequestSendResponse> {
|
||||
return this.httpResponse.getBody(this.dialogueController.sendFriendRequest(sessionID, request));
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle client/friend/request/accept-all
|
||||
*/
|
||||
public acceptAllFriendRequests(url: string, request: IEmptyRequestData, sessionID: string): INullResponseData
|
||||
{
|
||||
public acceptAllFriendRequests(url: string, request: IEmptyRequestData, sessionID: string): INullResponseData {
|
||||
return this.httpResponse.nullResponse();
|
||||
}
|
||||
|
||||
@ -209,8 +192,7 @@ export class DialogueCallbacks implements OnUpdate
|
||||
url: string,
|
||||
request: IAcceptFriendRequestData,
|
||||
sessionID: string,
|
||||
): IGetBodyResponseData<boolean>
|
||||
{
|
||||
): IGetBodyResponseData<boolean> {
|
||||
return this.httpResponse.getBody(true);
|
||||
}
|
||||
|
||||
@ -221,8 +203,7 @@ export class DialogueCallbacks implements OnUpdate
|
||||
url: string,
|
||||
request: IDeclineFriendRequestData,
|
||||
sessionID: string,
|
||||
): IGetBodyResponseData<boolean>
|
||||
{
|
||||
): IGetBodyResponseData<boolean> {
|
||||
return this.httpResponse.getBody(true);
|
||||
}
|
||||
|
||||
@ -233,41 +214,34 @@ export class DialogueCallbacks implements OnUpdate
|
||||
url: string,
|
||||
request: ICancelFriendRequestData,
|
||||
sessionID: string,
|
||||
): IGetBodyResponseData<boolean>
|
||||
{
|
||||
): IGetBodyResponseData<boolean> {
|
||||
return this.httpResponse.getBody(true);
|
||||
}
|
||||
|
||||
/** Handle client/friend/delete */
|
||||
public deleteFriend(url: string, request: IDeleteFriendRequest, sessionID: string): INullResponseData
|
||||
{
|
||||
public deleteFriend(url: string, request: IDeleteFriendRequest, sessionID: string): INullResponseData {
|
||||
return this.httpResponse.nullResponse();
|
||||
}
|
||||
|
||||
/** Handle client/friend/ignore/set */
|
||||
public ignoreFriend(url: string, request: IUIDRequestData, sessionID: string): INullResponseData
|
||||
{
|
||||
public ignoreFriend(url: string, request: IUIDRequestData, sessionID: string): INullResponseData {
|
||||
return this.httpResponse.nullResponse();
|
||||
}
|
||||
|
||||
/** Handle client/friend/ignore/remove */
|
||||
public unIgnoreFriend(url: string, request: IUIDRequestData, sessionID: string): INullResponseData
|
||||
{
|
||||
public unIgnoreFriend(url: string, request: IUIDRequestData, sessionID: string): INullResponseData {
|
||||
return this.httpResponse.nullResponse();
|
||||
}
|
||||
|
||||
public clearMail(url: string, request: IClearMailMessageRequest, sessionID: string): IGetBodyResponseData<any[]>
|
||||
{
|
||||
public clearMail(url: string, request: IClearMailMessageRequest, sessionID: string): IGetBodyResponseData<any[]> {
|
||||
return this.httpResponse.emptyArrayResponse();
|
||||
}
|
||||
|
||||
public removeMail(url: string, request: IRemoveMailMessageRequest, sessionID: string): IGetBodyResponseData<any[]>
|
||||
{
|
||||
public removeMail(url: string, request: IRemoveMailMessageRequest, sessionID: string): IGetBodyResponseData<any[]> {
|
||||
return this.httpResponse.emptyArrayResponse();
|
||||
}
|
||||
|
||||
public createGroupMail(url: string, info: ICreateGroupMailRequest, sessionID: string): IGetBodyResponseData<any[]>
|
||||
{
|
||||
public createGroupMail(url: string, info: ICreateGroupMailRequest, sessionID: string): IGetBodyResponseData<any[]> {
|
||||
return this.httpResponse.emptyArrayResponse();
|
||||
}
|
||||
|
||||
@ -275,13 +249,11 @@ export class DialogueCallbacks implements OnUpdate
|
||||
url: string,
|
||||
info: IChangeGroupMailOwnerRequest,
|
||||
sessionID: string,
|
||||
): IGetBodyResponseData<any[]>
|
||||
{
|
||||
): IGetBodyResponseData<any[]> {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
|
||||
public addUserToMail(url: string, info: IAddUserGroupMailRequest, sessionID: string): IGetBodyResponseData<any[]>
|
||||
{
|
||||
public addUserToMail(url: string, info: IAddUserGroupMailRequest, sessionID: string): IGetBodyResponseData<any[]> {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
|
||||
@ -289,19 +261,16 @@ export class DialogueCallbacks implements OnUpdate
|
||||
url: string,
|
||||
info: IRemoveUserGroupMailRequest,
|
||||
sessionID: string,
|
||||
): IGetBodyResponseData<any[]>
|
||||
{
|
||||
): IGetBodyResponseData<any[]> {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
|
||||
public async onUpdate(timeSinceLastRun: number): Promise<boolean>
|
||||
{
|
||||
public async onUpdate(timeSinceLastRun: number): Promise<boolean> {
|
||||
this.dialogueController.update();
|
||||
return true;
|
||||
}
|
||||
|
||||
public getRoute(): string
|
||||
{
|
||||
public getRoute(): string {
|
||||
return "spt-dialogue";
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { inject, injectable } from "tsyringe";
|
||||
import { GameController } from "@spt/controllers/GameController";
|
||||
import { OnLoad } from "@spt/di/OnLoad";
|
||||
import { IEmptyRequestData } from "@spt/models/eft/common/IEmptyRequestData";
|
||||
@ -21,25 +20,22 @@ import { INullResponseData } from "@spt/models/eft/httpResponse/INullResponseDat
|
||||
import { SaveServer } from "@spt/servers/SaveServer";
|
||||
import { HttpResponseUtil } from "@spt/utils/HttpResponseUtil";
|
||||
import { Watermark } from "@spt/utils/Watermark";
|
||||
import { inject, injectable } from "tsyringe";
|
||||
|
||||
@injectable()
|
||||
export class GameCallbacks implements OnLoad
|
||||
{
|
||||
export class GameCallbacks implements OnLoad {
|
||||
constructor(
|
||||
@inject("HttpResponseUtil") protected httpResponse: HttpResponseUtil,
|
||||
@inject("Watermark") protected watermark: Watermark,
|
||||
@inject("SaveServer") protected saveServer: SaveServer,
|
||||
@inject("GameController") protected gameController: GameController,
|
||||
)
|
||||
{}
|
||||
) {}
|
||||
|
||||
public async onLoad(): Promise<void>
|
||||
{
|
||||
public async onLoad(): Promise<void> {
|
||||
this.gameController.load();
|
||||
}
|
||||
|
||||
public getRoute(): string
|
||||
{
|
||||
public getRoute(): string {
|
||||
return "spt-game";
|
||||
}
|
||||
|
||||
@ -47,8 +43,7 @@ export class GameCallbacks implements OnLoad
|
||||
* Handle client/game/version/validate
|
||||
* @returns INullResponseData
|
||||
*/
|
||||
public versionValidate(url: string, info: IVersionValidateRequestData, sessionID: string): INullResponseData
|
||||
{
|
||||
public versionValidate(url: string, info: IVersionValidateRequestData, sessionID: string): INullResponseData {
|
||||
return this.httpResponse.nullResponse();
|
||||
}
|
||||
|
||||
@ -60,8 +55,7 @@ export class GameCallbacks implements OnLoad
|
||||
url: string,
|
||||
info: IEmptyRequestData,
|
||||
sessionID: string,
|
||||
): IGetBodyResponseData<IGameStartResponse>
|
||||
{
|
||||
): IGetBodyResponseData<IGameStartResponse> {
|
||||
const today = new Date().toUTCString();
|
||||
const startTimeStampMS = Date.parse(today);
|
||||
this.gameController.gameStart(url, info, sessionID, startTimeStampMS);
|
||||
@ -77,8 +71,7 @@ export class GameCallbacks implements OnLoad
|
||||
url: string,
|
||||
info: IEmptyRequestData,
|
||||
sessionID: string,
|
||||
): IGetBodyResponseData<IGameLogoutResponseData>
|
||||
{
|
||||
): IGetBodyResponseData<IGameLogoutResponseData> {
|
||||
this.saveServer.save();
|
||||
return this.httpResponse.getBody({ status: "ok" });
|
||||
}
|
||||
@ -91,8 +84,7 @@ export class GameCallbacks implements OnLoad
|
||||
url: string,
|
||||
info: IGameEmptyCrcRequestData,
|
||||
sessionID: string,
|
||||
): IGetBodyResponseData<IGameConfigResponse>
|
||||
{
|
||||
): IGetBodyResponseData<IGameConfigResponse> {
|
||||
return this.httpResponse.getBody(this.gameController.getGameConfig(sessionID));
|
||||
}
|
||||
|
||||
@ -104,16 +96,14 @@ export class GameCallbacks implements OnLoad
|
||||
url: string,
|
||||
info: IGameModeRequestData,
|
||||
sessionID: string,
|
||||
): IGetBodyResponseData<IGameModeResponse>
|
||||
{
|
||||
): IGetBodyResponseData<IGameModeResponse> {
|
||||
return this.httpResponse.getBody(this.gameController.getGameMode(sessionID, info));
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle client/server/list
|
||||
*/
|
||||
public getServer(url: string, info: IEmptyRequestData, sessionID: string): IGetBodyResponseData<IServerDetails[]>
|
||||
{
|
||||
public getServer(url: string, info: IEmptyRequestData, sessionID: string): IGetBodyResponseData<IServerDetails[]> {
|
||||
return this.httpResponse.getBody(this.gameController.getServer(sessionID));
|
||||
}
|
||||
|
||||
@ -124,8 +114,7 @@ export class GameCallbacks implements OnLoad
|
||||
url: string,
|
||||
info: IEmptyRequestData,
|
||||
sessionID: string,
|
||||
): IGetBodyResponseData<ICurrentGroupResponse>
|
||||
{
|
||||
): IGetBodyResponseData<ICurrentGroupResponse> {
|
||||
return this.httpResponse.getBody(this.gameController.getCurrentGroup(sessionID));
|
||||
}
|
||||
|
||||
@ -136,8 +125,7 @@ export class GameCallbacks implements OnLoad
|
||||
url: string,
|
||||
info: IEmptyRequestData,
|
||||
sessionID: string,
|
||||
): IGetBodyResponseData<ICheckVersionResponse>
|
||||
{
|
||||
): IGetBodyResponseData<ICheckVersionResponse> {
|
||||
return this.httpResponse.getBody(this.gameController.getValidGameVersion(sessionID));
|
||||
}
|
||||
|
||||
@ -149,8 +137,7 @@ export class GameCallbacks implements OnLoad
|
||||
url: string,
|
||||
info: IEmptyRequestData,
|
||||
sessionID: string,
|
||||
): IGetBodyResponseData<IGameKeepAliveResponse>
|
||||
{
|
||||
): IGetBodyResponseData<IGameKeepAliveResponse> {
|
||||
return this.httpResponse.getBody(this.gameController.getKeepAlive(sessionID));
|
||||
}
|
||||
|
||||
@ -158,8 +145,7 @@ export class GameCallbacks implements OnLoad
|
||||
* Handle singleplayer/settings/version
|
||||
* @returns string
|
||||
*/
|
||||
public getVersion(url: string, info: IEmptyRequestData, sessionID: string): string
|
||||
{
|
||||
public getVersion(url: string, info: IEmptyRequestData, sessionID: string): string {
|
||||
return this.httpResponse.noBody({ Version: this.watermark.getInGameVersionLabel() });
|
||||
}
|
||||
|
||||
@ -167,8 +153,7 @@ export class GameCallbacks implements OnLoad
|
||||
* Handle /client/report/send & /client/reports/lobby/send
|
||||
* @returns INullResponseData
|
||||
*/
|
||||
public reportNickname(url: string, info: IUIDRequestData, sessionID: string): INullResponseData
|
||||
{
|
||||
public reportNickname(url: string, info: IUIDRequestData, sessionID: string): INullResponseData {
|
||||
return this.httpResponse.nullResponse();
|
||||
}
|
||||
|
||||
@ -176,8 +161,7 @@ export class GameCallbacks implements OnLoad
|
||||
* Handle singleplayer/settings/getRaidTime
|
||||
* @returns string
|
||||
*/
|
||||
public getRaidTime(url: string, request: IGetRaidTimeRequest, sessionID: string): IGetRaidTimeResponse
|
||||
{
|
||||
public getRaidTime(url: string, request: IGetRaidTimeRequest, sessionID: string): IGetRaidTimeResponse {
|
||||
return this.httpResponse.noBody(this.gameController.getRaidTime(sessionID, request));
|
||||
}
|
||||
|
||||
@ -185,8 +169,7 @@ export class GameCallbacks implements OnLoad
|
||||
* Handle /client/survey
|
||||
* @returns INullResponseData
|
||||
*/
|
||||
public getSurvey(url: string, request: IEmptyRequestData, sessionID: string): INullResponseData
|
||||
{
|
||||
public getSurvey(url: string, request: IEmptyRequestData, sessionID: string): INullResponseData {
|
||||
return this.httpResponse.nullResponse();
|
||||
}
|
||||
}
|
||||
|
@ -1,20 +1,16 @@
|
||||
import { inject, injectable } from "tsyringe";
|
||||
import { HandbookController } from "@spt/controllers/HandbookController";
|
||||
import { OnLoad } from "@spt/di/OnLoad";
|
||||
import { inject, injectable } from "tsyringe";
|
||||
|
||||
@injectable()
|
||||
export class HandbookCallbacks implements OnLoad
|
||||
{
|
||||
constructor(@inject("HandbookController") protected handbookController: HandbookController)
|
||||
{}
|
||||
export class HandbookCallbacks implements OnLoad {
|
||||
constructor(@inject("HandbookController") protected handbookController: HandbookController) {}
|
||||
|
||||
public async onLoad(): Promise<void>
|
||||
{
|
||||
public async onLoad(): Promise<void> {
|
||||
this.handbookController.load();
|
||||
}
|
||||
|
||||
public getRoute(): string
|
||||
{
|
||||
public getRoute(): string {
|
||||
return "spt-handbook";
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { inject, injectable } from "tsyringe";
|
||||
import { HealthController } from "@spt/controllers/HealthController";
|
||||
import { ProfileHelper } from "@spt/helpers/ProfileHelper";
|
||||
import { IPmcData } from "@spt/models/eft/common/IPmcData";
|
||||
@ -10,16 +9,15 @@ import { IWorkoutData } from "@spt/models/eft/health/IWorkoutData";
|
||||
import { IGetBodyResponseData } from "@spt/models/eft/httpResponse/IGetBodyResponseData";
|
||||
import { IItemEventRouterResponse } from "@spt/models/eft/itemEvent/IItemEventRouterResponse";
|
||||
import { HttpResponseUtil } from "@spt/utils/HttpResponseUtil";
|
||||
import { inject, injectable } from "tsyringe";
|
||||
|
||||
@injectable()
|
||||
export class HealthCallbacks
|
||||
{
|
||||
export class HealthCallbacks {
|
||||
constructor(
|
||||
@inject("HttpResponseUtil") protected httpResponse: HttpResponseUtil,
|
||||
@inject("ProfileHelper") protected profileHelper: ProfileHelper,
|
||||
@inject("HealthController") protected healthController: HealthController,
|
||||
)
|
||||
{}
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Custom spt server request found in modules/HealthSynchronizer.cs
|
||||
@ -28,8 +26,7 @@ export class HealthCallbacks
|
||||
* @param sessionID session id
|
||||
* @returns empty response, no data sent back to client
|
||||
*/
|
||||
public syncHealth(url: string, info: ISyncHealthRequestData, sessionID: string): IGetBodyResponseData<string>
|
||||
{
|
||||
public syncHealth(url: string, info: ISyncHealthRequestData, sessionID: string): IGetBodyResponseData<string> {
|
||||
this.healthController.saveVitality(this.profileHelper.getPmcProfile(sessionID), info, sessionID);
|
||||
return this.httpResponse.emptyResponse();
|
||||
}
|
||||
@ -41,8 +38,7 @@ export class HealthCallbacks
|
||||
* @param sessionID session id
|
||||
* @returns empty response, no data sent back to client
|
||||
*/
|
||||
public handleWorkoutEffects(url: string, info: IWorkoutData, sessionID: string): IGetBodyResponseData<string>
|
||||
{
|
||||
public handleWorkoutEffects(url: string, info: IWorkoutData, sessionID: string): IGetBodyResponseData<string> {
|
||||
this.healthController.applyWorkoutChanges(this.profileHelper.getPmcProfile(sessionID), info, sessionID);
|
||||
return this.httpResponse.emptyResponse();
|
||||
}
|
||||
@ -51,8 +47,7 @@ export class HealthCallbacks
|
||||
* Handle Eat
|
||||
* @returns IItemEventRouterResponse
|
||||
*/
|
||||
public offraidEat(pmcData: IPmcData, body: IOffraidEatRequestData, sessionID: string): IItemEventRouterResponse
|
||||
{
|
||||
public offraidEat(pmcData: IPmcData, body: IOffraidEatRequestData, sessionID: string): IItemEventRouterResponse {
|
||||
return this.healthController.offraidEat(pmcData, body, sessionID);
|
||||
}
|
||||
|
||||
@ -60,8 +55,7 @@ export class HealthCallbacks
|
||||
* Handle Heal
|
||||
* @returns IItemEventRouterResponse
|
||||
*/
|
||||
public offraidHeal(pmcData: IPmcData, body: IOffraidHealRequestData, sessionID: string): IItemEventRouterResponse
|
||||
{
|
||||
public offraidHeal(pmcData: IPmcData, body: IOffraidHealRequestData, sessionID: string): IItemEventRouterResponse {
|
||||
return this.healthController.offraidHeal(pmcData, body, sessionID);
|
||||
}
|
||||
|
||||
@ -73,8 +67,7 @@ export class HealthCallbacks
|
||||
pmcData: IPmcData,
|
||||
info: IHealthTreatmentRequestData,
|
||||
sessionID: string,
|
||||
): IItemEventRouterResponse
|
||||
{
|
||||
): IItemEventRouterResponse {
|
||||
return this.healthController.healthTreatment(pmcData, info, sessionID);
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { inject, injectable } from "tsyringe";
|
||||
import { HideoutController } from "@spt/controllers/HideoutController";
|
||||
import { OnUpdate } from "@spt/di/OnUpdate";
|
||||
import { IPmcData } from "@spt/models/eft/common/IPmcData";
|
||||
@ -19,17 +18,16 @@ import { IItemEventRouterResponse } from "@spt/models/eft/itemEvent/IItemEventRo
|
||||
import { ConfigTypes } from "@spt/models/enums/ConfigTypes";
|
||||
import { IHideoutConfig } from "@spt/models/spt/config/IHideoutConfig";
|
||||
import { ConfigServer } from "@spt/servers/ConfigServer";
|
||||
import { inject, injectable } from "tsyringe";
|
||||
|
||||
@injectable()
|
||||
export class HideoutCallbacks implements OnUpdate
|
||||
{
|
||||
export class HideoutCallbacks implements OnUpdate {
|
||||
protected hideoutConfig: IHideoutConfig;
|
||||
|
||||
constructor(
|
||||
@inject("HideoutController") protected hideoutController: HideoutController, // TODO: delay needed
|
||||
@inject("ConfigServer") protected configServer: ConfigServer,
|
||||
)
|
||||
{
|
||||
) {
|
||||
this.hideoutConfig = this.configServer.getConfig(ConfigTypes.HIDEOUT);
|
||||
}
|
||||
|
||||
@ -41,8 +39,7 @@ export class HideoutCallbacks implements OnUpdate
|
||||
body: IHideoutUpgradeRequestData,
|
||||
sessionID: string,
|
||||
output: IItemEventRouterResponse,
|
||||
): IItemEventRouterResponse
|
||||
{
|
||||
): IItemEventRouterResponse {
|
||||
this.hideoutController.startUpgrade(pmcData, body, sessionID, output);
|
||||
|
||||
return output;
|
||||
@ -56,8 +53,7 @@ export class HideoutCallbacks implements OnUpdate
|
||||
body: IHideoutUpgradeCompleteRequestData,
|
||||
sessionID: string,
|
||||
output: IItemEventRouterResponse,
|
||||
): IItemEventRouterResponse
|
||||
{
|
||||
): IItemEventRouterResponse {
|
||||
this.hideoutController.upgradeComplete(pmcData, body, sessionID, output);
|
||||
|
||||
return output;
|
||||
@ -70,8 +66,7 @@ export class HideoutCallbacks implements OnUpdate
|
||||
pmcData: IPmcData,
|
||||
body: IHideoutPutItemInRequestData,
|
||||
sessionID: string,
|
||||
): IItemEventRouterResponse
|
||||
{
|
||||
): IItemEventRouterResponse {
|
||||
return this.hideoutController.putItemsInAreaSlots(pmcData, body, sessionID);
|
||||
}
|
||||
|
||||
@ -82,8 +77,7 @@ export class HideoutCallbacks implements OnUpdate
|
||||
pmcData: IPmcData,
|
||||
body: IHideoutTakeItemOutRequestData,
|
||||
sessionID: string,
|
||||
): IItemEventRouterResponse
|
||||
{
|
||||
): IItemEventRouterResponse {
|
||||
return this.hideoutController.takeItemsFromAreaSlots(pmcData, body, sessionID);
|
||||
}
|
||||
|
||||
@ -94,8 +88,7 @@ export class HideoutCallbacks implements OnUpdate
|
||||
pmcData: IPmcData,
|
||||
body: IHideoutToggleAreaRequestData,
|
||||
sessionID: string,
|
||||
): IItemEventRouterResponse
|
||||
{
|
||||
): IItemEventRouterResponse {
|
||||
return this.hideoutController.toggleArea(pmcData, body, sessionID);
|
||||
}
|
||||
|
||||
@ -106,8 +99,7 @@ export class HideoutCallbacks implements OnUpdate
|
||||
pmcData: IPmcData,
|
||||
body: IHideoutSingleProductionStartRequestData,
|
||||
sessionID: string,
|
||||
): IItemEventRouterResponse
|
||||
{
|
||||
): IItemEventRouterResponse {
|
||||
return this.hideoutController.singleProductionStart(pmcData, body, sessionID);
|
||||
}
|
||||
|
||||
@ -118,8 +110,7 @@ export class HideoutCallbacks implements OnUpdate
|
||||
pmcData: IPmcData,
|
||||
body: IHideoutScavCaseStartRequestData,
|
||||
sessionID: string,
|
||||
): IItemEventRouterResponse
|
||||
{
|
||||
): IItemEventRouterResponse {
|
||||
return this.hideoutController.scavCaseProductionStart(pmcData, body, sessionID);
|
||||
}
|
||||
|
||||
@ -130,8 +121,7 @@ export class HideoutCallbacks implements OnUpdate
|
||||
pmcData: IPmcData,
|
||||
body: IHideoutContinuousProductionStartRequestData,
|
||||
sessionID: string,
|
||||
): IItemEventRouterResponse
|
||||
{
|
||||
): IItemEventRouterResponse {
|
||||
return this.hideoutController.continuousProductionStart(pmcData, body, sessionID);
|
||||
}
|
||||
|
||||
@ -142,8 +132,7 @@ export class HideoutCallbacks implements OnUpdate
|
||||
pmcData: IPmcData,
|
||||
body: IHideoutTakeProductionRequestData,
|
||||
sessionID: string,
|
||||
): IItemEventRouterResponse
|
||||
{
|
||||
): IItemEventRouterResponse {
|
||||
return this.hideoutController.takeProduction(pmcData, body, sessionID);
|
||||
}
|
||||
|
||||
@ -155,8 +144,7 @@ export class HideoutCallbacks implements OnUpdate
|
||||
request: IHandleQTEEventRequestData,
|
||||
sessionId: string,
|
||||
output: IItemEventRouterResponse,
|
||||
): IItemEventRouterResponse
|
||||
{
|
||||
): IItemEventRouterResponse {
|
||||
this.hideoutController.handleQTEEventOutcome(sessionId, pmcData, request, output);
|
||||
|
||||
return output;
|
||||
@ -170,8 +158,7 @@ export class HideoutCallbacks implements OnUpdate
|
||||
request: IRecordShootingRangePoints,
|
||||
sessionId: string,
|
||||
output: IItemEventRouterResponse,
|
||||
): IItemEventRouterResponse
|
||||
{
|
||||
): IItemEventRouterResponse {
|
||||
this.hideoutController.recordShootingRangePoints(sessionId, pmcData, request);
|
||||
|
||||
return output;
|
||||
@ -184,8 +171,7 @@ export class HideoutCallbacks implements OnUpdate
|
||||
pmcData: IPmcData,
|
||||
request: IHideoutImproveAreaRequestData,
|
||||
sessionId: string,
|
||||
): IItemEventRouterResponse
|
||||
{
|
||||
): IItemEventRouterResponse {
|
||||
return this.hideoutController.improveArea(sessionId, pmcData, request);
|
||||
}
|
||||
|
||||
@ -196,23 +182,19 @@ export class HideoutCallbacks implements OnUpdate
|
||||
pmcData: IPmcData,
|
||||
request: IHideoutCancelProductionRequestData,
|
||||
sessionId: string,
|
||||
): IItemEventRouterResponse
|
||||
{
|
||||
): IItemEventRouterResponse {
|
||||
return this.hideoutController.cancelProduction(sessionId, pmcData, request);
|
||||
}
|
||||
|
||||
public async onUpdate(timeSinceLastRun: number): Promise<boolean>
|
||||
{
|
||||
if (timeSinceLastRun > this.hideoutConfig.runIntervalSeconds)
|
||||
{
|
||||
public async onUpdate(timeSinceLastRun: number): Promise<boolean> {
|
||||
if (timeSinceLastRun > this.hideoutConfig.runIntervalSeconds) {
|
||||
this.hideoutController.update();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public getRoute(): string
|
||||
{
|
||||
public getRoute(): string {
|
||||
return "spt-hideout";
|
||||
}
|
||||
}
|
||||
|
@ -1,25 +1,20 @@
|
||||
import { inject, injectable } from "tsyringe";
|
||||
import { OnLoad } from "@spt/di/OnLoad";
|
||||
import { HttpServer } from "@spt/servers/HttpServer";
|
||||
import { inject, injectable } from "tsyringe";
|
||||
|
||||
@injectable()
|
||||
export class HttpCallbacks implements OnLoad
|
||||
{
|
||||
constructor(@inject("HttpServer") protected httpServer: HttpServer)
|
||||
{}
|
||||
export class HttpCallbacks implements OnLoad {
|
||||
constructor(@inject("HttpServer") protected httpServer: HttpServer) {}
|
||||
|
||||
public async onLoad(): Promise<void>
|
||||
{
|
||||
public async onLoad(): Promise<void> {
|
||||
this.httpServer.load();
|
||||
}
|
||||
|
||||
public getRoute(): string
|
||||
{
|
||||
public getRoute(): string {
|
||||
return "spt-http";
|
||||
}
|
||||
|
||||
public getImage(): string
|
||||
{
|
||||
public getImage(): string {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
@ -1,22 +1,20 @@
|
||||
import { inject, injectable } from "tsyringe";
|
||||
import { InraidController } from "@spt/controllers/InraidController";
|
||||
import { IEmptyRequestData } from "@spt/models/eft/common/IEmptyRequestData";
|
||||
import { INullResponseData } from "@spt/models/eft/httpResponse/INullResponseData";
|
||||
import { IRegisterPlayerRequestData } from "@spt/models/eft/inRaid/IRegisterPlayerRequestData";
|
||||
import { IScavSaveRequestData } from "@spt/models/eft/inRaid/IScavSaveRequestData";
|
||||
import { HttpResponseUtil } from "@spt/utils/HttpResponseUtil";
|
||||
import { inject, injectable } from "tsyringe";
|
||||
|
||||
/**
|
||||
* Handle client requests
|
||||
*/
|
||||
@injectable()
|
||||
export class InraidCallbacks
|
||||
{
|
||||
export class InraidCallbacks {
|
||||
constructor(
|
||||
@inject("InraidController") protected inraidController: InraidController,
|
||||
@inject("HttpResponseUtil") protected httpResponse: HttpResponseUtil,
|
||||
)
|
||||
{}
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Handle client/location/getLocalloot
|
||||
@ -26,8 +24,7 @@ export class InraidCallbacks
|
||||
* @param sessionID Session id
|
||||
* @returns Null http response
|
||||
*/
|
||||
public registerPlayer(url: string, info: IRegisterPlayerRequestData, sessionID: string): INullResponseData
|
||||
{
|
||||
public registerPlayer(url: string, info: IRegisterPlayerRequestData, sessionID: string): INullResponseData {
|
||||
this.inraidController.addPlayer(sessionID, info);
|
||||
return this.httpResponse.nullResponse();
|
||||
}
|
||||
@ -39,8 +36,7 @@ export class InraidCallbacks
|
||||
* @param sessionID Session id
|
||||
* @returns Null http response
|
||||
*/
|
||||
public saveProgress(url: string, info: IScavSaveRequestData, sessionID: string): INullResponseData
|
||||
{
|
||||
public saveProgress(url: string, info: IScavSaveRequestData, sessionID: string): INullResponseData {
|
||||
this.inraidController.savePostRaidProfileForScav(info, sessionID);
|
||||
return this.httpResponse.nullResponse();
|
||||
}
|
||||
@ -50,8 +46,7 @@ export class InraidCallbacks
|
||||
* Handle singleplayer/settings/raid/endstate
|
||||
* @returns
|
||||
*/
|
||||
public getRaidEndState(): string
|
||||
{
|
||||
public getRaidEndState(): string {
|
||||
return this.httpResponse.noBody(this.inraidController.getInraidConfig().MIAOnRaidEnd);
|
||||
}
|
||||
|
||||
@ -59,18 +54,15 @@ export class InraidCallbacks
|
||||
* Handle singleplayer/settings/raid/menu
|
||||
* @returns JSON as string
|
||||
*/
|
||||
public getRaidMenuSettings(): string
|
||||
{
|
||||
public getRaidMenuSettings(): string {
|
||||
return this.httpResponse.noBody(this.inraidController.getInraidConfig().raidMenuSettings);
|
||||
}
|
||||
|
||||
public getTraitorScavHostileChance(url: string, info: IEmptyRequestData, sessionId: string): string
|
||||
{
|
||||
public getTraitorScavHostileChance(url: string, info: IEmptyRequestData, sessionId: string): string {
|
||||
return this.httpResponse.noBody(this.inraidController.getTraitorScavHostileChance(url, sessionId));
|
||||
}
|
||||
|
||||
public getBossConvertSettings(url: string, info: IEmptyRequestData, sessionId: string): string
|
||||
{
|
||||
public getBossConvertSettings(url: string, info: IEmptyRequestData, sessionId: string): string {
|
||||
return this.httpResponse.noBody(this.inraidController.getBossConvertSettings(url, sessionId));
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { inject, injectable } from "tsyringe";
|
||||
import { InsuranceController } from "@spt/controllers/InsuranceController";
|
||||
import { OnUpdate } from "@spt/di/OnUpdate";
|
||||
import { IPmcData } from "@spt/models/eft/common/IPmcData";
|
||||
@ -12,18 +11,17 @@ import { IInsuranceConfig } from "@spt/models/spt/config/IInsuranceConfig";
|
||||
import { ConfigServer } from "@spt/servers/ConfigServer";
|
||||
import { InsuranceService } from "@spt/services/InsuranceService";
|
||||
import { HttpResponseUtil } from "@spt/utils/HttpResponseUtil";
|
||||
import { inject, injectable } from "tsyringe";
|
||||
|
||||
@injectable()
|
||||
export class InsuranceCallbacks implements OnUpdate
|
||||
{
|
||||
export class InsuranceCallbacks implements OnUpdate {
|
||||
protected insuranceConfig: IInsuranceConfig;
|
||||
constructor(
|
||||
@inject("InsuranceController") protected insuranceController: InsuranceController,
|
||||
@inject("InsuranceService") protected insuranceService: InsuranceService,
|
||||
@inject("HttpResponseUtil") protected httpResponse: HttpResponseUtil,
|
||||
@inject("ConfigServer") protected configServer: ConfigServer,
|
||||
)
|
||||
{
|
||||
) {
|
||||
this.insuranceConfig = this.configServer.getConfig(ConfigTypes.INSURANCE);
|
||||
}
|
||||
|
||||
@ -35,8 +33,7 @@ export class InsuranceCallbacks implements OnUpdate
|
||||
url: string,
|
||||
info: IGetInsuranceCostRequestData,
|
||||
sessionID: string,
|
||||
): IGetBodyResponseData<IGetInsuranceCostResponseData>
|
||||
{
|
||||
): IGetBodyResponseData<IGetInsuranceCostResponseData> {
|
||||
return this.httpResponse.getBody(this.insuranceController.cost(info, sessionID));
|
||||
}
|
||||
|
||||
@ -44,24 +41,20 @@ export class InsuranceCallbacks implements OnUpdate
|
||||
* Handle Insure event
|
||||
* @returns IItemEventRouterResponse
|
||||
*/
|
||||
public insure(pmcData: IPmcData, body: IInsureRequestData, sessionID: string): IItemEventRouterResponse
|
||||
{
|
||||
public insure(pmcData: IPmcData, body: IInsureRequestData, sessionID: string): IItemEventRouterResponse {
|
||||
return this.insuranceController.insure(pmcData, body, sessionID);
|
||||
}
|
||||
|
||||
public async onUpdate(secondsSinceLastRun: number): Promise<boolean>
|
||||
{
|
||||
public async onUpdate(secondsSinceLastRun: number): Promise<boolean> {
|
||||
// People edit the config value to be 0 and break it, force value to no lower than 1
|
||||
if (secondsSinceLastRun > Math.max(this.insuranceConfig.runIntervalSeconds, 1))
|
||||
{
|
||||
if (secondsSinceLastRun > Math.max(this.insuranceConfig.runIntervalSeconds, 1)) {
|
||||
this.insuranceController.processReturn();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public getRoute(): string
|
||||
{
|
||||
public getRoute(): string {
|
||||
return "spt-insurance";
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { inject, injectable } from "tsyringe";
|
||||
import { InventoryController } from "@spt/controllers/InventoryController";
|
||||
import { QuestController } from "@spt/controllers/QuestController";
|
||||
import { IPmcData } from "@spt/models/eft/common/IPmcData";
|
||||
@ -23,15 +22,14 @@ import { IRedeemProfileRequestData } from "@spt/models/eft/inventory/IRedeemProf
|
||||
import { ISetFavoriteItems } from "@spt/models/eft/inventory/ISetFavoriteItems";
|
||||
import { IItemEventRouterResponse } from "@spt/models/eft/itemEvent/IItemEventRouterResponse";
|
||||
import { IFailQuestRequestData } from "@spt/models/eft/quests/IFailQuestRequestData";
|
||||
import { inject, injectable } from "tsyringe";
|
||||
|
||||
@injectable()
|
||||
export class InventoryCallbacks
|
||||
{
|
||||
export class InventoryCallbacks {
|
||||
constructor(
|
||||
@inject("InventoryController") protected inventoryController: InventoryController,
|
||||
@inject("QuestController") protected questController: QuestController,
|
||||
)
|
||||
{}
|
||||
) {}
|
||||
|
||||
/** Handle client/game/profile/items/moving Move event */
|
||||
public moveItem(
|
||||
@ -39,8 +37,7 @@ export class InventoryCallbacks
|
||||
body: IInventoryMoveRequestData,
|
||||
sessionID: string,
|
||||
output: IItemEventRouterResponse,
|
||||
): IItemEventRouterResponse
|
||||
{
|
||||
): IItemEventRouterResponse {
|
||||
this.inventoryController.moveItem(pmcData, body, sessionID, output);
|
||||
|
||||
return output;
|
||||
@ -52,8 +49,7 @@ export class InventoryCallbacks
|
||||
body: IInventoryRemoveRequestData,
|
||||
sessionID: string,
|
||||
output: IItemEventRouterResponse,
|
||||
): IItemEventRouterResponse
|
||||
{
|
||||
): IItemEventRouterResponse {
|
||||
this.inventoryController.discardItem(pmcData, body, sessionID, output);
|
||||
|
||||
return output;
|
||||
@ -65,8 +61,7 @@ export class InventoryCallbacks
|
||||
body: IInventorySplitRequestData,
|
||||
sessionID: string,
|
||||
output: IItemEventRouterResponse,
|
||||
): IItemEventRouterResponse
|
||||
{
|
||||
): IItemEventRouterResponse {
|
||||
return this.inventoryController.splitItem(pmcData, body, sessionID, output);
|
||||
}
|
||||
|
||||
@ -75,8 +70,7 @@ export class InventoryCallbacks
|
||||
body: IInventoryMergeRequestData,
|
||||
sessionID: string,
|
||||
output: IItemEventRouterResponse,
|
||||
): IItemEventRouterResponse
|
||||
{
|
||||
): IItemEventRouterResponse {
|
||||
return this.inventoryController.mergeItem(pmcData, body, sessionID, output);
|
||||
}
|
||||
|
||||
@ -85,20 +79,17 @@ export class InventoryCallbacks
|
||||
request: IInventoryTransferRequestData,
|
||||
sessionID: string,
|
||||
output: IItemEventRouterResponse,
|
||||
): IItemEventRouterResponse
|
||||
{
|
||||
): IItemEventRouterResponse {
|
||||
return this.inventoryController.transferItem(pmcData, request, sessionID, output);
|
||||
}
|
||||
|
||||
/** Handle Swap */
|
||||
// TODO: how is this triggered
|
||||
public swapItem(pmcData: IPmcData, body: IInventorySwapRequestData, sessionID: string): IItemEventRouterResponse
|
||||
{
|
||||
public swapItem(pmcData: IPmcData, body: IInventorySwapRequestData, sessionID: string): IItemEventRouterResponse {
|
||||
return this.inventoryController.swapItem(pmcData, body, sessionID);
|
||||
}
|
||||
|
||||
public foldItem(pmcData: IPmcData, body: IInventoryFoldRequestData, sessionID: string): IItemEventRouterResponse
|
||||
{
|
||||
public foldItem(pmcData: IPmcData, body: IInventoryFoldRequestData, sessionID: string): IItemEventRouterResponse {
|
||||
return this.inventoryController.foldItem(pmcData, body, sessionID);
|
||||
}
|
||||
|
||||
@ -106,13 +97,11 @@ export class InventoryCallbacks
|
||||
pmcData: IPmcData,
|
||||
body: IInventoryToggleRequestData,
|
||||
sessionID: string,
|
||||
): IItemEventRouterResponse
|
||||
{
|
||||
): IItemEventRouterResponse {
|
||||
return this.inventoryController.toggleItem(pmcData, body, sessionID);
|
||||
}
|
||||
|
||||
public tagItem(pmcData: IPmcData, body: IInventoryTagRequestData, sessionID: string): IItemEventRouterResponse
|
||||
{
|
||||
public tagItem(pmcData: IPmcData, body: IInventoryTagRequestData, sessionID: string): IItemEventRouterResponse {
|
||||
return this.inventoryController.tagItem(pmcData, body, sessionID);
|
||||
}
|
||||
|
||||
@ -121,8 +110,7 @@ export class InventoryCallbacks
|
||||
body: IInventoryBindRequestData,
|
||||
sessionID: string,
|
||||
output: IItemEventRouterResponse,
|
||||
): IItemEventRouterResponse
|
||||
{
|
||||
): IItemEventRouterResponse {
|
||||
this.inventoryController.bindItem(pmcData, body, sessionID);
|
||||
|
||||
return output;
|
||||
@ -133,8 +121,7 @@ export class InventoryCallbacks
|
||||
body: IInventoryBindRequestData,
|
||||
sessionID: string,
|
||||
output: IItemEventRouterResponse,
|
||||
): IItemEventRouterResponse
|
||||
{
|
||||
): IItemEventRouterResponse {
|
||||
this.inventoryController.unbindItem(pmcData, body, sessionID, output);
|
||||
|
||||
return output;
|
||||
@ -145,8 +132,7 @@ export class InventoryCallbacks
|
||||
body: IInventoryExamineRequestData,
|
||||
sessionID: string,
|
||||
output: IItemEventRouterResponse,
|
||||
): IItemEventRouterResponse
|
||||
{
|
||||
): IItemEventRouterResponse {
|
||||
return this.inventoryController.examineItem(pmcData, body, sessionID, output);
|
||||
}
|
||||
|
||||
@ -155,8 +141,7 @@ export class InventoryCallbacks
|
||||
pmcData: IPmcData,
|
||||
body: IInventoryReadEncyclopediaRequestData,
|
||||
sessionID: string,
|
||||
): IItemEventRouterResponse
|
||||
{
|
||||
): IItemEventRouterResponse {
|
||||
return this.inventoryController.readEncyclopedia(pmcData, body, sessionID);
|
||||
}
|
||||
|
||||
@ -166,8 +151,7 @@ export class InventoryCallbacks
|
||||
body: IInventorySortRequestData,
|
||||
sessionID: string,
|
||||
output: IItemEventRouterResponse,
|
||||
): IItemEventRouterResponse
|
||||
{
|
||||
): IItemEventRouterResponse {
|
||||
this.inventoryController.sortInventory(pmcData, body, sessionID);
|
||||
|
||||
return output;
|
||||
@ -178,8 +162,7 @@ export class InventoryCallbacks
|
||||
body: IInventoryCreateMarkerRequestData,
|
||||
sessionID: string,
|
||||
output: IItemEventRouterResponse,
|
||||
): IItemEventRouterResponse
|
||||
{
|
||||
): IItemEventRouterResponse {
|
||||
this.inventoryController.createMapMarker(pmcData, body, sessionID, output);
|
||||
|
||||
return output;
|
||||
@ -190,8 +173,7 @@ export class InventoryCallbacks
|
||||
body: IInventoryDeleteMarkerRequestData,
|
||||
sessionID: string,
|
||||
output: IItemEventRouterResponse,
|
||||
): IItemEventRouterResponse
|
||||
{
|
||||
): IItemEventRouterResponse {
|
||||
this.inventoryController.deleteMapMarker(pmcData, body, sessionID, output);
|
||||
|
||||
return output;
|
||||
@ -202,8 +184,7 @@ export class InventoryCallbacks
|
||||
body: IInventoryEditMarkerRequestData,
|
||||
sessionID: string,
|
||||
output: IItemEventRouterResponse,
|
||||
): IItemEventRouterResponse
|
||||
{
|
||||
): IItemEventRouterResponse {
|
||||
this.inventoryController.editMapMarker(pmcData, body, sessionID, output);
|
||||
|
||||
return output;
|
||||
@ -215,8 +196,7 @@ export class InventoryCallbacks
|
||||
body: IOpenRandomLootContainerRequestData,
|
||||
sessionID: string,
|
||||
output: IItemEventRouterResponse,
|
||||
): IItemEventRouterResponse
|
||||
{
|
||||
): IItemEventRouterResponse {
|
||||
this.inventoryController.openRandomLootContainer(pmcData, body, sessionID, output);
|
||||
|
||||
return output;
|
||||
@ -227,8 +207,7 @@ export class InventoryCallbacks
|
||||
body: IRedeemProfileRequestData,
|
||||
sessionId: string,
|
||||
output: IItemEventRouterResponse,
|
||||
): IItemEventRouterResponse
|
||||
{
|
||||
): IItemEventRouterResponse {
|
||||
this.inventoryController.redeemProfileReward(pmcData, body, sessionId);
|
||||
|
||||
return output;
|
||||
@ -239,8 +218,7 @@ export class InventoryCallbacks
|
||||
body: ISetFavoriteItems,
|
||||
sessionId: string,
|
||||
output: IItemEventRouterResponse,
|
||||
): IItemEventRouterResponse
|
||||
{
|
||||
): IItemEventRouterResponse {
|
||||
this.inventoryController.setFavoriteItem(pmcData, body, sessionId);
|
||||
|
||||
return output;
|
||||
@ -255,8 +233,7 @@ export class InventoryCallbacks
|
||||
request: IFailQuestRequestData,
|
||||
sessionID: string,
|
||||
output: IItemEventRouterResponse,
|
||||
): IItemEventRouterResponse
|
||||
{
|
||||
): IItemEventRouterResponse {
|
||||
return this.questController.failQuest(pmcData, request, sessionID, output);
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { inject, injectable } from "tsyringe";
|
||||
import { IGetBodyResponseData } from "@spt/models/eft/httpResponse/IGetBodyResponseData";
|
||||
import { Warning } from "@spt/models/eft/itemEvent/IItemEventRouterBase";
|
||||
import { IItemEventRouterRequest } from "@spt/models/eft/itemEvent/IItemEventRouterRequest";
|
||||
@ -6,22 +5,20 @@ import { IItemEventRouterResponse } from "@spt/models/eft/itemEvent/IItemEventRo
|
||||
import { BackendErrorCodes } from "@spt/models/enums/BackendErrorCodes";
|
||||
import { ItemEventRouter } from "@spt/routers/ItemEventRouter";
|
||||
import { HttpResponseUtil } from "@spt/utils/HttpResponseUtil";
|
||||
import { inject, injectable } from "tsyringe";
|
||||
|
||||
@injectable()
|
||||
export class ItemEventCallbacks
|
||||
{
|
||||
export class ItemEventCallbacks {
|
||||
constructor(
|
||||
@inject("HttpResponseUtil") protected httpResponse: HttpResponseUtil,
|
||||
@inject("ItemEventRouter") protected itemEventRouter: ItemEventRouter,
|
||||
)
|
||||
{}
|
||||
) {}
|
||||
|
||||
public async handleEvents(
|
||||
url: string,
|
||||
info: IItemEventRouterRequest,
|
||||
sessionID: string,
|
||||
): Promise<IGetBodyResponseData<IItemEventRouterResponse>>
|
||||
{
|
||||
): Promise<IGetBodyResponseData<IItemEventRouterResponse>> {
|
||||
const eventResponse = await this.itemEventRouter.handleEvents(info, sessionID);
|
||||
const result = this.isCriticalError(eventResponse.warnings)
|
||||
? this.httpResponse.getBody(
|
||||
@ -39,15 +36,12 @@ export class ItemEventCallbacks
|
||||
* @param warnings The list of warnings to check for critical errors
|
||||
* @returns
|
||||
*/
|
||||
private isCriticalError(warnings: Warning[]): boolean
|
||||
{
|
||||
private isCriticalError(warnings: Warning[]): boolean {
|
||||
// List of non-critical error codes, we return true if any error NOT included is passed in
|
||||
const nonCriticalErrorCodes: BackendErrorCodes[] = [BackendErrorCodes.NOTENOUGHSPACE];
|
||||
|
||||
for (const warning of warnings)
|
||||
{
|
||||
if (!nonCriticalErrorCodes.includes(+(warning?.code ?? "0")))
|
||||
{
|
||||
for (const warning of warnings) {
|
||||
if (!nonCriticalErrorCodes.includes(+(warning?.code ?? "0"))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -55,10 +49,8 @@ export class ItemEventCallbacks
|
||||
return false;
|
||||
}
|
||||
|
||||
protected getErrorCode(warnings: Warning[]): number
|
||||
{
|
||||
if (warnings[0]?.code)
|
||||
{
|
||||
protected getErrorCode(warnings: Warning[]): number {
|
||||
if (warnings[0]?.code) {
|
||||
return Number(warnings[0].code);
|
||||
}
|
||||
return BackendErrorCodes.UNKNOWN_ERROR;
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { inject, injectable } from "tsyringe";
|
||||
import { LauncherController } from "@spt/controllers/LauncherController";
|
||||
import { IEmptyRequestData } from "@spt/models/eft/common/IEmptyRequestData";
|
||||
import { IChangeRequestData } from "@spt/models/eft/launcher/IChangeRequestData";
|
||||
@ -8,86 +7,72 @@ import { IRemoveProfileData } from "@spt/models/eft/launcher/IRemoveProfileData"
|
||||
import { SaveServer } from "@spt/servers/SaveServer";
|
||||
import { HttpResponseUtil } from "@spt/utils/HttpResponseUtil";
|
||||
import { Watermark } from "@spt/utils/Watermark";
|
||||
import { inject, injectable } from "tsyringe";
|
||||
|
||||
@injectable()
|
||||
export class LauncherCallbacks
|
||||
{
|
||||
export class LauncherCallbacks {
|
||||
constructor(
|
||||
@inject("HttpResponseUtil") protected httpResponse: HttpResponseUtil,
|
||||
@inject("LauncherController") protected launcherController: LauncherController,
|
||||
@inject("SaveServer") protected saveServer: SaveServer,
|
||||
@inject("Watermark") protected watermark: Watermark,
|
||||
)
|
||||
{}
|
||||
) {}
|
||||
|
||||
public connect(): string
|
||||
{
|
||||
public connect(): string {
|
||||
return this.httpResponse.noBody(this.launcherController.connect());
|
||||
}
|
||||
|
||||
public login(url: string, info: ILoginRequestData, sessionID: string): string
|
||||
{
|
||||
public login(url: string, info: ILoginRequestData, sessionID: string): string {
|
||||
const output = this.launcherController.login(info);
|
||||
return !output ? "FAILED" : output;
|
||||
}
|
||||
|
||||
public register(url: string, info: IRegisterData, sessionID: string): "FAILED" | "OK"
|
||||
{
|
||||
public register(url: string, info: IRegisterData, sessionID: string): "FAILED" | "OK" {
|
||||
const output = this.launcherController.register(info);
|
||||
return !output ? "FAILED" : "OK";
|
||||
}
|
||||
|
||||
public get(url: string, info: ILoginRequestData, sessionID: string): string
|
||||
{
|
||||
public get(url: string, info: ILoginRequestData, sessionID: string): string {
|
||||
const output = this.launcherController.find(this.launcherController.login(info));
|
||||
return this.httpResponse.noBody(output);
|
||||
}
|
||||
|
||||
public changeUsername(url: string, info: IChangeRequestData, sessionID: string): "FAILED" | "OK"
|
||||
{
|
||||
public changeUsername(url: string, info: IChangeRequestData, sessionID: string): "FAILED" | "OK" {
|
||||
const output = this.launcherController.changeUsername(info);
|
||||
return !output ? "FAILED" : "OK";
|
||||
}
|
||||
|
||||
public changePassword(url: string, info: IChangeRequestData, sessionID: string): "FAILED" | "OK"
|
||||
{
|
||||
public changePassword(url: string, info: IChangeRequestData, sessionID: string): "FAILED" | "OK" {
|
||||
const output = this.launcherController.changePassword(info);
|
||||
return !output ? "FAILED" : "OK";
|
||||
}
|
||||
|
||||
public wipe(url: string, info: IRegisterData, sessionID: string): "FAILED" | "OK"
|
||||
{
|
||||
public wipe(url: string, info: IRegisterData, sessionID: string): "FAILED" | "OK" {
|
||||
const output = this.launcherController.wipe(info);
|
||||
return !output ? "FAILED" : "OK";
|
||||
}
|
||||
|
||||
public getServerVersion(): string
|
||||
{
|
||||
public getServerVersion(): string {
|
||||
return this.httpResponse.noBody(this.watermark.getVersionTag());
|
||||
}
|
||||
|
||||
public ping(url: string, info: IEmptyRequestData, sessionID: string): string
|
||||
{
|
||||
public ping(url: string, info: IEmptyRequestData, sessionID: string): string {
|
||||
return this.httpResponse.noBody("pong!");
|
||||
}
|
||||
|
||||
public removeProfile(url: string, info: IRemoveProfileData, sessionID: string): string
|
||||
{
|
||||
public removeProfile(url: string, info: IRemoveProfileData, sessionID: string): string {
|
||||
return this.httpResponse.noBody(this.saveServer.removeProfile(sessionID));
|
||||
}
|
||||
|
||||
public getCompatibleTarkovVersion(): string
|
||||
{
|
||||
public getCompatibleTarkovVersion(): string {
|
||||
return this.httpResponse.noBody(this.launcherController.getCompatibleTarkovVersion());
|
||||
}
|
||||
|
||||
public getLoadedServerMods(): string
|
||||
{
|
||||
public getLoadedServerMods(): string {
|
||||
return this.httpResponse.noBody(this.launcherController.getLoadedServerMods());
|
||||
}
|
||||
|
||||
public getServerModsProfileUsed(url: string, info: IEmptyRequestData, sessionId: string): string
|
||||
{
|
||||
public getServerModsProfileUsed(url: string, info: IEmptyRequestData, sessionId: string): string {
|
||||
return this.httpResponse.noBody(this.launcherController.getServerModsProfileUsed(sessionId));
|
||||
}
|
||||
}
|
||||
|
@ -1,27 +1,24 @@
|
||||
import { inject, injectable } from "tsyringe";
|
||||
import { LocationController } from "@spt/controllers/LocationController";
|
||||
import { IEmptyRequestData } from "@spt/models/eft/common/IEmptyRequestData";
|
||||
import { ILocationsGenerateAllResponse } from "@spt/models/eft/common/ILocationsSourceDestinationBase";
|
||||
import { IGetBodyResponseData } from "@spt/models/eft/httpResponse/IGetBodyResponseData";
|
||||
import { IGetAirdropLootResponse } from "@spt/models/eft/location/IGetAirdropLootResponse";
|
||||
import { HttpResponseUtil } from "@spt/utils/HttpResponseUtil";
|
||||
import { inject, injectable } from "tsyringe";
|
||||
|
||||
@injectable()
|
||||
export class LocationCallbacks
|
||||
{
|
||||
export class LocationCallbacks {
|
||||
constructor(
|
||||
@inject("HttpResponseUtil") protected httpResponse: HttpResponseUtil,
|
||||
@inject("LocationController") protected locationController: LocationController,
|
||||
)
|
||||
{}
|
||||
) {}
|
||||
|
||||
/** Handle client/locations */
|
||||
public getLocationData(
|
||||
url: string,
|
||||
info: IEmptyRequestData,
|
||||
sessionID: string,
|
||||
): IGetBodyResponseData<ILocationsGenerateAllResponse>
|
||||
{
|
||||
): IGetBodyResponseData<ILocationsGenerateAllResponse> {
|
||||
return this.httpResponse.getBody(this.locationController.generateAll(sessionID));
|
||||
}
|
||||
|
||||
@ -30,8 +27,7 @@ export class LocationCallbacks
|
||||
url: string,
|
||||
info: IEmptyRequestData,
|
||||
sessionID: string,
|
||||
): IGetBodyResponseData<IGetAirdropLootResponse>
|
||||
{
|
||||
): IGetBodyResponseData<IGetAirdropLootResponse> {
|
||||
return this.httpResponse.getBody(this.locationController.getAirdropLoot());
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { inject, injectable } from "tsyringe";
|
||||
import { MatchController } from "@spt/controllers/MatchController";
|
||||
import { IEmptyRequestData } from "@spt/models/eft/common/IEmptyRequestData";
|
||||
import { IGetBodyResponseData } from "@spt/models/eft/httpResponse/IGetBodyResponseData";
|
||||
@ -22,33 +21,29 @@ import { IUpdatePingRequestData } from "@spt/models/eft/match/IUpdatePingRequest
|
||||
import { DatabaseService } from "@spt/services/DatabaseService";
|
||||
import { HttpResponseUtil } from "@spt/utils/HttpResponseUtil";
|
||||
import { JsonUtil } from "@spt/utils/JsonUtil";
|
||||
import { inject, injectable } from "tsyringe";
|
||||
|
||||
@injectable()
|
||||
export class MatchCallbacks
|
||||
{
|
||||
export class MatchCallbacks {
|
||||
constructor(
|
||||
@inject("HttpResponseUtil") protected httpResponse: HttpResponseUtil,
|
||||
@inject("JsonUtil") protected jsonUtil: JsonUtil,
|
||||
@inject("MatchController") protected matchController: MatchController,
|
||||
@inject("DatabaseService") protected databaseService: DatabaseService,
|
||||
)
|
||||
{}
|
||||
) {}
|
||||
|
||||
/** Handle client/match/updatePing */
|
||||
public updatePing(url: string, info: IUpdatePingRequestData, sessionID: string): INullResponseData
|
||||
{
|
||||
public updatePing(url: string, info: IUpdatePingRequestData, sessionID: string): INullResponseData {
|
||||
return this.httpResponse.nullResponse();
|
||||
}
|
||||
|
||||
// Handle client/match/exit
|
||||
public exitMatch(url: string, info: IEmptyRequestData, sessionID: string): INullResponseData
|
||||
{
|
||||
public exitMatch(url: string, info: IEmptyRequestData, sessionID: string): INullResponseData {
|
||||
return this.httpResponse.nullResponse();
|
||||
}
|
||||
|
||||
/** Handle client/match/group/exit_from_menu */
|
||||
public exitToMenu(url: string, info: IEmptyRequestData, sessionID: string): INullResponseData
|
||||
{
|
||||
public exitToMenu(url: string, info: IEmptyRequestData, sessionID: string): INullResponseData {
|
||||
return this.httpResponse.nullResponse();
|
||||
}
|
||||
|
||||
@ -56,18 +51,15 @@ export class MatchCallbacks
|
||||
url: string,
|
||||
info: IEmptyRequestData,
|
||||
sessionID: string,
|
||||
): IGetBodyResponseData<IMatchGroupCurrentResponse>
|
||||
{
|
||||
): IGetBodyResponseData<IMatchGroupCurrentResponse> {
|
||||
return this.httpResponse.getBody({ squad: [] });
|
||||
}
|
||||
|
||||
public startGroupSearch(url: string, info: IEmptyRequestData, sessionID: string): INullResponseData
|
||||
{
|
||||
public startGroupSearch(url: string, info: IEmptyRequestData, sessionID: string): INullResponseData {
|
||||
return this.httpResponse.nullResponse();
|
||||
}
|
||||
|
||||
public stopGroupSearch(url: string, info: IEmptyRequestData, sessionID: string): INullResponseData
|
||||
{
|
||||
public stopGroupSearch(url: string, info: IEmptyRequestData, sessionID: string): INullResponseData {
|
||||
return this.httpResponse.nullResponse();
|
||||
}
|
||||
|
||||
@ -76,8 +68,7 @@ export class MatchCallbacks
|
||||
url: string,
|
||||
info: IMatchGroupInviteSendRequest,
|
||||
sessionID: string,
|
||||
): IGetBodyResponseData<string>
|
||||
{
|
||||
): IGetBodyResponseData<string> {
|
||||
return this.httpResponse.getBody("2427943f23698ay9f2863735");
|
||||
}
|
||||
|
||||
@ -86,8 +77,7 @@ export class MatchCallbacks
|
||||
url: string,
|
||||
info: IRequestIdRequest,
|
||||
sessionId: string,
|
||||
): IGetBodyResponseData<IGroupCharacter[]>
|
||||
{
|
||||
): IGetBodyResponseData<IGroupCharacter[]> {
|
||||
const result = [];
|
||||
result.push({});
|
||||
|
||||
@ -95,14 +85,12 @@ export class MatchCallbacks
|
||||
}
|
||||
|
||||
/** Handle client/match/group/invite/decline */
|
||||
public declineGroupInvite(url: string, info: IRequestIdRequest, sessionId: string): IGetBodyResponseData<boolean>
|
||||
{
|
||||
public declineGroupInvite(url: string, info: IRequestIdRequest, sessionId: string): IGetBodyResponseData<boolean> {
|
||||
return this.httpResponse.getBody(true);
|
||||
}
|
||||
|
||||
/** Handle client/match/group/invite/cancel */
|
||||
public cancelGroupInvite(url: string, info: IRequestIdRequest, sessionID: string): IGetBodyResponseData<boolean>
|
||||
{
|
||||
public cancelGroupInvite(url: string, info: IRequestIdRequest, sessionID: string): IGetBodyResponseData<boolean> {
|
||||
return this.httpResponse.getBody(true);
|
||||
}
|
||||
|
||||
@ -111,8 +99,7 @@ export class MatchCallbacks
|
||||
url: string,
|
||||
info: IMatchGroupTransferRequest,
|
||||
sessionId: string,
|
||||
): IGetBodyResponseData<boolean>
|
||||
{
|
||||
): IGetBodyResponseData<boolean> {
|
||||
return this.httpResponse.getBody(true);
|
||||
}
|
||||
|
||||
@ -121,19 +108,16 @@ export class MatchCallbacks
|
||||
url: string,
|
||||
info: IEmptyRequestData,
|
||||
sessionId: string,
|
||||
): IGetBodyResponseData<boolean>
|
||||
{
|
||||
): IGetBodyResponseData<boolean> {
|
||||
return this.httpResponse.getBody(true);
|
||||
}
|
||||
|
||||
public putMetrics(url: string, request: IPutMetricsRequestData, sessionId: string): INullResponseData
|
||||
{
|
||||
public putMetrics(url: string, request: IPutMetricsRequestData, sessionId: string): INullResponseData {
|
||||
return this.httpResponse.nullResponse();
|
||||
}
|
||||
|
||||
// Handle client/match/available
|
||||
public serverAvailable(url: string, info: IEmptyRequestData, sessionId: string): IGetBodyResponseData<boolean>
|
||||
{
|
||||
public serverAvailable(url: string, info: IEmptyRequestData, sessionId: string): IGetBodyResponseData<boolean> {
|
||||
const output = this.matchController.getEnabled();
|
||||
|
||||
return this.httpResponse.getBody(output);
|
||||
@ -144,14 +128,12 @@ export class MatchCallbacks
|
||||
url: string,
|
||||
info: IMatchGroupStartGameRequest,
|
||||
sessionID: string,
|
||||
): IGetBodyResponseData<IProfileStatusResponse>
|
||||
{
|
||||
): IGetBodyResponseData<IProfileStatusResponse> {
|
||||
return this.httpResponse.getBody(this.matchController.joinMatch(info, sessionID));
|
||||
}
|
||||
|
||||
/** Handle client/getMetricsConfig */
|
||||
public getMetrics(url: string, info: any, sessionID: string): IGetBodyResponseData<string>
|
||||
{
|
||||
public getMetrics(url: string, info: any, sessionID: string): IGetBodyResponseData<string> {
|
||||
return this.httpResponse.getBody(this.jsonUtil.serialize(this.databaseService.getMatch().metrics));
|
||||
}
|
||||
|
||||
@ -164,21 +146,18 @@ export class MatchCallbacks
|
||||
url: string,
|
||||
info: IMatchGroupStatusRequest,
|
||||
sessionID: string,
|
||||
): IGetBodyResponseData<IMatchGroupStatusResponse>
|
||||
{
|
||||
): IGetBodyResponseData<IMatchGroupStatusResponse> {
|
||||
return this.httpResponse.getBody(this.matchController.getGroupStatus(info));
|
||||
}
|
||||
|
||||
/** Handle client/match/group/delete */
|
||||
public deleteGroup(url: string, info: IEmptyRequestData, sessionID: string): IGetBodyResponseData<boolean>
|
||||
{
|
||||
public deleteGroup(url: string, info: IEmptyRequestData, sessionID: string): IGetBodyResponseData<boolean> {
|
||||
this.matchController.deleteGroup(info);
|
||||
return this.httpResponse.getBody(true);
|
||||
}
|
||||
|
||||
// Handle client/match/group/leave
|
||||
public leaveGroup(url: string, info: IEmptyRequestData, sessionID: string): IGetBodyResponseData<boolean>
|
||||
{
|
||||
public leaveGroup(url: string, info: IEmptyRequestData, sessionID: string): IGetBodyResponseData<boolean> {
|
||||
return this.httpResponse.getBody(true);
|
||||
}
|
||||
|
||||
@ -187,8 +166,7 @@ export class MatchCallbacks
|
||||
url: string,
|
||||
info: IMatchGroupPlayerRemoveRequest,
|
||||
sessionID: string,
|
||||
): IGetBodyResponseData<boolean>
|
||||
{
|
||||
): IGetBodyResponseData<boolean> {
|
||||
return this.httpResponse.getBody(true);
|
||||
}
|
||||
|
||||
@ -197,18 +175,12 @@ export class MatchCallbacks
|
||||
url: string,
|
||||
info: IStartLocalRaidRequestData,
|
||||
sessionID: string,
|
||||
): IGetBodyResponseData<IStartLocalRaidResponseData>
|
||||
{
|
||||
): IGetBodyResponseData<IStartLocalRaidResponseData> {
|
||||
return this.httpResponse.getBody(this.matchController.startLocalRaid(sessionID, info));
|
||||
}
|
||||
|
||||
/** Handle client/match/local/end */
|
||||
public endLocalRaid(
|
||||
url: string,
|
||||
info: IEndLocalRaidRequestData,
|
||||
sessionID: string,
|
||||
): INullResponseData
|
||||
{
|
||||
public endLocalRaid(url: string, info: IEndLocalRaidRequestData, sessionID: string): INullResponseData {
|
||||
this.matchController.endLocalRaid(sessionID, info);
|
||||
return this.httpResponse.nullResponse();
|
||||
}
|
||||
@ -218,8 +190,7 @@ export class MatchCallbacks
|
||||
url: string,
|
||||
info: IGetRaidConfigurationRequestData,
|
||||
sessionID: string,
|
||||
): INullResponseData
|
||||
{
|
||||
): INullResponseData {
|
||||
this.matchController.configureOfflineRaid(info, sessionID);
|
||||
return this.httpResponse.nullResponse();
|
||||
}
|
||||
@ -229,20 +200,17 @@ export class MatchCallbacks
|
||||
url: string,
|
||||
info: IGetRaidConfigurationRequestData,
|
||||
sessionID: string,
|
||||
): INullResponseData
|
||||
{
|
||||
): INullResponseData {
|
||||
return this.httpResponse.nullResponse();
|
||||
}
|
||||
|
||||
/** Handle client/match/group/raid/ready */
|
||||
public raidReady(url: string, info: IEmptyRequestData, sessionId: string): IGetBodyResponseData<boolean>
|
||||
{
|
||||
public raidReady(url: string, info: IEmptyRequestData, sessionId: string): IGetBodyResponseData<boolean> {
|
||||
return this.httpResponse.getBody(true);
|
||||
}
|
||||
|
||||
/** Handle client/match/group/raid/not-ready */
|
||||
public notRaidReady(url: string, info: IEmptyRequestData, sessionId: string): IGetBodyResponseData<boolean>
|
||||
{
|
||||
public notRaidReady(url: string, info: IEmptyRequestData, sessionId: string): IGetBodyResponseData<boolean> {
|
||||
return this.httpResponse.getBody(true);
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { inject, injectable } from "tsyringe";
|
||||
import { OnLoad } from "@spt/di/OnLoad";
|
||||
import { PostSptModLoader } from "@spt/loaders/PostSptModLoader";
|
||||
import { ConfigTypes } from "@spt/models/enums/ConfigTypes";
|
||||
@ -8,10 +7,10 @@ import { ConfigServer } from "@spt/servers/ConfigServer";
|
||||
import { LocalisationService } from "@spt/services/LocalisationService";
|
||||
import { HttpFileUtil } from "@spt/utils/HttpFileUtil";
|
||||
import { HttpResponseUtil } from "@spt/utils/HttpResponseUtil";
|
||||
import { inject, injectable } from "tsyringe";
|
||||
|
||||
@injectable()
|
||||
export class ModCallbacks implements OnLoad
|
||||
{
|
||||
export class ModCallbacks implements OnLoad {
|
||||
protected httpConfig: IHttpConfig;
|
||||
|
||||
constructor(
|
||||
@ -21,21 +20,17 @@ export class ModCallbacks implements OnLoad
|
||||
@inject("PostSptModLoader") protected postSptModLoader: PostSptModLoader,
|
||||
@inject("LocalisationService") protected localisationService: LocalisationService,
|
||||
@inject("ConfigServer") protected configServer: ConfigServer,
|
||||
)
|
||||
{
|
||||
) {
|
||||
this.httpConfig = this.configServer.getConfig(ConfigTypes.HTTP);
|
||||
}
|
||||
|
||||
public async onLoad(): Promise<void>
|
||||
{
|
||||
if (globalThis.G_MODS_ENABLED)
|
||||
{
|
||||
public async onLoad(): Promise<void> {
|
||||
if (globalThis.G_MODS_ENABLED) {
|
||||
await this.postSptModLoader.load();
|
||||
}
|
||||
}
|
||||
|
||||
public getRoute(): string
|
||||
{
|
||||
public getRoute(): string {
|
||||
return "spt-mods";
|
||||
}
|
||||
}
|
||||
|
@ -1,30 +1,25 @@
|
||||
import { inject, injectable } from "tsyringe";
|
||||
import { NoteController } from "@spt/controllers/NoteController";
|
||||
import { IPmcData } from "@spt/models/eft/common/IPmcData";
|
||||
import { IItemEventRouterResponse } from "@spt/models/eft/itemEvent/IItemEventRouterResponse";
|
||||
import { INoteActionData } from "@spt/models/eft/notes/INoteActionData";
|
||||
import { inject, injectable } from "tsyringe";
|
||||
|
||||
@injectable()
|
||||
export class NoteCallbacks
|
||||
{
|
||||
constructor(@inject("NoteController") protected noteController: NoteController)
|
||||
{}
|
||||
export class NoteCallbacks {
|
||||
constructor(@inject("NoteController") protected noteController: NoteController) {}
|
||||
|
||||
/** Handle AddNote event */
|
||||
public addNote(pmcData: IPmcData, body: INoteActionData, sessionID: string): IItemEventRouterResponse
|
||||
{
|
||||
public addNote(pmcData: IPmcData, body: INoteActionData, sessionID: string): IItemEventRouterResponse {
|
||||
return this.noteController.addNote(pmcData, body, sessionID);
|
||||
}
|
||||
|
||||
/** Handle EditNote event */
|
||||
public editNote(pmcData: IPmcData, body: INoteActionData, sessionID: string): IItemEventRouterResponse
|
||||
{
|
||||
public editNote(pmcData: IPmcData, body: INoteActionData, sessionID: string): IItemEventRouterResponse {
|
||||
return this.noteController.editNote(pmcData, body, sessionID);
|
||||
}
|
||||
|
||||
/** Handle DeleteNote event */
|
||||
public deleteNote(pmcData: IPmcData, body: INoteActionData, sessionID: string): IItemEventRouterResponse
|
||||
{
|
||||
public deleteNote(pmcData: IPmcData, body: INoteActionData, sessionID: string): IItemEventRouterResponse {
|
||||
return this.noteController.deleteNote(pmcData, body, sessionID);
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { inject, injectable } from "tsyringe";
|
||||
import { NotifierController } from "@spt/controllers/NotifierController";
|
||||
import { HttpServerHelper } from "@spt/helpers/HttpServerHelper";
|
||||
import { IEmptyRequestData } from "@spt/models/eft/common/IEmptyRequestData";
|
||||
@ -8,17 +7,16 @@ import { INotifierChannel } from "@spt/models/eft/notifier/INotifier";
|
||||
import { ISelectProfileResponse } from "@spt/models/eft/notifier/ISelectProfileResponse";
|
||||
import { HttpResponseUtil } from "@spt/utils/HttpResponseUtil";
|
||||
import { JsonUtil } from "@spt/utils/JsonUtil";
|
||||
import { inject, injectable } from "tsyringe";
|
||||
|
||||
@injectable()
|
||||
export class NotifierCallbacks
|
||||
{
|
||||
export class NotifierCallbacks {
|
||||
constructor(
|
||||
@inject("HttpServerHelper") protected httpServerHelper: HttpServerHelper,
|
||||
@inject("HttpResponseUtil") protected httpResponse: HttpResponseUtil,
|
||||
@inject("JsonUtil") protected jsonUtil: JsonUtil,
|
||||
@inject("NotifierController") protected notifierController: NotifierController,
|
||||
)
|
||||
{}
|
||||
) {}
|
||||
|
||||
/**
|
||||
* If we don't have anything to send, it's ok to not send anything back
|
||||
@ -26,8 +24,7 @@ export class NotifierCallbacks
|
||||
* until we actually have something to send because otherwise we'd spam the client
|
||||
* and the client would abort the connection due to spam.
|
||||
*/
|
||||
public sendNotification(sessionID: string, req: any, resp: any, data: any): void
|
||||
{
|
||||
public sendNotification(sessionID: string, req: any, resp: any, data: any): void {
|
||||
const splittedUrl = req.url.split("/");
|
||||
const tmpSessionID = splittedUrl[splittedUrl.length - 1].split("?last_id")[0];
|
||||
|
||||
@ -44,8 +41,7 @@ export class NotifierCallbacks
|
||||
/** Handle push/notifier/get */
|
||||
/** Handle push/notifier/getwebsocket */
|
||||
// TODO: removed from client?
|
||||
public getNotifier(url: string, info: any, sessionID: string): IGetBodyResponseData<any[]>
|
||||
{
|
||||
public getNotifier(url: string, info: any, sessionID: string): IGetBodyResponseData<any[]> {
|
||||
return this.httpResponse.emptyArrayResponse();
|
||||
}
|
||||
|
||||
@ -54,8 +50,7 @@ export class NotifierCallbacks
|
||||
url: string,
|
||||
info: IEmptyRequestData,
|
||||
sessionID: string,
|
||||
): IGetBodyResponseData<INotifierChannel>
|
||||
{
|
||||
): IGetBodyResponseData<INotifierChannel> {
|
||||
return this.httpResponse.getBody(this.notifierController.getChannel(sessionID));
|
||||
}
|
||||
|
||||
@ -67,13 +62,11 @@ export class NotifierCallbacks
|
||||
url: string,
|
||||
info: IUIDRequestData,
|
||||
sessionID: string,
|
||||
): IGetBodyResponseData<ISelectProfileResponse>
|
||||
{
|
||||
): IGetBodyResponseData<ISelectProfileResponse> {
|
||||
return this.httpResponse.getBody({ status: "ok" });
|
||||
}
|
||||
|
||||
public notify(url: string, info: any, sessionID: string): string
|
||||
{
|
||||
public notify(url: string, info: any, sessionID: string): string {
|
||||
return "NOTIFY";
|
||||
}
|
||||
}
|
||||
|
@ -1,20 +1,16 @@
|
||||
import { inject, injectable } from "tsyringe";
|
||||
import { PresetController } from "@spt/controllers/PresetController";
|
||||
import { OnLoad } from "@spt/di/OnLoad";
|
||||
import { inject, injectable } from "tsyringe";
|
||||
|
||||
@injectable()
|
||||
export class PresetCallbacks implements OnLoad
|
||||
{
|
||||
constructor(@inject("PresetController") protected presetController: PresetController)
|
||||
{}
|
||||
export class PresetCallbacks implements OnLoad {
|
||||
constructor(@inject("PresetController") protected presetController: PresetController) {}
|
||||
|
||||
public async onLoad(): Promise<void>
|
||||
{
|
||||
public async onLoad(): Promise<void> {
|
||||
this.presetController.initialize();
|
||||
}
|
||||
|
||||
public getRoute(): string
|
||||
{
|
||||
public getRoute(): string {
|
||||
return "spt-presets";
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { inject, injectable } from "tsyringe";
|
||||
import { ProfileController } from "@spt/controllers/ProfileController";
|
||||
import { ProfileHelper } from "@spt/helpers/ProfileHelper";
|
||||
import { IEmptyRequestData } from "@spt/models/eft/common/IEmptyRequestData";
|
||||
@ -19,18 +18,17 @@ import { ISearchFriendResponse } from "@spt/models/eft/profile/ISearchFriendResp
|
||||
import { IValidateNicknameRequestData } from "@spt/models/eft/profile/IValidateNicknameRequestData";
|
||||
import { HttpResponseUtil } from "@spt/utils/HttpResponseUtil";
|
||||
import { TimeUtil } from "@spt/utils/TimeUtil";
|
||||
import { inject, injectable } from "tsyringe";
|
||||
|
||||
/** Handle profile related client events */
|
||||
@injectable()
|
||||
export class ProfileCallbacks
|
||||
{
|
||||
export class ProfileCallbacks {
|
||||
constructor(
|
||||
@inject("HttpResponseUtil") protected httpResponse: HttpResponseUtil,
|
||||
@inject("TimeUtil") protected timeUtil: TimeUtil,
|
||||
@inject("ProfileController") protected profileController: ProfileController,
|
||||
@inject("ProfileHelper") protected profileHelper: ProfileHelper,
|
||||
)
|
||||
{}
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Handle client/game/profile/create
|
||||
@ -39,8 +37,7 @@ export class ProfileCallbacks
|
||||
url: string,
|
||||
info: IProfileCreateRequestData,
|
||||
sessionID: string,
|
||||
): IGetBodyResponseData<ICreateProfileResponse>
|
||||
{
|
||||
): IGetBodyResponseData<ICreateProfileResponse> {
|
||||
const id = this.profileController.createProfile(info, sessionID);
|
||||
return this.httpResponse.getBody({ uid: id });
|
||||
}
|
||||
@ -49,8 +46,7 @@ export class ProfileCallbacks
|
||||
* Handle client/game/profile/list
|
||||
* Get the complete player profile (scav + pmc character)
|
||||
*/
|
||||
public getProfileData(url: string, info: IEmptyRequestData, sessionID: string): IGetBodyResponseData<IPmcData[]>
|
||||
{
|
||||
public getProfileData(url: string, info: IEmptyRequestData, sessionID: string): IGetBodyResponseData<IPmcData[]> {
|
||||
return this.httpResponse.getBody(this.profileController.getCompleteProfile(sessionID));
|
||||
}
|
||||
|
||||
@ -63,16 +59,14 @@ export class ProfileCallbacks
|
||||
* @param sessionID Session id
|
||||
* @returns Profile object
|
||||
*/
|
||||
public regenerateScav(url: string, info: IEmptyRequestData, sessionID: string): IGetBodyResponseData<IPmcData[]>
|
||||
{
|
||||
public regenerateScav(url: string, info: IEmptyRequestData, sessionID: string): IGetBodyResponseData<IPmcData[]> {
|
||||
return this.httpResponse.getBody([this.profileController.generatePlayerScav(sessionID)]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle client/game/profile/voice/change event
|
||||
*/
|
||||
public changeVoice(url: string, info: IProfileChangeVoiceRequestData, sessionID: string): INullResponseData
|
||||
{
|
||||
public changeVoice(url: string, info: IProfileChangeVoiceRequestData, sessionID: string): INullResponseData {
|
||||
this.profileController.changeVoice(info, sessionID);
|
||||
return this.httpResponse.nullResponse();
|
||||
}
|
||||
@ -85,17 +79,14 @@ export class ProfileCallbacks
|
||||
url: string,
|
||||
info: IProfileChangeNicknameRequestData,
|
||||
sessionID: string,
|
||||
): IGetBodyResponseData<any>
|
||||
{
|
||||
): IGetBodyResponseData<any> {
|
||||
const output = this.profileController.changeNickname(info, sessionID);
|
||||
|
||||
if (output === "taken")
|
||||
{
|
||||
if (output === "taken") {
|
||||
return this.httpResponse.getBody(undefined, 255, "The nickname is already in use");
|
||||
}
|
||||
|
||||
if (output === "tooshort")
|
||||
{
|
||||
if (output === "tooshort") {
|
||||
return this.httpResponse.getBody(undefined, 1, "The nickname is too short");
|
||||
}
|
||||
|
||||
@ -109,17 +100,14 @@ export class ProfileCallbacks
|
||||
url: string,
|
||||
info: IValidateNicknameRequestData,
|
||||
sessionID: string,
|
||||
): IGetBodyResponseData<any>
|
||||
{
|
||||
): IGetBodyResponseData<any> {
|
||||
const output = this.profileController.validateNickname(info, sessionID);
|
||||
|
||||
if (output === "taken")
|
||||
{
|
||||
if (output === "taken") {
|
||||
return this.httpResponse.getBody(undefined, 255, "225 - ");
|
||||
}
|
||||
|
||||
if (output === "tooshort")
|
||||
{
|
||||
if (output === "tooshort") {
|
||||
return this.httpResponse.getBody(undefined, 256, "256 - ");
|
||||
}
|
||||
|
||||
@ -129,8 +117,7 @@ export class ProfileCallbacks
|
||||
/**
|
||||
* Handle client/game/profile/nickname/reserved
|
||||
*/
|
||||
public getReservedNickname(url: string, info: IEmptyRequestData, sessionID: string): IGetBodyResponseData<string>
|
||||
{
|
||||
public getReservedNickname(url: string, info: IEmptyRequestData, sessionID: string): IGetBodyResponseData<string> {
|
||||
return this.httpResponse.getBody("SPTarkov");
|
||||
}
|
||||
|
||||
@ -142,8 +129,7 @@ export class ProfileCallbacks
|
||||
url: string,
|
||||
info: IEmptyRequestData,
|
||||
sessionID: string,
|
||||
): IGetBodyResponseData<GetProfileStatusResponseData>
|
||||
{
|
||||
): IGetBodyResponseData<GetProfileStatusResponseData> {
|
||||
return this.httpResponse.getBody(this.profileController.getProfileStatus(sessionID));
|
||||
}
|
||||
|
||||
@ -155,8 +141,7 @@ export class ProfileCallbacks
|
||||
url: string,
|
||||
request: IGetOtherProfileRequest,
|
||||
sessionID: string,
|
||||
): IGetBodyResponseData<IGetOtherProfileResponse>
|
||||
{
|
||||
): IGetBodyResponseData<IGetOtherProfileResponse> {
|
||||
return this.httpResponse.getBody(this.profileController.getOtherProfile(sessionID, request));
|
||||
}
|
||||
|
||||
@ -167,8 +152,7 @@ export class ProfileCallbacks
|
||||
url: string,
|
||||
info: IGetProfileSettingsRequest,
|
||||
sessionId: string,
|
||||
): IGetBodyResponseData<boolean>
|
||||
{
|
||||
): IGetBodyResponseData<boolean> {
|
||||
return this.httpResponse.getBody(this.profileController.setChosenProfileIcon(sessionId, info));
|
||||
}
|
||||
|
||||
@ -179,24 +163,21 @@ export class ProfileCallbacks
|
||||
url: string,
|
||||
info: ISearchFriendRequestData,
|
||||
sessionID: string,
|
||||
): IGetBodyResponseData<ISearchFriendResponse[]>
|
||||
{
|
||||
): IGetBodyResponseData<ISearchFriendResponse[]> {
|
||||
return this.httpResponse.getBody(this.profileController.getFriends(info, sessionID));
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle launcher/profile/info
|
||||
*/
|
||||
public getMiniProfile(url: string, info: IGetMiniProfileRequestData, sessionID: string): string
|
||||
{
|
||||
public getMiniProfile(url: string, info: IGetMiniProfileRequestData, sessionID: string): string {
|
||||
return this.httpResponse.noBody(this.profileController.getMiniProfile(sessionID));
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle /launcher/profiles
|
||||
*/
|
||||
public getAllMiniProfiles(url: string, info: IEmptyRequestData, sessionID: string): string
|
||||
{
|
||||
public getAllMiniProfiles(url: string, info: IEmptyRequestData, sessionID: string): string {
|
||||
return this.httpResponse.noBody(this.profileController.getMiniProfiles());
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { inject, injectable } from "tsyringe";
|
||||
import { QuestController } from "@spt/controllers/QuestController";
|
||||
import { RepeatableQuestController } from "@spt/controllers/RepeatableQuestController";
|
||||
import { IEmptyRequestData } from "@spt/models/eft/common/IEmptyRequestData";
|
||||
@ -13,16 +12,15 @@ import { IHandoverQuestRequestData } from "@spt/models/eft/quests/IHandoverQuest
|
||||
import { IListQuestsRequestData } from "@spt/models/eft/quests/IListQuestsRequestData";
|
||||
import { IRepeatableQuestChangeRequest } from "@spt/models/eft/quests/IRepeatableQuestChangeRequest";
|
||||
import { HttpResponseUtil } from "@spt/utils/HttpResponseUtil";
|
||||
import { inject, injectable } from "tsyringe";
|
||||
|
||||
@injectable()
|
||||
export class QuestCallbacks
|
||||
{
|
||||
export class QuestCallbacks {
|
||||
constructor(
|
||||
@inject("HttpResponseUtil") protected httpResponse: HttpResponseUtil,
|
||||
@inject("QuestController") protected questController: QuestController,
|
||||
@inject("RepeatableQuestController") protected repeatableQuestController: RepeatableQuestController,
|
||||
)
|
||||
{}
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Handle RepeatableQuestChange event
|
||||
@ -31,18 +29,15 @@ export class QuestCallbacks
|
||||
pmcData: IPmcData,
|
||||
body: IRepeatableQuestChangeRequest,
|
||||
sessionID: string,
|
||||
): IItemEventRouterResponse
|
||||
{
|
||||
): IItemEventRouterResponse {
|
||||
return this.repeatableQuestController.changeRepeatableQuest(pmcData, body, sessionID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle QuestAccept event
|
||||
*/
|
||||
public acceptQuest(pmcData: IPmcData, body: IAcceptQuestRequestData, sessionID: string): IItemEventRouterResponse
|
||||
{
|
||||
if (body.type === "repeatable")
|
||||
{
|
||||
public acceptQuest(pmcData: IPmcData, body: IAcceptQuestRequestData, sessionID: string): IItemEventRouterResponse {
|
||||
if (body.type === "repeatable") {
|
||||
return this.questController.acceptRepeatableQuest(pmcData, body, sessionID);
|
||||
}
|
||||
return this.questController.acceptQuest(pmcData, body, sessionID);
|
||||
@ -55,8 +50,7 @@ export class QuestCallbacks
|
||||
pmcData: IPmcData,
|
||||
body: ICompleteQuestRequestData,
|
||||
sessionID: string,
|
||||
): IItemEventRouterResponse
|
||||
{
|
||||
): IItemEventRouterResponse {
|
||||
return this.questController.completeQuest(pmcData, body, sessionID);
|
||||
}
|
||||
|
||||
@ -67,16 +61,14 @@ export class QuestCallbacks
|
||||
pmcData: IPmcData,
|
||||
body: IHandoverQuestRequestData,
|
||||
sessionID: string,
|
||||
): IItemEventRouterResponse
|
||||
{
|
||||
): IItemEventRouterResponse {
|
||||
return this.questController.handoverQuest(pmcData, body, sessionID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle client/quest/list
|
||||
*/
|
||||
public listQuests(url: string, info: IListQuestsRequestData, sessionID: string): IGetBodyResponseData<IQuest[]>
|
||||
{
|
||||
public listQuests(url: string, info: IListQuestsRequestData, sessionID: string): IGetBodyResponseData<IQuest[]> {
|
||||
return this.httpResponse.getBody(this.questController.getClientQuests(sessionID));
|
||||
}
|
||||
|
||||
@ -87,8 +79,7 @@ export class QuestCallbacks
|
||||
url: string,
|
||||
info: IEmptyRequestData,
|
||||
sessionID: string,
|
||||
): IGetBodyResponseData<IPmcDataRepeatableQuest[]>
|
||||
{
|
||||
): IGetBodyResponseData<IPmcDataRepeatableQuest[]> {
|
||||
return this.httpResponse.getBody(this.repeatableQuestController.getClientRepeatableQuests(sessionID));
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { inject, injectable } from "tsyringe";
|
||||
import { RagfairController } from "@spt/controllers/RagfairController";
|
||||
import { OnLoad } from "@spt/di/OnLoad";
|
||||
import { OnUpdate } from "@spt/di/OnUpdate";
|
||||
@ -24,13 +23,13 @@ import { ConfigServer } from "@spt/servers/ConfigServer";
|
||||
import { RagfairServer } from "@spt/servers/RagfairServer";
|
||||
import { RagfairTaxService } from "@spt/services/RagfairTaxService";
|
||||
import { HttpResponseUtil } from "@spt/utils/HttpResponseUtil";
|
||||
import { inject, injectable } from "tsyringe";
|
||||
|
||||
/**
|
||||
* Handle ragfair related callback events
|
||||
*/
|
||||
@injectable()
|
||||
export class RagfairCallbacks implements OnLoad, OnUpdate
|
||||
{
|
||||
export class RagfairCallbacks implements OnLoad, OnUpdate {
|
||||
protected ragfairConfig: IRagfairConfig;
|
||||
|
||||
constructor(
|
||||
@ -39,25 +38,20 @@ export class RagfairCallbacks implements OnLoad, OnUpdate
|
||||
@inject("RagfairController") protected ragfairController: RagfairController,
|
||||
@inject("RagfairTaxService") protected ragfairTaxService: RagfairTaxService,
|
||||
@inject("ConfigServer") protected configServer: ConfigServer,
|
||||
)
|
||||
{
|
||||
) {
|
||||
this.ragfairConfig = this.configServer.getConfig(ConfigTypes.RAGFAIR);
|
||||
}
|
||||
|
||||
public async onLoad(): Promise<void>
|
||||
{
|
||||
public async onLoad(): Promise<void> {
|
||||
await this.ragfairServer.load();
|
||||
}
|
||||
|
||||
public getRoute(): string
|
||||
{
|
||||
public getRoute(): string {
|
||||
return "spt-ragfair";
|
||||
}
|
||||
|
||||
public async onUpdate(timeSinceLastRun: number): Promise<boolean>
|
||||
{
|
||||
if (timeSinceLastRun > this.ragfairConfig.runIntervalSeconds)
|
||||
{
|
||||
public async onUpdate(timeSinceLastRun: number): Promise<boolean> {
|
||||
if (timeSinceLastRun > this.ragfairConfig.runIntervalSeconds) {
|
||||
// There is a flag inside this class that only makes it run once.
|
||||
this.ragfairServer.addPlayerOffers();
|
||||
|
||||
@ -76,8 +70,7 @@ export class RagfairCallbacks implements OnLoad, OnUpdate
|
||||
* Handle client/ragfair/search
|
||||
* Handle client/ragfair/find
|
||||
*/
|
||||
public search(url: string, info: ISearchRequestData, sessionID: string): IGetBodyResponseData<IGetOffersResult>
|
||||
{
|
||||
public search(url: string, info: ISearchRequestData, sessionID: string): IGetBodyResponseData<IGetOffersResult> {
|
||||
return this.httpResponse.getBody(this.ragfairController.getOffers(sessionID, info));
|
||||
}
|
||||
|
||||
@ -86,26 +79,22 @@ export class RagfairCallbacks implements OnLoad, OnUpdate
|
||||
url: string,
|
||||
info: IGetMarketPriceRequestData,
|
||||
sessionID: string,
|
||||
): IGetBodyResponseData<IGetItemPriceResult>
|
||||
{
|
||||
): IGetBodyResponseData<IGetItemPriceResult> {
|
||||
return this.httpResponse.getBody(this.ragfairController.getItemMinAvgMaxFleaPriceValues(info));
|
||||
}
|
||||
|
||||
/** Handle RagFairAddOffer event */
|
||||
public addOffer(pmcData: IPmcData, info: IAddOfferRequestData, sessionID: string): IItemEventRouterResponse
|
||||
{
|
||||
public addOffer(pmcData: IPmcData, info: IAddOfferRequestData, sessionID: string): IItemEventRouterResponse {
|
||||
return this.ragfairController.addPlayerOffer(pmcData, info, sessionID);
|
||||
}
|
||||
|
||||
/** Handle RagFairRemoveOffer event */
|
||||
public removeOffer(pmcData: IPmcData, info: IRemoveOfferRequestData, sessionID: string): IItemEventRouterResponse
|
||||
{
|
||||
public removeOffer(pmcData: IPmcData, info: IRemoveOfferRequestData, sessionID: string): IItemEventRouterResponse {
|
||||
return this.ragfairController.removeOffer(info, sessionID);
|
||||
}
|
||||
|
||||
/** Handle RagFairRenewOffer event */
|
||||
public extendOffer(pmcData: IPmcData, info: IExtendOfferRequestData, sessionID: string): IItemEventRouterResponse
|
||||
{
|
||||
public extendOffer(pmcData: IPmcData, info: IExtendOfferRequestData, sessionID: string): IItemEventRouterResponse {
|
||||
return this.ragfairController.extendOffer(info, sessionID);
|
||||
}
|
||||
|
||||
@ -117,14 +106,12 @@ export class RagfairCallbacks implements OnLoad, OnUpdate
|
||||
url: string,
|
||||
request: IEmptyRequestData,
|
||||
sessionID: string,
|
||||
): IGetBodyResponseData<Record<string, number>>
|
||||
{
|
||||
): IGetBodyResponseData<Record<string, number>> {
|
||||
return this.httpResponse.getBody(this.ragfairController.getAllFleaPrices());
|
||||
}
|
||||
|
||||
/** Handle client/reports/ragfair/send */
|
||||
public sendReport(url: string, info: ISendRagfairReportRequestData, sessionID: string): INullResponseData
|
||||
{
|
||||
public sendReport(url: string, info: ISendRagfairReportRequestData, sessionID: string): INullResponseData {
|
||||
return this.httpResponse.nullResponse();
|
||||
}
|
||||
|
||||
@ -132,8 +119,7 @@ export class RagfairCallbacks implements OnLoad, OnUpdate
|
||||
url: string,
|
||||
request: IStorePlayerOfferTaxAmountRequestData,
|
||||
sessionId: string,
|
||||
): INullResponseData
|
||||
{
|
||||
): INullResponseData {
|
||||
this.ragfairTaxService.storeClientOfferTaxValue(sessionId, request);
|
||||
return this.httpResponse.nullResponse();
|
||||
}
|
||||
@ -143,8 +129,7 @@ export class RagfairCallbacks implements OnLoad, OnUpdate
|
||||
url: string,
|
||||
request: IGetRagfairOfferByIdRequest,
|
||||
sessionID: string,
|
||||
): IGetBodyResponseData<IRagfairOffer>
|
||||
{
|
||||
): IGetBodyResponseData<IRagfairOffer> {
|
||||
return this.httpResponse.getBody(this.ragfairController.getOfferById(sessionID, request));
|
||||
}
|
||||
}
|
||||
|
@ -1,15 +1,13 @@
|
||||
import { inject, injectable } from "tsyringe";
|
||||
import { RepairController } from "@spt/controllers/RepairController";
|
||||
import { IPmcData } from "@spt/models/eft/common/IPmcData";
|
||||
import { IItemEventRouterResponse } from "@spt/models/eft/itemEvent/IItemEventRouterResponse";
|
||||
import { IRepairActionDataRequest } from "@spt/models/eft/repair/IRepairActionDataRequest";
|
||||
import { ITraderRepairActionDataRequest } from "@spt/models/eft/repair/ITraderRepairActionDataRequest";
|
||||
import { inject, injectable } from "tsyringe";
|
||||
|
||||
@injectable()
|
||||
export class RepairCallbacks
|
||||
{
|
||||
constructor(@inject("RepairController") protected repairController: RepairController)
|
||||
{}
|
||||
export class RepairCallbacks {
|
||||
constructor(@inject("RepairController") protected repairController: RepairController) {}
|
||||
|
||||
/**
|
||||
* Handle TraderRepair event
|
||||
@ -23,8 +21,7 @@ export class RepairCallbacks
|
||||
pmcData: IPmcData,
|
||||
traderRepairRequest: ITraderRepairActionDataRequest,
|
||||
sessionID: string,
|
||||
): IItemEventRouterResponse
|
||||
{
|
||||
): IItemEventRouterResponse {
|
||||
return this.repairController.traderRepair(sessionID, traderRepairRequest, pmcData);
|
||||
}
|
||||
|
||||
@ -40,8 +37,7 @@ export class RepairCallbacks
|
||||
pmcData: IPmcData,
|
||||
repairRequest: IRepairActionDataRequest,
|
||||
sessionID: string,
|
||||
): IItemEventRouterResponse
|
||||
{
|
||||
): IItemEventRouterResponse {
|
||||
return this.repairController.repairWithKit(sessionID, repairRequest, pmcData);
|
||||
}
|
||||
}
|
||||
|
@ -1,39 +1,33 @@
|
||||
import { inject, injectable } from "tsyringe";
|
||||
import { OnLoad } from "@spt/di/OnLoad";
|
||||
import { OnUpdate } from "@spt/di/OnUpdate";
|
||||
import { ConfigTypes } from "@spt/models/enums/ConfigTypes";
|
||||
import { ICoreConfig } from "@spt/models/spt/config/ICoreConfig";
|
||||
import { ConfigServer } from "@spt/servers/ConfigServer";
|
||||
import { SaveServer } from "@spt/servers/SaveServer";
|
||||
import { inject, injectable } from "tsyringe";
|
||||
|
||||
@injectable()
|
||||
export class SaveCallbacks implements OnLoad, OnUpdate
|
||||
{
|
||||
export class SaveCallbacks implements OnLoad, OnUpdate {
|
||||
protected coreConfig: ICoreConfig;
|
||||
|
||||
constructor(
|
||||
@inject("SaveServer") protected saveServer: SaveServer,
|
||||
@inject("ConfigServer") protected configServer: ConfigServer,
|
||||
)
|
||||
{
|
||||
) {
|
||||
this.coreConfig = this.configServer.getConfig(ConfigTypes.CORE);
|
||||
}
|
||||
|
||||
public async onLoad(): Promise<void>
|
||||
{
|
||||
public async onLoad(): Promise<void> {
|
||||
this.saveServer.load();
|
||||
}
|
||||
|
||||
public getRoute(): string
|
||||
{
|
||||
public getRoute(): string {
|
||||
return "spt-save";
|
||||
}
|
||||
|
||||
public async onUpdate(secondsSinceLastRun: number): Promise<boolean>
|
||||
{
|
||||
public async onUpdate(secondsSinceLastRun: number): Promise<boolean> {
|
||||
// run every 15 seconds
|
||||
if (secondsSinceLastRun > this.coreConfig.profileSaveIntervalSeconds)
|
||||
{
|
||||
if (secondsSinceLastRun > this.coreConfig.profileSaveIntervalSeconds) {
|
||||
this.saveServer.save();
|
||||
return true;
|
||||
}
|
||||
|
@ -1,16 +1,14 @@
|
||||
import { inject, injectable } from "tsyringe";
|
||||
import { TradeController } from "@spt/controllers/TradeController";
|
||||
import { IPmcData } from "@spt/models/eft/common/IPmcData";
|
||||
import { IItemEventRouterResponse } from "@spt/models/eft/itemEvent/IItemEventRouterResponse";
|
||||
import { IProcessBaseTradeRequestData } from "@spt/models/eft/trade/IProcessBaseTradeRequestData";
|
||||
import { IProcessRagfairTradeRequestData } from "@spt/models/eft/trade/IProcessRagfairTradeRequestData";
|
||||
import { ISellScavItemsToFenceRequestData } from "@spt/models/eft/trade/ISellScavItemsToFenceRequestData";
|
||||
import { inject, injectable } from "tsyringe";
|
||||
|
||||
@injectable()
|
||||
export class TradeCallbacks
|
||||
{
|
||||
constructor(@inject("TradeController") protected tradeController: TradeController)
|
||||
{}
|
||||
export class TradeCallbacks {
|
||||
constructor(@inject("TradeController") protected tradeController: TradeController) {}
|
||||
|
||||
/**
|
||||
* Handle client/game/profile/items/moving TradingConfirm event
|
||||
@ -19,8 +17,7 @@ export class TradeCallbacks
|
||||
pmcData: IPmcData,
|
||||
body: IProcessBaseTradeRequestData,
|
||||
sessionID: string,
|
||||
): IItemEventRouterResponse
|
||||
{
|
||||
): IItemEventRouterResponse {
|
||||
// body can be IProcessBuyTradeRequestData or IProcessSellTradeRequestData
|
||||
return this.tradeController.confirmTrading(pmcData, body, sessionID);
|
||||
}
|
||||
@ -30,8 +27,7 @@ export class TradeCallbacks
|
||||
pmcData: IPmcData,
|
||||
body: IProcessRagfairTradeRequestData,
|
||||
sessionID: string,
|
||||
): IItemEventRouterResponse
|
||||
{
|
||||
): IItemEventRouterResponse {
|
||||
return this.tradeController.confirmRagfairTrading(pmcData, body, sessionID);
|
||||
}
|
||||
|
||||
@ -40,8 +36,7 @@ export class TradeCallbacks
|
||||
pmcData: IPmcData,
|
||||
body: ISellScavItemsToFenceRequestData,
|
||||
sessionID: string,
|
||||
): IItemEventRouterResponse
|
||||
{
|
||||
): IItemEventRouterResponse {
|
||||
return this.tradeController.sellScavItemsToFence(pmcData, body, sessionID);
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { inject, injectable } from "tsyringe";
|
||||
import { TraderController } from "@spt/controllers/TraderController";
|
||||
import { OnLoad } from "@spt/di/OnLoad";
|
||||
import { OnUpdate } from "@spt/di/OnUpdate";
|
||||
@ -6,28 +5,24 @@ import { IEmptyRequestData } from "@spt/models/eft/common/IEmptyRequestData";
|
||||
import { ITraderAssort, ITraderBase } from "@spt/models/eft/common/tables/ITrader";
|
||||
import { IGetBodyResponseData } from "@spt/models/eft/httpResponse/IGetBodyResponseData";
|
||||
import { HttpResponseUtil } from "@spt/utils/HttpResponseUtil";
|
||||
import { inject, injectable } from "tsyringe";
|
||||
|
||||
@injectable()
|
||||
export class TraderCallbacks implements OnLoad, OnUpdate
|
||||
{
|
||||
export class TraderCallbacks implements OnLoad, OnUpdate {
|
||||
constructor(
|
||||
@inject("HttpResponseUtil") protected httpResponse: HttpResponseUtil, // TODO: delay required
|
||||
@inject("TraderController") protected traderController: TraderController,
|
||||
)
|
||||
{}
|
||||
) {}
|
||||
|
||||
public async onLoad(): Promise<void>
|
||||
{
|
||||
public async onLoad(): Promise<void> {
|
||||
this.traderController.load();
|
||||
}
|
||||
|
||||
public async onUpdate(): Promise<boolean>
|
||||
{
|
||||
public async onUpdate(): Promise<boolean> {
|
||||
return this.traderController.update();
|
||||
}
|
||||
|
||||
public getRoute(): string
|
||||
{
|
||||
public getRoute(): string {
|
||||
return "spt-traders";
|
||||
}
|
||||
|
||||
@ -36,21 +31,18 @@ export class TraderCallbacks implements OnLoad, OnUpdate
|
||||
url: string,
|
||||
info: IEmptyRequestData,
|
||||
sessionID: string,
|
||||
): IGetBodyResponseData<ITraderBase[]>
|
||||
{
|
||||
): IGetBodyResponseData<ITraderBase[]> {
|
||||
return this.httpResponse.getBody(this.traderController.getAllTraders(sessionID));
|
||||
}
|
||||
|
||||
/** Handle client/trading/api/getTrader */
|
||||
public getTrader(url: string, info: IEmptyRequestData, sessionID: string): IGetBodyResponseData<ITraderBase>
|
||||
{
|
||||
public getTrader(url: string, info: IEmptyRequestData, sessionID: string): IGetBodyResponseData<ITraderBase> {
|
||||
const traderID = url.replace("/client/trading/api/getTrader/", "");
|
||||
return this.httpResponse.getBody(this.traderController.getTrader(sessionID, traderID));
|
||||
}
|
||||
|
||||
/** Handle client/trading/api/getTraderAssort */
|
||||
public getAssort(url: string, info: IEmptyRequestData, sessionID: string): IGetBodyResponseData<ITraderAssort>
|
||||
{
|
||||
public getAssort(url: string, info: IEmptyRequestData, sessionID: string): IGetBodyResponseData<ITraderAssort> {
|
||||
const traderID = url.replace("/client/trading/api/getTraderAssort/", "");
|
||||
return this.httpResponse.getBody(this.traderController.getAssort(sessionID, traderID));
|
||||
}
|
||||
|
@ -1,31 +1,31 @@
|
||||
import { inject, injectable } from "tsyringe";
|
||||
import { WeatherController } from "@spt/controllers/WeatherController";
|
||||
import { IEmptyRequestData } from "@spt/models/eft/common/IEmptyRequestData";
|
||||
import { IGetBodyResponseData } from "@spt/models/eft/httpResponse/IGetBodyResponseData";
|
||||
import { IWeatherData } from "@spt/models/eft/weather/IWeatherData";
|
||||
import { HttpResponseUtil } from "@spt/utils/HttpResponseUtil";
|
||||
import { IGetLocalWeatherResponseData } from "@spt/models/spt/weather/IGetLocalWeatherResponseData";
|
||||
import { HttpResponseUtil } from "@spt/utils/HttpResponseUtil";
|
||||
import { inject, injectable } from "tsyringe";
|
||||
|
||||
@injectable()
|
||||
export class WeatherCallbacks
|
||||
{
|
||||
export class WeatherCallbacks {
|
||||
constructor(
|
||||
@inject("HttpResponseUtil") protected httpResponse: HttpResponseUtil,
|
||||
@inject("WeatherController") protected weatherController: WeatherController,
|
||||
)
|
||||
{}
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Handle client/weather
|
||||
* @returns IWeatherData
|
||||
*/
|
||||
public getWeather(url: string, info: IEmptyRequestData, sessionID: string): IGetBodyResponseData<IWeatherData>
|
||||
{
|
||||
public getWeather(url: string, info: IEmptyRequestData, sessionID: string): IGetBodyResponseData<IWeatherData> {
|
||||
return this.httpResponse.getBody(this.weatherController.generate());
|
||||
}
|
||||
|
||||
public getLocalWeather(url: string, info: IEmptyRequestData, sessionID: string): IGetBodyResponseData<IGetLocalWeatherResponseData>
|
||||
{
|
||||
public getLocalWeather(
|
||||
url: string,
|
||||
info: IEmptyRequestData,
|
||||
sessionID: string,
|
||||
): IGetBodyResponseData<IGetLocalWeatherResponseData> {
|
||||
return this.httpResponse.getBody(this.weatherController.generateLocal(sessionID));
|
||||
}
|
||||
}
|
||||
|
@ -1,24 +1,21 @@
|
||||
import { inject, injectable } from "tsyringe";
|
||||
import { WishlistController } from "@spt/controllers/WishlistController";
|
||||
import { IPmcData } from "@spt/models/eft/common/IPmcData";
|
||||
import { IItemEventRouterResponse } from "@spt/models/eft/itemEvent/IItemEventRouterResponse";
|
||||
import { IAddToWishlistRequest } from "@spt/models/eft/wishlist/IAddToWishlistRequest";
|
||||
import { IChangeWishlistItemCategoryRequest } from "@spt/models/eft/wishlist/IChangeWishlistItemCategoryRequest";
|
||||
import { IRemoveFromWishlistRequest } from "@spt/models/eft/wishlist/IRemoveFromWishlistRequest";
|
||||
import { inject, injectable } from "tsyringe";
|
||||
|
||||
@injectable()
|
||||
export class WishlistCallbacks
|
||||
{
|
||||
constructor(@inject("WishlistController") protected wishlistController: WishlistController)
|
||||
{}
|
||||
export class WishlistCallbacks {
|
||||
constructor(@inject("WishlistController") protected wishlistController: WishlistController) {}
|
||||
|
||||
/** Handle AddToWishList event */
|
||||
public addToWishlist(
|
||||
pmcData: IPmcData,
|
||||
request: IAddToWishlistRequest,
|
||||
sessionID: string,
|
||||
): IItemEventRouterResponse
|
||||
{
|
||||
): IItemEventRouterResponse {
|
||||
return this.wishlistController.addToWishList(pmcData, request, sessionID);
|
||||
}
|
||||
|
||||
@ -27,8 +24,7 @@ export class WishlistCallbacks
|
||||
pmcData: IPmcData,
|
||||
request: IRemoveFromWishlistRequest,
|
||||
sessionID: string,
|
||||
): IItemEventRouterResponse
|
||||
{
|
||||
): IItemEventRouterResponse {
|
||||
return this.wishlistController.removeFromWishList(pmcData, request, sessionID);
|
||||
}
|
||||
|
||||
@ -37,8 +33,7 @@ export class WishlistCallbacks
|
||||
pmcData: IPmcData,
|
||||
request: IChangeWishlistItemCategoryRequest,
|
||||
sessionID: string,
|
||||
): IItemEventRouterResponse
|
||||
{
|
||||
): IItemEventRouterResponse {
|
||||
return this.wishlistController.changeWishlistItemCategory(pmcData, request, sessionID);
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,10 @@
|
||||
import { injectable } from "tsyringe";
|
||||
import { ContextVariable } from "@spt/context/ContextVariable";
|
||||
import { ContextVariableType } from "@spt/context/ContextVariableType";
|
||||
import { LinkedList } from "@spt/utils/collections/lists/LinkedList";
|
||||
import { injectable } from "tsyringe";
|
||||
|
||||
@injectable()
|
||||
export class ApplicationContext
|
||||
{
|
||||
export class ApplicationContext {
|
||||
private variables = new Map<ContextVariableType, LinkedList<ContextVariable>>();
|
||||
private static holderMaxSize = 10;
|
||||
|
||||
@ -19,23 +18,18 @@ export class ApplicationContext
|
||||
* const matchInfo = this.applicationContext.getLatestValue(ContextVariableType.RAID_CONFIGURATION).getValue<IGetRaidConfigurationRequestData>();
|
||||
* ```
|
||||
*/
|
||||
public getLatestValue(type: ContextVariableType): ContextVariable | undefined
|
||||
{
|
||||
if (this.variables.has(type))
|
||||
{
|
||||
public getLatestValue(type: ContextVariableType): ContextVariable | undefined {
|
||||
if (this.variables.has(type)) {
|
||||
return this.variables.get(type)?.getTail();
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
public getValues(type: ContextVariableType): ContextVariable[] | undefined
|
||||
{
|
||||
if (this.variables.has(type))
|
||||
{
|
||||
public getValues(type: ContextVariableType): ContextVariable[] | undefined {
|
||||
if (this.variables.has(type)) {
|
||||
const res: ContextVariable[] = [];
|
||||
|
||||
for (const value of this.variables.get(type)!.values())
|
||||
{
|
||||
for (const value of this.variables.get(type)!.values()) {
|
||||
res.push(value);
|
||||
}
|
||||
|
||||
@ -44,20 +38,15 @@ export class ApplicationContext
|
||||
return undefined;
|
||||
}
|
||||
|
||||
public addValue(type: ContextVariableType, value: any): void
|
||||
{
|
||||
public addValue(type: ContextVariableType, value: any): void {
|
||||
let list: LinkedList<ContextVariable>;
|
||||
if (this.variables.has(type))
|
||||
{
|
||||
if (this.variables.has(type)) {
|
||||
list = this.variables.get(type)!;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
list = new LinkedList<ContextVariable>();
|
||||
}
|
||||
|
||||
if (list.length >= ApplicationContext.holderMaxSize)
|
||||
{
|
||||
if (list.length >= ApplicationContext.holderMaxSize) {
|
||||
list.shift();
|
||||
}
|
||||
|
||||
@ -65,10 +54,8 @@ export class ApplicationContext
|
||||
this.variables.set(type, list);
|
||||
}
|
||||
|
||||
public clearValues(type: ContextVariableType): void
|
||||
{
|
||||
if (this.variables.has(type))
|
||||
{
|
||||
public clearValues(type: ContextVariableType): void {
|
||||
if (this.variables.has(type)) {
|
||||
this.variables.delete(type);
|
||||
}
|
||||
}
|
||||
|
@ -1,30 +1,25 @@
|
||||
import { ContextVariableType } from "@spt/context/ContextVariableType";
|
||||
|
||||
export class ContextVariable
|
||||
{
|
||||
export class ContextVariable {
|
||||
private value: any;
|
||||
private timestamp: Date;
|
||||
private type: ContextVariableType;
|
||||
|
||||
constructor(value: any, type: ContextVariableType)
|
||||
{
|
||||
constructor(value: any, type: ContextVariableType) {
|
||||
this.value = value;
|
||||
this.timestamp = new Date();
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public getValue<T>(): T
|
||||
{
|
||||
public getValue<T>(): T {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
public getTimestamp(): Date
|
||||
{
|
||||
public getTimestamp(): Date {
|
||||
return this.timestamp;
|
||||
}
|
||||
|
||||
public getType(): ContextVariableType
|
||||
{
|
||||
public getType(): ContextVariableType {
|
||||
return this.type;
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,4 @@
|
||||
export enum ContextVariableType
|
||||
{
|
||||
export enum ContextVariableType {
|
||||
/** Logged in users session id */
|
||||
SESSION_ID = 0,
|
||||
/** Currently acive raid information */
|
||||
|
@ -1,27 +1,24 @@
|
||||
import { inject, injectable } from "tsyringe";
|
||||
import { ICompletedAchievementsResponse } from "@spt/models/eft/profile/ICompletedAchievementsResponse";
|
||||
import { IGetAchievementsResponse } from "@spt/models/eft/profile/IGetAchievementsResponse";
|
||||
import { ILogger } from "@spt/models/spt/utils/ILogger";
|
||||
import { DatabaseService } from "@spt/services/DatabaseService";
|
||||
import { inject, injectable } from "tsyringe";
|
||||
|
||||
/**
|
||||
* Logic for handling In Raid callbacks
|
||||
*/
|
||||
@injectable()
|
||||
export class AchievementController
|
||||
{
|
||||
export class AchievementController {
|
||||
constructor(
|
||||
@inject("PrimaryLogger") protected logger: ILogger,
|
||||
@inject("DatabaseService") protected databaseService: DatabaseService,
|
||||
)
|
||||
{}
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Get base achievements
|
||||
* @param sessionID Session id
|
||||
*/
|
||||
public getAchievements(sessionID: string): IGetAchievementsResponse
|
||||
{
|
||||
public getAchievements(sessionID: string): IGetAchievementsResponse {
|
||||
return { elements: this.databaseService.getAchievements() };
|
||||
}
|
||||
|
||||
@ -30,13 +27,11 @@ export class AchievementController
|
||||
* @param sessionId Session id
|
||||
* @returns ICompletedAchievementsResponse
|
||||
*/
|
||||
public getAchievementStatistics(sessionId: string): ICompletedAchievementsResponse
|
||||
{
|
||||
public getAchievementStatistics(sessionId: string): ICompletedAchievementsResponse {
|
||||
const achievements = this.databaseService.getAchievements();
|
||||
const stats = {};
|
||||
|
||||
for (const achievement of achievements)
|
||||
{
|
||||
for (const achievement of achievements) {
|
||||
stats[achievement.id] = 0;
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { inject, injectable } from "tsyringe";
|
||||
import { ApplicationContext } from "@spt/context/ApplicationContext";
|
||||
import { ContextVariableType } from "@spt/context/ContextVariableType";
|
||||
import { BotGenerator } from "@spt/generators/BotGenerator";
|
||||
@ -25,12 +24,12 @@ import { DatabaseService } from "@spt/services/DatabaseService";
|
||||
import { LocalisationService } from "@spt/services/LocalisationService";
|
||||
import { MatchBotDetailsCacheService } from "@spt/services/MatchBotDetailsCacheService";
|
||||
import { SeasonalEventService } from "@spt/services/SeasonalEventService";
|
||||
import { ICloner } from "@spt/utils/cloners/ICloner";
|
||||
import { RandomUtil } from "@spt/utils/RandomUtil";
|
||||
import { ICloner } from "@spt/utils/cloners/ICloner";
|
||||
import { inject, injectable } from "tsyringe";
|
||||
|
||||
@injectable()
|
||||
export class BotController
|
||||
{
|
||||
export class BotController {
|
||||
protected botConfig: IBotConfig;
|
||||
protected pmcConfig: IPmcConfig;
|
||||
|
||||
@ -50,8 +49,7 @@ export class BotController
|
||||
@inject("ApplicationContext") protected applicationContext: ApplicationContext,
|
||||
@inject("RandomUtil") protected randomUtil: RandomUtil,
|
||||
@inject("PrimaryCloner") protected cloner: ICloner,
|
||||
)
|
||||
{
|
||||
) {
|
||||
this.botConfig = this.configServer.getConfig(ConfigTypes.BOT);
|
||||
this.pmcConfig = this.configServer.getConfig(ConfigTypes.PMC);
|
||||
}
|
||||
@ -61,12 +59,10 @@ export class BotController
|
||||
* @param type bot Type we want the load-out gen count for
|
||||
* @returns number of bots to generate
|
||||
*/
|
||||
public getBotPresetGenerationLimit(type: string): number
|
||||
{
|
||||
public getBotPresetGenerationLimit(type: string): number {
|
||||
const value = this.botConfig.presetBatch[type === "assaultGroup" ? "assault" : type];
|
||||
|
||||
if (!value)
|
||||
{
|
||||
if (!value) {
|
||||
this.logger.warning(this.localisationService.getText("bot-bot_preset_count_value_missing", type));
|
||||
|
||||
return 30;
|
||||
@ -80,8 +76,7 @@ export class BotController
|
||||
* Get the core.json difficulty settings from database/bots
|
||||
* @returns IBotCore
|
||||
*/
|
||||
public getBotCoreDifficulty(): IBotCore
|
||||
{
|
||||
public getBotCoreDifficulty(): IBotCore {
|
||||
return this.databaseService.getBots().core;
|
||||
}
|
||||
|
||||
@ -93,15 +88,13 @@ export class BotController
|
||||
* @param ignoreRaidSettings should raid settings chosen pre-raid be ignored
|
||||
* @returns Difficulty object
|
||||
*/
|
||||
public getBotDifficulty(type: string, diffLevel: string, ignoreRaidSettings = false): Difficulty
|
||||
{
|
||||
public getBotDifficulty(type: string, diffLevel: string, ignoreRaidSettings = false): Difficulty {
|
||||
let difficulty = diffLevel.toLowerCase();
|
||||
|
||||
const raidConfig = this.applicationContext
|
||||
.getLatestValue(ContextVariableType.RAID_CONFIGURATION)
|
||||
?.getValue<IGetRaidConfigurationRequestData>();
|
||||
if (!(raidConfig || ignoreRaidSettings))
|
||||
{
|
||||
if (!(raidConfig || ignoreRaidSettings)) {
|
||||
this.logger.error(
|
||||
this.localisationService.getText("bot-missing_application_context", "RAID_CONFIGURATION"),
|
||||
);
|
||||
@ -110,16 +103,14 @@ export class BotController
|
||||
// Check value chosen in pre-raid difficulty dropdown
|
||||
// If value is not 'asonline', change requested difficulty to be what was chosen in dropdown
|
||||
const botDifficultyDropDownValue = raidConfig?.wavesSettings.botDifficulty.toLowerCase() ?? "asonline";
|
||||
if (botDifficultyDropDownValue !== "asonline")
|
||||
{
|
||||
difficulty
|
||||
= this.botDifficultyHelper.convertBotDifficultyDropdownToBotDifficulty(botDifficultyDropDownValue);
|
||||
if (botDifficultyDropDownValue !== "asonline") {
|
||||
difficulty =
|
||||
this.botDifficultyHelper.convertBotDifficultyDropdownToBotDifficulty(botDifficultyDropDownValue);
|
||||
}
|
||||
|
||||
let difficultySettings: Difficulty;
|
||||
const lowercasedBotType = type.toLowerCase();
|
||||
switch (lowercasedBotType)
|
||||
{
|
||||
switch (lowercasedBotType) {
|
||||
case this.pmcConfig.bearType.toLowerCase():
|
||||
difficultySettings = this.botDifficultyHelper.getPmcDifficultySettings(
|
||||
"bear",
|
||||
@ -144,14 +135,12 @@ export class BotController
|
||||
return difficultySettings;
|
||||
}
|
||||
|
||||
public getAllBotDifficulties(): Record<string, any>
|
||||
{
|
||||
public getAllBotDifficulties(): Record<string, any> {
|
||||
const result = {};
|
||||
|
||||
const botTypesDb = this.databaseService.getBots().types;
|
||||
const botTypes = Object.keys(WildSpawnTypeNumber).filter((v) => Number.isNaN(Number(v)));
|
||||
for (let botType of botTypes)
|
||||
{
|
||||
for (let botType of botTypes) {
|
||||
const enumType = botType.toLowerCase();
|
||||
// pmcBEAR/pmcUSEC need to be converted into `usec`/`bear` so we can read difficulty settings from bots/types
|
||||
botType = this.botHelper.isBotPmc(botType)
|
||||
@ -159,15 +148,13 @@ export class BotController
|
||||
: botType.toLowerCase();
|
||||
|
||||
const botDetails = botTypesDb[botType];
|
||||
if (!botDetails?.difficulty)
|
||||
{
|
||||
if (!botDetails?.difficulty) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const botDifficulties = Object.keys(botDetails.difficulty);
|
||||
result[enumType] = {};
|
||||
for (const difficulty of botDifficulties)
|
||||
{
|
||||
for (const difficulty of botDifficulties) {
|
||||
result[enumType][difficulty] = this.getBotDifficulty(enumType, difficulty, true);
|
||||
}
|
||||
}
|
||||
@ -181,14 +168,12 @@ export class BotController
|
||||
* @param info bot generation request info
|
||||
* @returns IBotBase array
|
||||
*/
|
||||
public async generate(sessionId: string, info: IGenerateBotsRequestData): Promise<IBotBase[]>
|
||||
{
|
||||
public async generate(sessionId: string, info: IGenerateBotsRequestData): Promise<IBotBase[]> {
|
||||
const pmcProfile = this.profileHelper.getPmcProfile(sessionId);
|
||||
|
||||
// Use this opportunity to create and cache bots for later retreval
|
||||
const multipleBotTypesRequested = info.conditions.length > 1;
|
||||
if (multipleBotTypesRequested)
|
||||
{
|
||||
if (multipleBotTypesRequested) {
|
||||
return this.generateMultipleBotsAndCache(info, pmcProfile, sessionId);
|
||||
}
|
||||
|
||||
@ -206,26 +191,23 @@ export class BotController
|
||||
request: IGenerateBotsRequestData,
|
||||
pmcProfile: IPmcData,
|
||||
sessionId: string,
|
||||
): Promise<IBotBase[]>
|
||||
{
|
||||
): Promise<IBotBase[]> {
|
||||
const raidSettings = this.applicationContext
|
||||
.getLatestValue(ContextVariableType.RAID_CONFIGURATION)
|
||||
?.getValue<IGetRaidConfigurationRequestData>();
|
||||
|
||||
if (raidSettings === undefined)
|
||||
{
|
||||
if (raidSettings === undefined) {
|
||||
// throw new Error(this.localisationService.getText("bot-unable_to_load_raid_settings_from_appcontext"));
|
||||
}
|
||||
const pmcLevelRangeForMap
|
||||
= this.pmcConfig.locationSpecificPmcLevelOverride[raidSettings?.location.toLowerCase()];
|
||||
const pmcLevelRangeForMap =
|
||||
this.pmcConfig.locationSpecificPmcLevelOverride[raidSettings?.location.toLowerCase()];
|
||||
|
||||
const allPmcsHaveSameNameAsPlayer = this.randomUtil.getChance100(
|
||||
this.pmcConfig.allPMCsHavePlayerNameWithRandomPrefixChance,
|
||||
);
|
||||
|
||||
const conditionPromises: Promise<void>[] = [];
|
||||
for (const condition of request.conditions)
|
||||
{
|
||||
for (const condition of request.conditions) {
|
||||
const botGenerationDetails = this.getBotGenerationDetailsForWave(
|
||||
condition,
|
||||
pmcProfile,
|
||||
@ -260,8 +242,7 @@ export class BotController
|
||||
pmcLevelRangeForMap: MinMax,
|
||||
botCountToGenerate: number,
|
||||
generateAsPmc: boolean,
|
||||
): BotGenerationDetails
|
||||
{
|
||||
): BotGenerationDetails {
|
||||
return {
|
||||
isPmc: generateAsPmc,
|
||||
side: "Savage",
|
||||
@ -283,8 +264,7 @@ export class BotController
|
||||
* @param pmcProfile Profile to get level from
|
||||
* @returns Level as number
|
||||
*/
|
||||
protected getPlayerLevelFromProfile(pmcProfile: IPmcData): number
|
||||
{
|
||||
protected getPlayerLevelFromProfile(pmcProfile: IPmcData): number {
|
||||
return pmcProfile.Info.Level;
|
||||
}
|
||||
|
||||
@ -299,11 +279,9 @@ export class BotController
|
||||
condition: Condition,
|
||||
botGenerationDetails: BotGenerationDetails,
|
||||
sessionId: string,
|
||||
): Promise<void>
|
||||
{
|
||||
): Promise<void> {
|
||||
const isEventBot = condition.Role.toLowerCase().includes("event");
|
||||
if (isEventBot)
|
||||
{
|
||||
if (isEventBot) {
|
||||
// Add eventRole data + reassign role property to be base type
|
||||
botGenerationDetails.eventRole = condition.Role;
|
||||
botGenerationDetails.role = this.seasonalEventService.getBaseRoleForEventBot(
|
||||
@ -313,8 +291,7 @@ export class BotController
|
||||
|
||||
// Custom map waves can have spt roles in them
|
||||
// Is bot type pmcUSEC/pmcBEAR, set is pmc true and set side
|
||||
if (this.botHelper.botRoleIsPmc(condition.Role))
|
||||
{
|
||||
if (this.botHelper.botRoleIsPmc(condition.Role)) {
|
||||
botGenerationDetails.isPmc = true;
|
||||
botGenerationDetails.side = this.botHelper.getPmcSideByRole(condition.Role);
|
||||
}
|
||||
@ -328,20 +305,17 @@ export class BotController
|
||||
// Get number of bots we have in cache
|
||||
const botCacheCount = this.botGenerationCacheService.getCachedBotCount(cacheKey);
|
||||
const botPromises: Promise<void>[] = [];
|
||||
if (botCacheCount > botGenerationDetails.botCountToGenerate)
|
||||
{
|
||||
if (botCacheCount > botGenerationDetails.botCountToGenerate) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We're below desired count, add bots to cache
|
||||
for (let i = 0; i < botGenerationDetails.botCountToGenerate; i++)
|
||||
{
|
||||
for (let i = 0; i < botGenerationDetails.botCountToGenerate; i++) {
|
||||
const detailsClone = this.cloner.clone(botGenerationDetails);
|
||||
botPromises.push(this.generateSingleBotAndStoreInCache(detailsClone, sessionId, cacheKey));
|
||||
}
|
||||
|
||||
return Promise.all(botPromises).then(() =>
|
||||
{
|
||||
return Promise.all(botPromises).then(() => {
|
||||
this.logger.debug(
|
||||
`Generated ${botGenerationDetails.botCountToGenerate} ${botGenerationDetails.role} (${
|
||||
botGenerationDetails.eventRole ?? ""
|
||||
@ -361,8 +335,7 @@ export class BotController
|
||||
botGenerationDetails: BotGenerationDetails,
|
||||
sessionId: string,
|
||||
cacheKey: string,
|
||||
): Promise<void>
|
||||
{
|
||||
): Promise<void> {
|
||||
const botToCache = this.botGenerator.prepareAndGenerateBot(sessionId, botGenerationDetails);
|
||||
this.botGenerationCacheService.storeBots(cacheKey, [botToCache]);
|
||||
|
||||
@ -379,8 +352,7 @@ export class BotController
|
||||
protected async returnSingleBotFromCache(
|
||||
sessionId: string,
|
||||
request: IGenerateBotsRequestData,
|
||||
): Promise<IBotBase[]>
|
||||
{
|
||||
): Promise<IBotBase[]> {
|
||||
const pmcProfile = this.profileHelper.getPmcProfile(sessionId);
|
||||
const requestedBot = request.conditions[0];
|
||||
|
||||
@ -388,12 +360,11 @@ export class BotController
|
||||
.getLatestValue(ContextVariableType.RAID_CONFIGURATION)
|
||||
?.getValue<IGetRaidConfigurationRequestData>();
|
||||
|
||||
if (raidSettings === undefined)
|
||||
{
|
||||
if (raidSettings === undefined) {
|
||||
throw new Error(this.localisationService.getText("bot-unable_to_load_raid_settings_from_appcontext"));
|
||||
}
|
||||
const pmcLevelRangeForMap
|
||||
= this.pmcConfig.locationSpecificPmcLevelOverride[raidSettings.location.toLowerCase()];
|
||||
const pmcLevelRangeForMap =
|
||||
this.pmcConfig.locationSpecificPmcLevelOverride[raidSettings.location.toLowerCase()];
|
||||
|
||||
// Create gen request for when cache is empty
|
||||
const condition: Condition = {
|
||||
@ -412,8 +383,7 @@ export class BotController
|
||||
|
||||
// Event bots need special actions to occur, set data up for them
|
||||
const isEventBot = requestedBot.Role.toLowerCase().includes("event");
|
||||
if (isEventBot)
|
||||
{
|
||||
if (isEventBot) {
|
||||
// Add eventRole data + reassign role property
|
||||
botGenerationDetails.eventRole = requestedBot.Role;
|
||||
botGenerationDetails.role = this.seasonalEventService.getBaseRoleForEventBot(
|
||||
@ -421,20 +391,17 @@ export class BotController
|
||||
);
|
||||
}
|
||||
|
||||
if (this.botHelper.isBotPmc(botGenerationDetails.role))
|
||||
{
|
||||
if (this.botHelper.isBotPmc(botGenerationDetails.role)) {
|
||||
botGenerationDetails.isPmc = true;
|
||||
botGenerationDetails.side = this.botHelper.getPmcSideByRole(requestedBot.Role);
|
||||
}
|
||||
|
||||
// Roll chance to be pmc if type is allowed to be one
|
||||
const botConvertRateMinMax = this.pmcConfig.convertIntoPmcChance[requestedBot.Role.toLowerCase()];
|
||||
if (botConvertRateMinMax)
|
||||
{
|
||||
if (botConvertRateMinMax) {
|
||||
// Should bot become PMC
|
||||
const convertToPmc = this.botHelper.rollChanceToBePmc(requestedBot.Role, botConvertRateMinMax);
|
||||
if (convertToPmc)
|
||||
{
|
||||
if (convertToPmc) {
|
||||
botGenerationDetails.isPmc = true;
|
||||
botGenerationDetails.role = this.botHelper.getRandomizedPmcRole();
|
||||
botGenerationDetails.side = this.botHelper.getPmcSideByRole(botGenerationDetails.role);
|
||||
@ -443,22 +410,16 @@ export class BotController
|
||||
}
|
||||
}
|
||||
// Only convert to boss when not already converted to PMC & Boss Convert is enabled
|
||||
const {
|
||||
bossConvertEnabled,
|
||||
bossConvertMinMax,
|
||||
bossesToConvertToWeights } = this.botConfig.assaultToBossConversion;
|
||||
if (bossConvertEnabled && !botGenerationDetails.isPmc)
|
||||
{
|
||||
const { bossConvertEnabled, bossConvertMinMax, bossesToConvertToWeights } =
|
||||
this.botConfig.assaultToBossConversion;
|
||||
if (bossConvertEnabled && !botGenerationDetails.isPmc) {
|
||||
const bossConvertPercent = bossConvertMinMax[requestedBot.Role.toLowerCase()];
|
||||
if (bossConvertPercent)
|
||||
{
|
||||
if (bossConvertPercent) {
|
||||
// Roll a percentage check if we should convert scav to boss
|
||||
if (this.randomUtil.getChance100(
|
||||
this.randomUtil.getInt(bossConvertPercent.min, bossConvertPercent.max)))
|
||||
{
|
||||
this.updateBotGenerationDetailsToRandomBoss(
|
||||
botGenerationDetails,
|
||||
bossesToConvertToWeights);
|
||||
if (
|
||||
this.randomUtil.getChance100(this.randomUtil.getInt(bossConvertPercent.min, bossConvertPercent.max))
|
||||
) {
|
||||
this.updateBotGenerationDetailsToRandomBoss(botGenerationDetails, bossesToConvertToWeights);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -466,20 +427,18 @@ export class BotController
|
||||
// Create a compound key to store bots in cache against
|
||||
const cacheKey = this.botGenerationCacheService.createCacheKey(
|
||||
botGenerationDetails.eventRole ?? botGenerationDetails.role,
|
||||
botGenerationDetails.botDifficulty);
|
||||
botGenerationDetails.botDifficulty,
|
||||
);
|
||||
|
||||
// Check cache for bot using above key
|
||||
if (!this.botGenerationCacheService.cacheHasBotOfRole(cacheKey))
|
||||
{
|
||||
if (!this.botGenerationCacheService.cacheHasBotOfRole(cacheKey)) {
|
||||
const botPromises: Promise<void>[] = [];
|
||||
// No bot in cache, generate new and return one
|
||||
for (let i = 0; i < botGenerationDetails.botCountToGenerate; i++)
|
||||
{
|
||||
for (let i = 0; i < botGenerationDetails.botCountToGenerate; i++) {
|
||||
botPromises.push(this.generateSingleBotAndStoreInCache(botGenerationDetails, sessionId, cacheKey));
|
||||
}
|
||||
|
||||
await Promise.all(botPromises).then(() =>
|
||||
{
|
||||
await Promise.all(botPromises).then(() => {
|
||||
this.logger.debug(
|
||||
`Generated ${botGenerationDetails.botCountToGenerate} ${botGenerationDetails.role} (${
|
||||
botGenerationDetails.eventRole ?? ""
|
||||
@ -496,11 +455,10 @@ export class BotController
|
||||
|
||||
protected updateBotGenerationDetailsToRandomBoss(
|
||||
botGenerationDetails: BotGenerationDetails,
|
||||
possibleBossTypeWeights: Record<string, number>): void
|
||||
{
|
||||
possibleBossTypeWeights: Record<string, number>,
|
||||
): void {
|
||||
// Seems Actual bosses have the same Brain issues like PMC gaining Boss Brains We cant use all bosses
|
||||
botGenerationDetails.role
|
||||
= this.weightedRandomHelper.getWeightedValue(possibleBossTypeWeights);
|
||||
botGenerationDetails.role = this.weightedRandomHelper.getWeightedValue(possibleBossTypeWeights);
|
||||
|
||||
// Bosses are only ever 'normal'
|
||||
botGenerationDetails.botDifficulty = "normal";
|
||||
@ -512,16 +470,13 @@ export class BotController
|
||||
* @param requestedDifficulty
|
||||
* @returns
|
||||
*/
|
||||
public getPMCDifficulty(requestedDifficulty: string): string
|
||||
{
|
||||
public getPMCDifficulty(requestedDifficulty: string): string {
|
||||
// Maybe return a random difficulty...
|
||||
if (this.pmcConfig.difficulty.toLowerCase() === "asonline")
|
||||
{
|
||||
if (this.pmcConfig.difficulty.toLowerCase() === "asonline") {
|
||||
return requestedDifficulty;
|
||||
}
|
||||
|
||||
if (this.pmcConfig.difficulty.toLowerCase() === "random")
|
||||
{
|
||||
if (this.pmcConfig.difficulty.toLowerCase() === "random") {
|
||||
return this.botDifficultyHelper.chooseRandomDifficulty();
|
||||
}
|
||||
|
||||
@ -534,24 +489,18 @@ export class BotController
|
||||
* @param location The map location cap was requested for
|
||||
* @returns cap number
|
||||
*/
|
||||
public getBotCap(location: string): number
|
||||
{
|
||||
public getBotCap(location: string): number {
|
||||
const botCap = this.botConfig.maxBotCap[location.toLowerCase()];
|
||||
if (location === "default")
|
||||
{
|
||||
if (location === "default") {
|
||||
this.logger.warning(
|
||||
this.localisationService.getText(
|
||||
"bot-no_bot_cap_found_for_location",
|
||||
location.toLowerCase(),
|
||||
),
|
||||
this.localisationService.getText("bot-no_bot_cap_found_for_location", location.toLowerCase()),
|
||||
);
|
||||
}
|
||||
|
||||
return botCap;
|
||||
}
|
||||
|
||||
public getAiBotBrainTypes(): any
|
||||
{
|
||||
public getAiBotBrainTypes(): any {
|
||||
return {
|
||||
pmc: this.pmcConfig.pmcType,
|
||||
assault: this.botConfig.assaultBrainType,
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { inject, injectable } from "tsyringe";
|
||||
import { ItemHelper } from "@spt/helpers/ItemHelper";
|
||||
import { ProfileHelper } from "@spt/helpers/ProfileHelper";
|
||||
import { ISetMagazineRequest } from "@spt/models/eft/builds/ISetMagazineRequest";
|
||||
@ -11,12 +10,12 @@ import { EventOutputHolder } from "@spt/routers/EventOutputHolder";
|
||||
import { SaveServer } from "@spt/servers/SaveServer";
|
||||
import { DatabaseService } from "@spt/services/DatabaseService";
|
||||
import { LocalisationService } from "@spt/services/LocalisationService";
|
||||
import { ICloner } from "@spt/utils/cloners/ICloner";
|
||||
import { HashUtil } from "@spt/utils/HashUtil";
|
||||
import { ICloner } from "@spt/utils/cloners/ICloner";
|
||||
import { inject, injectable } from "tsyringe";
|
||||
|
||||
@injectable()
|
||||
export class BuildController
|
||||
{
|
||||
export class BuildController {
|
||||
constructor(
|
||||
@inject("PrimaryLogger") protected logger: ILogger,
|
||||
@inject("HashUtil") protected hashUtil: HashUtil,
|
||||
@ -27,16 +26,13 @@ export class BuildController
|
||||
@inject("ItemHelper") protected itemHelper: ItemHelper,
|
||||
@inject("SaveServer") protected saveServer: SaveServer,
|
||||
@inject("PrimaryCloner") protected cloner: ICloner,
|
||||
)
|
||||
{}
|
||||
) {}
|
||||
|
||||
/** Handle client/handbook/builds/my/list */
|
||||
public getUserBuilds(sessionID: string): IUserBuilds
|
||||
{
|
||||
public getUserBuilds(sessionID: string): IUserBuilds {
|
||||
const secureContainerSlotId = "SecuredContainer";
|
||||
const profile = this.saveServer.getProfile(sessionID);
|
||||
if (!profile.userbuilds)
|
||||
{
|
||||
if (!profile.userbuilds) {
|
||||
profile.userbuilds = { equipmentBuilds: [], weaponBuilds: [], magazineBuilds: [] };
|
||||
}
|
||||
|
||||
@ -50,15 +46,12 @@ export class BuildController
|
||||
const firstDefaultItemsSecureContainer = defaultEquipmentPresetsClone[0]?.Items?.find(
|
||||
(x) => x.slotId === secureContainerSlotId,
|
||||
);
|
||||
if (playerSecureContainer && playerSecureContainer?._tpl !== firstDefaultItemsSecureContainer?._tpl)
|
||||
{
|
||||
if (playerSecureContainer && playerSecureContainer?._tpl !== firstDefaultItemsSecureContainer?._tpl) {
|
||||
// Default equipment presets' secure container tpl doesn't match players secure container tpl
|
||||
for (const defaultPreset of defaultEquipmentPresetsClone)
|
||||
{
|
||||
for (const defaultPreset of defaultEquipmentPresetsClone) {
|
||||
// Find presets secure container
|
||||
const secureContainer = defaultPreset.Items.find((item) => item.slotId === secureContainerSlotId);
|
||||
if (secureContainer)
|
||||
{
|
||||
if (secureContainer) {
|
||||
secureContainer._tpl = playerSecureContainer._tpl;
|
||||
}
|
||||
}
|
||||
@ -72,8 +65,7 @@ export class BuildController
|
||||
}
|
||||
|
||||
/** Handle client/builds/weapon/save */
|
||||
public saveWeaponBuild(sessionId: string, body: IPresetBuildActionRequestData): void
|
||||
{
|
||||
public saveWeaponBuild(sessionId: string, body: IPresetBuildActionRequestData): void {
|
||||
const pmcData = this.profileHelper.getPmcProfile(sessionId);
|
||||
|
||||
// Replace duplicate Id's. The first item is the base item.
|
||||
@ -86,28 +78,24 @@ export class BuildController
|
||||
|
||||
const savedWeaponBuilds = this.saveServer.getProfile(sessionId).userbuilds.weaponBuilds;
|
||||
const existingBuild = savedWeaponBuilds.find((x) => x.Id === body.Id);
|
||||
if (existingBuild)
|
||||
{
|
||||
if (existingBuild) {
|
||||
// exists, replace
|
||||
this.saveServer
|
||||
.getProfile(sessionId)
|
||||
.userbuilds.weaponBuilds.splice(savedWeaponBuilds.indexOf(existingBuild), 1, newBuild);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
// Add fresh
|
||||
this.saveServer.getProfile(sessionId).userbuilds.weaponBuilds.push(newBuild);
|
||||
}
|
||||
}
|
||||
|
||||
/** Handle client/builds/equipment/save event */
|
||||
public saveEquipmentBuild(sessionID: string, request: IPresetBuildActionRequestData): void
|
||||
{
|
||||
public saveEquipmentBuild(sessionID: string, request: IPresetBuildActionRequestData): void {
|
||||
const buildType = "equipmentBuilds";
|
||||
const pmcData = this.profileHelper.getPmcProfile(sessionID);
|
||||
|
||||
const existingSavedEquipmentBuilds: IEquipmentBuild[]
|
||||
= this.saveServer.getProfile(sessionID).userbuilds[buildType];
|
||||
const existingSavedEquipmentBuilds: IEquipmentBuild[] =
|
||||
this.saveServer.getProfile(sessionID).userbuilds[buildType];
|
||||
|
||||
// Replace duplicate ID's. The first item is the base item.
|
||||
// Root ID and the base item ID need to match.
|
||||
@ -124,28 +112,23 @@ export class BuildController
|
||||
const existingBuild = existingSavedEquipmentBuilds.find(
|
||||
(build) => build.Name === request.Name || build.Id === request.Id,
|
||||
);
|
||||
if (existingBuild)
|
||||
{
|
||||
if (existingBuild) {
|
||||
// Already exists, replace
|
||||
this.saveServer
|
||||
.getProfile(sessionID)
|
||||
.userbuilds[buildType].splice(existingSavedEquipmentBuilds.indexOf(existingBuild), 1, newBuild);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
// Fresh, add new
|
||||
this.saveServer.getProfile(sessionID).userbuilds[buildType].push(newBuild);
|
||||
}
|
||||
}
|
||||
|
||||
/** Handle client/builds/delete */
|
||||
public removeBuild(sessionID: string, request: IRemoveBuildRequestData): void
|
||||
{
|
||||
public removeBuild(sessionID: string, request: IRemoveBuildRequestData): void {
|
||||
this.removePlayerBuild(request.id, sessionID);
|
||||
}
|
||||
|
||||
protected removePlayerBuild(idToRemove: string, sessionID: string): void
|
||||
{
|
||||
protected removePlayerBuild(idToRemove: string, sessionID: string): void {
|
||||
const profile = this.saveServer.getProfile(sessionID);
|
||||
const weaponBuilds = profile.userbuilds.weaponBuilds;
|
||||
const equipmentBuilds = profile.userbuilds.equipmentBuilds;
|
||||
@ -153,8 +136,7 @@ export class BuildController
|
||||
|
||||
// Check for id in weapon array first
|
||||
const matchingWeaponBuild = weaponBuilds.find((weaponBuild) => weaponBuild.Id === idToRemove);
|
||||
if (matchingWeaponBuild)
|
||||
{
|
||||
if (matchingWeaponBuild) {
|
||||
weaponBuilds.splice(weaponBuilds.indexOf(matchingWeaponBuild), 1);
|
||||
|
||||
return;
|
||||
@ -162,8 +144,7 @@ export class BuildController
|
||||
|
||||
// Id not found in weapons, try equipment
|
||||
const matchingEquipmentBuild = equipmentBuilds.find((equipmentBuild) => equipmentBuild.Id === idToRemove);
|
||||
if (matchingEquipmentBuild)
|
||||
{
|
||||
if (matchingEquipmentBuild) {
|
||||
equipmentBuilds.splice(equipmentBuilds.indexOf(matchingEquipmentBuild), 1);
|
||||
|
||||
return;
|
||||
@ -171,8 +152,7 @@ export class BuildController
|
||||
|
||||
// Id not found in weapons/equipment, try mags
|
||||
const matchingMagazineBuild = magazineBuilds.find((magBuild) => magBuild.Id === idToRemove);
|
||||
if (matchingMagazineBuild)
|
||||
{
|
||||
if (matchingMagazineBuild) {
|
||||
magazineBuilds.splice(magazineBuilds.indexOf(matchingMagazineBuild), 1);
|
||||
|
||||
return;
|
||||
@ -185,8 +165,7 @@ export class BuildController
|
||||
/**
|
||||
* Handle client/builds/magazine/save
|
||||
*/
|
||||
public createMagazineTemplate(sessionId: string, request: ISetMagazineRequest): void
|
||||
{
|
||||
public createMagazineTemplate(sessionId: string, request: ISetMagazineRequest): void {
|
||||
const result: IMagazineBuild = {
|
||||
Id: request.Id,
|
||||
Name: request.Name,
|
||||
@ -201,12 +180,9 @@ export class BuildController
|
||||
profile.userbuilds.magazineBuilds ||= [];
|
||||
|
||||
const existingArrayId = profile.userbuilds.magazineBuilds.findIndex((item) => item.Name === request.Name);
|
||||
if (existingArrayId === -1)
|
||||
{
|
||||
if (existingArrayId === -1) {
|
||||
profile.userbuilds.magazineBuilds.push(result);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
profile.userbuilds.magazineBuilds.splice(existingArrayId, 1, result);
|
||||
}
|
||||
}
|
||||
|
@ -1,21 +1,18 @@
|
||||
import { inject, injectable } from "tsyringe";
|
||||
import { IClientLogRequest } from "@spt/models/spt/logging/IClientLogRequest";
|
||||
import { LogBackgroundColor } from "@spt/models/spt/logging/LogBackgroundColor";
|
||||
import { LogLevel } from "@spt/models/spt/logging/LogLevel";
|
||||
import { LogTextColor } from "@spt/models/spt/logging/LogTextColor";
|
||||
import { ILogger } from "@spt/models/spt/utils/ILogger";
|
||||
import { inject, injectable } from "tsyringe";
|
||||
|
||||
@injectable()
|
||||
export class ClientLogController
|
||||
{
|
||||
constructor(@inject("PrimaryLogger") protected logger: ILogger)
|
||||
{}
|
||||
export class ClientLogController {
|
||||
constructor(@inject("PrimaryLogger") protected logger: ILogger) {}
|
||||
|
||||
/**
|
||||
* Handle /singleplayer/log
|
||||
*/
|
||||
public clientLog(logRequest: IClientLogRequest): void
|
||||
{
|
||||
public clientLog(logRequest: IClientLogRequest): void {
|
||||
const message = `[${logRequest.Source}] ${logRequest.Message}`;
|
||||
const color = logRequest.Color ?? LogTextColor.WHITE;
|
||||
const backgroundColor = logRequest.BackgroundColor ?? LogBackgroundColor.DEFAULT;
|
||||
@ -23,13 +20,11 @@ export class ClientLogController
|
||||
// Allow supporting either string or enum levels
|
||||
// Required due to the C# modules serializing enums as their name
|
||||
let level = logRequest.Level;
|
||||
if (typeof level === "string")
|
||||
{
|
||||
if (typeof level === "string") {
|
||||
level = LogLevel[level.toUpperCase() as keyof typeof LogLevel];
|
||||
}
|
||||
|
||||
switch (level)
|
||||
{
|
||||
switch (level) {
|
||||
case LogLevel.ERROR:
|
||||
this.logger.error(message);
|
||||
break;
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { inject, injectable } from "tsyringe";
|
||||
import { ProfileHelper } from "@spt/helpers/ProfileHelper";
|
||||
import { IPmcData } from "@spt/models/eft/common/IPmcData";
|
||||
import { ISuit } from "@spt/models/eft/common/tables/ITrader";
|
||||
@ -10,10 +9,10 @@ import { EventOutputHolder } from "@spt/routers/EventOutputHolder";
|
||||
import { SaveServer } from "@spt/servers/SaveServer";
|
||||
import { DatabaseService } from "@spt/services/DatabaseService";
|
||||
import { LocalisationService } from "@spt/services/LocalisationService";
|
||||
import { inject, injectable } from "tsyringe";
|
||||
|
||||
@injectable()
|
||||
export class CustomizationController
|
||||
{
|
||||
export class CustomizationController {
|
||||
protected readonly clothingIds = {
|
||||
lowerParentId: "5cd944d01388ce000a659df9",
|
||||
upperParentId: "5cd944ca1388ce03a44dc2a4",
|
||||
@ -26,8 +25,7 @@ export class CustomizationController
|
||||
@inject("SaveServer") protected saveServer: SaveServer,
|
||||
@inject("LocalisationService") protected localisationService: LocalisationService,
|
||||
@inject("ProfileHelper") protected profileHelper: ProfileHelper,
|
||||
)
|
||||
{}
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Get purchasable clothing items from trader that match players side (usec/bear)
|
||||
@ -35,8 +33,7 @@ export class CustomizationController
|
||||
* @param sessionID Session id
|
||||
* @returns ISuit array
|
||||
*/
|
||||
public getTraderSuits(traderID: string, sessionID: string): ISuit[]
|
||||
{
|
||||
public getTraderSuits(traderID: string, sessionID: string): ISuit[] {
|
||||
const pmcData = this.profileHelper.getPmcProfile(sessionID);
|
||||
const clothing = this.databaseService.getCustomization();
|
||||
const suits = this.databaseService.getTrader(traderID).suits;
|
||||
@ -45,7 +42,9 @@ export class CustomizationController
|
||||
const matchingSuits = suits?.filter((suit) => suit.suiteId in clothing);
|
||||
|
||||
// Return all suits that have a side array containing the players side (usec/bear)
|
||||
const matchedSuits = matchingSuits?.filter((matchingSuit) => clothing[matchingSuit.suiteId]._props.Side.includes(pmcData.Info.Side));
|
||||
const matchedSuits = matchingSuits?.filter((matchingSuit) =>
|
||||
clothing[matchingSuit.suiteId]._props.Side.includes(pmcData.Info.Side),
|
||||
);
|
||||
if (matchingSuits === undefined)
|
||||
throw new Error(this.localisationService.getText("customisation-unable_to_get_trader_suits", traderID));
|
||||
|
||||
@ -60,22 +59,18 @@ export class CustomizationController
|
||||
pmcData: IPmcData,
|
||||
wearClothingRequest: IWearClothingRequestData,
|
||||
sessionID: string,
|
||||
): IItemEventRouterResponse
|
||||
{
|
||||
for (const suitId of wearClothingRequest.suites)
|
||||
{
|
||||
): IItemEventRouterResponse {
|
||||
for (const suitId of wearClothingRequest.suites) {
|
||||
// Find desired clothing item in db
|
||||
const dbSuit = this.databaseService.getCustomization()[suitId];
|
||||
|
||||
// Legs
|
||||
if (dbSuit._parent === this.clothingIds.lowerParentId)
|
||||
{
|
||||
if (dbSuit._parent === this.clothingIds.lowerParentId) {
|
||||
pmcData.Customization.Feet = dbSuit._props.Feet;
|
||||
}
|
||||
|
||||
// Torso
|
||||
if (dbSuit._parent === this.clothingIds.upperParentId)
|
||||
{
|
||||
if (dbSuit._parent === this.clothingIds.upperParentId) {
|
||||
pmcData.Customization.Body = dbSuit._props.Body;
|
||||
pmcData.Customization.Hands = dbSuit._props.Hands;
|
||||
}
|
||||
@ -96,13 +91,11 @@ export class CustomizationController
|
||||
pmcData: IPmcData,
|
||||
buyClothingRequest: IBuyClothingRequestData,
|
||||
sessionId: string,
|
||||
): IItemEventRouterResponse
|
||||
{
|
||||
): IItemEventRouterResponse {
|
||||
const output = this.eventOutputHolder.getOutput(sessionId);
|
||||
|
||||
const traderOffer = this.getTraderClothingOffer(sessionId, buyClothingRequest.offer);
|
||||
if (!traderOffer)
|
||||
{
|
||||
if (!traderOffer) {
|
||||
this.logger.error(
|
||||
this.localisationService.getText("customisation-unable_to_find_suit_by_id", buyClothingRequest.offer),
|
||||
);
|
||||
@ -111,8 +104,7 @@ export class CustomizationController
|
||||
}
|
||||
|
||||
const suitId = traderOffer.suiteId;
|
||||
if (this.outfitAlreadyPurchased(suitId, sessionId))
|
||||
{
|
||||
if (this.outfitAlreadyPurchased(suitId, sessionId)) {
|
||||
const suitDetails = this.databaseService.getCustomization()[suitId];
|
||||
this.logger.error(
|
||||
this.localisationService.getText("customisation-item_already_purchased", {
|
||||
@ -133,11 +125,9 @@ export class CustomizationController
|
||||
return output;
|
||||
}
|
||||
|
||||
protected getTraderClothingOffer(sessionId: string, offerId: string): ISuit
|
||||
{
|
||||
protected getTraderClothingOffer(sessionId: string, offerId: string): ISuit {
|
||||
const foundSuit = this.getAllTraderSuits(sessionId).find((x) => x._id === offerId);
|
||||
if (foundSuit === undefined)
|
||||
{
|
||||
if (foundSuit === undefined) {
|
||||
throw new Error(this.localisationService.getText("customisation-unable_to_find_suit_with_id", offerId));
|
||||
}
|
||||
|
||||
@ -150,8 +140,7 @@ export class CustomizationController
|
||||
* @param sessionID Session id of profile to check for clothing in
|
||||
* @returns true if already purchased
|
||||
*/
|
||||
protected outfitAlreadyPurchased(suitId: string, sessionID: string): boolean
|
||||
{
|
||||
protected outfitAlreadyPurchased(suitId: string, sessionID: string): boolean {
|
||||
return this.saveServer.getProfile(sessionID).suits.includes(suitId);
|
||||
}
|
||||
|
||||
@ -167,10 +156,8 @@ export class CustomizationController
|
||||
pmcData: IPmcData,
|
||||
clothingItems: ClothingItem[],
|
||||
output: IItemEventRouterResponse,
|
||||
): void
|
||||
{
|
||||
for (const sellItem of clothingItems)
|
||||
{
|
||||
): void {
|
||||
for (const sellItem of clothingItems) {
|
||||
this.payForClothingItem(sessionId, pmcData, sellItem, output);
|
||||
}
|
||||
}
|
||||
@ -187,11 +174,9 @@ export class CustomizationController
|
||||
pmcData: IPmcData,
|
||||
clothingItem: ClothingItem,
|
||||
output: IItemEventRouterResponse,
|
||||
): void
|
||||
{
|
||||
): void {
|
||||
const relatedItem = pmcData.Inventory.items.find((x) => x._id === clothingItem.id);
|
||||
if (!relatedItem)
|
||||
{
|
||||
if (!relatedItem) {
|
||||
this.logger.error(
|
||||
this.localisationService.getText(
|
||||
"customisation-unable_to_find_clothing_item_in_inventory",
|
||||
@ -202,19 +187,18 @@ export class CustomizationController
|
||||
return;
|
||||
}
|
||||
|
||||
if (clothingItem.del === true)
|
||||
{
|
||||
if (clothingItem.del === true) {
|
||||
output.profileChanges[sessionId].items.del.push(relatedItem);
|
||||
pmcData.Inventory.items.splice(pmcData.Inventory.items.indexOf(relatedItem), 1);
|
||||
}
|
||||
|
||||
if (!relatedItem.upd || !relatedItem.upd.StackObjectsCount)
|
||||
{
|
||||
throw new Error(this.localisationService.getText("customisation-suit_lacks_upd_or_stack_property", relatedItem._tpl));
|
||||
if (!relatedItem.upd || !relatedItem.upd.StackObjectsCount) {
|
||||
throw new Error(
|
||||
this.localisationService.getText("customisation-suit_lacks_upd_or_stack_property", relatedItem._tpl),
|
||||
);
|
||||
}
|
||||
|
||||
if (relatedItem.upd.StackObjectsCount > clothingItem.count)
|
||||
{
|
||||
if (relatedItem.upd.StackObjectsCount > clothingItem.count) {
|
||||
relatedItem.upd.StackObjectsCount -= clothingItem.count;
|
||||
output.profileChanges[sessionId].items.change.push({
|
||||
_id: relatedItem._id,
|
||||
@ -227,15 +211,12 @@ export class CustomizationController
|
||||
}
|
||||
}
|
||||
|
||||
protected getAllTraderSuits(sessionID: string): ISuit[]
|
||||
{
|
||||
protected getAllTraderSuits(sessionID: string): ISuit[] {
|
||||
const traders = this.databaseService.getTraders();
|
||||
let result: ISuit[] = [];
|
||||
|
||||
for (const traderID in traders)
|
||||
{
|
||||
if (traders[traderID].base.customization_seller === true)
|
||||
{
|
||||
for (const traderID in traders) {
|
||||
if (traders[traderID].base.customization_seller === true) {
|
||||
result = [...result, ...this.getTraderSuits(traderID, sessionID)];
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { inject, injectAll, injectable } from "tsyringe";
|
||||
import { IDialogueChatBot } from "@spt/helpers/Dialogue/IDialogueChatBot";
|
||||
import { DialogueHelper } from "@spt/helpers/DialogueHelper";
|
||||
import { IFriendRequestData } from "@spt/models/eft/dialog/IFriendRequestData";
|
||||
@ -18,10 +17,10 @@ import { SaveServer } from "@spt/servers/SaveServer";
|
||||
import { LocalisationService } from "@spt/services/LocalisationService";
|
||||
import { MailSendService } from "@spt/services/MailSendService";
|
||||
import { TimeUtil } from "@spt/utils/TimeUtil";
|
||||
import { inject, injectAll, injectable } from "tsyringe";
|
||||
|
||||
@injectable()
|
||||
export class DialogueController
|
||||
{
|
||||
export class DialogueController {
|
||||
constructor(
|
||||
@inject("PrimaryLogger") protected logger: ILogger,
|
||||
@inject("SaveServer") protected saveServer: SaveServer,
|
||||
@ -31,37 +30,36 @@ export class DialogueController
|
||||
@inject("LocalisationService") protected localisationService: LocalisationService,
|
||||
@inject("ConfigServer") protected configServer: ConfigServer,
|
||||
@injectAll("DialogueChatBot") protected dialogueChatBots: IDialogueChatBot[],
|
||||
)
|
||||
{
|
||||
) {
|
||||
const coreConfigs = this.configServer.getConfig<ICoreConfig>(ConfigTypes.CORE);
|
||||
// if give command is disabled or commando commands are disabled
|
||||
if (!coreConfigs.features?.chatbotFeatures?.commandoEnabled)
|
||||
{
|
||||
const sptCommando = this.dialogueChatBots.find((c) => c.getChatBot()._id.toLocaleLowerCase() === "sptcommando")!;
|
||||
if (!coreConfigs.features?.chatbotFeatures?.commandoEnabled) {
|
||||
const sptCommando = this.dialogueChatBots.find(
|
||||
(c) => c.getChatBot()._id.toLocaleLowerCase() === "sptcommando",
|
||||
)!;
|
||||
this.dialogueChatBots.splice(this.dialogueChatBots.indexOf(sptCommando), 1);
|
||||
}
|
||||
if (!coreConfigs.features?.chatbotFeatures?.sptFriendEnabled)
|
||||
{
|
||||
const sptFriend = this.dialogueChatBots.find((c) => c.getChatBot()._id.toLocaleLowerCase() === "sptFriend")!;
|
||||
if (!coreConfigs.features?.chatbotFeatures?.sptFriendEnabled) {
|
||||
const sptFriend = this.dialogueChatBots.find(
|
||||
(c) => c.getChatBot()._id.toLocaleLowerCase() === "sptFriend",
|
||||
)!;
|
||||
this.dialogueChatBots.splice(this.dialogueChatBots.indexOf(sptFriend), 1);
|
||||
}
|
||||
}
|
||||
|
||||
public registerChatBot(chatBot: IDialogueChatBot): void
|
||||
{
|
||||
if (this.dialogueChatBots.some((cb) => cb.getChatBot()._id === chatBot.getChatBot()._id))
|
||||
{
|
||||
throw new Error(this.localisationService.getText("dialog-chatbot_id_already_exists", chatBot.getChatBot()._id));
|
||||
public registerChatBot(chatBot: IDialogueChatBot): void {
|
||||
if (this.dialogueChatBots.some((cb) => cb.getChatBot()._id === chatBot.getChatBot()._id)) {
|
||||
throw new Error(
|
||||
this.localisationService.getText("dialog-chatbot_id_already_exists", chatBot.getChatBot()._id),
|
||||
);
|
||||
}
|
||||
this.dialogueChatBots.push(chatBot);
|
||||
}
|
||||
|
||||
/** Handle onUpdate spt event */
|
||||
public update(): void
|
||||
{
|
||||
public update(): void {
|
||||
const profiles = this.saveServer.getProfiles();
|
||||
for (const sessionID in profiles)
|
||||
{
|
||||
for (const sessionID in profiles) {
|
||||
this.removeExpiredItemsFromMessages(sessionID);
|
||||
}
|
||||
}
|
||||
@ -70,8 +68,7 @@ export class DialogueController
|
||||
* Handle client/friend/list
|
||||
* @returns IGetFriendListDataResponse
|
||||
*/
|
||||
public getFriendList(sessionID: string): IGetFriendListDataResponse
|
||||
{
|
||||
public getFriendList(sessionID: string): IGetFriendListDataResponse {
|
||||
// Force a fake friend called SPT into friend list
|
||||
return { Friends: this.dialogueChatBots.map((v) => v.getChatBot()), Ignore: [], InIgnoreList: [] };
|
||||
}
|
||||
@ -83,11 +80,9 @@ export class DialogueController
|
||||
* @param sessionID Session Id
|
||||
* @returns array of dialogs
|
||||
*/
|
||||
public generateDialogueList(sessionID: string): DialogueInfo[]
|
||||
{
|
||||
public generateDialogueList(sessionID: string): DialogueInfo[] {
|
||||
const data: DialogueInfo[] = [];
|
||||
for (const dialogueId in this.dialogueHelper.getDialogsForProfile(sessionID))
|
||||
{
|
||||
for (const dialogueId in this.dialogueHelper.getDialogsForProfile(sessionID)) {
|
||||
data.push(this.getDialogueInfo(dialogueId, sessionID));
|
||||
}
|
||||
|
||||
@ -100,8 +95,7 @@ export class DialogueController
|
||||
* @param sessionID Session Id
|
||||
* @returns DialogueInfo
|
||||
*/
|
||||
public getDialogueInfo(dialogueID: string, sessionID: string): DialogueInfo
|
||||
{
|
||||
public getDialogueInfo(dialogueID: string, sessionID: string): DialogueInfo {
|
||||
const dialogs = this.dialogueHelper.getDialogsForProfile(sessionID);
|
||||
const dialogue = dialogs[dialogueID];
|
||||
|
||||
@ -125,18 +119,19 @@ export class DialogueController
|
||||
* @param sessionID Player id
|
||||
* @returns IUserDialogInfo array
|
||||
*/
|
||||
public getDialogueUsers(dialog: Dialogue, messageType: MessageType, sessionID: string): IUserDialogInfo[] | undefined
|
||||
{
|
||||
public getDialogueUsers(
|
||||
dialog: Dialogue,
|
||||
messageType: MessageType,
|
||||
sessionID: string,
|
||||
): IUserDialogInfo[] | undefined {
|
||||
const profile = this.saveServer.getProfile(sessionID);
|
||||
|
||||
// User to user messages are special in that they need the player to exist in them, add if they don't
|
||||
if (
|
||||
messageType === MessageType.USER_MESSAGE
|
||||
&& !dialog.Users?.some((userDialog) => userDialog._id === profile.characters.pmc.sessionId)
|
||||
)
|
||||
{
|
||||
if (!dialog.Users)
|
||||
{
|
||||
messageType === MessageType.USER_MESSAGE &&
|
||||
!dialog.Users?.some((userDialog) => userDialog._id === profile.characters.pmc.sessionId)
|
||||
) {
|
||||
if (!dialog.Users) {
|
||||
dialog.Users = [];
|
||||
}
|
||||
|
||||
@ -168,8 +163,7 @@ export class DialogueController
|
||||
public generateDialogueView(
|
||||
request: IGetMailDialogViewRequestData,
|
||||
sessionId: string,
|
||||
): IGetMailDialogViewResponseData
|
||||
{
|
||||
): IGetMailDialogViewResponseData {
|
||||
const dialogueId = request.dialogId;
|
||||
const fullProfile = this.saveServer.getProfile(sessionId);
|
||||
const dialogue = this.getDialogByIdFromProfile(fullProfile, request);
|
||||
@ -193,10 +187,8 @@ export class DialogueController
|
||||
* @param request get dialog request (params used when dialog doesnt exist in profile)
|
||||
* @returns Dialogue
|
||||
*/
|
||||
protected getDialogByIdFromProfile(profile: ISptProfile, request: IGetMailDialogViewRequestData): Dialogue
|
||||
{
|
||||
if (!profile.dialogues[request.dialogId])
|
||||
{
|
||||
protected getDialogByIdFromProfile(profile: ISptProfile, request: IGetMailDialogViewRequestData): Dialogue {
|
||||
if (!profile.dialogues[request.dialogId]) {
|
||||
profile.dialogues[request.dialogId] = {
|
||||
_id: request.dialogId,
|
||||
attachmentsNew: 0,
|
||||
@ -206,14 +198,11 @@ export class DialogueController
|
||||
type: request.type,
|
||||
};
|
||||
|
||||
if (request.type === MessageType.USER_MESSAGE)
|
||||
{
|
||||
if (request.type === MessageType.USER_MESSAGE) {
|
||||
profile.dialogues[request.dialogId].Users = [];
|
||||
const chatBot = this.dialogueChatBots.find((cb) => cb.getChatBot()._id === request.dialogId);
|
||||
if (chatBot)
|
||||
{
|
||||
if (!profile.dialogues[request.dialogId].Users)
|
||||
{
|
||||
if (chatBot) {
|
||||
if (!profile.dialogues[request.dialogId].Users) {
|
||||
profile.dialogues[request.dialogId].Users = [];
|
||||
}
|
||||
profile.dialogues[request.dialogId].Users!.push(chatBot.getChatBot());
|
||||
@ -230,15 +219,12 @@ export class DialogueController
|
||||
* @param dialogUsers The participants of the mail
|
||||
* @returns IUserDialogInfo array
|
||||
*/
|
||||
protected getProfilesForMail(fullProfile: ISptProfile, dialogUsers?: IUserDialogInfo[]): IUserDialogInfo[]
|
||||
{
|
||||
protected getProfilesForMail(fullProfile: ISptProfile, dialogUsers?: IUserDialogInfo[]): IUserDialogInfo[] {
|
||||
const result: IUserDialogInfo[] = [];
|
||||
if (dialogUsers)
|
||||
{
|
||||
if (dialogUsers) {
|
||||
result.push(...dialogUsers);
|
||||
|
||||
if (!result.some((userDialog) => userDialog._id === fullProfile.info.id))
|
||||
{
|
||||
if (!result.some((userDialog) => userDialog._id === fullProfile.info.id)) {
|
||||
// Player doesnt exist, add them in before returning
|
||||
const pmcProfile = fullProfile.characters.pmc;
|
||||
result.push({
|
||||
@ -264,14 +250,11 @@ export class DialogueController
|
||||
* @param dialogueID Dialog id
|
||||
* @returns Count of messages with attachments
|
||||
*/
|
||||
protected getUnreadMessagesWithAttachmentsCount(sessionID: string, dialogueID: string): number
|
||||
{
|
||||
protected getUnreadMessagesWithAttachmentsCount(sessionID: string, dialogueID: string): number {
|
||||
let newAttachmentCount = 0;
|
||||
const activeMessages = this.getActiveMessagesFromDialog(sessionID, dialogueID);
|
||||
for (const message of activeMessages)
|
||||
{
|
||||
if (message.hasRewards && !message.rewardCollected)
|
||||
{
|
||||
for (const message of activeMessages) {
|
||||
if (message.hasRewards && !message.rewardCollected) {
|
||||
newAttachmentCount++;
|
||||
}
|
||||
}
|
||||
@ -284,8 +267,7 @@ export class DialogueController
|
||||
* @param messages Messages to check
|
||||
* @returns true if uncollected rewards found
|
||||
*/
|
||||
protected messagesHaveUncollectedRewards(messages: Message[]): boolean
|
||||
{
|
||||
protected messagesHaveUncollectedRewards(messages: Message[]): boolean {
|
||||
return messages.some((message) => (message.items?.data?.length ?? 0) > 0);
|
||||
}
|
||||
|
||||
@ -295,13 +277,16 @@ export class DialogueController
|
||||
* @param dialogueId id of the dialog to remove
|
||||
* @param sessionId Player id
|
||||
*/
|
||||
public removeDialogue(dialogueId: string, sessionId: string): void
|
||||
{
|
||||
public removeDialogue(dialogueId: string, sessionId: string): void {
|
||||
const profile = this.saveServer.getProfile(sessionId);
|
||||
const dialog = profile.dialogues[dialogueId];
|
||||
if (!dialog)
|
||||
{
|
||||
this.logger.error(this.localisationService.getText("dialogue-unable_to_find_in_profile", { sessionId: sessionId, dialogueId: dialogueId }));
|
||||
if (!dialog) {
|
||||
this.logger.error(
|
||||
this.localisationService.getText("dialogue-unable_to_find_in_profile", {
|
||||
sessionId: sessionId,
|
||||
dialogueId: dialogueId,
|
||||
}),
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
@ -310,12 +295,15 @@ export class DialogueController
|
||||
}
|
||||
|
||||
/** Handle client/mail/dialog/pin && Handle client/mail/dialog/unpin */
|
||||
public setDialoguePin(dialogueId: string, shouldPin: boolean, sessionId: string): void
|
||||
{
|
||||
public setDialoguePin(dialogueId: string, shouldPin: boolean, sessionId: string): void {
|
||||
const dialog = this.dialogueHelper.getDialogsForProfile(sessionId)[dialogueId];
|
||||
if (!dialog)
|
||||
{
|
||||
this.logger.error(this.localisationService.getText("dialogue-unable_to_find_in_profile", { sessionId: sessionId, dialogueId: dialogueId }));
|
||||
if (!dialog) {
|
||||
this.logger.error(
|
||||
this.localisationService.getText("dialogue-unable_to_find_in_profile", {
|
||||
sessionId: sessionId,
|
||||
dialogueId: dialogueId,
|
||||
}),
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
@ -329,18 +317,19 @@ export class DialogueController
|
||||
* @param dialogueIds Dialog ids to set as read
|
||||
* @param sessionId Player profile id
|
||||
*/
|
||||
public setRead(dialogueIds: string[], sessionId: string): void
|
||||
{
|
||||
public setRead(dialogueIds: string[], sessionId: string): void {
|
||||
const dialogs = this.dialogueHelper.getDialogsForProfile(sessionId);
|
||||
if (!dialogs)
|
||||
{
|
||||
this.logger.error(this.localisationService.getText("dialogue-unable_to_find_dialogs_in_profile", { sessionId: sessionId }));
|
||||
if (!dialogs) {
|
||||
this.logger.error(
|
||||
this.localisationService.getText("dialogue-unable_to_find_dialogs_in_profile", {
|
||||
sessionId: sessionId,
|
||||
}),
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
for (const dialogId of dialogueIds)
|
||||
{
|
||||
for (const dialogId of dialogueIds) {
|
||||
dialogs[dialogId].new = 0;
|
||||
dialogs[dialogId].attachmentsNew = 0;
|
||||
}
|
||||
@ -353,13 +342,16 @@ export class DialogueController
|
||||
* @param sessionId Session id
|
||||
* @returns IGetAllAttachmentsResponse
|
||||
*/
|
||||
public getAllAttachments(dialogueId: string, sessionId: string): IGetAllAttachmentsResponse | undefined
|
||||
{
|
||||
public getAllAttachments(dialogueId: string, sessionId: string): IGetAllAttachmentsResponse | undefined {
|
||||
const dialogs = this.dialogueHelper.getDialogsForProfile(sessionId);
|
||||
const dialog = dialogs[dialogueId];
|
||||
if (!dialog)
|
||||
{
|
||||
this.logger.error(this.localisationService.getText("dialogue-unable_to_find_in_profile", { sessionId: sessionId, dialogueId: dialogueId }));
|
||||
if (!dialog) {
|
||||
this.logger.error(
|
||||
this.localisationService.getText("dialogue-unable_to_find_in_profile", {
|
||||
sessionId: sessionId,
|
||||
dialogueId: dialogueId,
|
||||
}),
|
||||
);
|
||||
|
||||
return undefined;
|
||||
}
|
||||
@ -378,8 +370,7 @@ export class DialogueController
|
||||
}
|
||||
|
||||
/** client/mail/msg/send */
|
||||
public sendMessage(sessionId: string, request: ISendMessageRequest): string
|
||||
{
|
||||
public sendMessage(sessionId: string, request: ISendMessageRequest): string {
|
||||
this.mailSendService.sendPlayerMessageToNpc(sessionId, request.dialogId, request.text);
|
||||
|
||||
return (
|
||||
@ -395,8 +386,7 @@ export class DialogueController
|
||||
* @param dialogueId Dialog to get mail attachments from
|
||||
* @returns Message array
|
||||
*/
|
||||
protected getActiveMessagesFromDialog(sessionId: string, dialogueId: string): Message[]
|
||||
{
|
||||
protected getActiveMessagesFromDialog(sessionId: string, dialogueId: string): Message[] {
|
||||
const timeNow = this.timeUtil.getTimestamp();
|
||||
const dialogs = this.dialogueHelper.getDialogsForProfile(sessionId);
|
||||
return dialogs[dialogueId].messages.filter((message) => timeNow < message.dt + (message.maxStorageTime ?? 0));
|
||||
@ -407,8 +397,7 @@ export class DialogueController
|
||||
* @param messages Messages to parse
|
||||
* @returns messages with items to collect
|
||||
*/
|
||||
protected getMessagesWithAttachments(messages: Message[]): Message[]
|
||||
{
|
||||
protected getMessagesWithAttachments(messages: Message[]): Message[] {
|
||||
return messages.filter((message) => (message.items?.data?.length ?? 0) > 0);
|
||||
}
|
||||
|
||||
@ -416,10 +405,8 @@ export class DialogueController
|
||||
* Delete expired items from all messages in player profile. triggers when updating traders.
|
||||
* @param sessionId Session id
|
||||
*/
|
||||
protected removeExpiredItemsFromMessages(sessionId: string): void
|
||||
{
|
||||
for (const dialogueId in this.dialogueHelper.getDialogsForProfile(sessionId))
|
||||
{
|
||||
protected removeExpiredItemsFromMessages(sessionId: string): void {
|
||||
for (const dialogueId in this.dialogueHelper.getDialogsForProfile(sessionId)) {
|
||||
this.removeExpiredItemsFromMessage(sessionId, dialogueId);
|
||||
}
|
||||
}
|
||||
@ -429,19 +416,15 @@ export class DialogueController
|
||||
* @param sessionId Session id
|
||||
* @param dialogueId Dialog id
|
||||
*/
|
||||
protected removeExpiredItemsFromMessage(sessionId: string, dialogueId: string): void
|
||||
{
|
||||
protected removeExpiredItemsFromMessage(sessionId: string, dialogueId: string): void {
|
||||
const dialogs = this.dialogueHelper.getDialogsForProfile(sessionId);
|
||||
const dialog = dialogs[dialogueId];
|
||||
if (!dialog.messages)
|
||||
{
|
||||
if (!dialog.messages) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const message of dialog.messages)
|
||||
{
|
||||
if (this.messageHasExpired(message))
|
||||
{
|
||||
for (const message of dialog.messages) {
|
||||
if (this.messageHasExpired(message)) {
|
||||
message.items = {};
|
||||
}
|
||||
}
|
||||
@ -452,14 +435,12 @@ export class DialogueController
|
||||
* @param message Message to check expiry of
|
||||
* @returns true or false
|
||||
*/
|
||||
protected messageHasExpired(message: Message): boolean
|
||||
{
|
||||
protected messageHasExpired(message: Message): boolean {
|
||||
return this.timeUtil.getTimestamp() > message.dt + (message.maxStorageTime ?? 0);
|
||||
}
|
||||
|
||||
/** Handle client/friend/request/send */
|
||||
public sendFriendRequest(sessionID: string, request: IFriendRequestData): IFriendRequestSendResponse
|
||||
{
|
||||
public sendFriendRequest(sessionID: string, request: IFriendRequestData): IFriendRequestSendResponse {
|
||||
return { status: 0, requestId: "12345", retryAfter: 600 };
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { inject, injectable } from "tsyringe";
|
||||
import { ApplicationContext } from "@spt/context/ApplicationContext";
|
||||
import { ContextVariableType } from "@spt/context/ContextVariableType";
|
||||
import { HideoutHelper } from "@spt/helpers/HideoutHelper";
|
||||
@ -44,14 +43,14 @@ import { ProfileActivityService } from "@spt/services/ProfileActivityService";
|
||||
import { ProfileFixerService } from "@spt/services/ProfileFixerService";
|
||||
import { RaidTimeAdjustmentService } from "@spt/services/RaidTimeAdjustmentService";
|
||||
import { SeasonalEventService } from "@spt/services/SeasonalEventService";
|
||||
import { ICloner } from "@spt/utils/cloners/ICloner";
|
||||
import { HashUtil } from "@spt/utils/HashUtil";
|
||||
import { RandomUtil } from "@spt/utils/RandomUtil";
|
||||
import { TimeUtil } from "@spt/utils/TimeUtil";
|
||||
import { ICloner } from "@spt/utils/cloners/ICloner";
|
||||
import { inject, injectable } from "tsyringe";
|
||||
|
||||
@injectable()
|
||||
export class GameController
|
||||
{
|
||||
export class GameController {
|
||||
protected httpConfig: IHttpConfig;
|
||||
protected coreConfig: ICoreConfig;
|
||||
protected locationConfig: ILocationConfig;
|
||||
@ -83,8 +82,7 @@ export class GameController
|
||||
@inject("ApplicationContext") protected applicationContext: ApplicationContext,
|
||||
@inject("ConfigServer") protected configServer: ConfigServer,
|
||||
@inject("PrimaryCloner") protected cloner: ICloner,
|
||||
)
|
||||
{
|
||||
) {
|
||||
this.httpConfig = this.configServer.getConfig(ConfigTypes.HTTP);
|
||||
this.coreConfig = this.configServer.getConfig(ConfigTypes.CORE);
|
||||
this.locationConfig = this.configServer.getConfig(ConfigTypes.LOCATION);
|
||||
@ -95,8 +93,7 @@ export class GameController
|
||||
this.botConfig = this.configServer.getConfig(ConfigTypes.BOT);
|
||||
}
|
||||
|
||||
public load(): void
|
||||
{
|
||||
public load(): void {
|
||||
// Regenerate base cache now mods are loaded and game is starting
|
||||
// Mods that add items and use the baseClass service generate the cache including their items, the next mod that
|
||||
// add items gets left out,causing warnings
|
||||
@ -107,30 +104,25 @@ export class GameController
|
||||
/**
|
||||
* Handle client/game/start
|
||||
*/
|
||||
public gameStart(_url: string, _info: IEmptyRequestData, sessionID: string, startTimeStampMS: number): void
|
||||
{
|
||||
public gameStart(_url: string, _info: IEmptyRequestData, sessionID: string, startTimeStampMS: number): void {
|
||||
// Store client start time in app context
|
||||
this.applicationContext.addValue(ContextVariableType.CLIENT_START_TIMESTAMP, startTimeStampMS);
|
||||
|
||||
this.profileActivityService.setActivityTimestamp(sessionID);
|
||||
|
||||
if (this.coreConfig.fixes.fixShotgunDispersion)
|
||||
{
|
||||
if (this.coreConfig.fixes.fixShotgunDispersion) {
|
||||
this.fixShotgunDispersions();
|
||||
}
|
||||
|
||||
if (this.locationConfig.addOpenZonesToAllMaps)
|
||||
{
|
||||
if (this.locationConfig.addOpenZonesToAllMaps) {
|
||||
this.openZoneService.applyZoneChangesToAllMaps();
|
||||
}
|
||||
|
||||
if (this.locationConfig.addCustomBotWavesToMaps)
|
||||
{
|
||||
if (this.locationConfig.addCustomBotWavesToMaps) {
|
||||
this.customLocationWaveService.applyWaveChangesToAllMaps();
|
||||
}
|
||||
|
||||
if (this.locationConfig.enableBotTypeLimits)
|
||||
{
|
||||
if (this.locationConfig.enableBotTypeLimits) {
|
||||
this.adjustMapBotLimits();
|
||||
}
|
||||
|
||||
@ -143,22 +135,18 @@ export class GameController
|
||||
// repeatableQuests are stored by in profile.Quests due to the responses of the client (e.g. Quests in
|
||||
// offraidData). Since we don't want to clutter the Quests list, we need to remove all completed (failed or
|
||||
// successful) repeatable quests. We also have to remove the Counters from the repeatableQuests
|
||||
if (sessionID)
|
||||
{
|
||||
if (sessionID) {
|
||||
const fullProfile = this.profileHelper.getFullProfile(sessionID);
|
||||
if (fullProfile.info.wipe)
|
||||
{
|
||||
if (fullProfile.info.wipe) {
|
||||
// Don't bother doing any fixes, we're resetting profile
|
||||
return;
|
||||
}
|
||||
|
||||
if (Array.isArray(fullProfile.characters.pmc.WishList))
|
||||
{
|
||||
if (Array.isArray(fullProfile.characters.pmc.WishList)) {
|
||||
fullProfile.characters.pmc.WishList = {};
|
||||
}
|
||||
|
||||
if (Array.isArray(fullProfile.characters.scav.WishList))
|
||||
{
|
||||
if (Array.isArray(fullProfile.characters.scav.WishList)) {
|
||||
fullProfile.characters.scav.WishList = {};
|
||||
}
|
||||
|
||||
@ -167,34 +155,28 @@ export class GameController
|
||||
this.logger.debug(`Started game with sessionId: ${sessionID} ${pmcProfile.Info?.Nickname}`);
|
||||
|
||||
// Migrate aki object data into spt for 3.9.0 release
|
||||
if ((fullProfile as any).aki)
|
||||
{
|
||||
if ((fullProfile as any).aki) {
|
||||
fullProfile.spt = this.cloner.clone((fullProfile as any).aki);
|
||||
delete (fullProfile as any).aki;
|
||||
}
|
||||
|
||||
if (this.coreConfig.fixes.fixProfileBreakingInventoryItemIssues)
|
||||
{
|
||||
if (this.coreConfig.fixes.fixProfileBreakingInventoryItemIssues) {
|
||||
this.profileFixerService.fixProfileBreakingInventoryItemIssues(pmcProfile);
|
||||
}
|
||||
|
||||
if (pmcProfile.Health)
|
||||
{
|
||||
if (pmcProfile.Health) {
|
||||
this.updateProfileHealthValues(pmcProfile);
|
||||
}
|
||||
|
||||
if (this.locationConfig.fixEmptyBotWavesSettings.enabled)
|
||||
{
|
||||
if (this.locationConfig.fixEmptyBotWavesSettings.enabled) {
|
||||
this.fixBrokenOfflineMapWaves();
|
||||
}
|
||||
|
||||
if (this.locationConfig.rogueLighthouseSpawnTimeSettings.enabled)
|
||||
{
|
||||
if (this.locationConfig.rogueLighthouseSpawnTimeSettings.enabled) {
|
||||
this.fixRoguesSpawningInstantlyOnLighthouse();
|
||||
}
|
||||
|
||||
if (this.locationConfig.splitWaveIntoSingleSpawnsSettings.enabled)
|
||||
{
|
||||
if (this.locationConfig.splitWaveIntoSingleSpawnsSettings.enabled) {
|
||||
this.splitBotWavesIntoSingleWaves();
|
||||
}
|
||||
|
||||
@ -202,8 +184,7 @@ export class GameController
|
||||
|
||||
this.profileFixerService.addMissingHideoutAreasToProfile(fullProfile);
|
||||
|
||||
if (pmcProfile.Inventory)
|
||||
{
|
||||
if (pmcProfile.Inventory) {
|
||||
// MUST occur prior to `profileFixerService.checkForAndFixPmcProfileIssues()`
|
||||
this.profileFixerService.fixIncorrectAidValue(fullProfile);
|
||||
|
||||
@ -218,8 +199,7 @@ export class GameController
|
||||
|
||||
this.profileFixerService.addMissingSptVersionTagToProfile(fullProfile);
|
||||
|
||||
if (pmcProfile.Hideout)
|
||||
{
|
||||
if (pmcProfile.Hideout) {
|
||||
this.profileFixerService.addMissingHideoutBonusesToProfile(pmcProfile);
|
||||
this.profileFixerService.addMissingUpgradesPropertyToHideout(pmcProfile);
|
||||
this.hideoutHelper.setHideoutImprovementsToCompleted(pmcProfile);
|
||||
@ -241,40 +221,33 @@ export class GameController
|
||||
|
||||
this.validateQuestAssortUnlocksExist();
|
||||
|
||||
if (pmcProfile.Info)
|
||||
{
|
||||
if (pmcProfile.Info) {
|
||||
this.addPlayerToPMCNames(pmcProfile);
|
||||
|
||||
this.checkForAndRemoveUndefinedDialogs(fullProfile);
|
||||
}
|
||||
|
||||
if (this.seasonalEventService.isAutomaticEventDetectionEnabled())
|
||||
{
|
||||
if (this.seasonalEventService.isAutomaticEventDetectionEnabled()) {
|
||||
this.seasonalEventService.enableSeasonalEvents(sessionID);
|
||||
}
|
||||
|
||||
if (pmcProfile?.Skills?.Common)
|
||||
{
|
||||
if (pmcProfile?.Skills?.Common) {
|
||||
this.warnOnActiveBotReloadSkill(pmcProfile);
|
||||
}
|
||||
|
||||
// Flea bsg blacklist is off
|
||||
if (!this.ragfairConfig.dynamic.blacklist.enableBsgList)
|
||||
{
|
||||
if (!this.ragfairConfig.dynamic.blacklist.enableBsgList) {
|
||||
this.setAllDbItemsAsSellableOnFlea();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected adjustHideoutCraftTimes(overrideSeconds: number): void
|
||||
{
|
||||
if (overrideSeconds === -1)
|
||||
{
|
||||
protected adjustHideoutCraftTimes(overrideSeconds: number): void {
|
||||
if (overrideSeconds === -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const craft of this.databaseService.getHideout().production)
|
||||
{
|
||||
for (const craft of this.databaseService.getHideout().production) {
|
||||
// Only adjust crafts ABOVE the override
|
||||
craft.productionTime = Math.min(craft.productionTime, overrideSeconds);
|
||||
}
|
||||
@ -283,32 +256,25 @@ export class GameController
|
||||
/**
|
||||
* Adjust all hideout craft times to be no higher than the override
|
||||
*/
|
||||
protected adjustHideoutBuildTimes(overrideSeconds: number): void
|
||||
{
|
||||
if (overrideSeconds === -1)
|
||||
{
|
||||
protected adjustHideoutBuildTimes(overrideSeconds: number): void {
|
||||
if (overrideSeconds === -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const area of this.databaseService.getHideout().areas)
|
||||
{
|
||||
for (const stage of Object.values(area.stages))
|
||||
{
|
||||
for (const area of this.databaseService.getHideout().areas) {
|
||||
for (const stage of Object.values(area.stages)) {
|
||||
// Only adjust crafts ABOVE the override
|
||||
stage.constructionTime = Math.min(stage.constructionTime, overrideSeconds);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected adjustLocationBotValues(): void
|
||||
{
|
||||
protected adjustLocationBotValues(): void {
|
||||
const mapsDb = this.databaseService.getLocations();
|
||||
|
||||
for (const locationKey in this.botConfig.maxBotCap)
|
||||
{
|
||||
for (const locationKey in this.botConfig.maxBotCap) {
|
||||
const map: ILocation = mapsDb[locationKey];
|
||||
if (!map)
|
||||
{
|
||||
if (!map) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -322,15 +288,16 @@ export class GameController
|
||||
/**
|
||||
* Out of date/incorrectly made trader mods forget this data
|
||||
*/
|
||||
protected checkTraderRepairValuesExist(): void
|
||||
{
|
||||
protected checkTraderRepairValuesExist(): void {
|
||||
const traders = this.databaseService.getTraders();
|
||||
for (const trader of Object.values(traders))
|
||||
{
|
||||
if (!trader?.base?.repair)
|
||||
{
|
||||
this.logger.warning(this.localisationService.getText("trader-missing_repair_property_using_default",
|
||||
{ traderId: trader.base._id, nickname: trader.base.nickname }));
|
||||
for (const trader of Object.values(traders)) {
|
||||
if (!trader?.base?.repair) {
|
||||
this.logger.warning(
|
||||
this.localisationService.getText("trader-missing_repair_property_using_default", {
|
||||
traderId: trader.base._id,
|
||||
nickname: trader.base.nickname,
|
||||
}),
|
||||
);
|
||||
|
||||
// use ragfair trader as a default
|
||||
trader.base.repair = this.cloner.clone(traders.ragfair.base.repair);
|
||||
@ -338,49 +305,46 @@ export class GameController
|
||||
return;
|
||||
}
|
||||
|
||||
if (trader.base.repair?.quality === undefined)
|
||||
{
|
||||
this.logger.warning(this.localisationService.getText("trader-missing_repair_quality_property_using_default",
|
||||
{ traderId: trader.base._id, nickname: trader.base.nickname }));
|
||||
if (trader.base.repair?.quality === undefined) {
|
||||
this.logger.warning(
|
||||
this.localisationService.getText("trader-missing_repair_quality_property_using_default", {
|
||||
traderId: trader.base._id,
|
||||
nickname: trader.base.nickname,
|
||||
}),
|
||||
);
|
||||
|
||||
// use ragfair trader as a default
|
||||
trader.base.repair.quality = this.cloner.clone(
|
||||
traders.ragfair.base.repair.quality,
|
||||
);
|
||||
trader.base.repair.quality = this.cloner.clone(traders.ragfair.base.repair.quality);
|
||||
trader.base.repair.quality = traders.ragfair.base.repair.quality;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected addCustomLooseLootPositions(): void
|
||||
{
|
||||
protected addCustomLooseLootPositions(): void {
|
||||
const looseLootPositionsToAdd = this.lootConfig.looseLoot;
|
||||
for (const [mapId, positionsToAdd] of Object.entries(looseLootPositionsToAdd))
|
||||
{
|
||||
if (!mapId)
|
||||
{
|
||||
this.logger.warning(this.localisationService.getText("location-unable_to_add_custom_loot_position", mapId));
|
||||
for (const [mapId, positionsToAdd] of Object.entries(looseLootPositionsToAdd)) {
|
||||
if (!mapId) {
|
||||
this.logger.warning(
|
||||
this.localisationService.getText("location-unable_to_add_custom_loot_position", mapId),
|
||||
);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
const mapLooseLoot = this.databaseService.getLocation(mapId).looseLoot;
|
||||
if (!mapLooseLoot)
|
||||
{
|
||||
if (!mapLooseLoot) {
|
||||
this.logger.warning(this.localisationService.getText("location-map_has_no_loose_loot_data", mapId));
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const positionToAdd of positionsToAdd)
|
||||
{
|
||||
for (const positionToAdd of positionsToAdd) {
|
||||
// Exists already, add new items to existing positions pool
|
||||
const existingLootPosition = mapLooseLoot.spawnpoints.find(
|
||||
(x) => x.template.Id === positionToAdd.template.Id,
|
||||
);
|
||||
|
||||
if (existingLootPosition)
|
||||
{
|
||||
if (existingLootPosition) {
|
||||
existingLootPosition.template.Items.push(...positionToAdd.template.Items);
|
||||
existingLootPosition.itemDistribution.push(...positionToAdd.itemDistribution);
|
||||
|
||||
@ -393,27 +357,27 @@ export class GameController
|
||||
}
|
||||
}
|
||||
|
||||
protected adjustLooseLootSpawnProbabilities(): void
|
||||
{
|
||||
protected adjustLooseLootSpawnProbabilities(): void {
|
||||
const adjustments = this.lootConfig.looseLootSpawnPointAdjustments;
|
||||
for (const [mapId, mapAdjustments] of Object.entries(adjustments))
|
||||
{
|
||||
for (const [mapId, mapAdjustments] of Object.entries(adjustments)) {
|
||||
const mapLooseLootData = this.databaseService.getLocation(mapId).looseLoot;
|
||||
if (!mapLooseLootData)
|
||||
{
|
||||
if (!mapLooseLootData) {
|
||||
this.logger.warning(this.localisationService.getText("location-map_has_no_loose_loot_data", mapId));
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const [lootKey, newChanceValue] of Object.entries(mapAdjustments))
|
||||
{
|
||||
const lootPostionToAdjust = mapLooseLootData.spawnpoints
|
||||
.find((spawnPoint) => spawnPoint.template.Id === lootKey);
|
||||
if (!lootPostionToAdjust)
|
||||
{
|
||||
this.logger.warning(this.localisationService.getText("location-unable_to_adjust_loot_position_on_map",
|
||||
{ lootKey: lootKey, mapId: mapId }));
|
||||
for (const [lootKey, newChanceValue] of Object.entries(mapAdjustments)) {
|
||||
const lootPostionToAdjust = mapLooseLootData.spawnpoints.find(
|
||||
(spawnPoint) => spawnPoint.template.Id === lootKey,
|
||||
);
|
||||
if (!lootPostionToAdjust) {
|
||||
this.logger.warning(
|
||||
this.localisationService.getText("location-unable_to_adjust_loot_position_on_map", {
|
||||
lootKey: lootKey,
|
||||
mapId: mapId,
|
||||
}),
|
||||
);
|
||||
|
||||
continue;
|
||||
}
|
||||
@ -424,36 +388,28 @@ export class GameController
|
||||
}
|
||||
|
||||
/** Apply custom limits on bot types as defined in configs/location.json/botTypeLimits */
|
||||
protected adjustMapBotLimits(): void
|
||||
{
|
||||
protected adjustMapBotLimits(): void {
|
||||
const mapsDb = this.databaseService.getLocations();
|
||||
if (!this.locationConfig.botTypeLimits)
|
||||
{
|
||||
if (!this.locationConfig.botTypeLimits) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const mapId in this.locationConfig.botTypeLimits)
|
||||
{
|
||||
for (const mapId in this.locationConfig.botTypeLimits) {
|
||||
const map: ILocation = mapsDb[mapId];
|
||||
if (!map)
|
||||
{
|
||||
if (!map) {
|
||||
this.logger.warning(
|
||||
this.localisationService.getText("bot-unable_to_edit_limits_of_unknown_map", mapId),
|
||||
);
|
||||
}
|
||||
|
||||
for (const botToLimit of this.locationConfig.botTypeLimits[mapId])
|
||||
{
|
||||
for (const botToLimit of this.locationConfig.botTypeLimits[mapId]) {
|
||||
const index = map.base.MinMaxBots.findIndex((x) => x.WildSpawnType === botToLimit.type);
|
||||
if (index !== -1)
|
||||
{
|
||||
if (index !== -1) {
|
||||
// Existing bot type found in MinMaxBots array, edit
|
||||
const limitObjectToUpdate = map.base.MinMaxBots[index];
|
||||
limitObjectToUpdate.min = botToLimit.min;
|
||||
limitObjectToUpdate.max = botToLimit.max;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
// Bot type not found, add new object
|
||||
map.base.MinMaxBots.push({
|
||||
// Bot type not found, add new object
|
||||
@ -469,11 +425,10 @@ export class GameController
|
||||
/**
|
||||
* Handle client/game/config
|
||||
*/
|
||||
public getGameConfig(sessionID: string): IGameConfigResponse
|
||||
{
|
||||
public getGameConfig(sessionID: string): IGameConfigResponse {
|
||||
const profile = this.profileHelper.getPmcProfile(sessionID);
|
||||
const gameTime
|
||||
= profile.Stats?.Eft.OverallCounters.Items?.find(
|
||||
const gameTime =
|
||||
profile.Stats?.Eft.OverallCounters.Items?.find(
|
||||
(counter) => counter.Key.includes("LifeTime") && counter.Key.includes("Pmc"),
|
||||
)?.Value ?? 0;
|
||||
|
||||
@ -504,40 +459,35 @@ export class GameController
|
||||
/**
|
||||
* Handle client/game/mode
|
||||
*/
|
||||
public getGameMode(sessionID: string, info: IGameModeRequestData): any
|
||||
{
|
||||
public getGameMode(sessionID: string, info: IGameModeRequestData): any {
|
||||
return { gameMode: ESessionMode.PVE, backendUrl: this.httpServerHelper.getBackendUrl() };
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle client/server/list
|
||||
*/
|
||||
public getServer(sessionId: string): IServerDetails[]
|
||||
{
|
||||
public getServer(sessionId: string): IServerDetails[] {
|
||||
return [{ ip: this.httpConfig.backendIp, port: Number.parseInt(this.httpConfig.backendPort) }];
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle client/match/group/current
|
||||
*/
|
||||
public getCurrentGroup(sessionId: string): ICurrentGroupResponse
|
||||
{
|
||||
public getCurrentGroup(sessionId: string): ICurrentGroupResponse {
|
||||
return { squad: [] };
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle client/checkVersion
|
||||
*/
|
||||
public getValidGameVersion(sessionId: string): ICheckVersionResponse
|
||||
{
|
||||
public getValidGameVersion(sessionId: string): ICheckVersionResponse {
|
||||
return { isvalid: true, latestVersion: this.coreConfig.compatibleTarkovVersion };
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle client/game/keepalive
|
||||
*/
|
||||
public getKeepAlive(sessionId: string): IGameKeepAliveResponse
|
||||
{
|
||||
public getKeepAlive(sessionId: string): IGameKeepAliveResponse {
|
||||
this.profileActivityService.setActivityTimestamp(sessionId);
|
||||
return { msg: "OK", utc_time: new Date().getTime() / 1000 };
|
||||
}
|
||||
@ -545,8 +495,7 @@ export class GameController
|
||||
/**
|
||||
* Handle singleplayer/settings/getRaidTime
|
||||
*/
|
||||
public getRaidTime(sessionId: string, request: IGetRaidTimeRequest): IGetRaidTimeResponse
|
||||
{
|
||||
public getRaidTime(sessionId: string, request: IGetRaidTimeRequest): IGetRaidTimeResponse {
|
||||
// Set interval times to in-raid value
|
||||
this.ragfairConfig.runIntervalSeconds = this.ragfairConfig.runIntervalValues.inRaid;
|
||||
|
||||
@ -558,19 +507,12 @@ export class GameController
|
||||
/**
|
||||
* BSG have two values for shotgun dispersion, we make sure both have the same value
|
||||
*/
|
||||
protected fixShotgunDispersions(): void
|
||||
{
|
||||
protected fixShotgunDispersions(): void {
|
||||
const itemDb = this.databaseService.getItems();
|
||||
|
||||
const shotguns = [
|
||||
Weapons.SHOTGUN_12G_SAIGA_12K,
|
||||
Weapons.SHOTGUN_20G_TOZ_106,
|
||||
Weapons.SHOTGUN_12G_M870,
|
||||
];
|
||||
for (const shotgunId of shotguns)
|
||||
{
|
||||
if (itemDb[shotgunId]._props.ShotgunDispersion)
|
||||
{
|
||||
const shotguns = [Weapons.SHOTGUN_12G_SAIGA_12K, Weapons.SHOTGUN_20G_TOZ_106, Weapons.SHOTGUN_12G_M870];
|
||||
for (const shotgunId of shotguns) {
|
||||
if (itemDb[shotgunId]._props.ShotgunDispersion) {
|
||||
itemDb[shotgunId]._props.shotgunDispersion = itemDb[shotgunId]._props.ShotgunDispersion;
|
||||
}
|
||||
}
|
||||
@ -580,22 +522,17 @@ export class GameController
|
||||
* Players set botReload to a high value and don't expect the crazy fast reload speeds, give them a warn about it
|
||||
* @param pmcProfile Player profile
|
||||
*/
|
||||
protected warnOnActiveBotReloadSkill(pmcProfile: IPmcData): void
|
||||
{
|
||||
protected warnOnActiveBotReloadSkill(pmcProfile: IPmcData): void {
|
||||
const botReloadSkill = this.profileHelper.getSkillFromProfile(pmcProfile, SkillTypes.BOT_RELOAD);
|
||||
if (botReloadSkill?.Progress > 0)
|
||||
{
|
||||
if (botReloadSkill?.Progress > 0) {
|
||||
this.logger.warning(this.localisationService.getText("server_start_player_active_botreload_skill"));
|
||||
}
|
||||
}
|
||||
|
||||
protected setAllDbItemsAsSellableOnFlea(): void
|
||||
{
|
||||
protected setAllDbItemsAsSellableOnFlea(): void {
|
||||
const dbItems = Object.values(this.databaseService.getItems());
|
||||
for (const item of dbItems)
|
||||
{
|
||||
if (item._type === "Item" && !item._props?.CanSellOnRagfair)
|
||||
{
|
||||
for (const item of dbItems) {
|
||||
if (item._type === "Item" && !item._props?.CanSellOnRagfair) {
|
||||
item._props.CanSellOnRagfair = true;
|
||||
}
|
||||
}
|
||||
@ -605,15 +542,13 @@ export class GameController
|
||||
* When player logs in, iterate over all active effects and reduce timer
|
||||
* @param pmcProfile Profile to adjust values for
|
||||
*/
|
||||
protected updateProfileHealthValues(pmcProfile: IPmcData): void
|
||||
{
|
||||
protected updateProfileHealthValues(pmcProfile: IPmcData): void {
|
||||
const healthLastUpdated = pmcProfile.Health.UpdateTime;
|
||||
const currentTimeStamp = this.timeUtil.getTimestamp();
|
||||
const diffSeconds = currentTimeStamp - healthLastUpdated;
|
||||
|
||||
// Last update is in past
|
||||
if (healthLastUpdated < currentTimeStamp)
|
||||
{
|
||||
if (healthLastUpdated < currentTimeStamp) {
|
||||
// Base values
|
||||
let energyRegenPerHour = 60;
|
||||
let hydrationRegenPerHour = 60;
|
||||
@ -632,53 +567,42 @@ export class GameController
|
||||
);
|
||||
|
||||
// Player has energy deficit
|
||||
if (pmcProfile.Health.Energy.Current !== pmcProfile.Health.Energy.Maximum)
|
||||
{
|
||||
if (pmcProfile.Health.Energy.Current !== pmcProfile.Health.Energy.Maximum) {
|
||||
// Set new value, whatever is smallest
|
||||
pmcProfile.Health.Energy.Current += Math.round(energyRegenPerHour * (diffSeconds / 3600));
|
||||
if (pmcProfile.Health.Energy.Current > pmcProfile.Health.Energy.Maximum)
|
||||
{
|
||||
if (pmcProfile.Health.Energy.Current > pmcProfile.Health.Energy.Maximum) {
|
||||
pmcProfile.Health.Energy.Current = pmcProfile.Health.Energy.Maximum;
|
||||
}
|
||||
}
|
||||
|
||||
// Player has hydration deficit
|
||||
if (pmcProfile.Health.Hydration.Current !== pmcProfile.Health.Hydration.Maximum)
|
||||
{
|
||||
if (pmcProfile.Health.Hydration.Current !== pmcProfile.Health.Hydration.Maximum) {
|
||||
pmcProfile.Health.Hydration.Current += Math.round(hydrationRegenPerHour * (diffSeconds / 3600));
|
||||
if (pmcProfile.Health.Hydration.Current > pmcProfile.Health.Hydration.Maximum)
|
||||
{
|
||||
if (pmcProfile.Health.Hydration.Current > pmcProfile.Health.Hydration.Maximum) {
|
||||
pmcProfile.Health.Hydration.Current = pmcProfile.Health.Hydration.Maximum;
|
||||
}
|
||||
}
|
||||
|
||||
// Check all body parts
|
||||
for (const bodyPartKey in pmcProfile.Health.BodyParts)
|
||||
{
|
||||
for (const bodyPartKey in pmcProfile.Health.BodyParts) {
|
||||
const bodyPart = pmcProfile.Health.BodyParts[bodyPartKey] as BodyPartHealth;
|
||||
|
||||
// Check part hp
|
||||
if (bodyPart.Health.Current < bodyPart.Health.Maximum)
|
||||
{
|
||||
if (bodyPart.Health.Current < bodyPart.Health.Maximum) {
|
||||
bodyPart.Health.Current += Math.round(hpRegenPerHour * (diffSeconds / 3600));
|
||||
}
|
||||
if (bodyPart.Health.Current > bodyPart.Health.Maximum)
|
||||
{
|
||||
if (bodyPart.Health.Current > bodyPart.Health.Maximum) {
|
||||
bodyPart.Health.Current = bodyPart.Health.Maximum;
|
||||
}
|
||||
|
||||
// Look for effects
|
||||
if (Object.keys(bodyPart.Effects ?? {}).length > 0)
|
||||
{
|
||||
if (Object.keys(bodyPart.Effects ?? {}).length > 0) {
|
||||
// Decrement effect time value by difference between current time and time health was last updated
|
||||
for (const effectKey in bodyPart.Effects)
|
||||
{
|
||||
for (const effectKey in bodyPart.Effects) {
|
||||
// remove effects below 1, .e.g. bleeds at -1
|
||||
if (bodyPart.Effects[effectKey].Time < 1)
|
||||
{
|
||||
if (bodyPart.Effects[effectKey].Time < 1) {
|
||||
// More than 30 mins has passed
|
||||
if (diffSeconds > 1800)
|
||||
{
|
||||
if (diffSeconds > 1800) {
|
||||
delete bodyPart.Effects[effectKey];
|
||||
}
|
||||
|
||||
@ -686,8 +610,7 @@ export class GameController
|
||||
}
|
||||
|
||||
bodyPart.Effects[effectKey].Time -= diffSeconds;
|
||||
if (bodyPart.Effects[effectKey].Time < 1)
|
||||
{
|
||||
if (bodyPart.Effects[effectKey].Time < 1) {
|
||||
// effect time was sub 1, set floor it can be
|
||||
bodyPart.Effects[effectKey].Time = 1;
|
||||
}
|
||||
@ -701,31 +624,25 @@ export class GameController
|
||||
/**
|
||||
* Waves with an identical min/max values spawn nothing, the number of bots that spawn is the difference between min and max
|
||||
*/
|
||||
protected fixBrokenOfflineMapWaves(): void
|
||||
{
|
||||
protected fixBrokenOfflineMapWaves(): void {
|
||||
const locations = this.databaseService.getLocations();
|
||||
for (const locationKey in locations)
|
||||
{
|
||||
for (const locationKey in locations) {
|
||||
// Skip ignored maps
|
||||
if (this.locationConfig.fixEmptyBotWavesSettings.ignoreMaps.includes(locationKey))
|
||||
{
|
||||
if (this.locationConfig.fixEmptyBotWavesSettings.ignoreMaps.includes(locationKey)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Loop over all of the locations waves and look for waves with identical min and max slots
|
||||
const location: ILocation = locations[locationKey];
|
||||
if (!location.base)
|
||||
{
|
||||
if (!location.base) {
|
||||
this.logger.warning(
|
||||
this.localisationService.getText("location-unable_to_fix_broken_waves_missing_base", locationKey),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const wave of location.base.waves ?? [])
|
||||
{
|
||||
if (wave.slots_max - wave.slots_min === 0)
|
||||
{
|
||||
for (const wave of location.base.waves ?? []) {
|
||||
if (wave.slots_max - wave.slots_min === 0) {
|
||||
this.logger.debug(
|
||||
`Fixed ${wave.WildSpawnType} Spawn: ${locationKey} wave: ${wave.number} of type: ${wave.WildSpawnType} in zone: ${wave.SpawnPoints} with Max Slots of ${wave.slots_max}`,
|
||||
);
|
||||
@ -738,21 +655,18 @@ export class GameController
|
||||
/**
|
||||
* Make Rogues spawn later to allow for scavs to spawn first instead of rogues filling up all spawn positions
|
||||
*/
|
||||
protected fixRoguesSpawningInstantlyOnLighthouse(): void
|
||||
{
|
||||
protected fixRoguesSpawningInstantlyOnLighthouse(): void {
|
||||
const rogueSpawnDelaySeconds = this.locationConfig.rogueLighthouseSpawnTimeSettings.waitTimeSeconds;
|
||||
const lighthouse = this.databaseService.getLocations().lighthouse?.base;
|
||||
if (!lighthouse)
|
||||
{
|
||||
if (!lighthouse) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Find Rogues that spawn instantly
|
||||
const instantRogueBossSpawns = lighthouse.BossLocationSpawn
|
||||
.filter((spawn) => spawn.BossName === "exUsec"
|
||||
&& spawn.Time === -1);
|
||||
for (const wave of instantRogueBossSpawns)
|
||||
{
|
||||
const instantRogueBossSpawns = lighthouse.BossLocationSpawn.filter(
|
||||
(spawn) => spawn.BossName === "exUsec" && spawn.Time === -1,
|
||||
);
|
||||
for (const wave of instantRogueBossSpawns) {
|
||||
wave.Time = rogueSpawnDelaySeconds;
|
||||
}
|
||||
}
|
||||
@ -761,21 +675,18 @@ export class GameController
|
||||
* Send starting gifts to profile after x days
|
||||
* @param pmcProfile Profile to add gifts to
|
||||
*/
|
||||
protected sendPraporGiftsToNewProfiles(pmcProfile: IPmcData): void
|
||||
{
|
||||
protected sendPraporGiftsToNewProfiles(pmcProfile: IPmcData): void {
|
||||
const timeStampProfileCreated = pmcProfile.Info.RegistrationDate;
|
||||
const oneDaySeconds = this.timeUtil.getHoursAsSeconds(24);
|
||||
const currentTimeStamp = this.timeUtil.getTimestamp();
|
||||
|
||||
// One day post-profile creation
|
||||
if (currentTimeStamp > timeStampProfileCreated + oneDaySeconds)
|
||||
{
|
||||
if (currentTimeStamp > timeStampProfileCreated + oneDaySeconds) {
|
||||
this.giftService.sendPraporStartingGift(pmcProfile.sessionId, 1);
|
||||
}
|
||||
|
||||
// Two day post-profile creation
|
||||
if (currentTimeStamp > timeStampProfileCreated + oneDaySeconds * 2)
|
||||
{
|
||||
if (currentTimeStamp > timeStampProfileCreated + oneDaySeconds * 2) {
|
||||
this.giftService.sendPraporStartingGift(pmcProfile.sessionId, 2);
|
||||
}
|
||||
}
|
||||
@ -784,26 +695,21 @@ export class GameController
|
||||
* Find and split waves with large numbers of bots into smaller waves - BSG appears to reduce the size of these
|
||||
* waves to one bot when they're waiting to spawn for too long
|
||||
*/
|
||||
protected splitBotWavesIntoSingleWaves(): void
|
||||
{
|
||||
protected splitBotWavesIntoSingleWaves(): void {
|
||||
const locations = this.databaseService.getLocations();
|
||||
for (const locationKey in locations)
|
||||
{
|
||||
if (this.locationConfig.splitWaveIntoSingleSpawnsSettings.ignoreMaps.includes(locationKey))
|
||||
{
|
||||
for (const locationKey in locations) {
|
||||
if (this.locationConfig.splitWaveIntoSingleSpawnsSettings.ignoreMaps.includes(locationKey)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Iterate over all maps
|
||||
const location: ILocation = locations[locationKey];
|
||||
for (const wave of location.base.waves)
|
||||
{
|
||||
for (const wave of location.base.waves) {
|
||||
// Wave has size that makes it candidate for splitting
|
||||
if (
|
||||
wave.slots_max - wave.slots_min
|
||||
>= this.locationConfig.splitWaveIntoSingleSpawnsSettings.waveSizeThreshold
|
||||
)
|
||||
{
|
||||
wave.slots_max - wave.slots_min >=
|
||||
this.locationConfig.splitWaveIntoSingleSpawnsSettings.waveSizeThreshold
|
||||
) {
|
||||
// Get count of bots to be spawned in wave
|
||||
const waveSize = wave.slots_max - wave.slots_min;
|
||||
|
||||
@ -819,14 +725,12 @@ export class GameController
|
||||
|
||||
// Add new waves to fill gap from bots we removed in above wave
|
||||
let wavesAddedCount = 0;
|
||||
for (let index = indexOfWaveToSplit + 1; index < indexOfWaveToSplit + waveSize; index++)
|
||||
{
|
||||
for (let index = indexOfWaveToSplit + 1; index < indexOfWaveToSplit + waveSize; index++) {
|
||||
// Clone wave ready to insert into array
|
||||
const waveToAddClone = this.cloner.clone(wave);
|
||||
|
||||
// Some waves have value of 0 for some reason, preserve
|
||||
if (waveToAddClone.number !== 0)
|
||||
{
|
||||
if (waveToAddClone.number !== 0) {
|
||||
// Update wave number to new location in array
|
||||
waveToAddClone.number = index;
|
||||
}
|
||||
@ -841,11 +745,9 @@ export class GameController
|
||||
let index = indexOfWaveToSplit + wavesAddedCount + 1;
|
||||
index < location.base.waves.length;
|
||||
index++
|
||||
)
|
||||
{
|
||||
) {
|
||||
// Some waves have value of 0, leave them as-is
|
||||
if (location.base.waves[index].number !== 0)
|
||||
{
|
||||
if (location.base.waves[index].number !== 0) {
|
||||
location.base.waves[index].number += wavesAddedCount;
|
||||
}
|
||||
}
|
||||
@ -858,28 +760,24 @@ export class GameController
|
||||
* Get a list of installed mods and save their details to the profile being used
|
||||
* @param fullProfile Profile to add mod details to
|
||||
*/
|
||||
protected saveActiveModsToProfile(fullProfile: ISptProfile): void
|
||||
{
|
||||
protected saveActiveModsToProfile(fullProfile: ISptProfile): void {
|
||||
// Add empty mod array if undefined
|
||||
if (!fullProfile.spt.mods)
|
||||
{
|
||||
if (!fullProfile.spt.mods) {
|
||||
fullProfile.spt.mods = [];
|
||||
}
|
||||
|
||||
// Get active mods
|
||||
const activeMods = this.preSptModLoader.getImportedModDetails();
|
||||
for (const modKey in activeMods)
|
||||
{
|
||||
for (const modKey in activeMods) {
|
||||
const modDetails = activeMods[modKey];
|
||||
if (
|
||||
fullProfile.spt.mods.some(
|
||||
(mod) =>
|
||||
mod.author === modDetails.author
|
||||
&& mod.name === modDetails.name
|
||||
&& mod.version === modDetails.version,
|
||||
mod.author === modDetails.author &&
|
||||
mod.name === modDetails.name &&
|
||||
mod.version === modDetails.version,
|
||||
)
|
||||
)
|
||||
{
|
||||
) {
|
||||
// Exists already, skip
|
||||
continue;
|
||||
}
|
||||
@ -897,17 +795,14 @@ export class GameController
|
||||
/**
|
||||
* Check for any missing assorts inside each traders assort.json data, checking against traders questassort.json
|
||||
*/
|
||||
protected validateQuestAssortUnlocksExist(): void
|
||||
{
|
||||
protected validateQuestAssortUnlocksExist(): void {
|
||||
const db = this.databaseService.getTables();
|
||||
const traders = db.traders!;
|
||||
const quests = db.templates!.quests;
|
||||
for (const traderId of Object.values(Traders))
|
||||
{
|
||||
for (const traderId of Object.values(Traders)) {
|
||||
const traderData = traders[traderId];
|
||||
const traderAssorts = traderData?.assort;
|
||||
if (!traderAssorts)
|
||||
{
|
||||
if (!traderAssorts) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -919,11 +814,9 @@ export class GameController
|
||||
};
|
||||
|
||||
// Loop over all assorts for trader
|
||||
for (const [assortKey, questKey] of Object.entries(mergedQuestAssorts))
|
||||
{
|
||||
for (const [assortKey, questKey] of Object.entries(mergedQuestAssorts)) {
|
||||
// Does assort key exist in trader assort file
|
||||
if (!traderAssorts.loyal_level_items[assortKey])
|
||||
{
|
||||
if (!traderAssorts.loyal_level_items[assortKey]) {
|
||||
// Reverse lookup of enum key by value
|
||||
const messageValues = {
|
||||
traderName: Object.keys(Traders)[Object.values(Traders).indexOf(traderId)],
|
||||
@ -941,20 +834,16 @@ export class GameController
|
||||
* Add the logged in players name to PMC name pool
|
||||
* @param pmcProfile Profile of player to get name from
|
||||
*/
|
||||
protected addPlayerToPMCNames(pmcProfile: IPmcData): void
|
||||
{
|
||||
protected addPlayerToPMCNames(pmcProfile: IPmcData): void {
|
||||
const playerName = pmcProfile.Info.Nickname;
|
||||
if (playerName)
|
||||
{
|
||||
if (playerName) {
|
||||
const bots = this.databaseService.getBots().types;
|
||||
|
||||
if (bots.bear)
|
||||
{
|
||||
if (bots.bear) {
|
||||
bots.bear.firstName.push(playerName);
|
||||
}
|
||||
|
||||
if (bots.usec)
|
||||
{
|
||||
if (bots.usec) {
|
||||
bots.usec.firstName.push(playerName);
|
||||
}
|
||||
}
|
||||
@ -964,11 +853,9 @@ export class GameController
|
||||
* Check for a dialog with the key 'undefined', and remove it
|
||||
* @param fullProfile Profile to check for dialog in
|
||||
*/
|
||||
protected checkForAndRemoveUndefinedDialogs(fullProfile: ISptProfile): void
|
||||
{
|
||||
protected checkForAndRemoveUndefinedDialogs(fullProfile: ISptProfile): void {
|
||||
const undefinedDialog = fullProfile.dialogues.undefined;
|
||||
if (undefinedDialog)
|
||||
{
|
||||
if (undefinedDialog) {
|
||||
delete fullProfile.dialogues.undefined;
|
||||
}
|
||||
}
|
||||
@ -976,12 +863,10 @@ export class GameController
|
||||
/**
|
||||
* Blank out the "test" mail message from prapor
|
||||
*/
|
||||
protected removePraporTestMessage(): void
|
||||
{
|
||||
protected removePraporTestMessage(): void {
|
||||
// Iterate over all languages (e.g. "en", "fr")
|
||||
const locales = this.databaseService.getLocales();
|
||||
for (const localeKey in locales.global)
|
||||
{
|
||||
for (const localeKey in locales.global) {
|
||||
locales.global[localeKey]["61687e2c3e526901fa76baf9"] = "";
|
||||
}
|
||||
}
|
||||
@ -989,24 +874,21 @@ export class GameController
|
||||
/**
|
||||
* Make non-trigger-spawned raiders spawn earlier + always
|
||||
*/
|
||||
protected adjustLabsRaiderSpawnRate(): void
|
||||
{
|
||||
protected adjustLabsRaiderSpawnRate(): void {
|
||||
const labsBase = this.databaseService.getLocations().laboratory!.base;
|
||||
|
||||
// Find spawns with empty string for triggerId/TriggerName
|
||||
const nonTriggerLabsBossSpawns = labsBase.BossLocationSpawn
|
||||
.filter((bossSpawn) => !bossSpawn.TriggerId
|
||||
&& !bossSpawn.TriggerName);
|
||||
const nonTriggerLabsBossSpawns = labsBase.BossLocationSpawn.filter(
|
||||
(bossSpawn) => !bossSpawn.TriggerId && !bossSpawn.TriggerName,
|
||||
);
|
||||
|
||||
for (const boss of nonTriggerLabsBossSpawns)
|
||||
{
|
||||
for (const boss of nonTriggerLabsBossSpawns) {
|
||||
boss.BossChance = 100;
|
||||
boss.Time /= 10;
|
||||
}
|
||||
}
|
||||
|
||||
protected logProfileDetails(fullProfile: ISptProfile): void
|
||||
{
|
||||
protected logProfileDetails(fullProfile: ISptProfile): void {
|
||||
this.logger.debug(`Profile made with: ${fullProfile.spt.version}`);
|
||||
this.logger.debug(
|
||||
`Server version: ${globalThis.G_SPTVERSION || this.coreConfig.sptVersion} ${globalThis.G_COMMIT}`,
|
||||
|
@ -1,18 +1,15 @@
|
||||
import { inject, injectable } from "tsyringe";
|
||||
import { HandbookHelper } from "@spt/helpers/HandbookHelper";
|
||||
import { DatabaseServer } from "@spt/servers/DatabaseServer";
|
||||
import { inject, injectable } from "tsyringe";
|
||||
|
||||
@injectable()
|
||||
export class HandbookController
|
||||
{
|
||||
export class HandbookController {
|
||||
constructor(
|
||||
@inject("DatabaseServer") protected databaseServer: DatabaseServer,
|
||||
@inject("HandbookHelper") protected handbookHelper: HandbookHelper,
|
||||
)
|
||||
{}
|
||||
) {}
|
||||
|
||||
public load(): void
|
||||
{
|
||||
public load(): void {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { inject, injectable } from "tsyringe";
|
||||
import { HealthHelper } from "@spt/helpers/HealthHelper";
|
||||
import { InventoryHelper } from "@spt/helpers/InventoryHelper";
|
||||
import { ItemHelper } from "@spt/helpers/ItemHelper";
|
||||
@ -15,12 +14,12 @@ import { ILogger } from "@spt/models/spt/utils/ILogger";
|
||||
import { EventOutputHolder } from "@spt/routers/EventOutputHolder";
|
||||
import { LocalisationService } from "@spt/services/LocalisationService";
|
||||
import { PaymentService } from "@spt/services/PaymentService";
|
||||
import { ICloner } from "@spt/utils/cloners/ICloner";
|
||||
import { HttpResponseUtil } from "@spt/utils/HttpResponseUtil";
|
||||
import { ICloner } from "@spt/utils/cloners/ICloner";
|
||||
import { inject, injectable } from "tsyringe";
|
||||
|
||||
@injectable()
|
||||
export class HealthController
|
||||
{
|
||||
export class HealthController {
|
||||
constructor(
|
||||
@inject("PrimaryLogger") protected logger: ILogger,
|
||||
@inject("EventOutputHolder") protected eventOutputHolder: EventOutputHolder,
|
||||
@ -31,8 +30,7 @@ export class HealthController
|
||||
@inject("HttpResponseUtil") protected httpResponse: HttpResponseUtil,
|
||||
@inject("HealthHelper") protected healthHelper: HealthHelper,
|
||||
@inject("PrimaryCloner") protected cloner: ICloner,
|
||||
)
|
||||
{}
|
||||
) {}
|
||||
|
||||
/**
|
||||
* stores in-raid player health
|
||||
@ -48,8 +46,7 @@ export class HealthController
|
||||
sessionID: string,
|
||||
addEffects = true,
|
||||
deleteExistingEffects = true,
|
||||
): void
|
||||
{
|
||||
): void {
|
||||
this.healthHelper.saveVitality(pmcData, info, sessionID, addEffects, deleteExistingEffects);
|
||||
}
|
||||
|
||||
@ -64,14 +61,12 @@ export class HealthController
|
||||
pmcData: IPmcData,
|
||||
request: IOffraidHealRequestData,
|
||||
sessionID: string,
|
||||
): IItemEventRouterResponse
|
||||
{
|
||||
): IItemEventRouterResponse {
|
||||
const output = this.eventOutputHolder.getOutput(sessionID);
|
||||
|
||||
// Update medkit used (hpresource)
|
||||
const healingItemToUse = pmcData.Inventory.items.find((item) => item._id === request.item);
|
||||
if (!healingItemToUse)
|
||||
{
|
||||
if (!healingItemToUse) {
|
||||
const errorMessage = this.localisationService.getText(
|
||||
"health-healing_item_not_found",
|
||||
healingItemToUse._id,
|
||||
@ -84,20 +79,16 @@ export class HealthController
|
||||
// Ensure item has a upd object
|
||||
this.itemHelper.addUpdObjectToItem(healingItemToUse);
|
||||
|
||||
if (healingItemToUse.upd.MedKit)
|
||||
{
|
||||
if (healingItemToUse.upd.MedKit) {
|
||||
healingItemToUse.upd.MedKit.HpResource -= request.count;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
// Get max healing from db
|
||||
const maxhp = this.itemHelper.getItem(healingItemToUse._tpl)[1]._props.MaxHpResource;
|
||||
healingItemToUse.upd.MedKit = { HpResource: maxhp - request.count }; // Subtract amout used from max
|
||||
}
|
||||
|
||||
// Resource in medkit is spent, delete it
|
||||
if (healingItemToUse.upd.MedKit.HpResource <= 0)
|
||||
{
|
||||
if (healingItemToUse.upd.MedKit.HpResource <= 0) {
|
||||
this.inventoryHelper.removeItem(pmcData, request.item, sessionID, output);
|
||||
}
|
||||
|
||||
@ -112,14 +103,12 @@ export class HealthController
|
||||
* @param sessionID Session id
|
||||
* @returns IItemEventRouterResponse
|
||||
*/
|
||||
public offraidEat(pmcData: IPmcData, request: IOffraidEatRequestData, sessionID: string): IItemEventRouterResponse
|
||||
{
|
||||
public offraidEat(pmcData: IPmcData, request: IOffraidEatRequestData, sessionID: string): IItemEventRouterResponse {
|
||||
const output = this.eventOutputHolder.getOutput(sessionID);
|
||||
let resourceLeft = 0;
|
||||
|
||||
const itemToConsume = pmcData.Inventory.items.find((item) => item._id === request.item);
|
||||
if (!itemToConsume)
|
||||
{
|
||||
if (!itemToConsume) {
|
||||
// Item not found, very bad
|
||||
return this.httpResponse.appendErrorToOutput(
|
||||
output,
|
||||
@ -128,17 +117,13 @@ export class HealthController
|
||||
}
|
||||
|
||||
const consumedItemMaxResource = this.itemHelper.getItem(itemToConsume._tpl)[1]._props.MaxResource;
|
||||
if (consumedItemMaxResource > 1)
|
||||
{
|
||||
if (consumedItemMaxResource > 1) {
|
||||
// Ensure item has a upd object
|
||||
this.itemHelper.addUpdObjectToItem(itemToConsume);
|
||||
|
||||
if (itemToConsume.upd.FoodDrink === undefined)
|
||||
{
|
||||
if (itemToConsume.upd.FoodDrink === undefined) {
|
||||
itemToConsume.upd.FoodDrink = { HpPercent: consumedItemMaxResource - request.count };
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
itemToConsume.upd.FoodDrink.HpPercent -= request.count;
|
||||
}
|
||||
|
||||
@ -146,8 +131,7 @@ export class HealthController
|
||||
}
|
||||
|
||||
// Remove item from inventory if resource has dropped below threshold
|
||||
if (consumedItemMaxResource === 1 || resourceLeft < 1)
|
||||
{
|
||||
if (consumedItemMaxResource === 1 || resourceLeft < 1) {
|
||||
this.inventoryHelper.removeItem(pmcData, request.item, sessionID, output);
|
||||
}
|
||||
|
||||
@ -166,8 +150,7 @@ export class HealthController
|
||||
pmcData: IPmcData,
|
||||
healthTreatmentRequest: IHealthTreatmentRequestData,
|
||||
sessionID: string,
|
||||
): IItemEventRouterResponse
|
||||
{
|
||||
): IItemEventRouterResponse {
|
||||
const output = this.eventOutputHolder.getOutput(sessionID);
|
||||
const payMoneyRequest: IProcessBuyTradeRequestData = {
|
||||
Action: healthTreatmentRequest.Action,
|
||||
@ -180,36 +163,30 @@ export class HealthController
|
||||
};
|
||||
|
||||
this.paymentService.payMoney(pmcData, payMoneyRequest, sessionID, output);
|
||||
if (output.warnings.length > 0)
|
||||
{
|
||||
if (output.warnings.length > 0) {
|
||||
return output;
|
||||
}
|
||||
|
||||
for (const bodyPartKey in healthTreatmentRequest.difference.BodyParts)
|
||||
{
|
||||
for (const bodyPartKey in healthTreatmentRequest.difference.BodyParts) {
|
||||
// Get body part from request + from pmc profile
|
||||
const partRequest: BodyPart = healthTreatmentRequest.difference.BodyParts[bodyPartKey];
|
||||
const profilePart = pmcData.Health.BodyParts[bodyPartKey];
|
||||
|
||||
// Bodypart healing is chosen when part request hp is above 0
|
||||
if (partRequest.Health > 0)
|
||||
{
|
||||
if (partRequest.Health > 0) {
|
||||
// Heal bodypart
|
||||
profilePart.Health.Current = profilePart.Health.Maximum;
|
||||
}
|
||||
|
||||
// Check for effects to remove
|
||||
if (partRequest.Effects?.length > 0)
|
||||
{
|
||||
if (partRequest.Effects?.length > 0) {
|
||||
// Found some, loop over them and remove from pmc profile
|
||||
for (const effect of partRequest.Effects)
|
||||
{
|
||||
for (const effect of partRequest.Effects) {
|
||||
delete pmcData.Health.BodyParts[bodyPartKey].Effects[effect];
|
||||
}
|
||||
|
||||
// Remove empty effect object
|
||||
if (Object.keys(pmcData.Health.BodyParts[bodyPartKey].Effects).length === 0)
|
||||
{
|
||||
if (Object.keys(pmcData.Health.BodyParts[bodyPartKey].Effects).length === 0) {
|
||||
delete pmcData.Health.BodyParts[bodyPartKey].Effects;
|
||||
}
|
||||
}
|
||||
@ -227,8 +204,7 @@ export class HealthController
|
||||
* @param info Request data
|
||||
* @param sessionID
|
||||
*/
|
||||
public applyWorkoutChanges(pmcData: IPmcData, info: IWorkoutData, sessionId: string): void
|
||||
{
|
||||
public applyWorkoutChanges(pmcData: IPmcData, info: IWorkoutData, sessionId: string): void {
|
||||
// https://dev.sp-tarkov.com/SPT/Server/issues/2674
|
||||
// TODO:
|
||||
// Health effects (fractures etc) are handled in /player/health/sync.
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { inject, injectable } from "tsyringe";
|
||||
import { ScavCaseRewardGenerator } from "@spt/generators/ScavCaseRewardGenerator";
|
||||
import { HideoutHelper } from "@spt/helpers/HideoutHelper";
|
||||
import { InventoryHelper } from "@spt/helpers/InventoryHelper";
|
||||
@ -7,12 +6,7 @@ import { PaymentHelper } from "@spt/helpers/PaymentHelper";
|
||||
import { PresetHelper } from "@spt/helpers/PresetHelper";
|
||||
import { ProfileHelper } from "@spt/helpers/ProfileHelper";
|
||||
import { IPmcData } from "@spt/models/eft/common/IPmcData";
|
||||
import {
|
||||
HideoutArea,
|
||||
ITaskConditionCounter,
|
||||
Product,
|
||||
ScavCase,
|
||||
} from "@spt/models/eft/common/tables/IBotBase";
|
||||
import { HideoutArea, ITaskConditionCounter, Product, ScavCase } from "@spt/models/eft/common/tables/IBotBase";
|
||||
import { Item } from "@spt/models/eft/common/tables/IItem";
|
||||
import { HideoutUpgradeCompleteRequestData } from "@spt/models/eft/hideout/HideoutUpgradeCompleteRequestData";
|
||||
import { IHandleQTEEventRequestData } from "@spt/models/eft/hideout/IHandleQTEEventRequestData";
|
||||
@ -47,15 +41,15 @@ import { FenceService } from "@spt/services/FenceService";
|
||||
import { LocalisationService } from "@spt/services/LocalisationService";
|
||||
import { PlayerService } from "@spt/services/PlayerService";
|
||||
import { ProfileActivityService } from "@spt/services/ProfileActivityService";
|
||||
import { ICloner } from "@spt/utils/cloners/ICloner";
|
||||
import { HashUtil } from "@spt/utils/HashUtil";
|
||||
import { HttpResponseUtil } from "@spt/utils/HttpResponseUtil";
|
||||
import { RandomUtil } from "@spt/utils/RandomUtil";
|
||||
import { TimeUtil } from "@spt/utils/TimeUtil";
|
||||
import { ICloner } from "@spt/utils/cloners/ICloner";
|
||||
import { inject, injectable } from "tsyringe";
|
||||
|
||||
@injectable()
|
||||
export class HideoutController
|
||||
{
|
||||
export class HideoutController {
|
||||
/** Key used in TaskConditionCounters array */
|
||||
protected static nameTaskConditionCountersCrafting = "CounterHoursCrafting";
|
||||
protected hideoutConfig: IHideoutConfig;
|
||||
@ -82,8 +76,7 @@ export class HideoutController
|
||||
@inject("ConfigServer") protected configServer: ConfigServer,
|
||||
@inject("FenceService") protected fenceService: FenceService,
|
||||
@inject("PrimaryCloner") protected cloner: ICloner,
|
||||
)
|
||||
{
|
||||
) {
|
||||
this.hideoutConfig = this.configServer.getConfig(ConfigTypes.HIDEOUT);
|
||||
}
|
||||
|
||||
@ -100,19 +93,15 @@ export class HideoutController
|
||||
request: IHideoutUpgradeRequestData,
|
||||
sessionID: string,
|
||||
output: IItemEventRouterResponse,
|
||||
): void
|
||||
{
|
||||
const items = request.items.map((reqItem) =>
|
||||
{
|
||||
): void {
|
||||
const items = request.items.map((reqItem) => {
|
||||
const item = pmcData.Inventory.items.find((invItem) => invItem._id === reqItem.id);
|
||||
return { inventoryItem: item, requestedItem: reqItem };
|
||||
});
|
||||
|
||||
// If it's not money, its construction / barter items
|
||||
for (const item of items)
|
||||
{
|
||||
if (!item.inventoryItem)
|
||||
{
|
||||
for (const item of items) {
|
||||
if (!item.inventoryItem) {
|
||||
this.logger.error(
|
||||
this.localisationService.getText("hideout-unable_to_find_item_in_inventory", item.requestedItem.id),
|
||||
);
|
||||
@ -122,24 +111,20 @@ export class HideoutController
|
||||
}
|
||||
|
||||
if (
|
||||
this.paymentHelper.isMoneyTpl(item.inventoryItem._tpl)
|
||||
&& item.inventoryItem.upd
|
||||
&& item.inventoryItem.upd.StackObjectsCount
|
||||
&& item.inventoryItem.upd.StackObjectsCount > item.requestedItem.count
|
||||
)
|
||||
{
|
||||
this.paymentHelper.isMoneyTpl(item.inventoryItem._tpl) &&
|
||||
item.inventoryItem.upd &&
|
||||
item.inventoryItem.upd.StackObjectsCount &&
|
||||
item.inventoryItem.upd.StackObjectsCount > item.requestedItem.count
|
||||
) {
|
||||
item.inventoryItem.upd.StackObjectsCount -= item.requestedItem.count;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
this.inventoryHelper.removeItem(pmcData, item.inventoryItem._id, sessionID, output);
|
||||
}
|
||||
}
|
||||
|
||||
// Construction time management
|
||||
const profileHideoutArea = pmcData.Hideout.Areas.find((area) => area.type === request.areaType);
|
||||
if (!profileHideoutArea)
|
||||
{
|
||||
if (!profileHideoutArea) {
|
||||
this.logger.error(this.localisationService.getText("hideout-unable_to_find_area", request.areaType));
|
||||
this.httpResponse.appendErrorToOutput(output);
|
||||
|
||||
@ -149,8 +134,7 @@ export class HideoutController
|
||||
const hideoutDataDb = this.databaseService
|
||||
.getTables()
|
||||
.hideout.areas.find((area) => area.type === request.areaType);
|
||||
if (!hideoutDataDb)
|
||||
{
|
||||
if (!hideoutDataDb) {
|
||||
this.logger.error(
|
||||
this.localisationService.getText("hideout-unable_to_find_area_in_database", request.areaType),
|
||||
);
|
||||
@ -160,10 +144,8 @@ export class HideoutController
|
||||
}
|
||||
|
||||
let ctime = hideoutDataDb.stages[profileHideoutArea.level + 1].constructionTime;
|
||||
if (ctime > 0)
|
||||
{
|
||||
if (this.profileHelper.isDeveloperAccount(sessionID))
|
||||
{
|
||||
if (ctime > 0) {
|
||||
if (this.profileHelper.isDeveloperAccount(sessionID)) {
|
||||
ctime = 40;
|
||||
}
|
||||
const timestamp = this.timeUtil.getTimestamp();
|
||||
@ -186,14 +168,12 @@ export class HideoutController
|
||||
request: HideoutUpgradeCompleteRequestData,
|
||||
sessionID: string,
|
||||
output: IItemEventRouterResponse,
|
||||
): void
|
||||
{
|
||||
): void {
|
||||
const hideout = this.databaseService.getHideout();
|
||||
const globals = this.databaseService.getGlobals();
|
||||
|
||||
const profileHideoutArea = pmcData.Hideout.Areas.find((area) => area.type === request.areaType);
|
||||
if (!profileHideoutArea)
|
||||
{
|
||||
if (!profileHideoutArea) {
|
||||
this.logger.error(this.localisationService.getText("hideout-unable_to_find_area", request.areaType));
|
||||
this.httpResponse.appendErrorToOutput(output);
|
||||
|
||||
@ -206,8 +186,7 @@ export class HideoutController
|
||||
profileHideoutArea.constructing = false;
|
||||
|
||||
const hideoutData = hideout.areas.find((area) => area.type === profileHideoutArea.type);
|
||||
if (!hideoutData)
|
||||
{
|
||||
if (!hideoutData) {
|
||||
this.logger.error(
|
||||
this.localisationService.getText("hideout-unable_to_find_area_in_database", request.areaType),
|
||||
);
|
||||
@ -219,17 +198,14 @@ export class HideoutController
|
||||
// Apply bonuses
|
||||
const hideoutStage = hideoutData.stages[profileHideoutArea.level];
|
||||
const bonuses = hideoutStage.bonuses;
|
||||
if (bonuses?.length > 0)
|
||||
{
|
||||
for (const bonus of bonuses)
|
||||
{
|
||||
if (bonuses?.length > 0) {
|
||||
for (const bonus of bonuses) {
|
||||
this.hideoutHelper.applyPlayerUpgradesBonuses(pmcData, bonus);
|
||||
}
|
||||
}
|
||||
|
||||
// Upgrade includes a container improvement/addition
|
||||
if (hideoutStage?.container)
|
||||
{
|
||||
if (hideoutStage?.container) {
|
||||
this.addContainerImprovementToProfile(
|
||||
output,
|
||||
sessionID,
|
||||
@ -242,10 +218,9 @@ export class HideoutController
|
||||
|
||||
// Upgrading water collector / med station
|
||||
if (
|
||||
profileHideoutArea.type === HideoutAreas.WATER_COLLECTOR
|
||||
|| profileHideoutArea.type === HideoutAreas.MEDSTATION
|
||||
)
|
||||
{
|
||||
profileHideoutArea.type === HideoutAreas.WATER_COLLECTOR ||
|
||||
profileHideoutArea.type === HideoutAreas.MEDSTATION
|
||||
) {
|
||||
this.checkAndUpgradeWall(pmcData);
|
||||
}
|
||||
|
||||
@ -261,15 +236,12 @@ export class HideoutController
|
||||
* Upgrade wall status to visible in profile if medstation/water collector are both level 1
|
||||
* @param pmcData Player profile
|
||||
*/
|
||||
protected checkAndUpgradeWall(pmcData: IPmcData): void
|
||||
{
|
||||
protected checkAndUpgradeWall(pmcData: IPmcData): void {
|
||||
const medStation = pmcData.Hideout.Areas.find((area) => area.type === HideoutAreas.MEDSTATION);
|
||||
const waterCollector = pmcData.Hideout.Areas.find((area) => area.type === HideoutAreas.WATER_COLLECTOR);
|
||||
if (medStation?.level >= 1 && waterCollector?.level >= 1)
|
||||
{
|
||||
if (medStation?.level >= 1 && waterCollector?.level >= 1) {
|
||||
const wall = pmcData.Hideout.Areas.find((area) => area.type === HideoutAreas.EMERGENCY_WALL);
|
||||
if (wall?.level === 0)
|
||||
{
|
||||
if (wall?.level === 0) {
|
||||
wall.level = 3;
|
||||
}
|
||||
}
|
||||
@ -290,11 +262,9 @@ export class HideoutController
|
||||
profileParentHideoutArea: HideoutArea,
|
||||
dbHideoutArea: IHideoutArea,
|
||||
hideoutStage: Stage,
|
||||
): void
|
||||
{
|
||||
): void {
|
||||
// Add key/value to `hideoutAreaStashes` dictionary - used to link hideout area to inventory stash by its id
|
||||
if (!pmcData.Inventory.hideoutAreaStashes[dbHideoutArea.type])
|
||||
{
|
||||
if (!pmcData.Inventory.hideoutAreaStashes[dbHideoutArea.type]) {
|
||||
pmcData.Inventory.hideoutAreaStashes[dbHideoutArea.type] = dbHideoutArea._id;
|
||||
}
|
||||
|
||||
@ -302,29 +272,24 @@ export class HideoutController
|
||||
this.addUpdateInventoryItemToProfile(pmcData, dbHideoutArea, hideoutStage);
|
||||
|
||||
// Dont inform client when upgraded area is hall of fame, BSG doesnt inform client upgrade has occurred, will break client if data is sent
|
||||
if (dbHideoutArea.type !== HideoutAreas.PLACE_OF_FAME)
|
||||
{
|
||||
if (dbHideoutArea.type !== HideoutAreas.PLACE_OF_FAME) {
|
||||
// Inform client of changes
|
||||
this.addContainerUpgradeToClientOutput(output, sessionID, dbHideoutArea.type, dbHideoutArea, hideoutStage);
|
||||
}
|
||||
|
||||
// Some areas like gun stand have a child area linked to it, it needs to do the same as above
|
||||
const childDbArea = this.databaseService.getHideout().areas
|
||||
.find((area) => area.parentArea === dbHideoutArea._id);
|
||||
if (childDbArea)
|
||||
{
|
||||
const childDbArea = this.databaseService
|
||||
.getHideout()
|
||||
.areas.find((area) => area.parentArea === dbHideoutArea._id);
|
||||
if (childDbArea) {
|
||||
// Add key/value to `hideoutAreaStashes` dictionary - used to link hideout area to inventory stash by its id
|
||||
if (!pmcData.Inventory.hideoutAreaStashes[childDbArea.type])
|
||||
{
|
||||
if (!pmcData.Inventory.hideoutAreaStashes[childDbArea.type]) {
|
||||
pmcData.Inventory.hideoutAreaStashes[childDbArea.type] = childDbArea._id;
|
||||
}
|
||||
|
||||
// Set child area level to same as parent area
|
||||
pmcData.Hideout.Areas
|
||||
.find((hideoutArea) => hideoutArea.type === childDbArea.type).level
|
||||
= pmcData.Hideout.Areas
|
||||
.find((x) => x.type === profileParentHideoutArea.type,
|
||||
).level;
|
||||
pmcData.Hideout.Areas.find((hideoutArea) => hideoutArea.type === childDbArea.type).level =
|
||||
pmcData.Hideout.Areas.find((x) => x.type === profileParentHideoutArea.type).level;
|
||||
|
||||
// Add/upgrade stash item in player inventory
|
||||
const childDbAreaStage = childDbArea.stages[profileParentHideoutArea.level];
|
||||
@ -345,11 +310,9 @@ export class HideoutController
|
||||
pmcData: IPmcData,
|
||||
dbHideoutData: IHideoutArea,
|
||||
hideoutStage: Stage,
|
||||
): void
|
||||
{
|
||||
): void {
|
||||
const existingInventoryItem = pmcData.Inventory.items.find((item) => item._id === dbHideoutData._id);
|
||||
if (existingInventoryItem)
|
||||
{
|
||||
if (existingInventoryItem) {
|
||||
// Update existing items container tpl to point to new id (tpl)
|
||||
existingInventoryItem._tpl = hideoutStage.container;
|
||||
|
||||
@ -373,10 +336,8 @@ export class HideoutController
|
||||
areaType: HideoutAreas,
|
||||
hideoutDbData: IHideoutArea,
|
||||
hideoutStage: Stage,
|
||||
): void
|
||||
{
|
||||
if (!output.profileChanges[sessionID].changedHideoutStashes)
|
||||
{
|
||||
): void {
|
||||
if (!output.profileChanges[sessionID].changedHideoutStashes) {
|
||||
output.profileChanges[sessionID].changedHideoutStashes = {};
|
||||
}
|
||||
|
||||
@ -398,19 +359,16 @@ export class HideoutController
|
||||
pmcData: IPmcData,
|
||||
addItemToHideoutRequest: IHideoutPutItemInRequestData,
|
||||
sessionID: string,
|
||||
): IItemEventRouterResponse
|
||||
{
|
||||
): IItemEventRouterResponse {
|
||||
const output = this.eventOutputHolder.getOutput(sessionID);
|
||||
|
||||
const itemsToAdd = Object.entries(addItemToHideoutRequest.items).map((kvp) =>
|
||||
{
|
||||
const itemsToAdd = Object.entries(addItemToHideoutRequest.items).map((kvp) => {
|
||||
const item = pmcData.Inventory.items.find((invItem) => invItem._id === kvp[1].id);
|
||||
return { inventoryItem: item, requestedItem: kvp[1], slot: kvp[0] };
|
||||
});
|
||||
|
||||
const hideoutArea = pmcData.Hideout.Areas.find((area) => area.type === addItemToHideoutRequest.areaType);
|
||||
if (!hideoutArea)
|
||||
{
|
||||
if (!hideoutArea) {
|
||||
this.logger.error(
|
||||
this.localisationService.getText(
|
||||
"hideout-unable_to_find_area_in_database",
|
||||
@ -420,10 +378,8 @@ export class HideoutController
|
||||
return this.httpResponse.appendErrorToOutput(output);
|
||||
}
|
||||
|
||||
for (const item of itemsToAdd)
|
||||
{
|
||||
if (!item.inventoryItem)
|
||||
{
|
||||
for (const item of itemsToAdd) {
|
||||
if (!item.inventoryItem) {
|
||||
this.logger.error(
|
||||
this.localisationService.getText("hideout-unable_to_find_item_in_inventory", {
|
||||
itemId: item.requestedItem.id,
|
||||
@ -465,19 +421,16 @@ export class HideoutController
|
||||
pmcData: IPmcData,
|
||||
request: IHideoutTakeItemOutRequestData,
|
||||
sessionID: string,
|
||||
): IItemEventRouterResponse
|
||||
{
|
||||
): IItemEventRouterResponse {
|
||||
const output = this.eventOutputHolder.getOutput(sessionID);
|
||||
|
||||
const hideoutArea = pmcData.Hideout.Areas.find((area) => area.type === request.areaType);
|
||||
if (!hideoutArea)
|
||||
{
|
||||
if (!hideoutArea) {
|
||||
this.logger.error(this.localisationService.getText("hideout-unable_to_find_area", request.areaType));
|
||||
return this.httpResponse.appendErrorToOutput(output);
|
||||
}
|
||||
|
||||
if (!hideoutArea.slots || hideoutArea.slots.length === 0)
|
||||
{
|
||||
if (!hideoutArea.slots || hideoutArea.slots.length === 0) {
|
||||
this.logger.error(
|
||||
this.localisationService.getText("hideout-unable_to_find_item_to_remove_from_area", hideoutArea.type),
|
||||
);
|
||||
@ -492,8 +445,7 @@ export class HideoutController
|
||||
HideoutAreas.GENERATOR,
|
||||
HideoutAreas.BITCOIN_FARM,
|
||||
].includes(hideoutArea.type)
|
||||
)
|
||||
{
|
||||
) {
|
||||
const response = this.removeResourceFromArea(sessionID, pmcData, request, output, hideoutArea);
|
||||
|
||||
// Force a refresh of productions/hideout areas with resources
|
||||
@ -521,13 +473,11 @@ export class HideoutController
|
||||
removeResourceRequest: IHideoutTakeItemOutRequestData,
|
||||
output: IItemEventRouterResponse,
|
||||
hideoutArea: HideoutArea,
|
||||
): IItemEventRouterResponse
|
||||
{
|
||||
): IItemEventRouterResponse {
|
||||
const slotIndexToRemove = removeResourceRequest.slots[0];
|
||||
|
||||
// Assume only one item in slot
|
||||
const itemToReturn = hideoutArea.slots
|
||||
.find((slot) => slot.locationIndex === slotIndexToRemove).item[0];
|
||||
const itemToReturn = hideoutArea.slots.find((slot) => slot.locationIndex === slotIndexToRemove).item[0];
|
||||
|
||||
const request: IAddItemDirectRequest = {
|
||||
itemWithModsToAdd: [itemToReturn],
|
||||
@ -537,8 +487,7 @@ export class HideoutController
|
||||
};
|
||||
|
||||
this.inventoryHelper.addItemToStash(sessionID, request, pmcData, output);
|
||||
if (output.warnings && output.warnings.length > 0)
|
||||
{
|
||||
if (output.warnings && output.warnings.length > 0) {
|
||||
// Adding to stash failed, drop out - dont remove item from hideout area slot
|
||||
return output;
|
||||
}
|
||||
@ -562,16 +511,14 @@ export class HideoutController
|
||||
pmcData: IPmcData,
|
||||
request: IHideoutToggleAreaRequestData,
|
||||
sessionID: string,
|
||||
): IItemEventRouterResponse
|
||||
{
|
||||
): IItemEventRouterResponse {
|
||||
const output = this.eventOutputHolder.getOutput(sessionID);
|
||||
|
||||
// Force a production update (occur before area is toggled as it could be generator and doing it after generator enabled would cause incorrect calculaton of production progress)
|
||||
this.hideoutHelper.updatePlayerHideout(sessionID);
|
||||
|
||||
const hideoutArea = pmcData.Hideout.Areas.find((area) => area.type === request.areaType);
|
||||
if (!hideoutArea)
|
||||
{
|
||||
if (!hideoutArea) {
|
||||
this.logger.error(this.localisationService.getText("hideout-unable_to_find_area", request.areaType));
|
||||
return this.httpResponse.appendErrorToOutput(output);
|
||||
}
|
||||
@ -593,14 +540,14 @@ export class HideoutController
|
||||
pmcData: IPmcData,
|
||||
body: IHideoutSingleProductionStartRequestData,
|
||||
sessionID: string,
|
||||
): IItemEventRouterResponse
|
||||
{
|
||||
): IItemEventRouterResponse {
|
||||
// Start production
|
||||
this.hideoutHelper.registerProduction(pmcData, body, sessionID);
|
||||
|
||||
// Find the recipe of the production
|
||||
const recipe = this.databaseService.getHideout().production
|
||||
.find((production) => production._id === body.recipeId);
|
||||
const recipe = this.databaseService
|
||||
.getHideout()
|
||||
.production.find((production) => production._id === body.recipeId);
|
||||
|
||||
// Find the actual amount of items we need to remove because body can send weird data
|
||||
const recipeRequirementsClone = this.cloner.clone(
|
||||
@ -609,8 +556,7 @@ export class HideoutController
|
||||
|
||||
const output = this.eventOutputHolder.getOutput(sessionID);
|
||||
const itemsToDelete = body.items.concat(body.tools);
|
||||
for (const itemToDelete of itemsToDelete)
|
||||
{
|
||||
for (const itemToDelete of itemsToDelete) {
|
||||
const itemToCheck = pmcData.Inventory.items.find((i) => i._id === itemToDelete.id);
|
||||
const requirement = recipeRequirementsClone.find(
|
||||
(requirement) => requirement.templateId === itemToCheck._tpl,
|
||||
@ -618,16 +564,14 @@ export class HideoutController
|
||||
|
||||
// Handle tools not having a `count`, but always only requiring 1
|
||||
const requiredCount = requirement.count ?? 1;
|
||||
if (requiredCount <= 0)
|
||||
{
|
||||
if (requiredCount <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
this.inventoryHelper.removeItemByCount(pmcData, itemToDelete.id, requiredCount, sessionID, output);
|
||||
|
||||
// Tools don't have a count
|
||||
if (requirement.type !== "Tool")
|
||||
{
|
||||
if (requirement.type !== "Tool") {
|
||||
requirement.count -= itemToDelete.count;
|
||||
}
|
||||
}
|
||||
@ -647,15 +591,12 @@ export class HideoutController
|
||||
pmcData: IPmcData,
|
||||
body: IHideoutScavCaseStartRequestData,
|
||||
sessionID: string,
|
||||
): IItemEventRouterResponse
|
||||
{
|
||||
): IItemEventRouterResponse {
|
||||
const output = this.eventOutputHolder.getOutput(sessionID);
|
||||
|
||||
for (const requestedItem of body.items)
|
||||
{
|
||||
for (const requestedItem of body.items) {
|
||||
const inventoryItem = pmcData.Inventory.items.find((item) => item._id === requestedItem.id);
|
||||
if (!inventoryItem)
|
||||
{
|
||||
if (!inventoryItem) {
|
||||
this.logger.error(
|
||||
this.localisationService.getText(
|
||||
"hideout-unable_to_find_scavcase_requested_item_in_profile_inventory",
|
||||
@ -665,20 +606,15 @@ export class HideoutController
|
||||
return this.httpResponse.appendErrorToOutput(output);
|
||||
}
|
||||
|
||||
if (inventoryItem.upd?.StackObjectsCount && inventoryItem.upd.StackObjectsCount > requestedItem.count)
|
||||
{
|
||||
if (inventoryItem.upd?.StackObjectsCount && inventoryItem.upd.StackObjectsCount > requestedItem.count) {
|
||||
inventoryItem.upd.StackObjectsCount -= requestedItem.count;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
this.inventoryHelper.removeItem(pmcData, requestedItem.id, sessionID, output);
|
||||
}
|
||||
}
|
||||
|
||||
const recipe = this.databaseService.getHideout().scavcase
|
||||
.find((r) => r._id === body.recipeId);
|
||||
if (!recipe)
|
||||
{
|
||||
const recipe = this.databaseService.getHideout().scavcase.find((r) => r._id === body.recipeId);
|
||||
if (!recipe) {
|
||||
this.logger.error(
|
||||
this.localisationService.getText("hideout-unable_to_find_scav_case_recipie_in_database", body.recipeId),
|
||||
);
|
||||
@ -689,9 +625,9 @@ export class HideoutController
|
||||
// @Important: Here we need to be very exact:
|
||||
// - normal recipe: Production time value is stored in attribute "productionTime" with small "p"
|
||||
// - scav case recipe: Production time value is stored in attribute "ProductionTime" with capital "P"
|
||||
const adjustedCraftTime
|
||||
= recipe.ProductionTime
|
||||
- this.hideoutHelper.getSkillProductionTimeReduction(
|
||||
const adjustedCraftTime =
|
||||
recipe.ProductionTime -
|
||||
this.hideoutHelper.getSkillProductionTimeReduction(
|
||||
pmcData,
|
||||
recipe.ProductionTime,
|
||||
SkillTypes.CRAFTING,
|
||||
@ -717,11 +653,9 @@ export class HideoutController
|
||||
* @param productionTime Time to complete scav case in seconds
|
||||
* @returns Adjusted scav case time in seconds
|
||||
*/
|
||||
protected getScavCaseTime(pmcData: IPmcData, productionTime: number): number
|
||||
{
|
||||
protected getScavCaseTime(pmcData: IPmcData, productionTime: number): number {
|
||||
const fenceLevel = this.fenceService.getFenceInfo(pmcData);
|
||||
if (!fenceLevel)
|
||||
{
|
||||
if (!fenceLevel) {
|
||||
return productionTime;
|
||||
}
|
||||
|
||||
@ -734,8 +668,7 @@ export class HideoutController
|
||||
* @param rewards reward items to add to profile
|
||||
* @param recipeId recipe id to save into Production dict
|
||||
*/
|
||||
protected addScavCaseRewardsToProfile(pmcData: IPmcData, rewards: Product[], recipeId: string): void
|
||||
{
|
||||
protected addScavCaseRewardsToProfile(pmcData: IPmcData, rewards: Product[], recipeId: string): void {
|
||||
pmcData.Hideout.Production[`ScavCase${recipeId}`] = { Products: rewards };
|
||||
}
|
||||
|
||||
@ -750,8 +683,7 @@ export class HideoutController
|
||||
pmcData: IPmcData,
|
||||
request: IHideoutContinuousProductionStartRequestData,
|
||||
sessionID: string,
|
||||
): IItemEventRouterResponse
|
||||
{
|
||||
): IItemEventRouterResponse {
|
||||
this.hideoutHelper.registerProduction(pmcData, request, sessionID);
|
||||
|
||||
return this.eventOutputHolder.getOutput(sessionID);
|
||||
@ -769,13 +701,11 @@ export class HideoutController
|
||||
pmcData: IPmcData,
|
||||
request: IHideoutTakeProductionRequestData,
|
||||
sessionID: string,
|
||||
): IItemEventRouterResponse
|
||||
{
|
||||
): IItemEventRouterResponse {
|
||||
const output = this.eventOutputHolder.getOutput(sessionID);
|
||||
const hideoutDb = this.databaseService.getHideout();
|
||||
|
||||
if (request.recipeId === HideoutHelper.bitcoinFarm)
|
||||
{
|
||||
if (request.recipeId === HideoutHelper.bitcoinFarm) {
|
||||
// Ensure server and client are in-sync when player presses 'get items' on farm
|
||||
this.hideoutHelper.updatePlayerHideout(sessionID);
|
||||
this.hideoutHelper.getBTC(pmcData, request, sessionID, output);
|
||||
@ -784,16 +714,14 @@ export class HideoutController
|
||||
}
|
||||
|
||||
const recipe = hideoutDb.production.find((r) => r._id === request.recipeId);
|
||||
if (recipe)
|
||||
{
|
||||
if (recipe) {
|
||||
this.handleRecipe(sessionID, recipe, pmcData, request, output);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
const scavCase = hideoutDb.scavcase.find((r) => r._id === request.recipeId);
|
||||
if (scavCase)
|
||||
{
|
||||
if (scavCase) {
|
||||
this.handleScavCase(sessionID, pmcData, request, output);
|
||||
|
||||
return output;
|
||||
@ -823,24 +751,19 @@ export class HideoutController
|
||||
pmcData: IPmcData,
|
||||
request: IHideoutTakeProductionRequestData,
|
||||
output: IItemEventRouterResponse,
|
||||
): void
|
||||
{
|
||||
): void {
|
||||
// Validate that we have a matching production
|
||||
const productionDict = Object.entries(pmcData.Hideout.Production);
|
||||
let prodId: string;
|
||||
for (const [productionId, production] of productionDict)
|
||||
{
|
||||
for (const [productionId, production] of productionDict) {
|
||||
// Skip undefined production objects
|
||||
if (!production)
|
||||
{
|
||||
if (!production) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (this.hideoutHelper.isProductionType(production))
|
||||
{
|
||||
if (this.hideoutHelper.isProductionType(production)) {
|
||||
// Production or ScavCase
|
||||
if (production.RecipeId === request.recipeId)
|
||||
{
|
||||
if (production.RecipeId === request.recipeId) {
|
||||
prodId = productionId; // Set to objects key
|
||||
break;
|
||||
}
|
||||
@ -848,8 +771,7 @@ export class HideoutController
|
||||
}
|
||||
|
||||
// If we're unable to find the production, send an error to the client
|
||||
if (prodId === undefined)
|
||||
{
|
||||
if (prodId === undefined) {
|
||||
this.logger.error(
|
||||
this.localisationService.getText(
|
||||
"hideout-unable_to_find_production_in_profile_by_recipie_id",
|
||||
@ -879,8 +801,7 @@ export class HideoutController
|
||||
|
||||
// Reward is weapon/armor preset, handle differently compared to 'normal' items
|
||||
const rewardIsPreset = this.presetHelper.hasPreset(recipe.endProduct);
|
||||
if (rewardIsPreset)
|
||||
{
|
||||
if (rewardIsPreset) {
|
||||
const preset = this.presetHelper.getDefaultPreset(recipe.endProduct);
|
||||
|
||||
// Ensure preset has unique ids and is cloned so we don't alter the preset data stored in memory
|
||||
@ -893,8 +814,7 @@ export class HideoutController
|
||||
}
|
||||
|
||||
const rewardIsStackable = this.itemHelper.isItemTplStackable(recipe.endProduct);
|
||||
if (rewardIsStackable)
|
||||
{
|
||||
if (rewardIsStackable) {
|
||||
// Create root item
|
||||
const rewardToAdd: Item = {
|
||||
_id: this.hashUtil.generate(),
|
||||
@ -905,32 +825,26 @@ export class HideoutController
|
||||
// Split item into separate items with acceptable stack sizes
|
||||
const splitReward = this.itemHelper.splitStackIntoSeparateItems(rewardToAdd);
|
||||
itemAndChildrenToSendToPlayer.push(...splitReward);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
// Not stackable, may have to send send multiple of reward
|
||||
|
||||
// Add the first reward item to array when not a preset (first preset added above earlier)
|
||||
if (!rewardIsPreset)
|
||||
{
|
||||
if (!rewardIsPreset) {
|
||||
itemAndChildrenToSendToPlayer.push([{ _id: this.hashUtil.generate(), _tpl: recipe.endProduct }]);
|
||||
}
|
||||
|
||||
// Add multiple of item if recipe requests it
|
||||
// Start index at one so we ignore first item in array
|
||||
const countOfItemsToReward = recipe.count;
|
||||
for (let index = 1; index < countOfItemsToReward; index++)
|
||||
{
|
||||
for (let index = 1; index < countOfItemsToReward; index++) {
|
||||
const itemAndMods: Item[] = this.itemHelper.replaceIDs(itemAndChildrenToSendToPlayer[0]);
|
||||
itemAndChildrenToSendToPlayer.push(...[itemAndMods]);
|
||||
}
|
||||
}
|
||||
|
||||
// Recipe has an `isEncoded` requirement for reward(s), Add `RecodableComponent` property
|
||||
if (recipe.isEncoded)
|
||||
{
|
||||
for (const reward of itemAndChildrenToSendToPlayer)
|
||||
{
|
||||
if (recipe.isEncoded) {
|
||||
for (const reward of itemAndChildrenToSendToPlayer) {
|
||||
this.itemHelper.addUpdObjectToItem(reward[0]);
|
||||
|
||||
reward[0].upd.RecodableComponent = { IsEncoded: true };
|
||||
@ -940,18 +854,15 @@ export class HideoutController
|
||||
// Build an array of the tools that need to be returned to the player
|
||||
const toolsToSendToPlayer: Item[][] = [];
|
||||
const production = pmcData.Hideout.Production[prodId];
|
||||
if (production.sptRequiredTools?.length > 0)
|
||||
{
|
||||
for (const tool of production.sptRequiredTools)
|
||||
{
|
||||
if (production.sptRequiredTools?.length > 0) {
|
||||
for (const tool of production.sptRequiredTools) {
|
||||
toolsToSendToPlayer.push([tool]);
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the recipe is the same as the last one - get bonus when crafting same thing multiple times
|
||||
const area = pmcData.Hideout.Areas.find((area) => area.type === recipe.areaType);
|
||||
if (area && request.recipeId !== area.lastRecipe)
|
||||
{
|
||||
if (area && request.recipeId !== area.lastRecipe) {
|
||||
// 1 point per craft upon the end of production for alternating between 2 different crafting recipes in the same module
|
||||
craftingExpAmount += this.hideoutConfig.expCraftAmount; // Default is 10
|
||||
}
|
||||
@ -959,8 +870,7 @@ export class HideoutController
|
||||
// Update variable with time spent crafting item(s)
|
||||
// 1 point per 8 hours of crafting
|
||||
hoursCrafting += recipe.productionTime;
|
||||
if (hoursCrafting / this.hideoutConfig.hoursForSkillCrafting >= 1)
|
||||
{
|
||||
if (hoursCrafting / this.hideoutConfig.hoursForSkillCrafting >= 1) {
|
||||
// Spent enough time crafting to get a bonus xp multipler
|
||||
const multiplierCrafting = Math.floor(hoursCrafting / this.hideoutConfig.hoursForSkillCrafting);
|
||||
craftingExpAmount += 1 * multiplierCrafting;
|
||||
@ -969,8 +879,7 @@ export class HideoutController
|
||||
|
||||
// Make sure we can fit both the craft result and tools in the stash
|
||||
const totalResultItems = toolsToSendToPlayer.concat(itemAndChildrenToSendToPlayer);
|
||||
if (!this.inventoryHelper.canPlaceItemsInInventory(sessionID, totalResultItems))
|
||||
{
|
||||
if (!this.inventoryHelper.canPlaceItemsInInventory(sessionID, totalResultItems)) {
|
||||
this.httpResponse.appendErrorToOutput(
|
||||
output,
|
||||
this.localisationService.getText("inventory-no_stash_space"),
|
||||
@ -980,8 +889,7 @@ export class HideoutController
|
||||
}
|
||||
|
||||
// Add the tools to the stash, we have to do this individually due to FiR state potentially being different
|
||||
for (const toolItem of toolsToSendToPlayer)
|
||||
{
|
||||
for (const toolItem of toolsToSendToPlayer) {
|
||||
// Note: FIR state will be based on the first item's SpawnedInSession property per item group
|
||||
const addToolsRequest: IAddItemsDirectRequest = {
|
||||
itemsWithModsToAdd: [toolItem],
|
||||
@ -991,8 +899,7 @@ export class HideoutController
|
||||
};
|
||||
|
||||
this.inventoryHelper.addItemsToStash(sessionID, addToolsRequest, pmcData, output);
|
||||
if (output.warnings.length > 0)
|
||||
{
|
||||
if (output.warnings.length > 0) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -1005,8 +912,7 @@ export class HideoutController
|
||||
callback: undefined,
|
||||
};
|
||||
this.inventoryHelper.addItemsToStash(sessionID, addItemsRequest, pmcData, output);
|
||||
if (output.warnings.length > 0)
|
||||
{
|
||||
if (output.warnings.length > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1023,13 +929,11 @@ export class HideoutController
|
||||
);
|
||||
|
||||
// Add Crafting skill to player profile
|
||||
if (craftingExpAmount > 0)
|
||||
{
|
||||
if (craftingExpAmount > 0) {
|
||||
this.profileHelper.addSkillPointsToPlayer(pmcData, SkillTypes.CRAFTING, craftingExpAmount);
|
||||
|
||||
const intellectAmountToGive = 0.5 * Math.round(craftingExpAmount / 15);
|
||||
if (intellectAmountToGive > 0)
|
||||
{
|
||||
if (intellectAmountToGive > 0) {
|
||||
this.profileHelper.addSkillPointsToPlayer(pmcData, SkillTypes.INTELLECT, intellectAmountToGive);
|
||||
}
|
||||
}
|
||||
@ -1043,8 +947,7 @@ export class HideoutController
|
||||
pmcData.Hideout.Production[prodId].sptIsContinuous = recipe.continuous;
|
||||
|
||||
// Flag normal (non continious) crafts as complete
|
||||
if (!recipe.continuous)
|
||||
{
|
||||
if (!recipe.continuous) {
|
||||
pmcData.Hideout.Production[prodId].inProgress = false;
|
||||
}
|
||||
}
|
||||
@ -1058,11 +961,9 @@ export class HideoutController
|
||||
protected getHoursCraftingTaskConditionCounter(
|
||||
pmcData: IPmcData,
|
||||
recipe: IHideoutProduction,
|
||||
): ITaskConditionCounter
|
||||
{
|
||||
): ITaskConditionCounter {
|
||||
let counterHoursCrafting = pmcData.TaskConditionCounters[HideoutController.nameTaskConditionCountersCrafting];
|
||||
if (!counterHoursCrafting)
|
||||
{
|
||||
if (!counterHoursCrafting) {
|
||||
// Doesn't exist, create
|
||||
pmcData.TaskConditionCounters[HideoutController.nameTaskConditionCountersCrafting] = {
|
||||
id: recipe._id,
|
||||
@ -1087,25 +988,20 @@ export class HideoutController
|
||||
pmcData: IPmcData,
|
||||
request: IHideoutTakeProductionRequestData,
|
||||
output: IItemEventRouterResponse,
|
||||
): void
|
||||
{
|
||||
): void {
|
||||
const ongoingProductions = Object.entries(pmcData.Hideout.Production);
|
||||
let prodId: string;
|
||||
for (const production of ongoingProductions)
|
||||
{
|
||||
if (this.hideoutHelper.isProductionType(production[1]))
|
||||
{
|
||||
for (const production of ongoingProductions) {
|
||||
if (this.hideoutHelper.isProductionType(production[1])) {
|
||||
// Production or ScavCase
|
||||
if ((production[1] as ScavCase).RecipeId === request.recipeId)
|
||||
{
|
||||
if ((production[1] as ScavCase).RecipeId === request.recipeId) {
|
||||
prodId = production[0]; // Set to objects key
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (prodId === undefined)
|
||||
{
|
||||
if (prodId === undefined) {
|
||||
this.logger.error(
|
||||
this.localisationService.getText(
|
||||
"hideout-unable_to_find_production_in_profile_by_recipie_id",
|
||||
@ -1129,8 +1025,7 @@ export class HideoutController
|
||||
};
|
||||
|
||||
this.inventoryHelper.addItemsToStash(sessionID, addItemsRequest, pmcData, output);
|
||||
if (output.warnings.length > 0)
|
||||
{
|
||||
if (output.warnings.length > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1150,8 +1045,7 @@ export class HideoutController
|
||||
* @param sessionId Session id
|
||||
* @returns IQteData array
|
||||
*/
|
||||
public getQteList(sessionId: string): IQteData[]
|
||||
{
|
||||
public getQteList(sessionId: string): IQteData[] {
|
||||
return this.databaseService.getHideout().qte;
|
||||
}
|
||||
|
||||
@ -1167,8 +1061,7 @@ export class HideoutController
|
||||
pmcData: IPmcData,
|
||||
request: IHandleQTEEventRequestData,
|
||||
output: IItemEventRouterResponse,
|
||||
): void
|
||||
{
|
||||
): void {
|
||||
// {
|
||||
// "Action": "HideoutQuickTimeEvent",
|
||||
// "results": [true, false, true, true, true, true, true, true, true, false, false, false, false, false, false],
|
||||
@ -1180,14 +1073,12 @@ export class HideoutController
|
||||
// /client/hideout/workout (applyWorkoutChanges).
|
||||
|
||||
pmcData.Health.Energy.Current -= 50;
|
||||
if (pmcData.Health.Energy.Current < 1)
|
||||
{
|
||||
if (pmcData.Health.Energy.Current < 1) {
|
||||
pmcData.Health.Energy.Current = 1;
|
||||
}
|
||||
|
||||
pmcData.Health.Hydration.Current -= 50;
|
||||
if (pmcData.Health.Hydration.Current < 1)
|
||||
{
|
||||
if (pmcData.Health.Hydration.Current < 1) {
|
||||
pmcData.Health.Hydration.Current = 1;
|
||||
}
|
||||
}
|
||||
@ -1199,15 +1090,13 @@ export class HideoutController
|
||||
* @param request shooting range score request
|
||||
* @returns IItemEventRouterResponse
|
||||
*/
|
||||
public recordShootingRangePoints(sessionId: string, pmcData: IPmcData, request: IRecordShootingRangePoints): void
|
||||
{
|
||||
public recordShootingRangePoints(sessionId: string, pmcData: IPmcData, request: IRecordShootingRangePoints): void {
|
||||
const shootingRangeKey = "ShootingRangePoints";
|
||||
const overallCounterItems = pmcData.Stats.Eft.OverallCounters.Items;
|
||||
|
||||
// Find counter by key
|
||||
let shootingRangeHighScore = overallCounterItems.find((counter) => counter.Key.includes(shootingRangeKey));
|
||||
if (!shootingRangeHighScore)
|
||||
{
|
||||
if (!shootingRangeHighScore) {
|
||||
// Counter not found, add blank one
|
||||
overallCounterItems.push({ Key: [shootingRangeKey], Value: 0 });
|
||||
shootingRangeHighScore = overallCounterItems.find((counter) => counter.Key.includes(shootingRangeKey));
|
||||
@ -1226,22 +1115,18 @@ export class HideoutController
|
||||
sessionId: string,
|
||||
pmcData: IPmcData,
|
||||
request: IHideoutImproveAreaRequestData,
|
||||
): IItemEventRouterResponse
|
||||
{
|
||||
): IItemEventRouterResponse {
|
||||
const output = this.eventOutputHolder.getOutput(sessionId);
|
||||
|
||||
// Create mapping of required item with corrisponding item from player inventory
|
||||
const items = request.items.map((reqItem) =>
|
||||
{
|
||||
const items = request.items.map((reqItem) => {
|
||||
const item = pmcData.Inventory.items.find((invItem) => invItem._id === reqItem.id);
|
||||
return { inventoryItem: item, requestedItem: reqItem };
|
||||
});
|
||||
|
||||
// If it's not money, its construction / barter items
|
||||
for (const item of items)
|
||||
{
|
||||
if (!item.inventoryItem)
|
||||
{
|
||||
for (const item of items) {
|
||||
if (!item.inventoryItem) {
|
||||
this.logger.error(
|
||||
this.localisationService.getText("hideout-unable_to_find_item_in_inventory", item.requestedItem.id),
|
||||
);
|
||||
@ -1249,31 +1134,25 @@ export class HideoutController
|
||||
}
|
||||
|
||||
if (
|
||||
this.paymentHelper.isMoneyTpl(item.inventoryItem._tpl)
|
||||
&& item.inventoryItem.upd
|
||||
&& item.inventoryItem.upd.StackObjectsCount
|
||||
&& item.inventoryItem.upd.StackObjectsCount > item.requestedItem.count
|
||||
)
|
||||
{
|
||||
this.paymentHelper.isMoneyTpl(item.inventoryItem._tpl) &&
|
||||
item.inventoryItem.upd &&
|
||||
item.inventoryItem.upd.StackObjectsCount &&
|
||||
item.inventoryItem.upd.StackObjectsCount > item.requestedItem.count
|
||||
) {
|
||||
item.inventoryItem.upd.StackObjectsCount -= item.requestedItem.count;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
this.inventoryHelper.removeItem(pmcData, item.inventoryItem._id, sessionId, output);
|
||||
}
|
||||
}
|
||||
|
||||
const profileHideoutArea = pmcData.Hideout.Areas.find((x) => x.type === request.areaType);
|
||||
if (!profileHideoutArea)
|
||||
{
|
||||
if (!profileHideoutArea) {
|
||||
this.logger.error(this.localisationService.getText("hideout-unable_to_find_area", request.areaType));
|
||||
return this.httpResponse.appendErrorToOutput(output);
|
||||
}
|
||||
|
||||
const hideoutDbData = this.databaseService.getHideout().areas
|
||||
.find((area) => area.type === request.areaType);
|
||||
if (!hideoutDbData)
|
||||
{
|
||||
const hideoutDbData = this.databaseService.getHideout().areas.find((area) => area.type === request.areaType);
|
||||
if (!hideoutDbData) {
|
||||
this.logger.error(
|
||||
this.localisationService.getText("hideout-unable_to_find_area_in_database", request.areaType),
|
||||
);
|
||||
@ -1284,13 +1163,11 @@ export class HideoutController
|
||||
const improvements = hideoutDbData.stages[profileHideoutArea.level].improvements;
|
||||
const timestamp = this.timeUtil.getTimestamp();
|
||||
|
||||
if (!output.profileChanges[sessionId].improvements)
|
||||
{
|
||||
if (!output.profileChanges[sessionId].improvements) {
|
||||
output.profileChanges[sessionId].improvements = {};
|
||||
}
|
||||
|
||||
for (const improvement of improvements)
|
||||
{
|
||||
for (const improvement of improvements) {
|
||||
const improvementDetails = {
|
||||
completed: false,
|
||||
improveCompleteTimestamp: timestamp + improvement.improvementTime,
|
||||
@ -1313,13 +1190,11 @@ export class HideoutController
|
||||
sessionId: string,
|
||||
pmcData: IPmcData,
|
||||
request: IHideoutCancelProductionRequestData,
|
||||
): IItemEventRouterResponse
|
||||
{
|
||||
): IItemEventRouterResponse {
|
||||
const output = this.eventOutputHolder.getOutput(sessionId);
|
||||
|
||||
const craftToCancel = pmcData.Hideout.Production[request.recipeId];
|
||||
if (!craftToCancel)
|
||||
{
|
||||
if (!craftToCancel) {
|
||||
const errorMessage = `Unable to find craft ${request.recipeId} to cancel`;
|
||||
this.logger.error(errorMessage);
|
||||
|
||||
@ -1337,18 +1212,15 @@ export class HideoutController
|
||||
/**
|
||||
* Function called every x seconds as part of onUpdate event
|
||||
*/
|
||||
public update(): void
|
||||
{
|
||||
for (const sessionID in this.saveServer.getProfiles())
|
||||
{
|
||||
public update(): void {
|
||||
for (const sessionID in this.saveServer.getProfiles()) {
|
||||
if (
|
||||
"Hideout" in this.saveServer.getProfile(sessionID).characters.pmc
|
||||
&& this.profileActivityService.activeWithinLastMinutes(
|
||||
"Hideout" in this.saveServer.getProfile(sessionID).characters.pmc &&
|
||||
this.profileActivityService.activeWithinLastMinutes(
|
||||
sessionID,
|
||||
this.hideoutConfig.updateProfileHideoutWhenActiveWithinMinutes,
|
||||
)
|
||||
)
|
||||
{
|
||||
) {
|
||||
this.hideoutHelper.updatePlayerHideout(sessionID);
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { inject, injectable } from "tsyringe";
|
||||
import { ApplicationContext } from "@spt/context/ApplicationContext";
|
||||
import { ContextVariableType } from "@spt/context/ContextVariableType";
|
||||
import { ProfileHelper } from "@spt/helpers/ProfileHelper";
|
||||
@ -11,13 +10,13 @@ import { ILogger } from "@spt/models/spt/utils/ILogger";
|
||||
import { ConfigServer } from "@spt/servers/ConfigServer";
|
||||
import { SaveServer } from "@spt/servers/SaveServer";
|
||||
import { LocalisationService } from "@spt/services/LocalisationService";
|
||||
import { inject, injectable } from "tsyringe";
|
||||
|
||||
/**
|
||||
* Logic for handling In Raid callbacks
|
||||
*/
|
||||
@injectable()
|
||||
export class InraidController
|
||||
{
|
||||
export class InraidController {
|
||||
protected inRaidConfig: IInRaidConfig;
|
||||
protected botConfig: IBotConfig;
|
||||
|
||||
@ -28,8 +27,7 @@ export class InraidController
|
||||
@inject("LocalisationService") protected localisationService: LocalisationService,
|
||||
@inject("ApplicationContext") protected applicationContext: ApplicationContext,
|
||||
@inject("ConfigServer") protected configServer: ConfigServer,
|
||||
)
|
||||
{
|
||||
) {
|
||||
this.inRaidConfig = this.configServer.getConfig(ConfigTypes.IN_RAID);
|
||||
this.botConfig = this.configServer.getConfig(ConfigTypes.BOT);
|
||||
}
|
||||
@ -39,18 +37,15 @@ export class InraidController
|
||||
* @param sessionID Session id
|
||||
* @param info Register player request
|
||||
*/
|
||||
public addPlayer(sessionID: string, info: IRegisterPlayerRequestData): void
|
||||
{
|
||||
public addPlayer(sessionID: string, info: IRegisterPlayerRequestData): void {
|
||||
this.applicationContext.addValue(ContextVariableType.REGISTER_PLAYER_REQUEST, info);
|
||||
const profile = this.saveServer.getProfile(sessionID);
|
||||
if (!profile)
|
||||
{
|
||||
if (!profile) {
|
||||
this.logger.error(this.localisationService.getText("inraid-no_profile_found", sessionID));
|
||||
|
||||
return;
|
||||
}
|
||||
if (!profile.inraid)
|
||||
{
|
||||
if (!profile.inraid) {
|
||||
profile.inraid = { character: sessionID, location: info.locationId };
|
||||
|
||||
return;
|
||||
@ -66,8 +61,7 @@ export class InraidController
|
||||
* @param offraidData post-raid request data
|
||||
* @param sessionID Session id
|
||||
*/
|
||||
public savePostRaidProfileForScav(offraidData: IScavSaveRequestData, sessionID: string): void
|
||||
{
|
||||
public savePostRaidProfileForScav(offraidData: IScavSaveRequestData, sessionID: string): void {
|
||||
const serverScavProfile = this.profileHelper.getScavProfile(sessionID);
|
||||
|
||||
serverScavProfile.Inventory.items = offraidData.profile.Inventory.items;
|
||||
@ -77,18 +71,15 @@ export class InraidController
|
||||
* Get the inraid config from configs/inraid.json
|
||||
* @returns InRaid Config
|
||||
*/
|
||||
public getInraidConfig(): IInRaidConfig
|
||||
{
|
||||
public getInraidConfig(): IInRaidConfig {
|
||||
return this.inRaidConfig;
|
||||
}
|
||||
|
||||
public getTraitorScavHostileChance(url: string, sessionID: string): number
|
||||
{
|
||||
public getTraitorScavHostileChance(url: string, sessionID: string): number {
|
||||
return this.inRaidConfig.playerScavHostileChancePercent;
|
||||
}
|
||||
|
||||
public getBossConvertSettings(url: string, sessionId: string): string[]
|
||||
{
|
||||
public getBossConvertSettings(url: string, sessionId: string): string[] {
|
||||
return Object.keys(this.botConfig.assaultToBossConversion.bossesToConvertToWeights);
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { inject, injectable } from "tsyringe";
|
||||
import { DialogueHelper } from "@spt/helpers/DialogueHelper";
|
||||
import { ItemHelper } from "@spt/helpers/ItemHelper";
|
||||
import { ProfileHelper } from "@spt/helpers/ProfileHelper";
|
||||
@ -26,15 +25,15 @@ import { LocalisationService } from "@spt/services/LocalisationService";
|
||||
import { MailSendService } from "@spt/services/MailSendService";
|
||||
import { PaymentService } from "@spt/services/PaymentService";
|
||||
import { RagfairPriceService } from "@spt/services/RagfairPriceService";
|
||||
import { ICloner } from "@spt/utils/cloners/ICloner";
|
||||
import { HashUtil } from "@spt/utils/HashUtil";
|
||||
import { MathUtil } from "@spt/utils/MathUtil";
|
||||
import { ProbabilityObject, ProbabilityObjectArray, RandomUtil } from "@spt/utils/RandomUtil";
|
||||
import { TimeUtil } from "@spt/utils/TimeUtil";
|
||||
import { ICloner } from "@spt/utils/cloners/ICloner";
|
||||
import { inject, injectable } from "tsyringe";
|
||||
|
||||
@injectable()
|
||||
export class InsuranceController
|
||||
{
|
||||
export class InsuranceController {
|
||||
protected insuranceConfig: IInsuranceConfig;
|
||||
|
||||
constructor(
|
||||
@ -58,8 +57,7 @@ export class InsuranceController
|
||||
@inject("LocalisationService") protected localisationService: LocalisationService,
|
||||
@inject("ConfigServer") protected configServer: ConfigServer,
|
||||
@inject("PrimaryCloner") protected cloner: ICloner,
|
||||
)
|
||||
{
|
||||
) {
|
||||
this.insuranceConfig = this.configServer.getConfig(ConfigTypes.INSURANCE);
|
||||
}
|
||||
|
||||
@ -68,11 +66,9 @@ export class InsuranceController
|
||||
*
|
||||
* @returns void
|
||||
*/
|
||||
public processReturn(): void
|
||||
{
|
||||
public processReturn(): void {
|
||||
// Process each installed profile.
|
||||
for (const sessionID in this.saveServer.getProfiles())
|
||||
{
|
||||
for (const sessionID in this.saveServer.getProfiles()) {
|
||||
this.processReturnByProfile(sessionID);
|
||||
}
|
||||
}
|
||||
@ -82,14 +78,12 @@ export class InsuranceController
|
||||
*
|
||||
* @returns void
|
||||
*/
|
||||
public processReturnByProfile(sessionID: string): void
|
||||
{
|
||||
public processReturnByProfile(sessionID: string): void {
|
||||
// Filter out items that don't need to be processed yet.
|
||||
const insuranceDetails = this.filterInsuredItems(sessionID);
|
||||
|
||||
// Skip profile if no insured items to process
|
||||
if (insuranceDetails.length === 0)
|
||||
{
|
||||
if (insuranceDetails.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -103,14 +97,12 @@ export class InsuranceController
|
||||
* @param time The time to check ready status against. Current time by default.
|
||||
* @returns All insured items that are ready to be processed.
|
||||
*/
|
||||
protected filterInsuredItems(sessionID: string, time?: number): Insurance[]
|
||||
{
|
||||
protected filterInsuredItems(sessionID: string, time?: number): Insurance[] {
|
||||
// Use the current time by default.
|
||||
const insuranceTime = time || this.timeUtil.getTimestamp();
|
||||
|
||||
const profileInsuranceDetails = this.saveServer.getProfile(sessionID).insurance;
|
||||
if (profileInsuranceDetails.length > 0)
|
||||
{
|
||||
if (profileInsuranceDetails.length > 0) {
|
||||
this.logger.debug(`Found ${profileInsuranceDetails.length} insurance packages in profile ${sessionID}`);
|
||||
}
|
||||
|
||||
@ -124,8 +116,7 @@ export class InsuranceController
|
||||
* @param sessionID The session ID that should receive the processed items.
|
||||
* @returns void
|
||||
*/
|
||||
protected processInsuredItems(insuranceDetails: Insurance[], sessionID: string): void
|
||||
{
|
||||
protected processInsuredItems(insuranceDetails: Insurance[], sessionID: string): void {
|
||||
this.logger.debug(
|
||||
`Processing ${insuranceDetails.length} insurance packages, which includes a total of ${this.countAllInsuranceItems(
|
||||
insuranceDetails,
|
||||
@ -136,8 +127,7 @@ export class InsuranceController
|
||||
const rootItemParentID = this.insuranceService.getRootItemParentID(sessionID);
|
||||
|
||||
// Iterate over each of the insurance packages.
|
||||
for (const insured of insuranceDetails)
|
||||
{
|
||||
for (const insured of insuranceDetails) {
|
||||
// Find items that should be deleted from the insured items.
|
||||
const itemsToDelete = this.findItemsToDelete(rootItemParentID, insured);
|
||||
|
||||
@ -160,8 +150,7 @@ export class InsuranceController
|
||||
* @param insurance
|
||||
* @returns
|
||||
*/
|
||||
protected countAllInsuranceItems(insurance: Insurance[]): number
|
||||
{
|
||||
protected countAllInsuranceItems(insurance: Insurance[]): number {
|
||||
return this.mathUtil.arraySum(insurance.map((ins) => ins.items.length));
|
||||
}
|
||||
|
||||
@ -172,15 +161,14 @@ export class InsuranceController
|
||||
* @param index The array index of the insurance package to remove.
|
||||
* @returns void
|
||||
*/
|
||||
protected removeInsurancePackageFromProfile(sessionID: string, insPackage: Insurance): void
|
||||
{
|
||||
protected removeInsurancePackageFromProfile(sessionID: string, insPackage: Insurance): void {
|
||||
const profile = this.saveServer.getProfile(sessionID);
|
||||
profile.insurance = profile.insurance.filter(
|
||||
(insurance) =>
|
||||
insurance.traderId !== insPackage.traderId
|
||||
|| insurance.systemData.date !== insPackage.systemData.date
|
||||
|| insurance.systemData.time !== insPackage.systemData.time
|
||||
|| insurance.systemData.location !== insPackage.systemData.location,
|
||||
insurance.traderId !== insPackage.traderId ||
|
||||
insurance.systemData.date !== insPackage.systemData.date ||
|
||||
insurance.systemData.time !== insPackage.systemData.time ||
|
||||
insurance.systemData.location !== insPackage.systemData.location,
|
||||
);
|
||||
|
||||
this.logger.debug(`Removed processed insurance package. Remaining packages: ${profile.insurance.length}`);
|
||||
@ -193,8 +181,7 @@ export class InsuranceController
|
||||
* @param insured - The insurance object containing the items to evaluate for deletion.
|
||||
* @returns A Set containing the IDs of items that should be deleted.
|
||||
*/
|
||||
protected findItemsToDelete(rootItemParentID: string, insured: Insurance): Set<string>
|
||||
{
|
||||
protected findItemsToDelete(rootItemParentID: string, insured: Insurance): Set<string> {
|
||||
const toDelete = new Set<string>();
|
||||
|
||||
// Populate a Map object of items for quick lookup by their ID and use it to populate a Map of main-parent items
|
||||
@ -208,14 +195,12 @@ export class InsuranceController
|
||||
);
|
||||
|
||||
// Process all items that are not attached, attachments; those are handled separately, by value.
|
||||
if (hasRegularItems)
|
||||
{
|
||||
if (hasRegularItems) {
|
||||
this.processRegularItems(insured, toDelete, parentAttachmentsMap);
|
||||
}
|
||||
|
||||
// Process attached, attachments, by value, only if there are any.
|
||||
if (parentAttachmentsMap.size > 0)
|
||||
{
|
||||
if (parentAttachmentsMap.size > 0) {
|
||||
// Remove attachments that can not be moddable in-raid from the parentAttachmentsMap. We only want to
|
||||
// process moddable attachments from here on out.
|
||||
parentAttachmentsMap = this.removeNonModdableAttachments(parentAttachmentsMap, itemsMap);
|
||||
@ -224,8 +209,7 @@ export class InsuranceController
|
||||
}
|
||||
|
||||
// Log the number of items marked for deletion, if any
|
||||
if (toDelete.size)
|
||||
{
|
||||
if (toDelete.size) {
|
||||
this.logger.debug(`Marked ${toDelete.size} items for deletion from insurance.`);
|
||||
}
|
||||
|
||||
@ -246,65 +230,58 @@ export class InsuranceController
|
||||
rootItemParentID: string,
|
||||
insured: Insurance,
|
||||
itemsMap: Map<string, Item>,
|
||||
): Map<string, Item[]>
|
||||
{
|
||||
): Map<string, Item[]> {
|
||||
const mainParentToAttachmentsMap = new Map<string, Item[]>();
|
||||
for (const insuredItem of insured.items)
|
||||
{
|
||||
for (const insuredItem of insured.items) {
|
||||
// Use the parent ID from the item to get the parent item.
|
||||
const parentItem = insured.items.find((item) => item._id === insuredItem.parentId);
|
||||
|
||||
// The parent (not the hideout) could not be found. Skip and warn.
|
||||
if (!parentItem && insuredItem.parentId !== rootItemParentID)
|
||||
{
|
||||
this.logger.warning(this.localisationService
|
||||
.getText("insurance-unable_to_find_parent_of_item",
|
||||
{
|
||||
if (!parentItem && insuredItem.parentId !== rootItemParentID) {
|
||||
this.logger.warning(
|
||||
this.localisationService.getText("insurance-unable_to_find_parent_of_item", {
|
||||
insuredItemId: insuredItem._id,
|
||||
insuredItemTpl: insuredItem._tpl,
|
||||
parentId: insuredItem.parentId,
|
||||
}));
|
||||
}),
|
||||
);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if this is an attachment currently attached to its parent.
|
||||
if (this.itemHelper.isAttachmentAttached(insuredItem))
|
||||
{
|
||||
if (this.itemHelper.isAttachmentAttached(insuredItem)) {
|
||||
// Make sure the template for the item exists.
|
||||
if (!this.itemHelper.getItem(insuredItem._tpl)[0])
|
||||
{
|
||||
this.logger.warning(this.localisationService.getText("insurance-unable_to_find_attachment_in_db",
|
||||
{
|
||||
if (!this.itemHelper.getItem(insuredItem._tpl)[0]) {
|
||||
this.logger.warning(
|
||||
this.localisationService.getText("insurance-unable_to_find_attachment_in_db", {
|
||||
insuredItemId: insuredItem._id,
|
||||
insuredItemTpl: insuredItem._tpl,
|
||||
}));
|
||||
}),
|
||||
);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get the main parent of this attachment. (e.g., The gun that this suppressor is attached to.)
|
||||
const mainParent = this.itemHelper.getAttachmentMainParent(insuredItem._id, itemsMap);
|
||||
if (!mainParent)
|
||||
{
|
||||
if (!mainParent) {
|
||||
// Odd. The parent couldn't be found. Skip this attachment and warn.
|
||||
this.logger.warning(this.localisationService.getText("insurance-unable_to_find_main_parent_for_attachment",
|
||||
{
|
||||
this.logger.warning(
|
||||
this.localisationService.getText("insurance-unable_to_find_main_parent_for_attachment", {
|
||||
insuredItemId: insuredItem._id,
|
||||
insuredItemTpl: insuredItem._tpl,
|
||||
parentId: insuredItem.parentId,
|
||||
}));
|
||||
}),
|
||||
);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Update (or add to) the main-parent to attachments map.
|
||||
if (mainParentToAttachmentsMap.has(mainParent._id))
|
||||
{
|
||||
if (mainParentToAttachmentsMap.has(mainParent._id)) {
|
||||
mainParentToAttachmentsMap.get(mainParent._id).push(insuredItem);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
mainParentToAttachmentsMap.set(mainParent._id, [insuredItem]);
|
||||
}
|
||||
}
|
||||
@ -323,40 +300,33 @@ export class InsuranceController
|
||||
protected removeNonModdableAttachments(
|
||||
parentAttachmentsMap: Map<string, Item[]>,
|
||||
itemsMap: Map<string, Item>,
|
||||
): Map<string, Item[]>
|
||||
{
|
||||
): Map<string, Item[]> {
|
||||
const updatedMap = new Map<string, Item[]>();
|
||||
|
||||
for (const [parentId, attachmentItems] of parentAttachmentsMap)
|
||||
{
|
||||
for (const [parentId, attachmentItems] of parentAttachmentsMap) {
|
||||
const parentItem = itemsMap.get(parentId);
|
||||
const moddableAttachments: Item[] = [];
|
||||
for (const attachment of attachmentItems)
|
||||
{
|
||||
for (const attachment of attachmentItems) {
|
||||
// By default, assume the parent of the current attachment is the main-parent included in the map.
|
||||
let attachmentParentItem = parentItem;
|
||||
|
||||
// If the attachment includes a parentId, use it to find its direct parent item, even if it's another
|
||||
// attachment on the main-parent. For example, if the attachment is a stock, we need to check to see if
|
||||
// it's moddable in the upper receiver (attachment/parent), which is attached to the gun (main-parent).
|
||||
if (attachment.parentId)
|
||||
{
|
||||
if (attachment.parentId) {
|
||||
const directParentItem = itemsMap.get(attachment.parentId);
|
||||
if (directParentItem)
|
||||
{
|
||||
if (directParentItem) {
|
||||
attachmentParentItem = directParentItem;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.itemHelper.isRaidModdable(attachment, attachmentParentItem))
|
||||
{
|
||||
if (this.itemHelper.isRaidModdable(attachment, attachmentParentItem)) {
|
||||
moddableAttachments.push(attachment);
|
||||
}
|
||||
}
|
||||
|
||||
// If any moddable attachments remain, add them to the updated map.
|
||||
if (moddableAttachments.length > 0)
|
||||
{
|
||||
if (moddableAttachments.length > 0) {
|
||||
updatedMap.set(parentId, moddableAttachments);
|
||||
}
|
||||
}
|
||||
@ -378,40 +348,32 @@ export class InsuranceController
|
||||
insured: Insurance,
|
||||
toDelete: Set<string>,
|
||||
parentAttachmentsMap: Map<string, Item[]>,
|
||||
): void
|
||||
{
|
||||
for (const insuredItem of insured.items)
|
||||
{
|
||||
): void {
|
||||
for (const insuredItem of insured.items) {
|
||||
// Skip if the item is an attachment. These are handled separately.
|
||||
if (this.itemHelper.isAttachmentAttached(insuredItem))
|
||||
{
|
||||
if (this.itemHelper.isAttachmentAttached(insuredItem)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Roll for item deletion
|
||||
const itemRoll = this.rollForDelete(insured.traderId, insuredItem);
|
||||
if (itemRoll)
|
||||
{
|
||||
if (itemRoll) {
|
||||
// Check to see if this item is a parent in the parentAttachmentsMap. If so, do a look-up for *all* of
|
||||
// its children and mark them for deletion as well. Additionally remove the parent (and its children)
|
||||
// from the parentAttachmentsMap so that it's children are not rolled for later in the process.
|
||||
if (parentAttachmentsMap.has(insuredItem._id))
|
||||
{
|
||||
if (parentAttachmentsMap.has(insuredItem._id)) {
|
||||
// This call will also return the parent item itself, queueing it for deletion as well.
|
||||
const itemAndChildren = this.itemHelper.findAndReturnChildrenAsItems(
|
||||
insured.items,
|
||||
insuredItem._id,
|
||||
);
|
||||
for (const item of itemAndChildren)
|
||||
{
|
||||
for (const item of itemAndChildren) {
|
||||
toDelete.add(item._id);
|
||||
}
|
||||
|
||||
// Remove the parent (and its children) from the parentAttachmentsMap.
|
||||
parentAttachmentsMap.delete(insuredItem._id);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
// This item doesn't have any children. Simply mark it for deletion.
|
||||
toDelete.add(insuredItem._id);
|
||||
}
|
||||
@ -432,14 +394,11 @@ export class InsuranceController
|
||||
itemsMap: Map<string, Item>,
|
||||
traderId: string,
|
||||
toDelete: Set<string>,
|
||||
): void
|
||||
{
|
||||
for (const [parentId, attachmentItems] of mainParentToAttachmentsMap)
|
||||
{
|
||||
): void {
|
||||
for (const [parentId, attachmentItems] of mainParentToAttachmentsMap) {
|
||||
// Skip processing if parentId is already marked for deletion, as all attachments for that parent will
|
||||
// already be marked for deletion as well.
|
||||
if (toDelete.has(parentId))
|
||||
{
|
||||
if (toDelete.has(parentId)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -464,8 +423,7 @@ export class InsuranceController
|
||||
* @param toDelete The array that accumulates the IDs of the items to be deleted.
|
||||
* @returns void
|
||||
*/
|
||||
protected processAttachmentByParent(attachments: Item[], traderId: string, toDelete: Set<string>): void
|
||||
{
|
||||
protected processAttachmentByParent(attachments: Item[], traderId: string, toDelete: Set<string>): void {
|
||||
// Create dict of item ids + their flea/handbook price (highest is chosen)
|
||||
const weightedAttachmentByPrice = this.weightAttachmentsByPrice(attachments);
|
||||
|
||||
@ -474,8 +432,7 @@ export class InsuranceController
|
||||
|
||||
// Create prob array and add all attachments with rouble price as the weight
|
||||
const attachmentsProbabilityArray = new ProbabilityObjectArray<string, number>(this.mathUtil, this.cloner);
|
||||
for (const attachmentTpl of Object.keys(weightedAttachmentByPrice))
|
||||
{
|
||||
for (const attachmentTpl of Object.keys(weightedAttachmentByPrice)) {
|
||||
attachmentsProbabilityArray.push(
|
||||
new ProbabilityObject(attachmentTpl, weightedAttachmentByPrice[attachmentTpl]),
|
||||
);
|
||||
@ -483,8 +440,7 @@ export class InsuranceController
|
||||
|
||||
// Draw x attachments from weighted array to remove from parent, remove from pool after being picked
|
||||
const attachmentIdsToRemove = attachmentsProbabilityArray.draw(countOfAttachmentsToRemove, false);
|
||||
for (const attachmentId of attachmentIdsToRemove)
|
||||
{
|
||||
for (const attachmentId of attachmentIdsToRemove) {
|
||||
toDelete.add(attachmentId);
|
||||
}
|
||||
|
||||
@ -497,11 +453,9 @@ export class InsuranceController
|
||||
attachmentIdsToRemove: string[],
|
||||
attachments: Item[],
|
||||
attachmentPrices: Record<string, number>,
|
||||
): void
|
||||
{
|
||||
): void {
|
||||
let index = 1;
|
||||
for (const attachmentId of attachmentIdsToRemove)
|
||||
{
|
||||
for (const attachmentId of attachmentIdsToRemove) {
|
||||
this.logger.debug(
|
||||
`Attachment ${index} Id: ${attachmentId} Tpl: ${
|
||||
attachments.find((x) => x._id === attachmentId)?._tpl
|
||||
@ -511,16 +465,13 @@ export class InsuranceController
|
||||
}
|
||||
}
|
||||
|
||||
protected weightAttachmentsByPrice(attachments: Item[]): Record<string, number>
|
||||
{
|
||||
protected weightAttachmentsByPrice(attachments: Item[]): Record<string, number> {
|
||||
const result: Record<string, number> = {};
|
||||
|
||||
// Get a dictionary of item tpls + their rouble price
|
||||
for (const attachment of attachments)
|
||||
{
|
||||
for (const attachment of attachments) {
|
||||
const price = this.ragfairPriceService.getDynamicItemPrice(attachment._tpl, Money.ROUBLES);
|
||||
if (price)
|
||||
{
|
||||
if (price) {
|
||||
result[attachment._id] = Math.round(price);
|
||||
}
|
||||
}
|
||||
@ -536,25 +487,20 @@ export class InsuranceController
|
||||
* @param traderId Trader attachment insured against
|
||||
* @returns Attachment count to remove
|
||||
*/
|
||||
protected getAttachmentCountToRemove(weightedAttachmentByPrice: Record<string, number>, traderId: string): number
|
||||
{
|
||||
protected getAttachmentCountToRemove(weightedAttachmentByPrice: Record<string, number>, traderId: string): number {
|
||||
let removeCount = 0;
|
||||
|
||||
if (this.randomUtil.getChance100(this.insuranceConfig.chanceNoAttachmentsTakenPercent))
|
||||
{
|
||||
if (this.randomUtil.getChance100(this.insuranceConfig.chanceNoAttachmentsTakenPercent)) {
|
||||
return removeCount;
|
||||
}
|
||||
|
||||
for (const attachmentId of Object.keys(weightedAttachmentByPrice))
|
||||
{
|
||||
for (const attachmentId of Object.keys(weightedAttachmentByPrice)) {
|
||||
// Below min price to be taken, skip
|
||||
if (weightedAttachmentByPrice[attachmentId] < this.insuranceConfig.minAttachmentRoublePriceToBeTaken)
|
||||
{
|
||||
if (weightedAttachmentByPrice[attachmentId] < this.insuranceConfig.minAttachmentRoublePriceToBeTaken) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (this.rollForDelete(traderId))
|
||||
{
|
||||
if (this.rollForDelete(traderId)) {
|
||||
removeCount++;
|
||||
}
|
||||
}
|
||||
@ -569,8 +515,7 @@ export class InsuranceController
|
||||
* @param toDelete The items that should be deleted.
|
||||
* @returns void
|
||||
*/
|
||||
protected removeItemsFromInsurance(insured: Insurance, toDelete: Set<string>): void
|
||||
{
|
||||
protected removeItemsFromInsurance(insured: Insurance, toDelete: Set<string>): void {
|
||||
insured.items = insured.items.filter((item) => !toDelete.has(item._id));
|
||||
}
|
||||
|
||||
@ -581,8 +526,7 @@ export class InsuranceController
|
||||
* @param insurance The context of insurance to use.
|
||||
* @returns void
|
||||
*/
|
||||
protected sendMail(sessionID: string, insurance: Insurance): void
|
||||
{
|
||||
protected sendMail(sessionID: string, insurance: Insurance): void {
|
||||
const labsId = "laboratory";
|
||||
// After all of the item filtering that we've done, if there are no items remaining, the insurance has
|
||||
// successfully "failed" to return anything and an appropriate message should be sent to the player.
|
||||
@ -590,21 +534,17 @@ export class InsuranceController
|
||||
|
||||
// Map is labs + insurance is disabled in base.json
|
||||
if (
|
||||
insurance.systemData?.location?.toLowerCase() === labsId
|
||||
&& !(this.databaseService.getLocation(labsId).base.Insurance)
|
||||
)
|
||||
{
|
||||
insurance.systemData?.location?.toLowerCase() === labsId &&
|
||||
!this.databaseService.getLocation(labsId).base.Insurance
|
||||
) {
|
||||
// Trader has labs-specific messages
|
||||
// Wipe out returnable items
|
||||
if (traderDialogMessages.insuranceFailedLabs?.length > 0)
|
||||
{
|
||||
if (traderDialogMessages.insuranceFailedLabs?.length > 0) {
|
||||
const insuranceFailedLabTemplates = traderDialogMessages.insuranceFailedLabs;
|
||||
insurance.messageTemplateId = this.randomUtil.getArrayValue(insuranceFailedLabTemplates);
|
||||
insurance.items = [];
|
||||
}
|
||||
}
|
||||
else if (insurance.items.length === 0)
|
||||
{
|
||||
} else if (insurance.items.length === 0) {
|
||||
// Not labs and no items to return
|
||||
const insuranceFailedTemplates = traderDialogMessages.insuranceFailed;
|
||||
insurance.messageTemplateId = this.randomUtil.getArrayValue(insuranceFailedTemplates);
|
||||
@ -630,11 +570,9 @@ export class InsuranceController
|
||||
* @param insuredItem Optional. The item to roll for. Only used for logging.
|
||||
* @returns true if the insured item should be removed from inventory, false otherwise, or undefined on error.
|
||||
*/
|
||||
protected rollForDelete(traderId: string, insuredItem?: Item): boolean | undefined
|
||||
{
|
||||
protected rollForDelete(traderId: string, insuredItem?: Item): boolean | undefined {
|
||||
const trader = this.traderHelper.getTraderById(traderId);
|
||||
if (!trader)
|
||||
{
|
||||
if (!trader) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@ -664,26 +602,25 @@ export class InsuranceController
|
||||
* @param sessionID Session id
|
||||
* @returns IItemEventRouterResponse object to send to client
|
||||
*/
|
||||
public insure(pmcData: IPmcData, body: IInsureRequestData, sessionID: string): IItemEventRouterResponse
|
||||
{
|
||||
public insure(pmcData: IPmcData, body: IInsureRequestData, sessionID: string): IItemEventRouterResponse {
|
||||
const output = this.eventOutputHolder.getOutput(sessionID);
|
||||
const itemsToInsureCount = body.items.length;
|
||||
const itemsToPay = [];
|
||||
const inventoryItemsHash = {};
|
||||
// Create hash of player inventory items (keyed by item id)
|
||||
for (const item of pmcData.Inventory.items)
|
||||
{
|
||||
for (const item of pmcData.Inventory.items) {
|
||||
inventoryItemsHash[item._id] = item;
|
||||
}
|
||||
|
||||
// Get price of all items being insured
|
||||
for (const key of body.items)
|
||||
{
|
||||
for (const key of body.items) {
|
||||
itemsToPay.push({
|
||||
id: Money.ROUBLES, // TODO: update to handle different currencies
|
||||
count: this.insuranceService.getRoublePriceToInsureItemWithTrader(
|
||||
pmcData, inventoryItemsHash[key],
|
||||
body.tid),
|
||||
pmcData,
|
||||
inventoryItemsHash[key],
|
||||
body.tid,
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
@ -699,14 +636,12 @@ export class InsuranceController
|
||||
|
||||
// pay for the item insurance
|
||||
this.paymentService.payMoney(pmcData, options, sessionID, output);
|
||||
if (output.warnings.length > 0)
|
||||
{
|
||||
if (output.warnings.length > 0) {
|
||||
return output;
|
||||
}
|
||||
|
||||
// add items to InsuredItems list once money has been paid
|
||||
for (const key of body.items)
|
||||
{
|
||||
for (const key of body.items) {
|
||||
pmcData.InsuredItems.push({ tid: body.tid, itemId: inventoryItemsHash[key]._id });
|
||||
}
|
||||
|
||||
@ -723,33 +658,30 @@ export class InsuranceController
|
||||
* @param sessionID session id
|
||||
* @returns IGetInsuranceCostResponseData object to send to client
|
||||
*/
|
||||
public cost(request: IGetInsuranceCostRequestData, sessionID: string): IGetInsuranceCostResponseData
|
||||
{
|
||||
public cost(request: IGetInsuranceCostRequestData, sessionID: string): IGetInsuranceCostResponseData {
|
||||
const response: IGetInsuranceCostResponseData = {};
|
||||
const pmcData = this.profileHelper.getPmcProfile(sessionID);
|
||||
const inventoryItemsHash: Record<string, Item> = {};
|
||||
|
||||
for (const item of pmcData.Inventory.items)
|
||||
{
|
||||
for (const item of pmcData.Inventory.items) {
|
||||
inventoryItemsHash[item._id] = item;
|
||||
}
|
||||
|
||||
// Loop over each trader in request
|
||||
for (const trader of request.traders)
|
||||
{
|
||||
for (const trader of request.traders) {
|
||||
const items: Record<string, number> = {};
|
||||
|
||||
for (const itemId of request.items)
|
||||
{
|
||||
for (const itemId of request.items) {
|
||||
// Ensure hash has item in it
|
||||
if (!inventoryItemsHash[itemId])
|
||||
{
|
||||
if (!inventoryItemsHash[itemId]) {
|
||||
this.logger.debug(`Item with id: ${itemId} missing from player inventory, skipping`);
|
||||
continue;
|
||||
}
|
||||
items[inventoryItemsHash[itemId]._tpl] = this.insuranceService.getRoublePriceToInsureItemWithTrader(
|
||||
pmcData, inventoryItemsHash[itemId],
|
||||
trader);
|
||||
pmcData,
|
||||
inventoryItemsHash[itemId],
|
||||
trader,
|
||||
);
|
||||
}
|
||||
|
||||
response[trader] = items;
|
||||
@ -760,8 +692,7 @@ export class InsuranceController
|
||||
}
|
||||
|
||||
// Represents an insurance item that has had it's common locale-name and value added to it.
|
||||
interface EnrichedItem extends Item
|
||||
{
|
||||
name: string
|
||||
dynamicPrice: number
|
||||
interface EnrichedItem extends Item {
|
||||
name: string;
|
||||
dynamicPrice: number;
|
||||
}
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { inject, injectable } from "tsyringe";
|
||||
import { LootGenerator } from "@spt/generators/LootGenerator";
|
||||
import { HideoutHelper } from "@spt/helpers/HideoutHelper";
|
||||
import { InventoryHelper } from "@spt/helpers/InventoryHelper";
|
||||
@ -44,14 +43,14 @@ import { LocalisationService } from "@spt/services/LocalisationService";
|
||||
import { MapMarkerService } from "@spt/services/MapMarkerService";
|
||||
import { PlayerService } from "@spt/services/PlayerService";
|
||||
import { RagfairOfferService } from "@spt/services/RagfairOfferService";
|
||||
import { ICloner } from "@spt/utils/cloners/ICloner";
|
||||
import { HashUtil } from "@spt/utils/HashUtil";
|
||||
import { HttpResponseUtil } from "@spt/utils/HttpResponseUtil";
|
||||
import { RandomUtil } from "@spt/utils/RandomUtil";
|
||||
import { ICloner } from "@spt/utils/cloners/ICloner";
|
||||
import { inject, injectable } from "tsyringe";
|
||||
|
||||
@injectable()
|
||||
export class InventoryController
|
||||
{
|
||||
export class InventoryController {
|
||||
constructor(
|
||||
@inject("PrimaryLogger") protected logger: ILogger,
|
||||
@inject("HashUtil") protected hashUtil: HashUtil,
|
||||
@ -74,8 +73,7 @@ export class InventoryController
|
||||
@inject("EventOutputHolder") protected eventOutputHolder: EventOutputHolder,
|
||||
@inject("HttpResponseUtil") protected httpResponseUtil: HttpResponseUtil,
|
||||
@inject("PrimaryCloner") protected cloner: ICloner,
|
||||
)
|
||||
{}
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Move Item
|
||||
@ -92,28 +90,23 @@ export class InventoryController
|
||||
moveRequest: IInventoryMoveRequestData,
|
||||
sessionID: string,
|
||||
output: IItemEventRouterResponse,
|
||||
): void
|
||||
{
|
||||
if (output.warnings.length > 0)
|
||||
{
|
||||
): void {
|
||||
if (output.warnings.length > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Changes made to result apply to character inventory
|
||||
const ownerInventoryItems = this.inventoryHelper.getOwnerInventoryItems(moveRequest, sessionID);
|
||||
if (ownerInventoryItems.sameInventory)
|
||||
{
|
||||
if (ownerInventoryItems.sameInventory) {
|
||||
// Dont move items from trader to profile, this can happen when editing a traders preset weapons
|
||||
if (moveRequest.fromOwner?.type === "Trader" && !ownerInventoryItems.isMail)
|
||||
{
|
||||
if (moveRequest.fromOwner?.type === "Trader" && !ownerInventoryItems.isMail) {
|
||||
this.appendTraderExploitErrorResponse(output);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for item in inventory before allowing internal transfer
|
||||
const originalItemLocation = ownerInventoryItems.from.find((item) => item._id === moveRequest.item);
|
||||
if (!originalItemLocation)
|
||||
{
|
||||
if (!originalItemLocation) {
|
||||
// Internal item move but item never existed, possible dupe glitch
|
||||
this.appendTraderExploitErrorResponse(output);
|
||||
return;
|
||||
@ -122,20 +115,16 @@ export class InventoryController
|
||||
const originalLocationSlotId = originalItemLocation?.slotId;
|
||||
|
||||
const moveResult = this.inventoryHelper.moveItemInternal(pmcData, ownerInventoryItems.from, moveRequest);
|
||||
if (!moveResult.success)
|
||||
{
|
||||
if (!moveResult.success) {
|
||||
this.httpResponseUtil.appendErrorToOutput(output, moveResult.errorMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
// Item is moving into or out of place of fame dogtag slot
|
||||
if (moveRequest.to.container.startsWith("dogtag") || originalLocationSlotId?.startsWith("dogtag"))
|
||||
{
|
||||
if (moveRequest.to.container.startsWith("dogtag") || originalLocationSlotId?.startsWith("dogtag")) {
|
||||
this.hideoutHelper.applyPlaceOfFameDogtagBonus(pmcData);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
this.inventoryHelper.moveItemToProfile(ownerInventoryItems.from, ownerInventoryItems.to, moveRequest);
|
||||
}
|
||||
}
|
||||
@ -145,8 +134,7 @@ export class InventoryController
|
||||
* @param output Item event router response
|
||||
* @returns Item event router response
|
||||
*/
|
||||
protected appendTraderExploitErrorResponse(output: IItemEventRouterResponse): void
|
||||
{
|
||||
protected appendTraderExploitErrorResponse(output: IItemEventRouterResponse): void {
|
||||
this.httpResponseUtil.appendErrorToOutput(
|
||||
output,
|
||||
this.localisationService.getText("inventory-edit_trader_item"),
|
||||
@ -164,17 +152,15 @@ export class InventoryController
|
||||
request: IInventoryRemoveRequestData,
|
||||
sessionID: string,
|
||||
output: IItemEventRouterResponse,
|
||||
): void
|
||||
{
|
||||
if (request.fromOwner?.type === "Mail")
|
||||
{
|
||||
): void {
|
||||
if (request.fromOwner?.type === "Mail") {
|
||||
this.inventoryHelper.removeItemAndChildrenFromMailRewards(sessionID, request, output);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const profileToRemoveItemFrom
|
||||
= !request.fromOwner || request.fromOwner.id === pmcData._id
|
||||
const profileToRemoveItemFrom =
|
||||
!request.fromOwner || request.fromOwner.id === pmcData._id
|
||||
? pmcData
|
||||
: this.profileHelper.getFullProfile(sessionID).characters.scav;
|
||||
|
||||
@ -195,22 +181,19 @@ export class InventoryController
|
||||
request: IInventorySplitRequestData,
|
||||
sessionID: string,
|
||||
output: IItemEventRouterResponse,
|
||||
): IItemEventRouterResponse
|
||||
{
|
||||
): IItemEventRouterResponse {
|
||||
// Changes made to result apply to character inventory
|
||||
const inventoryItems = this.inventoryHelper.getOwnerInventoryItems(request, sessionID);
|
||||
|
||||
// Handle cartridge edge-case
|
||||
if (!request.container.location && request.container.container === "cartridges")
|
||||
{
|
||||
if (!request.container.location && request.container.container === "cartridges") {
|
||||
const matchingItems = inventoryItems.to.filter((x) => x.parentId === request.container.id);
|
||||
request.container.location = matchingItems.length; // Wrong location for first cartridge
|
||||
}
|
||||
|
||||
// The item being merged has three possible sources: pmc, scav or mail, getOwnerInventoryItems() handles getting correct one
|
||||
const itemToSplit = inventoryItems.from.find((x) => x._id === request.splitItem);
|
||||
if (!itemToSplit)
|
||||
{
|
||||
if (!itemToSplit) {
|
||||
const errorMessage = `Unable to split stack as source item: ${request.splitItem} cannot be found`;
|
||||
this.logger.error(errorMessage);
|
||||
|
||||
@ -258,15 +241,13 @@ export class InventoryController
|
||||
body: IInventoryMergeRequestData,
|
||||
sessionID: string,
|
||||
output: IItemEventRouterResponse,
|
||||
): IItemEventRouterResponse
|
||||
{
|
||||
): IItemEventRouterResponse {
|
||||
// Changes made to result apply to character inventory
|
||||
const inventoryItems = this.inventoryHelper.getOwnerInventoryItems(body, sessionID);
|
||||
|
||||
// Get source item (can be from player or trader or mail)
|
||||
const sourceItem = inventoryItems.from.find((x) => x._id === body.item);
|
||||
if (!sourceItem)
|
||||
{
|
||||
if (!sourceItem) {
|
||||
const errorMessage = `Unable to merge stacks as source item: ${body.with} cannot be found`;
|
||||
this.logger.error(errorMessage);
|
||||
|
||||
@ -277,8 +258,7 @@ export class InventoryController
|
||||
|
||||
// Get item being merged into
|
||||
const destinationItem = inventoryItems.to.find((x) => x._id === body.with);
|
||||
if (!destinationItem)
|
||||
{
|
||||
if (!destinationItem) {
|
||||
const errorMessage = `Unable to merge stacks as destination item: ${body.with} cannot be found`;
|
||||
this.logger.error(errorMessage);
|
||||
|
||||
@ -287,25 +267,20 @@ export class InventoryController
|
||||
return output;
|
||||
}
|
||||
|
||||
if (!destinationItem.upd?.StackObjectsCount)
|
||||
{
|
||||
if (!destinationItem.upd?.StackObjectsCount) {
|
||||
// No stackcount on destination, add one
|
||||
destinationItem.upd = { StackObjectsCount: 1 };
|
||||
}
|
||||
|
||||
if (!sourceItem.upd)
|
||||
{
|
||||
if (!sourceItem.upd) {
|
||||
sourceItem.upd = { StackObjectsCount: 1 };
|
||||
}
|
||||
else if (!sourceItem.upd.StackObjectsCount)
|
||||
{
|
||||
} else if (!sourceItem.upd.StackObjectsCount) {
|
||||
// Items pulled out of raid can have no stackcount if the stack should be 1
|
||||
sourceItem.upd.StackObjectsCount = 1;
|
||||
}
|
||||
|
||||
// Remove FiR status from destination stack when source stack has no FiR but destination does
|
||||
if (!sourceItem.upd.SpawnedInSession && destinationItem.upd.SpawnedInSession)
|
||||
{
|
||||
if (!sourceItem.upd.SpawnedInSession && destinationItem.upd.SpawnedInSession) {
|
||||
delete destinationItem.upd.SpawnedInSession;
|
||||
}
|
||||
|
||||
@ -313,8 +288,7 @@ export class InventoryController
|
||||
output.profileChanges[sessionID].items.del.push({ _id: sourceItem._id }); // Inform client source item being deleted
|
||||
|
||||
const indexOfItemToRemove = inventoryItems.from.findIndex((x) => x._id === sourceItem._id);
|
||||
if (indexOfItemToRemove === -1)
|
||||
{
|
||||
if (indexOfItemToRemove === -1) {
|
||||
const errorMessage = `Unable to find item: ${sourceItem._id} to remove from sender inventory`;
|
||||
this.logger.error(errorMessage);
|
||||
|
||||
@ -342,14 +316,12 @@ export class InventoryController
|
||||
body: IInventoryTransferRequestData,
|
||||
sessionID: string,
|
||||
output: IItemEventRouterResponse,
|
||||
): IItemEventRouterResponse
|
||||
{
|
||||
): IItemEventRouterResponse {
|
||||
const inventoryItems = this.inventoryHelper.getOwnerInventoryItems(body, sessionID);
|
||||
const sourceItem = inventoryItems.from.find((item) => item._id === body.item);
|
||||
const destinationItem = inventoryItems.to.find((item) => item._id === body.with);
|
||||
|
||||
if (!sourceItem)
|
||||
{
|
||||
if (!sourceItem) {
|
||||
const errorMessage = `Unable to transfer stack, cannot find source: ${body.item}`;
|
||||
this.logger.error(errorMessage);
|
||||
|
||||
@ -358,8 +330,7 @@ export class InventoryController
|
||||
return output;
|
||||
}
|
||||
|
||||
if (!destinationItem)
|
||||
{
|
||||
if (!destinationItem) {
|
||||
const errorMessage = `Unable to transfer stack, cannot find destination: ${body.with} `;
|
||||
this.logger.error(errorMessage);
|
||||
|
||||
@ -368,25 +339,20 @@ export class InventoryController
|
||||
return output;
|
||||
}
|
||||
|
||||
if (!sourceItem.upd)
|
||||
{
|
||||
if (!sourceItem.upd) {
|
||||
sourceItem.upd = { StackObjectsCount: 1 };
|
||||
}
|
||||
|
||||
const sourceStackCount = sourceItem.upd.StackObjectsCount;
|
||||
if (sourceStackCount > body.count)
|
||||
{
|
||||
if (sourceStackCount > body.count) {
|
||||
// Source items stack count greater than new desired count
|
||||
sourceItem.upd.StackObjectsCount = sourceStackCount - body.count;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
// Moving a full stack onto a smaller stack
|
||||
sourceItem.upd.StackObjectsCount = sourceStackCount - 1;
|
||||
}
|
||||
|
||||
if (!destinationItem.upd)
|
||||
{
|
||||
if (!destinationItem.upd) {
|
||||
destinationItem.upd = { StackObjectsCount: 1 };
|
||||
}
|
||||
const destinationStackCount = destinationItem.upd.StackObjectsCount;
|
||||
@ -404,25 +370,31 @@ export class InventoryController
|
||||
pmcData: IPmcData,
|
||||
request: IInventorySwapRequestData,
|
||||
sessionID: string,
|
||||
): IItemEventRouterResponse
|
||||
{
|
||||
): IItemEventRouterResponse {
|
||||
// During post-raid scav transfer, the swap may be in the scav inventory
|
||||
let playerData = pmcData;
|
||||
if (request.fromOwner?.type === "Profile" && request.fromOwner.id !== playerData._id)
|
||||
{
|
||||
if (request.fromOwner?.type === "Profile" && request.fromOwner.id !== playerData._id) {
|
||||
playerData = this.profileHelper.getScavProfile(sessionID);
|
||||
}
|
||||
|
||||
const itemOne = playerData.Inventory.items.find((x) => x._id === request.item);
|
||||
if (!itemOne)
|
||||
{
|
||||
this.logger.error(this.localisationService.getText("inventory-unable_to_find_item_to_swap", { item1Id: request.item, item2Id: request.item2 }));
|
||||
if (!itemOne) {
|
||||
this.logger.error(
|
||||
this.localisationService.getText("inventory-unable_to_find_item_to_swap", {
|
||||
item1Id: request.item,
|
||||
item2Id: request.item2,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
const itemTwo = playerData.Inventory.items.find((x) => x._id === request.item2);
|
||||
if (!itemTwo)
|
||||
{
|
||||
this.logger.error(this.localisationService.getText("inventory-unable_to_find_item_to_swap", { item1Id: request.item2, item2Id: request.item }));
|
||||
if (!itemTwo) {
|
||||
this.logger.error(
|
||||
this.localisationService.getText("inventory-unable_to_find_item_to_swap", {
|
||||
item1Id: request.item2,
|
||||
item2Id: request.item,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
// to.id is the parentid
|
||||
@ -432,23 +404,17 @@ export class InventoryController
|
||||
itemOne.slotId = request.to.container;
|
||||
|
||||
// Request object has location data, add it in, otherwise remove existing location from object
|
||||
if (request.to.location)
|
||||
{
|
||||
if (request.to.location) {
|
||||
itemOne.location = request.to.location;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
delete itemOne.location;
|
||||
}
|
||||
|
||||
itemTwo.parentId = request.to2.id;
|
||||
itemTwo.slotId = request.to2.container;
|
||||
if (request.to2.location)
|
||||
{
|
||||
if (request.to2.location) {
|
||||
itemTwo.location = request.to2.location;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
delete itemTwo.location;
|
||||
}
|
||||
|
||||
@ -463,22 +429,21 @@ export class InventoryController
|
||||
pmcData: IPmcData,
|
||||
request: IInventoryFoldRequestData,
|
||||
sessionID: string,
|
||||
): IItemEventRouterResponse
|
||||
{
|
||||
): IItemEventRouterResponse {
|
||||
// May need to reassign to scav profile
|
||||
let playerData = pmcData;
|
||||
|
||||
// We may be folding data on scav profile, get that profile instead
|
||||
if (request.fromOwner?.type === "Profile" && request.fromOwner.id !== playerData._id)
|
||||
{
|
||||
if (request.fromOwner?.type === "Profile" && request.fromOwner.id !== playerData._id) {
|
||||
playerData = this.profileHelper.getScavProfile(sessionID);
|
||||
}
|
||||
|
||||
const itemToFold = playerData.Inventory.items.find((item) => item?._id === request.item);
|
||||
if (!itemToFold)
|
||||
{
|
||||
if (!itemToFold) {
|
||||
// Item not found
|
||||
this.logger.warning(this.localisationService.getText("inventory-unable_to_fold_item_not_found_in_inventory", request.item));
|
||||
this.logger.warning(
|
||||
this.localisationService.getText("inventory-unable_to_fold_item_not_found_in_inventory", request.item),
|
||||
);
|
||||
|
||||
return { warnings: [], profileChanges: {} };
|
||||
}
|
||||
@ -502,20 +467,17 @@ export class InventoryController
|
||||
pmcData: IPmcData,
|
||||
body: IInventoryToggleRequestData,
|
||||
sessionID: string,
|
||||
): IItemEventRouterResponse
|
||||
{
|
||||
): IItemEventRouterResponse {
|
||||
// May need to reassign to scav profile
|
||||
let playerData = pmcData;
|
||||
|
||||
// Fix for toggling items while on they're in the Scav inventory
|
||||
if (body.fromOwner?.type === "Profile" && body.fromOwner.id !== playerData._id)
|
||||
{
|
||||
if (body.fromOwner?.type === "Profile" && body.fromOwner.id !== playerData._id) {
|
||||
playerData = this.profileHelper.getScavProfile(sessionID);
|
||||
}
|
||||
|
||||
const itemToToggle = playerData.Inventory.items.find((x) => x._id === body.item);
|
||||
if (itemToToggle)
|
||||
{
|
||||
if (itemToToggle) {
|
||||
this.itemHelper.addUpdObjectToItem(
|
||||
itemToToggle,
|
||||
this.localisationService.getText("inventory-item_to_toggle_missing_upd", itemToToggle._id),
|
||||
@ -538,16 +500,13 @@ export class InventoryController
|
||||
* @param sessionID session id
|
||||
* @returns client response object
|
||||
*/
|
||||
public tagItem(pmcData: IPmcData, body: IInventoryTagRequestData, sessionID: string): IItemEventRouterResponse
|
||||
{
|
||||
public tagItem(pmcData: IPmcData, body: IInventoryTagRequestData, sessionID: string): IItemEventRouterResponse {
|
||||
const itemToTag = pmcData.Inventory.items.find((item) => item._id === body.item);
|
||||
if (!itemToTag)
|
||||
{
|
||||
if (!itemToTag) {
|
||||
return { warnings: [], profileChanges: {} };
|
||||
}
|
||||
|
||||
if (!itemToTag.upd)
|
||||
{
|
||||
if (!itemToTag.upd) {
|
||||
itemToTag.upd = {};
|
||||
}
|
||||
|
||||
@ -564,14 +523,11 @@ export class InventoryController
|
||||
* @param sessionID Session id
|
||||
* @returns IItemEventRouterResponse
|
||||
*/
|
||||
public bindItem(pmcData: IPmcData, bindRequest: IInventoryBindRequestData, sessionID: string): void
|
||||
{
|
||||
public bindItem(pmcData: IPmcData, bindRequest: IInventoryBindRequestData, sessionID: string): void {
|
||||
// TODO - replace with single .find() call
|
||||
for (const index in pmcData.Inventory.fastPanel)
|
||||
{
|
||||
for (const index in pmcData.Inventory.fastPanel) {
|
||||
// Find item with existing item in it and remove existing binding, you cant have same item bound to more than 1 slot
|
||||
if (pmcData.Inventory.fastPanel[index] === bindRequest.item)
|
||||
{
|
||||
if (pmcData.Inventory.fastPanel[index] === bindRequest.item) {
|
||||
pmcData.Inventory.fastPanel[index] = "";
|
||||
|
||||
break;
|
||||
@ -595,8 +551,7 @@ export class InventoryController
|
||||
request: IInventoryBindRequestData,
|
||||
sessionID: string,
|
||||
output: IItemEventRouterResponse,
|
||||
): void
|
||||
{
|
||||
): void {
|
||||
// Remove kvp from requested fast panel index
|
||||
delete pmcData.Inventory.fastPanel[request.index];
|
||||
}
|
||||
@ -614,48 +569,37 @@ export class InventoryController
|
||||
body: IInventoryExamineRequestData,
|
||||
sessionID: string,
|
||||
output: IItemEventRouterResponse,
|
||||
): IItemEventRouterResponse
|
||||
{
|
||||
): IItemEventRouterResponse {
|
||||
let itemId = "";
|
||||
if ("fromOwner" in body)
|
||||
{
|
||||
try
|
||||
{
|
||||
if ("fromOwner" in body) {
|
||||
try {
|
||||
itemId = this.getExaminedItemTpl(body);
|
||||
}
|
||||
catch
|
||||
{
|
||||
} catch {
|
||||
this.logger.error(this.localisationService.getText("inventory-examine_item_does_not_exist", body.item));
|
||||
}
|
||||
|
||||
// get hideout item
|
||||
if (body.fromOwner.type === "HideoutProduction")
|
||||
{
|
||||
if (body.fromOwner.type === "HideoutProduction") {
|
||||
itemId = body.item;
|
||||
}
|
||||
}
|
||||
|
||||
if (!itemId)
|
||||
{
|
||||
if (!itemId) {
|
||||
// item template
|
||||
if (body.item in this.databaseService.getItems())
|
||||
{
|
||||
if (body.item in this.databaseService.getItems()) {
|
||||
itemId = body.item;
|
||||
}
|
||||
}
|
||||
|
||||
if (!itemId)
|
||||
{
|
||||
if (!itemId) {
|
||||
// Player inventory
|
||||
const target = pmcData.Inventory.items.find((item) => item._id === body.item);
|
||||
if (target)
|
||||
{
|
||||
if (target) {
|
||||
itemId = target._tpl;
|
||||
}
|
||||
}
|
||||
|
||||
if (itemId)
|
||||
{
|
||||
if (itemId) {
|
||||
const fullProfile = this.profileHelper.getFullProfile(sessionID);
|
||||
this.flagItemsAsInspectedAndRewardXp([itemId], fullProfile);
|
||||
}
|
||||
@ -668,14 +612,13 @@ export class InventoryController
|
||||
* @param itemTpls Inspected item tpls
|
||||
* @param fullProfile Profile to add xp to
|
||||
*/
|
||||
protected flagItemsAsInspectedAndRewardXp(itemTpls: string[], fullProfile: ISptProfile): void
|
||||
{
|
||||
for (const itemTpl of itemTpls)
|
||||
{
|
||||
protected flagItemsAsInspectedAndRewardXp(itemTpls: string[], fullProfile: ISptProfile): void {
|
||||
for (const itemTpl of itemTpls) {
|
||||
const item = this.itemHelper.getItem(itemTpl);
|
||||
if (!item[0])
|
||||
{
|
||||
this.logger.warning(this.localisationService.getText("inventory-unable_to_inspect_item_not_in_db", itemTpl));
|
||||
if (!item[0]) {
|
||||
this.logger.warning(
|
||||
this.localisationService.getText("inventory-unable_to_inspect_item_not_in_db", itemTpl),
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
@ -700,47 +643,40 @@ export class InventoryController
|
||||
* @param request Response request
|
||||
* @returns tplId
|
||||
*/
|
||||
protected getExaminedItemTpl(request: IInventoryExamineRequestData): string
|
||||
{
|
||||
if (this.presetHelper.isPreset(request.item))
|
||||
{
|
||||
protected getExaminedItemTpl(request: IInventoryExamineRequestData): string {
|
||||
if (this.presetHelper.isPreset(request.item)) {
|
||||
return this.presetHelper.getBaseItemTpl(request.item);
|
||||
}
|
||||
|
||||
if (request.fromOwner.id === Traders.FENCE)
|
||||
{
|
||||
if (request.fromOwner.id === Traders.FENCE) {
|
||||
// Get tpl from fence assorts
|
||||
return this.fenceService.getRawFenceAssorts().items.find((x) => x._id === request.item)?._tpl;
|
||||
}
|
||||
|
||||
if (request.fromOwner.type === "Trader")
|
||||
{
|
||||
if (request.fromOwner.type === "Trader") {
|
||||
// Not fence
|
||||
// get tpl from trader assort
|
||||
return this.databaseService.getTrader(request.fromOwner.id).assort.items
|
||||
.find((item) => item._id === request.item)?._tpl;
|
||||
return this.databaseService
|
||||
.getTrader(request.fromOwner.id)
|
||||
.assort.items.find((item) => item._id === request.item)?._tpl;
|
||||
}
|
||||
|
||||
if (request.fromOwner.type === "RagFair")
|
||||
{
|
||||
if (request.fromOwner.type === "RagFair") {
|
||||
// Try to get tplid from items.json first
|
||||
const item = this.itemHelper.getItem(request.item);
|
||||
if (item[0])
|
||||
{
|
||||
if (item[0]) {
|
||||
return item[1]._id;
|
||||
}
|
||||
|
||||
// Try alternate way of getting offer if first approach fails
|
||||
let offer = this.ragfairOfferService.getOfferByOfferId(request.item);
|
||||
if (!offer)
|
||||
{
|
||||
if (!offer) {
|
||||
offer = this.ragfairOfferService.getOfferByOfferId(request.fromOwner.id);
|
||||
}
|
||||
|
||||
// Try find examine item inside offer items array
|
||||
const matchingItem = offer.items.find((offerItem) => offerItem._id === request.item);
|
||||
if (matchingItem)
|
||||
{
|
||||
if (matchingItem) {
|
||||
return matchingItem._tpl;
|
||||
}
|
||||
|
||||
@ -753,10 +689,8 @@ export class InventoryController
|
||||
pmcData: IPmcData,
|
||||
body: IInventoryReadEncyclopediaRequestData,
|
||||
sessionID: string,
|
||||
): IItemEventRouterResponse
|
||||
{
|
||||
for (const id of body.ids)
|
||||
{
|
||||
): IItemEventRouterResponse {
|
||||
for (const id of body.ids) {
|
||||
pmcData.Encyclopedia[id] = true;
|
||||
}
|
||||
|
||||
@ -770,26 +704,22 @@ export class InventoryController
|
||||
* @param request sort request
|
||||
* @param sessionID Session id
|
||||
*/
|
||||
public sortInventory(pmcData: IPmcData, request: IInventorySortRequestData, sessionID: string): void
|
||||
{
|
||||
for (const change of request.changedItems)
|
||||
{
|
||||
public sortInventory(pmcData: IPmcData, request: IInventorySortRequestData, sessionID: string): void {
|
||||
for (const change of request.changedItems) {
|
||||
const inventoryItem = pmcData.Inventory.items.find((item) => item._id === change._id);
|
||||
if (!inventoryItem)
|
||||
{
|
||||
this.logger.error(this.localisationService.getText("inventory-unable_to_sort_inventory_restart_game", change._id));
|
||||
if (!inventoryItem) {
|
||||
this.logger.error(
|
||||
this.localisationService.getText("inventory-unable_to_sort_inventory_restart_game", change._id),
|
||||
);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
inventoryItem.parentId = change.parentId;
|
||||
inventoryItem.slotId = change.slotId;
|
||||
if (change.location)
|
||||
{
|
||||
if (change.location) {
|
||||
inventoryItem.location = change.location;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
delete inventoryItem.location;
|
||||
}
|
||||
}
|
||||
@ -808,8 +738,7 @@ export class InventoryController
|
||||
request: IInventoryCreateMarkerRequestData,
|
||||
sessionID: string,
|
||||
output: IItemEventRouterResponse,
|
||||
): void
|
||||
{
|
||||
): void {
|
||||
const adjustedMapItem = this.mapMarkerService.createMarkerOnMap(pmcData, request);
|
||||
|
||||
// Sync with client
|
||||
@ -828,8 +757,7 @@ export class InventoryController
|
||||
request: IInventoryDeleteMarkerRequestData,
|
||||
sessionID: string,
|
||||
output: IItemEventRouterResponse,
|
||||
): void
|
||||
{
|
||||
): void {
|
||||
const mapItem = this.mapMarkerService.deleteMarkerFromMap(pmcData, request);
|
||||
|
||||
// sync with client
|
||||
@ -848,8 +776,7 @@ export class InventoryController
|
||||
request: IInventoryEditMarkerRequestData,
|
||||
sessionID: string,
|
||||
output: IItemEventRouterResponse,
|
||||
): void
|
||||
{
|
||||
): void {
|
||||
const mapItem = this.mapMarkerService.editMarkerOnMap(pmcData, request);
|
||||
|
||||
// sync with client
|
||||
@ -869,8 +796,7 @@ export class InventoryController
|
||||
body: IOpenRandomLootContainerRequestData,
|
||||
sessionID: string,
|
||||
output: IItemEventRouterResponse,
|
||||
): void
|
||||
{
|
||||
): void {
|
||||
/** Container player opened in their inventory */
|
||||
const openedItem = pmcData.Inventory.items.find((item) => item._id === body.item);
|
||||
const containerDetailsDb = this.itemHelper.getItem(openedItem._tpl);
|
||||
@ -878,37 +804,32 @@ export class InventoryController
|
||||
|
||||
let foundInRaid = openedItem.upd?.SpawnedInSession;
|
||||
const rewards: Item[][] = [];
|
||||
const unlockedWeaponCrates = ["665829424de4820934746ce6", "665732e7ac60f009f270d1ef", "665888282c4a1b73af576b77"];// Temp fix for unlocked weapon crate hideout craft
|
||||
if (isSealedWeaponBox || unlockedWeaponCrates.includes(containerDetailsDb[1]._id))
|
||||
{
|
||||
const unlockedWeaponCrates = [
|
||||
"665829424de4820934746ce6",
|
||||
"665732e7ac60f009f270d1ef",
|
||||
"665888282c4a1b73af576b77",
|
||||
]; // Temp fix for unlocked weapon crate hideout craft
|
||||
if (isSealedWeaponBox || unlockedWeaponCrates.includes(containerDetailsDb[1]._id)) {
|
||||
const containerSettings = this.inventoryHelper.getInventoryConfig().sealedAirdropContainer;
|
||||
rewards.push(...this.lootGenerator.getSealedWeaponCaseLoot(containerSettings));
|
||||
|
||||
if (containerSettings.foundInRaid)
|
||||
{
|
||||
if (containerSettings.foundInRaid) {
|
||||
foundInRaid = containerSettings.foundInRaid;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
const rewardContainerDetails = this.inventoryHelper.getRandomLootContainerRewardDetails(openedItem._tpl);
|
||||
if (!rewardContainerDetails || !rewardContainerDetails.rewardCount)
|
||||
{
|
||||
if (!rewardContainerDetails || !rewardContainerDetails.rewardCount) {
|
||||
this.logger.error(`Unable to add loot to container: ${openedItem._tpl}, no rewards found`);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
rewards.push(...this.lootGenerator.getRandomLootContainerLoot(rewardContainerDetails));
|
||||
|
||||
if (rewardContainerDetails.foundInRaid)
|
||||
{
|
||||
if (rewardContainerDetails.foundInRaid) {
|
||||
foundInRaid = rewardContainerDetails.foundInRaid;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (rewards.length > 0)
|
||||
{
|
||||
if (rewards.length > 0) {
|
||||
const addItemsRequest: IAddItemsDirectRequest = {
|
||||
itemsWithModsToAdd: rewards,
|
||||
foundInRaid: foundInRaid,
|
||||
@ -916,8 +837,7 @@ export class InventoryController
|
||||
useSortingTable: true,
|
||||
};
|
||||
this.inventoryHelper.addItemsToStash(sessionID, addItemsRequest, pmcData, output);
|
||||
if (output.warnings.length > 0)
|
||||
{
|
||||
if (output.warnings.length > 0) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -926,19 +846,16 @@ export class InventoryController
|
||||
this.inventoryHelper.removeItem(pmcData, body.item, sessionID, output);
|
||||
}
|
||||
|
||||
public redeemProfileReward(pmcData: IPmcData, request: IRedeemProfileRequestData, sessionId: string): void
|
||||
{
|
||||
public redeemProfileReward(pmcData: IPmcData, request: IRedeemProfileRequestData, sessionId: string): void {
|
||||
const fullProfile = this.profileHelper.getFullProfile(sessionId);
|
||||
for (const event of request.events)
|
||||
{
|
||||
for (const event of request.events) {
|
||||
// Hard coded to `SYSTEM` for now
|
||||
// TODO: make this dynamic
|
||||
const dialog = fullProfile.dialogues["59e7125688a45068a6249071"];
|
||||
const mail = dialog.messages.find((message) => message._id === event.MessageId);
|
||||
const mailEvent = mail.profileChangeEvents.find((changeEvent) => changeEvent._id === event.EventId);
|
||||
|
||||
switch (mailEvent.Type)
|
||||
{
|
||||
switch (mailEvent.Type) {
|
||||
case "TraderSalesSum":
|
||||
pmcData.TradersInfo[mailEvent.entity].salesSum = mailEvent.value;
|
||||
this.traderHelper.lvlUp(mailEvent.entity, pmcData);
|
||||
@ -955,11 +872,9 @@ export class InventoryController
|
||||
this.traderHelper.validateTraderStandingsAndPlayerLevelForProfile(sessionId);
|
||||
this.logger.success(`Set profile xp to: ${mailEvent.value}`);
|
||||
break;
|
||||
case "SkillPoints":
|
||||
{
|
||||
case "SkillPoints": {
|
||||
const profileSkill = pmcData.Skills.Common.find((x) => x.Id === mailEvent.entity);
|
||||
if (!profileSkill)
|
||||
{
|
||||
if (!profileSkill) {
|
||||
this.logger.warning(`Unable to find skill with name: ${mailEvent.entity}`);
|
||||
continue;
|
||||
}
|
||||
@ -967,8 +882,7 @@ export class InventoryController
|
||||
this.logger.success(`Set profile skill: ${mailEvent.entity} to: ${mailEvent.value}`);
|
||||
break;
|
||||
}
|
||||
case "ExamineAllItems":
|
||||
{
|
||||
case "ExamineAllItems": {
|
||||
const itemsToInspect = this.itemHelper.getItems().filter((x) => x._type !== "Node");
|
||||
this.flagItemsAsInspectedAndRewardXp(
|
||||
itemsToInspect.map((x) => x._id),
|
||||
@ -984,8 +898,7 @@ export class InventoryController
|
||||
|
||||
break;
|
||||
case "AssortmentUnlockRule":
|
||||
if (!fullProfile.spt.blacklistedItemTpls)
|
||||
{
|
||||
if (!fullProfile.spt.blacklistedItemTpls) {
|
||||
fullProfile.spt.blacklistedItemTpls = [];
|
||||
}
|
||||
fullProfile.spt.blacklistedItemTpls.push(mailEvent.entity);
|
||||
@ -996,11 +909,9 @@ export class InventoryController
|
||||
const areaName = mailEvent.entity;
|
||||
const newValue = mailEvent.value;
|
||||
const hideoutAreaCode = HideoutAreas[areaName.toUpperCase()];
|
||||
if (hideoutAreaCode !== undefined)
|
||||
{
|
||||
if (hideoutAreaCode !== undefined) {
|
||||
const desiredArea = pmcData.Hideout.Areas.find((area) => area.type === hideoutAreaCode);
|
||||
if (desiredArea)
|
||||
{
|
||||
if (desiredArea) {
|
||||
desiredArea.level = newValue;
|
||||
}
|
||||
}
|
||||
@ -1014,23 +925,17 @@ export class InventoryController
|
||||
}
|
||||
}
|
||||
|
||||
public setFavoriteItem(pmcData: IPmcData, request: ISetFavoriteItems, sessionId: string): void
|
||||
{
|
||||
if (!pmcData.Inventory.favoriteItems)
|
||||
{
|
||||
public setFavoriteItem(pmcData: IPmcData, request: ISetFavoriteItems, sessionId: string): void {
|
||||
if (!pmcData.Inventory.favoriteItems) {
|
||||
pmcData.Inventory.favoriteItems = [];
|
||||
}
|
||||
|
||||
for (const itemId of request.items)
|
||||
{
|
||||
for (const itemId of request.items) {
|
||||
// If id already exists in array, we're removing it
|
||||
const indexOfItemAlreadyFavorited = pmcData.Inventory.favoriteItems.findIndex((x) => x === itemId);
|
||||
if (indexOfItemAlreadyFavorited > -1)
|
||||
{
|
||||
if (indexOfItemAlreadyFavorited > -1) {
|
||||
pmcData.Inventory.favoriteItems.splice(indexOfItemAlreadyFavorited, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
pmcData.Inventory.favoriteItems.push(itemId);
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { inject, injectable } from "tsyringe";
|
||||
import { HttpServerHelper } from "@spt/helpers/HttpServerHelper";
|
||||
import { ProfileHelper } from "@spt/helpers/ProfileHelper";
|
||||
import { PreSptModLoader } from "@spt/loaders/PreSptModLoader";
|
||||
@ -18,10 +17,10 @@ import { LocalisationService } from "@spt/services/LocalisationService";
|
||||
import { HashUtil } from "@spt/utils/HashUtil";
|
||||
import { RandomUtil } from "@spt/utils/RandomUtil";
|
||||
import { TimeUtil } from "@spt/utils/TimeUtil";
|
||||
import { inject, injectable } from "tsyringe";
|
||||
|
||||
@injectable()
|
||||
export class LauncherController
|
||||
{
|
||||
export class LauncherController {
|
||||
protected coreConfig: ICoreConfig;
|
||||
|
||||
constructor(
|
||||
@ -36,16 +35,15 @@ export class LauncherController
|
||||
@inject("LocalisationService") protected localisationService: LocalisationService,
|
||||
@inject("PreSptModLoader") protected preSptModLoader: PreSptModLoader,
|
||||
@inject("ConfigServer") protected configServer: ConfigServer,
|
||||
)
|
||||
{
|
||||
) {
|
||||
this.coreConfig = this.configServer.getConfig(ConfigTypes.CORE);
|
||||
}
|
||||
|
||||
public connect(): IConnectResponse
|
||||
{
|
||||
public connect(): IConnectResponse {
|
||||
// Get all possible profile types + filter out any that are blacklisted
|
||||
const profileKeys = Object.keys(this.databaseService.getProfiles())
|
||||
.filter((key) => !this.coreConfig.features.createNewProfileTypesBlacklist.includes(key));
|
||||
const profileKeys = Object.keys(this.databaseService.getProfiles()).filter(
|
||||
(key) => !this.coreConfig.features.createNewProfileTypesBlacklist.includes(key),
|
||||
);
|
||||
|
||||
return {
|
||||
backendUrl: this.httpServerHelper.getBackendUrl(),
|
||||
@ -59,15 +57,12 @@ export class LauncherController
|
||||
* Get descriptive text for each of the profile edtions a player can choose, keyed by profile.json profile type e.g. "Edge Of Darkness"
|
||||
* @returns Dictionary of profile types with related descriptive text
|
||||
*/
|
||||
protected getProfileDescriptions(): Record<string, string>
|
||||
{
|
||||
protected getProfileDescriptions(): Record<string, string> {
|
||||
const result = {};
|
||||
const dbProfiles = this.databaseService.getProfiles();
|
||||
for (const profileKey in dbProfiles)
|
||||
{
|
||||
for (const profileKey in dbProfiles) {
|
||||
const localeKey = dbProfiles[profileKey]?.descriptionLocaleKey;
|
||||
if (!localeKey)
|
||||
{
|
||||
if (!localeKey) {
|
||||
this.logger.warning(this.localisationService.getText("launcher-missing_property", profileKey));
|
||||
continue;
|
||||
}
|
||||
@ -78,18 +73,14 @@ export class LauncherController
|
||||
return result;
|
||||
}
|
||||
|
||||
public find(sessionId: string): Info
|
||||
{
|
||||
public find(sessionId: string): Info {
|
||||
return this.saveServer.getProfiles()[sessionId]?.info;
|
||||
}
|
||||
|
||||
public login(info: ILoginRequestData): string
|
||||
{
|
||||
for (const sessionID in this.saveServer.getProfiles())
|
||||
{
|
||||
public login(info: ILoginRequestData): string {
|
||||
for (const sessionID in this.saveServer.getProfiles()) {
|
||||
const account = this.saveServer.getProfile(sessionID).info;
|
||||
if (info.username === account.username)
|
||||
{
|
||||
if (info.username === account.username) {
|
||||
return sessionID;
|
||||
}
|
||||
}
|
||||
@ -97,12 +88,9 @@ export class LauncherController
|
||||
return "";
|
||||
}
|
||||
|
||||
public register(info: IRegisterData): string
|
||||
{
|
||||
for (const sessionID in this.saveServer.getProfiles())
|
||||
{
|
||||
if (info.username === this.saveServer.getProfile(sessionID).info.username)
|
||||
{
|
||||
public register(info: IRegisterData): string {
|
||||
for (const sessionID in this.saveServer.getProfiles()) {
|
||||
if (info.username === this.saveServer.getProfile(sessionID).info.username) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
@ -110,8 +98,7 @@ export class LauncherController
|
||||
return this.createAccount(info);
|
||||
}
|
||||
|
||||
protected createAccount(info: IRegisterData): string
|
||||
{
|
||||
protected createAccount(info: IRegisterData): string {
|
||||
const profileId = this.generateProfileId();
|
||||
const scavId = this.generateProfileId();
|
||||
const newProfileDetails: Info = {
|
||||
@ -131,39 +118,33 @@ export class LauncherController
|
||||
return profileId;
|
||||
}
|
||||
|
||||
protected generateProfileId(): string
|
||||
{
|
||||
protected generateProfileId(): string {
|
||||
const timestamp = this.timeUtil.getTimestamp();
|
||||
|
||||
return this.formatID(timestamp, timestamp * this.randomUtil.getInt(1, 1000000));
|
||||
}
|
||||
|
||||
protected formatID(timeStamp: number, counter: number): string
|
||||
{
|
||||
protected formatID(timeStamp: number, counter: number): string {
|
||||
const timeStampStr = timeStamp.toString(16).padStart(8, "0");
|
||||
const counterStr = counter.toString(16).padStart(16, "0");
|
||||
|
||||
return timeStampStr.toLowerCase() + counterStr.toLowerCase();
|
||||
}
|
||||
|
||||
public changeUsername(info: IChangeRequestData): string
|
||||
{
|
||||
public changeUsername(info: IChangeRequestData): string {
|
||||
const sessionID = this.login(info);
|
||||
|
||||
if (sessionID)
|
||||
{
|
||||
if (sessionID) {
|
||||
this.saveServer.getProfile(sessionID).info.username = info.change;
|
||||
}
|
||||
|
||||
return sessionID;
|
||||
}
|
||||
|
||||
public changePassword(info: IChangeRequestData): string
|
||||
{
|
||||
public changePassword(info: IChangeRequestData): string {
|
||||
const sessionID = this.login(info);
|
||||
|
||||
if (sessionID)
|
||||
{
|
||||
if (sessionID) {
|
||||
this.saveServer.getProfile(sessionID).info.password = info.change;
|
||||
}
|
||||
|
||||
@ -175,17 +156,14 @@ export class LauncherController
|
||||
* @param info IRegisterData
|
||||
* @returns Session id
|
||||
*/
|
||||
public wipe(info: IRegisterData): string
|
||||
{
|
||||
if (!this.coreConfig.allowProfileWipe)
|
||||
{
|
||||
public wipe(info: IRegisterData): string {
|
||||
if (!this.coreConfig.allowProfileWipe) {
|
||||
return;
|
||||
}
|
||||
|
||||
const sessionID = this.login(info);
|
||||
|
||||
if (sessionID)
|
||||
{
|
||||
if (sessionID) {
|
||||
const profile = this.saveServer.getProfile(sessionID);
|
||||
profile.info.edition = info.edition;
|
||||
profile.info.wipe = true;
|
||||
@ -194,8 +172,7 @@ export class LauncherController
|
||||
return sessionID;
|
||||
}
|
||||
|
||||
public getCompatibleTarkovVersion(): string
|
||||
{
|
||||
public getCompatibleTarkovVersion(): string {
|
||||
return this.coreConfig.compatibleTarkovVersion;
|
||||
}
|
||||
|
||||
@ -203,8 +180,7 @@ export class LauncherController
|
||||
* Get the mods the server has currently loaded
|
||||
* @returns Dictionary of mod name and mod details
|
||||
*/
|
||||
public getLoadedServerMods(): Record<string, IPackageJsonData>
|
||||
{
|
||||
public getLoadedServerMods(): Record<string, IPackageJsonData> {
|
||||
return this.preSptModLoader.getImportedModDetails();
|
||||
}
|
||||
|
||||
@ -213,12 +189,10 @@ export class LauncherController
|
||||
* @param sessionId Player id
|
||||
* @returns Array of mod details
|
||||
*/
|
||||
public getServerModsProfileUsed(sessionId: string): ModDetails[]
|
||||
{
|
||||
public getServerModsProfileUsed(sessionId: string): ModDetails[] {
|
||||
const profile = this.profileHelper.getFullProfile(sessionId);
|
||||
|
||||
if (profile?.spt?.mods)
|
||||
{
|
||||
if (profile?.spt?.mods) {
|
||||
return this.preSptModLoader.getProfileModsGroupedByModName(profile?.spt?.mods);
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { inject, injectable } from "tsyringe";
|
||||
import { ILocationsGenerateAllResponse } from "@spt/models/eft/common/ILocationsSourceDestinationBase";
|
||||
import { IGetAirdropLootResponse } from "@spt/models/eft/location/IGetAirdropLootResponse";
|
||||
import { ConfigTypes } from "@spt/models/enums/ConfigTypes";
|
||||
@ -9,10 +8,10 @@ import { ConfigServer } from "@spt/servers/ConfigServer";
|
||||
import { AirdropService } from "@spt/services/AirdropService";
|
||||
import { DatabaseService } from "@spt/services/DatabaseService";
|
||||
import { ICloner } from "@spt/utils/cloners/ICloner";
|
||||
import { inject, injectable } from "tsyringe";
|
||||
|
||||
@injectable()
|
||||
export class LocationController
|
||||
{
|
||||
export class LocationController {
|
||||
protected locationConfig: ILocationConfig;
|
||||
|
||||
constructor(
|
||||
@ -21,8 +20,7 @@ export class LocationController
|
||||
@inject("AirdropService") protected airdropService: AirdropService,
|
||||
@inject("ConfigServer") protected configServer: ConfigServer,
|
||||
@inject("PrimaryCloner") protected cloner: ICloner,
|
||||
)
|
||||
{
|
||||
) {
|
||||
this.locationConfig = this.configServer.getConfig(ConfigTypes.LOCATION);
|
||||
}
|
||||
|
||||
@ -32,15 +30,12 @@ export class LocationController
|
||||
* @param sessionId Players Id
|
||||
* @returns ILocationsGenerateAllResponse
|
||||
*/
|
||||
public generateAll(sessionId: string): ILocationsGenerateAllResponse
|
||||
{
|
||||
public generateAll(sessionId: string): ILocationsGenerateAllResponse {
|
||||
const locationsFromDb = this.databaseService.getLocations();
|
||||
const locations: ILocations = {};
|
||||
for (const mapName in locationsFromDb)
|
||||
{
|
||||
for (const mapName in locationsFromDb) {
|
||||
const mapBase = locationsFromDb[mapName]?.base;
|
||||
if (!mapBase)
|
||||
{
|
||||
if (!mapBase) {
|
||||
this.logger.debug(`Map: ${mapName} has no base json file, skipping generation`);
|
||||
continue;
|
||||
}
|
||||
@ -55,8 +50,7 @@ export class LocationController
|
||||
}
|
||||
|
||||
/** Handle client/airdrop/loot */
|
||||
public getAirdropLoot(): IGetAirdropLootResponse
|
||||
{
|
||||
public getAirdropLoot(): IGetAirdropLootResponse {
|
||||
return this.airdropService.generateAirdropLoot();
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { inject, injectable } from "tsyringe";
|
||||
import { ApplicationContext } from "@spt/context/ApplicationContext";
|
||||
import { ContextVariableType } from "@spt/context/ContextVariableType";
|
||||
import { IEndLocalRaidRequestData } from "@spt/models/eft/match/IEndLocalRaidRequestData";
|
||||
@ -19,10 +18,10 @@ import { LocationLifecycleService } from "@spt/services/LocationLifecycleService
|
||||
import { MatchLocationService } from "@spt/services/MatchLocationService";
|
||||
import { ProfileSnapshotService } from "@spt/services/ProfileSnapshotService";
|
||||
import { ICloner } from "@spt/utils/cloners/ICloner";
|
||||
import { inject, injectable } from "tsyringe";
|
||||
|
||||
@injectable()
|
||||
export class MatchController
|
||||
{
|
||||
export class MatchController {
|
||||
protected matchConfig: IMatchConfig;
|
||||
protected pmcConfig: IPmcConfig;
|
||||
|
||||
@ -35,26 +34,22 @@ export class MatchController
|
||||
@inject("ApplicationContext") protected applicationContext: ApplicationContext,
|
||||
@inject("LocationLifecycleService") protected locationLifecycleService: LocationLifecycleService,
|
||||
@inject("PrimaryCloner") protected cloner: ICloner,
|
||||
)
|
||||
{
|
||||
) {
|
||||
this.matchConfig = this.configServer.getConfig(ConfigTypes.MATCH);
|
||||
this.pmcConfig = this.configServer.getConfig(ConfigTypes.PMC);
|
||||
}
|
||||
|
||||
public getEnabled(): boolean
|
||||
{
|
||||
public getEnabled(): boolean {
|
||||
return this.matchConfig.enabled;
|
||||
}
|
||||
|
||||
/** Handle client/match/group/delete */
|
||||
public deleteGroup(info: any): void
|
||||
{
|
||||
public deleteGroup(info: any): void {
|
||||
this.matchLocationService.deleteGroup(info);
|
||||
}
|
||||
|
||||
/** Handle match/group/start_game */
|
||||
public joinMatch(info: IMatchGroupStartGameRequest, sessionId: string): IProfileStatusResponse
|
||||
{
|
||||
public joinMatch(info: IMatchGroupStartGameRequest, sessionId: string): IProfileStatusResponse {
|
||||
const output: IProfileStatusResponse = { maxPveCountExceeded: false, profiles: [] };
|
||||
|
||||
// get list of players joining into the match
|
||||
@ -77,8 +72,7 @@ export class MatchController
|
||||
}
|
||||
|
||||
/** Handle client/match/group/status */
|
||||
public getGroupStatus(info: IMatchGroupStatusRequest): IMatchGroupStatusResponse
|
||||
{
|
||||
public getGroupStatus(info: IMatchGroupStatusRequest): IMatchGroupStatusResponse {
|
||||
return { players: [], maxPveCountExceeded: false };
|
||||
}
|
||||
|
||||
@ -87,16 +81,14 @@ export class MatchController
|
||||
* @param request Raid config request
|
||||
* @param sessionID Session id
|
||||
*/
|
||||
public configureOfflineRaid(request: IGetRaidConfigurationRequestData, sessionID: string): void
|
||||
{
|
||||
public configureOfflineRaid(request: IGetRaidConfigurationRequestData, sessionID: string): void {
|
||||
// Store request data for access during bot generation
|
||||
this.applicationContext.addValue(ContextVariableType.RAID_CONFIGURATION, request);
|
||||
|
||||
// TODO: add code to strip PMC of equipment now they've started the raid
|
||||
|
||||
// Set pmcs to difficulty set in pre-raid screen if override in bot config isnt enabled
|
||||
if (!this.pmcConfig.useDifficultyOverride)
|
||||
{
|
||||
if (!this.pmcConfig.useDifficultyOverride) {
|
||||
this.pmcConfig.difficulty = this.convertDifficultyDropdownIntoBotDifficulty(
|
||||
request.wavesSettings.botDifficulty,
|
||||
);
|
||||
@ -112,11 +104,9 @@ export class MatchController
|
||||
* @param botDifficulty dropdown difficulty value
|
||||
* @returns bot difficulty
|
||||
*/
|
||||
protected convertDifficultyDropdownIntoBotDifficulty(botDifficulty: string): string
|
||||
{
|
||||
protected convertDifficultyDropdownIntoBotDifficulty(botDifficulty: string): string {
|
||||
// Edge case medium - must be altered
|
||||
if (botDifficulty.toLowerCase() === "medium")
|
||||
{
|
||||
if (botDifficulty.toLowerCase() === "medium") {
|
||||
return "normal";
|
||||
}
|
||||
|
||||
@ -124,14 +114,12 @@ export class MatchController
|
||||
}
|
||||
|
||||
/** Handle client/match/local/start */
|
||||
public startLocalRaid(sessionId: string, request: IStartLocalRaidRequestData): IStartLocalRaidResponseData
|
||||
{
|
||||
public startLocalRaid(sessionId: string, request: IStartLocalRaidRequestData): IStartLocalRaidResponseData {
|
||||
return this.locationLifecycleService.startLocalRaid(sessionId, request);
|
||||
}
|
||||
|
||||
/** Handle client/match/local/end */
|
||||
public endLocalRaid(sessionId: string, request: IEndLocalRaidRequestData): void
|
||||
{
|
||||
public endLocalRaid(sessionId: string, request: IEndLocalRaidRequestData): void {
|
||||
return this.locationLifecycleService.endLocalRaid(sessionId, request);
|
||||
}
|
||||
}
|
||||
|
@ -1,26 +1,22 @@
|
||||
import { inject, injectable } from "tsyringe";
|
||||
import { IPmcData } from "@spt/models/eft/common/IPmcData";
|
||||
import { Note } from "@spt/models/eft/common/tables/IBotBase";
|
||||
import { IItemEventRouterResponse } from "@spt/models/eft/itemEvent/IItemEventRouterResponse";
|
||||
import { INoteActionData } from "@spt/models/eft/notes/INoteActionData";
|
||||
import { EventOutputHolder } from "@spt/routers/EventOutputHolder";
|
||||
import { inject, injectable } from "tsyringe";
|
||||
|
||||
@injectable()
|
||||
export class NoteController
|
||||
{
|
||||
constructor(@inject("EventOutputHolder") protected eventOutputHolder: EventOutputHolder)
|
||||
{}
|
||||
export class NoteController {
|
||||
constructor(@inject("EventOutputHolder") protected eventOutputHolder: EventOutputHolder) {}
|
||||
|
||||
public addNote(pmcData: IPmcData, body: INoteActionData, sessionID: string): IItemEventRouterResponse
|
||||
{
|
||||
public addNote(pmcData: IPmcData, body: INoteActionData, sessionID: string): IItemEventRouterResponse {
|
||||
const newNote: Note = { Time: body.note.Time, Text: body.note.Text };
|
||||
pmcData.Notes.Notes.push(newNote);
|
||||
|
||||
return this.eventOutputHolder.getOutput(sessionID);
|
||||
}
|
||||
|
||||
public editNote(pmcData: IPmcData, body: INoteActionData, sessionID: string): IItemEventRouterResponse
|
||||
{
|
||||
public editNote(pmcData: IPmcData, body: INoteActionData, sessionID: string): IItemEventRouterResponse {
|
||||
const noteToEdit: Note = pmcData.Notes.Notes[body.index];
|
||||
noteToEdit.Time = body.note.Time;
|
||||
noteToEdit.Text = body.note.Text;
|
||||
@ -28,8 +24,7 @@ export class NoteController
|
||||
return this.eventOutputHolder.getOutput(sessionID);
|
||||
}
|
||||
|
||||
public deleteNote(pmcData: IPmcData, body: INoteActionData, sessionID: string): IItemEventRouterResponse
|
||||
{
|
||||
public deleteNote(pmcData: IPmcData, body: INoteActionData, sessionID: string): IItemEventRouterResponse {
|
||||
pmcData.Notes.Notes.splice(body.index, 1);
|
||||
return this.eventOutputHolder.getOutput(sessionID);
|
||||
}
|
||||
|
@ -1,12 +1,11 @@
|
||||
import { inject, injectable } from "tsyringe";
|
||||
import { HttpServerHelper } from "@spt/helpers/HttpServerHelper";
|
||||
import { NotifierHelper } from "@spt/helpers/NotifierHelper";
|
||||
import { INotifierChannel } from "@spt/models/eft/notifier/INotifier";
|
||||
import { NotificationService } from "@spt/services/NotificationService";
|
||||
import { inject, injectable } from "tsyringe";
|
||||
|
||||
@injectable()
|
||||
export class NotifierController
|
||||
{
|
||||
export class NotifierController {
|
||||
protected pollInterval = 300;
|
||||
protected timeout = 15000;
|
||||
|
||||
@ -14,8 +13,7 @@ export class NotifierController
|
||||
@inject("NotifierHelper") protected notifierHelper: NotifierHelper,
|
||||
@inject("HttpServerHelper") protected httpServerHelper: HttpServerHelper,
|
||||
@inject("NotificationService") protected notificationService: NotificationService,
|
||||
)
|
||||
{}
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Resolve an array of session notifications.
|
||||
@ -24,10 +22,8 @@ export class NotifierController
|
||||
* one or more appear or when a timeout expires.
|
||||
* If no notifications are available after the timeout, use a default message.
|
||||
*/
|
||||
public async notifyAsync(sessionID: string): Promise<unknown>
|
||||
{
|
||||
return new Promise((resolve) =>
|
||||
{
|
||||
public async notifyAsync(sessionID: string): Promise<unknown> {
|
||||
return new Promise((resolve) => {
|
||||
// keep track of our timeout
|
||||
let counter = 0;
|
||||
|
||||
@ -35,17 +31,14 @@ export class NotifierController
|
||||
* Check for notifications, resolve if any, otherwise poll
|
||||
* intermittently for a period of time.
|
||||
*/
|
||||
const checkNotifications = () =>
|
||||
{
|
||||
const checkNotifications = () => {
|
||||
/**
|
||||
* If there are no pending messages we should either check again later
|
||||
* or timeout now with a default response.
|
||||
*/
|
||||
if (!this.notificationService.has(sessionID))
|
||||
{
|
||||
if (!this.notificationService.has(sessionID)) {
|
||||
// have we exceeded timeout? if so reply with default ping message
|
||||
if (counter > this.timeout)
|
||||
{
|
||||
if (counter > this.timeout) {
|
||||
return resolve([this.notifierHelper.getDefaultNotification()]);
|
||||
}
|
||||
|
||||
@ -71,14 +64,12 @@ export class NotifierController
|
||||
});
|
||||
}
|
||||
|
||||
public getServer(sessionID: string): string
|
||||
{
|
||||
public getServer(sessionID: string): string {
|
||||
return `${this.httpServerHelper.getBackendUrl()}/notifierServer/get/${sessionID}`;
|
||||
}
|
||||
|
||||
/** Handle client/notifier/channel/create */
|
||||
public getChannel(sessionID: string): INotifierChannel
|
||||
{
|
||||
public getChannel(sessionID: string): INotifierChannel {
|
||||
return {
|
||||
server: this.httpServerHelper.buildUrl(),
|
||||
channel_id: sessionID,
|
||||
|
@ -1,28 +1,23 @@
|
||||
import { inject, injectable } from "tsyringe";
|
||||
import { PresetHelper } from "@spt/helpers/PresetHelper";
|
||||
import { IPreset } from "@spt/models/eft/common/IGlobals";
|
||||
import { ILogger } from "@spt/models/spt/utils/ILogger";
|
||||
import { DatabaseService } from "@spt/services/DatabaseService";
|
||||
import { inject, injectable } from "tsyringe";
|
||||
|
||||
@injectable()
|
||||
export class PresetController
|
||||
{
|
||||
export class PresetController {
|
||||
constructor(
|
||||
@inject("PrimaryLogger") protected logger: ILogger,
|
||||
@inject("PresetHelper") protected presetHelper: PresetHelper,
|
||||
@inject("DatabaseService") protected databaseService: DatabaseService,
|
||||
)
|
||||
{}
|
||||
) {}
|
||||
|
||||
public initialize(): void
|
||||
{
|
||||
public initialize(): void {
|
||||
const presets: [string, IPreset][] = Object.entries(this.databaseService.getGlobals().ItemPresets);
|
||||
const reverse: Record<string, string[]> = {};
|
||||
|
||||
for (const [id, preset] of presets)
|
||||
{
|
||||
if (id !== preset._id)
|
||||
{
|
||||
for (const [id, preset] of presets) {
|
||||
if (id !== preset._id) {
|
||||
this.logger.error(
|
||||
`Preset for template tpl: '${preset._items[0]._tpl} ${preset._name}' has invalid key: (${id} != ${preset._id}). Skipping`,
|
||||
);
|
||||
@ -32,8 +27,7 @@ export class PresetController
|
||||
|
||||
const tpl = preset._items[0]._tpl;
|
||||
|
||||
if (!(tpl in reverse))
|
||||
{
|
||||
if (!(tpl in reverse)) {
|
||||
reverse[tpl] = [];
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { inject, injectable } from "tsyringe";
|
||||
import { PlayerScavGenerator } from "@spt/generators/PlayerScavGenerator";
|
||||
import { DialogueHelper } from "@spt/helpers/DialogueHelper";
|
||||
import { ItemHelper } from "@spt/helpers/ItemHelper";
|
||||
@ -31,13 +30,13 @@ import { LocalisationService } from "@spt/services/LocalisationService";
|
||||
import { MailSendService } from "@spt/services/MailSendService";
|
||||
import { ProfileFixerService } from "@spt/services/ProfileFixerService";
|
||||
import { SeasonalEventService } from "@spt/services/SeasonalEventService";
|
||||
import { ICloner } from "@spt/utils/cloners/ICloner";
|
||||
import { HashUtil } from "@spt/utils/HashUtil";
|
||||
import { TimeUtil } from "@spt/utils/TimeUtil";
|
||||
import { ICloner } from "@spt/utils/cloners/ICloner";
|
||||
import { inject, injectable } from "tsyringe";
|
||||
|
||||
@injectable()
|
||||
export class ProfileController
|
||||
{
|
||||
export class ProfileController {
|
||||
constructor(
|
||||
@inject("PrimaryLogger") protected logger: ILogger,
|
||||
@inject("HashUtil") protected hashUtil: HashUtil,
|
||||
@ -56,14 +55,12 @@ export class ProfileController
|
||||
@inject("DialogueHelper") protected dialogueHelper: DialogueHelper,
|
||||
@inject("QuestHelper") protected questHelper: QuestHelper,
|
||||
@inject("ProfileHelper") protected profileHelper: ProfileHelper,
|
||||
)
|
||||
{}
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Handle /launcher/profiles
|
||||
*/
|
||||
public getMiniProfiles(): IMiniProfile[]
|
||||
{
|
||||
public getMiniProfiles(): IMiniProfile[] {
|
||||
const allProfiles = Object.keys(this.saveServer.getProfiles());
|
||||
|
||||
return allProfiles.map((sessionId) => this.getMiniProfile(sessionId));
|
||||
@ -72,11 +69,9 @@ export class ProfileController
|
||||
/**
|
||||
* Handle launcher/profile/info
|
||||
*/
|
||||
public getMiniProfile(sessionID: string): IMiniProfile
|
||||
{
|
||||
public getMiniProfile(sessionID: string): IMiniProfile {
|
||||
const profile = this.saveServer.getProfile(sessionID);
|
||||
if (!profile || !profile.characters)
|
||||
{
|
||||
if (!profile || !profile.characters) {
|
||||
throw new Error(`Unable to find character data for id: ${sessionID}. Profile may be corrupt`);
|
||||
}
|
||||
|
||||
@ -84,8 +79,7 @@ export class ProfileController
|
||||
const maxlvl = this.profileHelper.getMaxLevel();
|
||||
|
||||
// Player hasn't completed profile creation process, send defaults
|
||||
if (!pmc?.Info?.Level)
|
||||
{
|
||||
if (!pmc?.Info?.Level) {
|
||||
return {
|
||||
username: profile.info?.username ?? "",
|
||||
nickname: "unknown",
|
||||
@ -109,9 +103,7 @@ export class ProfileController
|
||||
side: pmc.Info.Side,
|
||||
currlvl: pmc.Info.Level,
|
||||
currexp: pmc.Info.Experience ?? 0,
|
||||
prevexp: currlvl === 0
|
||||
? 0
|
||||
: this.profileHelper.getExperience(currlvl),
|
||||
prevexp: currlvl === 0 ? 0 : this.profileHelper.getExperience(currlvl),
|
||||
nextlvl: nextlvl,
|
||||
maxlvl: maxlvl,
|
||||
edition: profile.info?.edition ?? "",
|
||||
@ -123,8 +115,7 @@ export class ProfileController
|
||||
/**
|
||||
* Handle client/game/profile/list
|
||||
*/
|
||||
public getCompleteProfile(sessionID: string): IPmcData[]
|
||||
{
|
||||
public getCompleteProfile(sessionID: string): IPmcData[] {
|
||||
return this.profileHelper.getCompleteProfile(sessionID);
|
||||
}
|
||||
|
||||
@ -134,11 +125,11 @@ export class ProfileController
|
||||
* @param sessionID Player id
|
||||
* @returns Profiles _id value
|
||||
*/
|
||||
public createProfile(info: IProfileCreateRequestData, sessionID: string): string
|
||||
{
|
||||
public createProfile(info: IProfileCreateRequestData, sessionID: string): string {
|
||||
const account = this.saveServer.getProfile(sessionID).info;
|
||||
const profileTemplate: ITemplateSide
|
||||
= this.cloner.clone(this.databaseService.getProfiles()[account.edition][info.side.toLowerCase()]);
|
||||
const profileTemplate: ITemplateSide = this.cloner.clone(
|
||||
this.databaseService.getProfiles()[account.edition][info.side.toLowerCase()],
|
||||
);
|
||||
const pmcData = profileTemplate.character;
|
||||
|
||||
// Delete existing profile
|
||||
@ -166,8 +157,7 @@ export class ProfileController
|
||||
|
||||
this.updateInventoryEquipmentId(pmcData);
|
||||
|
||||
if (!pmcData.UnlockedInfo)
|
||||
{
|
||||
if (!pmcData.UnlockedInfo) {
|
||||
pmcData.UnlockedInfo = { unlockedProductionRecipe: [] };
|
||||
}
|
||||
|
||||
@ -200,14 +190,12 @@ export class ProfileController
|
||||
|
||||
this.saveServer.addProfile(profileDetails);
|
||||
|
||||
if (profileTemplate.trader.setQuestsAvailableForStart)
|
||||
{
|
||||
if (profileTemplate.trader.setQuestsAvailableForStart) {
|
||||
this.questHelper.addAllQuestsToProfile(profileDetails.characters.pmc, [QuestStatus.AvailableForStart]);
|
||||
}
|
||||
|
||||
// Profile is flagged as wanting quests set to ready to hand in and collect rewards
|
||||
if (profileTemplate.trader.setQuestsAvailableForFinish)
|
||||
{
|
||||
if (profileTemplate.trader.setQuestsAvailableForFinish) {
|
||||
this.questHelper.addAllQuestsToProfile(profileDetails.characters.pmc, [
|
||||
QuestStatus.AvailableForStart,
|
||||
QuestStatus.Started,
|
||||
@ -234,8 +222,7 @@ export class ProfileController
|
||||
this.saveServer.saveProfile(sessionID);
|
||||
|
||||
// Requires to enable seasonal changes after creating fresh profile
|
||||
if (this.seasonalEventService.isAutomaticEventDetectionEnabled())
|
||||
{
|
||||
if (this.seasonalEventService.isAutomaticEventDetectionEnabled()) {
|
||||
this.seasonalEventService.enableSeasonalEvents(sessionID);
|
||||
}
|
||||
|
||||
@ -246,22 +233,18 @@ export class ProfileController
|
||||
* make profiles pmcData.Inventory.equipment unique
|
||||
* @param pmcData Profile to update
|
||||
*/
|
||||
protected updateInventoryEquipmentId(pmcData: IPmcData): void
|
||||
{
|
||||
protected updateInventoryEquipmentId(pmcData: IPmcData): void {
|
||||
const oldEquipmentId = pmcData.Inventory.equipment;
|
||||
pmcData.Inventory.equipment = this.hashUtil.generate();
|
||||
|
||||
for (const item of pmcData.Inventory.items)
|
||||
{
|
||||
if (item.parentId === oldEquipmentId)
|
||||
{
|
||||
for (const item of pmcData.Inventory.items) {
|
||||
if (item.parentId === oldEquipmentId) {
|
||||
item.parentId = pmcData.Inventory.equipment;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (item._id === oldEquipmentId)
|
||||
{
|
||||
if (item._id === oldEquipmentId) {
|
||||
item._id = pmcData.Inventory.equipment;
|
||||
}
|
||||
}
|
||||
@ -271,14 +254,10 @@ export class ProfileController
|
||||
* Delete a profile
|
||||
* @param sessionID Id of profile to delete
|
||||
*/
|
||||
protected deleteProfileBySessionId(sessionID: string): void
|
||||
{
|
||||
if (sessionID in this.saveServer.getProfiles())
|
||||
{
|
||||
protected deleteProfileBySessionId(sessionID: string): void {
|
||||
if (sessionID in this.saveServer.getProfiles()) {
|
||||
this.saveServer.deleteProfileById(sessionID);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
this.logger.warning(
|
||||
this.localisationService.getText("profile-unable_to_find_profile_by_id_cannot_delete", sessionID),
|
||||
);
|
||||
@ -296,10 +275,8 @@ export class ProfileController
|
||||
profileDetails: ISptProfile,
|
||||
sessionID: string,
|
||||
response: IItemEventRouterResponse,
|
||||
): void
|
||||
{
|
||||
for (const quest of profileDetails.characters.pmc.Quests)
|
||||
{
|
||||
): void {
|
||||
for (const quest of profileDetails.characters.pmc.Quests) {
|
||||
const questFromDb = this.questHelper.getQuestFromDb(quest.qid, profileDetails.characters.pmc);
|
||||
|
||||
// Get messageId of text to send to player as text message in game
|
||||
@ -331,10 +308,8 @@ export class ProfileController
|
||||
* For each trader reset their state to what a level 1 player would see
|
||||
* @param sessionId Session id of profile to reset
|
||||
*/
|
||||
protected resetAllTradersInProfile(sessionId: string): void
|
||||
{
|
||||
for (const traderId in this.databaseService.getTraders())
|
||||
{
|
||||
protected resetAllTradersInProfile(sessionId: string): void {
|
||||
for (const traderId in this.databaseService.getTraders()) {
|
||||
this.traderHelper.resetTrader(sessionId, traderId);
|
||||
}
|
||||
}
|
||||
@ -345,23 +320,19 @@ export class ProfileController
|
||||
* @param sessionID
|
||||
* @returns IPmcData object
|
||||
*/
|
||||
public generatePlayerScav(sessionID: string): IPmcData
|
||||
{
|
||||
public generatePlayerScav(sessionID: string): IPmcData {
|
||||
return this.playerScavGenerator.generate(sessionID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle client/game/profile/nickname/validate
|
||||
*/
|
||||
public validateNickname(info: IValidateNicknameRequestData, sessionID: string): string
|
||||
{
|
||||
if (info.nickname.length < 3)
|
||||
{
|
||||
public validateNickname(info: IValidateNicknameRequestData, sessionID: string): string {
|
||||
if (info.nickname.length < 3) {
|
||||
return "tooshort";
|
||||
}
|
||||
|
||||
if (this.profileHelper.isNicknameTaken(info, sessionID))
|
||||
{
|
||||
if (this.profileHelper.isNicknameTaken(info, sessionID)) {
|
||||
return "taken";
|
||||
}
|
||||
|
||||
@ -372,12 +343,10 @@ export class ProfileController
|
||||
* Handle client/game/profile/nickname/change event
|
||||
* Client allows player to adjust their profile name
|
||||
*/
|
||||
public changeNickname(info: IProfileChangeNicknameRequestData, sessionID: string): string
|
||||
{
|
||||
public changeNickname(info: IProfileChangeNicknameRequestData, sessionID: string): string {
|
||||
const output = this.validateNickname(info, sessionID);
|
||||
|
||||
if (output === "OK")
|
||||
{
|
||||
if (output === "OK") {
|
||||
const pmcData = this.profileHelper.getPmcProfile(sessionID);
|
||||
|
||||
pmcData.Info.Nickname = info.nickname;
|
||||
@ -390,8 +359,7 @@ export class ProfileController
|
||||
/**
|
||||
* Handle client/game/profile/voice/change event
|
||||
*/
|
||||
public changeVoice(info: IProfileChangeVoiceRequestData, sessionID: string): void
|
||||
{
|
||||
public changeVoice(info: IProfileChangeVoiceRequestData, sessionID: string): void {
|
||||
const pmcData = this.profileHelper.getPmcProfile(sessionID);
|
||||
pmcData.Info.Voice = info.voice;
|
||||
}
|
||||
@ -399,8 +367,7 @@ export class ProfileController
|
||||
/**
|
||||
* Handle client/game/profile/search
|
||||
*/
|
||||
public getFriends(info: ISearchFriendRequestData, sessionID: string): ISearchFriendResponse[]
|
||||
{
|
||||
public getFriends(info: ISearchFriendRequestData, sessionID: string): ISearchFriendResponse[] {
|
||||
const profile = this.saveServer.getProfile(sessionID);
|
||||
|
||||
// return some of the current player info for now
|
||||
@ -421,8 +388,7 @@ export class ProfileController
|
||||
/**
|
||||
* Handle client/profile/status
|
||||
*/
|
||||
public getProfileStatus(sessionId: string): GetProfileStatusResponseData
|
||||
{
|
||||
public getProfileStatus(sessionId: string): GetProfileStatusResponseData {
|
||||
const account = this.saveServer.getProfile(sessionId).info;
|
||||
const response: GetProfileStatusResponseData = {
|
||||
maxPveCountExceeded: false,
|
||||
@ -442,8 +408,7 @@ export class ProfileController
|
||||
return response;
|
||||
}
|
||||
|
||||
public getOtherProfile(sessionId: string, request: IGetOtherProfileRequest): IGetOtherProfileResponse
|
||||
{
|
||||
public getOtherProfile(sessionId: string, request: IGetOtherProfileRequest): IGetOtherProfileResponse {
|
||||
const player = this.profileHelper.getFullProfile(sessionId);
|
||||
const playerPmc = player.characters.pmc;
|
||||
const playerScav = player.characters.scav;
|
||||
@ -493,21 +458,17 @@ export class ProfileController
|
||||
/**
|
||||
* Handle client/profile/settings
|
||||
*/
|
||||
public setChosenProfileIcon(sessionId: string, request: IGetProfileSettingsRequest): boolean
|
||||
{
|
||||
public setChosenProfileIcon(sessionId: string, request: IGetProfileSettingsRequest): boolean {
|
||||
const profileToUpdate = this.profileHelper.getPmcProfile(sessionId);
|
||||
if (!profileToUpdate)
|
||||
{
|
||||
if (!profileToUpdate) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (request.memberCategory !== null)
|
||||
{
|
||||
if (request.memberCategory !== null) {
|
||||
profileToUpdate.Info.SelectedMemberCategory = request.memberCategory;
|
||||
}
|
||||
|
||||
if (request.squadInviteRestriction !== null)
|
||||
{
|
||||
if (request.squadInviteRestriction !== null) {
|
||||
profileToUpdate.Info.SquadInviteRestriction = request.squadInviteRestriction;
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { inject, injectable } from "tsyringe";
|
||||
import { DialogueHelper } from "@spt/helpers/DialogueHelper";
|
||||
import { ItemHelper } from "@spt/helpers/ItemHelper";
|
||||
import { ProfileHelper } from "@spt/helpers/ProfileHelper";
|
||||
@ -27,13 +26,13 @@ import { LocaleService } from "@spt/services/LocaleService";
|
||||
import { LocalisationService } from "@spt/services/LocalisationService";
|
||||
import { MailSendService } from "@spt/services/MailSendService";
|
||||
import { PlayerService } from "@spt/services/PlayerService";
|
||||
import { ICloner } from "@spt/utils/cloners/ICloner";
|
||||
import { HttpResponseUtil } from "@spt/utils/HttpResponseUtil";
|
||||
import { TimeUtil } from "@spt/utils/TimeUtil";
|
||||
import { ICloner } from "@spt/utils/cloners/ICloner";
|
||||
import { inject, injectable } from "tsyringe";
|
||||
|
||||
@injectable()
|
||||
export class QuestController
|
||||
{
|
||||
export class QuestController {
|
||||
protected questConfig: IQuestConfig;
|
||||
|
||||
constructor(
|
||||
@ -54,8 +53,7 @@ export class QuestController
|
||||
@inject("LocalisationService") protected localisationService: LocalisationService,
|
||||
@inject("ConfigServer") protected configServer: ConfigServer,
|
||||
@inject("PrimaryCloner") protected cloner: ICloner,
|
||||
)
|
||||
{
|
||||
) {
|
||||
this.questConfig = this.configServer.getConfig(ConfigTypes.QUEST);
|
||||
}
|
||||
|
||||
@ -66,44 +64,37 @@ export class QuestController
|
||||
* @param sessionID session id
|
||||
* @returns array of IQuest
|
||||
*/
|
||||
public getClientQuests(sessionID: string): IQuest[]
|
||||
{
|
||||
public getClientQuests(sessionID: string): IQuest[] {
|
||||
const questsToShowPlayer: IQuest[] = [];
|
||||
const allQuests = this.questHelper.getQuestsFromDb();
|
||||
const profile: IPmcData = this.profileHelper.getPmcProfile(sessionID);
|
||||
|
||||
for (const quest of allQuests)
|
||||
{
|
||||
for (const quest of allQuests) {
|
||||
// Player already accepted the quest, show it regardless of status
|
||||
const questInProfile = profile.Quests.find((x) => x.qid === quest._id);
|
||||
if (questInProfile)
|
||||
{
|
||||
if (questInProfile) {
|
||||
quest.sptStatus = questInProfile.status;
|
||||
questsToShowPlayer.push(quest);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Filter out bear quests for usec and vice versa
|
||||
if (this.questHelper.questIsForOtherSide(profile.Info.Side, quest._id))
|
||||
{
|
||||
if (this.questHelper.questIsForOtherSide(profile.Info.Side, quest._id)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!this.questHelper.showEventQuestToPlayer(quest._id))
|
||||
{
|
||||
if (!this.questHelper.showEventQuestToPlayer(quest._id)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Don't add quests that have a level higher than the user's
|
||||
if (!this.playerLevelFulfillsQuestRequirement(quest, profile.Info.Level))
|
||||
{
|
||||
if (!this.playerLevelFulfillsQuestRequirement(quest, profile.Info.Level)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Player can use trader mods then remove them, leaving quests behind
|
||||
const trader = profile.TradersInfo[quest.traderId];
|
||||
if (!trader)
|
||||
{
|
||||
if (!trader) {
|
||||
this.logger.debug(
|
||||
`Unable to show quest: ${quest.QuestName} as its for a trader: ${quest.traderId} that no longer exists.`,
|
||||
);
|
||||
@ -121,11 +112,10 @@ export class QuestController
|
||||
|
||||
// Quest has no conditions, standing or loyalty conditions, add to visible quest list
|
||||
if (
|
||||
questRequirements.length === 0
|
||||
&& loyaltyRequirements.length === 0
|
||||
&& standingRequirements.length === 0
|
||||
)
|
||||
{
|
||||
questRequirements.length === 0 &&
|
||||
loyaltyRequirements.length === 0 &&
|
||||
standingRequirements.length === 0
|
||||
) {
|
||||
quest.sptStatus = QuestStatus.AvailableForStart;
|
||||
questsToShowPlayer.push(quest);
|
||||
continue;
|
||||
@ -134,34 +124,29 @@ export class QuestController
|
||||
// Check the status of each quest condition, if any are not completed
|
||||
// then this quest should not be visible
|
||||
let haveCompletedPreviousQuest = true;
|
||||
for (const conditionToFulfil of questRequirements)
|
||||
{
|
||||
for (const conditionToFulfil of questRequirements) {
|
||||
// If the previous quest isn't in the user profile, it hasn't been completed or started
|
||||
const prerequisiteQuest = profile.Quests.find((profileQuest) =>
|
||||
conditionToFulfil.target.includes(profileQuest.qid),
|
||||
);
|
||||
if (!prerequisiteQuest)
|
||||
{
|
||||
if (!prerequisiteQuest) {
|
||||
haveCompletedPreviousQuest = false;
|
||||
break;
|
||||
}
|
||||
|
||||
// Prereq does not have its status requirement fulfilled
|
||||
// Some bsg status ids are strings, MUST convert to number before doing includes check
|
||||
if (!conditionToFulfil.status.map((status) => Number(status)).includes(prerequisiteQuest.status))
|
||||
{
|
||||
if (!conditionToFulfil.status.map((status) => Number(status)).includes(prerequisiteQuest.status)) {
|
||||
haveCompletedPreviousQuest = false;
|
||||
break;
|
||||
}
|
||||
|
||||
// Has a wait timer
|
||||
if (conditionToFulfil.availableAfter > 0)
|
||||
{
|
||||
if (conditionToFulfil.availableAfter > 0) {
|
||||
// Compare current time to unlock time for previous quest
|
||||
const previousQuestCompleteTime = prerequisiteQuest.statusTimers[prerequisiteQuest.status];
|
||||
const unlockTime = previousQuestCompleteTime + conditionToFulfil.availableAfter;
|
||||
if (unlockTime > this.timeUtil.getTimestamp())
|
||||
{
|
||||
if (unlockTime > this.timeUtil.getTimestamp()) {
|
||||
this.logger.debug(
|
||||
`Quest ${quest.QuestName} is locked for another ${
|
||||
unlockTime - this.timeUtil.getTimestamp()
|
||||
@ -172,33 +157,27 @@ export class QuestController
|
||||
}
|
||||
|
||||
// Previous quest not completed, skip
|
||||
if (!haveCompletedPreviousQuest)
|
||||
{
|
||||
if (!haveCompletedPreviousQuest) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let passesLoyaltyRequirements = true;
|
||||
for (const condition of loyaltyRequirements)
|
||||
{
|
||||
if (!this.questHelper.traderLoyaltyLevelRequirementCheck(condition, profile))
|
||||
{
|
||||
for (const condition of loyaltyRequirements) {
|
||||
if (!this.questHelper.traderLoyaltyLevelRequirementCheck(condition, profile)) {
|
||||
passesLoyaltyRequirements = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let passesStandingRequirements = true;
|
||||
for (const condition of standingRequirements)
|
||||
{
|
||||
if (!this.questHelper.traderStandingRequirementCheck(condition, profile))
|
||||
{
|
||||
for (const condition of standingRequirements) {
|
||||
if (!this.questHelper.traderStandingRequirementCheck(condition, profile)) {
|
||||
passesStandingRequirements = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (haveCompletedPreviousQuest && passesLoyaltyRequirements && passesStandingRequirements)
|
||||
{
|
||||
if (haveCompletedPreviousQuest && passesLoyaltyRequirements && passesStandingRequirements) {
|
||||
quest.sptStatus = QuestStatus.AvailableForStart;
|
||||
questsToShowPlayer.push(quest);
|
||||
}
|
||||
@ -213,21 +192,16 @@ export class QuestController
|
||||
* @param playerLevel level of player to test against quest
|
||||
* @returns true if quest can be seen/accepted by player of defined level
|
||||
*/
|
||||
protected playerLevelFulfillsQuestRequirement(quest: IQuest, playerLevel: number): boolean
|
||||
{
|
||||
if (!quest.conditions)
|
||||
{
|
||||
protected playerLevelFulfillsQuestRequirement(quest: IQuest, playerLevel: number): boolean {
|
||||
if (!quest.conditions) {
|
||||
// No conditions
|
||||
return true;
|
||||
}
|
||||
|
||||
const levelConditions = this.questConditionHelper.getLevelConditions(quest.conditions.AvailableForStart);
|
||||
if (levelConditions.length)
|
||||
{
|
||||
for (const levelCondition of levelConditions)
|
||||
{
|
||||
if (!this.questHelper.doesPlayerLevelFulfilCondition(playerLevel, levelCondition))
|
||||
{
|
||||
if (levelConditions.length) {
|
||||
for (const levelCondition of levelConditions) {
|
||||
if (!this.questHelper.doesPlayerLevelFulfilCondition(playerLevel, levelCondition)) {
|
||||
// Not valid, exit out
|
||||
return false;
|
||||
}
|
||||
@ -252,23 +226,19 @@ export class QuestController
|
||||
pmcData: IPmcData,
|
||||
acceptedQuest: IAcceptQuestRequestData,
|
||||
sessionID: string,
|
||||
): IItemEventRouterResponse
|
||||
{
|
||||
): IItemEventRouterResponse {
|
||||
const acceptQuestResponse = this.eventOutputHolder.getOutput(sessionID);
|
||||
|
||||
// Does quest exist in profile
|
||||
// Restarting a failed quest can mean quest exists in profile
|
||||
const existingQuestStatus = pmcData.Quests.find((x) => x.qid === acceptedQuest.qid);
|
||||
if (existingQuestStatus)
|
||||
{
|
||||
if (existingQuestStatus) {
|
||||
// Update existing
|
||||
this.questHelper.resetQuestState(pmcData, QuestStatus.Started, acceptedQuest.qid);
|
||||
|
||||
// Need to send client an empty list of completedConditions (Unsure if this does anything)
|
||||
acceptQuestResponse.profileChanges[sessionID].questsStatus.push(existingQuestStatus);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
// Add new quest to server profile
|
||||
const newQuest = this.questHelper.getQuestReadyForProfile(pmcData, QuestStatus.Started, acceptedQuest);
|
||||
pmcData.Quests.push(newQuest);
|
||||
@ -324,8 +294,7 @@ export class QuestController
|
||||
pmcData: IPmcData,
|
||||
acceptedQuest: IAcceptQuestRequestData,
|
||||
sessionID: string,
|
||||
): IItemEventRouterResponse
|
||||
{
|
||||
): IItemEventRouterResponse {
|
||||
// Create and store quest status object inside player profile
|
||||
const newRepeatableQuest = this.questHelper.getQuestReadyForProfile(
|
||||
pmcData,
|
||||
@ -336,8 +305,7 @@ export class QuestController
|
||||
|
||||
// Look for the generated quest cache in profile.RepeatableQuests
|
||||
const repeatableQuestProfile = this.getRepeatableQuestFromProfile(pmcData, acceptedQuest);
|
||||
if (!repeatableQuestProfile)
|
||||
{
|
||||
if (!repeatableQuestProfile) {
|
||||
this.logger.error(
|
||||
this.localisationService.getText(
|
||||
"repeatable-accepted_repeatable_quest_not_found_in_active_quests",
|
||||
@ -350,13 +318,11 @@ export class QuestController
|
||||
|
||||
// Some scav quests need to be added to scav profile for them to show up in-raid
|
||||
if (
|
||||
repeatableQuestProfile.side === "Scav"
|
||||
&& ["PickUp", "Exploration", "Elimination"].includes(repeatableQuestProfile.type)
|
||||
)
|
||||
{
|
||||
repeatableQuestProfile.side === "Scav" &&
|
||||
["PickUp", "Exploration", "Elimination"].includes(repeatableQuestProfile.type)
|
||||
) {
|
||||
const fullProfile = this.profileHelper.getFullProfile(sessionID);
|
||||
if (!fullProfile.characters.scav.Quests)
|
||||
{
|
||||
if (!fullProfile.characters.scav.Quests) {
|
||||
fullProfile.characters.scav.Quests = [];
|
||||
}
|
||||
|
||||
@ -371,20 +337,21 @@ export class QuestController
|
||||
protected createAcceptedQuestClientResponse(
|
||||
sessionID: string,
|
||||
pmcData: IPmcData,
|
||||
repeatableQuestProfile: IRepeatableQuest): IItemEventRouterResponse
|
||||
{
|
||||
const repeatableSettings = pmcData.RepeatableQuests
|
||||
.find((quest) => quest.name === repeatableQuestProfile.sptRepatableGroupName);
|
||||
repeatableQuestProfile: IRepeatableQuest,
|
||||
): IItemEventRouterResponse {
|
||||
const repeatableSettings = pmcData.RepeatableQuests.find(
|
||||
(quest) => quest.name === repeatableQuestProfile.sptRepatableGroupName,
|
||||
);
|
||||
|
||||
const change = {};
|
||||
change[repeatableQuestProfile._id] = repeatableSettings!.changeRequirement[repeatableQuestProfile._id];
|
||||
|
||||
const repeatableData: IPmcDataRepeatableQuest = {
|
||||
id:
|
||||
repeatableSettings.id
|
||||
?? this.questConfig.repeatableQuests
|
||||
.find((repeatableQuest) => repeatableQuest.name === repeatableQuestProfile.sptRepatableGroupName)
|
||||
.id,
|
||||
repeatableSettings.id ??
|
||||
this.questConfig.repeatableQuests.find(
|
||||
(repeatableQuest) => repeatableQuest.name === repeatableQuestProfile.sptRepatableGroupName,
|
||||
).id,
|
||||
name: repeatableSettings.name,
|
||||
endTime: repeatableSettings.endTime,
|
||||
changeRequirement: change,
|
||||
@ -396,8 +363,7 @@ export class QuestController
|
||||
|
||||
// Nullguard
|
||||
const acceptQuestResponse = this.eventOutputHolder.getOutput(sessionID);
|
||||
if (!acceptQuestResponse.profileChanges[sessionID].repeatableQuests)
|
||||
{
|
||||
if (!acceptQuestResponse.profileChanges[sessionID].repeatableQuests) {
|
||||
acceptQuestResponse.profileChanges[sessionID].repeatableQuests = [];
|
||||
}
|
||||
|
||||
@ -416,13 +382,10 @@ export class QuestController
|
||||
protected getRepeatableQuestFromProfile(
|
||||
pmcData: IPmcData,
|
||||
acceptedQuest: IAcceptQuestRequestData,
|
||||
): IRepeatableQuest
|
||||
{
|
||||
for (const repeatableQuest of pmcData.RepeatableQuests)
|
||||
{
|
||||
): IRepeatableQuest {
|
||||
for (const repeatableQuest of pmcData.RepeatableQuests) {
|
||||
const matchingQuest = repeatableQuest.activeQuests.find((x) => x._id === acceptedQuest.qid);
|
||||
if (matchingQuest)
|
||||
{
|
||||
if (matchingQuest) {
|
||||
this.logger.debug(`Accepted repeatable quest ${acceptedQuest.qid} from ${repeatableQuest.name}`);
|
||||
matchingQuest.sptRepatableGroupName = repeatableQuest.name;
|
||||
|
||||
@ -447,8 +410,7 @@ export class QuestController
|
||||
pmcData: IPmcData,
|
||||
body: ICompleteQuestRequestData,
|
||||
sessionID: string,
|
||||
): IItemEventRouterResponse
|
||||
{
|
||||
): IItemEventRouterResponse {
|
||||
const completeQuestResponse = this.eventOutputHolder.getOutput(sessionID);
|
||||
|
||||
const completedQuest = this.questHelper.getQuestFromDb(body.qid, pmcData);
|
||||
@ -469,8 +431,7 @@ export class QuestController
|
||||
|
||||
// Check for linked failed + unrestartable quests (only get quests not already failed
|
||||
const questsToFail = this.getQuestsFailedByCompletingQuest(completedQuestId, pmcData);
|
||||
if (questsToFail?.length > 0)
|
||||
{
|
||||
if (questsToFail?.length > 0) {
|
||||
this.failQuests(sessionID, pmcData, questsToFail, completeQuestResponse);
|
||||
}
|
||||
|
||||
@ -487,16 +448,13 @@ export class QuestController
|
||||
completeQuestResponse.profileChanges[sessionID].quests.push(...questDelta);
|
||||
|
||||
// Check if it's a repeatable quest. If so, remove from Quests
|
||||
for (const currentRepeatable of pmcData.RepeatableQuests)
|
||||
{
|
||||
for (const currentRepeatable of pmcData.RepeatableQuests) {
|
||||
const repeatableQuest = currentRepeatable.activeQuests.find(
|
||||
(activeRepeatable) => activeRepeatable._id === completedQuestId,
|
||||
);
|
||||
if (repeatableQuest)
|
||||
{
|
||||
if (repeatableQuest) {
|
||||
// Need to remove redundant scav quest object as its no longer necessary, is tracked in pmc profile
|
||||
if (repeatableQuest.side === "Scav")
|
||||
{
|
||||
if (repeatableQuest.side === "Scav") {
|
||||
this.removeQuestFromScavProfile(sessionID, repeatableQuest._id);
|
||||
}
|
||||
}
|
||||
@ -504,8 +462,7 @@ export class QuestController
|
||||
|
||||
// Hydrate client response questsStatus array with data
|
||||
const questStatusChanges = this.getQuestsWithDifferentStatuses(preCompleteProfileQuests, pmcData.Quests);
|
||||
if (questStatusChanges)
|
||||
{
|
||||
if (questStatusChanges) {
|
||||
completeQuestResponse.profileChanges[sessionID].questsStatus.push(...questStatusChanges);
|
||||
}
|
||||
|
||||
@ -520,14 +477,11 @@ export class QuestController
|
||||
* @param completedQuestId quest completed id
|
||||
* @returns array of IQuest objects
|
||||
*/
|
||||
protected getQuestsFailedByCompletingQuest(completedQuestId: string, pmcProfile: IPmcData): IQuest[]
|
||||
{
|
||||
protected getQuestsFailedByCompletingQuest(completedQuestId: string, pmcProfile: IPmcData): IQuest[] {
|
||||
const questsInDb = this.questHelper.getQuestsFromDb();
|
||||
return questsInDb.filter((quest) =>
|
||||
{
|
||||
return questsInDb.filter((quest) => {
|
||||
// No fail conditions, skip
|
||||
if (!quest.conditions.Fail || quest.conditions.Fail.length === 0)
|
||||
{
|
||||
if (!quest.conditions.Fail || quest.conditions.Fail.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -536,8 +490,7 @@ export class QuestController
|
||||
pmcProfile.Quests.some(
|
||||
(profileQuest) => profileQuest.qid === quest._id && profileQuest.status === QuestStatus.Fail,
|
||||
)
|
||||
)
|
||||
{
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -550,17 +503,16 @@ export class QuestController
|
||||
* @param sessionId Player id
|
||||
* @param questIdToRemove Qid of quest to remove
|
||||
*/
|
||||
protected removeQuestFromScavProfile(sessionId: string, questIdToRemove: string): void
|
||||
{
|
||||
protected removeQuestFromScavProfile(sessionId: string, questIdToRemove: string): void {
|
||||
const fullProfile = this.profileHelper.getFullProfile(sessionId);
|
||||
const repeatableInScavProfile = fullProfile.characters.scav.Quests?.find((x) => x.qid === questIdToRemove);
|
||||
if (!repeatableInScavProfile)
|
||||
{
|
||||
this.logger.warning(this.localisationService.getText("quest-unable_to_remove_scav_quest_from_profile",
|
||||
{
|
||||
if (!repeatableInScavProfile) {
|
||||
this.logger.warning(
|
||||
this.localisationService.getText("quest-unable_to_remove_scav_quest_from_profile", {
|
||||
scavQuestId: questIdToRemove,
|
||||
profileId: sessionId,
|
||||
}));
|
||||
}),
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
@ -580,22 +532,18 @@ export class QuestController
|
||||
protected getQuestsWithDifferentStatuses(
|
||||
preQuestStatusus: IQuestStatus[],
|
||||
postQuestStatuses: IQuestStatus[],
|
||||
): IQuestStatus[] | undefined
|
||||
{
|
||||
): IQuestStatus[] | undefined {
|
||||
const result: IQuestStatus[] = [];
|
||||
|
||||
for (const quest of postQuestStatuses)
|
||||
{
|
||||
for (const quest of postQuestStatuses) {
|
||||
// Add quest if status differs or quest not found
|
||||
const preQuest = preQuestStatusus.find((x) => x.qid === quest.qid);
|
||||
if (!preQuest || preQuest.status !== quest.status)
|
||||
{
|
||||
if (!preQuest || preQuest.status !== quest.status) {
|
||||
result.push(quest);
|
||||
}
|
||||
}
|
||||
|
||||
if (result.length === 0)
|
||||
{
|
||||
if (result.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@ -614,8 +562,7 @@ export class QuestController
|
||||
pmcData: IPmcData,
|
||||
completedQuestId: string,
|
||||
questRewards: Item[],
|
||||
): void
|
||||
{
|
||||
): void {
|
||||
const quest = this.questHelper.getQuestFromDb(completedQuestId, pmcData);
|
||||
|
||||
this.mailSendService.sendLocalisedNpcMessageToPlayer(
|
||||
@ -634,24 +581,20 @@ export class QuestController
|
||||
* @param quests Quests to look for wait conditions in
|
||||
* @param completedQuestId Quest just completed
|
||||
*/
|
||||
protected addTimeLockedQuestsToProfile(pmcData: IPmcData, quests: IQuest[], completedQuestId: string): void
|
||||
{
|
||||
protected addTimeLockedQuestsToProfile(pmcData: IPmcData, quests: IQuest[], completedQuestId: string): void {
|
||||
// Iterate over quests, look for quests with right criteria
|
||||
for (const quest of quests)
|
||||
{
|
||||
for (const quest of quests) {
|
||||
// If quest has prereq of completed quest + availableAfter value > 0 (quest has wait time)
|
||||
const nextQuestWaitCondition = quest.conditions.AvailableForStart.find(
|
||||
(x) => x.target?.includes(completedQuestId) && x.availableAfter > 0,
|
||||
);
|
||||
if (nextQuestWaitCondition)
|
||||
{
|
||||
if (nextQuestWaitCondition) {
|
||||
// Now + wait time
|
||||
const availableAfterTimestamp = this.timeUtil.getTimestamp() + nextQuestWaitCondition.availableAfter;
|
||||
|
||||
// Update quest in profile with status of AvailableAfter
|
||||
const existingQuestInProfile = pmcData.Quests.find((x) => x.qid === quest._id);
|
||||
if (existingQuestInProfile)
|
||||
{
|
||||
if (existingQuestInProfile) {
|
||||
existingQuestInProfile.availableAfter = availableAfterTimestamp;
|
||||
existingQuestInProfile.status = QuestStatus.AvailableAfter;
|
||||
existingQuestInProfile.startTime = 0;
|
||||
@ -686,21 +629,16 @@ export class QuestController
|
||||
pmcData: IPmcData,
|
||||
questsToFail: IQuest[],
|
||||
output: IItemEventRouterResponse,
|
||||
): void
|
||||
{
|
||||
for (const questToFail of questsToFail)
|
||||
{
|
||||
): void {
|
||||
for (const questToFail of questsToFail) {
|
||||
// Skip failing a quest that has a fail status of something other than success
|
||||
if (questToFail.conditions.Fail?.some((x) => x.status?.some((status) => status !== QuestStatus.Success)))
|
||||
{
|
||||
if (questToFail.conditions.Fail?.some((x) => x.status?.some((status) => status !== QuestStatus.Success))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const isActiveQuestInPlayerProfile = pmcData.Quests.find((quest) => quest.qid === questToFail._id);
|
||||
if (isActiveQuestInPlayerProfile)
|
||||
{
|
||||
if (isActiveQuestInPlayerProfile.status !== QuestStatus.Fail)
|
||||
{
|
||||
if (isActiveQuestInPlayerProfile) {
|
||||
if (isActiveQuestInPlayerProfile.status !== QuestStatus.Fail) {
|
||||
const failBody: IFailQuestRequestData = {
|
||||
Action: "QuestFail",
|
||||
qid: questToFail._id,
|
||||
@ -708,9 +646,7 @@ export class QuestController
|
||||
};
|
||||
this.questHelper.failQuest(pmcData, failBody, sessionID, output);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
// Failing an entirely new quest that doesnt exist in profile
|
||||
const statusTimers = {};
|
||||
statusTimers[QuestStatus.Fail] = this.timeUtil.getTimestamp();
|
||||
@ -736,8 +672,7 @@ export class QuestController
|
||||
pmcData: IPmcData,
|
||||
handoverQuestRequest: IHandoverQuestRequestData,
|
||||
sessionID: string,
|
||||
): IItemEventRouterResponse
|
||||
{
|
||||
): IItemEventRouterResponse {
|
||||
const quest = this.questHelper.getQuestFromDb(handoverQuestRequest.qid, pmcData);
|
||||
const handoverQuestTypes = ["HandoverItem", "WeaponAssembly"];
|
||||
const output = this.eventOutputHolder.getOutput(sessionID);
|
||||
@ -747,25 +682,22 @@ export class QuestController
|
||||
|
||||
// Decrement number of items handed in
|
||||
let handoverRequirements: IQuestCondition;
|
||||
for (const condition of quest.conditions.AvailableForFinish)
|
||||
{
|
||||
for (const condition of quest.conditions.AvailableForFinish) {
|
||||
if (
|
||||
condition.id === handoverQuestRequest.conditionId
|
||||
&& handoverQuestTypes.includes(condition.conditionType)
|
||||
)
|
||||
{
|
||||
condition.id === handoverQuestRequest.conditionId &&
|
||||
handoverQuestTypes.includes(condition.conditionType)
|
||||
) {
|
||||
handedInCount = Number.parseInt(<string>condition.value);
|
||||
isItemHandoverQuest = condition.conditionType === handoverQuestTypes[0];
|
||||
handoverRequirements = condition;
|
||||
|
||||
const profileCounter
|
||||
= handoverQuestRequest.conditionId in pmcData.TaskConditionCounters
|
||||
const profileCounter =
|
||||
handoverQuestRequest.conditionId in pmcData.TaskConditionCounters
|
||||
? pmcData.TaskConditionCounters[handoverQuestRequest.conditionId].value
|
||||
: 0;
|
||||
handedInCount -= profileCounter;
|
||||
|
||||
if (handedInCount <= 0)
|
||||
{
|
||||
if (handedInCount <= 0) {
|
||||
this.logger.error(
|
||||
this.localisationService.getText(
|
||||
"repeatable-quest_handover_failed_condition_already_satisfied",
|
||||
@ -785,17 +717,14 @@ export class QuestController
|
||||
}
|
||||
}
|
||||
|
||||
if (isItemHandoverQuest && handedInCount === 0)
|
||||
{
|
||||
if (isItemHandoverQuest && handedInCount === 0) {
|
||||
return this.showRepeatableQuestInvalidConditionError(handoverQuestRequest, output);
|
||||
}
|
||||
|
||||
let totalItemCountToRemove = 0;
|
||||
for (const itemHandover of handoverQuestRequest.items)
|
||||
{
|
||||
for (const itemHandover of handoverQuestRequest.items) {
|
||||
const matchingItemInProfile = pmcData.Inventory.items.find((item) => item._id === itemHandover.id);
|
||||
if (!(matchingItemInProfile && handoverRequirements.target.includes(matchingItemInProfile._tpl)))
|
||||
{
|
||||
if (!(matchingItemInProfile && handoverRequirements.target.includes(matchingItemInProfile._tpl))) {
|
||||
// Item handed in by player doesnt match what was requested
|
||||
return this.showQuestItemHandoverMatchError(
|
||||
handoverQuestRequest,
|
||||
@ -808,8 +737,7 @@ export class QuestController
|
||||
// Remove the right quantity of given items
|
||||
const itemCountToRemove = Math.min(itemHandover.count, handedInCount - totalItemCountToRemove);
|
||||
totalItemCountToRemove += itemCountToRemove;
|
||||
if (itemHandover.count - itemCountToRemove > 0)
|
||||
{
|
||||
if (itemHandover.count - itemCountToRemove > 0) {
|
||||
// Remove single item with no children
|
||||
this.questHelper.changeItemStack(
|
||||
pmcData,
|
||||
@ -818,13 +746,10 @@ export class QuestController
|
||||
sessionID,
|
||||
output,
|
||||
);
|
||||
if (totalItemCountToRemove === handedInCount)
|
||||
{
|
||||
if (totalItemCountToRemove === handedInCount) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
// Remove item with children
|
||||
const toRemove = this.itemHelper.findAndReturnChildrenByItems(pmcData.Inventory.items, itemHandover.id);
|
||||
let index = pmcData.Inventory.items.length;
|
||||
@ -833,17 +758,14 @@ export class QuestController
|
||||
output.profileChanges[sessionID].items.del.push({ _id: itemHandover.id });
|
||||
|
||||
// Important: loop backward when removing items from the array we're looping on
|
||||
while (index-- > 0)
|
||||
{
|
||||
if (toRemove.includes(pmcData.Inventory.items[index]._id))
|
||||
{
|
||||
while (index-- > 0) {
|
||||
if (toRemove.includes(pmcData.Inventory.items[index]._id)) {
|
||||
// Remove the item
|
||||
const removedItem = pmcData.Inventory.items.splice(index, 1)[0];
|
||||
|
||||
// If the removed item has a numeric `location` property, re-calculate all the child
|
||||
// element `location` properties of the parent so they are sequential, while retaining order
|
||||
if (typeof removedItem.location === "number")
|
||||
{
|
||||
if (typeof removedItem.location === "number") {
|
||||
const childItems = this.itemHelper.findAndReturnChildrenAsItems(
|
||||
pmcData.Inventory.items,
|
||||
removedItem.parentId,
|
||||
@ -852,8 +774,7 @@ export class QuestController
|
||||
|
||||
// Sort by the current `location` and update
|
||||
childItems.sort((a, b) => (a.location > b.location ? 1 : -1));
|
||||
for (const [index, item] of childItems.entries())
|
||||
{
|
||||
for (const [index, item] of childItems.entries()) {
|
||||
item.location = index;
|
||||
}
|
||||
}
|
||||
@ -881,8 +802,7 @@ export class QuestController
|
||||
protected showRepeatableQuestInvalidConditionError(
|
||||
handoverQuestRequest: IHandoverQuestRequestData,
|
||||
output: IItemEventRouterResponse,
|
||||
): IItemEventRouterResponse
|
||||
{
|
||||
): IItemEventRouterResponse {
|
||||
const errorMessage = this.localisationService.getText("repeatable-quest_handover_failed_condition_invalid", {
|
||||
questId: handoverQuestRequest.qid,
|
||||
conditionId: handoverQuestRequest.conditionId,
|
||||
@ -905,8 +825,7 @@ export class QuestController
|
||||
itemHandedOver: Item,
|
||||
handoverRequirements: IQuestCondition,
|
||||
output: IItemEventRouterResponse,
|
||||
): IItemEventRouterResponse
|
||||
{
|
||||
): IItemEventRouterResponse {
|
||||
const errorMessage = this.localisationService.getText("quest-handover_wrong_item", {
|
||||
questId: handoverQuestRequest.qid,
|
||||
handedInTpl: itemHandedOver?._tpl ?? "UNKNOWN",
|
||||
@ -930,10 +849,8 @@ export class QuestController
|
||||
conditionId: string,
|
||||
questId: string,
|
||||
counterValue: number,
|
||||
): void
|
||||
{
|
||||
if (pmcData.TaskConditionCounters[conditionId] !== undefined)
|
||||
{
|
||||
): void {
|
||||
if (pmcData.TaskConditionCounters[conditionId] !== undefined) {
|
||||
pmcData.TaskConditionCounters[conditionId].value += counterValue;
|
||||
|
||||
return;
|
||||
@ -959,8 +876,7 @@ export class QuestController
|
||||
request: IFailQuestRequestData,
|
||||
sessionID: string,
|
||||
output: IItemEventRouterResponse,
|
||||
): IItemEventRouterResponse
|
||||
{
|
||||
): IItemEventRouterResponse {
|
||||
this.questHelper.failQuest(pmcData, request, sessionID, output);
|
||||
|
||||
return output;
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { inject, injectable } from "tsyringe";
|
||||
import { RagfairOfferGenerator } from "@spt/generators/RagfairOfferGenerator";
|
||||
import { HandbookHelper } from "@spt/helpers/HandbookHelper";
|
||||
import { InventoryHelper } from "@spt/helpers/InventoryHelper";
|
||||
@ -43,13 +42,13 @@ import { RagfairRequiredItemsService } from "@spt/services/RagfairRequiredItemsS
|
||||
import { RagfairTaxService } from "@spt/services/RagfairTaxService";
|
||||
import { HttpResponseUtil } from "@spt/utils/HttpResponseUtil";
|
||||
import { TimeUtil } from "@spt/utils/TimeUtil";
|
||||
import { inject, injectable } from "tsyringe";
|
||||
|
||||
/**
|
||||
* Handle RagfairCallback events
|
||||
*/
|
||||
@injectable()
|
||||
export class RagfairController
|
||||
{
|
||||
export class RagfairController {
|
||||
protected ragfairConfig: IRagfairConfig;
|
||||
|
||||
constructor(
|
||||
@ -78,8 +77,7 @@ export class RagfairController
|
||||
@inject("RagfairOfferGenerator") protected ragfairOfferGenerator: RagfairOfferGenerator,
|
||||
@inject("LocalisationService") protected localisationService: LocalisationService,
|
||||
@inject("ConfigServer") protected configServer: ConfigServer,
|
||||
)
|
||||
{
|
||||
) {
|
||||
this.ragfairConfig = this.configServer.getConfig(ConfigTypes.RAGFAIR);
|
||||
}
|
||||
|
||||
@ -90,8 +88,7 @@ export class RagfairController
|
||||
* @param searchRequest Search request data
|
||||
* @returns IGetOffersResult
|
||||
*/
|
||||
public getOffers(sessionID: string, searchRequest: ISearchRequestData): IGetOffersResult
|
||||
{
|
||||
public getOffers(sessionID: string, searchRequest: ISearchRequestData): IGetOffersResult {
|
||||
const profile = this.profileHelper.getFullProfile(sessionID);
|
||||
|
||||
const itemsToAdd = this.ragfairHelper.filterCategories(sessionID, searchRequest);
|
||||
@ -105,8 +102,7 @@ export class RagfairController
|
||||
result.offers = this.getOffersForSearchType(searchRequest, itemsToAdd, traderAssorts, profile.characters.pmc);
|
||||
|
||||
// Client requested a category refresh
|
||||
if (searchRequest.updateOfferCount)
|
||||
{
|
||||
if (searchRequest.updateOfferCount) {
|
||||
result.categories = this.getSpecificCategories(profile.characters.pmc, searchRequest, result.offers);
|
||||
}
|
||||
|
||||
@ -120,14 +116,11 @@ export class RagfairController
|
||||
);
|
||||
|
||||
// Match offers with quests and lock unfinished quests
|
||||
for (const offer of result.offers)
|
||||
{
|
||||
if (offer.user.memberType === MemberCategory.TRADER)
|
||||
{
|
||||
for (const offer of result.offers) {
|
||||
if (offer.user.memberType === MemberCategory.TRADER) {
|
||||
// for the items, check the barter schemes. The method getDisplayableAssorts sets a flag sptQuestLocked
|
||||
// to true if the quest is not completed yet
|
||||
if (this.ragfairOfferHelper.traderOfferItemQuestLocked(offer, traderAssorts))
|
||||
{
|
||||
if (this.ragfairOfferHelper.traderOfferItemQuestLocked(offer, traderAssorts)) {
|
||||
offer.locked = true;
|
||||
}
|
||||
|
||||
@ -140,8 +133,7 @@ export class RagfairController
|
||||
result.offersCount = result.offers.length;
|
||||
|
||||
// Handle paging before returning results only if searching for general items, not preset items
|
||||
if (searchRequest.buildCount === 0)
|
||||
{
|
||||
if (searchRequest.buildCount === 0) {
|
||||
const start = searchRequest.page * searchRequest.limit;
|
||||
const end = Math.min((searchRequest.page + 1) * searchRequest.limit, result.offers.length);
|
||||
result.offers = result.offers.slice(start, end);
|
||||
@ -156,8 +148,7 @@ export class RagfairController
|
||||
* @param request Request data
|
||||
* @returns IRagfairOffer
|
||||
*/
|
||||
public getOfferById(sessionId: string, request: IGetRagfairOfferByIdRequest): IRagfairOffer
|
||||
{
|
||||
public getOfferById(sessionId: string, request: IGetRagfairOfferByIdRequest): IRagfairOffer {
|
||||
const offers = this.ragfairOfferService.getOffers();
|
||||
const offerToReturn = offers.find((offer) => offer.intId === request.id);
|
||||
|
||||
@ -177,16 +168,13 @@ export class RagfairController
|
||||
itemsToAdd: string[],
|
||||
traderAssorts: Record<string, ITraderAssort>,
|
||||
pmcProfile: IPmcData,
|
||||
): IRagfairOffer[]
|
||||
{
|
||||
): IRagfairOffer[] {
|
||||
// Searching for items in preset menu
|
||||
if (searchRequest.buildCount)
|
||||
{
|
||||
if (searchRequest.buildCount) {
|
||||
return this.ragfairOfferHelper.getOffersForBuild(searchRequest, itemsToAdd, traderAssorts, pmcProfile);
|
||||
}
|
||||
|
||||
if (searchRequest.neededSearchId?.length > 0)
|
||||
{
|
||||
if (searchRequest.neededSearchId?.length > 0) {
|
||||
return this.ragfairOfferHelper.getOffersThatRequireItem(searchRequest, pmcProfile);
|
||||
}
|
||||
|
||||
@ -204,23 +192,17 @@ export class RagfairController
|
||||
pmcProfile: IPmcData,
|
||||
searchRequest: ISearchRequestData,
|
||||
offers: IRagfairOffer[],
|
||||
): Record<string, number>
|
||||
{
|
||||
): Record<string, number> {
|
||||
// Linked/required search categories
|
||||
const playerHasFleaUnlocked
|
||||
= pmcProfile.Info.Level >= this.databaseService.getGlobals().config.RagFair.minUserLevel;
|
||||
const playerHasFleaUnlocked =
|
||||
pmcProfile.Info.Level >= this.databaseService.getGlobals().config.RagFair.minUserLevel;
|
||||
let offerPool = [];
|
||||
if (this.isLinkedSearch(searchRequest) || this.isRequiredSearch(searchRequest))
|
||||
{
|
||||
if (this.isLinkedSearch(searchRequest) || this.isRequiredSearch(searchRequest)) {
|
||||
offerPool = offers;
|
||||
}
|
||||
else if (!(this.isLinkedSearch(searchRequest) || this.isRequiredSearch(searchRequest)))
|
||||
{
|
||||
} else if (!(this.isLinkedSearch(searchRequest) || this.isRequiredSearch(searchRequest))) {
|
||||
// Get all categories
|
||||
offerPool = this.ragfairOfferService.getOffers();
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
this.logger.error(this.localisationService.getText("ragfair-unable_to_get_categories"));
|
||||
this.logger.debug(JSON.stringify(searchRequest));
|
||||
return {};
|
||||
@ -233,12 +215,10 @@ export class RagfairController
|
||||
* Add index to all offers passed in (0-indexed)
|
||||
* @param offers Offers to add index value to
|
||||
*/
|
||||
protected addIndexValueToOffers(offers: IRagfairOffer[]): void
|
||||
{
|
||||
protected addIndexValueToOffers(offers: IRagfairOffer[]): void {
|
||||
let counter = 0;
|
||||
|
||||
for (const offer of offers)
|
||||
{
|
||||
for (const offer of offers) {
|
||||
offer.intId = ++counter;
|
||||
offer.items[0].parentId = ""; // Without this it causes error: "Item deserialization error: No parent with id hideout found for item x"
|
||||
}
|
||||
@ -249,8 +229,7 @@ export class RagfairController
|
||||
* @param offer Flea offer to update
|
||||
* @param fullProfile Players full profile
|
||||
*/
|
||||
protected setTraderOfferPurchaseLimits(offer: IRagfairOffer, fullProfile: ISptProfile): void
|
||||
{
|
||||
protected setTraderOfferPurchaseLimits(offer: IRagfairOffer, fullProfile: ISptProfile): void {
|
||||
// No trader found, create a blank record for them
|
||||
fullProfile.traderPurchases[offer.user.id] ||= {};
|
||||
|
||||
@ -270,14 +249,12 @@ export class RagfairController
|
||||
* Adjust ragfair offer stack count to match same value as traders assort stack count
|
||||
* @param offer Flea offer to adjust stack size of
|
||||
*/
|
||||
protected setTraderOfferStackSize(offer: IRagfairOffer): void
|
||||
{
|
||||
protected setTraderOfferStackSize(offer: IRagfairOffer): void {
|
||||
const firstItem = offer.items[0];
|
||||
const traderAssorts = this.traderHelper.getTraderAssortsByTraderId(offer.user.id).items;
|
||||
|
||||
const assortPurchased = traderAssorts.find((x) => x._id === offer.items[0]._id);
|
||||
if (!assortPurchased)
|
||||
{
|
||||
if (!assortPurchased) {
|
||||
this.logger.warning(
|
||||
this.localisationService.getText("ragfair-unable_to_adjust_stack_count_assort_not_found", {
|
||||
offerId: offer.items[0]._id,
|
||||
@ -296,8 +273,7 @@ export class RagfairController
|
||||
* @param info Search request
|
||||
* @returns True if it is a 'linked' search type
|
||||
*/
|
||||
protected isLinkedSearch(info: ISearchRequestData): boolean
|
||||
{
|
||||
protected isLinkedSearch(info: ISearchRequestData): boolean {
|
||||
return info.linkedSearchId !== "";
|
||||
}
|
||||
|
||||
@ -306,26 +282,22 @@ export class RagfairController
|
||||
* @param info Search request
|
||||
* @returns True if it is a 'required' search type
|
||||
*/
|
||||
protected isRequiredSearch(info: ISearchRequestData): boolean
|
||||
{
|
||||
protected isRequiredSearch(info: ISearchRequestData): boolean {
|
||||
return info.neededSearchId !== "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Check all profiles and sell player offers / send player money for listing if it sold
|
||||
*/
|
||||
public update(): void
|
||||
{
|
||||
public update(): void {
|
||||
const profilesDict = this.saveServer.getProfiles();
|
||||
for (const sessionID in this.saveServer.getProfiles())
|
||||
{
|
||||
for (const sessionID in this.saveServer.getProfiles()) {
|
||||
// Check profile is capable of creating offers
|
||||
const pmcProfile = profilesDict[sessionID].characters.pmc;
|
||||
if (
|
||||
pmcProfile.RagfairInfo !== undefined
|
||||
&& pmcProfile.Info.Level >= this.databaseService.getGlobals().config.RagFair.minUserLevel
|
||||
)
|
||||
{
|
||||
pmcProfile.RagfairInfo !== undefined &&
|
||||
pmcProfile.Info.Level >= this.databaseService.getGlobals().config.RagFair.minUserLevel
|
||||
) {
|
||||
this.ragfairOfferHelper.processOffersOnProfile(sessionID);
|
||||
}
|
||||
}
|
||||
@ -336,26 +308,22 @@ export class RagfairController
|
||||
* @param getPriceRequest
|
||||
* @returns min/avg/max values for an item based on flea offers available
|
||||
*/
|
||||
public getItemMinAvgMaxFleaPriceValues(getPriceRequest: IGetMarketPriceRequestData): IGetItemPriceResult
|
||||
{
|
||||
public getItemMinAvgMaxFleaPriceValues(getPriceRequest: IGetMarketPriceRequestData): IGetItemPriceResult {
|
||||
// Get all items of tpl
|
||||
const offers = this.ragfairOfferService.getOffersOfType(getPriceRequest.templateId);
|
||||
|
||||
// Offers exist for item, get averages of what's listed
|
||||
if (typeof offers === "object" && offers.length > 0)
|
||||
{
|
||||
if (typeof offers === "object" && offers.length > 0) {
|
||||
// These get calculated while iterating through the list below
|
||||
let min = Number.MAX_VALUE;
|
||||
let max = 0;
|
||||
|
||||
// Get the average offer price, excluding barter offers
|
||||
let avgOfferCount = 0;
|
||||
const avg
|
||||
= offers.reduce((sum, offer) =>
|
||||
{
|
||||
const avg =
|
||||
offers.reduce((sum, offer) => {
|
||||
// Exclude barter items, they tend to have outrageous equivalent prices
|
||||
if (offer.requirements.some((req) => !this.paymentHelper.isMoneyTpl(req._tpl)))
|
||||
{
|
||||
if (offer.requirements.some((req) => !this.paymentHelper.isMoneyTpl(req._tpl))) {
|
||||
return sum;
|
||||
}
|
||||
|
||||
@ -366,12 +334,9 @@ export class RagfairController
|
||||
const perItemPrice = offer.requirementsCost / offerItemCount;
|
||||
|
||||
// Handle min/max calculations based on the per-item price
|
||||
if (perItemPrice < min)
|
||||
{
|
||||
if (perItemPrice < min) {
|
||||
min = perItemPrice;
|
||||
}
|
||||
else if (perItemPrice > max)
|
||||
{
|
||||
} else if (perItemPrice > max) {
|
||||
max = perItemPrice;
|
||||
}
|
||||
|
||||
@ -380,8 +345,7 @@ export class RagfairController
|
||||
}, 0) / Math.max(avgOfferCount, 1);
|
||||
|
||||
// If no items were actually counted, min will still be MAX_VALUE, so set it to 0
|
||||
if (min === Number.MAX_VALUE)
|
||||
{
|
||||
if (min === Number.MAX_VALUE) {
|
||||
min = 0;
|
||||
}
|
||||
|
||||
@ -390,8 +354,7 @@ export class RagfairController
|
||||
|
||||
// No offers listed, get price from live ragfair price list prices.json
|
||||
let tplPrice = this.databaseService.getPrices()[getPriceRequest.templateId];
|
||||
if (!tplPrice)
|
||||
{
|
||||
if (!tplPrice) {
|
||||
// No flea price, get handbook price
|
||||
tplPrice = this.handbookHelper.getTemplatePrice(getPriceRequest.templateId);
|
||||
}
|
||||
@ -410,25 +373,21 @@ export class RagfairController
|
||||
pmcData: IPmcData,
|
||||
offerRequest: IAddOfferRequestData,
|
||||
sessionID: string,
|
||||
): IItemEventRouterResponse
|
||||
{
|
||||
): IItemEventRouterResponse {
|
||||
const output = this.eventOutputHolder.getOutput(sessionID);
|
||||
const fullProfile = this.saveServer.getProfile(sessionID);
|
||||
|
||||
const validationMessage = "";
|
||||
if (!this.isValidPlayerOfferRequest(offerRequest, validationMessage))
|
||||
{
|
||||
if (!this.isValidPlayerOfferRequest(offerRequest, validationMessage)) {
|
||||
return this.httpResponse.appendErrorToOutput(output, validationMessage);
|
||||
}
|
||||
|
||||
const typeOfOffer = this.getOfferType(offerRequest);
|
||||
if (typeOfOffer === FleaOfferType.UNKNOWN)
|
||||
{
|
||||
if (typeOfOffer === FleaOfferType.UNKNOWN) {
|
||||
return this.httpResponse.appendErrorToOutput(output, "Unknown offer type, cannot list item on flea");
|
||||
}
|
||||
|
||||
switch (typeOfOffer)
|
||||
{
|
||||
switch (typeOfOffer) {
|
||||
case FleaOfferType.SINGLE:
|
||||
return this.createSingleOffer(sessionID, offerRequest, fullProfile, output);
|
||||
case FleaOfferType.MULTI:
|
||||
@ -451,16 +410,15 @@ export class RagfairController
|
||||
sessionID: string,
|
||||
offerRequest: IAddOfferRequestData,
|
||||
fullProfile: ISptProfile,
|
||||
output: IItemEventRouterResponse): IItemEventRouterResponse
|
||||
{
|
||||
output: IItemEventRouterResponse,
|
||||
): IItemEventRouterResponse {
|
||||
const pmcData = fullProfile.characters.pmc;
|
||||
const itemsToListCount = offerRequest.items.length; // Does not count stack size, only items
|
||||
|
||||
// Find items to be listed on flea from player inventory
|
||||
const { items: itemsAndChildrenInInventoryToList, errorMessage: itemsInInventoryError }
|
||||
= this.getItemsToListOnFleaFromInventory(pmcData, offerRequest.items);
|
||||
if (!itemsAndChildrenInInventoryToList || itemsInInventoryError)
|
||||
{
|
||||
const { items: itemsAndChildrenInInventoryToList, errorMessage: itemsInInventoryError } =
|
||||
this.getItemsToListOnFleaFromInventory(pmcData, offerRequest.items);
|
||||
if (!itemsAndChildrenInInventoryToList || itemsInInventoryError) {
|
||||
this.httpResponse.appendErrorToOutput(output, itemsInInventoryError);
|
||||
}
|
||||
|
||||
@ -485,8 +443,7 @@ export class RagfairController
|
||||
|
||||
// Check for and apply item price modifer if it exists in config
|
||||
const itemPriceModifer = this.ragfairConfig.dynamic.itemPriceMultiplier[rootItem._tpl];
|
||||
if (itemPriceModifer)
|
||||
{
|
||||
if (itemPriceModifer) {
|
||||
averageOfferPriceSingleItem *= itemPriceModifer;
|
||||
}
|
||||
|
||||
@ -502,8 +459,7 @@ export class RagfairController
|
||||
offer.sellResult = this.ragfairSellHelper.rollForSale(sellChancePercent, stackCountTotal);
|
||||
|
||||
// Subtract flea market fee from stash
|
||||
if (this.ragfairConfig.sell.fees)
|
||||
{
|
||||
if (this.ragfairConfig.sell.fees) {
|
||||
const taxFeeChargeFailed = this.chargePlayerTaxFee(
|
||||
sessionID,
|
||||
rootItem,
|
||||
@ -513,8 +469,7 @@ export class RagfairController
|
||||
offerRequest,
|
||||
output,
|
||||
);
|
||||
if (taxFeeChargeFailed)
|
||||
{
|
||||
if (taxFeeChargeFailed) {
|
||||
return output;
|
||||
}
|
||||
}
|
||||
@ -524,8 +479,7 @@ export class RagfairController
|
||||
output.profileChanges[sessionID].ragFairOffers.push(offer);
|
||||
|
||||
// Remove items from inventory after creating offer
|
||||
for (const itemToRemove of offerRequest.items)
|
||||
{
|
||||
for (const itemToRemove of offerRequest.items) {
|
||||
this.inventoryHelper.removeItem(pmcData, itemToRemove, sessionID, output);
|
||||
}
|
||||
|
||||
@ -546,8 +500,8 @@ export class RagfairController
|
||||
sessionID: string,
|
||||
offerRequest: IAddOfferRequestData,
|
||||
fullProfile: ISptProfile,
|
||||
output: IItemEventRouterResponse): IItemEventRouterResponse
|
||||
{
|
||||
output: IItemEventRouterResponse,
|
||||
): IItemEventRouterResponse {
|
||||
const pmcData = fullProfile.characters.pmc;
|
||||
const itemsToListCount = offerRequest.items.length; // Does not count stack size, only items
|
||||
|
||||
@ -555,13 +509,13 @@ export class RagfairController
|
||||
// Get first item and its children and use as template
|
||||
const firstListingAndChidren = this.itemHelper.findAndReturnChildrenAsItems(
|
||||
pmcData.Inventory.items,
|
||||
offerRequest.items[0]);
|
||||
offerRequest.items[0],
|
||||
);
|
||||
|
||||
// Find items to be listed on flea (+ children) from player inventory
|
||||
const { items: itemsAndChildrenInInventoryToList, errorMessage: itemsInInventoryError }
|
||||
= this.getItemsToListOnFleaFromInventory(pmcData, offerRequest.items);
|
||||
if (!itemsAndChildrenInInventoryToList || itemsInInventoryError)
|
||||
{
|
||||
const { items: itemsAndChildrenInInventoryToList, errorMessage: itemsInInventoryError } =
|
||||
this.getItemsToListOnFleaFromInventory(pmcData, offerRequest.items);
|
||||
if (!itemsAndChildrenInInventoryToList || itemsInInventoryError) {
|
||||
this.httpResponse.appendErrorToOutput(output, itemsInInventoryError);
|
||||
}
|
||||
|
||||
@ -570,19 +524,13 @@ export class RagfairController
|
||||
|
||||
// When listing identical items on flea, condense separate items into one stack with a merged stack count
|
||||
// e.g. 2 ammo items, stackObjectCount = 3 for each, will result in 1 stack of 6
|
||||
if (!firstListingAndChidren[0].upd)
|
||||
{
|
||||
if (!firstListingAndChidren[0].upd) {
|
||||
firstListingAndChidren[0].upd = {};
|
||||
}
|
||||
firstListingAndChidren[0].upd.StackObjectsCount = stackCountTotal;
|
||||
|
||||
// Create flea object
|
||||
const offer = this.createPlayerOffer(
|
||||
sessionID,
|
||||
offerRequest.requirements,
|
||||
firstListingAndChidren,
|
||||
false,
|
||||
);
|
||||
const offer = this.createPlayerOffer(sessionID, offerRequest.requirements, firstListingAndChidren, false);
|
||||
|
||||
// This is the item that will be listed on flea, has merged stackObjectCount
|
||||
const newRootOfferItem = offer.items[0];
|
||||
@ -592,8 +540,7 @@ export class RagfairController
|
||||
|
||||
// Check for and apply item price modifer if it exists in config
|
||||
const itemPriceModifer = this.ragfairConfig.dynamic.itemPriceMultiplier[newRootOfferItem._tpl];
|
||||
if (itemPriceModifer)
|
||||
{
|
||||
if (itemPriceModifer) {
|
||||
averageOfferPrice *= itemPriceModifer;
|
||||
}
|
||||
|
||||
@ -617,8 +564,7 @@ export class RagfairController
|
||||
offer.sellResult = this.ragfairSellHelper.rollForSale(sellChancePercent, stackCountTotal);
|
||||
|
||||
// Subtract flea market fee from stash
|
||||
if (this.ragfairConfig.sell.fees)
|
||||
{
|
||||
if (this.ragfairConfig.sell.fees) {
|
||||
const taxFeeChargeFailed = this.chargePlayerTaxFee(
|
||||
sessionID,
|
||||
newRootOfferItem,
|
||||
@ -628,8 +574,7 @@ export class RagfairController
|
||||
offerRequest,
|
||||
output,
|
||||
);
|
||||
if (taxFeeChargeFailed)
|
||||
{
|
||||
if (taxFeeChargeFailed) {
|
||||
return output;
|
||||
}
|
||||
}
|
||||
@ -639,8 +584,7 @@ export class RagfairController
|
||||
output.profileChanges[sessionID].ragFairOffers.push(offer);
|
||||
|
||||
// Remove items from inventory after creating offer
|
||||
for (const itemToRemove of offerRequest.items)
|
||||
{
|
||||
for (const itemToRemove of offerRequest.items) {
|
||||
this.inventoryHelper.removeItem(pmcData, itemToRemove, sessionID, output);
|
||||
}
|
||||
|
||||
@ -661,8 +605,8 @@ export class RagfairController
|
||||
sessionID: string,
|
||||
offerRequest: IAddOfferRequestData,
|
||||
fullProfile: ISptProfile,
|
||||
output: IItemEventRouterResponse): IItemEventRouterResponse
|
||||
{
|
||||
output: IItemEventRouterResponse,
|
||||
): IItemEventRouterResponse {
|
||||
const pmcData = fullProfile.characters.pmc;
|
||||
const itemsToListCount = offerRequest.items.length; // Does not count stack size, only items
|
||||
|
||||
@ -670,13 +614,13 @@ export class RagfairController
|
||||
// Get first item and its children and use as template
|
||||
const firstListingAndChidren = this.itemHelper.findAndReturnChildrenAsItems(
|
||||
pmcData.Inventory.items,
|
||||
offerRequest.items[0]);
|
||||
offerRequest.items[0],
|
||||
);
|
||||
|
||||
// Find items to be listed on flea (+ children) from player inventory
|
||||
const { items: itemsAndChildrenInInventoryToList, errorMessage: itemsInInventoryError }
|
||||
= this.getItemsToListOnFleaFromInventory(pmcData, offerRequest.items);
|
||||
if (!itemsAndChildrenInInventoryToList || itemsInInventoryError)
|
||||
{
|
||||
const { items: itemsAndChildrenInInventoryToList, errorMessage: itemsInInventoryError } =
|
||||
this.getItemsToListOnFleaFromInventory(pmcData, offerRequest.items);
|
||||
if (!itemsAndChildrenInInventoryToList || itemsInInventoryError) {
|
||||
this.httpResponse.appendErrorToOutput(output, itemsInInventoryError);
|
||||
}
|
||||
|
||||
@ -685,19 +629,13 @@ export class RagfairController
|
||||
|
||||
// When listing identical items on flea, condense separate items into one stack with a merged stack count
|
||||
// e.g. 2 ammo items, stackObjectCount = 3 for each, will result in 1 stack of 6
|
||||
if (!firstListingAndChidren[0].upd)
|
||||
{
|
||||
if (!firstListingAndChidren[0].upd) {
|
||||
firstListingAndChidren[0].upd = {};
|
||||
}
|
||||
firstListingAndChidren[0].upd.StackObjectsCount = stackCountTotal;
|
||||
|
||||
// Create flea object
|
||||
const offer = this.createPlayerOffer(
|
||||
sessionID,
|
||||
offerRequest.requirements,
|
||||
firstListingAndChidren,
|
||||
true,
|
||||
);
|
||||
const offer = this.createPlayerOffer(sessionID, offerRequest.requirements, firstListingAndChidren, true);
|
||||
|
||||
// This is the item that will be listed on flea, has merged stackObjectCount
|
||||
const newRootOfferItem = offer.items[0];
|
||||
@ -707,8 +645,7 @@ export class RagfairController
|
||||
|
||||
// Check for and apply item price modifer if it exists in config
|
||||
const itemPriceModifer = this.ragfairConfig.dynamic.itemPriceMultiplier[newRootOfferItem._tpl];
|
||||
if (itemPriceModifer)
|
||||
{
|
||||
if (itemPriceModifer) {
|
||||
singleItemPrice *= itemPriceModifer;
|
||||
}
|
||||
|
||||
@ -732,8 +669,7 @@ export class RagfairController
|
||||
offer.sellResult = this.ragfairSellHelper.rollForSale(sellChancePercent, stackCountTotal, true);
|
||||
|
||||
// Subtract flea market fee from stash
|
||||
if (this.ragfairConfig.sell.fees)
|
||||
{
|
||||
if (this.ragfairConfig.sell.fees) {
|
||||
const taxFeeChargeFailed = this.chargePlayerTaxFee(
|
||||
sessionID,
|
||||
newRootOfferItem,
|
||||
@ -743,8 +679,7 @@ export class RagfairController
|
||||
offerRequest,
|
||||
output,
|
||||
);
|
||||
if (taxFeeChargeFailed)
|
||||
{
|
||||
if (taxFeeChargeFailed) {
|
||||
return output;
|
||||
}
|
||||
}
|
||||
@ -754,8 +689,7 @@ export class RagfairController
|
||||
output.profileChanges[sessionID].ragFairOffers.push(offer);
|
||||
|
||||
// Remove items from inventory after creating offer
|
||||
for (const itemToRemove of offerRequest.items)
|
||||
{
|
||||
for (const itemToRemove of offerRequest.items) {
|
||||
this.inventoryHelper.removeItem(pmcData, itemToRemove, sessionID, output);
|
||||
}
|
||||
|
||||
@ -768,18 +702,12 @@ export class RagfairController
|
||||
* @param offerRequest Client request
|
||||
* @returns FleaOfferType
|
||||
*/
|
||||
protected getOfferType(offerRequest: IAddOfferRequestData): FleaOfferType
|
||||
{
|
||||
if (offerRequest.items.length == 1 && !offerRequest.sellInOnePiece)
|
||||
{
|
||||
protected getOfferType(offerRequest: IAddOfferRequestData): FleaOfferType {
|
||||
if (offerRequest.items.length == 1 && !offerRequest.sellInOnePiece) {
|
||||
return FleaOfferType.SINGLE;
|
||||
}
|
||||
else if (offerRequest.items.length > 1 && !offerRequest.sellInOnePiece)
|
||||
{
|
||||
} else if (offerRequest.items.length > 1 && !offerRequest.sellInOnePiece) {
|
||||
return FleaOfferType.MULTI;
|
||||
}
|
||||
else if (offerRequest.sellInOnePiece)
|
||||
{
|
||||
} else if (offerRequest.sellInOnePiece) {
|
||||
return FleaOfferType.PACK;
|
||||
}
|
||||
|
||||
@ -805,8 +733,7 @@ export class RagfairController
|
||||
itemStackCount: number,
|
||||
offerRequest: IAddOfferRequestData,
|
||||
output: IItemEventRouterResponse,
|
||||
): boolean
|
||||
{
|
||||
): boolean {
|
||||
// Get tax from cache hydrated earlier by client, if that's missing fall back to server calculation (inaccurate)
|
||||
const storedClientTaxValue = this.ragfairTaxService.getStoredClientOfferTaxValueById(offerRequest.items[0]);
|
||||
const tax = storedClientTaxValue
|
||||
@ -826,8 +753,7 @@ export class RagfairController
|
||||
|
||||
const buyTradeRequest = this.createBuyTradeRequestObject("RUB", tax);
|
||||
this.paymentService.payMoney(pmcData, buyTradeRequest, sessionID, output);
|
||||
if (output.warnings.length > 0)
|
||||
{
|
||||
if (output.warnings.length > 0) {
|
||||
this.httpResponse.appendErrorToOutput(
|
||||
output,
|
||||
this.localisationService.getText("ragfair-unable_to_pay_commission_fee", tax),
|
||||
@ -844,17 +770,14 @@ export class RagfairController
|
||||
* @param errorMessage message to show to player when offer is invalid
|
||||
* @returns Is offer valid
|
||||
*/
|
||||
protected isValidPlayerOfferRequest(offerRequest: IAddOfferRequestData, errorMessage: string): boolean
|
||||
{
|
||||
if (!offerRequest?.items || offerRequest.items.length === 0)
|
||||
{
|
||||
protected isValidPlayerOfferRequest(offerRequest: IAddOfferRequestData, errorMessage: string): boolean {
|
||||
if (!offerRequest?.items || offerRequest.items.length === 0) {
|
||||
this.logger.error(this.localisationService.getText("ragfair-invalid_player_offer_request"));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!offerRequest.requirements)
|
||||
{
|
||||
if (!offerRequest.requirements) {
|
||||
this.logger.error(this.localisationService.getText("ragfair-unable_to_place_offer_with_no_requirements"));
|
||||
|
||||
return false;
|
||||
@ -868,21 +791,16 @@ export class RagfairController
|
||||
* @param requirements
|
||||
* @returns Rouble price
|
||||
*/
|
||||
protected calculateRequirementsPriceInRub(requirements: Requirement[]): number
|
||||
{
|
||||
protected calculateRequirementsPriceInRub(requirements: Requirement[]): number {
|
||||
let requirementsPriceInRub = 0;
|
||||
for (const item of requirements)
|
||||
{
|
||||
for (const item of requirements) {
|
||||
const requestedItemTpl = item._tpl;
|
||||
|
||||
if (this.paymentHelper.isMoneyTpl(requestedItemTpl))
|
||||
{
|
||||
if (this.paymentHelper.isMoneyTpl(requestedItemTpl)) {
|
||||
requirementsPriceInRub += this.handbookHelper.inRUB(item.count, requestedItemTpl);
|
||||
}
|
||||
else
|
||||
{
|
||||
requirementsPriceInRub
|
||||
+= this.ragfairPriceService.getDynamicPriceForItem(requestedItemTpl) * item.count;
|
||||
} else {
|
||||
requirementsPriceInRub +=
|
||||
this.ragfairPriceService.getDynamicPriceForItem(requestedItemTpl) * item.count;
|
||||
}
|
||||
}
|
||||
|
||||
@ -898,17 +816,14 @@ export class RagfairController
|
||||
protected getItemsToListOnFleaFromInventory(
|
||||
pmcData: IPmcData,
|
||||
itemIdsFromFleaOfferRequest: string[],
|
||||
): { items: Item[][] | undefined, errorMessage: string | undefined }
|
||||
{
|
||||
): { items: Item[][] | undefined; errorMessage: string | undefined } {
|
||||
const itemsToReturn: Item[][] = [];
|
||||
let errorMessage: string | undefined = undefined;
|
||||
|
||||
// Count how many items are being sold and multiply the requested amount accordingly
|
||||
for (const itemId of itemIdsFromFleaOfferRequest)
|
||||
{
|
||||
for (const itemId of itemIdsFromFleaOfferRequest) {
|
||||
let item = pmcData.Inventory.items.find((i) => i._id === itemId);
|
||||
if (!item)
|
||||
{
|
||||
if (!item) {
|
||||
errorMessage = this.localisationService.getText("ragfair-unable_to_find_item_in_inventory", {
|
||||
id: itemId,
|
||||
});
|
||||
@ -921,8 +836,7 @@ export class RagfairController
|
||||
itemsToReturn.push(this.itemHelper.findAndReturnChildrenAsItems(pmcData.Inventory.items, itemId));
|
||||
}
|
||||
|
||||
if (!itemsToReturn?.length)
|
||||
{
|
||||
if (!itemsToReturn?.length) {
|
||||
errorMessage = this.localisationService.getText("ragfair-unable_to_find_requested_items_in_inventory");
|
||||
this.logger.error(errorMessage);
|
||||
|
||||
@ -937,11 +851,9 @@ export class RagfairController
|
||||
requirements: Requirement[],
|
||||
items: Item[],
|
||||
sellInOnePiece: boolean,
|
||||
): IRagfairOffer
|
||||
{
|
||||
): IRagfairOffer {
|
||||
const loyalLevel = 1;
|
||||
const formattedItems: Item[] = items.map((item) =>
|
||||
{
|
||||
const formattedItems: Item[] = items.map((item) => {
|
||||
const isChild = items.some((subItem) => subItem._id === item.parentId);
|
||||
|
||||
return {
|
||||
@ -953,12 +865,12 @@ export class RagfairController
|
||||
};
|
||||
});
|
||||
|
||||
const formattedRequirements: IBarterScheme[] = requirements.map((item) =>
|
||||
{
|
||||
const formattedRequirements: IBarterScheme[] = requirements.map((item) => {
|
||||
return {
|
||||
_tpl: item._tpl,
|
||||
count: item.count,
|
||||
onlyFunctional: item.onlyFunctional };
|
||||
onlyFunctional: item.onlyFunctional,
|
||||
};
|
||||
});
|
||||
|
||||
return this.ragfairOfferGenerator.createAndAddFleaOffer(
|
||||
@ -971,13 +883,11 @@ export class RagfairController
|
||||
);
|
||||
}
|
||||
|
||||
public getAllFleaPrices(): Record<string, number>
|
||||
{
|
||||
public getAllFleaPrices(): Record<string, number> {
|
||||
return this.ragfairPriceService.getAllFleaPrices();
|
||||
}
|
||||
|
||||
public getStaticPrices(): Record<string, number>
|
||||
{
|
||||
public getStaticPrices(): Record<string, number> {
|
||||
return this.ragfairPriceService.getAllStaticPrices();
|
||||
}
|
||||
|
||||
@ -988,14 +898,12 @@ export class RagfairController
|
||||
* @param sessionId Players id
|
||||
* @returns IItemEventRouterResponse
|
||||
*/
|
||||
public removeOffer(removeRequest: IRemoveOfferRequestData, sessionId: string): IItemEventRouterResponse
|
||||
{
|
||||
public removeOffer(removeRequest: IRemoveOfferRequestData, sessionId: string): IItemEventRouterResponse {
|
||||
const output = this.eventOutputHolder.getOutput(sessionId);
|
||||
|
||||
const pmcData = this.saveServer.getProfile(sessionId).characters.pmc;
|
||||
const playerProfileOffers = pmcData.RagfairInfo.offers;
|
||||
if (!playerProfileOffers)
|
||||
{
|
||||
if (!playerProfileOffers) {
|
||||
this.logger.warning(
|
||||
this.localisationService.getText("ragfair-unable_to_remove_offer_not_found_in_profile", {
|
||||
profileId: sessionId,
|
||||
@ -1007,8 +915,7 @@ export class RagfairController
|
||||
}
|
||||
|
||||
const playerOfferIndex = playerProfileOffers.findIndex((offer) => offer._id === removeRequest.offerId);
|
||||
if (playerOfferIndex === -1)
|
||||
{
|
||||
if (playerOfferIndex === -1) {
|
||||
this.logger.error(
|
||||
this.localisationService.getText("ragfair-offer_not_found_in_profile", {
|
||||
offerId: removeRequest.offerId,
|
||||
@ -1021,8 +928,7 @@ export class RagfairController
|
||||
}
|
||||
|
||||
const differenceInSeconds = playerProfileOffers[playerOfferIndex].endTime - this.timeUtil.getTimestamp();
|
||||
if (differenceInSeconds > this.ragfairConfig.sell.expireSeconds)
|
||||
{
|
||||
if (differenceInSeconds > this.ragfairConfig.sell.expireSeconds) {
|
||||
// `expireSeconds` Default is 71 seconds
|
||||
const newEndTime = this.ragfairConfig.sell.expireSeconds + this.timeUtil.getTimestamp();
|
||||
playerProfileOffers[playerOfferIndex].endTime = Math.round(newEndTime);
|
||||
@ -1037,8 +943,7 @@ export class RagfairController
|
||||
* @param sessionId Players id
|
||||
* @returns IItemEventRouterResponse
|
||||
*/
|
||||
public extendOffer(extendRequest: IExtendOfferRequestData, sessionId: string): IItemEventRouterResponse
|
||||
{
|
||||
public extendOffer(extendRequest: IExtendOfferRequestData, sessionId: string): IItemEventRouterResponse {
|
||||
const output = this.eventOutputHolder.getOutput(sessionId);
|
||||
|
||||
const pmcData = this.saveServer.getProfile(sessionId).characters.pmc;
|
||||
@ -1046,8 +951,7 @@ export class RagfairController
|
||||
const playerOfferIndex = playerOffers.findIndex((offer) => offer._id === extendRequest.offerId);
|
||||
const secondsToAdd = extendRequest.renewalTime * TimeUtil.ONE_HOUR_AS_SECONDS;
|
||||
|
||||
if (playerOfferIndex === -1)
|
||||
{
|
||||
if (playerOfferIndex === -1) {
|
||||
this.logger.warning(
|
||||
this.localisationService.getText("ragfair-offer_not_found_in_profile", {
|
||||
offerId: extendRequest.offerId,
|
||||
@ -1060,12 +964,10 @@ export class RagfairController
|
||||
}
|
||||
|
||||
// MOD: Pay flea market fee
|
||||
if (this.ragfairConfig.sell.fees)
|
||||
{
|
||||
if (this.ragfairConfig.sell.fees) {
|
||||
const count = playerOffers[playerOfferIndex].sellInOnePiece
|
||||
? 1
|
||||
: playerOffers[playerOfferIndex].items.reduce((sum, item) =>
|
||||
{
|
||||
: playerOffers[playerOfferIndex].items.reduce((sum, item) => {
|
||||
return sum + item.upd.StackObjectsCount;
|
||||
}, 0);
|
||||
|
||||
@ -1079,8 +981,7 @@ export class RagfairController
|
||||
|
||||
const request = this.createBuyTradeRequestObject("RUB", tax);
|
||||
this.paymentService.payMoney(pmcData, request, sessionId, output);
|
||||
if (output.warnings.length > 0)
|
||||
{
|
||||
if (output.warnings.length > 0) {
|
||||
return this.httpResponse.appendErrorToOutput(
|
||||
output,
|
||||
this.localisationService.getText("ragfair-unable_to_pay_commission_fee"),
|
||||
@ -1100,8 +1001,7 @@ export class RagfairController
|
||||
* @param value Amount of currency
|
||||
* @returns IProcessBuyTradeRequestData
|
||||
*/
|
||||
protected createBuyTradeRequestObject(currency: string, value: number): IProcessBuyTradeRequestData
|
||||
{
|
||||
protected createBuyTradeRequestObject(currency: string, value: number): IProcessBuyTradeRequestData {
|
||||
return {
|
||||
tid: "ragfair",
|
||||
Action: "TradingConfirm",
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { inject, injectable } from "tsyringe";
|
||||
import { ProfileHelper } from "@spt/helpers/ProfileHelper";
|
||||
import { QuestHelper } from "@spt/helpers/QuestHelper";
|
||||
import { RepairHelper } from "@spt/helpers/RepairHelper";
|
||||
@ -13,10 +12,10 @@ import { EventOutputHolder } from "@spt/routers/EventOutputHolder";
|
||||
import { DatabaseService } from "@spt/services/DatabaseService";
|
||||
import { PaymentService } from "@spt/services/PaymentService";
|
||||
import { RepairService } from "@spt/services/RepairService";
|
||||
import { inject, injectable } from "tsyringe";
|
||||
|
||||
@injectable()
|
||||
export class RepairController
|
||||
{
|
||||
export class RepairController {
|
||||
protected repairConfig: IRepairConfig;
|
||||
|
||||
constructor(
|
||||
@ -29,8 +28,7 @@ export class RepairController
|
||||
@inject("RepairHelper") protected repairHelper: RepairHelper,
|
||||
@inject("RepairService") protected repairService: RepairService,
|
||||
@inject("ProfileHelper") protected profileHelper: ProfileHelper,
|
||||
)
|
||||
{}
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Handle TraderRepair event
|
||||
@ -44,13 +42,11 @@ export class RepairController
|
||||
sessionID: string,
|
||||
body: ITraderRepairActionDataRequest,
|
||||
pmcData: IPmcData,
|
||||
): IItemEventRouterResponse
|
||||
{
|
||||
): IItemEventRouterResponse {
|
||||
const output = this.eventOutputHolder.getOutput(sessionID);
|
||||
|
||||
// find the item to repair
|
||||
for (const repairItem of body.repairItems)
|
||||
{
|
||||
for (const repairItem of body.repairItems) {
|
||||
const repairDetails = this.repairService.repairItemByTrader(sessionID, pmcData, repairItem, body.tid);
|
||||
|
||||
this.repairService.payForRepair(
|
||||
@ -62,8 +58,7 @@ export class RepairController
|
||||
output,
|
||||
);
|
||||
|
||||
if (output.warnings.length > 0)
|
||||
{
|
||||
if (output.warnings.length > 0) {
|
||||
return output;
|
||||
}
|
||||
|
||||
@ -89,8 +84,7 @@ export class RepairController
|
||||
sessionID: string,
|
||||
body: IRepairActionDataRequest,
|
||||
pmcData: IPmcData,
|
||||
): IItemEventRouterResponse
|
||||
{
|
||||
): IItemEventRouterResponse {
|
||||
const output = this.eventOutputHolder.getOutput(sessionID);
|
||||
|
||||
// repair item
|
||||
|
@ -1,13 +1,9 @@
|
||||
import { inject, injectable } from "tsyringe";
|
||||
import { RepeatableQuestGenerator } from "@spt/generators/RepeatableQuestGenerator";
|
||||
import { ProfileHelper } from "@spt/helpers/ProfileHelper";
|
||||
import { QuestHelper } from "@spt/helpers/QuestHelper";
|
||||
import { RepeatableQuestHelper } from "@spt/helpers/RepeatableQuestHelper";
|
||||
import { IPmcData } from "@spt/models/eft/common/IPmcData";
|
||||
import {
|
||||
IPmcDataRepeatableQuest,
|
||||
IRepeatableQuest,
|
||||
} from "@spt/models/eft/common/tables/IRepeatableQuests";
|
||||
import { IPmcDataRepeatableQuest, IRepeatableQuest } from "@spt/models/eft/common/tables/IRepeatableQuests";
|
||||
import { IItemEventRouterResponse } from "@spt/models/eft/itemEvent/IItemEventRouterResponse";
|
||||
import { ISptProfile } from "@spt/models/eft/profile/ISptProfile";
|
||||
import { IRepeatableQuestChangeRequest } from "@spt/models/eft/quests/IRepeatableQuestChangeRequest";
|
||||
@ -26,15 +22,15 @@ import { DatabaseService } from "@spt/services/DatabaseService";
|
||||
import { LocalisationService } from "@spt/services/LocalisationService";
|
||||
import { PaymentService } from "@spt/services/PaymentService";
|
||||
import { ProfileFixerService } from "@spt/services/ProfileFixerService";
|
||||
import { ICloner } from "@spt/utils/cloners/ICloner";
|
||||
import { HttpResponseUtil } from "@spt/utils/HttpResponseUtil";
|
||||
import { ObjectId } from "@spt/utils/ObjectId";
|
||||
import { RandomUtil } from "@spt/utils/RandomUtil";
|
||||
import { TimeUtil } from "@spt/utils/TimeUtil";
|
||||
import { ICloner } from "@spt/utils/cloners/ICloner";
|
||||
import { inject, injectable } from "tsyringe";
|
||||
|
||||
@injectable()
|
||||
export class RepeatableQuestController
|
||||
{
|
||||
export class RepeatableQuestController {
|
||||
protected questConfig: IQuestConfig;
|
||||
|
||||
constructor(
|
||||
@ -54,8 +50,7 @@ export class RepeatableQuestController
|
||||
@inject("QuestHelper") protected questHelper: QuestHelper,
|
||||
@inject("ConfigServer") protected configServer: ConfigServer,
|
||||
@inject("PrimaryCloner") protected cloner: ICloner,
|
||||
)
|
||||
{
|
||||
) {
|
||||
this.questConfig = this.configServer.getConfig(ConfigTypes.QUEST);
|
||||
}
|
||||
|
||||
@ -84,30 +79,26 @@ export class RepeatableQuestController
|
||||
*
|
||||
* @returns {array} Array of "repeatableQuestObjects" as described above
|
||||
*/
|
||||
public getClientRepeatableQuests(sessionID: string): IPmcDataRepeatableQuest[]
|
||||
{
|
||||
public getClientRepeatableQuests(sessionID: string): IPmcDataRepeatableQuest[] {
|
||||
const returnData: Array<IPmcDataRepeatableQuest> = [];
|
||||
const fullProfile = this.profileHelper.getFullProfile(sessionID)!;
|
||||
const pmcData = fullProfile.characters.pmc;
|
||||
const currentTime = this.timeUtil.getTimestamp();
|
||||
|
||||
// Daily / weekly / Daily_Savage
|
||||
for (const repeatableConfig of this.questConfig.repeatableQuests)
|
||||
{
|
||||
for (const repeatableConfig of this.questConfig.repeatableQuests) {
|
||||
// Get daily/weekly data from profile, add empty object if missing
|
||||
const generatedRepeatables = this.getRepeatableQuestSubTypeFromProfile(repeatableConfig, pmcData);
|
||||
const repeatableTypeLower = repeatableConfig.name.toLowerCase();
|
||||
|
||||
const canAccessRepeatables = this.canProfileAccessRepeatableQuests(repeatableConfig, pmcData);
|
||||
if (!canAccessRepeatables)
|
||||
{
|
||||
if (!canAccessRepeatables) {
|
||||
// Dont send any repeatables, even existing ones
|
||||
continue;
|
||||
}
|
||||
|
||||
// Existing repeatables are still valid, add to return data and move to next sub-type
|
||||
if (currentTime < generatedRepeatables.endTime - 1)
|
||||
{
|
||||
if (currentTime < generatedRepeatables.endTime - 1) {
|
||||
returnData.push(generatedRepeatables);
|
||||
|
||||
this.logger.debug(`[Quest Check] ${repeatableTypeLower} quests are still valid.`);
|
||||
@ -132,12 +123,10 @@ export class RepeatableQuestController
|
||||
const questTypePool = this.generateQuestPool(repeatableConfig, pmcData.Info.Level);
|
||||
|
||||
// Add repeatable quests of this loops sub-type (daily/weekly)
|
||||
for (let i = 0; i < this.getQuestCount(repeatableConfig, pmcData); i++)
|
||||
{
|
||||
for (let i = 0; i < this.getQuestCount(repeatableConfig, pmcData); i++) {
|
||||
let quest: IRepeatableQuest | undefined = undefined;
|
||||
let lifeline = 0;
|
||||
while (!quest && questTypePool.types.length > 0)
|
||||
{
|
||||
while (!quest && questTypePool.types.length > 0) {
|
||||
quest = this.repeatableQuestGenerator.generateRepeatableQuest(
|
||||
pmcData.Info.Level,
|
||||
pmcData.TradersInfo,
|
||||
@ -145,8 +134,7 @@ export class RepeatableQuestController
|
||||
repeatableConfig,
|
||||
);
|
||||
lifeline++;
|
||||
if (lifeline > 10)
|
||||
{
|
||||
if (lifeline > 10) {
|
||||
this.logger.debug(
|
||||
"We were stuck in repeatable quest generation. This should never happen. Please report",
|
||||
);
|
||||
@ -155,8 +143,7 @@ export class RepeatableQuestController
|
||||
}
|
||||
|
||||
// check if there are no more quest types available
|
||||
if (questTypePool.types.length === 0)
|
||||
{
|
||||
if (questTypePool.types.length === 0) {
|
||||
break;
|
||||
}
|
||||
quest.side = repeatableConfig.side;
|
||||
@ -170,8 +157,7 @@ export class RepeatableQuestController
|
||||
fullProfile.spt.freeRepeatableRefreshUsedCount[repeatableTypeLower] = 0;
|
||||
|
||||
// Create stupid redundant change requirements from quest data
|
||||
for (const quest of generatedRepeatables.activeQuests)
|
||||
{
|
||||
for (const quest of generatedRepeatables.activeQuests) {
|
||||
generatedRepeatables.changeRequirement[quest._id] = {
|
||||
changeCost: quest.changeCost,
|
||||
changeStandingCost: this.randomUtil.getArrayValue([0, 0.01]), // Randomise standing cost to replace
|
||||
@ -202,20 +188,16 @@ export class RepeatableQuestController
|
||||
* @param generatedRepeatables Repeatables to process (daily/weekly)
|
||||
* @param pmcData Player profile
|
||||
*/
|
||||
protected processExpiredQuests(generatedRepeatables: IPmcDataRepeatableQuest, pmcData: IPmcData): void
|
||||
{
|
||||
protected processExpiredQuests(generatedRepeatables: IPmcDataRepeatableQuest, pmcData: IPmcData): void {
|
||||
const questsToKeep = [];
|
||||
for (const activeQuest of generatedRepeatables.activeQuests)
|
||||
{
|
||||
for (const activeQuest of generatedRepeatables.activeQuests) {
|
||||
const questStatusInProfile = pmcData.Quests.find((quest) => quest.qid === activeQuest._id);
|
||||
if (!questStatusInProfile)
|
||||
{
|
||||
if (!questStatusInProfile) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Keep finished quests in list so player can hand in
|
||||
if (questStatusInProfile.status === QuestStatus.AvailableForFinish)
|
||||
{
|
||||
if (questStatusInProfile.status === QuestStatus.AvailableForFinish) {
|
||||
questsToKeep.push(activeQuest);
|
||||
this.logger.debug(
|
||||
`Keeping repeatable quest: ${activeQuest._id} in activeQuests since it is available to hand in`,
|
||||
@ -243,17 +225,14 @@ export class RepeatableQuestController
|
||||
* @param pmcData Player profile
|
||||
* @returns True if profile is allowed to access dailies
|
||||
*/
|
||||
protected canProfileAccessRepeatableQuests(repeatableConfig: IRepeatableQuestConfig, pmcData: IPmcData): boolean
|
||||
{
|
||||
protected canProfileAccessRepeatableQuests(repeatableConfig: IRepeatableQuestConfig, pmcData: IPmcData): boolean {
|
||||
// PMC and daily quests not unlocked yet
|
||||
if (repeatableConfig.side === "Pmc" && !this.playerHasDailyPmcQuestsUnlocked(pmcData, repeatableConfig))
|
||||
{
|
||||
if (repeatableConfig.side === "Pmc" && !this.playerHasDailyPmcQuestsUnlocked(pmcData, repeatableConfig)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Scav and daily quests not unlocked yet
|
||||
if (repeatableConfig.side === "Scav" && !this.playerHasDailyScavQuestsUnlocked(pmcData))
|
||||
{
|
||||
if (repeatableConfig.side === "Scav" && !this.playerHasDailyScavQuestsUnlocked(pmcData)) {
|
||||
this.logger.debug("Daily scav quests still locked, Intel center not built");
|
||||
|
||||
return false;
|
||||
@ -267,11 +246,10 @@ export class RepeatableQuestController
|
||||
* @param pmcData Player profile to check
|
||||
* @returns True if unlocked
|
||||
*/
|
||||
protected playerHasDailyScavQuestsUnlocked(pmcData: IPmcData): boolean
|
||||
{
|
||||
return pmcData?.Hideout?.Areas
|
||||
?.find((hideoutArea) => hideoutArea.type === HideoutAreas.INTEL_CENTER)
|
||||
?.level >= 1;
|
||||
protected playerHasDailyScavQuestsUnlocked(pmcData: IPmcData): boolean {
|
||||
return (
|
||||
pmcData?.Hideout?.Areas?.find((hideoutArea) => hideoutArea.type === HideoutAreas.INTEL_CENTER)?.level >= 1
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -280,8 +258,7 @@ export class RepeatableQuestController
|
||||
* @param repeatableConfig Config of daily type to check
|
||||
* @returns True if unlocked
|
||||
*/
|
||||
protected playerHasDailyPmcQuestsUnlocked(pmcData: IPmcData, repeatableConfig: IRepeatableQuestConfig): boolean
|
||||
{
|
||||
protected playerHasDailyPmcQuestsUnlocked(pmcData: IPmcData, repeatableConfig: IRepeatableQuestConfig): boolean {
|
||||
return pmcData.Info.Level >= repeatableConfig.minPlayerLevel;
|
||||
}
|
||||
|
||||
@ -291,17 +268,15 @@ export class RepeatableQuestController
|
||||
* @param pmcData Player profile
|
||||
* @returns Quest count
|
||||
*/
|
||||
protected getQuestCount(repeatableConfig: IRepeatableQuestConfig, pmcData: IPmcData): number
|
||||
{
|
||||
protected getQuestCount(repeatableConfig: IRepeatableQuestConfig, pmcData: IPmcData): number {
|
||||
if (
|
||||
repeatableConfig.name.toLowerCase() === "daily"
|
||||
&& this.profileHelper.hasEliteSkillLevel(SkillTypes.CHARISMA, pmcData)
|
||||
)
|
||||
{
|
||||
repeatableConfig.name.toLowerCase() === "daily" &&
|
||||
this.profileHelper.hasEliteSkillLevel(SkillTypes.CHARISMA, pmcData)
|
||||
) {
|
||||
// Elite charisma skill gives extra daily quest(s)
|
||||
return (
|
||||
repeatableConfig.numQuests
|
||||
+ this.databaseService.getGlobals().config.SkillsSettings.Charisma.BonusSettings.EliteBonusSettings
|
||||
repeatableConfig.numQuests +
|
||||
this.databaseService.getGlobals().config.SkillsSettings.Charisma.BonusSettings.EliteBonusSettings
|
||||
.RepeatableQuestExtraCount
|
||||
);
|
||||
}
|
||||
@ -318,13 +293,13 @@ export class RepeatableQuestController
|
||||
protected getRepeatableQuestSubTypeFromProfile(
|
||||
repeatableConfig: IRepeatableQuestConfig,
|
||||
pmcData: IPmcData,
|
||||
): IPmcDataRepeatableQuest
|
||||
{
|
||||
): IPmcDataRepeatableQuest {
|
||||
// Get from profile, add if missing
|
||||
let repeatableQuestDetails = pmcData.RepeatableQuests
|
||||
.find((repeatable) => repeatable.name === repeatableConfig.name);
|
||||
if (!repeatableQuestDetails) // Not in profile, generate
|
||||
{
|
||||
let repeatableQuestDetails = pmcData.RepeatableQuests.find(
|
||||
(repeatable) => repeatable.name === repeatableConfig.name,
|
||||
);
|
||||
if (!repeatableQuestDetails) {
|
||||
// Not in profile, generate
|
||||
const hasAccess = this.profileHelper.hasAccessToRepeatableFreeRefreshSystem(pmcData);
|
||||
repeatableQuestDetails = {
|
||||
id: repeatableConfig.id,
|
||||
@ -347,13 +322,11 @@ export class RepeatableQuestController
|
||||
/**
|
||||
* Just for debug reasons. Draws dailies a random assort of dailies extracted from dumps
|
||||
*/
|
||||
public generateDebugDailies(dailiesPool: any, factory: any, number: number): any
|
||||
{
|
||||
public generateDebugDailies(dailiesPool: any, factory: any, number: number): any {
|
||||
let randomQuests = [];
|
||||
let numberOfQuests = number;
|
||||
|
||||
if (factory)
|
||||
{
|
||||
if (factory) {
|
||||
// First is factory extract always add for debugging
|
||||
randomQuests.push(dailiesPool[0]);
|
||||
numberOfQuests -= 1;
|
||||
@ -361,14 +334,11 @@ export class RepeatableQuestController
|
||||
|
||||
randomQuests = randomQuests.concat(this.randomUtil.drawRandomFromList(dailiesPool, numberOfQuests, false));
|
||||
|
||||
for (const element of randomQuests)
|
||||
{
|
||||
for (const element of randomQuests) {
|
||||
element._id = this.objectId.generate();
|
||||
const conditions = element.conditions.AvailableForFinish;
|
||||
for (const condition of conditions)
|
||||
{
|
||||
if ("counter" in condition._props)
|
||||
{
|
||||
for (const condition of conditions) {
|
||||
if ("counter" in condition._props) {
|
||||
condition._props.counter.id = this.objectId.generate();
|
||||
}
|
||||
}
|
||||
@ -384,18 +354,15 @@ export class RepeatableQuestController
|
||||
* @param pmcLevel level of pmc generating quest pool
|
||||
* @returns IQuestTypePool
|
||||
*/
|
||||
protected generateQuestPool(repeatableConfig: IRepeatableQuestConfig, pmcLevel: number): IQuestTypePool
|
||||
{
|
||||
protected generateQuestPool(repeatableConfig: IRepeatableQuestConfig, pmcLevel: number): IQuestTypePool {
|
||||
const questPool = this.createBaseQuestPool(repeatableConfig);
|
||||
|
||||
// Get the allowed locations based on the PMC's level
|
||||
const locations = this.getAllowedLocationsForPmcLevel(repeatableConfig.locations, pmcLevel);
|
||||
|
||||
// Populate Exploration and Pickup quest locations
|
||||
for (const location in locations)
|
||||
{
|
||||
if (location !== ELocationName.ANY)
|
||||
{
|
||||
for (const location in locations) {
|
||||
if (location !== ELocationName.ANY) {
|
||||
questPool.pool.Exploration.locations[location] = locations[location];
|
||||
questPool.pool.Pickup.locations[location] = locations[location];
|
||||
}
|
||||
@ -408,19 +375,16 @@ export class RepeatableQuestController
|
||||
const targetsConfig = this.repeatableQuestHelper.probabilityObjectArray(eliminationConfig.targets);
|
||||
|
||||
// Populate Elimination quest targets and their locations
|
||||
for (const { data: target, key: targetKey } of targetsConfig)
|
||||
{
|
||||
for (const { data: target, key: targetKey } of targetsConfig) {
|
||||
// Target is boss
|
||||
if (target.isBoss)
|
||||
{
|
||||
if (target.isBoss) {
|
||||
questPool.pool.Elimination.targets[targetKey] = { locations: ["any"] };
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
// Non-boss targets
|
||||
const possibleLocations = Object.keys(locations);
|
||||
|
||||
const allowedLocations = (targetKey === "Savage")
|
||||
const allowedLocations =
|
||||
targetKey === "Savage"
|
||||
? possibleLocations.filter((location) => location !== "laboratory") // Exclude labs for Savage targets.
|
||||
: possibleLocations;
|
||||
|
||||
@ -431,8 +395,7 @@ export class RepeatableQuestController
|
||||
return questPool;
|
||||
}
|
||||
|
||||
protected createBaseQuestPool(repeatableConfig: IRepeatableQuestConfig): IQuestTypePool
|
||||
{
|
||||
protected createBaseQuestPool(repeatableConfig: IRepeatableQuestConfig): IQuestTypePool {
|
||||
return {
|
||||
types: repeatableConfig.types.slice(),
|
||||
pool: { Exploration: { locations: {} }, Elimination: { targets: {} }, Pickup: { locations: {} } },
|
||||
@ -448,23 +411,18 @@ export class RepeatableQuestController
|
||||
protected getAllowedLocationsForPmcLevel(
|
||||
locations: Record<ELocationName, string[]>,
|
||||
pmcLevel: number,
|
||||
): Partial<Record<ELocationName, string[]>>
|
||||
{
|
||||
): Partial<Record<ELocationName, string[]>> {
|
||||
const allowedLocation: Partial<Record<ELocationName, string[]>> = {};
|
||||
|
||||
for (const location in locations)
|
||||
{
|
||||
for (const location in locations) {
|
||||
const locationNames = [];
|
||||
for (const locationName of locations[location])
|
||||
{
|
||||
if (this.isPmcLevelAllowedOnLocation(locationName, pmcLevel))
|
||||
{
|
||||
for (const locationName of locations[location]) {
|
||||
if (this.isPmcLevelAllowedOnLocation(locationName, pmcLevel)) {
|
||||
locationNames.push(locationName);
|
||||
}
|
||||
}
|
||||
|
||||
if (locationNames.length > 0)
|
||||
{
|
||||
if (locationNames.length > 0) {
|
||||
allowedLocation[location] = locationNames;
|
||||
}
|
||||
}
|
||||
@ -478,36 +436,29 @@ export class RepeatableQuestController
|
||||
* @param pmcLevel The level of the pmc
|
||||
* @returns True if the given pmc level is allowed to access the given location
|
||||
*/
|
||||
protected isPmcLevelAllowedOnLocation(location: string, pmcLevel: number): boolean
|
||||
{
|
||||
protected isPmcLevelAllowedOnLocation(location: string, pmcLevel: number): boolean {
|
||||
// All PMC levels are allowed for 'any' location requirement
|
||||
if (location === ELocationName.ANY)
|
||||
{
|
||||
if (location === ELocationName.ANY) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const locationBase = this.databaseService.getLocation(location.toLowerCase())?.base;
|
||||
if (!locationBase)
|
||||
{
|
||||
if (!locationBase) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return pmcLevel <= locationBase.RequiredPlayerLevelMax && pmcLevel >= locationBase.RequiredPlayerLevelMin;
|
||||
}
|
||||
|
||||
public debugLogRepeatableQuestIds(pmcData: IPmcData): void
|
||||
{
|
||||
for (const repeatable of pmcData.RepeatableQuests)
|
||||
{
|
||||
public debugLogRepeatableQuestIds(pmcData: IPmcData): void {
|
||||
for (const repeatable of pmcData.RepeatableQuests) {
|
||||
const activeQuestsIds = [];
|
||||
const inactiveQuestsIds = [];
|
||||
for (const active of repeatable.activeQuests)
|
||||
{
|
||||
for (const active of repeatable.activeQuests) {
|
||||
activeQuestsIds.push(active._id);
|
||||
}
|
||||
|
||||
for (const inactive of repeatable.inactiveQuests)
|
||||
{
|
||||
for (const inactive of repeatable.inactiveQuests) {
|
||||
inactiveQuestsIds.push(inactive._id);
|
||||
}
|
||||
|
||||
@ -529,15 +480,16 @@ export class RepeatableQuestController
|
||||
pmcData: IPmcData,
|
||||
changeRequest: IRepeatableQuestChangeRequest,
|
||||
sessionID: string,
|
||||
): IItemEventRouterResponse
|
||||
{
|
||||
): IItemEventRouterResponse {
|
||||
const output = this.eventOutputHolder.getOutput(sessionID);
|
||||
|
||||
const fullProfile = this.profileHelper.getFullProfile(sessionID);
|
||||
|
||||
// Check for existing quest in (daily/weekly/scav arrays)
|
||||
const { quest: questToReplace, repeatableType: repeatablesInProfile }
|
||||
= this.getRepeatableById(changeRequest.qid, pmcData);
|
||||
const { quest: questToReplace, repeatableType: repeatablesInProfile } = this.getRepeatableById(
|
||||
changeRequest.qid,
|
||||
pmcData,
|
||||
);
|
||||
|
||||
// Subtype name of quest - daily/weekly/scav
|
||||
const repeatableTypeLower = repeatablesInProfile.name.toLowerCase();
|
||||
@ -546,8 +498,9 @@ export class RepeatableQuestController
|
||||
const replacedQuestTraderId = questToReplace.traderId;
|
||||
|
||||
// Update active quests to exclude the quest we're replacing
|
||||
repeatablesInProfile.activeQuests = repeatablesInProfile.activeQuests
|
||||
.filter((quest) => quest._id !== changeRequest.qid);
|
||||
repeatablesInProfile.activeQuests = repeatablesInProfile.activeQuests.filter(
|
||||
(quest) => quest._id !== changeRequest.qid,
|
||||
);
|
||||
|
||||
// Save for later cost calculation
|
||||
const previousChangeRequirement = this.cloner.clone(repeatablesInProfile.changeRequirement[changeRequest.qid]);
|
||||
@ -556,15 +509,14 @@ export class RepeatableQuestController
|
||||
delete repeatablesInProfile.changeRequirement[changeRequest.qid];
|
||||
|
||||
// Get config for this repeatable sub-type (daily/weekly/scav)
|
||||
const repeatableConfig = this.questConfig.repeatableQuests
|
||||
.find((config) => config.name === repeatablesInProfile.name,
|
||||
const repeatableConfig = this.questConfig.repeatableQuests.find(
|
||||
(config) => config.name === repeatablesInProfile.name,
|
||||
);
|
||||
|
||||
// Generate meta-data for what type/levelrange of quests can be generated for player
|
||||
const allowedQuestTypes = this.generateQuestPool(repeatableConfig, pmcData.Info.Level);
|
||||
const newRepeatableQuest = this.attemptToGenerateRepeatableQuest(pmcData, allowedQuestTypes, repeatableConfig);
|
||||
if (!newRepeatableQuest)
|
||||
{
|
||||
if (!newRepeatableQuest) {
|
||||
// Unable to find quest being replaced
|
||||
const message = `Unable to generate repeatable quest of type: ${repeatableTypeLower} to replace trader: ${replacedQuestTraderId} quest ${changeRequest.qid}`;
|
||||
this.logger.error(message);
|
||||
@ -593,20 +545,17 @@ export class RepeatableQuestController
|
||||
|
||||
// Check if we should charge player for replacing quest
|
||||
const isFreeToReplace = this.useFreeRefreshIfAvailable(fullProfile, repeatablesInProfile, repeatableTypeLower);
|
||||
if (!isFreeToReplace)
|
||||
{
|
||||
if (!isFreeToReplace) {
|
||||
// Reduce standing with trader for not doing their quest
|
||||
const traderOfReplacedQuest = pmcData.TradersInfo[replacedQuestTraderId];
|
||||
traderOfReplacedQuest.standing -= previousChangeRequirement.changeStandingCost;
|
||||
|
||||
const charismaBonus = this.profileHelper.getSkillFromProfile(pmcData, SkillTypes.CHARISMA)?.Progress ?? 0;
|
||||
for (const cost of previousChangeRequirement.changeCost)
|
||||
{
|
||||
for (const cost of previousChangeRequirement.changeCost) {
|
||||
// Not free, Charge player + appy charisma bonus to cost of replacement
|
||||
cost.count = Math.trunc(cost.count * (1 - (Math.trunc((charismaBonus) / 100) * 0.001)) ?? 1);
|
||||
cost.count = Math.trunc(cost.count * (1 - Math.trunc(charismaBonus / 100) * 0.001) ?? 1);
|
||||
this.paymentService.addPaymentToOutput(pmcData, cost.templateId, cost.count, sessionID, output);
|
||||
if (output.warnings.length > 0)
|
||||
{
|
||||
if (output.warnings.length > 0) {
|
||||
return output;
|
||||
}
|
||||
}
|
||||
@ -618,8 +567,7 @@ export class RepeatableQuestController
|
||||
// Purge inactive repeatables
|
||||
repeatableToChangeClone.inactiveQuests = [];
|
||||
|
||||
if (!repeatableToChangeClone)
|
||||
{
|
||||
if (!repeatableToChangeClone) {
|
||||
// Unable to find quest being replaced
|
||||
const message = this.localisationService.getText("quest-unable_to_find_repeatable_to_replace");
|
||||
this.logger.error(message);
|
||||
@ -642,15 +590,11 @@ export class RepeatableQuestController
|
||||
* @param pmcData Profile that contains quests to look through
|
||||
* @returns IGetRepeatableByIdResult
|
||||
*/
|
||||
protected getRepeatableById(questId: string, pmcData: IPmcData): IGetRepeatableByIdResult
|
||||
{
|
||||
for (const repeatablesInProfile of pmcData.RepeatableQuests)
|
||||
{
|
||||
protected getRepeatableById(questId: string, pmcData: IPmcData): IGetRepeatableByIdResult {
|
||||
for (const repeatablesInProfile of pmcData.RepeatableQuests) {
|
||||
// Check for existing quest in (daily/weekly/scav arrays)
|
||||
const questToReplace = repeatablesInProfile.activeQuests
|
||||
.find((repeatable) => repeatable._id === questId);
|
||||
if (!questToReplace)
|
||||
{
|
||||
const questToReplace = repeatablesInProfile.activeQuests.find((repeatable) => repeatable._id === questId);
|
||||
if (!questToReplace) {
|
||||
// Not found, skip to next repeatable sub-type
|
||||
continue;
|
||||
}
|
||||
@ -665,14 +609,11 @@ export class RepeatableQuestController
|
||||
pmcData: IPmcData,
|
||||
questTypePool: IQuestTypePool,
|
||||
repeatableConfig: IRepeatableQuestConfig,
|
||||
): IRepeatableQuest
|
||||
{
|
||||
): IRepeatableQuest {
|
||||
const maxAttempts = 10;
|
||||
let newRepeatableQuest: IRepeatableQuest = undefined;
|
||||
let attempts = 0;
|
||||
while (attempts < maxAttempts
|
||||
&& questTypePool.types.length > 0)
|
||||
{
|
||||
while (attempts < maxAttempts && questTypePool.types.length > 0) {
|
||||
newRepeatableQuest = this.repeatableQuestGenerator.generateRepeatableQuest(
|
||||
pmcData.Info.Level,
|
||||
pmcData.TradersInfo,
|
||||
@ -680,8 +621,7 @@ export class RepeatableQuestController
|
||||
repeatableConfig,
|
||||
);
|
||||
|
||||
if (newRepeatableQuest)
|
||||
{
|
||||
if (newRepeatableQuest) {
|
||||
// Successfully generated a quest, exit loop
|
||||
break;
|
||||
}
|
||||
@ -689,11 +629,8 @@ export class RepeatableQuestController
|
||||
attempts++;
|
||||
}
|
||||
|
||||
if (attempts > maxAttempts)
|
||||
{
|
||||
this.logger.debug(
|
||||
"We were stuck in repeatable quest generation. This should never happen. Please report",
|
||||
);
|
||||
if (attempts > maxAttempts) {
|
||||
this.logger.debug("We were stuck in repeatable quest generation. This should never happen. Please report");
|
||||
}
|
||||
|
||||
return newRepeatableQuest;
|
||||
@ -710,11 +647,10 @@ export class RepeatableQuestController
|
||||
protected useFreeRefreshIfAvailable(
|
||||
fullProfile: ISptProfile,
|
||||
repeatableSubType: IPmcDataRepeatableQuest,
|
||||
repeatableTypeName: string): boolean
|
||||
{
|
||||
repeatableTypeName: string,
|
||||
): boolean {
|
||||
// No free refreshes, exit early
|
||||
if (repeatableSubType.freeChangesAvailable <= 0)
|
||||
{
|
||||
if (repeatableSubType.freeChangesAvailable <= 0) {
|
||||
// Reset counter to 0
|
||||
repeatableSubType.freeChangesAvailable = 0;
|
||||
|
||||
@ -722,12 +658,12 @@ export class RepeatableQuestController
|
||||
}
|
||||
|
||||
// Only certain game versions have access to free refreshes
|
||||
const hasAccessToFreeRefreshSystem
|
||||
= this.profileHelper.hasAccessToRepeatableFreeRefreshSystem(fullProfile.characters.pmc);
|
||||
const hasAccessToFreeRefreshSystem = this.profileHelper.hasAccessToRepeatableFreeRefreshSystem(
|
||||
fullProfile.characters.pmc,
|
||||
);
|
||||
|
||||
// If the player has access and available refreshes:
|
||||
if (hasAccessToFreeRefreshSystem)
|
||||
{
|
||||
if (hasAccessToFreeRefreshSystem) {
|
||||
// Initialize/retrieve free refresh count for the desired subtype: daily/weekly
|
||||
fullProfile.spt.freeRepeatableRefreshUsedCount ||= {};
|
||||
const repeatableRefreshCounts = fullProfile.spt.freeRepeatableRefreshUsedCount;
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { inject, injectable } from "tsyringe";
|
||||
import { ItemHelper } from "@spt/helpers/ItemHelper";
|
||||
import { ProfileHelper } from "@spt/helpers/ProfileHelper";
|
||||
import { RagfairOfferHelper } from "@spt/helpers/RagfairOfferHelper";
|
||||
@ -11,10 +10,7 @@ import { IItemEventRouterResponse } from "@spt/models/eft/itemEvent/IItemEventRo
|
||||
import { IRagfairOffer } from "@spt/models/eft/ragfair/IRagfairOffer";
|
||||
import { IProcessBaseTradeRequestData } from "@spt/models/eft/trade/IProcessBaseTradeRequestData";
|
||||
import { IProcessBuyTradeRequestData } from "@spt/models/eft/trade/IProcessBuyTradeRequestData";
|
||||
import {
|
||||
IOfferRequest,
|
||||
IProcessRagfairTradeRequestData,
|
||||
} from "@spt/models/eft/trade/IProcessRagfairTradeRequestData";
|
||||
import { IOfferRequest, IProcessRagfairTradeRequestData } from "@spt/models/eft/trade/IProcessRagfairTradeRequestData";
|
||||
import { IProcessSellTradeRequestData } from "@spt/models/eft/trade/IProcessSellTradeRequestData";
|
||||
import { ISellScavItemsToFenceRequestData } from "@spt/models/eft/trade/ISellScavItemsToFenceRequestData";
|
||||
import { BackendErrorCodes } from "@spt/models/enums/BackendErrorCodes";
|
||||
@ -37,10 +33,10 @@ import { HashUtil } from "@spt/utils/HashUtil";
|
||||
import { HttpResponseUtil } from "@spt/utils/HttpResponseUtil";
|
||||
import { RandomUtil } from "@spt/utils/RandomUtil";
|
||||
import { TimeUtil } from "@spt/utils/TimeUtil";
|
||||
import { inject, injectable } from "tsyringe";
|
||||
|
||||
@injectable()
|
||||
export class TradeController
|
||||
{
|
||||
export class TradeController {
|
||||
protected ragfairConfig: IRagfairConfig;
|
||||
protected traderConfig: ITraderConfig;
|
||||
|
||||
@ -62,8 +58,7 @@ export class TradeController
|
||||
@inject("RagfairPriceService") protected ragfairPriceService: RagfairPriceService,
|
||||
@inject("MailSendService") protected mailSendService: MailSendService,
|
||||
@inject("ConfigServer") protected configServer: ConfigServer,
|
||||
)
|
||||
{
|
||||
) {
|
||||
this.ragfairConfig = this.configServer.getConfig(ConfigTypes.RAGFAIR);
|
||||
this.traderConfig = this.configServer.getConfig(ConfigTypes.TRADER);
|
||||
}
|
||||
@ -73,13 +68,11 @@ export class TradeController
|
||||
pmcData: IPmcData,
|
||||
request: IProcessBaseTradeRequestData,
|
||||
sessionID: string,
|
||||
): IItemEventRouterResponse
|
||||
{
|
||||
): IItemEventRouterResponse {
|
||||
const output = this.eventOutputHolder.getOutput(sessionID);
|
||||
|
||||
// Buying
|
||||
if (request.type === "buy_from_trader")
|
||||
{
|
||||
if (request.type === "buy_from_trader") {
|
||||
const foundInRaid = this.traderConfig.purchasesAreFoundInRaid;
|
||||
const buyData = <IProcessBuyTradeRequestData>request;
|
||||
this.tradeHelper.buyItem(pmcData, buyData, sessionID, foundInRaid, output);
|
||||
@ -88,8 +81,7 @@ export class TradeController
|
||||
}
|
||||
|
||||
// Selling
|
||||
if (request.type === "sell_to_trader")
|
||||
{
|
||||
if (request.type === "sell_to_trader") {
|
||||
const sellData = <IProcessSellTradeRequestData>request;
|
||||
this.tradeHelper.sellItem(pmcData, pmcData, sellData, sessionID, output);
|
||||
|
||||
@ -107,15 +99,12 @@ export class TradeController
|
||||
pmcData: IPmcData,
|
||||
request: IProcessRagfairTradeRequestData,
|
||||
sessionID: string,
|
||||
): IItemEventRouterResponse
|
||||
{
|
||||
): IItemEventRouterResponse {
|
||||
const output = this.eventOutputHolder.getOutput(sessionID);
|
||||
|
||||
for (const offer of request.offers)
|
||||
{
|
||||
for (const offer of request.offers) {
|
||||
const fleaOffer = this.ragfairServer.getOffer(offer.id);
|
||||
if (!fleaOffer)
|
||||
{
|
||||
if (!fleaOffer) {
|
||||
return this.httpResponse.appendErrorToOutput(
|
||||
output,
|
||||
`Offer with ID ${offer.id} not found`,
|
||||
@ -123,8 +112,7 @@ export class TradeController
|
||||
);
|
||||
}
|
||||
|
||||
if (offer.count === 0)
|
||||
{
|
||||
if (offer.count === 0) {
|
||||
const errorMessage = this.localisationService.getText(
|
||||
"ragfair-unable_to_purchase_0_count_item",
|
||||
this.itemHelper.getItem(fleaOffer.items[0]._tpl)[1]._name,
|
||||
@ -133,18 +121,14 @@ export class TradeController
|
||||
}
|
||||
|
||||
const sellerIsTrader = fleaOffer.user.memberType === MemberCategory.TRADER;
|
||||
if (sellerIsTrader)
|
||||
{
|
||||
if (sellerIsTrader) {
|
||||
this.buyTraderItemFromRagfair(sessionID, pmcData, fleaOffer, offer, output);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
this.buyPmcItemFromRagfair(sessionID, pmcData, fleaOffer, offer, output);
|
||||
}
|
||||
|
||||
// Exit loop early if problem found
|
||||
if (output.warnings.length > 0)
|
||||
{
|
||||
if (output.warnings.length > 0) {
|
||||
return output;
|
||||
}
|
||||
}
|
||||
@ -166,11 +150,9 @@ export class TradeController
|
||||
fleaOffer: IRagfairOffer,
|
||||
requestOffer: IOfferRequest,
|
||||
output: IItemEventRouterResponse,
|
||||
): void
|
||||
{
|
||||
): void {
|
||||
// Skip buying items when player doesn't have needed loyalty
|
||||
if (this.playerLacksTraderLoyaltyLevelToBuyOffer(fleaOffer, pmcData))
|
||||
{
|
||||
if (this.playerLacksTraderLoyaltyLevelToBuyOffer(fleaOffer, pmcData)) {
|
||||
const errorMessage = `Unable to buy item: ${fleaOffer.items[0]._tpl} from trader: ${fleaOffer.user.id} as loyalty level too low, skipping`;
|
||||
this.logger.debug(errorMessage);
|
||||
|
||||
@ -206,8 +188,7 @@ export class TradeController
|
||||
fleaOffer: IRagfairOffer,
|
||||
requestOffer: IOfferRequest,
|
||||
output: IItemEventRouterResponse,
|
||||
): void
|
||||
{
|
||||
): void {
|
||||
const buyData: IProcessBuyTradeRequestData = {
|
||||
Action: "TradingConfirm",
|
||||
type: "buy_from_ragfair",
|
||||
@ -226,8 +207,7 @@ export class TradeController
|
||||
this.ragfairConfig.dynamic.purchasesAreFoundInRaid,
|
||||
output,
|
||||
);
|
||||
if (output.warnings.length > 0)
|
||||
{
|
||||
if (output.warnings.length > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -236,8 +216,7 @@ export class TradeController
|
||||
const offerBuyCount = requestOffer.count;
|
||||
|
||||
const isPlayerOffer = this.isPlayerOffer(fleaOffer._id, fleaOffer.user?.id);
|
||||
if (isPlayerOffer)
|
||||
{
|
||||
if (isPlayerOffer) {
|
||||
// Complete selling the offer now its been purchased
|
||||
this.ragfairOfferHelper.completeOffer(offerOwnerId, fleaOffer, offerBuyCount);
|
||||
|
||||
@ -254,17 +233,14 @@ export class TradeController
|
||||
* @param offerOwnerId Owner id
|
||||
* @returns true if offer was made by a player
|
||||
*/
|
||||
protected isPlayerOffer(offerId: string, offerOwnerId: string): boolean
|
||||
{
|
||||
protected isPlayerOffer(offerId: string, offerOwnerId: string): boolean {
|
||||
// No ownerid, not player offer
|
||||
if (!offerOwnerId)
|
||||
{
|
||||
if (!offerOwnerId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const offerCreatorProfile = this.profileHelper.getPmcProfile(offerOwnerId);
|
||||
if (!offerCreatorProfile || offerCreatorProfile.RagfairInfo.offers?.length === 0)
|
||||
{
|
||||
if (!offerCreatorProfile || offerCreatorProfile.RagfairInfo.offers?.length === 0) {
|
||||
// No profile or no offers
|
||||
return false;
|
||||
}
|
||||
@ -280,8 +256,7 @@ export class TradeController
|
||||
* @param pmcData Player profile
|
||||
* @returns True if player can buy offer
|
||||
*/
|
||||
protected playerLacksTraderLoyaltyLevelToBuyOffer(fleaOffer: IRagfairOffer, pmcData: IPmcData): boolean
|
||||
{
|
||||
protected playerLacksTraderLoyaltyLevelToBuyOffer(fleaOffer: IRagfairOffer, pmcData: IPmcData): boolean {
|
||||
return fleaOffer.loyaltyLevel > pmcData.TradersInfo[fleaOffer.user.id].loyaltyLevel;
|
||||
}
|
||||
|
||||
@ -290,8 +265,7 @@ export class TradeController
|
||||
pmcData: IPmcData,
|
||||
request: ISellScavItemsToFenceRequestData,
|
||||
sessionId: string,
|
||||
): IItemEventRouterResponse
|
||||
{
|
||||
): IItemEventRouterResponse {
|
||||
const output = this.eventOutputHolder.getOutput(sessionId);
|
||||
|
||||
this.mailMoneyToPlayer(sessionId, request.totalValue, Traders.FENCE);
|
||||
@ -305,8 +279,7 @@ export class TradeController
|
||||
* @param trader Trader to sell items to
|
||||
* @param output IItemEventRouterResponse
|
||||
*/
|
||||
protected mailMoneyToPlayer(sessionId: string, roublesToSend: number, trader: Traders): void
|
||||
{
|
||||
protected mailMoneyToPlayer(sessionId: string, roublesToSend: number, trader: Traders): void {
|
||||
this.logger.debug(`Selling scav items to fence for ${roublesToSend} roubles`);
|
||||
|
||||
// Create single currency item with all currency on it
|
||||
@ -325,7 +298,7 @@ export class TradeController
|
||||
this.traderHelper.getTraderById(trader),
|
||||
MessageType.MESSAGE_WITH_ITEMS,
|
||||
this.randomUtil.getArrayValue(this.databaseService.getTrader(trader).dialogue.soldItems),
|
||||
curencyReward.flatMap((x) => x),
|
||||
curencyReward.flat(),
|
||||
this.timeUtil.getHoursAsSeconds(72),
|
||||
);
|
||||
}
|
||||
@ -343,21 +316,18 @@ export class TradeController
|
||||
items: Item[],
|
||||
handbookPrices: Record<string, number>,
|
||||
traderDetails: ITraderBase,
|
||||
): number
|
||||
{
|
||||
): number {
|
||||
const itemWithChildren = this.itemHelper.findAndReturnChildrenAsItems(items, parentItemId);
|
||||
|
||||
let totalPrice = 0;
|
||||
for (const itemToSell of itemWithChildren)
|
||||
{
|
||||
for (const itemToSell of itemWithChildren) {
|
||||
const itemDetails = this.itemHelper.getItem(itemToSell._tpl);
|
||||
if (
|
||||
!(
|
||||
itemDetails[0]
|
||||
&& this.itemHelper.isOfBaseclasses(itemDetails[1]._id, traderDetails.items_buy.category)
|
||||
itemDetails[0] &&
|
||||
this.itemHelper.isOfBaseclasses(itemDetails[1]._id, traderDetails.items_buy.category)
|
||||
)
|
||||
)
|
||||
{
|
||||
) {
|
||||
// Skip if tpl isn't item OR item doesn't fulfil match traders buy categories
|
||||
continue;
|
||||
}
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { inject, injectable } from "tsyringe";
|
||||
import { FenceBaseAssortGenerator } from "@spt/generators/FenceBaseAssortGenerator";
|
||||
import { ProfileHelper } from "@spt/helpers/ProfileHelper";
|
||||
import { TraderAssortHelper } from "@spt/helpers/TraderAssortHelper";
|
||||
@ -13,12 +12,12 @@ import { DatabaseService } from "@spt/services/DatabaseService";
|
||||
import { FenceService } from "@spt/services/FenceService";
|
||||
import { TraderAssortService } from "@spt/services/TraderAssortService";
|
||||
import { TraderPurchasePersisterService } from "@spt/services/TraderPurchasePersisterService";
|
||||
import { ICloner } from "@spt/utils/cloners/ICloner";
|
||||
import { TimeUtil } from "@spt/utils/TimeUtil";
|
||||
import { ICloner } from "@spt/utils/cloners/ICloner";
|
||||
import { inject, injectable } from "tsyringe";
|
||||
|
||||
@injectable()
|
||||
export class TraderController
|
||||
{
|
||||
export class TraderController {
|
||||
protected traderConfig: ITraderConfig;
|
||||
|
||||
constructor(
|
||||
@ -35,8 +34,7 @@ export class TraderController
|
||||
@inject("FenceBaseAssortGenerator") protected fenceBaseAssortGenerator: FenceBaseAssortGenerator,
|
||||
@inject("ConfigServer") protected configServer: ConfigServer,
|
||||
@inject("PrimaryCloner") protected cloner: ICloner,
|
||||
)
|
||||
{
|
||||
) {
|
||||
this.traderConfig = this.configServer.getConfig(ConfigTypes.TRADER);
|
||||
}
|
||||
|
||||
@ -45,21 +43,17 @@ export class TraderController
|
||||
* Iterate over traders, ensure a pristine copy of their assorts is stored in traderAssortService
|
||||
* Store timestamp of next assort refresh in nextResupply property of traders .base object
|
||||
*/
|
||||
public load(): void
|
||||
{
|
||||
public load(): void {
|
||||
const nextHourTimestamp = this.timeUtil.getTimestampOfNextHour();
|
||||
const traderResetStartsWithServer = this.traderConfig.tradersResetFromServerStart;
|
||||
|
||||
const traders = this.databaseService.getTraders();
|
||||
for (const traderId in traders)
|
||||
{
|
||||
if (traderId === "ragfair" || traderId === Traders.LIGHTHOUSEKEEPER)
|
||||
{
|
||||
for (const traderId in traders) {
|
||||
if (traderId === "ragfair" || traderId === Traders.LIGHTHOUSEKEEPER) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (traderId === Traders.FENCE)
|
||||
{
|
||||
if (traderId === Traders.FENCE) {
|
||||
this.fenceBaseAssortGenerator.generateFenceBaseAssorts();
|
||||
this.fenceService.generateFenceAssorts();
|
||||
continue;
|
||||
@ -68,8 +62,7 @@ export class TraderController
|
||||
const trader = traders[traderId];
|
||||
|
||||
// Create dict of trader assorts on server start
|
||||
if (!this.traderAssortService.getPristineTraderAssort(traderId))
|
||||
{
|
||||
if (!this.traderAssortService.getPristineTraderAssort(traderId)) {
|
||||
const assortsClone = this.cloner.clone(trader.assort);
|
||||
this.traderAssortService.setPristineTraderAssort(traderId, assortsClone);
|
||||
}
|
||||
@ -90,19 +83,14 @@ export class TraderController
|
||||
* Fence is handled slightly differently
|
||||
* @returns has run
|
||||
*/
|
||||
public update(): boolean
|
||||
{
|
||||
for (const traderId in this.databaseService.getTables().traders)
|
||||
{
|
||||
if (traderId === "ragfair" || traderId === Traders.LIGHTHOUSEKEEPER)
|
||||
{
|
||||
public update(): boolean {
|
||||
for (const traderId in this.databaseService.getTables().traders) {
|
||||
if (traderId === "ragfair" || traderId === Traders.LIGHTHOUSEKEEPER) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (traderId === Traders.FENCE)
|
||||
{
|
||||
if (this.fenceService.needsPartialRefresh())
|
||||
{
|
||||
if (traderId === Traders.FENCE) {
|
||||
if (this.fenceService.needsPartialRefresh()) {
|
||||
this.fenceService.performPartialRefresh();
|
||||
}
|
||||
|
||||
@ -111,8 +99,7 @@ export class TraderController
|
||||
|
||||
// Trader needs to be refreshed
|
||||
const trader = this.databaseService.getTrader(traderId);
|
||||
if (this.traderAssortHelper.traderAssortsHaveExpired(traderId))
|
||||
{
|
||||
if (this.traderAssortHelper.traderAssortsHaveExpired(traderId)) {
|
||||
this.traderAssortHelper.resetExpiredTrader(trader);
|
||||
|
||||
// Reset purchase data per trader as they have independent reset times
|
||||
@ -129,21 +116,17 @@ export class TraderController
|
||||
* @param sessionID Session id
|
||||
* @returns array if ITraderBase objects
|
||||
*/
|
||||
public getAllTraders(sessionID: string): ITraderBase[]
|
||||
{
|
||||
public getAllTraders(sessionID: string): ITraderBase[] {
|
||||
const traders: ITraderBase[] = [];
|
||||
const pmcData = this.profileHelper.getPmcProfile(sessionID);
|
||||
for (const traderID in this.databaseService.getTables().traders)
|
||||
{
|
||||
if (this.databaseService.getTables().traders[traderID].base._id === "ragfair")
|
||||
{
|
||||
for (const traderID in this.databaseService.getTables().traders) {
|
||||
if (this.databaseService.getTables().traders[traderID].base._id === "ragfair") {
|
||||
continue;
|
||||
}
|
||||
|
||||
traders.push(this.traderHelper.getTrader(traderID, sessionID));
|
||||
|
||||
if (pmcData.Info)
|
||||
{
|
||||
if (pmcData.Info) {
|
||||
this.traderHelper.lvlUp(traderID, pmcData);
|
||||
}
|
||||
}
|
||||
@ -157,15 +140,12 @@ export class TraderController
|
||||
* @param traderB Second trader to compare
|
||||
* @returns 1,-1 or 0
|
||||
*/
|
||||
protected sortByTraderId(traderA: ITraderBase, traderB: ITraderBase): number
|
||||
{
|
||||
if (traderA._id > traderB._id)
|
||||
{
|
||||
protected sortByTraderId(traderA: ITraderBase, traderB: ITraderBase): number {
|
||||
if (traderA._id > traderB._id) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (traderA._id < traderB._id)
|
||||
{
|
||||
if (traderA._id < traderB._id) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@ -173,14 +153,12 @@ export class TraderController
|
||||
}
|
||||
|
||||
/** Handle client/trading/api/getTrader */
|
||||
public getTrader(sessionID: string, traderID: string): ITraderBase
|
||||
{
|
||||
public getTrader(sessionID: string, traderID: string): ITraderBase {
|
||||
return this.traderHelper.getTrader(sessionID, traderID);
|
||||
}
|
||||
|
||||
/** Handle client/trading/api/getTraderAssort */
|
||||
public getAssort(sessionId: string, traderId: string): ITraderAssort
|
||||
{
|
||||
public getAssort(sessionId: string, traderId: string): ITraderAssort {
|
||||
return this.traderAssortHelper.getAssort(sessionId, traderId);
|
||||
}
|
||||
}
|
||||
|
@ -1,16 +1,15 @@
|
||||
import { inject, injectable } from "tsyringe";
|
||||
import { WeatherGenerator } from "@spt/generators/WeatherGenerator";
|
||||
import { IWeatherData } from "@spt/models/eft/weather/IWeatherData";
|
||||
import { ConfigTypes } from "@spt/models/enums/ConfigTypes";
|
||||
import { IWeatherConfig } from "@spt/models/spt/config/IWeatherConfig";
|
||||
import { ILogger } from "@spt/models/spt/utils/ILogger";
|
||||
import { ConfigServer } from "@spt/servers/ConfigServer";
|
||||
import { IGetLocalWeatherResponseData } from "@spt/models/spt/weather/IGetLocalWeatherResponseData";
|
||||
import { ConfigServer } from "@spt/servers/ConfigServer";
|
||||
import { SeasonalEventService } from "@spt/services/SeasonalEventService";
|
||||
import { inject, injectable } from "tsyringe";
|
||||
|
||||
@injectable()
|
||||
export class WeatherController
|
||||
{
|
||||
export class WeatherController {
|
||||
protected weatherConfig: IWeatherConfig;
|
||||
|
||||
constructor(
|
||||
@ -18,14 +17,12 @@ export class WeatherController
|
||||
@inject("PrimaryLogger") protected logger: ILogger,
|
||||
@inject("ConfigServer") protected configServer: ConfigServer,
|
||||
@inject("SeasonalEventService") protected seasonalEventService: SeasonalEventService,
|
||||
)
|
||||
{
|
||||
) {
|
||||
this.weatherConfig = this.configServer.getConfig(ConfigTypes.WEATHER);
|
||||
}
|
||||
|
||||
/** Handle client/weather */
|
||||
public generate(): IWeatherData
|
||||
{
|
||||
public generate(): IWeatherData {
|
||||
let result: IWeatherData = { acceleration: 0, time: "", date: "", weather: undefined, season: 1 }; // defaults, hydrated below
|
||||
|
||||
result = this.weatherGenerator.calculateGameTime(result);
|
||||
@ -38,14 +35,15 @@ export class WeatherController
|
||||
* Get the current in-raid time (MUST HAVE PLAYER LOGGED INTO CLIENT TO WORK)
|
||||
* @returns Date object
|
||||
*/
|
||||
public getCurrentInRaidTime(): Date
|
||||
{
|
||||
public getCurrentInRaidTime(): Date {
|
||||
return this.weatherGenerator.getInRaidTime();
|
||||
}
|
||||
|
||||
public generateLocal(sesssionID: string): IGetLocalWeatherResponseData
|
||||
{
|
||||
let result: IGetLocalWeatherResponseData = { season: this.seasonalEventService.getActiveWeatherSeason(), weather: [] };
|
||||
public generateLocal(sesssionID: string): IGetLocalWeatherResponseData {
|
||||
const result: IGetLocalWeatherResponseData = {
|
||||
season: this.seasonalEventService.getActiveWeatherSeason(),
|
||||
weather: [],
|
||||
};
|
||||
|
||||
result.weather.push(this.weatherGenerator.generateWeather());
|
||||
|
||||
|
@ -1,22 +1,22 @@
|
||||
import { inject, injectable } from "tsyringe";
|
||||
import { IPmcData } from "@spt/models/eft/common/IPmcData";
|
||||
import { IItemEventRouterResponse } from "@spt/models/eft/itemEvent/IItemEventRouterResponse";
|
||||
import { IAddToWishlistRequest } from "@spt/models/eft/wishlist/IAddToWishlistRequest";
|
||||
import { IChangeWishlistItemCategoryRequest } from "@spt/models/eft/wishlist/IChangeWishlistItemCategoryRequest";
|
||||
import { IRemoveFromWishlistRequest } from "@spt/models/eft/wishlist/IRemoveFromWishlistRequest";
|
||||
import { EventOutputHolder } from "@spt/routers/EventOutputHolder";
|
||||
import { inject, injectable } from "tsyringe";
|
||||
|
||||
@injectable()
|
||||
export class WishlistController
|
||||
{
|
||||
constructor(@inject("EventOutputHolder") protected eventOutputHolder: EventOutputHolder)
|
||||
{}
|
||||
export class WishlistController {
|
||||
constructor(@inject("EventOutputHolder") protected eventOutputHolder: EventOutputHolder) {}
|
||||
|
||||
/** Handle AddToWishList */
|
||||
public addToWishList(pmcData: IPmcData, request: IAddToWishlistRequest, sessionID: string): IItemEventRouterResponse
|
||||
{
|
||||
for (const itemId of Object.keys(request.items))
|
||||
{
|
||||
public addToWishList(
|
||||
pmcData: IPmcData,
|
||||
request: IAddToWishlistRequest,
|
||||
sessionID: string,
|
||||
): IItemEventRouterResponse {
|
||||
for (const itemId of Object.keys(request.items)) {
|
||||
pmcData.WishList[itemId] = request.items[itemId];
|
||||
}
|
||||
|
||||
@ -28,10 +28,8 @@ export class WishlistController
|
||||
pmcData: IPmcData,
|
||||
request: IRemoveFromWishlistRequest,
|
||||
sessionID: string,
|
||||
): IItemEventRouterResponse
|
||||
{
|
||||
for (const itemId of request.items)
|
||||
{
|
||||
): IItemEventRouterResponse {
|
||||
for (const itemId of request.items) {
|
||||
delete pmcData.WishList[itemId];
|
||||
}
|
||||
|
||||
@ -43,8 +41,7 @@ export class WishlistController
|
||||
pmcData: IPmcData,
|
||||
request: IChangeWishlistItemCategoryRequest,
|
||||
sessionID: string,
|
||||
): IItemEventRouterResponse
|
||||
{
|
||||
): IItemEventRouterResponse {
|
||||
pmcData.WishList[request.item] = request.category;
|
||||
|
||||
return this.eventOutputHolder.getOutput(sessionID);
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { DependencyContainer, Lifecycle } from "tsyringe";
|
||||
import { AchievementCallbacks } from "@spt/callbacks/AchievementCallbacks";
|
||||
import { BotCallbacks } from "@spt/callbacks/BotCallbacks";
|
||||
import { BuildsCallbacks } from "@spt/callbacks/BuildsCallbacks";
|
||||
@ -70,18 +69,18 @@ import { BotWeaponGenerator } from "@spt/generators/BotWeaponGenerator";
|
||||
import { FenceBaseAssortGenerator } from "@spt/generators/FenceBaseAssortGenerator";
|
||||
import { LocationLootGenerator } from "@spt/generators/LocationLootGenerator";
|
||||
import { LootGenerator } from "@spt/generators/LootGenerator";
|
||||
import { PlayerScavGenerator } from "@spt/generators/PlayerScavGenerator";
|
||||
import { PMCLootGenerator } from "@spt/generators/PMCLootGenerator";
|
||||
import { PlayerScavGenerator } from "@spt/generators/PlayerScavGenerator";
|
||||
import { RagfairAssortGenerator } from "@spt/generators/RagfairAssortGenerator";
|
||||
import { RagfairOfferGenerator } from "@spt/generators/RagfairOfferGenerator";
|
||||
import { RepeatableQuestGenerator } from "@spt/generators/RepeatableQuestGenerator";
|
||||
import { RepeatableQuestRewardGenerator } from "@spt/generators/RepeatableQuestRewardGenerator";
|
||||
import { ScavCaseRewardGenerator } from "@spt/generators/ScavCaseRewardGenerator";
|
||||
import { WeatherGenerator } from "@spt/generators/WeatherGenerator";
|
||||
import { BarrelInventoryMagGen } from "@spt/generators/weapongen/implementations/BarrelInventoryMagGen";
|
||||
import { ExternalInventoryMagGen } from "@spt/generators/weapongen/implementations/ExternalInventoryMagGen";
|
||||
import { InternalMagazineInventoryMagGen } from "@spt/generators/weapongen/implementations/InternalMagazineInventoryMagGen";
|
||||
import { UbglExternalMagGen } from "@spt/generators/weapongen/implementations/UbglExternalMagGen";
|
||||
import { WeatherGenerator } from "@spt/generators/WeatherGenerator";
|
||||
import { AssortHelper } from "@spt/helpers/AssortHelper";
|
||||
import { BotDifficultyHelper } from "@spt/helpers/BotDifficultyHelper";
|
||||
import { BotGeneratorHelper } from "@spt/helpers/BotGeneratorHelper";
|
||||
@ -133,6 +132,10 @@ import { PostSptModLoader } from "@spt/loaders/PostSptModLoader";
|
||||
import { PreSptModLoader } from "@spt/loaders/PreSptModLoader";
|
||||
import { IAsyncQueue } from "@spt/models/spt/utils/IAsyncQueue";
|
||||
import { ILogger } from "@spt/models/spt/utils/ILogger";
|
||||
import { EventOutputHolder } from "@spt/routers/EventOutputHolder";
|
||||
import { HttpRouter } from "@spt/routers/HttpRouter";
|
||||
import { ImageRouter } from "@spt/routers/ImageRouter";
|
||||
import { ItemEventRouter } from "@spt/routers/ItemEventRouter";
|
||||
import { BotDynamicRouter } from "@spt/routers/dynamic/BotDynamicRouter";
|
||||
import { BundleDynamicRouter } from "@spt/routers/dynamic/BundleDynamicRouter";
|
||||
import { CustomizationDynamicRouter } from "@spt/routers/dynamic/CustomizationDynamicRouter";
|
||||
@ -142,9 +145,6 @@ import { InraidDynamicRouter } from "@spt/routers/dynamic/InraidDynamicRouter";
|
||||
import { LocationDynamicRouter } from "@spt/routers/dynamic/LocationDynamicRouter";
|
||||
import { NotifierDynamicRouter } from "@spt/routers/dynamic/NotifierDynamicRouter";
|
||||
import { TraderDynamicRouter } from "@spt/routers/dynamic/TraderDynamicRouter";
|
||||
import { EventOutputHolder } from "@spt/routers/EventOutputHolder";
|
||||
import { HttpRouter } from "@spt/routers/HttpRouter";
|
||||
import { ImageRouter } from "@spt/routers/ImageRouter";
|
||||
import { CustomizationItemEventRouter } from "@spt/routers/item_events/CustomizationItemEventRouter";
|
||||
import { HealthItemEventRouter } from "@spt/routers/item_events/HealthItemEventRouter";
|
||||
import { HideoutItemEventRouter } from "@spt/routers/item_events/HideoutItemEventRouter";
|
||||
@ -156,7 +156,6 @@ import { RagfairItemEventRouter } from "@spt/routers/item_events/RagfairItemEven
|
||||
import { RepairItemEventRouter } from "@spt/routers/item_events/RepairItemEventRouter";
|
||||
import { TradeItemEventRouter } from "@spt/routers/item_events/TradeItemEventRouter";
|
||||
import { WishlistItemEventRouter } from "@spt/routers/item_events/WishlistItemEventRouter";
|
||||
import { ItemEventRouter } from "@spt/routers/ItemEventRouter";
|
||||
import { HealthSaveLoadRouter } from "@spt/routers/save_load/HealthSaveLoadRouter";
|
||||
import { InraidSaveLoadRouter } from "@spt/routers/save_load/InraidSaveLoadRouter";
|
||||
import { InsuranceSaveLoadRouter } from "@spt/routers/save_load/InsuranceSaveLoadRouter";
|
||||
@ -188,23 +187,21 @@ import { TraderStaticRouter } from "@spt/routers/static/TraderStaticRouter";
|
||||
import { WeatherStaticRouter } from "@spt/routers/static/WeatherStaticRouter";
|
||||
import { ConfigServer } from "@spt/servers/ConfigServer";
|
||||
import { DatabaseServer } from "@spt/servers/DatabaseServer";
|
||||
import { SptHttpListener } from "@spt/servers/http/SptHttpListener";
|
||||
import { HttpServer } from "@spt/servers/HttpServer";
|
||||
import { RagfairServer } from "@spt/servers/RagfairServer";
|
||||
import { SaveServer } from "@spt/servers/SaveServer";
|
||||
import { WebSocketServer } from "@spt/servers/WebSocketServer";
|
||||
import { SptHttpListener } from "@spt/servers/http/SptHttpListener";
|
||||
import { IWebSocketConnectionHandler } from "@spt/servers/ws/IWebSocketConnectionHandler";
|
||||
import { SptWebSocketConnectionHandler } from "@spt/servers/ws/SptWebSocketConnectionHandler";
|
||||
import { DefaultSptWebSocketMessageHandler } from "@spt/servers/ws/message/DefaultSptWebSocketMessageHandler";
|
||||
import { ISptWebSocketMessageHandler } from "@spt/servers/ws/message/ISptWebSocketMessageHandler";
|
||||
import { SptWebSocketConnectionHandler } from "@spt/servers/ws/SptWebSocketConnectionHandler";
|
||||
import { AirdropService } from "@spt/services/AirdropService";
|
||||
import { BotEquipmentFilterService } from "@spt/services/BotEquipmentFilterService";
|
||||
import { BotEquipmentModPoolService } from "@spt/services/BotEquipmentModPoolService";
|
||||
import { BotGenerationCacheService } from "@spt/services/BotGenerationCacheService";
|
||||
import { BotLootCacheService } from "@spt/services/BotLootCacheService";
|
||||
import { BotWeaponModLimitService } from "@spt/services/BotWeaponModLimitService";
|
||||
import { BundleHashCacheService } from "@spt/services/cache/BundleHashCacheService";
|
||||
import { ModHashCacheService } from "@spt/services/cache/ModHashCacheService";
|
||||
import { CustomLocationWaveService } from "@spt/services/CustomLocationWaveService";
|
||||
import { DatabaseService } from "@spt/services/DatabaseService";
|
||||
import { FenceService } from "@spt/services/FenceService";
|
||||
@ -219,13 +216,6 @@ import { MailSendService } from "@spt/services/MailSendService";
|
||||
import { MapMarkerService } from "@spt/services/MapMarkerService";
|
||||
import { MatchBotDetailsCacheService } from "@spt/services/MatchBotDetailsCacheService";
|
||||
import { MatchLocationService } from "@spt/services/MatchLocationService";
|
||||
import { CustomItemService } from "@spt/services/mod/CustomItemService";
|
||||
import { DynamicRouterModService } from "@spt/services/mod/dynamicRouter/DynamicRouterModService";
|
||||
import { HttpListenerModService } from "@spt/services/mod/httpListener/HttpListenerModService";
|
||||
import { ImageRouteService } from "@spt/services/mod/image/ImageRouteService";
|
||||
import { OnLoadModService } from "@spt/services/mod/onLoad/OnLoadModService";
|
||||
import { OnUpdateModService } from "@spt/services/mod/onUpdate/OnUpdateModService";
|
||||
import { StaticRouterModService } from "@spt/services/mod/staticRouter/StaticRouterModService";
|
||||
import { ModCompilerService } from "@spt/services/ModCompilerService";
|
||||
import { NotificationService } from "@spt/services/NotificationService";
|
||||
import { OpenZoneService } from "@spt/services/OpenZoneService";
|
||||
@ -246,12 +236,17 @@ import { RepairService } from "@spt/services/RepairService";
|
||||
import { SeasonalEventService } from "@spt/services/SeasonalEventService";
|
||||
import { TraderAssortService } from "@spt/services/TraderAssortService";
|
||||
import { TraderPurchasePersisterService } from "@spt/services/TraderPurchasePersisterService";
|
||||
import { BundleHashCacheService } from "@spt/services/cache/BundleHashCacheService";
|
||||
import { ModHashCacheService } from "@spt/services/cache/ModHashCacheService";
|
||||
import { CustomItemService } from "@spt/services/mod/CustomItemService";
|
||||
import { DynamicRouterModService } from "@spt/services/mod/dynamicRouter/DynamicRouterModService";
|
||||
import { HttpListenerModService } from "@spt/services/mod/httpListener/HttpListenerModService";
|
||||
import { ImageRouteService } from "@spt/services/mod/image/ImageRouteService";
|
||||
import { OnLoadModService } from "@spt/services/mod/onLoad/OnLoadModService";
|
||||
import { OnUpdateModService } from "@spt/services/mod/onUpdate/OnUpdateModService";
|
||||
import { StaticRouterModService } from "@spt/services/mod/staticRouter/StaticRouterModService";
|
||||
import { App } from "@spt/utils/App";
|
||||
import { AsyncQueue } from "@spt/utils/AsyncQueue";
|
||||
import type { ICloner } from "@spt/utils/cloners/ICloner";
|
||||
import { JsonCloner } from "@spt/utils/cloners/JsonCloner";
|
||||
import { RecursiveCloner } from "@spt/utils/cloners/RecursiveCloner";
|
||||
import { StructuredCloner } from "@spt/utils/cloners/StructuredCloner";
|
||||
import { CompareUtil } from "@spt/utils/CompareUtil";
|
||||
import { DatabaseImporter } from "@spt/utils/DatabaseImporter";
|
||||
import { EncodingUtil } from "@spt/utils/EncodingUtil";
|
||||
@ -260,28 +255,30 @@ import { HttpFileUtil } from "@spt/utils/HttpFileUtil";
|
||||
import { HttpResponseUtil } from "@spt/utils/HttpResponseUtil";
|
||||
import { ImporterUtil } from "@spt/utils/ImporterUtil";
|
||||
import { JsonUtil } from "@spt/utils/JsonUtil";
|
||||
import { WinstonMainLogger } from "@spt/utils/logging/WinstonMainLogger";
|
||||
import { WinstonRequestLogger } from "@spt/utils/logging/WinstonRequestLogger";
|
||||
import { MathUtil } from "@spt/utils/MathUtil";
|
||||
import { ObjectId } from "@spt/utils/ObjectId";
|
||||
import { RandomUtil } from "@spt/utils/RandomUtil";
|
||||
import { TimeUtil } from "@spt/utils/TimeUtil";
|
||||
import { VFS } from "@spt/utils/VFS";
|
||||
import { Watermark, WatermarkLocale } from "@spt/utils/Watermark";
|
||||
import type { ICloner } from "@spt/utils/cloners/ICloner";
|
||||
import { JsonCloner } from "@spt/utils/cloners/JsonCloner";
|
||||
import { RecursiveCloner } from "@spt/utils/cloners/RecursiveCloner";
|
||||
import { StructuredCloner } from "@spt/utils/cloners/StructuredCloner";
|
||||
import { WinstonMainLogger } from "@spt/utils/logging/WinstonMainLogger";
|
||||
import { WinstonRequestLogger } from "@spt/utils/logging/WinstonRequestLogger";
|
||||
import { DependencyContainer, Lifecycle } from "tsyringe";
|
||||
|
||||
/**
|
||||
* Handle the registration of classes to be used by the Dependency Injection code
|
||||
*/
|
||||
export class Container
|
||||
{
|
||||
public static registerPostLoadTypes(container: DependencyContainer, childContainer: DependencyContainer): void
|
||||
{
|
||||
export class Container {
|
||||
public static registerPostLoadTypes(container: DependencyContainer, childContainer: DependencyContainer): void {
|
||||
container.register<SptHttpListener>("SptHttpListener", SptHttpListener, { lifecycle: Lifecycle.Singleton });
|
||||
childContainer.registerType("HttpListener", "SptHttpListener");
|
||||
}
|
||||
|
||||
public static registerTypes(depContainer: DependencyContainer): void
|
||||
{
|
||||
public static registerTypes(depContainer: DependencyContainer): void {
|
||||
depContainer.register("ApplicationContext", ApplicationContext, { lifecycle: Lifecycle.Singleton });
|
||||
Container.registerUtils(depContainer);
|
||||
|
||||
@ -304,16 +301,20 @@ export class Container
|
||||
Container.registerPrimaryDependencies(depContainer);
|
||||
}
|
||||
|
||||
public static registerPrimaryDependencies(depContainer: DependencyContainer): void
|
||||
{
|
||||
depContainer.register<ILogger>("PrimaryLogger", { useToken: "WinstonLogger" },
|
||||
{ lifecycle: Lifecycle.Singleton });
|
||||
depContainer.register<ICloner>("PrimaryCloner", { useToken: "RecursiveCloner" },
|
||||
{ lifecycle: Lifecycle.Singleton });
|
||||
public static registerPrimaryDependencies(depContainer: DependencyContainer): void {
|
||||
depContainer.register<ILogger>(
|
||||
"PrimaryLogger",
|
||||
{ useToken: "WinstonLogger" },
|
||||
{ lifecycle: Lifecycle.Singleton },
|
||||
);
|
||||
depContainer.register<ICloner>(
|
||||
"PrimaryCloner",
|
||||
{ useToken: "RecursiveCloner" },
|
||||
{ lifecycle: Lifecycle.Singleton },
|
||||
);
|
||||
}
|
||||
|
||||
public static registerListTypes(depContainer: DependencyContainer): void
|
||||
{
|
||||
public static registerListTypes(depContainer: DependencyContainer): void {
|
||||
depContainer.register("OnLoadModService", { useValue: new OnLoadModService(depContainer) });
|
||||
depContainer.register("HttpListenerModService", { useValue: new HttpListenerModService(depContainer) });
|
||||
depContainer.register("OnUpdateModService", { useValue: new OnUpdateModService(depContainer) });
|
||||
@ -409,8 +410,7 @@ export class Container
|
||||
depContainer.registerType("SptWebSocketMessageHandler", "DefaultSptWebSocketMessageHandler");
|
||||
}
|
||||
|
||||
private static registerUtils(depContainer: DependencyContainer): void
|
||||
{
|
||||
private static registerUtils(depContainer: DependencyContainer): void {
|
||||
// Utils
|
||||
depContainer.register<App>("App", App, { lifecycle: Lifecycle.Singleton });
|
||||
depContainer.register<DatabaseImporter>("DatabaseImporter", DatabaseImporter, {
|
||||
@ -444,8 +444,7 @@ export class Container
|
||||
depContainer.register<ICloner>("RecursiveCloner", RecursiveCloner, { lifecycle: Lifecycle.Singleton });
|
||||
}
|
||||
|
||||
private static registerRouters(depContainer: DependencyContainer): void
|
||||
{
|
||||
private static registerRouters(depContainer: DependencyContainer): void {
|
||||
// Routers
|
||||
depContainer.register<HttpRouter>("HttpRouter", HttpRouter, { lifecycle: Lifecycle.Singleton });
|
||||
depContainer.register<ImageRouter>("ImageRouter", ImageRouter);
|
||||
@ -530,8 +529,7 @@ export class Container
|
||||
depContainer.register<BuildsStaticRouter>("BuildsStaticRouter", { useClass: BuildsStaticRouter });
|
||||
}
|
||||
|
||||
private static registerGenerators(depContainer: DependencyContainer): void
|
||||
{
|
||||
private static registerGenerators(depContainer: DependencyContainer): void {
|
||||
// Generators
|
||||
depContainer.register<BotGenerator>("BotGenerator", BotGenerator);
|
||||
depContainer.register<BotWeaponGenerator>("BotWeaponGenerator", BotWeaponGenerator);
|
||||
@ -578,8 +576,7 @@ export class Container
|
||||
depContainer.registerType("InventoryMagGen", "UbglExternalMagGen");
|
||||
}
|
||||
|
||||
private static registerHelpers(depContainer: DependencyContainer): void
|
||||
{
|
||||
private static registerHelpers(depContainer: DependencyContainer): void {
|
||||
// Helpers
|
||||
depContainer.register<AssortHelper>("AssortHelper", { useClass: AssortHelper });
|
||||
depContainer.register<BotHelper>("BotHelper", { useClass: BotHelper });
|
||||
@ -642,8 +639,7 @@ export class Container
|
||||
});
|
||||
}
|
||||
|
||||
private static registerLoaders(depContainer: DependencyContainer): void
|
||||
{
|
||||
private static registerLoaders(depContainer: DependencyContainer): void {
|
||||
// Loaders
|
||||
depContainer.register<BundleLoader>("BundleLoader", BundleLoader, { lifecycle: Lifecycle.Singleton });
|
||||
depContainer.register<PreSptModLoader>("PreSptModLoader", PreSptModLoader, { lifecycle: Lifecycle.Singleton });
|
||||
@ -652,8 +648,7 @@ export class Container
|
||||
});
|
||||
}
|
||||
|
||||
private static registerCallbacks(depContainer: DependencyContainer): void
|
||||
{
|
||||
private static registerCallbacks(depContainer: DependencyContainer): void {
|
||||
// Callbacks
|
||||
depContainer.register<BotCallbacks>("BotCallbacks", { useClass: BotCallbacks });
|
||||
depContainer.register<BundleCallbacks>("BundleCallbacks", { useClass: BundleCallbacks });
|
||||
@ -691,8 +686,7 @@ export class Container
|
||||
depContainer.register<BuildsCallbacks>("BuildsCallbacks", { useClass: BuildsCallbacks });
|
||||
}
|
||||
|
||||
private static registerServices(depContainer: DependencyContainer): void
|
||||
{
|
||||
private static registerServices(depContainer: DependencyContainer): void {
|
||||
// Services
|
||||
depContainer.register<DatabaseService>("DatabaseService", DatabaseService, { lifecycle: Lifecycle.Singleton });
|
||||
depContainer.register<ImageRouteService>("ImageRouteService", ImageRouteService, {
|
||||
@ -800,21 +794,27 @@ export class Container
|
||||
});
|
||||
}
|
||||
|
||||
private static registerServers(depContainer: DependencyContainer): void
|
||||
{
|
||||
private static registerServers(depContainer: DependencyContainer): void {
|
||||
// Servers
|
||||
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<IWebSocketConnectionHandler>("SptWebSocketConnectionHandler", SptWebSocketConnectionHandler, { lifecycle: Lifecycle.Singleton });
|
||||
depContainer.register<ISptWebSocketMessageHandler>("DefaultSptWebSocketMessageHandler", DefaultSptWebSocketMessageHandler, { lifecycle: Lifecycle.Singleton });
|
||||
depContainer.register<IWebSocketConnectionHandler>(
|
||||
"SptWebSocketConnectionHandler",
|
||||
SptWebSocketConnectionHandler,
|
||||
{ lifecycle: Lifecycle.Singleton },
|
||||
);
|
||||
depContainer.register<ISptWebSocketMessageHandler>(
|
||||
"DefaultSptWebSocketMessageHandler",
|
||||
DefaultSptWebSocketMessageHandler,
|
||||
{ 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(depContainer: DependencyContainer): void
|
||||
{
|
||||
private static registerControllers(depContainer: DependencyContainer): void {
|
||||
// Controllers
|
||||
depContainer.register<BotController>("BotController", { useClass: BotController });
|
||||
depContainer.register<ClientLogController>("ClientLogController", { useClass: ClientLogController });
|
||||
|
@ -1,5 +1,4 @@
|
||||
export interface OnLoad
|
||||
{
|
||||
onLoad(): Promise<void>
|
||||
getRoute(): string
|
||||
export interface OnLoad {
|
||||
onLoad(): Promise<void>;
|
||||
getRoute(): string;
|
||||
}
|
||||
|
@ -1,5 +1,4 @@
|
||||
export interface OnUpdate
|
||||
{
|
||||
onUpdate(timeSinceLastRun: number): Promise<boolean>
|
||||
getRoute(): string
|
||||
export interface OnUpdate {
|
||||
onUpdate(timeSinceLastRun: number): Promise<boolean>;
|
||||
getRoute(): string;
|
||||
}
|
||||
|
@ -2,33 +2,26 @@ import { IPmcData } from "@spt/models/eft/common/IPmcData";
|
||||
import { IItemEventRouterResponse } from "@spt/models/eft/itemEvent/IItemEventRouterResponse";
|
||||
import { ISptProfile } from "@spt/models/eft/profile/ISptProfile";
|
||||
|
||||
export class Router
|
||||
{
|
||||
export class Router {
|
||||
protected handledRoutes: HandledRoute[] = [];
|
||||
|
||||
public getTopLevelRoute(): string
|
||||
{
|
||||
public getTopLevelRoute(): string {
|
||||
return "spt";
|
||||
}
|
||||
|
||||
protected getHandledRoutes(): HandledRoute[]
|
||||
{
|
||||
protected getHandledRoutes(): HandledRoute[] {
|
||||
throw new Error("This method needs to be overrode by the router classes");
|
||||
}
|
||||
|
||||
protected getInternalHandledRoutes(): HandledRoute[]
|
||||
{
|
||||
if (this.handledRoutes.length === 0)
|
||||
{
|
||||
protected getInternalHandledRoutes(): HandledRoute[] {
|
||||
if (this.handledRoutes.length === 0) {
|
||||
this.handledRoutes = this.getHandledRoutes();
|
||||
}
|
||||
return this.handledRoutes;
|
||||
}
|
||||
|
||||
public canHandle(url: string, partialMatch = false): boolean
|
||||
{
|
||||
if (partialMatch)
|
||||
{
|
||||
public canHandle(url: string, partialMatch = false): boolean {
|
||||
if (partialMatch) {
|
||||
return this.getInternalHandledRoutes()
|
||||
.filter((r) => r.dynamic)
|
||||
.some((r) => url.includes(r.route));
|
||||
@ -39,80 +32,64 @@ export class Router
|
||||
}
|
||||
}
|
||||
|
||||
export class StaticRouter extends Router
|
||||
{
|
||||
constructor(private routes: RouteAction[])
|
||||
{
|
||||
export class StaticRouter extends Router {
|
||||
constructor(private routes: RouteAction[]) {
|
||||
super();
|
||||
}
|
||||
|
||||
public async handleStatic(url: string, info: any, sessionID: string, output: string): Promise<any>
|
||||
{
|
||||
public async handleStatic(url: string, info: any, sessionID: string, output: string): Promise<any> {
|
||||
return this.routes.find((route) => route.url === url).action(url, info, sessionID, output);
|
||||
}
|
||||
|
||||
public override getHandledRoutes(): HandledRoute[]
|
||||
{
|
||||
public override getHandledRoutes(): HandledRoute[] {
|
||||
return this.routes.map((route) => new HandledRoute(route.url, false));
|
||||
}
|
||||
}
|
||||
|
||||
export class DynamicRouter extends Router
|
||||
{
|
||||
constructor(private routes: RouteAction[])
|
||||
{
|
||||
export class DynamicRouter extends Router {
|
||||
constructor(private routes: RouteAction[]) {
|
||||
super();
|
||||
}
|
||||
|
||||
public async handleDynamic(url: string, info: any, sessionID: string, output: string): Promise<any>
|
||||
{
|
||||
public async handleDynamic(url: string, info: any, sessionID: string, output: string): Promise<any> {
|
||||
return this.routes.find((r) => url.includes(r.url)).action(url, info, sessionID, output);
|
||||
}
|
||||
|
||||
public override getHandledRoutes(): HandledRoute[]
|
||||
{
|
||||
public override getHandledRoutes(): HandledRoute[] {
|
||||
return this.routes.map((route) => new HandledRoute(route.url, true));
|
||||
}
|
||||
}
|
||||
|
||||
// The name of this class should be ItemEventRouter, but that name is taken,
|
||||
// So instead I added the definition
|
||||
export class ItemEventRouterDefinition extends Router
|
||||
{
|
||||
export class ItemEventRouterDefinition extends Router {
|
||||
public async handleItemEvent(
|
||||
url: string,
|
||||
pmcData: IPmcData,
|
||||
body: any,
|
||||
sessionID: string,
|
||||
output: IItemEventRouterResponse,
|
||||
): Promise<any>
|
||||
{
|
||||
): Promise<any> {
|
||||
throw new Error("This method needs to be overrode by the router classes");
|
||||
}
|
||||
}
|
||||
|
||||
export class SaveLoadRouter extends Router
|
||||
{
|
||||
public handleLoad(profile: ISptProfile): ISptProfile
|
||||
{
|
||||
export class SaveLoadRouter extends Router {
|
||||
public handleLoad(profile: ISptProfile): ISptProfile {
|
||||
throw new Error("This method needs to be overrode by the router classes");
|
||||
}
|
||||
}
|
||||
|
||||
export class HandledRoute
|
||||
{
|
||||
export class HandledRoute {
|
||||
constructor(
|
||||
public route: string,
|
||||
public dynamic: boolean,
|
||||
)
|
||||
{}
|
||||
) {}
|
||||
}
|
||||
|
||||
export class RouteAction
|
||||
{
|
||||
export class RouteAction {
|
||||
constructor(
|
||||
public url: string,
|
||||
public action: (url: string, info: any, sessionID: string, output: string) => Promise<any>,
|
||||
)
|
||||
{}
|
||||
) {}
|
||||
}
|
||||
|
@ -1,14 +1,11 @@
|
||||
import { IncomingMessage, ServerResponse } from "node:http";
|
||||
|
||||
export class Serializer
|
||||
{
|
||||
public serialize(sessionID: string, req: IncomingMessage, resp: ServerResponse, body: any): void
|
||||
{
|
||||
export class Serializer {
|
||||
public serialize(sessionID: string, req: IncomingMessage, resp: ServerResponse, body: any): void {
|
||||
throw new Error("Should be extended and overrode");
|
||||
}
|
||||
|
||||
public canHandle(something: string): boolean
|
||||
{
|
||||
public canHandle(something: string): boolean {
|
||||
throw new Error("Should be extended and overrode");
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,4 +1,3 @@
|
||||
import { inject, injectable } from "tsyringe";
|
||||
import { BotInventoryGenerator } from "@spt/generators/BotInventoryGenerator";
|
||||
import { BotLevelGenerator } from "@spt/generators/BotLevelGenerator";
|
||||
import { BotDifficultyHelper } from "@spt/helpers/BotDifficultyHelper";
|
||||
@ -31,14 +30,14 @@ import { DatabaseService } from "@spt/services/DatabaseService";
|
||||
import { ItemFilterService } from "@spt/services/ItemFilterService";
|
||||
import { LocalisationService } from "@spt/services/LocalisationService";
|
||||
import { SeasonalEventService } from "@spt/services/SeasonalEventService";
|
||||
import { ICloner } from "@spt/utils/cloners/ICloner";
|
||||
import { HashUtil } from "@spt/utils/HashUtil";
|
||||
import { RandomUtil } from "@spt/utils/RandomUtil";
|
||||
import { TimeUtil } from "@spt/utils/TimeUtil";
|
||||
import { ICloner } from "@spt/utils/cloners/ICloner";
|
||||
import { inject, injectable } from "tsyringe";
|
||||
|
||||
@injectable()
|
||||
export class BotGenerator
|
||||
{
|
||||
export class BotGenerator {
|
||||
protected botConfig: IBotConfig;
|
||||
protected pmcConfig: IPmcConfig;
|
||||
|
||||
@ -60,8 +59,7 @@ export class BotGenerator
|
||||
@inject("ItemFilterService") protected itemFilterService: ItemFilterService,
|
||||
@inject("ConfigServer") protected configServer: ConfigServer,
|
||||
@inject("PrimaryCloner") protected cloner: ICloner,
|
||||
)
|
||||
{
|
||||
) {
|
||||
this.botConfig = this.configServer.getConfig(ConfigTypes.BOT);
|
||||
this.pmcConfig = this.configServer.getConfig(ConfigTypes.PMC);
|
||||
}
|
||||
@ -73,8 +71,7 @@ export class BotGenerator
|
||||
* @param botTemplate base bot template to use (e.g. assault/pmcbot)
|
||||
* @returns
|
||||
*/
|
||||
public generatePlayerScav(sessionId: string, role: string, difficulty: string, botTemplate: IBotType): IBotBase
|
||||
{
|
||||
public generatePlayerScav(sessionId: string, role: string, difficulty: string, botTemplate: IBotType): IBotBase {
|
||||
let bot = this.getCloneOfBotBase();
|
||||
bot.Info.Settings.BotDifficulty = difficulty;
|
||||
bot.Info.Settings.Role = role;
|
||||
@ -102,12 +99,12 @@ export class BotGenerator
|
||||
* @param botGenerationDetails details on how to generate bots
|
||||
* @returns constructed bot
|
||||
*/
|
||||
public prepareAndGenerateBot(sessionId: string, botGenerationDetails: BotGenerationDetails): IBotBase
|
||||
{
|
||||
public prepareAndGenerateBot(sessionId: string, botGenerationDetails: BotGenerationDetails): IBotBase {
|
||||
const preparedBotBase = this.getPreparedBotBase(
|
||||
botGenerationDetails.eventRole ?? botGenerationDetails.role, // Use eventRole if provided,
|
||||
botGenerationDetails.side,
|
||||
botGenerationDetails.botDifficulty);
|
||||
botGenerationDetails.botDifficulty,
|
||||
);
|
||||
|
||||
// Get raw json data for bot (Cloned)
|
||||
const botRole = botGenerationDetails.isPmc
|
||||
@ -125,8 +122,7 @@ export class BotGenerator
|
||||
* @param difficulty Difficult bot should have
|
||||
* @returns Cloned bot base
|
||||
*/
|
||||
protected getPreparedBotBase(botRole: string, botSide: string, difficulty: string): IBotBase
|
||||
{
|
||||
protected getPreparedBotBase(botRole: string, botSide: string, difficulty: string): IBotBase {
|
||||
const botBaseClone = this.getCloneOfBotBase();
|
||||
botBaseClone.Info.Settings.Role = botRole;
|
||||
botBaseClone.Info.Side = botSide;
|
||||
@ -139,8 +135,7 @@ export class BotGenerator
|
||||
* Get a clone of the database\bots\base.json file
|
||||
* @returns IBotBase object
|
||||
*/
|
||||
protected getCloneOfBotBase(): IBotBase
|
||||
{
|
||||
protected getCloneOfBotBase(): IBotBase {
|
||||
return this.cloner.clone(this.databaseService.getBots().base);
|
||||
}
|
||||
|
||||
@ -157,8 +152,7 @@ export class BotGenerator
|
||||
bot: IBotBase,
|
||||
botJsonTemplate: IBotType,
|
||||
botGenerationDetails: BotGenerationDetails,
|
||||
): IBotBase
|
||||
{
|
||||
): IBotBase {
|
||||
const botRole = botGenerationDetails.role.toLowerCase();
|
||||
const botLevel = this.botLevelGenerator.generateBotLevel(
|
||||
botJsonTemplate.experience.level,
|
||||
@ -166,8 +160,7 @@ export class BotGenerator
|
||||
bot,
|
||||
);
|
||||
|
||||
if (!botGenerationDetails.isPlayerScav)
|
||||
{
|
||||
if (!botGenerationDetails.isPlayerScav) {
|
||||
this.botEquipmentFilterService.filterBotEquipment(
|
||||
sessionId,
|
||||
botJsonTemplate,
|
||||
@ -178,11 +171,9 @@ export class BotGenerator
|
||||
|
||||
bot.Info.Nickname = this.generateBotNickname(botJsonTemplate, botGenerationDetails, botRole, sessionId);
|
||||
|
||||
if (!this.seasonalEventService.christmasEventEnabled())
|
||||
{
|
||||
if (!this.seasonalEventService.christmasEventEnabled()) {
|
||||
// Process all bots EXCEPT gifter, he needs christmas items
|
||||
if (botGenerationDetails.role !== "gifter")
|
||||
{
|
||||
if (botGenerationDetails.role !== "gifter") {
|
||||
this.seasonalEventService.removeChristmasItemsFromBotInventory(
|
||||
botJsonTemplate.inventory,
|
||||
botGenerationDetails.role,
|
||||
@ -193,8 +184,7 @@ export class BotGenerator
|
||||
this.removeBlacklistedLootFromBotTemplate(botJsonTemplate.inventory);
|
||||
|
||||
// Remove hideout data if bot is not a PMC or pscav - match what live sends
|
||||
if (!(botGenerationDetails.isPmc || botGenerationDetails.isPlayerScav))
|
||||
{
|
||||
if (!(botGenerationDetails.isPmc || botGenerationDetails.isPlayerScav)) {
|
||||
bot.Hideout = undefined;
|
||||
}
|
||||
|
||||
@ -209,12 +199,10 @@ export class BotGenerator
|
||||
bot.Health = this.generateHealth(botJsonTemplate.health, botGenerationDetails.isPlayerScav);
|
||||
bot.Skills = this.generateSkills(<any>botJsonTemplate.skills); // TODO: fix bad type, bot jsons store skills in dict, output needs to be array
|
||||
|
||||
if (botGenerationDetails.isPmc)
|
||||
{
|
||||
if (botGenerationDetails.isPmc) {
|
||||
bot.Info.IsStreamerModeAvailable = true; // Set to true so client patches can pick it up later - client sometimes alters botrole to assaultGroup
|
||||
this.setRandomisedGameVersionAndCategory(bot.Info);
|
||||
if (bot.Info.GameVersion === GameEditions.UNHEARD)
|
||||
{
|
||||
if (bot.Info.GameVersion === GameEditions.UNHEARD) {
|
||||
this.addAdditionalPocketLootWeightsForUnheardBot(botJsonTemplate);
|
||||
}
|
||||
}
|
||||
@ -230,8 +218,7 @@ export class BotGenerator
|
||||
bot.Info.GameVersion,
|
||||
);
|
||||
|
||||
if (this.botConfig.botRolesWithDogTags.includes(botRole))
|
||||
{
|
||||
if (this.botConfig.botRolesWithDogTags.includes(botRole)) {
|
||||
this.addDogtagToBot(bot);
|
||||
}
|
||||
|
||||
@ -242,16 +229,14 @@ export class BotGenerator
|
||||
this.generateInventoryId(bot);
|
||||
|
||||
// Set role back to originally requested now its been generated
|
||||
if (botGenerationDetails.eventRole)
|
||||
{
|
||||
if (botGenerationDetails.eventRole) {
|
||||
bot.Info.Settings.Role = botGenerationDetails.eventRole;
|
||||
}
|
||||
|
||||
return bot;
|
||||
}
|
||||
|
||||
protected addAdditionalPocketLootWeightsForUnheardBot(botJsonTemplate: IBotType): void
|
||||
{
|
||||
protected addAdditionalPocketLootWeightsForUnheardBot(botJsonTemplate: IBotType): void {
|
||||
// Adjust pocket loot weights to allow for 5 or 6 items
|
||||
const pocketWeights = botJsonTemplate.generation.items.pocketLoot.weights;
|
||||
pocketWeights["5"] = 1;
|
||||
@ -262,31 +247,25 @@ export class BotGenerator
|
||||
* Remove items from item.json/lootableItemBlacklist from bots inventory
|
||||
* @param botInventory Bot to filter
|
||||
*/
|
||||
protected removeBlacklistedLootFromBotTemplate(botInventory: Inventory): void
|
||||
{
|
||||
protected removeBlacklistedLootFromBotTemplate(botInventory: Inventory): void {
|
||||
const lootContainersToFilter = ["Backpack", "Pockets", "TacticalVest"];
|
||||
|
||||
// Remove blacklisted loot from loot containers
|
||||
for (const lootContainerKey of lootContainersToFilter)
|
||||
{
|
||||
for (const lootContainerKey of lootContainersToFilter) {
|
||||
// No container, skip
|
||||
if (botInventory.items[lootContainerKey]?.length === 0)
|
||||
{
|
||||
if (botInventory.items[lootContainerKey]?.length === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const tplsToRemove: string[] = [];
|
||||
const containerItems = botInventory.items[lootContainerKey];
|
||||
for (const tplKey of Object.keys(containerItems))
|
||||
{
|
||||
if (this.itemFilterService.isLootableItemBlacklisted(tplKey))
|
||||
{
|
||||
for (const tplKey of Object.keys(containerItems)) {
|
||||
if (this.itemFilterService.isLootableItemBlacklisted(tplKey)) {
|
||||
tplsToRemove.push(tplKey);
|
||||
}
|
||||
}
|
||||
|
||||
for (const blacklistedTplToRemove of tplsToRemove)
|
||||
{
|
||||
for (const blacklistedTplToRemove of tplsToRemove) {
|
||||
delete containerItems[blacklistedTplToRemove];
|
||||
}
|
||||
}
|
||||
@ -302,8 +281,7 @@ export class BotGenerator
|
||||
bot: IBotBase,
|
||||
appearance: Appearance,
|
||||
botGenerationDetails: BotGenerationDetails,
|
||||
): void
|
||||
{
|
||||
): void {
|
||||
bot.Customization.Head = this.weightedRandomHelper.getWeightedValue<string>(appearance.head);
|
||||
bot.Customization.Body = this.weightedRandomHelper.getWeightedValue<string>(appearance.body);
|
||||
bot.Customization.Feet = this.weightedRandomHelper.getWeightedValue<string>(appearance.feet);
|
||||
@ -314,8 +292,7 @@ export class BotGenerator
|
||||
|
||||
// Find the body/hands mapping
|
||||
const matchingBody: IWildBody = bodyGlobalDict[chosenBodyTemplate?._name];
|
||||
if (matchingBody?.isNotRandom)
|
||||
{
|
||||
if (matchingBody?.isNotRandom) {
|
||||
// Has fixed hands for this body, set them
|
||||
bot.Customization.Hands = matchingBody.hands;
|
||||
}
|
||||
@ -334,8 +311,7 @@ export class BotGenerator
|
||||
botGenerationDetails: BotGenerationDetails,
|
||||
botRole: string,
|
||||
sessionId?: string,
|
||||
): string
|
||||
{
|
||||
): string {
|
||||
const isPlayerScav = botGenerationDetails.isPlayerScav;
|
||||
|
||||
let name = `${this.randomUtil.getArrayValue(botJsonTemplate.firstName)} ${
|
||||
@ -345,19 +321,16 @@ export class BotGenerator
|
||||
|
||||
// Simulate bot looking like a player scav with the PMC name in brackets.
|
||||
// E.g. "ScavName (PMCName)"
|
||||
if (this.shouldSimulatePlayerScavName(botRole, isPlayerScav))
|
||||
{
|
||||
if (this.shouldSimulatePlayerScavName(botRole, isPlayerScav)) {
|
||||
return this.addPlayerScavNameSimulationSuffix(name);
|
||||
}
|
||||
|
||||
if (this.botConfig.showTypeInNickname && !isPlayerScav)
|
||||
{
|
||||
if (this.botConfig.showTypeInNickname && !isPlayerScav) {
|
||||
name += ` ${botRole}`;
|
||||
}
|
||||
|
||||
// We want to replace pmc bot names with player name + prefix
|
||||
if (botGenerationDetails.isPmc && botGenerationDetails.allPmcsHaveSameNameAsPlayer)
|
||||
{
|
||||
if (botGenerationDetails.isPmc && botGenerationDetails.allPmcsHaveSameNameAsPlayer) {
|
||||
const prefix = this.localisationService.getRandomTextThatMatchesPartialKey("pmc-name_prefix_");
|
||||
name = `${prefix} ${name}`;
|
||||
}
|
||||
@ -365,15 +338,15 @@ export class BotGenerator
|
||||
return name;
|
||||
}
|
||||
|
||||
protected shouldSimulatePlayerScavName(botRole: string, isPlayerScav: boolean): boolean
|
||||
{
|
||||
return botRole === "assault"
|
||||
&& this.randomUtil.getChance100(this.botConfig.chanceAssaultScavHasPlayerScavName)
|
||||
&& !isPlayerScav;
|
||||
protected shouldSimulatePlayerScavName(botRole: string, isPlayerScav: boolean): boolean {
|
||||
return (
|
||||
botRole === "assault" &&
|
||||
this.randomUtil.getChance100(this.botConfig.chanceAssaultScavHasPlayerScavName) &&
|
||||
!isPlayerScav
|
||||
);
|
||||
}
|
||||
|
||||
protected addPlayerScavNameSimulationSuffix(nickname: string): string
|
||||
{
|
||||
protected addPlayerScavNameSimulationSuffix(nickname: string): string {
|
||||
const pmcNames = [
|
||||
...this.databaseService.getBots().types.usec.firstName,
|
||||
...this.databaseService.getBots().types.bear.firstName,
|
||||
@ -385,10 +358,8 @@ export class BotGenerator
|
||||
* Log the number of PMCs generated to the debug console
|
||||
* @param output Generated bot array, ready to send to client
|
||||
*/
|
||||
protected logPmcGeneratedCount(output: IBotBase[]): void
|
||||
{
|
||||
const pmcCount = output.reduce((acc, cur) =>
|
||||
{
|
||||
protected logPmcGeneratedCount(output: IBotBase[]): void {
|
||||
const pmcCount = output.reduce((acc, cur) => {
|
||||
return cur.Info.Side === "Bear" || cur.Info.Side === "Usec" ? acc + 1 : acc;
|
||||
}, 0);
|
||||
this.logger.debug(`Generated ${output.length} total bots. Replaced ${pmcCount} with PMCs`);
|
||||
@ -400,8 +371,7 @@ export class BotGenerator
|
||||
* @param playerScav Is a pscav bot being generated
|
||||
* @returns PmcHealth object
|
||||
*/
|
||||
protected generateHealth(healthObj: Health, playerScav = false): PmcHealth
|
||||
{
|
||||
protected generateHealth(healthObj: Health, playerScav = false): PmcHealth {
|
||||
const bodyParts = playerScav
|
||||
? this.getLowestHpBody(healthObj.BodyParts)
|
||||
: this.randomUtil.getArrayValue(healthObj.BodyParts);
|
||||
@ -474,21 +444,17 @@ export class BotGenerator
|
||||
* @param bodies Body parts to sum up
|
||||
* @returns Lowest hp collection
|
||||
*/
|
||||
protected getLowestHpBody(bodies: BodyPart[]): BodyPart | undefined
|
||||
{
|
||||
if (bodies.length === 0)
|
||||
{
|
||||
protected getLowestHpBody(bodies: BodyPart[]): BodyPart | undefined {
|
||||
if (bodies.length === 0) {
|
||||
// Handle empty input
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let result: BodyPart;
|
||||
let currentHighest = Number.POSITIVE_INFINITY;
|
||||
for (const bodyParts of bodies)
|
||||
{
|
||||
for (const bodyParts of bodies) {
|
||||
const hpTotal = Object.values(bodyParts).reduce((acc, curr) => acc + curr.max, 0);
|
||||
if (hpTotal < currentHighest)
|
||||
{
|
||||
if (hpTotal < currentHighest) {
|
||||
// Found collection with lower value that previous, use it
|
||||
currentHighest = hpTotal;
|
||||
result = bodyParts;
|
||||
@ -503,8 +469,7 @@ export class BotGenerator
|
||||
* @param botSkills Skills that should have their progress value randomised
|
||||
* @returns
|
||||
*/
|
||||
protected generateSkills(botSkills: IBaseJsonSkills): botSkills
|
||||
{
|
||||
protected generateSkills(botSkills: IBaseJsonSkills): botSkills {
|
||||
const skillsToReturn: botSkills = {
|
||||
Common: this.getSkillsWithRandomisedProgressValue(botSkills.Common, true),
|
||||
Mastering: this.getSkillsWithRandomisedProgressValue(botSkills.Mastering, false),
|
||||
@ -523,20 +488,16 @@ export class BotGenerator
|
||||
protected getSkillsWithRandomisedProgressValue(
|
||||
skills: Record<string, IBaseSkill>,
|
||||
isCommonSkills: boolean,
|
||||
): IBaseSkill[]
|
||||
{
|
||||
if (Object.keys(skills ?? []).length === 0)
|
||||
{
|
||||
): IBaseSkill[] {
|
||||
if (Object.keys(skills ?? []).length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return Object.keys(skills)
|
||||
.map((skillKey): IBaseSkill =>
|
||||
{
|
||||
.map((skillKey): IBaseSkill => {
|
||||
// Get skill from dict, skip if not found
|
||||
const skill = skills[skillKey];
|
||||
if (!skill)
|
||||
{
|
||||
if (!skill) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@ -544,8 +505,7 @@ export class BotGenerator
|
||||
const skillToAdd: IBaseSkill = { Id: skillKey, Progress: this.randomUtil.getInt(skill.min, skill.max) };
|
||||
|
||||
// Common skills have additional props
|
||||
if (isCommonSkills)
|
||||
{
|
||||
if (isCommonSkills) {
|
||||
(skillToAdd as Common).PointsEarnedDuringSession = 0;
|
||||
(skillToAdd as Common).LastAccess = 0;
|
||||
}
|
||||
@ -560,8 +520,7 @@ export class BotGenerator
|
||||
* @param bot bot to update
|
||||
* @returns updated IBotBase object
|
||||
*/
|
||||
protected addIdsToBot(bot: IBotBase): void
|
||||
{
|
||||
protected addIdsToBot(bot: IBotBase): void {
|
||||
const botId = this.hashUtil.generate();
|
||||
|
||||
bot._id = botId;
|
||||
@ -573,15 +532,12 @@ export class BotGenerator
|
||||
* Update all inventory items that make use of this value too
|
||||
* @param profile Profile to update
|
||||
*/
|
||||
protected generateInventoryId(profile: IBotBase): void
|
||||
{
|
||||
protected generateInventoryId(profile: IBotBase): void {
|
||||
const newInventoryItemId = this.hashUtil.generate();
|
||||
|
||||
for (const item of profile.Inventory.items)
|
||||
{
|
||||
for (const item of profile.Inventory.items) {
|
||||
// Root item found, update its _id value to newly generated id
|
||||
if (item._tpl === ItemTpl.INVENTORY_DEFAULT)
|
||||
{
|
||||
if (item._tpl === ItemTpl.INVENTORY_DEFAULT) {
|
||||
item._id = newInventoryItemId;
|
||||
|
||||
continue;
|
||||
@ -589,14 +545,12 @@ export class BotGenerator
|
||||
|
||||
// Optimisation - skip items without a parentId
|
||||
// They are never linked to root inventory item + we already handled root item above
|
||||
if (!item.parentId)
|
||||
{
|
||||
if (!item.parentId) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Item is a child of root inventory item, update its parentId value to newly generated id
|
||||
if (item.parentId === profile.Inventory.equipment)
|
||||
{
|
||||
if (item.parentId === profile.Inventory.equipment) {
|
||||
item.parentId = newInventoryItemId;
|
||||
}
|
||||
}
|
||||
@ -612,11 +566,9 @@ export class BotGenerator
|
||||
* @param botInfo bot info object to update
|
||||
* @returns Chosen game version
|
||||
*/
|
||||
protected setRandomisedGameVersionAndCategory(botInfo: Info): string
|
||||
{
|
||||
protected setRandomisedGameVersionAndCategory(botInfo: Info): string {
|
||||
// Special case
|
||||
if (botInfo.Nickname.toLowerCase() === "nikita")
|
||||
{
|
||||
if (botInfo.Nickname.toLowerCase() === "nikita") {
|
||||
botInfo.GameVersion = GameEditions.UNHEARD;
|
||||
botInfo.MemberCategory = MemberCategory.DEVELOPER;
|
||||
|
||||
@ -627,8 +579,7 @@ export class BotGenerator
|
||||
botInfo.GameVersion = this.weightedRandomHelper.getWeightedValue(this.pmcConfig.gameVersionWeight);
|
||||
|
||||
// Choose appropriate member category value
|
||||
switch (botInfo.GameVersion)
|
||||
{
|
||||
switch (botInfo.GameVersion) {
|
||||
case GameEditions.EDGE_OF_DARKNESS:
|
||||
botInfo.MemberCategory = MemberCategory.UNIQUE_ID;
|
||||
break;
|
||||
@ -654,8 +605,7 @@ export class BotGenerator
|
||||
* @param bot bot to add dogtag to
|
||||
* @returns Bot with dogtag added
|
||||
*/
|
||||
protected addDogtagToBot(bot: IBotBase): void
|
||||
{
|
||||
protected addDogtagToBot(bot: IBotBase): void {
|
||||
const dogtagUpd: Upd = {
|
||||
SpawnedInSession: true,
|
||||
Dogtag: {
|
||||
@ -691,12 +641,9 @@ export class BotGenerator
|
||||
* @param gameVersion edge_of_darkness / standard
|
||||
* @returns item tpl
|
||||
*/
|
||||
protected getDogtagTplByGameVersionAndSide(side: string, gameVersion: string): string
|
||||
{
|
||||
if (side.toLowerCase() == "usec")
|
||||
{
|
||||
switch (gameVersion)
|
||||
{
|
||||
protected getDogtagTplByGameVersionAndSide(side: string, gameVersion: string): string {
|
||||
if (side.toLowerCase() == "usec") {
|
||||
switch (gameVersion) {
|
||||
case GameEditions.EDGE_OF_DARKNESS:
|
||||
return ItemTpl.BARTER_DOGTAG_USEC_EOD;
|
||||
case GameEditions.UNHEARD:
|
||||
@ -706,8 +653,7 @@ export class BotGenerator
|
||||
}
|
||||
}
|
||||
|
||||
switch (gameVersion)
|
||||
{
|
||||
switch (gameVersion) {
|
||||
case GameEditions.EDGE_OF_DARKNESS:
|
||||
return ItemTpl.BARTER_DOGTAG_BEAR_EOD;
|
||||
case GameEditions.UNHEARD:
|
||||
@ -721,10 +667,8 @@ export class BotGenerator
|
||||
* Adjust a PMCs pocket tpl to UHD if necessary, otherwise do nothing
|
||||
* @param bot Pmc object to adjust
|
||||
*/
|
||||
protected setPmcPocketsByGameVersion(bot: IBotBase): void
|
||||
{
|
||||
if (bot.Info.GameVersion === GameEditions.UNHEARD)
|
||||
{
|
||||
protected setPmcPocketsByGameVersion(bot: IBotBase): void {
|
||||
if (bot.Info.GameVersion === GameEditions.UNHEARD) {
|
||||
const pockets = bot.Inventory.items.find((item) => item.slotId === "Pockets");
|
||||
pockets._tpl = ItemTpl.POCKETS_1X4_TUE;
|
||||
}
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { inject, injectable } from "tsyringe";
|
||||
import { BotEquipmentModGenerator } from "@spt/generators/BotEquipmentModGenerator";
|
||||
import { BotLootGenerator } from "@spt/generators/BotLootGenerator";
|
||||
import { BotWeaponGenerator } from "@spt/generators/BotWeaponGenerator";
|
||||
@ -14,10 +13,7 @@ import { EquipmentSlots } from "@spt/models/enums/EquipmentSlots";
|
||||
import { GameEditions } from "@spt/models/enums/GameEditions";
|
||||
import { ItemTpl } from "@spt/models/enums/ItemTpl";
|
||||
import { IGenerateEquipmentProperties } from "@spt/models/spt/bots/IGenerateEquipmentProperties";
|
||||
import {
|
||||
EquipmentFilterDetails,
|
||||
IBotConfig,
|
||||
} from "@spt/models/spt/config/IBotConfig";
|
||||
import { EquipmentFilterDetails, IBotConfig } from "@spt/models/spt/config/IBotConfig";
|
||||
import { ILogger } from "@spt/models/spt/utils/ILogger";
|
||||
import { ConfigServer } from "@spt/servers/ConfigServer";
|
||||
import { BotEquipmentModPoolService } from "@spt/services/BotEquipmentModPoolService";
|
||||
@ -25,10 +21,10 @@ import { DatabaseService } from "@spt/services/DatabaseService";
|
||||
import { LocalisationService } from "@spt/services/LocalisationService";
|
||||
import { HashUtil } from "@spt/utils/HashUtil";
|
||||
import { RandomUtil } from "@spt/utils/RandomUtil";
|
||||
import { inject, injectable } from "tsyringe";
|
||||
|
||||
@injectable()
|
||||
export class BotInventoryGenerator
|
||||
{
|
||||
export class BotInventoryGenerator {
|
||||
protected botConfig: IBotConfig;
|
||||
|
||||
constructor(
|
||||
@ -46,8 +42,7 @@ export class BotInventoryGenerator
|
||||
@inject("BotEquipmentModPoolService") protected botEquipmentModPoolService: BotEquipmentModPoolService,
|
||||
@inject("BotEquipmentModGenerator") protected botEquipmentModGenerator: BotEquipmentModGenerator,
|
||||
@inject("ConfigServer") protected configServer: ConfigServer,
|
||||
)
|
||||
{
|
||||
) {
|
||||
this.botConfig = this.configServer.getConfig(ConfigTypes.BOT);
|
||||
}
|
||||
|
||||
@ -68,8 +63,7 @@ export class BotInventoryGenerator
|
||||
isPmc: boolean,
|
||||
botLevel: number,
|
||||
chosenGameVersion: string,
|
||||
): PmcInventory
|
||||
{
|
||||
): PmcInventory {
|
||||
const templateInventory = botJsonTemplate.inventory;
|
||||
const wornItemChances = botJsonTemplate.chances;
|
||||
const itemGenerationLimitsMinMax = botJsonTemplate.generation;
|
||||
@ -83,7 +77,8 @@ export class BotInventoryGenerator
|
||||
botRole,
|
||||
botInventory,
|
||||
botLevel,
|
||||
chosenGameVersion);
|
||||
chosenGameVersion,
|
||||
);
|
||||
|
||||
// Roll weapon spawns (primary/secondary/holster) and generate a weapon for each roll that passed
|
||||
this.generateAndAddWeaponsToBot(
|
||||
@ -107,8 +102,7 @@ export class BotInventoryGenerator
|
||||
* Create a pmcInventory object with all the base/generic items needed
|
||||
* @returns PmcInventory object
|
||||
*/
|
||||
protected generateInventoryBase(): PmcInventory
|
||||
{
|
||||
protected generateInventoryBase(): PmcInventory {
|
||||
const equipmentId = this.hashUtil.generate();
|
||||
const stashId = this.hashUtil.generate();
|
||||
const questRaidItemsId = this.hashUtil.generate();
|
||||
@ -150,8 +144,7 @@ export class BotInventoryGenerator
|
||||
botInventory: PmcInventory,
|
||||
botLevel: number,
|
||||
chosenGameVersion: string,
|
||||
): void
|
||||
{
|
||||
): void {
|
||||
// These will be handled later
|
||||
const excludedSlots: string[] = [
|
||||
EquipmentSlots.POCKETS,
|
||||
@ -167,11 +160,9 @@ export class BotInventoryGenerator
|
||||
|
||||
const botEquipConfig = this.botConfig.equipment[this.botGeneratorHelper.getBotEquipmentRole(botRole)];
|
||||
const randomistionDetails = this.botHelper.getBotRandomizationDetails(botLevel, botEquipConfig);
|
||||
for (const equipmentSlot in templateInventory.equipment)
|
||||
{
|
||||
for (const equipmentSlot in templateInventory.equipment) {
|
||||
// Weapons have special generation and will be generated separately; ArmorVest should be generated after TactivalVest
|
||||
if (excludedSlots.includes(equipmentSlot))
|
||||
{
|
||||
if (excludedSlots.includes(equipmentSlot)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -191,7 +182,8 @@ export class BotInventoryGenerator
|
||||
// Generate below in specific order
|
||||
this.generateEquipment({
|
||||
rootEquipmentSlot: EquipmentSlots.POCKETS,
|
||||
rootEquipmentPool: chosenGameVersion === GameEditions.UNHEARD
|
||||
rootEquipmentPool:
|
||||
chosenGameVersion === GameEditions.UNHEARD
|
||||
? { [ItemTpl.POCKETS_1X4_TUE]: 1 }
|
||||
: templateInventory.equipment.Pockets,
|
||||
modPool: templateInventory.mods,
|
||||
@ -249,15 +241,13 @@ export class BotInventoryGenerator
|
||||
});
|
||||
|
||||
// Bot has no armor vest and flagged to be foreced to wear armored rig in this event
|
||||
if (botEquipConfig.forceOnlyArmoredRigWhenNoArmor && !hasArmorVest)
|
||||
{
|
||||
if (botEquipConfig.forceOnlyArmoredRigWhenNoArmor && !hasArmorVest) {
|
||||
// Filter rigs down to only those with armor
|
||||
this.filterRigsToThoseWithProtection(templateInventory.equipment);
|
||||
}
|
||||
|
||||
// Optimisation - Remove armored rigs from pool
|
||||
if (hasArmorVest)
|
||||
{
|
||||
if (hasArmorVest) {
|
||||
// Filter rigs down to only those with armor
|
||||
this.filterRigsToThoseWithoutProtection(templateInventory.equipment);
|
||||
}
|
||||
@ -279,13 +269,10 @@ export class BotInventoryGenerator
|
||||
* Remove non-armored rigs from parameter data
|
||||
* @param templateEquipment Equpiment to filter TacticalVest of
|
||||
*/
|
||||
protected filterRigsToThoseWithProtection(templateEquipment: Equipment): void
|
||||
{
|
||||
protected filterRigsToThoseWithProtection(templateEquipment: Equipment): void {
|
||||
const tacVestsWithArmor = Object.entries(templateEquipment.TacticalVest).reduce(
|
||||
(newVestDictionary, [tplKey]) =>
|
||||
{
|
||||
if (this.itemHelper.itemHasSlots(tplKey))
|
||||
{
|
||||
(newVestDictionary, [tplKey]) => {
|
||||
if (this.itemHelper.itemHasSlots(tplKey)) {
|
||||
newVestDictionary[tplKey] = templateEquipment.TacticalVest[tplKey];
|
||||
}
|
||||
return newVestDictionary;
|
||||
@ -300,13 +287,10 @@ export class BotInventoryGenerator
|
||||
* Remove armored rigs from parameter data
|
||||
* @param templateEquipment Equpiment to filter TacticalVest of
|
||||
*/
|
||||
protected filterRigsToThoseWithoutProtection(templateEquipment: Equipment): void
|
||||
{
|
||||
protected filterRigsToThoseWithoutProtection(templateEquipment: Equipment): void {
|
||||
const tacVestsWithoutArmor = Object.entries(templateEquipment.TacticalVest).reduce(
|
||||
(newVestDictionary, [tplKey]) =>
|
||||
{
|
||||
if (!this.itemHelper.itemHasSlots(tplKey))
|
||||
{
|
||||
(newVestDictionary, [tplKey]) => {
|
||||
if (!this.itemHelper.itemHasSlots(tplKey)) {
|
||||
newVestDictionary[tplKey] = templateEquipment.TacticalVest[tplKey];
|
||||
}
|
||||
return newVestDictionary;
|
||||
@ -322,16 +306,14 @@ export class BotInventoryGenerator
|
||||
* @param settings Values to adjust how item is chosen and added to bot
|
||||
* @returns true when item added
|
||||
*/
|
||||
protected generateEquipment(settings: IGenerateEquipmentProperties): boolean
|
||||
{
|
||||
protected generateEquipment(settings: IGenerateEquipmentProperties): boolean {
|
||||
const spawnChance = ([EquipmentSlots.POCKETS, EquipmentSlots.SECURED_CONTAINER] as string[]).includes(
|
||||
settings.rootEquipmentSlot,
|
||||
)
|
||||
? 100
|
||||
: settings.spawnChances.equipment[settings.rootEquipmentSlot];
|
||||
|
||||
if (typeof spawnChance === "undefined")
|
||||
{
|
||||
if (typeof spawnChance === "undefined") {
|
||||
this.logger.warning(
|
||||
this.localisationService.getText(
|
||||
"bot-no_spawn_chance_defined_for_equipment_slot",
|
||||
@ -343,25 +325,21 @@ export class BotInventoryGenerator
|
||||
}
|
||||
|
||||
const shouldSpawn = this.randomUtil.getChance100(spawnChance);
|
||||
if (shouldSpawn && Object.keys(settings.rootEquipmentPool).length)
|
||||
{
|
||||
if (shouldSpawn && Object.keys(settings.rootEquipmentPool).length) {
|
||||
let pickedItemDb: ITemplateItem;
|
||||
let found = false;
|
||||
|
||||
const maxAttempts = Math.round(Object.keys(settings.rootEquipmentPool).length * 0.75); // Roughly 75% of pool size
|
||||
let attempts = 0;
|
||||
while (!found)
|
||||
{
|
||||
if (Object.values(settings.rootEquipmentPool).length === 0)
|
||||
{
|
||||
while (!found) {
|
||||
if (Object.values(settings.rootEquipmentPool).length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const chosenItemTpl = this.weightedRandomHelper.getWeightedValue<string>(settings.rootEquipmentPool);
|
||||
const dbResult = this.itemHelper.getItem(chosenItemTpl);
|
||||
|
||||
if (!dbResult[0])
|
||||
{
|
||||
if (!dbResult[0]) {
|
||||
this.logger.error(this.localisationService.getText("bot-missing_item_template", chosenItemTpl));
|
||||
this.logger.info(`EquipmentSlot -> ${settings.rootEquipmentSlot}`);
|
||||
|
||||
@ -378,11 +356,9 @@ export class BotInventoryGenerator
|
||||
chosenItemTpl,
|
||||
settings.rootEquipmentSlot,
|
||||
);
|
||||
if (compatabilityResult.incompatible)
|
||||
{
|
||||
if (compatabilityResult.incompatible) {
|
||||
// Tried x different items that failed, stop
|
||||
if (attempts > maxAttempts)
|
||||
{
|
||||
if (attempts > maxAttempts) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -390,9 +366,7 @@ export class BotInventoryGenerator
|
||||
delete settings.rootEquipmentPool[chosenItemTpl];
|
||||
|
||||
attempts++;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
// Success
|
||||
found = true;
|
||||
pickedItemDb = dbResult[1];
|
||||
@ -412,10 +386,9 @@ export class BotInventoryGenerator
|
||||
// Use dynamic mod pool if enabled in config for this bot
|
||||
const botEquipmentRole = this.botGeneratorHelper.getBotEquipmentRole(settings.botRole);
|
||||
if (
|
||||
this.botConfig.equipment[botEquipmentRole]
|
||||
&& settings.randomisationDetails?.randomisedArmorSlots?.includes(settings.rootEquipmentSlot)
|
||||
)
|
||||
{
|
||||
this.botConfig.equipment[botEquipmentRole] &&
|
||||
settings.randomisationDetails?.randomisedArmorSlots?.includes(settings.rootEquipmentSlot)
|
||||
) {
|
||||
settings.modPool[pickedItemDb._id] = this.getFilteredDynamicModsForItem(
|
||||
pickedItemDb._id,
|
||||
this.botConfig.equipment[botEquipmentRole].blacklist,
|
||||
@ -423,8 +396,7 @@ export class BotInventoryGenerator
|
||||
}
|
||||
|
||||
// Item has slots, fill them
|
||||
if (pickedItemDb._props.Slots?.length > 0 && !settings.generateModsBlacklist?.includes(pickedItemDb._id))
|
||||
{
|
||||
if (pickedItemDb._props.Slots?.length > 0 && !settings.generateModsBlacklist?.includes(pickedItemDb._id)) {
|
||||
const items = this.botEquipmentModGenerator.generateModsForEquipment(
|
||||
[item],
|
||||
id,
|
||||
@ -432,9 +404,7 @@ export class BotInventoryGenerator
|
||||
settings,
|
||||
);
|
||||
settings.inventory.items.push(...items);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
// No slots, push root item only
|
||||
settings.inventory.items.push(item);
|
||||
}
|
||||
@ -454,16 +424,13 @@ export class BotInventoryGenerator
|
||||
protected getFilteredDynamicModsForItem(
|
||||
itemTpl: string,
|
||||
equipmentBlacklist: EquipmentFilterDetails[],
|
||||
): Record<string, string[]>
|
||||
{
|
||||
): Record<string, string[]> {
|
||||
const modPool = this.botEquipmentModPoolService.getModsForGearSlot(itemTpl);
|
||||
for (const modSlot of Object.keys(modPool ?? []))
|
||||
{
|
||||
for (const modSlot of Object.keys(modPool ?? [])) {
|
||||
const blacklistedMods = equipmentBlacklist[0]?.equipment[modSlot] || [];
|
||||
const filteredMods = modPool[modSlot].filter((x) => !blacklistedMods.includes(x));
|
||||
|
||||
if (filteredMods.length > 0)
|
||||
{
|
||||
if (filteredMods.length > 0) {
|
||||
modPool[modSlot] = filteredMods;
|
||||
}
|
||||
}
|
||||
@ -491,14 +458,11 @@ export class BotInventoryGenerator
|
||||
isPmc: boolean,
|
||||
itemGenerationLimitsMinMax: Generation,
|
||||
botLevel: number,
|
||||
): void
|
||||
{
|
||||
): void {
|
||||
const weaponSlotsToFill = this.getDesiredWeaponsForBot(equipmentChances);
|
||||
for (const weaponSlot of weaponSlotsToFill)
|
||||
{
|
||||
for (const weaponSlot of weaponSlotsToFill) {
|
||||
// Add weapon to bot if true and bot json has something to put into the slot
|
||||
if (weaponSlot.shouldSpawn && Object.keys(templateInventory.equipment[weaponSlot.slot]).length)
|
||||
{
|
||||
if (weaponSlot.shouldSpawn && Object.keys(templateInventory.equipment[weaponSlot.slot]).length) {
|
||||
this.addWeaponAndMagazinesToInventory(
|
||||
sessionId,
|
||||
weaponSlot,
|
||||
@ -519,8 +483,7 @@ export class BotInventoryGenerator
|
||||
* @param equipmentChances Chances bot has certain equipment
|
||||
* @returns What slots bot should have weapons generated for
|
||||
*/
|
||||
protected getDesiredWeaponsForBot(equipmentChances: Chances): { slot: EquipmentSlots, shouldSpawn: boolean }[]
|
||||
{
|
||||
protected getDesiredWeaponsForBot(equipmentChances: Chances): { slot: EquipmentSlots; shouldSpawn: boolean }[] {
|
||||
const shouldSpawnPrimary = this.randomUtil.getChance100(equipmentChances.equipment.FirstPrimaryWeapon);
|
||||
return [
|
||||
{ slot: EquipmentSlots.FIRST_PRIMARY_WEAPON, shouldSpawn: shouldSpawnPrimary },
|
||||
@ -552,7 +515,7 @@ export class BotInventoryGenerator
|
||||
*/
|
||||
protected addWeaponAndMagazinesToInventory(
|
||||
sessionId: string,
|
||||
weaponSlot: { slot: EquipmentSlots, shouldSpawn: boolean },
|
||||
weaponSlot: { slot: EquipmentSlots; shouldSpawn: boolean },
|
||||
templateInventory: Inventory,
|
||||
botInventory: PmcInventory,
|
||||
equipmentChances: Chances,
|
||||
@ -560,8 +523,7 @@ export class BotInventoryGenerator
|
||||
isPmc: boolean,
|
||||
itemGenerationWeights: Generation,
|
||||
botLevel: number,
|
||||
): void
|
||||
{
|
||||
): void {
|
||||
const generatedWeapon = this.botWeaponGenerator.generateRandomWeapon(
|
||||
sessionId,
|
||||
weaponSlot.slot,
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { inject, injectable } from "tsyringe";
|
||||
import { MinMax } from "@spt/models/common/MinMax";
|
||||
import { IRandomisedBotLevelResult } from "@spt/models/eft/bot/IRandomisedBotLevelResult";
|
||||
import { IBotBase } from "@spt/models/eft/common/tables/IBotBase";
|
||||
@ -6,16 +5,15 @@ import { BotGenerationDetails } from "@spt/models/spt/bots/BotGenerationDetails"
|
||||
import { ILogger } from "@spt/models/spt/utils/ILogger";
|
||||
import { DatabaseService } from "@spt/services/DatabaseService";
|
||||
import { RandomUtil } from "@spt/utils/RandomUtil";
|
||||
import { inject, injectable } from "tsyringe";
|
||||
|
||||
@injectable()
|
||||
export class BotLevelGenerator
|
||||
{
|
||||
export class BotLevelGenerator {
|
||||
constructor(
|
||||
@inject("PrimaryLogger") protected logger: ILogger,
|
||||
@inject("RandomUtil") protected randomUtil: RandomUtil,
|
||||
@inject("DatabaseService") protected databaseService: DatabaseService,
|
||||
)
|
||||
{}
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Return a randomised bot level and exp value
|
||||
@ -28,8 +26,7 @@ export class BotLevelGenerator
|
||||
levelDetails: MinMax,
|
||||
botGenerationDetails: BotGenerationDetails,
|
||||
bot: IBotBase,
|
||||
): IRandomisedBotLevelResult
|
||||
{
|
||||
): IRandomisedBotLevelResult {
|
||||
const expTable = this.databaseService.getGlobals().config.exp.level.exp_table;
|
||||
const highestLevel = this.getHighestRelativeBotLevel(botGenerationDetails, levelDetails, expTable.length);
|
||||
const lowestLevel = this.getLowestRelativeBotLevel(botGenerationDetails, levelDetails, expTable.length);
|
||||
@ -38,14 +35,12 @@ export class BotLevelGenerator
|
||||
let exp = 0;
|
||||
const level = this.randomUtil.getInt(lowestLevel, highestLevel);
|
||||
|
||||
for (let i = 0; i < level; i++)
|
||||
{
|
||||
for (let i = 0; i < level; i++) {
|
||||
exp += expTable[i].exp;
|
||||
}
|
||||
|
||||
// Sprinkle in some random exp within the level, unless we are at max level.
|
||||
if (level < expTable.length - 1)
|
||||
{
|
||||
if (level < expTable.length - 1) {
|
||||
exp += this.randomUtil.getInt(0, expTable[level].exp - 1);
|
||||
}
|
||||
|
||||
@ -63,16 +58,14 @@ export class BotLevelGenerator
|
||||
botGenerationDetails: BotGenerationDetails,
|
||||
levelDetails: MinMax,
|
||||
maxLevel: number,
|
||||
): number
|
||||
{
|
||||
const maxPossibleLevel
|
||||
= botGenerationDetails.isPmc && botGenerationDetails.locationSpecificPmcLevelOverride
|
||||
): number {
|
||||
const maxPossibleLevel =
|
||||
botGenerationDetails.isPmc && botGenerationDetails.locationSpecificPmcLevelOverride
|
||||
? Math.min(botGenerationDetails.locationSpecificPmcLevelOverride.max, maxLevel) // Was a PMC and they have a level override
|
||||
: Math.min(levelDetails.max, maxLevel); // Not pmc with override or non-pmc
|
||||
|
||||
let level = botGenerationDetails.playerLevel + botGenerationDetails.botRelativeLevelDeltaMax;
|
||||
if (level > maxPossibleLevel)
|
||||
{
|
||||
if (level > maxPossibleLevel) {
|
||||
level = maxPossibleLevel;
|
||||
}
|
||||
|
||||
@ -90,10 +83,9 @@ export class BotLevelGenerator
|
||||
botGenerationDetails: BotGenerationDetails,
|
||||
levelDetails: MinMax,
|
||||
maxlevel: number,
|
||||
): number
|
||||
{
|
||||
const minPossibleLevel
|
||||
= botGenerationDetails.isPmc && botGenerationDetails.locationSpecificPmcLevelOverride
|
||||
): number {
|
||||
const minPossibleLevel =
|
||||
botGenerationDetails.isPmc && botGenerationDetails.locationSpecificPmcLevelOverride
|
||||
? Math.min(
|
||||
Math.max(levelDetails.min, botGenerationDetails.locationSpecificPmcLevelOverride.min), // Biggest between json min and the botgen min
|
||||
maxlevel, // Fallback if value above is crazy (default is 79)
|
||||
@ -101,8 +93,7 @@ export class BotLevelGenerator
|
||||
: Math.min(levelDetails.min, maxlevel); // Not pmc with override or non-pmc
|
||||
|
||||
let level = botGenerationDetails.playerLevel - botGenerationDetails.botRelativeLevelDeltaMin;
|
||||
if (level < minPossibleLevel)
|
||||
{
|
||||
if (level < minPossibleLevel) {
|
||||
level = minPossibleLevel;
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { inject, injectable } from "tsyringe";
|
||||
import { BotWeaponGenerator } from "@spt/generators/BotWeaponGenerator";
|
||||
import { BotGeneratorHelper } from "@spt/helpers/BotGeneratorHelper";
|
||||
import { BotHelper } from "@spt/helpers/BotHelper";
|
||||
@ -23,13 +22,13 @@ import { ConfigServer } from "@spt/servers/ConfigServer";
|
||||
import { BotLootCacheService } from "@spt/services/BotLootCacheService";
|
||||
import { DatabaseService } from "@spt/services/DatabaseService";
|
||||
import { LocalisationService } from "@spt/services/LocalisationService";
|
||||
import { ICloner } from "@spt/utils/cloners/ICloner";
|
||||
import { HashUtil } from "@spt/utils/HashUtil";
|
||||
import { RandomUtil } from "@spt/utils/RandomUtil";
|
||||
import { ICloner } from "@spt/utils/cloners/ICloner";
|
||||
import { inject, injectable } from "tsyringe";
|
||||
|
||||
@injectable()
|
||||
export class BotLootGenerator
|
||||
{
|
||||
export class BotLootGenerator {
|
||||
protected botConfig: IBotConfig;
|
||||
protected pmcConfig: IPmcConfig;
|
||||
|
||||
@ -49,14 +48,12 @@ export class BotLootGenerator
|
||||
@inject("LocalisationService") protected localisationService: LocalisationService,
|
||||
@inject("ConfigServer") protected configServer: ConfigServer,
|
||||
@inject("PrimaryCloner") protected cloner: ICloner,
|
||||
)
|
||||
{
|
||||
) {
|
||||
this.botConfig = this.configServer.getConfig(ConfigTypes.BOT);
|
||||
this.pmcConfig = this.configServer.getConfig(ConfigTypes.PMC);
|
||||
}
|
||||
|
||||
protected getItemSpawnLimitsForBot(botRole: string): IItemSpawnLimitSettings
|
||||
{
|
||||
protected getItemSpawnLimitsForBot(botRole: string): IItemSpawnLimitSettings {
|
||||
// Init item limits
|
||||
const limitsForBotDict: Record<string, number> = {};
|
||||
this.initItemLimitArray(botRole, limitsForBotDict);
|
||||
@ -80,25 +77,23 @@ export class BotLootGenerator
|
||||
botRole: string,
|
||||
botInventory: PmcInventory,
|
||||
botLevel: number,
|
||||
): void
|
||||
{
|
||||
): void {
|
||||
// Limits on item types to be added as loot
|
||||
const itemCounts = botJsonTemplate.generation.items;
|
||||
|
||||
if (
|
||||
!itemCounts.backpackLoot.weights
|
||||
|| !itemCounts.pocketLoot.weights
|
||||
|| !itemCounts.vestLoot.weights
|
||||
|| !itemCounts.specialItems.weights
|
||||
|| !itemCounts.healing.weights
|
||||
|| !itemCounts.drugs.weights
|
||||
|| !itemCounts.food.weights
|
||||
|| !itemCounts.drink.weights
|
||||
|| !itemCounts.currency.weights
|
||||
|| !itemCounts.stims.weights
|
||||
|| !itemCounts.grenades.weights
|
||||
)
|
||||
{
|
||||
!itemCounts.backpackLoot.weights ||
|
||||
!itemCounts.pocketLoot.weights ||
|
||||
!itemCounts.vestLoot.weights ||
|
||||
!itemCounts.specialItems.weights ||
|
||||
!itemCounts.healing.weights ||
|
||||
!itemCounts.drugs.weights ||
|
||||
!itemCounts.food.weights ||
|
||||
!itemCounts.drink.weights ||
|
||||
!itemCounts.currency.weights ||
|
||||
!itemCounts.stims.weights ||
|
||||
!itemCounts.grenades.weights
|
||||
) {
|
||||
this.logger.warning(this.localisationService.getText("bot-unable_to_generate_bot_loot", botRole));
|
||||
|
||||
return;
|
||||
@ -107,9 +102,7 @@ export class BotLootGenerator
|
||||
let backpackLootCount = Number(
|
||||
this.weightedRandomHelper.getWeightedValue<number>(itemCounts.backpackLoot.weights),
|
||||
);
|
||||
let pocketLootCount = Number(
|
||||
this.weightedRandomHelper.getWeightedValue<number>(itemCounts.pocketLoot.weights),
|
||||
);
|
||||
let pocketLootCount = Number(this.weightedRandomHelper.getWeightedValue<number>(itemCounts.pocketLoot.weights));
|
||||
let vestLootCount = this.weightedRandomHelper.getWeightedValue<number>(itemCounts.vestLoot.weights);
|
||||
const specialLootItemCount = Number(
|
||||
this.weightedRandomHelper.getWeightedValue<number>(itemCounts.specialItems.weights),
|
||||
@ -120,16 +113,13 @@ export class BotLootGenerator
|
||||
const foodItemCount = Number(this.weightedRandomHelper.getWeightedValue<number>(itemCounts.food.weights));
|
||||
const drinkItemCount = Number(this.weightedRandomHelper.getWeightedValue<number>(itemCounts.drink.weights));
|
||||
|
||||
let currencyItemCount = Number(
|
||||
this.weightedRandomHelper.getWeightedValue<number>(itemCounts.currency.weights),
|
||||
);
|
||||
let currencyItemCount = Number(this.weightedRandomHelper.getWeightedValue<number>(itemCounts.currency.weights));
|
||||
|
||||
const stimItemCount = Number(this.weightedRandomHelper.getWeightedValue<number>(itemCounts.stims.weights));
|
||||
const grenadeCount = Number(this.weightedRandomHelper.getWeightedValue<number>(itemCounts.grenades.weights));
|
||||
|
||||
// If bot has been flagged as not having loot, set below counts to 0
|
||||
if (this.botConfig.disableLootOnBotTypes?.includes(botRole.toLowerCase()))
|
||||
{
|
||||
if (this.botConfig.disableLootOnBotTypes?.includes(botRole.toLowerCase())) {
|
||||
backpackLootCount = 0;
|
||||
pocketLootCount = 0;
|
||||
vestLootCount = 0;
|
||||
@ -137,8 +127,7 @@ export class BotLootGenerator
|
||||
}
|
||||
|
||||
// Forced pmc healing loot into secure container
|
||||
if (isPmc && this.pmcConfig.forceHealingItemsIntoSecure)
|
||||
{
|
||||
if (isPmc && this.pmcConfig.forceHealingItemsIntoSecure) {
|
||||
this.addForcedMedicalItemsToPmcSecure(botInventory, botRole);
|
||||
}
|
||||
|
||||
@ -255,11 +244,9 @@ export class BotLootGenerator
|
||||
);
|
||||
|
||||
// Backpack - generate loot if they have one
|
||||
if (containersBotHasAvailable.includes(EquipmentSlots.BACKPACK))
|
||||
{
|
||||
if (containersBotHasAvailable.includes(EquipmentSlots.BACKPACK)) {
|
||||
// Add randomly generated weapon to PMC backpacks
|
||||
if (isPmc && this.randomUtil.getChance100(this.pmcConfig.looseWeaponInBackpackChancePercent))
|
||||
{
|
||||
if (isPmc && this.randomUtil.getChance100(this.pmcConfig.looseWeaponInBackpackChancePercent)) {
|
||||
this.addLooseWeaponsToInventorySlot(
|
||||
sessionId,
|
||||
botInventory,
|
||||
@ -287,8 +274,7 @@ export class BotLootGenerator
|
||||
}
|
||||
|
||||
// TacticalVest - generate loot if they have one
|
||||
if (containersBotHasAvailable.includes(EquipmentSlots.TACTICAL_VEST))
|
||||
{
|
||||
if (containersBotHasAvailable.includes(EquipmentSlots.TACTICAL_VEST)) {
|
||||
// Vest
|
||||
this.addLootFromPool(
|
||||
this.botLootCacheService.getLootFromCache(botRole, isPmc, LootCacheType.VEST, botJsonTemplate),
|
||||
@ -319,8 +305,7 @@ export class BotLootGenerator
|
||||
// Secure
|
||||
|
||||
// only add if not a pmc or is pmc and flag is true
|
||||
if (!isPmc || (isPmc && this.pmcConfig.addSecureContainerLootFromBotConfig))
|
||||
{
|
||||
if (!isPmc || (isPmc && this.pmcConfig.addSecureContainerLootFromBotConfig)) {
|
||||
this.addLootFromPool(
|
||||
this.botLootCacheService.getLootFromCache(botRole, isPmc, LootCacheType.SECURE, botJsonTemplate),
|
||||
[EquipmentSlots.SECURED_CONTAINER],
|
||||
@ -340,17 +325,14 @@ export class BotLootGenerator
|
||||
* @param botInventory Bot to check
|
||||
* @returns Array of available slots
|
||||
*/
|
||||
protected getAvailableContainersBotCanStoreItemsIn(botInventory: PmcInventory): EquipmentSlots[]
|
||||
{
|
||||
protected getAvailableContainersBotCanStoreItemsIn(botInventory: PmcInventory): EquipmentSlots[] {
|
||||
const result = [EquipmentSlots.POCKETS];
|
||||
|
||||
if (botInventory.items.some((item) => item.slotId === EquipmentSlots.TACTICAL_VEST))
|
||||
{
|
||||
if (botInventory.items.some((item) => item.slotId === EquipmentSlots.TACTICAL_VEST)) {
|
||||
result.push(EquipmentSlots.TACTICAL_VEST);
|
||||
}
|
||||
|
||||
if (botInventory.items.some((item) => item.slotId === EquipmentSlots.BACKPACK))
|
||||
{
|
||||
if (botInventory.items.some((item) => item.slotId === EquipmentSlots.BACKPACK)) {
|
||||
result.push(EquipmentSlots.BACKPACK);
|
||||
}
|
||||
|
||||
@ -362,8 +344,7 @@ export class BotLootGenerator
|
||||
* @param botInventory Inventory to add items to
|
||||
* @param botRole Role of bot (pmcBEAR/pmcUSEC)
|
||||
*/
|
||||
protected addForcedMedicalItemsToPmcSecure(botInventory: PmcInventory, botRole: string): void
|
||||
{
|
||||
protected addForcedMedicalItemsToPmcSecure(botInventory: PmcInventory, botRole: string): void {
|
||||
// surv12
|
||||
this.addLootFromPool(
|
||||
{ "5d02797c86f774203f38e30a": 1 },
|
||||
@ -396,8 +377,7 @@ export class BotLootGenerator
|
||||
* @param nValue Value to bias choice
|
||||
* @returns Chosen number
|
||||
*/
|
||||
protected getRandomisedCount(min: number, max: number, nValue: number): number
|
||||
{
|
||||
protected getRandomisedCount(min: number, max: number, nValue: number): number {
|
||||
const range = max - min;
|
||||
return this.randomUtil.getBiasedRandomNumber(min, max, range, nValue);
|
||||
}
|
||||
@ -423,28 +403,23 @@ export class BotLootGenerator
|
||||
totalValueLimitRub = 0,
|
||||
isPmc = false,
|
||||
containersIdFull = new Set<string>(),
|
||||
): void
|
||||
{
|
||||
): void {
|
||||
// Loot pool has items
|
||||
const poolSize = Object.keys(pool).length;
|
||||
if (poolSize > 0)
|
||||
{
|
||||
if (poolSize > 0) {
|
||||
let currentTotalRub = 0;
|
||||
|
||||
let fitItemIntoContainerAttempts = 0;
|
||||
for (let i = 0; i < totalItemCount; i++)
|
||||
{
|
||||
for (let i = 0; i < totalItemCount; i++) {
|
||||
// Pool can become empty if item spawn limits keep removing items
|
||||
if (Object.keys(pool).length === 0)
|
||||
{
|
||||
if (Object.keys(pool).length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const weightedItemTpl = this.weightedRandomHelper.getWeightedValue<string>(pool);
|
||||
const itemResult = this.itemHelper.getItem(weightedItemTpl);
|
||||
const itemToAddTemplate = itemResult[1];
|
||||
if (!itemResult[0])
|
||||
{
|
||||
if (!itemResult[0]) {
|
||||
this.logger.warning(
|
||||
`Unable to process item tpl: ${weightedItemTpl} for slots: ${equipmentSlots} on bot: ${botRole}`,
|
||||
);
|
||||
@ -452,10 +427,8 @@ export class BotLootGenerator
|
||||
continue;
|
||||
}
|
||||
|
||||
if (itemSpawnLimits)
|
||||
{
|
||||
if (this.itemHasReachedSpawnLimit(itemToAddTemplate, botRole, itemSpawnLimits))
|
||||
{
|
||||
if (itemSpawnLimits) {
|
||||
if (this.itemHasReachedSpawnLimit(itemToAddTemplate, botRole, itemSpawnLimits)) {
|
||||
// Remove item from pool to prevent it being picked again
|
||||
delete pool[weightedItemTpl];
|
||||
|
||||
@ -474,11 +447,9 @@ export class BotLootGenerator
|
||||
];
|
||||
|
||||
// Is Simple-Wallet / WZ wallet
|
||||
if (this.botConfig.walletLoot.walletTplPool.includes(weightedItemTpl))
|
||||
{
|
||||
if (this.botConfig.walletLoot.walletTplPool.includes(weightedItemTpl)) {
|
||||
const addCurrencyToWallet = this.randomUtil.getChance100(this.botConfig.walletLoot.chancePercent);
|
||||
if (addCurrencyToWallet)
|
||||
{
|
||||
if (addCurrencyToWallet) {
|
||||
// Create the currency items we want to add to wallet
|
||||
const itemsToAdd = this.createWalletLoot(newRootItemId);
|
||||
|
||||
@ -490,11 +461,9 @@ export class BotLootGenerator
|
||||
this.cloner.clone(containerGrid), // MUST clone grid before passing in as function modifies grid
|
||||
itemsToAdd,
|
||||
);
|
||||
if (canAddToContainer)
|
||||
{
|
||||
if (canAddToContainer) {
|
||||
// Add each currency to wallet
|
||||
for (const itemToAdd of itemsToAdd)
|
||||
{
|
||||
for (const itemToAdd of itemsToAdd) {
|
||||
this.inventoryHelper.placeItemInContainer(
|
||||
containerGrid,
|
||||
itemToAdd,
|
||||
@ -503,7 +472,7 @@ export class BotLootGenerator
|
||||
);
|
||||
}
|
||||
|
||||
itemWithChildrenToAdd.push(...itemsToAdd.flatMap((moneyStack) => moneyStack));
|
||||
itemWithChildrenToAdd.push(...itemsToAdd.flat());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -521,10 +490,8 @@ export class BotLootGenerator
|
||||
);
|
||||
|
||||
// Handle when item cannot be added
|
||||
if (itemAddedResult !== ItemAddedResult.SUCCESS)
|
||||
{
|
||||
if (itemAddedResult === ItemAddedResult.NO_CONTAINERS)
|
||||
{
|
||||
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`,
|
||||
@ -533,8 +500,7 @@ export class BotLootGenerator
|
||||
}
|
||||
|
||||
fitItemIntoContainerAttempts++;
|
||||
if (fitItemIntoContainerAttempts >= 4)
|
||||
{
|
||||
if (fitItemIntoContainerAttempts >= 4) {
|
||||
this.logger.debug(
|
||||
`Failed to place item ${i} of ${totalItemCount} items into ${botRole} containers: ${equipmentSlots.join(
|
||||
",",
|
||||
@ -554,11 +520,9 @@ export class BotLootGenerator
|
||||
fitItemIntoContainerAttempts = 0;
|
||||
|
||||
// Stop adding items to bots pool if rolling total is over total limit
|
||||
if (totalValueLimitRub > 0)
|
||||
{
|
||||
if (totalValueLimitRub > 0) {
|
||||
currentTotalRub += this.handbookHelper.getTemplatePrice(itemToAddTemplate._id);
|
||||
if (currentTotalRub > totalValueLimitRub)
|
||||
{
|
||||
if (currentTotalRub > totalValueLimitRub) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -566,8 +530,7 @@ export class BotLootGenerator
|
||||
}
|
||||
}
|
||||
|
||||
protected createWalletLoot(walletId: string): Item[][]
|
||||
{
|
||||
protected createWalletLoot(walletId: string): Item[][] {
|
||||
const result: Item[][] = [];
|
||||
|
||||
// Choose how many stacks of currency will be added to wallet
|
||||
@ -575,8 +538,7 @@ export class BotLootGenerator
|
||||
this.botConfig.walletLoot.itemCount.min,
|
||||
this.botConfig.walletLoot.itemCount.max,
|
||||
);
|
||||
for (let index = 0; index < itemCount; index++)
|
||||
{
|
||||
for (let index = 0; index < itemCount; index++) {
|
||||
// Choose the size of the currency stack - default is 5k, 10k, 15k, 20k, 25k
|
||||
const chosenStackCount = Number(
|
||||
this.weightedRandomHelper.getWeightedValue<string>(this.botConfig.walletLoot.stackSizeWeight),
|
||||
@ -606,26 +568,21 @@ export class BotLootGenerator
|
||||
itemToAddChildrenTo: Item[],
|
||||
isPmc: boolean,
|
||||
botRole: string,
|
||||
): void
|
||||
{
|
||||
): void {
|
||||
// Fill ammo box
|
||||
if (this.itemHelper.isOfBaseclass(itemToAddTemplate._id, BaseClasses.AMMO_BOX))
|
||||
{
|
||||
if (this.itemHelper.isOfBaseclass(itemToAddTemplate._id, BaseClasses.AMMO_BOX)) {
|
||||
this.itemHelper.addCartridgesToAmmoBox(itemToAddChildrenTo, itemToAddTemplate);
|
||||
}
|
||||
// Make money a stack
|
||||
else if (this.itemHelper.isOfBaseclass(itemToAddTemplate._id, BaseClasses.MONEY))
|
||||
{
|
||||
else if (this.itemHelper.isOfBaseclass(itemToAddTemplate._id, BaseClasses.MONEY)) {
|
||||
this.randomiseMoneyStackSize(botRole, itemToAddTemplate, itemToAddChildrenTo[0]);
|
||||
}
|
||||
// Make ammo a stack
|
||||
else if (this.itemHelper.isOfBaseclass(itemToAddTemplate._id, BaseClasses.AMMO))
|
||||
{
|
||||
else if (this.itemHelper.isOfBaseclass(itemToAddTemplate._id, BaseClasses.AMMO)) {
|
||||
this.randomiseAmmoStackSize(isPmc, itemToAddTemplate, itemToAddChildrenTo[0]);
|
||||
}
|
||||
// Must add soft inserts/plates
|
||||
else if (this.itemHelper.itemRequiresSoftInserts(itemToAddTemplate._id))
|
||||
{
|
||||
else if (this.itemHelper.itemRequiresSoftInserts(itemToAddTemplate._id)) {
|
||||
this.itemHelper.addChildSlotItems(itemToAddChildrenTo, itemToAddTemplate, undefined, false);
|
||||
}
|
||||
}
|
||||
@ -649,8 +606,7 @@ export class BotLootGenerator
|
||||
isPmc: boolean,
|
||||
botLevel: number,
|
||||
containersIdFull?: Set<string>,
|
||||
): void
|
||||
{
|
||||
): void {
|
||||
const chosenWeaponType = this.randomUtil.getArrayValue([
|
||||
EquipmentSlots.FIRST_PRIMARY_WEAPON,
|
||||
EquipmentSlots.FIRST_PRIMARY_WEAPON,
|
||||
@ -661,10 +617,8 @@ export class BotLootGenerator
|
||||
this.pmcConfig.looseWeaponInBackpackLootMinMax.min,
|
||||
this.pmcConfig.looseWeaponInBackpackLootMinMax.max,
|
||||
);
|
||||
if (randomisedWeaponCount > 0)
|
||||
{
|
||||
for (let i = 0; i < randomisedWeaponCount; i++)
|
||||
{
|
||||
if (randomisedWeaponCount > 0) {
|
||||
for (let i = 0; i < randomisedWeaponCount; i++) {
|
||||
const generatedWeapon = this.botWeaponGenerator.generateRandomWeapon(
|
||||
sessionId,
|
||||
chosenWeaponType,
|
||||
@ -684,8 +638,7 @@ export class BotLootGenerator
|
||||
containersIdFull,
|
||||
);
|
||||
|
||||
if (result !== ItemAddedResult.SUCCESS)
|
||||
{
|
||||
if (result !== ItemAddedResult.SUCCESS) {
|
||||
this.logger.debug(
|
||||
`Failed to add additional weapon ${generatedWeapon.weapon[0]._id} to bot backpack, reason: ${ItemAddedResult[result]}`,
|
||||
);
|
||||
@ -700,12 +653,10 @@ export class BotLootGenerator
|
||||
* @param botRole Role the bot has
|
||||
* @param limitCount
|
||||
*/
|
||||
protected initItemLimitArray(botRole: string, limitCount: Record<string, number>): void
|
||||
{
|
||||
protected initItemLimitArray(botRole: string, limitCount: Record<string, number>): void {
|
||||
// Init current count of items we want to limit
|
||||
const spawnLimits = this.getItemSpawnLimitsForBotType(botRole);
|
||||
for (const limit in spawnLimits)
|
||||
{
|
||||
for (const limit in spawnLimits) {
|
||||
limitCount[limit] = 0;
|
||||
}
|
||||
}
|
||||
@ -721,24 +672,20 @@ export class BotLootGenerator
|
||||
itemTemplate: ITemplateItem,
|
||||
botRole: string,
|
||||
itemSpawnLimits: IItemSpawnLimitSettings,
|
||||
): boolean
|
||||
{
|
||||
): boolean {
|
||||
// PMCs and scavs have different sections of bot config for spawn limits
|
||||
if (!!itemSpawnLimits && Object.keys(itemSpawnLimits.globalLimits).length === 0)
|
||||
{
|
||||
if (!!itemSpawnLimits && Object.keys(itemSpawnLimits.globalLimits).length === 0) {
|
||||
// No items found in spawn limit, drop out
|
||||
return false;
|
||||
}
|
||||
|
||||
// No spawn limits, skipping
|
||||
if (!itemSpawnLimits)
|
||||
{
|
||||
if (!itemSpawnLimits) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const idToCheckFor = this.getMatchingIdFromSpawnLimits(itemTemplate, itemSpawnLimits.globalLimits);
|
||||
if (!idToCheckFor)
|
||||
{
|
||||
if (!idToCheckFor) {
|
||||
// ParentId or tplid not found in spawnLimits, not a spawn limited item, skip
|
||||
return false;
|
||||
}
|
||||
@ -747,11 +694,9 @@ export class BotLootGenerator
|
||||
itemSpawnLimits.currentLimits[idToCheckFor]++;
|
||||
|
||||
// Check if over limit
|
||||
if (itemSpawnLimits.currentLimits[idToCheckFor] > itemSpawnLimits.globalLimits[idToCheckFor])
|
||||
{
|
||||
if (itemSpawnLimits.currentLimits[idToCheckFor] > itemSpawnLimits.globalLimits[idToCheckFor]) {
|
||||
// Prevent edge-case of small loot pools + code trying to add limited item over and over infinitely
|
||||
if (itemSpawnLimits.currentLimits[idToCheckFor] > itemSpawnLimits[idToCheckFor] * 10)
|
||||
{
|
||||
if (itemSpawnLimits.currentLimits[idToCheckFor] > itemSpawnLimits[idToCheckFor] * 10) {
|
||||
this.logger.debug(
|
||||
this.localisationService.getText("bot-item_spawn_limit_reached_skipping_item", {
|
||||
botRole: botRole,
|
||||
@ -775,12 +720,10 @@ export class BotLootGenerator
|
||||
* @param itemTemplate item details from db
|
||||
* @param moneyItem Money item to randomise
|
||||
*/
|
||||
protected randomiseMoneyStackSize(botRole: string, itemTemplate: ITemplateItem, moneyItem: Item): void
|
||||
{
|
||||
protected randomiseMoneyStackSize(botRole: string, itemTemplate: ITemplateItem, moneyItem: Item): void {
|
||||
// Get all currency weights for this bot type
|
||||
let currencyWeights = this.botConfig.currencyStackSize[botRole];
|
||||
if (!currencyWeights)
|
||||
{
|
||||
if (!currencyWeights) {
|
||||
currencyWeights = this.botConfig.currencyStackSize.default;
|
||||
}
|
||||
|
||||
@ -797,10 +740,9 @@ export class BotLootGenerator
|
||||
* @param itemTemplate item details from db
|
||||
* @param ammoItem Ammo item to randomise
|
||||
*/
|
||||
protected randomiseAmmoStackSize(isPmc: boolean, itemTemplate: ITemplateItem, ammoItem: Item): void
|
||||
{
|
||||
const randomSize
|
||||
= itemTemplate._props.StackMaxSize === 1
|
||||
protected randomiseAmmoStackSize(isPmc: boolean, itemTemplate: ITemplateItem, ammoItem: Item): void {
|
||||
const randomSize =
|
||||
itemTemplate._props.StackMaxSize === 1
|
||||
? 1
|
||||
: this.randomUtil.getInt(
|
||||
itemTemplate._props.StackMinRandom,
|
||||
@ -818,15 +760,12 @@ export class BotLootGenerator
|
||||
* @param botRole what role does the bot have
|
||||
* @returns Dictionary of tplIds and limit
|
||||
*/
|
||||
protected getItemSpawnLimitsForBotType(botRole: string): Record<string, number>
|
||||
{
|
||||
if (this.botHelper.isBotPmc(botRole))
|
||||
{
|
||||
protected getItemSpawnLimitsForBotType(botRole: string): Record<string, number> {
|
||||
if (this.botHelper.isBotPmc(botRole)) {
|
||||
return this.botConfig.itemSpawnLimits.pmc;
|
||||
}
|
||||
|
||||
if (this.botConfig.itemSpawnLimits[botRole.toLowerCase()])
|
||||
{
|
||||
if (this.botConfig.itemSpawnLimits[botRole.toLowerCase()]) {
|
||||
return this.botConfig.itemSpawnLimits[botRole.toLowerCase()];
|
||||
}
|
||||
|
||||
@ -843,16 +782,13 @@ export class BotLootGenerator
|
||||
* @param spawnLimits Limits to check for item
|
||||
* @returns id as string, otherwise undefined
|
||||
*/
|
||||
protected getMatchingIdFromSpawnLimits(itemTemplate: ITemplateItem, spawnLimits: Record<string, number>): string
|
||||
{
|
||||
if (itemTemplate._id in spawnLimits)
|
||||
{
|
||||
protected getMatchingIdFromSpawnLimits(itemTemplate: ITemplateItem, spawnLimits: Record<string, number>): string {
|
||||
if (itemTemplate._id in spawnLimits) {
|
||||
return itemTemplate._id;
|
||||
}
|
||||
|
||||
// tplId not found in spawnLimits, check if parentId is
|
||||
if (itemTemplate._parent in spawnLimits)
|
||||
{
|
||||
if (itemTemplate._parent in spawnLimits) {
|
||||
return itemTemplate._parent;
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { inject, injectAll, injectable } from "tsyringe";
|
||||
import { BotEquipmentModGenerator } from "@spt/generators/BotEquipmentModGenerator";
|
||||
import { IInventoryMagGen } from "@spt/generators/weapongen/IInventoryMagGen";
|
||||
import { InventoryMagGen } from "@spt/generators/weapongen/InventoryMagGen";
|
||||
@ -24,13 +23,13 @@ import { BotWeaponModLimitService } from "@spt/services/BotWeaponModLimitService
|
||||
import { DatabaseService } from "@spt/services/DatabaseService";
|
||||
import { LocalisationService } from "@spt/services/LocalisationService";
|
||||
import { RepairService } from "@spt/services/RepairService";
|
||||
import { ICloner } from "@spt/utils/cloners/ICloner";
|
||||
import { HashUtil } from "@spt/utils/HashUtil";
|
||||
import { RandomUtil } from "@spt/utils/RandomUtil";
|
||||
import { ICloner } from "@spt/utils/cloners/ICloner";
|
||||
import { inject, injectAll, injectable } from "tsyringe";
|
||||
|
||||
@injectable()
|
||||
export class BotWeaponGenerator
|
||||
{
|
||||
export class BotWeaponGenerator {
|
||||
protected readonly modMagazineSlotId = "mod_magazine";
|
||||
protected botConfig: IBotConfig;
|
||||
protected pmcConfig: IPmcConfig;
|
||||
@ -52,8 +51,7 @@ export class BotWeaponGenerator
|
||||
@inject("RepairService") protected repairService: RepairService,
|
||||
@injectAll("InventoryMagGen") protected inventoryMagGenComponents: IInventoryMagGen[],
|
||||
@inject("PrimaryCloner") protected cloner: ICloner,
|
||||
)
|
||||
{
|
||||
) {
|
||||
this.botConfig = this.configServer.getConfig(ConfigTypes.BOT);
|
||||
this.pmcConfig = this.configServer.getConfig(ConfigTypes.PMC);
|
||||
this.repairConfig = this.configServer.getConfig(ConfigTypes.REPAIR);
|
||||
@ -79,8 +77,7 @@ export class BotWeaponGenerator
|
||||
botRole: string,
|
||||
isPmc: boolean,
|
||||
botLevel: number,
|
||||
): GenerateWeaponResult
|
||||
{
|
||||
): GenerateWeaponResult {
|
||||
const weaponTpl = this.pickWeightedWeaponTplFromPool(equipmentSlot, botTemplateInventory);
|
||||
return this.generateWeaponByTpl(
|
||||
sessionId,
|
||||
@ -101,8 +98,7 @@ export class BotWeaponGenerator
|
||||
* @param botTemplateInventory e.g. assault.json
|
||||
* @returns weapon tpl
|
||||
*/
|
||||
public pickWeightedWeaponTplFromPool(equipmentSlot: string, botTemplateInventory: Inventory): string
|
||||
{
|
||||
public pickWeightedWeaponTplFromPool(equipmentSlot: string, botTemplateInventory: Inventory): string {
|
||||
const weaponPool = botTemplateInventory.equipment[equipmentSlot];
|
||||
return this.weightedRandomHelper.getWeightedValue<string>(weaponPool);
|
||||
}
|
||||
@ -128,13 +124,11 @@ export class BotWeaponGenerator
|
||||
botRole: string,
|
||||
isPmc: boolean,
|
||||
botLevel: number,
|
||||
): GenerateWeaponResult
|
||||
{
|
||||
): GenerateWeaponResult {
|
||||
const modPool = botTemplateInventory.mods;
|
||||
const weaponItemTemplate = this.itemHelper.getItem(weaponTpl)[1];
|
||||
|
||||
if (!weaponItemTemplate)
|
||||
{
|
||||
if (!weaponItemTemplate) {
|
||||
this.logger.error(this.localisationService.getText("bot-missing_item_template", weaponTpl));
|
||||
this.logger.error(`WeaponSlot -> ${equipmentSlot}`);
|
||||
|
||||
@ -142,8 +136,7 @@ export class BotWeaponGenerator
|
||||
}
|
||||
|
||||
// Find ammo to use when filling magazines/chamber
|
||||
if (!botTemplateInventory.Ammo)
|
||||
{
|
||||
if (!botTemplateInventory.Ammo) {
|
||||
this.logger.error(this.localisationService.getText("bot-no_ammo_found_in_bot_json", botRole));
|
||||
|
||||
throw new Error(this.localisationService.getText("bot-generation_failed"));
|
||||
@ -160,15 +153,13 @@ export class BotWeaponGenerator
|
||||
);
|
||||
|
||||
// Chance to add randomised weapon enhancement
|
||||
if (isPmc && this.randomUtil.getChance100(this.pmcConfig.weaponHasEnhancementChancePercent))
|
||||
{
|
||||
if (isPmc && this.randomUtil.getChance100(this.pmcConfig.weaponHasEnhancementChancePercent)) {
|
||||
const weaponConfig = this.repairConfig.repairKit.weapon;
|
||||
this.repairService.addBuff(weaponConfig, weaponWithModsArray[0]);
|
||||
}
|
||||
|
||||
// Add mods to weapon base
|
||||
if (Object.keys(modPool).includes(weaponTpl))
|
||||
{
|
||||
if (Object.keys(modPool).includes(weaponTpl)) {
|
||||
const botEquipmentRole = this.botGeneratorHelper.getBotEquipmentRole(botRole);
|
||||
const modLimits = this.botWeaponModLimitService.getWeaponModLimits(botEquipmentRole);
|
||||
|
||||
@ -190,8 +181,7 @@ export class BotWeaponGenerator
|
||||
}
|
||||
|
||||
// Use weapon preset from globals.json if weapon isnt valid
|
||||
if (!this.isWeaponValid(weaponWithModsArray, botRole))
|
||||
{
|
||||
if (!this.isWeaponValid(weaponWithModsArray, botRole)) {
|
||||
// Weapon is bad, fall back to weapons preset
|
||||
weaponWithModsArray = this.getPresetWeaponMods(
|
||||
weaponTpl,
|
||||
@ -203,17 +193,15 @@ export class BotWeaponGenerator
|
||||
}
|
||||
|
||||
// Fill existing magazines to full and sync ammo type
|
||||
for (const magazine of weaponWithModsArray.filter((item) => item.slotId === this.modMagazineSlotId))
|
||||
{
|
||||
for (const magazine of weaponWithModsArray.filter((item) => item.slotId === this.modMagazineSlotId)) {
|
||||
this.fillExistingMagazines(weaponWithModsArray, magazine, ammoTpl);
|
||||
}
|
||||
|
||||
// Add cartridge(s) to gun chamber(s)
|
||||
if (
|
||||
weaponItemTemplate._props.Chambers?.length > 0
|
||||
&& weaponItemTemplate._props.Chambers[0]?._props?.filters[0]?.Filter?.includes(ammoTpl)
|
||||
)
|
||||
{
|
||||
weaponItemTemplate._props.Chambers?.length > 0 &&
|
||||
weaponItemTemplate._props.Chambers[0]?._props?.filters[0]?.Filter?.includes(ammoTpl)
|
||||
) {
|
||||
// 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);
|
||||
@ -222,8 +210,7 @@ export class BotWeaponGenerator
|
||||
// Fill UBGL if found
|
||||
const ubglMod = weaponWithModsArray.find((x) => x.slotId === "mod_launcher");
|
||||
let ubglAmmoTpl: string = undefined;
|
||||
if (ubglMod)
|
||||
{
|
||||
if (ubglMod) {
|
||||
const ubglTemplate = this.itemHelper.getItem(ubglMod._tpl)[1];
|
||||
ubglAmmoTpl = this.getWeightedCompatibleAmmo(botTemplateInventory.Ammo, ubglTemplate);
|
||||
this.fillUbgl(weaponWithModsArray, ubglMod, ubglAmmoTpl);
|
||||
@ -245,13 +232,10 @@ export class BotWeaponGenerator
|
||||
* @param ammoTpl Cartridge to add to weapon
|
||||
* @param chamberSlotIds name of slots to create or add ammo to
|
||||
*/
|
||||
protected addCartridgeToChamber(weaponWithModsArray: Item[], ammoTpl: string, chamberSlotIds: string[]): void
|
||||
{
|
||||
for (const slotId of chamberSlotIds)
|
||||
{
|
||||
protected addCartridgeToChamber(weaponWithModsArray: Item[], ammoTpl: string, chamberSlotIds: string[]): void {
|
||||
for (const slotId of chamberSlotIds) {
|
||||
const existingItemWithSlot = weaponWithModsArray.find((x) => x.slotId === slotId);
|
||||
if (!existingItemWithSlot)
|
||||
{
|
||||
if (!existingItemWithSlot) {
|
||||
// Not found, add new slot to weapon
|
||||
weaponWithModsArray.push({
|
||||
_id: this.hashUtil.generate(),
|
||||
@ -260,9 +244,7 @@ export class BotWeaponGenerator
|
||||
slotId: slotId,
|
||||
upd: { StackObjectsCount: 1 },
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
// Already exists, update values
|
||||
existingItemWithSlot._tpl = ammoTpl;
|
||||
existingItemWithSlot.upd = { StackObjectsCount: 1 };
|
||||
@ -286,8 +268,7 @@ export class BotWeaponGenerator
|
||||
equipmentSlot: string,
|
||||
weaponItemTemplate: ITemplateItem,
|
||||
botRole: string,
|
||||
): Item[]
|
||||
{
|
||||
): Item[] {
|
||||
return [
|
||||
{
|
||||
_id: this.hashUtil.generate(),
|
||||
@ -312,8 +293,7 @@ export class BotWeaponGenerator
|
||||
weaponParentId: string,
|
||||
itemTemplate: ITemplateItem,
|
||||
botRole: string,
|
||||
): Item[]
|
||||
{
|
||||
): Item[] {
|
||||
// Invalid weapon generated, fallback to preset
|
||||
this.logger.warning(
|
||||
this.localisationService.getText(
|
||||
@ -325,17 +305,14 @@ export class BotWeaponGenerator
|
||||
|
||||
// TODO: Right now, preset weapons trigger a lot of warnings regarding missing ammo in magazines & such
|
||||
let preset: IPreset;
|
||||
for (const presetObj of Object.values(this.databaseService.getGlobals().ItemPresets))
|
||||
{
|
||||
if (presetObj._items[0]._tpl === weaponTpl)
|
||||
{
|
||||
for (const presetObj of Object.values(this.databaseService.getGlobals().ItemPresets)) {
|
||||
if (presetObj._items[0]._tpl === weaponTpl) {
|
||||
preset = this.cloner.clone(presetObj);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (preset)
|
||||
{
|
||||
if (preset) {
|
||||
const parentItem = preset._items[0];
|
||||
preset._items[0] = {
|
||||
...parentItem,
|
||||
@ -346,9 +323,7 @@ export class BotWeaponGenerator
|
||||
},
|
||||
};
|
||||
weaponMods.push(...preset._items);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
throw new Error(this.localisationService.getText("bot-missing_weapon_preset", weaponTpl));
|
||||
}
|
||||
|
||||
@ -361,25 +336,20 @@ export class BotWeaponGenerator
|
||||
* @param botRole role of bot weapon is for
|
||||
* @returns true if valid
|
||||
*/
|
||||
protected isWeaponValid(weaponItemArray: Item[], botRole: string): boolean
|
||||
{
|
||||
for (const mod of weaponItemArray)
|
||||
{
|
||||
protected isWeaponValid(weaponItemArray: Item[], botRole: string): boolean {
|
||||
for (const mod of weaponItemArray) {
|
||||
const modTemplate = this.itemHelper.getItem(mod._tpl)[1];
|
||||
if (!modTemplate._props.Slots?.length)
|
||||
{
|
||||
if (!modTemplate._props.Slots?.length) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Iterate over required slots in db item, check mod exists for that slot
|
||||
for (const modSlotTemplate of modTemplate._props.Slots.filter((slot) => slot._required))
|
||||
{
|
||||
for (const modSlotTemplate of modTemplate._props.Slots.filter((slot) => slot._required)) {
|
||||
const slotName = modSlotTemplate._name;
|
||||
const hasWeaponSlotItem = weaponItemArray.some(
|
||||
(weaponItem) => weaponItem.parentId === mod._id && weaponItem.slotId === slotName,
|
||||
);
|
||||
if (!hasWeaponSlotItem)
|
||||
{
|
||||
if (!hasWeaponSlotItem) {
|
||||
this.logger.warning(
|
||||
this.localisationService.getText("bot-weapons_required_slot_missing_item", {
|
||||
modSlot: modSlotTemplate._name,
|
||||
@ -410,23 +380,20 @@ export class BotWeaponGenerator
|
||||
magWeights: GenerationData,
|
||||
inventory: PmcInventory,
|
||||
botRole: string,
|
||||
): void
|
||||
{
|
||||
): void {
|
||||
const weaponAndMods = generatedWeaponResult.weapon;
|
||||
const weaponTemplate = generatedWeaponResult.weaponTemplate;
|
||||
const magazineTpl = this.getMagazineTplFromWeaponTemplate(weaponAndMods, weaponTemplate, botRole);
|
||||
|
||||
const magTemplate = this.itemHelper.getItem(magazineTpl)[1];
|
||||
if (!magTemplate)
|
||||
{
|
||||
if (!magTemplate) {
|
||||
this.logger.error(this.localisationService.getText("bot-unable_to_find_magazine_item", magazineTpl));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const ammoTemplate = this.itemHelper.getItem(generatedWeaponResult.chosenAmmoTpl)[1];
|
||||
if (!ammoTemplate)
|
||||
{
|
||||
if (!ammoTemplate) {
|
||||
this.logger.error(
|
||||
this.localisationService.getText("bot-unable_to_find_ammo_item", generatedWeaponResult.chosenAmmoTpl),
|
||||
);
|
||||
@ -435,8 +402,7 @@ export class BotWeaponGenerator
|
||||
}
|
||||
|
||||
// Has an UBGL
|
||||
if (generatedWeaponResult.chosenUbglAmmoTpl)
|
||||
{
|
||||
if (generatedWeaponResult.chosenUbglAmmoTpl) {
|
||||
this.addUbglGrenadesToBotInventory(weaponAndMods, generatedWeaponResult, inventory);
|
||||
}
|
||||
|
||||
@ -470,8 +436,7 @@ export class BotWeaponGenerator
|
||||
weaponMods: Item[],
|
||||
generatedWeaponResult: GenerateWeaponResult,
|
||||
inventory: PmcInventory,
|
||||
): void
|
||||
{
|
||||
): void {
|
||||
// Find ubgl mod item + get details of it from db
|
||||
const ubglMod = weaponMods.find((x) => x.slotId === "mod_launcher");
|
||||
const ubglDbTemplate = this.itemHelper.getItem(ubglMod._tpl)[1];
|
||||
@ -513,10 +478,8 @@ export class BotWeaponGenerator
|
||||
ammoTpl: string,
|
||||
stackSize: number,
|
||||
inventory: PmcInventory,
|
||||
): void
|
||||
{
|
||||
for (let i = 0; i < stackCount; i++)
|
||||
{
|
||||
): void {
|
||||
for (let i = 0; i < stackCount; i++) {
|
||||
const id = this.hashUtil.generate();
|
||||
this.botGeneratorHelper.addItemWithChildrenToEquipmentSlot(
|
||||
[EquipmentSlots.SECURED_CONTAINER],
|
||||
@ -539,21 +502,17 @@ export class BotWeaponGenerator
|
||||
weaponMods: Item[],
|
||||
weaponTemplate: ITemplateItem,
|
||||
botRole: string,
|
||||
): string
|
||||
{
|
||||
): string {
|
||||
const magazine = weaponMods.find((m) => m.slotId === this.modMagazineSlotId);
|
||||
if (!magazine)
|
||||
{
|
||||
if (!magazine) {
|
||||
// Edge case - magazineless chamber loaded weapons dont have magazines, e.g. mp18
|
||||
// return default mag tpl
|
||||
if (weaponTemplate._props.ReloadMode === "OnlyBarrel")
|
||||
{
|
||||
if (weaponTemplate._props.ReloadMode === "OnlyBarrel") {
|
||||
return this.botWeaponGeneratorHelper.getWeaponsDefaultMagazineTpl(weaponTemplate);
|
||||
}
|
||||
|
||||
// log error if no magazine AND not a chamber loaded weapon (e.g. shotgun revolver)
|
||||
if (!weaponTemplate._props.isChamberLoad)
|
||||
{
|
||||
if (!weaponTemplate._props.isChamberLoad) {
|
||||
// Shouldn't happen
|
||||
this.logger.warning(
|
||||
this.localisationService.getText("bot-weapon_missing_magazine_or_chamber", {
|
||||
@ -583,13 +542,11 @@ export class BotWeaponGenerator
|
||||
protected getWeightedCompatibleAmmo(
|
||||
ammo: Record<string, Record<string, number>>,
|
||||
weaponTemplate: ITemplateItem,
|
||||
): string
|
||||
{
|
||||
): string {
|
||||
const desiredCaliber = this.getWeaponCaliber(weaponTemplate);
|
||||
|
||||
const compatibleCartridges = this.cloner.clone(ammo[desiredCaliber]);
|
||||
if (!compatibleCartridges || compatibleCartridges?.length === 0)
|
||||
{
|
||||
if (!compatibleCartridges || compatibleCartridges?.length === 0) {
|
||||
this.logger.debug(
|
||||
this.localisationService.getText("bot-no_caliber_data_for_weapon_falling_back_to_default", {
|
||||
weaponId: weaponTemplate._id,
|
||||
@ -603,19 +560,16 @@ export class BotWeaponGenerator
|
||||
}
|
||||
|
||||
let chosenAmmoTpl: string;
|
||||
while (!chosenAmmoTpl)
|
||||
{
|
||||
while (!chosenAmmoTpl) {
|
||||
const possibleAmmo = this.weightedRandomHelper.getWeightedValue<string>(compatibleCartridges);
|
||||
|
||||
// Weapon has chamber but does not support cartridge
|
||||
if (
|
||||
weaponTemplate._props.Chambers[0]
|
||||
&& !weaponTemplate._props.Chambers[0]._props.filters[0].Filter.includes(possibleAmmo)
|
||||
)
|
||||
{
|
||||
weaponTemplate._props.Chambers[0] &&
|
||||
!weaponTemplate._props.Chambers[0]._props.filters[0].Filter.includes(possibleAmmo)
|
||||
) {
|
||||
// Ran out of possible choices, use default ammo
|
||||
if (Object.keys(compatibleCartridges).length === 0)
|
||||
{
|
||||
if (Object.keys(compatibleCartridges).length === 0) {
|
||||
this.logger.debug(
|
||||
this.localisationService.getText("bot-incompatible_ammo_for_weapon_falling_back_to_default", {
|
||||
chosenAmmo: chosenAmmoTpl,
|
||||
@ -632,9 +586,7 @@ export class BotWeaponGenerator
|
||||
|
||||
// Not compatible, remove item from possible list and try again
|
||||
delete compatibleCartridges[possibleAmmo];
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
// Compatible ammo found
|
||||
chosenAmmoTpl = possibleAmmo;
|
||||
break;
|
||||
@ -649,28 +601,23 @@ export class BotWeaponGenerator
|
||||
* @param weaponTemplate Weapon to look up caliber of
|
||||
* @returns caliber as string
|
||||
*/
|
||||
protected getWeaponCaliber(weaponTemplate: ITemplateItem): string
|
||||
{
|
||||
if (weaponTemplate._props.Caliber)
|
||||
{
|
||||
protected getWeaponCaliber(weaponTemplate: ITemplateItem): string {
|
||||
if (weaponTemplate._props.Caliber) {
|
||||
return weaponTemplate._props.Caliber;
|
||||
}
|
||||
|
||||
if (weaponTemplate._props.ammoCaliber)
|
||||
{
|
||||
if (weaponTemplate._props.ammoCaliber) {
|
||||
// 9x18pmm has a typo, should be Caliber9x18PM
|
||||
return weaponTemplate._props.ammoCaliber === "Caliber9x18PMM"
|
||||
? "Caliber9x18PM"
|
||||
: weaponTemplate._props.ammoCaliber;
|
||||
}
|
||||
|
||||
if (weaponTemplate._props.LinkedWeapon)
|
||||
{
|
||||
if (weaponTemplate._props.LinkedWeapon) {
|
||||
const ammoInChamber = this.itemHelper.getItem(
|
||||
weaponTemplate._props.Chambers[0]._props.filters[0].Filter[0],
|
||||
);
|
||||
if (!ammoInChamber[0])
|
||||
{
|
||||
if (!ammoInChamber[0]) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -684,11 +631,9 @@ export class BotWeaponGenerator
|
||||
* @param magazine Magazine item
|
||||
* @param cartridgeTpl Cartridge to insert into magazine
|
||||
*/
|
||||
protected fillExistingMagazines(weaponMods: Item[], magazine: Item, cartridgeTpl: string): void
|
||||
{
|
||||
protected fillExistingMagazines(weaponMods: Item[], magazine: Item, cartridgeTpl: string): void {
|
||||
const magazineTemplate = this.itemHelper.getItem(magazine._tpl)[1];
|
||||
if (!magazineTemplate)
|
||||
{
|
||||
if (!magazineTemplate) {
|
||||
this.logger.error(this.localisationService.getText("bot-unable_to_find_magazine_item", magazine._tpl));
|
||||
|
||||
return;
|
||||
@ -699,12 +644,9 @@ export class BotWeaponGenerator
|
||||
// the revolver shotgun uses a magazine with chambers, not cartridges ("camora_xxx")
|
||||
// Exchange of the camora ammo is not necessary we could also just check for stackSize > 0 here
|
||||
// and remove the else
|
||||
if (this.botWeaponGeneratorHelper.magazineIsCylinderRelated(parentItem._name))
|
||||
{
|
||||
if (this.botWeaponGeneratorHelper.magazineIsCylinderRelated(parentItem._name)) {
|
||||
this.fillCamorasWithAmmo(weaponMods, magazine._id, cartridgeTpl);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
this.addOrUpdateMagazinesChildWithAmmo(weaponMods, magazine, cartridgeTpl, magazineTemplate);
|
||||
}
|
||||
}
|
||||
@ -715,8 +657,7 @@ export class BotWeaponGenerator
|
||||
* @param ubglMod UBGL item
|
||||
* @param ubglAmmoTpl Grenade ammo tpl
|
||||
*/
|
||||
protected fillUbgl(weaponMods: Item[], ubglMod: Item, ubglAmmoTpl: string): void
|
||||
{
|
||||
protected fillUbgl(weaponMods: Item[], ubglMod: Item, ubglAmmoTpl: string): void {
|
||||
weaponMods.push({
|
||||
_id: this.hashUtil.generate(),
|
||||
_tpl: ubglAmmoTpl,
|
||||
@ -739,13 +680,11 @@ export class BotWeaponGenerator
|
||||
magazine: Item,
|
||||
chosenAmmoTpl: string,
|
||||
magazineTemplate: ITemplateItem,
|
||||
): void
|
||||
{
|
||||
): void {
|
||||
const magazineCartridgeChildItem = weaponWithMods.find(
|
||||
(m) => m.parentId === magazine._id && m.slotId === "cartridges",
|
||||
);
|
||||
if (magazineCartridgeChildItem)
|
||||
{
|
||||
if (magazineCartridgeChildItem) {
|
||||
// Delete the existing cartridge object and create fresh below
|
||||
weaponWithMods.splice(weaponWithMods.indexOf(magazineCartridgeChildItem), 1);
|
||||
}
|
||||
@ -766,21 +705,16 @@ export class BotWeaponGenerator
|
||||
* @param magazineId magazine id to find and add to
|
||||
* @param ammoTpl ammo template id to hydate with
|
||||
*/
|
||||
protected fillCamorasWithAmmo(weaponMods: Item[], magazineId: string, ammoTpl: string): void
|
||||
{
|
||||
protected fillCamorasWithAmmo(weaponMods: Item[], magazineId: string, ammoTpl: string): void {
|
||||
// for CylinderMagazine we exchange the ammo in the "camoras".
|
||||
// This might not be necessary since we already filled the camoras with a random whitelisted and compatible ammo type,
|
||||
// but I'm not sure whether this is also used elsewhere
|
||||
const camoras = weaponMods.filter((x) => x.parentId === magazineId && x.slotId.startsWith("camora"));
|
||||
for (const camora of camoras)
|
||||
{
|
||||
for (const camora of camoras) {
|
||||
camora._tpl = ammoTpl;
|
||||
if (camora.upd)
|
||||
{
|
||||
if (camora.upd) {
|
||||
camora.upd.StackObjectsCount = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
camora.upd = { StackObjectsCount: 1 };
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { inject, injectable } from "tsyringe";
|
||||
import { HandbookHelper } from "@spt/helpers/HandbookHelper";
|
||||
import { ItemHelper } from "@spt/helpers/ItemHelper";
|
||||
import { PresetHelper } from "@spt/helpers/PresetHelper";
|
||||
@ -18,10 +17,10 @@ import { ItemFilterService } from "@spt/services/ItemFilterService";
|
||||
import { LocalisationService } from "@spt/services/LocalisationService";
|
||||
import { SeasonalEventService } from "@spt/services/SeasonalEventService";
|
||||
import { HashUtil } from "@spt/utils/HashUtil";
|
||||
import { inject, injectable } from "tsyringe";
|
||||
|
||||
@injectable()
|
||||
export class FenceBaseAssortGenerator
|
||||
{
|
||||
export class FenceBaseAssortGenerator {
|
||||
protected traderConfig: ITraderConfig;
|
||||
|
||||
constructor(
|
||||
@ -36,54 +35,45 @@ export class FenceBaseAssortGenerator
|
||||
@inject("LocalisationService") protected localisationService: LocalisationService,
|
||||
@inject("ConfigServer") protected configServer: ConfigServer,
|
||||
@inject("FenceService") protected fenceService: FenceService,
|
||||
)
|
||||
{
|
||||
) {
|
||||
this.traderConfig = this.configServer.getConfig(ConfigTypes.TRADER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create base fence assorts dynamically and store in memory
|
||||
*/
|
||||
public generateFenceBaseAssorts(): void
|
||||
{
|
||||
public generateFenceBaseAssorts(): void {
|
||||
const blockedSeasonalItems = this.seasonalEventService.getInactiveSeasonalEventItems();
|
||||
const baseFenceAssort = this.databaseService.getTrader(Traders.FENCE).assort;
|
||||
|
||||
for (const rootItemDb of this.itemHelper.getItems().filter((item) => this.isValidFenceItem(item)))
|
||||
{
|
||||
for (const rootItemDb of this.itemHelper.getItems().filter((item) => this.isValidFenceItem(item))) {
|
||||
// Skip blacklisted items
|
||||
if (this.itemFilterService.isItemBlacklisted(rootItemDb._id))
|
||||
{
|
||||
if (this.itemFilterService.isItemBlacklisted(rootItemDb._id)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Invalid
|
||||
if (!this.itemHelper.isValidItem(rootItemDb._id))
|
||||
{
|
||||
if (!this.itemHelper.isValidItem(rootItemDb._id)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Item base type blacklisted
|
||||
if (this.traderConfig.fence.blacklist.length > 0)
|
||||
{
|
||||
if (this.traderConfig.fence.blacklist.length > 0) {
|
||||
if (
|
||||
this.traderConfig.fence.blacklist.includes(rootItemDb._id)
|
||||
|| this.itemHelper.isOfBaseclasses(rootItemDb._id, this.traderConfig.fence.blacklist)
|
||||
)
|
||||
{
|
||||
this.traderConfig.fence.blacklist.includes(rootItemDb._id) ||
|
||||
this.itemHelper.isOfBaseclasses(rootItemDb._id, this.traderConfig.fence.blacklist)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Only allow rigs with no slots (carrier rigs)
|
||||
if (this.itemHelper.isOfBaseclass(rootItemDb._id, BaseClasses.VEST) && rootItemDb._props.Slots.length > 0)
|
||||
{
|
||||
if (this.itemHelper.isOfBaseclass(rootItemDb._id, BaseClasses.VEST) && rootItemDb._props.Slots.length > 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip seasonal event items when not in seasonal event
|
||||
if (this.traderConfig.fence.blacklistSeasonalItems && blockedSeasonalItems.includes(rootItemDb._id))
|
||||
{
|
||||
if (this.traderConfig.fence.blacklistSeasonalItems && blockedSeasonalItems.includes(rootItemDb._id)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -99,27 +89,22 @@ export class FenceBaseAssortGenerator
|
||||
];
|
||||
|
||||
// Ensure ammo is not above penetration limit value
|
||||
if (this.itemHelper.isOfBaseclasses(rootItemDb._id, [BaseClasses.AMMO_BOX, BaseClasses.AMMO]))
|
||||
{
|
||||
if (this.isAmmoAbovePenetrationLimit(rootItemDb))
|
||||
{
|
||||
if (this.itemHelper.isOfBaseclasses(rootItemDb._id, [BaseClasses.AMMO_BOX, BaseClasses.AMMO])) {
|
||||
if (this.isAmmoAbovePenetrationLimit(rootItemDb)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.itemHelper.isOfBaseclass(rootItemDb._id, BaseClasses.AMMO_BOX))
|
||||
{
|
||||
if (this.itemHelper.isOfBaseclass(rootItemDb._id, BaseClasses.AMMO_BOX)) {
|
||||
// Only add cartridges to box if box has no children
|
||||
if (itemWithChildrenToAdd.length === 1)
|
||||
{
|
||||
if (itemWithChildrenToAdd.length === 1) {
|
||||
this.itemHelper.addCartridgesToAmmoBox(itemWithChildrenToAdd, rootItemDb);
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure IDs are unique
|
||||
this.itemHelper.remapRootItemId(itemWithChildrenToAdd);
|
||||
if (itemWithChildrenToAdd.length > 1)
|
||||
{
|
||||
if (itemWithChildrenToAdd.length > 1) {
|
||||
this.itemHelper.reparentItemAndChildren(itemWithChildrenToAdd[0], itemWithChildrenToAdd);
|
||||
itemWithChildrenToAdd[0].parentId = "hideout";
|
||||
}
|
||||
@ -142,11 +127,9 @@ export class FenceBaseAssortGenerator
|
||||
|
||||
// Add all default presets to base fence assort
|
||||
const defaultPresets = Object.values(this.presetHelper.getDefaultPresets());
|
||||
for (const defaultPreset of defaultPresets)
|
||||
{
|
||||
for (const defaultPreset of defaultPresets) {
|
||||
// Skip presets we've already added
|
||||
if (baseFenceAssort.items.some((item) => item.upd && item.upd.sptPresetId === defaultPreset._id))
|
||||
{
|
||||
if (baseFenceAssort.items.some((item) => item.upd && item.upd.sptPresetId === defaultPreset._id)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -154,13 +137,11 @@ export class FenceBaseAssortGenerator
|
||||
const itemAndChildren: Item[] = this.itemHelper.replaceIDs(defaultPreset._items);
|
||||
|
||||
// Find root item and add some properties to it
|
||||
for (let i = 0; i < itemAndChildren.length; i++)
|
||||
{
|
||||
for (let i = 0; i < itemAndChildren.length; i++) {
|
||||
const mod = itemAndChildren[i];
|
||||
|
||||
// Build root Item info
|
||||
if (!("parentId" in mod))
|
||||
{
|
||||
if (!("parentId" in mod)) {
|
||||
mod.parentId = "hideout";
|
||||
mod.slotId = "hideout";
|
||||
mod.upd = {
|
||||
@ -196,12 +177,12 @@ export class FenceBaseAssortGenerator
|
||||
* @param rootItemDb Ammo box or ammo item from items.db
|
||||
* @returns True if penetration value is above limit set in config
|
||||
*/
|
||||
protected isAmmoAbovePenetrationLimit(rootItemDb: ITemplateItem): boolean
|
||||
{
|
||||
protected isAmmoAbovePenetrationLimit(rootItemDb: ITemplateItem): boolean {
|
||||
const ammoPenetrationPower = this.getAmmoPenetrationPower(rootItemDb);
|
||||
if (ammoPenetrationPower === undefined)
|
||||
{
|
||||
this.logger.warning(this.localisationService.getText("fence-unable_to_get_ammo_penetration_value", rootItemDb._id));
|
||||
if (ammoPenetrationPower === undefined) {
|
||||
this.logger.warning(
|
||||
this.localisationService.getText("fence-unable_to_get_ammo_penetration_value", rootItemDb._id),
|
||||
);
|
||||
|
||||
return false;
|
||||
}
|
||||
@ -214,17 +195,14 @@ export class FenceBaseAssortGenerator
|
||||
* @param rootItemDb Ammo box or ammo item from items.db
|
||||
* @returns Penetration power of passed in item, undefined if it doesnt have a power
|
||||
*/
|
||||
protected getAmmoPenetrationPower(rootItemDb: ITemplateItem): number | undefined
|
||||
{
|
||||
if (this.itemHelper.isOfBaseclass(rootItemDb._id, BaseClasses.AMMO_BOX))
|
||||
{
|
||||
protected getAmmoPenetrationPower(rootItemDb: ITemplateItem): number | undefined {
|
||||
if (this.itemHelper.isOfBaseclass(rootItemDb._id, BaseClasses.AMMO_BOX)) {
|
||||
// Get the cartridge tpl found inside ammo box
|
||||
const cartridgeTplInBox = rootItemDb._props.StackSlots[0]._props.filters[0].Filter[0];
|
||||
|
||||
// Look up cartridge tpl in db
|
||||
const ammoItemDb = this.itemHelper.getItem(cartridgeTplInBox);
|
||||
if (!ammoItemDb[0])
|
||||
{
|
||||
if (!ammoItemDb[0]) {
|
||||
this.logger.warning(this.localisationService.getText("fence-ammo_not_found_in_db", cartridgeTplInBox));
|
||||
|
||||
return undefined;
|
||||
@ -234,8 +212,7 @@ export class FenceBaseAssortGenerator
|
||||
}
|
||||
|
||||
// Plain old ammo, get its pen property
|
||||
if (this.itemHelper.isOfBaseclass(rootItemDb._id, BaseClasses.AMMO))
|
||||
{
|
||||
if (this.itemHelper.isOfBaseclass(rootItemDb._id, BaseClasses.AMMO)) {
|
||||
return rootItemDb._props.PenetrationPower;
|
||||
}
|
||||
|
||||
@ -248,26 +225,21 @@ export class FenceBaseAssortGenerator
|
||||
* @param armor Armor item array to add mods into
|
||||
* @param itemDbDetails Armor items db template
|
||||
*/
|
||||
protected addChildrenToArmorModSlots(armor: Item[], itemDbDetails: ITemplateItem): void
|
||||
{
|
||||
protected addChildrenToArmorModSlots(armor: Item[], itemDbDetails: ITemplateItem): void {
|
||||
// Armor has no mods, make no additions
|
||||
const hasMods = itemDbDetails._props.Slots.length > 0;
|
||||
if (!hasMods)
|
||||
{
|
||||
if (!hasMods) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for and add required soft inserts to armors
|
||||
const requiredSlots = itemDbDetails._props.Slots.filter((slot) => slot._required);
|
||||
const hasRequiredSlots = requiredSlots.length > 0;
|
||||
if (hasRequiredSlots)
|
||||
{
|
||||
for (const requiredSlot of requiredSlots)
|
||||
{
|
||||
if (hasRequiredSlots) {
|
||||
for (const requiredSlot of requiredSlots) {
|
||||
const modItemDbDetails = this.itemHelper.getItem(requiredSlot._props.filters[0].Plate)[1];
|
||||
const plateTpl = requiredSlot._props.filters[0].Plate; // `Plate` property appears to be the 'default' item for slot
|
||||
if (plateTpl === "")
|
||||
{
|
||||
if (plateTpl === "") {
|
||||
// Some bsg plate properties are empty, skip mod
|
||||
continue;
|
||||
}
|
||||
@ -293,13 +265,10 @@ export class FenceBaseAssortGenerator
|
||||
const plateSlots = itemDbDetails._props.Slots.filter((slot) =>
|
||||
this.itemHelper.isRemovablePlateSlot(slot._name),
|
||||
);
|
||||
if (plateSlots.length > 0)
|
||||
{
|
||||
for (const plateSlot of plateSlots)
|
||||
{
|
||||
if (plateSlots.length > 0) {
|
||||
for (const plateSlot of plateSlots) {
|
||||
const plateTpl = plateSlot._props.filters[0].Plate;
|
||||
if (!plateTpl)
|
||||
{
|
||||
if (!plateTpl) {
|
||||
// Bsg data lacks a default plate, skip adding mod
|
||||
continue;
|
||||
}
|
||||
@ -325,10 +294,8 @@ export class FenceBaseAssortGenerator
|
||||
* @param item Item to check
|
||||
* @returns true if valid fence item
|
||||
*/
|
||||
protected isValidFenceItem(item: ITemplateItem): boolean
|
||||
{
|
||||
if (item._type === "Item")
|
||||
{
|
||||
protected isValidFenceItem(item: ITemplateItem): boolean {
|
||||
if (item._type === "Item") {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { inject, injectable } from "tsyringe";
|
||||
import { ContainerHelper } from "@spt/helpers/ContainerHelper";
|
||||
import { ItemHelper } from "@spt/helpers/ItemHelper";
|
||||
import { PresetHelper } from "@spt/helpers/PresetHelper";
|
||||
@ -22,29 +21,27 @@ import { DatabaseService } from "@spt/services/DatabaseService";
|
||||
import { ItemFilterService } from "@spt/services/ItemFilterService";
|
||||
import { LocalisationService } from "@spt/services/LocalisationService";
|
||||
import { SeasonalEventService } from "@spt/services/SeasonalEventService";
|
||||
import { ICloner } from "@spt/utils/cloners/ICloner";
|
||||
import { MathUtil } from "@spt/utils/MathUtil";
|
||||
import { ObjectId } from "@spt/utils/ObjectId";
|
||||
import { ProbabilityObject, ProbabilityObjectArray, RandomUtil } from "@spt/utils/RandomUtil";
|
||||
import { ICloner } from "@spt/utils/cloners/ICloner";
|
||||
import { inject, injectable } from "tsyringe";
|
||||
|
||||
export interface IContainerItem
|
||||
{
|
||||
items: Item[]
|
||||
width: number
|
||||
height: number
|
||||
export interface IContainerItem {
|
||||
items: Item[];
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
export interface IContainerGroupCount
|
||||
{
|
||||
export interface IContainerGroupCount {
|
||||
/** Containers this group has + probabilty to spawn */
|
||||
containerIdsWithProbability: Record<string, number>
|
||||
containerIdsWithProbability: Record<string, number>;
|
||||
/** How many containers the map should spawn with this group id */
|
||||
chosenCount: number
|
||||
chosenCount: number;
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class LocationLootGenerator
|
||||
{
|
||||
export class LocationLootGenerator {
|
||||
protected locationConfig: ILocationConfig;
|
||||
|
||||
constructor(
|
||||
@ -61,8 +58,7 @@ export class LocationLootGenerator
|
||||
@inject("ItemFilterService") protected itemFilterService: ItemFilterService,
|
||||
@inject("ConfigServer") protected configServer: ConfigServer,
|
||||
@inject("PrimaryCloner") protected cloner: ICloner,
|
||||
)
|
||||
{
|
||||
) {
|
||||
this.locationConfig = this.configServer.getConfig(ConfigTypes.LOCATION);
|
||||
}
|
||||
|
||||
@ -75,8 +71,7 @@ export class LocationLootGenerator
|
||||
public generateStaticContainers(
|
||||
locationBase: ILocationBase,
|
||||
staticAmmoDist: Record<string, IStaticAmmoDetails[]>,
|
||||
): SpawnpointTemplate[]
|
||||
{
|
||||
): SpawnpointTemplate[] {
|
||||
let staticLootItemCount = 0;
|
||||
const result: SpawnpointTemplate[] = [];
|
||||
const locationId = locationBase.Id.toLowerCase();
|
||||
@ -84,9 +79,10 @@ export class LocationLootGenerator
|
||||
const mapData = this.databaseService.getLocation(locationId);
|
||||
|
||||
const staticWeaponsOnMapClone = this.cloner.clone(mapData.staticContainers.staticWeapons);
|
||||
if (!staticWeaponsOnMapClone)
|
||||
{
|
||||
this.logger.error(this.localisationService.getText("location-unable_to_find_static_weapon_for_map", locationBase.Name));
|
||||
if (!staticWeaponsOnMapClone) {
|
||||
this.logger.error(
|
||||
this.localisationService.getText("location-unable_to_find_static_weapon_for_map", locationBase.Name),
|
||||
);
|
||||
}
|
||||
|
||||
// Add mounted weapons to output loot
|
||||
@ -94,18 +90,23 @@ export class LocationLootGenerator
|
||||
|
||||
const allStaticContainersOnMapClone = this.cloner.clone(mapData.staticContainers.staticContainers);
|
||||
|
||||
if (!allStaticContainersOnMapClone)
|
||||
{
|
||||
this.logger.error(this.localisationService.getText("location-unable_to_find_static_container_for_map", locationBase.Name));
|
||||
if (!allStaticContainersOnMapClone) {
|
||||
this.logger.error(
|
||||
this.localisationService.getText("location-unable_to_find_static_container_for_map", locationBase.Name),
|
||||
);
|
||||
}
|
||||
const staticRandomisableContainersOnMap = this.getRandomisableContainersOnMap(allStaticContainersOnMapClone);
|
||||
|
||||
// Containers that MUST be added to map (quest containers etc)
|
||||
const staticForcedOnMapClone = this.cloner.clone(mapData.staticContainers.staticForced);
|
||||
|
||||
if (!staticForcedOnMapClone)
|
||||
{
|
||||
this.logger.error(this.localisationService.getText("location-unable_to_find_forced_static_data_for_map", locationBase.Name));
|
||||
if (!staticForcedOnMapClone) {
|
||||
this.logger.error(
|
||||
this.localisationService.getText(
|
||||
"location-unable_to_find_forced_static_data_for_map",
|
||||
locationBase.Name,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Keep track of static loot count
|
||||
@ -117,8 +118,7 @@ export class LocationLootGenerator
|
||||
staticContainerCount += guaranteedContainers.length;
|
||||
|
||||
// Add loot to guaranteed containers and add to result
|
||||
for (const container of guaranteedContainers)
|
||||
{
|
||||
for (const container of guaranteedContainers) {
|
||||
const containerWithLoot = this.addLootToContainer(
|
||||
container,
|
||||
staticForcedOnMapClone,
|
||||
@ -136,16 +136,14 @@ export class LocationLootGenerator
|
||||
// Randomisation is turned off globally or just turned off for this map
|
||||
if (
|
||||
!(
|
||||
this.locationConfig.containerRandomisationSettings.enabled
|
||||
&& this.locationConfig.containerRandomisationSettings.maps[locationId]
|
||||
this.locationConfig.containerRandomisationSettings.enabled &&
|
||||
this.locationConfig.containerRandomisationSettings.maps[locationId]
|
||||
)
|
||||
)
|
||||
{
|
||||
) {
|
||||
this.logger.debug(
|
||||
`Container randomisation disabled, Adding ${staticRandomisableContainersOnMap.length} containers to ${locationBase.Name}`,
|
||||
);
|
||||
for (const container of staticRandomisableContainersOnMap)
|
||||
{
|
||||
for (const container of staticRandomisableContainersOnMap) {
|
||||
const containerWithLoot = this.addLootToContainer(
|
||||
container,
|
||||
staticForcedOnMapClone,
|
||||
@ -164,27 +162,25 @@ export class LocationLootGenerator
|
||||
}
|
||||
|
||||
// Group containers by their groupId
|
||||
if (!mapData.statics)
|
||||
{
|
||||
this.logger.warning(this.localisationService.getText("location-unable_to_generate_static_loot", locationId));
|
||||
if (!mapData.statics) {
|
||||
this.logger.warning(
|
||||
this.localisationService.getText("location-unable_to_generate_static_loot", locationId),
|
||||
);
|
||||
|
||||
return result;
|
||||
}
|
||||
const mapping = this.getGroupIdToContainerMappings(mapData.statics, staticRandomisableContainersOnMap);
|
||||
|
||||
// For each of the container groups, choose from the pool of containers, hydrate container with loot and add to result array
|
||||
for (const groupId in mapping)
|
||||
{
|
||||
for (const groupId in mapping) {
|
||||
const data = mapping[groupId];
|
||||
|
||||
// Count chosen was 0, skip
|
||||
if (data.chosenCount === 0)
|
||||
{
|
||||
if (data.chosenCount === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (Object.keys(data.containerIdsWithProbability).length === 0)
|
||||
{
|
||||
if (Object.keys(data.containerIdsWithProbability).length === 0) {
|
||||
this.logger.debug(
|
||||
`Group: ${groupId} has no containers with < 100% spawn chance to choose from, skipping`,
|
||||
);
|
||||
@ -192,15 +188,12 @@ export class LocationLootGenerator
|
||||
}
|
||||
|
||||
// EDGE CASE: These are containers without a group and have a probability < 100%
|
||||
if (groupId === "")
|
||||
{
|
||||
if (groupId === "") {
|
||||
const containerIdsCopy = this.cloner.clone(data.containerIdsWithProbability);
|
||||
// Roll each containers probability, if it passes, it gets added
|
||||
data.containerIdsWithProbability = {};
|
||||
for (const containerId in containerIdsCopy)
|
||||
{
|
||||
if (this.randomUtil.getChance100(containerIdsCopy[containerId] * 100))
|
||||
{
|
||||
for (const containerId in containerIdsCopy) {
|
||||
if (this.randomUtil.getChance100(containerIdsCopy[containerId] * 100)) {
|
||||
data.containerIdsWithProbability[containerId] = containerIdsCopy[containerId];
|
||||
}
|
||||
}
|
||||
@ -209,22 +202,19 @@ export class LocationLootGenerator
|
||||
data.chosenCount = Object.keys(data.containerIdsWithProbability).length;
|
||||
|
||||
// EDGE CASE: chosen container count could be 0
|
||||
if (data.chosenCount === 0)
|
||||
{
|
||||
if (data.chosenCount === 0) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Pass possible containers into function to choose some
|
||||
const chosenContainerIds = this.getContainersByProbabilty(groupId, data);
|
||||
for (const chosenContainerId of chosenContainerIds)
|
||||
{
|
||||
for (const chosenContainerId of chosenContainerIds) {
|
||||
// Look up container object from full list of containers on map
|
||||
const containerObject = staticRandomisableContainersOnMap.find(
|
||||
(staticContainer) => staticContainer.template.Id === chosenContainerId,
|
||||
);
|
||||
if (!containerObject)
|
||||
{
|
||||
if (!containerObject) {
|
||||
this.logger.debug(
|
||||
`Container: ${chosenContainerIds[chosenContainerId]} not found in staticRandomisableContainersOnMap, this is bad`,
|
||||
);
|
||||
@ -260,13 +250,12 @@ export class LocationLootGenerator
|
||||
* @param staticContainers
|
||||
* @returns IStaticContainerData array
|
||||
*/
|
||||
protected getRandomisableContainersOnMap(staticContainers: IStaticContainerData[]): IStaticContainerData[]
|
||||
{
|
||||
protected getRandomisableContainersOnMap(staticContainers: IStaticContainerData[]): IStaticContainerData[] {
|
||||
return staticContainers.filter(
|
||||
(staticContainer) =>
|
||||
staticContainer.probability !== 1
|
||||
&& !staticContainer.template.IsAlwaysSpawn
|
||||
&& !this.locationConfig.containerRandomisationSettings.containerTypesToNotRandomise.includes(
|
||||
staticContainer.probability !== 1 &&
|
||||
!staticContainer.template.IsAlwaysSpawn &&
|
||||
!this.locationConfig.containerRandomisationSettings.containerTypesToNotRandomise.includes(
|
||||
staticContainer.template.Items[0]._tpl,
|
||||
),
|
||||
);
|
||||
@ -277,13 +266,12 @@ export class LocationLootGenerator
|
||||
* @param staticContainersOnMap
|
||||
* @returns IStaticContainerData array
|
||||
*/
|
||||
protected getGuaranteedContainers(staticContainersOnMap: IStaticContainerData[]): IStaticContainerData[]
|
||||
{
|
||||
protected getGuaranteedContainers(staticContainersOnMap: IStaticContainerData[]): IStaticContainerData[] {
|
||||
return staticContainersOnMap.filter(
|
||||
(staticContainer) =>
|
||||
staticContainer.probability === 1
|
||||
|| staticContainer.template.IsAlwaysSpawn
|
||||
|| this.locationConfig.containerRandomisationSettings.containerTypesToNotRandomise.includes(
|
||||
staticContainer.probability === 1 ||
|
||||
staticContainer.template.IsAlwaysSpawn ||
|
||||
this.locationConfig.containerRandomisationSettings.containerTypesToNotRandomise.includes(
|
||||
staticContainer.template.Items[0]._tpl,
|
||||
),
|
||||
);
|
||||
@ -295,13 +283,11 @@ export class LocationLootGenerator
|
||||
* @param containerData Containers and probability values for a groupId
|
||||
* @returns List of chosen container Ids
|
||||
*/
|
||||
protected getContainersByProbabilty(groupId: string, containerData: IContainerGroupCount): string[]
|
||||
{
|
||||
protected getContainersByProbabilty(groupId: string, containerData: IContainerGroupCount): string[] {
|
||||
const chosenContainerIds: string[] = [];
|
||||
|
||||
const containerIds = Object.keys(containerData.containerIdsWithProbability);
|
||||
if (containerData.chosenCount > containerIds.length)
|
||||
{
|
||||
if (containerData.chosenCount > containerIds.length) {
|
||||
this.logger.debug(
|
||||
`Group: ${groupId} wants ${containerData.chosenCount} containers but pool only has ${containerIds.length}, add what's available`,
|
||||
);
|
||||
@ -310,8 +296,7 @@ export class LocationLootGenerator
|
||||
|
||||
// Create probability array with all possible container ids in this group and their relataive probability of spawning
|
||||
const containerDistribution = new ProbabilityObjectArray<string>(this.mathUtil, this.cloner);
|
||||
for (const x of containerIds)
|
||||
{
|
||||
for (const x of containerIds) {
|
||||
containerDistribution.push(new ProbabilityObject(x, containerData.containerIdsWithProbability[x]));
|
||||
}
|
||||
|
||||
@ -328,25 +313,22 @@ export class LocationLootGenerator
|
||||
protected getGroupIdToContainerMappings(
|
||||
staticContainerGroupData: IStaticContainer | Record<string, IContainerMinMax>,
|
||||
staticContainersOnMap: IStaticContainerData[],
|
||||
): Record<string, IContainerGroupCount>
|
||||
{
|
||||
): Record<string, IContainerGroupCount> {
|
||||
// Create dictionary of all group ids and choose a count of containers the map will spawn of that group
|
||||
const mapping: Record<string, IContainerGroupCount> = {};
|
||||
for (const groupId in staticContainerGroupData.containersGroups)
|
||||
{
|
||||
for (const groupId in staticContainerGroupData.containersGroups) {
|
||||
const groupData = staticContainerGroupData.containersGroups[groupId];
|
||||
if (!mapping[groupId])
|
||||
{
|
||||
if (!mapping[groupId]) {
|
||||
mapping[groupId] = {
|
||||
containerIdsWithProbability: {},
|
||||
chosenCount: this.randomUtil.getInt(
|
||||
Math.round(
|
||||
groupData.minContainers
|
||||
* this.locationConfig.containerRandomisationSettings.containerGroupMinSizeMultiplier,
|
||||
groupData.minContainers *
|
||||
this.locationConfig.containerRandomisationSettings.containerGroupMinSizeMultiplier,
|
||||
),
|
||||
Math.round(
|
||||
groupData.maxContainers
|
||||
* this.locationConfig.containerRandomisationSettings.containerGroupMaxSizeMultiplier,
|
||||
groupData.maxContainers *
|
||||
this.locationConfig.containerRandomisationSettings.containerGroupMaxSizeMultiplier,
|
||||
),
|
||||
),
|
||||
};
|
||||
@ -359,18 +341,20 @@ export class LocationLootGenerator
|
||||
|
||||
// Iterate over all containers and add to group keyed by groupId
|
||||
// Containers without a group go into a group with empty key ""
|
||||
for (const container of staticContainersOnMap)
|
||||
{
|
||||
for (const container of staticContainersOnMap) {
|
||||
const groupData = staticContainerGroupData.containers[container.template.Id];
|
||||
if (!groupData)
|
||||
{
|
||||
this.logger.error(this.localisationService.getText("location-unable_to_find_container_in_statics_json", container.template.Id));
|
||||
if (!groupData) {
|
||||
this.logger.error(
|
||||
this.localisationService.getText(
|
||||
"location-unable_to_find_container_in_statics_json",
|
||||
container.template.Id,
|
||||
),
|
||||
);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (container.probability === 1)
|
||||
{
|
||||
if (container.probability === 1) {
|
||||
this.logger.debug(
|
||||
`Container ${container.template.Id} with group ${groupData.groupId} had 100% chance to spawn was picked as random container, skipping`,
|
||||
);
|
||||
@ -399,8 +383,7 @@ export class LocationLootGenerator
|
||||
staticLootDist: Record<string, IStaticLootDetails>,
|
||||
staticAmmoDist: Record<string, IStaticAmmoDetails[]>,
|
||||
locationName: string,
|
||||
): IStaticContainerData
|
||||
{
|
||||
): IStaticContainerData {
|
||||
const containerClone = this.cloner.clone(staticContainer);
|
||||
const containerTpl = containerClone.template.Items[0]._tpl;
|
||||
|
||||
@ -435,8 +418,7 @@ export class LocationLootGenerator
|
||||
|
||||
// Add forced loot to chosen item pool
|
||||
const tplsToAddToContainer = tplsForced.concat(chosenTpls);
|
||||
for (const tplToAdd of tplsToAddToContainer)
|
||||
{
|
||||
for (const tplToAdd of tplsToAddToContainer) {
|
||||
const chosenItemWithChildren = this.createStaticLootItem(tplToAdd, staticAmmoDist, parentId);
|
||||
const items = chosenItemWithChildren.items;
|
||||
const width = chosenItemWithChildren.width;
|
||||
@ -444,10 +426,8 @@ export class LocationLootGenerator
|
||||
|
||||
// look for open slot to put chosen item into
|
||||
const result = this.containerHelper.findSlotForItem(containerMap, width, height);
|
||||
if (!result.success)
|
||||
{
|
||||
if (failedToFitCount >= this.locationConfig.fitLootIntoContainerAttempts)
|
||||
{
|
||||
if (!result.success) {
|
||||
if (failedToFitCount >= this.locationConfig.fitLootIntoContainerAttempts) {
|
||||
// x attempts to fit an item, container is probably full, stop trying to add more
|
||||
break;
|
||||
}
|
||||
@ -472,8 +452,7 @@ export class LocationLootGenerator
|
||||
items[0].location = { x: result.x!, y: result.y!, r: rotation };
|
||||
|
||||
// Add loot to container before returning
|
||||
for (const item of items)
|
||||
{
|
||||
for (const item of items) {
|
||||
containerClone.template.Items.push(item);
|
||||
}
|
||||
}
|
||||
@ -486,8 +465,7 @@ export class LocationLootGenerator
|
||||
* @param containerTpl Tpl id of the container
|
||||
* @returns number[][]
|
||||
*/
|
||||
protected getContainerMapping(containerTpl: string): number[][]
|
||||
{
|
||||
protected getContainerMapping(containerTpl: string): number[][] {
|
||||
// Get template from db
|
||||
const containerTemplate = this.itemHelper.getItem(containerTpl)[1];
|
||||
|
||||
@ -512,24 +490,22 @@ export class LocationLootGenerator
|
||||
containerTypeId: string,
|
||||
staticLootDist: Record<string, IStaticLootDetails>,
|
||||
locationName: string,
|
||||
): number
|
||||
{
|
||||
): number {
|
||||
// Create probability array to calcualte the total count of lootable items inside container
|
||||
const itemCountArray = new ProbabilityObjectArray<number>(this.mathUtil, this.cloner);
|
||||
const countDistribution = staticLootDist[containerTypeId]?.itemcountDistribution;
|
||||
if (!countDistribution)
|
||||
{
|
||||
this.logger.warning(this.localisationService.getText("location-unable_to_find_count_distribution_for_container",
|
||||
{
|
||||
if (!countDistribution) {
|
||||
this.logger.warning(
|
||||
this.localisationService.getText("location-unable_to_find_count_distribution_for_container", {
|
||||
containerId: containerTypeId,
|
||||
locationName: locationName,
|
||||
}));
|
||||
}),
|
||||
);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
for (const itemCountDistribution of countDistribution)
|
||||
{
|
||||
for (const itemCountDistribution of countDistribution) {
|
||||
// Add each count of items into array
|
||||
itemCountArray.push(
|
||||
new ProbabilityObject(itemCountDistribution.count, itemCountDistribution.relativeProbability),
|
||||
@ -549,31 +525,28 @@ export class LocationLootGenerator
|
||||
protected getPossibleLootItemsForContainer(
|
||||
containerTypeId: string,
|
||||
staticLootDist: Record<string, IStaticLootDetails>,
|
||||
): ProbabilityObjectArray<string, number>
|
||||
{
|
||||
): ProbabilityObjectArray<string, number> {
|
||||
const seasonalEventActive = this.seasonalEventService.seasonalEventEnabled();
|
||||
const seasonalItemTplBlacklist = this.seasonalEventService.getInactiveSeasonalEventItems();
|
||||
|
||||
const itemDistribution = new ProbabilityObjectArray<string, number>(this.mathUtil, this.cloner);
|
||||
|
||||
const itemContainerDistribution = staticLootDist[containerTypeId]?.itemDistribution;
|
||||
if (!itemContainerDistribution)
|
||||
{
|
||||
this.logger.warning(this.localisationService.getText("location-missing_item_distribution_data", containerTypeId));
|
||||
if (!itemContainerDistribution) {
|
||||
this.logger.warning(
|
||||
this.localisationService.getText("location-missing_item_distribution_data", containerTypeId),
|
||||
);
|
||||
|
||||
return itemDistribution;
|
||||
}
|
||||
for (const icd of itemContainerDistribution)
|
||||
{
|
||||
if (!seasonalEventActive && seasonalItemTplBlacklist.includes(icd.tpl))
|
||||
{
|
||||
for (const icd of itemContainerDistribution) {
|
||||
if (!seasonalEventActive && seasonalItemTplBlacklist.includes(icd.tpl)) {
|
||||
// Skip seasonal event items if they're not enabled
|
||||
continue;
|
||||
}
|
||||
|
||||
// Ensure no blacklisted lootable items are in pool
|
||||
if (this.itemFilterService.isLootableItemBlacklisted(icd.tpl))
|
||||
{
|
||||
if (this.itemFilterService.isLootableItemBlacklisted(icd.tpl)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -583,13 +556,11 @@ export class LocationLootGenerator
|
||||
return itemDistribution;
|
||||
}
|
||||
|
||||
protected getLooseLootMultiplerForLocation(location: string): number
|
||||
{
|
||||
protected getLooseLootMultiplerForLocation(location: string): number {
|
||||
return this.locationConfig.looseLootMultiplier[location];
|
||||
}
|
||||
|
||||
protected getStaticLootMultiplerForLocation(location: string): number
|
||||
{
|
||||
protected getStaticLootMultiplerForLocation(location: string): number {
|
||||
return this.locationConfig.staticLootMultiplier[location];
|
||||
}
|
||||
|
||||
@ -604,8 +575,7 @@ export class LocationLootGenerator
|
||||
dynamicLootDist: ILooseLoot,
|
||||
staticAmmoDist: Record<string, IStaticAmmoDetails[]>,
|
||||
locationName: string,
|
||||
): SpawnpointTemplate[]
|
||||
{
|
||||
): SpawnpointTemplate[] {
|
||||
const loot: SpawnpointTemplate[] = [];
|
||||
const dynamicForcedSpawnPoints: SpawnpointsForced[] = [];
|
||||
|
||||
@ -620,8 +590,8 @@ export class LocationLootGenerator
|
||||
|
||||
// Draw from random distribution
|
||||
const desiredSpawnpointCount = Math.round(
|
||||
this.getLooseLootMultiplerForLocation(locationName)
|
||||
* this.randomUtil.getNormallyDistributedRandomNumber(
|
||||
this.getLooseLootMultiplerForLocation(locationName) *
|
||||
this.randomUtil.getNormallyDistributedRandomNumber(
|
||||
dynamicLootDist.spawnpointCount.mean,
|
||||
dynamicLootDist.spawnpointCount.std,
|
||||
),
|
||||
@ -633,24 +603,20 @@ export class LocationLootGenerator
|
||||
const blacklistedSpawnpoints = this.locationConfig.looseLootBlacklist[locationName];
|
||||
const spawnpointArray = new ProbabilityObjectArray<string, Spawnpoint>(this.mathUtil, this.cloner);
|
||||
|
||||
for (const spawnpoint of allDynamicSpawnpoints)
|
||||
{
|
||||
for (const spawnpoint of allDynamicSpawnpoints) {
|
||||
// Point is blacklsited, skip
|
||||
if (blacklistedSpawnpoints?.includes(spawnpoint.template.Id))
|
||||
{
|
||||
if (blacklistedSpawnpoints?.includes(spawnpoint.template.Id)) {
|
||||
this.logger.debug(`Ignoring loose loot location: ${spawnpoint.template.Id}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// We've handled IsAlwaysSpawn above, so skip them
|
||||
if (spawnpoint.template.IsAlwaysSpawn)
|
||||
{
|
||||
if (spawnpoint.template.IsAlwaysSpawn) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 100%, add it to guaranteed
|
||||
if (spawnpoint.probability === 1)
|
||||
{
|
||||
if (spawnpoint.probability === 1) {
|
||||
guaranteedLoosePoints.push(spawnpoint);
|
||||
continue;
|
||||
}
|
||||
@ -664,11 +630,9 @@ export class LocationLootGenerator
|
||||
|
||||
const randomSpawnpointCount = desiredSpawnpointCount - chosenSpawnpoints.length;
|
||||
// Only draw random spawn points if needed
|
||||
if (randomSpawnpointCount > 0 && spawnpointArray.length > 0)
|
||||
{
|
||||
if (randomSpawnpointCount > 0 && spawnpointArray.length > 0) {
|
||||
// Add randomly chosen spawn points
|
||||
for (const si of spawnpointArray.draw(randomSpawnpointCount, false))
|
||||
{
|
||||
for (const si of spawnpointArray.draw(randomSpawnpointCount, false)) {
|
||||
chosenSpawnpoints.push(spawnpointArray.data(si)!);
|
||||
}
|
||||
}
|
||||
@ -680,8 +644,7 @@ export class LocationLootGenerator
|
||||
|
||||
// Do we have enough items in pool to fulfill requirement
|
||||
const tooManySpawnPointsRequested = desiredSpawnpointCount - chosenSpawnpoints.length > 0;
|
||||
if (tooManySpawnPointsRequested)
|
||||
{
|
||||
if (tooManySpawnPointsRequested) {
|
||||
this.logger.debug(
|
||||
this.localisationService.getText("location-spawn_point_count_requested_vs_found", {
|
||||
requested: desiredSpawnpointCount + guaranteedLoosePoints.length,
|
||||
@ -694,11 +657,9 @@ export class LocationLootGenerator
|
||||
// Iterate over spawnpoints
|
||||
const seasonalEventActive = this.seasonalEventService.seasonalEventEnabled();
|
||||
const seasonalItemTplBlacklist = this.seasonalEventService.getInactiveSeasonalEventItems();
|
||||
for (const spawnPoint of chosenSpawnpoints)
|
||||
{
|
||||
for (const spawnPoint of chosenSpawnpoints) {
|
||||
// Spawnpoint is invalid, skip it
|
||||
if (!spawnPoint.template)
|
||||
{
|
||||
if (!spawnPoint.template) {
|
||||
this.logger.warning(
|
||||
this.localisationService.getText("location-missing_dynamic_template", spawnPoint.locationId),
|
||||
);
|
||||
@ -707,19 +668,19 @@ export class LocationLootGenerator
|
||||
}
|
||||
|
||||
// Ensure no blacklisted lootable items are in pool
|
||||
spawnPoint.template.Items = spawnPoint.template.Items
|
||||
.filter((item) => !this.itemFilterService.isLootableItemBlacklisted(item._tpl));
|
||||
spawnPoint.template.Items = spawnPoint.template.Items.filter(
|
||||
(item) => !this.itemFilterService.isLootableItemBlacklisted(item._tpl),
|
||||
);
|
||||
|
||||
// Ensure no seasonal items are in pool if not in-season
|
||||
if (!seasonalEventActive)
|
||||
{
|
||||
spawnPoint.template.Items = spawnPoint.template.Items
|
||||
.filter((item) => !seasonalItemTplBlacklist.includes(item._tpl));
|
||||
if (!seasonalEventActive) {
|
||||
spawnPoint.template.Items = spawnPoint.template.Items.filter(
|
||||
(item) => !seasonalItemTplBlacklist.includes(item._tpl),
|
||||
);
|
||||
}
|
||||
|
||||
// Spawn point has no items after filtering, skip
|
||||
if (!spawnPoint.template.Items || spawnPoint.template.Items.length === 0)
|
||||
{
|
||||
if (!spawnPoint.template.Items || spawnPoint.template.Items.length === 0) {
|
||||
this.logger.warning(
|
||||
this.localisationService.getText("location-spawnpoint_missing_items", spawnPoint.template.Id),
|
||||
);
|
||||
@ -732,19 +693,18 @@ export class LocationLootGenerator
|
||||
|
||||
// Construct container to hold above filtered items, letting us pick an item for the spot
|
||||
const itemArray = new ProbabilityObjectArray<string>(this.mathUtil, this.cloner);
|
||||
for (const itemDist of spawnPoint.itemDistribution)
|
||||
{
|
||||
if (!validItemIds.includes(itemDist.composedKey.key))
|
||||
{
|
||||
for (const itemDist of spawnPoint.itemDistribution) {
|
||||
if (!validItemIds.includes(itemDist.composedKey.key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
itemArray.push(new ProbabilityObject(itemDist.composedKey.key, itemDist.relativeProbability));
|
||||
}
|
||||
|
||||
if (itemArray.length === 0)
|
||||
{
|
||||
this.logger.warning(this.localisationService.getText("location-loot_pool_is_empty_skipping", spawnPoint.template.Id));
|
||||
if (itemArray.length === 0) {
|
||||
this.logger.warning(
|
||||
this.localisationService.getText("location-loot_pool_is_empty_skipping", spawnPoint.template.Id),
|
||||
);
|
||||
|
||||
continue;
|
||||
}
|
||||
@ -775,20 +735,16 @@ export class LocationLootGenerator
|
||||
lootLocationTemplates: SpawnpointTemplate[],
|
||||
forcedSpawnPoints: SpawnpointsForced[],
|
||||
locationName: string,
|
||||
): void
|
||||
{
|
||||
): void {
|
||||
const lootToForceSingleAmountOnMap = this.locationConfig.forcedLootSingleSpawnById[locationName];
|
||||
if (lootToForceSingleAmountOnMap)
|
||||
{
|
||||
if (lootToForceSingleAmountOnMap) {
|
||||
// Process loot items defined as requiring only 1 spawn position as they appear in multiple positions on the map
|
||||
for (const itemTpl of lootToForceSingleAmountOnMap)
|
||||
{
|
||||
for (const itemTpl of lootToForceSingleAmountOnMap) {
|
||||
// Get all spawn positions for item tpl in forced loot array
|
||||
const items = forcedSpawnPoints.filter(
|
||||
(forcedSpawnPoint) => forcedSpawnPoint.template.Items[0]._tpl === itemTpl,
|
||||
);
|
||||
if (!items || items.length === 0)
|
||||
{
|
||||
if (!items || items.length === 0) {
|
||||
this.logger.debug(
|
||||
`Unable to adjust loot item ${itemTpl} as it does not exist inside ${locationName} forced loot.`,
|
||||
);
|
||||
@ -800,20 +756,19 @@ export class LocationLootGenerator
|
||||
this.mathUtil,
|
||||
this.cloner,
|
||||
);
|
||||
for (const si of items)
|
||||
{
|
||||
for (const si of items) {
|
||||
// use locationId as template.Id is the same across all items
|
||||
spawnpointArray.push(new ProbabilityObject(si.locationId, si.probability, si));
|
||||
}
|
||||
|
||||
// Choose 1 out of all found spawn positions for spawn id and add to loot array
|
||||
for (const spawnPointLocationId of spawnpointArray.draw(1, false))
|
||||
{
|
||||
for (const spawnPointLocationId of spawnpointArray.draw(1, false)) {
|
||||
const itemToAdd = items.find((item) => item.locationId === spawnPointLocationId);
|
||||
const lootItem = itemToAdd?.template;
|
||||
if (!lootItem)
|
||||
{
|
||||
this.logger.warning(`Item with spawn point id ${spawnPointLocationId} could not be found, skipping`);
|
||||
if (!lootItem) {
|
||||
this.logger.warning(
|
||||
`Item with spawn point id ${spawnPointLocationId} could not be found, skipping`,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
lootItem.Root = this.objectId.generate();
|
||||
@ -827,19 +782,16 @@ export class LocationLootGenerator
|
||||
const seasonalItemTplBlacklist = this.seasonalEventService.getInactiveSeasonalEventItems();
|
||||
|
||||
// Add remaining forced loot to array
|
||||
for (const forcedLootLocation of forcedSpawnPoints)
|
||||
{
|
||||
for (const forcedLootLocation of forcedSpawnPoints) {
|
||||
const firstLootItemTpl = forcedLootLocation.template.Items[0]._tpl;
|
||||
|
||||
// Skip spawn positions processed already
|
||||
if (lootToForceSingleAmountOnMap?.includes(firstLootItemTpl))
|
||||
{
|
||||
if (lootToForceSingleAmountOnMap?.includes(firstLootItemTpl)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip adding seasonal items when seasonal event is not active
|
||||
if (!seasonalEventActive && seasonalItemTplBlacklist.includes(firstLootItemTpl))
|
||||
{
|
||||
if (!seasonalEventActive && seasonalItemTplBlacklist.includes(firstLootItemTpl)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -853,12 +805,9 @@ export class LocationLootGenerator
|
||||
const existingLocation = lootLocationTemplates.some(
|
||||
(spawnPoint) => spawnPoint.Id === locationTemplateToAdd.Id,
|
||||
);
|
||||
if (!existingLocation)
|
||||
{
|
||||
if (!existingLocation) {
|
||||
lootLocationTemplates.push(locationTemplateToAdd);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
this.logger.debug(
|
||||
`Attempted to add a forced loot location with Id: ${locationTemplateToAdd.Id} to map ${locationName} that already has that id in use, skipping`,
|
||||
);
|
||||
@ -877,12 +826,10 @@ export class LocationLootGenerator
|
||||
chosenComposedKey: string,
|
||||
spawnPoint: Spawnpoint,
|
||||
staticAmmoDist: Record<string, IStaticAmmoDetails[]>,
|
||||
): IContainerItem
|
||||
{
|
||||
): IContainerItem {
|
||||
const chosenItem = spawnPoint.template.Items.find((item) => item._id === chosenComposedKey);
|
||||
const chosenTpl = chosenItem?._tpl;
|
||||
if (!chosenTpl)
|
||||
{
|
||||
if (!chosenTpl) {
|
||||
throw new Error(`Item for tpl ${chosenComposedKey} was not found in the spawn point`);
|
||||
}
|
||||
const itemTemplate = this.itemHelper.getItem(chosenTpl)[1];
|
||||
@ -891,10 +838,9 @@ export class LocationLootGenerator
|
||||
const itemWithMods: Item[] = [];
|
||||
|
||||
// Money/Ammo - don't rely on items in spawnPoint.template.Items so we can randomise it ourselves
|
||||
if (this.itemHelper.isOfBaseclasses(chosenTpl, [BaseClasses.MONEY, BaseClasses.AMMO]))
|
||||
{
|
||||
const stackCount
|
||||
= itemTemplate._props.StackMaxSize === 1
|
||||
if (this.itemHelper.isOfBaseclasses(chosenTpl, [BaseClasses.MONEY, BaseClasses.AMMO])) {
|
||||
const stackCount =
|
||||
itemTemplate._props.StackMaxSize === 1
|
||||
? 1
|
||||
: this.randomUtil.getInt(itemTemplate._props.StackMinRandom!, itemTemplate._props.StackMaxRandom!);
|
||||
|
||||
@ -903,21 +849,16 @@ export class LocationLootGenerator
|
||||
_tpl: chosenTpl,
|
||||
upd: { StackObjectsCount: stackCount },
|
||||
});
|
||||
}
|
||||
else if (this.itemHelper.isOfBaseclass(chosenTpl, BaseClasses.AMMO_BOX))
|
||||
{
|
||||
} else if (this.itemHelper.isOfBaseclass(chosenTpl, BaseClasses.AMMO_BOX)) {
|
||||
// Fill with cartridges
|
||||
const ammoBoxItem: Item[] = [{ _id: this.objectId.generate(), _tpl: chosenTpl }];
|
||||
this.itemHelper.addCartridgesToAmmoBox(ammoBoxItem, itemTemplate);
|
||||
itemWithMods.push(...ammoBoxItem);
|
||||
}
|
||||
else if (this.itemHelper.isOfBaseclass(chosenTpl, BaseClasses.MAGAZINE))
|
||||
{
|
||||
} else if (this.itemHelper.isOfBaseclass(chosenTpl, BaseClasses.MAGAZINE)) {
|
||||
// Create array with just magazine
|
||||
const magazineItem: Item[] = [{ _id: this.objectId.generate(), _tpl: chosenTpl }];
|
||||
|
||||
if (this.randomUtil.getChance100(this.locationConfig.staticMagazineLootHasAmmoChancePercent))
|
||||
{
|
||||
if (this.randomUtil.getChance100(this.locationConfig.staticMagazineLootHasAmmoChancePercent)) {
|
||||
// Add randomised amount of cartridges
|
||||
this.itemHelper.fillMagazineWithRandomCartridge(
|
||||
magazineItem,
|
||||
@ -929,9 +870,7 @@ export class LocationLootGenerator
|
||||
}
|
||||
|
||||
itemWithMods.push(...magazineItem);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
// Also used by armors to get child mods
|
||||
// Get item + children and add into array we return
|
||||
const itemWithChildren = this.itemHelper.findAndReturnChildrenAsItems(
|
||||
@ -956,8 +895,7 @@ export class LocationLootGenerator
|
||||
* @param itemWithChildren Item with mods to update
|
||||
* @param newId new id to add on chidren of base item
|
||||
*/
|
||||
protected reparentItemAndChildren(itemWithChildren: Item[], newId = this.objectId.generate()): void
|
||||
{
|
||||
protected reparentItemAndChildren(itemWithChildren: Item[], newId = this.objectId.generate()): void {
|
||||
// original id on base item
|
||||
const oldId = itemWithChildren[0]._id;
|
||||
|
||||
@ -965,10 +903,8 @@ export class LocationLootGenerator
|
||||
itemWithChildren[0]._id = newId;
|
||||
|
||||
// Update all parentIds of items attached to base item to use new id
|
||||
for (const item of itemWithChildren)
|
||||
{
|
||||
if (item.parentId === oldId)
|
||||
{
|
||||
for (const item of itemWithChildren) {
|
||||
if (item.parentId === oldId) {
|
||||
item.parentId = newId;
|
||||
}
|
||||
}
|
||||
@ -980,10 +916,8 @@ export class LocationLootGenerator
|
||||
* @param chosenTpl Tpl we want to get item with
|
||||
* @returns Item object
|
||||
*/
|
||||
protected getItemInArray(items: Item[], chosenTpl: string): Item | undefined
|
||||
{
|
||||
if (this.itemHelper.isOfBaseclass(chosenTpl, BaseClasses.WEAPON))
|
||||
{
|
||||
protected getItemInArray(items: Item[], chosenTpl: string): Item | undefined {
|
||||
if (this.itemHelper.isOfBaseclass(chosenTpl, BaseClasses.WEAPON)) {
|
||||
return items.find((v) => v._tpl === chosenTpl && v.parentId === undefined);
|
||||
}
|
||||
|
||||
@ -995,8 +929,7 @@ export class LocationLootGenerator
|
||||
chosenTpl: string,
|
||||
staticAmmoDist: Record<string, IStaticAmmoDetails[]>,
|
||||
parentId?: string,
|
||||
): IContainerItem
|
||||
{
|
||||
): IContainerItem {
|
||||
const itemTemplate = this.itemHelper.getItem(chosenTpl)[1];
|
||||
let width = itemTemplate._props.Width!;
|
||||
let height = itemTemplate._props.Height!;
|
||||
@ -1004,37 +937,30 @@ export class LocationLootGenerator
|
||||
const rootItem = items[0];
|
||||
|
||||
// Use passed in parentId as override for new item
|
||||
if (parentId)
|
||||
{
|
||||
if (parentId) {
|
||||
rootItem.parentId = parentId;
|
||||
}
|
||||
|
||||
if (
|
||||
this.itemHelper.isOfBaseclass(chosenTpl, BaseClasses.MONEY)
|
||||
|| this.itemHelper.isOfBaseclass(chosenTpl, BaseClasses.AMMO)
|
||||
)
|
||||
{
|
||||
this.itemHelper.isOfBaseclass(chosenTpl, BaseClasses.MONEY) ||
|
||||
this.itemHelper.isOfBaseclass(chosenTpl, BaseClasses.AMMO)
|
||||
) {
|
||||
// Edge case - some ammos e.g. flares or M406 grenades shouldn't be stacked
|
||||
const stackCount
|
||||
= itemTemplate._props.StackMaxSize === 1
|
||||
const stackCount =
|
||||
itemTemplate._props.StackMaxSize === 1
|
||||
? 1
|
||||
: this.randomUtil.getInt(itemTemplate._props.StackMinRandom!, itemTemplate._props.StackMaxRandom!);
|
||||
|
||||
rootItem.upd = { StackObjectsCount: stackCount };
|
||||
}
|
||||
// No spawn point, use default template
|
||||
else if (this.itemHelper.isOfBaseclass(chosenTpl, BaseClasses.WEAPON))
|
||||
{
|
||||
else if (this.itemHelper.isOfBaseclass(chosenTpl, BaseClasses.WEAPON)) {
|
||||
let children: Item[] = [];
|
||||
const defaultPreset = this.cloner.clone(this.presetHelper.getDefaultPreset(chosenTpl));
|
||||
if (defaultPreset?._items)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (defaultPreset?._items) {
|
||||
try {
|
||||
children = this.itemHelper.reparentItemAndChildren(defaultPreset._items[0], defaultPreset._items);
|
||||
}
|
||||
catch (error)
|
||||
{
|
||||
} catch (error) {
|
||||
// this item already broke it once without being reproducible tpl = "5839a40f24597726f856b511"; AKS-74UB Default
|
||||
// 5ea03f7400685063ec28bfa8 // ppsh default
|
||||
// 5ba26383d4351e00334c93d9 //mp7_devgru
|
||||
@ -1049,16 +975,13 @@ export class LocationLootGenerator
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
// RSP30 (62178be9d0050232da3485d9/624c0b3340357b5f566e8766/6217726288ed9f0845317459) doesnt have any default presets and kills this code below as it has no chidren to reparent
|
||||
this.logger.debug(`createItem() No preset found for weapon: ${chosenTpl}`);
|
||||
}
|
||||
|
||||
const rootItem = items[0];
|
||||
if (!rootItem)
|
||||
{
|
||||
if (!rootItem) {
|
||||
this.logger.error(
|
||||
this.localisationService.getText("location-missing_root_item", {
|
||||
tpl: chosenTpl,
|
||||
@ -1069,15 +992,11 @@ export class LocationLootGenerator
|
||||
throw new Error(this.localisationService.getText("location-critical_error_see_log"));
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (children?.length > 0)
|
||||
{
|
||||
try {
|
||||
if (children?.length > 0) {
|
||||
items = this.itemHelper.reparentItemAndChildren(rootItem, children);
|
||||
}
|
||||
}
|
||||
catch (error)
|
||||
{
|
||||
} catch (error) {
|
||||
this.logger.error(
|
||||
this.localisationService.getText("location-unable_to_reparent_item", {
|
||||
tpl: chosenTpl,
|
||||
@ -1094,8 +1013,7 @@ export class LocationLootGenerator
|
||||
// BotGenerator
|
||||
const magazine = items.filter((item) => item.slotId === "mod_magazine")[0];
|
||||
// some weapon presets come without magazine; only fill the mag if it exists
|
||||
if (magazine)
|
||||
{
|
||||
if (magazine) {
|
||||
const magTemplate = this.itemHelper.getItem(magazine._tpl)[1];
|
||||
const weaponTemplate = this.itemHelper.getItem(chosenTpl)[1];
|
||||
|
||||
@ -1121,14 +1039,10 @@ export class LocationLootGenerator
|
||||
height = size.height;
|
||||
}
|
||||
// No spawnpoint to fall back on, generate manually
|
||||
else if (this.itemHelper.isOfBaseclass(chosenTpl, BaseClasses.AMMO_BOX))
|
||||
{
|
||||
else if (this.itemHelper.isOfBaseclass(chosenTpl, BaseClasses.AMMO_BOX)) {
|
||||
this.itemHelper.addCartridgesToAmmoBox(items, itemTemplate);
|
||||
}
|
||||
else if (this.itemHelper.isOfBaseclass(chosenTpl, BaseClasses.MAGAZINE))
|
||||
{
|
||||
if (this.randomUtil.getChance100(this.locationConfig.magazineLootHasAmmoChancePercent))
|
||||
{
|
||||
} else if (this.itemHelper.isOfBaseclass(chosenTpl, BaseClasses.MAGAZINE)) {
|
||||
if (this.randomUtil.getChance100(this.locationConfig.magazineLootHasAmmoChancePercent)) {
|
||||
// Create array with just magazine
|
||||
const magazineWithCartridges = [rootItem];
|
||||
this.itemHelper.fillMagazineWithRandomCartridge(
|
||||
@ -1142,24 +1056,18 @@ export class LocationLootGenerator
|
||||
// Replace existing magazine with above array
|
||||
items.splice(items.indexOf(rootItem), 1, ...magazineWithCartridges);
|
||||
}
|
||||
}
|
||||
else if (this.itemHelper.armorItemCanHoldMods(chosenTpl))
|
||||
{
|
||||
} else if (this.itemHelper.armorItemCanHoldMods(chosenTpl)) {
|
||||
const defaultPreset = this.presetHelper.getDefaultPreset(chosenTpl);
|
||||
if (defaultPreset)
|
||||
{
|
||||
if (defaultPreset) {
|
||||
const presetAndMods: Item[] = this.itemHelper.replaceIDs(defaultPreset._items);
|
||||
this.itemHelper.remapRootItemId(presetAndMods);
|
||||
|
||||
// Use original items parentId otherwise item doesnt get added to container correctly
|
||||
presetAndMods[0].parentId = rootItem.parentId;
|
||||
items = presetAndMods;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
// We make base item above, at start of function, no need to do it here
|
||||
if ((itemTemplate._props.Slots?.length ?? 0) > 0)
|
||||
{
|
||||
if ((itemTemplate._props.Slots?.length ?? 0) > 0) {
|
||||
items = this.itemHelper.addChildSlotItems(
|
||||
items,
|
||||
itemTemplate,
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { inject, injectable } from "tsyringe";
|
||||
import { InventoryHelper } from "@spt/helpers/InventoryHelper";
|
||||
import { ItemHelper } from "@spt/helpers/ItemHelper";
|
||||
import { PresetHelper } from "@spt/helpers/PresetHelper";
|
||||
@ -16,12 +15,12 @@ import { LocalisationService } from "@spt/services/LocalisationService";
|
||||
import { RagfairLinkedItemService } from "@spt/services/RagfairLinkedItemService";
|
||||
import { HashUtil } from "@spt/utils/HashUtil";
|
||||
import { RandomUtil } from "@spt/utils/RandomUtil";
|
||||
import { inject, injectable } from "tsyringe";
|
||||
|
||||
type ItemLimit = { current: number, max: number };
|
||||
type ItemLimit = { current: number; max: number };
|
||||
|
||||
@injectable()
|
||||
export class LootGenerator
|
||||
{
|
||||
export class LootGenerator {
|
||||
constructor(
|
||||
@inject("PrimaryLogger") protected logger: ILogger,
|
||||
@inject("HashUtil") protected hashUtil: HashUtil,
|
||||
@ -34,16 +33,14 @@ export class LootGenerator
|
||||
@inject("LocalisationService") protected localisationService: LocalisationService,
|
||||
@inject("RagfairLinkedItemService") protected ragfairLinkedItemService: RagfairLinkedItemService,
|
||||
@inject("ItemFilterService") protected itemFilterService: ItemFilterService,
|
||||
)
|
||||
{}
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Generate a list of items based on configuration options parameter
|
||||
* @param options parameters to adjust how loot is generated
|
||||
* @returns An array of loot items
|
||||
*/
|
||||
public createRandomLoot(options: LootRequest): Item[]
|
||||
{
|
||||
public createRandomLoot(options: LootRequest): Item[] {
|
||||
const result: Item[] = [];
|
||||
const itemTypeCounts = this.initItemLimitCounter(options.itemLimits);
|
||||
|
||||
@ -53,16 +50,13 @@ export class LootGenerator
|
||||
...options.itemBlacklist,
|
||||
]);
|
||||
|
||||
if (options.useRewarditemBlacklist)
|
||||
{
|
||||
if (options.useRewarditemBlacklist) {
|
||||
const itemsToAdd = this.itemFilterService.getItemRewardBlacklist();
|
||||
itemBlacklist = new Set([...itemBlacklist, ...itemsToAdd]);
|
||||
}
|
||||
|
||||
if (!options.allowBossItems)
|
||||
{
|
||||
for (const bossItem of this.itemFilterService.getBossItems())
|
||||
{
|
||||
if (!options.allowBossItems) {
|
||||
for (const bossItem of this.itemFilterService.getBossItems()) {
|
||||
itemBlacklist.add(bossItem);
|
||||
}
|
||||
}
|
||||
@ -72,15 +66,13 @@ export class LootGenerator
|
||||
options.weaponCrateCount.min,
|
||||
options.weaponCrateCount.max,
|
||||
);
|
||||
if (sealedWeaponCrateCount > 0)
|
||||
{
|
||||
if (sealedWeaponCrateCount > 0) {
|
||||
// Get list of all sealed containers from db - they're all the same, just for flavor
|
||||
const sealedWeaponContainerPool = Object.values(itemsDb).filter((item) =>
|
||||
item._name.includes("event_container_airdrop"),
|
||||
);
|
||||
|
||||
for (let index = 0; index < sealedWeaponCrateCount; index++)
|
||||
{
|
||||
for (let index = 0; index < sealedWeaponCrateCount; index++) {
|
||||
// Choose one at random + add to results array
|
||||
const chosenSealedContainer = this.randomUtil.getArrayValue(sealedWeaponContainerPool);
|
||||
result.push({
|
||||
@ -97,20 +89,17 @@ export class LootGenerator
|
||||
// Get items from items.json that have a type of item + not in global blacklist + basetype is in whitelist
|
||||
const items = Object.entries(itemsDb).filter(
|
||||
(item) =>
|
||||
!itemBlacklist.has(item[1]._id)
|
||||
&& item[1]._type.toLowerCase() === "item"
|
||||
&& !item[1]._props.QuestItem
|
||||
&& options.itemTypeWhitelist.includes(item[1]._parent),
|
||||
!itemBlacklist.has(item[1]._id) &&
|
||||
item[1]._type.toLowerCase() === "item" &&
|
||||
!item[1]._props.QuestItem &&
|
||||
options.itemTypeWhitelist.includes(item[1]._parent),
|
||||
);
|
||||
|
||||
// Pool has items we could add as loot, proceed
|
||||
if (items.length > 0)
|
||||
{
|
||||
if (items.length > 0) {
|
||||
const randomisedItemCount = this.randomUtil.getInt(options.itemCount.min, options.itemCount.max);
|
||||
for (let index = 0; index < randomisedItemCount; index++)
|
||||
{
|
||||
if (!this.findAndAddRandomItemToLoot(items, itemTypeCounts, options, result))
|
||||
{
|
||||
for (let index = 0; index < randomisedItemCount; index++) {
|
||||
if (!this.findAndAddRandomItemToLoot(items, itemTypeCounts, options, result)) {
|
||||
// Failed to add, reduce index so we get another attempt
|
||||
index--;
|
||||
}
|
||||
@ -125,16 +114,13 @@ export class LootGenerator
|
||||
options.weaponPresetCount.min,
|
||||
options.weaponPresetCount.max,
|
||||
);
|
||||
if (randomisedWeaponPresetCount > 0)
|
||||
{
|
||||
if (randomisedWeaponPresetCount > 0) {
|
||||
const weaponDefaultPresets = globalDefaultPresets.filter((preset) =>
|
||||
this.itemHelper.isOfBaseclass(preset._encyclopedia, BaseClasses.WEAPON),
|
||||
);
|
||||
|
||||
if (weaponDefaultPresets.length > 0)
|
||||
{
|
||||
for (let index = 0; index < randomisedWeaponPresetCount; index++)
|
||||
{
|
||||
if (weaponDefaultPresets.length > 0) {
|
||||
for (let index = 0; index < randomisedWeaponPresetCount; index++) {
|
||||
if (
|
||||
!this.findAndAddRandomPresetToLoot(
|
||||
weaponDefaultPresets,
|
||||
@ -142,8 +128,7 @@ export class LootGenerator
|
||||
itemBlacklistArray,
|
||||
result,
|
||||
)
|
||||
)
|
||||
{
|
||||
) {
|
||||
// Failed to add, reduce index so we get another attempt
|
||||
index--;
|
||||
}
|
||||
@ -156,8 +141,7 @@ export class LootGenerator
|
||||
options.armorPresetCount.min,
|
||||
options.armorPresetCount.max,
|
||||
);
|
||||
if (randomisedArmorPresetCount > 0)
|
||||
{
|
||||
if (randomisedArmorPresetCount > 0) {
|
||||
const armorDefaultPresets = globalDefaultPresets.filter((preset) =>
|
||||
this.itemHelper.armorItemCanHoldMods(preset._encyclopedia),
|
||||
);
|
||||
@ -166,10 +150,8 @@ export class LootGenerator
|
||||
);
|
||||
|
||||
// Add some armors to rewards
|
||||
if (levelFilteredArmorPresets.length > 0)
|
||||
{
|
||||
for (let index = 0; index < randomisedArmorPresetCount; index++)
|
||||
{
|
||||
if (levelFilteredArmorPresets.length > 0) {
|
||||
for (let index = 0; index < randomisedArmorPresetCount; index++) {
|
||||
if (
|
||||
!this.findAndAddRandomPresetToLoot(
|
||||
levelFilteredArmorPresets,
|
||||
@ -177,8 +159,7 @@ export class LootGenerator
|
||||
itemBlacklistArray,
|
||||
result,
|
||||
)
|
||||
)
|
||||
{
|
||||
) {
|
||||
// Failed to add, reduce index so we get another attempt
|
||||
index--;
|
||||
}
|
||||
@ -195,14 +176,11 @@ export class LootGenerator
|
||||
* @param options Loot request options - armor level etc
|
||||
* @returns True if item has desired armor level
|
||||
*/
|
||||
protected isArmorOfDesiredProtectionLevel(armor: IPreset, options: LootRequest): boolean
|
||||
{
|
||||
protected isArmorOfDesiredProtectionLevel(armor: IPreset, options: LootRequest): boolean {
|
||||
const relevantSlots = ["front_plate", "helmet_top", "soft_armor_front"];
|
||||
for (const slotId of relevantSlots)
|
||||
{
|
||||
for (const slotId of relevantSlots) {
|
||||
const armorItem = armor._items.find((item) => item?.slotId?.toLowerCase() === slotId);
|
||||
if (!armorItem)
|
||||
{
|
||||
if (!armorItem) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -220,11 +198,9 @@ export class LootGenerator
|
||||
* @param limits limits as defined in config
|
||||
* @returns record, key: item tplId, value: current/max item count allowed
|
||||
*/
|
||||
protected initItemLimitCounter(limits: Record<string, number>): Record<string, ItemLimit>
|
||||
{
|
||||
protected initItemLimitCounter(limits: Record<string, number>): Record<string, ItemLimit> {
|
||||
const itemTypeCounts: Record<string, ItemLimit> = {};
|
||||
for (const itemTypeId in limits)
|
||||
{
|
||||
for (const itemTypeId in limits) {
|
||||
itemTypeCounts[itemTypeId] = { current: 0, max: limits[itemTypeId] };
|
||||
}
|
||||
|
||||
@ -241,22 +217,19 @@ export class LootGenerator
|
||||
*/
|
||||
protected findAndAddRandomItemToLoot(
|
||||
items: [string, ITemplateItem][],
|
||||
itemTypeCounts: Record<string, { current: number, max: number }>,
|
||||
itemTypeCounts: Record<string, { current: number; max: number }>,
|
||||
options: LootRequest,
|
||||
result: Item[],
|
||||
): boolean
|
||||
{
|
||||
): boolean {
|
||||
const randomItem = this.randomUtil.getArrayValue(items)[1];
|
||||
|
||||
const itemLimitCount = itemTypeCounts[randomItem._parent];
|
||||
if (itemLimitCount && itemLimitCount.current > itemLimitCount.max)
|
||||
{
|
||||
if (itemLimitCount && itemLimitCount.current > itemLimitCount.max) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Skip armors as they need to come from presets
|
||||
if (this.itemHelper.armorItemCanHoldMods(randomItem._id))
|
||||
{
|
||||
if (this.itemHelper.armorItemCanHoldMods(randomItem._id)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -270,16 +243,14 @@ export class LootGenerator
|
||||
};
|
||||
|
||||
// Special case - handle items that need a stackcount > 1
|
||||
if (randomItem._props.StackMaxSize > 1)
|
||||
{
|
||||
if (randomItem._props.StackMaxSize > 1) {
|
||||
newLootItem.upd.StackObjectsCount = this.getRandomisedStackCount(randomItem, options);
|
||||
}
|
||||
|
||||
newLootItem._tpl = randomItem._id;
|
||||
result.push(newLootItem);
|
||||
|
||||
if (itemLimitCount)
|
||||
{
|
||||
if (itemLimitCount) {
|
||||
// Increment item count as it's in limit array
|
||||
itemLimitCount.current++;
|
||||
}
|
||||
@ -294,13 +265,11 @@ export class LootGenerator
|
||||
* @param options loot options
|
||||
* @returns stack count
|
||||
*/
|
||||
protected getRandomisedStackCount(item: ITemplateItem, options: LootRequest): number
|
||||
{
|
||||
protected getRandomisedStackCount(item: ITemplateItem, options: LootRequest): number {
|
||||
let min = item._props.StackMinRandom;
|
||||
let max = item._props.StackMaxSize;
|
||||
|
||||
if (options.itemStackLimits[item._id])
|
||||
{
|
||||
if (options.itemStackLimits[item._id]) {
|
||||
min = options.itemStackLimits[item._id].min;
|
||||
max = options.itemStackLimits[item._id].max;
|
||||
}
|
||||
@ -318,23 +287,20 @@ export class LootGenerator
|
||||
*/
|
||||
protected findAndAddRandomPresetToLoot(
|
||||
presetPool: IPreset[],
|
||||
itemTypeCounts: Record<string, { current: number, max: number }>,
|
||||
itemTypeCounts: Record<string, { current: number; max: number }>,
|
||||
itemBlacklist: string[],
|
||||
result: Item[],
|
||||
): boolean
|
||||
{
|
||||
): boolean {
|
||||
// Choose random preset and get details from item db using encyclopedia value (encyclopedia === tplId)
|
||||
const chosenPreset = this.randomUtil.getArrayValue(presetPool);
|
||||
if (!chosenPreset)
|
||||
{
|
||||
if (!chosenPreset) {
|
||||
this.logger.warning("Unable to find random preset in given presets, skipping");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// No `_encyclopedia` property, not possible to reliably get root item tpl
|
||||
if (!chosenPreset?._encyclopedia)
|
||||
{
|
||||
if (!chosenPreset?._encyclopedia) {
|
||||
this.logger.debug(`Preset with id: ${chosenPreset?._id} lacks encyclopedia property, skipping`);
|
||||
|
||||
return false;
|
||||
@ -342,22 +308,19 @@ export class LootGenerator
|
||||
|
||||
// Get preset root item db details via its `_encyclopedia` property
|
||||
const itemDbDetails = this.itemHelper.getItem(chosenPreset._encyclopedia);
|
||||
if (!itemDbDetails[0])
|
||||
{
|
||||
if (!itemDbDetails[0]) {
|
||||
this.logger.debug(`Unable to find preset with tpl: ${chosenPreset._encyclopedia}, skipping`);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Skip preset if root item is blacklisted
|
||||
if (itemBlacklist.includes(chosenPreset._items[0]._tpl))
|
||||
{
|
||||
if (itemBlacklist.includes(chosenPreset._items[0]._tpl)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Some custom mod items lack a parent property
|
||||
if (!itemDbDetails[1]._parent)
|
||||
{
|
||||
if (!itemDbDetails[1]._parent) {
|
||||
this.logger.error(this.localisationService.getText("loot-item_missing_parentid", itemDbDetails[1]?._name));
|
||||
|
||||
return false;
|
||||
@ -365,21 +328,18 @@ export class LootGenerator
|
||||
|
||||
// Check chosen preset hasn't exceeded spawn limit
|
||||
const itemLimitCount = itemTypeCounts[itemDbDetails[1]._parent];
|
||||
if (itemLimitCount && itemLimitCount.current > itemLimitCount.max)
|
||||
{
|
||||
if (itemLimitCount && itemLimitCount.current > itemLimitCount.max) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const presetAndMods: Item[] = this.itemHelper.replaceIDs(chosenPreset._items);
|
||||
this.itemHelper.remapRootItemId(presetAndMods);
|
||||
// Add chosen preset tpl to result array
|
||||
presetAndMods.forEach((item) =>
|
||||
{
|
||||
presetAndMods.forEach((item) => {
|
||||
result.push(item);
|
||||
});
|
||||
|
||||
if (itemLimitCount)
|
||||
{
|
||||
if (itemLimitCount) {
|
||||
// Increment item count as item has been chosen and its inside itemLimitCount dictionary
|
||||
itemLimitCount.current++;
|
||||
}
|
||||
@ -393,8 +353,7 @@ export class LootGenerator
|
||||
* @param containerSettings sealed weapon container settings
|
||||
* @returns Array of item with children arrays
|
||||
*/
|
||||
public getSealedWeaponCaseLoot(containerSettings: ISealedAirdropContainerSettings): Item[][]
|
||||
{
|
||||
public getSealedWeaponCaseLoot(containerSettings: ISealedAirdropContainerSettings): Item[][] {
|
||||
const itemsToReturn: Item[][] = [];
|
||||
|
||||
// Choose a weapon to give to the player (weighted)
|
||||
@ -404,8 +363,7 @@ export class LootGenerator
|
||||
|
||||
// Get itemDb details of weapon
|
||||
const weaponDetailsDb = this.itemHelper.getItem(chosenWeaponTpl);
|
||||
if (!weaponDetailsDb[0])
|
||||
{
|
||||
if (!weaponDetailsDb[0]) {
|
||||
this.logger.error(
|
||||
this.localisationService.getText("loot-non_item_picked_as_sealed_weapon_crate_reward", chosenWeaponTpl),
|
||||
);
|
||||
@ -419,9 +377,10 @@ export class LootGenerator
|
||||
: this.randomUtil.getArrayValue(this.presetHelper.getPresets(chosenWeaponTpl));
|
||||
|
||||
// No default preset found for weapon, choose a random one
|
||||
if (!chosenWeaponPreset)
|
||||
{
|
||||
this.logger.warning(this.localisationService.getText("loot-default_preset_not_found_using_random", chosenWeaponTpl));
|
||||
if (!chosenWeaponPreset) {
|
||||
this.logger.warning(
|
||||
this.localisationService.getText("loot-default_preset_not_found_using_random", chosenWeaponTpl),
|
||||
);
|
||||
chosenWeaponPreset = this.randomUtil.getArrayValue(this.presetHelper.getPresets(chosenWeaponTpl));
|
||||
}
|
||||
|
||||
@ -453,26 +412,21 @@ export class LootGenerator
|
||||
protected getSealedContainerNonWeaponModRewards(
|
||||
containerSettings: ISealedAirdropContainerSettings,
|
||||
weaponDetailsDb: ITemplateItem,
|
||||
): Item[][]
|
||||
{
|
||||
): Item[][] {
|
||||
const rewards: Item[][] = [];
|
||||
|
||||
for (const rewardTypeId in containerSettings.rewardTypeLimits)
|
||||
{
|
||||
for (const rewardTypeId in containerSettings.rewardTypeLimits) {
|
||||
const settings = containerSettings.rewardTypeLimits[rewardTypeId];
|
||||
const rewardCount = this.randomUtil.getInt(settings.min, settings.max);
|
||||
|
||||
if (rewardCount === 0)
|
||||
{
|
||||
if (rewardCount === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Edge case - ammo boxes
|
||||
if (rewardTypeId === BaseClasses.AMMO_BOX)
|
||||
{
|
||||
if (rewardTypeId === BaseClasses.AMMO_BOX) {
|
||||
// Get ammoboxes from db
|
||||
const ammoBoxesDetails = containerSettings.ammoBoxWhitelist.map((tpl) =>
|
||||
{
|
||||
const ammoBoxesDetails = containerSettings.ammoBoxWhitelist.map((tpl) => {
|
||||
const itemDetails = this.itemHelper.getItem(tpl);
|
||||
return itemDetails[1];
|
||||
});
|
||||
@ -480,15 +434,13 @@ export class LootGenerator
|
||||
// Need to find boxes that matches weapons caliber
|
||||
const weaponCaliber = weaponDetailsDb._props.ammoCaliber;
|
||||
const ammoBoxesMatchingCaliber = ammoBoxesDetails.filter((x) => x._props.ammoCaliber === weaponCaliber);
|
||||
if (ammoBoxesMatchingCaliber.length === 0)
|
||||
{
|
||||
if (ammoBoxesMatchingCaliber.length === 0) {
|
||||
this.logger.debug(`No ammo box with caliber ${weaponCaliber} found, skipping`);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
for (let index = 0; index < rewardCount; index++)
|
||||
{
|
||||
for (let index = 0; index < rewardCount; index++) {
|
||||
const chosenAmmoBox = this.randomUtil.getArrayValue(ammoBoxesMatchingCaliber);
|
||||
const ammoBoxItem: Item[] = [{ _id: this.hashUtil.generate(), _tpl: chosenAmmoBox._id }];
|
||||
this.itemHelper.addCartridgesToAmmoBox(ammoBoxItem, chosenAmmoBox);
|
||||
@ -501,22 +453,20 @@ export class LootGenerator
|
||||
// Get all items of the desired type + not quest items + not globally blacklisted
|
||||
const rewardItemPool = Object.values(this.databaseService.getItems()).filter(
|
||||
(item) =>
|
||||
item._parent === rewardTypeId
|
||||
&& item._type.toLowerCase() === "item"
|
||||
&& !this.itemFilterService.isItemBlacklisted(item._id)
|
||||
&& !(containerSettings.allowBossItems || this.itemFilterService.isBossItem(item._id))
|
||||
&& !item._props.QuestItem,
|
||||
item._parent === rewardTypeId &&
|
||||
item._type.toLowerCase() === "item" &&
|
||||
!this.itemFilterService.isItemBlacklisted(item._id) &&
|
||||
!(containerSettings.allowBossItems || this.itemFilterService.isBossItem(item._id)) &&
|
||||
!item._props.QuestItem,
|
||||
);
|
||||
|
||||
if (rewardItemPool.length === 0)
|
||||
{
|
||||
if (rewardItemPool.length === 0) {
|
||||
this.logger.debug(`No items with base type of ${rewardTypeId} found, skipping`);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
for (let index = 0; index < rewardCount; index++)
|
||||
{
|
||||
for (let index = 0; index < rewardCount; index++) {
|
||||
// Choose a random item from pool
|
||||
const chosenRewardItem = this.randomUtil.getArrayValue(rewardItemPool);
|
||||
const rewardItem: Item[] = [{ _id: this.hashUtil.generate(), _tpl: chosenRewardItem._id }];
|
||||
@ -539,17 +489,14 @@ export class LootGenerator
|
||||
containerSettings: ISealedAirdropContainerSettings,
|
||||
linkedItemsToWeapon: ITemplateItem[],
|
||||
chosenWeaponPreset: IPreset,
|
||||
): Item[][]
|
||||
{
|
||||
): Item[][] {
|
||||
const modRewards: Item[][] = [];
|
||||
for (const rewardTypeId in containerSettings.weaponModRewardLimits)
|
||||
{
|
||||
for (const rewardTypeId in containerSettings.weaponModRewardLimits) {
|
||||
const settings = containerSettings.weaponModRewardLimits[rewardTypeId];
|
||||
const rewardCount = this.randomUtil.getInt(settings.min, settings.max);
|
||||
|
||||
// Nothing to add, skip reward type
|
||||
if (rewardCount === 0)
|
||||
{
|
||||
if (rewardCount === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -557,8 +504,7 @@ export class LootGenerator
|
||||
const relatedItems = linkedItemsToWeapon?.filter(
|
||||
(item) => item._parent === rewardTypeId && !this.itemFilterService.isItemBlacklisted(item._id),
|
||||
);
|
||||
if (!relatedItems || relatedItems.length === 0)
|
||||
{
|
||||
if (!relatedItems || relatedItems.length === 0) {
|
||||
this.logger.debug(
|
||||
`No items found to fulfil reward type: ${rewardTypeId} for weapon: ${chosenWeaponPreset._name}, skipping type`,
|
||||
);
|
||||
@ -566,8 +512,7 @@ export class LootGenerator
|
||||
}
|
||||
|
||||
// Find a random item of the desired type and add as reward
|
||||
for (let index = 0; index < rewardCount; index++)
|
||||
{
|
||||
for (let index = 0; index < rewardCount; index++) {
|
||||
const chosenItem = this.randomUtil.drawRandomFromList(relatedItems);
|
||||
const item: Item[] = [{ _id: this.hashUtil.generate(), _tpl: chosenItem[0]._id }];
|
||||
|
||||
@ -583,13 +528,11 @@ export class LootGenerator
|
||||
* @param rewardContainerDetails
|
||||
* @returns Array of item with children arrays
|
||||
*/
|
||||
public getRandomLootContainerLoot(rewardContainerDetails: RewardDetails): Item[][]
|
||||
{
|
||||
public getRandomLootContainerLoot(rewardContainerDetails: RewardDetails): Item[][] {
|
||||
const itemsToReturn: Item[][] = [];
|
||||
|
||||
// Get random items and add to newItemRequest
|
||||
for (let index = 0; index < rewardContainerDetails.rewardCount; index++)
|
||||
{
|
||||
for (let index = 0; index < rewardContainerDetails.rewardCount; index++) {
|
||||
// Pick random reward from pool, add to request object
|
||||
const chosenRewardItemTpl = this.weightedRandomHelper.getWeightedValue<string>(
|
||||
rewardContainerDetails.rewardTplPool,
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { inject, injectable } from "tsyringe";
|
||||
import { ItemHelper } from "@spt/helpers/ItemHelper";
|
||||
import { WeightedRandomHelper } from "@spt/helpers/WeightedRandomHelper";
|
||||
import { ITemplateItem } from "@spt/models/eft/common/tables/ITemplateItem";
|
||||
@ -10,14 +9,14 @@ import { DatabaseService } from "@spt/services/DatabaseService";
|
||||
import { ItemFilterService } from "@spt/services/ItemFilterService";
|
||||
import { RagfairPriceService } from "@spt/services/RagfairPriceService";
|
||||
import { SeasonalEventService } from "@spt/services/SeasonalEventService";
|
||||
import { inject, injectable } from "tsyringe";
|
||||
|
||||
/**
|
||||
* Handle the generation of dynamic PMC loot in pockets and backpacks
|
||||
* and the removal of blacklisted items
|
||||
*/
|
||||
@injectable()
|
||||
export class PMCLootGenerator
|
||||
{
|
||||
export class PMCLootGenerator {
|
||||
protected pocketLootPool: Record<string, number> = {};
|
||||
protected vestLootPool: Record<string, number> = {};
|
||||
protected backpackLootPool: Record<string, number> = {};
|
||||
@ -31,8 +30,7 @@ export class PMCLootGenerator
|
||||
@inject("RagfairPriceService") protected ragfairPriceService: RagfairPriceService,
|
||||
@inject("SeasonalEventService") protected seasonalEventService: SeasonalEventService,
|
||||
@inject("WeightedRandomHelper") protected weightedRandomHelper: WeightedRandomHelper,
|
||||
)
|
||||
{
|
||||
) {
|
||||
this.pmcConfig = this.configServer.getConfig(ConfigTypes.PMC);
|
||||
}
|
||||
|
||||
@ -40,15 +38,12 @@ export class PMCLootGenerator
|
||||
* Create an array of loot items a PMC can have in their pockets
|
||||
* @returns string array of tpls
|
||||
*/
|
||||
public generatePMCPocketLootPool(botRole: string): Record<string, number>
|
||||
{
|
||||
public generatePMCPocketLootPool(botRole: string): Record<string, number> {
|
||||
// Hydrate loot dictionary if empty
|
||||
if (Object.keys(this.pocketLootPool).length === 0)
|
||||
{
|
||||
if (Object.keys(this.pocketLootPool).length === 0) {
|
||||
const items = this.databaseService.getItems();
|
||||
const pmcPriceOverrides
|
||||
= this.databaseService.getBots().types[botRole === "pmcBEAR" ? "bear" : "usec"].inventory.items
|
||||
.Pockets;
|
||||
const pmcPriceOverrides =
|
||||
this.databaseService.getBots().types[botRole === "pmcBEAR" ? "bear" : "usec"].inventory.items.Pockets;
|
||||
|
||||
const allowedItemTypes = this.pmcConfig.pocketLoot.whitelist;
|
||||
const pmcItemBlacklist = this.pmcConfig.pocketLoot.blacklist;
|
||||
@ -59,22 +54,18 @@ export class PMCLootGenerator
|
||||
|
||||
const itemsToAdd = Object.values(items).filter(
|
||||
(item) =>
|
||||
allowedItemTypes.includes(item._parent)
|
||||
&& this.itemHelper.isValidItem(item._id)
|
||||
&& !pmcItemBlacklist.includes(item._id)
|
||||
&& !itemBlacklist.includes(item._id)
|
||||
&& this.itemFitsInto1By2Slot(item),
|
||||
allowedItemTypes.includes(item._parent) &&
|
||||
this.itemHelper.isValidItem(item._id) &&
|
||||
!pmcItemBlacklist.includes(item._id) &&
|
||||
!itemBlacklist.includes(item._id) &&
|
||||
this.itemFitsInto1By2Slot(item),
|
||||
);
|
||||
|
||||
for (const itemToAdd of itemsToAdd)
|
||||
{
|
||||
for (const itemToAdd of itemsToAdd) {
|
||||
// If pmc has override, use that. Otherwise use flea price
|
||||
if (pmcPriceOverrides[itemToAdd._id])
|
||||
{
|
||||
if (pmcPriceOverrides[itemToAdd._id]) {
|
||||
this.pocketLootPool[itemToAdd._id] = pmcPriceOverrides[itemToAdd._id];
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
// Set price of item as its weight
|
||||
const price = this.ragfairPriceService.getDynamicItemPrice(itemToAdd._id, Money.ROUBLES);
|
||||
this.pocketLootPool[itemToAdd._id] = price;
|
||||
@ -82,8 +73,7 @@ export class PMCLootGenerator
|
||||
}
|
||||
|
||||
const highestPrice = Math.max(...Object.values(this.backpackLootPool));
|
||||
for (const key of Object.keys(this.pocketLootPool))
|
||||
{
|
||||
for (const key of Object.keys(this.pocketLootPool)) {
|
||||
// Invert price so cheapest has a larger weight
|
||||
// Times by highest price so most expensive item has weight of 1
|
||||
this.pocketLootPool[key] = Math.round((1 / this.pocketLootPool[key]) * highestPrice);
|
||||
@ -99,14 +89,12 @@ export class PMCLootGenerator
|
||||
* Create an array of loot items a PMC can have in their vests
|
||||
* @returns string array of tpls
|
||||
*/
|
||||
public generatePMCVestLootPool(botRole: string): Record<string, number>
|
||||
{
|
||||
public generatePMCVestLootPool(botRole: string): Record<string, number> {
|
||||
// Hydrate loot dictionary if empty
|
||||
if (Object.keys(this.vestLootPool).length === 0)
|
||||
{
|
||||
if (Object.keys(this.vestLootPool).length === 0) {
|
||||
const items = this.databaseService.getItems();
|
||||
const pmcPriceOverrides
|
||||
= this.databaseService.getBots().types[botRole === "pmcBEAR" ? "bear" : "usec"].inventory.items
|
||||
const pmcPriceOverrides =
|
||||
this.databaseService.getBots().types[botRole === "pmcBEAR" ? "bear" : "usec"].inventory.items
|
||||
.TacticalVest;
|
||||
|
||||
const allowedItemTypes = this.pmcConfig.vestLoot.whitelist;
|
||||
@ -118,22 +106,18 @@ export class PMCLootGenerator
|
||||
|
||||
const itemsToAdd = Object.values(items).filter(
|
||||
(item) =>
|
||||
allowedItemTypes.includes(item._parent)
|
||||
&& this.itemHelper.isValidItem(item._id)
|
||||
&& !pmcItemBlacklist.includes(item._id)
|
||||
&& !itemBlacklist.includes(item._id)
|
||||
&& this.itemFitsInto2By2Slot(item),
|
||||
allowedItemTypes.includes(item._parent) &&
|
||||
this.itemHelper.isValidItem(item._id) &&
|
||||
!pmcItemBlacklist.includes(item._id) &&
|
||||
!itemBlacklist.includes(item._id) &&
|
||||
this.itemFitsInto2By2Slot(item),
|
||||
);
|
||||
|
||||
for (const itemToAdd of itemsToAdd)
|
||||
{
|
||||
for (const itemToAdd of itemsToAdd) {
|
||||
// If pmc has override, use that. Otherwise use flea price
|
||||
if (pmcPriceOverrides[itemToAdd._id])
|
||||
{
|
||||
if (pmcPriceOverrides[itemToAdd._id]) {
|
||||
this.vestLootPool[itemToAdd._id] = pmcPriceOverrides[itemToAdd._id];
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
// Set price of item as its weight
|
||||
const price = this.ragfairPriceService.getDynamicItemPrice(itemToAdd._id, Money.ROUBLES);
|
||||
this.vestLootPool[itemToAdd._id] = price;
|
||||
@ -141,8 +125,7 @@ export class PMCLootGenerator
|
||||
}
|
||||
|
||||
const highestPrice = Math.max(...Object.values(this.backpackLootPool));
|
||||
for (const key of Object.keys(this.vestLootPool))
|
||||
{
|
||||
for (const key of Object.keys(this.vestLootPool)) {
|
||||
// Invert price so cheapest has a larger weight
|
||||
// Times by highest price so most expensive item has weight of 1
|
||||
this.vestLootPool[key] = Math.round((1 / this.vestLootPool[key]) * highestPrice);
|
||||
@ -160,8 +143,7 @@ export class PMCLootGenerator
|
||||
* @param item Item to check size of
|
||||
* @returns true if it fits
|
||||
*/
|
||||
protected itemFitsInto2By2Slot(item: ITemplateItem): boolean
|
||||
{
|
||||
protected itemFitsInto2By2Slot(item: ITemplateItem): boolean {
|
||||
return item._props.Width <= 2 && item._props.Height <= 2;
|
||||
}
|
||||
|
||||
@ -171,10 +153,8 @@ export class PMCLootGenerator
|
||||
* @param item Item to check size of
|
||||
* @returns true if it fits
|
||||
*/
|
||||
protected itemFitsInto1By2Slot(item: ITemplateItem): boolean
|
||||
{
|
||||
switch (`${item._props.Width}x${item._props.Height}`)
|
||||
{
|
||||
protected itemFitsInto1By2Slot(item: ITemplateItem): boolean {
|
||||
switch (`${item._props.Width}x${item._props.Height}`) {
|
||||
case "1x1":
|
||||
case "1x2":
|
||||
case "2x1":
|
||||
@ -189,15 +169,12 @@ export class PMCLootGenerator
|
||||
* Create an array of loot items a PMC can have in their backpack
|
||||
* @returns string array of tpls
|
||||
*/
|
||||
public generatePMCBackpackLootPool(botRole: string): Record<string, number>
|
||||
{
|
||||
public generatePMCBackpackLootPool(botRole: string): Record<string, number> {
|
||||
// Hydrate loot dictionary if empty
|
||||
if (Object.keys(this.backpackLootPool).length === 0)
|
||||
{
|
||||
if (Object.keys(this.backpackLootPool).length === 0) {
|
||||
const items = this.databaseService.getItems();
|
||||
const pmcPriceOverrides
|
||||
= this.databaseService.getBots().types[botRole === "pmcBEAR" ? "bear" : "usec"].inventory.items
|
||||
.Backpack;
|
||||
const pmcPriceOverrides =
|
||||
this.databaseService.getBots().types[botRole === "pmcBEAR" ? "bear" : "usec"].inventory.items.Backpack;
|
||||
|
||||
const allowedItemTypes = this.pmcConfig.backpackLoot.whitelist;
|
||||
const pmcItemBlacklist = this.pmcConfig.backpackLoot.blacklist;
|
||||
@ -208,21 +185,17 @@ export class PMCLootGenerator
|
||||
|
||||
const itemsToAdd = Object.values(items).filter(
|
||||
(item) =>
|
||||
allowedItemTypes.includes(item._parent)
|
||||
&& this.itemHelper.isValidItem(item._id)
|
||||
&& !pmcItemBlacklist.includes(item._id)
|
||||
&& !itemBlacklist.includes(item._id),
|
||||
allowedItemTypes.includes(item._parent) &&
|
||||
this.itemHelper.isValidItem(item._id) &&
|
||||
!pmcItemBlacklist.includes(item._id) &&
|
||||
!itemBlacklist.includes(item._id),
|
||||
);
|
||||
|
||||
for (const itemToAdd of itemsToAdd)
|
||||
{
|
||||
for (const itemToAdd of itemsToAdd) {
|
||||
// If pmc has price override, use that. Otherwise use flea price
|
||||
if (pmcPriceOverrides[itemToAdd._id])
|
||||
{
|
||||
if (pmcPriceOverrides[itemToAdd._id]) {
|
||||
this.backpackLootPool[itemToAdd._id] = pmcPriceOverrides[itemToAdd._id];
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
// Set price of item as its weight
|
||||
const price = this.ragfairPriceService.getDynamicItemPrice(itemToAdd._id, Money.ROUBLES);
|
||||
this.backpackLootPool[itemToAdd._id] = price;
|
||||
@ -230,8 +203,7 @@ export class PMCLootGenerator
|
||||
}
|
||||
|
||||
const highestPrice = Math.max(...Object.values(this.backpackLootPool));
|
||||
for (const key of Object.keys(this.backpackLootPool))
|
||||
{
|
||||
for (const key of Object.keys(this.backpackLootPool)) {
|
||||
// Invert price so cheapest has a larger weight
|
||||
// Times by highest price so most expensive item has weight of 1
|
||||
this.backpackLootPool[key] = Math.round((1 / this.backpackLootPool[key]) * highestPrice);
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { inject, injectable } from "tsyringe";
|
||||
import { BotGenerator } from "@spt/generators/BotGenerator";
|
||||
import { BotGeneratorHelper } from "@spt/helpers/BotGeneratorHelper";
|
||||
import { BotHelper } from "@spt/helpers/BotHelper";
|
||||
@ -22,13 +21,13 @@ import { BotLootCacheService } from "@spt/services/BotLootCacheService";
|
||||
import { DatabaseService } from "@spt/services/DatabaseService";
|
||||
import { FenceService } from "@spt/services/FenceService";
|
||||
import { LocalisationService } from "@spt/services/LocalisationService";
|
||||
import { ICloner } from "@spt/utils/cloners/ICloner";
|
||||
import { HashUtil } from "@spt/utils/HashUtil";
|
||||
import { RandomUtil } from "@spt/utils/RandomUtil";
|
||||
import { ICloner } from "@spt/utils/cloners/ICloner";
|
||||
import { inject, injectable } from "tsyringe";
|
||||
|
||||
@injectable()
|
||||
export class PlayerScavGenerator
|
||||
{
|
||||
export class PlayerScavGenerator {
|
||||
protected playerScavConfig: IPlayerScavConfig;
|
||||
|
||||
constructor(
|
||||
@ -47,8 +46,7 @@ export class PlayerScavGenerator
|
||||
@inject("BotGenerator") protected botGenerator: BotGenerator,
|
||||
@inject("ConfigServer") protected configServer: ConfigServer,
|
||||
@inject("PrimaryCloner") protected cloner: ICloner,
|
||||
)
|
||||
{
|
||||
) {
|
||||
this.playerScavConfig = this.configServer.getConfig(ConfigTypes.PLAYERSCAV);
|
||||
}
|
||||
|
||||
@ -57,8 +55,7 @@ export class PlayerScavGenerator
|
||||
* @param sessionID session id to specify what profile is updated
|
||||
* @returns profile object
|
||||
*/
|
||||
public generate(sessionID: string): IPmcData
|
||||
{
|
||||
public generate(sessionID: string): IPmcData {
|
||||
// get karma level from profile
|
||||
const profile = this.saveServer.getProfile(sessionID);
|
||||
const pmcDataClone = this.cloner.clone(profile.characters.pmc);
|
||||
@ -68,8 +65,7 @@ export class PlayerScavGenerator
|
||||
|
||||
// use karma level to get correct karmaSettings
|
||||
const playerScavKarmaSettings = this.playerScavConfig.karmaLevel[scavKarmaLevel];
|
||||
if (!playerScavKarmaSettings)
|
||||
{
|
||||
if (!playerScavKarmaSettings) {
|
||||
this.logger.error(this.localisationService.getText("scav-missing_karma_settings", scavKarmaLevel));
|
||||
}
|
||||
|
||||
@ -144,19 +140,15 @@ export class PlayerScavGenerator
|
||||
possibleItemsToAdd: Record<string, number>,
|
||||
scavData: IBotBase,
|
||||
containersToAddTo: string[],
|
||||
): void
|
||||
{
|
||||
for (const tpl in possibleItemsToAdd)
|
||||
{
|
||||
): void {
|
||||
for (const tpl in possibleItemsToAdd) {
|
||||
const shouldAdd = this.randomUtil.getChance100(possibleItemsToAdd[tpl]);
|
||||
if (!shouldAdd)
|
||||
{
|
||||
if (!shouldAdd) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const itemResult = this.itemHelper.getItem(tpl);
|
||||
if (!itemResult[0])
|
||||
{
|
||||
if (!itemResult[0]) {
|
||||
this.logger.warning(this.localisationService.getText("scav-unable_to_add_item_to_player_scav", tpl));
|
||||
|
||||
continue;
|
||||
@ -179,8 +171,7 @@ export class PlayerScavGenerator
|
||||
scavData.Inventory,
|
||||
);
|
||||
|
||||
if (result !== ItemAddedResult.SUCCESS)
|
||||
{
|
||||
if (result !== ItemAddedResult.SUCCESS) {
|
||||
this.logger.debug(`Unable to add keycard to bot. Reason: ${ItemAddedResult[result]}`);
|
||||
}
|
||||
}
|
||||
@ -192,20 +183,17 @@ export class PlayerScavGenerator
|
||||
* @param pmcData pmc profile
|
||||
* @returns karma level
|
||||
*/
|
||||
protected getScavKarmaLevel(pmcData: IPmcData): number
|
||||
{
|
||||
protected getScavKarmaLevel(pmcData: IPmcData): number {
|
||||
const fenceInfo = pmcData.TradersInfo[Traders.FENCE];
|
||||
|
||||
// Can be empty during profile creation
|
||||
if (!fenceInfo)
|
||||
{
|
||||
if (!fenceInfo) {
|
||||
this.logger.warning(this.localisationService.getText("scav-missing_karma_level_getting_default"));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (fenceInfo.standing > 6)
|
||||
{
|
||||
if (fenceInfo.standing > 6) {
|
||||
return 6;
|
||||
}
|
||||
|
||||
@ -219,14 +207,12 @@ export class PlayerScavGenerator
|
||||
* @param botTypeForLoot bot type to use for inventory/chances
|
||||
* @returns IBotType object
|
||||
*/
|
||||
protected constructBotBaseTemplate(botTypeForLoot: string): IBotType
|
||||
{
|
||||
protected constructBotBaseTemplate(botTypeForLoot: string): IBotType {
|
||||
const baseScavType = "assault";
|
||||
const assaultBase = this.cloner.clone(this.botHelper.getBotTemplate(baseScavType));
|
||||
|
||||
// Loot bot is same as base bot, return base with no modification
|
||||
if (botTypeForLoot === baseScavType)
|
||||
{
|
||||
if (botTypeForLoot === baseScavType) {
|
||||
return assaultBase;
|
||||
}
|
||||
|
||||
@ -243,13 +229,10 @@ export class PlayerScavGenerator
|
||||
* @param karmaSettings Values to modify the bot template with
|
||||
* @param baseBotNode bot template to modify according to karama level settings
|
||||
*/
|
||||
protected adjustBotTemplateWithKarmaSpecificSettings(karmaSettings: KarmaLevel, baseBotNode: IBotType): void
|
||||
{
|
||||
protected adjustBotTemplateWithKarmaSpecificSettings(karmaSettings: KarmaLevel, baseBotNode: IBotType): void {
|
||||
// Adjust equipment chance values
|
||||
for (const equipmentKey in karmaSettings.modifiers.equipment)
|
||||
{
|
||||
if (karmaSettings.modifiers.equipment[equipmentKey] === 0)
|
||||
{
|
||||
for (const equipmentKey in karmaSettings.modifiers.equipment) {
|
||||
if (karmaSettings.modifiers.equipment[equipmentKey] === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -257,10 +240,8 @@ export class PlayerScavGenerator
|
||||
}
|
||||
|
||||
// Adjust mod chance values
|
||||
for (const modKey in karmaSettings.modifiers.mod)
|
||||
{
|
||||
if (karmaSettings.modifiers.mod[modKey] === 0)
|
||||
{
|
||||
for (const modKey in karmaSettings.modifiers.mod) {
|
||||
if (karmaSettings.modifiers.mod[modKey] === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -268,63 +249,51 @@ export class PlayerScavGenerator
|
||||
}
|
||||
|
||||
// Adjust item spawn quantity values
|
||||
for (const itemLimitkey in karmaSettings.itemLimits)
|
||||
{
|
||||
for (const itemLimitkey in karmaSettings.itemLimits) {
|
||||
baseBotNode.generation.items[itemLimitkey] = karmaSettings.itemLimits[itemLimitkey];
|
||||
}
|
||||
|
||||
// Blacklist equipment
|
||||
for (const equipmentKey in karmaSettings.equipmentBlacklist)
|
||||
{
|
||||
for (const equipmentKey in karmaSettings.equipmentBlacklist) {
|
||||
const blacklistedItemTpls = karmaSettings.equipmentBlacklist[equipmentKey];
|
||||
for (const itemToRemove of blacklistedItemTpls)
|
||||
{
|
||||
for (const itemToRemove of blacklistedItemTpls) {
|
||||
delete baseBotNode.inventory.equipment[equipmentKey][itemToRemove];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected getScavSkills(scavProfile: IPmcData): Skills
|
||||
{
|
||||
if (scavProfile.Skills)
|
||||
{
|
||||
protected getScavSkills(scavProfile: IPmcData): Skills {
|
||||
if (scavProfile.Skills) {
|
||||
return scavProfile.Skills;
|
||||
}
|
||||
|
||||
return this.getDefaultScavSkills();
|
||||
}
|
||||
|
||||
protected getDefaultScavSkills(): Skills
|
||||
{
|
||||
protected getDefaultScavSkills(): Skills {
|
||||
return { Common: [], Mastering: [], Points: 0 };
|
||||
}
|
||||
|
||||
protected getScavStats(scavProfile: IPmcData): Stats
|
||||
{
|
||||
if (scavProfile.Stats)
|
||||
{
|
||||
protected getScavStats(scavProfile: IPmcData): Stats {
|
||||
if (scavProfile.Stats) {
|
||||
return scavProfile.Stats;
|
||||
}
|
||||
|
||||
return this.profileHelper.getDefaultCounters();
|
||||
}
|
||||
|
||||
protected getScavLevel(scavProfile: IPmcData): number
|
||||
{
|
||||
protected getScavLevel(scavProfile: IPmcData): number {
|
||||
// Info can be undefined on initial account creation
|
||||
if (!scavProfile.Info?.Level)
|
||||
{
|
||||
if (!scavProfile.Info?.Level) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return scavProfile.Info.Level;
|
||||
}
|
||||
|
||||
protected getScavExperience(scavProfile: IPmcData): number
|
||||
{
|
||||
protected getScavExperience(scavProfile: IPmcData): number {
|
||||
// Info can be undefined on initial account creation
|
||||
if (!scavProfile.Info?.Experience)
|
||||
{
|
||||
if (!scavProfile.Info?.Experience) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -338,17 +307,14 @@ export class PlayerScavGenerator
|
||||
* @param pmcData pmc profile
|
||||
* @returns
|
||||
*/
|
||||
protected setScavCooldownTimer(scavData: IPmcData, pmcData: IPmcData): IPmcData
|
||||
{
|
||||
protected setScavCooldownTimer(scavData: IPmcData, pmcData: IPmcData): IPmcData {
|
||||
// Set cooldown time.
|
||||
// Make sure to apply ScavCooldownTimer bonus from Hideout if the player has it.
|
||||
let scavLockDuration = this.databaseService.getGlobals().config.SavagePlayCooldown;
|
||||
let modifier = 1;
|
||||
|
||||
for (const bonus of pmcData.Bonuses)
|
||||
{
|
||||
if (bonus.type === BonusType.SCAV_COOLDOWN_TIMER)
|
||||
{
|
||||
for (const bonus of pmcData.Bonuses) {
|
||||
if (bonus.type === BonusType.SCAV_COOLDOWN_TIMER) {
|
||||
// Value is negative, so add.
|
||||
// Also note that for scav cooldown, multiple bonuses stack additively.
|
||||
modifier += bonus.value / 100;
|
||||
@ -360,8 +326,7 @@ export class PlayerScavGenerator
|
||||
scavLockDuration *= modifier;
|
||||
|
||||
const fullProfile = this.profileHelper.getFullProfile(pmcData?.sessionId);
|
||||
if (fullProfile?.info?.edition?.toLowerCase?.().startsWith?.(AccountTypes.SPT_DEVELOPER))
|
||||
{
|
||||
if (fullProfile?.info?.edition?.toLowerCase?.().startsWith?.(AccountTypes.SPT_DEVELOPER)) {
|
||||
// Set scav cooldown timer to 10 seconds for spt developer account
|
||||
scavLockDuration = 10;
|
||||
}
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { inject, injectable } from "tsyringe";
|
||||
import { ItemHelper } from "@spt/helpers/ItemHelper";
|
||||
import { PresetHelper } from "@spt/helpers/PresetHelper";
|
||||
import { IPreset } from "@spt/models/eft/common/IGlobals";
|
||||
@ -10,10 +9,10 @@ import { ConfigServer } from "@spt/servers/ConfigServer";
|
||||
import { DatabaseServer } from "@spt/servers/DatabaseServer";
|
||||
import { SeasonalEventService } from "@spt/services/SeasonalEventService";
|
||||
import { HashUtil } from "@spt/utils/HashUtil";
|
||||
import { inject, injectable } from "tsyringe";
|
||||
|
||||
@injectable()
|
||||
export class RagfairAssortGenerator
|
||||
{
|
||||
export class RagfairAssortGenerator {
|
||||
protected generatedAssortItems: Item[][] = [];
|
||||
protected ragfairConfig: IRagfairConfig;
|
||||
|
||||
@ -34,8 +33,7 @@ export class RagfairAssortGenerator
|
||||
@inject("DatabaseServer") protected databaseServer: DatabaseServer,
|
||||
@inject("SeasonalEventService") protected seasonalEventService: SeasonalEventService,
|
||||
@inject("ConfigServer") protected configServer: ConfigServer,
|
||||
)
|
||||
{
|
||||
) {
|
||||
this.ragfairConfig = this.configServer.getConfig(ConfigTypes.RAGFAIR);
|
||||
}
|
||||
|
||||
@ -44,10 +42,8 @@ export class RagfairAssortGenerator
|
||||
* Each sub array contains item + children (if any)
|
||||
* @returns array of arrays
|
||||
*/
|
||||
public getAssortItems(): Item[][]
|
||||
{
|
||||
if (!this.assortsAreGenerated())
|
||||
{
|
||||
public getAssortItems(): Item[][] {
|
||||
if (!this.assortsAreGenerated()) {
|
||||
this.generatedAssortItems = this.generateRagfairAssortItems();
|
||||
}
|
||||
|
||||
@ -58,8 +54,7 @@ export class RagfairAssortGenerator
|
||||
* Check internal generatedAssortItems array has objects
|
||||
* @returns true if array has objects
|
||||
*/
|
||||
protected assortsAreGenerated(): boolean
|
||||
{
|
||||
protected assortsAreGenerated(): boolean {
|
||||
return this.generatedAssortItems.length > 0;
|
||||
}
|
||||
|
||||
@ -67,8 +62,7 @@ export class RagfairAssortGenerator
|
||||
* Generate an array of arrays (item + children) the flea can sell
|
||||
* @returns array of arrays (item + children)
|
||||
*/
|
||||
protected generateRagfairAssortItems(): Item[][]
|
||||
{
|
||||
protected generateRagfairAssortItems(): Item[][] {
|
||||
const results: Item[][] = [];
|
||||
|
||||
/** Get cloned items from db */
|
||||
@ -80,8 +74,7 @@ export class RagfairAssortGenerator
|
||||
const seasonalItemTplBlacklist = this.seasonalEventService.getInactiveSeasonalEventItems();
|
||||
|
||||
const presets = this.getPresetsToAdd();
|
||||
for (const preset of presets)
|
||||
{
|
||||
for (const preset of presets) {
|
||||
// Update Ids and clone
|
||||
const presetAndMods: Item[] = this.itemHelper.replaceIDs(preset._items);
|
||||
this.itemHelper.remapRootItemId(presetAndMods);
|
||||
@ -96,25 +89,21 @@ export class RagfairAssortGenerator
|
||||
results.push(presetAndMods);
|
||||
}
|
||||
|
||||
for (const item of dbItemsClone)
|
||||
{
|
||||
if (!this.itemHelper.isValidItem(item._id, this.ragfairItemInvalidBaseTypes))
|
||||
{
|
||||
for (const item of dbItemsClone) {
|
||||
if (!this.itemHelper.isValidItem(item._id, this.ragfairItemInvalidBaseTypes)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip seasonal items when not in-season
|
||||
if (
|
||||
this.ragfairConfig.dynamic.removeSeasonalItemsWhenNotInEvent
|
||||
&& !seasonalEventActive
|
||||
&& seasonalItemTplBlacklist.includes(item._id)
|
||||
)
|
||||
{
|
||||
this.ragfairConfig.dynamic.removeSeasonalItemsWhenNotInEvent &&
|
||||
!seasonalEventActive &&
|
||||
seasonalItemTplBlacklist.includes(item._id)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (processedArmorItems.includes(item._id))
|
||||
{
|
||||
if (processedArmorItems.includes(item._id)) {
|
||||
// Already processed
|
||||
continue;
|
||||
}
|
||||
@ -132,8 +121,7 @@ export class RagfairAssortGenerator
|
||||
* ragfairConfig.dynamic.showDefaultPresetsOnly decides if its all presets or just defaults
|
||||
* @returns IPreset array
|
||||
*/
|
||||
protected getPresetsToAdd(): IPreset[]
|
||||
{
|
||||
protected getPresetsToAdd(): IPreset[] {
|
||||
return this.ragfairConfig.dynamic.showDefaultPresetsOnly
|
||||
? Object.values(this.presetHelper.getDefaultPresets())
|
||||
: this.presetHelper.getAllPresets();
|
||||
@ -145,8 +133,7 @@ export class RagfairAssortGenerator
|
||||
* @param id id to add to item
|
||||
* @returns Hydrated Item object
|
||||
*/
|
||||
protected createRagfairAssortRootItem(tplId: string, id = this.hashUtil.generate()): Item
|
||||
{
|
||||
protected createRagfairAssortRootItem(tplId: string, id = this.hashUtil.generate()): Item {
|
||||
return {
|
||||
_id: id,
|
||||
_tpl: tplId,
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { inject, injectable } from "tsyringe";
|
||||
import { RagfairAssortGenerator } from "@spt/generators/RagfairAssortGenerator";
|
||||
import { BotHelper } from "@spt/helpers/BotHelper";
|
||||
import { HandbookHelper } from "@spt/helpers/HandbookHelper";
|
||||
@ -29,16 +28,16 @@ import { FenceService } from "@spt/services/FenceService";
|
||||
import { LocalisationService } from "@spt/services/LocalisationService";
|
||||
import { RagfairOfferService } from "@spt/services/RagfairOfferService";
|
||||
import { RagfairPriceService } from "@spt/services/RagfairPriceService";
|
||||
import { ICloner } from "@spt/utils/cloners/ICloner";
|
||||
import { HashUtil } from "@spt/utils/HashUtil";
|
||||
import { RandomUtil } from "@spt/utils/RandomUtil";
|
||||
import { TimeUtil } from "@spt/utils/TimeUtil";
|
||||
import { ICloner } from "@spt/utils/cloners/ICloner";
|
||||
import { inject, injectable } from "tsyringe";
|
||||
|
||||
@injectable()
|
||||
export class RagfairOfferGenerator
|
||||
{
|
||||
export class RagfairOfferGenerator {
|
||||
protected ragfairConfig: IRagfairConfig;
|
||||
protected allowedFleaPriceItemsForBarter: { tpl: string, price: number }[];
|
||||
protected allowedFleaPriceItemsForBarter: { tpl: string; price: number }[];
|
||||
|
||||
/** Internal counter to ensure each offer created has a unique value for its intId property */
|
||||
protected offerCounter = 0;
|
||||
@ -64,8 +63,7 @@ export class RagfairOfferGenerator
|
||||
@inject("ItemHelper") protected itemHelper: ItemHelper,
|
||||
@inject("ConfigServer") protected configServer: ConfigServer,
|
||||
@inject("PrimaryCloner") protected cloner: ICloner,
|
||||
)
|
||||
{
|
||||
) {
|
||||
this.ragfairConfig = this.configServer.getConfig(ConfigTypes.RAGFAIR);
|
||||
}
|
||||
|
||||
@ -86,8 +84,7 @@ export class RagfairOfferGenerator
|
||||
barterScheme: IBarterScheme[],
|
||||
loyalLevel: number,
|
||||
sellInOnePiece = false,
|
||||
): IRagfairOffer
|
||||
{
|
||||
): IRagfairOffer {
|
||||
const offer = this.createOffer(userID, time, items, barterScheme, loyalLevel, sellInOnePiece);
|
||||
this.ragfairOfferService.addOffer(offer);
|
||||
|
||||
@ -111,12 +108,10 @@ export class RagfairOfferGenerator
|
||||
barterScheme: IBarterScheme[],
|
||||
loyalLevel: number,
|
||||
isPackOffer = false,
|
||||
): IRagfairOffer
|
||||
{
|
||||
): IRagfairOffer {
|
||||
const isTrader = this.ragfairServerHelper.isTrader(userID);
|
||||
|
||||
const offerRequirements = barterScheme.map((barter) =>
|
||||
{
|
||||
const offerRequirements = barterScheme.map((barter) => {
|
||||
const offerRequirement: OfferRequirement = {
|
||||
_tpl: barter._tpl,
|
||||
count: +barter.count.toFixed(2),
|
||||
@ -124,8 +119,7 @@ export class RagfairOfferGenerator
|
||||
};
|
||||
|
||||
// Dogtags define level and side
|
||||
if (barter.level !== undefined)
|
||||
{
|
||||
if (barter.level !== undefined) {
|
||||
offerRequirement.level = barter.level;
|
||||
offerRequirement.side = barter.side;
|
||||
}
|
||||
@ -139,9 +133,7 @@ export class RagfairOfferGenerator
|
||||
|
||||
// Hydrate ammo boxes with cartridges + ensure only 1 item is present (ammo box)
|
||||
// On offer refresh dont re-add cartridges to ammo box that already has cartridges
|
||||
if (this.itemHelper.isOfBaseclass(itemsClone[0]._tpl, BaseClasses.AMMO_BOX)
|
||||
&& itemsClone.length === 1)
|
||||
{
|
||||
if (this.itemHelper.isOfBaseclass(itemsClone[0]._tpl, BaseClasses.AMMO_BOX) && itemsClone.length === 1) {
|
||||
this.itemHelper.addCartridgesToAmmoBox(itemsClone, this.itemHelper.getItem(items[0]._tpl)[1]);
|
||||
}
|
||||
|
||||
@ -176,11 +168,9 @@ export class RagfairOfferGenerator
|
||||
* @param isTrader Is the user creating the offer a trader
|
||||
* @returns IRagfairOfferUser
|
||||
*/
|
||||
createUserDataForFleaOffer(userID: string, isTrader: boolean): IRagfairOfferUser
|
||||
{
|
||||
createUserDataForFleaOffer(userID: string, isTrader: boolean): IRagfairOfferUser {
|
||||
// Trader offer
|
||||
if (isTrader)
|
||||
{
|
||||
if (isTrader) {
|
||||
return {
|
||||
id: userID,
|
||||
memberType: MemberCategory.TRADER,
|
||||
@ -188,8 +178,7 @@ export class RagfairOfferGenerator
|
||||
}
|
||||
|
||||
const isPlayerOffer = this.profileHelper.isPlayer(userID);
|
||||
if (isPlayerOffer)
|
||||
{
|
||||
if (isPlayerOffer) {
|
||||
const playerProfile = this.profileHelper.getPmcProfile(userID)!;
|
||||
return {
|
||||
id: playerProfile._id,
|
||||
@ -210,7 +199,8 @@ export class RagfairOfferGenerator
|
||||
nickname: this.botHelper.getPmcNicknameOfMaxLength(userID, 50),
|
||||
rating: this.randomUtil.getFloat(
|
||||
this.ragfairConfig.dynamic.rating.min,
|
||||
this.ragfairConfig.dynamic.rating.max),
|
||||
this.ragfairConfig.dynamic.rating.max,
|
||||
),
|
||||
isRatingGrowing: this.randomUtil.getBool(),
|
||||
avatar: undefined,
|
||||
aid: this.hashUtil.generateAccountId(),
|
||||
@ -222,11 +212,9 @@ export class RagfairOfferGenerator
|
||||
* @param offerRequirements barter requirements for offer
|
||||
* @returns rouble cost of offer
|
||||
*/
|
||||
protected convertOfferRequirementsIntoRoubles(offerRequirements: OfferRequirement[]): number
|
||||
{
|
||||
protected convertOfferRequirementsIntoRoubles(offerRequirements: OfferRequirement[]): number {
|
||||
let roublePrice = 0;
|
||||
for (const requirement of offerRequirements)
|
||||
{
|
||||
for (const requirement of offerRequirements) {
|
||||
roublePrice += this.paymentHelper.isMoneyTpl(requirement._tpl)
|
||||
? Math.round(this.calculateRoublePrice(requirement.count, requirement._tpl))
|
||||
: this.ragfairPriceService.getFleaPriceForItem(requirement._tpl) * requirement.count; // get flea price for barter offer items
|
||||
@ -241,10 +229,8 @@ export class RagfairOfferGenerator
|
||||
* @param userId persons id to get avatar of
|
||||
* @returns url of avatar
|
||||
*/
|
||||
protected getAvatarUrl(isTrader: boolean, userId: string): string
|
||||
{
|
||||
if (isTrader)
|
||||
{
|
||||
protected getAvatarUrl(isTrader: boolean, userId: string): string {
|
||||
if (isTrader) {
|
||||
return this.databaseService.getTrader(userId).base.avatar;
|
||||
}
|
||||
|
||||
@ -257,10 +243,8 @@ export class RagfairOfferGenerator
|
||||
* @param currencyType Type of currency (euro/dollar/rouble)
|
||||
* @returns count of roubles
|
||||
*/
|
||||
protected calculateRoublePrice(currencyCount: number, currencyType: string): number
|
||||
{
|
||||
if (currencyType === Money.ROUBLES)
|
||||
{
|
||||
protected calculateRoublePrice(currencyCount: number, currencyType: string): number {
|
||||
if (currencyType === Money.ROUBLES) {
|
||||
return currencyCount;
|
||||
}
|
||||
|
||||
@ -272,10 +256,8 @@ export class RagfairOfferGenerator
|
||||
* @param userId Users Id to check
|
||||
* @returns Users Id
|
||||
*/
|
||||
protected getTraderId(userId: string): string
|
||||
{
|
||||
if (this.profileHelper.isPlayer(userId))
|
||||
{
|
||||
protected getTraderId(userId: string): string {
|
||||
if (this.profileHelper.isPlayer(userId)) {
|
||||
return this.saveServer.getProfile(userId).characters.pmc._id;
|
||||
}
|
||||
|
||||
@ -287,16 +269,13 @@ export class RagfairOfferGenerator
|
||||
* @param userId User to get flea rating of
|
||||
* @returns Flea rating value
|
||||
*/
|
||||
protected getRating(userId: string): number
|
||||
{
|
||||
if (this.profileHelper.isPlayer(userId))
|
||||
{
|
||||
protected getRating(userId: string): number {
|
||||
if (this.profileHelper.isPlayer(userId)) {
|
||||
// Player offer
|
||||
return this.saveServer.getProfile(userId).characters.pmc.RagfairInfo.rating;
|
||||
}
|
||||
|
||||
if (this.ragfairServerHelper.isTrader(userId))
|
||||
{
|
||||
if (this.ragfairServerHelper.isTrader(userId)) {
|
||||
// Trader offer
|
||||
return 1;
|
||||
}
|
||||
@ -310,16 +289,13 @@ export class RagfairOfferGenerator
|
||||
* @param userID user to check rating of
|
||||
* @returns true if its growing
|
||||
*/
|
||||
protected getRatingGrowing(userID: string): boolean
|
||||
{
|
||||
if (this.profileHelper.isPlayer(userID))
|
||||
{
|
||||
protected getRatingGrowing(userID: string): boolean {
|
||||
if (this.profileHelper.isPlayer(userID)) {
|
||||
// player offer
|
||||
return this.saveServer.getProfile(userID).characters.pmc.RagfairInfo.isRatingGrowing;
|
||||
}
|
||||
|
||||
if (this.ragfairServerHelper.isTrader(userID))
|
||||
{
|
||||
if (this.ragfairServerHelper.isTrader(userID)) {
|
||||
// trader offer
|
||||
return true;
|
||||
}
|
||||
@ -335,26 +311,22 @@ export class RagfairOfferGenerator
|
||||
* @param time Time the offer is posted
|
||||
* @returns number of seconds until offer expires
|
||||
*/
|
||||
protected getOfferEndTime(userID: string, time: number): number
|
||||
{
|
||||
if (this.profileHelper.isPlayer(userID))
|
||||
{
|
||||
protected getOfferEndTime(userID: string, time: number): number {
|
||||
if (this.profileHelper.isPlayer(userID)) {
|
||||
// Player offer = current time + offerDurationTimeInHour;
|
||||
const offerDurationTimeHours
|
||||
= this.databaseService.getGlobals().config.RagFair.offerDurationTimeInHour;
|
||||
const offerDurationTimeHours = this.databaseService.getGlobals().config.RagFair.offerDurationTimeInHour;
|
||||
return this.timeUtil.getTimestamp() + Math.round(offerDurationTimeHours * TimeUtil.ONE_HOUR_AS_SECONDS);
|
||||
}
|
||||
|
||||
if (this.ragfairServerHelper.isTrader(userID))
|
||||
{
|
||||
if (this.ragfairServerHelper.isTrader(userID)) {
|
||||
// Trader offer
|
||||
return this.databaseService.getTrader(userID).base.nextResupply;
|
||||
}
|
||||
|
||||
// Generated fake-player offer
|
||||
return Math.round(
|
||||
time
|
||||
+ this.randomUtil.getInt(
|
||||
time +
|
||||
this.randomUtil.getInt(
|
||||
this.ragfairConfig.dynamic.endTimeSeconds.min,
|
||||
this.ragfairConfig.dynamic.endTimeSeconds.max,
|
||||
),
|
||||
@ -365,8 +337,7 @@ export class RagfairOfferGenerator
|
||||
* Create multiple offers for items by using a unique list of items we've generated previously
|
||||
* @param expiredOffers optional, expired offers to regenerate
|
||||
*/
|
||||
public async generateDynamicOffers(expiredOffers?: Item[][]): Promise<void>
|
||||
{
|
||||
public async generateDynamicOffers(expiredOffers?: Item[][]): Promise<void> {
|
||||
const replacingExpiredOffers = Boolean(expiredOffers?.length);
|
||||
|
||||
// get assort items from param if they exist, otherwise grab freshly generated assorts
|
||||
@ -375,9 +346,11 @@ export class RagfairOfferGenerator
|
||||
: this.ragfairAssortGenerator.getAssortItems();
|
||||
|
||||
// Create offers for each item set concurrently
|
||||
await Promise.all(assortItemsToProcess.map((assortItemWithChildren) =>
|
||||
await Promise.all(
|
||||
assortItemsToProcess.map((assortItemWithChildren) =>
|
||||
this.createOffersFromAssort(assortItemWithChildren, replacingExpiredOffers, this.ragfairConfig.dynamic),
|
||||
));
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -389,20 +362,17 @@ export class RagfairOfferGenerator
|
||||
assortItemWithChildren: Item[],
|
||||
isExpiredOffer: boolean,
|
||||
config: Dynamic,
|
||||
): Promise<void>
|
||||
{
|
||||
): Promise<void> {
|
||||
const itemDetails = this.itemHelper.getItem(assortItemWithChildren[0]._tpl);
|
||||
const isPreset = this.presetHelper.isPreset(assortItemWithChildren[0].upd.sptPresetId);
|
||||
|
||||
// Only perform checks on newly generated items, skip expired items being refreshed
|
||||
if (!(isExpiredOffer || this.ragfairServerHelper.isItemValidRagfairItem(itemDetails)))
|
||||
{
|
||||
if (!(isExpiredOffer || this.ragfairServerHelper.isItemValidRagfairItem(itemDetails))) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Armor presets can hold plates above the allowed flea level, remove if necessary
|
||||
if (isPreset && this.ragfairConfig.dynamic.blacklist.enableBsgList)
|
||||
{
|
||||
if (isPreset && this.ragfairConfig.dynamic.blacklist.enableBsgList) {
|
||||
this.removeBannedPlatesFromPreset(assortItemWithChildren, this.ragfairConfig.dynamic.blacklist.armorPlate);
|
||||
}
|
||||
|
||||
@ -414,8 +384,7 @@ export class RagfairOfferGenerator
|
||||
|
||||
// Store all functions to create offers for this item and pass into Promise.all to run async
|
||||
const assortSingleOfferProcesses = [];
|
||||
for (let index = 0; index < offerCount; index++)
|
||||
{
|
||||
for (let index = 0; index < offerCount; index++) {
|
||||
// Clone the item so we don't have shared references and generate new item IDs
|
||||
const clonedAssort = this.cloner.clone(assortItemWithChildren);
|
||||
this.itemHelper.reparentItemAndChildren(clonedAssort[0], clonedAssort);
|
||||
@ -439,10 +408,8 @@ export class RagfairOfferGenerator
|
||||
protected removeBannedPlatesFromPreset(
|
||||
presetWithChildren: Item[],
|
||||
plateSettings: IArmorPlateBlacklistSettings,
|
||||
): boolean
|
||||
{
|
||||
if (!this.itemHelper.armorItemCanHoldMods(presetWithChildren[0]._tpl))
|
||||
{
|
||||
): boolean {
|
||||
if (!this.itemHelper.armorItemCanHoldMods(presetWithChildren[0]._tpl)) {
|
||||
// Cant hold armor inserts, skip
|
||||
return false;
|
||||
}
|
||||
@ -450,24 +417,20 @@ export class RagfairOfferGenerator
|
||||
const plateSlots = presetWithChildren.filter((item) =>
|
||||
this.itemHelper.getRemovablePlateSlotIds().includes(item.slotId?.toLowerCase()),
|
||||
);
|
||||
if (plateSlots.length === 0)
|
||||
{
|
||||
if (plateSlots.length === 0) {
|
||||
// Has no plate slots e.g. "front_plate", exit
|
||||
return false;
|
||||
}
|
||||
|
||||
let removedPlate = false;
|
||||
for (const plateSlot of plateSlots)
|
||||
{
|
||||
for (const plateSlot of plateSlots) {
|
||||
const plateDetails = this.itemHelper.getItem(plateSlot._tpl)[1];
|
||||
if (plateSettings.ignoreSlots.includes(plateSlot.slotId.toLowerCase()))
|
||||
{
|
||||
if (plateSettings.ignoreSlots.includes(plateSlot.slotId.toLowerCase())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const plateArmorLevel = Number.parseInt(<string>plateDetails._props.armorClass) ?? 0;
|
||||
if (plateArmorLevel > plateSettings.maxProtectionLevel)
|
||||
{
|
||||
if (plateArmorLevel > plateSettings.maxProtectionLevel) {
|
||||
presetWithChildren.splice(presetWithChildren.indexOf(plateSlot), 1);
|
||||
removedPlate = true;
|
||||
}
|
||||
@ -487,8 +450,7 @@ export class RagfairOfferGenerator
|
||||
itemWithChildren: Item[],
|
||||
isPreset: boolean,
|
||||
itemDetails: [boolean, ITemplateItem],
|
||||
): Promise<void>
|
||||
{
|
||||
): Promise<void> {
|
||||
// Set stack size to random value
|
||||
itemWithChildren[0].upd.StackObjectsCount = this.ragfairServerHelper.calculateDynamicStackCount(
|
||||
itemWithChildren[0]._tpl,
|
||||
@ -496,11 +458,11 @@ export class RagfairOfferGenerator
|
||||
);
|
||||
|
||||
const isBarterOffer = this.randomUtil.getChance100(this.ragfairConfig.dynamic.barter.chancePercent);
|
||||
const isPackOffer
|
||||
= this.randomUtil.getChance100(this.ragfairConfig.dynamic.pack.chancePercent)
|
||||
&& !isBarterOffer
|
||||
&& itemWithChildren.length === 1
|
||||
&& this.itemHelper.isOfBaseclasses(
|
||||
const isPackOffer =
|
||||
this.randomUtil.getChance100(this.ragfairConfig.dynamic.pack.chancePercent) &&
|
||||
!isBarterOffer &&
|
||||
itemWithChildren.length === 1 &&
|
||||
this.itemHelper.isOfBaseclasses(
|
||||
itemWithChildren[0]._tpl,
|
||||
this.ragfairConfig.dynamic.pack.itemTypeWhitelist,
|
||||
);
|
||||
@ -508,27 +470,23 @@ export class RagfairOfferGenerator
|
||||
const randomUserId = this.hashUtil.generate();
|
||||
|
||||
// Remove removable plates if % check passes
|
||||
if (this.itemHelper.armorItemCanHoldMods(itemWithChildren[0]._tpl))
|
||||
{
|
||||
if (this.itemHelper.armorItemCanHoldMods(itemWithChildren[0]._tpl)) {
|
||||
const armorConfig = this.ragfairConfig.dynamic.armor;
|
||||
|
||||
const shouldRemovePlates = this.randomUtil.getChance100(armorConfig.removeRemovablePlateChance);
|
||||
if (shouldRemovePlates && this.itemHelper.armorItemHasRemovablePlateSlots(itemWithChildren[0]._tpl))
|
||||
{
|
||||
if (shouldRemovePlates && this.itemHelper.armorItemHasRemovablePlateSlots(itemWithChildren[0]._tpl)) {
|
||||
const offerItemPlatesToRemove = itemWithChildren.filter((item) =>
|
||||
armorConfig.plateSlotIdToRemovePool.includes(item.slotId?.toLowerCase()),
|
||||
);
|
||||
|
||||
for (const plateItem of offerItemPlatesToRemove)
|
||||
{
|
||||
for (const plateItem of offerItemPlatesToRemove) {
|
||||
itemWithChildren.splice(itemWithChildren.indexOf(plateItem), 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let barterScheme: IBarterScheme[];
|
||||
if (isPackOffer)
|
||||
{
|
||||
if (isPackOffer) {
|
||||
// Set pack size
|
||||
const stackSize = this.randomUtil.getInt(
|
||||
this.ragfairConfig.dynamic.pack.itemCountMin,
|
||||
@ -538,15 +496,11 @@ export class RagfairOfferGenerator
|
||||
|
||||
// Don't randomise pack items
|
||||
barterScheme = this.createCurrencyBarterScheme(itemWithChildren, isPackOffer, stackSize);
|
||||
}
|
||||
else if (isBarterOffer)
|
||||
{
|
||||
} else if (isBarterOffer) {
|
||||
// Apply randomised properties
|
||||
this.randomiseOfferItemUpdProperties(randomUserId, itemWithChildren, itemDetails[1]);
|
||||
barterScheme = this.createBarterBarterScheme(itemWithChildren);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
// Apply randomised properties
|
||||
this.randomiseOfferItemUpdProperties(randomUserId, itemWithChildren, itemDetails[1]);
|
||||
barterScheme = this.createCurrencyBarterScheme(itemWithChildren, isPackOffer);
|
||||
@ -566,8 +520,7 @@ export class RagfairOfferGenerator
|
||||
* Generate trader offers on flea using the traders assort data
|
||||
* @param traderID Trader to generate offers for
|
||||
*/
|
||||
public generateFleaOffersForTrader(traderID: string): void
|
||||
{
|
||||
public generateFleaOffersForTrader(traderID: string): void {
|
||||
// Purge
|
||||
this.ragfairOfferService.removeAllOffersByTrader(traderID);
|
||||
|
||||
@ -576,8 +529,7 @@ export class RagfairOfferGenerator
|
||||
const assorts = trader.assort;
|
||||
|
||||
// Trader assorts / assort items are missing
|
||||
if (!assorts?.items?.length)
|
||||
{
|
||||
if (!assorts?.items?.length) {
|
||||
this.logger.error(
|
||||
this.localisationService.getText(
|
||||
"ragfair-no_trader_assorts_cant_generate_flea_offers",
|
||||
@ -588,28 +540,23 @@ export class RagfairOfferGenerator
|
||||
}
|
||||
|
||||
const blacklist = this.ragfairConfig.dynamic.blacklist;
|
||||
for (const item of assorts.items)
|
||||
{
|
||||
for (const item of assorts.items) {
|
||||
// We only want to process 'base/root' items, no children
|
||||
if (item.slotId !== "hideout")
|
||||
{
|
||||
if (item.slotId !== "hideout") {
|
||||
// skip mod items
|
||||
continue;
|
||||
}
|
||||
|
||||
// Run blacklist check on trader offers
|
||||
if (blacklist.traderItems)
|
||||
{
|
||||
if (blacklist.traderItems) {
|
||||
const itemDetails = this.itemHelper.getItem(item._tpl);
|
||||
if (!itemDetails[0])
|
||||
{
|
||||
if (!itemDetails[0]) {
|
||||
this.logger.warning(this.localisationService.getText("ragfair-tpl_not_a_valid_item", item._tpl));
|
||||
continue;
|
||||
}
|
||||
|
||||
// Don't include items that BSG has blacklisted from flea
|
||||
if (blacklist.enableBsgList && !itemDetails[1]._props.CanSellOnRagfair)
|
||||
{
|
||||
if (blacklist.enableBsgList && !itemDetails[1]._props.CanSellOnRagfair) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@ -620,8 +567,7 @@ export class RagfairOfferGenerator
|
||||
: [...[item], ...this.itemHelper.findAndReturnChildrenByAssort(item._id, assorts.items)];
|
||||
|
||||
const barterScheme = assorts.barter_scheme[item._id];
|
||||
if (!barterScheme)
|
||||
{
|
||||
if (!barterScheme) {
|
||||
this.logger.warning(
|
||||
this.localisationService.getText("ragfair-missing_barter_scheme", {
|
||||
itemId: item._id,
|
||||
@ -649,23 +595,19 @@ export class RagfairOfferGenerator
|
||||
* @param itemWithMods Item and mods, get condition of first item (only first array item is modified)
|
||||
* @param itemDetails db details of first item
|
||||
*/
|
||||
protected randomiseOfferItemUpdProperties(userID: string, itemWithMods: Item[], itemDetails: ITemplateItem): void
|
||||
{
|
||||
protected randomiseOfferItemUpdProperties(userID: string, itemWithMods: Item[], itemDetails: ITemplateItem): void {
|
||||
// Add any missing properties to first item in array
|
||||
this.addMissingConditions(itemWithMods[0]);
|
||||
|
||||
if (!(this.profileHelper.isPlayer(userID) || this.ragfairServerHelper.isTrader(userID)))
|
||||
{
|
||||
if (!(this.profileHelper.isPlayer(userID) || this.ragfairServerHelper.isTrader(userID))) {
|
||||
const parentId = this.getDynamicConditionIdForTpl(itemDetails._id);
|
||||
if (!parentId)
|
||||
{
|
||||
if (!parentId) {
|
||||
// No condition details found, don't proceed with modifying item conditions
|
||||
return;
|
||||
}
|
||||
|
||||
// Roll random chance to randomise item condition
|
||||
if (this.randomUtil.getChance100(this.ragfairConfig.dynamic.condition[parentId].conditionChance * 100))
|
||||
{
|
||||
if (this.randomUtil.getChance100(this.ragfairConfig.dynamic.condition[parentId].conditionChance * 100)) {
|
||||
this.randomiseItemCondition(parentId, itemWithMods, itemDetails);
|
||||
}
|
||||
}
|
||||
@ -676,14 +618,11 @@ export class RagfairOfferGenerator
|
||||
* @param tpl Item to look for matching condition object
|
||||
* @returns condition id
|
||||
*/
|
||||
protected getDynamicConditionIdForTpl(tpl: string): string | undefined
|
||||
{
|
||||
protected getDynamicConditionIdForTpl(tpl: string): string | undefined {
|
||||
// Get keys from condition config dictionary
|
||||
const configConditions = Object.keys(this.ragfairConfig.dynamic.condition);
|
||||
for (const baseClass of configConditions)
|
||||
{
|
||||
if (this.itemHelper.isOfBaseclass(tpl, baseClass))
|
||||
{
|
||||
for (const baseClass of configConditions) {
|
||||
if (this.itemHelper.isOfBaseclass(tpl, baseClass)) {
|
||||
return baseClass;
|
||||
}
|
||||
}
|
||||
@ -701,8 +640,7 @@ export class RagfairOfferGenerator
|
||||
conditionSettingsId: string,
|
||||
itemWithMods: Item[],
|
||||
itemDetails: ITemplateItem,
|
||||
): void
|
||||
{
|
||||
): void {
|
||||
const rootItem = itemWithMods[0];
|
||||
|
||||
const itemConditionValues: Condition = this.ragfairConfig.dynamic.condition[conditionSettingsId];
|
||||
@ -714,18 +652,16 @@ export class RagfairOfferGenerator
|
||||
|
||||
// Randomise armor + plates + armor related things
|
||||
if (
|
||||
this.itemHelper.armorItemCanHoldMods(rootItem._tpl)
|
||||
|| this.itemHelper.isOfBaseclasses(rootItem._tpl, [BaseClasses.ARMOR_PLATE, BaseClasses.ARMORED_EQUIPMENT])
|
||||
)
|
||||
{
|
||||
this.itemHelper.armorItemCanHoldMods(rootItem._tpl) ||
|
||||
this.itemHelper.isOfBaseclasses(rootItem._tpl, [BaseClasses.ARMOR_PLATE, BaseClasses.ARMORED_EQUIPMENT])
|
||||
) {
|
||||
this.randomiseArmorDurabilityValues(itemWithMods, currentMultiplier, maxMultiplier);
|
||||
|
||||
// Add hits to visor
|
||||
const visorMod = itemWithMods.find(
|
||||
(item) => item.parentId === BaseClasses.ARMORED_EQUIPMENT && item.slotId === "mod_equipment_000",
|
||||
);
|
||||
if (this.randomUtil.getChance100(25) && visorMod)
|
||||
{
|
||||
if (this.randomUtil.getChance100(25) && visorMod) {
|
||||
this.itemHelper.addUpdObjectToItem(visorMod);
|
||||
|
||||
visorMod.upd.FaceShield = { Hits: this.randomUtil.getInt(1, 3) };
|
||||
@ -735,48 +671,42 @@ export class RagfairOfferGenerator
|
||||
}
|
||||
|
||||
// Randomise Weapons
|
||||
if (this.itemHelper.isOfBaseclass(itemDetails._id, BaseClasses.WEAPON))
|
||||
{
|
||||
if (this.itemHelper.isOfBaseclass(itemDetails._id, BaseClasses.WEAPON)) {
|
||||
this.randomiseWeaponDurability(itemWithMods[0], itemDetails, maxMultiplier, currentMultiplier);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (rootItem.upd.MedKit)
|
||||
{
|
||||
if (rootItem.upd.MedKit) {
|
||||
// Randomize health
|
||||
rootItem.upd.MedKit.HpResource = Math.round(rootItem.upd.MedKit.HpResource * maxMultiplier) || 1;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (rootItem.upd.Key && itemDetails._props.MaximumNumberOfUsage > 1)
|
||||
{
|
||||
if (rootItem.upd.Key && itemDetails._props.MaximumNumberOfUsage > 1) {
|
||||
// Randomize key uses
|
||||
rootItem.upd.Key.NumberOfUsages
|
||||
= Math.round(itemDetails._props.MaximumNumberOfUsage * (1 - maxMultiplier)) || 0;
|
||||
rootItem.upd.Key.NumberOfUsages =
|
||||
Math.round(itemDetails._props.MaximumNumberOfUsage * (1 - maxMultiplier)) || 0;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (rootItem.upd.FoodDrink)
|
||||
{
|
||||
if (rootItem.upd.FoodDrink) {
|
||||
// randomize food/drink value
|
||||
rootItem.upd.FoodDrink.HpPercent = Math.round(itemDetails._props.MaxResource * maxMultiplier) || 1;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (rootItem.upd.RepairKit)
|
||||
{
|
||||
if (rootItem.upd.RepairKit) {
|
||||
// randomize repair kit (armor/weapon) uses
|
||||
rootItem.upd.RepairKit.Resource = Math.round(itemDetails._props.MaxRepairResource * maxMultiplier) || 1;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.itemHelper.isOfBaseclass(itemDetails._id, BaseClasses.FUEL))
|
||||
{
|
||||
if (this.itemHelper.isOfBaseclass(itemDetails._id, BaseClasses.FUEL)) {
|
||||
const totalCapacity = itemDetails._props.MaxResource;
|
||||
const remainingFuel = Math.round(totalCapacity * maxMultiplier);
|
||||
rootItem.upd.Resource = { UnitsConsumed: totalCapacity - remainingFuel, Value: remainingFuel };
|
||||
@ -795,14 +725,11 @@ export class RagfairOfferGenerator
|
||||
itemDbDetails: ITemplateItem,
|
||||
maxMultiplier: number,
|
||||
currentMultiplier: number,
|
||||
): void
|
||||
{
|
||||
): void {
|
||||
// Max
|
||||
const baseMaxDurability = itemDbDetails._props.MaxDurability;
|
||||
const lowestMaxDurability = this.randomUtil.getFloat(maxMultiplier, 1) * baseMaxDurability;
|
||||
const chosenMaxDurability = Math.round(
|
||||
this.randomUtil.getFloat(lowestMaxDurability, baseMaxDurability),
|
||||
);
|
||||
const chosenMaxDurability = Math.round(this.randomUtil.getFloat(lowestMaxDurability, baseMaxDurability));
|
||||
|
||||
// Current
|
||||
const lowestCurrentDurability = this.randomUtil.getFloat(currentMultiplier, 1) * chosenMaxDurability;
|
||||
@ -824,18 +751,14 @@ export class RagfairOfferGenerator
|
||||
armorWithMods: Item[],
|
||||
currentMultiplier: number,
|
||||
maxMultiplier: number,
|
||||
): void
|
||||
{
|
||||
for (const armorItem of armorWithMods)
|
||||
{
|
||||
): void {
|
||||
for (const armorItem of armorWithMods) {
|
||||
const itemDbDetails = this.itemHelper.getItem(armorItem._tpl)[1];
|
||||
if (Number.parseInt(<string>itemDbDetails._props.armorClass) > 1)
|
||||
{
|
||||
if (Number.parseInt(<string>itemDbDetails._props.armorClass) > 1) {
|
||||
this.itemHelper.addUpdObjectToItem(armorItem);
|
||||
|
||||
const baseMaxDurability = itemDbDetails._props.MaxDurability;
|
||||
const lowestMaxDurability
|
||||
= this.randomUtil.getFloat(maxMultiplier, 1) * baseMaxDurability;
|
||||
const lowestMaxDurability = this.randomUtil.getFloat(maxMultiplier, 1) * baseMaxDurability;
|
||||
const chosenMaxDurability = Math.round(
|
||||
this.randomUtil.getFloat(lowestMaxDurability, baseMaxDurability),
|
||||
);
|
||||
@ -859,8 +782,7 @@ export class RagfairOfferGenerator
|
||||
* HpResource for medical items
|
||||
* @param item item to add conditions to
|
||||
*/
|
||||
protected addMissingConditions(item: Item): void
|
||||
{
|
||||
protected addMissingConditions(item: Item): void {
|
||||
const props = this.itemHelper.getItem(item._tpl)[1]._props;
|
||||
const isRepairable = "Durability" in props;
|
||||
const isMedkit = "MaxHpResource" in props;
|
||||
@ -868,37 +790,32 @@ export class RagfairOfferGenerator
|
||||
const isConsumable = props.MaxResource > 1 && "foodUseTime" in props;
|
||||
const isRepairKit = "MaxRepairResource" in props;
|
||||
|
||||
if (isRepairable && props.Durability > 0)
|
||||
{
|
||||
if (isRepairable && props.Durability > 0) {
|
||||
item.upd.Repairable = { Durability: props.Durability, MaxDurability: props.Durability };
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (isMedkit && props.MaxHpResource > 0)
|
||||
{
|
||||
if (isMedkit && props.MaxHpResource > 0) {
|
||||
item.upd.MedKit = { HpResource: props.MaxHpResource };
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (isKey)
|
||||
{
|
||||
if (isKey) {
|
||||
item.upd.Key = { NumberOfUsages: 0 };
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Food/drink
|
||||
if (isConsumable)
|
||||
{
|
||||
if (isConsumable) {
|
||||
item.upd.FoodDrink = { HpPercent: props.MaxResource };
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (isRepairKit)
|
||||
{
|
||||
if (isRepairKit) {
|
||||
item.upd.RepairKit = { Resource: props.MaxRepairResource };
|
||||
}
|
||||
}
|
||||
@ -908,8 +825,7 @@ export class RagfairOfferGenerator
|
||||
* @param offerItems Items for sale in offer
|
||||
* @returns Barter scheme
|
||||
*/
|
||||
protected createBarterBarterScheme(offerItems: Item[]): IBarterScheme[]
|
||||
{
|
||||
protected createBarterBarterScheme(offerItems: Item[]): IBarterScheme[] {
|
||||
// get flea price of item being sold
|
||||
const priceOfItemOffer = this.ragfairPriceService.getDynamicOfferPriceForOffer(
|
||||
offerItems,
|
||||
@ -918,8 +834,7 @@ export class RagfairOfferGenerator
|
||||
);
|
||||
|
||||
// Dont make items under a designated rouble value into barter offers
|
||||
if (priceOfItemOffer < this.ragfairConfig.dynamic.barter.minRoubleCostToBecomeBarter)
|
||||
{
|
||||
if (priceOfItemOffer < this.ragfairConfig.dynamic.barter.minRoubleCostToBecomeBarter) {
|
||||
return this.createCurrencyBarterScheme(offerItems, false);
|
||||
}
|
||||
|
||||
@ -940,14 +855,13 @@ export class RagfairOfferGenerator
|
||||
// Filter possible barters to items that match the price range + not itself
|
||||
const filtered = fleaPrices.filter(
|
||||
(x) =>
|
||||
x.price >= desiredItemCost - offerCostVariance
|
||||
&& x.price <= desiredItemCost + offerCostVariance
|
||||
&& x.tpl !== offerItems[0]._tpl,
|
||||
x.price >= desiredItemCost - offerCostVariance &&
|
||||
x.price <= desiredItemCost + offerCostVariance &&
|
||||
x.tpl !== offerItems[0]._tpl,
|
||||
);
|
||||
|
||||
// No items on flea have a matching price, fall back to currency
|
||||
if (filtered.length === 0)
|
||||
{
|
||||
if (filtered.length === 0) {
|
||||
return this.createCurrencyBarterScheme(offerItems, false);
|
||||
}
|
||||
|
||||
@ -961,11 +875,9 @@ export class RagfairOfferGenerator
|
||||
* Get an array of flea prices + item tpl, cached in generator class inside `allowedFleaPriceItemsForBarter`
|
||||
* @returns array with tpl/price values
|
||||
*/
|
||||
protected getFleaPricesAsArray(): { tpl: string, price: number }[]
|
||||
{
|
||||
protected getFleaPricesAsArray(): { tpl: string; price: number }[] {
|
||||
// Generate if needed
|
||||
if (!this.allowedFleaPriceItemsForBarter)
|
||||
{
|
||||
if (!this.allowedFleaPriceItemsForBarter) {
|
||||
const fleaPrices = this.databaseService.getPrices();
|
||||
|
||||
// Only get prices for items that also exist in items.json
|
||||
@ -993,11 +905,10 @@ export class RagfairOfferGenerator
|
||||
offerWithChildren: Item[],
|
||||
isPackOffer: boolean,
|
||||
multipler = 1,
|
||||
): IBarterScheme[]
|
||||
{
|
||||
): IBarterScheme[] {
|
||||
const currency = this.ragfairServerHelper.getDynamicOfferCurrency();
|
||||
const price = this.ragfairPriceService.getDynamicOfferPriceForOffer(offerWithChildren, currency, isPackOffer)
|
||||
* multipler;
|
||||
const price =
|
||||
this.ragfairPriceService.getDynamicOfferPriceForOffer(offerWithChildren, currency, isPackOffer) * multipler;
|
||||
|
||||
return [{ count: price, _tpl: currency }];
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user