mirror of
				https://github.com/GeyserMC/Geyser.git
				synced 2024-08-14 23:57:35 +00:00 
			
		
		
		
	Fix: Piston listener on Fabric/NeoForge (#4899)
* Fix: Sticky pistons not retracting on Geyser-Spigot/turning visually into normal pistons on all other platforms * Initial attempt: Mod piston listener * fix piston retracting
This commit is contained in:
		
							parent
							
								
									45f96a03e7
								
							
						
					
					
						commit
						efe2736635
					
				
					 7 changed files with 148 additions and 12 deletions
				
			
		| 
						 | 
				
			
			@ -16,7 +16,8 @@ afterEvaluate {
 | 
			
		|||
dependencies {
 | 
			
		||||
    api(projects.core)
 | 
			
		||||
    compileOnly(libs.mixin)
 | 
			
		||||
    compileOnly(libs.mixinextras)
 | 
			
		||||
 | 
			
		||||
    // Only here to suppress "unknown enum constant EnvType.CLIENT" warnings. DO NOT USE!
 | 
			
		||||
    compileOnly(libs.fabric.loader)
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,127 @@
 | 
			
		|||
/*
 | 
			
		||||
 * Copyright (c) 2024 GeyserMC. http://geysermc.org
 | 
			
		||||
 *
 | 
			
		||||
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
			
		||||
 * of this software and associated documentation files (the "Software"), to deal
 | 
			
		||||
 * in the Software without restriction, including without limitation the rights
 | 
			
		||||
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | 
			
		||||
 * copies of the Software, and to permit persons to whom the Software is
 | 
			
		||||
 * furnished to do so, subject to the following conditions:
 | 
			
		||||
 *
 | 
			
		||||
 * The above copyright notice and this permission notice shall be included in
 | 
			
		||||
 * all copies or substantial portions of the Software.
 | 
			
		||||
 *
 | 
			
		||||
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
			
		||||
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
			
		||||
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | 
			
		||||
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
			
		||||
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
			
		||||
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 | 
			
		||||
 * THE SOFTWARE.
 | 
			
		||||
 *
 | 
			
		||||
 * @author GeyserMC
 | 
			
		||||
 * @link https://github.com/GeyserMC/Geyser
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
package org.geysermc.geyser.platform.mod.mixin.server;
 | 
			
		||||
 | 
			
		||||
import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
 | 
			
		||||
import com.llamalad7.mixinextras.sugar.Share;
 | 
			
		||||
import com.llamalad7.mixinextras.sugar.ref.LocalRef;
 | 
			
		||||
import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap;
 | 
			
		||||
import it.unimi.dsi.fastutil.objects.Object2ObjectMap;
 | 
			
		||||
import net.minecraft.core.BlockPos;
 | 
			
		||||
import net.minecraft.core.Direction;
 | 
			
		||||
import net.minecraft.world.entity.player.Player;
 | 
			
		||||
import net.minecraft.world.level.Level;
 | 
			
		||||
import net.minecraft.world.level.block.Block;
 | 
			
		||||
import net.minecraft.world.level.block.piston.PistonBaseBlock;
 | 
			
		||||
import net.minecraft.world.level.block.state.BlockState;
 | 
			
		||||
import org.cloudburstmc.math.vector.Vector3i;
 | 
			
		||||
import org.geysermc.geyser.GeyserImpl;
 | 
			
		||||
import org.geysermc.geyser.session.GeyserSession;
 | 
			
		||||
import org.geysermc.geyser.session.cache.PistonCache;
 | 
			
		||||
import org.geysermc.geyser.translator.level.block.entity.PistonBlockEntity;
 | 
			
		||||
import org.geysermc.mcprotocollib.protocol.data.game.level.block.value.PistonValueType;
 | 
			
		||||
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 org.spongepowered.asm.mixin.injection.At;
 | 
			
		||||
import org.spongepowered.asm.mixin.injection.Inject;
 | 
			
		||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
 | 
			
		||||
 | 
			
		||||
import java.util.HashMap;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
import java.util.UUID;
 | 
			
		||||
 | 
			
		||||
@Mixin(PistonBaseBlock.class)
 | 
			
		||||
public class PistonBaseBlockMixin {
 | 
			
		||||
 | 
			
		||||
    @Shadow
 | 
			
		||||
    @Final
 | 
			
		||||
    private boolean isSticky;
 | 
			
		||||
 | 
			
		||||
    @ModifyExpressionValue(method = "moveBlocks",
 | 
			
		||||
        at = @At(value = "INVOKE", target = "Lcom/google/common/collect/Maps;newHashMap()Ljava/util/HashMap;")
 | 
			
		||||
    )
 | 
			
		||||
    private HashMap<BlockPos, BlockState> geyser$onMapCreate(HashMap<BlockPos, BlockState> original, @Share("pushBlocks") LocalRef<Map<BlockPos, BlockState>> localRef) {
 | 
			
		||||
        localRef.set(original);
 | 
			
		||||
        return original;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Inject(method = "moveBlocks",
 | 
			
		||||
        at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/block/piston/PistonStructureResolver;getToDestroy()Ljava/util/List;")
 | 
			
		||||
    )
 | 
			
		||||
    private void geyser$onBlocksMove(Level level, BlockPos blockPos, Direction direction, boolean isExtending, CallbackInfoReturnable<Boolean> cir, @Share("pushBlocks") LocalRef<Map<BlockPos, BlockState>> localRef) {
 | 
			
		||||
        PistonValueType type = isExtending ? PistonValueType.PUSHING : PistonValueType.PULLING;
 | 
			
		||||
        boolean sticky = this.isSticky;
 | 
			
		||||
 | 
			
		||||
        Object2ObjectMap<Vector3i, org.geysermc.geyser.level.block.type.BlockState> attachedBlocks = new Object2ObjectArrayMap<>();
 | 
			
		||||
        boolean blocksFilled = false;
 | 
			
		||||
 | 
			
		||||
        for (Map.Entry<UUID, GeyserSession> entry : GeyserImpl.getInstance().getSessionManager().getSessions().entrySet()) {
 | 
			
		||||
            Player player = level.getPlayerByUUID(entry.getKey());
 | 
			
		||||
            //noinspection resource
 | 
			
		||||
            if (player == null || !player.level().equals(level)) {
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
            GeyserSession session = entry.getValue();
 | 
			
		||||
 | 
			
		||||
            int dX = Math.abs(blockPos.getX() - player.getBlockX()) >> 4;
 | 
			
		||||
            int dZ = Math.abs(blockPos.getZ() - player.getBlockZ()) >> 4;
 | 
			
		||||
            if ((dX * dX + dZ * dZ) > session.getServerRenderDistance() * session.getServerRenderDistance()) {
 | 
			
		||||
                // Ignore pistons outside the player's render distance
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Trying to grab the blocks from the world like other platforms would result in the moving piston block
 | 
			
		||||
            // being returned instead.
 | 
			
		||||
            if (!blocksFilled) {
 | 
			
		||||
                Map<BlockPos, net.minecraft.world.level.block.state.BlockState> blocks = localRef.get();
 | 
			
		||||
                for (Map.Entry<BlockPos, BlockState> blockStateEntry : blocks.entrySet()) {
 | 
			
		||||
                    int blockStateId = Block.BLOCK_STATE_REGISTRY.getId(blockStateEntry.getValue());
 | 
			
		||||
                    org.geysermc.geyser.level.block.type.BlockState state = org.geysermc.geyser.level.block.type.BlockState.of(blockStateId);
 | 
			
		||||
                    attachedBlocks.put(geyser$fromBlockPos(blockStateEntry.getKey()), state);
 | 
			
		||||
                }
 | 
			
		||||
                blocksFilled = true;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            org.geysermc.geyser.level.physics.Direction orientation = org.geysermc.geyser.level.physics.Direction.VALUES[direction.ordinal()];
 | 
			
		||||
 | 
			
		||||
            Vector3i position = geyser$fromBlockPos(blockPos);
 | 
			
		||||
            session.executeInEventLoop(() -> {
 | 
			
		||||
                PistonCache pistonCache = session.getPistonCache();
 | 
			
		||||
                PistonBlockEntity blockEntity = pistonCache.getPistons().computeIfAbsent(position, pos ->
 | 
			
		||||
                    new PistonBlockEntity(session, position, orientation, sticky, !isExtending));
 | 
			
		||||
                blockEntity.setAction(type, attachedBlocks);
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Unique
 | 
			
		||||
    private static Vector3i geyser$fromBlockPos(BlockPos pos) {
 | 
			
		||||
        return Vector3i.from(pos.getX(), pos.getY(), pos.getZ());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -5,6 +5,7 @@
 | 
			
		|||
  "compatibilityLevel": "JAVA_17",
 | 
			
		||||
  "mixins": [
 | 
			
		||||
    "server.BlockPlaceMixin",
 | 
			
		||||
    "server.PistonBaseBlockMixin",
 | 
			
		||||
    "server.ServerConnectionListenerMixin"
 | 
			
		||||
  ],
 | 
			
		||||
  "server": [
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -129,7 +129,7 @@ public final class BlockRegistryPopulator {
 | 
			
		|||
                    NbtMapBuilder builder = vanillaBlockStates.get(i).toBuilder();
 | 
			
		||||
                    builder.remove("version"); // Remove all nbt tags which are not needed for differentiating states
 | 
			
		||||
                    builder.remove("name_hash"); // Quick workaround - was added in 1.19.20
 | 
			
		||||
                    builder.remove("network_id"); // Added in 1.19.80 - ????
 | 
			
		||||
                    builder.remove("network_id"); // Added in 1.19.80
 | 
			
		||||
                    builder.remove("block_id"); // Added in 1.20.60
 | 
			
		||||
                    //noinspection UnstableApiUsage
 | 
			
		||||
                    builder.putCompound("states", statesInterner.intern((NbtMap) builder.remove("states")));
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -37,7 +37,6 @@ import org.cloudburstmc.math.vector.Vector3i;
 | 
			
		|||
import org.cloudburstmc.nbt.NbtMap;
 | 
			
		||||
import org.cloudburstmc.nbt.NbtMapBuilder;
 | 
			
		||||
import org.cloudburstmc.protocol.bedrock.packet.UpdateBlockPacket;
 | 
			
		||||
import org.geysermc.geyser.api.util.PlatformType;
 | 
			
		||||
import org.geysermc.geyser.level.block.BlockStateValues;
 | 
			
		||||
import org.geysermc.geyser.level.block.Blocks;
 | 
			
		||||
import org.geysermc.geyser.level.block.property.Properties;
 | 
			
		||||
| 
						 | 
				
			
			@ -230,8 +229,8 @@ public class PistonBlockEntity {
 | 
			
		|||
        BlockState state = session.getGeyser().getWorldManager().blockAt(session, blockInFront);
 | 
			
		||||
        if (state.is(Blocks.PISTON_HEAD)) {
 | 
			
		||||
            ChunkUtils.updateBlock(session, Block.JAVA_AIR_ID, blockInFront);
 | 
			
		||||
        } else if ((session.getGeyser().getPlatformType() == PlatformType.SPIGOT || session.getErosionHandler().isActive()) && state.is(Blocks.AIR)) {
 | 
			
		||||
            // Spigot removes the piston head from the cache, but we need to send the block update ourselves
 | 
			
		||||
        } else if ((session.getGeyser().getWorldManager().hasOwnChunkCache() || session.getErosionHandler().isActive()) && state.is(Blocks.AIR)) {
 | 
			
		||||
            // The platform removes the piston head from the cache, but we need to send the block update ourselves
 | 
			
		||||
            ChunkUtils.updateBlock(session, Block.JAVA_AIR_ID, blockInFront);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -82,16 +82,22 @@ public class JavaBlockEventTranslator extends PacketTranslator<ClientboundBlockE
 | 
			
		|||
            Direction direction = Direction.fromPistonValue(pistonValue.getDirection());
 | 
			
		||||
            PistonCache pistonCache = session.getPistonCache();
 | 
			
		||||
 | 
			
		||||
            if (session.getGeyser().getPlatformType() == PlatformType.SPIGOT || session.getErosionHandler().isActive()) {
 | 
			
		||||
                // Mostly handled in the GeyserPistonEvents class
 | 
			
		||||
                // Retracting sticky pistons is an exception, since the event is not called on Spigot from 1.13.2 - 1.17.1
 | 
			
		||||
                // See https://github.com/PaperMC/Paper/blob/6fa1983e9ce177a4a412d5b950fd978620174777/patches/server/0304-Fire-BlockPistonRetractEvent-for-all-empty-pistons.patch
 | 
			
		||||
            if (session.getGeyser().getWorldManager().hasOwnChunkCache() || session.getErosionHandler().isActive()) {
 | 
			
		||||
                // Mostly handled in the GeyserPistonEvents class (Spigot) / the PistonBlockBaseMixin (Mod platforms)
 | 
			
		||||
                // However, the retracting event is not fully covered. (Spigot)
 | 
			
		||||
                // Mod platforms only handle pistons moving blocks; not the retracting of pistons.
 | 
			
		||||
                if (action == PistonValueType.PULLING || action == PistonValueType.CANCELLED_MID_PUSH) {
 | 
			
		||||
                    BlockState pistonBlock = session.getGeyser().getWorldManager().blockAt(session, position);
 | 
			
		||||
                    if (!isSticky(pistonBlock)) {
 | 
			
		||||
 | 
			
		||||
                    // Retracting sticky pistons is an exception, since the event is not called on Spigot from 1.13.2 - 1.17.1
 | 
			
		||||
                    // See https://github.com/PaperMC/Paper/blob/6fa1983e9ce177a4a412d5b950fd978620174777/patches/server/0304-Fire-BlockPistonRetractEvent-for-all-empty-pistons.patch
 | 
			
		||||
                    boolean isSticky = isSticky(pistonBlock);
 | 
			
		||||
                    if (session.getGeyser().getPlatformType() == PlatformType.SPIGOT && !isSticky) {
 | 
			
		||||
                        return;
 | 
			
		||||
                    }
 | 
			
		||||
                    if (action != PistonValueType.CANCELLED_MID_PUSH) {
 | 
			
		||||
 | 
			
		||||
                    // Only sticky pistons that don't pull any blocks are affected
 | 
			
		||||
                    if (action != PistonValueType.CANCELLED_MID_PUSH && isSticky) {
 | 
			
		||||
                        Vector3i blockInFrontPos = position.add(direction.getUnitVector());
 | 
			
		||||
                        int blockInFront = session.getGeyser().getWorldManager().getBlockAt(session, blockInFrontPos);
 | 
			
		||||
                        if (blockInFront != Block.JAVA_AIR_ID) {
 | 
			
		||||
| 
						 | 
				
			
			@ -99,7 +105,7 @@ public class JavaBlockEventTranslator extends PacketTranslator<ClientboundBlockE
 | 
			
		|||
                            return;
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    PistonBlockEntity blockEntity = pistonCache.getPistons().computeIfAbsent(position, pos -> new PistonBlockEntity(session, pos, direction, true, true));
 | 
			
		||||
                    PistonBlockEntity blockEntity = pistonCache.getPistons().computeIfAbsent(position, pos -> new PistonBlockEntity(session, pos, direction, isSticky, true));
 | 
			
		||||
                    if (blockEntity.getAction() != action) {
 | 
			
		||||
                        blockEntity.setAction(action, Object2ObjectMaps.emptyMap());
 | 
			
		||||
                    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -33,6 +33,7 @@ fabric-api = "0.100.1+1.21"
 | 
			
		|||
fabric-permissions = "0.2-SNAPSHOT"
 | 
			
		||||
neoforge-minecraft = "21.0.0-beta"
 | 
			
		||||
mixin = "0.8.5"
 | 
			
		||||
mixinextras = "0.3.5"
 | 
			
		||||
minecraft = "1.21"
 | 
			
		||||
 | 
			
		||||
# plugin versions
 | 
			
		||||
| 
						 | 
				
			
			@ -89,6 +90,7 @@ folia-api = { group = "dev.folia", name = "folia-api", version.ref = "folia" }
 | 
			
		|||
paper-mojangapi = { group = "io.papermc.paper", name = "paper-mojangapi", version.ref = "folia" }
 | 
			
		||||
 | 
			
		||||
mixin = { group = "org.spongepowered", name = "mixin", version.ref = "mixin" }
 | 
			
		||||
mixinextras = { module = "io.github.llamalad7:mixinextras-common", version.ref = "mixinextras" }
 | 
			
		||||
 | 
			
		||||
minecraft = { group = "com.mojang", name = "minecraft", version.ref = "minecraft" }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue