// ============================================================ // YALootBoxes_ActionOpen.c // Custom UserAction: hold-to-open a locked lootbox. // Lives in: scripts/4_World/YALootBoxes_ActionOpen.c // // Registration of this action onto YALootBoxes_Base happens by // overriding SetActions()/AddAction() on the entity — see note // at bottom of file. // ============================================================ class YALB_OpenLootboxActionData : ActionData { // currently empty - base ActionData carries everything we need }; class YALB_ActionOpenLootbox : ActionContinuousBase { void YALB_ActionOpenLootbox() { m_CommandUID = DayZPlayerConstants.CMD_ACTIONFB_ONCE; // generic "use" anim hook m_FullBody = false; m_ReloadFinishAnim = false; } override void CreateConditionComponents() { m_ConditionItem = new CCINone(); m_ConditionTarget = new CCTObject(UAMaxDistances.DEFAULT); } override string GetText() { return "Open lootbox"; } override typename GetInputType() { return ContinuousInteractActionInput; } // Only offer the action if: target is our lootbox, it's locked, // and the player is holding a valid key item for it. override bool ActionCondition(PlayerBase player, ActionTarget target, ItemBase item) { YALootBoxes_Base box = YALootBoxes_Base.Cast(target.GetObject()); if (!box) return false; if (!box.IsLocked()) return false; ItemBase handItem = player.GetItemInHands(); if (!handItem) return false; YALB_BoxTypeDef def = box.GetTypeDef(); if (!def) return false; return def.IsValidKeyItem(handItem.GetType()); } override int GetDuration(ActionData action_data) { if (YALB_ConfigLoader.MainConfig) return YALB_ConfigLoader.MainConfig.OpenChannelDurationSeconds * 1000; // ms return 5000; } // Fired server-side when the channel completes WITHOUT being cancelled. override void OnFinishProgressServer(ActionData action_data) { super.OnFinishProgressServer(action_data); YALootBoxes_Base box = YALootBoxes_Base.Cast(action_data.m_Target.GetObject()); PlayerBase player = action_data.m_Player; if (!box || !player) return; YALB_BoxTypeDef def = box.GetTypeDef(); if (!def) return; ItemBase handItem = player.GetItemInHands(); float roll = Math.RandomFloat01(); if (roll <= def.openChance) { // SUCCESS box.UnlockAndFill(); if (handItem) handItem.DecreaseHealth(def.itemDamageOnOpen, false); YALB_NotifyPlayer(player, "The lock gives way."); } else { // FAILURE - apply exactly one weighted-random effect YALB_ApplyRandomFailEffect(player, def, handItem); YALB_NotifyPlayer(player, "The attempt failed."); } } // NOTE: early bail-out (per requirements - fixed channel, instant roll // at the end, interruption allowed) is handled by ActionContinuousBase // itself: releasing the button cancels the action WITHOUT calling // OnFinishProgressServer, so no roll/penalty/progress is saved. No // override is needed here. }; // ------------------------------------------------------------ // Failure-effect resolution // ------------------------------------------------------------ void YALB_ApplyRandomFailEffect(PlayerBase player, YALB_BoxTypeDef def, ItemBase handItem) { if (!def.failEffects || def.failEffects.Count() == 0) return; // Weighted pick: sum chances, roll within sum, walk the list. float totalWeight = 0; foreach (YALB_FailEffect e : def.failEffects) totalWeight += e.chance; if (totalWeight <= 0) return; float roll = Math.RandomFloat(0, totalWeight); float cumulative = 0; YALB_FailEffect chosen = null; foreach (YALB_FailEffect effect : def.failEffects) { cumulative += effect.chance; if (roll <= cumulative) { chosen = effect; break; } } if (!chosen) return; string playerName = ""; if (player.GetIdentity()) playerName = player.GetIdentity().GetName(); Log_Verbose(string.Format("Player %1 triggered fail effect '%2' on box type '%3'.", playerName, chosen.type, def.type)); switch (chosen.type) { case "EXPLOSIVE": YALB_ApplyExplosiveDamage(player, chosen.damage, chosen.radius); break; case "HEALTH": player.DecreaseHealth("", "Health", chosen.damage); break; case "BLEEDING": for (int i = 0; i < chosen.bleedSources; i++) { player.GetBleedingManagerServer().AttemptAddBleedingSourceByMaxCount(); } break; case "SICKNESS": YALB_ApplySickness(player, chosen.agent); break; case "BROKEN_BONE": // Vanilla broken-leg API. The player modifier system drives // the HUD icon / forced-prone reaction off this state. player.SetBrokenLegs(eBrokenLegs.BROKEN_LEGS); break; default: Log_Warn("Unknown fail effect type: " + chosen.type); break; } } void YALB_ApplyExplosiveDamage(PlayerBase player, float damage, float radius) { // Direct damage to the player rather than a real grenade explosion entity, // since we don't want a separate live explosive object to manage/clean up. vector pos = player.GetPosition(); player.DecreaseHealth("", "Health", damage); // Optional: damage nearby players too, to sell the "explosion" effect array nearbyPlayers = new array(); GetGame().GetWorld().GetPlayerList(nearbyPlayers); foreach (Man m : nearbyPlayers) { PlayerBase other = PlayerBase.Cast(m); if (!other || other == player) continue; float dist = vector.Distance(other.GetPosition(), pos); if (dist <= radius) { float falloff = 1.0 - (dist / radius); other.DecreaseHealth("", "Health", damage * falloff); } } } void YALB_ApplySickness(PlayerBase player, string agentName) { // Maps to vanilla DayZ agent system (e.g. eAgents.SALMONELLA, eAgents.INFLUENZA) int agentId = YALB_AgentNameToId(agentName); if (agentId >= 0) { player.InsertAgent(agentId, 1000); // 1000 = full dose, matches vanilla scale } } int YALB_AgentNameToId(string agentName) { if (agentName == "Salmonella") return eAgents.SALMONELLA; if (agentName == "Influenza") return eAgents.INFLUENZA; if (agentName == "Cholera") return eAgents.CHOLERA; if (agentName == "Brain") return eAgents.BRAIN; if (agentName == "Wound_Bacterial") return eAgents.WOUND_AGENT; return -1; } void YALB_NotifyPlayer(PlayerBase player, string message) { if (!player || !player.GetIdentity()) return; GetGame().RPCSingleParam(player, ERPCs.RPC_USER_ACTION_MESSAGE, new Param1(message), true, player.GetIdentity()); } // ------------------------------------------------------------ // NOTE: Hooking this action onto the lootbox class // ------------------------------------------------------------ // The cleanest approach for a fully custom action without // touching vanilla configs is to override SetActions() directly // on YALootBoxes_Base: // // override void SetActions() // { // super.SetActions(); // AddAction(YALB_ActionOpenLootbox); // } // // This override lives in YALootBoxes_Lootbox.c.