Logging: - Replace EnableDebugLogging (bool) with DebugLogLevel (string) in YALB_MainConfig; default "INFO". Stored as a string for admin-friendliness, parsed once at load into YALB_ConfigLoader.LogLevel (int) for cheap compares. - Add YALB_LogLevel enum NONE(0)<ERROR(1)<WARN(2)<INFO(3)<DEBUG(4)<VERBOSE(5), YALB_ParseLogLevel (case-insensitive, falls back to INFO), and leveled helpers Log_Error/Warn/Info/Debug/Verbose. ERROR also routes via ErrorEx(). - Reclassify every existing log call by content (missing critical config -> ERROR, recoverable -> WARN, milestones -> INFO, per-tick summary -> DEBUG, per-player/per-roll detail -> VERBOSE). Add a VERBOSE proximity-send log. - Update main_config.json example and the default constructor to "INFO". - Add README.md documenting levels, the config field, and the VERBOSE warning. Green cleanups (from Phase 2): - Remove dead OnActionInfoUpdate override (behaviour is inherited). - Remove unused m_LocationInUse map (the live pool is m_FreeLocationNames). - Null-guard player identity in the fail-effect log. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
342 lines
No EOL
10 KiB
C
342 lines
No EOL
10 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.
|
|
static int LogLevel = 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.
|
|
LogLevel = 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.LogLevel < 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.LogLevel < 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); } |