feat(bridge): boss frames for the site's public triggers (runicnpc stage 6) #24

Merged
whitlocktech merged 1 commits from feat/runicnpc-stage6 into edge 2026-10-05 19:45:34 -05:00
2 changed files with 66 additions and 5 deletions

View File

@@ -80,7 +80,7 @@ requires_plugins = ["Kits", "ZoneManager"]
# ── The RunicNPC API the bridge needs ───────────────────────────────────────
#
# RunicNPC (docs/runicnpc/PLAN.md stages 4 and 5): the bridge's npc.* commands call
# RunicNPC (docs/runicnpc/PLAN.md stages 4 to 6): the bridge's npc.* commands call
# RunicNPC's API and need at least this version of it. It MUST equal
# `RunicNpcApiNeeded` in RunicGateway.cs; `node scripts/checkPlugin.js` holds the
# two equal. The release copies it into manifest.json, and the installer's
@@ -88,4 +88,4 @@ requires_plugins = ["Kits", "ZoneManager"]
#
# RunicNPC is optional to the bridge until its stage 9: without it every npc.*
# command answers `runicnpc-missing`, and events place Rust's own scientists.
runicnpc_api = 4
runicnpc_api = 5

View File

@@ -249,7 +249,8 @@ namespace Oxide.Plugins
"OnHealingItemUse", "OnPlayerRevive", "OnRocketLaunched",
"OnExplosiveThrown", "OnExplosiveDropped", "OnMissionSucceeded",
"OnEnterZone", "OnExitZone", "OnRgDomesReady",
"OnRunicNpcDied", "OnRunicNpcHealth", "OnRunicNpcPlacementChanged"
"OnRunicNpcDied", "OnRunicNpcHealth", "OnRunicNpcPlacementChanged",
"OnRunicNpcBossSpawned", "OnRunicNpcBossPhase", "OnRunicNpcBossDied"
};
private void Init()
@@ -10369,9 +10370,10 @@ namespace Oxide.Plugins
/// <summary>
/// The RunicNPC API these commands call: 3 added placements from a website (D249), 4 the
/// faction table and an event's escort, ally and tether (runicnpc stage 5).
/// faction table and an event's escort, ally and tether (runicnpc stage 5), 5 bosses and
/// passive NPCs (runicnpc stage 6): a profile's `boss` and `use`, and the boss hooks.
/// </summary>
private const int RunicNpcApiNeeded = 4;
private const int RunicNpcApiNeeded = 5;
private Plugin RunicNpcPlugin()
{
@@ -10824,6 +10826,65 @@ namespace Oxide.Plugins
}
/// <summary>The fields every NPC frame shares. <c>owner</c> null reads it from the NPC's placement, if any.</summary>
// ---- bosses (runicnpc stage 6: D277) ----
//
// Three frames for the site's public boss triggers: spawned, a phase, killed. A boss's death
// also raises npc.died as every NPC's does; npc.boss.died adds the boss's own facts, and names
// no Steam id but the killer's, because the triggers it feeds are public.
/// <summary>A boss spawned: an admin's placement, an event's step or another plugin.</summary>
private void OnRunicNpcBossSpawned(BasePlayer npc, string profile, string owner)
{
MarkHook("OnRunicNpcBossSpawned");
if (npc == null)
return;
Dictionary<string, object> frame = Frame("npc.boss.spawned", "event");
PutNpc(frame, npc, profile, owner);
frame["maxHealth"] = (float)Math.Round(npc.MaxHealth(), 1);
EmitFrame(frame);
}
/// <summary>A boss entered a phase: <c>phase</c> counts from 1, <c>at</c> is its health fraction.</summary>
private void OnRunicNpcBossPhase(BasePlayer npc, string profile, string owner, int phase, float at)
{
MarkHook("OnRunicNpcBossPhase");
if (npc == null)
return;
Dictionary<string, object> frame = Frame("npc.boss.phase", "event");
PutNpc(frame, npc, profile, owner);
frame["phase"] = phase;
frame["at"] = at;
frame["health"] = (float)Math.Round(npc.health, 1);
frame["maxHealth"] = (float)Math.Round(npc.MaxHealth(), 1);
EmitFrame(frame);
}
/// <summary>A boss died: the killing blow's player, and how many players hurt it.</summary>
private void OnRunicNpcBossDied(BasePlayer npc, string profile, string owner, HitInfo info, Dictionary<ulong, float> contributors)
{
MarkHook("OnRunicNpcBossDied");
if (npc == null)
return;
Dictionary<string, object> frame = Frame("npc.boss.died", "event");
PutNpc(frame, npc, profile, owner);
BasePlayer killer = info != null ? info.InitiatorPlayer : null;
if (killer != null && !killer.IsNpc)
{
frame["killerId"] = ((ulong)killer.userID).ToString();
frame["killerName"] = killer.displayName;
}
frame["damagers"] = contributors != null ? contributors.Count : 0;
EmitFrame(frame);
}
private void PutNpc(Dictionary<string, object> frame, BasePlayer npc, string profile, string owner)
{
frame["netId"] = npc.net != null ? npc.net.ID.Value.ToString(System.Globalization.CultureInfo.InvariantCulture) : null;