diff --git a/core/build.gradle b/core/build.gradle index 038c8ed26..03609ef43 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -10,6 +10,7 @@ dependencies { shadow(project(path: ":nms:shared", configuration: "apiElements")) shadow(project(path: ":nms:latest")) { transitive = false } + shadow(project(path: ":nms:v1_21")) { transitive = false } implementation(group: "com.comphenix.protocol", name: "ProtocolLib", version: "5.1.0") { transitive = false } implementation(group: "com.github.libraryaddict", name: "LibsDisguises", version: "v10.0.25") { transitive = false } diff --git a/core/src/main/java/com/nisovin/magicspells/util/AttributeUtil.java b/core/src/main/java/com/nisovin/magicspells/util/AttributeUtil.java index e74687cb1..64fe36adb 100644 --- a/core/src/main/java/com/nisovin/magicspells/util/AttributeUtil.java +++ b/core/src/main/java/com/nisovin/magicspells/util/AttributeUtil.java @@ -46,6 +46,53 @@ public class AttributeUtil { attributeNameMap.put(rep, attribute); attributeNameMap.put(rep.substring(rep.indexOf('.') + 1), attribute); } + + loadLegacyAttributeNames(); + } + + private static void loadLegacyAttributeNames() { + Map legacy = new HashMap<>(); + legacy.put("generic.max_health", Attribute.GENERIC_MAX_HEALTH); + legacy.put("generic.follow_range", Attribute.GENERIC_FOLLOW_RANGE); + legacy.put("generic.knockback_resistance", Attribute.GENERIC_KNOCKBACK_RESISTANCE); + legacy.put("generic.movement_speed", Attribute.GENERIC_MOVEMENT_SPEED); + legacy.put("generic.flying_speed", Attribute.GENERIC_FLYING_SPEED); + legacy.put("generic.attack_damage", Attribute.GENERIC_ATTACK_DAMAGE); + legacy.put("generic.attack_knockback", Attribute.GENERIC_ATTACK_KNOCKBACK); + legacy.put("generic.attack_speed", Attribute.GENERIC_ATTACK_SPEED); + legacy.put("generic.armor", Attribute.GENERIC_ARMOR); + legacy.put("generic.armor_toughness", Attribute.GENERIC_ARMOR_TOUGHNESS); + legacy.put("generic.fall_damage_multiplier", Attribute.GENERIC_FALL_DAMAGE_MULTIPLIER); + legacy.put("generic.luck", Attribute.GENERIC_LUCK); + legacy.put("generic.max_absorption", Attribute.GENERIC_MAX_ABSORPTION); + legacy.put("generic.safe_fall_distance", Attribute.GENERIC_SAFE_FALL_DISTANCE); + legacy.put("generic.scale", Attribute.GENERIC_SCALE); + legacy.put("generic.step_height", Attribute.GENERIC_STEP_HEIGHT); + legacy.put("generic.gravity", Attribute.GENERIC_GRAVITY); + legacy.put("generic.jump_strength", Attribute.GENERIC_JUMP_STRENGTH); + legacy.put("generic.burning_time", Attribute.GENERIC_BURNING_TIME); + legacy.put("generic.explosion_knockback_resistance", Attribute.GENERIC_EXPLOSION_KNOCKBACK_RESISTANCE); + legacy.put("generic.movement_efficiency", Attribute.GENERIC_MOVEMENT_EFFICIENCY); + legacy.put("generic.oxygen_bonus", Attribute.GENERIC_OXYGEN_BONUS); + legacy.put("generic.water_movement_efficiency", Attribute.GENERIC_WATER_MOVEMENT_EFFICIENCY); + legacy.put("player.block_interaction_range", Attribute.PLAYER_BLOCK_INTERACTION_RANGE); + legacy.put("player.entity_interaction_range", Attribute.PLAYER_ENTITY_INTERACTION_RANGE); + legacy.put("player.block_break_speed", Attribute.PLAYER_BLOCK_BREAK_SPEED); + legacy.put("player.mining_efficiency", Attribute.PLAYER_MINING_EFFICIENCY); + legacy.put("player.sneaking_speed", Attribute.PLAYER_SNEAKING_SPEED); + legacy.put("player.submerged_mining_speed", Attribute.PLAYER_SUBMERGED_MINING_SPEED); + legacy.put("player.sweeping_damage_ratio", Attribute.PLAYER_SWEEPING_DAMAGE_RATIO); + legacy.put("zombie.spawn_reinforcements", Attribute.ZOMBIE_SPAWN_REINFORCEMENTS); + + attributeNameMap.putAll(legacy); + + for (Map.Entry entry : legacy.entrySet()) { + String legacyKey = entry.getKey(); + Attribute attribute = entry.getValue(); + + attributeNameMap.put(legacyKey.replaceFirst(".", "_"), attribute); + attributeNameMap.put(legacyKey.replaceAll("[._]", ""), attribute); + } } public static Attribute getAttribute(String attribute) { diff --git a/core/src/main/java/com/nisovin/magicspells/volatilecode/ManagerVolatile.java b/core/src/main/java/com/nisovin/magicspells/volatilecode/ManagerVolatile.java index 3c008ab06..b08a69490 100644 --- a/core/src/main/java/com/nisovin/magicspells/volatilecode/ManagerVolatile.java +++ b/core/src/main/java/com/nisovin/magicspells/volatilecode/ManagerVolatile.java @@ -1,5 +1,7 @@ package com.nisovin.magicspells.volatilecode; +import java.util.Map; + import org.bukkit.Bukkit; import com.nisovin.magicspells.MagicSpells; @@ -7,6 +9,10 @@ public class ManagerVolatile { + private static final Map COMPATIBLE_VERSIONS = Map.of( + "1.21.1", "1.21" + ); + private static final VolatileCodeHelper helper = new VolatileCodeHelper() { @Override @@ -25,7 +31,8 @@ public static VolatileCodeHandle constructVolatileCodeHandler() { VolatileCodeHandle handle; try { String mcVersion = Bukkit.getMinecraftVersion(); - String version = "v" + mcVersion.replace(".", "_"); + String convertedVersion = COMPATIBLE_VERSIONS.getOrDefault(mcVersion, mcVersion); + String version = "v" + convertedVersion.replace(".", "_"); Class volatileCode = Class.forName("com.nisovin.magicspells.volatilecode." + version + ".VolatileCode_" + version); handle = (VolatileCodeHandle) volatileCode.getConstructor(VolatileCodeHelper.class).newInstance(helper); diff --git a/nms/latest/build.gradle b/nms/latest/build.gradle index 13f90fe9e..fae901db9 100644 --- a/nms/latest/build.gradle +++ b/nms/latest/build.gradle @@ -1,8 +1,8 @@ plugins { - id "io.papermc.paperweight.userdev" version "1.7.3" + id "io.papermc.paperweight.userdev" version "1.7.4" } dependencies { - paperweightDevelopmentBundle("io.papermc.paper:dev-bundle:1.21-R0.1-SNAPSHOT") + paperweightDevelopmentBundle("io.papermc.paper:dev-bundle:1.21.3-R0.1-SNAPSHOT") implementation 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 55f569c85..05e6a6a3a 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 @@ -29,9 +29,9 @@ import com.nisovin.magicspells.volatilecode.VolatileCodeHandle; import com.nisovin.magicspells.volatilecode.VolatileCodeHelper; +import net.minecraft.util.ARGB; import net.minecraft.core.BlockPos; import net.minecraft.advancements.*; -import net.minecraft.util.FastColor; import net.minecraft.world.phys.Vec3; import net.minecraft.world.entity.EntityType; import net.minecraft.network.protocol.game.*; @@ -83,7 +83,7 @@ public void addPotionGraphicalEffect(LivingEntity entity, int color, long durati entityData.set( DATA_EFFECT_PARTICLES, - List.of(ColorParticleOption.create(ParticleTypes.ENTITY_EFFECT, FastColor.ARGB32.color(255, color))) + List.of(ColorParticleOption.create(ParticleTypes.ENTITY_EFFECT, ARGB.opaque(color))) ); entityData.set(DATA_EFFECT_AMBIENCE_ID, false); diff --git a/nms/v1_21/build.gradle b/nms/v1_21/build.gradle new file mode 100644 index 000000000..6f19011b3 --- /dev/null +++ b/nms/v1_21/build.gradle @@ -0,0 +1,8 @@ +plugins { + id "io.papermc.paperweight.userdev" version "1.7.4" +} + +dependencies { + paperweightDevelopmentBundle("io.papermc.paper:dev-bundle:1.21-R0.1-SNAPSHOT") + implementation project(":nms:shared") +} diff --git a/nms/v1_21/src/main/java/com/nisovin/magicspells/volatilecode/v1_21/VolatileCode_v1_21.java b/nms/v1_21/src/main/java/com/nisovin/magicspells/volatilecode/v1_21/VolatileCode_v1_21.java new file mode 100644 index 000000000..cd2cccde4 --- /dev/null +++ b/nms/v1_21/src/main/java/com/nisovin/magicspells/volatilecode/v1_21/VolatileCode_v1_21.java @@ -0,0 +1,229 @@ +package com.nisovin.magicspells.volatilecode.v1_21; + +import java.util.*; +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.util.Vector; +import org.bukkit.entity.Player; +import org.bukkit.entity.LivingEntity; +import org.bukkit.attribute.Attribute; +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.CraftPlayer; +import org.bukkit.craftbukkit.entity.CraftTNTPrimed; +import org.bukkit.craftbukkit.inventory.CraftItemStack; +import org.bukkit.craftbukkit.entity.CraftLivingEntity; + +import net.kyori.adventure.text.Component; + +import io.papermc.paper.util.MCUtil; +import io.papermc.paper.adventure.PaperAdventure; +import io.papermc.paper.advancement.AdvancementDisplay; + +import com.nisovin.magicspells.volatilecode.VolatileCodeHandle; +import com.nisovin.magicspells.volatilecode.VolatileCodeHelper; + +import net.minecraft.core.BlockPos; +import net.minecraft.advancements.*; +import net.minecraft.util.FastColor; +import net.minecraft.world.phys.Vec3; +import net.minecraft.world.entity.EntityType; +import net.minecraft.network.protocol.game.*; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.item.PrimedTnt; +import net.minecraft.core.particles.ParticleTypes; +import net.minecraft.core.particles.ParticleOptions; +import net.minecraft.network.syncher.SynchedEntityData; +import net.minecraft.network.syncher.EntityDataAccessor; +import net.minecraft.core.particles.ColorParticleOption; +import net.minecraft.advancements.critereon.ImpossibleTrigger; +import net.minecraft.world.entity.boss.enderdragon.EnderDragon; +import net.minecraft.network.protocol.common.ClientboundCustomPayloadPacket; +import net.minecraft.network.protocol.common.custom.GameTestAddMarkerDebugPayload; +import net.minecraft.network.protocol.common.custom.GameTestClearMarkersDebugPayload; + +public class VolatileCode_v1_21 extends VolatileCodeHandle { + + private final ResourceLocation TOAST_KEY = ResourceLocation.fromNamespaceAndPath("magicspells", "toast_effect"); + + private final EntityDataAccessor> DATA_EFFECT_PARTICLES; + private final EntityDataAccessor DATA_EFFECT_AMBIENCE_ID; + private final Method UPDATE_EFFECT_PARTICLES; + + @SuppressWarnings("unchecked") + public VolatileCode_v1_21(VolatileCodeHelper helper) throws Exception { + super(helper); + + Class nmsEntityClass = net.minecraft.world.entity.LivingEntity.class; + + Field dataEffectParticlesField = nmsEntityClass.getDeclaredField("DATA_EFFECT_PARTICLES"); + dataEffectParticlesField.setAccessible(true); + DATA_EFFECT_PARTICLES = (EntityDataAccessor>) dataEffectParticlesField.get(null); + + Field dataEffectAmbienceIdField = nmsEntityClass.getDeclaredField("DATA_EFFECT_AMBIENCE_ID"); + dataEffectAmbienceIdField.setAccessible(true); + DATA_EFFECT_AMBIENCE_ID = (EntityDataAccessor) dataEffectAmbienceIdField.get(null); + + UPDATE_EFFECT_PARTICLES = nmsEntityClass.getDeclaredMethod("updateSynchronizedMobEffectParticles"); + UPDATE_EFFECT_PARTICLES.setAccessible(true); + } + + @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, FastColor.ARGB32.color(255, color))) + ); + + entityData.set(DATA_EFFECT_AMBIENCE_ID, false); + + if (duration <= 0) return; + helper.scheduleDelayedTask(() -> { + try { + UPDATE_EFFECT_PARTICLES.invoke(nmsEntity); + } catch (Exception 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(EntityType.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 startAutoSpinAttack(Player player, int ticks) { + ((CraftPlayer) player).getHandle().startAutoSpinAttack(ticks, 0f, net.minecraft.world.item.ItemStack.EMPTY); + } + + @Override + public void playHurtSound(LivingEntity entity) { + var nmsEntity = ((CraftLivingEntity) entity).getHandle(); + + if (nmsEntity.isSilent()) return; + nmsEntity.level().playSound( + null, + nmsEntity.blockPosition(), + nmsEntity.getHurtSound0(nmsEntity.damageSources().generic()), + nmsEntity.getSoundSource(), + nmsEntity.getSoundVolume(), + nmsEntity.getVoicePitch() + ); + } + + @Override + public void sendToastEffect(Player receiver, ItemStack icon, AdvancementDisplay.Frame frameType, Component text) { + var iconNms = CraftItemStack.asNMSCopy(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) + )); + player.connection.send(new ClientboundUpdateAdvancementsPacket( + false, + Collections.emptySet(), + Collections.singleton(TOAST_KEY), + Collections.emptyMap() + )); + } + + @Override + public void sendStatusUpdate(Player player, double health, int food, float saturation) { + double displayedHealth = (float) health; + if (player.isHealthScaled()) { + displayedHealth = player.getHealth() / player.getAttribute(Attribute.GENERIC_MAX_HEALTH).getValue() * player.getHealthScale(); + } + + ((CraftPlayer) player).getHandle().connection.send(new ClientboundSetHealthPacket((float) displayedHealth, food, saturation)); + } + + @Override + public void addGameTestMarker(Player player, Location location, int color, String name, int lifetime) { + GameTestAddMarkerDebugPayload payload = new GameTestAddMarkerDebugPayload(MCUtil.toBlockPosition(location), color, name, lifetime); + ((CraftPlayer) player).getHandle().connection.send(new ClientboundCustomPayloadPacket(payload)); + } + + @Override + public void clearGameTestMarkers(Player player) { + GameTestClearMarkersDebugPayload payload = new GameTestClearMarkersDebugPayload(); + ((CraftPlayer) player).getHandle().connection.send(new ClientboundCustomPayloadPacket(payload)); + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 457725184..440418e23 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -9,6 +9,7 @@ include("towny") include(":nms:shared") include(":nms:latest") +include(":nms:v1_21") startParameter.isParallelProjectExecutionEnabled = true