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;