YAInteraction/scripts/4_World/YAInteraction/Actions/ActionYASearch.c
smthspec c1cd01f224 Replace EnScript config with JSON-based system
Move the registry, loot tables and scalar tunables out of hardcoded EnScript
into JSON loaded via DayZ's JsonFileLoader. Defaults are written to
<ProfileFolder>/YAInteraction/{settings.json,interactables.json} on first start
and read back (overriding code defaults) on subsequent starts.

- Add YAInteractionSettings (settings.json) and YAInteractionData
  (interactables.json) serializable classes with inline defaults
- Add YAInteractionConfigLoader singleton: MakeDirectory + FileExist driven
  load-or-save, plus client-safe in-code default fallback (EnsureDefaults)
- Load server-side via modded MissionServer.OnInit before players connect
- Gut YAInteractionConfig down to compile-time constants
- Rebuild manager lookups from loaded Data; update field names and float weights
- Remove redundant 5_Mission placeholder; update README

CF persistence and config hot-reload remain marked with // TODO (v2).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 21:45:56 +03:00

141 lines
4.7 KiB
C

// ============================================================================
// ActionYASearch
// ----------------------------------------------------------------------------
// Generic "Search" action. The target's registry entry (resolved by classname
// via YAInteractionManager) decides the loot table and the per-object cooldown.
// No classnames live here - everything comes from config.
//
// Cooldown model (v1): per-object, keyed by network id, in a static map.
// Resets on server restart.
// TODO (v2): swap the static map for CF persistence in IsOnCooldown()/
// StampCooldown() - those two methods are the only persistence surface.
// ============================================================================
class ActionYASearchCB : ActionContinuousBaseCB
{
override void CreateActionComponent()
{
m_ActionData.m_ActionComponent = new CAContinuousTime(YAInteractionConfig.SEARCH_TIME);
}
}
class ActionYASearch : ActionContinuousBase
{
// network id string -> server time (ms) when the cooldown expires.
protected static ref map<string, int> m_ObjectCooldowns = new map<string, int>();
void ActionYASearch()
{
m_CallbackClass = ActionYASearchCB;
m_CommandUID = DayZPlayerConstants.CMD_ACTIONFB_INTERACT; // generic full-body interact anim
m_FullBody = true;
m_StanceMask = DayZPlayerConstants.STANCEMASK_ERECT | DayZPlayerConstants.STANCEMASK_CROUCH;
m_Text = "Search";
}
override typename GetInputType()
{
return ContinuousInteractActionInput;
}
override void CreateConditionComponents()
{
m_ConditionItem = new CCINone();
m_ConditionTarget = new CCTNonRuined(UAMaxDistances.DEFAULT);
}
override bool HasTarget()
{
return true;
}
// Empty-handed; target must be a registered "search" interactable.
override bool ActionCondition(PlayerBase player, ActionTarget target, ItemBase item)
{
Object targetObject = target.GetObject();
if (!targetObject)
return false;
if (player.GetItemInHands())
return false;
YAInteractableEntry entry = YAInteractionManager.GetEntry(targetObject.GetType());
return entry != null && entry.ActionType == YAInteractionConfig.ACTION_SEARCH;
}
override bool IsLockTargetOnUse()
{
return false;
}
override void OnFinishProgressServer(ActionData action_data)
{
PlayerBase player = action_data.m_Player;
Object targetObject = action_data.m_Target.GetObject();
if (!player || !targetObject)
return;
YAInteractableEntry entry = YAInteractionManager.GetEntry(targetObject.GetType());
if (!entry || entry.ActionType != YAInteractionConfig.ACTION_SEARCH)
return;
string key = targetObject.GetNetworkIDString();
// --- Per-object cooldown gate ---
if (IsOnCooldown(key))
{
int remaining = GetCooldownRemaining(key);
Print(string.Format("[YAInteraction] Search on cooldown (%1s left) for %2", remaining, key));
player.MessageStatus(string.Format("You already searched this. (%1s)", remaining));
return;
}
// --- Roll & spawn ---
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());
// Stamp cooldown even on an empty result so the object is gated either way.
StampCooldown(key, entry.CooldownSec);
Print(string.Format("[YAInteraction] %1 searched '%2' -> %3 item(s)", player.GetIdentity().GetId(), targetObject.GetType(), spawned.Count()));
if (spawned.Count() > 0)
player.MessageStatus(string.Format("You found %1 item(s).", spawned.Count()));
else
player.MessageStatus("You found nothing useful.");
}
// ------------------------------------------------------------------
// Cooldown helpers (the only persistence-aware surface area).
// ------------------------------------------------------------------
// TODO (v2): read last-search time from CF storage instead of the static map.
protected static bool IsOnCooldown(string key)
{
int expiry;
if (!m_ObjectCooldowns.Find(key, expiry))
return false;
return GetGame().GetTime() < expiry;
}
protected static int GetCooldownRemaining(string key)
{
int expiry;
if (!m_ObjectCooldowns.Find(key, expiry))
return 0;
int remainingMs = expiry - GetGame().GetTime();
if (remainingMs < 0)
remainingMs = 0;
return remainingMs / 1000;
}
// TODO (v2): persist this stamp via CF so it survives server restarts.
protected static void StampCooldown(string key, float cooldownSec)
{
int expiry = GetGame().GetTime() + (int)(cooldownSec * 1000);
m_ObjectCooldowns.Set(key, expiry);
Print(string.Format("[YAInteraction] Search cooldown stamped for %1 (%2s)", key, cooldownSec));
}
}