0
0
mirror of https://github.com/sp-tarkov/server.git synced 2025-02-12 13:50: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:
Refringe 2024-07-23 11:12:53 -04:00
parent 7c76342ee2
commit 5740774a46
Signed by: Refringe
SSH Key Fingerprint: SHA256:t865XsQpfTeqPRBMN2G6+N8wlDjkgUCZF3WGW6O9N/k
755 changed files with 22787 additions and 31002 deletions

View File

@ -15,14 +15,10 @@
"keepClassNames": true,
"baseUrl": "./",
"paths": {
"@spt/*": [
"src/*"
]
"@spt/*": ["src/*"]
}
},
"exclude": [
"node_modules/"
],
"exclude": ["node_modules/"],
"module": {
"type": "commonjs",
"strict": false,

View File

@ -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,10 +337,8 @@
"max": 50
}
},
"armorLevelWhitelist": [
0
],
"armorLevelWhitelist": [0],
"allowBossItems": false
}
}
}
}

View File

@ -47,8 +47,8 @@
"sectactPriestEvent": 10,
"pmcUSEC": 15,
"pmcBEAR": 15,
"peacemaker": 10,
"skier": 10
"peacemaker": 10,
"skier": 10
},
"bosses": [
"bossBully",
@ -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"],
@ -430,8 +427,8 @@
"544fb37f4bdc2dee738b4567": 2
},
"shooterbtr": {},
"skier": {},
"peacemaker": {}
"skier": {},
"peacemaker": {}
},
"equipment": {
"assault": {
@ -495,9 +492,7 @@
"lightIsActiveNightChancePercent": 90,
"laserIsActiveChancePercent": 85,
"forceStock": true,
"weaponSlotIdsToMakeRequired": [
"mod_reciever"
]
"weaponSlotIdsToMakeRequired": ["mod_reciever"]
},
"bossbully": {
"nvgIsActiveChanceDayPercent": 10,
@ -776,11 +771,9 @@
"scopeLimit": 1,
"lightLaserLimit": 1
},
"weaponSlotIdsToMakeRequired": [
"mod_reciever"
]
"weaponSlotIdsToMakeRequired": ["mod_reciever"]
},
"peacemaker": {
"peacemaker": {
"nvgIsActiveChanceDayPercent": 10,
"nvgIsActiveChanceNightPercent": 95,
"faceShieldIsActiveChancePercent": 100,
@ -793,7 +786,7 @@
"lightLaserLimit": 1
}
},
"skier": {
"skier": {
"nvgIsActiveChanceDayPercent": 10,
"nvgIsActiveChanceNightPercent": 95,
"faceShieldIsActiveChancePercent": 100,
@ -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": {
@ -2705,7 +2664,7 @@
"1": 8,
"2": 4,
"5": 4,
"10": 1
"10": 1
}
},
"assault": {
@ -2735,7 +2694,14 @@
}
}
},
"lowProfileGasBlockTpls": ["61702f1b67085e45ef140b26", "5dfa3d45dfc58d14537c20b0", "5bb20dcad4351e3bac1212da", "56eabcd4d2720b66698b4574", "6065dc8a132d4d12c81fd8e3", "55d4af3a4bdc2d972f8b456f"],
"lowProfileGasBlockTpls": [
"61702f1b67085e45ef140b26",
"5dfa3d45dfc58d14537c20b0",
"5bb20dcad4351e3bac1212da",
"56eabcd4d2720b66698b4574",
"6065dc8a132d4d12c81fd8e3",
"55d4af3a4bdc2d972f8b456f"
],
"disableLootOnBotTypes": [],
"assaultToBossConversion": {
"bossConvertEnabled": false,
@ -2758,4 +2724,4 @@
}
}
}
}
}

View File

@ -27,11 +27,11 @@
"commandoFeatures": {
"giveCommandEnabled": true
},
"commandUseLimits": {
"StashRows": 15
}
"commandUseLimits": {
"StashRows": 15
}
},
"createNewProfileTypesBlacklist": []
"createNewProfileTypesBlacklist": []
},
"customWatermarkLocaleKeys": []
"customWatermarkLocaleKeys": []
}

File diff suppressed because it is too large Load Diff

View File

@ -7,4 +7,4 @@
"health": true,
"effects": true
}
}
}

View File

@ -8,5 +8,5 @@
"expCraftAmount": 10,
"overrideCraftTimeSeconds": -1,
"overrideBuildTimeSeconds": -1,
"updateProfileHideoutWhenActiveWithinMinutes": 90
}
"updateProfileHideoutWhenActiveWithinMinutes": 90
}

View File

@ -1,9 +1,9 @@
{
"ip": "127.0.0.1",
"port": 6969,
"backendIp": "127.0.0.1",
"backendPort": 6969,
"backendIp": "127.0.0.1",
"backendPort": 6969,
"webSocketPingDelayMs": 90000,
"logRequests": true,
"serverImagePathOverride": {}
}
}

View File

@ -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",
@ -37,4 +29,4 @@
"pmcKillProbabilityForScavGain": 0.2,
"keepFiRSecureContainerOnDeath": false,
"playerScavHostileChancePercent": 15
}
}

View File

@ -3,17 +3,10 @@
"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,
"chanceNoAttachmentsTakenPercent": 10
}
}

View File

@ -49,10 +49,7 @@
"6241c2c2117ad530666a5108",
"5580239d4bdc2de7118b4583"
],
"lootableItemBlacklist": [
"660bbc47c38b837877075e47",
"660bc341c38b837877075e4c"
],
"lootableItemBlacklist": ["660bbc47c38b837877075e47", "660bc341c38b837877075e4c"],
"rewardItemBlacklist": [
"58ac60eb86f77401897560ff",
"5e997f0b86f7741ac73993e2",
@ -101,7 +98,7 @@
"6662e9f37fa79a6d83730fa0",
"6662ea05f6259762c56f3189",
"6638a5474e92f038531e210e",
"65ddcc9cfa85b9f17d0dfb07"
"65ddcc9cfa85b9f17d0dfb07"
],
"bossItems": [
"6275303a9f372d6ea97f9ec7",
@ -148,4 +145,4 @@
"63a898a328e385334e0640a5": 5000,
"63a897c6b1ff6e29734fcc95": 10000
}
}
}

View File

@ -35,4 +35,4 @@
"zh-*": "zh-cn",
"es-*": "es-es"
}
}
}

View File

@ -16,7 +16,7 @@
"tarkovstreets": 3,
"terminal": 1,
"sandbox": 2.8,
"sandbox_high": 2.8,
"sandbox_high": 2.8,
"town": 0
},
"staticLootMultiplier": {
@ -36,7 +36,7 @@
"tarkovstreets": 1,
"terminal": 1,
"sandbox": 1,
"sandbox_high": 1,
"sandbox_high": 1,
"town": 1
},
"customWaves": {
@ -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"
}
],
@ -795,7 +731,7 @@
"ChanceGroup": 0
}
],
"sandbox_high": [
"sandbox_high": [
{
"sptId": "pmcBEARSandboxNormalSpawn",
"BossName": "pmcBEAR",
@ -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,36 +979,29 @@
}
},
"openZones": {
"bigmap": [
"ZoneScavBase"
]
"bigmap": ["ZoneScavBase"]
},
"forcedLootSingleSpawnById": {
"bigmap": [
"5ac620eb86f7743a8e6e0da0",
"5939e5a786f77461f11c0098",
"64e74a3d4d49d23b2c39d319",
"6614230055afee107f05e998"
],
"interchange": [
"64e74a5ac2b4f829615ec336"
"6614230055afee107f05e998"
],
"interchange": ["64e74a5ac2b4f829615ec336"],
"lighthouse": [
"6331bb0d1aa9f42b804997a6",
"6398a0861c712b1e1d4dadf1",
"6399f54b0a36db13c823ad21",
"64e74a64aac4cd0a7264ecdf",
"661666458c2aa9cb1602503b"
],
"rezervbase": [
"64e74a4baac4cd0a7264ecdd",
"6398a072e301557ae24cec92"
"661666458c2aa9cb1602503b"
],
"rezervbase": ["64e74a4baac4cd0a7264ecdd", "6398a072e301557ae24cec92"],
"shoreline": [
"64e74a534d49d23b2c39d31b",
"661421c7c1f2f548c50ee649",
"6614217b6d9d5abcad0ff098",
"661423200d240a5f5d0f679b"
"661421c7c1f2f548c50ee649",
"6614217b6d9d5abcad0ff098",
"661423200d240a5f5d0f679b"
],
"tarkovstreets": [
"638df4cc7b560b03794a18d2",
@ -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,
@ -1201,7 +1112,7 @@
"minFillStaticMagazinePercent": 50,
"allowDuplicateItemsInStaticContainers": true,
"magazineLootHasAmmoChancePercent": 50,
"staticMagazineLootHasAmmoChancePercent": 0,
"staticMagazineLootHasAmmoChancePercent": 0,
"looseLootBlacklist": {},
"scavRaidTimeSettings": {
"settings": {
@ -1424,4 +1335,4 @@
"mod_equipment": 5
}
}
}
}

View File

@ -9,4 +9,4 @@
"rezervbase": {},
"tarkovstreets": {}
}
}
}

View File

@ -18,4 +18,4 @@
},
"questItems": true,
"specialSlotItems": false
}
}

View File

@ -12,4 +12,4 @@
"laboratory": false,
"rezervbase": false
}
}
}

View File

@ -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": {
@ -1271,4 +1226,4 @@
}
}
}
}
}

View File

@ -66,7 +66,7 @@
"6391fcf5744e45201147080f",
"614451b71e5874611e2c7ae5",
"6540d2162ae6d96b540afcaf",
"65ca457b4aafb5d7fc0dcb5d"
"65ca457b4aafb5d7fc0dcb5d"
]
},
"pocketLoot": {
@ -120,8 +120,8 @@
"57864c8c245977548867e7f1",
"6087e570b998180e9f76dc24",
"6391fcf5744e45201147080f",
"65ca457b4aafb5d7fc0dcb5d",
"660bbc98c38b837877075e4a"
"65ca457b4aafb5d7fc0dcb5d",
"660bbc98c38b837877075e4a"
]
},
"backpackLoot": {
@ -179,8 +179,8 @@
"6391fcf5744e45201147080f",
"614451b71e5874611e2c7ae5",
"6540d2162ae6d96b540afcaf",
"65ca457b4aafb5d7fc0dcb5d",
"660bbc98c38b837877075e4a"
"65ca457b4aafb5d7fc0dcb5d",
"660bbc98c38b837877075e4a"
]
},
"useDifficultyOverride": false,
@ -212,7 +212,7 @@
"arenaFighterEvent": 0,
"crazyAssaultEvent": 3,
"pmcBot": 5,
"pmcBEAR": 2
"pmcBEAR": 2
},
"factory4_night": {
"bossKilla": 2,
@ -233,7 +233,7 @@
"arenaFighterEvent": 0,
"crazyAssaultEvent": 3,
"pmcBot": 5,
"pmcBEAR": 2
"pmcBEAR": 2
},
"bigmap": {
"bossKilla": 2,
@ -254,7 +254,7 @@
"arenaFighterEvent": 0,
"crazyAssaultEvent": 3,
"pmcBot": 5,
"pmcBEAR": 2
"pmcBEAR": 2
},
"laboratory": {
"bossKilla": 2,
@ -275,7 +275,7 @@
"arenaFighterEvent": 0,
"crazyAssaultEvent": 3,
"pmcBot": 5,
"pmcBEAR": 2
"pmcBEAR": 2
},
"woods": {
"bossKilla": 2,
@ -297,7 +297,7 @@
"arenaFighterEvent": 0,
"crazyAssaultEvent": 3,
"pmcBot": 5,
"pmcBEAR": 2
"pmcBEAR": 2
},
"interchange": {
"bossKilla": 2,
@ -318,7 +318,7 @@
"arenaFighterEvent": 0,
"crazyAssaultEvent": 3,
"pmcBot": 5,
"pmcBEAR": 2
"pmcBEAR": 2
},
"lighthouse": {
"bossKilla": 2,
@ -339,7 +339,7 @@
"arenaFighterEvent": 0,
"crazyAssaultEvent": 3,
"pmcBot": 5,
"pmcBEAR": 2
"pmcBEAR": 2
},
"rezervbase": {
"bossKilla": 2,
@ -360,7 +360,7 @@
"arenaFighterEvent": 1,
"crazyAssaultEvent": 3,
"pmcBot": 5,
"pmcBEAR": 2
"pmcBEAR": 2
},
"shoreline": {
"bossKilla": 2,
@ -381,7 +381,7 @@
"arenaFighterEvent": 0,
"crazyAssaultEvent": 3,
"pmcBot": 5,
"pmcBEAR": 2
"pmcBEAR": 2
},
"tarkovstreets": {
"bossKilla": 2,
@ -402,7 +402,7 @@
"arenaFighterEvent": 0,
"crazyAssaultEvent": 3,
"pmcBot": 5,
"pmcBEAR": 2
"pmcBEAR": 2
},
"sandbox": {
"bossKilla": 2,
@ -425,7 +425,7 @@
"arenaFighterEvent": 1,
"crazyAssaultEvent": 1,
"pmcBot": 7,
"pmcBEAR": 2
"pmcBEAR": 2
},
"sandbox_high": {
"bossKilla": 2,
@ -448,7 +448,7 @@
"arenaFighterEvent": 1,
"crazyAssaultEvent": 1,
"pmcBot": 7,
"pmcBEAR": 2
"pmcBEAR": 2
}
},
"pmcusec": {
@ -471,7 +471,7 @@
"arenaFighterEvent": 0,
"crazyAssaultEvent": 3,
"pmcBot": 5,
"pmcUSEC": 2
"pmcUSEC": 2
},
"factory4_night": {
"bossKilla": 2,
@ -492,7 +492,7 @@
"arenaFighterEvent": 0,
"crazyAssaultEvent": 3,
"pmcBot": 5,
"pmcUSEC": 2
"pmcUSEC": 2
},
"bigmap": {
"bossKilla": 0,
@ -513,7 +513,7 @@
"arenaFighterEvent": 0,
"crazyAssaultEvent": 2,
"pmcBot": 6,
"pmcUSEC": 2
"pmcUSEC": 2
},
"laboratory": {
"bossKilla": 2,
@ -534,7 +534,7 @@
"arenaFighterEvent": 0,
"crazyAssaultEvent": 3,
"pmcBot": 5,
"pmcUSEC": 2
"pmcUSEC": 2
},
"woods": {
"bossKilla": 2,
@ -556,7 +556,7 @@
"arenaFighterEvent": 0,
"crazyAssaultEvent": 3,
"pmcBot": 5,
"pmcUSEC": 2
"pmcUSEC": 2
},
"interchange": {
"bossKilla": 2,
@ -577,7 +577,7 @@
"arenaFighterEvent": 0,
"crazyAssaultEvent": 3,
"pmcBot": 5,
"pmcUSEC": 2
"pmcUSEC": 2
},
"lighthouse": {
"bossKilla": 2,
@ -598,7 +598,7 @@
"arenaFighterEvent": 0,
"crazyAssaultEvent": 3,
"pmcBot": 5,
"pmcUSEC": 2
"pmcUSEC": 2
},
"rezervbase": {
"bossKilla": 2,
@ -619,7 +619,7 @@
"arenaFighterEvent": 1,
"crazyAssaultEvent": 3,
"pmcBot": 5,
"pmcUSEC": 2
"pmcUSEC": 2
},
"shoreline": {
"bossKilla": 2,
@ -640,7 +640,7 @@
"arenaFighterEvent": 0,
"crazyAssaultEvent": 3,
"pmcBot": 5,
"pmcUSEC": 2
"pmcUSEC": 2
},
"tarkovstreets": {
"bossKilla": 2,
@ -661,7 +661,7 @@
"arenaFighterEvent": 0,
"crazyAssaultEvent": 3,
"pmcBot": 5,
"pmcUSEC": 2
"pmcUSEC": 2
},
"sandbox": {
"bossKilla": 2,
@ -684,7 +684,7 @@
"arenaFighterEvent": 1,
"crazyAssaultEvent": 1,
"pmcBot": 7,
"pmcUSEC": 2
"pmcUSEC": 2
},
"sandbox_high": {
"bossKilla": 2,
@ -707,7 +707,7 @@
"arenaFighterEvent": 1,
"crazyAssaultEvent": 1,
"pmcBot": 7,
"pmcUSEC": 2
"pmcUSEC": 2
}
}
},

File diff suppressed because it is too large Load Diff

View File

@ -276,10 +276,7 @@
},
"armor": {
"removeRemovablePlateChance": 40,
"plateSlotIdToRemovePool": [
"front_plate",
"back_plate"
]
"plateSlotIdToRemovePool": ["front_plate", "back_plate"]
},
"itemPriceMultiplier": {
"5737292724597765e5728562": 6,
@ -303,7 +300,7 @@
"62e910aaf957f2915e0a5e36",
"5447e1d04bdc2dff2f8b4567",
"57bef4c42459772e8d35a53b",
"55802f4a4bdc2ddb688b4569"
"55802f4a4bdc2ddb688b4569"
],
"removeSeasonalItemsWhenNotInEvent": true,
"blacklist": {
@ -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": []
@ -337,4 +331,4 @@
}
}
}
}
}

View File

@ -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,
@ -183,4 +183,4 @@
}
}
}
}
}

View File

@ -86,9 +86,7 @@
"ammoRewardChancePercent": 10,
"minStackSize": 10,
"ammoRewardBlacklist": {
"common": [
"59e690b686f7746c9f75e848"
],
"common": ["59e690b686f7746c9f75e848"],
"rare": [],
"superrare": []
},
@ -110,4 +108,4 @@
"allowMultipleMoneyRewardsPerRarity": false,
"allowMultipleAmmoRewardsPerRarity": true,
"allowBossItemsAsRewards": false
}
}

View File

@ -721,4 +721,4 @@
"endMonth": "2"
}
]
}
}

View File

@ -202,11 +202,11 @@
}
},
"chancePlateExistsInArmorPercent": {
"3": 95,
"4": 75,
"5": 25,
"6": 10
},
"3": 95,
"4": 75,
"5": 25,
"6": 10
},
"armorMaxDurabilityPercentMinMax": {
"current": {
"min": 50,

View File

@ -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,
@ -100,55 +38,55 @@
"max": 764
}
},
"seasonDates": [
{
"seasonType": 0,
"name": "SUMMER",
"startDay": "2",
"seasonDates": [
{
"seasonType": 0,
"name": "SUMMER",
"startDay": "2",
"startMonth": "6",
"endDay": "1",
"endMonth": "8"
},
{
"seasonType": 1,
"name": "AUTUMN",
"startDay": "2",
},
{
"seasonType": 1,
"name": "AUTUMN",
"startDay": "2",
"startMonth": "8",
"endDay": "1",
"endMonth": "11"
},
{
"seasonType": 2,
"name": "WINTER_END",
"startDay": "2",
},
{
"seasonType": 2,
"name": "WINTER_END",
"startDay": "2",
"startMonth": "11",
"endDay": "31",
"endMonth": "12"
},
{
"seasonType": 2,
"name": "WINTER_START",
"startDay": "1",
},
{
"seasonType": 2,
"name": "WINTER_START",
"startDay": "1",
"startMonth": "1",
"endDay": "24",
"endMonth": "3"
},
{
"seasonType": 3,
"name": "SPRING",
"startDay": "25",
},
{
"seasonType": 3,
"name": "SPRING",
"startDay": "25",
"startMonth": "3",
"endDay": "1",
"endMonth": "6"
},
{
"seasonType": 4,
"name": "STORM",
"startDay": "24",
},
{
"seasonType": 4,
"name": "STORM",
"startDay": "24",
"startMonth": "10",
"endDay": "4",
"endMonth": "11"
}
],
"overrideSeason": null
}
}
],
"overrideSeason": null
}

View File

@ -34,16 +34,16 @@
"enabled": true,
"formatWithErrors": false,
"ignore": [
"**/.git",
"**/.pkg-cache",
"**/.vscode",
"**/build",
"**/node_modules",
"**/types",
"**/tests/__cache__",
"**/tests/__coverage__",
"**/.editorconfig"
],
"**/.git",
"**/.pkg-cache",
"**/.vscode",
"**/build",
"**/node_modules",
"**/types",
"**/tests/__cache__",
"**/tests/__coverage__",
"**/.editorconfig"
],
"attributePosition": "auto",
"indentStyle": "space",
"indentWidth": 4,

View File

@ -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 }, {
ProductName: manifest.author,
FileDescription: manifest.description,
CompanyName: manifest.name,
LegalCopyright: manifest.license,
});
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 });

View File

@ -1,11 +1,6 @@
{
"pkg": {
"scripts": [
"obj/**/*.js"
],
"assets": [
"obj/**/*.js.map",
"package.json"
]
"scripts": ["obj/**/*.js"],
"assets": ["obj/**/*.js.map", "package.json"]
}
}

View File

@ -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}`);
}

View File

@ -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));
}
}

View File

@ -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));
}
}

View File

@ -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());
}
}

View File

@ -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();

View File

@ -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";
}
}

View File

@ -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);
}

View File

@ -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);
}
}

View File

@ -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: {

View File

@ -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";
}
}

View File

@ -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();
}
}

View File

@ -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";
}
}

View File

@ -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);
}
}

View File

@ -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";
}
}

View File

@ -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 "";
}
}

View File

@ -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));
}
}

View File

@ -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";
}
}

View File

@ -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);
}
}

View File

@ -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,29 +5,27 @@ 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(
eventResponse,
this.getErrorCode(eventResponse.warnings),
eventResponse.warnings[0].errmsg,
)
eventResponse,
this.getErrorCode(eventResponse.warnings),
eventResponse.warnings[0].errmsg,
)
: this.httpResponse.getBody(eventResponse);
return result;
@ -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;

View File

@ -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));
}
}

View File

@ -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());
}
}

View File

@ -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);
}
}

View File

@ -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";
}
}

View File

@ -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);
}
}

View File

@ -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";
}
}

View File

@ -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";
}
}

View File

@ -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());
}
}

View File

@ -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));
}
}

View File

@ -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));
}
}

View File

@ -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);
}
}

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -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));
}

View File

@ -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));
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -1,5 +1,4 @@
export enum ContextVariableType
{
export enum ContextVariableType {
/** Logged in users session id */
SESSION_ID = 0,
/** Currently acive raid information */

View File

@ -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;
}

View File

@ -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,

View File

@ -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);
}
}

View File

@ -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;

View File

@ -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)];
}
}

View File

@ -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 };
}
}

View File

@ -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}`,

View File

@ -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;
}
}

View File

@ -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.

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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",
{
insuredItemId: insuredItem._id,
insuredItemTpl: insuredItem._tpl,
parentId: insuredItem.parentId,
}));
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;
}

View File

@ -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);
}
}

View File

@ -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);
}

View File

@ -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();
}
}

View File

@ -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);
}
}

View File

@ -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);
}

View File

@ -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,

View File

@ -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] = [];
}

View File

@ -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;
}

View File

@ -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;

View File

@ -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,19 +733,18 @@ 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
? storedClientTaxValue.fee
: this.ragfairTaxService.calculateTax(
rootItem,
pmcData,
requirementsPriceInRub,
itemStackCount,
offerRequest.sellInOnePiece,
);
rootItem,
pmcData,
requirementsPriceInRub,
itemStackCount,
offerRequest.sellInOnePiece,
);
this.logger.debug(`Offer tax to charge: ${tax}, pulled from client: ${!!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,14 +964,12 @@ 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) =>
{
return sum + item.upd.StackObjectsCount;
}, 0);
: playerOffers[playerOfferIndex].items.reduce((sum, item) => {
return sum + item.upd.StackObjectsCount;
}, 0);
const tax = this.ragfairTaxService.calculateTax(
playerOffers[playerOfferIndex].items[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",

View File

@ -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

View File

@ -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,21 +375,18 @@ 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")
? possibleLocations.filter((location) => location !== "laboratory") // Exclude labs for Savage targets.
: possibleLocations;
const allowedLocations =
targetKey === "Savage"
? possibleLocations.filter((location) => location !== "laboratory") // Exclude labs for Savage targets.
: possibleLocations;
questPool.pool.Elimination.targets[targetKey] = { locations: allowedLocations };
}
@ -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;

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -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());

View File

@ -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);

View File

@ -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 });

View File

@ -1,5 +1,4 @@
export interface OnLoad
{
onLoad(): Promise<void>
getRoute(): string
export interface OnLoad {
onLoad(): Promise<void>;
getRoute(): string;
}

View File

@ -1,5 +1,4 @@
export interface OnUpdate
{
onUpdate(timeSinceLastRun: number): Promise<boolean>
getRoute(): string
export interface OnUpdate {
onUpdate(timeSinceLastRun: number): Promise<boolean>;
getRoute(): string;
}

View File

@ -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>,
)
{}
) {}
}

View File

@ -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

Some files were not shown because too many files have changed in this diff Show More