fix: animals and heals count toward titles; an event's zone enforces its flags (runicnpc 9d, D320–D322) #28
@@ -1875,7 +1875,22 @@ namespace Oxide.Plugins
|
|||||||
// two meanings of one word would be two answers to "how many NPCs did I kill".
|
// two meanings of one word would be two answers to "how many NPCs did I kill".
|
||||||
// `animalKills` counts it AGAIN, separately (§5.2): the public NPC column keeps its
|
// `animalKills` counts it AGAIN, separately (§5.2): the public NPC column keeps its
|
||||||
// meaning mid-wipe, and the site takes the human NPCs as the difference.
|
// meaning mid-wipe, and the site takes the human NPCs as the difference.
|
||||||
if (entity is BaseNpc)
|
//
|
||||||
|
// Rust's own animals are now `Rust.Ai.Gen2.BaseNPC2` — stag, boar, wolf, bears, big cats,
|
||||||
|
// crocodile, farm animals, critters, sea life — and not `BaseNpc`, which is all this
|
||||||
|
// tested until the RunicNPC 9d walk found no animal kill had counted at all. `BaseNpc`
|
||||||
|
// stays for whatever still derives from it. `ScientistNPC2` is the one person among
|
||||||
|
// them, so it is a human NPC kill, not an animal.
|
||||||
|
if (entity is Rust.Ai.Gen2.ScientistNPC2)
|
||||||
|
{
|
||||||
|
Tally tally = TallyFor(attacker);
|
||||||
|
tally.NpcKills++;
|
||||||
|
tally.Killed(info, entity, false);
|
||||||
|
CreditKill(attacker, false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entity is BaseNpc || entity is Rust.Ai.Gen2.BaseNPC2)
|
||||||
{
|
{
|
||||||
Tally tally = TallyFor(attacker);
|
Tally tally = TallyFor(attacker);
|
||||||
tally.NpcKills++;
|
tally.NpcKills++;
|
||||||
@@ -2065,55 +2080,56 @@ namespace Oxide.Plugins
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// A syringe on a downed player raises `OnPlayerRevive` and then `OnHealingItemUse` for the
|
// A syringe on a downed player raises `OnHealingItemUse` and then, from inside the same
|
||||||
// same use; one act of help is one heal. The revive is remembered for the frame it happened in.
|
// `GiveEffectsTo`, `OnPlayerRevive`; one act of help is one heal. The last one counted is
|
||||||
private ulong _reviveHealer;
|
// remembered for the frame it happened in, and the other hook skips the same pair.
|
||||||
private ulong _reviveTarget;
|
private ulong _helpHealer;
|
||||||
private int _reviveFrame = -1;
|
private ulong _helpTarget;
|
||||||
|
private int _helpFrame = -1;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>Counts one act of help unless it is yourself, an NPC, or the other hook already did this frame.</summary>
|
||||||
/// Void: a non-null return refuses the heal. <paramref name="target"/> is what is healed;
|
private void CountHelp(BasePlayer healer, BasePlayer player)
|
||||||
/// the tool's owner is who heals. Healing yourself is not a heal (D174).
|
|
||||||
///
|
|
||||||
/// <para>
|
|
||||||
/// The target is taken as <c>object</c>: the game's `GiveEffectsTo` now takes an
|
|
||||||
/// `IMedicalToolTarget`, which is not always a player, and a parameter typed
|
|
||||||
/// `BasePlayer` would simply never be called for the hook's current shape. Only a
|
|
||||||
/// player healed counts.
|
|
||||||
/// </para>
|
|
||||||
/// </summary>
|
|
||||||
private void OnHealingItemUse(MedicalTool tool, object target)
|
|
||||||
{
|
{
|
||||||
MarkHook("OnHealingItemUse");
|
|
||||||
|
|
||||||
BasePlayer player = target as BasePlayer;
|
|
||||||
BasePlayer healer = tool != null ? tool.GetOwnerPlayer() : null;
|
|
||||||
|
|
||||||
if (healer == null || healer.IsNpc || player == null || player.IsNpc
|
if (healer == null || healer.IsNpc || player == null || player.IsNpc
|
||||||
|| (ulong)healer.userID == (ulong)player.userID)
|
|| (ulong)healer.userID == (ulong)player.userID)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (_reviveFrame == UnityEngine.Time.frameCount
|
int frame = UnityEngine.Time.frameCount;
|
||||||
&& _reviveHealer == (ulong)healer.userID && _reviveTarget == (ulong)player.userID)
|
|
||||||
|
if (_helpFrame == frame && _helpHealer == (ulong)healer.userID && _helpTarget == (ulong)player.userID)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
_helpHealer = healer.userID;
|
||||||
|
_helpTarget = player.userID;
|
||||||
|
_helpFrame = frame;
|
||||||
|
|
||||||
TallyFor(healer).Healed++;
|
TallyFor(healer).Healed++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Void: a non-null return refuses the heal. Healing yourself is not a heal (D174).
|
||||||
|
///
|
||||||
|
/// <para>
|
||||||
|
/// Rust's `MedicalTool.GiveEffectsTo(fromPlayer, toTarget)` raises it as
|
||||||
|
/// <c>(tool, fromPlayer, toTarget)</c>. Until the RunicNPC 9d walk this took two
|
||||||
|
/// parameters, so the second — the healer — arrived as the target, every heal looked like
|
||||||
|
/// healing yourself, and none counted. The target is <c>object</c>: `IMedicalToolTarget`
|
||||||
|
/// is not always a player, and only a player healed counts.
|
||||||
|
/// </para>
|
||||||
|
/// </summary>
|
||||||
|
private void OnHealingItemUse(MedicalTool tool, BasePlayer fromPlayer, object target)
|
||||||
|
{
|
||||||
|
MarkHook("OnHealingItemUse");
|
||||||
|
|
||||||
|
CountHelp(fromPlayer ?? (tool != null ? tool.GetOwnerPlayer() : null), target as BasePlayer);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>Void: a non-null return refuses the revive. Reviving yourself is not possible, and is not counted.</summary>
|
/// <summary>Void: a non-null return refuses the revive. Reviving yourself is not possible, and is not counted.</summary>
|
||||||
private void OnPlayerRevive(BasePlayer reviver, BasePlayer player)
|
private void OnPlayerRevive(BasePlayer reviver, BasePlayer player)
|
||||||
{
|
{
|
||||||
MarkHook("OnPlayerRevive");
|
MarkHook("OnPlayerRevive");
|
||||||
|
|
||||||
if (reviver == null || reviver.IsNpc || player == null || player.IsNpc
|
CountHelp(reviver, player);
|
||||||
|| (ulong)reviver.userID == (ulong)player.userID)
|
|
||||||
return;
|
|
||||||
|
|
||||||
_reviveHealer = reviver.userID;
|
|
||||||
_reviveTarget = player.userID;
|
|
||||||
_reviveFrame = UnityEngine.Time.frameCount;
|
|
||||||
|
|
||||||
TallyFor(reviver).Healed++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>No return behaviour. One rocket leaving a launcher.</summary>
|
/// <summary>No return behaviour. One rocket leaving a launcher.</summary>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Runic Gateway Zones — a helper for ZoneManager, shipped beside the Runic Gateway bridge.
|
// Runic Gateway Zones — a helper for ZoneManager, shipped beside the Runic Gateway bridge.
|
||||||
// Copyright (C) Runic Gateway contributors. GPL-3.0-or-later.
|
// Copyright (C) Runic Gateway contributors. GPL-3.0-or-later.
|
||||||
//
|
//
|
||||||
// ONE JOB (docs/modules/rust/PLAN_FIXES.md F12, D168, D181, D182):
|
// ITS FIRST JOB (docs/modules/rust/PLAN_FIXES.md F12, D168, D181, D182):
|
||||||
//
|
//
|
||||||
// ZoneManager counts a player as inside a zone only when the zone's trigger fires on ENTRY. A
|
// ZoneManager counts a player as inside a zone only when the zone's trigger fires on ENTRY. A
|
||||||
// zone created, updated or loaded around somebody who is already standing there never fires it,
|
// zone created, updated or loaded around somebody who is already standing there never fires it,
|
||||||
@@ -27,6 +27,17 @@
|
|||||||
// * **It fixes every zone on the server**, not only the bridge's. That is deliberate: the fault is
|
// * **It fixes every zone on the server**, not only the bridge's. That is deliberate: the fault is
|
||||||
// ZoneManager's, and it is the same fault for an arena another plugin made.
|
// ZoneManager's, and it is the same fault for an arena another plugin made.
|
||||||
//
|
//
|
||||||
|
// A SECOND JOB (RunicNPC stage 9d walk, the org lead's call, 2026-10-07):
|
||||||
|
//
|
||||||
|
// ZoneManager subscribes the hooks that enforce its flags (`OnEntityTakeDamage` for PvpGod and the
|
||||||
|
// other damage flags, `OnEntityBuilt` for NoBuild, and so on) only for the flags some zone had when
|
||||||
|
// ZoneManager loaded, or when a flag is toggled by command. A zone made later through its API —
|
||||||
|
// which is every zone an event opens — never re-subscribes them, so on a server with no such zone
|
||||||
|
// already, an event's PvpGod or NoBuild zone enforces nothing: the 9d walk shot a player dead and
|
||||||
|
// built a foundation inside one. The same postfix that sees every zone made therefore also has
|
||||||
|
// ZoneManager run its own `UpdateHookSubscriptions` on the next tick. ZoneManager decides what to
|
||||||
|
// subscribe; this only asks it to look.
|
||||||
|
//
|
||||||
// It is optional. The bridge never depends on it to load: without it — deleted, or loaded but
|
// It is optional. The bridge never depends on it to load: without it — deleted, or loaded but
|
||||||
// unable to find what it patches after a ZoneManager update — the bridge scores the zones it made
|
// unable to find what it patches after a ZoneManager update — the bridge scores the zones it made
|
||||||
// by `IsPositionInZone` instead (D182), and reports this helper's state at hello so the site can
|
// by `IsPositionInZone` instead (D182), and reports this helper's state at hello so the site can
|
||||||
@@ -41,8 +52,8 @@ using UnityEngine;
|
|||||||
|
|
||||||
namespace Oxide.Plugins
|
namespace Oxide.Plugins
|
||||||
{
|
{
|
||||||
[Info("Runic Gateway Zones", "Runic Gateway", "0.1.0")]
|
[Info("Runic Gateway Zones", "Runic Gateway", "0.2.0")]
|
||||||
[Description("Helper for Runic Gateway: ZoneManager counts players already inside a zone when it is created, updated or loaded.")]
|
[Description("Helper for Runic Gateway: ZoneManager counts players already inside a zone when it is created, updated or loaded, and enforces a new zone's flags.")]
|
||||||
public class RunicGatewayZones : RustPlugin
|
public class RunicGatewayZones : RustPlugin
|
||||||
{
|
{
|
||||||
private const string HarmonyId = "runicgateway.zones";
|
private const string HarmonyId = "runicgateway.zones";
|
||||||
@@ -61,6 +72,9 @@ namespace Oxide.Plugins
|
|||||||
private FieldInfo _definition;
|
private FieldInfo _definition;
|
||||||
private PropertyInfo _id;
|
private PropertyInfo _id;
|
||||||
|
|
||||||
|
/// <summary>ZoneManager's private <c>UpdateHookSubscriptions</c>, or null when this ZoneManager has none (the second job is then off).</summary>
|
||||||
|
private MethodInfo _updateHooks;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// What the bridge reads through <see cref="State"/>: <c>patched</c>, <c>unsupported</c>
|
/// What the bridge reads through <see cref="State"/>: <c>patched</c>, <c>unsupported</c>
|
||||||
/// (ZoneManager is here but not the shape this helper patches), or <c>no-zonemanager</c>.
|
/// (ZoneManager is here but not the shape this helper patches), or <c>no-zonemanager</c>.
|
||||||
@@ -134,6 +148,7 @@ namespace Oxide.Plugins
|
|||||||
MethodInfo initialize = zoneType?.GetMethod("InitializeZone", any);
|
MethodInfo initialize = zoneType?.GetMethod("InitializeZone", any);
|
||||||
|
|
||||||
_getZone = zmType.GetMethod("GetZoneByID", any, null, new[] { typeof(string) }, null);
|
_getZone = zmType.GetMethod("GetZoneByID", any, null, new[] { typeof(string) }, null);
|
||||||
|
_updateHooks = zmType.GetMethod("UpdateHookSubscriptions", any, null, Type.EmptyTypes, null);
|
||||||
_definition = zoneType?.GetField("definition", any);
|
_definition = zoneType?.GetField("definition", any);
|
||||||
_id = _definition?.FieldType.GetProperty("Id", any);
|
_id = _definition?.FieldType.GetProperty("Id", any);
|
||||||
_enter = zoneType == null
|
_enter = zoneType == null
|
||||||
@@ -169,10 +184,14 @@ namespace Oxide.Plugins
|
|||||||
|
|
||||||
_state = "patched";
|
_state = "patched";
|
||||||
_reason = null;
|
_reason = null;
|
||||||
Puts($"patched ZoneManager {_zoneManager.Version}: a zone takes in the players already inside it");
|
Puts($"patched ZoneManager {_zoneManager.Version}: a zone takes in the players already inside it" +
|
||||||
|
(_updateHooks != null ? ", and a new zone's flags are enforced" : ""));
|
||||||
|
if (_updateHooks == null)
|
||||||
|
PrintWarning($"this ZoneManager ({_zoneManager.Version}) has no UpdateHookSubscriptions: a zone made after it loaded may not enforce its flags until ZoneManager reloads");
|
||||||
|
|
||||||
// The zones ZoneManager loaded before this helper did were initialised unwatched —
|
// The zones ZoneManager loaded before this helper did were initialised unwatched —
|
||||||
// which, at a server start, is all of them. Sweep them once now.
|
// which, at a server start, is all of them. Sweep them once now.
|
||||||
|
RefreshHooks();
|
||||||
SweepAll();
|
SweepAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -258,10 +277,32 @@ namespace Oxide.Plugins
|
|||||||
var batch = new List<KeyValuePair<string, object>>(_pending);
|
var batch = new List<KeyValuePair<string, object>>(_pending);
|
||||||
_pending.Clear();
|
_pending.Clear();
|
||||||
|
|
||||||
|
// The second job first: the hooks that enforce the new zones' flags, then the players.
|
||||||
|
RefreshHooks();
|
||||||
|
|
||||||
foreach (KeyValuePair<string, object> entry in batch)
|
foreach (KeyValuePair<string, object> entry in batch)
|
||||||
Sweep(entry.Key, entry.Value);
|
Sweep(entry.Key, entry.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Main thread. Has ZoneManager subscribe the hooks its zones' flags now need. ZoneManager
|
||||||
|
/// only adds subscriptions here, and subscribing a hook twice is a no-op.
|
||||||
|
/// </summary>
|
||||||
|
private void RefreshHooks()
|
||||||
|
{
|
||||||
|
if (_updateHooks == null || _zoneManager == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_updateHooks.Invoke(_zoneManager, null);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
PrintWarning($"could not have ZoneManager refresh its hooks: {(ex.InnerException ?? ex).Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void SweepAll()
|
private void SweepAll()
|
||||||
{
|
{
|
||||||
var ids = _zoneManager.Call("GetZoneIDs") as string[];
|
var ids = _zoneManager.Call("GetZoneIDs") as string[];
|
||||||
@@ -328,7 +369,8 @@ namespace Oxide.Plugins
|
|||||||
if (arg.Connection != null && !arg.IsAdmin)
|
if (arg.Connection != null && !arg.IsAdmin)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
arg.ReplyWith($"Runic Gateway Zones {Version}: {_state}{(_reason != null ? $" ({_reason})" : "")}");
|
arg.ReplyWith($"Runic Gateway Zones {Version}: {_state}{(_reason != null ? $" ({_reason})" : "")}" +
|
||||||
|
$"; new zones' flags {(_updateHooks != null ? "enforced" : "NOT refreshed (no UpdateHookSubscriptions)")}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user