0
0
mirror of https://github.com/sp-tarkov/server.git synced 2025-02-13 09:50:43 -05:00

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

# Conflicts:
#	project/assets/configs/core.json
This commit is contained in:
Dev 2023-11-22 14:28:30 +00:00
commit aa6f765a64
25 changed files with 11585 additions and 11378 deletions

View File

@ -61,32 +61,33 @@
"6241c2c2117ad530666a5108" "6241c2c2117ad530666a5108"
], ],
"bossItems": [ "bossItems": [
"6275303a9f372d6ea97f9ec7", "6275303a9f372d6ea97f9ec7",
"62a61bbf8ec41a51b34758d2", "62a61bbf8ec41a51b34758d2",
"628e4dd1f477aa12234918aa", "628e4dd1f477aa12234918aa",
"628b9784bcf6e2659e09b8a2", "628b9784bcf6e2659e09b8a2",
"628bc7fb408e2b2e9c0801b1", "628bc7fb408e2b2e9c0801b1",
"628baf0b967de16aab5a4f36", "628baf0b967de16aab5a4f36",
"62963c18dbc8ab5f0d382d0b", "62963c18dbc8ab5f0d382d0b",
"628b9c7d45122232a872358f", "628b9c7d45122232a872358f",
"5fc64ea372b0dd78d51159dc", "5fc64ea372b0dd78d51159dc",
"64ca3d3954fc657e230529cc", "64ca3d3954fc657e230529cc",
"64637076203536ad5600c990", "64637076203536ad5600c990",
"5c0e874186f7745dc7616606", "5c0e874186f7745dc7616606",
"5c0e842486f77443a74d2976", "5c0e842486f77443a74d2976",
"5c0e541586f7747fa54205c9", "5c0e541586f7747fa54205c9",
"5b3b713c5acfc4330140bd8d", "5b3b713c5acfc4330140bd8d",
"5e997f0b86f7741ac73993e2", "5e997f0b86f7741ac73993e2",
"5d08d21286f774736e7c94c3", "5d08d21286f774736e7c94c3",
"6087e570b998180e9f76dc24", "6087e570b998180e9f76dc24",
"60a7ad2a2198820d95707a2e", "60a7ad2a2198820d95707a2e",
"60a7ad3a0c5cb24b0134664a", "60a7ad3a0c5cb24b0134664a",
"60a7acf20c5cb24b01346648", "60a7acf20c5cb24b01346648",
"636270263f2495c26f00b007", "636270263f2495c26f00b007",
"63626d904aa74b8fe30ab426", "63626d904aa74b8fe30ab426",
"63611865ba5b90db0c0399d1", "63611865ba5b90db0c0399d1",
"5eff09cd30a7dc22fd1ddfed", "5eff09cd30a7dc22fd1ddfed",
"5efde6b4f5448336730dbd61" "5efde6b4f5448336730dbd61",
] "609e860ebd219504d8507525"
]
} }

View File

@ -6,7 +6,7 @@
"edge_of_darkness": 4 "edge_of_darkness": 4
}, },
"accountTypeWeight": { "accountTypeWeight": {
"0": 25, "0": 75,
"1": 1, "1": 1,
"256": 2, "256": 2,
"512": 2 "512": 2

View File

@ -149,7 +149,7 @@
"items": [2, 4, 5, 5, 5, 5, 5], "items": [2, 4, 5, 5, 5, 5, 5],
"reputation": [0.01, 0.01, 0.02, 0.02, 0.03, 0.03, 0.03], "reputation": [0.01, 0.01, 0.02, 0.02, 0.03, 0.03, 0.03],
"rewardSpread": 0.5, "rewardSpread": 0.5,
"skillRewardChance": [0, 0.01, 0.05, 0.1, 0.15, 0.2, 0.2], "skillRewardChance": [0, 0.01, 0.05, 0.1, 0.1, 0.15, 0.15],
"skillPointReward": [10, 15, 20, 25, 30, 35, 40] "skillPointReward": [10, 15, 20, 25, 30, 35, 40]
}, },
"locations": { "locations": {
@ -166,104 +166,135 @@
}, },
"traderWhitelist": [{ "traderWhitelist": [{
"traderId": "54cb50c76803fa8b248b4571", "traderId": "54cb50c76803fa8b248b4571",
"name": "prapor", "name": "prapor",
"questTypes": ["Completion", "Exploration", "Elimination"], "questTypes": ["Completion", "Exploration", "Elimination"],
"rewardBaseWhitelist": [ "rewardBaseWhitelist": [
"543be6564bdc2df4348b4568", "543be6564bdc2df4348b4568",
"5485a8684bdc2da71d8b4567", "5485a8684bdc2da71d8b4567",
"590c745b86f7743cc433c5f2", "590c745b86f7743cc433c5f2",
"5422acb9af1c889c16000029" "57864c322459775490116fbf",
] "57864a66245977548f04a81f"
],
"rewardCanBeWeapon": true,
"weaponRewardChancePercent": 20
}, { }, {
"traderId": "54cb57776803fa99248b456e", "traderId": "54cb57776803fa99248b456e",
"name": "therapist", "name": "therapist",
"questTypes": ["Completion", "Exploration", "Elimination"], "questTypes": ["Completion", "Exploration", "Elimination"],
"rewardBaseWhitelist": [ "rewardBaseWhitelist": [
"57864a66245977548f04a81f", "57864a66245977548f04a81f",
"5448f39d4bdc2d0a728b4568", "5448f39d4bdc2d0a728b4568",
"5448f3ac4bdc2dce718b4569", "5448f3ac4bdc2dce718b4569",
"5448f3a64bdc2d60728b456a", "5448f3a64bdc2d60728b456a",
"57864c322459775490116fbf", "57864c322459775490116fbf",
"57864c8c245977548867e7f1", "57864c8c245977548867e7f1",
"5448e8d04bdc2ddf718b4569", "5448e8d04bdc2ddf718b4569",
"57864e4c24597754843f8723", "57864e4c24597754843f8723",
"57864ee62459775490116fc1" "57864ee62459775490116fc1",
] "543be5664bdc2dd4348b4569"
],
"rewardCanBeWeapon": false,
"weaponRewardChancePercent": 0
}, { }, {
"traderId": "58330581ace78e27b8b10cee", "traderId": "58330581ace78e27b8b10cee",
"name": "skier", "name": "skier",
"questTypes": ["Completion", "Exploration", "Elimination"], "questTypes": ["Completion", "Exploration", "Elimination"],
"rewardBaseWhitelist": [ "rewardBaseWhitelist": [
"5a341c4086f77401f2541505", "5a341c4086f77401f2541505",
"5448e8d64bdc2dce718b4568", "5448e8d64bdc2dce718b4568",
"5448e8d04bdc2ddf718b4569", "5448e8d04bdc2ddf718b4569",
"5422acb9af1c889c16000029", "55818ad54bdc2ddc698b4569",
"55818ad54bdc2ddc698b4569", "57864a3d24597754843f8721",
"57864a3d24597754843f8721", "5a341c4686f77469e155819e",
"5a341c4686f77469e155819e", "55818b224bdc2dde698b456f",
"55818b224bdc2dde698b456f", "5c99f98d86f7745c314214b3",
"5c99f98d86f7745c314214b3", "55818aeb4bdc2ddc698b456a",
"55818aeb4bdc2ddc698b456a", "55818acf4bdc2dde698b456b",
"55818acf4bdc2dde698b456b", "57864bb7245977548b3b66c2",
"57864bb7245977548b3b66c2", "590c745b86f7743cc433c5f2",
"590c745b86f7743cc433c5f2" "57864a66245977548f04a81f",
] "5c164d2286f774194c5e69fa"
],
"rewardCanBeWeapon": true,
"weaponRewardChancePercent": 20
}, { }, {
"traderId": "5935c25fb3acc3127c3d8cd9", "traderId": "5935c25fb3acc3127c3d8cd9",
"name": "peacekeeper", "name": "peacekeeper",
"questTypes": ["Completion", "Exploration", "Elimination"], "questTypes": ["Completion", "Exploration", "Elimination"],
"rewardBaseWhitelist": [ "rewardBaseWhitelist": [
"5422acb9af1c889c16000029", "543be6564bdc2df4348b4568",
"543be6564bdc2df4348b4568", "5448e5284bdc2dcb718b4567",
"5448e5284bdc2dcb718b4567", "5485a8684bdc2da71d8b4567",
"5485a8684bdc2da71d8b4567", "57864a3d24597754843f8721",
"57864a3d24597754843f8721", "55818af64bdc2d5b648b4570",
"55818af64bdc2d5b648b4570" "57864e4c24597754843f8723",
] "57864a66245977548f04a81f",
"57864ee62459775490116fc1",
"590c745b86f7743cc433c5f2"
],
"rewardCanBeWeapon": true,
"weaponRewardChancePercent": 20
}, { }, {
"traderId": "5a7c2eca46aef81a7ca2145d", "traderId": "5a7c2eca46aef81a7ca2145d",
"name": "mechanic", "name": "mechanic",
"questTypes": ["Completion", "Exploration"], "questTypes": ["Completion", "Exploration"],
"rewardBaseWhitelist": [ "rewardBaseWhitelist": [
"55818af64bdc2d5b648b4570", "55818af64bdc2d5b648b4570",
"5448bc234bdc2d3c308b4569", "5448bc234bdc2d3c308b4569",
"55818b164bdc2ddc698b456c", "55818b164bdc2ddc698b456c",
"55818a684bdc2ddd698b456d", "55818a684bdc2ddd698b456d",
"550aa4cd4bdc2dd8348b456c", "550aa4cd4bdc2dd8348b456c",
"5422acb9af1c889c16000029", "5485a8684bdc2da71d8b4567",
"5485a8684bdc2da71d8b4567", "55818b224bdc2dde698b456f",
"55818b224bdc2dde698b456f" "57864a66245977548f04a81f",
] "55818af64bdc2d5b648b4570",
"550aa4dd4bdc2dc9348b4569",
"55818a594bdc2db9688b456a",
"55818a104bdc2db9688b4569"
],
"rewardCanBeWeapon": true,
"weaponRewardChancePercent": 20
}, { }, {
"traderId": "5ac3b934156ae10c4430e83c", "traderId": "5ac3b934156ae10c4430e83c",
"name": "ragman", "name": "ragman",
"questTypes": ["Completion", "Exploration", "Elimination"], "questTypes": ["Completion", "Exploration", "Elimination"],
"rewardBaseWhitelist": [ "rewardBaseWhitelist": [
"5a341c4086f77401f2541505", "5a341c4086f77401f2541505",
"5448e5724bdc2ddf718b4568", "5448e5724bdc2ddf718b4568",
"5448e54d4bdc2dcc718b4568", "5448e54d4bdc2dcc718b4568",
"590c745b86f7743cc433c5f2", "590c745b86f7743cc433c5f2",
"57bef4c42459772e8d35a53b", "57bef4c42459772e8d35a53b",
"5448e53e4bdc2d60728b4567", "5448e53e4bdc2d60728b4567",
"5448e5284bdc2dcb718b4567", "5448e5284bdc2dcb718b4567",
"57864a66245977548f04a81f" "57864a66245977548f04a81f"
] ],
"rewardCanBeWeapon": false,
"weaponRewardChancePercent": 0
}, { }, {
"traderId": "5c0647fdd443bc2504c2d371", "traderId": "5c0647fdd443bc2504c2d371",
"name": "jaeger", "name": "jaeger",
"questTypes": ["Completion", "Exploration", "Elimination"], "questTypes": ["Completion", "Exploration", "Elimination"],
"rewardBaseWhitelist": [ "rewardBaseWhitelist": [
"5448f3ac4bdc2dce718b4569", "5448f3ac4bdc2dce718b4569",
"5c99f98d86f7745c314214b3", "5c99f98d86f7745c314214b3",
"590c745b86f7743cc433c5f2", "590c745b86f7743cc433c5f2",
"57864bb7245977548b3b66c2" "57864bb7245977548b3b66c2",
] "57864a66245977548f04a81f",
"57864e4c24597754843f8723",
"57864a3d24597754843f8721",
"5448f3a64bdc2d60728b456a",
"5795f317245977243854e041",
"5d650c3e815116009f6201d2",
"57864ada245977548638de91"
],
"rewardCanBeWeapon": false,
"weaponRewardChancePercent": 0
} }
], ],
"questConfig": { "questConfig": {
"Exploration": { "Exploration": {
"maxExtracts": 5, "maxExtracts": 5,
"maxExtractsWithSpecificExit": 3, "maxExtractsWithSpecificExit": 3,
"possibleSkillRewards": ["Endurance", "Strength", "Vitality"], "possibleSkillRewards": ["Endurance", "Strength", "Vitality"],
"specificExits": { "specificExits": {
"probability": 0.25, "probability": 0.25,
@ -281,6 +312,7 @@
"possibleSkillRewards": ["Endurance", "Strength", "Vitality"], "possibleSkillRewards": ["Endurance", "Strength", "Vitality"],
"minRequestedAmount": 1, "minRequestedAmount": 1,
"maxRequestedAmount": 4, "maxRequestedAmount": 4,
"uniqueItemCount": 2,
"minRequestedBulletAmount": 20, "minRequestedBulletAmount": 20,
"maxRequestedBulletAmount": 60, "maxRequestedBulletAmount": 60,
"useWhitelist": true, "useWhitelist": true,
@ -788,7 +820,7 @@
"items": [4, 5, 5, 6, 6, 7, 7], "items": [4, 5, 5, 6, 6, 7, 7],
"reputation": [0.02, 0.03, 0.04, 0.04, 0.05, 0.05, 0.05], "reputation": [0.02, 0.03, 0.04, 0.04, 0.05, 0.05, 0.05],
"rewardSpread": 0.5, "rewardSpread": 0.5,
"skillRewardChance": [0, 0.05, 0.1, 0.15, 0.25, 0.3, 0.3], "skillRewardChance": [0, 0.05, 0.1, 0.15, 0.2, 0.2, 0.2],
"skillPointReward": [25, 35, 45, 50, 55, 60, 65] "skillPointReward": [25, 35, 45, 50, 55, 60, 65]
}, },
"locations": { "locations": {
@ -805,105 +837,136 @@
}, },
"traderWhitelist": [{ "traderWhitelist": [{
"traderId": "54cb50c76803fa8b248b4571", "traderId": "54cb50c76803fa8b248b4571",
"name": "prapor", "name": "prapor",
"questTypes": ["Completion", "Exploration", "Elimination"], "questTypes": ["Completion", "Exploration", "Elimination"],
"rewardBaseWhitelist": [ "rewardBaseWhitelist": [
"543be6564bdc2df4348b4568", "543be6564bdc2df4348b4568",
"5485a8684bdc2da71d8b4567", "5485a8684bdc2da71d8b4567",
"590c745b86f7743cc433c5f2", "590c745b86f7743cc433c5f2",
"5422acb9af1c889c16000029" "57864c322459775490116fbf",
] "57864a66245977548f04a81f"
],
"rewardCanBeWeapon": true,
"weaponRewardChancePercent": 25
}, { }, {
"traderId": "54cb57776803fa99248b456e", "traderId": "54cb57776803fa99248b456e",
"name": "therapist", "name": "therapist",
"questTypes": ["Completion", "Exploration", "Elimination"], "questTypes": ["Completion", "Exploration", "Elimination"],
"rewardBaseWhitelist": [ "rewardBaseWhitelist": [
"57864a66245977548f04a81f", "57864a66245977548f04a81f",
"5448f39d4bdc2d0a728b4568", "5448f39d4bdc2d0a728b4568",
"5448f3ac4bdc2dce718b4569", "5448f3ac4bdc2dce718b4569",
"5448f3a64bdc2d60728b456a", "5448f3a64bdc2d60728b456a",
"57864c322459775490116fbf", "57864c322459775490116fbf",
"57864c8c245977548867e7f1", "57864c8c245977548867e7f1",
"5448e8d04bdc2ddf718b4569", "5448e8d04bdc2ddf718b4569",
"57864e4c24597754843f8723", "57864e4c24597754843f8723",
"57864ee62459775490116fc1" "57864ee62459775490116fc1",
] "543be5664bdc2dd4348b4569"
],
"rewardCanBeWeapon": false,
"weaponRewardChancePercent": 0
}, { }, {
"traderId": "58330581ace78e27b8b10cee", "traderId": "58330581ace78e27b8b10cee",
"name": "skier", "name": "skier",
"questTypes": ["Completion", "Exploration", "Elimination"], "questTypes": ["Completion", "Exploration", "Elimination"],
"rewardBaseWhitelist": [ "rewardBaseWhitelist": [
"5a341c4086f77401f2541505", "5a341c4086f77401f2541505",
"5448e8d64bdc2dce718b4568", "5448e8d64bdc2dce718b4568",
"5448e8d04bdc2ddf718b4569", "5448e8d04bdc2ddf718b4569",
"5422acb9af1c889c16000029", "55818ad54bdc2ddc698b4569",
"55818ad54bdc2ddc698b4569", "57864a3d24597754843f8721",
"57864a3d24597754843f8721", "5a341c4686f77469e155819e",
"5a341c4686f77469e155819e", "55818b224bdc2dde698b456f",
"55818b224bdc2dde698b456f", "5c99f98d86f7745c314214b3",
"5c99f98d86f7745c314214b3", "55818aeb4bdc2ddc698b456a",
"55818aeb4bdc2ddc698b456a", "55818acf4bdc2dde698b456b",
"55818acf4bdc2dde698b456b", "57864bb7245977548b3b66c2",
"57864bb7245977548b3b66c2", "590c745b86f7743cc433c5f2",
"590c745b86f7743cc433c5f2" "57864a66245977548f04a81f",
] "5c164d2286f774194c5e69fa"
],
"rewardCanBeWeapon": true,
"weaponRewardChancePercent": 25
}, { }, {
"traderId": "5935c25fb3acc3127c3d8cd9", "traderId": "5935c25fb3acc3127c3d8cd9",
"name": "peacekeeper", "name": "peacekeeper",
"questTypes": ["Completion", "Exploration", "Elimination"], "questTypes": ["Completion", "Exploration", "Elimination"],
"rewardBaseWhitelist": [ "rewardBaseWhitelist": [
"5422acb9af1c889c16000029", "543be6564bdc2df4348b4568",
"543be6564bdc2df4348b4568", "5448e5284bdc2dcb718b4567",
"5448e5284bdc2dcb718b4567", "5485a8684bdc2da71d8b4567",
"5485a8684bdc2da71d8b4567", "57864a3d24597754843f8721",
"57864a3d24597754843f8721", "55818af64bdc2d5b648b4570",
"55818af64bdc2d5b648b4570" "57864e4c24597754843f8723",
] "57864a66245977548f04a81f",
"57864ee62459775490116fc1",
"590c745b86f7743cc433c5f2"
],
"rewardCanBeWeapon": true,
"weaponRewardChancePercent": 25
}, { }, {
"traderId": "5a7c2eca46aef81a7ca2145d", "traderId": "5a7c2eca46aef81a7ca2145d",
"name": "mechanic", "name": "mechanic",
"questTypes": ["Completion", "Exploration"], "questTypes": ["Completion", "Exploration"],
"rewardBaseWhitelist": [ "rewardBaseWhitelist": [
"55818af64bdc2d5b648b4570", "55818af64bdc2d5b648b4570",
"5448bc234bdc2d3c308b4569", "5448bc234bdc2d3c308b4569",
"55818b164bdc2ddc698b456c", "55818b164bdc2ddc698b456c",
"55818a684bdc2ddd698b456d", "55818a684bdc2ddd698b456d",
"550aa4cd4bdc2dd8348b456c", "550aa4cd4bdc2dd8348b456c",
"5422acb9af1c889c16000029", "5485a8684bdc2da71d8b4567",
"5485a8684bdc2da71d8b4567", "55818b224bdc2dde698b456f",
"55818b224bdc2dde698b456f" "57864a66245977548f04a81f",
] "55818af64bdc2d5b648b4570",
"550aa4dd4bdc2dc9348b4569",
"55818a594bdc2db9688b456a",
"55818a104bdc2db9688b4569"
],
"rewardCanBeWeapon": true,
"weaponRewardChancePercent": 25
}, { }, {
"traderId": "5ac3b934156ae10c4430e83c", "traderId": "5ac3b934156ae10c4430e83c",
"name": "ragman", "name": "ragman",
"questTypes": ["Completion", "Exploration", "Elimination"], "questTypes": ["Completion", "Exploration", "Elimination"],
"rewardBaseWhitelist": [ "rewardBaseWhitelist": [
"5a341c4086f77401f2541505", "5a341c4086f77401f2541505",
"5448e5724bdc2ddf718b4568", "5448e5724bdc2ddf718b4568",
"5448e54d4bdc2dcc718b4568", "5448e54d4bdc2dcc718b4568",
"590c745b86f7743cc433c5f2", "590c745b86f7743cc433c5f2",
"57bef4c42459772e8d35a53b", "57bef4c42459772e8d35a53b",
"5448e53e4bdc2d60728b4567", "5448e53e4bdc2d60728b4567",
"5448e5284bdc2dcb718b4567", "5448e5284bdc2dcb718b4567",
"57864a66245977548f04a81f" "57864a66245977548f04a81f"
] ],
"rewardCanBeWeapon": false,
"weaponRewardChancePercent": 0
}, { }, {
"traderId": "5c0647fdd443bc2504c2d371", "traderId": "5c0647fdd443bc2504c2d371",
"name": "jaeger", "name": "jaeger",
"questTypes": ["Completion", "Exploration", "Elimination"], "questTypes": ["Completion", "Exploration", "Elimination"],
"rewardBaseWhitelist": [ "rewardBaseWhitelist": [
"5448f3ac4bdc2dce718b4569", "5448f3ac4bdc2dce718b4569",
"5c99f98d86f7745c314214b3", "5c99f98d86f7745c314214b3",
"590c745b86f7743cc433c5f2", "590c745b86f7743cc433c5f2",
"57864bb7245977548b3b66c2" "57864bb7245977548b3b66c2",
] "57864a66245977548f04a81f",
"57864e4c24597754843f8723",
"57864a3d24597754843f8721",
"5448f3a64bdc2d60728b456a",
"5795f317245977243854e041",
"5d650c3e815116009f6201d2",
"57864ada245977548638de91"
],
"rewardCanBeWeapon": false,
"weaponRewardChancePercent": 0
} }
], ],
"questConfig": { "questConfig": {
"Exploration": { "Exploration": {
"possibleSkillRewards": ["Endurance", "Strength", "Vitality"],
"maxExtracts": 25, "maxExtracts": 25,
"maxExtractsWithSpecificExit": 12, "maxExtractsWithSpecificExit": 12,
"possibleSkillRewards": ["Endurance", "Strength", "Vitality"],
"specificExits": { "specificExits": {
"probability": 0.4, "probability": 0.4,
"passageRequirementWhitelist": [ "passageRequirementWhitelist": [
@ -918,8 +981,9 @@
}, },
"Completion": { "Completion": {
"possibleSkillRewards": ["Endurance", "Strength", "Vitality"], "possibleSkillRewards": ["Endurance", "Strength", "Vitality"],
"minRequestedAmount": 2, "minRequestedAmount": 4,
"maxRequestedAmount": 10, "maxRequestedAmount": 12,
"uniqueItemCount": 4,
"minRequestedBulletAmount": 20, "minRequestedBulletAmount": 20,
"maxRequestedBulletAmount": 60, "maxRequestedBulletAmount": 60,
"useWhitelist": true, "useWhitelist": true,
@ -1511,21 +1575,22 @@
"traderWhitelist": [{ "traderWhitelist": [{
"traderId": "579dc571d53a0658a154fbec", "traderId": "579dc571d53a0658a154fbec",
"questTypes": ["Completion", "Exploration", "Elimination", "Pickup"], "questTypes": ["Completion", "Exploration", "Elimination", "Pickup"],
"rewardBaseWhitelist": [ "rewardBaseWhitelist": [
"55818a684bdc2ddd698b456d", "55818a684bdc2ddd698b456d",
"55818a594bdc2db9688b456a", "55818a594bdc2db9688b456a",
"57864c8c245977548867e7f1", "57864c8c245977548867e7f1",
"5448ecbe4bdc2d60728b4568", "5448ecbe4bdc2d60728b4568",
"5422acb9af1c889c16000029", "57864bb7245977548b3b66c2"
"57864bb7245977548b3b66c2" ],
] "rewardCanBeWeapon": true,
"weaponRewardChancePercent": 10
} }
], ],
"questConfig": { "questConfig": {
"Exploration": { "Exploration": {
"possibleSkillRewards": ["Endurance", "Strength", "Vitality"], "possibleSkillRewards": ["Endurance", "Strength", "Vitality"],
"maxExtracts": 4, "maxExtracts": 4,
"maxExtractsWithSpecificExit": 2, "maxExtractsWithSpecificExit": 2,
"specificExits": { "specificExits": {
"probability": 0.25, "probability": 0.25,
"passageRequirementWhitelist": [ "passageRequirementWhitelist": [
@ -1587,7 +1652,8 @@
"Completion": { "Completion": {
"possibleSkillRewards": ["Endurance", "Strength", "Vitality"], "possibleSkillRewards": ["Endurance", "Strength", "Vitality"],
"minRequestedAmount": 1, "minRequestedAmount": 1,
"maxRequestedAmount": 5, "maxRequestedAmount": 3,
"uniqueItemCount": 1,
"minRequestedBulletAmount": 20, "minRequestedBulletAmount": 20,
"maxRequestedBulletAmount": 60, "maxRequestedBulletAmount": 60,
"useWhitelist": true, "useWhitelist": true,

View File

@ -40,7 +40,8 @@
"619bdeb986e01e16f839a99e", "619bdeb986e01e16f839a99e",
"619bddffc9546643a67df6f0", "619bddffc9546643a67df6f0",
"619bdf9cc9546643a67df6f8", "619bdf9cc9546643a67df6f8",
"63a0b2eabea67a6d93009e52" "63a0b2eabea67a6d93009e52",
"609e860ebd219504d8507525"
], ],
"moneyRewards": { "moneyRewards": {
@ -115,5 +116,5 @@
}, },
"allowMultipleMoneyRewardsPerRarity": false, "allowMultipleMoneyRewardsPerRarity": false,
"allowMultipleAmmoRewardsPerRarity": true, "allowMultipleAmmoRewardsPerRarity": true,
"allowBossItemsAsRewards": false "allowBossItemsAsRewards": false
} }

View File

@ -4499,6 +4499,8 @@
"mod_magazine": [ "mod_magazine": [
"5448c12b4bdc2d02308b456f" "5448c12b4bdc2d02308b456f"
], ],
"mod_reciever": ["6374a822e629013b9c0645c8"],
"mod_pistolgrip": ["6374a7e7417239a7bf00f042"],
"patron_in_weapon": [ "patron_in_weapon": [
"573719762459775a626ccbc1", "573719762459775a626ccbc1",
"573720e02459776143012541", "573720e02459776143012541",
@ -4764,7 +4766,7 @@
"mod_muzzle": [ "mod_muzzle": [
"56e05b06d2720bb2668b4586" "56e05b06d2720bb2668b4586"
], ],
"mod_pistol_grip": [ "mod_pistolgrip": [
"56e05a6ed2720bd0748b4567" "56e05a6ed2720bd0748b4567"
], ],
"patron_in_weapon": [ "patron_in_weapon": [
@ -4890,10 +4892,15 @@
"mod_magazine": [ "mod_magazine": [
"5448c12b4bdc2d02308b456f" "5448c12b4bdc2d02308b456f"
], ],
"mod_reciever": ["6374a822e629013b9c0645c8"],
"mod_pistolgrip": ["637784c5f7b3f4ac1a0d1a9a", "637b6d610aef6cfc5e02dd14"],
"patron_in_weapon": [ "patron_in_weapon": [
"5737201124597760fc4431f1" "5737201124597760fc4431f1"
] ]
}, },
"6374a822e629013b9c0645c8": {
"mod_sight_rear": ["63c6adcfb4ba094317063742"]
},
"57acb6222459771ec34b5cb0": { "57acb6222459771ec34b5cb0": {
"mod_scope": [ "mod_scope": [
"570fd721d2720bc5458b4596", "570fd721d2720bc5458b4596",

View File

@ -143,7 +143,7 @@
"modloader-main_property_points_to_nothing": "Mod %s package.json main property points to non-existing file", "modloader-main_property_points_to_nothing": "Mod %s package.json main property points to non-existing file",
"modloader-missing_akiversion_field": "Mod %s is missing the akiVersion field, most likely due to being out of date and incompatible with the current version of AKI", "modloader-missing_akiversion_field": "Mod %s is missing the akiVersion field, most likely due to being out of date and incompatible with the current version of AKI",
"modloader-missing_dependency": "Mod {{mod}} requires {{modDependency}} to be installed.", "modloader-missing_dependency": "Mod {{mod}} requires {{modDependency}} to be installed.",
"modloader-missing_package_json": "Mod (%s) is missing package.json", "modloader-missing_package_json": "Mod (%s) is missing package.json. Make sure you have checked the mods hub page for install instructions",
"modloader-missing_package_json_property": "Mod {{modName}} package.json requires {{prop}} property", "modloader-missing_package_json_property": "Mod {{modName}} package.json requires {{prop}} property",
"modloader-mod_incompatible": "ModLoader: Mod (%s) is incompatible. It must implement at least one of IPostAkiLoadMod, IPostDBLoadMod, IPreAkiLoadMod", "modloader-mod_incompatible": "ModLoader: Mod (%s) is incompatible. It must implement at least one of IPostAkiLoadMod, IPostDBLoadMod, IPreAkiLoadMod",
"modloader-mod_has_no_main_property": "ModLoader: Mod (%s) is incompatible. It lacks a 'main' property", "modloader-mod_has_no_main_property": "ModLoader: Mod (%s) is incompatible. It lacks a 'main' property",

File diff suppressed because it is too large Load Diff

View File

@ -394,7 +394,9 @@
"573720e02459776143012541", "573720e02459776143012541",
"5c06779c86f77426e00dd782", "5c06779c86f77426e00dd782",
"5c06782b86f77426df5407d2", "5c06782b86f77426df5407d2",
"590a3efd86f77437d351a25b" "590a3efd86f77437d351a25b",
"57371aab2459775a77142f22",
"56dff2ced2720bb4668b4567"
] ]
}, { }, {
"minPlayerLevel": 15, "minPlayerLevel": 15,
@ -478,7 +480,21 @@
"573478bc24597738002c6175", "573478bc24597738002c6175",
"590c37d286f77443be3d7827", "590c37d286f77443be3d7827",
"590c645c86f77412b01304d9", "590c645c86f77412b01304d9",
"5e2aedd986f7746d404f3aa4" "5e2aedd986f7746d404f3aa4",
"560d61e84bdc2da74d8b4571",
"5cc80f38e4a949001152b560",
"5c0d591486f7744c505b416f",
"5c925fa22e221601da359b7b",
"573719df2459775a626ccbc2",
"64b8ee384b75259c590fa89b",
"59e6906286f7746c9f75e847",
"5c0d668f86f7747ccb7f13b2",
"5f0596629e22f464da6bbdd9",
"5a26ac0ec4a28200741e1e18",
"58dd3ad986f77403051cba8f",
"5efb0cabfb3e451d70735af5",
"64b7af434b75259c590fa893",
"5d6e68a8a4b9360b6c0d54e2"
] ]
}, { }, {
"minPlayerLevel": 40, "minPlayerLevel": 40,
@ -509,7 +525,16 @@
"5bc9b355d4351e6d1509862a", "5bc9b355d4351e6d1509862a",
"5bc9b9ecd4351e3bac122519", "5bc9b9ecd4351e3bac122519",
"5c12620d86f7743f8b198b72", "5c12620d86f7743f8b198b72",
"5751a89d24597722aa0e8db0" "5751a89d24597722aa0e8db0",
"60194943740c5d77f6705eea",
"5c0d5e4486f77478390952fe",
"5e85a9f4add9fe03027d9bf1",
"64b8725c4b75259c590fa899",
"5cadf6eeae921500134b2799",
"5ba26835d4351e0035628ff5",
"61962d879bb3d20b0946d385",
"5efb0da7a29a85116f6ea05f",
"59e690b686f7746c9f75e848"
] ]
} }
] ]

View File

@ -1,34 +1,35 @@
{ {
"fail": {},
"started": {}, "started": {},
"success": { "success": {
"6507ff30644a656aee0f86c9": "5ae3270f86f77445ba41d4dd", "648a275aa937dd374c0d7399": "6179b5b06e9dd54ac275e409",
"6507ff31644a656aee0f8883": "5ac2426c86f774138762edfe",
"6507ff32644a656aee0f8999": "5d25e2e286f77444001e2e48",
"6507ff2f644a656aee0f85d8": "60e71bb4e456d449cd47ca75",
"6507ff30644a656aee0f86ac": "59674eb386f774539f14813a",
"6507ff2f644a656aee0f861c": "5967530a86f77462ba22226b",
"6507ff30644a656aee0f8702": "59675d6c86f7740a842fc482",
"6507ff31644a656aee0f88d1": "59c512ad86f7741f0d09de9b",
"6507ff31644a656aee0f88df": "6179ac7511973d018217d0b9",
"6507ff2f644a656aee0f85d2": "5c0bd94186f7747a727f09b2", "6507ff2f644a656aee0f85d2": "5c0bd94186f7747a727f09b2",
"6507ff2f644a656aee0f85d8": "60e71bb4e456d449cd47ca75",
"6507ff2f644a656aee0f85ed": "59c50a9e86f7745fef66f4ff",
"6507ff2f644a656aee0f861c": "5967530a86f77462ba22226b",
"6507ff2f644a656aee0f8620": "639135b04ed9512be67647d7",
"6507ff2f644a656aee0f8632": "59ca264786f77445a80ed044",
"6507ff2f644a656aee0f864c": "5967725e86f774601a446662",
"6507ff30644a656aee0f8673": "59c50c8886f7745fed3193bf",
"6507ff30644a656aee0f8675": "639135b04ed9512be67647d7",
"6507ff30644a656aee0f868a": "59c124d686f774189b3c843f",
"6507ff30644a656aee0f86ac": "59674eb386f774539f14813a",
"6507ff30644a656aee0f86bc": "5936d90786f7742b1420ba5b",
"6507ff30644a656aee0f86c9": "5ae3270f86f77445ba41d4dd",
"6507ff30644a656aee0f8702": "59675d6c86f7740a842fc482",
"6507ff30644a656aee0f87cf": "5c0bd94186f7747a727f09b2", "6507ff30644a656aee0f87cf": "5c0bd94186f7747a727f09b2",
"6507ff31644a656aee0f8832": "5d4bec3486f7743cac246665", "6507ff31644a656aee0f8832": "5d4bec3486f7743cac246665",
"6507ff2f644a656aee0f864c": "5967725e86f774601a446662", "6507ff31644a656aee0f8883": "5ac2426c86f774138762edfe",
"6507ff30644a656aee0f868a": "59c124d686f774189b3c843f",
"6507ff2f644a656aee0f85ed": "59c50a9e86f7745fef66f4ff",
"6507ff30644a656aee0f86bc": "5936d90786f7742b1420ba5b",
"6507ff31644a656aee0f88e3": "59674cd986f7744ab26e32f2",
"6507ff30644a656aee0f8673": "59c50c8886f7745fed3193bf",
"6507ff2f644a656aee0f8632": "59ca264786f77445a80ed044",
"6507ff31644a656aee0f8898": "6179acbdc760af5ad2053585", "6507ff31644a656aee0f8898": "6179acbdc760af5ad2053585",
"6507ff31644a656aee0f88a6": "5bc4893c86f774626f5ebf3e", "6507ff31644a656aee0f88a6": "5bc4893c86f774626f5ebf3e",
"6507ff2f644a656aee0f8620": "639135b04ed9512be67647d7", "6507ff31644a656aee0f88d1": "59c512ad86f7741f0d09de9b",
"6507ff30644a656aee0f8675": "639135b04ed9512be67647d7", "6507ff31644a656aee0f88df": "6179ac7511973d018217d0b9",
"652376e2f6c67195e4061507": "64e7b9a4aac4cd0a726562cb", "6507ff31644a656aee0f88e3": "59674cd986f7744ab26e32f2",
"6507ff31644a656aee0f893f": "63a9ae24009ffc6a551631a5", "6507ff31644a656aee0f893f": "63a9ae24009ffc6a551631a5",
"6507ff31644a656aee0f8954": "64e7b99017ab941a6f7bf9d7",
"6507ff31644a656aee0f895c": "63a9ae24009ffc6a551631a5", "6507ff31644a656aee0f895c": "63a9ae24009ffc6a551631a5",
"6507ff32644a656aee0f8999": "5d25e2e286f77444001e2e48",
"6507ff32644a656aee0f8a03": "64f5aac4b63b74469b6c14c2", "6507ff32644a656aee0f8a03": "64f5aac4b63b74469b6c14c2",
"6507ff31644a656aee0f8954": "64e7b99017ab941a6f7bf9d7" "652376e2f6c67195e4061507": "64e7b9a4aac4cd0a726562cb"
}, }
"fail": {} }
}

View File

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

View File

@ -711,19 +711,20 @@ export class HideoutController
): IItemEventRouterResponse ): IItemEventRouterResponse
{ {
const output = this.eventOutputHolder.getOutput(sessionID); const output = this.eventOutputHolder.getOutput(sessionID);
const hideoutDb = this.databaseServer.getTables().hideout;
if (request.recipeId === HideoutHelper.bitcoinFarm) if (request.recipeId === HideoutHelper.bitcoinFarm)
{ {
return this.hideoutHelper.getBTC(pmcData, request, sessionID); return this.hideoutHelper.getBTC(pmcData, request, sessionID);
} }
const recipe = this.databaseServer.getTables().hideout.production.find((r) => r._id === request.recipeId); const recipe = hideoutDb.production.find((r) => r._id === request.recipeId);
if (recipe) if (recipe)
{ {
return this.handleRecipe(sessionID, recipe, pmcData, request, output); return this.handleRecipe(sessionID, recipe, pmcData, request, output);
} }
const scavCase = this.databaseServer.getTables().hideout.scavcase.find((r) => r._id === request.recipeId); const scavCase = hideoutDb.scavcase.find((r) => r._id === request.recipeId);
if (scavCase) if (scavCase)
{ {
return this.handleScavCase(sessionID, pmcData, request, output); return this.handleScavCase(sessionID, pmcData, request, output);
@ -814,7 +815,7 @@ export class HideoutController
return this.httpResponse.appendErrorToOutput(output); return this.httpResponse.appendErrorToOutput(output);
} }
// check if the recipe is the same as the last one // Check if the recipe is the same as the last one
const area = pmcData.Hideout.Areas.find((x) => x.type === recipe.areaType); const area = pmcData.Hideout.Areas.find((x) => x.type === recipe.areaType);
if (area && request.recipeId !== area.lastRecipe) if (area && request.recipeId !== area.lastRecipe)
{ {
@ -831,13 +832,13 @@ export class HideoutController
hoursCrafting -= this.hideoutConfig.hoursForSkillCrafting * multiplierCrafting; hoursCrafting -= this.hideoutConfig.hoursForSkillCrafting * multiplierCrafting;
} }
// increment // Increment
// if addItem passes validation: // if addItem passes validation:
// - increment skill point for crafting // - increment skill point for crafting
// - delete the production in profile Hideout.Production // - delete the production in profile Hideout.Production
const callback = () => const callback = () =>
{ {
// manager Hideout skill // Manager Hideout skill
// ? use a configuration variable for the value? // ? use a configuration variable for the value?
const globals = this.databaseServer.getTables().globals; const globals = this.databaseServer.getTables().globals;
this.profileHelper.addSkillPointsToPlayer( this.profileHelper.addSkillPointsToPlayer(
@ -859,12 +860,16 @@ export class HideoutController
area.lastRecipe = request.recipeId; area.lastRecipe = request.recipeId;
counterHoursCrafting.value = hoursCrafting; counterHoursCrafting.value = hoursCrafting;
// Null production data now it's complete - will be cleaned up later by update() process // Continuous crafts have special handling in EventOutputHolder.updateOutputProperties()
pmcData.Hideout.Production[prodId].sptIsComplete = true; pmcData.Hideout.Production[prodId].sptIsComplete = true;
}; pmcData.Hideout.Production[prodId].sptIsContinuous = recipe.continuous;
// Remove the old production from output object before its sent to client // Flag normal crafts as complete
delete output.profileChanges[sessionID].production[request.recipeId]; if (!recipe.continuous)
{
pmcData.Hideout.Production[prodId].inProgress = false;
}
};
// Handle the isEncoded flag from recipe // Handle the isEncoded flag from recipe
if (recipe.isEncoded) if (recipe.isEncoded)
@ -946,6 +951,8 @@ export class HideoutController
{ {
// Flag as complete - will be cleaned up later by update() process // Flag as complete - will be cleaned up later by update() process
pmcData.Hideout.Production[prodId].sptIsComplete = true; pmcData.Hideout.Production[prodId].sptIsComplete = true;
pmcData.Hideout.Production[prodId].inProgress = false;
}; };
return this.inventoryHelper.addItem(pmcData, newReq, output, sessionID, callback, true); return this.inventoryHelper.addItem(pmcData, newReq, output, sessionID, callback, true);

View File

@ -107,19 +107,20 @@ export class InraidController
*/ */
protected savePmcProgress(sessionID: string, postRaidRequest: ISaveProgressRequestData): void protected savePmcProgress(sessionID: string, postRaidRequest: ISaveProgressRequestData): void
{ {
const serverProfile = this.saveServer.getProfile(sessionID); const serveProfile = this.saveServer.getProfile(sessionID);
const locationName = serverProfile.inraid.location.toLowerCase(); const locationName = serveProfile.inraid.location.toLowerCase();
const map: ILocationBase = this.databaseServer.getTables().locations[locationName].base; const map: ILocationBase = this.databaseServer.getTables().locations[locationName].base;
const mapHasInsuranceEnabled = map.Insurance; const mapHasInsuranceEnabled = map.Insurance;
let serverPmcData = serverProfile.characters.pmc; let serverPmcProfile = serveProfile.characters.pmc;
const isDead = this.isPlayerDead(postRaidRequest.exit); const isDead = this.isPlayerDead(postRaidRequest.exit);
const preRaidGear = this.inRaidHelper.getPlayerGear(serverPmcData.Inventory.items); const preRaidGear = this.inRaidHelper.getPlayerGear(serverPmcProfile.Inventory.items);
serverProfile.inraid.character = "pmc"; serveProfile.inraid.character = "pmc";
serverPmcData = this.inRaidHelper.updateProfileBaseStats(serverPmcData, postRaidRequest, sessionID); this.inRaidHelper.updateProfileBaseStats(serverPmcProfile, postRaidRequest, sessionID);
this.inRaidHelper.updatePmcProfileDataPostRaid(serverPmcProfile, postRaidRequest, sessionID);
// Check for exit status // Check for exit status
this.markOrRemoveFoundInRaidItems(postRaidRequest); this.markOrRemoveFoundInRaidItems(postRaidRequest);
@ -127,20 +128,20 @@ export class InraidController
postRaidRequest.profile.Inventory.items = this.itemHelper.replaceIDs( postRaidRequest.profile.Inventory.items = this.itemHelper.replaceIDs(
postRaidRequest.profile, postRaidRequest.profile,
postRaidRequest.profile.Inventory.items, postRaidRequest.profile.Inventory.items,
serverPmcData.InsuredItems, serverPmcProfile.InsuredItems,
postRaidRequest.profile.Inventory.fastPanel, postRaidRequest.profile.Inventory.fastPanel,
); );
this.inRaidHelper.addUpdToMoneyFromRaid(postRaidRequest.profile.Inventory.items); this.inRaidHelper.addUpdToMoneyFromRaid(postRaidRequest.profile.Inventory.items);
// Purge profile of equipment/container items // Purge profile of equipment/container items
serverPmcData = this.inRaidHelper.setInventory(sessionID, serverPmcData, postRaidRequest.profile); serverPmcProfile = this.inRaidHelper.setInventory(sessionID, serverPmcProfile, postRaidRequest.profile);
this.healthHelper.saveVitality(serverPmcData, postRaidRequest.health, sessionID); this.healthHelper.saveVitality(serverPmcProfile, postRaidRequest.health, sessionID);
// Remove inventory if player died and send insurance items // Remove inventory if player died and send insurance items
if (mapHasInsuranceEnabled) if (mapHasInsuranceEnabled)
{ {
this.insuranceService.storeLostGear(serverPmcData, postRaidRequest, preRaidGear, sessionID, isDead); this.insuranceService.storeLostGear(serverPmcProfile, postRaidRequest, preRaidGear, sessionID, isDead);
} }
else else
{ {
@ -151,7 +152,7 @@ export class InraidController
if (locationName === "lighthouse" && postRaidRequest.profile.Info.Side.toLowerCase() === "usec") if (locationName === "lighthouse" && postRaidRequest.profile.Info.Side.toLowerCase() === "usec")
{ {
// Decrement counter if it exists, don't go below 0 // Decrement counter if it exists, don't go below 0
const remainingCounter = serverPmcData?.Stats.Eft.OverallCounters.Items.find((x) => const remainingCounter = serverPmcProfile?.Stats.Eft.OverallCounters.Items.find((x) =>
x.Key.includes("UsecRaidRemainKills") x.Key.includes("UsecRaidRemainKills")
); );
if (remainingCounter?.Value > 0) if (remainingCounter?.Value > 0)
@ -164,12 +165,12 @@ export class InraidController
{ {
this.pmcChatResponseService.sendKillerResponse( this.pmcChatResponseService.sendKillerResponse(
sessionID, sessionID,
serverPmcData, serverPmcProfile,
postRaidRequest.profile.Stats.Eft.Aggressor, postRaidRequest.profile.Stats.Eft.Aggressor,
); );
this.matchBotDetailsCacheService.clearCache(); this.matchBotDetailsCacheService.clearCache();
serverPmcData = this.performPostRaidActionsWhenDead(postRaidRequest, serverPmcData, sessionID); serverPmcProfile = this.performPostRaidActionsWhenDead(postRaidRequest, serverPmcProfile, sessionID);
} }
const victims = postRaidRequest.profile.Stats.Eft.Victims.filter((x) => const victims = postRaidRequest.profile.Stats.Eft.Victims.filter((x) =>
@ -177,12 +178,12 @@ export class InraidController
); );
if (victims?.length > 0) if (victims?.length > 0)
{ {
this.pmcChatResponseService.sendVictimResponse(sessionID, victims, serverPmcData); this.pmcChatResponseService.sendVictimResponse(sessionID, victims, serverPmcProfile);
} }
if (mapHasInsuranceEnabled) if (mapHasInsuranceEnabled)
{ {
this.insuranceService.sendInsuredItems(serverPmcData, sessionID, map.Id); this.insuranceService.sendInsuredItems(serverPmcProfile, sessionID, map.Id);
} }
} }
@ -275,39 +276,43 @@ export class InraidController
*/ */
protected savePlayerScavProgress(sessionID: string, postRaidRequest: ISaveProgressRequestData): void protected savePlayerScavProgress(sessionID: string, postRaidRequest: ISaveProgressRequestData): void
{ {
const pmcData = this.profileHelper.getPmcProfile(sessionID); const serverPmcProfile = this.profileHelper.getPmcProfile(sessionID);
let scavData = this.profileHelper.getScavProfile(sessionID); const serverScavProfile = this.profileHelper.getScavProfile(sessionID);
const isDead = this.isPlayerDead(postRaidRequest.exit); const isDead = this.isPlayerDead(postRaidRequest.exit);
this.saveServer.getProfile(sessionID).inraid.character = "scav"; this.saveServer.getProfile(sessionID).inraid.character = "scav";
scavData = this.inRaidHelper.updateProfileBaseStats(scavData, postRaidRequest, sessionID); this.inRaidHelper.updateProfileBaseStats(serverScavProfile, postRaidRequest, sessionID);
this.inRaidHelper.updateScavProfileDataPostRaid(serverScavProfile, postRaidRequest, sessionID);
// Completing scav quests create ConditionCounters, these values need to be transported to the PMC profile // Completing scav quests create ConditionCounters, these values need to be transported to the PMC profile
if (this.profileHasConditionCounters(scavData)) if (this.profileHasConditionCounters(serverScavProfile))
{ {
// Scav quest progress needs to be moved to pmc so player can see it in menu / hand them in // Scav quest progress needs to be moved to pmc so player can see it in menu / hand them in
this.migrateScavQuestProgressToPmcProfile(scavData, pmcData); this.migrateScavQuestProgressToPmcProfile(serverScavProfile, serverPmcProfile);
} }
// Check for exit status // Change loot FiR status based on exit status
this.markOrRemoveFoundInRaidItems(postRaidRequest); this.markOrRemoveFoundInRaidItems(postRaidRequest);
postRaidRequest.profile.Inventory.items = this.itemHelper.replaceIDs( postRaidRequest.profile.Inventory.items = this.itemHelper.replaceIDs(
postRaidRequest.profile, postRaidRequest.profile,
postRaidRequest.profile.Inventory.items, postRaidRequest.profile.Inventory.items,
pmcData.InsuredItems, serverPmcProfile.InsuredItems,
postRaidRequest.profile.Inventory.fastPanel, postRaidRequest.profile.Inventory.fastPanel,
); );
// Some items from client profile don't have upd objects when they're single stack items
this.inRaidHelper.addUpdToMoneyFromRaid(postRaidRequest.profile.Inventory.items); this.inRaidHelper.addUpdToMoneyFromRaid(postRaidRequest.profile.Inventory.items);
this.handlePostRaidPlayerScavProcess(scavData, sessionID, postRaidRequest, pmcData, isDead); // Reset hp/regenerate loot
this.handlePostRaidPlayerScavProcess(serverScavProfile, sessionID, postRaidRequest, serverPmcProfile, isDead);
} }
/** /**
* Does provided profile contain any condition counters * Does provided profile contain any condition counters
* @param profile Profile to check for condition counters * @param profile Profile to check for condition counters
* @returns * @returns Profile has condition counters
*/ */
protected profileHasConditionCounters(profile: IPmcData): boolean protected profileHasConditionCounters(profile: IPmcData): boolean
{ {
@ -318,28 +323,32 @@ export class InraidController
return profile.ConditionCounters.Counters.length > 0; return profile.ConditionCounters.Counters.length > 0;
} }
/**
* Scav quest progress isnt transferred automatically from scav to pmc, we do this manually
* @param scavProfile Scav profile with quest progress post-raid
* @param pmcProfile Server pmc profile to copy scav quest progress into
*/
protected migrateScavQuestProgressToPmcProfile(scavProfile: IPmcData, pmcProfile: IPmcData): void protected migrateScavQuestProgressToPmcProfile(scavProfile: IPmcData, pmcProfile: IPmcData): void
{ {
for (const quest of scavProfile.Quests) for (const quest of scavProfile.Quests)
{ {
const pmcQuest = pmcProfile.Quests.find((x) => x.qid === quest.qid); const pmcQuest = pmcProfile.Quests.find(x => x.qid === quest.qid);
if (!pmcQuest) if (!pmcQuest)
{ {
this.logger.warning(`No PMC quest found for ID: ${quest.qid}`); this.logger.warning(`No PMC quest found for ID: ${quest.qid}`);
continue; continue;
} }
// Post-raid status is enum word e.g. `Started` but pmc quest status is number e.g. 2
// Status values mismatch or statusTimers counts mismatch // Status values mismatch or statusTimers counts mismatch
if ( if (
quest.status !== <any>QuestStatus[pmcQuest.status] quest.status !== pmcQuest.status
|| quest.statusTimers.length !== pmcQuest.statusTimers.length || Object.keys(quest.statusTimers).length !== Object.keys(pmcQuest.statusTimers).length
) )
{ {
this.logger.warning( this.logger.debug(
`Quest: ${quest.qid} found in PMC profile has different status/statustimer. Scav: ${quest.status} vs PMC: ${pmcQuest.status}`, `Quest: ${quest.qid} found in PMC profile has different status/statustimer. Scav: ${quest.status} vs PMC: ${pmcQuest.status}`,
); );
pmcQuest.status = <any>QuestStatus[quest.status]; pmcQuest.status = quest.status;
// Copy status timers over + fix bad enum key for each // Copy status timers over + fix bad enum key for each
pmcQuest.statusTimers = quest.statusTimers; pmcQuest.statusTimers = quest.statusTimers;
@ -357,7 +366,7 @@ export class InraidController
// Loop over all scav counters and add into pmc profile // Loop over all scav counters and add into pmc profile
for (const scavCounter of scavProfile.ConditionCounters.Counters) for (const scavCounter of scavProfile.ConditionCounters.Counters)
{ {
this.logger.warning( this.logger.debug(
`Processing counter: ${scavCounter.id} value:${scavCounter.value} quest:${scavCounter.qid}`, `Processing counter: ${scavCounter.id} value:${scavCounter.value} quest:${scavCounter.qid}`,
); );
const counterInPmcProfile = pmcProfile.ConditionCounters.Counters.find((x) => x.id === scavCounter.id); const counterInPmcProfile = pmcProfile.ConditionCounters.Counters.find((x) => x.id === scavCounter.id);
@ -368,14 +377,14 @@ export class InraidController
continue; continue;
} }
this.logger.warning( this.logger.debug(
`Counter id: ${scavCounter.id} already exists in pmc profile! with value: ${counterInPmcProfile.value} for quest: ${counterInPmcProfile.qid}`, `Counter id: ${scavCounter.id} already exists in pmc profile! with value: ${counterInPmcProfile.value} for quest: ${counterInPmcProfile.qid}`,
); );
// Only adjust counter value if its changed // Only adjust counter value if its changed
if (counterInPmcProfile.value !== scavCounter.value) if (counterInPmcProfile.value !== scavCounter.value)
{ {
this.logger.warning(`OVERWRITING with values: ${scavCounter.value} quest: ${scavCounter.qid}`); this.logger.debug(`OVERWRITING with values: ${scavCounter.value} quest: ${scavCounter.qid}`);
counterInPmcProfile.value = scavCounter.value; counterInPmcProfile.value = scavCounter.value;
} }
} }

View File

@ -592,7 +592,7 @@ export class BotWeaponGenerator
{ {
const desiredCaliber = this.getWeaponCaliber(weaponTemplate); const desiredCaliber = this.getWeaponCaliber(weaponTemplate);
const compatibleCartridges = ammo[desiredCaliber]; const compatibleCartridges = this.jsonUtil.clone(ammo[desiredCaliber]);
if (!compatibleCartridges || compatibleCartridges?.length === 0) if (!compatibleCartridges || compatibleCartridges?.length === 0)
{ {
this.logger.debug( this.logger.debug(
@ -607,23 +607,42 @@ export class BotWeaponGenerator
return weaponTemplate._props.defAmmo; return weaponTemplate._props.defAmmo;
} }
const chosenAmmoTpl = this.weightedRandomHelper.getWeightedValue<string>(compatibleCartridges); let chosenAmmoTpl: string;
if ( while (!chosenAmmoTpl)
weaponTemplate._props.Chambers[0]
&& !weaponTemplate._props.Chambers[0]._props.filters[0].Filter.includes(chosenAmmoTpl)
)
{ {
this.logger.debug( const possibleAmmo = this.weightedRandomHelper.getWeightedValue<string>(compatibleCartridges);
this.localisationService.getText("bot-incompatible_ammo_for_weapon_falling_back_to_default", {
chosenAmmo: chosenAmmoTpl, // Check compatibility
weaponId: weaponTemplate._id, if (weaponTemplate._props.Chambers[0]
weaponName: weaponTemplate._name, && !weaponTemplate._props.Chambers[0]._props.filters[0].Filter.includes(possibleAmmo)
defaultAmmo: weaponTemplate._props.defAmmo, )
}), {
); // Ran out of possible choices, use default ammo
if (Object.keys(compatibleCartridges).length === 0)
{
this.logger.debug(
this.localisationService.getText("bot-incompatible_ammo_for_weapon_falling_back_to_default", {
chosenAmmo: chosenAmmoTpl,
weaponId: weaponTemplate._id,
weaponName: weaponTemplate._name,
defaultAmmo: weaponTemplate._props.defAmmo,
}),
);
// Incompatible ammo found, return default (can happen with .366 and 7.62x39 weapons) // Set ammo to default and exit
return weaponTemplate._props.defAmmo; chosenAmmoTpl = weaponTemplate._props.defAmmo;
break;
}
// Not compatible, remove item from possible list and try again
delete compatibleCartridges[possibleAmmo];
}
else
{
// Compatible ammo found
chosenAmmoTpl = possibleAmmo;
break;
}
} }
return chosenAmmoTpl; return chosenAmmoTpl;

View File

@ -498,12 +498,7 @@ export class RepeatableQuestGenerator
const levelsConfig = repeatableConfig.rewardScaling.levels; const levelsConfig = repeatableConfig.rewardScaling.levels;
const roublesConfig = repeatableConfig.rewardScaling.roubles; const roublesConfig = repeatableConfig.rewardScaling.roubles;
// In the available dumps only 2 distinct items were ever requested const distinctItemsToRetrieveCount = this.randomUtil.getInt(1, completionConfig.uniqueItemCount);
let numberDistinctItems = 1;
if (Math.random() > 0.75)
{
numberDistinctItems = 2;
}
const quest = this.generateRepeatableTemplate("Completion", traderId, repeatableConfig.side) as ICompletion; const quest = this.generateRepeatableTemplate("Completion", traderId, repeatableConfig.side) as ICompletion;
@ -570,7 +565,8 @@ export class RepeatableQuestGenerator
} }
// Draw items to ask player to retrieve // Draw items to ask player to retrieve
for (let i = 0; i < numberDistinctItems; i++) let isAmmo = 0
for (let i = 0; i < distinctItemsToRetrieveCount; i++)
{ {
const itemSelected = itemSelection[this.randomUtil.randInt(itemSelection.length)]; const itemSelected = itemSelection[this.randomUtil.randInt(itemSelection.length)];
const itemUnitPrice = this.itemHelper.getItemPrice(itemSelected[0]); const itemUnitPrice = this.itemHelper.getItemPrice(itemSelected[0]);
@ -578,6 +574,14 @@ export class RepeatableQuestGenerator
let maxValue = completionConfig.maxRequestedAmount; let maxValue = completionConfig.maxRequestedAmount;
if (this.itemHelper.isOfBaseclass(itemSelected[0], BaseClasses.AMMO)) if (this.itemHelper.isOfBaseclass(itemSelected[0], BaseClasses.AMMO))
{ {
// Prevent multiple ammo requirements from being picked, stop after 6 attempts
if (isAmmo > 0 && isAmmo < 6)
{
isAmmo++;
i--;
continue;
}
isAmmo++;
minValue = completionConfig.minRequestedBulletAmount; minValue = completionConfig.minRequestedBulletAmount;
maxValue = completionConfig.maxRequestedBulletAmount; maxValue = completionConfig.maxRequestedBulletAmount;
} }
@ -907,13 +911,23 @@ export class RepeatableQuestGenerator
} }
rewardIndex++; rewardIndex++;
const traderWhitelistDetails = repeatableConfig.traderWhitelist.find((x) => x.traderId === traderId);
if (traderWhitelistDetails.rewardCanBeWeapon && this.randomUtil.getChance100(traderWhitelistDetails.weaponRewardChancePercent))
{
// Add a random default preset weapon as reward
const defaultPresets = Object.values(this.presetHelper.getDefaultPresets());
const defaultPreset = this.randomUtil.getArrayValue(defaultPresets);
// use _encyclopedia as its always the base items _tpl, items[0] isnt guaranteed to be base item
rewards.Success.push(this.generateRewardItem(defaultPreset._encyclopedia, 1, rewardIndex, defaultPreset._items));
rewardIndex++;
}
if (rewardItemPool.length > 0) if (rewardItemPool.length > 0)
{ {
let weaponRewardCount = 0;
for (let i = 0; i < rewardNumItems; i++) for (let i = 0; i < rewardNumItems; i++)
{ {
let itemCount = 1; let rewardItemStackCount = 1;
let children: Item[] = null;
const itemSelected = rewardItemPool[this.randomUtil.randInt(rewardItemPool.length)]; const itemSelected = rewardItemPool[this.randomUtil.randInt(rewardItemPool.length)];
if (this.itemHelper.isOfBaseclass(itemSelected._id, BaseClasses.AMMO)) if (this.itemHelper.isOfBaseclass(itemSelected._id, BaseClasses.AMMO))
{ {
@ -924,46 +938,23 @@ export class RepeatableQuestGenerator
} }
// Randomise the cartridge count returned // Randomise the cartridge count returned
itemCount = this.randomUtil.randInt( rewardItemStackCount = this.randomUtil.randInt(
repeatableConfig.rewardAmmoStackMinSize, repeatableConfig.rewardAmmoStackMinSize,
itemSelected._props.StackMaxSize, itemSelected._props.StackMaxSize,
); );
} }
else if (this.itemHelper.isOfBaseclass(itemSelected._id, BaseClasses.WEAPON))
{
if (weaponRewardCount >= 1)
{
// Limit weapon rewards to 1 per daily
rewardItemPool = rewardItemPool.filter((x) =>
!this.itemHelper.isOfBaseclass(x._id, BaseClasses.WEAPON)
);
continue;
}
let defaultPreset = this.presetHelper.getDefaultPreset(itemSelected._id);
if (!defaultPreset)
{
// No default for chosen weapon found, get any random default weapon preset
const defaultPresets = Object.values(this.presetHelper.getDefaultPresets());
defaultPreset = this.randomUtil.getArrayValue(defaultPresets);
}
children = this.ragfairServerHelper.reparentPresets(defaultPreset._items[0], defaultPreset._items);
weaponRewardCount++;
}
// 25% chance to double reward stack (item should be stackable and not weapon) // 25% chance to double reward stack (item should be stackable and not weapon)
if (this.increaseRewardItemStackSize(itemSelected)) if (this.canIncreaseRewardItemStackSize(itemSelected, 70000))
{ {
itemCount = 2; rewardItemStackCount = this.getRandomisedRewardItemStackSizeByPrice(itemSelected);
} }
rewards.Success.push(this.generateRewardItem(itemSelected._id, itemCount, rewardIndex, children)); rewards.Success.push(this.generateRewardItem(itemSelected._id, rewardItemStackCount, rewardIndex));
rewardIndex++; rewardIndex++;
const itemCost = (this.itemHelper.isOfBaseclass(itemSelected._id, BaseClasses.WEAPON)) const itemCost = this.itemHelper.getStaticItemPrice(itemSelected._id);
? this.itemHelper.getItemMaxPrice(children[0]._tpl) // use if preset is not default : this.itemHelper.getWeaponPresetPrice(children[0], children, this.itemHelper.getStaticItemPrice(itemSelected._id)) roublesBudget -= rewardItemStackCount * itemCost;
: this.itemHelper.getStaticItemPrice(itemSelected._id);
roublesBudget -= itemCount * itemCost;
// If we still have budget narrow down possible items // If we still have budget narrow down possible items
if (roublesBudget > 0) if (roublesBudget > 0)
@ -1010,15 +1001,34 @@ export class RepeatableQuestGenerator
/** /**
* Should reward item have stack size increased (25% chance) * Should reward item have stack size increased (25% chance)
* @param item Item to possibly increase stack size of * @param item Item to possibly increase stack size of
* @param maxRoublePriceToStack Maximum rouble price an item can be to still be chosen for stacking
* @returns True if it should * @returns True if it should
*/ */
protected increaseRewardItemStackSize(item: ITemplateItem): boolean protected canIncreaseRewardItemStackSize(item: ITemplateItem, maxRoublePriceToStack: number): boolean
{ {
return item._props.StackMaxSize > 1 return this.itemHelper.getStaticItemPrice(item._id) < maxRoublePriceToStack
&& !this.itemHelper.isOfBaseclass(item._id, BaseClasses.WEAPON) && !this.itemHelper.isOfBaseclasses(item._id, [BaseClasses.WEAPON, BaseClasses.AMMO])
&& this.randomUtil.getChance100(25); && this.randomUtil.getChance100(25);
} }
/**
* Get a randomised number a reward items stack size should be based on its handbook price
* @param item Reward item to get stack size for
* @returns Stack size value
*/
protected getRandomisedRewardItemStackSizeByPrice(item: ITemplateItem): number
{
const rewardItemPrice = this.itemHelper.getStaticItemPrice(item._id);
if (rewardItemPrice < 3000) {
return this.randomUtil.getArrayValue([2, 3, 4]);
}
else if (rewardItemPrice < 10000) {
return this.randomUtil.getArrayValue([2, 3]);
}
return 2;
}
/** /**
* Select a number of items that have a colelctive value of the passed in parameter * Select a number of items that have a colelctive value of the passed in parameter
* @param repeatableConfig Config * @param repeatableConfig Config
@ -1063,19 +1073,20 @@ export class RepeatableQuestGenerator
* @param {integer} index All rewards will be appended to a list, for unknown reasons the client wants the index * @param {integer} index All rewards will be appended to a list, for unknown reasons the client wants the index
* @returns {object} Object of "Reward"-item-type * @returns {object} Object of "Reward"-item-type
*/ */
protected generateRewardItem(tpl: string, value: number, index: number, preset = null): IReward protected generateRewardItem(tpl: string, value: number, index: number, preset: Item[] = null): IReward
{ {
const id = this.objectId.generate(); const id = this.objectId.generate();
const rewardItem: IReward = { target: id, value: value, type: "Item", index: index }; const rewardItem: IReward = { target: id, value: value, type: "Item", index: index };
const rootItem = { _id: id, _tpl: tpl, upd: { StackObjectsCount: value, SpawnedInSession: true } };
if (preset) if (preset)
{ {
const rootItem = preset.find(x => x._tpl === tpl);
rewardItem.target = rootItem._id; // Target property and root items id must match
rewardItem.items = this.ragfairServerHelper.reparentPresets(rootItem, preset); rewardItem.items = this.ragfairServerHelper.reparentPresets(rootItem, preset);
} }
else else
{ {
const rootItem = { _id: id, _tpl: tpl, upd: { StackObjectsCount: value, SpawnedInSession: true } };
rewardItem.items = [rootItem]; rewardItem.items = [rootItem];
} }
return rewardItem; return rewardItem;

View File

@ -2,6 +2,7 @@ import { inject, injectable } from "tsyringe";
import { Money } from "@spt-aki/models/enums/Money"; import { Money } from "@spt-aki/models/enums/Money";
import { DatabaseServer } from "@spt-aki/servers/DatabaseServer"; import { DatabaseServer } from "@spt-aki/servers/DatabaseServer";
import { JsonUtil } from "@spt-aki/utils/JsonUtil";
class LookupItem<T, I> class LookupItem<T, I>
{ {
@ -33,7 +34,10 @@ export class HandbookHelper
protected lookupCacheGenerated = false; protected lookupCacheGenerated = false;
protected handbookPriceCache = new LookupCollection(); protected handbookPriceCache = new LookupCollection();
constructor(@inject("DatabaseServer") protected databaseServer: DatabaseServer) constructor(
@inject("DatabaseServer") protected databaseServer: DatabaseServer,
@inject("JsonUtil") protected jsonUtil: JsonUtil
)
{} {}
/** /**
@ -41,7 +45,7 @@ export class HandbookHelper
*/ */
public hydrateLookup(): void public hydrateLookup(): void
{ {
const handbookDb = this.databaseServer.getTables().templates.handbook; const handbookDb = this.jsonUtil.clone(this.databaseServer.getTables().templates.handbook);
for (const handbookItem of handbookDb.Items) for (const handbookItem of handbookDb.Items)
{ {
this.handbookPriceCache.items.byId.set(handbookItem.Id, handbookItem.Price); this.handbookPriceCache.items.byId.set(handbookItem.Id, handbookItem.Price);

View File

@ -21,6 +21,7 @@ import { SaveServer } from "@spt-aki/servers/SaveServer";
import { LocalisationService } from "@spt-aki/services/LocalisationService"; import { LocalisationService } from "@spt-aki/services/LocalisationService";
import { ProfileFixerService } from "@spt-aki/services/ProfileFixerService"; import { ProfileFixerService } from "@spt-aki/services/ProfileFixerService";
import { JsonUtil } from "@spt-aki/utils/JsonUtil"; import { JsonUtil } from "@spt-aki/utils/JsonUtil";
import { ProfileHelper } from "./ProfileHelper";
@injectable() @injectable()
export class InRaidHelper export class InRaidHelper
@ -35,6 +36,7 @@ export class InRaidHelper
@inject("ItemHelper") protected itemHelper: ItemHelper, @inject("ItemHelper") protected itemHelper: ItemHelper,
@inject("DatabaseServer") protected databaseServer: DatabaseServer, @inject("DatabaseServer") protected databaseServer: DatabaseServer,
@inject("InventoryHelper") protected inventoryHelper: InventoryHelper, @inject("InventoryHelper") protected inventoryHelper: InventoryHelper,
@inject("ProfileHelper") protected profileHelper: ProfileHelper,
@inject("QuestHelper") protected questHelper: QuestHelper, @inject("QuestHelper") protected questHelper: QuestHelper,
@inject("PaymentHelper") protected paymentHelper: PaymentHelper, @inject("PaymentHelper") protected paymentHelper: PaymentHelper,
@inject("LocalisationService") protected localisationService: LocalisationService, @inject("LocalisationService") protected localisationService: LocalisationService,
@ -62,19 +64,16 @@ export class InRaidHelper
*/ */
public addUpdToMoneyFromRaid(items: Item[]): void public addUpdToMoneyFromRaid(items: Item[]): void
{ {
for (const item of items) for (const item of items.filter(x => this.paymentHelper.isMoneyTpl(x._tpl)))
{ {
if (this.paymentHelper.isMoneyTpl(item._tpl)) if (!item.upd)
{ {
if (!item.upd) item.upd = {};
{ }
item.upd = {};
}
if (!item.upd.StackObjectsCount) if (!item.upd.StackObjectsCount)
{ {
item.upd.StackObjectsCount = 1; item.upd.StackObjectsCount = 1;
}
} }
} }
} }
@ -140,18 +139,44 @@ export class InRaidHelper
profileData: IPmcData, profileData: IPmcData,
saveProgressRequest: ISaveProgressRequestData, saveProgressRequest: ISaveProgressRequestData,
sessionID: string, sessionID: string,
): IPmcData ): void
{ {
// remove old skill fatigue // Remove skill fatigue values
this.resetSkillPointsEarnedDuringRaid(saveProgressRequest.profile); this.resetSkillPointsEarnedDuringRaid(saveProgressRequest.profile);
// set profile data // Set profile data
profileData.Info.Level = saveProgressRequest.profile.Info.Level; profileData.Info.Level = saveProgressRequest.profile.Info.Level;
profileData.Skills = saveProgressRequest.profile.Skills; profileData.Skills = saveProgressRequest.profile.Skills;
profileData.Stats.Eft = saveProgressRequest.profile.Stats.Eft; profileData.Stats.Eft = saveProgressRequest.profile.Stats.Eft;
profileData.Encyclopedia = saveProgressRequest.profile.Encyclopedia; profileData.Encyclopedia = saveProgressRequest.profile.Encyclopedia;
profileData.ConditionCounters = saveProgressRequest.profile.ConditionCounters; profileData.ConditionCounters = saveProgressRequest.profile.ConditionCounters;
this.validateBackendCounters(saveProgressRequest, profileData);
profileData.SurvivorClass = saveProgressRequest.profile.SurvivorClass;
// Add experience points
profileData.Info.Experience += profileData.Stats.Eft.TotalSessionExperience;
profileData.Stats.Eft.TotalSessionExperience = 0;
this.setPlayerInRaidLocationStatusToNone(sessionID);
}
/**
* Reset the skill points earned in a raid to 0, ready for next raid
* @param profile Profile to update
*/
protected resetSkillPointsEarnedDuringRaid(profile: IPmcData): void
{
for (const skill of profile.Skills.Common)
{
skill.PointsEarnedDuringSession = 0.0;
}
}
/** Check counters are correct in profile */
protected validateBackendCounters(saveProgressRequest: ISaveProgressRequestData, profileData: IPmcData): void
{
for (const backendCounterKey in saveProgressRequest.profile.BackendCounters) for (const backendCounterKey in saveProgressRequest.profile.BackendCounters)
{ {
// Skip counters with no id // Skip counters with no id
@ -178,37 +203,51 @@ export class InRaidHelper
if (matchingPreRaidCounter.value !== postRaidValue) if (matchingPreRaidCounter.value !== postRaidValue)
{ {
this.logger.error( this.logger.error(
`Backendcounter: ${backendCounterKey} value is different post raid, old: ${matchingPreRaidCounter.value} new: ${postRaidValue}`, `Backendcounter: ${backendCounterKey} value is different post raid, old: ${matchingPreRaidCounter.value} new: ${postRaidValue}`
); );
} }
} }
this.processFailedQuests(sessionID, profileData, profileData.Quests, saveProgressRequest.profile.Quests);
profileData.Quests = saveProgressRequest.profile.Quests;
// Transfer effects from request to profile
this.transferPostRaidLimbEffectsToProfile(saveProgressRequest, profileData);
this.applyTraderStandingAdjustments(profileData.TradersInfo, saveProgressRequest.profile.TradersInfo);
profileData.SurvivorClass = saveProgressRequest.profile.SurvivorClass;
// Add experience points
profileData.Info.Experience += profileData.Stats.Eft.TotalSessionExperience;
profileData.Stats.Eft.TotalSessionExperience = 0;
this.setPlayerInRaidLocationStatusToNone(sessionID);
if (!saveProgressRequest.isPlayerScav)
{
this.profileFixerService.checkForAndFixPmcProfileIssues(profileData);
}
return profileData;
} }
/** /**
* Look for quests not are now status = fail that were not failed pre-raid and run the failQuest() function * Update various serverPMC profile values; quests/limb hp/trader standing with values post-raic
* @param pmcData Server PMC profile
* @param saveProgressRequest Post-raid request data
* @param sessionId Session id
*/
public updatePmcProfileDataPostRaid(pmcData: IPmcData, saveProgressRequest: ISaveProgressRequestData, sessionId: string): void
{
// Process failed quests then copy everything
this.processFailedQuests(sessionId, pmcData, pmcData.Quests, saveProgressRequest.profile.Quests);
pmcData.Quests = saveProgressRequest.profile.Quests;
// No need to do this for scav, old scav is deleted and new one generated
this.transferPostRaidLimbEffectsToProfile(saveProgressRequest, pmcData);
// Trader standing only occur on pmc profile, scav kills are handled in handlePostRaidPlayerScavKarmaChanges()
// Scav client data has standing values of 0 for all traders, DO NOT RUN ON SCAV RAIDS
this.applyTraderStandingAdjustments(pmcData.TradersInfo, saveProgressRequest.profile.TradersInfo);
this.profileFixerService.checkForAndFixPmcProfileIssues(pmcData);
}
/**
* Update scav quest values on server profile with updated values post-raid
* @param scavData Server scav profile
* @param saveProgressRequest Post-raid request data
* @param sessionId Session id
*/
public updateScavProfileDataPostRaid(scavData: IPmcData, saveProgressRequest: ISaveProgressRequestData, sessionId: string): void
{
// Only copy active quests into scav profile // Progress will later to copied over to PMC profile
const existingActiveQuestIds = scavData.Quests.filter(x => x.status !== QuestStatus.AvailableForStart).map(x => x.qid);
scavData.Quests = saveProgressRequest.profile.Quests.filter(x => existingActiveQuestIds.includes(x.qid));
this.profileFixerService.checkForAndFixScavProfileIssues(scavData);
}
/**
* Look for quests with status = fail that were not failed pre-raid and run the failQuest() function
* @param sessionId Player id * @param sessionId Player id
* @param pmcData Player profile * @param pmcData Player profile
* @param preRaidQuests Quests prior to starting raid * @param preRaidQuests Quests prior to starting raid
@ -250,14 +289,6 @@ export class InRaidHelper
} }
} }
protected resetSkillPointsEarnedDuringRaid(profile: IPmcData): void
{
for (const skill of profile.Skills.Common)
{
skill.PointsEarnedDuringSession = 0.0;
}
}
/** /**
* Take body part effects from client profile and apply to server profile * Take body part effects from client profile and apply to server profile
* @param saveProgressRequest post-raid request * @param saveProgressRequest post-raid request

View File

@ -735,7 +735,7 @@ export class QuestHelper
/** /**
* Give player quest rewards - Skills/exp/trader standing/items/assort unlocks - Returns reward items player earned * Give player quest rewards - Skills/exp/trader standing/items/assort unlocks - Returns reward items player earned
* @param pmcData Player profile * @param profileData Player profile (scav or pmc)
* @param questId questId of quest to get rewards for * @param questId questId of quest to get rewards for
* @param state State of the quest to get rewards for * @param state State of the quest to get rewards for
* @param sessionId Session id * @param sessionId Session id
@ -743,14 +743,17 @@ export class QuestHelper
* @returns Array of reward objects * @returns Array of reward objects
*/ */
public applyQuestReward( public applyQuestReward(
pmcData: IPmcData, profileData: IPmcData,
questId: string, questId: string,
state: QuestStatus, state: QuestStatus,
sessionId: string, sessionId: string,
questResponse: IItemEventRouterResponse, questResponse: IItemEventRouterResponse,
): Reward[] ): Reward[]
{ {
let questDetails = this.getQuestFromDb(questId, pmcData); // Repeatable quest base data is always in PMCProfile, `profileData` may be scav profile
// TODO: consider moving repeatable quest data to profile-agnostic location
const pmcProfile = this.profileHelper.getPmcProfile(sessionId);
let questDetails = this.getQuestFromDb(questId, pmcProfile);
if (!questDetails) if (!questDetails)
{ {
this.logger.warning(`Unable to find quest: ${questId} from db, unable to give quest rewards`); this.logger.warning(`Unable to find quest: ${questId} from db, unable to give quest rewards`);
@ -759,7 +762,7 @@ export class QuestHelper
} }
// Check for and apply intel center money bonus if it exists // Check for and apply intel center money bonus if it exists
const questMoneyRewardBonus = this.getQuestMoneyRewardBonus(pmcData); const questMoneyRewardBonus = this.getQuestMoneyRewardBonus(pmcProfile);
if (questMoneyRewardBonus > 0) if (questMoneyRewardBonus > 0)
{ {
// Apply additional bonus from hideout skill // Apply additional bonus from hideout skill
@ -774,7 +777,7 @@ export class QuestHelper
{ {
case QuestRewardType.SKILL: case QuestRewardType.SKILL:
this.profileHelper.addSkillPointsToPlayer( this.profileHelper.addSkillPointsToPlayer(
pmcData, profileData,
reward.target as SkillTypes, reward.target as SkillTypes,
Number(reward.value), Number(reward.value),
); );
@ -799,7 +802,7 @@ export class QuestHelper
break; break;
case QuestRewardType.PRODUCTIONS_SCHEME: case QuestRewardType.PRODUCTIONS_SCHEME:
this.findAndAddHideoutProductionIdToProfile( this.findAndAddHideoutProductionIdToProfile(
pmcData, pmcProfile,
reward, reward,
questDetails, questDetails,
sessionId, sessionId,

View File

@ -390,6 +390,8 @@ export interface Productive
sptIsScavCase?: boolean; sptIsScavCase?: boolean;
/** Some crafts are always inProgress, but need to be reset, e.g. water collector */ /** Some crafts are always inProgress, but need to be reset, e.g. water collector */
sptIsComplete?: boolean; sptIsComplete?: boolean;
/** Is the craft a Continuous, e.g bitcoins/water collector */
sptIsContinuous?: boolean;
} }
export interface Production extends Productive export interface Production extends Productive

View File

@ -77,6 +77,8 @@ export interface ITraderWhitelist
traderId: string; traderId: string;
questTypes: string[]; questTypes: string[];
rewardBaseWhitelist: string[]; rewardBaseWhitelist: string[];
rewardCanBeWeapon: boolean
weaponRewardChancePercent: number
} }
export interface IRepeatableQuestTypesConfig export interface IRepeatableQuestTypesConfig
@ -104,6 +106,7 @@ export interface ICompletion extends IBaseQuestConfig
{ {
minRequestedAmount: number; minRequestedAmount: number;
maxRequestedAmount: number; maxRequestedAmount: number;
uniqueItemCount: number
minRequestedBulletAmount: number; minRequestedBulletAmount: number;
maxRequestedBulletAmount: number; maxRequestedBulletAmount: number;
useWhitelist: boolean; useWhitelist: boolean;

View File

@ -84,8 +84,8 @@ export class EventOutputHolder
profileChanges.improvements = this.jsonUtil.clone(this.getImprovementsFromProfileAndFlagComplete(pmcData)); profileChanges.improvements = this.jsonUtil.clone(this.getImprovementsFromProfileAndFlagComplete(pmcData));
profileChanges.traderRelations = this.constructTraderRelations(pmcData.TradersInfo); profileChanges.traderRelations = this.constructTraderRelations(pmcData.TradersInfo);
// Fixes container craft from water collector not resetting after collection // Fixes container craft from water collector not resetting after collection + removed completed normal crafts
this.resetSptIsCompleteFlaggedCrafts(pmcData.Hideout.Production); this.cleanUpCompleteCraftsInProfile(pmcData.Hideout.Production);
} }
/** /**
@ -156,9 +156,8 @@ export class EventOutputHolder
continue; continue;
} }
// Complete and is a water collector craft // Complete and is Continuous e.g. water collector
// Needed as canister craft (water collector) is continuous if (production.sptIsComplete && production.sptIsContinuous)
if (production.sptIsComplete && productionKey === "5d5589c1f934db045e6c5492")
{ {
continue; continue;
} }
@ -184,7 +183,7 @@ export class EventOutputHolder
} }
} }
// Return null of there's no crafts to send to client to match live behaviour // Return null if there's no crafts to send to client to match live behaviour
return (Object.keys(productions).length > 0) ? productions : null return (Object.keys(productions).length > 0) ? productions : null
} }
@ -192,17 +191,23 @@ export class EventOutputHolder
* Required as continuous productions don't reset and stay at 100% completion but client thinks it hasn't started * Required as continuous productions don't reset and stay at 100% completion but client thinks it hasn't started
* @param productions Productions in a profile * @param productions Productions in a profile
*/ */
protected resetSptIsCompleteFlaggedCrafts(productions: Record<string, Productive>): void protected cleanUpCompleteCraftsInProfile(productions: Record<string, Productive>): void
{ {
for (const productionKey in productions) for (const productionKey in productions)
{ {
const production = productions[productionKey]; const production = productions[productionKey];
if (production.sptIsComplete) if (production.sptIsComplete && production.sptIsContinuous)
{ {
// Water collector / Bitcoin etc
production.sptIsComplete = false; production.sptIsComplete = false;
production.Progress = 0; production.Progress = 0;
production.StartTimestamp = this.timeUtil.getTimestamp(); production.StartTimestamp = this.timeUtil.getTimestamp();
} }
else if (!production.inProgress)
{
// Normal completed craft, delete
delete productions[productionKey];
}
} }
} }
} }

View File

@ -185,7 +185,7 @@ export class SaveServer
{ {
const filePath = `${this.profileFilepath}${sessionID}.json`; const filePath = `${this.profileFilepath}${sessionID}.json`;
// run callbacks // Run pre-save callbacks before we save into json
for (const callback in this.onBeforeSaveCallbacks) for (const callback in this.onBeforeSaveCallbacks)
{ {
const previous = this.profiles[sessionID]; const previous = this.profiles[sessionID];

View File

@ -162,6 +162,15 @@ export class ProfileFixerService
} }
} }
/**
* Find issues in the scav profile data that may cause issues
* @param scavProfile profile to check and fix
*/
public checkForAndFixScavProfileIssues(scavProfile: IPmcData): void
{
this.updateProfileQuestDataValues(scavProfile);
}
protected addMissingGunStandContainerImprovements(pmcProfile: IPmcData): void protected addMissingGunStandContainerImprovements(pmcProfile: IPmcData): void
{ {
const weaponStandArea = pmcProfile.Hideout.Areas.find((x) => x.type === HideoutAreas.WEAPON_STAND); const weaponStandArea = pmcProfile.Hideout.Areas.find((x) => x.type === HideoutAreas.WEAPON_STAND);
@ -474,20 +483,20 @@ export class ProfileFixerService
* Adjust profile quest status and statusTimers object values * Adjust profile quest status and statusTimers object values
* quest.status is numeric e.g. 2 * quest.status is numeric e.g. 2
* quest.statusTimers keys are numeric as strings e.g. "2" * quest.statusTimers keys are numeric as strings e.g. "2"
* @param pmcProfile profile to update * @param profile profile to update
*/ */
protected updateProfileQuestDataValues(pmcProfile: IPmcData): void protected updateProfileQuestDataValues(profile: IPmcData): void
{ {
if (!pmcProfile.Quests) if (!profile.Quests)
{ {
return; return;
} }
const fixes = new Map<any, number>(); const fixes = new Map<any, number>();
const questsToDelete: IQuestStatus[] = []; const questsToDelete: IQuestStatus[] = [];
const fullProfile = this.profileHelper.getFullProfile(pmcProfile.sessionId); const fullProfile = this.profileHelper.getFullProfile(profile.sessionId);
const isDevProfile = fullProfile?.info.edition.toLowerCase() === "spt developer"; const isDevProfile = fullProfile?.info.edition.toLowerCase() === "spt developer";
for (const quest of pmcProfile.Quests) for (const quest of profile.Quests)
{ {
// Old profiles had quests with a bad status of 0 (invalid) added to profile, remove them // Old profiles had quests with a bad status of 0 (invalid) added to profile, remove them
// E.g. compensation for damage showing before standing check was added to getClientQuests() // E.g. compensation for damage showing before standing check was added to getClientQuests()
@ -526,7 +535,7 @@ export class ProfileFixerService
for (const questToDelete of questsToDelete) for (const questToDelete of questsToDelete)
{ {
pmcProfile.Quests.splice(pmcProfile.Quests.indexOf(questToDelete), 1); profile.Quests.splice(profile.Quests.indexOf(questToDelete), 1);
} }
if (fixes.size > 0) if (fixes.size > 0)
@ -563,6 +572,10 @@ export class ProfileFixerService
this.logger.debug("Missing RepeatableQuests property added to profile"); this.logger.debug("Missing RepeatableQuests property added to profile");
} }
} }
else
{
pmcProfile.RepeatableQuests = [];
}
} }
/** /**
@ -925,7 +938,7 @@ export class ProfileFixerService
} }
} }
for (const repeatable of fullProfile.characters.pmc.RepeatableQuests) for (const repeatable of fullProfile.characters.pmc.RepeatableQuests ?? [])
{ {
for (const activeQuest of repeatable.activeQuests ?? []) for (const activeQuest of repeatable.activeQuests ?? [])
{ {

View File

@ -239,7 +239,7 @@ export class RepairService
skillPoints += this.repairConfig.weaponTreatment.critSuccessAmount; skillPoints += this.repairConfig.weaponTreatment.critSuccessAmount;
} }
return skillPoints; return Math.max(skillPoints, 0);
} }
/** /**