fix(bridge): Rust's animals and heals count toward titles again (runicnpc 9d)

- Animal kills: Rust's animals are now Rust.Ai.Gen2.BaseNPC2 (stag, boar, wolf,
  bears, big cats, crocodile, farm animals, critters, sea life), not BaseNpc, so
  no animal kill counted as an animal or an NPC kill. They count as both again;
  ScientistNPC2, the one person among them, counts as a human NPC kill.
- Heals: OnHealingItemUse is (tool, fromPlayer, toTarget). The bridge took two
  parameters, received the healer as the target, and dropped every heal as
  healing yourself. It now reads the target, and one act of help (a syringe
  revive raises both hooks, the heal first) counts once whichever hook comes
  first.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
This commit is contained in:
2026-10-07 13:41:57 -05:00
parent 912b115e83
commit e5f04e0c0c

View File

@@ -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>