diff --git a/overlay/oxide/plugins/RunicGateway.cs b/overlay/oxide/plugins/RunicGateway.cs index e83d082..2e60acf 100644 --- a/overlay/oxide/plugins/RunicGateway.cs +++ b/overlay/oxide/plugins/RunicGateway.cs @@ -13076,6 +13076,73 @@ namespace Oxide.Plugins return e == null || e.IsDestroyed || e.transform == null; } + /// + /// Main thread. RunicNPC's NPCs keyed by net id as text (a ledger entry's id), from + /// `RunicNpc_List`; empty when RunicNPC is not loaded or answers anything else. + /// + private Dictionary> RunicNpcsForMap() + { + var byId = new Dictionary>(); + Plugin npc = RunicNpcPlugin(); + + if (npc == null) + return byId; + + var listed = npc.Call("RunicNpc_List", (string)null) as List>; + + if (listed == null) + return byId; + + foreach (Dictionary d in listed) + { + object id; + + if (d != null && d.TryGetValue("netId", out id) && id != null) + byId[id.ToString()] = d; + } + + return byId; + } + + /// + /// A RunicNPC NPC's name, and for a boss `boss` and its health as a fraction. The health is + /// for the event page's progress line; the site's map does not show it (D303). + /// + private static void MapNpcFields(Dictionary row, Dictionary npc) + { + object value; + + if (npc.TryGetValue("name", out value) && value is string) + row["name"] = value; + + if (!npc.TryGetValue("boss", out value) || value == null) + return; + + row["boss"] = true; + + object health, max; + + if (npc.TryGetValue("health", out health) && npc.TryGetValue("maxHealth", out max)) + { + double h = Convert.ToDouble(health), m = Convert.ToDouble(max); + + if (m > 0) + row["health"] = Math.Round(Math.Max(0, Math.Min(1, h / m)), 3); + } + } + + /// Where one of RunicNPC's NPCs stands, from its `RunicNpc_List` row. + private static Dictionary MapNpcAt(Dictionary npc) + { + object value; + var p = npc.TryGetValue("position", out value) ? value as Dictionary : null; + + if (p == null) + return At(UnityEngine.Vector3.zero); + + return At(new UnityEngine.Vector3(Convert.ToSingle(p["x"]), 0f, Convert.ToSingle(p["z"]))); + } + private static Dictionary At(UnityEngine.Vector3 p) { return new Dictionary @@ -13140,6 +13207,10 @@ namespace Oxide.Plugins var events = new List(); + // RunicNPC's own NPCs by net id, for their names, bosses and a boss's adds (runicnpc + // stage 8, D302, D303). Empty without RunicNPC. + Dictionary> runic = RunicNpcsForMap(); + foreach (WorldEntry w in _world.Values) { Dictionary row; @@ -13162,6 +13233,17 @@ namespace Oxide.Plugins row = At(found.transform.position); row["prefab"] = w.Prefab; + + // The step that placed it, so a step's NPCs still standing can be counted. + row["key"] = w.Key; + + Dictionary npc; + + if (runic.TryGetValue(w.Id, out npc)) + { + MapNpcFields(row, npc); + runic.Remove(w.Id); + } } row["kind"] = w.Kind; @@ -13169,6 +13251,37 @@ namespace Oxide.Plugins events.Add(row); } + // What is left of RunicNPC's list was placed by no step. A boss's adds are owned by its + // run (`run:`) and join the events layer under it; any other boss is in the open + // world, as cargo or a helicopter is. A placement's ordinary NPCs are not on the map. + foreach (Dictionary npc in runic.Values) + { + object value; + string owner = npc.TryGetValue("owner", out value) ? value as string : null; + bool boss = npc.TryGetValue("boss", out value) && value != null; + Dictionary row; + + if (owner != null && owner.StartsWith("run:", StringComparison.Ordinal)) + { + row = MapNpcAt(npc); + MapNpcFields(row, npc); + row["kind"] = WorldKindNpc; + row["runId"] = owner.Substring(4); + row["add"] = true; + events.Add(row); + } + else if (boss) + { + row = MapNpcAt(npc); + row["kind"] = "boss"; + + if (npc.TryGetValue("name", out value) && value is string) + row["name"] = value; + + world.Add(row); + } + } + var players = new List(); foreach (BasePlayer p in BasePlayer.activePlayerList)