diff --git a/overlay.toml b/overlay.toml
index 07d111a..18c7490 100644
--- a/overlay.toml
+++ b/overlay.toml
@@ -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
diff --git a/overlay/oxide/plugins/RunicGateway.cs b/overlay/oxide/plugins/RunicGateway.cs
index 26ef994..870a237 100644
--- a/overlay/oxide/plugins/RunicGateway.cs
+++ b/overlay/oxide/plugins/RunicGateway.cs
@@ -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
///
/// 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.
///
- private const int RunicNpcApiNeeded = 4;
+ private const int RunicNpcApiNeeded = 5;
private Plugin RunicNpcPlugin()
{
@@ -10824,6 +10826,65 @@ namespace Oxide.Plugins
}
/// The fields every NPC frame shares. owner null reads it from the NPC's placement, if any.
+ // ---- 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.
+
+ /// A boss spawned: an admin's placement, an event's step or another plugin.
+ private void OnRunicNpcBossSpawned(BasePlayer npc, string profile, string owner)
+ {
+ MarkHook("OnRunicNpcBossSpawned");
+
+ if (npc == null)
+ return;
+
+ Dictionary frame = Frame("npc.boss.spawned", "event");
+ PutNpc(frame, npc, profile, owner);
+ frame["maxHealth"] = (float)Math.Round(npc.MaxHealth(), 1);
+ EmitFrame(frame);
+ }
+
+ /// A boss entered a phase: phase counts from 1, at is its health fraction.
+ private void OnRunicNpcBossPhase(BasePlayer npc, string profile, string owner, int phase, float at)
+ {
+ MarkHook("OnRunicNpcBossPhase");
+
+ if (npc == null)
+ return;
+
+ Dictionary 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);
+ }
+
+ /// A boss died: the killing blow's player, and how many players hurt it.
+ private void OnRunicNpcBossDied(BasePlayer npc, string profile, string owner, HitInfo info, Dictionary contributors)
+ {
+ MarkHook("OnRunicNpcBossDied");
+
+ if (npc == null)
+ return;
+
+ Dictionary 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 frame, BasePlayer npc, string profile, string owner)
{
frame["netId"] = npc.net != null ? npc.net.ID.Value.ToString(System.Globalization.CultureInfo.InvariantCulture) : null;