diff --git a/core/build.gradle b/core/build.gradle index efeefe0a4..6be45d5c6 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -7,8 +7,8 @@ dependencies { implementation("org.apache.commons:commons-math4-core:4.0-beta1") implementation("com.github.ben-manes.caffeine:caffeine:3.2.2") implementation("com.github.Chronoken:EffectLib:071cc3c") - implementation("org.incendo:cloud-paper:2.0.0-beta.16") - implementation("org.incendo:cloud-minecraft-extras:2.0.0-beta.16") + implementation("org.incendo:cloud-paper:2.0.1") + implementation("org.incendo:cloud-minecraft-extras:2.0.1") implementation("org.incendo:cloud-processors-requirements:1.0.0-rc.1") implementation("org.bstats:bstats-bukkit:3.2.1") implementation("com.github.ezylang:EvalEx:3.6.2") @@ -19,6 +19,7 @@ dependencies { implementation(project(path: ":nms:shared")) implementation(project(path: ":nms:latest")) implementation(project(path: ":nms:v26_1_2")) + implementation(project(path: ":nms:v26_2")) compileOnly("com.github.retrooper:packetevents-spigot:2.11.2") compileOnly("net.dmulloy2:ProtocolLib:5.4.0") diff --git a/gradle.properties b/gradle.properties index 931e73225..c2c04921a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ -version = 4.0-Beta-19 +version = 4.0-Beta-20 groupId = com.nisovin.magicspells org.gradle.parallel=true diff --git a/nms/latest/build.gradle b/nms/latest/build.gradle index 229ded7f4..8ae01d961 100644 --- a/nms/latest/build.gradle +++ b/nms/latest/build.gradle @@ -3,6 +3,6 @@ plugins { } dependencies { - paperweight.paperDevBundle("26.2.build.+") + paperweight.paperDevBundle("26.3.build.+") compileOnly project(":nms:shared") } diff --git a/nms/latest/src/main/java/com/nisovin/magicspells/volatilecode/latest/VolatileCodeLatest.java b/nms/latest/src/main/java/com/nisovin/magicspells/volatilecode/latest/VolatileCodeLatest.java index 09a873933..88bd098ef 100644 --- a/nms/latest/src/main/java/com/nisovin/magicspells/volatilecode/latest/VolatileCodeLatest.java +++ b/nms/latest/src/main/java/com/nisovin/magicspells/volatilecode/latest/VolatileCodeLatest.java @@ -67,6 +67,7 @@ import net.minecraft.core.particles.ColorParticleOption; import net.minecraft.advancements.triggers.ImpossibleTrigger; import net.minecraft.world.entity.boss.enderdragon.EnderDragon; +import net.minecraft.network.protocol.game.ClientboundUpdateAdvancementsPacket.PositionedAdvancement; public class VolatileCodeLatest extends VolatileCodeHandle { @@ -220,9 +221,10 @@ public void sendToastEffect(Player receiver, ItemStack icon, AdvancementDisplay. } AdvancementHolder advancement = Advancement.Builder.advancement() - .display(iconNms, textNms, description, null, frame, true, false, true) + .display(iconNms, textNms, description, frame, true, false, true) .addCriterion("impossible", new Criterion<>(new ImpossibleTrigger(), new ImpossibleTrigger.TriggerInstance())) .build(TOAST_KEY); + AdvancementProgress progress = new AdvancementProgress(); progress.update(new AdvancementRequirements(List.of(List.of("impossible")))); progress.grantProgress("impossible"); @@ -230,14 +232,14 @@ public void sendToastEffect(Player receiver, ItemStack icon, AdvancementDisplay. ServerPlayer player = ((CraftPlayer) receiver).getHandle(); player.connection.send(new ClientboundUpdateAdvancementsPacket( false, - Collections.singleton(advancement), + List.of(new PositionedAdvancement(advancement, 0, 0)), Collections.emptySet(), Collections.singletonMap(TOAST_KEY, progress), true )); player.connection.send(new ClientboundUpdateAdvancementsPacket( false, - Collections.emptySet(), + Collections.emptyList(), Collections.singleton(TOAST_KEY), Collections.emptyMap(), true diff --git a/nms/latest/src/main/java/com/nisovin/magicspells/volatilecode/latest/VolatileGlowManagerLatest.java b/nms/latest/src/main/java/com/nisovin/magicspells/volatilecode/latest/VolatileGlowManagerLatest.java index a59ef0817..709aa9f35 100644 --- a/nms/latest/src/main/java/com/nisovin/magicspells/volatilecode/latest/VolatileGlowManagerLatest.java +++ b/nms/latest/src/main/java/com/nisovin/magicspells/volatilecode/latest/VolatileGlowManagerLatest.java @@ -61,7 +61,7 @@ public VolatileGlowManagerLatest(VolatileCodeHelper helper) { MethodType.methodType(void.class, String.class, int.class, Optional.class, Collection.class) ); } catch (Exception e) { - throw new RuntimeException("Encountered an error while initializing VolatileGlowManagerLatest", e); + throw new RuntimeException("Encountered an error while initializing " + getClass().getSimpleName(), e); } helper.registerEvents(this); diff --git a/nms/v26_1_2/src/main/java/com/nisovin/magicspells/volatilecode/v26_1_2/VolatileGlowManager_v26_1_2.java b/nms/v26_1_2/src/main/java/com/nisovin/magicspells/volatilecode/v26_1_2/VolatileGlowManager_v26_1_2.java index 44f290f97..adbf17545 100644 --- a/nms/v26_1_2/src/main/java/com/nisovin/magicspells/volatilecode/v26_1_2/VolatileGlowManager_v26_1_2.java +++ b/nms/v26_1_2/src/main/java/com/nisovin/magicspells/volatilecode/v26_1_2/VolatileGlowManager_v26_1_2.java @@ -61,7 +61,7 @@ public VolatileGlowManager_v26_1_2(VolatileCodeHelper helper) { MethodType.methodType(void.class, String.class, int.class, Optional.class, Collection.class) ); } catch (Exception e) { - throw new RuntimeException("Encountered an error while initializing VolatileGlowManagerLatest", e); + throw new RuntimeException("Encountered an error while initializing " + getClass().getSimpleName(), e); } helper.registerEvents(this); diff --git a/nms/v26_2/build.gradle b/nms/v26_2/build.gradle new file mode 100644 index 000000000..229ded7f4 --- /dev/null +++ b/nms/v26_2/build.gradle @@ -0,0 +1,8 @@ +plugins { + id "io.papermc.paperweight.userdev" +} + +dependencies { + paperweight.paperDevBundle("26.2.build.+") + compileOnly project(":nms:shared") +} diff --git a/nms/v26_2/src/main/java/com/nisovin/magicspells/volatilecode/v26_2/VolatileCode_v26_2.java b/nms/v26_2/src/main/java/com/nisovin/magicspells/volatilecode/v26_2/VolatileCode_v26_2.java new file mode 100644 index 000000000..8e07136fd --- /dev/null +++ b/nms/v26_2/src/main/java/com/nisovin/magicspells/volatilecode/v26_2/VolatileCode_v26_2.java @@ -0,0 +1,343 @@ +package com.nisovin.magicspells.volatilecode.v26_2; + +import java.util.*; +import java.lang.invoke.VarHandle; +import java.util.function.Consumer; +import java.lang.invoke.MethodType; +import java.util.function.Supplier; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; + +import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; + +import org.bukkit.World; +import org.bukkit.Bukkit; +import org.bukkit.Particle; +import org.bukkit.Location; +import org.bukkit.util.Vector; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.plugin.Plugin; +import org.bukkit.entity.LivingEntity; +import org.bukkit.inventory.ItemStack; +import org.bukkit.event.entity.ExplosionPrimeEvent; + +import org.bukkit.craftbukkit.CraftWorld; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.craftbukkit.entity.CraftEntity; +import org.bukkit.craftbukkit.entity.CraftPlayer; +import org.bukkit.craftbukkit.entity.CraftTNTPrimed; +import org.bukkit.craftbukkit.inventory.CraftItemStack; +import org.bukkit.craftbukkit.entity.CraftLivingEntity; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import net.kyori.adventure.key.Key; +import net.kyori.adventure.text.Component; + +import io.papermc.paper.adventure.PaperAdventure; +import io.papermc.paper.advancement.AdvancementDisplay; +import io.papermc.paper.threadedregions.EntityScheduler; +import io.papermc.paper.threadedregions.scheduler.ScheduledTask; +import io.papermc.paper.threadedregions.scheduler.FoliaGlobalRegionScheduler; + +import com.nisovin.magicspells.util.glow.GlowManager; +import com.nisovin.magicspells.volatilecode.VolatileCodeHandle; +import com.nisovin.magicspells.volatilecode.VolatileCodeHelper; + +import net.minecraft.nbt.Tag; +import net.minecraft.util.ARGB; +import net.minecraft.core.BlockPos; +import net.minecraft.advancements.*; +import net.minecraft.world.phys.Vec3; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.resources.Identifier; +import net.minecraft.server.MinecraftServer; +import net.minecraft.network.protocol.game.*; +import net.minecraft.world.entity.EntityTypes; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.item.PrimedTnt; +import net.minecraft.core.particles.ParticleTypes; +import net.minecraft.core.particles.ParticleOptions; +import net.minecraft.advancements.triggers.Criterion; +import net.minecraft.network.syncher.SynchedEntityData; +import net.minecraft.network.syncher.EntityDataAccessor; +import net.minecraft.core.particles.ColorParticleOption; +import net.minecraft.advancements.triggers.ImpossibleTrigger; +import net.minecraft.world.entity.boss.enderdragon.EnderDragon; + +public class VolatileCode_v26_2 extends VolatileCodeHandle { + + private final Identifier TOAST_KEY = Identifier.fromNamespaceAndPath("magicspells", "toast_effect"); + + private final EntityDataAccessor> DATA_EFFECT_PARTICLES; + private final EntityDataAccessor DATA_EFFECT_AMBIENCE_ID; + private final EntityDataAccessor DATA_SHARED_FLAGS_ID; + private final MethodHandle UPDATE_EFFECT_PARTICLES; + + private final Long2ObjectOpenHashMap> GLOBAL_REGION_TASKS; + private final VarHandle CURRENTLY_EXECUTING_HANDLE; + private final VarHandle ONE_TIME_DELAYED_HANDLE; + private final VarHandle RUN_HANDLE; + + @SuppressWarnings("unchecked") + public VolatileCode_v26_2(VolatileCodeHelper helper) throws Exception { + super(helper); + + MethodHandles.Lookup lookup = MethodHandles.lookup(); + + Class leClass = net.minecraft.world.entity.LivingEntity.class; + Class eClass = net.minecraft.world.entity.Entity.class; + + DATA_SHARED_FLAGS_ID = (EntityDataAccessor<@NotNull Byte>) MethodHandles.privateLookupIn(eClass, lookup) + .findStaticVarHandle(eClass, "DATA_SHARED_FLAGS_ID", EntityDataAccessor.class).get(); + + MethodHandles.Lookup leLookup = MethodHandles.privateLookupIn(leClass, lookup); + + DATA_EFFECT_PARTICLES = (EntityDataAccessor<@NotNull List>) leLookup + .findStaticVarHandle(leClass, "DATA_EFFECT_PARTICLES", EntityDataAccessor.class).get(); + + DATA_EFFECT_AMBIENCE_ID = (EntityDataAccessor<@NotNull Boolean>) leLookup + .findStaticVarHandle(leClass, "DATA_EFFECT_AMBIENCE_ID", EntityDataAccessor.class).get(); + + UPDATE_EFFECT_PARTICLES = leLookup.findVirtual(leClass, "updateSynchronizedMobEffectParticles", MethodType.methodType(void.class)); + + GLOBAL_REGION_TASKS = (Long2ObjectOpenHashMap>) MethodHandles.privateLookupIn(FoliaGlobalRegionScheduler.class, lookup) + .findVarHandle(FoliaGlobalRegionScheduler.class, "tasksByDeadline", Long2ObjectOpenHashMap.class) + .get(Bukkit.getGlobalRegionScheduler()); + + MethodHandles.Lookup esLookup = MethodHandles.privateLookupIn(EntityScheduler.class, lookup); + + CURRENTLY_EXECUTING_HANDLE = esLookup.findVarHandle(EntityScheduler.class, "currentlyExecuting", ArrayDeque.class); + ONE_TIME_DELAYED_HANDLE = esLookup.findVarHandle(EntityScheduler.class, "oneTimeDelayed", Long2ObjectOpenHashMap.class); + + Class scheduledTaskClass = esLookup.findClass("io.papermc.paper.threadedregions.EntityScheduler$ScheduledTask"); + RUN_HANDLE = esLookup.findVarHandle(scheduledTaskClass, "run", Consumer.class); + } + + @Override + public void addPotionGraphicalEffect(LivingEntity entity, int color, long duration) { + var nmsEntity = (((CraftLivingEntity) entity)).getHandle(); + SynchedEntityData entityData = nmsEntity.getEntityData(); + + entityData.set( + DATA_EFFECT_PARTICLES, + List.of(ColorParticleOption.create(ParticleTypes.ENTITY_EFFECT, ARGB.opaque(color))) + ); + + entityData.set(DATA_EFFECT_AMBIENCE_ID, false); + + if (duration <= 0) return; + helper.scheduleDelayedTask(() -> { + try { + UPDATE_EFFECT_PARTICLES.invoke(nmsEntity); + } catch (Throwable e) { + e.printStackTrace(); + } + }, duration); + } + + @Override + public void sendFakeSlotUpdate(Player player, int slot, ItemStack item) { + var nmsItem = CraftItemStack.asNMSCopy(item); + ClientboundContainerSetSlotPacket packet = new ClientboundContainerSetSlotPacket(0, 0, slot + 36, nmsItem); + + ((CraftPlayer) player).getHandle().connection.send(packet); + } + + @Override + public boolean simulateTnt(Location target, LivingEntity source, float explosionSize, boolean fire) { + ServerLevel world = ((CraftWorld) target.getWorld()).getHandle(); + var igniter = ((CraftLivingEntity) source).getHandle(); + + PrimedTnt tnt = new PrimedTnt(world, target.x(), target.y(), target.z(), igniter); + CraftTNTPrimed craftTNT = new CraftTNTPrimed((CraftServer) Bukkit.getServer(), tnt); + + return !new ExplosionPrimeEvent(craftTNT, explosionSize, fire).callEvent(); + } + + @Override + public void playDragonDeathEffect(Location location) { + EnderDragon dragon = new EnderDragon(EntityTypes.ENDER_DRAGON, ((CraftWorld) location.getWorld()).getHandle()); + dragon.setPos(location.x(), location.y(), location.z()); + + BlockPos pos = new BlockPos(location.getBlockX(), location.getBlockY(), location.getBlockZ()); + ClientboundAddEntityPacket addMobPacket = new ClientboundAddEntityPacket(dragon, 0, pos); + ClientboundEntityEventPacket entityEventPacket = new ClientboundEntityEventPacket(dragon, (byte) 3); + ClientboundRemoveEntitiesPacket removeEntityPacket = new ClientboundRemoveEntitiesPacket(dragon.getId()); + + List players = new ArrayList<>(); + for (Player player : location.getNearbyPlayers(64.0)) { + players.add(player); + ServerPlayer nmsPlayer = ((CraftPlayer) player).getHandle(); + nmsPlayer.connection.send(addMobPacket); + nmsPlayer.connection.send(entityEventPacket); + } + + helper.scheduleDelayedTask(() -> { + for (Player player : players) { + if (!player.isValid()) continue; + ((CraftPlayer) player).getHandle().connection.send(removeEntityPacket); + } + }, 200); + } + + @Override + public void setClientVelocity(Player player, Vector velocity) { + Vec3 pos = new Vec3(velocity.getX(), velocity.getY(), velocity.getZ()); + ClientboundSetEntityMotionPacket packet = new ClientboundSetEntityMotionPacket(player.getEntityId(), pos); + ((CraftPlayer) player).getHandle().connection.send(packet); + } + + @Override + public void playHurtSound(LivingEntity entity) { + var nmsEntity = ((CraftLivingEntity) entity).getHandle(); + var sound = nmsEntity.getHurtSound(nmsEntity.damageSources().generic()); + + if (sound == null || nmsEntity.isSilent()) return; + nmsEntity.level().playSound( + null, + nmsEntity.blockPosition(), + sound, + nmsEntity.getSoundSource(), + nmsEntity.getSoundVolume(), + nmsEntity.getVoicePitch() + ); + } + + @Override + public void sendToastEffect(Player receiver, ItemStack icon, AdvancementDisplay.Frame frameType, Component text) { + var iconNms = CraftItemStack.asTemplate(icon); + var textNms = PaperAdventure.asVanilla(text); + var description = PaperAdventure.asVanilla(Component.empty()); + AdvancementType frame; + try { + frame = AdvancementType.valueOf(frameType.name()); + } catch (IllegalArgumentException ignored) { + frame = AdvancementType.TASK; + } + + AdvancementHolder advancement = Advancement.Builder.advancement() + .display(iconNms, textNms, description, null, frame, true, false, true) + .addCriterion("impossible", new Criterion<>(new ImpossibleTrigger(), new ImpossibleTrigger.TriggerInstance())) + .build(TOAST_KEY); + AdvancementProgress progress = new AdvancementProgress(); + progress.update(new AdvancementRequirements(List.of(List.of("impossible")))); + progress.grantProgress("impossible"); + + ServerPlayer player = ((CraftPlayer) receiver).getHandle(); + player.connection.send(new ClientboundUpdateAdvancementsPacket( + false, + Collections.singleton(advancement), + Collections.emptySet(), + Collections.singletonMap(TOAST_KEY, progress), + true + )); + player.connection.send(new ClientboundUpdateAdvancementsPacket( + false, + Collections.emptySet(), + Collections.singleton(TOAST_KEY), + Collections.emptyMap(), + true + )); + } + + @Override + public byte getEntityMetadata(Entity entity) { + return ((CraftEntity) entity).getHandle().getEntityData().get(DATA_SHARED_FLAGS_ID); + } + + @Override + public Entity getEntityFromId(World world, int id) { + var entity = ((CraftWorld) world).getHandle().moonrise$getEntityLookup().get(id); + return entity == null ? null : entity.getBukkitEntity(); + } + + @Override + public GlowManager getGlowManager() { + return new VolatileGlowManager_v26_2(helper); + } + + @Override + public long countGlobalRegionSchedulerTasks() { + Plugin plugin = helper.getPlugin(); + + return GLOBAL_REGION_TASKS.values().stream() + .flatMap(List::stream) + .filter(task -> task.getOwningPlugin() == plugin) + .count(); + } + + @SuppressWarnings({"rawtypes", "unchecked"}) + @Override + public long countEntitySchedulerTasks() { + EntityScheduler.EntitySchedulerTickList entitySchedulerTickList = MinecraftServer.getServer().entitySchedulerTickList; + EntityScheduler[] schedulers = entitySchedulerTickList.getAllSchedulers(); + Plugin plugin = helper.getPlugin(); + + int count = 0; + for (EntityScheduler scheduler : schedulers) { + Long2ObjectOpenHashMap oneTimeDelayed = (Long2ObjectOpenHashMap) ONE_TIME_DELAYED_HANDLE.get(scheduler); + ArrayDeque currentlyExecuting = (ArrayDeque) CURRENTLY_EXECUTING_HANDLE.get(scheduler); + + for (List taskList : oneTimeDelayed.values()) { + for (Object taskObject : taskList) { + ScheduledTask task = (ScheduledTask) (Consumer) RUN_HANDLE.get(taskObject); + if (task.getOwningPlugin() == plugin) count++; + } + } + + for (Object taskObject : currentlyExecuting) { + ScheduledTask task = (ScheduledTask) (Consumer) RUN_HANDLE.get(taskObject); + if (task.getOwningPlugin() == plugin) count++; + } + } + + return count; + } + + @Override + public @Nullable Object getVolatileParticleData( + Particle particle, + Supplier geyserWaterBlocks, + Supplier geyserBurstImpulse + ) { + Class type = particle.getDataType(); + + if (type == Particle.Geyser.class) { + Integer waterBlocks = geyserWaterBlocks.get(); + if (waterBlocks == null) return null; + + return new Particle.Geyser(waterBlocks); + } + + if (type == Particle.GeyserBase.class) { + Integer waterBlocks = geyserWaterBlocks.get(); + Float burstImpulse = geyserBurstImpulse.get(); + if (waterBlocks == null || burstImpulse == null) return null; + + return new Particle.GeyserBase(waterBlocks, burstImpulse); + } + + return null; + } + + @Override + public @Nullable String getCommandStorageString(Key containerId, String tagKey) { + Tag tag = getCommandStorage(containerId).get(tagKey); + return tag == null ? null : tag.asString().orElse(tag.toString()); + } + + @Override + public @Nullable Double getCommandStorageDouble(Key containerId, String tagKey) { + return getCommandStorage(containerId).getDouble(tagKey).orElse(null); + } + + private CompoundTag getCommandStorage(Key id) { + MinecraftServer server = ((CraftServer) Bukkit.getServer()).getServer(); + return server.getCommandStorage().get(PaperAdventure.asVanilla(id)); + } + +} diff --git a/nms/v26_2/src/main/java/com/nisovin/magicspells/volatilecode/v26_2/VolatileGlowManager_v26_2.java b/nms/v26_2/src/main/java/com/nisovin/magicspells/volatilecode/v26_2/VolatileGlowManager_v26_2.java new file mode 100644 index 000000000..755054dca --- /dev/null +++ b/nms/v26_2/src/main/java/com/nisovin/magicspells/volatilecode/v26_2/VolatileGlowManager_v26_2.java @@ -0,0 +1,317 @@ +package com.nisovin.magicspells.volatilecode.v26_2; + +import org.jetbrains.annotations.NotNull; + +import java.util.*; +import java.lang.invoke.MethodType; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; + +import io.netty.channel.ChannelPromise; +import io.netty.channel.ChannelPipeline; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelOutboundHandlerAdapter; + +import net.minecraft.world.scores.TeamColor; +import net.minecraft.network.protocol.Packet; +import net.minecraft.world.scores.PlayerTeam; +import net.minecraft.world.scores.Scoreboard; +import net.minecraft.util.StringRepresentable; +import net.minecraft.world.scores.Team.Visibility; +import net.minecraft.world.scores.Team.CollisionRule; +import net.minecraft.network.syncher.SynchedEntityData; +import net.minecraft.network.syncher.EntityDataAccessor; +import net.minecraft.network.syncher.EntityDataSerializers; +import net.minecraft.server.network.ServerGamePacketListenerImpl; +import net.minecraft.network.protocol.game.ClientboundSetEntityDataPacket; +import net.minecraft.network.protocol.game.ClientboundSetPlayerTeamPacket; + +import org.bukkit.Bukkit; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.event.Listener; +import org.bukkit.scoreboard.Team; +import org.bukkit.event.EventHandler; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.craftbukkit.entity.CraftEntity; +import org.bukkit.craftbukkit.entity.CraftPlayer; +import org.bukkit.configuration.ConfigurationSection; + +import com.nisovin.magicspells.util.glow.LibsDisguiseHelper; +import com.nisovin.magicspells.volatilecode.VolatileCodeHelper; +import com.nisovin.magicspells.util.glow.PacketBasedGlowManager; + +public class VolatileGlowManager_v26_2 extends PacketBasedGlowManager, ClientboundSetEntityDataPacket, ClientboundSetPlayerTeamPacket> { + + private static final EntityDataAccessor DATA_SHARED_FLAGS_ID = new EntityDataAccessor<>(0, EntityDataSerializers.BYTE); + + private final Set> handled = Collections.synchronizedSet(Collections.newSetFromMap(new WeakHashMap<>())); + private final MethodHandle teamPacketHandle; + private final VolatileCodeHelper helper; + + public VolatileGlowManager_v26_2(VolatileCodeHelper helper) { + this.helper = helper; + + try { + teamPacketHandle = MethodHandles + .privateLookupIn(ClientboundSetPlayerTeamPacket.class, MethodHandles.lookup()) + .findConstructor( + ClientboundSetPlayerTeamPacket.class, + MethodType.methodType(void.class, String.class, int.class, Optional.class, Collection.class) + ); + } catch (Exception e) { + throw new RuntimeException("Encountered an error while initializing " + getClass().getSimpleName(), e); + } + + helper.registerEvents(this); + } + + @Override + public void load() { + super.load(); + + Bukkit.getOnlinePlayers().forEach(this::addGlowChannelHandler); + } + + @Override + public synchronized void unload() { + Bukkit.getOnlinePlayers().forEach(this::removeGlowChannelHandler); + + super.unload(); + handled.clear(); + } + + @Override + protected Collection createAddTeamPackets() { + List teamPackets = new ArrayList<>(); + + ConfigurationSection config = helper.getMainConfig(); + boolean seeFriendlyInvisibles = config.getBoolean("general.glow-spell-scoreboard-teams.see-friendly-invisibles", false); + CollisionRule collision = getStringOption("collision-rule", CollisionRule.ALWAYS, StringRepresentable.createNameLookup(CollisionRule.values()), config, helper::error); + Visibility visibility = getStringOption("name-tag-visibility", Visibility.ALWAYS, StringRepresentable.createNameLookup(Visibility.values()), config, helper::error); + + Scoreboard scoreboard = new Scoreboard(); + for (TeamColor color : TeamColor.values()) { + PlayerTeam team = new PlayerTeam(scoreboard, "magicspells:" + color.getSerializedName()); + team.setSeeFriendlyInvisibles(seeFriendlyInvisibles); + team.setNameTagVisibility(visibility); + team.setCollisionRule(collision); + team.setColor(Optional.of(color)); + + teamPackets.add(ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(team, true)); + } + + return teamPackets; + } + + @Override + protected Collection createRemoveTeamPackets() { + List packets = new ArrayList<>(); + + Scoreboard scoreboard = new Scoreboard(); + for (TeamColor color : TeamColor.values()) { + PlayerTeam team = new PlayerTeam(scoreboard, "magicspells:" + color.getSerializedName()); + packets.add(ClientboundSetPlayerTeamPacket.createRemovePacket(team)); + } + + return packets; + } + + @Override + protected ClientboundSetEntityDataPacket createEntityDataPacket(@NotNull Entity entity, boolean forceGlow) { + byte metadata = ((CraftEntity) entity).getHandle().getEntityData().get(DATA_SHARED_FLAGS_ID); + if (forceGlow) metadata |= 0x40; + + return new ClientboundSetEntityDataPacket( + entity.getEntityId(), + list(new SynchedEntityData.DataValue<>( + 0, + EntityDataSerializers.BYTE, + metadata + )) + ); + } + + @Override + protected Collection createJoinTeamPacket(@NotNull GlowData data) { + return data.lastScoreboardEntry().map(entry -> { + try { + return (ClientboundSetPlayerTeamPacket) teamPacketHandle.invoke( + "magicspells:" + data.color(), + 3, + Optional.empty(), + list(entry) + ); + } catch (Throwable e) { + throw new RuntimeException(e); + } + }); + } + + @Override + protected Collection createResetTeamPacket(org.bukkit.scoreboard.@NotNull Scoreboard scoreboard, @NotNull GlowData data) { + return data.lastScoreboardEntry().map(entry -> { + try { + Team team = scoreboard.getEntryTeam(entry); + if (team != null) { + return (ClientboundSetPlayerTeamPacket) teamPacketHandle.invoke( + team.getName(), + 3, + Optional.empty(), + list(entry) + ); + } + + return (ClientboundSetPlayerTeamPacket) teamPacketHandle.invoke( + "magicspells:" + data.color(), + 4, + Optional.empty(), + list(entry) + ); + } catch (Throwable e) { + throw new RuntimeException(e); + } + }); + } + + @Override + protected void sendPacket(@NotNull Player player, @NotNull Packet packet) { + if (!(packet instanceof ClientboundSetPlayerTeamPacket teamPacket) || teamPacket.getTeamAction() == null) + handled.add(packet); + + ServerGamePacketListenerImpl connection = ((CraftPlayer) player).getHandle().connection; + connection.send(packet); + } + + @Override + protected void registerEvents(Listener listener) { + helper.registerEvents(listener); + } + + @Override + protected void cancelTask(int taskId) { + helper.cancelTask(taskId); + } + + @Override + public int scheduleDelayedTask(Runnable runnable, long delay) { + return helper.scheduleDelayedTask(runnable, delay); + } + + private void addGlowChannelHandler(Player player) { + ChannelPipeline pipeline = ((CraftPlayer) player).getHandle().connection.connection.channel.pipeline(); + pipeline.addBefore("unbundler", "magicspells:glow_channel_handler", new GlowChannelHandler(player)); + } + + private void removeGlowChannelHandler(Player player) { + ChannelPipeline pipeline = ((CraftPlayer) player).getHandle().connection.connection.channel.pipeline(); + + if (pipeline.get("magicspells:glow_channel_handler") != null) + pipeline.remove("magicspells:glow_channel_handler"); + } + + private List list(T element) { + List list = new ArrayList<>(1); + list.add(element); + + return list; + } + + @Override + @EventHandler + public void onPlayerJoin(PlayerJoinEvent event) { + super.onPlayerJoin(event); + addGlowChannelHandler(event.getPlayer()); + } + + @EventHandler + public void onPlayerQuit(PlayerQuitEvent event) { + removeGlowChannelHandler(event.getPlayer()); + } + + private class GlowChannelHandler extends ChannelOutboundHandlerAdapter { + + private final Player player; + + private GlowChannelHandler(Player player) { + this.player = player; + } + + @Override + public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { + if (msg instanceof Packet p && handled.contains(p)) { + super.write(ctx, msg, promise); + return; + } + + synchronized (VolatileGlowManager_v26_2.this) { + if (glows.isEmpty() && perPlayerGlows.isEmpty()) { + super.write(ctx, msg, promise); + return; + } + } + + switch (msg) { + case ClientboundSetEntityDataPacket packet -> handleEntityData(packet); + case ClientboundSetPlayerTeamPacket packet -> { + msg = handleTeamPacket(packet); + if (msg == null) return; + } + default -> {} + } + + super.write(ctx, msg, promise); + } + + private void handleEntityData(ClientboundSetEntityDataPacket packet) { + List> packedItems = packet.packedItems(); + if (packedItems.isEmpty()) return; + + SynchedEntityData.DataValue item = packedItems.getFirst(); + if (item.id() != 0) return; + + byte flags = (byte) item.value(); + if ((flags & 0x40) > 0) return; + + UUID uuid; + if (!libsDisguisesLoaded || packet.id() != LibsDisguiseHelper.getSelfDisguiseId()) { + var entity = ((CraftPlayer) player).getHandle().level().moonrise$getEntityLookup().get(packet.id()); + if (entity == null) return; + + uuid = entity.getUUID(); + } else uuid = player.getUniqueId(); + + GlowData data = getGlowData(player.getUniqueId(), uuid); + if (data == null) return; + + flags |= 0x40; + packedItems.set(0, new SynchedEntityData.DataValue<>(0, EntityDataSerializers.BYTE, flags)); + } + + private ClientboundSetPlayerTeamPacket handleTeamPacket(ClientboundSetPlayerTeamPacket packet) { + ClientboundSetPlayerTeamPacket.Action playerAction = packet.getPlayerAction(); + if (playerAction == null || packet.getTeamAction() != null) return packet; + + Collection entries = packet.getPlayers(); + if (entries.isEmpty()) return packet; + + Collection filtered = filterTeamEntries(player, entries); + if (filtered == null) return packet; + if (filtered.isEmpty()) return null; + + try { + return (ClientboundSetPlayerTeamPacket) teamPacketHandle.invoke( + packet.getName(), + playerAction == ClientboundSetPlayerTeamPacket.Action.ADD ? 3 : 4, + packet.getParameters(), + filtered + ); + } catch (Throwable e) { + throw new RuntimeException(e); + } + } + + } + +} diff --git a/settings.gradle.kts b/settings.gradle.kts index da0f9c09a..30c6bc5c0 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -10,6 +10,7 @@ include("towny") include(":nms:shared") include(":nms:latest") include(":nms:v26_1_2") +include(":nms:v26_2") pluginManagement { repositories {