diff --git a/README.md b/README.md index 6bd36cf..c453023 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,11 @@ A DayZ server mod that adds interactable furniture (search for loot) and a two-stage repairable sink (repair, then gather water). +> **Requires [@YAProxy](http://192.168.88.23/edvinas/YAProxy)** — the furniture +> are proxies baked into house models, not entities. @YAProxy handles proxy +> detection + position sync; YAInteraction's actions extend `YAProxyActionBase`. +> Load `@YAProxy` before `@YAInteraction` (handled via `requiredAddons`). + ## Features - **Searchable furniture** — medical cabinet, fridge, and vending machine yield diff --git a/config.cpp b/config.cpp index 61a7587..66f82c0 100644 --- a/config.cpp +++ b/config.cpp @@ -9,7 +9,7 @@ class CfgPatches { "DZ_Data", "DZ_Scripts", - "ZenProxyFramework" // dependency: ensures ZPF loads before us (proxy API + ZenProxySinkTest) + "YAProxy" // dependency: proxy detection framework (loads before us) }; }; }; diff --git a/scripts/4_World/YAInteraction/Actions/ActionYAGatherWater.c b/scripts/4_World/YAInteraction/Actions/ActionYAGatherWater.c index bd7bbed..9fbdc35 100644 --- a/scripts/4_World/YAInteraction/Actions/ActionYAGatherWater.c +++ b/scripts/4_World/YAInteraction/Actions/ActionYAGatherWater.c @@ -2,29 +2,22 @@ // ActionYAGatherWater // ---------------------------------------------------------------------------- // Stage 2 of the sink. Attached to the liquid container in the player's hands -// (Bottle_Base). Only usable once the sink is repaired (a ZenProxySinkTest -// entity exists at the proxy world position). Fills the held container with a -// random amount of water from config, gated by a per-player cooldown. +// (Bottle_Base). Only succeeds once the sink is repaired (server state keyed by +// proxy world pos). Fills the held container with a random amount of water from +// config, gated by a per-player cooldown. // // Registered on Bottle_Base (see YAInteractionInit) - item-based. // ============================================================================ - -class ActionYAGatherWaterCB : ActionContinuousBaseCB +class ActionYAGatherWater : YAProxyActionBase { - override void CreateActionComponent() + override float GetDuration() { - m_ActionData.m_ActionComponent = new CAContinuousTime(YAInteractionConfig.GATHER_WATER_TIME); + return YAInteractionConfig.GATHER_WATER_TIME; } -} -class ActionYAGatherWater : ActionContinuousBase -{ - void ActionYAGatherWater() + override string GetProxyKeywords() { - m_CallbackClass = ActionYAGatherWaterCB; - m_CommandUID = DayZPlayerConstants.CMD_ACTIONFB_FILLBOTTLEWELL; - m_FullBody = true; - m_StanceMask = DayZPlayerConstants.STANCEMASK_ERECT | DayZPlayerConstants.STANCEMASK_CROUCH; + return YAInteractionManager.GetRepairWaterProxyNames(); } override string GetText() @@ -32,46 +25,15 @@ class ActionYAGatherWater : ActionContinuousBase return "Gather Water"; } - override bool HasZenProxyTarget() - { - return true; - } - - override string GetZenProxyNameTarget() - { - return YAInteractionManager.GetRepairWaterProxyNames(); - } - override void CreateConditionComponents() { m_ConditionItem = new CCINonRuined(); // the container in hands - m_ConditionTarget = new CCTCursorParent(UAMaxDistances.SMALL); + m_ConditionTarget = new CCTCursorParent(UAMaxDistances.DEFAULT); } - override bool ActionCondition(PlayerBase player, ActionTarget target, ItemBase item) + override bool YA_ExtraCondition(PlayerBase player, ActionTarget target, ItemBase item, string proxyName, vector proxyWS) { - if (!CanFill(item)) - return false; - - if (!IsTargetingZenActionProxy(player, target, item)) - return false; - - // Client-only: only available when the sink is repaired (entity exists). - if (GetGame().IsClient()) - { - vector ls, ws; - string houseType; - GetZenActionProxyTargetEx(player, target, item, ls, ws, houseType); - if (!GetProxyObject(ws, YAInteractionConfig.SINK_PROXY_ENTITY)) - return false; - } - - return true; - } - - override bool IsLockTargetOnUse() - { - return false; + return CanFill(item); } override void OnFinishProgressServer(ActionData action_data) @@ -80,10 +42,10 @@ class ActionYAGatherWater : ActionContinuousBase if (!player || !player.GetIdentity()) return; - vector proxyWS = GetZenProxyTargetWS(action_data); + vector proxyWS = GetProxyWS(action_data); + string posKey = proxyWS.ToString(false); - // Defensive: sink must be repaired. - if (!GetProxyObject(proxyWS, YAInteractionConfig.SINK_PROXY_ENTITY)) + if (!YAInteractionManager.IsSinkRepaired(posKey)) { player.MessageStatus("The sink needs to be repaired first."); return; @@ -92,8 +54,7 @@ class ActionYAGatherWater : ActionContinuousBase string uid = player.GetIdentity().GetId(); if (YAInteractionManager.IsWaterOnCooldown(uid)) { - int remaining = YAInteractionManager.GetWaterCooldownRemaining(uid); - player.MessageStatus(string.Format("You need to wait before gathering water again. (%1s)", remaining)); + player.MessageStatus(string.Format("You need to wait before gathering water again. (%1s)", YAInteractionManager.GetWaterCooldownRemaining(uid))); return; } @@ -107,9 +68,8 @@ class ActionYAGatherWater : ActionContinuousBase float amount = Math.RandomFloatInclusive(YAInteractionConfigLoader.Settings.WaterAmountMin, YAInteractionConfigLoader.Settings.WaterAmountMax); Liquid.FillContainer(container, YAInteractionConfig.WATER_LIQUID_TYPE, amount); - // Cooldown comes from the sink's registry entry. - YAInteractableEntry entry = YAInteractionManager.GetEntryByProxyName(GetZenProxyTargetType(action_data)); float cd = YAInteractionConfigLoader.Settings.SinkPlayerCooldownSec; + YAInteractableEntry entry = YAInteractionManager.GetEntryByProxyName(GetProxyName(action_data)); if (entry && entry.CooldownSec > 0) cd = entry.CooldownSec; YAInteractionManager.StampWaterCooldown(uid, cd); diff --git a/scripts/4_World/YAInteraction/Actions/ActionYARepairSink.c b/scripts/4_World/YAInteraction/Actions/ActionYARepairSink.c index 5d1d845..486b766 100644 --- a/scripts/4_World/YAInteraction/Actions/ActionYARepairSink.c +++ b/scripts/4_World/YAInteraction/Actions/ActionYARepairSink.c @@ -1,31 +1,23 @@ // ============================================================================ // ActionYARepairSink // ---------------------------------------------------------------------------- -// Stage 1 of the sink. Requires a wrench/pipe wrench (the item this action is -// attached to). On completion it spawns a ZenProxySinkTest entity at the sink -// proxy's world position - the entity's PRESENCE represents "repaired" and is -// networked to all clients (ZenProxyFramework pattern). +// Stage 1 of the sink. Requires a wrench / pipe wrench (the item this action +// is attached to). On completion it flags the sink (keyed by proxy world pos) +// as repaired in YAInteractionManager, which unlocks ActionYAGatherWater. // -// Registered on Wrench + PipeWrench (see YAInteractionInit) - item-based, so -// it is unaffected by PlayerBase action load-order issues. +// Registered on Wrench + PipeWrench (see YAInteractionInit) - item-based. +// Repaired state is in-memory and resets on server restart (v1). // ============================================================================ - -class ActionYARepairSinkCB : ActionContinuousBaseCB +class ActionYARepairSink : YAProxyActionBase { - override void CreateActionComponent() + override float GetDuration() { - m_ActionData.m_ActionComponent = new CAContinuousTime(YAInteractionConfig.REPAIR_TIME); + return YAInteractionConfig.REPAIR_TIME; } -} -class ActionYARepairSink : ActionContinuousBase -{ - void ActionYARepairSink() + override string GetProxyKeywords() { - m_CallbackClass = ActionYARepairSinkCB; - m_CommandUID = DayZPlayerConstants.CMD_ACTIONFB_INTERACT; - m_FullBody = true; - m_StanceMask = DayZPlayerConstants.STANCEMASK_ERECT | DayZPlayerConstants.STANCEMASK_CROUCH; + return YAInteractionManager.GetRepairWaterProxyNames(); } override string GetText() @@ -33,66 +25,31 @@ class ActionYARepairSink : ActionContinuousBase return "Repair Sink"; } - override bool HasZenProxyTarget() - { - return true; - } - - override string GetZenProxyNameTarget() - { - return YAInteractionManager.GetRepairWaterProxyNames(); - } - override void CreateConditionComponents() { m_ConditionItem = new CCINonRuined(); // the wrench in hands - m_ConditionTarget = new CCTCursorParent(UAMaxDistances.SMALL); - } - - override bool ActionCondition(PlayerBase player, ActionTarget target, ItemBase item) - { - if (!IsTargetingZenActionProxy(player, target, item)) - return false; - - // Client-only: hide if the sink is already repaired (entity exists). - if (GetGame().IsClient()) - { - vector ls, ws; - string houseType; - GetZenActionProxyTargetEx(player, target, item, ls, ws, houseType); - if (GetProxyObject(ws, YAInteractionConfig.SINK_PROXY_ENTITY)) - return false; - } - - return true; - } - - override bool IsLockTargetOnUse() - { - return false; + m_ConditionTarget = new CCTCursorParent(UAMaxDistances.DEFAULT); } override void OnFinishProgressServer(ActionData action_data) { PlayerBase player = action_data.m_Player; - vector proxyWS = GetZenProxyTargetWS(action_data); + vector proxyWS = GetProxyWS(action_data); + string posKey = proxyWS.ToString(false); - // Already repaired? (entity present) - if (GetProxyObject(proxyWS, YAInteractionConfig.SINK_PROXY_ENTITY)) + if (YAInteractionManager.IsSinkRepaired(posKey)) { if (player) player.MessageStatus("This sink is already repaired."); return; } - Object marker = GetGame().CreateObjectEx(YAInteractionConfig.SINK_PROXY_ENTITY, proxyWS, ECE_FULL); - if (!marker) - { - Print("[YAInteraction] FAILED to spawn sink marker @ " + proxyWS.ToString(false)); - return; - } + YAInteractionManager.SetSinkRepaired(posKey, true); - Print(string.Format("[YAInteraction] %1 repaired sink @ %2", player.GetIdentity().GetId(), proxyWS.ToString(false))); - player.MessageStatus("You repaired the sink. You can now gather water."); + if (player) + { + Print(string.Format("[YAInteraction] %1 repaired sink @ %2", player.GetIdentity().GetId(), posKey)); + player.MessageStatus("You repaired the sink. You can now gather water."); + } } } diff --git a/scripts/4_World/YAInteraction/Actions/ActionYASearch.c b/scripts/4_World/YAInteraction/Actions/ActionYASearch.c index 727879c..3045099 100644 --- a/scripts/4_World/YAInteraction/Actions/ActionYASearch.c +++ b/scripts/4_World/YAInteraction/Actions/ActionYASearch.c @@ -1,32 +1,23 @@ // ============================================================================ // ActionYASearch // ---------------------------------------------------------------------------- -// Empty-handed "Search" on a searchable furniture PROXY (fridge / med cabinet -// / vending machine). Uses ZenProxyFramework: the action targets proxies by -// name (GetZenProxyNameTarget) and the proxy world position is synced to the -// server via ActionData, so OnFinishProgressServer can roll loot + gate the -// per-object cooldown by that position. +// Empty-handed "Search" on a searchable furniture proxy (fridge / med cabinet +// / vending machine). Built on YAProxy: the framework resolves which proxy the +// cursor is on and syncs its name + world position to the server. We roll our +// loot table for that proxy and gate a per-object cooldown by the world pos. // -// Registered on PlayerBase (see YAInteractionInit). Cooldown is in-memory. -// TODO (v2): persist the per-proxy cooldown via CF. +// Registered on PlayerBase (see YAInteractionInit). // ============================================================================ - -class ActionYASearchCB : ActionContinuousBaseCB +class ActionYASearch : YAProxyActionBase { - override void CreateActionComponent() + override float GetDuration() { - m_ActionData.m_ActionComponent = new CAContinuousTime(YAInteractionConfig.SEARCH_TIME); + return YAInteractionConfig.SEARCH_TIME; } -} -class ActionYASearch : ActionContinuousBase -{ - void ActionYASearch() + override string GetProxyKeywords() { - m_CallbackClass = ActionYASearchCB; - m_CommandUID = DayZPlayerConstants.CMD_ACTIONFB_INTERACT; - m_FullBody = true; - m_StanceMask = DayZPlayerConstants.STANCEMASK_ERECT | DayZPlayerConstants.STANCEMASK_CROUCH; + return YAInteractionManager.GetSearchProxyNames(); } override string GetText() @@ -34,33 +25,15 @@ class ActionYASearch : ActionContinuousBase return "Search"; } - override bool HasZenProxyTarget() - { - return true; - } - - override string GetZenProxyNameTarget() - { - return YAInteractionManager.GetSearchProxyNames(); - } - override void CreateConditionComponents() { - m_ConditionItem = new CCINone(); - m_ConditionTarget = new CCTCursorParent(UAMaxDistances.SMALL); + m_ConditionItem = new CCINotPresent(); // empty-handed + m_ConditionTarget = new CCTCursorParent(UAMaxDistances.DEFAULT); } - override bool ActionCondition(PlayerBase player, ActionTarget target, ItemBase item) + override bool YA_ExtraCondition(PlayerBase player, ActionTarget target, ItemBase item, string proxyName, vector proxyWS) { - if (player.GetItemInHands()) - return false; - - return IsTargetingZenActionProxy(player, target, item); - } - - override bool IsLockTargetOnUse() - { - return false; + return !player.GetItemInHands(); } override void OnFinishProgressServer(ActionData action_data) @@ -69,10 +42,10 @@ class ActionYASearch : ActionContinuousBase if (!player) return; - string proxyType = GetZenProxyTargetType(action_data); - vector proxyWS = GetZenProxyTargetWS(action_data); + string proxyName = GetProxyName(action_data); + vector proxyWS = GetProxyWS(action_data); - YAInteractableEntry entry = YAInteractionManager.GetEntryByProxyName(proxyType); + YAInteractableEntry entry = YAInteractionManager.GetEntryByProxyName(proxyName); if (!entry || entry.ActionType != YAInteractionConfig.ACTION_SEARCH) return; @@ -80,8 +53,7 @@ class ActionYASearch : ActionContinuousBase if (YAInteractionManager.IsSearchOnCooldown(posKey)) { - int remaining = YAInteractionManager.GetSearchCooldownRemaining(posKey); - player.MessageStatus(string.Format("You already searched this. (%1s)", remaining)); + player.MessageStatus(string.Format("You already searched this. (%1s)", YAInteractionManager.GetSearchCooldownRemaining(posKey))); return; } @@ -91,7 +63,7 @@ class ActionYASearch : ActionContinuousBase YAInteractionManager.StampSearchCooldown(posKey, entry.CooldownSec); - Print(string.Format("[YAInteraction] %1 searched proxy '%2' @ %3 -> %4 item(s)", player.GetIdentity().GetId(), proxyType, posKey, spawned.Count())); + Print(string.Format("[YAInteraction] %1 searched '%2' @ %3 -> %4 item(s)", player.GetIdentity().GetId(), proxyName, posKey, spawned.Count())); if (spawned.Count() > 0) player.MessageStatus(string.Format("You found %1 item(s).", spawned.Count())); diff --git a/scripts/4_World/YAInteraction/Config/YAInteractionConfig.c b/scripts/4_World/YAInteraction/Config/YAInteractionConfig.c index 0225777..ccc1acd 100644 --- a/scripts/4_World/YAInteraction/Config/YAInteractionConfig.c +++ b/scripts/4_World/YAInteraction/Config/YAInteractionConfig.c @@ -16,9 +16,6 @@ class YAInteractionConfig static const string TABLE_FOOD = "FoodTable"; static const string TABLE_VENDING = "VendingTable"; - // ZenProxyFramework entity reused as the sink "repaired" marker. - static const string SINK_PROXY_ENTITY = "ZenProxySinkTest"; - // Non-JSON engine constants. static const int WATER_LIQUID_TYPE = LIQUID_WATER; static const int SEARCH_ROLLS_MIN = 1; diff --git a/scripts/4_World/YAInteraction/Config/YAInteractionConfigLoader.c b/scripts/4_World/YAInteraction/Config/YAInteractionConfigLoader.c index 1baf5ba..8e1fc1d 100644 --- a/scripts/4_World/YAInteraction/Config/YAInteractionConfigLoader.c +++ b/scripts/4_World/YAInteraction/Config/YAInteractionConfigLoader.c @@ -107,10 +107,13 @@ class YAInteractionConfigLoader // "case_sink_a" is confirmed (it's in ZenProxyConstants.SINKS). The search // names are best guesses - confirm via ZPF's proxy dump / debug F-key and // correct them in interactables.json (or here + rebuild). - 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("vending_machine", YAInteractionConfig.ACTION_SEARCH, YAInteractionConfig.TABLE_VENDING, Settings.ObjectCooldownSec)); + // Proxy names confirmed via YAProxy debug: case_sink_a / fridge / lekarnicka. + // Vending not yet confirmed - "sodamachine" is ZenProxyFramework's name; + // adjust here (or in interactables.json) if your map differs. + 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("sodamachine", YAInteractionConfig.ACTION_SEARCH, YAInteractionConfig.TABLE_VENDING, Settings.ObjectCooldownSec)); // --- Loot tables (classnames verified in P:\DZ) --- YALootTable med = MakeTable(YAInteractionConfig.TABLE_MED); diff --git a/scripts/4_World/YAInteraction/Entities/YAInteractionManager.c b/scripts/4_World/YAInteraction/Entities/YAInteractionManager.c index 6963dd1..bbba0a6 100644 --- a/scripts/4_World/YAInteraction/Entities/YAInteractionManager.c +++ b/scripts/4_World/YAInteraction/Entities/YAInteractionManager.c @@ -1,27 +1,26 @@ // ============================================================================ // YAInteractionManager // ---------------------------------------------------------------------------- -// Builds runtime lookups from the loaded config (keyed by ZenProxyFramework -// proxy name) and owns the in-memory cooldowns. +// Builds runtime lookups from the loaded config (keyed by YAProxy proxy name) +// and owns the in-memory cooldowns + sink repaired-state. // -// Repair state is NOT tracked here - it's represented by the presence of a -// ZenProxySinkTest entity spawned at the sink (ZenProxyFramework pattern), so -// it is networked to clients for free. -// -// Cooldowns (reset on restart): -// * Search - per-object, keyed by proxy world-position string. -// * Water - per-player, keyed by player UID. -// TODO (v2): persist cooldowns via CF, keyed by proxy world position. +// In-memory, reset on server restart (v1 spec): +// * Search cooldown - per-object, keyed by proxy world-position string. +// * Water cooldown - per-player, keyed by player UID. +// * Sink repaired flag - per-object, keyed by proxy world-position string. +// TODO (v2): persist via CF, and/or spawn a networked invisible marker so the +// client can hide the Repair action once a sink is fixed. // ============================================================================ class YAInteractionManager { private static ref map m_EntryByProxy = new map(); private static ref map m_TableByName = new map(); - private static ref map m_SearchCooldowns = new map(); // proxyWorldPos -> expiry ms - private static ref map m_WaterCooldowns = new map(); // playerUID -> expiry ms + private static ref map m_SearchCooldowns = new map(); // proxyWorldPos -> expiry ms + private static ref map m_WaterCooldowns = new map(); // playerUID -> expiry ms + private static ref map m_RepairedSinks = new map(); // proxyWorldPos -> repaired (resets on restart) - // Cached comma-joined proxy-name lists for the actions' GetZenProxyNameTarget(). + // Cached comma-joined proxy-name lists for the actions' GetProxyKeywords(). private static string m_SearchProxyNames = ""; private static string m_RepairWaterProxyNames = ""; @@ -99,17 +98,33 @@ class YAInteractionManager return m_RepairWaterProxyNames; } - // Case-insensitive lookup by proxy name. + // Lookup by proxy name. The resolved name often carries an instance suffix + // (e.g. "fridge.001"), so we match the registry key as a substring. static YAInteractableEntry GetEntryByProxyName(string proxyName) { EnsureReady(); if (proxyName == "") return null; - string key = proxyName; - key.ToLower(); - YAInteractableEntry e; - if (m_EntryByProxy.Find(key, e)) - return e; + + string lower = proxyName; + lower.ToLower(); + + YAInteractableEntry exact; + if (m_EntryByProxy.Find(lower, exact)) + return exact; + + if (!YAInteractionConfigLoader.Data) + return null; + + foreach (YAInteractableEntry entry : YAInteractionConfigLoader.Data.Interactables) + { + if (!entry || entry.ProxyName == "") + continue; + string key = entry.ProxyName; + key.ToLower(); + if (lower.Contains(key)) + return entry; + } return null; } @@ -176,4 +191,25 @@ class YAInteractionManager { m_WaterCooldowns.Set(uid, GetGame().GetTime() + (int)(cooldownSec * 1000)); } + + // ------------------------------------------------------------------ + // Sink repaired state (server-side, keyed by proxy world position). + // In-memory only -> resets on server restart (v1 spec). + // TODO (v2): spawn a networked invisible marker (so the client hides the + // Repair action once done) + CF/types.xml lifetime for cross-restart expiry. + // ------------------------------------------------------------------ + static bool IsSinkRepaired(string posKey) + { + bool repaired; + if (m_RepairedSinks.Find(posKey, repaired)) + return repaired; + return false; + } + + static void SetSinkRepaired(string posKey, bool value = true) + { + m_RepairedSinks.Set(posKey, value); + Print(string.Format("[YAInteraction] Sink repaired state %1 = %2", posKey, value)); + } } + diff --git a/scripts/4_World/YAInteraction/YAInteractionInit.c b/scripts/4_World/YAInteraction/YAInteractionInit.c index 4fd48c1..c3ce81c 100644 --- a/scripts/4_World/YAInteraction/YAInteractionInit.c +++ b/scripts/4_World/YAInteraction/YAInteractionInit.c @@ -1,7 +1,7 @@ // ============================================================================ // YAInteractionInit // ---------------------------------------------------------------------------- -// Wires the actions into the game, following the ZenProxyFramework pattern: +// Wires the actions into the game (actions extend YAProxyActionBase): // // * ActionConstructor - register custom actions so GetAction() resolves them // (otherwise AddAction() dereferences null -> crash). @@ -9,8 +9,8 @@ // * Wrench / PipeWrench - Repair Sink (item-in-hands; load-order safe). // * Bottle_Base - Gather Water (item-in-hands; load-order safe). // -// Furniture identification + position sync is handled by ZenProxyFramework's -// modded ActionBase (GetZenProxyNameTarget / GetZenProxyTargetWS). +// Furniture identification + position sync is handled by @YAProxy +// (YAProxyActionBase: GetProxyKeywords / GetProxyName / GetProxyWS). // ============================================================================ modded class ActionConstructor