diff --git a/plugin/RunicNPC.cs b/plugin/RunicNPC.cs index f11a9be..12a3701 100644 --- a/plugin/RunicNPC.cs +++ b/plugin/RunicNPC.cs @@ -3212,6 +3212,10 @@ namespace Oxide.Plugins Index(npc, false); if (npc.IsBoss) 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) { @@ -3542,6 +3546,12 @@ namespace Oxide.Plugins /// The bosses alive now. private readonly HashSet _bosses = new HashSet(); + /// + /// 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. + /// + private readonly Dictionary> _addsOf = new Dictionary>(); + /// Whose bar each player sees, by Steam id: one boss at a time, the nearest that qualifies (D274). private readonly Dictionary _barShown = new Dictionary(); @@ -3692,6 +3702,7 @@ namespace Oxide.Plugins Vector3 centre = boss.transform.position; string owner = boss.Owner; + ulong bossId = boss.NetId; var attackers = boss.Contributors.Keys.ToList(); float turn = UnityEngine.Random.value * Mathf.PI * 2f; int i = 0; @@ -3712,12 +3723,18 @@ namespace Oxide.Plugins if (add == null) PrintWarning($"{boss.RnName} ({boss.ProfileName}): add {i + 1} of {adds.Count} ('{adds.Profile}') not placed: {error}"); else + { + HashSet mine; + if (!_addsOf.TryGetValue(bossId, out mine)) + _addsOf[bossId] = mine = new HashSet(); + mine.Add(add); foreach (ulong id in attackers) { BasePlayer who = BasePlayer.FindByID(id); if (who != null && who.IsAlive() && Vector3.Distance(who.transform.position, add.transform.position) <= 100f) Learn(add, who, true); } + } i++; if (i < adds.Count) @@ -4251,10 +4268,25 @@ namespace Oxide.Plugins return npc; } + /// + /// 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. + /// private int RunicNpc_Despawn(ulong netId) { + var gone = new List(); RunicNpcPlayer npc; - return _npcs.TryGetValue(netId, out npc) ? Despawn(new[] { npc }) : 0; + if (_npcs.TryGetValue(netId, out npc)) + gone.Add(npc); + HashSet 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)); diff --git a/tools/RunicNpcTest.cs b/tools/RunicNpcTest.cs index b2d36a7..1c3972a 100644 --- a/tools/RunicNpcTest.cs +++ b/tools/RunicNpcTest.cs @@ -2218,12 +2218,17 @@ namespace Oxide.Plugins if (second != null) Api("RunicNpc_Despawn", Id(second)); // 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)); yield return new WaitForSeconds(1f); 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)}"); 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)"); + // 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("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"), ""); // ---- the examples (D280, D284, D289) ----