// ============================================================ // YALootBoxes_Lootbox.c // The actual in-world lootbox entity. // Lives in: scripts/4_World/YALootBoxes_Lootbox.c // // NOTE ON className: each entry in box_types.json should have a // matching config.cpp class inheriting from YALootBoxes_Base // (see config.cpp artifact). This script class provides the // shared behavior for ALL of them. // ============================================================ class YALootBoxes_Base extends Container_Base { protected bool m_IsLocked; protected string m_BoxTypeId; // matches "type" in box_types.json protected float m_SpawnTime; // world time (seconds) when spawned protected float m_UnlockedTime; // world time when it became unlocked (-1 if still locked) protected ref YALB_BoxTypeDef m_TypeDef; // cached definition void YALootBoxes_Base() { m_IsLocked = true; m_UnlockedTime = -1; RegisterNetSyncVariableBool("m_IsLocked"); } override void EEInit() { super.EEInit(); SetSynchDirty(); } // Called by the manager right after spawning, before placing in world void Init(string boxTypeId) { m_BoxTypeId = boxTypeId; m_TypeDef = YALB_ConfigLoader.GetBoxTypeByType(boxTypeId); m_IsLocked = true; m_UnlockedTime = -1; m_SpawnTime = GetGame().GetTime() / 1000.0; if (!m_TypeDef) { Log_Warn("Lootbox spawned with unknown type '" + boxTypeId + "'. Using fallback values."); } SetSynchDirty(); } bool IsLocked() { return m_IsLocked; } string GetBoxTypeId() { return m_BoxTypeId; } YALB_BoxTypeDef GetTypeDef() { if (!m_TypeDef) m_TypeDef = YALB_ConfigLoader.GetBoxTypeByType(m_BoxTypeId); return m_TypeDef; } float GetSpawnTime() { return m_SpawnTime; } float GetUnlockedTime() { return m_UnlockedTime; } // Locked-box lifetime in minutes: per-type despawnMinutes if set (>0), // otherwise the global main_config DespawnMinutes. int GetLockedLifetimeMinutes() { if (m_TypeDef && m_TypeDef.despawnMinutes > 0) return m_TypeDef.despawnMinutes; if (YALB_ConfigLoader.MainConfig) return YALB_ConfigLoader.MainConfig.DespawnMinutes; return 15; } // Opened-box lifetime in minutes: always the global OpenedDespawnMinutes. int GetOpenedLifetimeMinutes() { if (YALB_ConfigLoader.MainConfig) return YALB_ConfigLoader.MainConfig.OpenedDespawnMinutes; return 5; } // True once a still-locked, never-opened box has outlived DespawnMinutes; // the manager then removes it and respawns one elsewhere (keeps the loot // table fresh even if nobody opens it). bool HasExpiredUnopened(float currentWorldTime) { if (!m_IsLocked) return false; // handled by HasExpiredAfterOpening once opened float ageMinutes = (currentWorldTime - m_SpawnTime) / 60.0; return ageMinutes >= GetLockedLifetimeMinutes(); } // True once an opened box has outlived OpenedDespawnMinutes since unlock. bool HasExpiredAfterOpening(float currentWorldTime) { if (m_IsLocked || m_UnlockedTime < 0) return false; float ageMinutes = (currentWorldTime - m_UnlockedTime) / 60.0; return ageMinutes >= GetOpenedLifetimeMinutes(); } // Called by YALB_OpenLootboxAction on a successful unlock roll. // Converts this from a locked decoration into a real container // populated with loot, then starts its post-open despawn clock. void UnlockAndFill() { if (!m_IsLocked) return; // already unlocked, no-op m_IsLocked = false; m_UnlockedTime = GetGame().GetTime() / 1000.0; SetSynchDirty(); PopulateLoot(); Log_Info(string.Format("Lootbox '%1' (%2) unlocked and filled.", GetBoxTypeId(), GetPosition().ToString())); } protected void PopulateLoot() { array lootList = YALB_ConfigLoader.GetLootListForType(m_BoxTypeId); if (!lootList) { Log_Warn("No loot list found for type '" + m_BoxTypeId + "' - box will be empty."); return; } foreach (YALB_LootEntry entry : lootList) { float roll = Math.RandomFloat01(); if (roll > entry.chance) continue; int count = Math.RandomIntInclusive(entry.minCount, entry.maxCount); for (int i = 0; i < count; i++) { GetInventory().CreateInInventory(entry.classname); } } } // Player-facing string used by the proximity message and the action menu string GetRequiredItemsMessage() { if (!m_TypeDef) return "Locked (unknown requirements)"; return "Requires: " + m_TypeDef.GetRequiredItemsDisplayString(); } // Hooks our custom hold-to-open action onto this object without // needing to touch any vanilla cfgVehicles action arrays. // NOTE: the engine-called override is SetActions()/AddAction(), // NOT GetActions(out array) - the latter is never // invoked by the action manager and silently registers nothing. override void SetActions() { super.SetActions(); AddAction(YALB_ActionOpenLootbox); } };