diff --git a/nmsutils-plugin/pom.xml b/nmsutils-plugin/pom.xml
index 60cc40e..3e9e343 100644
--- a/nmsutils-plugin/pom.xml
+++ b/nmsutils-plugin/pom.xml
@@ -56,6 +56,7 @@
de.cubeside.nmsutils:nmsutils-v1_19_R1_1
de.cubeside.nmsutils:nmsutils-v1_19_R2
de.cubeside.nmsutils:nmsutils-v1_19_R3
+ de.cubeside.nmsutils:nmsutils-v1_20_R1
diff --git a/nmsutils-v1_20_R1/pom.xml b/nmsutils-v1_20_R1/pom.xml
new file mode 100644
index 0000000..2f1ea61
--- /dev/null
+++ b/nmsutils-v1_20_R1/pom.xml
@@ -0,0 +1,100 @@
+
+ 4.0.0
+ nmsutils-v1_20_R1
+
+ de.cubeside.nmsutils
+ nmsutils-parent
+ 0.0.1-SNAPSHOT
+
+
+
+ io.papermc.paper
+ paper-api
+ 1.20-R0.1-SNAPSHOT
+ provided
+
+
+ io.papermc.paper
+ paper-server
+ 1.20-R0.1-SNAPSHOT
+ mojang-mapped
+ provided
+
+
+ ${project.groupId}
+ nmsutils-core
+ ${project.version}
+ provided
+
+
+ net.fabricmc
+ mapping-io
+ 0.3.0
+ provided
+
+
+ com.mojang
+ datafixerupper
+ 5.0.28
+ provided
+
+
+
+
+ minecraft-repo
+ https://libraries.minecraft.net/
+
+
+ spigot-repo
+ https://hub.spigotmc.org/nexus/content/groups/public/
+
+
+ fabric-repo
+ https://maven.fabricmc.net/
+
+
+
+
+
+ maven-compiler-plugin
+ 3.8.1
+
+ 17
+
+
+
+ net.md-5
+ specialsource-maven-plugin
+ 1.2.3
+
+
+ package
+
+ remap
+
+ remap-obf
+
+ org.spigotmc:minecraft-server:1.20-R0.1-SNAPSHOT:txt:maps-mojang
+ true
+ org.spigotmc:spigot:1.20-R0.1-SNAPSHOT:jar:remapped-mojang
+ true
+ remapped-obf
+
+
+
+ package
+
+ remap
+
+ remap-spigot
+
+ ${project.build.directory}/${project.artifactId}-${project.version}-remapped-obf.jar
+ org.spigotmc:minecraft-server:1.20-R0.1-SNAPSHOT:csrg:maps-spigot
+ org.spigotmc:spigot:1.20-R0.1-SNAPSHOT:jar:remapped-obf
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/nmsutils-v1_20_R1/src/main/java/de/cubeside/nmsutils/v1_20_R1/BiomeUtilsImpl.java b/nmsutils-v1_20_R1/src/main/java/de/cubeside/nmsutils/v1_20_R1/BiomeUtilsImpl.java
new file mode 100644
index 0000000..8cbeda8
--- /dev/null
+++ b/nmsutils-v1_20_R1/src/main/java/de/cubeside/nmsutils/v1_20_R1/BiomeUtilsImpl.java
@@ -0,0 +1,154 @@
+package de.cubeside.nmsutils.v1_20_R1;
+
+import com.mojang.serialization.Lifecycle;
+import de.cubeside.nmsutils.BiomeUtils;
+import de.cubeside.nmsutils.NMSUtils;
+import de.cubeside.nmsutils.biome.CustomBiome;
+import de.cubeside.nmsutils.util.ReobfHelper;
+import java.lang.reflect.Field;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
+import net.minecraft.core.BlockPos;
+import net.minecraft.core.Holder;
+import net.minecraft.core.Holder.Reference;
+import net.minecraft.core.MappedRegistry;
+import net.minecraft.core.WritableRegistry;
+import net.minecraft.core.registries.Registries;
+import net.minecraft.resources.ResourceKey;
+import net.minecraft.resources.ResourceLocation;
+import net.minecraft.server.dedicated.DedicatedServer;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.biome.AmbientMoodSettings;
+import net.minecraft.world.level.biome.Biome;
+import net.minecraft.world.level.biome.Biome.BiomeBuilder;
+import net.minecraft.world.level.biome.Biome.TemperatureModifier;
+import net.minecraft.world.level.biome.BiomeSpecialEffects;
+import net.minecraft.world.level.biome.BiomeSpecialEffects.Builder;
+import net.minecraft.world.level.biome.Biomes;
+import net.minecraft.world.level.chunk.LevelChunk;
+import org.bukkit.Location;
+import org.bukkit.NamespacedKey;
+import org.bukkit.Server;
+import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R1.CraftWorld;
+
+public class BiomeUtilsImpl implements BiomeUtils {
+ private final NMSUtilsImpl nmsUtils;
+
+ private HashMap customBiomes;
+ private HashMap customBiomesByBiome;
+ private Collection extends CustomBiome> unmodifiableCustomBiomes;
+
+ private static final Field FIELD_MAPPED_REGISTRY_FROZEN = ReobfHelper.getFieldByMojangName(MappedRegistry.class, "frozen");
+ private static final Field FIELD_MAPPED_REGISTRY_UNREGISTERED_INTRUSIVE_HOLDERS = ReobfHelper.getFieldByMojangName(MappedRegistry.class, "unregisteredIntrusiveHolders");
+
+ public BiomeUtilsImpl(NMSUtilsImpl nmsUtils) {
+ this.nmsUtils = nmsUtils;
+ this.customBiomes = new HashMap<>();
+ this.customBiomesByBiome = new HashMap<>();
+ this.unmodifiableCustomBiomes = Collections.unmodifiableCollection(customBiomes.values());
+ }
+
+ public NMSUtils getNMSUtils() {
+ return nmsUtils;
+ }
+
+ @Override
+ public CustomBiome registerCustomBiome(NamespacedKey id, float downfall, float temperature, de.cubeside.nmsutils.biome.Precipitation precipitation, Integer fogColor, Integer waterColor, Integer waterFogColor, Integer skyColor, Integer foliageColor, Integer grassColor) {
+ Server server = nmsUtils.getPlugin().getServer();
+ CraftServer craftserver = (CraftServer) server;
+ DedicatedServer dedicatedserver = craftserver.getServer();
+ ResourceKey newKey = ResourceKey.create(Registries.BIOME, new ResourceLocation(id.getNamespace(), id.getKey()));
+
+ ResourceKey oldKey = Biomes.FOREST;
+ WritableRegistry registrywritable = (WritableRegistry) dedicatedserver.registryAccess().registryOrThrow(Registries.BIOME);
+
+ try {
+ FIELD_MAPPED_REGISTRY_FROZEN.set(registrywritable, false);
+ FIELD_MAPPED_REGISTRY_UNREGISTERED_INTRUSIVE_HOLDERS.set(registrywritable, new IdentityHashMap<>());
+ } catch (IllegalArgumentException | IllegalAccessException e) {
+ throw new RuntimeException(e);
+ }
+
+ Biome forestbiome = registrywritable.get(oldKey);
+
+ BiomeBuilder builder = new Biome.BiomeBuilder();
+ builder.downfall(downfall);
+ builder.temperature(temperature);
+ boolean mojangPrecipitation = false;
+
+ if (precipitation == de.cubeside.nmsutils.biome.Precipitation.RAIN) {
+ mojangPrecipitation = true;
+ } else if (precipitation == de.cubeside.nmsutils.biome.Precipitation.SNOW) {
+ mojangPrecipitation = true;
+ }
+ builder.hasPrecipitation(mojangPrecipitation);
+ builder.mobSpawnSettings(forestbiome.getMobSettings());
+ builder.generationSettings(forestbiome.getGenerationSettings());
+ builder.temperatureAdjustment(TemperatureModifier.NONE);
+ Builder effects = new BiomeSpecialEffects.Builder();
+ effects.waterColor(waterColor == null ? forestbiome.getWaterColor() : waterColor);
+ effects.waterFogColor(waterFogColor == null ? forestbiome.getWaterFogColor() : waterFogColor);
+ effects.fogColor(fogColor == null ? forestbiome.getFogColor() : fogColor);
+ effects.skyColor(skyColor == null ? forestbiome.getSkyColor() : skyColor);
+ if (foliageColor != null) {
+ effects.foliageColorOverride(foliageColor);
+ }
+ if (grassColor != null) {
+ effects.grassColorOverride(grassColor);
+ }
+ effects.ambientMoodSound(AmbientMoodSettings.LEGACY_CAVE_SETTINGS);
+ builder.specialEffects(effects.build());
+
+ Biome newbiome = builder.build();
+
+ registrywritable.createIntrusiveHolder(newbiome);
+ Reference biomeHolder = registrywritable.register(newKey, newbiome, Lifecycle.stable());
+
+ try {
+ FIELD_MAPPED_REGISTRY_UNREGISTERED_INTRUSIVE_HOLDERS.set(registrywritable, null);
+ } catch (IllegalArgumentException | IllegalAccessException e) {
+ throw new RuntimeException(e);
+ }
+ CustomBiomeImpl impl = new CustomBiomeImpl(id, newKey, newbiome, biomeHolder);
+ customBiomes.put(id, impl);
+ customBiomesByBiome.put(newbiome, impl);
+
+ return new CustomBiomeImpl(id, newKey, newbiome, biomeHolder);
+ }
+
+ @Override
+ public Collection extends CustomBiome> getAllCustomBiomes() {
+ return unmodifiableCustomBiomes;
+ }
+
+ @Override
+ public CustomBiome getCustomBiome(NamespacedKey id) {
+ return customBiomes.get(id);
+ }
+
+ @Override
+ public CustomBiome getCustomBiomeAt(Location location) {
+ location.getWorld().getChunkAt(location);
+ Level level = ((CraftWorld) location.getWorld()).getHandle();
+ int x = location.getBlockX();
+ int y = location.getBlockY();
+ int z = location.getBlockZ();
+
+ BlockPos pos = new BlockPos(x, 0, z);
+ if (level.isLoaded(pos)) {
+ LevelChunk chunk = level.getChunkAt(pos);
+ if (chunk != null) {
+
+ Holder biomeHolder = chunk.getNoiseBiome(x >> 2, y >> 2, z >> 2);
+ if (biomeHolder.isBound()) {
+ Biome biome = biomeHolder.value();
+ return customBiomesByBiome.get(biome);
+ }
+ }
+ }
+ return null;
+ }
+}
diff --git a/nmsutils-v1_20_R1/src/main/java/de/cubeside/nmsutils/v1_20_R1/CustomBiomeImpl.java b/nmsutils-v1_20_R1/src/main/java/de/cubeside/nmsutils/v1_20_R1/CustomBiomeImpl.java
new file mode 100644
index 0000000..593db26
--- /dev/null
+++ b/nmsutils-v1_20_R1/src/main/java/de/cubeside/nmsutils/v1_20_R1/CustomBiomeImpl.java
@@ -0,0 +1,49 @@
+package de.cubeside.nmsutils.v1_20_R1;
+
+import de.cubeside.nmsutils.biome.CustomBiome;
+import net.minecraft.core.BlockPos.MutableBlockPos;
+import net.minecraft.core.Holder.Reference;
+import net.minecraft.resources.ResourceKey;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.biome.Biome;
+import net.minecraft.world.level.chunk.LevelChunk;
+import org.bukkit.Location;
+import org.bukkit.NamespacedKey;
+import org.bukkit.craftbukkit.v1_20_R1.CraftWorld;
+
+public class CustomBiomeImpl implements CustomBiome {
+ private NamespacedKey bukkitKey;
+ private Reference biomeHolder;
+ private MutableBlockPos pos = new MutableBlockPos();
+
+ public CustomBiomeImpl(NamespacedKey bukkitKey, ResourceKey key, Biome biome, Reference biomeHolder) {
+ this.bukkitKey = bukkitKey;
+ this.biomeHolder = biomeHolder;
+ }
+
+ @Override
+ public NamespacedKey getId() {
+ return bukkitKey;
+ }
+
+ @Override
+ public boolean setBiome(Location location) {
+ location.getWorld().getChunkAt(location);
+ Level level = ((CraftWorld) location.getWorld()).getHandle();
+ int x = location.getBlockX();
+ int y = location.getBlockY();
+ int z = location.getBlockZ();
+
+ pos.set(x, 0, z);
+ if (level.isLoaded(pos)) {
+ LevelChunk chunk = level.getChunkAt(pos);
+ if (chunk != null) {
+
+ chunk.setBiome(x >> 2, y >> 2, z >> 2, biomeHolder);
+ chunk.setUnsaved(true);
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/nmsutils-v1_20_R1/src/main/java/de/cubeside/nmsutils/v1_20_R1/EntityUtilsImpl.java b/nmsutils-v1_20_R1/src/main/java/de/cubeside/nmsutils/v1_20_R1/EntityUtilsImpl.java
new file mode 100644
index 0000000..e5c60f6
--- /dev/null
+++ b/nmsutils-v1_20_R1/src/main/java/de/cubeside/nmsutils/v1_20_R1/EntityUtilsImpl.java
@@ -0,0 +1,345 @@
+package de.cubeside.nmsutils.v1_20_R1;
+
+import com.destroystokyo.paper.entity.ai.VanillaGoal;
+import de.cubeside.nmsutils.EntityUtils;
+import de.cubeside.nmsutils.NMSUtils;
+import java.util.function.Function;
+import java.util.logging.Level;
+import net.minecraft.core.BlockPos;
+import net.minecraft.network.protocol.game.ClientboundTeleportEntityPacket;
+import net.minecraft.server.level.ChunkMap;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.LivingEntity;
+import net.minecraft.world.entity.MoverType;
+import net.minecraft.world.entity.PathfinderMob;
+import net.minecraft.world.entity.ai.goal.FloatGoal;
+import net.minecraft.world.entity.animal.Wolf;
+import net.minecraft.world.entity.animal.camel.Camel;
+import net.minecraft.world.level.ChunkPos;
+import net.minecraft.world.phys.Vec3;
+import org.bukkit.Bukkit;
+import org.bukkit.Chunk;
+import org.bukkit.Location;
+import org.bukkit.World;
+import org.bukkit.craftbukkit.v1_20_R1.CraftWorld;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftBat;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftCamel;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftCreature;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftMob;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftPiglin;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftRaider;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftShulker;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftVex;
+import org.bukkit.craftbukkit.v1_20_R1.entity.CraftWolf;
+import org.bukkit.entity.Bat;
+import org.bukkit.entity.Creature;
+import org.bukkit.entity.EntityType;
+import org.bukkit.entity.Mob;
+import org.bukkit.entity.Pose;
+import org.bukkit.entity.Vex;
+import org.bukkit.util.Vector;
+
+public class EntityUtilsImpl implements EntityUtils {
+ private final NMSUtilsImpl nmsUtils;
+
+ public EntityUtilsImpl(NMSUtilsImpl nmsUtils) {
+ this.nmsUtils = nmsUtils;
+ }
+
+ @Override
+ public NMSUtils getNMSUtils() {
+ return nmsUtils;
+ }
+
+ @Override
+ public void clearAI(org.bukkit.entity.Entity entity) {
+ if (entity instanceof Mob) {
+ nmsUtils.getPlugin().getServer().getMobGoals().removeAllGoals((Mob) entity);
+ }
+ }
+
+ @Override
+ public void addGoalFloat(Mob mob) {
+ if (!Bukkit.getMobGoals().hasGoal(mob, VanillaGoal.FLOAT)) {
+ net.minecraft.world.entity.Mob h = ((CraftMob) mob).getHandle();
+ h.goalSelector.addGoal(1, new FloatGoal(h));
+ }
+ }
+
+ @Override
+ public void addGoalLimitedStrollLand(Creature mob, double velocity, Function checkTargetFunction) {
+ PathfinderMob h = ((CraftCreature) mob).getHandle();
+ h.goalSelector.addGoal(7, new PathfinderGoalLimitedRandomStrollLand(h, velocity, checkTargetFunction));
+ }
+
+ @Override
+ public int getShulkerOpenState(org.bukkit.entity.Entity shulker) {
+ try {
+ if (shulker.getType() != EntityType.SHULKER) {
+ return 0;
+ }
+ CraftShulker cs = (CraftShulker) shulker;
+ return cs.getHandle().getRawPeekAmount();
+ } catch (Exception e) {
+ nmsUtils.getPlugin().getLogger().log(Level.SEVERE, "Could not get shulker open state", e);
+ }
+ return 0;
+ }
+
+ @Override
+ public void setShulkerOpenState(org.bukkit.entity.Entity shulker, int state) {
+ try {
+ if (shulker.getType() != EntityType.SHULKER) {
+ return;
+ }
+ state = Math.max(0, Math.min(Byte.MAX_VALUE, state));
+ CraftShulker cs = (CraftShulker) shulker;
+ cs.getHandle().setRawPeekAmount(state);
+ } catch (Exception e) {
+ nmsUtils.getPlugin().getLogger().log(Level.SEVERE, "Could not set shulker open state", e);
+ }
+ }
+
+ @Override
+ public boolean isPiglinDancing(org.bukkit.entity.Entity piglin) {
+ try {
+ if (piglin.getType() != EntityType.PIGLIN) {
+ return false;
+ }
+ CraftPiglin cs = (CraftPiglin) piglin;
+ return cs.getHandle().isDancing();
+ } catch (Exception e) {
+ nmsUtils.getPlugin().getLogger().log(Level.SEVERE, "Could not get piglin dancing state", e);
+ }
+ return false;
+ }
+
+ @Override
+ public void setPiglinDancing(org.bukkit.entity.Entity piglin, boolean dancing) {
+ try {
+ if (piglin.getType() != EntityType.PIGLIN) {
+ return;
+ }
+ CraftPiglin cs = (CraftPiglin) piglin;
+ cs.getHandle().setDancing(dancing);
+ } catch (Exception e) {
+ nmsUtils.getPlugin().getLogger().log(Level.SEVERE, "Could not set piglin dancing state", e);
+ }
+ }
+
+ @Override
+ public void sendEntityPositionUpdate(org.bukkit.entity.Entity entity) {
+ Entity handle = ((CraftEntity) entity).getHandle();
+ ChunkMap.TrackedEntity ete = handle.tracker;
+ if (ete != null) {
+ ClientboundTeleportEntityPacket positionPacket = new ClientboundTeleportEntityPacket(handle);
+ ete.seenBy.stream().forEach(viewer -> {
+ viewer.send(positionPacket);
+ });
+ }
+ }
+
+ @Override
+ public void moveEntity(org.bukkit.entity.Entity e, double x, double y, double z) {
+ Entity handle = ((CraftEntity) e).getHandle();
+ handle.move(MoverType.SELF, new Vec3(x, y, z));
+ }
+
+ @Override
+ public void moveEntity(org.bukkit.entity.Entity e, Vector v) {
+ moveEntity(e, v.getX(), v.getY(), v.getZ());
+ }
+
+ @Override
+ public void setEntityHeadRotation(org.bukkit.entity.Entity e, float headRotation) {
+ Entity handle = ((CraftEntity) e).getHandle();
+ if (handle instanceof LivingEntity) {
+ // required for goats
+ ((LivingEntity) handle).yHeadRot = headRotation;
+ } else {
+ handle.setYHeadRot(headRotation);
+ }
+ }
+
+ @Override
+ public float getEntityHeadRotation(org.bukkit.entity.Entity e) {
+ Entity handle = ((CraftEntity) e).getHandle();
+ return handle.getYHeadRot();
+ }
+
+ @Override
+ public void setEntityYaw(org.bukkit.entity.Entity e, float yaw) {
+ Entity handle = ((CraftEntity) e).getHandle();
+ handle.setYRot(yaw);
+ }
+
+ @Override
+ public float getEntityYaw(org.bukkit.entity.Entity e) {
+ Entity handle = ((CraftEntity) e).getHandle();
+ return handle.getYRot();
+ }
+
+ @Override
+ public void setEntityPitch(org.bukkit.entity.Entity e, float pitch) {
+ Entity handle = ((CraftEntity) e).getHandle();
+ handle.setXRot(pitch);
+ }
+
+ @Override
+ public float getEntityPitch(org.bukkit.entity.Entity e) {
+ Entity handle = ((CraftEntity) e).getHandle();
+ return handle.getXRot();
+ }
+
+ @Override
+ public void setEntityNavigationTarget(org.bukkit.entity.Entity entity, Location target, double speed) {
+ if (entity instanceof Bat) {
+ ((CraftBat) entity).getHandle().targetPosition = new BlockPos(target.getBlockX(), target.getBlockY(), target.getBlockZ());
+ } else if (entity instanceof Vex) {
+ net.minecraft.world.entity.monster.Vex entityVex = ((CraftVex) entity).getHandle();
+ entityVex.getMoveControl().setWantedPosition(target.getX(), target.getY(), target.getZ(), speed);
+ if (entityVex.getTarget() == null) {
+ entityVex.getLookControl().setLookAt(target.getX(), target.getY(), target.getZ(), 180, 20);
+ }
+ } else if (entity instanceof CraftMob) {
+ ((CraftMob) entity).getHandle().getNavigation().moveTo(target.getX(), target.getY(), target.getZ(), speed);
+ } else {
+ throw new IllegalArgumentException("Cannot set the navigation target for this mob: " + (entity == null ? "null" : entity.getType()));
+ }
+ }
+
+ @Override
+ public void setEntityLeftHanded(org.bukkit.entity.Entity ent, boolean left) {
+ Entity nmsEntity = ((CraftEntity) ent).getHandle();
+ if (nmsEntity instanceof net.minecraft.world.entity.Mob mob) {
+ mob.setLeftHanded(left);
+ }
+ }
+
+ @Override
+ public boolean isEntityLeftHanded(org.bukkit.entity.Entity ent) {
+ Entity nmsEntity = ((CraftEntity) ent).getHandle();
+ if (nmsEntity instanceof net.minecraft.world.entity.Mob mob) {
+ return mob.isLeftHanded();
+ }
+ return false;
+ }
+
+ @Override
+ public boolean isEntityInvisible(org.bukkit.entity.Entity entity) {
+ Entity nmsEntity = ((CraftEntity) entity).getHandle();
+ return nmsEntity.isInvisible();
+ }
+
+ @Override
+ public void setEntityInvisible(org.bukkit.entity.Entity entity, boolean invisible) {
+ Entity nmsEntity = ((CraftEntity) entity).getHandle();
+ nmsEntity.setInvisible(invisible);
+ }
+
+ @Override
+ public boolean hasEntityNoClip(org.bukkit.entity.Entity entity) {
+ Entity nmsEntity = ((CraftEntity) entity).getHandle();
+ return nmsEntity.noPhysics;
+ }
+
+ @Override
+ public void setEntityNoClip(org.bukkit.entity.Entity entity, boolean noClip) {
+ Entity nmsEntity = ((CraftEntity) entity).getHandle();
+ nmsEntity.noPhysics = noClip;
+ }
+
+ @Override
+ public boolean areChunkEntitiesLoaded(Chunk c) {
+ return ((CraftWorld) c.getWorld()).getHandle().areEntitiesLoaded(ChunkPos.asLong(c.getX(), c.getZ()));
+ }
+
+ @Override
+ public void loadChunkEntities(Chunk c) {
+ nmsUtils.getPlugin().getLogger().log(Level.SEVERE, "Call to unimplemented method", new RuntimeException());
+ // int x = c.getX();
+ // int z = c.getZ();
+ // World world = c.getWorld();
+ // if (!world.isChunkLoaded(x, z)) {
+ // world.getChunkAt(x, z);
+ // }
+ //
+ // if (areChunkEntitiesLoaded(c)) {
+ // return;
+ // }
+ // ServerLevel serverLevel = ((CraftWorld) world).getHandle();
+ // serverLevel.entityManager.updateChunkStatus(new ChunkPos(x, z), Visibility.TRACKED);
+ // // now wait until entities are loaded
+ // if (!areChunkEntitiesLoaded(c)) {
+ // serverLevel.getServer().managedBlock(() -> {
+ // if (areChunkEntitiesLoaded(c)) {
+ // return true;
+ // }
+ // // process chunk inbox
+ // serverLevel.entityManager.tick();
+ // return areChunkEntitiesLoaded(c);
+ // });
+ // }
+ }
+
+ @Override
+ public void setOnGround(org.bukkit.entity.Entity entity, boolean onGround) {
+ Entity nmsEntity = ((CraftEntity) entity).getHandle();
+ nmsEntity.setOnGround(onGround);
+ }
+
+ @Override
+ public org.bukkit.entity.Entity getEntityById(World world, int id) {
+ Entity entity = ((CraftWorld) world).getHandle().getEntity(id);
+ return entity == null ? null : entity.getBukkitEntity();
+ }
+
+ @Override
+ public void setPose(org.bukkit.entity.Entity entity, Pose pose) {
+ Entity nmsEntity = ((CraftEntity) entity).getHandle();
+ nmsEntity.setPose(net.minecraft.world.entity.Pose.values()[pose.ordinal()]);
+ }
+
+ @Override
+ public void setWolfAngerTime(org.bukkit.entity.Wolf entity, int timeInTicks) {
+ Wolf nmsEntity = ((CraftWolf) entity).getHandle();
+ nmsEntity.setRemainingPersistentAngerTime(timeInTicks);
+ }
+
+ @Override
+ public void setAggressive(org.bukkit.entity.Mob entity, boolean aggressive) {
+ net.minecraft.world.entity.Mob nmsEntity = ((CraftMob) entity).getHandle();
+ nmsEntity.setAggressive(aggressive);
+ }
+
+ @Override
+ public boolean isAggressive(org.bukkit.entity.Mob entity) {
+ net.minecraft.world.entity.Mob nmsEntity = ((CraftMob) entity).getHandle();
+ return nmsEntity.isAggressive();
+ }
+
+ @Override
+ public boolean isCelebrating(org.bukkit.entity.Raider entity) {
+ net.minecraft.world.entity.raid.Raider nmsEntity = ((CraftRaider) entity).getHandle();
+ return nmsEntity.isCelebrating();
+ }
+
+ @Override
+ public void setCelebrating(org.bukkit.entity.Raider entity, boolean celebrating) {
+ net.minecraft.world.entity.raid.Raider nmsEntity = ((CraftRaider) entity).getHandle();
+ nmsEntity.setCelebrating(celebrating);
+ }
+
+ @Override
+ public void setCamelLastPoseChange(org.bukkit.entity.Camel entity, long tick) {
+ Camel nmsEntity = ((CraftCamel) entity).getHandle();
+ nmsEntity.resetLastPoseChangeTick(nmsEntity.level().getGameTime() - tick);
+ }
+
+ @Override
+ public long getCamelLastPoseChange(org.bukkit.entity.Camel entity) {
+ Camel nmsEntity = ((CraftCamel) entity).getHandle();
+ return nmsEntity.getPoseTime();
+ }
+}
diff --git a/nmsutils-v1_20_R1/src/main/java/de/cubeside/nmsutils/v1_20_R1/MiscUtilsImpl.java b/nmsutils-v1_20_R1/src/main/java/de/cubeside/nmsutils/v1_20_R1/MiscUtilsImpl.java
new file mode 100644
index 0000000..f023b96
--- /dev/null
+++ b/nmsutils-v1_20_R1/src/main/java/de/cubeside/nmsutils/v1_20_R1/MiscUtilsImpl.java
@@ -0,0 +1,82 @@
+package de.cubeside.nmsutils.v1_20_R1;
+
+import de.cubeside.nmsutils.MiscUtils;
+import de.cubeside.nmsutils.NMSUtils;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import net.minecraft.world.level.block.Block;
+import net.minecraft.world.level.block.state.BlockBehaviour;
+import net.minecraft.world.level.block.state.BlockBehaviour.BlockStateBase;
+import net.minecraft.world.level.block.state.BlockBehaviour.Properties;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.level.material.MapColor;
+import org.bukkit.Material;
+import org.bukkit.craftbukkit.v1_20_R1.util.CraftMagicNumbers;
+
+public class MiscUtilsImpl implements MiscUtils {
+ private final NMSUtilsImpl nmsUtils;
+
+ private Field fieldBlockBehaviour_properties;
+ private MapColor transparentColor;
+ private Field fieldBlockStateBase_materialColor;
+
+ public MiscUtilsImpl(NMSUtilsImpl nmsUtils) {
+ this.nmsUtils = nmsUtils;
+ }
+
+ @Override
+ public NMSUtils getNMSUtils() {
+ return nmsUtils;
+ }
+
+ @Override
+ public void setBlockMapColorTransparent(Material m) {
+ if (fieldBlockBehaviour_properties == null) {
+ Class classBlockProperties = BlockBehaviour.Properties.class;
+ Class classBlockBehaviour = BlockBehaviour.class;
+ fieldBlockBehaviour_properties = null;
+ for (Field f : classBlockBehaviour.getDeclaredFields()) {
+ if (f.getType() == classBlockProperties) {
+ fieldBlockBehaviour_properties = f;
+ fieldBlockBehaviour_properties.setAccessible(true);
+ }
+ }
+ if (fieldBlockBehaviour_properties == null) {
+ throw new IllegalStateException("Could not find block properties field!");
+ }
+
+ Class classBlockStateBase = BlockBehaviour.BlockStateBase.class;
+ for (Field f : classBlockStateBase.getDeclaredFields()) {
+ if (f.getType() == MapColor.class) {
+ fieldBlockStateBase_materialColor = f;
+ fieldBlockStateBase_materialColor.setAccessible(true);
+ }
+ }
+ if (fieldBlockStateBase_materialColor == null) {
+ throw new IllegalStateException("Could not find BlockStateBase materialColor field!");
+ }
+
+ try {
+ Constructor constructorMaterialColor = MapColor.class.getDeclaredConstructor(int.class, int.class);
+ constructorMaterialColor.setAccessible(true);
+ transparentColor = constructorMaterialColor.newInstance(0, 0);
+ } catch (ReflectiveOperationException e) {
+ throw new IllegalStateException("Could not create custom transparent MaterialColor!");
+ }
+ }
+
+ if (!m.isBlock()) {
+ throw new IllegalArgumentException("Material must be a block");
+ }
+ Block b = CraftMagicNumbers.getBlock(m);
+ try {
+ BlockBehaviour.Properties properties = (Properties) fieldBlockBehaviour_properties.get(b);
+ properties.mapColor(transparentColor);
+ for (BlockState state : b.getStateDefinition().getPossibleStates()) {
+ fieldBlockStateBase_materialColor.set(state, transparentColor);
+ }
+ } catch (IllegalAccessException e) {
+ throw new IllegalStateException("Could not set the MaterialColor!");
+ }
+ }
+}
diff --git a/nmsutils-v1_20_R1/src/main/java/de/cubeside/nmsutils/v1_20_R1/NMSUtilsImpl.java b/nmsutils-v1_20_R1/src/main/java/de/cubeside/nmsutils/v1_20_R1/NMSUtilsImpl.java
new file mode 100644
index 0000000..510f881
--- /dev/null
+++ b/nmsutils-v1_20_R1/src/main/java/de/cubeside/nmsutils/v1_20_R1/NMSUtilsImpl.java
@@ -0,0 +1,49 @@
+package de.cubeside.nmsutils.v1_20_R1;
+
+import de.cubeside.nmsutils.BiomeUtils;
+import de.cubeside.nmsutils.EntityUtils;
+import de.cubeside.nmsutils.MiscUtils;
+import de.cubeside.nmsutils.NMSUtils;
+import de.cubeside.nmsutils.WorldUtils;
+import org.bukkit.plugin.Plugin;
+
+public class NMSUtilsImpl implements NMSUtils {
+ private final Plugin plugin;
+ private EntityUtils entityUtilsImpl;
+ private WorldUtilsImpl worldUtilsImpl;
+ private MiscUtilsImpl miscUtilsImpl;
+ private BiomeUtilsImpl biomeUtils;
+
+ public NMSUtilsImpl(Plugin plugin) {
+ this.plugin = plugin;
+ this.entityUtilsImpl = new EntityUtilsImpl(this);
+ this.worldUtilsImpl = new WorldUtilsImpl(this);
+ this.miscUtilsImpl = new MiscUtilsImpl(this);
+ this.biomeUtils = new BiomeUtilsImpl(this);
+ }
+
+ @Override
+ public Plugin getPlugin() {
+ return plugin;
+ }
+
+ @Override
+ public EntityUtils getEntityUtils() {
+ return entityUtilsImpl;
+ }
+
+ @Override
+ public WorldUtils getWorldUtils() {
+ return worldUtilsImpl;
+ }
+
+ @Override
+ public MiscUtils getMiscUtils() {
+ return miscUtilsImpl;
+ }
+
+ @Override
+ public BiomeUtils getBiomeUtils() {
+ return biomeUtils;
+ }
+}
diff --git a/nmsutils-v1_20_R1/src/main/java/de/cubeside/nmsutils/v1_20_R1/PathfinderGoalLimitedRandomStrollLand.java b/nmsutils-v1_20_R1/src/main/java/de/cubeside/nmsutils/v1_20_R1/PathfinderGoalLimitedRandomStrollLand.java
new file mode 100644
index 0000000..589fee8
--- /dev/null
+++ b/nmsutils-v1_20_R1/src/main/java/de/cubeside/nmsutils/v1_20_R1/PathfinderGoalLimitedRandomStrollLand.java
@@ -0,0 +1,28 @@
+package de.cubeside.nmsutils.v1_20_R1;
+
+import java.util.function.Function;
+import net.minecraft.world.entity.PathfinderMob;
+import net.minecraft.world.entity.ai.goal.RandomStrollGoal;
+import net.minecraft.world.phys.Vec3;
+import org.bukkit.util.Vector;
+
+public class PathfinderGoalLimitedRandomStrollLand extends RandomStrollGoal {
+
+ private Function checkVectorFunction;
+ // private EntityCreature entity;
+
+ public PathfinderGoalLimitedRandomStrollLand(PathfinderMob entity, double velocity, Function checkVectorFunction) {
+ super(entity, velocity, 1, false);
+ // this.entity = entity;
+ this.checkVectorFunction = checkVectorFunction;
+ }
+
+ @Override
+ protected Vec3 getPosition() {
+ Vec3 base = super.getPosition();
+ if (base == null || checkVectorFunction.apply(org.bukkit.craftbukkit.v1_20_R1.util.CraftVector.toBukkit(base)) != Boolean.TRUE) {
+ base = null;
+ }
+ return base;
+ }
+}
diff --git a/nmsutils-v1_20_R1/src/main/java/de/cubeside/nmsutils/v1_20_R1/WorldUtilsImpl.java b/nmsutils-v1_20_R1/src/main/java/de/cubeside/nmsutils/v1_20_R1/WorldUtilsImpl.java
new file mode 100644
index 0000000..39e294d
--- /dev/null
+++ b/nmsutils-v1_20_R1/src/main/java/de/cubeside/nmsutils/v1_20_R1/WorldUtilsImpl.java
@@ -0,0 +1,98 @@
+package de.cubeside.nmsutils.v1_20_R1;
+
+import de.cubeside.nmsutils.WorldUtils;
+import io.papermc.paper.chunk.system.io.RegionFileIOThread;
+import io.papermc.paper.chunk.system.scheduling.NewChunkHolder;
+import java.util.ArrayList;
+import java.util.logging.Level;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.server.level.ServerPlayer;
+import net.minecraft.world.level.chunk.ChunkStatus;
+import net.minecraft.world.level.chunk.LevelChunk;
+import org.bukkit.Bukkit;
+import org.bukkit.Chunk;
+import org.bukkit.Location;
+import org.bukkit.World;
+import org.bukkit.craftbukkit.v1_20_R1.CraftChunk;
+import org.bukkit.craftbukkit.v1_20_R1.CraftWorld;
+import org.bukkit.entity.Player;
+
+public class WorldUtilsImpl implements WorldUtils {
+ private final NMSUtilsImpl nmsUtils;
+
+ public WorldUtilsImpl(NMSUtilsImpl nmsUtils) {
+ this.nmsUtils = nmsUtils;
+ }
+
+ @Override
+ public void saveWorldNow(World world) {
+ world.save();
+
+ CraftWorld craftWorld = (CraftWorld) world;
+ ServerLevel handle = craftWorld.getHandle();
+ try {
+ handle.save(null, true, false);
+ } catch (Exception e) {
+ Bukkit.getLogger().log(Level.SEVERE, "Exception while saving world", e);
+ }
+ }
+
+ @Override
+ public void forceUnloadWorldWithoutSaving(World world, Location playerTarget) {
+ // final long t0 = System.currentTimeMillis();
+ // String worldName = world.getName();
+
+ if (playerTarget.getWorld() == null || playerTarget.getWorld() == world) {
+ throw new IllegalArgumentException("Valid target world required");
+ }
+ // move players out of this world
+ try {
+ for (Player p : world.getPlayers()) {
+ if (p.isDead()) {
+ p.spigot().respawn();
+ }
+ if (p.getWorld() == world) {
+ p.teleport(playerTarget);
+ }
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ // remove broken players
+ CraftWorld craftWorld = (CraftWorld) world;
+ ServerLevel handle = craftWorld.getHandle();
+ if (!handle.players().isEmpty()) {
+ for (ServerPlayer human : new ArrayList<>(handle.players())) {
+ kickPlayer((human.getBukkitEntity()), "Connection lost");
+ }
+ handle.players().clear();
+ }
+
+ // unload the now empty world from the server
+ if (!nmsUtils.getPlugin().getServer().unloadWorld(world, false)) {
+ throw new IllegalStateException("Could not unload world");
+ }
+
+ // long t = System.currentTimeMillis();
+ // nmsUtils.getPlugin().getLogger().info("Unloading world " + worldName + " completed in " + (t - t0) + "ms.");
+ }
+
+ @Override
+ public void saveChunkNow(Chunk chunk) {
+ CraftChunk craftChunk = (CraftChunk) chunk;
+ if (craftChunk.getHandle(ChunkStatus.FULL) instanceof LevelChunk levelChunk) {
+ NewChunkHolder holder = levelChunk.getChunkHolder();
+ holder.save(false, false);
+ }
+ }
+
+ @Override
+ public void flushChunkSaves() {
+ RegionFileIOThread.flush();
+ }
+
+ @SuppressWarnings("deprecation")
+ private void kickPlayer(Player player, String message) {
+ player.kickPlayer(message);
+ }
+}
diff --git a/nmsutils/pom.xml b/nmsutils/pom.xml
index c9575bc..adc4771 100644
--- a/nmsutils/pom.xml
+++ b/nmsutils/pom.xml
@@ -15,6 +15,22 @@
+
+ v1_20
+
+ true
+
+ all
+
+
+
+
+ ${project.groupId}
+ nmsutils-v1_20_R1
+ ${project.version}
+
+
+
v1_19_4
@@ -34,7 +50,6 @@
v1_19
- true
all
diff --git a/pom.xml b/pom.xml
index a22d02b..685d16f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -24,6 +24,18 @@
+
+ v1_20
+
+ true
+
+ all
+
+
+
+ nmsutils-v1_20_R1
+
+
v1_19_4
@@ -39,7 +51,6 @@
v1_19
- true
all