Stacking root cause: boxes that persisted from sessions before the non-persistency change load into the world AFTER MissionServer.OnInit, so the one-shot init cleanup missed them; they were untracked and never despawned, and new boxes spawned on top. - RemoveUntrackedBoxesAt(pos): deletes YALootBoxes_Base near a point that are NOT in m_ActiveBoxes (skips live boxes via IsTrackedBox), so it is safe to call any time. - SpawnOneAtRandomFreeLocation now clears its exact location first, guaranteeing one box per location and never stacking on a leftover. - CleanupOrphanedBoxes now runs at startup AND at the start of every TickLifecycle (self-healing), so ghosts that stream in late are removed within one check interval. Radius tightened 5m -> 3m. Despawn timers (per request): split into two main_config keys - DespawnMinutes: locked/unopened box lifetime (per-type despawnMinutes still overrides when > 0; default per-type is now 0 = use global). - OpenedDespawnMinutes: lifetime after a box is opened. Replaces the single DefaultDespawnMinutes. Lootbox uses GetLockedLifetimeMinutes / GetOpenedLifetimeMinutes accordingly. README/configfiles.readme + _comment docs updated. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
135 lines
No EOL
5.8 KiB
Text
135 lines
No EOL
5.8 KiB
Text
// ============================================================
|
|
// These are REFERENCE/DEFAULT files. The mod auto-generates
|
|
// these on first run at: $profile:YALootBoxes\*.json
|
|
// Edit them there - do NOT edit inside the .pbo.
|
|
// ============================================================
|
|
|
|
|
|
// ------------------------------------------------------------
|
|
// FILE: main_config.json
|
|
// ------------------------------------------------------------
|
|
// On first run the mod also writes a "_comment" array into each generated
|
|
// file describing every field and its range. (Delete the json to regenerate.)
|
|
{
|
|
"MaxActiveLootboxes": 30, // max boxes alive at once (1-100; capped by spawn_locations count)
|
|
"GlobalCheckIntervalSeconds": 60, // seconds between despawn/respawn checks + leftover sweep (10-600)
|
|
"DespawnMinutes": 15, // how long a LOCKED (unopened) box stays before despawn/respawn (1-1440)
|
|
"OpenedDespawnMinutes": 5, // how long a box stays in the world after being OPENED (1-1440)
|
|
"OpenChannelDurationSeconds": 5, // hold-to-open time in seconds (1-30)
|
|
"ProximityMessageRangeMeters": 3.0,// range of the "Requires: X" hint, meters (1-20)
|
|
"DebugLogLevel": 3 // 0=off 1=ERROR 2=WARN 3=INFO 4=DEBUG 5=VERBOSE
|
|
}
|
|
|
|
|
|
// ------------------------------------------------------------
|
|
// FILE: box_types.json
|
|
// ------------------------------------------------------------
|
|
{
|
|
"BoxTypes": [
|
|
{
|
|
"className": "YALootBoxes_Crate_Military",
|
|
"type": "military",
|
|
"requiredItems": [
|
|
{ "classname": "BoltCutters", "displayName": "Bolt Cutters" },
|
|
{ "classname": "Crowbar", "displayName": "Crowbar" }
|
|
],
|
|
"openChance": 0.65,
|
|
"itemDamageOnOpen": 8.0,
|
|
"despawnMinutes": 0,
|
|
"failEffects": [
|
|
{ "type": "EXPLOSIVE", "chance": 0.15, "damage": 45.0, "radius": 2.5 },
|
|
{ "type": "HEALTH", "chance": 0.25, "damage": 15.0 },
|
|
{ "type": "BLEEDING", "chance": 0.30, "bleedSources": 2 },
|
|
{ "type": "SICKNESS", "chance": 0.15, "agent": "Salmonella" },
|
|
{ "type": "BROKEN_BONE", "chance": 0.15 }
|
|
]
|
|
},
|
|
{
|
|
"className": "YALootBoxes_Crate_Medical",
|
|
"type": "medical",
|
|
"requiredItems": [
|
|
{ "classname": "Hacksaw", "displayName": "Hacksaw" }
|
|
],
|
|
"openChance": 0.80,
|
|
"itemDamageOnOpen": 4.0,
|
|
"despawnMinutes": 0,
|
|
"failEffects": [
|
|
{ "type": "SICKNESS", "chance": 0.50, "agent": "Influenza" },
|
|
{ "type": "HEALTH", "chance": 0.30, "damage": 8.0 },
|
|
{ "type": "BLEEDING", "chance": 0.20, "bleedSources": 1 }
|
|
]
|
|
},
|
|
{
|
|
"className": "YALootBoxes_Crate_Industrial",
|
|
"type": "industrial",
|
|
"requiredItems": [
|
|
{ "classname": "Crowbar", "displayName": "Crowbar" },
|
|
{ "classname": "WeldingMask", "displayName": "Angle Grinder" }
|
|
],
|
|
"openChance": 0.50,
|
|
"itemDamageOnOpen": 12.0,
|
|
"despawnMinutes": 0,
|
|
"failEffects": [
|
|
{ "type": "EXPLOSIVE", "chance": 0.20, "damage": 60.0, "radius": 3.0 },
|
|
{ "type": "BROKEN_BONE", "chance": 0.25 },
|
|
{ "type": "BLEEDING", "chance": 0.30, "bleedSources": 2 },
|
|
{ "type": "HEALTH", "chance": 0.25, "damage": 20.0 }
|
|
]
|
|
}
|
|
]
|
|
}
|
|
|
|
|
|
// ------------------------------------------------------------
|
|
// FILE: loot_lists.json
|
|
// ------------------------------------------------------------
|
|
{
|
|
"LootLists": [
|
|
{
|
|
"type": "military",
|
|
"entries": [
|
|
{ "classname": "AKM", "chance": 0.10, "minCount": 1, "maxCount": 1 },
|
|
{ "classname": "Mag_AKM_30Rnd", "chance": 0.40, "minCount": 1, "maxCount": 3 },
|
|
{ "classname": "PlateCarrierVest","chance": 0.20, "minCount": 1, "maxCount": 1 },
|
|
{ "classname": "TacticalHelmet", "chance": 0.15, "minCount": 1, "maxCount": 1 },
|
|
{ "classname": "Ammo_762x39", "chance": 0.50, "minCount": 1, "maxCount": 2 }
|
|
]
|
|
},
|
|
{
|
|
"type": "medical",
|
|
"entries": [
|
|
{ "classname": "Morphine", "chance": 0.30, "minCount": 1, "maxCount": 1 },
|
|
{ "classname": "SalineBagIV", "chance": 0.25, "minCount": 1, "maxCount": 2 },
|
|
{ "classname": "VitaminBottle", "chance": 0.50, "minCount": 1, "maxCount": 3 },
|
|
{ "classname": "Bandage", "chance": 0.60, "minCount": 2, "maxCount": 5 },
|
|
{ "classname": "TetracyclineAntibiotics", "chance": 0.20, "minCount": 1, "maxCount": 2 }
|
|
]
|
|
},
|
|
{
|
|
"type": "industrial",
|
|
"entries": [
|
|
{ "classname": "WeldingMask", "chance": 0.15, "minCount": 1, "maxCount": 1 },
|
|
{ "classname": "Battery_Car", "chance": 0.30, "minCount": 1, "maxCount": 1 },
|
|
{ "classname": "SparkPlug", "chance": 0.40, "minCount": 1, "maxCount": 2 },
|
|
{ "classname": "MetalPipe", "chance": 0.50, "minCount": 1, "maxCount": 4 },
|
|
{ "classname": "DuctTape", "chance": 0.45, "minCount": 1, "maxCount": 2 }
|
|
]
|
|
}
|
|
]
|
|
}
|
|
|
|
|
|
// ------------------------------------------------------------
|
|
// FILE: spawn_locations.json
|
|
// ------------------------------------------------------------
|
|
{
|
|
"SpawnLocations": [
|
|
{ "name": "NWAF_Hangar1", "position": [4985.3, 320.5, 2456.8], "boxType": "military" },
|
|
{ "name": "NWAF_Hangar2", "position": [5102.7, 318.9, 2401.2], "boxType": "military" },
|
|
{ "name": "Tisy_Bunker_A", "position": [1606.4, 280.1, 14383.9],"boxType": "military" },
|
|
{ "name": "Berezino_Hospital", "position": [7245.1, 12.4, 13889.6], "boxType": "medical" },
|
|
{ "name": "Zelenogorsk_Clinic", "position": [2543.9, 8.2, 5234.7], "boxType": "medical" },
|
|
{ "name": "Tractor_Plant_1", "position": [9821.2, 145.6, 8421.3], "boxType": "industrial" },
|
|
{ "name": "Tractor_Plant_2", "position": [9890.5, 142.1, 8390.7], "boxType": "industrial" }
|
|
]
|
|
} |