YAInteraction/scripts/4_World/YAInteraction/Actions/ActionYASearch.c
smthspec ac90535b5e Initial commit: YAInteraction DayZ mod
Searchable furniture (med cabinet, fridge, vending machine) with weighted,
config-driven loot tables, and a two-stage repairable sink (repair then gather
water). Data-driven registry, in-memory cooldowns/repair state with v2 CF hooks.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 21:11:55 +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.m_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.m_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.m_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));
}
}