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"
],
"bossItems": [
"6275303a9f372d6ea97f9ec7",
"62a61bbf8ec41a51b34758d2",
"628e4dd1f477aa12234918aa",
"628b9784bcf6e2659e09b8a2",
"628bc7fb408e2b2e9c0801b1",
"628baf0b967de16aab5a4f36",
"62963c18dbc8ab5f0d382d0b",
"628b9c7d45122232a872358f",
"5fc64ea372b0dd78d51159dc",
"64ca3d3954fc657e230529cc",
"64637076203536ad5600c990",
"5c0e874186f7745dc7616606",
"5c0e842486f77443a74d2976",
"5c0e541586f7747fa54205c9",
"5b3b713c5acfc4330140bd8d",
"5e997f0b86f7741ac73993e2",
"5d08d21286f774736e7c94c3",
"6087e570b998180e9f76dc24",
"60a7ad2a2198820d95707a2e",
"60a7ad3a0c5cb24b0134664a",
"60a7acf20c5cb24b01346648",
"636270263f2495c26f00b007",
"63626d904aa74b8fe30ab426",
"63611865ba5b90db0c0399d1",
"5eff09cd30a7dc22fd1ddfed",
"5efde6b4f5448336730dbd61"
]
"bossItems": [
"6275303a9f372d6ea97f9ec7",
"62a61bbf8ec41a51b34758d2",
"628e4dd1f477aa12234918aa",
"628b9784bcf6e2659e09b8a2",
"628bc7fb408e2b2e9c0801b1",
"628baf0b967de16aab5a4f36",
"62963c18dbc8ab5f0d382d0b",
"628b9c7d45122232a872358f",
"5fc64ea372b0dd78d51159dc",
"64ca3d3954fc657e230529cc",
"64637076203536ad5600c990",
"5c0e874186f7745dc7616606",
"5c0e842486f77443a74d2976",
"5c0e541586f7747fa54205c9",
"5b3b713c5acfc4330140bd8d",
"5e997f0b86f7741ac73993e2",
"5d08d21286f774736e7c94c3",
"6087e570b998180e9f76dc24",
"60a7ad2a2198820d95707a2e",
"60a7ad3a0c5cb24b0134664a",
"60a7acf20c5cb24b01346648",
"636270263f2495c26f00b007",
"63626d904aa74b8fe30ab426",
"63611865ba5b90db0c0399d1",
"5eff09cd30a7dc22fd1ddfed",
"5efde6b4f5448336730dbd61",
"609e860ebd219504d8507525"
]
}

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

@ -1,40 +1,41 @@
{
"fail": {},
"started": {},
"success": {
"6507ff2c644a656aee0f82b2": "5ac244c486f77413e12cf945",
"6507ff2c644a656aee0f8335": "639872fe8871e1272b10ccf6",
"6492e44bf4287b13040fca51": "60e71ce009d7c801eb0c0ec6",
"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",
"6507ff2d644a656aee0f848d": "5c0d4c12d09282029f539173",
"6507ff2a644a656aee0f7fce": "5a27bb3d86f77411ea361a21",
"6507ff2b644a656aee0f8107": "5a27bc6986f7741c7358402b",
"6507ff2d644a656aee0f845f": "5a27bc6986f7741c7358402b",
"6507ff2c644a656aee0f830d": "6179b4d1bca27a099552e04e",
"6507ff2d644a656aee0f835c": "5b47825886f77468074618d3",
"6507ff2c644a656aee0f8260": "61958c366726521dd96828ec",
"6507ff2d644a656aee0f83b1": "639135f286e646067c176a87",
"6507ff2b644a656aee0f816b": "6179aff8f57fb279792c60a1",
"6507ff2a644a656aee0f800a": "5edac020218d181e29451446",
"6507ff2a644a656aee0f800e": "5c0d4e61d09282029f53920e",
"6507ff2a644a656aee0f8029": "5a27b80086f774429a5d7e20",
"6507ff2b644a656aee0f8089": "5a27ba9586f7741b543d8e85",
"6507ff2b644a656aee0f809e": "5a03173786f77451cb427172",
"6507ff2b644a656aee0f80df": "5a27b9de86f77464e5044585",
"6507ff2b644a656aee0f80e3": "5b477f7686f7744d1b23c4d2",
"6507ff2e644a656aee0f8562": "63a9b229813bba58a50c9ee5",
"6507ff2d644a656aee0f838c": "63a9b229813bba58a50c9ee5"
},
"fail": {}
"6507ff2b644a656aee0f8107": "5a27bc6986f7741c7358402b",
"6507ff2b644a656aee0f8159": "5a27b75b86f7742e97191958",
"6507ff2b644a656aee0f816b": "6179aff8f57fb279792c60a1",
"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
{
const output = this.eventOutputHolder.getOutput(sessionID);
const hideoutDb = this.databaseServer.getTables().hideout;
if (request.recipeId === HideoutHelper.bitcoinFarm)
{
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)
{
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)
{
return this.handleScavCase(sessionID, pmcData, request, output);
@ -814,7 +815,7 @@ export class HideoutController
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);
if (area && request.recipeId !== area.lastRecipe)
{
@ -831,13 +832,13 @@ export class HideoutController
hoursCrafting -= this.hideoutConfig.hoursForSkillCrafting * multiplierCrafting;
}
// increment
// Increment
// if addItem passes validation:
// - increment skill point for crafting
// - delete the production in profile Hideout.Production
const callback = () =>
{
// manager Hideout skill
// Manager Hideout skill
// ? use a configuration variable for the value?
const globals = this.databaseServer.getTables().globals;
this.profileHelper.addSkillPointsToPlayer(
@ -859,12 +860,16 @@ export class HideoutController
area.lastRecipe = request.recipeId;
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].sptIsContinuous = recipe.continuous;
// Remove the old production from output object before its sent to client
delete output.profileChanges[sessionID].production[request.recipeId];
// Flag normal crafts as complete
if (!recipe.continuous)
{
pmcData.Hideout.Production[prodId].inProgress = false;
}
};
// Handle the isEncoded flag from recipe
if (recipe.isEncoded)
@ -946,6 +951,8 @@ export class HideoutController
{
// Flag as complete - will be cleaned up later by update() process
pmcData.Hideout.Production[prodId].sptIsComplete = true;
pmcData.Hideout.Production[prodId].inProgress = false;
};
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
{
const serverProfile = this.saveServer.getProfile(sessionID);
const locationName = serverProfile.inraid.location.toLowerCase();
const serveProfile = this.saveServer.getProfile(sessionID);
const locationName = serveProfile.inraid.location.toLowerCase();
const map: ILocationBase = this.databaseServer.getTables().locations[locationName].base;
const mapHasInsuranceEnabled = map.Insurance;
let serverPmcData = serverProfile.characters.pmc;
let serverPmcProfile = serveProfile.characters.pmc;
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
this.markOrRemoveFoundInRaidItems(postRaidRequest);
@ -127,20 +128,20 @@ export class InraidController
postRaidRequest.profile.Inventory.items = this.itemHelper.replaceIDs(
postRaidRequest.profile,
postRaidRequest.profile.Inventory.items,
serverPmcData.InsuredItems,
serverPmcProfile.InsuredItems,
postRaidRequest.profile.Inventory.fastPanel,
);
this.inRaidHelper.addUpdToMoneyFromRaid(postRaidRequest.profile.Inventory.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
if (mapHasInsuranceEnabled)
{
this.insuranceService.storeLostGear(serverPmcData, postRaidRequest, preRaidGear, sessionID, isDead);
this.insuranceService.storeLostGear(serverPmcProfile, postRaidRequest, preRaidGear, sessionID, isDead);
}
else
{
@ -151,7 +152,7 @@ export class InraidController
if (locationName === "lighthouse" && postRaidRequest.profile.Info.Side.toLowerCase() === "usec")
{
// 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")
);
if (remainingCounter?.Value > 0)
@ -164,12 +165,12 @@ export class InraidController
{
this.pmcChatResponseService.sendKillerResponse(
sessionID,
serverPmcData,
serverPmcProfile,
postRaidRequest.profile.Stats.Eft.Aggressor,
);
this.matchBotDetailsCacheService.clearCache();
serverPmcData = this.performPostRaidActionsWhenDead(postRaidRequest, serverPmcData, sessionID);
serverPmcProfile = this.performPostRaidActionsWhenDead(postRaidRequest, serverPmcProfile, sessionID);
}
const victims = postRaidRequest.profile.Stats.Eft.Victims.filter((x) =>
@ -177,12 +178,12 @@ export class InraidController
);
if (victims?.length > 0)
{
this.pmcChatResponseService.sendVictimResponse(sessionID, victims, serverPmcData);
this.pmcChatResponseService.sendVictimResponse(sessionID, victims, serverPmcProfile);
}
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
{
const pmcData = this.profileHelper.getPmcProfile(sessionID);
let scavData = this.profileHelper.getScavProfile(sessionID);
const serverPmcProfile = this.profileHelper.getPmcProfile(sessionID);
const serverScavProfile = this.profileHelper.getScavProfile(sessionID);
const isDead = this.isPlayerDead(postRaidRequest.exit);
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
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
this.migrateScavQuestProgressToPmcProfile(scavData, pmcData);
this.migrateScavQuestProgressToPmcProfile(serverScavProfile, serverPmcProfile);
}
// Check for exit status
// Change loot FiR status based on exit status
this.markOrRemoveFoundInRaidItems(postRaidRequest);
postRaidRequest.profile.Inventory.items = this.itemHelper.replaceIDs(
postRaidRequest.profile,
postRaidRequest.profile.Inventory.items,
pmcData.InsuredItems,
serverPmcProfile.InsuredItems,
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.handlePostRaidPlayerScavProcess(scavData, sessionID, postRaidRequest, pmcData, isDead);
// Reset hp/regenerate loot
this.handlePostRaidPlayerScavProcess(serverScavProfile, sessionID, postRaidRequest, serverPmcProfile, isDead);
}
/**
* Does provided profile contain any condition counters
* @param profile Profile to check for condition counters
* @returns
* @returns Profile has condition counters
*/
protected profileHasConditionCounters(profile: IPmcData): boolean
{
@ -318,28 +323,32 @@ export class InraidController
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
{
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)
{
this.logger.warning(`No PMC quest found for ID: ${quest.qid}`);
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
if (
quest.status !== <any>QuestStatus[pmcQuest.status]
|| quest.statusTimers.length !== pmcQuest.statusTimers.length
quest.status !== pmcQuest.status
|| 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}`,
);
pmcQuest.status = <any>QuestStatus[quest.status];
pmcQuest.status = quest.status;
// Copy status timers over + fix bad enum key for each
pmcQuest.statusTimers = quest.statusTimers;
@ -357,7 +366,7 @@ export class InraidController
// Loop over all scav counters and add into pmc profile
for (const scavCounter of scavProfile.ConditionCounters.Counters)
{
this.logger.warning(
this.logger.debug(
`Processing counter: ${scavCounter.id} value:${scavCounter.value} quest:${scavCounter.qid}`,
);
const counterInPmcProfile = pmcProfile.ConditionCounters.Counters.find((x) => x.id === scavCounter.id);
@ -368,14 +377,14 @@ export class InraidController
continue;
}
this.logger.warning(
this.logger.debug(
`Counter id: ${scavCounter.id} already exists in pmc profile! with value: ${counterInPmcProfile.value} for quest: ${counterInPmcProfile.qid}`,
);
// Only adjust counter value if its changed
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;
}
}

View File

@ -592,7 +592,7 @@ export class BotWeaponGenerator
{
const desiredCaliber = this.getWeaponCaliber(weaponTemplate);
const compatibleCartridges = ammo[desiredCaliber];
const compatibleCartridges = this.jsonUtil.clone(ammo[desiredCaliber]);
if (!compatibleCartridges || compatibleCartridges?.length === 0)
{
this.logger.debug(
@ -607,23 +607,42 @@ export class BotWeaponGenerator
return weaponTemplate._props.defAmmo;
}
const chosenAmmoTpl = this.weightedRandomHelper.getWeightedValue<string>(compatibleCartridges);
if (
weaponTemplate._props.Chambers[0]
&& !weaponTemplate._props.Chambers[0]._props.filters[0].Filter.includes(chosenAmmoTpl)
)
let chosenAmmoTpl: string;
while (!chosenAmmoTpl)
{
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,
}),
);
const possibleAmmo = this.weightedRandomHelper.getWeightedValue<string>(compatibleCartridges);
// Incompatible ammo found, return default (can happen with .366 and 7.62x39 weapons)
return weaponTemplate._props.defAmmo;
// Check compatibility
if (weaponTemplate._props.Chambers[0]
&& !weaponTemplate._props.Chambers[0]._props.filters[0].Filter.includes(possibleAmmo)
)
{
// Ran out of possible choices, use default ammo
if (Object.keys(compatibleCartridges).length === 0)
{
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,
}),
);
// Set ammo to default and exit
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;

View File

@ -498,12 +498,7 @@ export class RepeatableQuestGenerator
const levelsConfig = repeatableConfig.rewardScaling.levels;
const roublesConfig = repeatableConfig.rewardScaling.roubles;
// In the available dumps only 2 distinct items were ever requested
let numberDistinctItems = 1;
if (Math.random() > 0.75)
{
numberDistinctItems = 2;
}
const distinctItemsToRetrieveCount = this.randomUtil.getInt(1, completionConfig.uniqueItemCount);
const quest = this.generateRepeatableTemplate("Completion", traderId, repeatableConfig.side) as ICompletion;
@ -570,7 +565,8 @@ export class RepeatableQuestGenerator
}
// 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 itemUnitPrice = this.itemHelper.getItemPrice(itemSelected[0]);
@ -578,6 +574,14 @@ export class RepeatableQuestGenerator
let maxValue = completionConfig.maxRequestedAmount;
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;
maxValue = completionConfig.maxRequestedBulletAmount;
}
@ -907,13 +911,23 @@ export class RepeatableQuestGenerator
}
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)
{
let weaponRewardCount = 0;
for (let i = 0; i < rewardNumItems; i++)
{
let itemCount = 1;
let children: Item[] = null;
let rewardItemStackCount = 1;
const itemSelected = rewardItemPool[this.randomUtil.randInt(rewardItemPool.length)];
if (this.itemHelper.isOfBaseclass(itemSelected._id, BaseClasses.AMMO))
{
@ -924,46 +938,23 @@ export class RepeatableQuestGenerator
}
// Randomise the cartridge count returned
itemCount = this.randomUtil.randInt(
rewardItemStackCount = this.randomUtil.randInt(
repeatableConfig.rewardAmmoStackMinSize,
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)
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++;
const itemCost = (this.itemHelper.isOfBaseclass(itemSelected._id, BaseClasses.WEAPON))
? this.itemHelper.getItemMaxPrice(children[0]._tpl) // use if preset is not default : this.itemHelper.getWeaponPresetPrice(children[0], children, this.itemHelper.getStaticItemPrice(itemSelected._id))
: this.itemHelper.getStaticItemPrice(itemSelected._id);
roublesBudget -= itemCount * itemCost;
const itemCost = this.itemHelper.getStaticItemPrice(itemSelected._id);
roublesBudget -= rewardItemStackCount * itemCost;
// If we still have budget narrow down possible items
if (roublesBudget > 0)
@ -1010,15 +1001,34 @@ export class RepeatableQuestGenerator
/**
* Should reward item have stack size increased (25% chance)
* @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
*/
protected increaseRewardItemStackSize(item: ITemplateItem): boolean
protected canIncreaseRewardItemStackSize(item: ITemplateItem, maxRoublePriceToStack: number): boolean
{
return item._props.StackMaxSize > 1
&& !this.itemHelper.isOfBaseclass(item._id, BaseClasses.WEAPON)
return this.itemHelper.getStaticItemPrice(item._id) < maxRoublePriceToStack
&& !this.itemHelper.isOfBaseclasses(item._id, [BaseClasses.WEAPON, BaseClasses.AMMO])
&& 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
* @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
* @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 rewardItem: IReward = { target: id, value: value, type: "Item", index: index };
const rootItem = { _id: id, _tpl: tpl, upd: { StackObjectsCount: value, SpawnedInSession: true } };
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);
}
else
{
const rootItem = { _id: id, _tpl: tpl, upd: { StackObjectsCount: value, SpawnedInSession: true } };
rewardItem.items = [rootItem];
}
return rewardItem;

View File

@ -2,6 +2,7 @@ import { inject, injectable } from "tsyringe";
import { Money } from "@spt-aki/models/enums/Money";
import { DatabaseServer } from "@spt-aki/servers/DatabaseServer";
import { JsonUtil } from "@spt-aki/utils/JsonUtil";
class LookupItem<T, I>
{
@ -33,7 +34,10 @@ export class HandbookHelper
protected lookupCacheGenerated = false;
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
{
const handbookDb = this.databaseServer.getTables().templates.handbook;
const handbookDb = this.jsonUtil.clone(this.databaseServer.getTables().templates.handbook);
for (const handbookItem of handbookDb.Items)
{
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 { ProfileFixerService } from "@spt-aki/services/ProfileFixerService";
import { JsonUtil } from "@spt-aki/utils/JsonUtil";
import { ProfileHelper } from "./ProfileHelper";
@injectable()
export class InRaidHelper
@ -35,6 +36,7 @@ export class InRaidHelper
@inject("ItemHelper") protected itemHelper: ItemHelper,
@inject("DatabaseServer") protected databaseServer: DatabaseServer,
@inject("InventoryHelper") protected inventoryHelper: InventoryHelper,
@inject("ProfileHelper") protected profileHelper: ProfileHelper,
@inject("QuestHelper") protected questHelper: QuestHelper,
@inject("PaymentHelper") protected paymentHelper: PaymentHelper,
@inject("LocalisationService") protected localisationService: LocalisationService,
@ -62,19 +64,16 @@ export class InRaidHelper
*/
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)
{
item.upd.StackObjectsCount = 1;
}
if (!item.upd.StackObjectsCount)
{
item.upd.StackObjectsCount = 1;
}
}
}
@ -140,18 +139,44 @@ export class InRaidHelper
profileData: IPmcData,
saveProgressRequest: ISaveProgressRequestData,
sessionID: string,
): IPmcData
): void
{
// remove old skill fatigue
// Remove skill fatigue values
this.resetSkillPointsEarnedDuringRaid(saveProgressRequest.profile);
// set profile data
// Set profile data
profileData.Info.Level = saveProgressRequest.profile.Info.Level;
profileData.Skills = saveProgressRequest.profile.Skills;
profileData.Stats.Eft = saveProgressRequest.profile.Stats.Eft;
profileData.Encyclopedia = saveProgressRequest.profile.Encyclopedia;
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)
{
// Skip counters with no id
@ -178,37 +203,51 @@ export class InRaidHelper
if (matchingPreRaidCounter.value !== postRaidValue)
{
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 pmcData Player profile
* @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
* @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
* @param pmcData Player profile
* @param profileData Player profile (scav or pmc)
* @param questId questId of quest to get rewards for
* @param state State of the quest to get rewards for
* @param sessionId Session id
@ -743,14 +743,17 @@ export class QuestHelper
* @returns Array of reward objects
*/
public applyQuestReward(
pmcData: IPmcData,
profileData: IPmcData,
questId: string,
state: QuestStatus,
sessionId: string,
questResponse: IItemEventRouterResponse,
): 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)
{
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
const questMoneyRewardBonus = this.getQuestMoneyRewardBonus(pmcData);
const questMoneyRewardBonus = this.getQuestMoneyRewardBonus(pmcProfile);
if (questMoneyRewardBonus > 0)
{
// Apply additional bonus from hideout skill
@ -774,7 +777,7 @@ export class QuestHelper
{
case QuestRewardType.SKILL:
this.profileHelper.addSkillPointsToPlayer(
pmcData,
profileData,
reward.target as SkillTypes,
Number(reward.value),
);
@ -799,7 +802,7 @@ export class QuestHelper
break;
case QuestRewardType.PRODUCTIONS_SCHEME:
this.findAndAddHideoutProductionIdToProfile(
pmcData,
pmcProfile,
reward,
questDetails,
sessionId,

View File

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

View File

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

View File

@ -84,8 +84,8 @@ export class EventOutputHolder
profileChanges.improvements = this.jsonUtil.clone(this.getImprovementsFromProfileAndFlagComplete(pmcData));
profileChanges.traderRelations = this.constructTraderRelations(pmcData.TradersInfo);
// Fixes container craft from water collector not resetting after collection
this.resetSptIsCompleteFlaggedCrafts(pmcData.Hideout.Production);
// Fixes container craft from water collector not resetting after collection + removed completed normal crafts
this.cleanUpCompleteCraftsInProfile(pmcData.Hideout.Production);
}
/**
@ -156,9 +156,8 @@ export class EventOutputHolder
continue;
}
// Complete and is a water collector craft
// Needed as canister craft (water collector) is continuous
if (production.sptIsComplete && productionKey === "5d5589c1f934db045e6c5492")
// Complete and is Continuous e.g. water collector
if (production.sptIsComplete && production.sptIsContinuous)
{
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
}
@ -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
* @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)
{
const production = productions[productionKey];
if (production.sptIsComplete)
if (production.sptIsComplete && production.sptIsContinuous)
{
// Water collector / Bitcoin etc
production.sptIsComplete = false;
production.Progress = 0;
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`;
// run callbacks
// Run pre-save callbacks before we save into json
for (const callback in this.onBeforeSaveCallbacks)
{
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
{
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
* quest.status is numeric 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;
}
const fixes = new Map<any, number>();
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";
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
// 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)
{
pmcProfile.Quests.splice(pmcProfile.Quests.indexOf(questToDelete), 1);
profile.Quests.splice(profile.Quests.indexOf(questToDelete), 1);
}
if (fixes.size > 0)
@ -563,6 +572,10 @@ export class ProfileFixerService
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 ?? [])
{

View File

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