From 81d54bba57cba6aec7675da371567ea6e71ad322 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?ZX=E5=A4=8F=E5=A4=9C=E4=B9=8B=E9=A3=8E?= Date: Thu, 31 Oct 2024 22:08:39 +0800 Subject: [PATCH] fix: stop mobs from targeting NPCs when they are no longer targetable (#3171) --- .../java/net/citizensnpcs/EventListen.java | 42 +++++++++++---- .../citizensnpcs/commands/NPCCommands.java | 7 +++ .../npc/CitizensTraitFactory.java | 2 + .../trait/TrackTargetedByTrait.java | 54 +++++++++++++++++++ 4 files changed, 96 insertions(+), 9 deletions(-) create mode 100644 main/src/main/java/net/citizensnpcs/trait/TrackTargetedByTrait.java diff --git a/main/src/main/java/net/citizensnpcs/EventListen.java b/main/src/main/java/net/citizensnpcs/EventListen.java index 7f26a1ca3..a445730e3 100644 --- a/main/src/main/java/net/citizensnpcs/EventListen.java +++ b/main/src/main/java/net/citizensnpcs/EventListen.java @@ -4,6 +4,7 @@ import java.lang.reflect.Method; import java.util.List; import java.util.Objects; +import net.citizensnpcs.trait.TrackTargetedByTrait; import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.Material; @@ -12,6 +13,7 @@ import org.bukkit.entity.EntityType; import org.bukkit.entity.FishHook; import org.bukkit.entity.LivingEntity; import org.bukkit.entity.Minecart; +import org.bukkit.entity.Mob; import org.bukkit.entity.Player; import org.bukkit.entity.Snowman; import org.bukkit.entity.Vehicle; @@ -445,15 +447,37 @@ public class EventListen implements Listener { @EventHandler(priority = EventPriority.LOWEST) public void onEntityTarget(EntityTargetEvent event) { - NPC npc = plugin.getNPCRegistry().getNPC(event.getTarget()); - if (npc == null) - return; - - final EntityTargetNPCEvent targetNPCEvent = new EntityTargetNPCEvent(event, npc); - targetNPCEvent.setCancelled(!npc.data().get(NPC.Metadata.TARGETABLE, !npc.isProtected())); - Bukkit.getPluginManager().callEvent(targetNPCEvent); - if (targetNPCEvent.isCancelled()) { - event.setCancelled(true); + final Entity targeted = event.getTarget(); + NPC npc = plugin.getNPCRegistry().getNPC(targeted); + final Entity cause = event.getEntity(); + if (npc != null) { + final EntityTargetNPCEvent targetNPCEvent = new EntityTargetNPCEvent(event, npc); + targetNPCEvent.setCancelled(!npc.data().get(NPC.Metadata.TARGETABLE, !npc.isProtected())); + Bukkit.getPluginManager().callEvent(targetNPCEvent); + if (targetNPCEvent.isCancelled()) { + event.setCancelled(true); + } else { + if (event.isCancelled()) { + return; + } + if (!(cause instanceof Mob)) { + return; + } + final TrackTargetedByTrait beTargetedBy = npc.getOrAddTrait(TrackTargetedByTrait.class); + beTargetedBy.add(cause.getUniqueId()); + } + } else { + if (cause instanceof Mob) { + final LivingEntity previousTarget = ((Mob) cause).getTarget(); + if (previousTarget == null) { // normally it is impossible + return; + } + final NPC previousAsNPC = plugin.getNPCRegistry().getNPC(previousTarget); + if (previousAsNPC != null) { + final TrackTargetedByTrait beTargetedBy = previousAsNPC.getOrAddTrait(TrackTargetedByTrait.class); + beTargetedBy.remove(cause.getUniqueId()); + } + } } } diff --git a/main/src/main/java/net/citizensnpcs/commands/NPCCommands.java b/main/src/main/java/net/citizensnpcs/commands/NPCCommands.java index e25c69cb9..b1d43cd7f 100644 --- a/main/src/main/java/net/citizensnpcs/commands/NPCCommands.java +++ b/main/src/main/java/net/citizensnpcs/commands/NPCCommands.java @@ -18,6 +18,7 @@ import java.util.Objects; import java.util.UUID; import java.util.stream.Collectors; +import net.citizensnpcs.trait.TrackTargetedByTrait; import org.bukkit.Art; import org.bukkit.Bukkit; import org.bukkit.ChatColor; @@ -3418,6 +3419,12 @@ public class NPCCommands { NMS.addOrRemoveFromPlayerList(npc.getEntity(), false); } } + if (!targetable) { + final TrackTargetedByTrait trackTargetedByTrait = npc.getTraitNullable(TrackTargetedByTrait.class); + if (trackTargetedByTrait != null) { // may not be targeted by anything so prevent possible garbages + trackTargetedByTrait.clearTargets(); + } + } Messaging.sendTr(sender, targetable ? Messages.TARGETABLE_SET : Messages.TARGETABLE_UNSET, npc.getName()); } diff --git a/main/src/main/java/net/citizensnpcs/npc/CitizensTraitFactory.java b/main/src/main/java/net/citizensnpcs/npc/CitizensTraitFactory.java index b185e1f16..fb46e7b8c 100644 --- a/main/src/main/java/net/citizensnpcs/npc/CitizensTraitFactory.java +++ b/main/src/main/java/net/citizensnpcs/npc/CitizensTraitFactory.java @@ -25,6 +25,7 @@ import net.citizensnpcs.trait.Anchors; import net.citizensnpcs.trait.ArmorStandTrait; import net.citizensnpcs.trait.AttributeTrait; import net.citizensnpcs.trait.BatTrait; +import net.citizensnpcs.trait.TrackTargetedByTrait; import net.citizensnpcs.trait.BoundingBoxTrait; import net.citizensnpcs.trait.ClickRedirectTrait; import net.citizensnpcs.trait.CommandTrait; @@ -103,6 +104,7 @@ public class CitizensTraitFactory implements TraitFactory { registerTrait(TraitInfo.create(ItemFrameTrait.class)); registerTrait(TraitInfo.create(LookClose.class)); registerTrait(TraitInfo.create(PaintingTrait.class)); + registerTrait(TraitInfo.create(TrackTargetedByTrait.class)); registerTrait(TraitInfo.create(MirrorTrait.class).optInToStats()); registerTrait(TraitInfo.create(MountTrait.class)); registerTrait(TraitInfo.create(MobType.class).asDefaultTrait()); diff --git a/main/src/main/java/net/citizensnpcs/trait/TrackTargetedByTrait.java b/main/src/main/java/net/citizensnpcs/trait/TrackTargetedByTrait.java new file mode 100644 index 000000000..b1bab89c3 --- /dev/null +++ b/main/src/main/java/net/citizensnpcs/trait/TrackTargetedByTrait.java @@ -0,0 +1,54 @@ +package net.citizensnpcs.trait; + +import net.citizensnpcs.api.trait.Trait; +import net.citizensnpcs.api.trait.TraitName; +import org.bukkit.Bukkit; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Mob; + +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + +@TraitName("tracktargetedby") +public class TrackTargetedByTrait extends Trait { + private Set beTargetedBy; + + public TrackTargetedByTrait() { + super("tracktargetedby"); + } + + @Override + public void onDespawn() { + clearTargets(); + } + + // Only for internal use + public void add(UUID uuid) { + if (beTargetedBy == null) { + beTargetedBy = new HashSet<>(); + } + beTargetedBy.add(uuid); + } + + // Only for internal use + public void remove(UUID uuid) { + if (beTargetedBy != null) { + beTargetedBy.remove(uuid); + } + } + + public void clearTargets() { + if (beTargetedBy != null) { + for (UUID entityUUID : beTargetedBy) { + final Entity entity = Bukkit.getEntity(entityUUID); + if (entity instanceof Mob) { + if (entity.isValid()) { + ((Mob) entity).setTarget(null); + } + } + } + beTargetedBy = null; + } + } +}