Restructure: self-starting bootstrap + auto-provision all config files
Structure / module placement: - Add scripts/5_Mission/YALootBoxes_MissionServer.c: a modded MissionServer that self-starts the manager in OnInit(). No init.c edit required anymore. - config.cpp: bind 5_Mission to missionScriptModule (4_World stays on worldScriptModule). Core logic correctly stays in 4_World; only the mission-lifecycle bootstrap lives in 5_Mission. Config create/read/write: - LoadAll now writes populated defaults for ALL four JSON files when missing (previously only main_config.json was generated; box_types/loot_lists/ spawn_locations were left empty, so a fresh install spawned nothing). - Add default-data factories (YALB_MkReq/MkFail/MkLoot/MkLoc/MkBoxType) and DefaultBoxTypes/DefaultLootLists/DefaultSpawnLocations builders mirroring configfiles.readme. The mod is now self-provisioning on first run. Docs: - Prefix.cpp: refreshed folder-layout summary; dropped stale init.c / simulationModule notes. - Manager.c: bootstrap comment now points to the 5_Mission self-start. - README.md: add Install & first-run section. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
910f972d3a
commit
d28cebd5d6
6 changed files with 223 additions and 44 deletions
35
Prefix.cpp
35
Prefix.cpp
|
|
@ -13,25 +13,26 @@
|
|||
// FOLDER LAYOUT SUMMARY
|
||||
// ============================================================
|
||||
// YALootBoxes/
|
||||
// ├── config.cpp
|
||||
// ├── config.cpp (CfgPatches/CfgMods module bindings + CfgVehicles)
|
||||
// ├── Prefix.cpp
|
||||
// ├── configfiles.readme (reference copies of the four default JSONs)
|
||||
// ├── README.md
|
||||
// ├── tools/ (Collect-YALootBoxesLogs.ps1 debug helper)
|
||||
// └── scripts/
|
||||
// └── 4_World/
|
||||
// ├── YALootBoxes_Config.c
|
||||
// ├── YALootBoxes_Lootbox.c
|
||||
// ├── YALootBoxes_ActionOpen.c
|
||||
// └── YALootBoxes_Manager.c
|
||||
// ├── 4_World/ (-> worldScriptModule)
|
||||
// │ ├── YALootBoxes_Config.c (data classes, loader, logging, defaults)
|
||||
// │ ├── YALootBoxes_Lootbox.c (YALootBoxes_Base entity)
|
||||
// │ ├── YALootBoxes_ActionOpen.c(hold-to-open action + fail effects)
|
||||
// │ └── YALootBoxes_Manager.c (spawn/despawn lifecycle + proximity msgs)
|
||||
// └── 5_Mission/ (-> missionScriptModule)
|
||||
// └── YALootBoxes_MissionServer.c (modded MissionServer; self-starts manager)
|
||||
//
|
||||
// Server mission init.c needs the small hook shown at the
|
||||
// bottom of YALootBoxes_Manager.c.
|
||||
// The manager self-starts via the 5_Mission modded MissionServer -
|
||||
// NO init.c edit is required.
|
||||
//
|
||||
// IMPORTANT NOTE ON simulationModule / class binding:
|
||||
// DayZ's Enforce Script binds a .c class to a cfgVehicles entry
|
||||
// either by the entry's class name matching the script class
|
||||
// name directly, OR via simulationModule for non-standard cases.
|
||||
// For most setups, simply naming the cfgVehicles class identically
|
||||
// to your Enforce Script class (as done above: e.g.
|
||||
// "YALootBoxes_Crate_Military") is what actually binds them -
|
||||
// if your DayZ Server/SDK version doesn't honor simulationModule,
|
||||
// delete that line; the name match alone is sufficient.
|
||||
// CLASS BINDING: the CfgVehicles crate classes inherit from a shared
|
||||
// "YALootBoxes_Base" config class whose name matches the YALootBoxes_Base
|
||||
// script class; the engine resolves the script type by walking that config
|
||||
// inheritance chain. (The old simulationModule approach did not bind script
|
||||
// classes and has been removed.)
|
||||
// ============================================================
|
||||
18
README.md
18
README.md
|
|
@ -19,6 +19,24 @@ All settings live in four JSON files auto-generated on first run at
|
|||
> Edit the copies under `$profile:YALootBoxes\`, **not** the ones inside the PBO.
|
||||
> Reference/default versions of all four are in `configfiles.readme`.
|
||||
|
||||
## Install & first run
|
||||
|
||||
1. Pack the mod into `@YALootBoxes\addons` and add `@YALootBoxes` to the
|
||||
server's `-mod=` list (after any frameworks it uses).
|
||||
2. Start the server. On first run the mod **auto-generates all four JSON files
|
||||
with working defaults** at `$profile:YALootBoxes\`, so boxes spawn
|
||||
immediately — no manual config needed.
|
||||
3. **No `init.c` edit is required.** The mod self-starts via a modded
|
||||
`MissionServer` (`scripts/5_Mission/YALootBoxes_MissionServer.c`).
|
||||
4. Stop the server, edit the generated JSONs to taste, restart.
|
||||
|
||||
Confirm it loaded by looking for these lines in the server `.RPT`:
|
||||
|
||||
```
|
||||
[YALootBoxes][INFO] Config loaded: 3 box types, 7 spawn locations, 3 loot lists. Log level = INFO.
|
||||
[YALootBoxes][INFO] Manager initialized. N lootboxes spawned.
|
||||
```
|
||||
|
||||
## Logging
|
||||
|
||||
Logging verbosity is controlled by a single field in `main_config.json`:
|
||||
|
|
|
|||
|
|
@ -44,6 +44,14 @@ class CfgMods
|
|||
value = "";
|
||||
files[] = { "YALootBoxes/scripts/4_World" };
|
||||
};
|
||||
// 5_Mission bootstrap: a modded MissionServer that self-starts the
|
||||
// lootbox manager. Lives in the Mission module because that's the
|
||||
// only module with a mission-lifecycle entry point (OnInit).
|
||||
class missionScriptModule
|
||||
{
|
||||
value = "";
|
||||
files[] = { "YALootBoxes/scripts/5_Mission" };
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -174,6 +174,50 @@ class YALB_SpawnLocationsFile
|
|||
}
|
||||
};
|
||||
|
||||
// ------------------------------------------------------------
|
||||
// DEFAULT-DATA FACTORIES
|
||||
// Small constructors used to build the first-run default files so
|
||||
// the mod is self-provisioning. These mirror configfiles.readme.
|
||||
// ------------------------------------------------------------
|
||||
YALB_RequiredItem YALB_MkReq(string cls, string disp)
|
||||
{
|
||||
YALB_RequiredItem r = new YALB_RequiredItem();
|
||||
r.classname = cls; r.displayName = disp;
|
||||
return r;
|
||||
}
|
||||
|
||||
YALB_FailEffect YALB_MkFail(string type, float chance, float damage = 0.0, float radius = 0.0, int bleed = 0, string agent = "")
|
||||
{
|
||||
YALB_FailEffect e = new YALB_FailEffect();
|
||||
e.type = type; e.chance = chance; e.damage = damage; e.radius = radius;
|
||||
e.bleedSources = bleed; e.agent = agent;
|
||||
return e;
|
||||
}
|
||||
|
||||
YALB_LootEntry YALB_MkLoot(string cls, float chance, int mn, int mx)
|
||||
{
|
||||
YALB_LootEntry l = new YALB_LootEntry();
|
||||
l.classname = cls; l.chance = chance; l.minCount = mn; l.maxCount = mx;
|
||||
return l;
|
||||
}
|
||||
|
||||
YALB_SpawnLocationDef YALB_MkLoc(string name, float x, float y, float z, string boxType)
|
||||
{
|
||||
YALB_SpawnLocationDef d = new YALB_SpawnLocationDef();
|
||||
d.name = name; d.boxType = boxType;
|
||||
d.position = new array<float>();
|
||||
d.position.Insert(x); d.position.Insert(y); d.position.Insert(z);
|
||||
return d;
|
||||
}
|
||||
|
||||
YALB_BoxTypeDef YALB_MkBoxType(string className, string type, float openChance, float itemDmg, int despawn)
|
||||
{
|
||||
YALB_BoxTypeDef b = new YALB_BoxTypeDef(); // requiredItems/failEffects arrays alloc'd in ctor
|
||||
b.className = className; b.type = type;
|
||||
b.openChance = openChance; b.itemDamageOnOpen = itemDmg; b.despawnMinutes = despawn;
|
||||
return b;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------
|
||||
// LOADER / SAVER
|
||||
// ------------------------------------------------------------
|
||||
|
|
@ -198,61 +242,147 @@ class YALB_ConfigLoader
|
|||
}
|
||||
}
|
||||
|
||||
// Loads each config file, writing a populated default if it is missing,
|
||||
// so a fresh install is fully self-provisioning (boxes spawn on first run).
|
||||
static void LoadAll()
|
||||
{
|
||||
EnsureDirectoryExists();
|
||||
|
||||
// --- main config (load, or save defaults) ---
|
||||
MainConfig = new YALB_MainConfig();
|
||||
bool mainConfigExisted = FileExist(YALB_MAIN_CONFIG_FILE);
|
||||
if (mainConfigExisted)
|
||||
{
|
||||
JsonFileLoader<YALB_MainConfig>.JsonLoadFile(YALB_MAIN_CONFIG_FILE, MainConfig);
|
||||
}
|
||||
else
|
||||
{
|
||||
bool createdMain = !FileExist(YALB_MAIN_CONFIG_FILE);
|
||||
if (createdMain)
|
||||
JsonFileLoader<YALB_MainConfig>.JsonSaveFile(YALB_MAIN_CONFIG_FILE, MainConfig);
|
||||
}
|
||||
else
|
||||
JsonFileLoader<YALB_MainConfig>.JsonLoadFile(YALB_MAIN_CONFIG_FILE, MainConfig);
|
||||
|
||||
// Parse the configured level NOW so every log below honours it.
|
||||
ActiveLogLevel = YALB_ParseLogLevel(MainConfig.DebugLogLevel);
|
||||
if (createdMain) Log_Info("main_config.json not found - created with defaults.");
|
||||
|
||||
if (!mainConfigExisted)
|
||||
Log_Info("main_config.json not found - created with defaults.");
|
||||
|
||||
BoxTypes = new YALB_BoxTypesFile();
|
||||
// --- box types ---
|
||||
if (FileExist(YALB_BOX_TYPES_FILE))
|
||||
{
|
||||
BoxTypes = new YALB_BoxTypesFile();
|
||||
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.");
|
||||
BoxTypes = DefaultBoxTypes();
|
||||
JsonFileLoader<YALB_BoxTypesFile>.JsonSaveFile(YALB_BOX_TYPES_FILE, BoxTypes);
|
||||
Log_Info("box_types.json not found - created with defaults.");
|
||||
}
|
||||
|
||||
LootLists = new YALB_LootListsFile();
|
||||
// --- loot lists ---
|
||||
if (FileExist(YALB_LOOT_LISTS_FILE))
|
||||
{
|
||||
LootLists = new YALB_LootListsFile();
|
||||
JsonFileLoader<YALB_LootListsFile>.JsonLoadFile(YALB_LOOT_LISTS_FILE, LootLists);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log_Warn("loot_lists.json not found - lootboxes will open empty!");
|
||||
LootLists = DefaultLootLists();
|
||||
JsonFileLoader<YALB_LootListsFile>.JsonSaveFile(YALB_LOOT_LISTS_FILE, LootLists);
|
||||
Log_Info("loot_lists.json not found - created with defaults.");
|
||||
}
|
||||
|
||||
SpawnLocations = new YALB_SpawnLocationsFile();
|
||||
// --- spawn locations ---
|
||||
if (FileExist(YALB_SPAWN_LOCATIONS_FILE))
|
||||
{
|
||||
SpawnLocations = new YALB_SpawnLocationsFile();
|
||||
JsonFileLoader<YALB_SpawnLocationsFile>.JsonLoadFile(YALB_SPAWN_LOCATIONS_FILE, SpawnLocations);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log_Error("spawn_locations.json not found - no spawn points available!");
|
||||
SpawnLocations = DefaultSpawnLocations();
|
||||
JsonFileLoader<YALB_SpawnLocationsFile>.JsonSaveFile(YALB_SPAWN_LOCATIONS_FILE, SpawnLocations);
|
||||
Log_Info("spawn_locations.json not found - created with defaults.");
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------
|
||||
// First-run default builders (mirror configfiles.readme)
|
||||
// ------------------------------------------------------------
|
||||
static YALB_BoxTypesFile DefaultBoxTypes()
|
||||
{
|
||||
YALB_BoxTypesFile f = new YALB_BoxTypesFile();
|
||||
|
||||
YALB_BoxTypeDef mil = YALB_MkBoxType("YALootBoxes_Crate_Military", "military", 0.65, 8.0, 15);
|
||||
mil.requiredItems.Insert(YALB_MkReq("BoltCutters", "Bolt Cutters"));
|
||||
mil.requiredItems.Insert(YALB_MkReq("Crowbar", "Crowbar"));
|
||||
mil.failEffects.Insert(YALB_MkFail("EXPLOSIVE", 0.15, 45.0, 2.5));
|
||||
mil.failEffects.Insert(YALB_MkFail("HEALTH", 0.25, 15.0));
|
||||
mil.failEffects.Insert(YALB_MkFail("BLEEDING", 0.30, 0.0, 0.0, 2));
|
||||
mil.failEffects.Insert(YALB_MkFail("SICKNESS", 0.15, 0.0, 0.0, 0, "Salmonella"));
|
||||
mil.failEffects.Insert(YALB_MkFail("BROKEN_BONE", 0.15));
|
||||
f.BoxTypes.Insert(mil);
|
||||
|
||||
YALB_BoxTypeDef med = YALB_MkBoxType("YALootBoxes_Crate_Medical", "medical", 0.80, 4.0, 15);
|
||||
med.requiredItems.Insert(YALB_MkReq("Hacksaw", "Hacksaw"));
|
||||
med.failEffects.Insert(YALB_MkFail("SICKNESS", 0.50, 0.0, 0.0, 0, "Influenza"));
|
||||
med.failEffects.Insert(YALB_MkFail("HEALTH", 0.30, 8.0));
|
||||
med.failEffects.Insert(YALB_MkFail("BLEEDING", 0.20, 0.0, 0.0, 1));
|
||||
f.BoxTypes.Insert(med);
|
||||
|
||||
YALB_BoxTypeDef ind = YALB_MkBoxType("YALootBoxes_Crate_Industrial", "industrial", 0.50, 12.0, 20);
|
||||
ind.requiredItems.Insert(YALB_MkReq("Crowbar", "Crowbar"));
|
||||
ind.requiredItems.Insert(YALB_MkReq("WeldingMask", "Angle Grinder"));
|
||||
ind.failEffects.Insert(YALB_MkFail("EXPLOSIVE", 0.20, 60.0, 3.0));
|
||||
ind.failEffects.Insert(YALB_MkFail("BROKEN_BONE", 0.25));
|
||||
ind.failEffects.Insert(YALB_MkFail("BLEEDING", 0.30, 0.0, 0.0, 2));
|
||||
ind.failEffects.Insert(YALB_MkFail("HEALTH", 0.25, 20.0));
|
||||
f.BoxTypes.Insert(ind);
|
||||
|
||||
return f;
|
||||
}
|
||||
|
||||
static YALB_LootListsFile DefaultLootLists()
|
||||
{
|
||||
YALB_LootListsFile f = new YALB_LootListsFile();
|
||||
|
||||
YALB_LootList mil = new YALB_LootList(); mil.type = "military";
|
||||
mil.entries.Insert(YALB_MkLoot("AKM", 0.10, 1, 1));
|
||||
mil.entries.Insert(YALB_MkLoot("Mag_AKM_30Rnd", 0.40, 1, 3));
|
||||
mil.entries.Insert(YALB_MkLoot("PlateCarrierVest", 0.20, 1, 1));
|
||||
mil.entries.Insert(YALB_MkLoot("TacticalHelmet", 0.15, 1, 1));
|
||||
mil.entries.Insert(YALB_MkLoot("Ammo_762x39", 0.50, 1, 2));
|
||||
f.LootLists.Insert(mil);
|
||||
|
||||
YALB_LootList med = new YALB_LootList(); med.type = "medical";
|
||||
med.entries.Insert(YALB_MkLoot("Morphine", 0.30, 1, 1));
|
||||
med.entries.Insert(YALB_MkLoot("SalineBagIV", 0.25, 1, 2));
|
||||
med.entries.Insert(YALB_MkLoot("VitaminBottle", 0.50, 1, 3));
|
||||
med.entries.Insert(YALB_MkLoot("Bandage", 0.60, 2, 5));
|
||||
med.entries.Insert(YALB_MkLoot("TetracyclineAntibiotics", 0.20, 1, 2));
|
||||
f.LootLists.Insert(med);
|
||||
|
||||
YALB_LootList ind = new YALB_LootList(); ind.type = "industrial";
|
||||
ind.entries.Insert(YALB_MkLoot("WeldingMask", 0.15, 1, 1));
|
||||
ind.entries.Insert(YALB_MkLoot("Battery_Car", 0.30, 1, 1));
|
||||
ind.entries.Insert(YALB_MkLoot("SparkPlug", 0.40, 1, 2));
|
||||
ind.entries.Insert(YALB_MkLoot("MetalPipe", 0.50, 1, 4));
|
||||
ind.entries.Insert(YALB_MkLoot("DuctTape", 0.45, 1, 2));
|
||||
f.LootLists.Insert(ind);
|
||||
|
||||
return f;
|
||||
}
|
||||
|
||||
static YALB_SpawnLocationsFile DefaultSpawnLocations()
|
||||
{
|
||||
YALB_SpawnLocationsFile f = new YALB_SpawnLocationsFile();
|
||||
f.SpawnLocations.Insert(YALB_MkLoc("NWAF_Hangar1", 4985.3, 320.5, 2456.8, "military"));
|
||||
f.SpawnLocations.Insert(YALB_MkLoc("NWAF_Hangar2", 5102.7, 318.9, 2401.2, "military"));
|
||||
f.SpawnLocations.Insert(YALB_MkLoc("Tisy_Bunker_A", 1606.4, 280.1, 14383.9, "military"));
|
||||
f.SpawnLocations.Insert(YALB_MkLoc("Berezino_Hospital", 7245.1, 12.4, 13889.6, "medical"));
|
||||
f.SpawnLocations.Insert(YALB_MkLoc("Zelenogorsk_Clinic",2543.9, 8.2, 5234.7, "medical"));
|
||||
f.SpawnLocations.Insert(YALB_MkLoc("Tractor_Plant_1", 9821.2, 145.6, 8421.3, "industrial"));
|
||||
f.SpawnLocations.Insert(YALB_MkLoc("Tractor_Plant_2", 9890.5, 142.1, 8390.7, "industrial"));
|
||||
return f;
|
||||
}
|
||||
|
||||
static YALB_BoxTypeDef GetBoxTypeByClassName(string className)
|
||||
{
|
||||
if (!BoxTypes) return null;
|
||||
|
|
|
|||
|
|
@ -223,18 +223,17 @@ class YALootBoxesManager
|
|||
};
|
||||
|
||||
// ============================================================
|
||||
// MISSION INIT HOOK
|
||||
// Add this to your mission's init.c (server side only):
|
||||
// BOOTSTRAP
|
||||
// This manager is started automatically by the modded MissionServer
|
||||
// in scripts/5_Mission/YALootBoxes_MissionServer.c (OnInit). No
|
||||
// init.c edit is required.
|
||||
//
|
||||
// YALootBoxesManager g_YALootBoxesManager;
|
||||
// If you instead run a mission that does NOT go through MissionServer
|
||||
// and want to start it manually, the equivalent init.c hook is:
|
||||
//
|
||||
// void main()
|
||||
// if (GetGame().IsServer())
|
||||
// {
|
||||
// ...
|
||||
// if (GetGame().IsServer())
|
||||
// {
|
||||
// g_YALootBoxesManager = new YALootBoxesManager();
|
||||
// g_YALootBoxesManager.Init();
|
||||
// }
|
||||
// g_YALootBoxesManager = new YALootBoxesManager();
|
||||
// g_YALootBoxesManager.Init();
|
||||
// }
|
||||
// ============================================================
|
||||
23
scripts/5_Mission/YALootBoxes_MissionServer.c
Normal file
23
scripts/5_Mission/YALootBoxes_MissionServer.c
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
// ============================================================
|
||||
// YALootBoxes_MissionServer.c
|
||||
// Self-starts the lootbox manager on the server - no init.c edit
|
||||
// required. Lives in: scripts/5_Mission/
|
||||
//
|
||||
// MissionServer exists/instantiates on the dedicated server only,
|
||||
// so this whole bootstrap is inherently server-side. It augments
|
||||
// whatever mission the map ships (via 'modded') instead of
|
||||
// replacing it, so it coexists with other mission mods.
|
||||
// ============================================================
|
||||
|
||||
modded class MissionServer
|
||||
{
|
||||
ref YALootBoxesManager m_YALootBoxes_Manager;
|
||||
|
||||
override void OnInit()
|
||||
{
|
||||
super.OnInit();
|
||||
|
||||
m_YALootBoxes_Manager = new YALootBoxesManager();
|
||||
m_YALootBoxes_Manager.Init();
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue