YALootBoxes/scripts/4_World/YALootBoxes_ActionOpen.c
smthspec 354dfe34d5 Phase 1: rename audit complete (LootboxSpawner -> YALootBoxes)
- Renamed code prefix LB_ -> YALB_ across all four script files
- Renamed source files LootboxSpawner_*.c -> YALootBoxes_*.c
- Deleted three empty 0-byte stub files (_BoxTypes/_LootLists/_SpawnLocations)
- Fixed folder case scripts/4_world -> scripts/4_World to match config.cpp
- Verified box_types.json className values match CfgVehicles class names

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 21:27:38 +03:00

235 lines
No EOL
7.6 KiB
C

// ============================================================
// 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
// via GetActionComponentName/cfgVehicles "actions[]" in config.cpp,
// OR (simpler, used here) by overriding GetActions() — 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.");
}
}
// Player can bail out early (per requirements: fixed channel,
// instant roll at the end, but interruption is allowed and
// simply cancels - no roll, no penalty, no progress saved).
override void OnActionInfoUpdate(PlayerBase player, ActionData action_data)
{
super.OnActionInfoUpdate(player, action_data);
}
};
// ------------------------------------------------------------
// 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;
YALB_Log(string.Format("Player %1 triggered fail effect '%2' on box type '%3'.",
player.GetIdentity().GetName(), chosen.type, def.type));
switch (chosen.type)
{
case "EXPLOSIVE":
YALB_ApplyExplosiveDamage(player, chosen.damage, chosen.radius);
break;
case "HEALTH":
player.DecreaseHealth("", "", 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":
player.GetHealthDamageManager().SetHealthLimitDamage(true);
player.SetHealth("", "Health", player.GetHealth("", "Health") - 1); // nudge to trigger fracture check
player.GetHealthDamageManager().SetFracture(true);
break;
default:
YALB_Log("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("", "", damage);
// Optional: damage nearby players too, to sell the "explosion" effect
array<Man> nearbyPlayers = new array<Man>();
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("", "", 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<string>(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 GetActions() directly
// on YALootBoxes_Base:
//
// override void GetActions(out array<typename> actions)
// {
// actions.Insert(YALB_ActionOpenLootbox);
// }
//
// This override is included in YALootBoxes_Lootbox.c via a
// partial addition — see the updated lootbox entity file.