Add per-search find-chance and item count to config

YAInteractableEntry gains SearchChance (% chance a search yields anything),
SearchMin/SearchMax (item count on success), replacing the global roll
constants. Search rolls the chance first; on success spawns a random count in
[SearchMin, SearchMax] from the loot table. Defaults: lekarnicka 60% 1-2,
fridge 75% 1-2. Fields have inline defaults so older JSON loads safely.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
smthspec 2026-06-19 14:23:28 +03:00
parent 41c8e29cea
commit 7fb07aac4a
5 changed files with 42 additions and 13 deletions

View file

@ -40,6 +40,9 @@ Generated on first start under `<ProfileFolder>/YAInteraction/`:
- **`settings.json`** — scalar tunables: `WaterAmountMin`, `WaterAmountMax`,
`SinkPlayerCooldownSec`, `ObjectCooldownSec`.
- **`interactables.json`** — the `Interactables` registry and all `LootTables`.
Each search entry has `SearchChance` (% chance a search yields anything),
`SearchMin`/`SearchMax` (item count on a successful search), `CooldownSec`,
and a `LootTableName`.
Delete a file and restart to regenerate it from code defaults.

View file

@ -57,16 +57,33 @@ class ActionYASearch : YAProxyActionBase
return;
}
array<ref YALootEntry> table = YAInteractionManager.GetLootTableForEntry(entry);
int rolls = Math.RandomIntInclusive(YAInteractionConfig.SEARCH_ROLLS_MIN, YAInteractionConfig.SEARCH_ROLLS_MAX);
array<EntityAI> spawned = YALootTableManager.GetInstance().SpawnFromTable(table, rolls, player, player.GetPosition());
// Searching gates the object whether or not it yields anything.
YAInteractionManager.StampSearchCooldown(posKey, entry.CooldownSec);
Print(string.Format("[YAInteraction] %1 searched '%2' @ %3 -> %4 item(s)", player.GetIdentity().GetId(), proxyName, posKey, spawned.Count()));
// Find-chance roll.
if (Math.RandomFloat(0, 100) >= entry.SearchChance)
{
Print(string.Format("[YAInteraction] %1 searched '%2' @ %3 -> nothing (%4%% chance)", player.GetIdentity().GetId(), proxyName, posKey, entry.SearchChance));
player.MessageStatus("You found nothing useful.");
return;
}
if (spawned.Count() > 0)
player.MessageStatus(string.Format("You found %1 item(s).", spawned.Count()));
int count = Math.RandomIntInclusive(entry.SearchMin, entry.SearchMax);
array<EntityAI> spawned;
if (count > 0)
{
array<ref YALootEntry> table = YAInteractionManager.GetLootTableForEntry(entry);
spawned = YALootTableManager.GetInstance().SpawnFromTable(table, count, player, player.GetPosition());
}
int found;
if (spawned)
found = spawned.Count();
Print(string.Format("[YAInteraction] %1 searched '%2' @ %3 -> %4 item(s)", player.GetIdentity().GetId(), proxyName, posKey, found));
if (found > 0)
player.MessageStatus(string.Format("You found %1 item(s).", found));
else
player.MessageStatus("You found nothing useful.");
}

View file

@ -18,8 +18,8 @@ class YAInteractionConfig
// Non-JSON engine constants.
static const int WATER_LIQUID_TYPE = LIQUID_WATER;
static const int SEARCH_ROLLS_MIN = 1;
static const int SEARCH_ROLLS_MAX = 2;
// (search find-chance and item count are per-interactable: YAInteractableEntry
// SearchChance / SearchMin / SearchMax)
// Progress-bar durations (seconds).
static const float SEARCH_TIME = 3.0;

View file

@ -110,9 +110,10 @@ class YAInteractionConfigLoader
// Proxy names (from YAProxy scan / DayZ editor): the resolved name carries
// a ".NNN" instance suffix (e.g. "fridge.001"); the keyword is matched as
// a substring, so the base name is enough.
// (proxyName, actionType, lootTable, cooldownSec, searchChance%, searchMin, searchMax)
d.Interactables.Insert(MakeEntry("case_sink_a", YAInteractionConfig.ACTION_REPAIR_WATER, "", Settings.SinkPlayerCooldownSec));
d.Interactables.Insert(MakeEntry("lekarnicka", YAInteractionConfig.ACTION_SEARCH, YAInteractionConfig.TABLE_MED, Settings.ObjectCooldownSec));
d.Interactables.Insert(MakeEntry("fridge", YAInteractionConfig.ACTION_SEARCH, YAInteractionConfig.TABLE_FOOD, Settings.ObjectCooldownSec));
d.Interactables.Insert(MakeEntry("lekarnicka", YAInteractionConfig.ACTION_SEARCH, YAInteractionConfig.TABLE_MED, Settings.ObjectCooldownSec, 60.0, 1, 2));
d.Interactables.Insert(MakeEntry("fridge", YAInteractionConfig.ACTION_SEARCH, YAInteractionConfig.TABLE_FOOD, Settings.ObjectCooldownSec, 75.0, 1, 2));
// Vending machines deferred: in tested buildings the proxy has no targetable
// object (target.GetObject()==null), so it can't be detected/overridden
// cleanly. VendingTable below is kept for when that's solved or moved to a
@ -151,13 +152,16 @@ class YAInteractionConfigLoader
// --- Factory helpers (data classes have no parameterised ctor so the
// JSON serializer can default-construct them). ---
private static YAInteractableEntry MakeEntry(string proxyName, string actionType, string lootTableName, float cooldownSec)
private static YAInteractableEntry MakeEntry(string proxyName, string actionType, string lootTableName, float cooldownSec, float searchChance = 100.0, int searchMin = 1, int searchMax = 1)
{
YAInteractableEntry e = new YAInteractableEntry();
e.ProxyName = proxyName;
e.ActionType = actionType;
e.LootTableName = lootTableName;
e.CooldownSec = cooldownSec;
e.SearchChance = searchChance;
e.SearchMin = searchMin;
e.SearchMax = searchMax;
return e;
}

View file

@ -29,10 +29,15 @@ class YALootTable
class YAInteractableEntry
{
string ProxyName; // in-house proxy selection name (ZenProxyFramework)
string ProxyName; // in-house proxy selection name (matched as substring)
string ActionType; // "search" or "repair_water" (YAInteractionConfig.ACTION_*)
string LootTableName; // named loot table (empty for repair_water)
float CooldownSec; // search: per-object cooldown / water: per-player cooldown
// --- search tuning (ignored for repair_water) ---
float SearchChance = 100.0; // % chance a search yields anything (0-100)
int SearchMin = 1; // min items on a successful search
int SearchMax = 1; // max items on a successful search
}
class YAInteractionData