YALootBoxes/scripts/4_World/YALootBoxes_Config.c
smthspec 663db39ab6 Fix server compile failure: script module binding + LogLevel name clash
Root causes from the script log of a failed boot:
- config.cpp bound the 4_World scripts to BOTH gameScriptModule and
  worldScriptModule. Compiling 4_World code inside 3_Game made PlayerBase,
  Container_Base, ActionContinuousBase and ActionData unknown types, which
  cascaded to YALootBoxes_Base and aborted the Game module. Now bound to
  worldScriptModule only.
- Static YALB_ConfigLoader.LogLevel collided with a framework type named
  LogLevel (CommunityFramework/DabsFramework). Renamed to ActiveLogLevel.

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

344 lines
No EOL
11 KiB
C

// ============================================================
// YALootBoxes_Config.c
// Typed data classes for all JSON files + load/save/defaults.
// Lives in: scripts/4_World/YALootBoxes_Config.c
// ============================================================
const string YALB_CONFIG_DIR = "$profile:YALootBoxes\\";
const string YALB_MAIN_CONFIG_FILE = YALB_CONFIG_DIR + "main_config.json";
const string YALB_BOX_TYPES_FILE = YALB_CONFIG_DIR + "box_types.json";
const string YALB_LOOT_LISTS_FILE = YALB_CONFIG_DIR + "loot_lists.json";
const string YALB_SPAWN_LOCATIONS_FILE = YALB_CONFIG_DIR + "spawn_locations.json";
// ------------------------------------------------------------
// MAIN CONFIG
// ------------------------------------------------------------
class YALB_MainConfig
{
int MaxActiveLootboxes;
int GlobalCheckIntervalSeconds;
int DefaultDespawnMinutes;
int OpenChannelDurationSeconds;
float ProximityMessageRangeMeters;
// Logging verbosity. Stored as a STRING in JSON (admin-friendly to hand-edit)
// and parsed once at load into YALB_ConfigLoader.LogLevel (an int) for cheap
// runtime comparisons. Valid: NONE, ERROR, WARN, INFO, DEBUG, VERBOSE.
string DebugLogLevel;
void YALB_MainConfig()
{
// Sane defaults if file is missing/corrupt
MaxActiveLootboxes = 30;
GlobalCheckIntervalSeconds = 60;
DefaultDespawnMinutes = 15;
OpenChannelDurationSeconds = 5;
ProximityMessageRangeMeters = 3.0;
DebugLogLevel = "INFO";
}
};
// ------------------------------------------------------------
// BOX TYPES
// ------------------------------------------------------------
class YALB_RequiredItem
{
string classname;
string displayName;
};
class YALB_FailEffect
{
string type; // EXPLOSIVE, HEALTH, BLEEDING, SICKNESS, BROKEN_BONE
float chance; // RELATIVE WEIGHT for the single weighted pick - NOT an
// independent probability. Exactly one effect is chosen
// per failure; an effect's odds = its chance / sum(chances).
float damage; // used by EXPLOSIVE / HEALTH
float radius; // used by EXPLOSIVE
int bleedSources; // used by BLEEDING
string agent; // used by SICKNESS
};
class YALB_BoxTypeDef
{
string className;
string type;
ref array<ref YALB_RequiredItem> requiredItems;
float openChance;
float itemDamageOnOpen;
int despawnMinutes;
ref array<ref YALB_FailEffect> failEffects;
void YALB_BoxTypeDef()
{
requiredItems = new array<ref YALB_RequiredItem>();
failEffects = new array<ref YALB_FailEffect>();
openChance = 0.5;
itemDamageOnOpen = 5.0;
despawnMinutes = 15;
}
// Builds the player-facing "requires: X, Y" string
string GetRequiredItemsDisplayString()
{
string result = "";
for (int i = 0; i < requiredItems.Count(); i++)
{
if (i > 0) result += ", ";
result += requiredItems[i].displayName;
}
return result;
}
bool IsValidKeyItem(string itemClassName)
{
for (int i = 0; i < requiredItems.Count(); i++)
{
if (requiredItems[i].classname == itemClassName)
return true;
}
return false;
}
};
class YALB_BoxTypesFile
{
ref array<ref YALB_BoxTypeDef> BoxTypes;
void YALB_BoxTypesFile()
{
BoxTypes = new array<ref YALB_BoxTypeDef>();
}
};
// ------------------------------------------------------------
// LOOT LISTS
// ------------------------------------------------------------
class YALB_LootEntry
{
string classname;
float chance;
int minCount;
int maxCount;
};
// One loot list per box "type". Stored as a flat array of {type, entries}
// objects rather than a map<string, array<...>> - DayZ's JsonFileLoader is
// unreliable at deserializing nested generic containers (a map whose values
// are themselves ref arrays), whereas an array of typed objects deserializes
// dependably. Lookup is a small linear scan (only a handful of types).
class YALB_LootList
{
string type;
ref array<ref YALB_LootEntry> entries;
void YALB_LootList()
{
entries = new array<ref YALB_LootEntry>();
}
};
class YALB_LootListsFile
{
ref array<ref YALB_LootList> LootLists;
void YALB_LootListsFile()
{
LootLists = new array<ref YALB_LootList>();
}
};
// ------------------------------------------------------------
// SPAWN LOCATIONS
// ------------------------------------------------------------
class YALB_SpawnLocationDef
{
string name;
ref array<float> position; // [x, y, z] - 'ref' so the array member is owned/allocated
string boxType;
vector GetPosition()
{
if (position && position.Count() == 3)
return Vector(position[0], position[1], position[2]);
return vector.Zero;
}
};
class YALB_SpawnLocationsFile
{
ref array<ref YALB_SpawnLocationDef> SpawnLocations;
void YALB_SpawnLocationsFile()
{
SpawnLocations = new array<ref YALB_SpawnLocationDef>();
}
};
// ------------------------------------------------------------
// LOADER / SAVER
// ------------------------------------------------------------
class YALB_ConfigLoader
{
static ref YALB_MainConfig MainConfig;
static ref YALB_BoxTypesFile BoxTypes;
static ref YALB_LootListsFile LootLists;
static ref YALB_SpawnLocationsFile SpawnLocations;
// Parsed-once numeric mirror of MainConfig.DebugLogLevel (see YALB_LogLevel).
// Defaults to INFO so logging behaves sanely before LoadAll() runs.
// NB: named ActiveLogLevel, not LogLevel - a loaded framework
// (CommunityFramework/DabsFramework) already defines a type 'LogLevel'.
static int ActiveLogLevel = YALB_LogLevel.INFO;
static void EnsureDirectoryExists()
{
if (!FileExist(YALB_CONFIG_DIR))
{
MakeDirectory(YALB_CONFIG_DIR);
}
}
static void LoadAll()
{
EnsureDirectoryExists();
MainConfig = new YALB_MainConfig();
bool mainConfigExisted = FileExist(YALB_MAIN_CONFIG_FILE);
if (mainConfigExisted)
{
JsonFileLoader<YALB_MainConfig>.JsonLoadFile(YALB_MAIN_CONFIG_FILE, MainConfig);
}
else
{
JsonFileLoader<YALB_MainConfig>.JsonSaveFile(YALB_MAIN_CONFIG_FILE, MainConfig);
}
// Parse the configured level NOW so every log below honours it.
ActiveLogLevel = YALB_ParseLogLevel(MainConfig.DebugLogLevel);
if (!mainConfigExisted)
Log_Info("main_config.json not found - created with defaults.");
BoxTypes = new YALB_BoxTypesFile();
if (FileExist(YALB_BOX_TYPES_FILE))
{
JsonFileLoader<YALB_BoxTypesFile>.JsonLoadFile(YALB_BOX_TYPES_FILE, BoxTypes);
}
else
{
Log_Error("box_types.json not found - no lootbox types loaded! Mod will not spawn anything until this exists.");
}
LootLists = new YALB_LootListsFile();
if (FileExist(YALB_LOOT_LISTS_FILE))
{
JsonFileLoader<YALB_LootListsFile>.JsonLoadFile(YALB_LOOT_LISTS_FILE, LootLists);
}
else
{
Log_Warn("loot_lists.json not found - lootboxes will open empty!");
}
SpawnLocations = new YALB_SpawnLocationsFile();
if (FileExist(YALB_SPAWN_LOCATIONS_FILE))
{
JsonFileLoader<YALB_SpawnLocationsFile>.JsonLoadFile(YALB_SPAWN_LOCATIONS_FILE, SpawnLocations);
}
else
{
Log_Error("spawn_locations.json not found - no spawn points available!");
}
Log_Info(string.Format("Config loaded: %1 box types, %2 spawn locations, %3 loot lists. Log level = %4.",
BoxTypes.BoxTypes.Count(), SpawnLocations.SpawnLocations.Count(), LootLists.LootLists.Count(), MainConfig.DebugLogLevel));
}
static YALB_BoxTypeDef GetBoxTypeByClassName(string className)
{
if (!BoxTypes) return null;
foreach (YALB_BoxTypeDef def : BoxTypes.BoxTypes)
{
if (def.className == className)
return def;
}
return null;
}
static YALB_BoxTypeDef GetBoxTypeByType(string type)
{
if (!BoxTypes) return null;
foreach (YALB_BoxTypeDef def : BoxTypes.BoxTypes)
{
if (def.type == type)
return def;
}
return null;
}
static array<ref YALB_LootEntry> GetLootListForType(string type)
{
if (!LootLists) return null;
foreach (YALB_LootList list : LootLists.LootLists)
{
if (list.type == type)
return list.entries;
}
return null;
}
};
// ------------------------------------------------------------
// LOGGING SYSTEM
// ------------------------------------------------------------
// Ordered severity. A message at level X is emitted only when the
// configured level >= X. NONE silences everything.
enum YALB_LogLevel
{
NONE = 0,
ERROR = 1,
WARN = 2,
INFO = 3,
DEBUG = 4,
VERBOSE = 5
};
// Parse the admin-facing string (case-insensitive) to a YALB_LogLevel int.
// Unrecognised values fall back to INFO so a typo never silences the log.
int YALB_ParseLogLevel(string raw)
{
string s = raw;
s.ToUpper();
switch (s)
{
case "NONE": return YALB_LogLevel.NONE;
case "ERROR": return YALB_LogLevel.ERROR;
case "WARN": return YALB_LogLevel.WARN;
case "WARNING": return YALB_LogLevel.WARN; // tolerate common variant
case "INFO": return YALB_LogLevel.INFO;
case "DEBUG": return YALB_LogLevel.DEBUG;
case "VERBOSE": return YALB_LogLevel.VERBOSE;
}
return YALB_LogLevel.INFO;
}
// Shared gate + format for the non-error levels.
void YALB_Emit(int level, string tag, string msg)
{
if (YALB_ConfigLoader.ActiveLogLevel < level)
return;
Print("[YALootBoxes][" + tag + "] " + msg);
}
// ERROR is routed through ErrorEx() so it stands out in the .RPT, in
// addition to honouring the level gate. (ErrorEx is standard DayZ API.)
void Log_Error(string msg)
{
if (YALB_ConfigLoader.ActiveLogLevel < YALB_LogLevel.ERROR)
return;
ErrorEx("[YALootBoxes][ERROR] " + msg);
}
void Log_Warn(string msg) { YALB_Emit(YALB_LogLevel.WARN, "WARN", msg); }
void Log_Info(string msg) { YALB_Emit(YALB_LogLevel.INFO, "INFO", msg); }
void Log_Debug(string msg) { YALB_Emit(YALB_LogLevel.DEBUG, "DEBUG", msg); }
void Log_Verbose(string msg) { YALB_Emit(YALB_LogLevel.VERBOSE, "VERBOSE", msg); }