Merge branch 'version/7.2.x'

This commit is contained in:
Madeline Miller 2023-09-27 23:16:21 +10:00
commit 10cd6820c9
No known key found for this signature in database
GPG Key ID: B8EA2E5693115D81
47 changed files with 5041 additions and 565 deletions

View File

@ -53,7 +53,7 @@
implementation("org.jfrog.buildinfo:build-info-extractor-gradle:4.32.0")
implementation("org.spongepowered:spongegradle-plugin-development:2.1.1")
implementation("org.spongepowered:vanillagradle:0.2.1-20230603.203956-54")
implementation("net.minecraftforge.gradle:ForgeGradle:6.0.11")
implementation("net.minecraftforge.gradle:ForgeGradle:6.0.13")
implementation("net.fabricmc:fabric-loom:$loomVersion")
implementation("net.fabricmc:sponge-mixin:$mixinVersion")
implementation("org.enginehub.gradle:gradle-codecov-plugin:0.2.0")

View File

@ -4,5 +4,5 @@ version=7.3.0-SNAPSHOT
org.gradle.jvmargs=-Xmx2G
org.gradle.parallel=true
loom.version=1.2.7
loom.version=1.3.9
mixin.version=0.12.5+mixin.0.8.5

View File

@ -2,7 +2,7 @@
include("worldedit-libs")
listOf("1.17.1", "1.18.2", "1.19.4", "1.20").forEach {
listOf("1.17.1", "1.18.2", "1.19.4", "1.20", "1.20.2").forEach {
include("worldedit-bukkit:adapters:adapter-$it")
}

View File

@ -0,0 +1,8 @@
import io.papermc.paperweight.userdev.PaperweightUserDependenciesExtension
applyPaperweightAdapterConfiguration()
dependencies {
// https://repo.papermc.io/service/rest/repository/browse/maven-public/io/papermc/paper/dev-bundle/
the<PaperweightUserDependenciesExtension>().paperDevBundle("1.20.2-R0.1-20230924.232656-10")
}

View File

@ -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.impl.v1_20_R2;
import com.mojang.authlib.GameProfile;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ClientInformation;
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.entity.HumanoidArm;
import net.minecraft.world.entity.player.ChatVisiblity;
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);
private static final ClientInformation FAKE_CLIENT_INFO = new ClientInformation(
"en_US", 16, ChatVisiblity.FULL, true, 0, HumanoidArm.LEFT, false, false
);
PaperweightFakePlayer(ServerLevel world) {
super(world.getServer(), world, FAKE_WORLDEDIT_PROFILE, FAKE_CLIENT_INFO);
}
@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(ClientInformation clientOptions) {
}
@Override
public void displayClientMessage(Component message, boolean actionBar) {
}
@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, boolean front) {
}
}

View File

@ -0,0 +1,150 @@
/*
* 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.impl.v1_20_R2;
import com.sk89q.worldedit.EditSession;
import com.sk89q.worldedit.MaxChangedBlocksException;
import com.sk89q.worldedit.bukkit.BukkitAdapter;
import com.sk89q.worldedit.entity.BaseEntity;
import com.sk89q.worldedit.math.BlockVector3;
import com.sk89q.worldedit.util.Location;
import com.sk89q.worldedit.util.concurrency.LazyReference;
import com.sk89q.worldedit.world.block.BlockTypes;
import com.sk89q.worldedit.world.entity.EntityTypes;
import net.minecraft.core.BlockPos;
import net.minecraft.core.registries.Registries;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.WorldGenLevel;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.Vec3;
import org.enginehub.linbus.tree.LinCompoundTag;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class PaperweightServerLevelDelegateProxy implements InvocationHandler {
private final EditSession editSession;
private final ServerLevel serverLevel;
private final PaperweightAdapter adapter;
private PaperweightServerLevelDelegateProxy(EditSession editSession, ServerLevel serverLevel, PaperweightAdapter adapter) {
this.editSession = editSession;
this.serverLevel = serverLevel;
this.adapter = adapter;
}
public static WorldGenLevel newInstance(EditSession editSession, ServerLevel serverLevel, PaperweightAdapter adapter) {
return (WorldGenLevel) Proxy.newProxyInstance(
serverLevel.getClass().getClassLoader(),
serverLevel.getClass().getInterfaces(),
new PaperweightServerLevelDelegateProxy(editSession, serverLevel, adapter)
);
}
@Nullable
private BlockEntity getBlockEntity(BlockPos blockPos) {
BlockEntity tileEntity = this.serverLevel.getChunkAt(blockPos).getBlockEntity(blockPos);
if (tileEntity == null) {
return null;
}
BlockEntity newEntity = tileEntity.getType().create(blockPos, getBlockState(blockPos));
newEntity.load((CompoundTag) adapter.fromNative(this.editSession.getFullBlock(BlockVector3.at(blockPos.getX(), blockPos.getY(), blockPos.getZ())).getNbtReference().getValue()));
return newEntity;
}
private BlockState getBlockState(BlockPos blockPos) {
return adapter.adapt(this.editSession.getBlock(BlockVector3.at(blockPos.getX(), blockPos.getY(), blockPos.getZ())));
}
private boolean setBlock(BlockPos blockPos, BlockState blockState) {
try {
return editSession.setBlock(BlockVector3.at(blockPos.getX(), blockPos.getY(), blockPos.getZ()), adapter.adapt(blockState));
} catch (MaxChangedBlocksException e) {
throw new RuntimeException(e);
}
}
private boolean removeBlock(BlockPos blockPos, boolean bl) {
try {
return editSession.setBlock(BlockVector3.at(blockPos.getX(), blockPos.getY(), blockPos.getZ()), BlockTypes.AIR.getDefaultState());
} catch (MaxChangedBlocksException e) {
throw new RuntimeException(e);
}
}
private boolean addEntity(Entity entity) {
Vec3 pos = entity.getPosition(0.0f);
Location location = new Location(BukkitAdapter.adapt(serverLevel.getWorld()), pos.x(), pos.y(), pos.z());
ResourceLocation id = serverLevel.registryAccess().registryOrThrow(Registries.ENTITY_TYPE).getKey(entity.getType());
CompoundTag tag = new CompoundTag();
entity.saveWithoutId(tag);
BaseEntity baseEntity = new BaseEntity(EntityTypes.get(id.toString()), LazyReference.from(() -> (LinCompoundTag) adapter.toNative(tag)));
return editSession.createEntity(location, baseEntity) != null;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
switch (method.getName()) {
case "a_", "getBlockState", "addFreshEntityWithPassengers" -> {
if (args.length == 1 && args[0] instanceof BlockPos blockPos) {
// getBlockState
return getBlockState(blockPos);
} else if (args.length >= 1 && args[0] instanceof Entity entity) {
// addFreshEntityWithPassengers
return addEntity(entity);
}
}
case "c_", "getBlockEntity" -> {
if (args.length == 1 && args[0] instanceof BlockPos blockPos) {
// getBlockEntity
return getBlockEntity(blockPos);
}
}
case "a", "setBlock", "removeBlock", "destroyBlock" -> {
if (args.length >= 2 && args[0] instanceof BlockPos blockPos && args[1] instanceof BlockState blockState) {
// setBlock
return setBlock(blockPos, blockState);
} else if (args.length >= 2 && args[0] instanceof BlockPos blockPos && args[1] instanceof Boolean bl) {
// removeBlock (and also matches destroyBlock)
return removeBlock(blockPos, bl);
}
}
case "j", "addEntity" -> {
if (args.length >= 1 && args[0] instanceof Entity entity) {
return addEntity(entity);
}
}
default -> { }
}
return method.invoke(this.serverLevel, args);
}
}

View File

@ -0,0 +1,188 @@
/*
* 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.impl.v1_20_R2;
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.FullChunkStatus;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.chunk.LevelChunk;
import org.bukkit.craftbukkit.v1_20_R2.CraftWorld;
import org.bukkit.craftbukkit.v1_20_R2.block.data.CraftBlockData;
import org.bukkit.event.block.BlockPhysicsEvent;
import org.enginehub.linbus.tree.LinCompoundTag;
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, LinCompoundTag 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(FullChunkStatus.BLOCK_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());
}
}
@Override
public void updateBlock(BlockPos pos, net.minecraft.world.level.block.state.BlockState oldState, net.minecraft.world.level.block.state.BlockState newState) {
ServerLevel world = getWorld();
newState.onPlace(world, pos, oldState, false);
}
// Not sure why neighborChanged is deprecated
@SuppressWarnings("deprecation")
private void fireNeighborChanged(BlockPos pos, ServerLevel world, Block block, BlockPos neighborPos) {
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();
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);
}
}

View File

@ -37,6 +37,7 @@
import com.sk89q.worldedit.util.lifecycle.Lifecycled;
import com.sk89q.worldedit.world.DataFixer;
import com.sk89q.worldedit.world.registry.Registries;
import io.papermc.lib.PaperLib;
import org.bukkit.Bukkit;
import org.bukkit.NamespacedKey;
import org.bukkit.Registry;
@ -239,6 +240,14 @@ public Set<SideEffect> getSupportedSideEffects() {
return SUPPORTED_SIDE_EFFECTS;
}
@Override
public long getTickCount() {
if (PaperLib.isPaper()) {
return Bukkit.getCurrentTick();
}
return super.getTickCount();
}
public void unregisterCommands() {
dynamicCommands.unregisterCommands();
}

View File

@ -25,6 +25,7 @@
import com.sk89q.worldedit.entity.Player;
import com.sk89q.worldedit.event.platform.SessionIdleEvent;
import com.sk89q.worldedit.extension.platform.Actor;
import com.sk89q.worldedit.internal.event.InteractionDebouncer;
import com.sk89q.worldedit.util.Direction;
import com.sk89q.worldedit.util.Location;
import com.sk89q.worldedit.world.World;
@ -52,6 +53,7 @@
public class WorldEditListener implements Listener {
private final WorldEditPlugin plugin;
private final InteractionDebouncer debouncer;
/**
* Construct the object.
@ -60,6 +62,7 @@ public class WorldEditListener implements Listener {
*/
public WorldEditListener(WorldEditPlugin plugin) {
this.plugin = plugin;
debouncer = new InteractionDebouncer(plugin.getInternalPlatform());
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
@ -93,62 +96,59 @@ public void onPlayerCommandSend(PlayerCommandSendEvent event) {
*/
@EventHandler
public void onPlayerInteract(PlayerInteractEvent event) {
if (!plugin.getInternalPlatform().isHookingEvents()) {
return;
}
if (event.useItemInHand() == Result.DENY) {
return;
}
if (event.getHand() == EquipmentSlot.OFF_HAND) {
if (!plugin.getInternalPlatform().isHookingEvents()
|| event.useItemInHand() == Result.DENY
|| event.getHand() == EquipmentSlot.OFF_HAND
|| event.getAction() == Action.PHYSICAL) {
return;
}
final Player player = plugin.wrapPlayer(event.getPlayer());
if (event.getAction() != Action.LEFT_CLICK_BLOCK) {
Optional<Boolean> previousResult = debouncer.getDuplicateInteractionResult(player);
if (previousResult.isPresent()) {
if (previousResult.get()) {
event.setCancelled(true);
}
return;
}
}
final World world = player.getWorld();
final WorldEdit we = plugin.getWorldEdit();
final Direction direction = BukkitAdapter.adapt(event.getBlockFace());
final Block clickedBlock = event.getClickedBlock();
final Location pos = clickedBlock == null ? null : new Location(world, clickedBlock.getX(), clickedBlock.getY(), clickedBlock.getZ());
Action action = event.getAction();
if (action == Action.LEFT_CLICK_BLOCK) {
final Block clickedBlock = event.getClickedBlock();
final Location pos = new Location(world, clickedBlock.getX(), clickedBlock.getY(), clickedBlock.getZ());
boolean result = false;
switch (event.getAction()) {
case LEFT_CLICK_BLOCK:
result = we.handleBlockLeftClick(player, pos, direction) || we.handleArmSwing(player);
break;
case LEFT_CLICK_AIR:
result = we.handleArmSwing(player);
break;
case RIGHT_CLICK_BLOCK:
result = we.handleBlockRightClick(player, pos, direction) || we.handleRightClick(player);
break;
case RIGHT_CLICK_AIR:
result = we.handleRightClick(player);
break;
default:
break;
}
if (we.handleBlockLeftClick(player, pos, direction)) {
event.setCancelled(true);
}
if (we.handleArmSwing(player)) {
event.setCancelled(true);
}
} else if (action == Action.LEFT_CLICK_AIR) {
if (we.handleArmSwing(player)) {
event.setCancelled(true);
}
} else if (action == Action.RIGHT_CLICK_BLOCK) {
final Block clickedBlock = event.getClickedBlock();
final Location pos = new Location(world, clickedBlock.getX(), clickedBlock.getY(), clickedBlock.getZ());
if (we.handleBlockRightClick(player, pos, direction)) {
event.setCancelled(true);
}
if (we.handleRightClick(player)) {
event.setCancelled(true);
}
} else if (action == Action.RIGHT_CLICK_AIR) {
if (we.handleRightClick(player)) {
event.setCancelled(true);
}
debouncer.setLastInteraction(player, result);
if (result) {
event.setCancelled(true);
}
}
@EventHandler
public void onPlayerQuit(PlayerQuitEvent event) {
debouncer.clearInteraction(plugin.wrapPlayer(event.getPlayer()));
plugin.getWorldEdit().getEventBus().post(new SessionIdleEvent(new BukkitPlayer.SessionKeyImpl(event.getPlayer())));
}
}

View File

@ -97,6 +97,8 @@ public YAMLProcessor(File file, boolean writeDefaults, YAMLFormat format) {
representer.setDefaultFlowStyle(format.getStyle());
LoaderOptions loaderOptions = new LoaderOptions();
try {
int yamlAliasLimit = Integer.getInteger("worldedit.yaml.aliasLimit", 50);
loaderOptions.setMaxAliasesForCollections(yamlAliasLimit);
// 64 MB default
int yamlCodePointLimit = Integer.getInteger("worldedit.yaml.codePointLimit", 64 * 1024 * 1024);
loaderOptions.setCodePointLimit(yamlCodePointLimit);
@ -104,7 +106,7 @@ public YAMLProcessor(File file, boolean writeDefaults, YAMLFormat format) {
// pre-1.32 snakeyaml
}
yaml = new Yaml(new SafeConstructor(new LoaderOptions()), representer, dumperOptions, loaderOptions);
yaml = new Yaml(new SafeConstructor(loaderOptions), representer, dumperOptions, loaderOptions);
this.file = file;
}

View File

@ -1044,10 +1044,17 @@ public int fillXZ(BlockVector3 origin, Pattern pattern, double radius, int depth
checkArgument(radius >= 0, "radius >= 0");
checkArgument(depth >= 1, "depth >= 1");
// Avoid int overflow (negative coordinate space allows for overflow back round to positive if the depth is large enough).
// Depth is always 1 or greater, thus the lower bound should always be <= origin y.
int lowerBound = origin.getBlockY() - depth + 1;
if (lowerBound > origin.getBlockY()) {
lowerBound = Integer.MIN_VALUE;
}
MaskIntersection mask = new MaskIntersection(
new RegionMask(new EllipsoidRegion(null, origin, Vector3.at(radius, radius, radius))),
new BoundedHeightMask(
Math.max(origin.getBlockY() - depth + 1, getWorld().getMinY()),
Math.max(lowerBound, getWorld().getMinY()),
Math.min(getWorld().getMaxY(), origin.getBlockY())),
Masks.negate(new ExistingBlockMask(this)));

View File

@ -84,4 +84,8 @@ public void reload() {
getTranslationManager().reload();
}
@Override
public long getTickCount() {
return System.nanoTime() / 50_000_000;
}
}

View File

@ -222,4 +222,12 @@ default String getId() {
* @return A set of supported side effects
*/
Set<SideEffect> getSupportedSideEffects();
/**
* Get the number of ticks since the server started.
* On some platforms this value may be an approximation based on the JVM run time.
*
* @return The number of ticks since the server started.
*/
long getTickCount();
}

View File

@ -0,0 +1,69 @@
/*
* 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.internal.event;
import com.sk89q.worldedit.extension.platform.Platform;
import com.sk89q.worldedit.util.Identifiable;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
public class InteractionDebouncer {
private final Platform platform;
private final Map<UUID, Interaction> lastInteractions = new HashMap<>();
public InteractionDebouncer(Platform platform) {
this.platform = platform;
}
public void clearInteraction(Identifiable player) {
lastInteractions.remove(player.getUniqueId());
}
public void setLastInteraction(Identifiable player, boolean result) {
lastInteractions.put(player.getUniqueId(), new Interaction(platform.getTickCount(), result));
}
public Optional<Boolean> getDuplicateInteractionResult(Identifiable player) {
Interaction last = lastInteractions.get(player.getUniqueId());
if (last == null) {
return Optional.empty();
}
long now = platform.getTickCount();
if (now - last.tick <= 1) {
return Optional.of(last.result);
}
return Optional.empty();
}
private static class Interaction {
public final long tick;
public final boolean result;
public Interaction(long tick, boolean result) {
this.tick = tick;
this.result = result;
}
}
}

View File

@ -1,5 +1,6 @@
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
import net.fabricmc.loom.api.LoomGradleExtensionAPI
import net.fabricmc.loom.configuration.FabricApiExtension
import net.fabricmc.loom.task.RemapJarTask
buildscript {
@ -21,8 +22,8 @@
apply(plugin = "fabric-loom")
apply(plugin = "java-library")
val minecraftVersion = "1.20"
val loaderVersion = "0.14.21"
val minecraftVersion = "1.20.2"
val loaderVersion = "0.14.22"
val fabricApiConfiguration: Configuration = configurations.create("fabricApi")
@ -52,52 +53,20 @@
"mappings"(project.the<LoomGradleExtensionAPI>().officialMojangMappings())
"modImplementation"("net.fabricmc:fabric-loader:$loaderVersion")
// [1] declare fabric-api dependency...
"fabricApi"("net.fabricmc.fabric-api:fabric-api:0.83.0+1.20")
// [2] Load the API dependencies from the fabric mod json...
// [1] Load the API dependencies from the fabric mod json...
@Suppress("UNCHECKED_CAST")
val fabricModJson = file("src/main/resources/fabric.mod.json").bufferedReader().use {
groovy.json.JsonSlurper().parse(it) as Map<String, Map<String, *>>
}
val wantedDependencies = (fabricModJson["depends"] ?: error("no depends in fabric.mod.json")).keys
.filter { it == "fabric-api-base" || it.contains(Regex("v\\d$")) }
.map { "net.fabricmc.fabric-api:$it" }
.toSet()
logger.lifecycle("Looking for these dependencies:")
// [2] Request the matching dependency from fabric-loom
for (wantedDependency in wantedDependencies) {
logger.lifecycle(wantedDependency)
}
// [3] and now we resolve it to pick out what we want :D
val fabricApiDependencies = fabricApiConfiguration.incoming.resolutionResult.allDependencies
.onEach {
if (it is UnresolvedDependencyResult) {
throw kotlin.IllegalStateException("Failed to resolve Fabric API", it.failure)
}
}
.filterIsInstance<ResolvedDependencyResult>()
// pick out transitive dependencies
.flatMap {
it.selected.dependencies
}
// grab the requested versions
.map { it.requested }
.filterIsInstance<ModuleComponentSelector>()
// map to standard notation
.associateByTo(
mutableMapOf(),
keySelector = { "${it.group}:${it.module}" },
valueTransform = { "${it.group}:${it.module}:${it.version}" }
)
fabricApiDependencies.keys.retainAll(wantedDependencies)
// sanity check
for (wantedDep in wantedDependencies) {
check(wantedDep in fabricApiDependencies) { "Fabric API library $wantedDep is missing!" }
}
fabricApiDependencies.values.forEach {
"include"(it)
"modImplementation"(it)
val dep = project.the<FabricApiExtension>().module(wantedDependency, "0.89.1+1.20.2")
"include"(dep)
"modImplementation"(dep)
}
// No need for this at runtime

View File

@ -218,6 +218,11 @@ public Set<SideEffect> getSupportedSideEffects() {
: SUPPORTED_SIDE_EFFECTS_NO_MIXIN;
}
@Override
public long getTickCount() {
return FabricWorldEdit.LIFECYCLED_SERVER.valueOrThrow().getTickCount();
}
@Override
public Collection<Actor> getConnectedUsers() {
List<Actor> users = new ArrayList<>();

View File

@ -24,9 +24,7 @@
import com.sk89q.worldedit.entity.BaseEntity;
import com.sk89q.worldedit.extension.platform.AbstractPlayerActor;
import com.sk89q.worldedit.extent.inventory.BlockBag;
import com.sk89q.worldedit.fabric.internal.ExtendedPlayerEntity;
import com.sk89q.worldedit.fabric.internal.NBTConverter;
import com.sk89q.worldedit.fabric.mixin.AccessorClientboundBlockEntityDataPacket;
import com.sk89q.worldedit.fabric.net.handler.WECUIPacketHandler;
import com.sk89q.worldedit.internal.cui.CUIEvent;
import com.sk89q.worldedit.math.BlockVector3;
@ -48,6 +46,7 @@
import net.minecraft.core.BlockPos;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.chat.MutableComponent;
import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket;
import net.minecraft.network.protocol.game.ClientboundBlockUpdatePacket;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
@ -141,7 +140,7 @@ public void dispatchCUIEvent(CUIEvent event) {
@Override
public Locale getLocale() {
return TextUtils.getLocaleByMinecraftTag(((ExtendedPlayerEntity) this.player).getLanguage());
return TextUtils.getLocaleByMinecraftTag(this.player.clientInformation().language());
}
@Override
@ -244,10 +243,10 @@ public <B extends BlockStateHolder<B>> void sendFakeBlock(BlockVector3 pos, B bl
if (block instanceof BaseBlock && block.getBlockType().equals(BlockTypes.STRUCTURE_BLOCK)) {
final LinCompoundTag nbtData = ((BaseBlock) block).getNbt();
if (nbtData != null) {
player.connection.send(AccessorClientboundBlockEntityDataPacket.construct(
new BlockPos(pos.getBlockX(), pos.getBlockY(), pos.getBlockZ()),
BlockEntityType.STRUCTURE_BLOCK,
NBTConverter.toNative(nbtData))
player.connection.send(new ClientboundBlockEntityDataPacket(
new BlockPos(pos.getBlockX(), pos.getBlockY(), pos.getBlockZ()),
BlockEntityType.STRUCTURE_BLOCK,
NBTConverter.toNative(nbtData))
);
}
}

View File

@ -38,9 +38,6 @@
import com.sk89q.worldedit.fabric.internal.FabricServerLevelDelegateProxy;
import com.sk89q.worldedit.fabric.internal.FabricWorldNativeAccess;
import com.sk89q.worldedit.fabric.internal.NBTConverter;
import com.sk89q.worldedit.fabric.mixin.AccessorDerivedLevelData;
import com.sk89q.worldedit.fabric.mixin.AccessorPrimaryLevelData;
import com.sk89q.worldedit.fabric.mixin.AccessorServerChunkCache;
import com.sk89q.worldedit.function.mask.AbstractExtentMask;
import com.sk89q.worldedit.function.mask.Mask;
import com.sk89q.worldedit.function.mask.Mask2D;
@ -104,8 +101,10 @@
import net.minecraft.world.level.levelgen.structure.BoundingBox;
import net.minecraft.world.level.levelgen.structure.Structure;
import net.minecraft.world.level.levelgen.structure.StructureStart;
import net.minecraft.world.level.storage.DerivedLevelData;
import net.minecraft.world.level.storage.LevelData;
import net.minecraft.world.level.storage.LevelStorageSource;
import net.minecraft.world.level.storage.PrimaryLevelData;
import net.minecraft.world.level.storage.ServerLevelData;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.BlockHitResult;
@ -333,20 +332,14 @@ private void doRegen(Region region, Extent extent, RegenOptions options) throws
LevelStorageSource levelStorage = LevelStorageSource.createDefault(tempDir);
try (LevelStorageSource.LevelStorageAccess session = levelStorage.createAccess("WorldEditTempGen")) {
ServerLevel originalWorld = (ServerLevel) getWorld();
AccessorPrimaryLevelData levelProperties;
if (originalWorld.getLevelData() instanceof AccessorDerivedLevelData derivedLevelData) {
levelProperties = (AccessorPrimaryLevelData) derivedLevelData.getWrapped();
} else {
levelProperties = (AccessorPrimaryLevelData) originalWorld.getLevelData();
}
PrimaryLevelData levelProperties = getPrimaryLevelData(originalWorld.getLevelData());
WorldOptions originalOpts = levelProperties.worldGenOptions();
long seed = options.getSeed().orElse(originalWorld.getSeed());
WorldOptions newOpts = options.getSeed().isPresent()
levelProperties.worldOptions = options.getSeed().isPresent()
? originalOpts.withSeed(OptionalLong.of(seed))
: originalOpts;
levelProperties.setWorldOptions(newOpts);
ResourceKey<Level> worldRegKey = originalWorld.dimension();
try (ServerLevel serverWorld = new ServerLevel(
originalWorld.getServer(), Util.backgroundExecutor(), session,
@ -372,19 +365,29 @@ private void doRegen(Region region, Extent extent, RegenOptions options) throws
Thread.yield();
}
} finally {
levelProperties.setWorldOptions(originalOpts);
levelProperties.worldOptions = originalOpts;
}
} finally {
SafeFiles.tryHardToDeleteDir(tempDir);
}
}
private static PrimaryLevelData getPrimaryLevelData(LevelData levelData) {
if (levelData instanceof DerivedLevelData derivedLevelData) {
return getPrimaryLevelData(derivedLevelData.wrapped);
} else if (levelData instanceof PrimaryLevelData primaryLevelData) {
return primaryLevelData;
} else {
throw new IllegalStateException("Unknown level data type: " + levelData.getClass());
}
}
private void regenForWorld(Region region, Extent extent, ServerLevel serverWorld,
RegenOptions options) throws WorldEditException {
List<CompletableFuture<ChunkAccess>> chunkLoadings = submitChunkLoadTasks(region, serverWorld);
// drive executor until loading finishes
((AccessorServerChunkCache) serverWorld.getChunkSource()).getMainThreadProcessor()
serverWorld.getChunkSource().mainThreadProcessor
.managedBlock(() -> {
// bail out early if a future fails
if (chunkLoadings.stream().anyMatch(ftr ->
@ -422,12 +425,11 @@ private void regenForWorld(Region region, Extent extent, ServerLevel serverWorld
}
private List<CompletableFuture<ChunkAccess>> submitChunkLoadTasks(Region region, ServerLevel world) {
AccessorServerChunkCache chunkManager = (AccessorServerChunkCache) world.getChunkSource();
List<CompletableFuture<ChunkAccess>> chunkLoadings = new ArrayList<>();
// Pre-gen all the chunks
for (BlockVector2 chunk : region.getChunks()) {
chunkLoadings.add(
chunkManager.callGetChunkFuture(chunk.getX(), chunk.getZ(), ChunkStatus.FEATURES, true)
world.getChunkSource().getChunkFuture(chunk.getX(), chunk.getZ(), ChunkStatus.FEATURES, true)
.thenApply(either -> either.left().orElse(null))
);
}

View File

@ -32,6 +32,7 @@
import com.sk89q.worldedit.extension.platform.PlatformManager;
import com.sk89q.worldedit.fabric.net.handler.WECUIPacketHandler;
import com.sk89q.worldedit.internal.anvil.ChunkDeleter;
import com.sk89q.worldedit.internal.event.InteractionDebouncer;
import com.sk89q.worldedit.internal.util.LogManagerCompat;
import com.sk89q.worldedit.util.Location;
import com.sk89q.worldedit.util.lifecycle.Lifecycled;
@ -87,6 +88,7 @@
import java.nio.file.Path;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
@ -120,6 +122,7 @@ public static <T> Registry<T> getRegistry(ResourceKey<Registry<T>> key) {
public static FabricWorldEdit inst;
private InteractionDebouncer debouncer;
private FabricPlatform platform;
private FabricConfiguration config;
private Path workingDir;
@ -146,6 +149,7 @@ public void onInitialize() {
}
}
this.platform = new FabricPlatform(this);
debouncer = new InteractionDebouncer(platform);
WorldEdit.getInstance().getPlatformManager().register(platform);
@ -284,16 +288,16 @@ private void onStopServer(MinecraftServer minecraftServer) {
WorldEdit.getInstance().getEventBus().post(new PlatformUnreadyEvent(platform));
}
private boolean shouldSkip() {
if (platform == null) {
return true;
}
private boolean skipEvents() {
return platform == null || !platform.isHookingEvents();
}
return !platform.isHookingEvents(); // We have to be told to catch these events
private boolean skipInteractionEvent(Player player, InteractionHand hand) {
return skipEvents() || hand != InteractionHand.MAIN_HAND || player.level().isClientSide || !(player instanceof ServerPlayer);
}
private InteractionResult onLeftClickBlock(Player playerEntity, Level world, InteractionHand hand, BlockPos blockPos, Direction direction) {
if (shouldSkip() || hand == InteractionHand.OFF_HAND || world.isClientSide) {
if (skipInteractionEvent(playerEntity, hand)) {
return InteractionResult.PASS;
}
@ -307,19 +311,14 @@ private InteractionResult onLeftClickBlock(Player playerEntity, Level world, Int
);
com.sk89q.worldedit.util.Direction weDirection = FabricAdapter.adaptEnumFacing(direction);
if (we.handleBlockLeftClick(player, pos, weDirection)) {
return InteractionResult.SUCCESS;
}
boolean result = we.handleBlockLeftClick(player, pos, weDirection) || we.handleArmSwing(player);
debouncer.setLastInteraction(player, result);
if (we.handleArmSwing(player)) {
return InteractionResult.SUCCESS;
}
return InteractionResult.PASS;
return result ? InteractionResult.SUCCESS : InteractionResult.PASS;
}
private InteractionResult onRightClickBlock(Player playerEntity, Level world, InteractionHand hand, BlockHitResult blockHitResult) {
if (shouldSkip() || hand == InteractionHand.OFF_HAND || world.isClientSide) {
if (skipInteractionEvent(playerEntity, hand)) {
return InteractionResult.PASS;
}
@ -333,36 +332,52 @@ private InteractionResult onRightClickBlock(Player playerEntity, Level world, In
);
com.sk89q.worldedit.util.Direction direction = FabricAdapter.adaptEnumFacing(blockHitResult.getDirection());
if (we.handleBlockRightClick(player, pos, direction)) {
return InteractionResult.SUCCESS;
boolean result = we.handleBlockRightClick(player, pos, direction) || we.handleRightClick(player);
debouncer.setLastInteraction(player, result);
return result ? InteractionResult.SUCCESS : InteractionResult.PASS;
}
public void onLeftClickAir(ServerPlayer playerEntity, InteractionHand hand) {
if (skipInteractionEvent(playerEntity, hand)) {
return;
}
if (we.handleRightClick(player)) {
return InteractionResult.SUCCESS;
WorldEdit we = WorldEdit.getInstance();
FabricPlayer player = adaptPlayer(playerEntity);
Optional<Boolean> previousResult = debouncer.getDuplicateInteractionResult(player);
if (previousResult.isPresent()) {
return;
}
return InteractionResult.PASS;
boolean result = we.handleArmSwing(player);
debouncer.setLastInteraction(player, result);
}
private InteractionResultHolder<ItemStack> onRightClickAir(Player playerEntity, Level world, InteractionHand hand) {
ItemStack stackInHand = playerEntity.getItemInHand(hand);
if (shouldSkip() || hand == InteractionHand.OFF_HAND || world.isClientSide) {
if (skipInteractionEvent(playerEntity, hand)) {
return InteractionResultHolder.pass(stackInHand);
}
WorldEdit we = WorldEdit.getInstance();
FabricPlayer player = adaptPlayer((ServerPlayer) playerEntity);
if (we.handleRightClick(player)) {
return InteractionResultHolder.success(stackInHand);
Optional<Boolean> previousResult = debouncer.getDuplicateInteractionResult(player);
if (previousResult.isPresent()) {
return previousResult.get() ? InteractionResultHolder.success(stackInHand) : InteractionResultHolder.pass(stackInHand);
}
return InteractionResultHolder.pass(stackInHand);
boolean result = we.handleRightClick(player);
debouncer.setLastInteraction(player, result);
return result ? InteractionResultHolder.success(stackInHand) : InteractionResultHolder.pass(stackInHand);
}
// TODO Pass empty left click to server
private void onPlayerDisconnect(ServerGamePacketListenerImpl handler, MinecraftServer server) {
debouncer.clearInteraction(adaptPlayer(handler.player));
WorldEdit.getInstance().getEventBus()
.post(new SessionIdleEvent(new FabricPlayer.SessionKeyImpl(handler.player)));
}

View File

@ -21,18 +21,24 @@
import com.mojang.authlib.GameProfile;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ClientInformation;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.stats.Stat;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.entity.HumanoidArm;
import net.minecraft.world.entity.player.ChatVisiblity;
import java.util.UUID;
public class WorldEditFakePlayer extends ServerPlayer {
private static final GameProfile FAKE_WORLDEDIT_PROFILE = new GameProfile(UUID.nameUUIDFromBytes("worldedit".getBytes()), "[WorldEdit]");
private static final ClientInformation FAKE_CLIENT_INFO = new ClientInformation(
"en_US", 16, ChatVisiblity.FULL, true, 0, HumanoidArm.LEFT, false, false
);
public WorldEditFakePlayer(ServerLevel world) {
super(world.getServer(), world, FAKE_WORLDEDIT_PROFILE);
super(world.getServer(), world, FAKE_WORLDEDIT_PROFILE, FAKE_CLIENT_INFO);
}
@Override

View File

@ -1,26 +0,0 @@
/*
* 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.fabric.internal;
public interface ExtendedPlayerEntity {
String getLanguage();
}

View File

@ -1,35 +0,0 @@
/*
* 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.fabric.mixin;
import net.minecraft.core.BlockPos;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket;
import net.minecraft.world.level.block.entity.BlockEntityType;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Invoker;
@Mixin(ClientboundBlockEntityDataPacket.class)
public interface AccessorClientboundBlockEntityDataPacket {
@Invoker("<init>")
static ClientboundBlockEntityDataPacket construct(BlockPos blockPos, BlockEntityType<?> blockEntityType, CompoundTag compoundTag) {
throw new AssertionError("This is replaced by Mixin to call the constructor.");
}
}

View File

@ -1,36 +0,0 @@
/*
* 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.fabric.mixin;
import net.minecraft.world.level.levelgen.WorldOptions;
import net.minecraft.world.level.storage.PrimaryLevelData;
import net.minecraft.world.level.storage.WorldData;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Mutable;
import org.spongepowered.asm.mixin.gen.Accessor;
@Mixin(PrimaryLevelData.class)
public interface AccessorPrimaryLevelData extends WorldData {
@Accessor
@Mutable
void setWorldOptions(WorldOptions worldOptions);
}

View File

@ -1,42 +0,0 @@
/*
* 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.fabric.mixin;
import com.mojang.datafixers.util.Either;
import net.minecraft.server.level.ChunkHolder;
import net.minecraft.server.level.ServerChunkCache;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.ChunkStatus;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
import org.spongepowered.asm.mixin.gen.Invoker;
import java.util.concurrent.CompletableFuture;
@Mixin(ServerChunkCache.class)
public interface AccessorServerChunkCache {
@Invoker
CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> callGetChunkFuture(int chunkX, int chunkZ, ChunkStatus leastStatus, boolean create);
@Accessor
ServerChunkCache.MainThreadExecutor getMainThreadProcessor();
}

View File

@ -34,6 +34,7 @@
import net.minecraft.world.level.chunk.UpgradeData;
import net.minecraft.world.level.levelgen.blending.BlendingData;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.Slice;
@ -42,12 +43,14 @@
@Mixin(LevelChunk.class)
public abstract class MixinLevelChunkSetBlockHook extends ChunkAccess implements ExtendedChunk {
@Unique
private boolean shouldUpdate = true;
public MixinLevelChunkSetBlockHook(ChunkPos chunkPos, UpgradeData upgradeData, LevelHeightAccessor levelHeightAccessor, Registry<Biome> registry, long l, @org.jetbrains.annotations.Nullable LevelChunkSection[] levelChunkSections, @org.jetbrains.annotations.Nullable BlendingData blendingData) {
super(chunkPos, upgradeData, levelHeightAccessor, registry, l, levelChunkSections, blendingData);
}
@Unique
@Nullable
@Override
public BlockState setBlockState(BlockPos pos, BlockState state, boolean moved, boolean update) {

View File

@ -28,6 +28,7 @@
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import java.nio.file.Path;
@ -40,11 +41,13 @@ public abstract class MixinMinecraftServer implements Watchdog, ExtendedMinecraf
@Shadow
protected LevelStorageSource.LevelStorageAccess storageSource;
@Unique
@Override
public void tick() {
nextTickTime = Util.getMillis();
}
@Unique
@Override
public Path getStoragePath(Level world) {
return storageSource.getDimensionPath(world.dimension());

View File

@ -0,0 +1,61 @@
/*
* 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.fabric.mixin;
import com.sk89q.worldedit.fabric.FabricWorldEdit;
import net.minecraft.network.protocol.game.ServerboundPlayerActionPacket;
import net.minecraft.network.protocol.game.ServerboundSwingPacket;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.server.network.ServerGamePacketListenerImpl;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(ServerGamePacketListenerImpl.class)
public class MixinServerGamePacketListenerImpl {
@Shadow
public ServerPlayer player;
@Unique
private int ignoreSwingPackets;
@Inject(method = "handleAnimate", at = @At("HEAD"))
private void onAnimate(ServerboundSwingPacket packet, CallbackInfo ci) {
if (!this.player.gameMode.isDestroyingBlock) {
if (this.ignoreSwingPackets > 0) {
this.ignoreSwingPackets--;
} else if (FabricWorldEdit.inst != null) {
FabricWorldEdit.inst.onLeftClickAir(this.player, packet.getHand());
}
}
}
@Inject(method = "handlePlayerAction", at = @At("HEAD"))
private void onAction(ServerboundPlayerActionPacket packet, CallbackInfo ci) {
switch (packet.getAction()) {
case DROP_ITEM, DROP_ALL_ITEMS, START_DESTROY_BLOCK -> this.ignoreSwingPackets++;
default -> {
}
}
}
}

View File

@ -1,46 +0,0 @@
/*
* 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.fabric.mixin;
import com.sk89q.worldedit.fabric.internal.ExtendedPlayerEntity;
import net.minecraft.network.protocol.game.ServerboundClientInformationPacket;
import net.minecraft.server.level.ServerPlayer;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(ServerPlayer.class)
public abstract class MixinServerPlayer implements ExtendedPlayerEntity {
private String language = "en_us";
@Inject(method = "updateOptions", at = @At(value = "HEAD"))
public void updateOptions(ServerboundClientInformationPacket clientSettingsC2SPacket,
CallbackInfo callbackInfo) {
this.language = clientSettingsC2SPacket.language();
}
@Override
public String getLanguage() {
return language;
}
}

View File

@ -3,13 +3,9 @@
"package": "com.sk89q.worldedit.fabric.mixin",
"compatibilityLevel": "JAVA_8",
"mixins": [
"AccessorClientboundBlockEntityDataPacket",
"AccessorPrimaryLevelData",
"AccessorDerivedLevelData",
"AccessorServerChunkCache",
"MixinLevelChunkSetBlockHook",
"MixinMinecraftServer",
"MixinServerPlayer"
"MixinServerGamePacketListenerImpl"
],
"plugin": "com.sk89q.worldedit.fabric.internal.MixinConfigPlugin",
"server": [

View File

@ -1,3 +1,15 @@
accessWidener v2 named
accessible class net/minecraft/server/level/ServerChunkCache$MainThreadExecutor
accessible field net/minecraft/commands/CommandSourceStack source Lnet/minecraft/commands/CommandSource;
accessible field net/minecraft/server/level/ServerChunkCache mainThreadProcessor Lnet/minecraft/server/level/ServerChunkCache$MainThreadExecutor;
accessible field net/minecraft/commands/CommandSourceStack source Lnet/minecraft/commands/CommandSource;
accessible field net/minecraft/server/level/ServerPlayerGameMode isDestroyingBlock Z
accessible field net/minecraft/world/level/storage/DerivedLevelData wrapped Lnet/minecraft/world/level/storage/ServerLevelData;
accessible field net/minecraft/world/level/storage/PrimaryLevelData worldOptions Lnet/minecraft/world/level/levelgen/WorldOptions;
mutable field net/minecraft/world/level/storage/PrimaryLevelData worldOptions Lnet/minecraft/world/level/levelgen/WorldOptions;
accessible method net/minecraft/network/protocol/game/ClientboundBlockEntityDataPacket <init> (Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/block/entity/BlockEntityType;Lnet/minecraft/nbt/CompoundTag;)V

View File

@ -12,11 +12,11 @@
applyPlatformAndCoreConfiguration(javaRelease = 17)
applyShadowConfiguration()
val minecraftVersion = "1.20"
val minecraftVersion = "1.20.2"
val nextMajorMinecraftVersion: String = minecraftVersion.split('.').let { (useless, major) ->
"$useless.${major.toInt() + 1}"
}
val forgeVersion = "46.0.1"
val forgeVersion = "48.0.1"
val apiClasspath = configurations.create("apiClasspath") {
isCanBeResolved = true

View File

@ -63,18 +63,14 @@
class ForgePlatform extends AbstractPlatform implements MultiUserPlatform {
private final ForgeWorldEdit mod;
private final MinecraftServer server;
private final ForgeDataFixer dataFixer;
private final @Nullable ForgeWatchdog watchdog;
private @Nullable ForgeWatchdog watchdog;
private boolean hookingEvents = false;
private final ResourceLoader resourceLoader = new ForgeResourceLoader(WorldEdit.getInstance());
ForgePlatform(ForgeWorldEdit mod) {
this.mod = mod;
this.server = ServerLifecycleHooks.getCurrentServer();
this.dataFixer = new ForgeDataFixer(getDataVersion());
this.watchdog = server instanceof DedicatedServer
? new ForgeWatchdog((DedicatedServer) server) : null;
}
boolean isHookingEvents() {
@ -120,12 +116,18 @@ public int schedule(long delay, long period, Runnable task) {
@Override
@Nullable
public ForgeWatchdog getWatchdog() {
if (watchdog == null) {
MinecraftServer server = ServerLifecycleHooks.getCurrentServer();
if (server instanceof DedicatedServer) {
watchdog = new ForgeWatchdog((DedicatedServer) server);
}
}
return watchdog;
}
@Override
public List<? extends World> getWorlds() {
Iterable<ServerLevel> worlds = server.getAllLevels();
Iterable<ServerLevel> worlds = ServerLifecycleHooks.getCurrentServer().getAllLevels();
List<World> ret = new ArrayList<>();
for (ServerLevel world : worlds) {
ret.add(new ForgeWorld(world));
@ -139,7 +141,7 @@ public Player matchPlayer(Player player) {
if (player instanceof ForgePlayer) {
return player;
} else {
ServerPlayer entity = server.getPlayerList().getPlayerByName(player.getName());
ServerPlayer entity = ServerLifecycleHooks.getCurrentServer().getPlayerList().getPlayerByName(player.getName());
return entity != null ? new ForgePlayer(entity) : null;
}
}
@ -150,7 +152,7 @@ public World matchWorld(World world) {
if (world instanceof ForgeWorld) {
return world;
} else {
for (ServerLevel ws : server.getAllLevels()) {
for (ServerLevel ws : ServerLifecycleHooks.getCurrentServer().getAllLevels()) {
if (((ServerLevelData) ws.getLevelData()).getLevelName().equals(world.getName())) {
return new ForgeWorld(ws);
}
@ -162,6 +164,7 @@ public World matchWorld(World world) {
@Override
public void registerCommands(CommandManager manager) {
MinecraftServer server = ServerLifecycleHooks.getCurrentServer();
if (server == null) {
return;
}
@ -239,10 +242,15 @@ public Set<SideEffect> getSupportedSideEffects() {
: SUPPORTED_SIDE_EFFECTS_NO_MIXIN;
}
@Override
public long getTickCount() {
return ServerLifecycleHooks.getCurrentServer().getTickCount();
}
@Override
public Collection<Actor> getConnectedUsers() {
List<Actor> users = new ArrayList<>();
PlayerList scm = server.getPlayerList();
PlayerList scm = ServerLifecycleHooks.getCurrentServer().getPlayerList();
for (ServerPlayer entity : scm.getPlayers()) {
if (entity != null) {
users.add(new ForgePlayer(entity));

View File

@ -25,6 +25,7 @@
import com.sk89q.worldedit.extension.platform.AbstractPlayerActor;
import com.sk89q.worldedit.extent.inventory.BlockBag;
import com.sk89q.worldedit.forge.internal.NBTConverter;
import com.sk89q.worldedit.forge.net.handler.WECUIPacketHandler;
import com.sk89q.worldedit.internal.cui.CUIEvent;
import com.sk89q.worldedit.math.BlockVector3;
import com.sk89q.worldedit.math.Vector3;
@ -45,14 +46,11 @@
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket;
import net.minecraft.network.protocol.game.ClientboundBlockUpdatePacket;
import net.minecraft.network.protocol.game.ClientboundCustomPayloadPacket;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.entity.StructureBlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
import org.enginehub.linbus.tree.LinCompoundTag;
import java.nio.charset.StandardCharsets;
@ -134,11 +132,7 @@ public void dispatchCUIEvent(CUIEvent event) {
send = send + "|" + StringUtil.joinString(params, "|");
}
FriendlyByteBuf buffer = new FriendlyByteBuf(Unpooled.copiedBuffer(send, StandardCharsets.UTF_8));
ClientboundCustomPayloadPacket packet = new ClientboundCustomPayloadPacket(
new ResourceLocation(ForgeWorldEdit.MOD_ID, ForgeWorldEdit.CUI_PLUGIN_CHANNEL),
buffer
);
this.player.connection.send(packet);
WECUIPacketHandler.send(this.player.connection.getConnection(), buffer);
}
private void sendMessage(net.minecraft.network.chat.Component textComponent) {
@ -251,12 +245,10 @@ public <B extends BlockStateHolder<B>> void sendFakeBlock(BlockVector3 pos, B bl
if (block instanceof BaseBlock baseBlock && block.getBlockType().equals(BlockTypes.STRUCTURE_BLOCK)) {
final LinCompoundTag nbtData = baseBlock.getNbt();
if (nbtData != null) {
player.connection.send(ClientboundBlockEntityDataPacket.create(
new StructureBlockEntity(
new BlockPos(pos.getBlockX(), pos.getBlockY(), pos.getBlockZ()),
Blocks.STRUCTURE_BLOCK.defaultBlockState()
),
__ -> NBTConverter.toNative(nbtData)
player.connection.send(new ClientboundBlockEntityDataPacket(
new BlockPos(pos.getBlockX(), pos.getBlockY(), pos.getBlockZ()),
BlockEntityType.STRUCTURE_BLOCK,
NBTConverter.toNative(nbtData)
));
}
}

View File

@ -31,10 +31,9 @@
import com.sk89q.worldedit.extension.platform.Capability;
import com.sk89q.worldedit.extension.platform.Platform;
import com.sk89q.worldedit.extension.platform.PlatformManager;
import com.sk89q.worldedit.forge.net.handler.InternalPacketHandler;
import com.sk89q.worldedit.forge.net.handler.WECUIPacketHandler;
import com.sk89q.worldedit.forge.net.packet.LeftClickAirEventMessage;
import com.sk89q.worldedit.internal.anvil.ChunkDeleter;
import com.sk89q.worldedit.internal.event.InteractionDebouncer;
import com.sk89q.worldedit.internal.util.LogManagerCompat;
import com.sk89q.worldedit.util.Direction;
import com.sk89q.worldedit.util.Location;
@ -48,6 +47,7 @@
import com.sk89q.worldedit.world.item.ItemCategory;
import com.sk89q.worldedit.world.item.ItemType;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Holder;
import net.minecraft.core.HolderSet;
import net.minecraft.core.Registry;
@ -58,13 +58,13 @@
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.tags.TagKey;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.biome.Biome;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.event.CommandEvent;
import net.minecraftforge.event.RegisterCommandsEvent;
import net.minecraftforge.event.entity.player.PlayerEvent;
import net.minecraftforge.event.entity.player.PlayerInteractEvent;
import net.minecraftforge.event.entity.player.PlayerInteractEvent.LeftClickEmpty;
import net.minecraftforge.event.server.ServerAboutToStartEvent;
import net.minecraftforge.event.server.ServerStartedEvent;
import net.minecraftforge.event.server.ServerStoppingEvent;
@ -78,7 +78,6 @@
import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
import net.minecraftforge.fml.loading.FMLPaths;
import net.minecraftforge.network.NetworkConstants;
import net.minecraftforge.registries.ForgeRegistries;
import org.apache.logging.log4j.Logger;
import org.enginehub.piston.Command;
@ -90,6 +89,7 @@
import java.nio.file.Path;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.function.Supplier;
import java.util.stream.Collectors;
@ -113,6 +113,7 @@ public class ForgeWorldEdit {
public static ForgeWorldEdit inst;
private InteractionDebouncer debouncer;
private ForgePlatform platform;
private ForgeConfiguration config;
private Path workingDir;
@ -136,7 +137,7 @@ public ForgeWorldEdit() {
ModLoadingContext.get(),
IExtensionPoint.DisplayTest.class,
(Supplier<?>) () -> new IExtensionPoint.DisplayTest(
() -> NetworkConstants.IGNORESERVERONLY,
() -> IExtensionPoint.DisplayTest.IGNORESERVERONLY,
(a, b) -> true
)
);
@ -161,13 +162,13 @@ private void init(FMLCommonSetupEvent event) {
setupPlatform();
WECUIPacketHandler.init();
InternalPacketHandler.init();
LOGGER.info("WorldEdit for Forge (version " + getInternalVersion() + ") is loaded");
}
private void setupPlatform() {
this.platform = new ForgePlatform(this);
debouncer = new InteractionDebouncer(platform);
WorldEdit.getInstance().getPlatformManager().register(platform);
@ -293,61 +294,100 @@ public void serverStarted(ServerStartedEvent event) {
WorldEdit.getInstance().getEventBus().post(new PlatformReadyEvent(platform));
}
private boolean skipEvents() {
return platform == null || !platform.isHookingEvents();
}
private boolean skipInteractionEvent(Player player, InteractionHand hand) {
return skipEvents() || hand != InteractionHand.MAIN_HAND || player.level().isClientSide || !(player instanceof ServerPlayer);
}
@SubscribeEvent
public void onPlayerInteract(PlayerInteractEvent event) {
if (platform == null) {
public void onLeftClickBlock(PlayerInteractEvent.LeftClickBlock event) {
if (skipInteractionEvent(event.getEntity(), event.getHand()) || event.getUseItem() == Event.Result.DENY) {
return;
}
if (!platform.isHookingEvents()) {
return; // We have to be told to catch these events
}
ServerPlayer playerEntity = (ServerPlayer) event.getEntity();
WorldEdit we = WorldEdit.getInstance();
ForgePlayer player = adaptPlayer(playerEntity);
ForgeWorld world = getWorld((ServerLevel) playerEntity.level());
Direction direction = ForgeAdapter.adaptEnumFacing(event.getFace());
if (event.getLevel().isClientSide && event instanceof LeftClickEmpty) {
// catch LCE, pass it to server
InternalPacketHandler.getHandler().sendToServer(LeftClickAirEventMessage.INSTANCE);
BlockPos blockPos = event.getPos();
Location pos = new Location(world, blockPos.getX(), blockPos.getY(), blockPos.getZ());
boolean result = we.handleBlockLeftClick(player, pos, direction) || we.handleArmSwing(player);
debouncer.setLastInteraction(player, result);
if (result) {
event.setCanceled(true);
}
}
@SubscribeEvent
public void onRightClickBlock(PlayerInteractEvent.RightClickBlock event) {
if (skipInteractionEvent(event.getEntity(), event.getHand()) || event.getUseItem() == Event.Result.DENY) {
return;
}
boolean isLeftDeny = event instanceof PlayerInteractEvent.LeftClickBlock lcb
&& lcb.getUseItem() == Event.Result.DENY;
boolean isRightDeny = event instanceof PlayerInteractEvent.RightClickBlock rcb
&& rcb.getUseItem() == Event.Result.DENY;
if (isLeftDeny || isRightDeny || event.getEntity().level().isClientSide || event.getHand() == InteractionHand.OFF_HAND) {
ServerPlayer playerEntity = (ServerPlayer) event.getEntity();
WorldEdit we = WorldEdit.getInstance();
ForgePlayer player = adaptPlayer(playerEntity);
ForgeWorld world = getWorld((ServerLevel) playerEntity.level());
Direction direction = ForgeAdapter.adaptEnumFacing(event.getFace());
BlockPos blockPos = event.getPos();
Location pos = new Location(world, blockPos.getX(), blockPos.getY(), blockPos.getZ());
boolean result = we.handleBlockRightClick(player, pos, direction) || we.handleRightClick(player);
debouncer.setLastInteraction(player, result);
if (result) {
event.setCanceled(true);
}
}
public void onLeftClickAir(ServerPlayer playerEntity, InteractionHand hand) {
if (skipInteractionEvent(playerEntity, hand)) {
return;
}
WorldEdit we = WorldEdit.getInstance();
ForgePlayer player = adaptPlayer((ServerPlayer) event.getEntity());
ForgeWorld world = getWorld((ServerLevel) event.getEntity().level());
Direction direction = ForgeAdapter.adaptEnumFacing(event.getFace());
ForgePlayer player = adaptPlayer(playerEntity);
if (event instanceof PlayerInteractEvent.LeftClickEmpty) {
we.handleArmSwing(player); // this event cannot be canceled
} else if (event instanceof PlayerInteractEvent.LeftClickBlock) {
Location pos = new Location(world, event.getPos().getX(), event.getPos().getY(), event.getPos().getZ());
Optional<Boolean> previousResult = debouncer.getDuplicateInteractionResult(player);
if (previousResult.isPresent()) {
return;
}
if (we.handleBlockLeftClick(player, pos, direction)) {
boolean result = we.handleArmSwing(player);
debouncer.setLastInteraction(player, result);
}
@SubscribeEvent
public void onRightClickItem(PlayerInteractEvent.RightClickItem event) {
if (skipInteractionEvent(event.getEntity(), event.getHand())) {
return;
}
ServerPlayer playerEntity = (ServerPlayer) event.getEntity();
WorldEdit we = WorldEdit.getInstance();
ForgePlayer player = adaptPlayer(playerEntity);
Optional<Boolean> previousResult = debouncer.getDuplicateInteractionResult(player);
if (previousResult.isPresent()) {
if (previousResult.get()) {
event.setCanceled(true);
}
return;
}
if (we.handleArmSwing(player)) {
event.setCanceled(true);
}
} else if (event instanceof PlayerInteractEvent.RightClickBlock) {
Location pos = new Location(world, event.getPos().getX(), event.getPos().getY(), event.getPos().getZ());
boolean result = we.handleRightClick(player);
debouncer.setLastInteraction(player, result);
if (we.handleBlockRightClick(player, pos, direction)) {
event.setCanceled(true);
}
if (we.handleRightClick(player)) {
event.setCanceled(true);
}
} else if (event instanceof PlayerInteractEvent.RightClickItem) {
if (we.handleRightClick(player)) {
event.setCanceled(true);
}
if (result) {
event.setCanceled(true);
}
}
@ -370,6 +410,8 @@ public void onCommandEvent(CommandEvent event) throws CommandSyntaxException {
@SubscribeEvent
public void onPlayerLogOut(PlayerEvent.PlayerLoggedOutEvent event) {
if (event.getEntity() instanceof ServerPlayer player) {
debouncer.clearInteraction(adaptPlayer(player));
WorldEdit.getInstance().getEventBus()
.post(new SessionIdleEvent(new ForgePlayer.SessionKeyImpl(player)));
}

View File

@ -20,8 +20,11 @@
package com.sk89q.worldedit.forge;
import com.mojang.authlib.GameProfile;
import net.minecraft.server.level.ClientInformation;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.MenuProvider;
import net.minecraft.world.entity.HumanoidArm;
import net.minecraft.world.entity.player.ChatVisiblity;
import net.minecraftforge.common.util.FakePlayer;
import java.util.OptionalInt;
@ -29,11 +32,13 @@
import javax.annotation.Nullable;
public class WorldEditFakePlayer extends FakePlayer {
private static final GameProfile FAKE_GAME_PROFILE = new GameProfile(UUID.nameUUIDFromBytes("worldedit".getBytes()), "[WorldEdit]");
private static final ClientInformation FAKE_CLIENT_INFO = new ClientInformation(
"en_US", 16, ChatVisiblity.FULL, true, 0, HumanoidArm.LEFT, false, false
);
public WorldEditFakePlayer(ServerLevel world) {
super(world, FAKE_GAME_PROFILE);
super(world, FAKE_GAME_PROFILE, FAKE_CLIENT_INFO);
}
@Override

View File

@ -17,16 +17,15 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.fabric.mixin;
package com.sk89q.worldedit.forge.mixin;
import net.minecraft.world.level.storage.DerivedLevelData;
import net.minecraft.world.level.storage.ServerLevelData;
import net.minecraft.server.level.ServerPlayerGameMode;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
@Mixin(DerivedLevelData.class)
public interface AccessorDerivedLevelData extends ServerLevelData {
@Mixin(ServerPlayerGameMode.class)
public interface AccessorServerPlayerGameMode {
@Accessor
ServerLevelData getWrapped();
@Accessor("isDestroyingBlock")
boolean isDestroyingBlock();
}

View File

@ -0,0 +1,59 @@
/*
* 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.forge.mixin;
import com.sk89q.worldedit.forge.ForgeWorldEdit;
import net.minecraft.network.protocol.game.ServerboundPlayerActionPacket;
import net.minecraft.network.protocol.game.ServerboundSwingPacket;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.server.network.ServerGamePacketListenerImpl;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(ServerGamePacketListenerImpl.class)
public class MixinServerGamePacketListenerImpl {
@Shadow
public ServerPlayer player;
private int ignoreSwingPackets;
@Inject(method = "handleAnimate", at = @At("HEAD"))
private void onAnimate(ServerboundSwingPacket packet, CallbackInfo ci) {
if (!((AccessorServerPlayerGameMode) this.player.gameMode).isDestroyingBlock()) {
if (this.ignoreSwingPackets > 0) {
this.ignoreSwingPackets--;
} else if (ForgeWorldEdit.inst != null) {
ForgeWorldEdit.inst.onLeftClickAir(this.player, packet.getHand());
}
}
}
@Inject(method = "handlePlayerAction", at = @At("HEAD"))
private void onAction(ServerboundPlayerActionPacket packet, CallbackInfo ci) {
switch (packet.getAction()) {
case DROP_ITEM, DROP_ALL_ITEMS, START_DESTROY_BLOCK -> this.ignoreSwingPackets++;
default -> {
}
}
}
}

View File

@ -1,43 +0,0 @@
/*
* 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.forge.net.handler;
import com.sk89q.worldedit.forge.net.packet.LeftClickAirEventMessage;
import com.sk89q.worldedit.forge.net.packet.LeftClickAirEventMessage.Handler;
import net.minecraftforge.network.simple.SimpleChannel;
public final class InternalPacketHandler {
private static final int PROTOCOL_VERSION = 1;
private static final SimpleChannel HANDLER = PacketHandlerUtil
.buildLenientHandler("internal", PROTOCOL_VERSION)
.simpleChannel();
private InternalPacketHandler() {
}
public static void init() {
HANDLER.registerMessage(0, LeftClickAirEventMessage.class,
LeftClickAirEventMessage::encode, LeftClickAirEventMessage::decode, Handler::handle);
}
public static SimpleChannel getHandler() {
return HANDLER;
}
}

View File

@ -21,28 +21,27 @@
import com.sk89q.worldedit.forge.ForgeWorldEdit;
import net.minecraft.resources.ResourceLocation;
import net.minecraftforge.network.NetworkRegistry;
import java.util.function.Predicate;
import net.minecraftforge.network.Channel;
import net.minecraftforge.network.ChannelBuilder;
final class PacketHandlerUtil {
private PacketHandlerUtil() {
}
static NetworkRegistry.ChannelBuilder buildLenientHandler(String id, int protocolVersion) {
final String verStr = Integer.toString(protocolVersion);
final Predicate<String> validator = validateLenient(verStr);
return NetworkRegistry.ChannelBuilder
static ChannelBuilder buildLenientHandler(String id, int protocolVersion) {
final Channel.VersionTest validator = validateLenient(protocolVersion);
return ChannelBuilder
.named(new ResourceLocation(ForgeWorldEdit.MOD_ID, id))
.clientAcceptedVersions(validator)
.serverAcceptedVersions(validator)
.networkProtocolVersion(() -> verStr);
.networkProtocolVersion(protocolVersion);
}
private static Predicate<String> validateLenient(String protocolVersion) {
return remoteVersion ->
protocolVersion.equals(remoteVersion)
|| NetworkRegistry.ABSENT.version().equals(remoteVersion)
|| NetworkRegistry.ACCEPTVANILLA.equals(remoteVersion);
private static Channel.VersionTest validateLenient(int protocolVersion) {
return (status, remoteVersion) ->
protocolVersion == remoteVersion
// These two ignore protocolVersion anyway so it doesn't matter what it is
|| Channel.VersionTest.ACCEPT_MISSING.accepts(status, protocolVersion)
|| Channel.VersionTest.ACCEPT_VANILLA.accepts(status, protocolVersion);
}
}

View File

@ -22,9 +22,11 @@
import com.sk89q.worldedit.LocalSession;
import com.sk89q.worldedit.forge.ForgePlayer;
import com.sk89q.worldedit.forge.ForgeWorldEdit;
import net.minecraft.network.Connection;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.server.level.ServerPlayer;
import net.minecraftforge.network.NetworkEvent;
import net.minecraftforge.network.event.EventNetworkChannel;
import net.minecraftforge.event.network.CustomPayloadEvent;
import net.minecraftforge.network.EventNetworkChannel;
import java.nio.charset.StandardCharsets;
@ -43,12 +45,16 @@ public static void init() {
HANDLER.addListener(WECUIPacketHandler::onPacketData);
}
public static void onPacketData(NetworkEvent.ClientCustomPayloadEvent event) {
ServerPlayer player = event.getSource().get().getSender();
public static void onPacketData(CustomPayloadEvent event) {
ServerPlayer player = event.getSource().getSender();
LocalSession session = ForgeWorldEdit.inst.getSession(player);
String text = event.getPayload().toString(StandardCharsets.UTF_8);
final ForgePlayer actor = adaptPlayer(player);
session.handleCUIInitializationMessage(text, actor);
}
public static void send(Connection connection, FriendlyByteBuf friendlyByteBuf) {
HANDLER.send(friendlyByteBuf, connection);
}
}

View File

@ -1,51 +0,0 @@
/*
* 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.forge.net.packet;
import com.sk89q.worldedit.forge.ForgeWorldEdit;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraftforge.event.entity.player.PlayerInteractEvent.LeftClickEmpty;
import net.minecraftforge.network.NetworkEvent;
import java.util.Objects;
import java.util.function.Supplier;
public class LeftClickAirEventMessage {
public static final LeftClickAirEventMessage INSTANCE = new LeftClickAirEventMessage();
public static final class Handler {
public static void handle(final LeftClickAirEventMessage message, Supplier<NetworkEvent.Context> ctx) {
NetworkEvent.Context context = ctx.get();
context.enqueueWork(() -> ForgeWorldEdit.inst.onPlayerInteract(new LeftClickEmpty(Objects.requireNonNull(context.getSender()))));
}
}
public static LeftClickAirEventMessage decode(FriendlyByteBuf buf) {
return INSTANCE;
}
public static void encode(LeftClickAirEventMessage msg, FriendlyByteBuf buf) {
}
private LeftClickAirEventMessage() {
}
}

View File

@ -7,3 +7,5 @@ public net.minecraft.server.level.ServerChunkCache m_8456_(IILnet/minecraft/worl
public net.minecraft.world.level.chunk.ChunkBiomeContainer f_62112_ # biomes
public-f net.minecraft.world.level.storage.PrimaryLevelData f_244409_ # worldOptions
public net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket <init>(Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/block/entity/BlockEntityType;Lnet/minecraft/nbt/CompoundTag;)V # constructor

View File

@ -3,7 +3,9 @@
"package": "com.sk89q.worldedit.forge.mixin",
"compatibilityLevel": "JAVA_17",
"mixins": [
"MixinLevelChunkSetBlockHook"
"AccessorServerPlayerGameMode",
"MixinLevelChunkSetBlockHook",
"MixinServerGamePacketListenerImpl"
],
"server": [
],

View File

@ -188,6 +188,11 @@ public Set<SideEffect> getSupportedSideEffects() {
);
}
@Override
public long getTickCount() {
return Sponge.server().runningTimeTicks().ticks();
}
@Override
public Collection<Actor> getConnectedUsers() {
return Sponge.server().onlinePlayers().stream().map(SpongePlayer::new).collect(toList());

View File

@ -35,6 +35,7 @@
import com.sk89q.worldedit.extension.platform.PlatformManager;
import com.sk89q.worldedit.internal.anvil.ChunkDeleter;
import com.sk89q.worldedit.internal.command.CommandUtil;
import com.sk89q.worldedit.internal.event.InteractionDebouncer;
import com.sk89q.worldedit.sponge.config.SpongeConfiguration;
import com.sk89q.worldedit.world.biome.BiomeCategory;
import com.sk89q.worldedit.world.biome.BiomeType;
@ -48,7 +49,6 @@
import org.spongepowered.api.Sponge;
import org.spongepowered.api.block.BlockSnapshot;
import org.spongepowered.api.block.BlockType;
import org.spongepowered.api.block.BlockTypes;
import org.spongepowered.api.block.entity.BlockEntity;
import org.spongepowered.api.block.entity.CommandBlock;
import org.spongepowered.api.command.Command;
@ -57,8 +57,11 @@
import org.spongepowered.api.command.CommandResult;
import org.spongepowered.api.command.parameter.ArgumentReader;
import org.spongepowered.api.config.ConfigDir;
import org.spongepowered.api.data.type.HandTypes;
import org.spongepowered.api.entity.living.player.server.ServerPlayer;
import org.spongepowered.api.event.EventContextKeys;
import org.spongepowered.api.event.Listener;
import org.spongepowered.api.event.action.InteractEvent;
import org.spongepowered.api.event.block.InteractBlockEvent;
import org.spongepowered.api.event.filter.cause.Root;
import org.spongepowered.api.event.item.inventory.InteractItemEvent;
@ -109,6 +112,7 @@ public static SpongeWorldEdit inst() {
private final SpongeConfiguration config;
private final Path workingDir;
private InteractionDebouncer debouncer;
private SpongePermissionsProvider provider;
private SpongePlatform platform;
@ -130,6 +134,8 @@ public SpongeWorldEdit(Logger logger,
@Listener
public void onPluginConstruction(ConstructPluginEvent event) {
this.platform = new SpongePlatform(this);
debouncer = new InteractionDebouncer(platform);
WorldEdit.getInstance().getPlatformManager().register(platform);
this.provider = new SpongePermissionsProvider();
@ -287,84 +293,117 @@ public List<CommandCompletion> complete(CommandCause cause, ArgumentReader.Mutab
);
}
private boolean isHookingEvents() {
return platform != null && platform.isHookingEvents();
private boolean skipEvents() {
return platform == null || !platform.isHookingEvents();
}
private boolean skipInteractionEvent(InteractEvent event) {
return skipEvents() || event.context().get(EventContextKeys.USED_HAND).orElse(null) != HandTypes.MAIN_HAND.get();
}
@Listener
public void onPlayerItemInteract(InteractItemEvent.Secondary event, @Root ServerPlayer spongePlayer) {
if (!isHookingEvents()) {
public void onPlayerInteractItemPrimary(InteractItemEvent.Primary event, @Root ServerPlayer spongePlayer) {
if (skipInteractionEvent(event)) {
return;
}
WorldEdit we = WorldEdit.getInstance();
SpongePlayer player = SpongeAdapter.adapt(spongePlayer);
if (we.handleRightClick(player)) {
Optional<Boolean> previousResult = debouncer.getDuplicateInteractionResult(player);
if (previousResult.isPresent()) {
return;
}
boolean result = we.handleArmSwing(player);
debouncer.setLastInteraction(player, result);
}
@Listener
public void onPlayerInteractItemSecondary(InteractItemEvent.Secondary event, @Root ServerPlayer spongePlayer) {
if (skipInteractionEvent(event)) {
return;
}
WorldEdit we = WorldEdit.getInstance();
SpongePlayer player = SpongeAdapter.adapt(spongePlayer);
Optional<Boolean> previousResult = debouncer.getDuplicateInteractionResult(player);
if (previousResult.isPresent()) {
if (previousResult.get()) {
event.setCancelled(true);
}
return;
}
boolean result = we.handleRightClick(player);
debouncer.setLastInteraction(player, result);
if (result) {
event.setCancelled(true);
}
}
@Listener
public void onPlayerInteract(InteractBlockEvent event, @Root ServerPlayer spongePlayer) {
if (platform == null) {
public void onPlayerInteractBlockPrimary(InteractBlockEvent.Primary.Start event, @Root ServerPlayer spongePlayer) {
if (skipInteractionEvent(event)) {
return;
}
if (!platform.isHookingEvents()) {
return; // We have to be told to catch these events
}
WorldEdit we = WorldEdit.getInstance();
SpongePlayer player = SpongeAdapter.adapt(spongePlayer);
BlockSnapshot targetBlock = event.block();
Optional<ServerLocation> optLoc = targetBlock.location();
BlockType interactedType = targetBlock.state().type();
if (event instanceof InteractBlockEvent.Primary.Start) {
InteractBlockEvent.Primary.Start eventCast = ((InteractBlockEvent.Primary.Start) event);
if (interactedType != BlockTypes.AIR.get()) {
if (!optLoc.isPresent()) {
return;
}
ServerLocation loc = optLoc.get();
com.sk89q.worldedit.util.Location pos = SpongeAdapter.adapt(
loc, Vector3d.ZERO
);
if (we.handleBlockLeftClick(player, pos, SpongeAdapter.adapt(eventCast.targetSide()))) {
eventCast.setCancelled(true);
}
}
if (we.handleArmSwing(player)) {
eventCast.setCancelled(true);
}
} else if (event instanceof InteractBlockEvent.Secondary) {
if (!optLoc.isPresent()) {
return;
}
InteractBlockEvent.Secondary eventCast = ((InteractBlockEvent.Secondary) event);
boolean result = false;
if (optLoc.isPresent()) {
ServerLocation loc = optLoc.get();
com.sk89q.worldedit.util.Location pos = SpongeAdapter.adapt(
loc, Vector3d.ZERO
);
com.sk89q.worldedit.util.Location pos = SpongeAdapter.adapt(loc, Vector3d.ZERO);
if (we.handleBlockRightClick(player, pos, SpongeAdapter.adapt(eventCast.targetSide()))) {
eventCast.setCancelled(true);
}
result = we.handleBlockLeftClick(player, pos, SpongeAdapter.adapt(event.targetSide()));
}
if (we.handleRightClick(player)) {
eventCast.setCancelled(true);
}
result = we.handleArmSwing(player) || result;
debouncer.setLastInteraction(player, result);
if (result) {
event.setCancelled(true);
}
}
@Listener
public void onPlayerInteractBlockSecondary(InteractBlockEvent.Secondary event, @Root ServerPlayer spongePlayer) {
if (skipInteractionEvent(event)) {
return;
}
WorldEdit we = WorldEdit.getInstance();
SpongePlayer player = SpongeAdapter.adapt(spongePlayer);
BlockSnapshot targetBlock = event.block();
Optional<ServerLocation> optLoc = targetBlock.location();
boolean result = false;
if (optLoc.isPresent()) {
ServerLocation loc = optLoc.get();
com.sk89q.worldedit.util.Location pos = SpongeAdapter.adapt(loc, Vector3d.ZERO);
result = we.handleBlockRightClick(player, pos, SpongeAdapter.adapt(event.targetSide()));
}
result = we.handleRightClick(player) || result;
debouncer.setLastInteraction(player, result);
if (result) {
event.setCancelled(true);
}
}
@Listener
public void onPlayerQuit(ServerSideConnectionEvent.Disconnect event) {
debouncer.clearInteraction(SpongeAdapter.adapt(event.player()));
WorldEdit.getInstance().getEventBus()
.post(new SessionIdleEvent(new SpongePlayer.SessionKeyImpl(event.player())));
}