YALootBoxes/scripts/4_World/YALootBoxes_Lootbox.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

155 lines
No EOL
4.8 KiB
C

// ============================================================
// 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 ContainerObject
{
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)
{
YALB_Log("WARNING: 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;
}
// How many minutes this box should live for, based on its current state
int GetDespawnMinutes()
{
if (m_TypeDef)
return m_TypeDef.despawnMinutes;
return YALB_ConfigLoader.MainConfig.DefaultDespawnMinutes;
}
// Returns true if (locked, never-opened) box has expired and should
// be removed/respawned elsewhere. Locked boxes use the SAME despawn
// timer as unlocked ones by design (keeps the loot table fresh even
// if nobody opens it) - manager checks this each cycle.
bool HasExpiredUnopened(float currentWorldTime)
{
if (!m_IsLocked) return false; // handled separately once opened
float ageMinutes = (currentWorldTime - m_SpawnTime) / 60.0;
return ageMinutes >= GetDespawnMinutes();
}
bool HasExpiredAfterOpening(float currentWorldTime)
{
if (m_IsLocked || m_UnlockedTime < 0) return false;
float ageMinutes = (currentWorldTime - m_UnlockedTime) / 60.0;
return ageMinutes >= GetDespawnMinutes();
}
// 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();
YALB_Log(string.Format("Lootbox '%1' (%2) unlocked and filled.", GetBoxTypeId(), GetPosition().ToString()));
}
protected void PopulateLoot()
{
array<ref YALB_LootEntry> lootList = YALB_ConfigLoader.GetLootListForType(m_BoxTypeId);
if (!lootList)
{
YALB_Log("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.
override void GetActions(out array<typename> actions)
{
super.GetActions(actions);
actions.Insert(YALB_ActionOpenLootbox);
}
};