mirror of
https://github.com/plexusorg/Plex-FAWE.git
synced 2024-12-23 01:37:37 +00:00
Port initial paperweight classes to 1.18 folder structure
This commit is contained in:
parent
930025afac
commit
e296a329c2
@ -6,7 +6,7 @@ guava = "31.0.1-jre"
|
|||||||
gson = "2.8.8"
|
gson = "2.8.8"
|
||||||
|
|
||||||
# Platform expectations
|
# Platform expectations
|
||||||
paper = "1.17.1-R0.1-SNAPSHOT"
|
paper = "1.18-rc3-R0.1-SNAPSHOT"
|
||||||
|
|
||||||
# Plugins
|
# Plugins
|
||||||
vault = "1.7.1"
|
vault = "1.7.1"
|
||||||
@ -25,7 +25,7 @@ redprotect = "1.9.6"
|
|||||||
|
|
||||||
# Third party
|
# Third party
|
||||||
flow-math = "1.0.3"
|
flow-math = "1.0.3"
|
||||||
paperlib = "1.0.7"
|
paperlib = "1.0.8-SNAPSHOT"
|
||||||
bstats = "2.2.1"
|
bstats = "2.2.1"
|
||||||
serverlib = "2.3.1"
|
serverlib = "2.3.1"
|
||||||
paster = "1.1.1"
|
paster = "1.1.1"
|
||||||
|
@ -12,6 +12,7 @@ repositories {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
paperDevBundle("1.18-rc3-R0.1-SNAPSHOT")
|
paperDevBundle("1.18-rc3-R0.1-20211129.221606-5")
|
||||||
compileOnly(libs.paperlib)
|
compileOnly(libs.paperlib)
|
||||||
|
compileOnly(libs.paper)
|
||||||
}
|
}
|
||||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,98 @@
|
|||||||
|
/*
|
||||||
|
* WorldEdit, a Minecraft world manipulation toolkit
|
||||||
|
* Copyright (C) sk89q <http://www.sk89q.com>
|
||||||
|
* Copyright (C) WorldEdit team and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.sk89q.worldedit.bukkit.adapter.ext.fawe.v1_18_R1;
|
||||||
|
|
||||||
|
import com.mojang.authlib.GameProfile;
|
||||||
|
import net.minecraft.network.chat.ChatType;
|
||||||
|
import net.minecraft.network.chat.Component;
|
||||||
|
import net.minecraft.network.protocol.game.ServerboundClientInformationPacket;
|
||||||
|
import net.minecraft.server.level.ServerLevel;
|
||||||
|
import net.minecraft.server.level.ServerPlayer;
|
||||||
|
import net.minecraft.stats.Stat;
|
||||||
|
import net.minecraft.world.MenuProvider;
|
||||||
|
import net.minecraft.world.damagesource.DamageSource;
|
||||||
|
import net.minecraft.world.entity.Entity;
|
||||||
|
import net.minecraft.world.level.block.entity.SignBlockEntity;
|
||||||
|
import net.minecraft.world.phys.Vec3;
|
||||||
|
import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause;
|
||||||
|
|
||||||
|
import java.util.OptionalInt;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
PaperweightFakePlayer(ServerLevel world) {
|
||||||
|
super(world.getServer(), world, FAKE_WORLDEDIT_PROFILE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Vec3 position() {
|
||||||
|
return ORIGIN;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void tick() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void die(DamageSource damagesource) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Entity changeDimension(ServerLevel worldserver, TeleportCause cause) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public OptionalInt openMenu(MenuProvider factory) {
|
||||||
|
return OptionalInt.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateOptions(ServerboundClientInformationPacket packet) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void displayClientMessage(Component message, boolean actionBar) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sendMessage(Component message, ChatType type, UUID sender) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void awardStat(Stat<?> stat, int amount) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void awardStat(Stat<?> stat) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isInvulnerableTo(DamageSource damageSource) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void openTextEdit(SignBlockEntity sign) {
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,182 @@
|
|||||||
|
/*
|
||||||
|
* WorldEdit, a Minecraft world manipulation toolkit
|
||||||
|
* Copyright (C) sk89q <http://www.sk89q.com>
|
||||||
|
* Copyright (C) WorldEdit team and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.sk89q.worldedit.bukkit.adapter.ext.fawe.v1_18_R1;
|
||||||
|
|
||||||
|
import com.sk89q.jnbt.CompoundTag;
|
||||||
|
import com.sk89q.worldedit.bukkit.BukkitAdapter;
|
||||||
|
import com.sk89q.worldedit.internal.block.BlockStateIdAccess;
|
||||||
|
import com.sk89q.worldedit.internal.wna.WorldNativeAccess;
|
||||||
|
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.ChunkHolder;
|
||||||
|
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_18_R1.CraftWorld;
|
||||||
|
import org.bukkit.craftbukkit.v1_18_R1.block.data.CraftBlockData;
|
||||||
|
import org.bukkit.event.block.BlockPhysicsEvent;
|
||||||
|
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
|
import java.util.Objects;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
public class PaperweightWorldNativeAccess implements WorldNativeAccess<LevelChunk, net.minecraft.world.level.block.state.BlockState, BlockPos> {
|
||||||
|
private static final int UPDATE = 1;
|
||||||
|
private static final int NOTIFY = 2;
|
||||||
|
|
||||||
|
private final PaperweightAdapter adapter;
|
||||||
|
private final WeakReference<ServerLevel> world;
|
||||||
|
private SideEffectSet sideEffectSet;
|
||||||
|
|
||||||
|
public PaperweightWorldNativeAccess(PaperweightAdapter adapter, WeakReference<ServerLevel> world) {
|
||||||
|
this.adapter = adapter;
|
||||||
|
this.world = world;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ServerLevel getWorld() {
|
||||||
|
return Objects.requireNonNull(world.get(), "The reference to the world was lost");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setCurrentSideEffectSet(SideEffectSet sideEffectSet) {
|
||||||
|
this.sideEffectSet = sideEffectSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LevelChunk getChunk(int x, int z) {
|
||||||
|
return getWorld().getChunk(x, z);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public net.minecraft.world.level.block.state.BlockState getBlockState(LevelChunk chunk, BlockPos position) {
|
||||||
|
return chunk.getBlockState(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public net.minecraft.world.level.block.state.BlockState setBlockState(LevelChunk chunk, BlockPos position, net.minecraft.world.level.block.state.BlockState state) {
|
||||||
|
return chunk.setBlockState(position, state, false, this.sideEffectSet.shouldApply(SideEffect.UPDATE));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public net.minecraft.world.level.block.state.BlockState getValidBlockForPosition(net.minecraft.world.level.block.state.BlockState block, BlockPos position) {
|
||||||
|
return Block.updateFromNeighbourShapes(block, getWorld(), position);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BlockPos getPosition(int x, int y, int z) {
|
||||||
|
return new BlockPos(x, y, z);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateLightingForBlock(BlockPos position) {
|
||||||
|
getWorld().getChunkSource().getLightEngine().checkBlock(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean updateTileEntity(BlockPos position, CompoundTag tag) {
|
||||||
|
// We will assume that the tile entity was created for us
|
||||||
|
BlockEntity tileEntity = getWorld().getBlockEntity(position);
|
||||||
|
if (tileEntity == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Tag nativeTag = adapter.fromNative(tag);
|
||||||
|
PaperweightAdapter.readTagIntoTileEntity((net.minecraft.nbt.CompoundTag) nativeTag, tileEntity);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void notifyBlockUpdate(LevelChunk chunk, BlockPos position, net.minecraft.world.level.block.state.BlockState oldState, net.minecraft.world.level.block.state.BlockState newState) {
|
||||||
|
if (chunk.getSections()[getWorld().getSectionIndex(position.getY())] != null) {
|
||||||
|
getWorld().sendBlockUpdated(position, oldState, newState, UPDATE | NOTIFY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isChunkTicking(LevelChunk chunk) {
|
||||||
|
return chunk.getFullStatus().isOrAfter(ChunkHolder.FullChunkStatus.TICKING);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void markBlockChanged(LevelChunk chunk, BlockPos position) {
|
||||||
|
if (chunk.getSections()[getWorld().getSectionIndex(position.getY())] != null) {
|
||||||
|
getWorld().getChunkSource().blockChanged(position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void notifyNeighbors(BlockPos pos, net.minecraft.world.level.block.state.BlockState oldState, net.minecraft.world.level.block.state.BlockState newState) {
|
||||||
|
ServerLevel world = getWorld();
|
||||||
|
if (sideEffectSet.shouldApply(SideEffect.EVENTS)) {
|
||||||
|
world.updateNeighborsAt(pos, oldState.getBlock());
|
||||||
|
} else {
|
||||||
|
// When we don't want events, manually run the physics without them.
|
||||||
|
Block block = oldState.getBlock();
|
||||||
|
fireNeighborChanged(pos, world, block, pos.west());
|
||||||
|
fireNeighborChanged(pos, world, block, pos.east());
|
||||||
|
fireNeighborChanged(pos, world, block, pos.below());
|
||||||
|
fireNeighborChanged(pos, world, block, pos.above());
|
||||||
|
fireNeighborChanged(pos, world, block, pos.north());
|
||||||
|
fireNeighborChanged(pos, world, block, pos.south());
|
||||||
|
}
|
||||||
|
if (newState.hasAnalogOutputSignal()) {
|
||||||
|
world.updateNeighbourForOutputSignal(pos, newState.getBlock());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void fireNeighborChanged(BlockPos pos, ServerLevel world, Block block, BlockPos neighborPos) {
|
||||||
|
world.getBlockState(neighborPos).neighborChanged(world, neighborPos, block, pos, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@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();
|
||||||
|
BlockPhysicsEvent event = new BlockPhysicsEvent(craftWorld.getBlockAt(pos.getX(), pos.getY(), pos.getZ()), CraftBlockData.fromData(newState));
|
||||||
|
world.getCraftServer().getPluginManager().callEvent(event);
|
||||||
|
if (event.isCancelled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
newState.updateNeighbourShapes(world, pos, NOTIFY, recursionLimit);
|
||||||
|
newState.updateIndirectNeighbourShapes(world, pos, NOTIFY, recursionLimit);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBlockStateChange(BlockPos pos, net.minecraft.world.level.block.state.BlockState oldState, net.minecraft.world.level.block.state.BlockState newState) {
|
||||||
|
getWorld().onBlockStateChange(pos, oldState, newState);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,191 @@
|
|||||||
|
package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_18_R1;
|
||||||
|
|
||||||
|
import com.google.common.base.Suppliers;
|
||||||
|
import com.sk89q.jnbt.CompoundTag;
|
||||||
|
import com.sk89q.util.ReflectionUtil;
|
||||||
|
import com.sk89q.worldedit.bukkit.adapter.Refraction;
|
||||||
|
import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_18_R1.nbt.PaperweightLazyCompoundTag;
|
||||||
|
import com.sk89q.worldedit.world.registry.BlockMaterial;
|
||||||
|
import net.minecraft.core.BlockPos;
|
||||||
|
import net.minecraft.world.level.EmptyBlockGetter;
|
||||||
|
import net.minecraft.world.level.block.Block;
|
||||||
|
import net.minecraft.world.level.block.EntityBlock;
|
||||||
|
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||||
|
import net.minecraft.world.level.block.state.BlockBehaviour;
|
||||||
|
import net.minecraft.world.level.block.state.BlockState;
|
||||||
|
import net.minecraft.world.level.material.Material;
|
||||||
|
import net.minecraft.world.level.material.PushReaction;
|
||||||
|
import org.bukkit.craftbukkit.v1_18_R1.block.data.CraftBlockData;
|
||||||
|
|
||||||
|
public class PaperweightBlockMaterial implements BlockMaterial {
|
||||||
|
|
||||||
|
private final Block block;
|
||||||
|
private final BlockState blockState;
|
||||||
|
private final Material material;
|
||||||
|
private final boolean isTranslucent;
|
||||||
|
private final CraftBlockData craftBlockData;
|
||||||
|
private final org.bukkit.Material craftMaterial;
|
||||||
|
private final int opacity;
|
||||||
|
//TODO 1.18 be a CompoundBinaryTag ?
|
||||||
|
private final CompoundTag tile;
|
||||||
|
|
||||||
|
public PaperweightBlockMaterial(Block block) {
|
||||||
|
this(block, block.defaultBlockState());
|
||||||
|
}
|
||||||
|
|
||||||
|
public PaperweightBlockMaterial(Block block, BlockState blockState) {
|
||||||
|
this.block = block;
|
||||||
|
this.blockState = blockState;
|
||||||
|
this.material = blockState.getMaterial();
|
||||||
|
this.craftBlockData = CraftBlockData.fromData(blockState);
|
||||||
|
this.craftMaterial = craftBlockData.getMaterial();
|
||||||
|
BlockBehaviour.Properties blockInfo = ReflectionUtil.getField(BlockBehaviour.class, block, Refraction.pickName(
|
||||||
|
"properties", "aP"));
|
||||||
|
this.isTranslucent = !(boolean) ReflectionUtil.getField(BlockBehaviour.Properties.class, blockInfo,
|
||||||
|
Refraction.pickName("canOcclude", "n")
|
||||||
|
);
|
||||||
|
opacity = blockState.getLightBlock(EmptyBlockGetter.INSTANCE, BlockPos.ZERO);
|
||||||
|
BlockEntity tileEntity = !(block instanceof EntityBlock) ? null : ((EntityBlock) block).newBlockEntity(
|
||||||
|
BlockPos.ZERO,
|
||||||
|
blockState
|
||||||
|
);
|
||||||
|
tile = tileEntity == null
|
||||||
|
? null
|
||||||
|
: new PaperweightLazyCompoundTag(Suppliers.memoize(() -> tileEntity.save(new net.minecraft.nbt.CompoundTag())));
|
||||||
|
// TODO 1.18 save -> saveAdditional
|
||||||
|
}
|
||||||
|
|
||||||
|
public Block getBlock() {
|
||||||
|
return block;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BlockState getState() {
|
||||||
|
return blockState;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CraftBlockData getCraftBlockData() {
|
||||||
|
return craftBlockData;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Material getMaterial() {
|
||||||
|
return material;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAir() {
|
||||||
|
return blockState.isAir();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isFullCube() {
|
||||||
|
return craftMaterial.isOccluding();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isOpaque() {
|
||||||
|
return material.isSolidBlocking();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isPowerSource() {
|
||||||
|
return blockState.isSignalSource();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isLiquid() {
|
||||||
|
return material.isLiquid();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSolid() {
|
||||||
|
return material.isSolid();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float getHardness() {
|
||||||
|
return craftBlockData.getState().destroySpeed;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float getResistance() {
|
||||||
|
return block.getExplosionResistance();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float getSlipperiness() {
|
||||||
|
return block.getFriction();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getLightValue() {
|
||||||
|
return blockState.getLightEmission();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getLightOpacity() {
|
||||||
|
return opacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isFragileWhenPushed() {
|
||||||
|
return material.getPushReaction() == PushReaction.DESTROY;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isUnpushable() {
|
||||||
|
return material.getPushReaction() == PushReaction.BLOCK;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isTicksRandomly() {
|
||||||
|
return block.isRandomlyTicking(blockState);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isMovementBlocker() {
|
||||||
|
return material.isSolid();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isBurnable() {
|
||||||
|
return material.isFlammable();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isToolRequired() {
|
||||||
|
// Removed in 1.16.1, this is not present in higher versions
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isReplacedDuringPlacement() {
|
||||||
|
return material.isReplaceable();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isTranslucent() {
|
||||||
|
return isTranslucent;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasContainer() {
|
||||||
|
return block instanceof EntityBlock;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isTile() {
|
||||||
|
return block instanceof EntityBlock;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CompoundTag getDefaultTile() {
|
||||||
|
return tile;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getMapColor() {
|
||||||
|
// rgb field
|
||||||
|
return material.getColor().col;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,673 @@
|
|||||||
|
package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_18_R1;
|
||||||
|
|
||||||
|
import com.fastasyncworldedit.bukkit.adapter.CachedBukkitAdapter;
|
||||||
|
import com.fastasyncworldedit.bukkit.adapter.IDelegateBukkitImplAdapter;
|
||||||
|
import com.fastasyncworldedit.bukkit.adapter.NMSRelighterFactory;
|
||||||
|
import com.fastasyncworldedit.core.FaweCache;
|
||||||
|
import com.fastasyncworldedit.core.entity.LazyBaseEntity;
|
||||||
|
import com.fastasyncworldedit.core.extent.processor.lighting.RelighterFactory;
|
||||||
|
import com.fastasyncworldedit.core.queue.IChunkGet;
|
||||||
|
import com.fastasyncworldedit.core.queue.implementation.packet.ChunkPacket;
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
import com.sk89q.jnbt.CompoundTag;
|
||||||
|
import com.sk89q.jnbt.StringTag;
|
||||||
|
import com.sk89q.jnbt.Tag;
|
||||||
|
import com.sk89q.worldedit.EditSession;
|
||||||
|
import com.sk89q.worldedit.blocks.BaseItemStack;
|
||||||
|
import com.sk89q.worldedit.blocks.TileEntityBlock;
|
||||||
|
import com.sk89q.worldedit.bukkit.BukkitAdapter;
|
||||||
|
import com.sk89q.worldedit.bukkit.BukkitWorld;
|
||||||
|
import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter;
|
||||||
|
import com.sk89q.worldedit.bukkit.adapter.ext.fawe.v1_18_R1.PaperweightAdapter;
|
||||||
|
import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_18_R1.nbt.PaperweightLazyCompoundTag;
|
||||||
|
import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_18_R1.regen.PaperweightRegen;
|
||||||
|
import com.sk89q.worldedit.entity.BaseEntity;
|
||||||
|
import com.sk89q.worldedit.extent.Extent;
|
||||||
|
import com.sk89q.worldedit.internal.block.BlockStateIdAccess;
|
||||||
|
import com.sk89q.worldedit.internal.util.LogManagerCompat;
|
||||||
|
import com.sk89q.worldedit.internal.wna.WorldNativeAccess;
|
||||||
|
import com.sk89q.worldedit.math.BlockVector3;
|
||||||
|
import com.sk89q.worldedit.regions.Region;
|
||||||
|
import com.sk89q.worldedit.registry.state.BooleanProperty;
|
||||||
|
import com.sk89q.worldedit.registry.state.DirectionalProperty;
|
||||||
|
import com.sk89q.worldedit.registry.state.EnumProperty;
|
||||||
|
import com.sk89q.worldedit.registry.state.IntegerProperty;
|
||||||
|
import com.sk89q.worldedit.registry.state.Property;
|
||||||
|
import com.sk89q.worldedit.util.Direction;
|
||||||
|
import com.sk89q.worldedit.util.SideEffect;
|
||||||
|
import com.sk89q.worldedit.util.SideEffectSet;
|
||||||
|
import com.sk89q.worldedit.util.TreeGenerator;
|
||||||
|
import com.sk89q.worldedit.util.formatting.text.Component;
|
||||||
|
import com.sk89q.worldedit.world.RegenOptions;
|
||||||
|
import com.sk89q.worldedit.world.biome.BiomeType;
|
||||||
|
import com.sk89q.worldedit.world.block.BaseBlock;
|
||||||
|
import com.sk89q.worldedit.world.block.BlockState;
|
||||||
|
import com.sk89q.worldedit.world.block.BlockStateHolder;
|
||||||
|
import com.sk89q.worldedit.world.block.BlockType;
|
||||||
|
import com.sk89q.worldedit.world.block.BlockTypes;
|
||||||
|
import com.sk89q.worldedit.world.block.BlockTypesCache;
|
||||||
|
import com.sk89q.worldedit.world.entity.EntityType;
|
||||||
|
import com.sk89q.worldedit.world.item.ItemType;
|
||||||
|
import com.sk89q.worldedit.world.registry.BlockMaterial;
|
||||||
|
import net.minecraft.core.BlockPos;
|
||||||
|
import net.minecraft.core.Registry;
|
||||||
|
import net.minecraft.core.WritableRegistry;
|
||||||
|
import net.minecraft.nbt.IntTag;
|
||||||
|
import net.minecraft.network.protocol.game.ClientboundLevelChunkPacketData;
|
||||||
|
import net.minecraft.resources.ResourceLocation;
|
||||||
|
import net.minecraft.server.MinecraftServer;
|
||||||
|
import net.minecraft.server.level.ChunkHolder;
|
||||||
|
import net.minecraft.server.level.ServerLevel;
|
||||||
|
import net.minecraft.server.level.ServerPlayer;
|
||||||
|
import net.minecraft.util.StringRepresentable;
|
||||||
|
import net.minecraft.world.entity.Entity;
|
||||||
|
import net.minecraft.world.item.ItemStack;
|
||||||
|
import net.minecraft.world.level.Level;
|
||||||
|
import net.minecraft.world.level.biome.Biome;
|
||||||
|
import net.minecraft.world.level.block.Block;
|
||||||
|
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||||
|
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
|
||||||
|
import net.minecraft.world.level.block.state.properties.DirectionProperty;
|
||||||
|
import net.minecraft.world.level.chunk.LevelChunk;
|
||||||
|
import net.minecraft.world.level.chunk.LevelChunkSection;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.Location;
|
||||||
|
import org.bukkit.Material;
|
||||||
|
import org.bukkit.NamespacedKey;
|
||||||
|
import org.bukkit.TreeType;
|
||||||
|
import org.bukkit.block.data.BlockData;
|
||||||
|
import org.bukkit.craftbukkit.v1_18_R1.CraftChunk;
|
||||||
|
import org.bukkit.craftbukkit.v1_18_R1.CraftServer;
|
||||||
|
import org.bukkit.craftbukkit.v1_18_R1.CraftWorld;
|
||||||
|
import org.bukkit.craftbukkit.v1_18_R1.block.CraftBlock;
|
||||||
|
import org.bukkit.craftbukkit.v1_18_R1.block.CraftBlockState;
|
||||||
|
import org.bukkit.craftbukkit.v1_18_R1.block.data.CraftBlockData;
|
||||||
|
import org.bukkit.craftbukkit.v1_18_R1.entity.CraftEntity;
|
||||||
|
import org.bukkit.craftbukkit.v1_18_R1.entity.CraftPlayer;
|
||||||
|
import org.bukkit.craftbukkit.v1_18_R1.inventory.CraftItemStack;
|
||||||
|
import org.bukkit.craftbukkit.v1_18_R1.util.CraftNamespacedKey;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.OptionalInt;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
public final class PaperweightFaweAdapter extends CachedBukkitAdapter implements
|
||||||
|
IDelegateBukkitImplAdapter<net.minecraft.nbt.Tag> {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = LogManagerCompat.getLogger();
|
||||||
|
|
||||||
|
private final PaperweightAdapter parent;
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// Code that may break between versions of Minecraft
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
private final PaperweightMapChunkUtil mapUtil = new PaperweightMapChunkUtil();
|
||||||
|
private char[] ibdToStateOrdinal = null;
|
||||||
|
private int[] ordinalToIbdID = null;
|
||||||
|
private boolean initialised = false;
|
||||||
|
private Map<String, List<Property<?>>> allBlockProperties = null;
|
||||||
|
|
||||||
|
public PaperweightFaweAdapter() throws NoSuchFieldException, NoSuchMethodException {
|
||||||
|
this.parent = new PaperweightAdapter();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private static String getEntityId(Entity entity) {
|
||||||
|
ResourceLocation resourceLocation = net.minecraft.world.entity.EntityType.getKey(entity.getType());
|
||||||
|
return resourceLocation == null ? null : resourceLocation.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void readEntityIntoTag(Entity entity, net.minecraft.nbt.CompoundTag compoundTag) {
|
||||||
|
entity.save(compoundTag);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BukkitImplAdapter<net.minecraft.nbt.Tag> getParent() {
|
||||||
|
return parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized boolean init() {
|
||||||
|
if (ibdToStateOrdinal != null && ibdToStateOrdinal[1] != 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ibdToStateOrdinal = new char[BlockTypesCache.states.length]; // size
|
||||||
|
ordinalToIbdID = new int[ibdToStateOrdinal.length]; // size
|
||||||
|
for (int i = 0; i < ibdToStateOrdinal.length; i++) {
|
||||||
|
BlockState blockState = BlockTypesCache.states[i];
|
||||||
|
PaperweightBlockMaterial material = (PaperweightBlockMaterial) blockState.getMaterial();
|
||||||
|
int id = Block.BLOCK_STATE_REGISTRY.getId(material.getState());
|
||||||
|
char ordinal = blockState.getOrdinalChar();
|
||||||
|
ibdToStateOrdinal[id] = ordinal;
|
||||||
|
ordinalToIbdID[ordinal] = id;
|
||||||
|
}
|
||||||
|
Map<String, List<Property<?>>> properties = new HashMap<>();
|
||||||
|
try {
|
||||||
|
for (Field field : BlockStateProperties.class.getDeclaredFields()) {
|
||||||
|
Object obj = field.get(null);
|
||||||
|
if (!(obj instanceof net.minecraft.world.level.block.state.properties.Property<?> state)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Property<?> property;
|
||||||
|
if (state instanceof net.minecraft.world.level.block.state.properties.BooleanProperty) {
|
||||||
|
property = new BooleanProperty(
|
||||||
|
state.getName(),
|
||||||
|
(List<Boolean>) ImmutableList.copyOf(state.getPossibleValues())
|
||||||
|
);
|
||||||
|
} else if (state instanceof DirectionProperty) {
|
||||||
|
property = new DirectionalProperty(
|
||||||
|
state.getName(),
|
||||||
|
state
|
||||||
|
.getPossibleValues()
|
||||||
|
.stream()
|
||||||
|
.map(e -> Direction.valueOf(((StringRepresentable) e).getSerializedName().toUpperCase()))
|
||||||
|
.collect(Collectors.toList())
|
||||||
|
);
|
||||||
|
} else if (state instanceof net.minecraft.world.level.block.state.properties.EnumProperty) {
|
||||||
|
property = new EnumProperty(
|
||||||
|
state.getName(),
|
||||||
|
state
|
||||||
|
.getPossibleValues()
|
||||||
|
.stream()
|
||||||
|
.map(e -> ((StringRepresentable) e).getSerializedName())
|
||||||
|
.collect(Collectors.toList())
|
||||||
|
);
|
||||||
|
} else if (state instanceof net.minecraft.world.level.block.state.properties.IntegerProperty) {
|
||||||
|
property = new IntegerProperty(
|
||||||
|
state.getName(),
|
||||||
|
(List<Integer>) ImmutableList.copyOf(state.getPossibleValues())
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("FastAsyncWorldEdit needs an update to support " + state
|
||||||
|
.getClass()
|
||||||
|
.getSimpleName());
|
||||||
|
}
|
||||||
|
properties.compute(property.getName().toLowerCase(Locale.ROOT), (k, v) -> {
|
||||||
|
if (v == null) {
|
||||||
|
v = new ArrayList<>(Collections.singletonList(property));
|
||||||
|
} else {
|
||||||
|
v.add(property);
|
||||||
|
}
|
||||||
|
return v;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (IllegalAccessException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} finally {
|
||||||
|
allBlockProperties = ImmutableMap.copyOf(properties);
|
||||||
|
}
|
||||||
|
initialised = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BlockMaterial getMaterial(BlockType blockType) {
|
||||||
|
Block block = getBlock(blockType);
|
||||||
|
return new PaperweightBlockMaterial(block);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized BlockMaterial getMaterial(BlockState state) {
|
||||||
|
net.minecraft.world.level.block.state.BlockState blockState = ((CraftBlockData) Bukkit.createBlockData(state.getAsString())).getState();
|
||||||
|
return new PaperweightBlockMaterial(blockState.getBlock(), blockState);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Block getBlock(BlockType blockType) {
|
||||||
|
return Registry.BLOCK.get(new ResourceLocation(blockType.getNamespace(), blockType.getResource()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
@Override
|
||||||
|
public BaseBlock getBlock(Location location) {
|
||||||
|
Preconditions.checkNotNull(location);
|
||||||
|
|
||||||
|
CraftWorld craftWorld = ((CraftWorld) location.getWorld());
|
||||||
|
int x = location.getBlockX();
|
||||||
|
int y = location.getBlockY();
|
||||||
|
int z = location.getBlockZ();
|
||||||
|
|
||||||
|
final ServerLevel serverLevel = craftWorld.getHandle();
|
||||||
|
LevelChunk levelChunk = serverLevel.getChunk(x >> 4, z >> 4);
|
||||||
|
final BlockPos blockPos = new BlockPos(x, y, z);
|
||||||
|
org.bukkit.block.Block bukkitBlock = location.getBlock();
|
||||||
|
BlockState state = BukkitAdapter.adapt(bukkitBlock.getBlockData());
|
||||||
|
if (state.getBlockType().getMaterial().hasContainer()) {
|
||||||
|
|
||||||
|
// Read the NBT data
|
||||||
|
BlockEntity blockEntity = levelChunk.getBlockEntity(blockPos, LevelChunk.EntityCreationType.CHECK);
|
||||||
|
if (blockEntity != null) {
|
||||||
|
net.minecraft.nbt.CompoundTag tag = new net.minecraft.nbt.CompoundTag();
|
||||||
|
//TODO save -> saveAdditional
|
||||||
|
blockEntity.save(tag); // readTileEntityIntoTag - load data
|
||||||
|
return state.toBaseBlock((CompoundTag) toNative(tag));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return state.toBaseBlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<SideEffect> getSupportedSideEffects() {
|
||||||
|
return SideEffectSet.defaults().getSideEffectsToApply();
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO 1.18 address raw access of BlockStateHolder
|
||||||
|
public boolean setBlock(org.bukkit.Chunk chunk, int x, int y, int z, BlockStateHolder state, boolean update) {
|
||||||
|
CraftChunk craftChunk = (CraftChunk) chunk;
|
||||||
|
LevelChunk levelChunk = craftChunk.getHandle();
|
||||||
|
Level level = levelChunk.getLevel();
|
||||||
|
|
||||||
|
BlockPos blockPos = new BlockPos(x, y, z);
|
||||||
|
net.minecraft.world.level.block.state.BlockState blockState = ((PaperweightBlockMaterial) state.getMaterial()).getState();
|
||||||
|
LevelChunkSection[] levelChunkSections = levelChunk.getSections();
|
||||||
|
int y4 = y >> 4;
|
||||||
|
LevelChunkSection section = levelChunkSections[y4];
|
||||||
|
|
||||||
|
net.minecraft.world.level.block.state.BlockState existing;
|
||||||
|
if (section == null) {
|
||||||
|
existing = ((PaperweightBlockMaterial) BlockTypes.AIR.getDefaultState().getMaterial()).getState();
|
||||||
|
} else {
|
||||||
|
existing = section.getBlockState(x & 15, y & 15, z & 15);
|
||||||
|
}
|
||||||
|
|
||||||
|
levelChunk.removeBlockEntity(blockPos); // Force delete the old tile entity
|
||||||
|
|
||||||
|
CompoundTag compoundTag = state instanceof BaseBlock ? state.getNbtData() : null;
|
||||||
|
if (compoundTag != null || existing instanceof TileEntityBlock) {
|
||||||
|
level.setBlock(blockPos, blockState, 0);
|
||||||
|
// remove tile
|
||||||
|
if (compoundTag != null) {
|
||||||
|
// We will assume that the tile entity was created for us,
|
||||||
|
// though we do not do this on the Forge version
|
||||||
|
BlockEntity blockEntity = level.getBlockEntity(blockPos);
|
||||||
|
if (blockEntity != null) {
|
||||||
|
net.minecraft.nbt.CompoundTag tag = (net.minecraft.nbt.CompoundTag) fromNative(compoundTag);
|
||||||
|
tag.put("x", IntTag.valueOf(x));
|
||||||
|
tag.put("y", IntTag.valueOf(y));
|
||||||
|
tag.put("z", IntTag.valueOf(z));
|
||||||
|
blockEntity.load(tag); // readTagIntoTileEntity - load data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (existing == blockState) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (section == null) {
|
||||||
|
if (blockState.isAir()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
levelChunkSections[y4] = section = new LevelChunkSection(y4 << 4);
|
||||||
|
}
|
||||||
|
levelChunk.setBlockState(blockPos, blockState, false);
|
||||||
|
}
|
||||||
|
if (update) {
|
||||||
|
level.getMinecraftWorld().sendBlockUpdated(blockPos, existing, blockState, 0);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public WorldNativeAccess<?, ?, ?> createWorldNativeAccess(org.bukkit.World world) {
|
||||||
|
return new PaperweightFaweWorldNativeAccess(
|
||||||
|
this,
|
||||||
|
new WeakReference<>(((CraftWorld) world).getHandle())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BaseEntity getEntity(org.bukkit.entity.Entity entity) {
|
||||||
|
Preconditions.checkNotNull(entity);
|
||||||
|
|
||||||
|
CraftEntity craftEntity = ((CraftEntity) entity);
|
||||||
|
Entity mcEntity = craftEntity.getHandle();
|
||||||
|
|
||||||
|
String id = getEntityId(mcEntity);
|
||||||
|
|
||||||
|
if (id != null) {
|
||||||
|
EntityType type = com.sk89q.worldedit.world.entity.EntityTypes.get(id);
|
||||||
|
//TODO 1.18 be a CBT ?
|
||||||
|
Supplier<CompoundTag> saveTag = () -> {
|
||||||
|
final net.minecraft.nbt.CompoundTag minecraftTag = new net.minecraft.nbt.CompoundTag();
|
||||||
|
readEntityIntoTag(mcEntity, minecraftTag);
|
||||||
|
//add Id for AbstractChangeSet to work
|
||||||
|
final CompoundTag tag = (CompoundTag) toNative(minecraftTag);
|
||||||
|
final Map<String, Tag> tags = new HashMap<>(tag.getValue());
|
||||||
|
tags.put("Id", new StringTag(id));
|
||||||
|
return new CompoundTag(tags);
|
||||||
|
};
|
||||||
|
return new LazyBaseEntity(type, saveTag);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Component getRichBlockName(BlockType blockType) {
|
||||||
|
return parent.getRichBlockName(blockType);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Component getRichItemName(ItemType itemType) {
|
||||||
|
return parent.getRichItemName(itemType);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Component getRichItemName(BaseItemStack itemStack) {
|
||||||
|
return parent.getRichItemName(itemStack);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public OptionalInt getInternalBlockStateId(BlockState state) {
|
||||||
|
PaperweightBlockMaterial material = (PaperweightBlockMaterial) state.getMaterial();
|
||||||
|
net.minecraft.world.level.block.state.BlockState mcState = material.getCraftBlockData().getState();
|
||||||
|
return OptionalInt.of(Block.BLOCK_STATE_REGISTRY.getId(mcState));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BlockState adapt(BlockData blockData) {
|
||||||
|
CraftBlockData cbd = ((CraftBlockData) blockData);
|
||||||
|
net.minecraft.world.level.block.state.BlockState ibd = cbd.getState();
|
||||||
|
return adapt(ibd);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BlockState adapt(net.minecraft.world.level.block.state.BlockState blockState) {
|
||||||
|
return BlockTypesCache.states[adaptToChar(blockState)];
|
||||||
|
}
|
||||||
|
|
||||||
|
public char adaptToChar(net.minecraft.world.level.block.state.BlockState blockState) {
|
||||||
|
int id = Block.BLOCK_STATE_REGISTRY.getId(blockState);
|
||||||
|
if (initialised) {
|
||||||
|
return ibdToStateOrdinal[id];
|
||||||
|
}
|
||||||
|
synchronized (this) {
|
||||||
|
if (initialised) {
|
||||||
|
return ibdToStateOrdinal[id];
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
init();
|
||||||
|
return ibdToStateOrdinal[id];
|
||||||
|
} catch (ArrayIndexOutOfBoundsException e1) {
|
||||||
|
LOGGER.error("Attempted to convert {} with ID {} to char. ibdToStateOrdinal length: {}. Defaulting to air!",
|
||||||
|
blockState.getBlock(), Block.BLOCK_STATE_REGISTRY.getId(blockState), ibdToStateOrdinal.length, e1
|
||||||
|
);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public char ibdIDToOrdinal(int id) {
|
||||||
|
if (initialised) {
|
||||||
|
return ibdToStateOrdinal[id];
|
||||||
|
}
|
||||||
|
synchronized (this) {
|
||||||
|
if (initialised) {
|
||||||
|
return ibdToStateOrdinal[id];
|
||||||
|
}
|
||||||
|
init();
|
||||||
|
return ibdToStateOrdinal[id];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public char[] getIbdToStateOrdinal() {
|
||||||
|
if (initialised) {
|
||||||
|
return ibdToStateOrdinal;
|
||||||
|
}
|
||||||
|
synchronized (this) {
|
||||||
|
if (initialised) {
|
||||||
|
return ibdToStateOrdinal;
|
||||||
|
}
|
||||||
|
init();
|
||||||
|
return ibdToStateOrdinal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int ordinalToIbdID(char ordinal) {
|
||||||
|
if (initialised) {
|
||||||
|
return ordinalToIbdID[ordinal];
|
||||||
|
}
|
||||||
|
synchronized (this) {
|
||||||
|
if (initialised) {
|
||||||
|
return ordinalToIbdID[ordinal];
|
||||||
|
}
|
||||||
|
init();
|
||||||
|
return ordinalToIbdID[ordinal];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int[] getOrdinalToIbdID() {
|
||||||
|
if (initialised) {
|
||||||
|
return ordinalToIbdID;
|
||||||
|
}
|
||||||
|
synchronized (this) {
|
||||||
|
if (initialised) {
|
||||||
|
return ordinalToIbdID;
|
||||||
|
}
|
||||||
|
init();
|
||||||
|
return ordinalToIbdID;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <B extends BlockStateHolder<B>> BlockData adapt(B state) {
|
||||||
|
PaperweightBlockMaterial material = (PaperweightBlockMaterial) state.getMaterial();
|
||||||
|
return material.getCraftBlockData();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sendFakeChunk(org.bukkit.World world, Player player, ChunkPacket chunkPacket) {
|
||||||
|
ServerLevel nmsWorld = ((CraftWorld) world).getHandle();
|
||||||
|
ChunkHolder map = PaperweightPlatformAdapter.getPlayerChunk(nmsWorld, chunkPacket.getChunkX(), chunkPacket.getChunkZ());
|
||||||
|
if (map != null && map.wasAccessibleSinceLastSave()) {
|
||||||
|
boolean flag = false;
|
||||||
|
// PlayerChunk.d players = map.players;
|
||||||
|
Stream<ServerPlayer> stream = /*players.a(new ChunkCoordIntPair(packet.getChunkX(), packet.getChunkZ()), flag)
|
||||||
|
*/ Stream.empty();
|
||||||
|
|
||||||
|
ServerPlayer checkPlayer = player == null ? null : ((CraftPlayer) player).getHandle();
|
||||||
|
stream.filter(entityPlayer -> checkPlayer == null || entityPlayer == checkPlayer)
|
||||||
|
.forEach(entityPlayer -> {
|
||||||
|
synchronized (chunkPacket) {
|
||||||
|
ClientboundLevelChunkPacketData nmsPacket = (ClientboundLevelChunkPacketData) chunkPacket.getNativePacket();
|
||||||
|
if (nmsPacket == null) {
|
||||||
|
nmsPacket = mapUtil.create(this, chunkPacket);
|
||||||
|
chunkPacket.setNativePacket(nmsPacket);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
FaweCache.IMP.CHUNK_FLAG.get().set(true);
|
||||||
|
entityPlayer.connection.send(nmsPacket);
|
||||||
|
} finally {
|
||||||
|
FaweCache.IMP.CHUNK_FLAG.get().set(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, ? extends Property<?>> getProperties(BlockType blockType) {
|
||||||
|
return getParent().getProperties(blockType);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canPlaceAt(org.bukkit.World world, BlockVector3 blockVector3, BlockState blockState) {
|
||||||
|
int internalId = BlockStateIdAccess.getBlockStateId(blockState);
|
||||||
|
net.minecraft.world.level.block.state.BlockState blockState1 = Block.stateById(internalId);
|
||||||
|
return blockState1.hasPostProcess(
|
||||||
|
((CraftWorld) world).getHandle(),
|
||||||
|
new BlockPos(blockVector3.getX(), blockVector3.getY(), blockVector3.getZ())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public org.bukkit.inventory.ItemStack adapt(BaseItemStack baseItemStack) {
|
||||||
|
ItemStack stack = new ItemStack(
|
||||||
|
Registry.ITEM.get(ResourceLocation.tryParse(baseItemStack.getType().getId())),
|
||||||
|
baseItemStack.getAmount()
|
||||||
|
);
|
||||||
|
stack.setTag(((net.minecraft.nbt.CompoundTag) fromNative(baseItemStack.getNbtData())));
|
||||||
|
return CraftItemStack.asCraftMirror(stack);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean generateTree(
|
||||||
|
TreeGenerator.TreeType treeType, EditSession editSession, BlockVector3 blockVector3,
|
||||||
|
org.bukkit.World bukkitWorld
|
||||||
|
) {
|
||||||
|
TreeType bukkitType = BukkitWorld.toBukkitTreeType(treeType);
|
||||||
|
if (bukkitType == TreeType.CHORUS_PLANT) {
|
||||||
|
blockVector3 = blockVector3.add(
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
0
|
||||||
|
); // bukkit skips the feature gen which does this offset normally, so we have to add it back
|
||||||
|
}
|
||||||
|
ServerLevel serverLevel = ((CraftWorld) bukkitWorld).getHandle();
|
||||||
|
serverLevel.captureTreeGeneration = true;
|
||||||
|
serverLevel.captureBlockStates = true;
|
||||||
|
boolean grownTree = bukkitWorld.generateTree(BukkitAdapter.adapt(bukkitWorld, blockVector3), bukkitType);
|
||||||
|
serverLevel.captureBlockStates = false;
|
||||||
|
serverLevel.captureTreeGeneration = false;
|
||||||
|
if (!grownTree) {
|
||||||
|
serverLevel.capturedBlockStates.clear();
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
for (CraftBlockState craftBlockState : serverLevel.capturedBlockStates.values()) {
|
||||||
|
if (craftBlockState == null || craftBlockState.getType() == Material.AIR) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
editSession.setBlock(craftBlockState.getX(), craftBlockState.getY(), craftBlockState.getZ(),
|
||||||
|
BukkitAdapter.adapt(((org.bukkit.block.BlockState) craftBlockState).getBlockData())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
serverLevel.capturedBlockStates.clear();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<org.bukkit.entity.Entity> getEntities(org.bukkit.World world) {
|
||||||
|
// Quickly add each entity to a list copy.
|
||||||
|
List<Entity> mcEntities = new ArrayList<>();
|
||||||
|
((CraftWorld) world).getHandle().entityManager.getEntityGetter().getAll().forEach(mcEntities::add);
|
||||||
|
|
||||||
|
List<org.bukkit.entity.Entity> list = new ArrayList<>();
|
||||||
|
mcEntities.forEach((mcEnt) -> {
|
||||||
|
org.bukkit.entity.Entity bukkitEntity = mcEnt.getBukkitEntity();
|
||||||
|
if (bukkitEntity.isValid()) {
|
||||||
|
list.add(bukkitEntity);
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BaseItemStack adapt(org.bukkit.inventory.ItemStack itemStack) {
|
||||||
|
final ItemStack nmsStack = CraftItemStack.asNMSCopy(itemStack);
|
||||||
|
final BaseItemStack weStack = new BaseItemStack(BukkitAdapter.asItemType(itemStack.getType()), itemStack.getAmount());
|
||||||
|
weStack.setNbtData(((CompoundTag) toNative(nmsStack.getTag())));
|
||||||
|
return weStack;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Tag toNative(net.minecraft.nbt.Tag foreign) {
|
||||||
|
return parent.toNative(foreign);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public net.minecraft.nbt.Tag fromNative(Tag foreign) {
|
||||||
|
if (foreign instanceof PaperweightLazyCompoundTag) {
|
||||||
|
return ((PaperweightLazyCompoundTag) foreign).get();
|
||||||
|
}
|
||||||
|
return parent.fromNative(foreign);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean regenerate(org.bukkit.World bukkitWorld, Region region, Extent target, RegenOptions options) throws Exception {
|
||||||
|
return new PaperweightRegen(bukkitWorld, region, target, options).regenerate();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IChunkGet get(org.bukkit.World world, int chunkX, int chunkZ) {
|
||||||
|
return new PaperweightGetBlocks(world, chunkX, chunkZ);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getInternalBiomeId(BiomeType biomeType) {
|
||||||
|
if (biomeType.getId().startsWith("minecraft:")) {
|
||||||
|
Biome biomeBase = CraftBlock.biomeToBiomeBase(
|
||||||
|
MinecraftServer.getServer().registryAccess().ownedRegistryOrThrow(Registry.BIOME_REGISTRY),
|
||||||
|
BukkitAdapter.adapt(biomeType)
|
||||||
|
);
|
||||||
|
return MinecraftServer.getServer().registryAccess().ownedRegistryOrThrow(Registry.BIOME_REGISTRY).getId(biomeBase);
|
||||||
|
} else {
|
||||||
|
WritableRegistry<Biome> biomeRegistry = MinecraftServer.getServer().registryAccess()
|
||||||
|
.ownedRegistryOrThrow(Registry.BIOME_REGISTRY);
|
||||||
|
|
||||||
|
ResourceLocation resourceLocation = biomeRegistry.keySet().stream()
|
||||||
|
.filter(resource -> resource.toString().equals(biomeType.getId()))
|
||||||
|
.findAny().orElse(null);
|
||||||
|
|
||||||
|
return biomeRegistry.getId(biomeRegistry.get(resourceLocation));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Iterable<NamespacedKey> getRegisteredBiomes() {
|
||||||
|
WritableRegistry<Biome> biomeRegistry = ((CraftServer) Bukkit.getServer())
|
||||||
|
.getServer()
|
||||||
|
.registryAccess()
|
||||||
|
.ownedRegistryOrThrow(
|
||||||
|
Registry.BIOME_REGISTRY);
|
||||||
|
return biomeRegistry.stream()
|
||||||
|
.map(biomeRegistry::getKey)
|
||||||
|
.map(CraftNamespacedKey::fromMinecraft)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RelighterFactory getRelighterFactory() {
|
||||||
|
try {
|
||||||
|
Class.forName("ca.spottedleaf.starlight.light.StarLightEngine");
|
||||||
|
if (PaperweightStarlightRelighter.isUsable()) {
|
||||||
|
return new PaperweightStarlightRelighterFactory();
|
||||||
|
}
|
||||||
|
} catch (ThreadDeath td) {
|
||||||
|
throw td;
|
||||||
|
} catch (Throwable ignored) {
|
||||||
|
|
||||||
|
}
|
||||||
|
return new NMSRelighterFactory();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, List<Property<?>>> getAllProperties() {
|
||||||
|
if (initialised) {
|
||||||
|
return allBlockProperties;
|
||||||
|
}
|
||||||
|
synchronized (this) {
|
||||||
|
if (initialised) {
|
||||||
|
return allBlockProperties;
|
||||||
|
}
|
||||||
|
init();
|
||||||
|
return allBlockProperties;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,283 @@
|
|||||||
|
package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_18_R1;
|
||||||
|
|
||||||
|
import com.fastasyncworldedit.core.Fawe;
|
||||||
|
import com.fastasyncworldedit.core.math.IntPair;
|
||||||
|
import com.fastasyncworldedit.core.util.TaskManager;
|
||||||
|
import com.fastasyncworldedit.core.util.task.RunnableVal;
|
||||||
|
import com.sk89q.worldedit.bukkit.BukkitAdapter;
|
||||||
|
import com.sk89q.worldedit.internal.block.BlockStateIdAccess;
|
||||||
|
import com.sk89q.worldedit.internal.wna.WorldNativeAccess;
|
||||||
|
import com.sk89q.worldedit.util.SideEffect;
|
||||||
|
import com.sk89q.worldedit.util.SideEffectSet;
|
||||||
|
import com.sk89q.worldedit.util.nbt.CompoundBinaryTag;
|
||||||
|
import com.sk89q.worldedit.world.block.BlockState;
|
||||||
|
import net.minecraft.core.BlockPos;
|
||||||
|
import net.minecraft.core.Direction;
|
||||||
|
import net.minecraft.nbt.CompoundTag;
|
||||||
|
import net.minecraft.server.MinecraftServer;
|
||||||
|
import net.minecraft.server.level.ChunkHolder;
|
||||||
|
import net.minecraft.server.level.ServerChunkCache;
|
||||||
|
import net.minecraft.world.level.Level;
|
||||||
|
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_18_R1.CraftWorld;
|
||||||
|
import org.bukkit.craftbukkit.v1_18_R1.block.data.CraftBlockData;
|
||||||
|
import org.bukkit.event.block.BlockPhysicsEvent;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
public class PaperweightFaweWorldNativeAccess implements WorldNativeAccess<LevelChunk,
|
||||||
|
net.minecraft.world.level.block.state.BlockState, BlockPos> {
|
||||||
|
|
||||||
|
private static final int UPDATE = 1;
|
||||||
|
private static final int NOTIFY = 2;
|
||||||
|
private static final Direction[] NEIGHBOUR_ORDER = {
|
||||||
|
Direction.EAST,
|
||||||
|
Direction.WEST,
|
||||||
|
Direction.DOWN,
|
||||||
|
Direction.UP,
|
||||||
|
Direction.NORTH,
|
||||||
|
Direction.SOUTH
|
||||||
|
};
|
||||||
|
private final PaperweightFaweAdapter paperweightFaweAdapter;
|
||||||
|
private final WeakReference<Level> level;
|
||||||
|
private final AtomicInteger lastTick;
|
||||||
|
private final Set<CachedChange> cachedChanges = new HashSet<>();
|
||||||
|
private final Set<IntPair> cachedChunksToSend = new HashSet<>();
|
||||||
|
private SideEffectSet sideEffectSet;
|
||||||
|
|
||||||
|
public PaperweightFaweWorldNativeAccess(PaperweightFaweAdapter paperweightFaweAdapter, WeakReference<Level> level) {
|
||||||
|
this.paperweightFaweAdapter = paperweightFaweAdapter;
|
||||||
|
this.level = level;
|
||||||
|
// Use the actual tick as minecraft-defined so we don't try to force blocks into the world when the server's already lagging.
|
||||||
|
// - With the caveat that we don't want to have too many cached changed (1024) so we'd flush those at 1024 anyway.
|
||||||
|
this.lastTick = new AtomicInteger(MinecraftServer.currentTick);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Level getLevel() {
|
||||||
|
return Objects.requireNonNull(level.get(), "The reference to the world was lost");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setCurrentSideEffectSet(SideEffectSet sideEffectSet) {
|
||||||
|
this.sideEffectSet = sideEffectSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LevelChunk getChunk(int x, int z) {
|
||||||
|
return getLevel().getChunk(x, z);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public net.minecraft.world.level.block.state.BlockState toNative(BlockState blockState) {
|
||||||
|
int stateId = paperweightFaweAdapter.ordinalToIbdID(blockState.getOrdinalChar());
|
||||||
|
return BlockStateIdAccess.isValidInternalId(stateId)
|
||||||
|
? Block.stateById(stateId)
|
||||||
|
: ((CraftBlockData) BukkitAdapter.adapt(blockState)).getState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public net.minecraft.world.level.block.state.BlockState getBlockState(LevelChunk levelChunk, BlockPos blockPos) {
|
||||||
|
return levelChunk.getBlockState(blockPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public synchronized net.minecraft.world.level.block.state.BlockState setBlockState(
|
||||||
|
LevelChunk levelChunk, BlockPos blockPos,
|
||||||
|
net.minecraft.world.level.block.state.BlockState blockState
|
||||||
|
) {
|
||||||
|
int currentTick = MinecraftServer.currentTick;
|
||||||
|
if (Fawe.isMainThread()) {
|
||||||
|
return levelChunk.setBlockState(blockPos, blockState,
|
||||||
|
this.sideEffectSet != null && this.sideEffectSet.shouldApply(SideEffect.UPDATE)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// Since FAWE is.. Async we need to do it on the main thread (wooooo.. :( )
|
||||||
|
cachedChanges.add(new CachedChange(levelChunk, blockPos, blockState));
|
||||||
|
cachedChunksToSend.add(new IntPair(levelChunk.bukkitChunk.getX(), levelChunk.bukkitChunk.getZ()));
|
||||||
|
boolean nextTick = lastTick.get() > currentTick;
|
||||||
|
if (nextTick || cachedChanges.size() >= 1024) {
|
||||||
|
if (nextTick) {
|
||||||
|
lastTick.set(currentTick);
|
||||||
|
}
|
||||||
|
flushAsync(nextTick);
|
||||||
|
}
|
||||||
|
return blockState;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public net.minecraft.world.level.block.state.BlockState getValidBlockForPosition(
|
||||||
|
net.minecraft.world.level.block.state.BlockState blockState,
|
||||||
|
BlockPos blockPos
|
||||||
|
) {
|
||||||
|
return Block.updateFromNeighbourShapes(blockState, getLevel(), blockPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BlockPos getPosition(int x, int y, int z) {
|
||||||
|
return new BlockPos(x, y, z);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateLightingForBlock(BlockPos blockPos) {
|
||||||
|
getLevel().getChunkSource().getLightEngine().checkBlock(blockPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean updateTileEntity(BlockPos blockPos, CompoundBinaryTag tag) {
|
||||||
|
// We will assume that the tile entity was created for us,
|
||||||
|
// though we do not do this on the other versions
|
||||||
|
BlockEntity blockEntity = getLevel().getBlockEntity(blockPos);
|
||||||
|
if (blockEntity == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
net.minecraft.nbt.Tag nativeTag = paperweightFaweAdapter.fromNativeBinary(tag);
|
||||||
|
blockEntity.load((CompoundTag) nativeTag);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void notifyBlockUpdate(
|
||||||
|
LevelChunk levelChunk, BlockPos blockPos,
|
||||||
|
net.minecraft.world.level.block.state.BlockState oldState,
|
||||||
|
net.minecraft.world.level.block.state.BlockState newState
|
||||||
|
) {
|
||||||
|
if (levelChunk.getSections()[level.get().getSectionIndex(blockPos.getY())] != null) {
|
||||||
|
getLevel().sendBlockUpdated(blockPos, oldState, newState, UPDATE | NOTIFY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isChunkTicking(LevelChunk levelChunk) {
|
||||||
|
return levelChunk.getFullStatus().isOrAfter(ChunkHolder.FullChunkStatus.TICKING);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void markBlockChanged(LevelChunk levelChunk, BlockPos blockPos) {
|
||||||
|
if (levelChunk.getSections()[level.get().getSectionIndex(blockPos.getY())] != null) {
|
||||||
|
((ServerChunkCache) getLevel().getChunkSource()).blockChanged(blockPos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void notifyNeighbors(
|
||||||
|
BlockPos blockPos,
|
||||||
|
net.minecraft.world.level.block.state.BlockState oldState,
|
||||||
|
net.minecraft.world.level.block.state.BlockState newState
|
||||||
|
) {
|
||||||
|
Level level = getLevel();
|
||||||
|
if (sideEffectSet.shouldApply(SideEffect.EVENTS)) {
|
||||||
|
level.blockUpdated(blockPos, oldState.getBlock());
|
||||||
|
} else {
|
||||||
|
// When we don't want events, manually run the physics without them.
|
||||||
|
// Un-nest neighbour updating
|
||||||
|
for (Direction direction : NEIGHBOUR_ORDER) {
|
||||||
|
BlockPos shifted = blockPos.relative(direction);
|
||||||
|
level.getBlockState(shifted).neighborChanged(level, shifted, oldState.getBlock(), blockPos, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (newState.hasAnalogOutputSignal()) {
|
||||||
|
level.updateNeighbourForOutputSignal(blockPos, newState.getBlock());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateNeighbors(
|
||||||
|
BlockPos blockPos,
|
||||||
|
net.minecraft.world.level.block.state.BlockState oldState,
|
||||||
|
net.minecraft.world.level.block.state.BlockState newState,
|
||||||
|
int recursionLimit
|
||||||
|
) {
|
||||||
|
Level level = getLevel();
|
||||||
|
// a == updateNeighbors
|
||||||
|
// b == updateDiagonalNeighbors
|
||||||
|
oldState.updateIndirectNeighbourShapes(level, blockPos, NOTIFY, recursionLimit);
|
||||||
|
if (sideEffectSet.shouldApply(SideEffect.EVENTS)) {
|
||||||
|
CraftWorld craftWorld = level.getWorld();
|
||||||
|
if (craftWorld != null) {
|
||||||
|
BlockPhysicsEvent event = new BlockPhysicsEvent(
|
||||||
|
craftWorld.getBlockAt(blockPos.getX(), blockPos.getY(), blockPos.getZ()),
|
||||||
|
CraftBlockData.fromData(newState)
|
||||||
|
);
|
||||||
|
level.getCraftServer().getPluginManager().callEvent(event);
|
||||||
|
if (event.isCancelled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
newState.triggerEvent(level, blockPos, NOTIFY, recursionLimit);
|
||||||
|
newState.updateIndirectNeighbourShapes(level, blockPos, NOTIFY, recursionLimit);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBlockStateChange(
|
||||||
|
BlockPos blockPos,
|
||||||
|
net.minecraft.world.level.block.state.BlockState oldState,
|
||||||
|
net.minecraft.world.level.block.state.BlockState newState
|
||||||
|
) {
|
||||||
|
getLevel().onBlockStateChange(blockPos, oldState, newState);
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized void flushAsync(final boolean sendChunks) {
|
||||||
|
final Set<CachedChange> changes = Set.copyOf(cachedChanges);
|
||||||
|
cachedChanges.clear();
|
||||||
|
final Set<IntPair> toSend;
|
||||||
|
if (sendChunks) {
|
||||||
|
toSend = Set.copyOf(cachedChunksToSend);
|
||||||
|
cachedChunksToSend.clear();
|
||||||
|
} else {
|
||||||
|
toSend = Collections.emptySet();
|
||||||
|
}
|
||||||
|
RunnableVal<Object> runnableVal = new RunnableVal<>() {
|
||||||
|
@Override
|
||||||
|
public void run(Object value) {
|
||||||
|
changes.forEach(cc -> cc.levelChunk.setBlockState(cc.blockPos, cc.blockState,
|
||||||
|
sideEffectSet != null && sideEffectSet.shouldApply(SideEffect.UPDATE)
|
||||||
|
));
|
||||||
|
if (!sendChunks) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (IntPair chunk : toSend) {
|
||||||
|
PaperweightPlatformAdapter.sendChunk(getLevel().getWorld().getHandle(), chunk.x, chunk.z, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
TaskManager.IMP.async(() -> TaskManager.IMP.sync(runnableVal));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void flush() {
|
||||||
|
RunnableVal<Object> runnableVal = new RunnableVal<>() {
|
||||||
|
@Override
|
||||||
|
public void run(Object value) {
|
||||||
|
cachedChanges.forEach(cc -> cc.levelChunk.setBlockState(cc.blockPos, cc.blockState,
|
||||||
|
sideEffectSet != null && sideEffectSet.shouldApply(SideEffect.UPDATE)
|
||||||
|
));
|
||||||
|
for (IntPair chunk : cachedChunksToSend) {
|
||||||
|
PaperweightPlatformAdapter.sendChunk(getLevel().getWorld().getHandle(), chunk.x, chunk.z, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if (Fawe.isMainThread()) {
|
||||||
|
runnableVal.run();
|
||||||
|
} else {
|
||||||
|
TaskManager.IMP.sync(runnableVal);
|
||||||
|
}
|
||||||
|
cachedChanges.clear();
|
||||||
|
cachedChunksToSend.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
private record CachedChange(LevelChunk levelChunk, BlockPos blockPos,
|
||||||
|
net.minecraft.world.level.block.state.BlockState blockState) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,253 @@
|
|||||||
|
package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_18_R1;
|
||||||
|
|
||||||
|
import com.fastasyncworldedit.core.extent.processor.heightmap.HeightMapType;
|
||||||
|
import com.fastasyncworldedit.core.queue.IBlocks;
|
||||||
|
import com.fastasyncworldedit.core.queue.IChunkGet;
|
||||||
|
import com.fastasyncworldedit.core.queue.IChunkSet;
|
||||||
|
import com.google.common.base.Suppliers;
|
||||||
|
import com.sk89q.jnbt.CompoundTag;
|
||||||
|
import com.sk89q.worldedit.bukkit.WorldEditPlugin;
|
||||||
|
import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter;
|
||||||
|
import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_18_R1.nbt.PaperweightLazyCompoundTag;
|
||||||
|
import com.sk89q.worldedit.math.BlockVector3;
|
||||||
|
import com.sk89q.worldedit.world.biome.BiomeType;
|
||||||
|
import com.sk89q.worldedit.world.block.BaseBlock;
|
||||||
|
import com.sk89q.worldedit.world.block.BlockState;
|
||||||
|
import com.sk89q.worldedit.world.block.BlockTypesCache;
|
||||||
|
import net.minecraft.server.level.ServerLevel;
|
||||||
|
import net.minecraft.world.entity.Entity;
|
||||||
|
import net.minecraft.world.level.biome.Biome;
|
||||||
|
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||||
|
import net.minecraft.world.level.chunk.ChunkBiomeContainer;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.Future;
|
||||||
|
|
||||||
|
public class PaperweightGetBlocks_Copy implements IChunkGet {
|
||||||
|
|
||||||
|
private final Map<BlockVector3, CompoundTag> tiles = new HashMap<>();
|
||||||
|
private final Set<CompoundTag> entities = new HashSet<>();
|
||||||
|
private final char[][] blocks;
|
||||||
|
private final int minHeight;
|
||||||
|
private final int maxHeight;
|
||||||
|
private final ServerLevel serverLevel;
|
||||||
|
private ChunkBiomeContainer chunkBiomeContainer;
|
||||||
|
|
||||||
|
protected PaperweightGetBlocks_Copy(ServerLevel world) {
|
||||||
|
this.serverLevel = world;
|
||||||
|
this.minHeight = world.getMinBuildHeight();
|
||||||
|
this.maxHeight = world.getMaxBuildHeight() - 1; // Minecraft max limit is exclusive.
|
||||||
|
this.blocks = new char[getSectionCount()][];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void storeTile(BlockEntity blockEntity) {
|
||||||
|
tiles.put(
|
||||||
|
BlockVector3.at(
|
||||||
|
blockEntity.getBlockPos().getX(),
|
||||||
|
blockEntity.getBlockPos().getY(),
|
||||||
|
blockEntity.getBlockPos().getZ()
|
||||||
|
),
|
||||||
|
new PaperweightLazyCompoundTag(Suppliers.memoize(() -> blockEntity.save(new net.minecraft.nbt.CompoundTag())))
|
||||||
|
//TODO 1.18 save -> saveAdditional
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<BlockVector3, CompoundTag> getTiles() {
|
||||||
|
return tiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
public CompoundTag getTile(int x, int y, int z) {
|
||||||
|
return tiles.get(BlockVector3.at(x, y, z));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void storeEntity(Entity entity) {
|
||||||
|
//TODO 1.18 address raw access & possibly be a CBT ?
|
||||||
|
BukkitImplAdapter adapter = WorldEditPlugin.getInstance().getBukkitImplAdapter();
|
||||||
|
net.minecraft.nbt.CompoundTag compoundTag = new net.minecraft.nbt.CompoundTag();
|
||||||
|
entities.add((CompoundTag) adapter.toNative(entity.save(compoundTag)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<CompoundTag> getEntities() {
|
||||||
|
return this.entities;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CompoundTag getEntity(UUID uuid) {
|
||||||
|
for (CompoundTag tag : entities) {
|
||||||
|
UUID tagUUID;
|
||||||
|
if (tag.containsKey("UUID")) {
|
||||||
|
int[] arr = tag.getIntArray("UUID");
|
||||||
|
tagUUID = new UUID((long) arr[0] << 32 | (arr[1] & 0xFFFFFFFFL), (long) arr[2] << 32 | (arr[3] & 0xFFFFFFFFL));
|
||||||
|
} else if (tag.containsKey("UUIDMost")) {
|
||||||
|
tagUUID = new UUID(tag.getLong("UUIDMost"), tag.getLong("UUIDLeast"));
|
||||||
|
} else if (tag.containsKey("PersistentIDMSB")) {
|
||||||
|
tagUUID = new UUID(tag.getLong("PersistentIDMSB"), tag.getLong("PersistentIDLSB"));
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (uuid.equals(tagUUID)) {
|
||||||
|
return tag;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isCreateCopy() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setCreateCopy(boolean createCopy) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setLightingToGet(char[][] lighting, int minSectionPosition, int maxSectionPosition) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setSkyLightingToGet(char[][] lighting, int minSectionPosition, int maxSectionPosition) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setHeightmapToGet(HeightMapType type, int[] data) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getMaxY() {
|
||||||
|
return maxHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getMinY() {
|
||||||
|
return minHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getMaxSectionPosition() {
|
||||||
|
return maxHeight >> 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getMinSectionPosition() {
|
||||||
|
return minHeight >> 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void storeBiomes(ChunkBiomeContainer chunkBiomeContainer) {
|
||||||
|
// The to do one line below is pre-paperweight and needs to be revised
|
||||||
|
// TODO revisit last parameter, BiomeStorage[] *would* be more efficient
|
||||||
|
this.chunkBiomeContainer = new ChunkBiomeContainer(chunkBiomeContainer.biomeRegistry, serverLevel,
|
||||||
|
chunkBiomeContainer.writeBiomes()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BiomeType getBiomeType(int x, int y, int z) {
|
||||||
|
Biome biome = null;
|
||||||
|
if (y == -1) {
|
||||||
|
for (y = serverLevel.getMinBuildHeight(); y <= serverLevel.getMaxBuildHeight(); y += 4) {
|
||||||
|
biome = this.chunkBiomeContainer.getNoiseBiome(x >> 2, y >> 2, z >> 2);
|
||||||
|
if (biome != null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
biome = this.chunkBiomeContainer.getNoiseBiome(x >> 2, y >> 2, z >> 2);
|
||||||
|
}
|
||||||
|
return biome != null ? PaperweightPlatformAdapter.adapt(biome, serverLevel) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeSectionLighting(int layer, boolean sky) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean trim(boolean aggressive, int layer) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IBlocks reset() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getSectionCount() {
|
||||||
|
return serverLevel.getSectionsCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void storeSection(int layer, char[] data) {
|
||||||
|
blocks[layer] = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BaseBlock getFullBlock(int x, int y, int z) {
|
||||||
|
BlockState state = BlockTypesCache.states[get(x, y, z)];
|
||||||
|
return state.toBaseBlock(this, x, y, z);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasSection(int layer) {
|
||||||
|
layer -= getMinSectionPosition();
|
||||||
|
return blocks[layer] != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public char[] load(int layer) {
|
||||||
|
layer -= getMinSectionPosition();
|
||||||
|
return blocks[layer];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public char[] loadIfPresent(int layer) {
|
||||||
|
layer -= getMinSectionPosition();
|
||||||
|
return blocks[layer];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BlockState getBlock(int x, int y, int z) {
|
||||||
|
return BlockTypesCache.states[get(x, y, z)];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getSkyLight(int x, int y, int z) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getEmittedLight(int x, int y, int z) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int[] getHeightMap(HeightMapType type) {
|
||||||
|
return new int[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T extends Future<T>> T call(IChunkSet set, Runnable finalize) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public char get(int x, int y, int z) {
|
||||||
|
final int layer = (y >> 4) - getMinSectionPosition();
|
||||||
|
final int index = (y & 15) << 8 | z << 4 | x;
|
||||||
|
return blocks[layer][index];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean trim(boolean aggressive) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,32 @@
|
|||||||
|
package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_18_R1;
|
||||||
|
|
||||||
|
import com.fastasyncworldedit.bukkit.adapter.MapChunkUtil;
|
||||||
|
import com.sk89q.worldedit.bukkit.adapter.Refraction;
|
||||||
|
import net.minecraft.network.protocol.game.ClientboundLevelChunkPacketData;
|
||||||
|
|
||||||
|
public class PaperweightMapChunkUtil extends MapChunkUtil<ClientboundLevelChunkPacketData> {
|
||||||
|
|
||||||
|
public PaperweightMapChunkUtil() throws NoSuchFieldException {
|
||||||
|
fieldX = ClientboundLevelChunkPacketData.class.getDeclaredField(Refraction.pickName("TWO_MEGABYTES", "a"));
|
||||||
|
fieldZ = ClientboundLevelChunkPacketData.class.getDeclaredField(Refraction.pickName("x", "b"));
|
||||||
|
fieldBitMask = ClientboundLevelChunkPacketData.class.getDeclaredField(Refraction.pickName("z", "c"));
|
||||||
|
fieldHeightMap = ClientboundLevelChunkPacketData.class.getDeclaredField(Refraction.pickName("availableSections", "d"));
|
||||||
|
fieldChunkData = ClientboundLevelChunkPacketData.class.getDeclaredField(Refraction.pickName("biomes", "f"));
|
||||||
|
fieldBlockEntities = ClientboundLevelChunkPacketData.class.getDeclaredField(Refraction.pickName("buffer", "g"));
|
||||||
|
fieldFull = ClientboundLevelChunkPacketData.class.getDeclaredField(Refraction.pickName("blockEntitiesTags", "h"));
|
||||||
|
fieldX.setAccessible(true);
|
||||||
|
fieldZ.setAccessible(true);
|
||||||
|
fieldBitMask.setAccessible(true);
|
||||||
|
fieldHeightMap.setAccessible(true);
|
||||||
|
fieldChunkData.setAccessible(true);
|
||||||
|
fieldBlockEntities.setAccessible(true);
|
||||||
|
fieldFull.setAccessible(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ClientboundLevelChunkPacketData createPacket() {
|
||||||
|
// TODO ??? return new ClientboundLevelChunkPacket();
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,449 @@
|
|||||||
|
package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_18_R1;
|
||||||
|
|
||||||
|
import com.fastasyncworldedit.bukkit.adapter.CachedBukkitAdapter;
|
||||||
|
import com.fastasyncworldedit.bukkit.adapter.DelegateSemaphore;
|
||||||
|
import com.fastasyncworldedit.bukkit.adapter.NMSAdapter;
|
||||||
|
import com.fastasyncworldedit.core.Fawe;
|
||||||
|
import com.fastasyncworldedit.core.FaweCache;
|
||||||
|
import com.fastasyncworldedit.core.configuration.Settings;
|
||||||
|
import com.fastasyncworldedit.core.math.BitArrayUnstretched;
|
||||||
|
import com.fastasyncworldedit.core.util.MathMan;
|
||||||
|
import com.fastasyncworldedit.core.util.ReflectionUtils;
|
||||||
|
import com.fastasyncworldedit.core.util.TaskManager;
|
||||||
|
import com.mojang.datafixers.util.Either;
|
||||||
|
import com.sk89q.worldedit.bukkit.adapter.Refraction;
|
||||||
|
import com.sk89q.worldedit.math.BlockVector3;
|
||||||
|
import com.sk89q.worldedit.world.biome.BiomeType;
|
||||||
|
import com.sk89q.worldedit.world.biome.BiomeTypes;
|
||||||
|
import com.sk89q.worldedit.world.block.BlockState;
|
||||||
|
import com.sk89q.worldedit.world.block.BlockTypesCache;
|
||||||
|
import io.papermc.lib.PaperLib;
|
||||||
|
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||||
|
import net.minecraft.core.BlockPos;
|
||||||
|
import net.minecraft.core.Registry;
|
||||||
|
import net.minecraft.core.SectionPos;
|
||||||
|
import net.minecraft.nbt.NbtUtils;
|
||||||
|
import net.minecraft.network.protocol.game.ClientboundLevelChunkPacketData;
|
||||||
|
import net.minecraft.network.protocol.game.ClientboundLightUpdatePacket;
|
||||||
|
import net.minecraft.resources.ResourceLocation;
|
||||||
|
import net.minecraft.server.level.ChunkHolder;
|
||||||
|
import net.minecraft.server.level.ChunkMap;
|
||||||
|
import net.minecraft.server.level.ServerLevel;
|
||||||
|
import net.minecraft.server.level.ServerPlayer;
|
||||||
|
import net.minecraft.util.BitStorage;
|
||||||
|
import net.minecraft.world.level.ChunkPos;
|
||||||
|
import net.minecraft.world.level.LevelAccessor;
|
||||||
|
import net.minecraft.world.level.biome.Biome;
|
||||||
|
import net.minecraft.world.level.block.Block;
|
||||||
|
import net.minecraft.world.level.block.EntityBlock;
|
||||||
|
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||||
|
import net.minecraft.world.level.chunk.ChunkBiomeContainer;
|
||||||
|
import net.minecraft.world.level.chunk.HashMapPalette;
|
||||||
|
import net.minecraft.world.level.chunk.LevelChunk;
|
||||||
|
import net.minecraft.world.level.chunk.LevelChunkSection;
|
||||||
|
import net.minecraft.world.level.chunk.LinearPalette;
|
||||||
|
import net.minecraft.world.level.chunk.Palette;
|
||||||
|
import net.minecraft.world.level.chunk.PalettedContainer;
|
||||||
|
import net.minecraft.world.level.gameevent.GameEventDispatcher;
|
||||||
|
import net.minecraft.world.level.gameevent.GameEventListener;
|
||||||
|
import org.bukkit.craftbukkit.v1_18_R1.CraftChunk;
|
||||||
|
import sun.misc.Unsafe;
|
||||||
|
|
||||||
|
import java.lang.invoke.MethodHandle;
|
||||||
|
import java.lang.invoke.MethodHandles;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.concurrent.Semaphore;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
public final class PaperweightPlatformAdapter extends NMSAdapter {
|
||||||
|
|
||||||
|
public static final Field fieldStorage;
|
||||||
|
public static final Field fieldPalette;
|
||||||
|
public static final Field fieldBits;
|
||||||
|
|
||||||
|
public static final Field fieldBitsPerEntry;
|
||||||
|
|
||||||
|
public static final Field fieldTickingFluidContent;
|
||||||
|
public static final Field fieldTickingBlockCount;
|
||||||
|
public static final Field fieldNonEmptyBlockCount;
|
||||||
|
|
||||||
|
private static final Field fieldBiomes;
|
||||||
|
|
||||||
|
private static final MethodHandle methodGetVisibleChunk;
|
||||||
|
|
||||||
|
private static final int CHUNKSECTION_BASE;
|
||||||
|
private static final int CHUNKSECTION_SHIFT;
|
||||||
|
|
||||||
|
private static final Field fieldLock;
|
||||||
|
private static final long fieldLockOffset;
|
||||||
|
|
||||||
|
private static final Field fieldGameEventDispatcherSections;
|
||||||
|
private static final MethodHandle methodremoveTickingBlockEntity;
|
||||||
|
|
||||||
|
private static final Field fieldRemove;
|
||||||
|
|
||||||
|
static {
|
||||||
|
try {
|
||||||
|
fieldBits = PalettedContainer.class.getDeclaredField(Refraction.pickName("bits", "l"));
|
||||||
|
fieldBits.setAccessible(true);
|
||||||
|
fieldStorage = PalettedContainer.class.getDeclaredField(Refraction.pickName("storage", "c"));
|
||||||
|
fieldStorage.setAccessible(true);
|
||||||
|
fieldPalette = PalettedContainer.class.getDeclaredField(Refraction.pickName("palette", "k"));
|
||||||
|
fieldPalette.setAccessible(true);
|
||||||
|
|
||||||
|
fieldBitsPerEntry = BitStorage.class.getDeclaredField(Refraction.pickName("bits", "c"));
|
||||||
|
fieldBitsPerEntry.setAccessible(true);
|
||||||
|
|
||||||
|
fieldTickingFluidContent = LevelChunkSection.class.getDeclaredField(Refraction.pickName("tickingFluidCount", "h"));
|
||||||
|
fieldTickingFluidContent.setAccessible(true);
|
||||||
|
fieldTickingBlockCount = LevelChunkSection.class.getDeclaredField(Refraction.pickName("tickingBlockCount", "g"));
|
||||||
|
fieldTickingBlockCount.setAccessible(true);
|
||||||
|
fieldNonEmptyBlockCount = LevelChunkSection.class.getDeclaredField(Refraction.pickName("nonEmptyBlockCount", "f"));
|
||||||
|
fieldNonEmptyBlockCount.setAccessible(true);
|
||||||
|
|
||||||
|
fieldBiomes = ChunkBiomeContainer.class.getDeclaredField(Refraction.pickName("biomes", "f"));
|
||||||
|
fieldBiomes.setAccessible(true);
|
||||||
|
|
||||||
|
Method getVisibleChunkIfPresent = ChunkMap.class.getDeclaredMethod(Refraction.pickName(
|
||||||
|
"getVisibleChunkIfPresent",
|
||||||
|
"getVisibleChunk"
|
||||||
|
), long.class);
|
||||||
|
getVisibleChunkIfPresent.setAccessible(true);
|
||||||
|
methodGetVisibleChunk = MethodHandles.lookup().unreflect(getVisibleChunkIfPresent);
|
||||||
|
|
||||||
|
Unsafe unsafe = ReflectionUtils.getUnsafe();
|
||||||
|
fieldLock = PalettedContainer.class.getDeclaredField(Refraction.pickName("lock", "m"));
|
||||||
|
fieldLockOffset = unsafe.objectFieldOffset(fieldLock);
|
||||||
|
|
||||||
|
fieldGameEventDispatcherSections = LevelChunk.class.getDeclaredField(Refraction.pickName(
|
||||||
|
"gameEventDispatcherSections", "x"));
|
||||||
|
fieldGameEventDispatcherSections.setAccessible(true);
|
||||||
|
Method removeBlockEntityTicker = LevelChunk.class.getDeclaredMethod(
|
||||||
|
Refraction.pickName(
|
||||||
|
"removeBlockEntityTicker",
|
||||||
|
"l"
|
||||||
|
), BlockPos.class
|
||||||
|
);
|
||||||
|
removeBlockEntityTicker.setAccessible(true);
|
||||||
|
methodremoveTickingBlockEntity = MethodHandles.lookup().unreflect(removeBlockEntityTicker);
|
||||||
|
|
||||||
|
fieldRemove = BlockEntity.class.getDeclaredField(Refraction.pickName("remove", "p"));
|
||||||
|
fieldRemove.setAccessible(true);
|
||||||
|
|
||||||
|
CHUNKSECTION_BASE = unsafe.arrayBaseOffset(LevelChunkSection[].class);
|
||||||
|
int scale = unsafe.arrayIndexScale(LevelChunkSection[].class);
|
||||||
|
if ((scale & (scale - 1)) != 0) {
|
||||||
|
throw new Error("data type scale not a power of two");
|
||||||
|
}
|
||||||
|
CHUNKSECTION_SHIFT = 31 - Integer.numberOfLeadingZeros(scale);
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
throw e;
|
||||||
|
} catch (Throwable rethrow) {
|
||||||
|
rethrow.printStackTrace();
|
||||||
|
throw new RuntimeException(rethrow);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static boolean setSectionAtomic(
|
||||||
|
LevelChunkSection[] sections,
|
||||||
|
LevelChunkSection expected,
|
||||||
|
LevelChunkSection value,
|
||||||
|
int layer
|
||||||
|
) {
|
||||||
|
long offset = ((long) layer << CHUNKSECTION_SHIFT) + CHUNKSECTION_BASE;
|
||||||
|
if (layer >= 0 && layer < sections.length) {
|
||||||
|
return ReflectionUtils.getUnsafe().compareAndSwapObject(sections, offset, expected, value);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static DelegateSemaphore applyLock(LevelChunkSection section) {
|
||||||
|
//todo there has to be a better way to do this. Maybe using a() in DataPaletteBlock which acquires the lock in NMS?
|
||||||
|
try {
|
||||||
|
synchronized (section) {
|
||||||
|
Unsafe unsafe = ReflectionUtils.getUnsafe();
|
||||||
|
PalettedContainer<net.minecraft.world.level.block.state.BlockState> blocks = section.getStates();
|
||||||
|
Semaphore currentLock = (Semaphore) unsafe.getObject(blocks, fieldLockOffset);
|
||||||
|
if (currentLock instanceof DelegateSemaphore) {
|
||||||
|
return (DelegateSemaphore) currentLock;
|
||||||
|
}
|
||||||
|
DelegateSemaphore newLock = new DelegateSemaphore(1, currentLock);
|
||||||
|
unsafe.putObject(blocks, fieldLockOffset, newLock);
|
||||||
|
return newLock;
|
||||||
|
}
|
||||||
|
} catch (Throwable e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static LevelChunk ensureLoaded(ServerLevel serverLevel, int chunkX, int chunkZ) {
|
||||||
|
if (!PaperLib.isPaper()) {
|
||||||
|
LevelChunk nmsChunk = serverLevel.getChunkSource().getChunk(chunkX, chunkZ, false);
|
||||||
|
if (nmsChunk != null) {
|
||||||
|
return nmsChunk;
|
||||||
|
}
|
||||||
|
if (Fawe.isMainThread()) {
|
||||||
|
return serverLevel.getChunk(chunkX, chunkZ);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
LevelChunk nmsChunk = serverLevel.getChunkSource().getChunkAtIfCachedImmediately(chunkX, chunkZ);
|
||||||
|
if (nmsChunk != null) {
|
||||||
|
return nmsChunk;
|
||||||
|
}
|
||||||
|
nmsChunk = serverLevel.getChunkSource().getChunkAtIfLoadedImmediately(chunkX, chunkZ);
|
||||||
|
if (nmsChunk != null) {
|
||||||
|
return nmsChunk;
|
||||||
|
}
|
||||||
|
// Avoid "async" methods from the main thread.
|
||||||
|
if (Fawe.isMainThread()) {
|
||||||
|
return serverLevel.getChunk(chunkX, chunkZ);
|
||||||
|
}
|
||||||
|
CompletableFuture<org.bukkit.Chunk> future = serverLevel.getWorld().getChunkAtAsync(chunkX, chunkZ, true, true);
|
||||||
|
try {
|
||||||
|
CraftChunk chunk = (CraftChunk) future.get();
|
||||||
|
return chunk.getHandle();
|
||||||
|
} catch (Throwable e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return TaskManager.IMP.sync(() -> serverLevel.getChunk(chunkX, chunkZ));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ChunkHolder getPlayerChunk(ServerLevel nmsWorld, final int chunkX, final int chunkZ) {
|
||||||
|
ChunkMap chunkMap = nmsWorld.getChunkSource().chunkMap;
|
||||||
|
try {
|
||||||
|
return (ChunkHolder) methodGetVisibleChunk.invoke(chunkMap, ChunkPos.asLong(chunkX, chunkZ));
|
||||||
|
} catch (Throwable thr) {
|
||||||
|
throw new RuntimeException(thr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void sendChunk(ServerLevel nmsWorld, int chunkX, int chunkZ, boolean lighting) {
|
||||||
|
ChunkHolder chunkHolder = getPlayerChunk(nmsWorld, chunkX, chunkZ);
|
||||||
|
if (chunkHolder == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ChunkPos coordIntPair = new ChunkPos(chunkX, chunkZ);
|
||||||
|
// UNLOADED_CHUNK
|
||||||
|
Optional<LevelChunk> optional = ((Either) chunkHolder
|
||||||
|
.getTickingChunkFuture()
|
||||||
|
.getNow(ChunkHolder.UNLOADED_LEVEL_CHUNK)).left();
|
||||||
|
if (PaperLib.isPaper()) {
|
||||||
|
// getChunkAtIfLoadedImmediately is paper only
|
||||||
|
optional = optional.or(() -> Optional.ofNullable(nmsWorld
|
||||||
|
.getChunkSource()
|
||||||
|
.getChunkAtIfLoadedImmediately(chunkX, chunkZ)));
|
||||||
|
}
|
||||||
|
if (optional.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
LevelChunk levelChunk = optional.get();
|
||||||
|
TaskManager.IMP.task(() -> {
|
||||||
|
ClientboundLevelChunkPacketData chunkPacket = new ClientboundLevelChunkPacketData(levelChunk);
|
||||||
|
nearbyPlayers(nmsWorld, coordIntPair).forEach(p -> p.connection.send(chunkPacket));
|
||||||
|
if (lighting) {
|
||||||
|
//This needs to be true otherwise Minecraft will update lighting from/at the chunk edges (bad)
|
||||||
|
boolean trustEdges = true;
|
||||||
|
ClientboundLightUpdatePacket packet =
|
||||||
|
new ClientboundLightUpdatePacket(coordIntPair, nmsWorld.getChunkSource().getLightEngine(), null, null,
|
||||||
|
trustEdges
|
||||||
|
);
|
||||||
|
nearbyPlayers(nmsWorld, coordIntPair).forEach(p -> p.connection.send(packet));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Stream<ServerPlayer> nearbyPlayers(ServerLevel serverLevel, ChunkPos coordIntPair) {
|
||||||
|
return serverLevel.getChunkSource().chunkMap.getPlayers(coordIntPair, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
NMS conversion
|
||||||
|
*/
|
||||||
|
public static LevelChunkSection newChunkSection(
|
||||||
|
final int layer, final char[] blocks, boolean fastmode,
|
||||||
|
CachedBukkitAdapter adapter
|
||||||
|
) {
|
||||||
|
return newChunkSection(layer, null, blocks, fastmode, adapter);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static LevelChunkSection newChunkSection(
|
||||||
|
final int layer, final Function<Integer, char[]> get, char[] set,
|
||||||
|
boolean fastmode, CachedBukkitAdapter adapter
|
||||||
|
) {
|
||||||
|
if (set == null) {
|
||||||
|
return newChunkSection(layer);
|
||||||
|
}
|
||||||
|
final int[] blockToPalette = FaweCache.IMP.BLOCK_TO_PALETTE.get();
|
||||||
|
final int[] paletteToBlock = FaweCache.IMP.PALETTE_TO_BLOCK.get();
|
||||||
|
final long[] blockStates = FaweCache.IMP.BLOCK_STATES.get();
|
||||||
|
final int[] blocksCopy = FaweCache.IMP.SECTION_BLOCKS.get();
|
||||||
|
try {
|
||||||
|
int[] num_palette_buffer = new int[1];
|
||||||
|
Map<BlockVector3, Integer> ticking_blocks = new HashMap<>();
|
||||||
|
int air;
|
||||||
|
if (get == null) {
|
||||||
|
air = createPalette(blockToPalette, paletteToBlock, blocksCopy, num_palette_buffer,
|
||||||
|
set, ticking_blocks, fastmode, adapter
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
air = createPalette(layer, blockToPalette, paletteToBlock, blocksCopy,
|
||||||
|
num_palette_buffer, get, set, ticking_blocks, fastmode, adapter
|
||||||
|
);
|
||||||
|
}
|
||||||
|
int num_palette = num_palette_buffer[0];
|
||||||
|
// BlockStates
|
||||||
|
int bitsPerEntry = MathMan.log2nlz(num_palette - 1);
|
||||||
|
if (Settings.IMP.PROTOCOL_SUPPORT_FIX || num_palette != 1) {
|
||||||
|
bitsPerEntry = Math.max(bitsPerEntry, 4); // Protocol support breaks <4 bits per entry
|
||||||
|
} else {
|
||||||
|
bitsPerEntry = Math.max(bitsPerEntry, 1); // For some reason minecraft needs 4096 bits to store 0 entries
|
||||||
|
}
|
||||||
|
if (bitsPerEntry > 8) {
|
||||||
|
bitsPerEntry = MathMan.log2nlz(Block.BLOCK_STATE_REGISTRY.size() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
final int blocksPerLong = MathMan.floorZero((double) 64 / bitsPerEntry);
|
||||||
|
final int blockBitArrayEnd = MathMan.ceilZero((float) 4096 / blocksPerLong);
|
||||||
|
|
||||||
|
if (num_palette == 1) {
|
||||||
|
for (int i = 0; i < blockBitArrayEnd; i++) {
|
||||||
|
blockStates[i] = 0;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
final BitArrayUnstretched bitArray = new BitArrayUnstretched(bitsPerEntry, 4096, blockStates);
|
||||||
|
bitArray.fromRaw(blocksCopy);
|
||||||
|
}
|
||||||
|
|
||||||
|
LevelChunkSection levelChunkSection = newChunkSection(layer);
|
||||||
|
// set palette & data bits
|
||||||
|
final PalettedContainer<net.minecraft.world.level.block.state.BlockState> dataPaletteBlocks =
|
||||||
|
levelChunkSection.getStates();
|
||||||
|
// private DataPalette<T> h;
|
||||||
|
// protected DataBits a;
|
||||||
|
final long[] bits = Arrays.copyOfRange(blockStates, 0, blockBitArrayEnd);
|
||||||
|
final BitStorage nmsBits = new BitStorage(bitsPerEntry, 4096, bits);
|
||||||
|
final Palette<net.minecraft.world.level.block.state.BlockState> blockStatePalettedContainer;
|
||||||
|
if (bitsPerEntry <= 4) {
|
||||||
|
blockStatePalettedContainer = new LinearPalette<>(Block.BLOCK_STATE_REGISTRY, bitsPerEntry, dataPaletteBlocks,
|
||||||
|
NbtUtils::readBlockState
|
||||||
|
);
|
||||||
|
} else if (bitsPerEntry < 9) {
|
||||||
|
blockStatePalettedContainer = new HashMapPalette<>(
|
||||||
|
Block.BLOCK_STATE_REGISTRY,
|
||||||
|
bitsPerEntry,
|
||||||
|
dataPaletteBlocks,
|
||||||
|
NbtUtils::readBlockState,
|
||||||
|
NbtUtils::writeBlockState
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
blockStatePalettedContainer = LevelChunkSection.GLOBAL_BLOCKSTATE_PALETTE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// set palette if required
|
||||||
|
if (bitsPerEntry < 9) {
|
||||||
|
for (int i = 0; i < num_palette; i++) {
|
||||||
|
final int ordinal = paletteToBlock[i];
|
||||||
|
blockToPalette[ordinal] = Integer.MAX_VALUE;
|
||||||
|
final BlockState state = BlockTypesCache.states[ordinal];
|
||||||
|
final net.minecraft.world.level.block.state.BlockState blockState = ((PaperweightBlockMaterial) state.getMaterial()).getState();
|
||||||
|
blockStatePalettedContainer.idFor(blockState);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
fieldStorage.set(dataPaletteBlocks, nmsBits);
|
||||||
|
fieldPalette.set(dataPaletteBlocks, blockStatePalettedContainer);
|
||||||
|
fieldBits.set(dataPaletteBlocks, bitsPerEntry);
|
||||||
|
setCount(ticking_blocks.size(), 4096 - air, levelChunkSection);
|
||||||
|
if (!fastmode) {
|
||||||
|
ticking_blocks.forEach((pos, ordinal) -> levelChunkSection
|
||||||
|
.setBlockState(pos.getBlockX(), pos.getBlockY(), pos.getBlockZ(),
|
||||||
|
Block.stateById(ordinal)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
} catch (final IllegalAccessException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return levelChunkSection;
|
||||||
|
} catch (final Throwable e) {
|
||||||
|
Arrays.fill(blockToPalette, Integer.MAX_VALUE);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static LevelChunkSection newChunkSection(int layer) {
|
||||||
|
return new LevelChunkSection(layer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setCount(final int tickingBlockCount, final int nonEmptyBlockCount, final LevelChunkSection section) throws
|
||||||
|
IllegalAccessException {
|
||||||
|
fieldTickingFluidContent.setShort(section, (short) 0); // TODO FIXME
|
||||||
|
fieldTickingBlockCount.setShort(section, (short) tickingBlockCount);
|
||||||
|
fieldNonEmptyBlockCount.setShort(section, (short) nonEmptyBlockCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Biome[] getBiomeArray(ChunkBiomeContainer chunkBiomeContainer) {
|
||||||
|
try {
|
||||||
|
return (Biome[]) fieldBiomes.get(chunkBiomeContainer);
|
||||||
|
} catch (IllegalAccessException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BiomeType adapt(Biome biome, LevelAccessor levelAccessor) {
|
||||||
|
ResourceLocation resourceLocation = levelAccessor.registryAccess().ownedRegistryOrThrow(Registry.BIOME_REGISTRY).getKey(
|
||||||
|
biome);
|
||||||
|
if (resourceLocation == null) {
|
||||||
|
return levelAccessor.registryAccess().ownedRegistryOrThrow(Registry.BIOME_REGISTRY).getId(biome) == -1
|
||||||
|
? BiomeTypes.OCEAN
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
return BiomeTypes.get(resourceLocation.toString().toLowerCase(Locale.ROOT));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void removeBeacon(BlockEntity beacon, LevelChunk levelChunk) {
|
||||||
|
try {
|
||||||
|
// Do the method ourselves to avoid trying to reflect generic method parameters
|
||||||
|
if (levelChunk.loaded || levelChunk.level.isClientSide()) {
|
||||||
|
BlockEntity blockEntity = levelChunk.blockEntities.remove(beacon.getBlockPos());
|
||||||
|
if (blockEntity != null) {
|
||||||
|
if (!levelChunk.level.isClientSide) {
|
||||||
|
Block block = beacon.getBlockState().getBlock();
|
||||||
|
if (block instanceof EntityBlock) {
|
||||||
|
GameEventListener gameEventListener = ((EntityBlock) block).getListener(levelChunk.level, beacon);
|
||||||
|
if (gameEventListener != null) {
|
||||||
|
int i = SectionPos.blockToSectionCoord(beacon.getBlockPos().getY());
|
||||||
|
GameEventDispatcher gameEventDispatcher = levelChunk.getEventDispatcher(i);
|
||||||
|
gameEventDispatcher.unregister(gameEventListener);
|
||||||
|
if (gameEventDispatcher.isEmpty()) {
|
||||||
|
try {
|
||||||
|
((Int2ObjectMap<GameEventDispatcher>) fieldGameEventDispatcherSections.get(levelChunk))
|
||||||
|
.remove(i);
|
||||||
|
} catch (IllegalAccessException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fieldRemove.set(beacon, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
methodremoveTickingBlockEntity.invoke(levelChunk, beacon.getBlockPos());
|
||||||
|
} catch (Throwable throwable) {
|
||||||
|
throwable.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,239 @@
|
|||||||
|
package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_18_R1;
|
||||||
|
|
||||||
|
import com.fastasyncworldedit.core.configuration.Settings;
|
||||||
|
import com.fastasyncworldedit.core.extent.processor.lighting.NMSRelighter;
|
||||||
|
import com.fastasyncworldedit.core.extent.processor.lighting.Relighter;
|
||||||
|
import com.fastasyncworldedit.core.queue.IQueueChunk;
|
||||||
|
import com.fastasyncworldedit.core.queue.IQueueExtent;
|
||||||
|
import com.fastasyncworldedit.core.util.MathMan;
|
||||||
|
import com.fastasyncworldedit.core.util.TaskManager;
|
||||||
|
import com.sk89q.worldedit.internal.util.LogManagerCompat;
|
||||||
|
import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap;
|
||||||
|
import it.unimi.dsi.fastutil.longs.LongArraySet;
|
||||||
|
import it.unimi.dsi.fastutil.longs.LongIterator;
|
||||||
|
import it.unimi.dsi.fastutil.longs.LongSet;
|
||||||
|
import net.minecraft.server.MCUtil;
|
||||||
|
import net.minecraft.server.level.ServerLevel;
|
||||||
|
import net.minecraft.server.level.ThreadedLevelLightEngine;
|
||||||
|
import net.minecraft.server.level.TicketType;
|
||||||
|
import net.minecraft.util.Unit;
|
||||||
|
import net.minecraft.world.level.ChunkPos;
|
||||||
|
import net.minecraft.world.level.chunk.ChunkStatus;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
|
import java.lang.invoke.MethodHandle;
|
||||||
|
import java.lang.invoke.MethodHandles;
|
||||||
|
import java.lang.invoke.MethodType;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.function.IntConsumer;
|
||||||
|
|
||||||
|
public class PaperweightStarlightRelighter implements Relighter {
|
||||||
|
|
||||||
|
public static final MethodHandle RELIGHT;
|
||||||
|
private static final Logger LOGGER = LogManagerCompat.getLogger();
|
||||||
|
private static final int CHUNKS_PER_BATCH = 1024; // 32 * 32
|
||||||
|
private static final int CHUNKS_PER_BATCH_SQRT_LOG2 = 5; // for shifting
|
||||||
|
|
||||||
|
private static final TicketType<Unit> FAWE_TICKET = TicketType.create("fawe_ticket", (a, b) -> 0);
|
||||||
|
private static final int LIGHT_LEVEL = MCUtil.getTicketLevelFor(ChunkStatus.LIGHT);
|
||||||
|
|
||||||
|
static {
|
||||||
|
MethodHandle tmp = null;
|
||||||
|
try {
|
||||||
|
MethodHandles.Lookup lookup = MethodHandles.lookup();
|
||||||
|
tmp = lookup.findVirtual(
|
||||||
|
ThreadedLevelLightEngine.class,
|
||||||
|
"relight",
|
||||||
|
MethodType.methodType(
|
||||||
|
int.class, // return type
|
||||||
|
// params
|
||||||
|
Set.class,
|
||||||
|
Consumer.class,
|
||||||
|
IntConsumer.class
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} catch (NoSuchMethodException | IllegalAccessException e) {
|
||||||
|
LOGGER.error("Failed to locate 'relight' method in ThreadedLevelLightEngine. Is everything up to date?", e);
|
||||||
|
}
|
||||||
|
RELIGHT = tmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
private final ServerLevel serverLevel;
|
||||||
|
private final ReentrantLock lock = new ReentrantLock();
|
||||||
|
private final Long2ObjectLinkedOpenHashMap<LongSet> regions = new Long2ObjectLinkedOpenHashMap<>();
|
||||||
|
private final ReentrantLock areaLock = new ReentrantLock();
|
||||||
|
private final NMSRelighter delegate;
|
||||||
|
|
||||||
|
//TODO 1.18 address raw access of IQueueChunk
|
||||||
|
public PaperweightStarlightRelighter(ServerLevel serverLevel, IQueueExtent<IQueueChunk> queue) {
|
||||||
|
this.serverLevel = serverLevel;
|
||||||
|
this.delegate = new NMSRelighter(queue);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isUsable() {
|
||||||
|
return RELIGHT != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean addChunk(int cx, int cz, byte[] skipReason, int bitmask) {
|
||||||
|
areaLock.lock();
|
||||||
|
try {
|
||||||
|
long key = MathMan.pairInt(cx >> CHUNKS_PER_BATCH_SQRT_LOG2, cz >> CHUNKS_PER_BATCH_SQRT_LOG2);
|
||||||
|
// TODO probably submit here already if chunks.size == CHUNKS_PER_BATCH?
|
||||||
|
LongSet chunks = this.regions.computeIfAbsent(key, k -> new LongArraySet(CHUNKS_PER_BATCH >> 2));
|
||||||
|
chunks.add(ChunkPos.asLong(cx, cz));
|
||||||
|
} finally {
|
||||||
|
areaLock.unlock();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addLightUpdate(int x, int y, int z) {
|
||||||
|
delegate.addLightUpdate(x, y, z);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This method is called "recursively", iterating and removing elements
|
||||||
|
* from the regions linked map. This way, chunks are loaded in batches to avoid
|
||||||
|
* OOMEs.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void fixLightingSafe(boolean sky) {
|
||||||
|
this.areaLock.lock();
|
||||||
|
try {
|
||||||
|
if (regions.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
LongSet first = regions.removeFirst();
|
||||||
|
fixLighting(first, () -> fixLightingSafe(true));
|
||||||
|
} finally {
|
||||||
|
this.areaLock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Processes a set of chunks and runs an action afterwards.
|
||||||
|
* The action is run async, the chunks are partly processed on the main thread
|
||||||
|
* (as required by the server).
|
||||||
|
*/
|
||||||
|
private void fixLighting(LongSet chunks, Runnable andThen) {
|
||||||
|
// convert from long keys to ChunkPos
|
||||||
|
Set<ChunkPos> coords = new HashSet<>();
|
||||||
|
LongIterator iterator = chunks.iterator();
|
||||||
|
while (iterator.hasNext()) {
|
||||||
|
coords.add(new ChunkPos(iterator.nextLong()));
|
||||||
|
}
|
||||||
|
TaskManager.IMP.task(() -> {
|
||||||
|
// trigger chunk load and apply ticket on main thread
|
||||||
|
List<CompletableFuture<?>> futures = new ArrayList<>();
|
||||||
|
for (ChunkPos pos : coords) {
|
||||||
|
futures.add(serverLevel.getWorld().getChunkAtAsync(pos.x, pos.z)
|
||||||
|
.thenAccept(c -> serverLevel.getChunkSource().addTicketAtLevel(
|
||||||
|
FAWE_TICKET,
|
||||||
|
pos,
|
||||||
|
LIGHT_LEVEL,
|
||||||
|
Unit.INSTANCE
|
||||||
|
))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// collect futures and trigger relight once all chunks are loaded
|
||||||
|
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).thenAccept(v ->
|
||||||
|
invokeRelight(
|
||||||
|
coords,
|
||||||
|
c -> {
|
||||||
|
}, // no callback for single chunks required
|
||||||
|
i -> {
|
||||||
|
if (i != coords.size()) {
|
||||||
|
LOGGER.warn("Processed {} chunks instead of {}", i, coords.size());
|
||||||
|
}
|
||||||
|
// post process chunks on main thread
|
||||||
|
TaskManager.IMP.task(() -> postProcessChunks(coords));
|
||||||
|
// call callback on our own threads
|
||||||
|
TaskManager.IMP.async(andThen);
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void invokeRelight(
|
||||||
|
Set<ChunkPos> coords,
|
||||||
|
Consumer<ChunkPos> chunkCallback,
|
||||||
|
IntConsumer processCallback
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
int unused = (int) RELIGHT.invokeExact(
|
||||||
|
serverLevel.getChunkSource().getLightEngine(),
|
||||||
|
coords,
|
||||||
|
chunkCallback, // callback per chunk
|
||||||
|
processCallback // callback for all chunks
|
||||||
|
);
|
||||||
|
} catch (Throwable throwable) {
|
||||||
|
LOGGER.error("Error occurred on relighting", throwable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Allow the server to unload the chunks again.
|
||||||
|
* Also, if chunk packets are sent delayed, we need to do that here
|
||||||
|
*/
|
||||||
|
private void postProcessChunks(Set<ChunkPos> coords) {
|
||||||
|
boolean delay = Settings.IMP.LIGHTING.DELAY_PACKET_SENDING;
|
||||||
|
for (ChunkPos pos : coords) {
|
||||||
|
int x = pos.x;
|
||||||
|
int z = pos.z;
|
||||||
|
if (delay) { // we still need to send the block changes of that chunk
|
||||||
|
PaperweightPlatformAdapter.sendChunk(serverLevel, x, z, false);
|
||||||
|
}
|
||||||
|
serverLevel.getChunkSource().removeTicketAtLevel(FAWE_TICKET, pos, LIGHT_LEVEL, Unit.INSTANCE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clear() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeLighting() {
|
||||||
|
this.delegate.removeLighting();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void fixBlockLighting() {
|
||||||
|
fixLightingSafe(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void fixSkyLighting() {
|
||||||
|
fixLightingSafe(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEmpty() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ReentrantLock getLock() {
|
||||||
|
return this.lock;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isFinished() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws Exception {
|
||||||
|
fixLightingSafe(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,28 @@
|
|||||||
|
package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_18_R1;
|
||||||
|
|
||||||
|
import com.fastasyncworldedit.core.extent.processor.lighting.NullRelighter;
|
||||||
|
import com.fastasyncworldedit.core.extent.processor.lighting.RelightMode;
|
||||||
|
import com.fastasyncworldedit.core.extent.processor.lighting.Relighter;
|
||||||
|
import com.fastasyncworldedit.core.extent.processor.lighting.RelighterFactory;
|
||||||
|
import com.fastasyncworldedit.core.queue.IQueueChunk;
|
||||||
|
import com.fastasyncworldedit.core.queue.IQueueExtent;
|
||||||
|
import com.sk89q.worldedit.world.World;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.craftbukkit.v1_18_R1.CraftWorld;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
|
public class PaperweightStarlightRelighterFactory implements RelighterFactory {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nonnull
|
||||||
|
//TODO 1.18 address raw access of IQueueChunk
|
||||||
|
Relighter createRelighter(RelightMode relightMode, World world, IQueueExtent<IQueueChunk> queue) {
|
||||||
|
org.bukkit.World w = Bukkit.getWorld(world.getName());
|
||||||
|
if (w == null) {
|
||||||
|
return NullRelighter.INSTANCE;
|
||||||
|
}
|
||||||
|
return new PaperweightStarlightRelighter(((CraftWorld) w).getHandle(), queue);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,159 @@
|
|||||||
|
package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_18_R1.nbt;
|
||||||
|
|
||||||
|
import com.sk89q.jnbt.CompoundTag;
|
||||||
|
import com.sk89q.jnbt.LazyCompoundTag;
|
||||||
|
import com.sk89q.jnbt.ListTag;
|
||||||
|
import com.sk89q.jnbt.StringTag;
|
||||||
|
import com.sk89q.jnbt.Tag;
|
||||||
|
import com.sk89q.worldedit.bukkit.WorldEditPlugin;
|
||||||
|
import com.sk89q.worldedit.util.nbt.CompoundBinaryTag;
|
||||||
|
import net.minecraft.nbt.NumericTag;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
public class PaperweightLazyCompoundTag extends LazyCompoundTag {
|
||||||
|
|
||||||
|
private final Supplier<net.minecraft.nbt.CompoundTag> compoundTagSupplier;
|
||||||
|
private CompoundTag compoundTag;
|
||||||
|
|
||||||
|
public PaperweightLazyCompoundTag(Supplier<net.minecraft.nbt.CompoundTag> compoundTagSupplier) {
|
||||||
|
super(new HashMap<>());
|
||||||
|
this.compoundTagSupplier = compoundTagSupplier;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PaperweightLazyCompoundTag(net.minecraft.nbt.CompoundTag compoundTag) {
|
||||||
|
this(() -> compoundTag);
|
||||||
|
}
|
||||||
|
|
||||||
|
public net.minecraft.nbt.CompoundTag get() {
|
||||||
|
return compoundTagSupplier.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, Tag> getValue() {
|
||||||
|
if (compoundTag == null) {
|
||||||
|
compoundTag = (CompoundTag) WorldEditPlugin.getInstance().getBukkitImplAdapter().toNative(compoundTagSupplier.get());
|
||||||
|
}
|
||||||
|
return compoundTag.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CompoundBinaryTag asBinaryTag() {
|
||||||
|
getValue();
|
||||||
|
return compoundTag.asBinaryTag();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean containsKey(String key) {
|
||||||
|
return compoundTagSupplier.get().contains(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getByteArray(String key) {
|
||||||
|
return compoundTagSupplier.get().getByteArray(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte getByte(String key) {
|
||||||
|
return compoundTagSupplier.get().getByte(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getDouble(String key) {
|
||||||
|
return compoundTagSupplier.get().getDouble(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public double asDouble(String key) {
|
||||||
|
net.minecraft.nbt.Tag tag = compoundTagSupplier.get().get(key);
|
||||||
|
if (tag instanceof NumericTag) {
|
||||||
|
return ((NumericTag) tag).getAsDouble();
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getFloat(String key) {
|
||||||
|
return compoundTagSupplier.get().getFloat(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int[] getIntArray(String key) {
|
||||||
|
return compoundTagSupplier.get().getIntArray(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getInt(String key) {
|
||||||
|
return compoundTagSupplier.get().getInt(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int asInt(String key) {
|
||||||
|
net.minecraft.nbt.Tag tag = compoundTagSupplier.get().get(key);
|
||||||
|
if (tag instanceof NumericTag) {
|
||||||
|
return ((NumericTag) tag).getAsInt();
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Tag> getList(String key) {
|
||||||
|
net.minecraft.nbt.Tag tag = compoundTagSupplier.get().get(key);
|
||||||
|
if (tag instanceof net.minecraft.nbt.ListTag nbtList) {
|
||||||
|
ArrayList<Tag> list = new ArrayList<>();
|
||||||
|
for (net.minecraft.nbt.Tag elem : nbtList) {
|
||||||
|
if (elem instanceof net.minecraft.nbt.CompoundTag) {
|
||||||
|
list.add(new PaperweightLazyCompoundTag((net.minecraft.nbt.CompoundTag) elem));
|
||||||
|
} else {
|
||||||
|
list.add(WorldEditPlugin.getInstance().getBukkitImplAdapter().toNative(elem));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO 1.18 be a ListBinaryTag ?
|
||||||
|
public ListTag getListTag(String key) {
|
||||||
|
net.minecraft.nbt.Tag tag = compoundTagSupplier.get().get(key);
|
||||||
|
if (tag instanceof net.minecraft.nbt.ListTag) {
|
||||||
|
return (ListTag) WorldEditPlugin.getInstance().getBukkitImplAdapter().toNative(tag);
|
||||||
|
}
|
||||||
|
return new ListTag(StringTag.class, Collections.emptyList());
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public <T extends Tag> List<T> getList(String key, Class<T> listType) {
|
||||||
|
ListTag listTag = getListTag(key);
|
||||||
|
if (listTag.getType().equals(listType)) {
|
||||||
|
return (List<T>) listTag.getValue();
|
||||||
|
} else {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public long[] getLongArray(String key) {
|
||||||
|
return compoundTagSupplier.get().getLongArray(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getLong(String key) {
|
||||||
|
return compoundTagSupplier.get().getLong(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public long asLong(String key) {
|
||||||
|
net.minecraft.nbt.Tag tag = compoundTagSupplier.get().get(key);
|
||||||
|
if (tag instanceof NumericTag) {
|
||||||
|
return ((NumericTag) tag).getAsLong();
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public short getShort(String key) {
|
||||||
|
return compoundTagSupplier.get().getShort(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getString(String key) {
|
||||||
|
return compoundTagSupplier.get().getString(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return compoundTagSupplier.get().toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,721 @@
|
|||||||
|
package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_18_R1.regen;
|
||||||
|
|
||||||
|
import com.fastasyncworldedit.bukkit.adapter.Regenerator;
|
||||||
|
import com.fastasyncworldedit.core.Fawe;
|
||||||
|
import com.fastasyncworldedit.core.queue.IChunkCache;
|
||||||
|
import com.fastasyncworldedit.core.queue.IChunkGet;
|
||||||
|
import com.fastasyncworldedit.core.util.ReflectionUtils;
|
||||||
|
import com.fastasyncworldedit.core.util.TaskManager;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.mojang.datafixers.util.Either;
|
||||||
|
import com.mojang.serialization.Codec;
|
||||||
|
import com.mojang.serialization.Dynamic;
|
||||||
|
import com.mojang.serialization.Lifecycle;
|
||||||
|
import com.sk89q.worldedit.bukkit.adapter.Refraction;
|
||||||
|
import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_18_R1.PaperweightGetBlocks;
|
||||||
|
import com.sk89q.worldedit.extent.Extent;
|
||||||
|
import com.sk89q.worldedit.internal.util.LogManagerCompat;
|
||||||
|
import com.sk89q.worldedit.regions.Region;
|
||||||
|
import com.sk89q.worldedit.util.io.file.SafeFiles;
|
||||||
|
import com.sk89q.worldedit.world.RegenOptions;
|
||||||
|
import io.papermc.lib.PaperLib;
|
||||||
|
import net.minecraft.core.MappedRegistry;
|
||||||
|
import net.minecraft.core.Registry;
|
||||||
|
import net.minecraft.core.RegistryAccess;
|
||||||
|
import net.minecraft.data.BuiltinRegistries;
|
||||||
|
import net.minecraft.data.worldgen.biome.Biomes;
|
||||||
|
import net.minecraft.nbt.CompoundTag;
|
||||||
|
import net.minecraft.nbt.NbtOps;
|
||||||
|
import net.minecraft.resources.RegistryReadOps;
|
||||||
|
import net.minecraft.resources.ResourceKey;
|
||||||
|
import net.minecraft.resources.ResourceLocation;
|
||||||
|
import net.minecraft.server.MinecraftServer;
|
||||||
|
import net.minecraft.server.level.ServerChunkCache;
|
||||||
|
import net.minecraft.server.level.ServerLevel;
|
||||||
|
import net.minecraft.server.level.ThreadedLevelLightEngine;
|
||||||
|
import net.minecraft.server.level.progress.ChunkProgressListener;
|
||||||
|
import net.minecraft.util.LinearCongruentialGenerator;
|
||||||
|
import net.minecraft.world.level.ChunkPos;
|
||||||
|
import net.minecraft.world.level.Level;
|
||||||
|
import net.minecraft.world.level.LevelHeightAccessor;
|
||||||
|
import net.minecraft.world.level.LevelSettings;
|
||||||
|
import net.minecraft.world.level.biome.Biome;
|
||||||
|
import net.minecraft.world.level.biome.BiomeSource;
|
||||||
|
import net.minecraft.world.level.biome.OverworldBiomeSource;
|
||||||
|
import net.minecraft.world.level.chunk.ChunkAccess;
|
||||||
|
import net.minecraft.world.level.chunk.ChunkGenerator;
|
||||||
|
import net.minecraft.world.level.chunk.ChunkStatus;
|
||||||
|
import net.minecraft.world.level.chunk.LevelChunk;
|
||||||
|
import net.minecraft.world.level.chunk.ProtoChunk;
|
||||||
|
import net.minecraft.world.level.chunk.UpgradeData;
|
||||||
|
import net.minecraft.world.level.dimension.LevelStem;
|
||||||
|
import net.minecraft.world.level.levelgen.FlatLevelSource;
|
||||||
|
import net.minecraft.world.level.levelgen.NoiseBasedChunkGenerator;
|
||||||
|
import net.minecraft.world.level.levelgen.NoiseGeneratorSettings;
|
||||||
|
import net.minecraft.world.level.levelgen.SimpleRandomSource;
|
||||||
|
import net.minecraft.world.level.levelgen.WorldGenSettings;
|
||||||
|
import net.minecraft.world.level.levelgen.XoroshiroRandomSource;
|
||||||
|
import net.minecraft.world.level.levelgen.flat.FlatLevelGeneratorSettings;
|
||||||
|
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureManager;
|
||||||
|
import net.minecraft.world.level.levelgen.synth.ImprovedNoise;
|
||||||
|
import net.minecraft.world.level.newbiome.area.Area;
|
||||||
|
import net.minecraft.world.level.newbiome.area.AreaFactory;
|
||||||
|
import net.minecraft.world.level.newbiome.context.BigContext;
|
||||||
|
import net.minecraft.world.level.newbiome.layer.Layer;
|
||||||
|
import net.minecraft.world.level.newbiome.layer.Layers;
|
||||||
|
import net.minecraft.world.level.newbiome.layer.traits.PixelTransformer;
|
||||||
|
import net.minecraft.world.level.storage.LevelStorageSource;
|
||||||
|
import net.minecraft.world.level.storage.PrimaryLevelData;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.craftbukkit.v1_18_R1.CraftServer;
|
||||||
|
import org.bukkit.craftbukkit.v1_18_R1.CraftWorld;
|
||||||
|
import org.bukkit.craftbukkit.v1_18_R1.generator.CustomChunkGenerator;
|
||||||
|
import org.bukkit.generator.BlockPopulator;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Random;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.function.BooleanSupplier;
|
||||||
|
import java.util.function.LongFunction;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
public class PaperweightRegen extends Regenerator<ChunkAccess, ProtoChunk, LevelChunk, PaperweightRegen.ChunkStatusWrap> {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = LogManagerCompat.getLogger();
|
||||||
|
|
||||||
|
private static final Field serverWorldsField;
|
||||||
|
private static final Field worldPaperConfigField;
|
||||||
|
private static final Field flatBedrockField;
|
||||||
|
private static final Field generatorSettingFlatField;
|
||||||
|
private static final Field generatorSettingBaseSupplierField;
|
||||||
|
private static final Field delegateField;
|
||||||
|
private static final Field chunkProviderField;
|
||||||
|
|
||||||
|
//list of chunk stati in correct order without FULL
|
||||||
|
private static final Map<ChunkStatus, Concurrency> chunkStati = new LinkedHashMap<>();
|
||||||
|
|
||||||
|
static {
|
||||||
|
chunkStati.put(ChunkStatus.EMPTY, Concurrency.FULL); // empty: radius -1, does nothing
|
||||||
|
chunkStati.put(ChunkStatus.STRUCTURE_STARTS, Concurrency.NONE); // structure starts: uses unsynchronized maps
|
||||||
|
chunkStati.put(
|
||||||
|
ChunkStatus.STRUCTURE_REFERENCES,
|
||||||
|
Concurrency.FULL
|
||||||
|
); // structure refs: radius 8, but only writes to current chunk
|
||||||
|
chunkStati.put(ChunkStatus.BIOMES, Concurrency.FULL); // biomes: radius 0
|
||||||
|
chunkStati.put(ChunkStatus.NOISE, Concurrency.RADIUS); // noise: radius 8
|
||||||
|
chunkStati.put(ChunkStatus.SURFACE, Concurrency.NONE); // surface: radius 0, requires NONE
|
||||||
|
chunkStati.put(ChunkStatus.CARVERS, Concurrency.NONE); // carvers: radius 0, but RADIUS and FULL change results
|
||||||
|
chunkStati.put(
|
||||||
|
ChunkStatus.LIQUID_CARVERS,
|
||||||
|
Concurrency.NONE
|
||||||
|
); // liquid carvers: radius 0, but RADIUS and FULL change results
|
||||||
|
chunkStati.put(ChunkStatus.FEATURES, Concurrency.NONE); // features: uses unsynchronized maps
|
||||||
|
chunkStati.put(
|
||||||
|
ChunkStatus.LIGHT,
|
||||||
|
Concurrency.FULL
|
||||||
|
); // light: radius 1, but no writes to other chunks, only current chunk
|
||||||
|
chunkStati.put(ChunkStatus.SPAWN, Concurrency.FULL); // spawn: radius 0
|
||||||
|
chunkStati.put(ChunkStatus.HEIGHTMAPS, Concurrency.FULL); // heightmaps: radius 0
|
||||||
|
|
||||||
|
try {
|
||||||
|
serverWorldsField = CraftServer.class.getDeclaredField("worlds");
|
||||||
|
serverWorldsField.setAccessible(true);
|
||||||
|
|
||||||
|
Field tmpPaperConfigField;
|
||||||
|
Field tmpFlatBedrockField;
|
||||||
|
try { //only present on paper
|
||||||
|
tmpPaperConfigField = Level.class.getDeclaredField("paperConfig");
|
||||||
|
tmpPaperConfigField.setAccessible(true);
|
||||||
|
|
||||||
|
tmpFlatBedrockField = tmpPaperConfigField.getType().getDeclaredField("generateFlatBedrock");
|
||||||
|
tmpFlatBedrockField.setAccessible(true);
|
||||||
|
} catch (Exception e) {
|
||||||
|
tmpPaperConfigField = null;
|
||||||
|
tmpFlatBedrockField = null;
|
||||||
|
}
|
||||||
|
worldPaperConfigField = tmpPaperConfigField;
|
||||||
|
flatBedrockField = tmpFlatBedrockField;
|
||||||
|
|
||||||
|
generatorSettingBaseSupplierField = NoiseBasedChunkGenerator.class.getDeclaredField(Refraction.pickName(
|
||||||
|
"settings", "g"));
|
||||||
|
generatorSettingBaseSupplierField.setAccessible(true);
|
||||||
|
|
||||||
|
generatorSettingFlatField = FlatLevelSource.class.getDeclaredField(Refraction.pickName("settings", "e"));
|
||||||
|
generatorSettingFlatField.setAccessible(true);
|
||||||
|
|
||||||
|
delegateField = CustomChunkGenerator.class.getDeclaredField("delegate");
|
||||||
|
delegateField.setAccessible(true);
|
||||||
|
|
||||||
|
chunkProviderField = ServerLevel.class.getDeclaredField(Refraction.pickName("chunkSource", "C"));
|
||||||
|
chunkProviderField.setAccessible(true);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//runtime
|
||||||
|
private ServerLevel originalServerWorld;
|
||||||
|
private ServerChunkCache originalChunkProvider;
|
||||||
|
private ServerLevel freshWorld;
|
||||||
|
private ServerChunkCache freshChunkProvider;
|
||||||
|
private LevelStorageSource.LevelStorageAccess session;
|
||||||
|
private StructureManager structureManager;
|
||||||
|
private ThreadedLevelLightEngine threadedLevelLightEngine;
|
||||||
|
private ChunkGenerator chunkGenerator;
|
||||||
|
|
||||||
|
private Path tempDir;
|
||||||
|
|
||||||
|
private boolean generateFlatBedrock = false;
|
||||||
|
|
||||||
|
public PaperweightRegen(org.bukkit.World originalBukkitWorld, Region region, Extent target, RegenOptions options) {
|
||||||
|
super(originalBukkitWorld, region, target, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean prepare() {
|
||||||
|
this.originalServerWorld = ((CraftWorld) originalBukkitWorld).getHandle();
|
||||||
|
originalChunkProvider = originalServerWorld.getChunkSource();
|
||||||
|
if (!(originalChunkProvider instanceof ServerChunkCache)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
//flat bedrock? (only on paper)
|
||||||
|
if (worldPaperConfigField != null) {
|
||||||
|
try {
|
||||||
|
generateFlatBedrock = flatBedrockField.getBoolean(worldPaperConfigField.get(originalServerWorld));
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
seed = options.getSeed().orElse(originalServerWorld.getSeed());
|
||||||
|
chunkStati.forEach((s, c) -> super.chunkStati.put(new ChunkStatusWrap(s), c));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean initNewWorld() throws Exception {
|
||||||
|
//world folder
|
||||||
|
tempDir = java.nio.file.Files.createTempDirectory("FastAsyncWorldEditWorldGen");
|
||||||
|
|
||||||
|
//prepare for world init (see upstream implementation for reference)
|
||||||
|
org.bukkit.World.Environment environment = originalBukkitWorld.getEnvironment();
|
||||||
|
org.bukkit.generator.ChunkGenerator generator = originalBukkitWorld.getGenerator();
|
||||||
|
LevelStorageSource levelStorageSource = LevelStorageSource.createDefault(tempDir);
|
||||||
|
ResourceKey<LevelStem> levelStemResourceKey = getWorldDimKey(environment);
|
||||||
|
session = levelStorageSource.createAccess("worldeditregentempworld", levelStemResourceKey);
|
||||||
|
PrimaryLevelData originalWorldData = originalServerWorld.serverLevelData;
|
||||||
|
|
||||||
|
MinecraftServer server = originalServerWorld.getCraftServer().getServer();
|
||||||
|
PrimaryLevelData levelProperties = (PrimaryLevelData) server.getWorldData();
|
||||||
|
RegistryReadOps<net.minecraft.nbt.Tag> nbtRegOps = RegistryReadOps.createAndLoad(
|
||||||
|
NbtOps.INSTANCE, server.resources.getResourceManager(),
|
||||||
|
RegistryAccess.builtin()
|
||||||
|
);
|
||||||
|
WorldGenSettings newOpts = WorldGenSettings.CODEC
|
||||||
|
.encodeStart(nbtRegOps, levelProperties.worldGenSettings())
|
||||||
|
.flatMap(tag -> WorldGenSettings.CODEC.parse(this.recursivelySetSeed(
|
||||||
|
new Dynamic<>(nbtRegOps, tag),
|
||||||
|
seed,
|
||||||
|
new HashSet<>()
|
||||||
|
)))
|
||||||
|
.result()
|
||||||
|
.orElseThrow(() -> new IllegalStateException("Unable to map GeneratorOptions"));
|
||||||
|
LevelSettings newWorldSettings = new LevelSettings(
|
||||||
|
"worldeditregentempworld",
|
||||||
|
originalWorldData.settings.gameType(),
|
||||||
|
originalWorldData.settings.hardcore(),
|
||||||
|
originalWorldData.settings.difficulty(),
|
||||||
|
originalWorldData.settings.allowCommands(),
|
||||||
|
originalWorldData.settings.gameRules(),
|
||||||
|
originalWorldData.settings.getDataPackConfig()
|
||||||
|
);
|
||||||
|
PrimaryLevelData newWorldData = new PrimaryLevelData(newWorldSettings, newOpts, Lifecycle.stable());
|
||||||
|
|
||||||
|
//init world
|
||||||
|
freshWorld = Fawe.get().getQueueHandler().sync((Supplier<ServerLevel>) () -> new ServerLevel(
|
||||||
|
server,
|
||||||
|
server.executor,
|
||||||
|
session,
|
||||||
|
newWorldData,
|
||||||
|
originalServerWorld.dimension(),
|
||||||
|
originalServerWorld.dimensionType(),
|
||||||
|
new RegenNoOpWorldLoadListener(),
|
||||||
|
// placeholder. Required for new ChunkProviderServer, but we create and then set it later
|
||||||
|
newOpts.dimensions().get(levelStemResourceKey).generator(),
|
||||||
|
originalServerWorld.isDebug(),
|
||||||
|
seed,
|
||||||
|
ImmutableList.of(),
|
||||||
|
false,
|
||||||
|
environment,
|
||||||
|
generator,
|
||||||
|
originalBukkitWorld.getBiomeProvider()
|
||||||
|
) {
|
||||||
|
private final Biome singleBiome = options.hasBiomeType() ? BuiltinRegistries.BIOME.get(ResourceLocation.tryParse(
|
||||||
|
options
|
||||||
|
.getBiomeType()
|
||||||
|
.getId())) : null;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void tick(BooleanSupplier shouldKeepTicking) { //no ticking
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Biome getUncachedNoiseBiome(int biomeX, int biomeY, int biomeZ) {
|
||||||
|
if (options.hasBiomeType()) {
|
||||||
|
return singleBiome;
|
||||||
|
}
|
||||||
|
return PaperweightRegen.this.chunkGenerator.getBiomeSource().getNoiseBiome(biomeX, biomeY, biomeZ);
|
||||||
|
}
|
||||||
|
}).get();
|
||||||
|
freshWorld.noSave = true;
|
||||||
|
removeWorldFromWorldsMap();
|
||||||
|
newWorldData.checkName(originalServerWorld.serverLevelData.getLevelName()); //rename to original world name
|
||||||
|
if (worldPaperConfigField != null) {
|
||||||
|
worldPaperConfigField.set(freshWorld, originalServerWorld.paperConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
//generator
|
||||||
|
if (originalChunkProvider.getGenerator() instanceof FlatLevelSource) {
|
||||||
|
FlatLevelGeneratorSettings generatorSettingFlat = (FlatLevelGeneratorSettings) generatorSettingFlatField.get(
|
||||||
|
originalChunkProvider.getGenerator());
|
||||||
|
chunkGenerator = new FlatLevelSource(generatorSettingFlat);
|
||||||
|
} else if (originalChunkProvider.getGenerator() instanceof NoiseBasedChunkGenerator) {
|
||||||
|
Supplier<NoiseGeneratorSettings> generatorSettingBaseSupplier = (Supplier<NoiseGeneratorSettings>) generatorSettingBaseSupplierField
|
||||||
|
.get(originalChunkProvider.getGenerator());
|
||||||
|
BiomeSource biomeSource = originalChunkProvider.getGenerator().getBiomeSource();
|
||||||
|
if (biomeSource instanceof OverworldBiomeSource) {
|
||||||
|
biomeSource = fastOverworldBiomeSource(biomeSource);
|
||||||
|
}
|
||||||
|
chunkGenerator = new NoiseBasedChunkGenerator(biomeSource, seed, generatorSettingBaseSupplier);
|
||||||
|
} else if (originalChunkProvider.getGenerator() instanceof CustomChunkGenerator) {
|
||||||
|
chunkGenerator = (ChunkGenerator) delegateField.get(originalChunkProvider.getGenerator());
|
||||||
|
} else {
|
||||||
|
LOGGER.error("Unsupported generator type {}", originalChunkProvider.getGenerator().getClass().getName());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (generator != null) {
|
||||||
|
chunkGenerator = new CustomChunkGenerator(freshWorld, chunkGenerator, generator);
|
||||||
|
generateConcurrent = generator.isParallelCapable();
|
||||||
|
}
|
||||||
|
|
||||||
|
freshChunkProvider = new ServerChunkCache(
|
||||||
|
freshWorld,
|
||||||
|
session,
|
||||||
|
server.getFixerUpper(),
|
||||||
|
server.getStructureManager(),
|
||||||
|
server.executor,
|
||||||
|
chunkGenerator,
|
||||||
|
freshWorld.spigotConfig.viewDistance,
|
||||||
|
server.forceSynchronousWrites(),
|
||||||
|
new RegenNoOpWorldLoadListener(),
|
||||||
|
(chunkCoordIntPair, state) -> {
|
||||||
|
},
|
||||||
|
() -> server.overworld().getDataStorage()
|
||||||
|
) {
|
||||||
|
// redirect to LevelChunks created in #createChunks
|
||||||
|
@Override
|
||||||
|
public ChunkAccess getChunk(int x, int z, ChunkStatus chunkstatus, boolean flag) {
|
||||||
|
return getChunkAt(x, z);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ReflectionUtils.unsafeSet(chunkProviderField, freshWorld, freshChunkProvider);
|
||||||
|
//let's start then
|
||||||
|
structureManager = server.getStructureManager();
|
||||||
|
threadedLevelLightEngine = freshChunkProvider.getLightEngine();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void cleanup() {
|
||||||
|
try {
|
||||||
|
session.close();
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
|
||||||
|
//shutdown chunk provider
|
||||||
|
try {
|
||||||
|
Fawe.get().getQueueHandler().sync(() -> {
|
||||||
|
try {
|
||||||
|
freshChunkProvider.close(false);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
|
||||||
|
//remove world from server
|
||||||
|
try {
|
||||||
|
Fawe.get().getQueueHandler().sync(this::removeWorldFromWorldsMap);
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
|
||||||
|
//delete directory
|
||||||
|
try {
|
||||||
|
SafeFiles.tryHardToDeleteDir(tempDir);
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected ProtoChunk createProtoChunk(int x, int z) {
|
||||||
|
return PaperLib.isPaper()
|
||||||
|
? new FastProtoChunk(new ChunkPos(x, z), UpgradeData.EMPTY, freshWorld, freshWorld) // paper
|
||||||
|
: new FastProtoChunk(new ChunkPos(x, z), UpgradeData.EMPTY, freshWorld); // spigot
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected LevelChunk createChunk(ProtoChunk protoChunk) {
|
||||||
|
return new LevelChunk(
|
||||||
|
freshWorld,
|
||||||
|
protoChunk,
|
||||||
|
null // we don't want to add entities
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected ChunkStatusWrap getFullChunkStatus() {
|
||||||
|
return new ChunkStatusWrap(ChunkStatus.FULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected List<BlockPopulator> getBlockPopulators() {
|
||||||
|
return originalServerWorld.getWorld().getPopulators();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void populate(LevelChunk levelChunk, Random random, BlockPopulator blockPopulator) {
|
||||||
|
// BlockPopulator#populate has to be called synchronously for TileEntity access
|
||||||
|
TaskManager.IMP.task(() -> blockPopulator.populate(freshWorld.getWorld(), random, levelChunk.getBukkitChunk()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected IChunkCache<IChunkGet> initSourceQueueCache() {
|
||||||
|
return (chunkX, chunkZ) -> new PaperweightGetBlocks(freshWorld, chunkX, chunkZ) {
|
||||||
|
@Override
|
||||||
|
public LevelChunk ensureLoaded(ServerLevel nmsWorld, int x, int z) {
|
||||||
|
return getChunkAt(x, z);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
//util
|
||||||
|
private void removeWorldFromWorldsMap() {
|
||||||
|
Fawe.get().getQueueHandler().sync(() -> {
|
||||||
|
try {
|
||||||
|
Map<String, org.bukkit.World> map = (Map<String, org.bukkit.World>) serverWorldsField.get(Bukkit.getServer());
|
||||||
|
map.remove("worldeditregentempworld");
|
||||||
|
} catch (IllegalAccessException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private ResourceKey<LevelStem> getWorldDimKey(org.bukkit.World.Environment env) {
|
||||||
|
switch (env) {
|
||||||
|
case NETHER:
|
||||||
|
return LevelStem.NETHER;
|
||||||
|
case THE_END:
|
||||||
|
return LevelStem.END;
|
||||||
|
case NORMAL:
|
||||||
|
default:
|
||||||
|
return LevelStem.OVERWORLD;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Dynamic<net.minecraft.nbt.Tag> recursivelySetSeed(
|
||||||
|
Dynamic<net.minecraft.nbt.Tag> dynamic,
|
||||||
|
long seed,
|
||||||
|
Set<Dynamic<net.minecraft.nbt.Tag>> dynamicSet
|
||||||
|
) {
|
||||||
|
return !dynamicSet.add(dynamic) ? dynamic : dynamic.updateMapValues((pair) -> {
|
||||||
|
if (pair.getFirst().asString("").equals("seed")) {
|
||||||
|
return pair.mapSecond((v) -> v.createLong(seed));
|
||||||
|
} else {
|
||||||
|
//TODO 1.18 address raw access
|
||||||
|
return ((Dynamic) pair.getSecond()).getValue() instanceof CompoundTag
|
||||||
|
? pair.mapSecond((v) -> this.recursivelySetSeed((Dynamic) v, seed, dynamicSet))
|
||||||
|
: pair;
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private BiomeSource fastOverworldBiomeSource(BiomeSource biomeSource) throws Exception {
|
||||||
|
Field legacyBiomeInitLayerField = OverworldBiomeSource.class.getDeclaredField(
|
||||||
|
Refraction.pickName("legacyBiomeInitLayer", "i"));
|
||||||
|
legacyBiomeInitLayerField.setAccessible(true);
|
||||||
|
Field largeBiomesField = OverworldBiomeSource.class.getDeclaredField(Refraction.pickName("largeBiomes", "j"));
|
||||||
|
largeBiomesField.setAccessible(true);
|
||||||
|
Field biomeRegistryField = OverworldBiomeSource.class.getDeclaredField(Refraction.pickName("biomes", "k"));
|
||||||
|
biomeRegistryField.setAccessible(true);
|
||||||
|
Field areaLazyField = Layer.class.getDeclaredField(Refraction.pickName("area", "b"));
|
||||||
|
areaLazyField.setAccessible(true);
|
||||||
|
Method initAreaFactoryMethod = Layers.class.getDeclaredMethod(
|
||||||
|
Refraction.pickName("getDefaultLayer", "a"),
|
||||||
|
boolean.class,
|
||||||
|
int.class,
|
||||||
|
int.class,
|
||||||
|
LongFunction.class
|
||||||
|
);
|
||||||
|
initAreaFactoryMethod.setAccessible(true);
|
||||||
|
|
||||||
|
//init new WorldChunkManagerOverworld
|
||||||
|
boolean legacyBiomeInitLayer = legacyBiomeInitLayerField.getBoolean(biomeSource);
|
||||||
|
boolean largebiomes = largeBiomesField.getBoolean(biomeSource);
|
||||||
|
Registry<Biome> biomeRegistryMojang = (Registry<Biome>) biomeRegistryField.get(biomeSource);
|
||||||
|
Registry<Biome> biomeRegistry;
|
||||||
|
if (options.hasBiomeType()) {
|
||||||
|
Biome biome = BuiltinRegistries.BIOME.get(ResourceLocation.tryParse(options.getBiomeType().getId()));
|
||||||
|
biomeRegistry = new MappedRegistry<>(
|
||||||
|
ResourceKey.createRegistryKey(new ResourceLocation("fawe_biomes")),
|
||||||
|
Lifecycle.experimental()
|
||||||
|
);
|
||||||
|
((MappedRegistry) biomeRegistry).registerMapping(0, BuiltinRegistries.BIOME.getResourceKey(biome).get(), biome,
|
||||||
|
Lifecycle.experimental()
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
biomeRegistry = biomeRegistryMojang;
|
||||||
|
}
|
||||||
|
|
||||||
|
//replace genLayer
|
||||||
|
AreaFactory<FastAreaLazy> factory = (AreaFactory<FastAreaLazy>) initAreaFactoryMethod.invoke(
|
||||||
|
null,
|
||||||
|
legacyBiomeInitLayer,
|
||||||
|
largebiomes ? 6 : 4,
|
||||||
|
4,
|
||||||
|
(LongFunction) (salt -> new FastWorldGenContextArea(seed, salt))
|
||||||
|
);
|
||||||
|
biomeSource = new FastOverworldBiomeSource(biomeRegistry, new FastGenLayer(factory));
|
||||||
|
|
||||||
|
return biomeSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class FastOverworldBiomeSource extends BiomeSource {
|
||||||
|
|
||||||
|
private final Registry<Biome> biomeRegistry;
|
||||||
|
private final boolean isSingleRegistry;
|
||||||
|
private final FastGenLayer fastGenLayer;
|
||||||
|
|
||||||
|
public FastOverworldBiomeSource(
|
||||||
|
Registry<Biome> biomeRegistry,
|
||||||
|
FastGenLayer genLayer
|
||||||
|
) {
|
||||||
|
super(biomeRegistry.stream().collect(Collectors.toList()));
|
||||||
|
this.biomeRegistry = biomeRegistry;
|
||||||
|
this.isSingleRegistry = biomeRegistry.entrySet().size() == 1;
|
||||||
|
this.fastGenLayer = genLayer;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Codec<? extends BiomeSource> codec() {
|
||||||
|
return OverworldBiomeSource.CODEC;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BiomeSource withSeed(final long seed) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Biome getNoiseBiome(int biomeX, int biomeY, int biomeZ) {
|
||||||
|
if (this.isSingleRegistry) {
|
||||||
|
return this.biomeRegistry.byId(0);
|
||||||
|
}
|
||||||
|
return this.fastGenLayer.get(this.biomeRegistry, biomeX, biomeZ);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class FastWorldGenContextArea implements BigContext<FastAreaLazy> {
|
||||||
|
|
||||||
|
private final ConcurrentHashMap<Long, Integer> sharedAreaMap = new ConcurrentHashMap<>();
|
||||||
|
private final ImprovedNoise improvedNoise;
|
||||||
|
private final long magicrandom;
|
||||||
|
private final ConcurrentHashMap<Long, Long> map = new ConcurrentHashMap<>(); //needed for multithreaded generation
|
||||||
|
|
||||||
|
public FastWorldGenContextArea(long seed, long lconst) {
|
||||||
|
this.magicrandom = mix(seed, lconst);
|
||||||
|
this.improvedNoise = new ImprovedNoise(new XoroshiroRandomSource(seed));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static long mix(long seed, long salt) {
|
||||||
|
long l = LinearCongruentialGenerator.next(salt, salt);
|
||||||
|
l = LinearCongruentialGenerator.next(l, salt);
|
||||||
|
l = LinearCongruentialGenerator.next(l, salt);
|
||||||
|
long m = LinearCongruentialGenerator.next(seed, l);
|
||||||
|
m = LinearCongruentialGenerator.next(m, l);
|
||||||
|
m = LinearCongruentialGenerator.next(m, l);
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public FastAreaLazy createResult(PixelTransformer pixelTransformer) {
|
||||||
|
return new FastAreaLazy(sharedAreaMap, pixelTransformer);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initRandom(long x, long z) {
|
||||||
|
long l = this.magicrandom;
|
||||||
|
l = LinearCongruentialGenerator.next(l, x);
|
||||||
|
l = LinearCongruentialGenerator.next(l, z);
|
||||||
|
l = LinearCongruentialGenerator.next(l, x);
|
||||||
|
l = LinearCongruentialGenerator.next(l, z);
|
||||||
|
this.map.put(Thread.currentThread().getId(), l);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int nextRandom(int y) {
|
||||||
|
long tid = Thread.currentThread().getId();
|
||||||
|
long e = this.map.computeIfAbsent(tid, i -> 0L);
|
||||||
|
int mod = (int) Math.floorMod(e >> 24L, (long) y);
|
||||||
|
this.map.put(tid, LinearCongruentialGenerator.next(e, this.magicrandom));
|
||||||
|
return mod;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ImprovedNoise getBiomeNoise() {
|
||||||
|
return this.improvedNoise;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class FastGenLayer extends Layer {
|
||||||
|
|
||||||
|
private final FastAreaLazy fastAreaLazy;
|
||||||
|
|
||||||
|
public FastGenLayer(AreaFactory<FastAreaLazy> factory) {
|
||||||
|
super(() -> null);
|
||||||
|
this.fastAreaLazy = factory.make();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Biome get(Registry<Biome> registry, int x, int z) {
|
||||||
|
ResourceKey<Biome> key = Biomes.byId(this.fastAreaLazy.get(x, z));
|
||||||
|
if (key == null) {
|
||||||
|
return registry.get(Biomes.byId(0));
|
||||||
|
}
|
||||||
|
Biome biome = registry.get(key);
|
||||||
|
if (biome == null) {
|
||||||
|
return registry.get(Biomes.byId(0));
|
||||||
|
}
|
||||||
|
return biome;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private record FastAreaLazy(ConcurrentHashMap<Long, Integer> sharedMap,
|
||||||
|
PixelTransformer transformer) implements Area {
|
||||||
|
//ConcurrentHashMap is 50% faster that Long2IntLinkedOpenHashMap in a synchronized context
|
||||||
|
//using a map for each thread worsens the performance significantly due to cache misses (factor 5)
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int get(int x, int z) {
|
||||||
|
long zx = ChunkPos.asLong(x, z);
|
||||||
|
return this.sharedMap.computeIfAbsent(zx, i -> this.transformer.apply(x, z));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class RegenNoOpWorldLoadListener implements ChunkProgressListener {
|
||||||
|
|
||||||
|
private RegenNoOpWorldLoadListener() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateSpawnPos(ChunkPos spawnPos) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStatusChange(ChunkPos pos, @Nullable ChunkStatus status) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void start() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void stop() {
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO Paper only(?) @Override
|
||||||
|
public void setChunkRadius(int radius) {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private class FastProtoChunk extends ProtoChunk {
|
||||||
|
|
||||||
|
// avoid warning on paper
|
||||||
|
public FastProtoChunk(ChunkPos pos, UpgradeData upgradeData, LevelHeightAccessor world, ServerLevel serverLevel) {
|
||||||
|
super(pos, upgradeData, world, serverLevel);
|
||||||
|
}
|
||||||
|
|
||||||
|
// compatibility with spigot
|
||||||
|
public FastProtoChunk(ChunkPos pos, UpgradeData upgradeData, LevelHeightAccessor levelHeightAccessor) {
|
||||||
|
super(pos, upgradeData, levelHeightAccessor);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean generateFlatBedrock() {
|
||||||
|
return generateFlatBedrock;
|
||||||
|
}
|
||||||
|
|
||||||
|
// no one will ever see the entities!
|
||||||
|
@Override
|
||||||
|
public List<CompoundTag> getEntities() {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected class ChunkStatusWrap extends ChunkStatusWrapper<ChunkAccess> {
|
||||||
|
|
||||||
|
private final ChunkStatus chunkStatus;
|
||||||
|
|
||||||
|
public ChunkStatusWrap(ChunkStatus chunkStatus) {
|
||||||
|
this.chunkStatus = chunkStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int requiredNeighborChunkRadius() {
|
||||||
|
return chunkStatus.getRange();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String name() {
|
||||||
|
return chunkStatus.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CompletableFuture<?> processChunk(Long xz, List<ChunkAccess> accessibleChunks) {
|
||||||
|
return chunkStatus.generate(
|
||||||
|
Runnable::run, // TODO revisit, we might profit from this somehow?
|
||||||
|
freshWorld,
|
||||||
|
chunkGenerator,
|
||||||
|
structureManager,
|
||||||
|
threadedLevelLightEngine,
|
||||||
|
c -> CompletableFuture.completedFuture(Either.left(c)),
|
||||||
|
accessibleChunks
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -555,7 +555,7 @@ public class BukkitWorld extends AbstractWorld {
|
|||||||
@Override
|
@Override
|
||||||
public boolean fullySupports3DBiomes() {
|
public boolean fullySupports3DBiomes() {
|
||||||
// Supports if API does and we're not in the overworld
|
// Supports if API does and we're not in the overworld
|
||||||
return HAS_3D_BIOMES && getWorld().getEnvironment() != World.Environment.NORMAL;
|
return HAS_3D_BIOMES && getWorld().getEnvironment() != World.Environment.NORMAL || PaperLib.isVersion(18);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("deprecation")
|
@SuppressWarnings("deprecation")
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
kotlin("jvm") version "1.5.0-RC"
|
kotlin("jvm") version "1.5.30"
|
||||||
application
|
application
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user