Merge tag '4.0.4'

This commit is contained in:
Archangel
2025-11-11 07:10:02 +01:00
24 changed files with 536 additions and 185 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
<Project>
<PropertyGroup>
<!-- SPT specific -->
<SptVersion Condition="'$(SptVersion)' == ''">4.0.2</SptVersion>
<SptVersion Condition="'$(SptVersion)' == ''">4.0.3</SptVersion>
<SptCommit Condition="'$(SptCommit)' == ''">a12b34</SptCommit>
<SptBuildTime Condition="'$(SptBuildTime)' == ''">0000000000</SptBuildTime>
<SptBuildType Condition="'$(SptBuildType)' == ''">LOCAL</SptBuildType>
@@ -3,6 +3,7 @@ namespace SPTarkov.Reflection.Patching;
/// <summary>
/// Cache of active patches for mod developers to use for compatibility reasons
/// </summary>
[Obsolete("Patches will be injectable through IEnumerable<IRuntimePatch> in SPT 4.1, making this redundant")]
public static class ModPatchCache
{
private static readonly List<AbstractPatch> _activePatches = [];
@@ -22,6 +23,7 @@ public static class ModPatchCache
/// <remarks>
/// This should never be called before PreSptLoad is completed, otherwise could be empty.
/// </remarks>
[Obsolete("Patches will be injectable through IEnumerable<IRuntimePatch> in SPT 4.1, making this redundant")]
public static IReadOnlyList<AbstractPatch> GetActivePatches()
{
// We're not exposing _activePatches so it cant be altered outside of this class. Do NOT implement this as a property.
@@ -38,6 +40,7 @@ public static class ModPatchCache
/// <remarks>
/// This should never be called before PreSptLoad is completed, otherwise could be empty.
/// </remarks>
[Obsolete("Patches will be injectable through IEnumerable<IRuntimePatch> in SPT 4.1, making this redundant")]
public static List<string> GetActivePatchedMethodNames()
{
var result = new List<string>();
@@ -2895,10 +2895,19 @@
"bossKilla",
"bossKojaniy",
"bossSanitar",
"bossKolontay",
"bossKnight"
"bossKolontay"
],
"resetDay": "Monday"
},
"goonSpawnSystem": {
"enabled": true,
"locationPool": [
"bigmap",
"woods",
"shoreline",
"lighthouse"
],
"spawnChance": 35
},
"replaceScavWith": "assault"
}
@@ -61,7 +61,8 @@
"679baa2c61f588ae2b062a24",
"679baa4f59b8961f370dd683",
"679baa5a59b8961f370dd685",
"679baa9091966fe40408f149"
"679baa9091966fe40408f149",
"67ab3d4b83869afd170fdd3f"
],
"bossItems": [
"6275303a9f372d6ea97f9ec7",
@@ -475,6 +476,74 @@
"_name": "FN SCAR-H X-17 762x51 default",
"_parent": "679f5f42ca975ceee4001927",
"_type": "Preset"
},
{
"_changeWeaponName": false,
"_encyclopedia": "67d0576f29f580ebc10efd08",
"_id": "690a065b64b2a3bab0a986da",
"_items": [
{
"_id": "690a06bc6c65be6d001b4cd1",
"_tpl": "67d0576f29f580ebc10efd08",
"upd": {
"Repairable": {
"Durability": 100,
"MaxDurability": 100
}
}
},
{
"_id": "690a06bc6c65be6d001b4d0a",
"_tpl": "67d4178bffb910d21f04720a",
"parentId": "690a06bc6c65be6d001b4cd1",
"slotId": "mod_barrel"
},
{
"_id": "690a06bc6c65be6d001b4d18",
"_tpl": "67d417c023ec241bb70d4896",
"parentId": "690a06bc6c65be6d001b4d0a",
"slotId": "mod_gas_block"
},
{
"_id": "690a06bc6c65be6d001b4d32",
"_tpl": "67d41883f378a36c4706eeb7",
"parentId": "690a06bc6c65be6d001b4d0a",
"slotId": "mod_muzzle"
},
{
"_id": "690a06bc6c65be6d001b4d3b",
"_tpl": "67d416e19bd76ef20f0e743b",
"parentId": "690a06bc6c65be6d001b4cd1",
"slotId": "mod_reciever"
},
{
"_id": "690a06bc6c65be6d001b4d43",
"_tpl": "5aa66a9be5b5b0214e506e89",
"parentId": "690a06bc6c65be6d001b4d3b",
"slotId": "mod_scope"
},
{
"_id": "690a06bc6c65be6d001b4d4b",
"_tpl": "5aa66be6e5b5b0214e506e97",
"parentId": "690a06bc6c65be6d001b4d43",
"slotId": "mod_scope"
},
{
"_id": "690a06bc6c65be6d001b4d5c",
"_tpl": "6087e663132d4d12c81fd96b",
"parentId": "690a06bc6c65be6d001b4cd1",
"slotId": "mod_pistol_grip"
},
{
"_id": "690a06bc6c65be6d001b4d65",
"_tpl": "67d418d0ffb910d21f04720e",
"parentId": "690a06bc6c65be6d001b4cd1",
"slotId": "mod_magazine"
}
],
"_name": "AK-50 .50 BMG sniper rifle default",
"_parent": "690a06bc6c65be6d001b4cd1",
"_type": "Preset"
}
],
"handbookPriceOverride": {
@@ -194,14 +194,14 @@
},
"5d650c3e815116009f6201d2": {
"_name": "FUEL",
"conditionChance": 0.12,
"conditionChance": 0.23,
"current": {
"max": 1,
"min": 1
},
"max": {
"max": 1,
"min": 0.7
"min": 0.1
}
},
"644120aa86ffbe10ee032b6f": {
@@ -323,7 +323,8 @@
"57bef4c42459772e8d35a53b",
"55802f4a4bdc2ddb688b4569",
"616eb7aea207f41933308f46",
"543be5cb4bdc2deb348b4568"
"543be5cb4bdc2deb348b4568",
"5d650c3e815116009f6201d2"
],
"showDefaultPresetsOnly": true,
"stackablePercent": {
@@ -1264,7 +1264,8 @@
"BossName": "infectedAssault",
"BossPlayer": false,
"BossZone": "",
"IgnoreMaxBots": false,
"ForceSpawn": true,
"IgnoreMaxBots": true,
"RandomTimeSpawn": false,
"SpawnMode": [
"regular",
@@ -1284,7 +1285,8 @@
"BossName": "infectedAssault",
"BossPlayer": false,
"BossZone": "",
"IgnoreMaxBots": false,
"ForceSpawn": true,
"IgnoreMaxBots": true,
"RandomTimeSpawn": false,
"SpawnMode": [
"regular",
@@ -1304,7 +1306,8 @@
"BossName": "infectedAssault",
"BossPlayer": false,
"BossZone": "",
"IgnoreMaxBots": false,
"ForceSpawn": true,
"IgnoreMaxBots": true,
"RandomTimeSpawn": false,
"SpawnMode": [],
"Supports": null,
@@ -1321,7 +1324,8 @@
"BossName": "infectedAssault",
"BossPlayer": false,
"BossZone": "",
"IgnoreMaxBots": false,
"ForceSpawn": true,
"IgnoreMaxBots": true,
"RandomTimeSpawn": false,
"SpawnMode": [],
"Supports": null,
@@ -1338,7 +1342,8 @@
"BossName": "infectedPmc",
"BossPlayer": false,
"BossZone": "",
"IgnoreMaxBots": false,
"ForceSpawn": true,
"IgnoreMaxBots": true,
"RandomTimeSpawn": false,
"SpawnMode": [],
"Supports": null,
@@ -1355,7 +1360,8 @@
"BossName": "infectedPmc",
"BossPlayer": false,
"BossZone": "",
"IgnoreMaxBots": false,
"ForceSpawn": true,
"IgnoreMaxBots": true,
"RandomTimeSpawn": false,
"SpawnMode": [
"regular",
@@ -1375,7 +1381,8 @@
"BossName": "infectedAssault",
"BossPlayer": false,
"BossZone": "",
"IgnoreMaxBots": false,
"ForceSpawn": true,
"IgnoreMaxBots": true,
"RandomTimeSpawn": false,
"SpawnMode": [
"regular",
@@ -1395,7 +1402,8 @@
"BossName": "infectedPmc",
"BossPlayer": false,
"BossZone": "",
"IgnoreMaxBots": false,
"ForceSpawn": true,
"IgnoreMaxBots": true,
"RandomTimeSpawn": false,
"SpawnMode": [
"regular",
@@ -1415,7 +1423,8 @@
"BossName": "infectedAssault",
"BossPlayer": false,
"BossZone": "",
"IgnoreMaxBots": false,
"ForceSpawn": true,
"IgnoreMaxBots": true,
"RandomTimeSpawn": false,
"SpawnMode": [
"regular",
@@ -1450,7 +1459,8 @@
"BossName": "infectedAssault",
"BossPlayer": false,
"BossZone": "",
"IgnoreMaxBots": false,
"ForceSpawn": true,
"IgnoreMaxBots": true,
"RandomTimeSpawn": false,
"SpawnMode": [
"regular",
@@ -1485,7 +1495,8 @@
"BossName": "infectedAssault",
"BossPlayer": false,
"BossZone": "",
"IgnoreMaxBots": false,
"ForceSpawn": true,
"IgnoreMaxBots": true,
"RandomTimeSpawn": false,
"SpawnMode": [
"regular",
@@ -1513,7 +1524,8 @@
"BossName": "infectedAssault",
"BossPlayer": false,
"BossZone": "",
"IgnoreMaxBots": false,
"ForceSpawn": true,
"IgnoreMaxBots": true,
"RandomTimeSpawn": false,
"SpawnMode": [
"regular",
@@ -1541,7 +1553,8 @@
"BossName": "infectedPmc",
"BossPlayer": false,
"BossZone": "",
"IgnoreMaxBots": false,
"ForceSpawn": true,
"IgnoreMaxBots": true,
"RandomTimeSpawn": false,
"SpawnMode": [
"regular",
@@ -1569,7 +1582,8 @@
"BossName": "infectedPmc",
"BossPlayer": false,
"BossZone": "",
"IgnoreMaxBots": false,
"ForceSpawn": true,
"IgnoreMaxBots": true,
"RandomTimeSpawn": false,
"SpawnMode": [
"regular",
@@ -1597,7 +1611,8 @@
"BossName": "infectedPmc",
"BossPlayer": false,
"BossZone": "",
"IgnoreMaxBots": false,
"ForceSpawn": true,
"IgnoreMaxBots": true,
"RandomTimeSpawn": false,
"SpawnMode": [
"regular",
@@ -1625,7 +1640,8 @@
"BossName": "infectedPmc",
"BossPlayer": false,
"BossZone": "",
"IgnoreMaxBots": false,
"ForceSpawn": true,
"IgnoreMaxBots": true,
"RandomTimeSpawn": false,
"SpawnMode": [
"regular",
@@ -1653,7 +1669,8 @@
"BossName": "infectedPmc",
"BossPlayer": false,
"BossZone": "",
"IgnoreMaxBots": false,
"ForceSpawn": true,
"IgnoreMaxBots": true,
"RandomTimeSpawn": false,
"SpawnMode": [
"regular",
@@ -1902,7 +1919,8 @@
"BossName": "infectedAssault",
"BossPlayer": false,
"BossZone": "",
"IgnoreMaxBots": false,
"ForceSpawn": true,
"IgnoreMaxBots": true,
"SpawnMode": [
"regular",
"pve"
@@ -1921,7 +1939,8 @@
"BossName": "infectedAssault",
"BossPlayer": false,
"BossZone": "",
"IgnoreMaxBots": false,
"ForceSpawn": true,
"IgnoreMaxBots": true,
"SpawnMode": [
"regular",
"pve"
@@ -1940,7 +1959,8 @@
"BossName": "infectedAssault",
"BossPlayer": false,
"BossZone": "",
"IgnoreMaxBots": false,
"ForceSpawn": true,
"IgnoreMaxBots": true,
"SpawnMode": [],
"Supports": null,
"Time": 9999,
@@ -1956,7 +1976,8 @@
"BossName": "infectedAssault",
"BossPlayer": false,
"BossZone": "",
"IgnoreMaxBots": false,
"ForceSpawn": true,
"IgnoreMaxBots": true,
"SpawnMode": [],
"Supports": null,
"Time": 9999,
@@ -1972,7 +1993,8 @@
"BossName": "infectedPmc",
"BossPlayer": false,
"BossZone": "",
"IgnoreMaxBots": false,
"ForceSpawn": true,
"IgnoreMaxBots": true,
"SpawnMode": [],
"Supports": null,
"Time": 9999,
@@ -1988,7 +2010,8 @@
"BossName": "infectedPmc",
"BossPlayer": false,
"BossZone": "",
"IgnoreMaxBots": false,
"ForceSpawn": true,
"IgnoreMaxBots": true,
"SpawnMode": [
"regular",
"pve"
@@ -2007,7 +2030,8 @@
"BossName": "infectedAssault",
"BossPlayer": false,
"BossZone": "",
"IgnoreMaxBots": false,
"ForceSpawn": true,
"IgnoreMaxBots": true,
"SpawnMode": [
"regular",
"pve"
@@ -2026,7 +2050,8 @@
"BossName": "infectedPmc",
"BossPlayer": false,
"BossZone": "",
"IgnoreMaxBots": false,
"ForceSpawn": true,
"IgnoreMaxBots": true,
"SpawnMode": [
"regular",
"pve"
@@ -2045,7 +2070,8 @@
"BossName": "infectedAssault",
"BossPlayer": false,
"BossZone": "",
"IgnoreMaxBots": false,
"ForceSpawn": true,
"IgnoreMaxBots": true,
"SpawnMode": [
"regular",
"pve"
@@ -2079,7 +2105,8 @@
"BossName": "infectedAssault",
"BossPlayer": false,
"BossZone": "",
"IgnoreMaxBots": false,
"ForceSpawn": true,
"IgnoreMaxBots": true,
"SpawnMode": [
"regular",
"pve"
@@ -2113,7 +2140,8 @@
"BossName": "infectedAssault",
"BossPlayer": false,
"BossZone": "",
"IgnoreMaxBots": false,
"ForceSpawn": true,
"IgnoreMaxBots": true,
"SpawnMode": [
"regular",
"pve"
@@ -2140,7 +2168,8 @@
"BossName": "infectedAssault",
"BossPlayer": false,
"BossZone": "",
"IgnoreMaxBots": false,
"ForceSpawn": true,
"IgnoreMaxBots": true,
"SpawnMode": [
"regular",
"pve"
@@ -2167,7 +2196,8 @@
"BossName": "infectedPmc",
"BossPlayer": false,
"BossZone": "",
"IgnoreMaxBots": false,
"ForceSpawn": true,
"IgnoreMaxBots": true,
"SpawnMode": [
"regular",
"pve"
@@ -2194,7 +2224,8 @@
"BossName": "infectedPmc",
"BossPlayer": false,
"BossZone": "",
"IgnoreMaxBots": false,
"ForceSpawn": true,
"IgnoreMaxBots": true,
"SpawnMode": [
"regular",
"pve"
@@ -2221,7 +2252,8 @@
"BossName": "infectedPmc",
"BossPlayer": false,
"BossZone": "",
"IgnoreMaxBots": false,
"ForceSpawn": true,
"IgnoreMaxBots": true,
"SpawnMode": [
"regular",
"pve"
@@ -2248,7 +2280,8 @@
"BossName": "infectedPmc",
"BossPlayer": false,
"BossZone": "",
"IgnoreMaxBots": false,
"ForceSpawn": true,
"IgnoreMaxBots": true,
"SpawnMode": [
"regular",
"pve"
@@ -2275,7 +2308,8 @@
"BossName": "infectedPmc",
"BossPlayer": false,
"BossZone": "",
"IgnoreMaxBots": false,
"ForceSpawn": true,
"IgnoreMaxBots": true,
"SpawnMode": [
"regular",
"pve"
@@ -3262,7 +3296,8 @@
"BossPlayer": false,
"BossZone": "BotZoneFloor1,BotZoneFloor2,BotZoneBasement",
"Delay": 0,
"IgnoreMaxBots": false,
"ForceSpawn": true,
"IgnoreMaxBots": true,
"RandomTimeSpawn": false,
"SpawnMode": [
"regular",
@@ -3283,7 +3318,8 @@
"BossPlayer": false,
"BossZone": "BotZoneFloor1,BotZoneFloor2,BotZoneBasement",
"Delay": 0,
"IgnoreMaxBots": false,
"ForceSpawn": true,
"IgnoreMaxBots": true,
"RandomTimeSpawn": false,
"SpawnMode": [
"regular",
@@ -3304,7 +3340,8 @@
"BossPlayer": false,
"BossZone": "BotZoneFloor1,BotZoneFloor2,BotZoneBasement",
"Delay": 0,
"IgnoreMaxBots": false,
"ForceSpawn": true,
"IgnoreMaxBots": true,
"RandomTimeSpawn": false,
"SpawnMode": [],
"Supports": null,
@@ -3322,7 +3359,8 @@
"BossPlayer": false,
"BossZone": "BotZoneFloor1,BotZoneFloor2,BotZoneBasement",
"Delay": 0,
"IgnoreMaxBots": false,
"ForceSpawn": true,
"IgnoreMaxBots": true,
"RandomTimeSpawn": false,
"SpawnMode": [
"regular",
@@ -3343,7 +3381,8 @@
"BossPlayer": false,
"BossZone": "BotZoneFloor1,BotZoneFloor2,BotZoneBasement",
"Delay": 0,
"IgnoreMaxBots": false,
"ForceSpawn": true,
"IgnoreMaxBots": true,
"RandomTimeSpawn": false,
"SpawnMode": [],
"Supports": null,
@@ -3361,7 +3400,8 @@
"BossPlayer": false,
"BossZone": "BotZoneFloor1,BotZoneFloor2,BotZoneBasement",
"Delay": 0,
"IgnoreMaxBots": false,
"ForceSpawn": true,
"IgnoreMaxBots": true,
"RandomTimeSpawn": false,
"SpawnMode": [],
"Supports": null,
@@ -3379,7 +3419,8 @@
"BossPlayer": false,
"BossZone": "BotZoneFloor1,BotZoneFloor2,BotZoneBasement",
"Delay": 0,
"IgnoreMaxBots": false,
"ForceSpawn": true,
"IgnoreMaxBots": true,
"RandomTimeSpawn": false,
"SpawnMode": [
"regular",
@@ -3400,7 +3441,8 @@
"BossPlayer": false,
"BossZone": "BotZoneFloor1,BotZoneFloor2,BotZoneBasement",
"Delay": 0,
"IgnoreMaxBots": false,
"ForceSpawn": true,
"IgnoreMaxBots": true,
"RandomTimeSpawn": false,
"SpawnMode": [
"regular",
@@ -3421,7 +3463,8 @@
"BossPlayer": false,
"BossZone": "BotZoneFloor1,BotZoneFloor2,BotZoneBasement",
"Delay": 0,
"IgnoreMaxBots": false,
"ForceSpawn": true,
"IgnoreMaxBots": true,
"RandomTimeSpawn": false,
"SpawnMode": [
"regular",
@@ -3457,7 +3500,8 @@
"BossPlayer": false,
"BossZone": "BotZoneFloor1,BotZoneFloor2,BotZoneBasement",
"Delay": 0,
"IgnoreMaxBots": false,
"ForceSpawn": true,
"IgnoreMaxBots": true,
"RandomTimeSpawn": false,
"SpawnMode": [
"regular",
@@ -3500,7 +3544,8 @@
"BossPlayer": false,
"BossZone": "BotZoneFloor1,BotZoneFloor2,BotZoneBasement",
"Delay": 0,
"IgnoreMaxBots": false,
"ForceSpawn": true,
"IgnoreMaxBots": true,
"RandomTimeSpawn": false,
"SpawnMode": [
"regular",
@@ -3529,7 +3574,8 @@
"BossPlayer": false,
"BossZone": "BotZoneFloor1,BotZoneFloor2,BotZoneBasement",
"Delay": 0,
"IgnoreMaxBots": false,
"ForceSpawn": true,
"IgnoreMaxBots": true,
"RandomTimeSpawn": false,
"SpawnMode": [
"regular",
@@ -3558,7 +3604,8 @@
"BossPlayer": false,
"BossZone": "BotZoneFloor1,BotZoneFloor2,BotZoneBasement",
"Delay": 0,
"IgnoreMaxBots": false,
"ForceSpawn": true,
"IgnoreMaxBots": true,
"RandomTimeSpawn": false,
"SpawnMode": [
"regular",
@@ -3587,7 +3634,8 @@
"BossPlayer": false,
"BossZone": "BotZoneFloor1,BotZoneFloor2,BotZoneBasement",
"Delay": 0,
"IgnoreMaxBots": false,
"ForceSpawn": true,
"IgnoreMaxBots": true,
"RandomTimeSpawn": false,
"SpawnMode": [
"regular",
@@ -3616,7 +3664,8 @@
"BossPlayer": false,
"BossZone": "BotZoneFloor1,BotZoneFloor2,BotZoneBasement",
"Delay": 0,
"IgnoreMaxBots": false,
"ForceSpawn": true,
"IgnoreMaxBots": true,
"RandomTimeSpawn": false,
"SpawnMode": [
"regular",
@@ -3645,7 +3694,8 @@
"BossPlayer": false,
"BossZone": "BotZoneFloor1,BotZoneFloor2,BotZoneBasement",
"Delay": 0,
"IgnoreMaxBots": false,
"ForceSpawn": true,
"IgnoreMaxBots": true,
"RandomTimeSpawn": false,
"SpawnMode": [
"regular",
@@ -3674,7 +3724,8 @@
"BossPlayer": false,
"BossZone": "BotZoneFloor1,BotZoneFloor2,BotZoneBasement",
"Delay": 0,
"IgnoreMaxBots": false,
"ForceSpawn": true,
"IgnoreMaxBots": true,
"RandomTimeSpawn": false,
"SpawnMode": [
"regular",
@@ -5590,7 +5641,8 @@
"BossName": "infectedCivil",
"BossPlayer": false,
"BossZone": "",
"IgnoreMaxBots": false,
"ForceSpawn": true,
"IgnoreMaxBots": true,
"RandomTimeSpawn": false,
"SpawnMode": [
"regular",
@@ -5610,7 +5662,8 @@
"BossName": "infectedCivil",
"BossPlayer": false,
"BossZone": "",
"IgnoreMaxBots": false,
"ForceSpawn": true,
"IgnoreMaxBots": true,
"RandomTimeSpawn": false,
"SpawnMode": [
"regular",
@@ -5630,7 +5683,8 @@
"BossName": "infectedCivil",
"BossPlayer": false,
"BossZone": "",
"IgnoreMaxBots": false,
"ForceSpawn": true,
"IgnoreMaxBots": true,
"RandomTimeSpawn": false,
"SpawnMode": [
"regular",
@@ -5650,7 +5704,8 @@
"BossName": "infectedCivil",
"BossPlayer": false,
"BossZone": "",
"IgnoreMaxBots": false,
"ForceSpawn": true,
"IgnoreMaxBots": true,
"RandomTimeSpawn": false,
"SpawnMode": [
"regular",
@@ -5670,7 +5725,8 @@
"BossName": "infectedCivil",
"BossPlayer": false,
"BossZone": "",
"IgnoreMaxBots": false,
"ForceSpawn": true,
"IgnoreMaxBots": true,
"RandomTimeSpawn": false,
"SpawnMode": [
"regular",
@@ -5690,7 +5746,8 @@
"BossName": "infectedCivil",
"BossPlayer": false,
"BossZone": "",
"IgnoreMaxBots": false,
"ForceSpawn": true,
"IgnoreMaxBots": true,
"RandomTimeSpawn": false,
"SpawnMode": [
"regular",
@@ -5710,7 +5767,8 @@
"BossName": "infectedCivil",
"BossPlayer": false,
"BossZone": "",
"IgnoreMaxBots": false,
"ForceSpawn": true,
"IgnoreMaxBots": true,
"RandomTimeSpawn": false,
"SpawnMode": [
"regular",
@@ -5730,7 +5788,8 @@
"BossName": "infectedCivil",
"BossPlayer": false,
"BossZone": "",
"IgnoreMaxBots": false,
"ForceSpawn": true,
"IgnoreMaxBots": true,
"RandomTimeSpawn": false,
"SpawnMode": [
"regular",
@@ -5750,7 +5809,8 @@
"BossName": "infectedCivil",
"BossPlayer": false,
"BossZone": "",
"IgnoreMaxBots": false,
"ForceSpawn": true,
"IgnoreMaxBots": true,
"RandomTimeSpawn": false,
"SpawnMode": [
"regular",
@@ -5792,7 +5852,8 @@
"BossName": "infectedPmc",
"BossPlayer": false,
"BossZone": "",
"IgnoreMaxBots": false,
"ForceSpawn": true,
"IgnoreMaxBots": true,
"RandomTimeSpawn": false,
"SpawnMode": [
"regular",
@@ -5834,7 +5895,8 @@
"BossName": "infectedPmc",
"BossPlayer": false,
"BossZone": "",
"IgnoreMaxBots": false,
"ForceSpawn": true,
"IgnoreMaxBots": true,
"RandomTimeSpawn": false,
"SpawnMode": [
"regular",
@@ -5869,7 +5931,8 @@
"BossName": "infectedPmc",
"BossPlayer": false,
"BossZone": "",
"IgnoreMaxBots": false,
"ForceSpawn": true,
"IgnoreMaxBots": true,
"RandomTimeSpawn": false,
"SpawnMode": [
"regular",
@@ -5904,7 +5967,8 @@
"BossName": "infectedPmc",
"BossPlayer": false,
"BossZone": "",
"IgnoreMaxBots": false,
"ForceSpawn": true,
"IgnoreMaxBots": true,
"RandomTimeSpawn": false,
"SpawnMode": [
"regular",
@@ -5939,7 +6003,8 @@
"BossName": "infectedPmc",
"BossPlayer": false,
"BossZone": "",
"IgnoreMaxBots": false,
"ForceSpawn": true,
"IgnoreMaxBots": true,
"RandomTimeSpawn": false,
"SpawnMode": [
"regular",
@@ -5974,7 +6039,8 @@
"BossName": "infectedPmc",
"BossPlayer": false,
"BossZone": "",
"IgnoreMaxBots": false,
"ForceSpawn": true,
"IgnoreMaxBots": true,
"RandomTimeSpawn": false,
"SpawnMode": [
"regular",
@@ -6009,7 +6075,8 @@
"BossName": "infectedPmc",
"BossPlayer": false,
"BossZone": "",
"IgnoreMaxBots": false,
"ForceSpawn": true,
"IgnoreMaxBots": true,
"RandomTimeSpawn": false,
"SpawnMode": [
"regular",
@@ -6044,7 +6111,8 @@
"BossName": "infectedPmc",
"BossPlayer": false,
"BossZone": "",
"IgnoreMaxBots": false,
"ForceSpawn": true,
"IgnoreMaxBots": true,
"RandomTimeSpawn": false,
"SpawnMode": [
"regular",
@@ -6281,7 +6349,8 @@
"BossName": "infectedCivil",
"BossPlayer": false,
"BossZone": "",
"IgnoreMaxBots": false,
"ForceSpawn": true,
"IgnoreMaxBots": true,
"RandomTimeSpawn": false,
"SpawnMode": [
"regular",
@@ -6301,7 +6370,8 @@
"BossName": "infectedCivil",
"BossPlayer": false,
"BossZone": "",
"IgnoreMaxBots": false,
"ForceSpawn": true,
"IgnoreMaxBots": true,
"RandomTimeSpawn": false,
"SpawnMode": [
"regular",
@@ -6321,7 +6391,8 @@
"BossName": "infectedCivil",
"BossPlayer": false,
"BossZone": "",
"IgnoreMaxBots": false,
"ForceSpawn": true,
"IgnoreMaxBots": true,
"RandomTimeSpawn": false,
"SpawnMode": [
"regular",
@@ -6341,7 +6412,8 @@
"BossName": "infectedCivil",
"BossPlayer": false,
"BossZone": "",
"IgnoreMaxBots": false,
"ForceSpawn": true,
"IgnoreMaxBots": true,
"RandomTimeSpawn": false,
"SpawnMode": [
"regular",
@@ -6361,7 +6433,8 @@
"BossName": "infectedCivil",
"BossPlayer": false,
"BossZone": "",
"IgnoreMaxBots": false,
"ForceSpawn": true,
"IgnoreMaxBots": true,
"RandomTimeSpawn": false,
"SpawnMode": [
"regular",
@@ -6381,7 +6454,8 @@
"BossName": "infectedCivil",
"BossPlayer": false,
"BossZone": "",
"IgnoreMaxBots": false,
"ForceSpawn": true,
"IgnoreMaxBots": true,
"RandomTimeSpawn": false,
"SpawnMode": [
"regular",
@@ -6401,7 +6475,8 @@
"BossName": "infectedCivil",
"BossPlayer": false,
"BossZone": "",
"IgnoreMaxBots": false,
"ForceSpawn": true,
"IgnoreMaxBots": true,
"RandomTimeSpawn": false,
"SpawnMode": [
"regular",
@@ -6421,7 +6496,8 @@
"BossName": "infectedCivil",
"BossPlayer": false,
"BossZone": "",
"IgnoreMaxBots": false,
"ForceSpawn": true,
"IgnoreMaxBots": true,
"RandomTimeSpawn": false,
"SpawnMode": [
"regular",
@@ -6441,7 +6517,8 @@
"BossName": "infectedCivil",
"BossPlayer": false,
"BossZone": "",
"IgnoreMaxBots": false,
"ForceSpawn": true,
"IgnoreMaxBots": true,
"RandomTimeSpawn": false,
"SpawnMode": [
"regular",
@@ -6483,7 +6560,8 @@
"BossName": "infectedPmc",
"BossPlayer": false,
"BossZone": "",
"IgnoreMaxBots": false,
"ForceSpawn": true,
"IgnoreMaxBots": true,
"RandomTimeSpawn": false,
"SpawnMode": [
"regular",
@@ -6525,7 +6603,8 @@
"BossName": "infectedPmc",
"BossPlayer": false,
"BossZone": "",
"IgnoreMaxBots": false,
"ForceSpawn": true,
"IgnoreMaxBots": true,
"RandomTimeSpawn": false,
"SpawnMode": [
"regular",
@@ -6560,7 +6639,8 @@
"BossName": "infectedPmc",
"BossPlayer": false,
"BossZone": "",
"IgnoreMaxBots": false,
"ForceSpawn": true,
"IgnoreMaxBots": true,
"RandomTimeSpawn": false,
"SpawnMode": [
"regular",
@@ -6595,7 +6675,8 @@
"BossName": "infectedPmc",
"BossPlayer": false,
"BossZone": "",
"IgnoreMaxBots": false,
"ForceSpawn": true,
"IgnoreMaxBots": true,
"RandomTimeSpawn": false,
"SpawnMode": [
"regular",
@@ -6630,7 +6711,8 @@
"BossName": "infectedPmc",
"BossPlayer": false,
"BossZone": "",
"IgnoreMaxBots": false,
"ForceSpawn": true,
"IgnoreMaxBots": true,
"RandomTimeSpawn": false,
"SpawnMode": [
"regular",
@@ -6665,7 +6747,8 @@
"BossName": "infectedPmc",
"BossPlayer": false,
"BossZone": "",
"IgnoreMaxBots": false,
"ForceSpawn": true,
"IgnoreMaxBots": true,
"RandomTimeSpawn": false,
"SpawnMode": [
"regular",
@@ -6700,7 +6783,8 @@
"BossName": "infectedPmc",
"BossPlayer": false,
"BossZone": "",
"IgnoreMaxBots": false,
"ForceSpawn": true,
"IgnoreMaxBots": true,
"RandomTimeSpawn": false,
"SpawnMode": [
"regular",
@@ -6735,7 +6819,8 @@
"BossName": "infectedPmc",
"BossPlayer": false,
"BossZone": "",
"IgnoreMaxBots": false,
"ForceSpawn": true,
"IgnoreMaxBots": true,
"RandomTimeSpawn": false,
"SpawnMode": [
"regular",
@@ -9807,7 +9892,7 @@
"endMonth": "11",
"name": "halloween",
"settings": {
"enableSummoning": false,
"enableSummoning": true,
"removeEntryRequirement": [
"laboratory"
],
@@ -9817,7 +9902,7 @@
"laboratory"
],
"disableWaves": [],
"enabled": true,
"enabled": false,
"mapInfectionAmount": {
"Sandbox": 25,
"factory4": 50,
@@ -13894,6 +13979,22 @@
"UsecPlayerBehaviour": "AlwaysEnemies"
}
]
},
"summon": {
"default": [
{
"AlwaysEnemies": [],
"AlwaysFriends": [],
"BearPlayerBehaviour": "AlwaysFriends",
"BotRole": "peacefullZryachiyEvent",
"ChancedEnemies": [],
"SavageEnemyChance": 0,
"BearEnemyChance": 0,
"UsecEnemyChance": 0,
"SavagePlayerBehaviour": "AlwaysFriends",
"UsecPlayerBehaviour": "AlwaysFriends"
}
]
}
}
}
@@ -475,7 +475,7 @@
"visibilityConditions": [],
"globalQuestCounterId": "",
"parentId": "",
"target": "66058cc208308761cf390993",
"target": "68341f6fe2e7ef70a3060a0a",
"status": [
4
],
@@ -623,6 +623,7 @@
"ragfair-invalid_player_offer_request": "Unable to place offer, request is invalid",
"ragfair-item_not_in_db_unable_to_generate_dynamic_stack_count": "Item with tpl: %s not found in db. Unable to generate a dynamic stack count",
"ragfair-missing_barter_scheme": "generateFleaOffersForTrader() Failed to find barterScheme for item id: {{itemId}} tpl: {{tpl}} on {{name}}",
"ragfair-missing_loyal_level_item": "generateFleaOffersForTrader() Failed to find LoyalLevelItem for item id: {{itemId}} tpl: {{tpl}} on {{name}}",
"ragfair-no_trader_assorts_cant_generate_flea_offers": "Unable to generate flea offers for trader %s, no assort found",
"ragfair-offer_no_longer_exists": "Offer no longer exists",
"ragfair-offer_not_found_in_profile": "Could not find offer with id: {{offerId}} in profile: {{profileId}} to remove",
@@ -0,0 +1,5 @@
{
"barter_scheme": {},
"items": [],
"loyal_level_items": {}
}
@@ -953,7 +953,7 @@ public class HideoutController(
// Continuous crafts have special handling in EventOutputHolder.updateOutputProperties()
hideoutProduction.SptIsComplete = true;
hideoutProduction.SptIsContinuous = recipe.Continuous;
hideoutProduction.SptIsContinuous = recipe.Continuous ?? false;
// Continuous recipes need the craft time refreshed as it gets created once on initial craft and stays the same regardless of what
// production.json is set to
@@ -772,23 +772,33 @@ public class RagfairController(
var offerRootItem = offer.Items.FirstOrDefault(x => x.Id == offerRequest.Items[0]);
// Get average of items quality+children
var qualityMultiplier = itemHelper.GetItemQualityModifierForItems(offer.Items, true);
var qualityMiltiplierForPlayerOffer = itemHelper.GetItemQualityModifierForItems(offer.Items, true);
// Check for and apply item price modifer if it exists in config
if (RagfairConfig.Dynamic.ItemPriceMultiplier.TryGetValue(offerRootItem.Template, out var itemPriceModifer))
// Player may be listing a custom weapon with non-standard mods, calculate the average price of the listed weapons' mods
if (itemHelper.IsOfBaseclass(offerRootItem.Template, BaseClasses.WEAPON))
{
averageOfferPriceSingleItem *= itemPriceModifer;
averageOfferPriceSingleItem = ragfairPriceService.GetPresetPriceByChildren(offer.Items);
}
else
{
// Check for and apply item price modifer if it exists in config
if (RagfairConfig.Dynamic.ItemPriceMultiplier.TryGetValue(offerRootItem.Template, out var itemPriceModifer))
{
averageOfferPriceSingleItem *= itemPriceModifer;
}
}
// Multiply single item price by quality
averageOfferPriceSingleItem *= qualityMultiplier;
// Target price is adjusted to match quality of player item to create better comparison
averageOfferPriceSingleItem *= qualityMiltiplierForPlayerOffer;
// Packs are reduced to the average price of a single item in the pack vs the averaged single price of an item
var sellChancePercent = ragfairSellHelper.CalculateSellChance(
averageOfferPriceSingleItem.Value,
playerListedPriceInRub,
qualityMultiplier
qualityMiltiplierForPlayerOffer
);
offer.SellResults = ragfairSellHelper.RollForSale(sellChancePercent, (int)stackCountTotal);
// Subtract flea market fee from stash
@@ -6,8 +6,10 @@
///
/// This should not be used at all when having direct access to DI.
/// </summary>
[Obsolete("This will be removed in the next version of SPT in favor of DI injecting patches")]
public static class ServiceLocator
{
[Obsolete("This will be removed in the next version of SPT in favor of DI injecting patches")]
public static IServiceProvider ServiceProvider { get; private set; }
internal static void SetServiceProvider(IServiceProvider provider)
@@ -588,7 +588,21 @@ public class RagfairOfferGenerator(
}
var barterSchemeItems = barterScheme[0];
var loyalLevel = assortsClone.LoyalLevelItems[item.Id];
if (!assortsClone.LoyalLevelItems.TryGetValue(item.Id, out var loyalLevel))
{
logger.Warning(
localisationService.GetText(
"ragfair-missing_loyal_level_item",
new
{
itemId = item.Id,
tpl = item.Template,
name = trader.Base.Nickname,
}
)
);
continue;
}
var createOfferDetails = new CreateFleaOfferDetails
{
@@ -742,7 +756,10 @@ public class RagfairOfferGenerator(
if (itemHelper.IsOfBaseclass(itemDetails.Id, BaseClasses.FUEL))
{
var totalCapacity = itemDetails.Properties.MaxResource;
var remainingFuel = Math.Round((double)totalCapacity * maxMultiplier);
// Randomise multi between value in config and 1 (100%)
var randomisedMulti = randomUtil.GetDouble(maxMultiplier, 1);
var remainingFuel = Math.Round((double)totalCapacity * randomisedMulti);
rootItem.Upd.Resource = new UpdResource { UnitsConsumed = totalCapacity - remainingFuel, Value = remainingFuel };
}
}
@@ -537,6 +537,7 @@ public class ItemHelper(
{
if (IsOfBaseclass(itemWithChildren.First().Template, BaseClasses.WEAPON))
{
// Only root of weapon has durability
return Math.Round(GetItemQualityModifier(itemWithChildren.First()), 5);
}
@@ -594,42 +595,44 @@ public class ItemHelper(
return -1;
}
if (item.Upd is not null)
if (item.Upd is null)
{
if (item.Upd.MedKit is not null)
{
// Meds
result = (item.Upd.MedKit.HpResource ?? 0) / (itemDetails.Properties?.MaxHpResource ?? 0);
}
else if (item.Upd.Repairable is not null)
{
result = GetRepairableItemQualityValue(itemDetails, item.Upd.Repairable, item);
}
else if (item.Upd.FoodDrink is not null)
{
result = (item.Upd.FoodDrink.HpPercent ?? 0) / (itemDetails.Properties?.MaxResource ?? 0);
}
else if (item.Upd.Key?.NumberOfUsages > 0 && itemDetails.Properties?.MaximumNumberOfUsage > 0)
{
// keys - keys count upwards, not down like everything else
var maxNumOfUsages = itemDetails.Properties.MaximumNumberOfUsage;
result = (maxNumOfUsages ?? 0 - item.Upd.Key.NumberOfUsages) / maxNumOfUsages ?? 0;
}
else if (item.Upd.Resource?.UnitsConsumed > 0)
{
// E.g. fuel tank
result = (item.Upd.Resource.Value ?? 0) / (itemDetails.Properties?.MaxResource ?? 0);
}
else if (item.Upd.RepairKit is not null)
{
result = (item.Upd.RepairKit.Resource ?? 0) / (itemDetails.Properties?.MaxRepairResource ?? 0);
}
return result;
}
if (result == 0)
// make item non-zero but still very low
{
result = 0.01;
}
if (item.Upd.MedKit is not null)
{
// Meds
result = (item.Upd.MedKit.HpResource ?? 0) / (itemDetails.Properties?.MaxHpResource ?? 0);
}
else if (item.Upd.Repairable is not null)
{
result = GetRepairableItemQualityValue(itemDetails, item.Upd.Repairable, item);
}
else if (item.Upd.FoodDrink is not null)
{
result = (item.Upd.FoodDrink.HpPercent ?? 0) / (itemDetails.Properties?.MaxResource ?? 0);
}
else if (item.Upd.Key?.NumberOfUsages > 0 && itemDetails.Properties?.MaximumNumberOfUsage > 0)
{
// keys - keys count upwards, not down like everything else
var maxNumOfUsages = itemDetails.Properties.MaximumNumberOfUsage;
result = (maxNumOfUsages ?? 0 - item.Upd.Key.NumberOfUsages) / maxNumOfUsages ?? 0;
}
else if (item.Upd.Resource?.UnitsConsumed > 0) // Item is less than 100% usage
{
// E.g. fuel tank
result = (item.Upd.Resource.Value ?? 0) / (itemDetails.Properties?.MaxResource ?? 0);
}
else if (item.Upd.RepairKit is not null)
{
result = (item.Upd.RepairKit.Resource ?? 0) / (itemDetails.Properties?.MaxRepairResource ?? 0);
}
if (result == 0)
// make item non-zero but still very low
{
result = 0.01;
}
return result;
@@ -37,7 +37,7 @@ public class RagfairSellHelper(
// Modifier gets applied twice to either penalize or incentivize over/under pricing (Probably a cleaner way to do this)
var sellModifier = averageOfferPriceRub / playerListedPriceRub * sellConfig.SellMultiplier;
var sellChance = Math.Round(baseSellChancePercent * sellModifier * Math.Pow(sellModifier, 3) + 10); // Power of 3
var sellChance = Math.Round(baseSellChancePercent * sellModifier * (Math.Pow(sellModifier, 3) + 10)); // Power of 3
// Adjust sell chance if below config value
if (sellChance < sellConfig.MinSellChancePercent)
@@ -229,18 +229,7 @@ public class RewardHelper(
// "TraderId" holds area ID that will be used to craft unlocked item
var desiredHideoutAreaType = (HideoutAreas)int.Parse(craftUnlockReward.TraderId.ToString());
var matchingProductions = GetMatchingProductions(desiredHideoutAreaType, questId, craftUnlockReward);
// More/less than single match, above filtering wasn't strict enough
if (matchingProductions.Count != 1)
// Multiple matches were found, last ditch attempt to match by questid (value we add manually to production.json via `gen:productionquests` command)
{
matchingProductions = matchingProductions
.Where(prod => prod.Requirements.Any(requirement => requirement.QuestId == questId))
.ToList();
}
return matchingProductions;
return GetMatchingProductions(desiredHideoutAreaType, questId, craftUnlockReward);
}
/// <summary>
@@ -252,20 +241,35 @@ public class RewardHelper(
/// <returns>Hideout crafts that match input parameters</returns>
protected List<HideoutProduction> GetMatchingProductions(HideoutAreas desiredHideoutAreaType, MongoId questId, Reward craftUnlockReward)
{
var rewardItemTpl = craftUnlockReward.Items.FirstOrDefault()?.Template;
var rewardItemTpl = craftUnlockReward.Items?.FirstOrDefault()?.Template;
if (rewardItemTpl is null)
{
logger.Warning("Unable to get matching hideout craft as reward item tpl is missing");
var craftingRecipes = databaseService.GetHideout().Production.Recipes;
return craftingRecipes
return [];
}
var craftingRecipesDb = databaseService.GetHideout().Production.Recipes;
var result = craftingRecipesDb
.Where(production =>
// Some crafts have the questId, easy match
// Attempt to match by questId (value we add manually to production.json via `gen:productionquests` command)
production.Requirements.Any(req => req.QuestId == questId)
||
// Couldn't get craft by questId, get the closest match based on information we know
)
.ToList();
if (result.Count == 1)
{
// One result, good match
return result;
}
// Found more than or less than 1 craft by questId, try to get closest match based on information we know
return craftingRecipesDb
.Where(production =>
(
rewardItemTpl is not null
&& production.AreaType == desiredHideoutAreaType
production.AreaType == desiredHideoutAreaType
&& production.EndProduct == rewardItemTpl.Value
&& production.Requirements.Any(req => req.Type is "QuestComplete")
&& production.Locked.GetValueOrDefault(false) // Craft would be locked if we're unlocking it
&& production.Requirements.Any(req => req.RequiredLevel == craftUnlockReward.LoyaltyLevel)
)
)
@@ -137,6 +137,21 @@ public record BotConfig : BaseConfig
/// </summary>
[JsonPropertyName("replaceScavWith")]
public required WildSpawnType ReplaceScavWith { get; set; }
[JsonPropertyName("goonSpawnSystem")]
public required GoonSpawnSystem GoonSpawnSystem { get; set; }
}
public record GoonSpawnSystem
{
[JsonPropertyName("enabled")]
public bool Enabled { get; set; }
[JsonPropertyName("locationPool")]
public IEnumerable<string> LocationPool { get; set; }
[JsonPropertyName("spawnChance")]
public double SpawnChance { get; set; }
}
public record WeeklyBossSettings
@@ -31,6 +31,7 @@ public class SaveServer(
protected const string profileFilepath = "user/profiles/";
// onLoad = require("../bindings/SaveLoad");
[Obsolete("This will be removed in the next version of SPT")]
protected readonly Dictionary<string, Func<SptProfile, SptProfile>> onBeforeSaveCallbacks = new();
protected readonly ConcurrentDictionary<MongoId, SptProfile> profiles = new();
@@ -42,6 +43,7 @@ public class SaveServer(
/// </summary>
/// <param name="id"> ID for the save callback </param>
/// <param name="callback"> Callback to execute prior to running SaveServer.saveProfile() </param>
[Obsolete("This will be removed in the next version of SPT")]
public void AddBeforeSaveCallback(string id, Func<SptProfile, SptProfile> callback)
{
onBeforeSaveCallbacks[id] = callback;
@@ -51,6 +53,7 @@ public class SaveServer(
/// Remove a callback from being executed prior to saving profile in SaveServer.saveProfile()
/// </summary>
/// <param name="id"> ID of Callback to remove </param>
[Obsolete("This will be removed in the next version of SPT")]
public void RemoveBeforeSaveCallback(string id)
{
onBeforeSaveCallbacks.Remove(id);
@@ -56,6 +56,7 @@ public class LocationLifecycleService(
protected readonly RagfairConfig RagfairConfig = configServer.GetConfig<RagfairConfig>();
protected readonly HideoutConfig HideoutConfig = configServer.GetConfig<HideoutConfig>();
protected readonly PmcConfig PMCConfig = configServer.GetConfig<PmcConfig>();
protected readonly BotConfig BotConfig = configServer.GetConfig<BotConfig>();
protected readonly LostOnDeathConfig LostOnDeathConfig = configServer.GetConfig<LostOnDeathConfig>();
protected const string Pmc = "pmc";
@@ -320,6 +321,11 @@ public class LocationLifecycleService(
return locationBaseClone;
}
if (BotConfig.GoonSpawnSystem.Enabled)
{
AdjustGoonMapSpawns();
}
// Add custom PMCs to map every time its run
pmcWaveGenerator.ApplyWaveChangesToMap(locationBaseClone);
@@ -348,6 +354,63 @@ public class LocationLifecycleService(
return locationBaseClone;
}
/// <summary>
/// Goons will spawn on one map each hour, changing randomly based on a consistent seed made from current utc year + utc hour
/// </summary>
/// <param name="locationBlacklist">LocationIds to always ignore when choosing a spawn</param>
protected void AdjustGoonMapSpawns(HashSet<string>? locationBlacklist = null)
{
locationBlacklist ??= ["hideout", "develop"];
// Reset all maps with goons to 0% spawn, ignore blacklisted locations
var allLocations = databaseService.GetLocations().GetDictionary();
foreach (var (locationId, location) in allLocations)
{
if (!locationBlacklist.Contains(locationId) && location?.Base?.BossLocationSpawn is not null)
{
foreach (var goonSpawn in location.Base.BossLocationSpawn.Where(x => x.BossName == "bossKnight"))
{
goonSpawn.BossChance = 0;
}
}
}
var now = DateTime.UtcNow;
// Create consistent seed for hour (use prime)
var seed = (now.Year * 1009) + now.Hour;
// Init Random class with unique seed
var random = new Random(seed);
// Filter locations pool
var validLocationIds = BotConfig
.GoonSpawnSystem.LocationPool.Where(locationId =>
!locationBlacklist.Contains(locationId)
&& allLocations.TryGetValue(locationId, out var location)
&& location?.Base?.BossLocationSpawn is not null
)
.ToList();
if (validLocationIds.Count == 0)
{
logger.Error("Unable to adjust goon spawn chance, no valid locations found");
return;
}
// Choose a spawn location for goons
var chosenMapId = validLocationIds[random.Next(0, validLocationIds.Count)];
var chosenMap = allLocations[chosenMapId];
// "Where" just incase there's multiple knight spawns for some reason
var goonSpawns = chosenMap.Base.BossLocationSpawn.Where(x => x.BossName == "bossKnight");
foreach (var goonSpawn in goonSpawns)
{
goonSpawn.BossChance = BotConfig.GoonSpawnSystem.SpawnChance;
}
}
/// <summary>
/// Handle client/match/local/end
/// </summary>
@@ -216,7 +216,7 @@ public class PostDbLoadService(
// Create a consistent seed for the week using the year and the day of the year of above monday chosen
// This results in seed being identical for the week
var seed = startOfWeek.Year * 1000 + startOfWeek.DayOfYear;
var seed = startOfWeek.Year * 1009 + startOfWeek.DayOfYear;
// Init Random class with unique seed
var random = new Random(seed);
@@ -717,6 +717,11 @@ public class PostDbLoadService(
continue;
}
if (traderData.QuestAssort is null)
{
continue;
}
// Merge started/success/fail quest assorts into one dictionary
var mergedQuestAssorts = new Dictionary<MongoId, MongoId>();
mergedQuestAssorts = mergedQuestAssorts
@@ -531,15 +531,17 @@ public class RagfairPriceService(
/// </summary>
/// <param name="weaponWithChildren">weapon plus mods</param>
/// <returns>price of weapon in roubles</returns>
protected double GetPresetPriceByChildren(IEnumerable<Item> weaponWithChildren)
public double GetPresetPriceByChildren(IEnumerable<Item> weaponWithChildren)
{
var priceTotal = 0d;
foreach (var item in weaponWithChildren)
{
// Root item uses static price
if (item.ParentId == null)
if (item.ParentId == null || string.Equals(item.ParentId, "hideout", StringComparison.OrdinalIgnoreCase))
{
priceTotal += GetStaticPriceForItem(item.Template) ?? 0;
continue;
}
priceTotal += GetFleaPriceForItem(item.Template);
@@ -29,6 +29,7 @@ public class RepairService(
PaymentService paymentService,
ProfileHelper profileHelper,
RepairHelper repairHelper,
InventoryHelper inventoryHelper,
ServerLocalisationService serverLocalisationService,
ConfigServer configServer,
WeightedRandomHelper weightedRandomHelper
@@ -49,14 +50,16 @@ public class RepairService(
var itemToRepair = pmcData.Inventory.Items.FirstOrDefault(item => item.Id == repairItemDetails.Id);
if (itemToRepair is null)
{
logger.Error(serverLocalisationService.GetText("repair-unable_to_find_item_in_inventory_cant_repair", repairItemDetails.Id));
logger.Error(
serverLocalisationService.GetText("repair-unable_to_find_item_in_inventory_cant_repair", repairItemDetails.Id.ToString())
);
}
var priceCoef = traderHelper.GetLoyaltyLevel(traderId, pmcData).RepairPriceCoefficient;
var traderRepairDetails = traderHelper.GetTrader(traderId, sessionID)?.Repair;
if (traderRepairDetails is null)
{
logger.Error(serverLocalisationService.GetText("repair-unable_to_find_trader_details_by_id", traderId));
logger.Error(serverLocalisationService.GetText("repair-unable_to_find_trader_details_by_id", traderId.ToString()));
}
var repairQualityMultiplier = traderRepairDetails.Quality;
@@ -80,7 +83,7 @@ public class RepairService(
var itemRepairCost = items[itemToRepair.Template].Properties.RepairCost;
if (itemRepairCost is null)
{
logger.Error(serverLocalisationService.GetText("repair-unable_to_find_item_repair_cost", itemToRepair.Template));
logger.Error(serverLocalisationService.GetText("repair-unable_to_find_item_repair_cost", itemToRepair.Template.ToString()));
}
var repairCost = Math.Round(itemRepairCost.Value * repairItemDetails.Count.Value * repairRate.Value * RepairConfig.PriceMultiplier);
@@ -166,7 +169,9 @@ public class RepairService(
if (!itemDetails.Key)
{
// No item found
logger.Error(serverLocalisationService.GetText("repair-unable_to_find_item_in_db", repairDetails.RepairedItem.Template));
logger.Error(
serverLocalisationService.GetText("repair-unable_to_find_item_in_db", repairDetails.RepairedItem.Template.ToString())
);
return;
}
@@ -175,7 +180,9 @@ public class RepairService(
var vestSkillToLevel = isHeavyArmor ? SkillTypes.HeavyVests : SkillTypes.LightVests;
if (repairDetails.RepairPoints is null)
{
logger.Error(serverLocalisationService.GetText("repair-item_has_no_repair_points", repairDetails.RepairedItem.Template));
logger.Error(
serverLocalisationService.GetText("repair-item_has_no_repair_points", repairDetails.RepairedItem.Template.ToString())
);
}
var pointsToAddToVestSkill = repairDetails.RepairPoints * RepairConfig.ArmorKitSkillPointGainPerRepairPointMultiplier;
@@ -205,7 +212,9 @@ public class RepairService(
// Limit gain to a max value defined in config.maxIntellectGainPerRepair
if (repairDetails.RepairPoints is null)
{
logger.Error(serverLocalisationService.GetText("repair-item_has_no_repair_points", repairDetails.RepairedItem.Template));
logger.Error(
serverLocalisationService.GetText("repair-item_has_no_repair_points", repairDetails.RepairedItem.Template.ToString())
);
}
return Math.Min(repairDetails.RepairPoints.Value * intRepairMultiplier, RepairConfig.MaxIntellectGainPerRepair.Kit);
@@ -272,43 +281,61 @@ public class RepairService(
var itemToRepair = pmcData.Inventory.Items.FirstOrDefault(x => x.Id == itemToRepairId);
if (itemToRepair is null)
{
logger.Error(serverLocalisationService.GetText("repair-item_not_found_unable_to_repair", itemToRepairId));
logger.Error(serverLocalisationService.GetText("repair-item_not_found_unable_to_repair", itemToRepairId.ToString()));
}
var itemsDb = databaseService.GetItems();
var itemToRepairDetails = itemsDb[itemToRepair.Template];
var repairItemIsArmor = itemToRepairDetails.Properties.ArmorMaterial is not null;
var repairAmount = repairKits[0].Count / GetKitDivisor(itemToRepairDetails, repairItemIsArmor, pmcData);
var shouldApplyDurabilityLoss = ShouldRepairKitApplyDurabilityLoss(pmcData, RepairConfig.ApplyRandomizeDurabilityLoss);
// Amount to add to gun as durability
var repairAmountTotal = repairKits[0].Count / GetKitDivisor(itemToRepairDetails, repairItemIsArmor, pmcData);
repairHelper.UpdateItemDurability(
itemToRepair,
itemToRepairDetails,
repairItemIsArmor,
repairAmount.Value,
repairAmountTotal.Value,
true,
1,
shouldApplyDurabilityLoss
ShouldRepairKitApplyDurabilityLoss(pmcData, RepairConfig.ApplyRandomizeDurabilityLoss)
);
// Find and use repair kit defined in body
List<MongoId> kitIdsToDelete = [];
foreach (var repairKit in repairKits)
{
var repairKitInInventory = pmcData.Inventory.Items.FirstOrDefault(item => item.Id == repairKit.Id);
if (repairKitInInventory is null)
{
logger.Error(serverLocalisationService.GetText("repair-repair_kit_not_found_in_inventory", repairKit.Id));
logger.Error(serverLocalisationService.GetText("repair-repair_kit_not_found_in_inventory", repairKit.Id.ToString()));
}
var repairKitDetails = itemsDb[repairKitInInventory.Template];
var repairKitReductionAmount = repairKit.Count;
var repairKitDbDetails = itemsDb[repairKitInInventory.Template];
AddMaxResourceToKitIfMissing(repairKitDbDetails, repairKitInInventory);
AddMaxResourceToKitIfMissing(repairKitDetails, repairKitInInventory);
if (repairKitInInventory.Upd.RepairKit.Resource <= repairKit.Count)
{
// Repair kit will be fully used up
// Flag kit for deletion
kitIdsToDelete.Add(repairKit.Id);
// reduce usages on repairkit used
repairKitInInventory.Upd.RepairKit.Resource -= repairKitReductionAmount;
// Move on to next repair kit
continue;
}
// Repair kit had enough resources to repair in one go
// Update server item resource value
repairKitInInventory.Upd.RepairKit.Resource -= repairKit.Count;
output.ProfileChanges[sessionId].Items.ChangedItems.Add(repairKitInInventory);
break;
}
foreach (var kitId in kitIdsToDelete)
{
inventoryHelper.RemoveItem(pmcData, kitId, sessionId, output);
}
return new RepairDetails
@@ -316,7 +343,7 @@ public class RepairService(
RepairPoints = repairKits[0].Count,
RepairedItem = itemToRepair,
RepairedItemIsArmor = repairItemIsArmor,
RepairAmount = repairAmount,
RepairAmount = repairAmountTotal,
RepairedByKit = true,
};
}
@@ -348,7 +375,7 @@ public class RepairService(
var destructability = 1 + armorMaterial.Destructibility;
var armorClass = itemToRepairDetails.Properties.ArmorClass.Value;
var armorClassDivisor = globals.Configuration.RepairSettings.ArmorClassDivisor;
var armorClassMultiplier = 1.0 + armorClass / armorClassDivisor;
var armorClassMultiplier = 1.0 + (armorClass / armorClassDivisor);
return durabilityPointCostArmor * armorBonus * destructability * armorClassMultiplier;
}
@@ -414,7 +441,7 @@ public class RepairService(
{
if (logger.IsLogEnabled(LogLevel.Debug))
{
logger.Debug($"Repair kit: {repairKitInInventory.Id} in inventory lacks upd object, adding");
logger.Debug($"Repair kit: {repairKitInInventory.Id.ToString()} in inventory lacks upd object, adding");
}
repairKitInInventory.Upd = new Upd { RepairKit = new UpdRepairKit { Resource = maxRepairAmount } };
@@ -550,7 +577,9 @@ public class RepairService(
if (repairDetails.RepairPoints is null)
{
logger.Error(serverLocalisationService.GetText("repair-item_has_no_repair_points", repairDetails.RepairedItem.Template));
logger.Error(
serverLocalisationService.GetText("repair-item_has_no_repair_points", repairDetails.RepairedItem.Template.ToString())
);
}
var durabilityToRestorePercent = repairDetails.RepairPoints / template.Properties.MaxDurability;
@@ -651,6 +651,9 @@ public class SeasonalEventService(
);
if (matchingBaseSettings is null)
{
// Doesn't exist, add it
locationBase.Base.BotLocationModifier.AdditionalHostilitySettings.Append(settings);
continue;
}
@@ -769,6 +772,11 @@ public class SeasonalEventService(
protected void EnableHalloweenSummonEvent()
{
databaseService.GetGlobals().Configuration.EventSettings.EventActive = true;
if (SeasonalEventConfig.HostilitySettingsForEvent.TryGetValue("summon", out var botData))
{
ReplaceBotHostility(botData);
}
}
protected void ConfigureZombies(ZombieSettings zombieSettings)
+3 -2
View File
@@ -1,6 +1,5 @@
using System.Net;
using System.Net.Sockets;
using System.Runtime;
using System.Runtime.InteropServices;
using System.Security.Authentication;
using System.Text;
@@ -44,7 +43,9 @@ public static class Program
catch (Exception e)
{
Console.WriteLine("=========================================================================================================");
Console.WriteLine("The server has unexpectedly stopped, please check your log files and reach out to #spt-support in discord");
Console.WriteLine(
"The server has unexpectedly stopped, reach out to #spt-support in discord, create a support thread by following the instructions found in #support-guidelines. Also include a screenshot of this message + the below error"
);
Console.WriteLine(e);
Console.WriteLine("=========================================================================================================");
Console.WriteLine("Press any key to exit...");