Add a BiomeCategories API (#2338) (#2777)

* Add a BiomeCategories API (#2338)

* Add a BiomeCategories API

* licenses

* Use a supplier to retain the lazy-load & dynamicness of the existing system, but still retaining the inversion of control that this PR was intended to provide

* Minor fawe adapter cleanup

* Actually add the new files?

* Fixes

---------

Co-authored-by: Maddy Miller <mnmiller1@me.com>
This commit is contained in:
Jordan
2024-06-27 15:15:14 +02:00
committed by GitHub
parent ad5739e014
commit fee9029bf0
25 changed files with 975 additions and 371 deletions

View File

@ -23,6 +23,7 @@ import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.common.util.concurrent.Futures;
import com.mojang.datafixers.util.Either;
@ -56,6 +57,7 @@ import com.sk89q.worldedit.util.formatting.text.TranslatableComponent;
import com.sk89q.worldedit.util.io.file.SafeFiles;
import com.sk89q.worldedit.world.DataFixer;
import com.sk89q.worldedit.world.RegenOptions;
import com.sk89q.worldedit.world.biome.BiomeCategory;
import com.sk89q.worldedit.world.biome.BiomeType;
import com.sk89q.worldedit.world.biome.BiomeTypes;
import com.sk89q.worldedit.world.block.BaseBlock;
@ -67,6 +69,8 @@ import com.sk89q.worldedit.world.item.ItemType;
import net.minecraft.Util;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Holder;
import net.minecraft.core.HolderSet;
import net.minecraft.core.Registry;
import net.minecraft.core.registries.Registries;
import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket;
import net.minecraft.network.protocol.game.ClientboundEntityEventPacket;
@ -78,6 +82,7 @@ import net.minecraft.server.level.ChunkHolder;
import net.minecraft.server.level.ServerChunkCache;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.progress.ChunkProgressListener;
import net.minecraft.util.RandomSource;
import net.minecraft.util.StringRepresentable;
import net.minecraft.util.thread.BlockableEventLoop;
import net.minecraft.world.Clearable;
@ -108,6 +113,7 @@ import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.Vec3;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.World.Environment;
import org.bukkit.block.data.BlockData;
import org.bukkit.craftbukkit.v1_20_R2.CraftServer;
@ -147,6 +153,7 @@ import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
@ -174,6 +181,8 @@ public final class PaperweightAdapter implements BukkitImplAdapter<net.minecraft
private final Field chunkProviderExecutorField;
private final Watchdog watchdog;
private static final RandomSource random = RandomSource.create();
// ------------------------------------------------------------------------
// Code that may break between versions of Minecraft
// ------------------------------------------------------------------------
@ -191,13 +200,13 @@ public final class PaperweightAdapter implements BukkitImplAdapter<net.minecraft
serverWorldsField.setAccessible(true);
getChunkFutureMethod = ServerChunkCache.class.getDeclaredMethod(
Refraction.pickName("getChunkFutureMainThread", "c"),
int.class, int.class, ChunkStatus.class, boolean.class
Refraction.pickName("getChunkFutureMainThread", "c"),
int.class, int.class, ChunkStatus.class, boolean.class
);
getChunkFutureMethod.setAccessible(true);
chunkProviderExecutorField = ServerChunkCache.class.getDeclaredField(
Refraction.pickName("mainThreadProcessor", "g")
Refraction.pickName("mainThreadProcessor", "g")
);
chunkProviderExecutorField.setAccessible(true);
@ -282,7 +291,6 @@ public final class PaperweightAdapter implements BukkitImplAdapter<net.minecraft
}
private static Block getBlockFromType(BlockType blockType) {
return DedicatedServer.getServer().registryAccess().registryOrThrow(Registries.BLOCK).get(ResourceLocation.tryParse(blockType.id()));
}
@ -307,6 +315,29 @@ public final class PaperweightAdapter implements BukkitImplAdapter<net.minecraft
return combinedId == 0 && state.getBlockType() != BlockTypes.AIR ? OptionalInt.empty() : OptionalInt.of(combinedId);
}
public BlockState adapt(net.minecraft.world.level.block.state.BlockState blockState) {
int internalId = Block.getId(blockState);
BlockState state = BlockStateIdAccess.getBlockStateById(internalId);
if (state == null) {
state = BukkitAdapter.adapt(CraftBlockData.createData(blockState));
}
return state;
}
public BiomeType adapt(Biome biome) {
var mcBiome = ((CraftServer) Bukkit.getServer()).getServer().registryAccess().registryOrThrow(Registries.BIOME).getKey(biome);
if (mcBiome == null) {
return null;
}
return BiomeType.REGISTRY.get(mcBiome.toString());
}
public net.minecraft.world.level.block.state.BlockState adapt(BlockState blockState) {
int internalId = BlockStateIdAccess.getBlockStateId(blockState);
return Block.stateById(internalId);
}
@Override
public BlockState getBlock(Location location) {
checkNotNull(location);
@ -320,14 +351,7 @@ public final class PaperweightAdapter implements BukkitImplAdapter<net.minecraft
LevelChunk chunk = handle.getChunk(x >> 4, z >> 4);
final BlockPos blockPos = new BlockPos(x, y, z);
final net.minecraft.world.level.block.state.BlockState blockData = chunk.getBlockState(blockPos);
int internalId = Block.getId(blockData);
BlockState state = BlockStateIdAccess.getBlockStateById(internalId);
if (state == null) {
org.bukkit.block.Block bukkitBlock = location.getBlock();
state = BukkitAdapter.adapt(bukkitBlock.getBlockData());
}
return state;
return adapt(blockData);
}
@Override
@ -357,8 +381,40 @@ public final class PaperweightAdapter implements BukkitImplAdapter<net.minecraft
private static final HashMap<Holder<Biome>, BiomeType> biomeTypeFromNMSCache = new HashMap<>();
@Override
public WorldNativeAccess<?, ?, ?> createWorldNativeAccess(org.bukkit.World world) {
return new PaperweightWorldNativeAccess(this, new WeakReference<>(((CraftWorld) world).getHandle()));
public BiomeType getBiome(Location location) {
checkNotNull(location);
CraftWorld craftWorld = ((CraftWorld) location.getWorld());
int x = location.getBlockX();
int y = location.getBlockY();
int z = location.getBlockZ();
final ServerLevel handle = craftWorld.getHandle();
LevelChunk chunk = handle.getChunk(x >> 4, z >> 4);
return biomeTypeFromNMSCache.computeIfAbsent(chunk.getNoiseBiome(x >> 2, y >> 2, z >> 2), b -> BiomeType.REGISTRY.get(b.unwrapKey().get().location().toString()));
}
@Override
public void setBiome(Location location, BiomeType biome) {
checkNotNull(location);
checkNotNull(biome);
CraftWorld craftWorld = ((CraftWorld) location.getWorld());
int x = location.getBlockX();
int y = location.getBlockY();
int z = location.getBlockZ();
final ServerLevel handle = craftWorld.getHandle();
LevelChunk chunk = handle.getChunk(x >> 4, z >> 4);
chunk.setBiome(x >> 2, y >> 2, z >> 2, biomeTypeToNMSCache.computeIfAbsent(biome, b -> ((CraftServer) Bukkit.getServer()).getServer().registryAccess().registryOrThrow(Registries.BIOME).getHolderOrThrow(ResourceKey.create(Registries.BIOME, new ResourceLocation(b.id())))));
chunk.setUnsaved(true);
}
@Override
public WorldNativeAccess<?, ?, ?> createWorldNativeAccess(World world) {
return new PaperweightWorldNativeAccess(this,
new WeakReference<>(((CraftWorld) world).getHandle()));
}
private static net.minecraft.core.Direction adapt(Direction face) {
@ -381,13 +437,13 @@ public final class PaperweightAdapter implements BukkitImplAdapter<net.minecraft
@SuppressWarnings({"rawtypes", "unchecked"})
private net.minecraft.world.level.block.state.BlockState applyProperties(
StateDefinition<Block, net.minecraft.world.level.block.state.BlockState> stateContainer,
net.minecraft.world.level.block.state.BlockState newState,
Map<Property<?>, Object> states
StateDefinition<Block, net.minecraft.world.level.block.state.BlockState> stateContainer,
net.minecraft.world.level.block.state.BlockState newState,
Map<Property<?>, Object> states
) {
for (Map.Entry<Property<?>, Object> state : states.entrySet()) {
net.minecraft.world.level.block.state.properties.Property<?> property =
stateContainer.getProperty(state.getKey().getName());
stateContainer.getProperty(state.getKey().getName());
Comparable<?> value = (Comparable) state.getValue();
// we may need to adapt this value, depending on the source prop
if (property instanceof DirectionProperty) {
@ -396,16 +452,16 @@ public final class PaperweightAdapter implements BukkitImplAdapter<net.minecraft
} else if (property instanceof net.minecraft.world.level.block.state.properties.EnumProperty) {
String enumName = (String) value;
value = ((net.minecraft.world.level.block.state.properties.EnumProperty<?>) property)
.getValue(enumName).orElseThrow(() ->
new IllegalStateException(
"Enum property " + property.getName() + " does not contain " + enumName
)
);
.getValue(enumName).orElseThrow(() ->
new IllegalStateException(
"Enum property " + property.getName() + " does not contain " + enumName
)
);
}
newState = newState.setValue(
(net.minecraft.world.level.block.state.properties.Property) property,
(Comparable) value
(net.minecraft.world.level.block.state.properties.Property) property,
(Comparable) value
);
}
return newState;
@ -502,10 +558,10 @@ public final class PaperweightAdapter implements BukkitImplAdapter<net.minecraft
return new BooleanProperty(state.getName(), ImmutableList.copyOf(state.getPossibleValues()));
} else if (state instanceof DirectionProperty) {
return new DirectionalProperty(state.getName(),
(List<Direction>) state.getPossibleValues().stream().map(e -> Direction.valueOf(((StringRepresentable) e).getSerializedName().toUpperCase(Locale.ROOT))).collect(Collectors.toList()));
(List<Direction>) state.getPossibleValues().stream().map(e -> Direction.valueOf(((StringRepresentable) e).getSerializedName().toUpperCase(Locale.ROOT))).toList());
} else if (state instanceof net.minecraft.world.level.block.state.properties.EnumProperty) {
return new EnumProperty(state.getName(),
(List<String>) state.getPossibleValues().stream().map(e -> ((StringRepresentable) e).getSerializedName()).collect(Collectors.toList()));
(List<String>) state.getPossibleValues().stream().map(e -> ((StringRepresentable) e).getSerializedName()).toList());
} else if (state instanceof net.minecraft.world.level.block.state.properties.IntegerProperty) {
return new IntegerProperty(state.getName(), ImmutableList.copyOf(state.getPossibleValues()));
} else {
@ -520,7 +576,7 @@ public final class PaperweightAdapter implements BukkitImplAdapter<net.minecraft
Map<String, Property<?>> properties = new TreeMap<>();
Block block = getBlockFromType(blockType);
StateDefinition<Block, net.minecraft.world.level.block.state.BlockState> blockStateList =
block.getStateDefinition();
block.getStateDefinition();
for (net.minecraft.world.level.block.state.properties.Property state : blockStateList.getProperties()) {
Property<?> property = PROPERTY_CACHE.getUnchecked(state);
properties.put(property.getName(), property);
@ -542,15 +598,15 @@ public final class PaperweightAdapter implements BukkitImplAdapter<net.minecraft
@Override
public void sendFakeOP(Player player) {
((CraftPlayer) player).getHandle().connection.send(new ClientboundEntityEventPacket(
((CraftPlayer) player).getHandle(), (byte) 28
((CraftPlayer) player).getHandle(), (byte) 28
));
}
@Override
public org.bukkit.inventory.ItemStack adapt(BaseItemStack item) {
ItemStack stack = new ItemStack(
DedicatedServer.getServer().registryAccess().registryOrThrow(Registries.ITEM).get(ResourceLocation.tryParse(item.getType().id())),
item.getAmount()
DedicatedServer.getServer().registryAccess().registryOrThrow(Registries.ITEM).get(ResourceLocation.tryParse(item.getType().id())),
item.getAmount()
);
stack.setTag(((net.minecraft.nbt.CompoundTag) fromNative(item.getNbtData())));
return CraftItemStack.asCraftMirror(stack);
@ -565,7 +621,7 @@ public final class PaperweightAdapter implements BukkitImplAdapter<net.minecraft
}
private final LoadingCache<ServerLevel, PaperweightFakePlayer> fakePlayers
= CacheBuilder.newBuilder().weakKeys().softValues().build(CacheLoader.from(PaperweightFakePlayer::new));
= CacheBuilder.newBuilder().weakKeys().softValues().build(CacheLoader.from(PaperweightFakePlayer::new));
@Override
public boolean simulateItemUse(org.bukkit.World world, BlockVector3 position, BaseItem item, Direction face) {
@ -583,7 +639,7 @@ public final class PaperweightAdapter implements BukkitImplAdapter<net.minecraft
}
fakePlayer.setItemInHand(InteractionHand.MAIN_HAND, stack);
fakePlayer.absMoveTo(position.x(), position.y(), position.z(),
(float) face.toVector().toYaw(), (float) face.toVector().toPitch());
(float) face.toVector().toYaw(), (float) face.toVector().toPitch());
final BlockPos blockPos = new BlockPos(position.x(), position.y(), position.z());
final Vec3 blockVec = Vec3.atLowerCornerOf(blockPos);
@ -603,14 +659,14 @@ public final class PaperweightAdapter implements BukkitImplAdapter<net.minecraft
}
@Override
public boolean canPlaceAt(org.bukkit.World world, BlockVector3 position, BlockState blockState) {
public boolean canPlaceAt(World world, BlockVector3 position, BlockState blockState) {
int internalId = BlockStateIdAccess.getBlockStateId(blockState);
net.minecraft.world.level.block.state.BlockState blockData = Block.stateById(internalId);
return blockData.canSurvive(((CraftWorld) world).getHandle(), new BlockPos(position.x(), position.y(), position.z()));
}
@Override
public boolean regenerate(org.bukkit.World bukkitWorld, Region region, Extent extent, RegenOptions options) {
public boolean regenerate(World bukkitWorld, Region region, Extent extent, RegenOptions options) {
try {
doRegen(bukkitWorld, region, extent, options);
} catch (Exception e) {
@ -620,7 +676,7 @@ public final class PaperweightAdapter implements BukkitImplAdapter<net.minecraft
return true;
}
private void doRegen(org.bukkit.World bukkitWorld, Region region, Extent extent, RegenOptions options) throws Exception {
private void doRegen(World bukkitWorld, Region region, Extent extent, RegenOptions options) throws Exception {
Environment env = bukkitWorld.getEnvironment();
ChunkGenerator gen = bukkitWorld.getGenerator();
@ -630,51 +686,52 @@ public final class PaperweightAdapter implements BukkitImplAdapter<net.minecraft
try (LevelStorageSource.LevelStorageAccess session = levelStorage.createAccess("faweregentempworld", worldDimKey)) {
ServerLevel originalWorld = ((CraftWorld) bukkitWorld).getHandle();
PrimaryLevelData levelProperties = (PrimaryLevelData) originalWorld.getServer()
.getWorldData().overworldData();
.getWorldData().overworldData();
WorldOptions originalOpts = levelProperties.worldGenOptions();
long seed = options.getSeed().orElse(originalWorld.getSeed());
WorldOptions newOpts = options.getSeed().isPresent()
? originalOpts.withSeed(OptionalLong.of(seed))
: originalOpts;
? originalOpts.withSeed(OptionalLong.of(seed))
: originalOpts;
LevelSettings newWorldSettings = new LevelSettings(
"faweregentempworld",
levelProperties.settings.gameType(),
levelProperties.settings.hardcore(),
levelProperties.settings.difficulty(),
levelProperties.settings.allowCommands(),
levelProperties.settings.gameRules(),
levelProperties.settings.getDataConfiguration()
"faweregentempworld",
levelProperties.settings.gameType(),
levelProperties.settings.hardcore(),
levelProperties.settings.difficulty(),
levelProperties.settings.allowCommands(),
levelProperties.settings.gameRules(),
levelProperties.settings.getDataConfiguration()
);
@SuppressWarnings("deprecation")
PrimaryLevelData.SpecialWorldProperty specialWorldProperty =
levelProperties.isFlatWorld()
? PrimaryLevelData.SpecialWorldProperty.FLAT
: levelProperties.isDebugWorld()
? PrimaryLevelData.SpecialWorldProperty.DEBUG
: PrimaryLevelData.SpecialWorldProperty.NONE;
levelProperties.isFlatWorld()
? PrimaryLevelData.SpecialWorldProperty.FLAT
: levelProperties.isDebugWorld()
? PrimaryLevelData.SpecialWorldProperty.DEBUG
: PrimaryLevelData.SpecialWorldProperty.NONE;
PrimaryLevelData newWorldData = new PrimaryLevelData(newWorldSettings, newOpts, specialWorldProperty, Lifecycle.stable());
ServerLevel freshWorld = new ServerLevel(
originalWorld.getServer(),
originalWorld.getServer().executor,
session, newWorldData,
originalWorld.dimension(),
new LevelStem(
originalWorld.dimensionTypeRegistration(),
originalWorld.getChunkSource().getGenerator()
),
new NoOpWorldLoadListener(),
originalWorld.isDebug(),
seed,
ImmutableList.of(),
false,
originalWorld.getRandomSequences(),
env,
gen,
bukkitWorld.getBiomeProvider()
originalWorld.getServer(),
originalWorld.getServer().executor,
session, newWorldData,
originalWorld.dimension(),
new LevelStem(
originalWorld.dimensionTypeRegistration(),
originalWorld.getChunkSource().getGenerator()
),
new NoOpWorldLoadListener(),
originalWorld.isDebug(),
seed,
ImmutableList.of(),
false,
originalWorld.getRandomSequences(),
env,
gen,
bukkitWorld.getBiomeProvider()
);
try {
regenForWorld(region, extent, freshWorld, options);
@ -684,7 +741,7 @@ public final class PaperweightAdapter implements BukkitImplAdapter<net.minecraft
} finally {
try {
@SuppressWarnings("unchecked")
Map<String, org.bukkit.World> map = (Map<String, org.bukkit.World>) serverWorldsField.get(Bukkit.getServer());
Map<String, World> map = (Map<String, World>) serverWorldsField.get(Bukkit.getServer());
map.remove("faweregentempworld");
} catch (IllegalAccessException ignored) {
}
@ -712,7 +769,7 @@ public final class PaperweightAdapter implements BukkitImplAdapter<net.minecraft
executor.managedBlock(() -> {
// bail out early if a future fails
if (chunkLoadings.stream().anyMatch(ftr ->
ftr.isDone() && Futures.getUnchecked(ftr) == null
ftr.isDone() && Futures.getUnchecked(ftr) == null
)) {
return false;
}
@ -758,9 +815,9 @@ public final class PaperweightAdapter implements BukkitImplAdapter<net.minecraft
try {
//noinspection unchecked
chunkLoadings.add(
((CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>>)
getChunkFutureMethod.invoke(chunkManager, chunk.x(), chunk.z(), ChunkStatus.FEATURES, true))
.thenApply(either -> either.left().orElse(null))
((CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>>)
getChunkFutureMethod.invoke(chunkManager, chunk.x(), chunk.z(), ChunkStatus.FEATURES, true))
.thenApply(either -> either.left().orElse(null))
);
} catch (IllegalAccessException | InvocationTargetException e) {
throw new IllegalStateException("Couldn't load chunk for regen.", e);
@ -782,12 +839,12 @@ public final class PaperweightAdapter implements BukkitImplAdapter<net.minecraft
}
private static final Set<SideEffect> SUPPORTED_SIDE_EFFECTS = Sets.immutableEnumSet(
SideEffect.NEIGHBORS,
SideEffect.LIGHTING,
SideEffect.VALIDATION,
SideEffect.ENTITY_AI,
SideEffect.EVENTS,
SideEffect.UPDATE
SideEffect.NEIGHBORS,
SideEffect.LIGHTING,
SideEffect.VALIDATION,
SideEffect.ENTITY_AI,
SideEffect.EVENTS,
SideEffect.UPDATE
);
@Override
@ -796,7 +853,7 @@ public final class PaperweightAdapter implements BukkitImplAdapter<net.minecraft
}
@Override
public boolean clearContainerBlockContents(org.bukkit.World world, BlockVector3 pt) {
public boolean clearContainerBlockContents(World world, BlockVector3 pt) {
ServerLevel originalWorld = ((CraftWorld) world).getHandle();
BlockEntity entity = originalWorld.getBlockEntity(new BlockPos(pt.x(), pt.y(), pt.z()));
@ -807,6 +864,45 @@ public final class PaperweightAdapter implements BukkitImplAdapter<net.minecraft
return false;
}
@Override
public void initializeRegistries() {
DedicatedServer server = ((CraftServer) Bukkit.getServer()).getServer();
// Biomes
for (ResourceLocation name : server.registryAccess().registryOrThrow(Registries.BIOME).keySet()) {
if (BiomeType.REGISTRY.get(name.toString()) == null) {
BiomeType.REGISTRY.register(name.toString(), new BiomeType(name.toString()));
}
}
// BiomeCategories
Registry<Biome> biomeRegistry = server.registryAccess().registryOrThrow(Registries.BIOME);
biomeRegistry.getTagNames().forEach(tagKey -> {
String key = tagKey.location().toString();
if (BiomeCategory.REGISTRY.get(key) == null) {
BiomeCategory.REGISTRY.register(key, new BiomeCategory(
key,
() -> biomeRegistry.getTag(tagKey)
.stream()
.flatMap(HolderSet.Named::stream)
.map(Holder::value)
.map(this::adapt)
.collect(Collectors.toSet()))
);
}
});
}
@Override
public void sendBiomeUpdates(World world, Iterable<BlockVector2> chunks) {
ServerLevel originalWorld = ((CraftWorld) world).getHandle();
List<ChunkAccess> nativeChunks = chunks instanceof Collection<BlockVector2> chunkCollection ? Lists.newArrayListWithCapacity(chunkCollection.size()) : Lists.newArrayList();
for (BlockVector2 chunk : chunks) {
nativeChunks.add(originalWorld.getChunk(chunk.x(), chunk.z(), ChunkStatus.BIOMES, false));
}
originalWorld.getChunkSource().chunkMap.resendBiomesForChunks(nativeChunks);
}
// ------------------------------------------------------------------------
// Code that is less likely to break
// ------------------------------------------------------------------------
@ -978,7 +1074,7 @@ public final class PaperweightAdapter implements BukkitImplAdapter<net.minecraft
MojangWatchdog(DedicatedServer server) throws NoSuchFieldException {
this.server = server;
Field tickField = MinecraftServer.class.getDeclaredField(
Refraction.pickName("nextTickTime", "ah")
Refraction.pickName("nextTickTime", "ag")
);
if (tickField.getType() != long.class) {
throw new IllegalStateException("nextTickTime is not a long field, mapping is likely incorrect");

View File

@ -41,7 +41,7 @@ class PaperweightFakePlayer extends ServerPlayer {
private static final GameProfile FAKE_WORLDEDIT_PROFILE = new GameProfile(UUID.nameUUIDFromBytes("worldedit".getBytes()), "[WorldEdit]");
private static final Vec3 ORIGIN = new Vec3(0.0D, 0.0D, 0.0D);
private static final ClientInformation FAKE_CLIENT_INFO = new ClientInformation(
"en_US", 16, ChatVisiblity.FULL, true, 0, HumanoidArm.LEFT, false, false
"en_US", 16, ChatVisiblity.FULL, true, 0, HumanoidArm.LEFT, false, false
);
PaperweightFakePlayer(ServerLevel world) {

View File

@ -26,9 +26,11 @@ import com.sk89q.worldedit.util.SideEffect;
import com.sk89q.worldedit.util.SideEffectSet;
import com.sk89q.worldedit.world.block.BlockState;
import net.minecraft.core.BlockPos;
import net.minecraft.nbt.Tag;
import net.minecraft.server.level.FullChunkStatus;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.chunk.LevelChunk;
import org.bukkit.craftbukkit.v1_20_R2.CraftWorld;
import org.bukkit.craftbukkit.v1_20_R2.block.data.CraftBlockData;
@ -70,8 +72,8 @@ public class PaperweightWorldNativeAccess implements WorldNativeAccess<LevelChun
public net.minecraft.world.level.block.state.BlockState toNative(BlockState state) {
int stateId = BlockStateIdAccess.getBlockStateId(state);
return BlockStateIdAccess.isValidInternalId(stateId)
? Block.stateById(stateId)
: ((CraftBlockData) BukkitAdapter.adapt(state)).getState();
? Block.stateById(stateId)
: ((CraftBlockData) BukkitAdapter.adapt(state)).getState();
}
@Override
@ -102,7 +104,14 @@ public class PaperweightWorldNativeAccess implements WorldNativeAccess<LevelChun
@Override
public boolean updateTileEntity(final BlockPos position, final LinCompoundTag tag) {
return false;
// We will assume that the tile entity was created for us
BlockEntity tileEntity = getWorld().getBlockEntity(position);
if (tileEntity == null) {
return false;
}
Tag nativeTag = adapter.fromNativeLin(tag);
PaperweightAdapter.readTagIntoTileEntity((net.minecraft.nbt.CompoundTag) nativeTag, tileEntity);
return true;
}
@Override
@ -144,6 +153,12 @@ public class PaperweightWorldNativeAccess implements WorldNativeAccess<LevelChun
}
}
@Override
public void updateBlock(BlockPos pos, net.minecraft.world.level.block.state.BlockState oldState, net.minecraft.world.level.block.state.BlockState newState) {
ServerLevel world = getWorld();
newState.onPlace(world, pos, oldState, false);
}
// Not sure why neighborChanged is deprecated
@SuppressWarnings("deprecation")
private void fireNeighborChanged(BlockPos pos, ServerLevel world, Block block, BlockPos neighborPos) {
@ -153,8 +168,6 @@ public class PaperweightWorldNativeAccess implements WorldNativeAccess<LevelChun
@Override
public void updateNeighbors(BlockPos pos, net.minecraft.world.level.block.state.BlockState oldState, net.minecraft.world.level.block.state.BlockState newState, int recursionLimit) {
ServerLevel world = getWorld();
// a == updateNeighbors
// b == updateDiagonalNeighbors
oldState.updateIndirectNeighbourShapes(world, pos, NOTIFY, recursionLimit);
if (sideEffectSet.shouldApply(SideEffect.EVENTS)) {
CraftWorld craftWorld = world.getWorld();

View File

@ -217,6 +217,12 @@ public class PaperweightFaweWorldNativeAccess implements WorldNativeAccess<Level
newState.updateIndirectNeighbourShapes(level, blockPos, NOTIFY, recursionLimit);
}
@Override
public void updateBlock(BlockPos pos, net.minecraft.world.level.block.state.BlockState oldState, net.minecraft.world.level.block.state.BlockState newState) {
Level world = getLevel();
newState.onPlace(world, pos, oldState, false);
}
@Override
public void onBlockStateChange(
BlockPos blockPos,