YAInteraction/scripts/4_World/YAInteraction/Config/YAInteractionConfigLoader.c
2026-06-19 19:33:59 +03:00

187 lines
8.8 KiB
C

// ============================================================================
// YAInteractionConfigLoader
// ----------------------------------------------------------------------------
// Owns the loaded configuration (Settings + Data) and the JSON load/save flow.
//
// Behaviour:
// * First server start (no JSON present): write code defaults to
// <ProfileFolder>/YAInteraction/{settings.json,interactables.json}.
// * Subsequent starts: read those files; values present in JSON override the
// inline/code defaults.
//
// Init() is called server-side from modded MissionServer.OnInit(), before any
// player connects (see YAInteractionMissionInit.c).
//
// Clients never run MissionServer.OnInit and have no JSON in their profile, so
// EnsureDefaults() provides the in-code default registry without touching files
// - enough for client-side action injection (loot/cooldowns are server-only).
// TODO (v2): sync the registry to clients so admin JSON edits to Interactables
// are reflected client-side without shipping a new build.
// ============================================================================
class YAInteractionConfigLoader
{
static ref YAInteractionSettings Settings;
static ref YAInteractionData Data;
private static const string DIR_NAME = "YAInteraction";
private static const string SETTINGS_FILE = "settings.json";
private static const string DATA_FILE = "interactables.json";
// ------------------------------------------------------------------
// Full server-side init: build paths, ensure dir, load-or-save each
// file, then rebuild the manager lookups.
// ------------------------------------------------------------------
static void Init()
{
// "$profile:" is the engine-mounted virtual path to the server profiles
// folder. (DayZGame has no GetProfileFolder() in this build; $profile: is
// the portable idiom used by e.g. ZenRepairWells / TerjeMods.)
string folder = "$profile:\\" + DIR_NAME + "\\";
if (!FileExist(folder))
MakeDirectory(folder);
string settingsPath = folder + SETTINGS_FILE;
string dataPath = folder + DATA_FILE;
// --- settings.json (per-key override of inline defaults) ---
Settings = new YAInteractionSettings(); // inline defaults
if (FileExist(settingsPath))
{
JsonFileLoader<YAInteractionSettings>.JsonLoadFile(settingsPath, Settings);
Print("[YAInteraction] Loaded settings from " + settingsPath);
}
else
{
JsonFileLoader<YAInteractionSettings>.JsonSaveFile(settingsPath, Settings);
Print("[YAInteraction] No settings.json - wrote defaults to " + settingsPath);
}
// --- interactables.json (full registry/loot definition) ---
if (FileExist(dataPath))
{
Data = new YAInteractionData(); // fresh empty arrays, fully defined by JSON
JsonFileLoader<YAInteractionData>.JsonLoadFile(dataPath, Data);
Print(string.Format("[YAInteraction] Loaded interactables from %1 (%2 entries, %3 tables)", dataPath, Data.Interactables.Count(), Data.LootTables.Count()));
}
else
{
Data = BuildDefaultData();
JsonFileLoader<YAInteractionData>.JsonSaveFile(dataPath, Data);
Print(string.Format("[YAInteraction] No interactables.json - wrote defaults to %1 (%2 entries, %3 tables)", dataPath, Data.Interactables.Count(), Data.LootTables.Count()));
}
YAInteractionManager.RebuildLookups();
// TODO (v2): hot-reload - re-run Init() + RebuildLookups() on an admin
// command or file watch so JSON edits apply without a server restart.
}
// ------------------------------------------------------------------
// Client-safe fallback: ensure Settings/Data exist from code defaults
// without any file I/O. Used when Init() hasn't run (i.e. on clients).
// ------------------------------------------------------------------
static void EnsureDefaults()
{
if (!Settings)
Settings = new YAInteractionSettings();
if (!Data)
{
Data = BuildDefaultData();
Print("[YAInteraction] EnsureDefaults: built in-code default registry (no JSON / client side)");
}
}
// ==================================================================
// Code defaults - the same items as the original config. This is the
// single place defaults are defined; edit here, delete the JSON, and
// restart to regenerate.
// ==================================================================
static YAInteractionData BuildDefaultData()
{
if (!Settings)
Settings = new YAInteractionSettings(); // default cooldowns feed the default entries below
YAInteractionData d = new YAInteractionData();
// --- Interactable registry (keyed by ZenProxyFramework PROXY NAME) ---
// (proxyName, actionType, lootTableName, cooldownSec)
// "case_sink_a" is confirmed (it's in ZenProxyConstants.SINKS). The search
// names are best guesses - confirm via ZPF's proxy dump / debug F-key and
// correct them in interactables.json (or here + rebuild).
// Proxy names (from YAProxy scan / DayZ editor): the resolved name carries
// a ".NNN" instance suffix (e.g. "fridge.001"); the keyword is matched as
// a substring, so the base name is enough.
// (proxyName, actionType, lootTable, cooldownSec, searchChance%, searchMin, searchMax)
d.Interactables.Insert(MakeEntry("case_sink_a", YAInteractionConfig.ACTION_REPAIR_WATER, "", Settings.SinkPlayerCooldownSec));
d.Interactables.Insert(MakeEntry("sink", YAInteractionConfig.ACTION_REPAIR_WATER, "", Settings.SinkPlayerCooldownSec));
d.Interactables.Insert(MakeEntry("lekarnicka", YAInteractionConfig.ACTION_SEARCH, YAInteractionConfig.TABLE_MED, Settings.ObjectCooldownSec, 60.0, 1, 2));
d.Interactables.Insert(MakeEntry("fridge", YAInteractionConfig.ACTION_SEARCH, YAInteractionConfig.TABLE_FOOD, Settings.ObjectCooldownSec, 75.0, 1, 2));
// Works for vending machines SPAWNED STANDALONE (init.c / CE) - matched as
// a real entity by classname (StaticObj_Furniture_vending_machine). House-
// baked vending proxies still can't be detected (no targetable object).
d.Interactables.Insert(MakeEntry("vending_machine", YAInteractionConfig.ACTION_SEARCH, YAInteractionConfig.TABLE_VENDING, Settings.ObjectCooldownSec, 80.0, 1, 2));
//For future reference, static objects along patrhways (e.g. crates, barrels, boxes) with various random loot.
//d.Interactables.Insert(MakeEntry("Wooden_Crate", YAInteractionConfig.ACTION_SEARCH, YAInteractionConfig.TABLE_FOOD, Settings.ObjectCooldownSec, 90.0, 1, 3));
// --- Loot tables (classnames verified in P:\DZ) ---
YALootTable med = MakeTable(YAInteractionConfig.TABLE_MED);
med.Entries.Insert(MakeLoot("BandageDressing", 40, 1, 2));
med.Entries.Insert(MakeLoot("CharcoalTablets", 25, 1, 1));
med.Entries.Insert(MakeLoot("TetracyclineAntibiotics", 15, 1, 1));
med.Entries.Insert(MakeLoot("SalineBag", 10, 1, 1));
med.Entries.Insert(MakeLoot("Morphine", 6, 1, 1));
med.Entries.Insert(MakeLoot("Epinephrine", 4, 1, 1));
d.LootTables.Insert(med);
YALootTable food = MakeTable(YAInteractionConfig.TABLE_FOOD);
food.Entries.Insert(MakeLoot("Apple", 30, 1, 2));
food.Entries.Insert(MakeLoot("BakedBeansCan", 20, 1, 1));
food.Entries.Insert(MakeLoot("SardinesCan", 15, 1, 1));
food.Entries.Insert(MakeLoot("Rice", 15, 1, 1));
food.Entries.Insert(MakeLoot("Banana", 12, 1, 2)); // vanilla has no "dried banana"
food.Entries.Insert(MakeLoot("PowderedMilk", 8, 1, 1));
d.LootTables.Insert(food);
YALootTable vending = MakeTable(YAInteractionConfig.TABLE_VENDING);
vending.Entries.Insert(MakeLoot("SodaCan_Cola", 20, 0, 0));
vending.Entries.Insert(MakeLoot("SodaCan_Spite", 20, 0, 0));
vending.Entries.Insert(MakeLoot("SodaCan_Fronta", 20, 0, 0));
vending.Entries.Insert(MakeLoot("SodaCan_Pipsi", 15, 0, 0));
vending.Entries.Insert(MakeLoot("WaterBottle", 25, 0, 0)); // "PET_BottledWater" does not exist
d.LootTables.Insert(vending);
return d;
}
// --- Factory helpers (data classes have no parameterised ctor so the
// JSON serializer can default-construct them). ---
private static YAInteractableEntry MakeEntry(string proxyName, string actionType, string lootTableName, float cooldownSec, float searchChance = 100.0, int searchMin = 1, int searchMax = 1)
{
YAInteractableEntry e = new YAInteractableEntry();
e.ProxyName = proxyName;
e.ActionType = actionType;
e.LootTableName = lootTableName;
e.CooldownSec = cooldownSec;
e.SearchChance = searchChance;
e.SearchMin = searchMin;
e.SearchMax = searchMax;
return e;
}
private static YALootTable MakeTable(string name)
{
YALootTable t = new YALootTable();
t.Name = name;
return t;
}
private static YALootEntry MakeLoot(string className, float weight, int minQty, int maxQty)
{
YALootEntry e = new YALootEntry();
e.ClassName = className;
e.Weight = weight;
e.MinQty = minQty;
e.MaxQty = maxQty;
return e;
}
}