From c7c38172c984e0ff4bd932b152fcfcd4a9973b6d Mon Sep 17 00:00:00 2001 From: JasperLorelai Date: Wed, 26 Aug 2026 19:17:02 +0200 Subject: [PATCH 1/4] feat: Add config for targetable state --- .../listeners/MagicSpellListener.java | 59 ++++++++++++------ .../effecttypes/ArmorStandEffect.java | 11 ++++ .../effecttypes/EntityEffect.java | 13 +++- .../effecttypes/FireworksEffect.java | 6 +- .../effecttypes/ItemSprayEffect.java | 6 +- .../spells/instant/MagnetSpell.java | 2 +- .../nisovin/magicspells/util/EntityData.java | 41 +++++++++++-- .../util/pdc/PersistentDataEntry.java | 60 +++++++++++++++++++ .../magicspells/util/pdc/UUIDTagType.java | 44 ++++++++++++++ 9 files changed, 210 insertions(+), 32 deletions(-) create mode 100644 core/src/main/java/com/nisovin/magicspells/util/pdc/PersistentDataEntry.java create mode 100644 core/src/main/java/com/nisovin/magicspells/util/pdc/UUIDTagType.java diff --git a/core/src/main/java/com/nisovin/magicspells/listeners/MagicSpellListener.java b/core/src/main/java/com/nisovin/magicspells/listeners/MagicSpellListener.java index 5c9a87538..b48307610 100644 --- a/core/src/main/java/com/nisovin/magicspells/listeners/MagicSpellListener.java +++ b/core/src/main/java/com/nisovin/magicspells/listeners/MagicSpellListener.java @@ -1,10 +1,13 @@ package com.nisovin.magicspells.listeners; +import java.util.UUID; + import org.bukkit.entity.Entity; import org.bukkit.event.Listener; import org.bukkit.event.EventHandler; import org.bukkit.entity.LivingEntity; import org.bukkit.event.world.ChunkLoadEvent; +import org.bukkit.persistence.PersistentDataType; import org.bukkit.event.entity.EntityDamageEvent; import org.bukkit.event.entity.EntityRemoveEvent; import org.bukkit.event.entity.EntityDismountEvent; @@ -16,39 +19,61 @@ import com.nisovin.magicspells.Spell; import com.nisovin.magicspells.util.Util; import com.nisovin.magicspells.MagicSpells; +import com.nisovin.magicspells.util.SpellData; import com.nisovin.magicspells.util.EntityData; +import com.nisovin.magicspells.util.pdc.UUIDTagType; import com.nisovin.magicspells.events.SpellTargetEvent; import com.nisovin.magicspells.zones.NoMagicZoneManager; import com.nisovin.magicspells.spelleffects.effecttypes.*; +import com.nisovin.magicspells.util.pdc.PersistentDataEntry; import com.nisovin.magicspells.events.ParticleProjectileHitEvent; public class MagicSpellListener implements Listener { - private final NoMagicZoneManager noMagicZoneManager = MagicSpells.getNoMagicZoneManager(); + public static final PersistentDataEntry PDC_CASTER = new PersistentDataEntry<>(UUIDTagType.INSTANCE, "caster"); + public static final PersistentDataEntry PDC_TARGETABLE = new PersistentDataEntry<>(PersistentDataType.BOOLEAN, "targetable"); + public static final PersistentDataEntry PDC_TARGETABLE_BY_CASTER = new PersistentDataEntry<>(PersistentDataType.BOOLEAN, "targetable_by_caster"); @EventHandler public void onSpellTarget(SpellTargetEvent event) { - // Check if target has noTarget permission / is in noMagicZone / is an invisible marker armorstand - LivingEntity target = event.getTarget(); Spell spell = event.getSpell(); + SpellData data = event.getSpellData(); + if (!data.hasTarget()) return; - if (target == null) - return; - - if (isMSEntity(target)) { + if (!isTargetable(data.target(), data.caster())) { event.setCancelled(true); return; } - if (Perm.NO_TARGET.has(target)) { + if (Perm.NO_TARGET.has(data.target())) { event.setCancelled(true); return; } - if (spell != null && noMagicZoneManager != null && noMagicZoneManager.willFizzle(target, spell)) + NoMagicZoneManager zoneManager = MagicSpells.getNoMagicZoneManager(); + if (spell != null && zoneManager != null && zoneManager.willFizzle(data.target(), spell)) event.setCancelled(true); } + private boolean isTargetable(Entity target, Entity caster) { + PersistentDataContainer pdc = target.getPersistentDataContainer(); + + if (caster != null && caster.getUniqueId().equals(PDC_CASTER.get(pdc))) { + Boolean targetableByCaster = PDC_TARGETABLE_BY_CASTER.get(pdc); + if (targetableByCaster != null) return targetableByCaster; + } + + Boolean targetable = PDC_TARGETABLE.get(pdc); + if (targetable != null) return targetable; + + return !isMSEntity(target); + } + + private boolean isMSEntity(Entity entity) { + return entity.getScoreboardTags().contains(ArmorStandEffect.ENTITY_TAG) + || entity.getScoreboardTags().contains(EntityEffect.ENTITY_TAG); + } + @EventHandler public void onProjectileHit(ParticleProjectileHitEvent event) { LivingEntity target = event.getTarget(); @@ -78,11 +103,11 @@ public void onEntityDamage(EntityDamageEvent event) { @EventHandler public void onEntityRemove(EntityRemoveEvent event) { Util.forEachPassenger(event.getEntity(), passenger -> { - PersistentDataContainer container = passenger.getPersistentDataContainer(); - if (!container.has(EntityData.MS_PASSENGER)) return; + PersistentDataContainer pdc = passenger.getPersistentDataContainer(); + if (!EntityData.MS_PASSENGER.has(pdc)) return; if (passenger.isPersistent()) { - container.remove(EntityData.MS_PASSENGER); + EntityData.MS_PASSENGER.remove(pdc); return; } @@ -92,22 +117,18 @@ public void onEntityRemove(EntityRemoveEvent event) { @EventHandler public void onEntityDismount(EntityDismountEvent event) { - event.getEntity().getPersistentDataContainer().remove(EntityData.MS_PASSENGER); - } - - private boolean isMSEntity(Entity entity) { - return entity.getScoreboardTags().contains(ArmorStandEffect.ENTITY_TAG) || entity.getScoreboardTags().contains(EntityEffect.ENTITY_TAG); + EntityData.MS_PASSENGER.remove(event.getEntity().getPersistentDataContainer()); } @EventHandler public void onFireworkDamage(EntityDamageByEntityEvent event) { - if (!event.getDamager().getPersistentDataContainer().has(FireworksEffect.MS_FIREWORK)) return; + if (!FireworksEffect.MS_FIREWORK.has(event.getDamager().getPersistentDataContainer())) return; event.setCancelled(true); } @EventHandler public void onInvPickup(InventoryPickupItemEvent event) { - if (!event.getItem().getPersistentDataContainer().has(ItemSprayEffect.MS_ITEM_SPRAY)) return; + if (!ItemSprayEffect.MS_ITEM_SPRAY.has(event.getItem().getPersistentDataContainer())) return; event.setCancelled(true); } diff --git a/core/src/main/java/com/nisovin/magicspells/spelleffects/effecttypes/ArmorStandEffect.java b/core/src/main/java/com/nisovin/magicspells/spelleffects/effecttypes/ArmorStandEffect.java index 74e430355..1febab30d 100644 --- a/core/src/main/java/com/nisovin/magicspells/spelleffects/effecttypes/ArmorStandEffect.java +++ b/core/src/main/java/com/nisovin/magicspells/spelleffects/effecttypes/ArmorStandEffect.java @@ -6,6 +6,7 @@ import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.EquipmentSlot; import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.persistence.PersistentDataContainer; import com.nisovin.magicspells.util.Name; import com.nisovin.magicspells.util.Util; @@ -16,6 +17,7 @@ import com.nisovin.magicspells.util.magicitems.MagicItem; import com.nisovin.magicspells.util.magicitems.MagicItems; import com.nisovin.magicspells.util.config.ConfigDataUtil; +import com.nisovin.magicspells.listeners.MagicSpellListener; @Name("armorstand") public class ArmorStandEffect extends SpellEffect { @@ -62,12 +64,21 @@ protected ArmorStand playArmorStandEffectLocation(Location location, SpellData d stand.setItem(EquipmentSlot.HEAD, headItem); stand.setItem(EquipmentSlot.HAND, mainhandItem); stand.setItem(EquipmentSlot.OFF_HAND, offhandItem); + + preSpawn(stand); + Util.forEachPassenger(stand, this::preSpawn); }, stand -> { postSpawn(stand); Util.forEachPassenger(stand, this::postSpawn); }); } + private void preSpawn(Entity entity) { + PersistentDataContainer pdc = entity.getPersistentDataContainer(); + MagicSpellListener.PDC_TARGETABLE.set(pdc, false); + MagicSpellListener.PDC_TARGETABLE_BY_CASTER.set(pdc, false); + } + private void postSpawn(Entity entity) { entity.setPersistent(false); entity.addScoreboardTag(ENTITY_TAG); diff --git a/core/src/main/java/com/nisovin/magicspells/spelleffects/effecttypes/EntityEffect.java b/core/src/main/java/com/nisovin/magicspells/spelleffects/effecttypes/EntityEffect.java index 91977d47b..115cc445e 100644 --- a/core/src/main/java/com/nisovin/magicspells/spelleffects/effecttypes/EntityEffect.java +++ b/core/src/main/java/com/nisovin/magicspells/spelleffects/effecttypes/EntityEffect.java @@ -6,6 +6,7 @@ import org.bukkit.Location; import org.bukkit.entity.Entity; import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.persistence.PersistentDataContainer; import com.nisovin.magicspells.util.Name; import com.nisovin.magicspells.util.Util; @@ -15,11 +16,12 @@ import com.nisovin.magicspells.util.config.ConfigData; import com.nisovin.magicspells.spelleffects.SpellEffect; import com.nisovin.magicspells.util.config.ConfigDataUtil; +import com.nisovin.magicspells.listeners.MagicSpellListener; @Name("entity") public class EntityEffect extends SpellEffect { - public static final Set entities = new HashSet<>(); + private static final Set entities = new HashSet<>(); public static final String ENTITY_TAG = "MS_ENTITY"; @@ -45,12 +47,21 @@ protected void loadFromConfig(ConfigurationSection config) { protected Entity playEntityEffectLocation(Location location, SpellData data) { return entityData.spawn(location, data, entity -> { entity.setGravity(gravity.get(data)); + + preSpawn(entity); + Util.forEachPassenger(entity, this::preSpawn); }, entity -> { postSpawn(entity); Util.forEachPassenger(entity, this::postSpawn); }); } + private void preSpawn(Entity entity) { + PersistentDataContainer pdc = entity.getPersistentDataContainer(); + MagicSpellListener.PDC_TARGETABLE.set(pdc, false); + MagicSpellListener.PDC_TARGETABLE_BY_CASTER.set(pdc, false); + } + private void postSpawn(Entity entity) { entity.setPersistent(false); entity.addScoreboardTag(ENTITY_TAG); diff --git a/core/src/main/java/com/nisovin/magicspells/spelleffects/effecttypes/FireworksEffect.java b/core/src/main/java/com/nisovin/magicspells/spelleffects/effecttypes/FireworksEffect.java index 4fdfbdacb..951603081 100644 --- a/core/src/main/java/com/nisovin/magicspells/spelleffects/effecttypes/FireworksEffect.java +++ b/core/src/main/java/com/nisovin/magicspells/spelleffects/effecttypes/FireworksEffect.java @@ -7,16 +7,16 @@ import org.bukkit.configuration.ConfigurationSection; import com.nisovin.magicspells.util.Name; -import com.nisovin.magicspells.MagicSpells; import com.nisovin.magicspells.util.SpellData; import com.nisovin.magicspells.util.config.ConfigData; import com.nisovin.magicspells.spelleffects.SpellEffect; import com.nisovin.magicspells.util.config.ConfigDataUtil; +import com.nisovin.magicspells.util.pdc.PersistentDataEntry; @Name("fireworks") public class FireworksEffect extends SpellEffect { - public static final NamespacedKey MS_FIREWORK = new NamespacedKey(MagicSpells.getInstance(), "fireworks_effect"); + public static final PersistentDataEntry MS_FIREWORK = new PersistentDataEntry<>(PersistentDataType.BOOLEAN, "fireworks_effect"); private ConfigData type; private ConfigData flightDuration; @@ -95,7 +95,7 @@ public Runnable playEffectLocation(Location location, SpellData data) { firework.setSilent(true); firework.setTicksToDetonate(flightDuration.get(data)); - firework.getPersistentDataContainer().set(MS_FIREWORK, PersistentDataType.BOOLEAN, true); + MS_FIREWORK.set(firework.getPersistentDataContainer(), true); }); return null; diff --git a/core/src/main/java/com/nisovin/magicspells/spelleffects/effecttypes/ItemSprayEffect.java b/core/src/main/java/com/nisovin/magicspells/spelleffects/effecttypes/ItemSprayEffect.java index e67bf79cb..887777355 100644 --- a/core/src/main/java/com/nisovin/magicspells/spelleffects/effecttypes/ItemSprayEffect.java +++ b/core/src/main/java/com/nisovin/magicspells/spelleffects/effecttypes/ItemSprayEffect.java @@ -7,7 +7,6 @@ import org.bukkit.Material; import org.bukkit.entity.Item; import org.bukkit.util.Vector; -import org.bukkit.NamespacedKey; import org.bukkit.entity.Entity; import org.bukkit.inventory.ItemStack; import org.bukkit.persistence.PersistentDataType; @@ -21,11 +20,12 @@ import com.nisovin.magicspells.util.config.ConfigData; import com.nisovin.magicspells.spelleffects.SpellEffect; import com.nisovin.magicspells.util.config.ConfigDataUtil; +import com.nisovin.magicspells.util.pdc.PersistentDataEntry; @Name("itemspray") public class ItemSprayEffect extends SpellEffect { - public static final NamespacedKey MS_ITEM_SPRAY = new NamespacedKey(MagicSpells.getInstance(), "ms_item_spray"); + public static final PersistentDataEntry MS_ITEM_SPRAY = new PersistentDataEntry<>(PersistentDataType.BOOLEAN, "ms_item_spray"); private static final List items = new ArrayList<>(); @@ -80,7 +80,7 @@ public Runnable playEffectLocation(Location location, SpellData data) { int amount = this.amount.get(data); for (int i = 0; i < amount; i++) { Item dropped = loc.getWorld().dropItem(loc, itemStack, item -> { - item.getPersistentDataContainer().set(MS_ITEM_SPRAY, PersistentDataType.BOOLEAN, true); + MS_ITEM_SPRAY.set(item.getPersistentDataContainer(), true); Vector velocity = this.velocity.get(data); if (velocity == null) velocity = new Vector( diff --git a/core/src/main/java/com/nisovin/magicspells/spells/instant/MagnetSpell.java b/core/src/main/java/com/nisovin/magicspells/spells/instant/MagnetSpell.java index 3bca2750e..3f001ec6e 100644 --- a/core/src/main/java/com/nisovin/magicspells/spells/instant/MagnetSpell.java +++ b/core/src/main/java/com/nisovin/magicspells/spells/instant/MagnetSpell.java @@ -70,7 +70,7 @@ public CastResult castAtLocation(SpellData data) { Collection items = location.getNearbyEntitiesByType(Item.class, radius, item -> { if (!item.isValid() || item.getItemStack().isEmpty()) return false; - if (item.getPersistentDataContainer().has(ItemSprayEffect.MS_ITEM_SPRAY)) return false; + if (ItemSprayEffect.MS_ITEM_SPRAY.has(item.getPersistentDataContainer())) return false; if (data.hasCaster()) { LivingEntity caster = data.caster(); diff --git a/core/src/main/java/com/nisovin/magicspells/util/EntityData.java b/core/src/main/java/com/nisovin/magicspells/util/EntityData.java index 132e30393..6579ee157 100644 --- a/core/src/main/java/com/nisovin/magicspells/util/EntityData.java +++ b/core/src/main/java/com/nisovin/magicspells/util/EntityData.java @@ -43,6 +43,7 @@ import org.bukkit.persistence.PersistentDataType; import org.bukkit.entity.minecart.CommandMinecart; import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.persistence.PersistentDataContainer; import io.papermc.paper.entity.Frictional; import io.papermc.paper.registry.RegistryKey; @@ -57,11 +58,13 @@ import com.nisovin.magicspells.util.config.ConfigData; import com.nisovin.magicspells.util.config.FunctionData; import com.nisovin.magicspells.util.config.ConfigDataUtil; +import com.nisovin.magicspells.util.pdc.PersistentDataEntry; +import com.nisovin.magicspells.listeners.MagicSpellListener; import com.nisovin.magicspells.util.itemreader.AttributeHandler; public class EntityData { - public static final NamespacedKey MS_PASSENGER = new NamespacedKey(MagicSpells.getInstance(), "entity_passenger"); + public static final PersistentDataEntry MS_PASSENGER = new PersistentDataEntry<>(PersistentDataType.BOOLEAN, "entity_passenger"); private final Multimap> options = MultimapBuilder.enumKeys(EntityType.class).arrayListValues().build(); private final List delayedEntityData = new ArrayList<>(); @@ -72,6 +75,7 @@ public class EntityData { private final ConfigData yaw; private final ConfigData pitch; private final ConfigData relativeOffset; + private final ConfigData untargetableAfterSpawn; // Legacy support for DisguiseSpell section format @@ -137,7 +141,7 @@ public EntityData(ConfigurationSection config, boolean forceOptional) { pitch = ConfigDataUtil.getAngle(config, "pitch", Angle.DEFAULT); relativeOffset = ConfigDataUtil.getVector(config, "relative-offset", new Vector(0, 0, 0)); - Multimap<@NotNull Class, @NotNull Transformer> transformers = MultimapBuilder.linkedHashKeys().arrayListValues().build(); + Multimap, Transformer> transformers = MultimapBuilder.linkedHashKeys().arrayListValues().build(); // Entity addOptBoolean(transformers, config, "silent", Entity.class, Entity::setSilent); @@ -184,6 +188,25 @@ public EntityData(ConfigurationSection config, boolean forceOptional) { transformers.put(Entity.class, (Entity entity, SpellData data) -> entity.addScoreboardTag(tag.get(data))); } + ConfigData targetable = addOptBoolean(transformers, config, "targetable", Entity.class, (entity, value) -> + MagicSpellListener.PDC_TARGETABLE.set(entity.getPersistentDataContainer(), value) + ); + + ConfigData targetableByCaster = ConfigDataUtil.getBoolean(config, "targetable-by-caster", targetable); + transformers.put(Entity.class, (Entity entity, SpellData data) -> { + Boolean value = targetableByCaster.get(data); + if (value == null) return; + + MagicSpellListener.PDC_TARGETABLE_BY_CASTER.set(entity.getPersistentDataContainer(), value); + }); + + transformers.put(Entity.class, (Entity entity, SpellData data) -> { + if (!data.hasCaster()) return; + MagicSpellListener.PDC_CASTER.set(entity.getPersistentDataContainer(), data.caster().getUniqueId()); + }); + + untargetableAfterSpawn = ConfigDataUtil.getBoolean(config, "untargetable-after-spawn", false); + // Ageable baby = addBoolean(transformers, config, "baby", false, Ageable.class, (ageable, baby) -> { if (baby) ageable.setBaby(); @@ -194,7 +217,7 @@ public EntityData(ConfigurationSection config, boolean forceOptional) { // Attributable List attributeModifierStrings = config.getList("attribute-modifiers", new ArrayList<>()); - Multimap<@NotNull Attribute, @NotNull AttributeModifier> attributeModifiers = AttributeHandler.getAttributeModifiers(attributeModifierStrings, null); + Multimap attributeModifiers = AttributeHandler.getAttributeModifiers(attributeModifierStrings, null); if (!attributeModifiers.isEmpty()) { transformers.put(Attributable.class, (Attributable entity, SpellData data) -> { attributeModifiers.asMap().forEach((attribute, modifiers) -> { @@ -949,11 +972,11 @@ public T spawn(@NotNull Location location, @NotNull SpellData spawnLocation.setYaw(yaw.get(data).apply(spawnLocation.getYaw())); spawnLocation.setPitch(pitch.get(data).apply(spawnLocation.getPitch())); - return spawnLocation.getWorld().spawn(spawnLocation, entityClass, entity -> { + T spawned = spawnLocation.getWorld().spawn(spawnLocation, entityClass, entity -> { for (EntityData passengerData : passengers) { passengerData.spawn(entity.getLocation(), data, passenger -> { entity.addPassenger(passenger); - passenger.getPersistentDataContainer().set(MS_PASSENGER, PersistentDataType.BOOLEAN, true); + MS_PASSENGER.set(passenger.getPersistentDataContainer(), true); }); } @@ -963,6 +986,14 @@ public T spawn(@NotNull Location location, @NotNull SpellData if (postConsumer != null) postConsumer.accept(entity); }); + + if (untargetableAfterSpawn.get(data)) { + PersistentDataContainer pdc = spawned.getPersistentDataContainer(); + MagicSpellListener.PDC_TARGETABLE.set(pdc, false); + MagicSpellListener.PDC_TARGETABLE_BY_CASTER.set(pdc, false); + } + + return spawned; } public void apply(@NotNull Entity entity, @NotNull SpellData data) { diff --git a/core/src/main/java/com/nisovin/magicspells/util/pdc/PersistentDataEntry.java b/core/src/main/java/com/nisovin/magicspells/util/pdc/PersistentDataEntry.java new file mode 100644 index 000000000..b140637d6 --- /dev/null +++ b/core/src/main/java/com/nisovin/magicspells/util/pdc/PersistentDataEntry.java @@ -0,0 +1,60 @@ +package com.nisovin.magicspells.util.pdc; + +import com.nisovin.magicspells.MagicSpells; + +import org.jetbrains.annotations.Nullable; +import org.jspecify.annotations.NullMarked; + +import org.bukkit.NamespacedKey; +import org.bukkit.persistence.PersistentDataType; +import org.bukkit.persistence.PersistentDataContainer; + +import io.papermc.paper.persistence.PersistentDataContainerView; + +@NullMarked +public record PersistentDataEntry(PersistentDataType type, NamespacedKey key) { + + /** + * Uses the "magicspells" plugin namespace. + * @throws IllegalArgumentException If the created {@link NamespacedKey} is invalid. + */ + public PersistentDataEntry(PersistentDataType type, String key) { + this(type, new NamespacedKey(MagicSpells.getInstance(), key)); + } + + /** + * @see PersistentDataContainerView#has(NamespacedKey, PersistentDataType) + */ + public boolean has(PersistentDataContainerView container) { + return container.has(key, type); + } + + /** + * @see PersistentDataContainerView#get(NamespacedKey, PersistentDataType) + */ + public @Nullable C get(PersistentDataContainerView container) { + return container.get(key, type); + } + + /** + * @see PersistentDataContainerView#getOrDefault(NamespacedKey, PersistentDataType, C) + */ + public C getOrDefault(PersistentDataContainerView container, C defaultValue) { + return container.getOrDefault(key, type, defaultValue); + } + + /** + * @see PersistentDataContainer#set(NamespacedKey, PersistentDataType, C) + */ + public void set(PersistentDataContainer container, C value) { + container.set(key, type, value); + } + + /** + * @see PersistentDataContainer#remove(NamespacedKey) + */ + public void remove(PersistentDataContainer container) { + container.remove(key); + } + +} diff --git a/core/src/main/java/com/nisovin/magicspells/util/pdc/UUIDTagType.java b/core/src/main/java/com/nisovin/magicspells/util/pdc/UUIDTagType.java new file mode 100644 index 000000000..4d484f29a --- /dev/null +++ b/core/src/main/java/com/nisovin/magicspells/util/pdc/UUIDTagType.java @@ -0,0 +1,44 @@ +package com.nisovin.magicspells.util.pdc; + +import java.util.UUID; +import java.nio.ByteBuffer; + +import org.jspecify.annotations.NullMarked; + +import org.bukkit.persistence.PersistentDataType; +import org.bukkit.persistence.PersistentDataAdapterContext; + +@NullMarked +public class UUIDTagType implements PersistentDataType { + + public static final UUIDTagType INSTANCE = new UUIDTagType(); + + private UUIDTagType() {} + + @Override + public Class getPrimitiveType() { + return byte[].class; + } + + @Override + public Class getComplexType() { + return UUID.class; + } + + @Override + public byte[] toPrimitive(UUID complex, PersistentDataAdapterContext context) { + ByteBuffer bb = ByteBuffer.allocate(Long.BYTES * 2); + bb.putLong(complex.getMostSignificantBits()); + bb.putLong(complex.getLeastSignificantBits()); + return bb.array(); + } + + @Override + public UUID fromPrimitive(byte[] primitive, PersistentDataAdapterContext context) { + ByteBuffer bb = ByteBuffer.wrap(primitive); + long firstLong = bb.getLong(); + long secondLong = bb.getLong(); + return new UUID(firstLong, secondLong); + } + +} From f79fbf3f4dd61ef0fa81d0c24c0febe7d2dedaee Mon Sep 17 00:00:00 2001 From: JasperLorelai Date: Wed, 19 Aug 2026 03:51:33 +0200 Subject: [PATCH 2/4] feat: Add "spell-on-spawn" --- .../com/nisovin/magicspells/util/EntityData.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/core/src/main/java/com/nisovin/magicspells/util/EntityData.java b/core/src/main/java/com/nisovin/magicspells/util/EntityData.java index 6579ee157..8e4e7cf08 100644 --- a/core/src/main/java/com/nisovin/magicspells/util/EntityData.java +++ b/core/src/main/java/com/nisovin/magicspells/util/EntityData.java @@ -53,6 +53,7 @@ import io.papermc.paper.datacomponent.item.ResolvableProfile; import io.papermc.paper.threadedregions.scheduler.ScheduledTask; +import com.nisovin.magicspells.Subspell; import com.nisovin.magicspells.MagicSpells; import com.nisovin.magicspells.util.ai.CustomGoal; import com.nisovin.magicspells.util.config.ConfigData; @@ -77,6 +78,8 @@ public class EntityData { private final ConfigData relativeOffset; private final ConfigData untargetableAfterSpawn; + private Subspell spellOnSpawn = null; + // Legacy support for DisguiseSpell section format // Ageable @@ -141,6 +144,14 @@ public EntityData(ConfigurationSection config, boolean forceOptional) { pitch = ConfigDataUtil.getAngle(config, "pitch", Angle.DEFAULT); relativeOffset = ConfigDataUtil.getVector(config, "relative-offset", new Vector(0, 0, 0)); + String spellOnSpawnName = config.getString("spell-on-spawn"); + if (spellOnSpawnName != null && !spellOnSpawnName.isEmpty()) { + Subspell subspell = new Subspell(spellOnSpawnName); + + if (subspell.process()) spellOnSpawn = subspell; + else MagicSpells.error("Invalid 'spell-on-spawn' specified on EntityData: '" + spellOnSpawnName + "'"); + } + Multimap, Transformer> transformers = MultimapBuilder.linkedHashKeys().arrayListValues().build(); // Entity @@ -987,6 +998,11 @@ public T spawn(@NotNull Location location, @NotNull SpellData if (postConsumer != null) postConsumer.accept(entity); }); + if (spellOnSpawn != null) { + if (spawned instanceof LivingEntity le) spellOnSpawn.subcast(data.retarget(le, null)); + else spellOnSpawn.subcast(data.retarget(null, spawned.getLocation())); + } + if (untargetableAfterSpawn.get(data)) { PersistentDataContainer pdc = spawned.getPersistentDataContainer(); MagicSpellListener.PDC_TARGETABLE.set(pdc, false); From ff69a6ffaac5fdcceb46c22c07d1ac1c23a94264 Mon Sep 17 00:00:00 2001 From: JasperLorelai Date: Thu, 27 Aug 2026 01:16:41 +0200 Subject: [PATCH 3/4] feat: Add key-less glow removal --- .../com/nisovin/magicspells/MagicSpells.java | 17 +++++++ .../spells/targeted/GlowSpell.java | 31 +++---------- .../magicspells/util/glow/GlowManager.java | 4 ++ .../util/glow/PacketBasedGlowManager.java | 46 +++++++++++++++++++ 4 files changed, 73 insertions(+), 25 deletions(-) diff --git a/core/src/main/java/com/nisovin/magicspells/MagicSpells.java b/core/src/main/java/com/nisovin/magicspells/MagicSpells.java index 502140822..6761961a2 100644 --- a/core/src/main/java/com/nisovin/magicspells/MagicSpells.java +++ b/core/src/main/java/com/nisovin/magicspells/MagicSpells.java @@ -73,6 +73,7 @@ import com.nisovin.magicspells.mana.ManaHandler; import com.nisovin.magicspells.variables.Variable; import com.nisovin.magicspells.spells.PassiveSpell; +import com.nisovin.magicspells.util.glow.GlowManager; import com.nisovin.magicspells.util.compat.EventUtil; import com.nisovin.magicspells.commands.MagicCommands; import com.nisovin.magicspells.storage.StorageHandler; @@ -93,6 +94,7 @@ import com.nisovin.magicspells.events.SpellLearnEvent.LearnSource; import com.nisovin.magicspells.spelleffects.trackers.EffectTracker; import com.nisovin.magicspells.spells.passive.util.PassiveListener; +import com.nisovin.magicspells.util.glow.impl.PacketEventsGlowManager; import com.nisovin.magicspells.variables.variabletypes.GlobalVariable; import com.nisovin.magicspells.spelleffects.trackers.AsyncEffectTracker; import com.nisovin.magicspells.spelleffects.effecttypes.EffectLibEffect; @@ -139,6 +141,7 @@ public class MagicSpells extends JavaPlugin { private DeprecationHandler deprecationHandler; private VolatileCodeHandle volatileCodeHandle; + private GlowManager glowManager; private BuffManager buffManager; private EffectManager effectManager; private BossBarManager bossBarManager; @@ -408,6 +411,10 @@ public void load() { lifeLengthTracker = new LifeLengthTracker(); expressionDictionary = new ExpressionDictionary(); + if (Bukkit.getPluginManager().isPluginEnabled("packetevents")) glowManager = new PacketEventsGlowManager(); + else glowManager = MagicSpells.getVolatileCodeHandler().getGlowManager(); + glowManager.load(); + // Call loading event Bukkit.getPluginManager().callEvent(new MagicSpellsLoadingEvent(this)); @@ -1442,6 +1449,10 @@ public static CustomGoalsManager getCustomGoalsManager() { return plugin.customGoalsManager; } + public static GlowManager getGlowManager() { + return plugin.glowManager; + } + public static BuffManager getBuffManager() { return plugin.buffManager; } @@ -2269,6 +2280,12 @@ public void unload() { buffManager = null; } + // Turn off glow manager + if (glowManager != null) { + glowManager.unload(); + glowManager = null; + } + // Clear memory spells.clear(); spells = null; diff --git a/core/src/main/java/com/nisovin/magicspells/spells/targeted/GlowSpell.java b/core/src/main/java/com/nisovin/magicspells/spells/targeted/GlowSpell.java index e7a4cb1f9..73e4ebb97 100644 --- a/core/src/main/java/com/nisovin/magicspells/spells/targeted/GlowSpell.java +++ b/core/src/main/java/com/nisovin/magicspells/spells/targeted/GlowSpell.java @@ -2,7 +2,6 @@ import java.util.UUID; -import org.bukkit.Bukkit; import org.bukkit.entity.Player; import org.bukkit.NamespacedKey; import org.bukkit.entity.LivingEntity; @@ -18,12 +17,9 @@ import com.nisovin.magicspells.util.config.ConfigData; import com.nisovin.magicspells.util.glow.GlowManager; import com.nisovin.magicspells.spells.TargetedEntitySpell; -import com.nisovin.magicspells.util.glow.impl.PacketEventsGlowManager; public class GlowSpell extends TargetedSpell implements TargetedEntitySpell { - private static GlowManager glowManager; - private final ConfigData global; private final ConfigData remove; private final ConfigData duration; @@ -40,13 +36,6 @@ public GlowSpell(MagicConfig config, String spellName) { remove = getConfigDataBoolean("remove", false); duration = getConfigDataInt("duration", 0); priority = getConfigDataInt("priority", 0); - - if (glowManager == null) { - if (Bukkit.getPluginManager().isPluginEnabled("packetevents")) glowManager = new PacketEventsGlowManager(); - else glowManager = MagicSpells.getVolatileCodeHandler().getGlowManager(); - - glowManager.load(); - } } @Override @@ -59,13 +48,14 @@ public CastResult cast(SpellData data) { @Override public CastResult castAtEntity(SpellData data) { + GlowManager glowManager = MagicSpells.getGlowManager(); + if (global.get(data)) { NamespacedKey key = this.key.get(data); if (remove.get(data)) { - if (key == null) return new CastResult(PostCastAction.ALREADY_HANDLED, data); - - glowManager.removeGlow(data.target(), key); + if (key == null) glowManager.removeGlow(data.target()); + else glowManager.removeGlow(data.target(), key); } else { glowManager.applyGlow( data.target(), @@ -85,9 +75,8 @@ public CastResult castAtEntity(SpellData data) { NamespacedKey key = this.key.get(data); if (remove.get(data)) { - if (key == null) return new CastResult(PostCastAction.ALREADY_HANDLED, data); - - glowManager.removeGlow(caster, data.target(), key); + if (key == null) glowManager.removeGlow(caster, data.target()); + else glowManager.removeGlow(caster, data.target(), key); } else { glowManager.applyGlow( caster, @@ -103,12 +92,4 @@ public CastResult castAtEntity(SpellData data) { return new CastResult(PostCastAction.HANDLE_NORMALLY, data); } - @Override - protected void turnOff() { - if (glowManager == null) return; - - glowManager.unload(); - glowManager = null; - } - } diff --git a/nms/shared/src/main/java/com/nisovin/magicspells/util/glow/GlowManager.java b/nms/shared/src/main/java/com/nisovin/magicspells/util/glow/GlowManager.java index 10678fa98..04f97a6ff 100644 --- a/nms/shared/src/main/java/com/nisovin/magicspells/util/glow/GlowManager.java +++ b/nms/shared/src/main/java/com/nisovin/magicspells/util/glow/GlowManager.java @@ -19,8 +19,12 @@ public interface GlowManager { void applyGlow(@NotNull Player player, @NotNull Entity entity, @NotNull NamespacedKey key, @NotNull NamedTextColor color, int priority, @Range(from = 0, to = Integer.MAX_VALUE) int duration); + void removeGlow(@NotNull Entity entity); + void removeGlow(@NotNull Entity entity, @NotNull NamespacedKey key); + void removeGlow(@NotNull Player player, @NotNull Entity entity); + void removeGlow(@NotNull Player player, @NotNull Entity entity, @NotNull NamespacedKey key); } diff --git a/nms/shared/src/main/java/com/nisovin/magicspells/util/glow/PacketBasedGlowManager.java b/nms/shared/src/main/java/com/nisovin/magicspells/util/glow/PacketBasedGlowManager.java index 142229323..fb2572ae7 100644 --- a/nms/shared/src/main/java/com/nisovin/magicspells/util/glow/PacketBasedGlowManager.java +++ b/nms/shared/src/main/java/com/nisovin/magicspells/util/glow/PacketBasedGlowManager.java @@ -157,6 +157,29 @@ public void applyGlow(@NotNull Player player, @NotNull Entity entity, @NotNull N updateGlow(player, entity, prev, curr); } + @Override + public void removeGlow(@NotNull Entity entity) { + UUID uuid = entity.getUniqueId(); + + if (!glows.containsKey(uuid)) return; + + GlowData prev, curr; + synchronized (this) { + prev = getGlowData(null, uuid); + + glows.remove(uuid); + + curr = getGlowData(null, uuid); + } + + if (curr == null) { + resetGlow(null, entity, prev); + return; + } + + updateGlow(null, entity, prev, curr); + } + @Override public void removeGlow(@NotNull Entity entity, @NotNull NamespacedKey key) { UUID uuid = entity.getUniqueId(); @@ -182,6 +205,29 @@ public void removeGlow(@NotNull Entity entity, @NotNull NamespacedKey key) { updateGlow(null, entity, prev, curr); } + @Override + public void removeGlow(@NotNull Player player, @NotNull Entity entity) { + Pair pair = Pair.of(player.getUniqueId(), entity.getUniqueId()); + + if (!perPlayerGlows.containsKey(pair)) return; + + GlowData prev, curr; + synchronized (this) { + prev = getGlowData(pair); + + perPlayerGlows.remove(pair); + + curr = getGlowData(pair); + } + + if (curr == null) { + resetGlow(player, entity, prev); + return; + } + + updateGlow(player, entity, prev, curr); + } + @Override public void removeGlow(@NotNull Player player, @NotNull Entity entity, @NotNull NamespacedKey key) { Pair pair = Pair.of(player.getUniqueId(), entity.getUniqueId()); From 1c4667b083f2046487bc1ba0524891623941e584 Mon Sep 17 00:00:00 2001 From: JasperLorelai Date: Fri, 21 Aug 2026 04:29:27 +0200 Subject: [PATCH 4/4] feat: Add "visible-only-to-recipient" --- .../java/com/nisovin/magicspells/util/EntityData.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/core/src/main/java/com/nisovin/magicspells/util/EntityData.java b/core/src/main/java/com/nisovin/magicspells/util/EntityData.java index 8e4e7cf08..00e478c6f 100644 --- a/core/src/main/java/com/nisovin/magicspells/util/EntityData.java +++ b/core/src/main/java/com/nisovin/magicspells/util/EntityData.java @@ -164,6 +164,15 @@ public EntityData(ConfigurationSection config, boolean forceOptional) { addOptBoolean(transformers, config, "visible-by-default", Entity.class, Entity::setVisibleByDefault); addOptBoolean(transformers, config, "custom-name-visible", Entity.class, Entity::setCustomNameVisible); + ConfigData visibleOnlyToRecipient = ConfigDataUtil.getBoolean(config, "visible-only-to-recipient", false); + transformers.put(Entity.class, (Entity entity, SpellData data) -> { + if (!(data.recipient() instanceof Player recipient)) return; + if (!visibleOnlyToRecipient.get(data)) return; + + entity.setVisibleByDefault(false); + recipient.showEntity(MagicSpells.getInstance(), entity); + }); + addOptEnum(transformers, config, "visual-fire", Entity.class, TriState.class, Entity::setVisualFire); addOptInteger(transformers, config, "fire-ticks", Entity.class, Entity::setFireTicks);