disguise api

and generate maps to allow entity metadata syncher validation
This commit is contained in:
Yannick Lamprecht 2024-03-10 22:17:48 +01:00
parent 85bfdc09bc
commit f3e843a5be
14 changed files with 4656 additions and 0 deletions

View File

@ -8,3 +8,4 @@ updatingMinecraft=false
org.gradle.caching=true org.gradle.caching=true
org.gradle.parallel=true org.gradle.parallel=true
org.gradle.vfs.watch=false org.gradle.vfs.watch=false
org.gradle.jvmargs=-Xmx4096m

View File

@ -0,0 +1,2 @@
// Uncomment to enable the 'paper-server-generator' project
// include(":paper-server-generator")

View File

@ -0,0 +1,36 @@
import io.papermc.paperweight.PaperweightSourceGeneratorHelper
import io.papermc.paperweight.extension.PaperweightSourceGeneratorExt
plugins {
java
}
plugins.apply(PaperweightSourceGeneratorHelper::class)
extensions.configure(PaperweightSourceGeneratorExt::class) {
atFile.set(projectDir.toPath().resolve("wideners.at").toFile())
}
dependencies {
implementation("com.squareup:javapoet:1.13.0")
implementation(project(":paper-api"))
implementation(project(":paper-api-generator"))
implementation("io.github.classgraph:classgraph:4.8.112")
implementation("org.jetbrains:annotations:24.0.1")
testImplementation("org.junit.jupiter:junit-jupiter:5.10.2")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}
tasks.register<JavaExec>("generate") {
dependsOn(tasks.check)
mainClass.set("io.papermc.generator.Main")
classpath(sourceSets.main.map { it.runtimeClasspath })
args(projectDir.toPath().resolve("generated").toString())
}
tasks.test {
useJUnitPlatform()
}
group = "io.papermc.paper"
version = "1.0-SNAPSHOT"

View File

@ -0,0 +1,309 @@
package io.papermc.paper.entity.meta;
import io.papermc.paper.generated.GeneratedFrom;
import java.util.HashMap;
import java.util.Map;
import net.minecraft.world.entity.AreaEffectCloud;
import net.minecraft.world.entity.Display;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.ExperienceOrb;
import net.minecraft.world.entity.GlowSquid;
import net.minecraft.world.entity.Interaction;
import net.minecraft.world.entity.LightningBolt;
import net.minecraft.world.entity.Marker;
import net.minecraft.world.entity.OminousItemSpawner;
import net.minecraft.world.entity.ambient.Bat;
import net.minecraft.world.entity.animal.Bee;
import net.minecraft.world.entity.animal.Cat;
import net.minecraft.world.entity.animal.Chicken;
import net.minecraft.world.entity.animal.Cod;
import net.minecraft.world.entity.animal.Cow;
import net.minecraft.world.entity.animal.Dolphin;
import net.minecraft.world.entity.animal.Fox;
import net.minecraft.world.entity.animal.IronGolem;
import net.minecraft.world.entity.animal.MushroomCow;
import net.minecraft.world.entity.animal.Ocelot;
import net.minecraft.world.entity.animal.Panda;
import net.minecraft.world.entity.animal.Parrot;
import net.minecraft.world.entity.animal.Pig;
import net.minecraft.world.entity.animal.PolarBear;
import net.minecraft.world.entity.animal.Pufferfish;
import net.minecraft.world.entity.animal.Rabbit;
import net.minecraft.world.entity.animal.Salmon;
import net.minecraft.world.entity.animal.Sheep;
import net.minecraft.world.entity.animal.SnowGolem;
import net.minecraft.world.entity.animal.Squid;
import net.minecraft.world.entity.animal.TropicalFish;
import net.minecraft.world.entity.animal.Turtle;
import net.minecraft.world.entity.animal.Wolf;
import net.minecraft.world.entity.animal.allay.Allay;
import net.minecraft.world.entity.animal.armadillo.Armadillo;
import net.minecraft.world.entity.animal.axolotl.Axolotl;
import net.minecraft.world.entity.animal.camel.Camel;
import net.minecraft.world.entity.animal.frog.Frog;
import net.minecraft.world.entity.animal.frog.Tadpole;
import net.minecraft.world.entity.animal.goat.Goat;
import net.minecraft.world.entity.animal.horse.Donkey;
import net.minecraft.world.entity.animal.horse.Horse;
import net.minecraft.world.entity.animal.horse.Llama;
import net.minecraft.world.entity.animal.horse.Mule;
import net.minecraft.world.entity.animal.horse.SkeletonHorse;
import net.minecraft.world.entity.animal.horse.TraderLlama;
import net.minecraft.world.entity.animal.horse.ZombieHorse;
import net.minecraft.world.entity.animal.sniffer.Sniffer;
import net.minecraft.world.entity.boss.enderdragon.EndCrystal;
import net.minecraft.world.entity.boss.enderdragon.EnderDragon;
import net.minecraft.world.entity.boss.wither.WitherBoss;
import net.minecraft.world.entity.decoration.ArmorStand;
import net.minecraft.world.entity.decoration.GlowItemFrame;
import net.minecraft.world.entity.decoration.ItemFrame;
import net.minecraft.world.entity.decoration.LeashFenceKnotEntity;
import net.minecraft.world.entity.decoration.Painting;
import net.minecraft.world.entity.item.FallingBlockEntity;
import net.minecraft.world.entity.item.ItemEntity;
import net.minecraft.world.entity.item.PrimedTnt;
import net.minecraft.world.entity.monster.Blaze;
import net.minecraft.world.entity.monster.Bogged;
import net.minecraft.world.entity.monster.CaveSpider;
import net.minecraft.world.entity.monster.Creeper;
import net.minecraft.world.entity.monster.Drowned;
import net.minecraft.world.entity.monster.ElderGuardian;
import net.minecraft.world.entity.monster.EnderMan;
import net.minecraft.world.entity.monster.Endermite;
import net.minecraft.world.entity.monster.Evoker;
import net.minecraft.world.entity.monster.Ghast;
import net.minecraft.world.entity.monster.Giant;
import net.minecraft.world.entity.monster.Guardian;
import net.minecraft.world.entity.monster.Husk;
import net.minecraft.world.entity.monster.Illusioner;
import net.minecraft.world.entity.monster.MagmaCube;
import net.minecraft.world.entity.monster.Phantom;
import net.minecraft.world.entity.monster.Pillager;
import net.minecraft.world.entity.monster.Ravager;
import net.minecraft.world.entity.monster.Shulker;
import net.minecraft.world.entity.monster.Silverfish;
import net.minecraft.world.entity.monster.Skeleton;
import net.minecraft.world.entity.monster.Slime;
import net.minecraft.world.entity.monster.Spider;
import net.minecraft.world.entity.monster.Stray;
import net.minecraft.world.entity.monster.Strider;
import net.minecraft.world.entity.monster.Vex;
import net.minecraft.world.entity.monster.Vindicator;
import net.minecraft.world.entity.monster.Witch;
import net.minecraft.world.entity.monster.WitherSkeleton;
import net.minecraft.world.entity.monster.Zoglin;
import net.minecraft.world.entity.monster.Zombie;
import net.minecraft.world.entity.monster.ZombieVillager;
import net.minecraft.world.entity.monster.ZombifiedPiglin;
import net.minecraft.world.entity.monster.breeze.Breeze;
import net.minecraft.world.entity.monster.creaking.Creaking;
import net.minecraft.world.entity.monster.creaking.CreakingTransient;
import net.minecraft.world.entity.monster.hoglin.Hoglin;
import net.minecraft.world.entity.monster.piglin.Piglin;
import net.minecraft.world.entity.monster.piglin.PiglinBrute;
import net.minecraft.world.entity.monster.warden.Warden;
import net.minecraft.world.entity.npc.Villager;
import net.minecraft.world.entity.npc.WanderingTrader;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.entity.projectile.Arrow;
import net.minecraft.world.entity.projectile.DragonFireball;
import net.minecraft.world.entity.projectile.EvokerFangs;
import net.minecraft.world.entity.projectile.EyeOfEnder;
import net.minecraft.world.entity.projectile.FireworkRocketEntity;
import net.minecraft.world.entity.projectile.FishingHook;
import net.minecraft.world.entity.projectile.LargeFireball;
import net.minecraft.world.entity.projectile.LlamaSpit;
import net.minecraft.world.entity.projectile.ShulkerBullet;
import net.minecraft.world.entity.projectile.SmallFireball;
import net.minecraft.world.entity.projectile.Snowball;
import net.minecraft.world.entity.projectile.SpectralArrow;
import net.minecraft.world.entity.projectile.ThrownEgg;
import net.minecraft.world.entity.projectile.ThrownEnderpearl;
import net.minecraft.world.entity.projectile.ThrownExperienceBottle;
import net.minecraft.world.entity.projectile.ThrownPotion;
import net.minecraft.world.entity.projectile.ThrownTrident;
import net.minecraft.world.entity.projectile.WitherSkull;
import net.minecraft.world.entity.projectile.windcharge.BreezeWindCharge;
import net.minecraft.world.entity.projectile.windcharge.WindCharge;
import net.minecraft.world.entity.vehicle.Boat;
import net.minecraft.world.entity.vehicle.ChestBoat;
import net.minecraft.world.entity.vehicle.ChestRaft;
import net.minecraft.world.entity.vehicle.Minecart;
import net.minecraft.world.entity.vehicle.MinecartChest;
import net.minecraft.world.entity.vehicle.MinecartCommandBlock;
import net.minecraft.world.entity.vehicle.MinecartFurnace;
import net.minecraft.world.entity.vehicle.MinecartHopper;
import net.minecraft.world.entity.vehicle.MinecartSpawner;
import net.minecraft.world.entity.vehicle.MinecartTNT;
import net.minecraft.world.entity.vehicle.Raft;
import org.bukkit.entity.EntityType;
import org.jspecify.annotations.NullMarked;
@SuppressWarnings({
"unused",
"SpellCheckingInspection"
})
@GeneratedFrom("1.21.3")
@NullMarked
public final class EntityTypeToEntityClass {
private static final Map<EntityType, Class<? extends Entity>> ENTITY_TYPE_TO_CLASS = initialize();
private static final Map<EntityType, Class<? extends Entity>> initialize() {
Map<EntityType, Class<? extends Entity>> result = new HashMap<>();
result.put(EntityType.ACACIA_BOAT, Boat.class);
result.put(EntityType.ACACIA_CHEST_BOAT, ChestBoat.class);
result.put(EntityType.ALLAY, Allay.class);
result.put(EntityType.AREA_EFFECT_CLOUD, AreaEffectCloud.class);
result.put(EntityType.ARMADILLO, Armadillo.class);
result.put(EntityType.ARMOR_STAND, ArmorStand.class);
result.put(EntityType.ARROW, Arrow.class);
result.put(EntityType.AXOLOTL, Axolotl.class);
result.put(EntityType.BAMBOO_CHEST_RAFT, ChestRaft.class);
result.put(EntityType.BAMBOO_RAFT, Raft.class);
result.put(EntityType.BAT, Bat.class);
result.put(EntityType.BEE, Bee.class);
result.put(EntityType.BIRCH_BOAT, Boat.class);
result.put(EntityType.BIRCH_CHEST_BOAT, ChestBoat.class);
result.put(EntityType.BLAZE, Blaze.class);
result.put(EntityType.BLOCK_DISPLAY, Display.BlockDisplay.class);
result.put(EntityType.BOGGED, Bogged.class);
result.put(EntityType.BREEZE, Breeze.class);
result.put(EntityType.BREEZE_WIND_CHARGE, BreezeWindCharge.class);
result.put(EntityType.CAMEL, Camel.class);
result.put(EntityType.CAT, Cat.class);
result.put(EntityType.CAVE_SPIDER, CaveSpider.class);
result.put(EntityType.CHERRY_BOAT, Boat.class);
result.put(EntityType.CHERRY_CHEST_BOAT, ChestBoat.class);
result.put(EntityType.CHEST_MINECART, MinecartChest.class);
result.put(EntityType.CHICKEN, Chicken.class);
result.put(EntityType.COD, Cod.class);
result.put(EntityType.COMMAND_BLOCK_MINECART, MinecartCommandBlock.class);
result.put(EntityType.COW, Cow.class);
result.put(EntityType.CREAKING, Creaking.class);
result.put(EntityType.CREAKING_TRANSIENT, CreakingTransient.class);
result.put(EntityType.CREEPER, Creeper.class);
result.put(EntityType.DARK_OAK_BOAT, Boat.class);
result.put(EntityType.DARK_OAK_CHEST_BOAT, ChestBoat.class);
result.put(EntityType.DOLPHIN, Dolphin.class);
result.put(EntityType.DONKEY, Donkey.class);
result.put(EntityType.DRAGON_FIREBALL, DragonFireball.class);
result.put(EntityType.DROWNED, Drowned.class);
result.put(EntityType.EGG, ThrownEgg.class);
result.put(EntityType.ELDER_GUARDIAN, ElderGuardian.class);
result.put(EntityType.ENDERMAN, EnderMan.class);
result.put(EntityType.ENDERMITE, Endermite.class);
result.put(EntityType.ENDER_DRAGON, EnderDragon.class);
result.put(EntityType.ENDER_PEARL, ThrownEnderpearl.class);
result.put(EntityType.END_CRYSTAL, EndCrystal.class);
result.put(EntityType.EVOKER, Evoker.class);
result.put(EntityType.EVOKER_FANGS, EvokerFangs.class);
result.put(EntityType.EXPERIENCE_BOTTLE, ThrownExperienceBottle.class);
result.put(EntityType.EXPERIENCE_ORB, ExperienceOrb.class);
result.put(EntityType.EYE_OF_ENDER, EyeOfEnder.class);
result.put(EntityType.FALLING_BLOCK, FallingBlockEntity.class);
result.put(EntityType.FIREBALL, LargeFireball.class);
result.put(EntityType.FIREWORK_ROCKET, FireworkRocketEntity.class);
result.put(EntityType.FOX, Fox.class);
result.put(EntityType.FROG, Frog.class);
result.put(EntityType.FURNACE_MINECART, MinecartFurnace.class);
result.put(EntityType.GHAST, Ghast.class);
result.put(EntityType.GIANT, Giant.class);
result.put(EntityType.GLOW_ITEM_FRAME, GlowItemFrame.class);
result.put(EntityType.GLOW_SQUID, GlowSquid.class);
result.put(EntityType.GOAT, Goat.class);
result.put(EntityType.GUARDIAN, Guardian.class);
result.put(EntityType.HOGLIN, Hoglin.class);
result.put(EntityType.HOPPER_MINECART, MinecartHopper.class);
result.put(EntityType.HORSE, Horse.class);
result.put(EntityType.HUSK, Husk.class);
result.put(EntityType.ILLUSIONER, Illusioner.class);
result.put(EntityType.INTERACTION, Interaction.class);
result.put(EntityType.IRON_GOLEM, IronGolem.class);
result.put(EntityType.ITEM, ItemEntity.class);
result.put(EntityType.ITEM_DISPLAY, Display.ItemDisplay.class);
result.put(EntityType.ITEM_FRAME, ItemFrame.class);
result.put(EntityType.JUNGLE_BOAT, Boat.class);
result.put(EntityType.JUNGLE_CHEST_BOAT, ChestBoat.class);
result.put(EntityType.LEASH_KNOT, LeashFenceKnotEntity.class);
result.put(EntityType.LIGHTNING_BOLT, LightningBolt.class);
result.put(EntityType.LLAMA, Llama.class);
result.put(EntityType.LLAMA_SPIT, LlamaSpit.class);
result.put(EntityType.MAGMA_CUBE, MagmaCube.class);
result.put(EntityType.MANGROVE_BOAT, Boat.class);
result.put(EntityType.MANGROVE_CHEST_BOAT, ChestBoat.class);
result.put(EntityType.MARKER, Marker.class);
result.put(EntityType.MINECART, Minecart.class);
result.put(EntityType.MOOSHROOM, MushroomCow.class);
result.put(EntityType.MULE, Mule.class);
result.put(EntityType.OAK_BOAT, Boat.class);
result.put(EntityType.OAK_CHEST_BOAT, ChestBoat.class);
result.put(EntityType.OCELOT, Ocelot.class);
result.put(EntityType.OMINOUS_ITEM_SPAWNER, OminousItemSpawner.class);
result.put(EntityType.PAINTING, Painting.class);
result.put(EntityType.PALE_OAK_BOAT, Boat.class);
result.put(EntityType.PALE_OAK_CHEST_BOAT, ChestBoat.class);
result.put(EntityType.PANDA, Panda.class);
result.put(EntityType.PARROT, Parrot.class);
result.put(EntityType.PHANTOM, Phantom.class);
result.put(EntityType.PIG, Pig.class);
result.put(EntityType.PIGLIN, Piglin.class);
result.put(EntityType.PIGLIN_BRUTE, PiglinBrute.class);
result.put(EntityType.PILLAGER, Pillager.class);
result.put(EntityType.POLAR_BEAR, PolarBear.class);
result.put(EntityType.POTION, ThrownPotion.class);
result.put(EntityType.PUFFERFISH, Pufferfish.class);
result.put(EntityType.RABBIT, Rabbit.class);
result.put(EntityType.RAVAGER, Ravager.class);
result.put(EntityType.SALMON, Salmon.class);
result.put(EntityType.SHEEP, Sheep.class);
result.put(EntityType.SHULKER, Shulker.class);
result.put(EntityType.SHULKER_BULLET, ShulkerBullet.class);
result.put(EntityType.SILVERFISH, Silverfish.class);
result.put(EntityType.SKELETON, Skeleton.class);
result.put(EntityType.SKELETON_HORSE, SkeletonHorse.class);
result.put(EntityType.SLIME, Slime.class);
result.put(EntityType.SMALL_FIREBALL, SmallFireball.class);
result.put(EntityType.SNIFFER, Sniffer.class);
result.put(EntityType.SNOWBALL, Snowball.class);
result.put(EntityType.SNOW_GOLEM, SnowGolem.class);
result.put(EntityType.SPAWNER_MINECART, MinecartSpawner.class);
result.put(EntityType.SPECTRAL_ARROW, SpectralArrow.class);
result.put(EntityType.SPIDER, Spider.class);
result.put(EntityType.SPRUCE_BOAT, Boat.class);
result.put(EntityType.SPRUCE_CHEST_BOAT, ChestBoat.class);
result.put(EntityType.SQUID, Squid.class);
result.put(EntityType.STRAY, Stray.class);
result.put(EntityType.STRIDER, Strider.class);
result.put(EntityType.TADPOLE, Tadpole.class);
result.put(EntityType.TEXT_DISPLAY, Display.TextDisplay.class);
result.put(EntityType.TNT, PrimedTnt.class);
result.put(EntityType.TNT_MINECART, MinecartTNT.class);
result.put(EntityType.TRADER_LLAMA, TraderLlama.class);
result.put(EntityType.TRIDENT, ThrownTrident.class);
result.put(EntityType.TROPICAL_FISH, TropicalFish.class);
result.put(EntityType.TURTLE, Turtle.class);
result.put(EntityType.VEX, Vex.class);
result.put(EntityType.VILLAGER, Villager.class);
result.put(EntityType.VINDICATOR, Vindicator.class);
result.put(EntityType.WANDERING_TRADER, WanderingTrader.class);
result.put(EntityType.WARDEN, Warden.class);
result.put(EntityType.WIND_CHARGE, WindCharge.class);
result.put(EntityType.WITCH, Witch.class);
result.put(EntityType.WITHER, WitherBoss.class);
result.put(EntityType.WITHER_SKELETON, WitherSkeleton.class);
result.put(EntityType.WITHER_SKULL, WitherSkull.class);
result.put(EntityType.WOLF, Wolf.class);
result.put(EntityType.ZOGLIN, Zoglin.class);
result.put(EntityType.ZOMBIE, Zombie.class);
result.put(EntityType.ZOMBIE_HORSE, ZombieHorse.class);
result.put(EntityType.ZOMBIE_VILLAGER, ZombieVillager.class);
result.put(EntityType.ZOMBIFIED_PIGLIN, ZombifiedPiglin.class);
result.put(EntityType.PLAYER, Player.class);
result.put(EntityType.FISHING_BOBBER, FishingHook.class);
return Map.copyOf(result);
}
public static final Class<? extends Entity> getClassByEntityType(EntityType entityType) {
return ENTITY_TYPE_TO_CLASS.get(entityType);
}
}

View File

@ -0,0 +1,98 @@
package io.papermc.generator;
import com.google.common.util.concurrent.MoreExecutors;
import com.mojang.logging.LogUtils;
import io.papermc.generator.types.EntityMetaWatcherGenerator;
import io.papermc.generator.types.EntityTypeToEntityClassGenerator;
import io.papermc.generator.types.SourceGenerator;
import io.papermc.generator.utils.TagCollector;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.Map;
import net.minecraft.SharedConstants;
import net.minecraft.commands.Commands;
import net.minecraft.core.HolderLookup;
import net.minecraft.core.LayeredRegistryAccess;
import net.minecraft.core.Registry;
import net.minecraft.core.RegistryAccess;
import net.minecraft.resources.RegistryDataLoader;
import net.minecraft.server.Bootstrap;
import net.minecraft.server.RegistryLayer;
import net.minecraft.server.ReloadableServerResources;
import net.minecraft.server.packs.PackType;
import net.minecraft.server.packs.repository.Pack;
import net.minecraft.server.packs.repository.PackRepository;
import net.minecraft.server.packs.repository.ServerPacksSource;
import net.minecraft.server.packs.resources.MultiPackResourceManager;
import net.minecraft.tags.TagKey;
import net.minecraft.tags.TagLoader;
import net.minecraft.world.flag.FeatureFlags;
import org.apache.commons.io.file.PathUtils;
import org.slf4j.Logger;
public final class Main {
private static final Logger LOGGER = LogUtils.getLogger();
public static final RegistryAccess.Frozen REGISTRY_ACCESS;
public static final Map<TagKey<?>, String> EXPERIMENTAL_TAGS;
static {
SharedConstants.tryDetectVersion();
Bootstrap.bootStrap();
final PackRepository resourceRepository = ServerPacksSource.createVanillaTrustedRepository();
resourceRepository.reload();
final MultiPackResourceManager resourceManager = new MultiPackResourceManager(PackType.SERVER_DATA, resourceRepository.getAvailablePacks().stream().map(Pack::open).toList());
LayeredRegistryAccess<RegistryLayer> layers = RegistryLayer.createRegistryAccess();
final List<Registry.PendingTags<?>> pendingTags = TagLoader.loadTagsForExistingRegistries(resourceManager, layers.getLayer(RegistryLayer.STATIC));
final List<HolderLookup.RegistryLookup<?>> worldGenLayer = TagLoader.buildUpdatedLookups(layers.getAccessForLoading(RegistryLayer.WORLDGEN), pendingTags);
final RegistryAccess.Frozen frozenWorldgenRegistries = RegistryDataLoader.load(resourceManager, worldGenLayer, RegistryDataLoader.WORLDGEN_REGISTRIES);
layers = layers.replaceFrom(RegistryLayer.WORLDGEN, frozenWorldgenRegistries);
REGISTRY_ACCESS = layers.compositeAccess().freeze();
final ReloadableServerResources reloadableServerResources = ReloadableServerResources.loadResources(
resourceManager,
layers,
pendingTags,
FeatureFlags.VANILLA_SET,
Commands.CommandSelection.DEDICATED,
0,
MoreExecutors.directExecutor(),
MoreExecutors.directExecutor()
).join();
reloadableServerResources.updateStaticRegistryTags();
EXPERIMENTAL_TAGS = TagCollector.grabExperimental(resourceManager);
}
private Main() {
}
public static void main(final String[] args) {
LOGGER.info("Running SERVER generators...");
SourceGenerator[] SERVER = {
new EntityMetaWatcherGenerator("EntityMetaWatcher", "io.papermc.paper.entity.meta"),
new EntityTypeToEntityClassGenerator("EntityTypeToEntityClass", "io.papermc.paper.entity.meta"),
};
generate(Paths.get(args[0]), SERVER);
}
private static void generate(Path output, SourceGenerator[] generators) {
try {
if (Files.exists(output)) {
PathUtils.deleteDirectory(output);
}
Files.createDirectories(output);
for (final SourceGenerator generator : generators) {
generator.writeToFile(output);
}
LOGGER.info("Files written to {}", output.toAbsolutePath());
} catch (final Exception ex) {
throw new RuntimeException(ex);
}
}
}

View File

@ -0,0 +1,182 @@
package io.papermc.generator.types;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.FieldSpec;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeSpec;
import com.squareup.javapoet.WildcardTypeName;
import io.github.classgraph.ClassGraph;
import io.github.classgraph.ScanResult;
import io.papermc.generator.utils.Annotations;
import io.papermc.generator.utils.ReflectionHelper;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.TreeMap;
import java.util.stream.Collectors;
import javax.lang.model.element.Modifier;
import net.minecraft.network.syncher.EntityDataAccessor;
import net.minecraft.network.syncher.EntityDataSerializer;
import net.minecraft.network.syncher.EntityDataSerializers;
import net.minecraft.world.entity.Entity;
import org.apache.commons.lang3.StringUtils;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.framework.qual.DefaultQualifier;
@DefaultQualifier(NonNull.class)
public class EntityMetaWatcherGenerator extends SimpleGenerator {
private static final ParameterizedTypeName GENERIC_ENTITY_DATA_SERIALIZER = ParameterizedTypeName.get(ClassName.get(Map.class), ClassName.get(Long.class), ParameterizedTypeName.get(ClassName.get(EntityDataSerializer.class), WildcardTypeName.subtypeOf(Object.class)));
private static final ParameterizedTypeName ENTITY_CLASS = ParameterizedTypeName.get(ClassName.get(Class.class), WildcardTypeName.subtypeOf(Entity.class));
private static final ParameterizedTypeName OUTER_MAP_TYPE = ParameterizedTypeName.get(ClassName.get(Map.class), ENTITY_CLASS, GENERIC_ENTITY_DATA_SERIALIZER);
public EntityMetaWatcherGenerator(String className, String packageName) {
super(className, packageName);
}
@Override
protected TypeSpec getTypeSpec() {
Map<EntityDataSerializer<?>, String> dataAccessorStringMap = serializerMap();
List<Class<?>> classes;
try (ScanResult scanResult = new ClassGraph().enableAllInfo().whitelistPackages("net.minecraft").scan()) {
classes = scanResult.getSubclasses(net.minecraft.world.entity.Entity.class.getName()).loadClasses();
}
classes = classes.stream()
.filter(clazz -> !java.lang.reflect.Modifier.isAbstract(clazz.getModifiers()))
.toList();
record Pair(Class<?> clazz, List<? extends EntityDataAccessor<?>> metaResults) {}
final List<Pair> list = classes.stream()
.map(clazz -> new Pair(
clazz,
ReflectionHelper.getAllForAllParents(clazz, EntityMetaWatcherGenerator::doFilter)
.stream()
.map(this::createData)
.filter(Objects::nonNull)
.toList()
)
)
.toList();
Map<Class<?>, List<? extends EntityDataAccessor<?>>> vanillaNames = new TreeMap<>(Comparator.comparing(Class::getSimpleName));
vanillaNames.putAll(list.stream()
.collect(Collectors.toMap(pair -> pair.clazz, pair -> pair.metaResults)));
TypeSpec.Builder typeBuilder = TypeSpec.classBuilder(this.className)
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addAnnotations(Annotations.CLASS_HEADER);
generateIdAccessorMethods(vanillaNames, dataAccessorStringMap, typeBuilder);
generateClassToTypeMap(typeBuilder, vanillaNames.keySet());
generateIsValidAccessorForEntity(typeBuilder);
return typeBuilder.build();
}
private void generateIsValidAccessorForEntity(TypeSpec.Builder builder) {
var methodBuilder = MethodSpec.methodBuilder("isValidForClass")
.addModifiers(Modifier.PUBLIC, Modifier.FINAL, Modifier.STATIC)
.returns(boolean.class)
.addParameter(ParameterizedTypeName.get(ClassName.get(Class.class), WildcardTypeName.subtypeOf(Entity.class)), "clazz")
.addParameter(ParameterizedTypeName.get(ClassName.get(EntityDataSerializer.class), WildcardTypeName.subtypeOf(Object.class)), "entityDataSerializer")
.addParameter(int.class, "id")
.addStatement("Map<Long, EntityDataSerializer<?>> serializerMap = VALID_ENTITY_META_MAP.get(clazz)")
.beginControlFlow("if(serializerMap == null)")
.addStatement("return false")
.endControlFlow()
.addStatement("var serializer = serializerMap.get(id)")
.addStatement("return serializer != null && serializer == entityDataSerializer");
builder.addMethod(methodBuilder.build());
}
private void generateClassToTypeMap(TypeSpec.Builder typeBuilder, Set<Class<?>> classes){
typeBuilder.addField(
FieldSpec.builder(OUTER_MAP_TYPE, "VALID_ENTITY_META_MAP", Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL)
.initializer("initialize()")
.build()
);
MethodSpec.Builder builder = MethodSpec.methodBuilder("initialize")
.addModifiers(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL)
.returns(OUTER_MAP_TYPE)
.addStatement("$T result = new $T<>()", OUTER_MAP_TYPE, ClassName.get(HashMap.class));
classes.forEach(aClass -> {
String name = StringUtils.uncapitalize(aClass.getSimpleName());
if(!name.isBlank()) {
builder.addStatement("result.put($T.class, $L())", aClass, name);
}
});
typeBuilder.addMethod(builder.addStatement("return $T.copyOf(result)", Map.class).build());
}
private static void generateIdAccessorMethods(Map<Class<?>, List<? extends EntityDataAccessor<?>>> vanillaNames, Map<EntityDataSerializer<?>, String> dataAccessorStringMap, TypeSpec.Builder typeBuilder) {
for (final Map.Entry<Class<?>, List<? extends EntityDataAccessor<?>>> perClassResults : vanillaNames.entrySet()) {
if (perClassResults.getKey().getSimpleName().isBlank()) {
continue;
}
var simpleName = perClassResults.getKey().getSimpleName();
ClassName hashMap = ClassName.get(HashMap.class);
MethodSpec.Builder builder = MethodSpec.methodBuilder(StringUtils.uncapitalize(simpleName))
.addModifiers(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL)
.returns(GENERIC_ENTITY_DATA_SERIALIZER)
.addStatement("$T result = new $T<>()", GENERIC_ENTITY_DATA_SERIALIZER, hashMap);
perClassResults.getValue().stream().sorted(Comparator.comparing(EntityDataAccessor::id)).forEach(result -> {
builder.addStatement("result.put($LL, $T.$L)", result.id(), EntityDataSerializers.class, dataAccessorStringMap.get(result.serializer()));
});
var method = builder.addStatement("return $T.copyOf(result)", Map.class)
.build();
typeBuilder.addMethod(method);
}
}
private @Nullable EntityDataAccessor<?> createData(Field field) {
try {
field.setAccessible(true);
return (EntityDataAccessor<?>) field.get(null);
} catch (IllegalAccessException e) {
return null;
}
}
private static boolean doFilter(Field field) {
return java.lang.reflect.Modifier.isStatic(field.getModifiers()) && field.getType().isAssignableFrom(EntityDataAccessor.class);
}
@Override
protected JavaFile.Builder file(JavaFile.Builder builder) {
return builder.skipJavaLangImports(true);
}
private Map<EntityDataSerializer<?>, String> serializerMap(){
return Arrays.stream(EntityDataSerializers.class.getDeclaredFields())
.filter(field -> field.getType() == EntityDataSerializer.class)
.map(field -> {
try {
return Map.entry((EntityDataSerializer<?>)field.get(0), field.getName());
} catch (IllegalAccessException e) {
return null;
}
})
.filter(Objects::nonNull)
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
}
}

View File

@ -0,0 +1,95 @@
package io.papermc.generator.types;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.FieldSpec;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeSpec;
import com.squareup.javapoet.WildcardTypeName;
import io.papermc.generator.utils.Annotations;
import io.papermc.generator.utils.ReflectionHelper;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.lang.model.element.Modifier;
import net.minecraft.world.entity.Entity;
import org.bukkit.entity.EntityType;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.framework.qual.DefaultQualifier;
@DefaultQualifier(NonNull.class)
public class EntityTypeToEntityClassGenerator extends SimpleGenerator {
private static final ParameterizedTypeName ENTITY_CLASS = ParameterizedTypeName.get(ClassName.get(Class.class), WildcardTypeName.subtypeOf(Entity.class));
private static final ParameterizedTypeName OUTER_MAP_TYPE = ParameterizedTypeName.get(ClassName.get(Map.class), ClassName.get(EntityType.class), ENTITY_CLASS);
public EntityTypeToEntityClassGenerator(String className, String packageName) {
super(className, packageName);
}
@Override
protected TypeSpec getTypeSpec() {
final List<TypeToClass> typeToClasses = ReflectionHelper.forClass(net.minecraft.world.entity.EntityType.class, field ->
java.lang.reflect.Modifier.isStatic(field.getModifiers()) &&
java.lang.reflect.Modifier.isPublic(field.getModifiers()) &&
java.lang.reflect.Modifier.isFinal(field.getModifiers()) &&
field.getType().isAssignableFrom(net.minecraft.world.entity.EntityType.class)
).stream().map(this::createFromField).toList();
TypeSpec.Builder typeBuilder = TypeSpec.classBuilder(this.className)
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addAnnotations(Annotations.CLASS_HEADER);
generateEntityTypeToEntityClassMap(typeBuilder, typeToClasses);
generateGetClassByEntityType(typeBuilder);
return typeBuilder.build();
}
private TypeToClass createFromField(Field field) {
return new TypeToClass(field.getName(), (Class<?>) ((ParameterizedType)field.getGenericType()).getActualTypeArguments()[0]);
}
record TypeToClass(String entityType, Class<?> className) {}
private void generateGetClassByEntityType(TypeSpec.Builder builder) {
var methodBuilder = MethodSpec.methodBuilder("getClassByEntityType")
.addModifiers(Modifier.PUBLIC, Modifier.FINAL, Modifier.STATIC)
.returns(ENTITY_CLASS)
.addParameter(ClassName.get(EntityType.class), "entityType")
.addStatement("return ENTITY_TYPE_TO_CLASS.get(entityType)");
builder.addMethod(methodBuilder.build());
}
private void generateEntityTypeToEntityClassMap(TypeSpec.Builder typeBuilder, List<TypeToClass> classes) {
typeBuilder.addField(
FieldSpec.builder(OUTER_MAP_TYPE, "ENTITY_TYPE_TO_CLASS", Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL)
.initializer("initialize()")
.build()
);
MethodSpec.Builder builder = MethodSpec.methodBuilder("initialize")
.addModifiers(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL)
.returns(OUTER_MAP_TYPE)
.addStatement("$T result = new $T<>()", OUTER_MAP_TYPE, ClassName.get(HashMap.class));
classes.forEach(aClass -> {
builder.addStatement("result.put(EntityType.$L, $T.class)", aClass.entityType, aClass.className);
});
typeBuilder.addMethod(builder.addStatement("return $T.copyOf(result)", Map.class).build());
}
@Override
protected JavaFile.Builder file(final JavaFile.Builder builder) {
return builder.skipJavaLangImports(true);
}
}

View File

@ -0,0 +1,39 @@
package io.papermc.generator.utils;
import io.papermc.generator.types.EntityMetaWatcherGenerator;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.function.Predicate;
public final class ReflectionHelper {
private ReflectionHelper(){}
public static List<Field> getAllForAllParents(Class<?> clazz, Predicate<Field> filter) {
List<Field> allClasses = new ArrayList<>(forClass(clazz, filter));
for (final Class<?> aClass : allParents(clazz)) {
allClasses.addAll(forClass(aClass, filter));
}
return allClasses;
}
public static List<Class<?>> allParents(Class<?> clazz){
List<Class<?>> allClasses = new ArrayList<>();
Class<?> current = clazz;
while (current.getSuperclass() != null) {
var toAdd = current.getSuperclass();
if (net.minecraft.world.entity.Entity.class.isAssignableFrom(toAdd)) {
allClasses.add(toAdd);
}
current = toAdd;
}
Collections.reverse(allClasses);
return allClasses;
}
public static List<Field> forClass(Class<?> clazz, Predicate<Field> filter) {
return Arrays.stream(clazz.getDeclaredFields()).filter(filter).toList();
}
}

View File

View File

@ -0,0 +1,269 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Yannick Lamprecht <yannicklamprecht@live.de>
Date: Wed, 27 Dec 2023 14:51:59 +0100
Subject: [PATCH] add disguise api
diff --git a/src/main/java/com/destroystokyo/paper/SkinParts.java b/src/main/java/com/destroystokyo/paper/SkinParts.java
index 4a0c39405d4fbed457787e3c6ded4cc6591bc8c2..c4cc74b95bd1a8378c53a6dee68875991a68bec6 100644
--- a/src/main/java/com/destroystokyo/paper/SkinParts.java
+++ b/src/main/java/com/destroystokyo/paper/SkinParts.java
@@ -17,4 +17,15 @@ public interface SkinParts {
boolean hasHatsEnabled();
int getRaw();
+
+ interface Builder {
+ @org.jetbrains.annotations.NotNull Builder withCape(boolean cape);
+ @org.jetbrains.annotations.NotNull Builder withJacket(boolean jacket);
+ @org.jetbrains.annotations.NotNull Builder withLeftSleeve(boolean leftSleeve);
+ @org.jetbrains.annotations.NotNull Builder withRightSleeve(boolean rightSleeve);
+ @org.jetbrains.annotations.NotNull Builder withLeftPants(boolean leftPants);
+ @org.jetbrains.annotations.NotNull Builder withRightPants(boolean rightPants);
+ @org.jetbrains.annotations.NotNull Builder withHat(boolean hat);
+ @org.jetbrains.annotations.NotNull SkinParts build();
+ }
}
diff --git a/src/main/java/io/papermc/paper/disguise/DisguiseData.java b/src/main/java/io/papermc/paper/disguise/DisguiseData.java
new file mode 100644
index 0000000000000000000000000000000000000000..d83666c349d19b4341c1335434653f7a0be199f1
--- /dev/null
+++ b/src/main/java/io/papermc/paper/disguise/DisguiseData.java
@@ -0,0 +1,66 @@
+package io.papermc.paper.disguise;
+
+import com.destroystokyo.paper.profile.PlayerProfile;
+import org.bukkit.entity.EntityType;
+import org.jetbrains.annotations.ApiStatus;
+import org.jspecify.annotations.NullMarked;
+
+/**
+ * Represents the data used to disguise an entity as another.
+ * Also supports disguising an entity as a player commonly known as `FakePlayer`.
+ */
+@NullMarked
+public sealed interface DisguiseData permits DisguiseData.OriginalDisguise, EntityTypeDisguise, PlayerDisguise {
+
+ /**
+ * Creates an original disguise data that can be used to reset disguising.
+ * <p>
+ * The original instance is set by default when a new entity is spawned
+ * and represents the state of no disguise should be made.
+ * <p>
+ * Same as {@link #reset()}
+ *
+ * @return an original disguise data
+ */
+ static DisguiseData original() {
+ return reset();
+ }
+
+ /**
+ * Creates a {@link PlayerDisguise.Builder} where you can configure certain properties of the fake player appearance.
+ *
+ *
+ * @param playerProfile a already completed player profile that will be the fake players skin
+ * @return a builder to configure certain attributes
+ */
+ static PlayerDisguise.Builder player(PlayerProfile playerProfile) {
+ return new PlayerDisguise.Builder(playerProfile);
+ }
+
+ /**
+ * Creates a {@link EntityTypeDisguise.Builder} to allow disguising your entity as the given {@link EntityType}.
+ *
+ *
+ * @param entityType the entity type as which the entity should appear as.
+ * @return an entity disguise
+ */
+ static EntityTypeDisguise.Builder entity(EntityType entityType) {
+ return new EntityTypeDisguise.Builder(entityType);
+ }
+
+ /**
+ * An alias for {@link #original()} to cover certain views on it.
+ *
+ * @see #original()
+ *
+ * @return an original disguise data
+ */
+ static OriginalDisguise reset() {
+ return new OriginalDisguise();
+ }
+
+ record OriginalDisguise() implements DisguiseData{
+ @ApiStatus.Internal
+ public OriginalDisguise() {}
+ }
+}
diff --git a/src/main/java/io/papermc/paper/disguise/EntityTypeDisguise.java b/src/main/java/io/papermc/paper/disguise/EntityTypeDisguise.java
new file mode 100644
index 0000000000000000000000000000000000000000..1482b831df9d33e9694fedff6638fe32e15a14a7
--- /dev/null
+++ b/src/main/java/io/papermc/paper/disguise/EntityTypeDisguise.java
@@ -0,0 +1,35 @@
+package io.papermc.paper.disguise;
+
+import java.util.Objects;
+import org.bukkit.entity.EntityType;
+import org.jetbrains.annotations.ApiStatus;
+import org.jspecify.annotations.NullMarked;
+
+@NullMarked
+public record EntityTypeDisguise(EntityType entityType) implements DisguiseData {
+ @ApiStatus.Internal
+ public EntityTypeDisguise {
+ Objects.requireNonNull(entityType, "type cannot be null");
+ }
+
+ /**
+ * Represents the builder to configure certain appearance settings.
+ */
+ public static class Builder {
+ private final EntityType entityType;
+
+ @ApiStatus.Internal
+ public Builder(EntityType entityType) {
+ this.entityType = entityType;
+ }
+
+ /**
+ * Builds the disguise
+ *
+ * @return the built disguise
+ */
+ public EntityTypeDisguise build() {
+ return new EntityTypeDisguise(entityType);
+ }
+ }
+}
diff --git a/src/main/java/io/papermc/paper/disguise/PlayerDisguise.java b/src/main/java/io/papermc/paper/disguise/PlayerDisguise.java
new file mode 100644
index 0000000000000000000000000000000000000000..534867703242a1e943a19143af82c2b2607fb20b
--- /dev/null
+++ b/src/main/java/io/papermc/paper/disguise/PlayerDisguise.java
@@ -0,0 +1,69 @@
+package io.papermc.paper.disguise;
+
+import com.destroystokyo.paper.SkinParts;
+import com.destroystokyo.paper.profile.PlayerProfile;
+import java.util.Objects;
+import org.bukkit.Server;
+import org.jetbrains.annotations.ApiStatus;
+import org.jetbrains.annotations.Nullable;
+import org.jspecify.annotations.NullMarked;
+
+@NullMarked
+public record PlayerDisguise(PlayerProfile playerProfile, boolean listed, @Nullable SkinParts skinParts) implements DisguiseData {
+
+ @ApiStatus.Internal
+ public PlayerDisguise {
+ Objects.requireNonNull(playerProfile, "profile cannot be null");
+ }
+ public static Builder builder(PlayerProfile playerProfile) {
+ return new Builder(playerProfile);
+ }
+
+ /**
+ * Represents the builder to configure certain appearance settings.
+ */
+ public static class Builder {
+ private final PlayerProfile playerProfile;
+ private boolean listed;
+ @Nullable
+ private SkinParts skinParts;
+
+ @ApiStatus.Internal
+ public Builder(PlayerProfile playerProfile) {
+ this.playerProfile = playerProfile;
+ }
+
+ /**
+ * Defines if the fake player will be shown in player list.
+ *
+ * @param listed true, if the player should be listed else false
+ * @return the builder instance
+ */
+ public Builder listed(boolean listed) {
+ this.listed = listed;
+ return this;
+ }
+
+ /**
+ * Defines which skin parts should be enabled for the fake player.
+ * <p>
+ * Use {@link Server#newSkinPartsBuilder()} to get a fresh builder instance for configuration.
+ *
+ * @param skinParts the skin parts that should be shown.
+ * @return the builder instance
+ */
+ public Builder skinParts(SkinParts skinParts) {
+ this.skinParts = skinParts;
+ return this;
+ }
+
+ /**
+ * Builds the disguise
+ *
+ * @return the built disguise
+ */
+ public PlayerDisguise build() {
+ return new PlayerDisguise(playerProfile, listed, skinParts);
+ }
+ }
+}
diff --git a/src/main/java/org/bukkit/Server.java b/src/main/java/org/bukkit/Server.java
index 0b78564256ebc647ebac402e549d86ab6e307c8d..61945f9bf9ae73b67e2447b92533e7b57a3cf3d7 100644
--- a/src/main/java/org/bukkit/Server.java
+++ b/src/main/java/org/bukkit/Server.java
@@ -2572,4 +2572,11 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi
*/
boolean isOwnedByCurrentRegion(@NotNull Entity entity);
// Paper end - Folia region threading API
+ // Paper start - add disguise api
+ /**
+ * Creates a new skinparts builder used for overriding skin settings
+ * @return a new builder for skin parts
+ */
+ com.destroystokyo.paper.SkinParts.@NotNull Builder newSkinPartsBuilder();
+ // Paper end - add disguise api
}
diff --git a/src/main/java/org/bukkit/entity/Entity.java b/src/main/java/org/bukkit/entity/Entity.java
index d0ae8a94db20281d3664d74718c65234eb2e5f83..87caf3490af05be6710eb8f730d8c3d7754323fd 100644
--- a/src/main/java/org/bukkit/entity/Entity.java
+++ b/src/main/java/org/bukkit/entity/Entity.java
@@ -1172,4 +1172,34 @@ public interface Entity extends Metadatable, CommandSender, Nameable, Persistent
*/
void broadcastHurtAnimation(@NotNull java.util.Collection<Player> players);
// Paper end - broadcast hurt animation
+ // Paper start - disguise api
+
+ /**
+ * Gets the current {@link io.papermc.paper.disguise.DisguiseData} of the entity.
+ *
+ * @return {@link io.papermc.paper.disguise.DisguiseData.OriginalDisguise} if entity is not disguised.
+ * Otherwise, one of {@link io.papermc.paper.disguise.EntityTypeDisguise} or {@link io.papermc.paper.disguise.PlayerDisguise}
+ */
+ @NotNull io.papermc.paper.disguise.DisguiseData getDisguiseData();
+
+ /**
+ * Sets the current {@link io.papermc.paper.disguise.DisguiseData} of the entity.
+ * <p>
+ * Following {@link io.papermc.paper.disguise.DisguiseData} can be set:
+ * <ul>
+ * <li>{@link io.papermc.paper.disguise.PlayerDisguise} use {@link io.papermc.paper.disguise.DisguiseData#player(com.destroystokyo.paper.profile.PlayerProfile)}.
+ * It returns a builder where you are able to configure additional settings</li>
+ * <li>{@link io.papermc.paper.disguise.EntityTypeDisguise} use {@link io.papermc.paper.disguise.DisguiseData#entity(EntityType)}</li>
+ * <li>{@link io.papermc.paper.disguise.DisguiseData.OriginalDisguise} use {@link io.papermc.paper.disguise.DisguiseData#original()} or {@link io.papermc.paper.disguise.DisguiseData#reset()} to reset it again to the original state</li>
+ * </ul>
+ * <p>
+ * The following entities are not supported:
+ * <ul>
+ * <li>{@link ExperienceOrb}</li>
+ * </ul>
+ *
+ * @param disguiseData the {@link io.papermc.paper.disguise.DisguiseData} that will be set.
+ */
+ void setDisguiseData(@NotNull io.papermc.paper.disguise.DisguiseData disguiseData);
+ // Paper end - disguise api
}

View File

@ -0,0 +1,59 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Yannick Lamprecht <yannicklamprecht@live.de>
Date: Mon, 11 Mar 2024 00:52:56 +0100
Subject: [PATCH] add paper server generator dependency
diff --git a/build.gradle.kts b/build.gradle.kts
index ee5f662ed0e84be997807a9faf97191ef4fc0449..29768de87e4e07517d39ede1ed25a98afdf2c14b 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -4,6 +4,7 @@ import java.time.Instant
plugins {
java
`maven-publish`
+ idea // Paper
}
val log4jPlugins = sourceSets.create("log4jPlugins")
@@ -82,6 +83,22 @@ dependencies {
// Paper end - spark
}
+// Paper start
+val generatedServerPath: java.nio.file.Path = rootProject.projectDir.toPath().resolve("paper-server-generator/generated")
+idea {
+ module {
+ generatedSourceDirs.add(generatedServerPath.toFile())
+ }
+}
+sourceSets {
+ main {
+ java {
+ srcDir(generatedServerPath)
+ }
+ }
+}
+// Paper end
+
paperweight {
craftBukkitPackageVersion.set("v1_21_R2") // also needs to be updated in MappingEnvironment
}
@@ -140,6 +157,17 @@ tasks.check {
dependsOn(scanJar)
}
// Paper end
+// Paper start
+val scanJarForOldGeneratedCode = tasks.register("scanJarForOldGeneratedCode", io.papermc.paperweight.tasks.ScanJarForOldGeneratedCode::class) {
+ mcVersion.set(providers.gradleProperty("mcVersion"))
+ annotation.set("Lio/papermc/paper/generated/GeneratedFrom;")
+ jarToScan.set(tasks.jar.flatMap { it.archiveFile })
+ classpath.from(configurations.compileClasspath)
+}
+tasks.check {
+ dependsOn(scanJarForOldGeneratedCode)
+}
+// Paper end
// Paper start - use TCA for console improvements
tasks.serverJar {
from(alsoShade.elements.map {

View File

@ -0,0 +1,447 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Yannick Lamprecht <yannicklamprecht@live.de>
Date: Sat, 16 Mar 2024 22:58:19 +0100
Subject: [PATCH] add disguise api
diff --git a/src/main/java/com/destroystokyo/paper/PaperSkinParts.java b/src/main/java/com/destroystokyo/paper/PaperSkinParts.java
index b6f4400df3d8ec7e06a996de54f8cabba57885e1..41bb75ab2b38c86b9fe2233ef6fe0ecec13350fd 100644
--- a/src/main/java/com/destroystokyo/paper/PaperSkinParts.java
+++ b/src/main/java/com/destroystokyo/paper/PaperSkinParts.java
@@ -3,6 +3,7 @@ package com.destroystokyo.paper;
import com.google.common.base.Objects;
import java.util.StringJoiner;
+import net.minecraft.world.entity.player.PlayerModelPart;
public class PaperSkinParts implements SkinParts {
@@ -71,4 +72,81 @@ public class PaperSkinParts implements SkinParts {
.add("hats=" + hasHatsEnabled())
.toString();
}
+
+ public static SkinParts.Builder builder(){
+ return new Builder();
+ }
+
+ public static class Builder implements SkinParts.Builder {
+
+ private boolean cape;
+ private boolean jacket;
+ private boolean leftSleeve;
+ private boolean rightSleeve;
+ private boolean leftPants;
+ private boolean rightPants;
+ private boolean hats;
+
+ private static final int CAPE = PlayerModelPart.CAPE.getMask();
+ private static final int JACKET = PlayerModelPart.JACKET.getMask();
+ private static final int LEFT_SLEEVE = PlayerModelPart.LEFT_SLEEVE.getMask();
+ private static final int RIGHT_SLEEVE = PlayerModelPart.RIGHT_SLEEVE.getMask();
+ private static final int LEFT_PANTS = PlayerModelPart.LEFT_PANTS_LEG.getMask();
+ private static final int RIGHT_PANTS = PlayerModelPart.RIGHT_PANTS_LEG.getMask();
+ private static final int HAT = PlayerModelPart.HAT.getMask();
+
+ @Override
+ public @org.jetbrains.annotations.NotNull Builder withCape(boolean cape) {
+ this.cape = cape;
+ return this;
+ }
+
+ @Override
+ public @org.jetbrains.annotations.NotNull Builder withJacket(boolean jacket) {
+ this.jacket = jacket;
+ return this;
+ }
+
+ @Override
+ public @org.jetbrains.annotations.NotNull Builder withLeftSleeve(boolean leftSleeve) {
+ this.leftSleeve = leftSleeve;
+ return this;
+ }
+
+ @Override
+ public @org.jetbrains.annotations.NotNull Builder withRightSleeve(boolean rightSleeve) {
+ this.rightSleeve = rightSleeve;
+ return this;
+ }
+
+ @Override
+ public @org.jetbrains.annotations.NotNull Builder withLeftPants(boolean leftPants) {
+ this.leftPants = leftPants;
+ return this;
+ }
+
+ @Override
+ public @org.jetbrains.annotations.NotNull Builder withRightPants(boolean rightPants) {
+ this.rightPants = rightPants;
+ return this;
+ }
+
+ @Override
+ public @org.jetbrains.annotations.NotNull Builder withHat(boolean hat) {
+ this.hats = hat;
+ return this;
+ }
+
+ public @org.jetbrains.annotations.NotNull SkinParts build() {
+ int raw = 0;
+ if (cape) raw |= CAPE;
+ if (jacket) raw |= JACKET;
+ if (leftSleeve) raw |= LEFT_SLEEVE;
+ if (rightSleeve) raw |= RIGHT_SLEEVE;
+ if (leftPants) raw |= LEFT_PANTS;
+ if (rightPants) raw |= RIGHT_PANTS;
+ if (hats) raw |= HAT;
+ return new PaperSkinParts(raw);
+ }
+ }
}
diff --git a/src/main/java/io/papermc/paper/disguise/DisguiseUtil.java b/src/main/java/io/papermc/paper/disguise/DisguiseUtil.java
new file mode 100644
index 0000000000000000000000000000000000000000..298169c8bc5a00659a53c8c1e13481e2e198d7b4
--- /dev/null
+++ b/src/main/java/io/papermc/paper/disguise/DisguiseUtil.java
@@ -0,0 +1,148 @@
+package io.papermc.paper.disguise;
+
+import com.destroystokyo.paper.profile.CraftPlayerProfile;
+import com.destroystokyo.paper.profile.PlayerProfile;
+import io.papermc.paper.entity.meta.EntityTypeToEntityClass;
+import java.util.ArrayList;
+import java.util.EnumSet;
+import java.util.List;
+import net.minecraft.network.protocol.Packet;
+import net.minecraft.network.protocol.game.ClientboundAddEntityPacket;
+import net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket;
+import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket;
+import net.minecraft.network.syncher.EntityDataAccessor;
+import net.minecraft.network.syncher.EntityDataSerializer;
+import net.minecraft.network.syncher.SynchedEntityData;
+import net.minecraft.server.level.ServerPlayer;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.LivingEntity;
+import net.minecraft.world.entity.player.Player;
+import net.minecraft.world.phys.Vec3;
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.entity.CraftEntityType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket.Action;
+import static net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket.Entry;
+
+public final class DisguiseUtil {
+ private static final Logger LOG = LoggerFactory.getLogger(DisguiseUtil.class);
+
+ private DisguiseUtil(){}
+
+ public static boolean tryDisguise(ServerPlayer player, Entity entity, Packet<?> packet) {
+ if(!(packet instanceof ClientboundAddEntityPacket clientboundAddEntityPacket)) {
+ return false;
+ }
+ return switch (entity.getBukkitEntity().getDisguiseData()) {
+ case DisguiseData.OriginalDisguise disguise -> false;
+ case io.papermc.paper.disguise.EntityTypeDisguise(var type) -> {
+ player.connection.send(create(clientboundAddEntityPacket, CraftEntityType.bukkitToMinecraft(type)));
+ yield true;
+ }
+ case PlayerDisguise(var playerProfile, var listed, var skinParts) -> {
+ PlayerProfile adapted = Bukkit.createProfile(entity.getUUID(), playerProfile.getName());
+ adapted.setProperties(playerProfile.getProperties());
+ Entry playerUpdate = new Entry(
+ entity.getUUID(),
+ CraftPlayerProfile.asAuthlibCopy(adapted),
+ listed,
+ 0,
+ net.minecraft.world.level.GameType.DEFAULT_MODE,
+ entity.getCustomName(),
+ 0,
+ null
+ );
+ player.connection.send(new ClientboundPlayerInfoUpdatePacket(EnumSet.of(Action.ADD_PLAYER, Action.UPDATE_LISTED), playerUpdate));
+ player.connection.send(create(clientboundAddEntityPacket, net.minecraft.world.entity.EntityType.PLAYER));
+ if(skinParts != null) {
+ player.connection.send(new net.minecraft.network.protocol.game.ClientboundSetEntityDataPacket(
+ clientboundAddEntityPacket.getId(),
+ List.of(new SynchedEntityData.DataItem<>(Player.DATA_PLAYER_MODE_CUSTOMISATION, (byte) skinParts.getRaw()).value())
+ ));
+ }
+ yield true;
+ }
+ };
+ }
+
+ /*
+ * Only player disguise needs to be handled specially
+ * because the client doesn't forget the player profile otherwise.
+ * This would result in player being kicked cause the entities type mismatches the previously disguised one.
+ */
+ public static void tryDespawn(ServerPlayer player, Entity entity) {
+ if(entity.getBukkitEntity().getDisguiseData() instanceof PlayerDisguise) {
+ player.connection.send(new ClientboundPlayerInfoRemovePacket(List.of(entity.getUUID())));
+ }
+ }
+
+ private static ClientboundAddEntityPacket create(ClientboundAddEntityPacket packet, EntityType<?> entityType) {
+ return new net.minecraft.network.protocol.game.ClientboundAddEntityPacket(
+ packet.getId(),
+ packet.getUUID(),
+ packet.getX(),
+ packet.getY(),
+ packet.getZ(),
+ packet.getXRot(),
+ packet.getYRot(),
+ entityType,
+ 0,
+ Vec3.ZERO.add(packet.getX(), packet.getY(), packet.getZ()).scale(1/8000.0D),
+ packet.getYHeadRot()
+ );
+ }
+
+
+ /*
+ * Is used to skip entity meta that doesn't fit the disguised type.
+ * e.g. Player having a float at index 15 (additional hearts) and the server side entity is an Armorstand
+ * that has a byte at that index.
+ */
+
+ public static boolean shouldSkip(Entity entity, EntityDataAccessor<?> dataAccessor) {
+ return shouldSkip(entity, dataAccessor.serializer(), dataAccessor.id());
+ }
+
+ public static boolean shouldSkip(Entity entity, EntityDataSerializer<?> entityDataSerializer, int id) {
+ return switch (entity.getBukkitEntity().getDisguiseData()) {
+ case DisguiseData.OriginalDisguise original -> false;
+ case EntityTypeDisguise entityTypeDisguise -> !io.papermc.paper.entity.meta.EntityMetaWatcher.isValidForClass(
+ EntityTypeToEntityClass.getClassByEntityType(entityTypeDisguise.entityType()),
+ entityDataSerializer, id
+ );
+ case PlayerDisguise playerDisguise -> !io.papermc.paper.entity.meta.EntityMetaWatcher.isValidForClass(
+ ServerPlayer.class,
+ entityDataSerializer, id
+ );
+ };
+ }
+
+ public static List<SynchedEntityData.DataValue<?>> filter(Entity entity, List<SynchedEntityData.DataValue<?>> values) {
+ List<SynchedEntityData.DataValue<?>> list = new ArrayList<>();
+ for (SynchedEntityData.DataValue<?> value : values) {
+ if (!shouldSkip(entity, value.serializer(), value.id())) {
+ list.add(value);
+ }
+ }
+ return list;
+ }
+
+ public static boolean shouldSkipAttributeSending(Entity entity) {
+ return switch (entity.getBukkitEntity().getDisguiseData()) {
+ case DisguiseData.OriginalDisguise original -> false;
+ case EntityTypeDisguise entityTypeDisguise -> !entityTypeDisguise.entityType().hasDefaultAttributes();
+ case PlayerDisguise playerDisguise -> false;
+ };
+ }
+
+ public static boolean canSendAnimation(Entity entity) {
+ return switch (entity.getBukkitEntity().getDisguiseData()) {
+ case DisguiseData.OriginalDisguise original -> true;
+ case EntityTypeDisguise entityTypeDisguise -> LivingEntity.class.isAssignableFrom(EntityTypeToEntityClass.getClassByEntityType(entityTypeDisguise.entityType()));
+ case PlayerDisguise playerDisguise -> true;
+ };
+ }
+}
diff --git a/src/main/java/net/minecraft/network/syncher/SynchedEntityData.java b/src/main/java/net/minecraft/network/syncher/SynchedEntityData.java
index 0f99733660f91280e4c6262cf75b3c9cae86f65a..c49606b5f8e459a1574c3111c10f2c66c0888f87 100644
--- a/src/main/java/net/minecraft/network/syncher/SynchedEntityData.java
+++ b/src/main/java/net/minecraft/network/syncher/SynchedEntityData.java
@@ -100,6 +100,7 @@ public class SynchedEntityData {
if (datawatcher_item.isDirty()) {
datawatcher_item.setDirty(false);
+ if (io.papermc.paper.disguise.DisguiseUtil.shouldSkip((net.minecraft.world.entity.Entity) entity, datawatcher_item.getAccessor())) continue; // Paper - disguise api
list.add(datawatcher_item.value());
}
}
@@ -117,6 +118,7 @@ public class SynchedEntityData {
for (int j = 0; j < i; ++j) {
SynchedEntityData.DataItem<?> datawatcher_item = adatawatcher_item[j];
+ if (io.papermc.paper.disguise.DisguiseUtil.shouldSkip((net.minecraft.world.entity.Entity) entity, datawatcher_item.getAccessor())) continue; // Paper - disguise api
if (!datawatcher_item.isSetToDefault()) {
if (list == null) {
list = new ArrayList();
@@ -136,6 +138,7 @@ public class SynchedEntityData {
SynchedEntityData.DataValue<?> datawatcher_c = (SynchedEntityData.DataValue) iterator.next();
SynchedEntityData.DataItem<?> datawatcher_item = this.itemsById[datawatcher_c.id];
+ if (io.papermc.paper.disguise.DisguiseUtil.shouldSkip((net.minecraft.world.entity.Entity) entity, datawatcher_item.getAccessor())) continue; // Paper - disguise api
this.assignValue(datawatcher_item, datawatcher_c);
this.entity.onSyncedDataUpdated(datawatcher_item.getAccessor());
}
@@ -158,6 +161,7 @@ public class SynchedEntityData {
public List<SynchedEntityData.DataValue<?>> packAll() {
final List<SynchedEntityData.DataValue<?>> list = new ArrayList<>();
for (final DataItem<?> dataItem : this.itemsById) {
+ if (io.papermc.paper.disguise.DisguiseUtil.shouldSkip((net.minecraft.world.entity.Entity) entity, dataItem.getAccessor())) continue; // Paper - disguise api
list.add(dataItem.value());
}
diff --git a/src/main/java/net/minecraft/server/level/ServerEntity.java b/src/main/java/net/minecraft/server/level/ServerEntity.java
index 90eb4927fa51ce3df86aa7b6c71f49150a03e337..dafc35b8e8cdee2852a73922f10cf2efb834c91f 100644
--- a/src/main/java/net/minecraft/server/level/ServerEntity.java
+++ b/src/main/java/net/minecraft/server/level/ServerEntity.java
@@ -334,6 +334,7 @@ public class ServerEntity {
public void removePairing(ServerPlayer player) {
this.entity.stopSeenByPlayer(player);
+ io.papermc.paper.disguise.DisguiseUtil.tryDespawn(player, this.entity); // Paper - disguise api
player.connection.send(new ClientboundRemoveEntitiesPacket(new int[]{this.entity.getId()}));
}
@@ -355,15 +356,18 @@ public class ServerEntity {
}
Packet<ClientGamePacketListener> packet = this.entity.getAddEntityPacket(this);
-
+ // Paper start - disguise api
+ if(!io.papermc.paper.disguise.DisguiseUtil.tryDisguise(player, entity, packet)){
sender.accept(packet);
+ }
+ // Paper end - disguise api
if (this.trackedDataValues != null) {
- sender.accept(new ClientboundSetEntityDataPacket(this.entity.getId(), this.trackedDataValues));
+ sender.accept(new ClientboundSetEntityDataPacket(this.entity.getId(), io.papermc.paper.disguise.DisguiseUtil.filter(this.entity, this.trackedDataValues))); // Paper - disguise api
}
boolean flag = this.trackDelta;
- if (this.entity instanceof LivingEntity) {
+ if (this.entity instanceof LivingEntity && !io.papermc.paper.disguise.DisguiseUtil.shouldSkipAttributeSending(this.entity)) { // Paper - disguise api
Collection<AttributeInstance> collection = ((LivingEntity) this.entity).getAttributes().getSyncableAttributes();
// CraftBukkit start - If sending own attributes send scaled health instead of current maximum health
@@ -461,7 +465,9 @@ public class ServerEntity {
((ServerPlayer) this.entity).getBukkitEntity().injectScaledMaxHealth(set, false);
}
// CraftBukkit end
+ if(!io.papermc.paper.disguise.DisguiseUtil.shouldSkipAttributeSending(this.entity)) { // Paper start - disguise api
this.broadcastAndSend(new ClientboundUpdateAttributesPacket(this.entity.getId(), set));
+ } // Paper end - disguise api
}
set.clear();
diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java
index 2e8ecf3bbb9f9ceba6f896738fa1ab8e2bd0fed6..9cf061b41f9b2b6e55e1df46ad5b41aec295218f 100644
--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java
+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java
@@ -1867,7 +1867,7 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player imple
return;
}
// CraftBukkit end
- if (this.isSleeping()) {
+ if (this.isSleeping() && io.papermc.paper.disguise.DisguiseUtil.canSendAnimation(this) /* Paper - disguise api */) {
this.serverLevel().getChunkSource().broadcastAndSend(this, new ClientboundAnimatePacket(this, 2));
}
@@ -2406,11 +2406,13 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player imple
@Override
public void crit(Entity target) {
+ if(!io.papermc.paper.disguise.DisguiseUtil.canSendAnimation(this)) return; // Paper - disguise api
this.serverLevel().getChunkSource().broadcastAndSend(this, new ClientboundAnimatePacket(target, 4));
}
@Override
public void magicCrit(Entity target) {
+ if(!io.papermc.paper.disguise.DisguiseUtil.canSendAnimation(this)) return; // Paper - disguise api
this.serverLevel().getChunkSource().broadcastAndSend(this, new ClientboundAnimatePacket(target, 5));
}
diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java
index 8be1b051543cda2b2e9e3d337834757e53f442de..0f57a951237b4ddef223ca9c759efe8d6b8820e9 100644
--- a/src/main/java/net/minecraft/world/entity/Entity.java
+++ b/src/main/java/net/minecraft/world/entity/Entity.java
@@ -781,6 +781,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
final List<SynchedEntityData.DataValue<?>> values = new java.util.ArrayList<>(keys.size());
for (final EntityDataAccessor<?> key : keys) {
+ if (io.papermc.paper.disguise.DisguiseUtil.shouldSkip(this, key)) continue; // Paper - disguise api
final SynchedEntityData.DataItem<?> synchedValue = this.entityData.getItem(key);
values.add(synchedValue.value());
}
diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java
index f36a075dbee2b96d01899e02460b1d8443e91749..56e6a3eda5164378620dd027d493583aeff9ab06 100644
--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java
+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java
@@ -1343,6 +1343,7 @@ public abstract class LivingEntity extends Entity implements Attackable {
private void refreshDirtyAttributes() {
Set<AttributeInstance> set = this.getAttributes().getAttributesToUpdate();
+ if (io.papermc.paper.disguise.DisguiseUtil.shouldSkipAttributeSending(this)) return; // Paper - disguise api
Iterator iterator = set.iterator();
while (iterator.hasNext()) {
@@ -2600,6 +2601,7 @@ public abstract class LivingEntity extends Entity implements Attackable {
this.swinging = true;
this.swingingArm = hand;
if (this.level() instanceof ServerLevel) {
+ if(!io.papermc.paper.disguise.DisguiseUtil.canSendAnimation(this)) return; // Paper - disguise api
ClientboundAnimatePacket packetplayoutanimation = new ClientboundAnimatePacket(this, hand == InteractionHand.MAIN_HAND ? 0 : 3);
ServerChunkCache chunkproviderserver = ((ServerLevel) this.level()).getChunkSource();
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
index 806e56cb60235a99f468d36a059fdbd54c2d46e3..22c37df8b9f78de848ac196e5aa0057f1a31c8fe 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
@@ -383,6 +383,12 @@ public final class CraftServer implements Server {
return ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(((org.bukkit.craftbukkit.entity.CraftEntity) entity).getHandleRaw());
}
// Paper end - Folia reagion threading API
+ // Paper start - add disguise api
+ @Override
+ public com.destroystokyo.paper.SkinParts.@org.jetbrains.annotations.NotNull Builder newSkinPartsBuilder() {
+ return com.destroystokyo.paper.PaperSkinParts.builder();
+ }
+ // Paper end - add disguise api
static {
ConfigurationSerialization.registerClass(CraftOfflinePlayer.class);
diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
index ddabaed899c755925ad8618b78c33dacaf2126ac..fedd8f064a5c398da828be61e8d8c9a364f7af04 100644
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
@@ -1306,4 +1306,18 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity {
}
}
// Paper end - broadcast hurt animation
+ // Paper start - disguise api
+ private io.papermc.paper.disguise.DisguiseData disguiseData = io.papermc.paper.disguise.DisguiseData.original();
+ @Override
+ public @org.jetbrains.annotations.NotNull io.papermc.paper.disguise.DisguiseData getDisguiseData() {
+ return disguiseData;
+ }
+
+ @Override
+ public void setDisguiseData(@org.jetbrains.annotations.NotNull io.papermc.paper.disguise.DisguiseData disguiseData) {
+ getHandle().moonrise$getTrackedEntity().moonrise$clearPlayers();
+ this.disguiseData = disguiseData;
+ this.getHandle().moonrise$getTrackedEntity().updatePlayers(((ServerLevel)getHandle().getCommandSenderWorld()).players());
+ }
+ // Paper end - disguise api
}
diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
index d0010dfd22463986bf3be9b3ee015ce92735753e..76d2b33cdb433772edbd785e9eeb91b74471e949 100644
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
@@ -2848,7 +2848,9 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
// SPIGOT-3813: Attributes before health
if (this.getHandle().connection != null) {
+ if(!io.papermc.paper.disguise.DisguiseUtil.shouldSkipAttributeSending(this.getHandle())){ // Paper start - disguise api
this.getHandle().connection.send(new ClientboundUpdateAttributesPacket(this.getHandle().getId(), set));
+ } // Paper end - disguise api
if (sendHealth) {
this.sendHealthUpdate();
}

View File

@ -41,6 +41,7 @@ for (name in listOf("Paper-API", "Paper-Server")) {
optionalInclude("test-plugin") optionalInclude("test-plugin")
optionalInclude("paper-api-generator") optionalInclude("paper-api-generator")
optionalInclude("paper-server-generator")
fun optionalInclude(name: String, op: (ProjectDescriptor.() -> Unit)? = null) { fun optionalInclude(name: String, op: (ProjectDescriptor.() -> Unit)? = null) {
val settingsFile = file("$name.settings.gradle.kts") val settingsFile = file("$name.settings.gradle.kts")