fix: an event's teardown removes a boss's adds (D287)
All checks were successful
PR Checks / plugin-checks (pull_request) Successful in -1m33s
All checks were successful
PR Checks / plugin-checks (pull_request) Successful in -1m33s
The walk found an event run's boss adds outliving the run: teardown removes what a step placed by net id, and the adds RunicNPC spawned for a boss's phase are in no step's ledger. RunicNPC now keeps each boss's live adds by the boss's net id, after the boss dies too, and RunicNpc_Despawn(<boss net id>) removes them with it, which is the call the bridge's teardown makes. An add leaves the set when it dies. Walked on the Oxide rig: event 36, run 65 placed the boss, its phase summoned two adds owned by run:65, the boss was killed, the event advanced on rust.boss.killed, and the run's cleanup removed both adds. New harness check s6.boss.despawnTakesAdds. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
This commit is contained in:
@@ -3212,6 +3212,10 @@ namespace Oxide.Plugins
|
|||||||
Index(npc, false);
|
Index(npc, false);
|
||||||
if (npc.IsBoss)
|
if (npc.IsBoss)
|
||||||
ClearBar(npc);
|
ClearBar(npc);
|
||||||
|
if (_addsOf.Count > 0)
|
||||||
|
foreach (ulong boss in _addsOf.Keys.ToList())
|
||||||
|
if (_addsOf[boss].Remove(npc) && _addsOf[boss].Count == 0)
|
||||||
|
_addsOf.Remove(boss);
|
||||||
|
|
||||||
if (npc.PlacementId != null)
|
if (npc.PlacementId != null)
|
||||||
{
|
{
|
||||||
@@ -3542,6 +3546,12 @@ namespace Oxide.Plugins
|
|||||||
/// <summary>The bosses alive now.</summary>
|
/// <summary>The bosses alive now.</summary>
|
||||||
private readonly HashSet<RunicNpcPlayer> _bosses = new HashSet<RunicNpcPlayer>();
|
private readonly HashSet<RunicNpcPlayer> _bosses = new HashSet<RunicNpcPlayer>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Each boss's live adds, by the boss's net id, kept after the boss dies (D287) so that despawning
|
||||||
|
/// the boss's id removes them too. An add leaves its set when it dies or despawns.
|
||||||
|
/// </summary>
|
||||||
|
private readonly Dictionary<ulong, HashSet<RunicNpcPlayer>> _addsOf = new Dictionary<ulong, HashSet<RunicNpcPlayer>>();
|
||||||
|
|
||||||
/// <summary>Whose bar each player sees, by Steam id: one boss at a time, the nearest that qualifies (D274).</summary>
|
/// <summary>Whose bar each player sees, by Steam id: one boss at a time, the nearest that qualifies (D274).</summary>
|
||||||
private readonly Dictionary<ulong, RunicNpcPlayer> _barShown = new Dictionary<ulong, RunicNpcPlayer>();
|
private readonly Dictionary<ulong, RunicNpcPlayer> _barShown = new Dictionary<ulong, RunicNpcPlayer>();
|
||||||
|
|
||||||
@@ -3692,6 +3702,7 @@ namespace Oxide.Plugins
|
|||||||
|
|
||||||
Vector3 centre = boss.transform.position;
|
Vector3 centre = boss.transform.position;
|
||||||
string owner = boss.Owner;
|
string owner = boss.Owner;
|
||||||
|
ulong bossId = boss.NetId;
|
||||||
var attackers = boss.Contributors.Keys.ToList();
|
var attackers = boss.Contributors.Keys.ToList();
|
||||||
float turn = UnityEngine.Random.value * Mathf.PI * 2f;
|
float turn = UnityEngine.Random.value * Mathf.PI * 2f;
|
||||||
int i = 0;
|
int i = 0;
|
||||||
@@ -3712,12 +3723,18 @@ namespace Oxide.Plugins
|
|||||||
if (add == null)
|
if (add == null)
|
||||||
PrintWarning($"{boss.RnName} ({boss.ProfileName}): add {i + 1} of {adds.Count} ('{adds.Profile}') not placed: {error}");
|
PrintWarning($"{boss.RnName} ({boss.ProfileName}): add {i + 1} of {adds.Count} ('{adds.Profile}') not placed: {error}");
|
||||||
else
|
else
|
||||||
|
{
|
||||||
|
HashSet<RunicNpcPlayer> mine;
|
||||||
|
if (!_addsOf.TryGetValue(bossId, out mine))
|
||||||
|
_addsOf[bossId] = mine = new HashSet<RunicNpcPlayer>();
|
||||||
|
mine.Add(add);
|
||||||
foreach (ulong id in attackers)
|
foreach (ulong id in attackers)
|
||||||
{
|
{
|
||||||
BasePlayer who = BasePlayer.FindByID(id);
|
BasePlayer who = BasePlayer.FindByID(id);
|
||||||
if (who != null && who.IsAlive() && Vector3.Distance(who.transform.position, add.transform.position) <= 100f)
|
if (who != null && who.IsAlive() && Vector3.Distance(who.transform.position, add.transform.position) <= 100f)
|
||||||
Learn(add, who, true);
|
Learn(add, who, true);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
i++;
|
i++;
|
||||||
if (i < adds.Count)
|
if (i < adds.Count)
|
||||||
@@ -4251,10 +4268,25 @@ namespace Oxide.Plugins
|
|||||||
return npc;
|
return npc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes one NPC, and for a boss the adds it summoned, alive or not (D287): an event's teardown
|
||||||
|
/// removes what its step placed by net id, and a boss's adds are in no step's ledger. The boss may
|
||||||
|
/// already be dead; its adds are still found by its net id.
|
||||||
|
/// </summary>
|
||||||
private int RunicNpc_Despawn(ulong netId)
|
private int RunicNpc_Despawn(ulong netId)
|
||||||
{
|
{
|
||||||
|
var gone = new List<RunicNpcPlayer>();
|
||||||
RunicNpcPlayer npc;
|
RunicNpcPlayer npc;
|
||||||
return _npcs.TryGetValue(netId, out npc) ? Despawn(new[] { npc }) : 0;
|
if (_npcs.TryGetValue(netId, out npc))
|
||||||
|
gone.Add(npc);
|
||||||
|
HashSet<RunicNpcPlayer> adds;
|
||||||
|
if (_addsOf.TryGetValue(netId, out adds))
|
||||||
|
{
|
||||||
|
gone.AddRange(adds);
|
||||||
|
_addsOf.Remove(netId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Despawn(gone);
|
||||||
}
|
}
|
||||||
|
|
||||||
private int RunicNpc_DespawnOwner(string owner) => owner == null ? 0 : Despawn(_npcs.Values.Where(n => n.Owner == owner));
|
private int RunicNpc_DespawnOwner(string owner) => owner == null ? 0 : Despawn(_npcs.Values.Where(n => n.Owner == owner));
|
||||||
|
|||||||
@@ -2218,12 +2218,17 @@ namespace Oxide.Plugins
|
|||||||
if (second != null) Api<int>("RunicNpc_Despawn", Id(second));
|
if (second != null) Api<int>("RunicNpc_Despawn", Id(second));
|
||||||
|
|
||||||
// The kill: the death hook with every player who hurt it; the adds fight on (D287).
|
// The kill: the death hook with every player who hurt it; the adds fight on (D287).
|
||||||
|
ulong bossId = Id(boss);
|
||||||
boss.Hurt(new HitInfo(shooter, boss, Rust.DamageType.Bullet, 5000f));
|
boss.Hurt(new HitInfo(shooter, boss, Rust.DamageType.Bullet, 5000f));
|
||||||
yield return new WaitForSeconds(1f);
|
yield return new WaitForSeconds(1f);
|
||||||
Check("s6.boss.died", _bossEvents.Contains("died:t_boss") && _bossContributors.ContainsKey(StandInId + 64) && _bossContributors[StandInId + 64] > 990f,
|
Check("s6.boss.died", _bossEvents.Contains("died:t_boss") && _bossContributors.ContainsKey(StandInId + 64) && _bossContributors[StandInId + 64] > 990f,
|
||||||
$"events {string.Join(", ", _bossEvents.ToArray())}, contributors {JsonConvert.SerializeObject(_bossContributors)}");
|
$"events {string.Join(", ", _bossEvents.ToArray())}, contributors {JsonConvert.SerializeObject(_bossContributors)}");
|
||||||
int addsLeft = List(Owner).Count(d => (string)d["profile"] == "t_roam");
|
int addsLeft = List(Owner).Count(d => (string)d["profile"] == "t_roam");
|
||||||
Check("s6.boss.addsFightOn", addsLeft == 2, $"{addsLeft} add(s) alive after the boss died (D287)");
|
Check("s6.boss.addsFightOn", addsLeft == 2, $"{addsLeft} add(s) alive after the boss died (D287)");
|
||||||
|
// An event's teardown despawns what its step placed, by net id: the dead boss's id takes its adds with it.
|
||||||
|
int removed = Api<int>("RunicNpc_Despawn", bossId);
|
||||||
|
int addsAfter = List(Owner).Count(d => (string)d["profile"] == "t_roam");
|
||||||
|
Check("s6.boss.despawnTakesAdds", removed == 2 && addsAfter == 0, $"RunicNpc_Despawn(the dead boss's id) removed {removed}; adds left {addsAfter}");
|
||||||
Check("s6.boss.unlisted", List(Owner).All(d => (string)d["profile"] != "t_boss"), "");
|
Check("s6.boss.unlisted", List(Owner).All(d => (string)d["profile"] != "t_boss"), "");
|
||||||
|
|
||||||
// ---- the examples (D280, D284, D289) ----
|
// ---- the examples (D280, D284, D289) ----
|
||||||
|
|||||||
Reference in New Issue
Block a user