feat(bridge): map.live names RunicNPC's NPCs, marks bosses and adds (runicnpc stage 8) #26

Merged
whitlocktech merged 1 commits from feat/runicnpc-stage8 into edge 2026-10-06 08:32:33 -05:00

View File

@@ -13076,6 +13076,73 @@ namespace Oxide.Plugins
return e == null || e.IsDestroyed || e.transform == null;
}
/// <summary>
/// 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.
/// </summary>
private Dictionary<string, Dictionary<string, object>> RunicNpcsForMap()
{
var byId = new Dictionary<string, Dictionary<string, object>>();
Plugin npc = RunicNpcPlugin();
if (npc == null)
return byId;
var listed = npc.Call("RunicNpc_List", (string)null) as List<Dictionary<string, object>>;
if (listed == null)
return byId;
foreach (Dictionary<string, object> d in listed)
{
object id;
if (d != null && d.TryGetValue("netId", out id) && id != null)
byId[id.ToString()] = d;
}
return byId;
}
/// <summary>
/// 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).
/// </summary>
private static void MapNpcFields(Dictionary<string, object> row, Dictionary<string, object> 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);
}
}
/// <summary>Where one of RunicNPC's NPCs stands, from its `RunicNpc_List` row.</summary>
private static Dictionary<string, object> MapNpcAt(Dictionary<string, object> npc)
{
object value;
var p = npc.TryGetValue("position", out value) ? value as Dictionary<string, object> : 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<string, object> At(UnityEngine.Vector3 p)
{
return new Dictionary<string, object>
@@ -13140,6 +13207,10 @@ namespace Oxide.Plugins
var events = new List<object>();
// 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<string, Dictionary<string, object>> runic = RunicNpcsForMap();
foreach (WorldEntry w in _world.Values)
{
Dictionary<string, object> 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<string, object> 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:<id>`) 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<string, object> 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<string, object> 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<object>();
foreach (BasePlayer p in BasePlayer.activePlayerList)