feat!: reworked replace mode to work server side, added propper stair replacement support

Signed-off-by: moonleay <contact@moonleay.net>
This commit is contained in:
moonleay 2024-05-04 02:35:49 +02:00
parent c321746b08
commit dca01b275f
Signed by: moonleay
GPG key ID: 82667543CCD715FB
4 changed files with 108 additions and 52 deletions

View file

@ -18,70 +18,50 @@
package net.moonleay.gimbal.mixin;
import net.minecraft.block.BlockState;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.network.ClientPlayerEntity;
import net.minecraft.client.network.ClientPlayerInteractionManager;
import net.minecraft.client.particle.ParticleManager;
import net.minecraft.network.packet.c2s.play.PlayerActionC2SPacket;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.ItemPlacementContext;
import net.minecraft.item.ItemStack;
import net.minecraft.item.ItemUsageContext;
import net.minecraft.util.Hand;
import net.minecraft.util.hit.BlockHitResult;
import net.minecraft.util.hit.HitResult;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.moonleay.gimbal.client.editor.ClientEditor;
import net.minecraft.world.World;
import net.moonleay.gimbal.editor.ServerEditorManager;
import net.moonleay.gimbal.editor.state.mode.Capability;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Mutable;
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(MinecraftClient.class)
public abstract class ReplaceModeMixin {
import java.util.UUID;
@Mixin(ItemPlacementContext.class)
public abstract class ReplaceModeMixin extends ItemUsageContext {
@Shadow protected abstract void handleBlockBreaking(boolean breaking);
@Shadow
protected boolean canReplaceExisting;
@Shadow @Nullable public ClientPlayerInteractionManager interactionManager;
@Mutable
@Shadow
@Final
private BlockPos placementPos;
@Shadow @Final public ParticleManager particleManager;
@Shadow @Nullable public ClientPlayerEntity player;
@Shadow @Nullable public HitResult crosshairTarget;
@Inject(method = "doItemUse", at = @At("HEAD"))
private void replaceBlock(CallbackInfo ci) {
assert this.player != null;
if (!this.player.isCreative())
return;
// Check if should run
if (!ClientEditor.INSTANCE.shouldClient(Capability.REPLACE))
return; // Mode is not REPLACE, ignore
MinecraftClient client = MinecraftClient.getInstance();
assert this.interactionManager != null;
if (!this.interactionManager.getCurrentGameMode().isCreative())
return;
if (!(this.crosshairTarget instanceof BlockHitResult blockHitResult))
return;
if (blockHitResult == null)
return;
// Gather data
BlockPos pos = blockHitResult.getBlockPos();
Direction direction = blockHitResult.getSide();
BlockState blockState = client.world.getBlockState(pos);
// Start sending shit
client.getTutorialManager().onBlockBreaking(client.world, pos, blockState, 1.0F);
this.interactionManager.sendSequencedPacket(client.world, sequence -> {
this.interactionManager.breakBlock(pos);
return new PlayerActionC2SPacket(PlayerActionC2SPacket.Action.START_DESTROY_BLOCK, pos, direction, sequence);
});
this.player.swingHand(Hand.MAIN_HAND);
public ReplaceModeMixin(PlayerEntity player, Hand hand, BlockHitResult hit) {
super(player, hand, hit);
}
@Inject(method = "<init>(Lnet/minecraft/world/World;Lnet/minecraft/entity/player/PlayerEntity;Lnet/minecraft/util/Hand;Lnet/minecraft/item/ItemStack;Lnet/minecraft/util/hit/BlockHitResult;)V", at = @At(value = "RETURN"))
private void func(World world, PlayerEntity playerEntity, Hand hand, ItemStack itemStack, BlockHitResult blockHitResult, CallbackInfo ci) {
if (playerEntity == null)
return;
UUID id = playerEntity.getGameProfile().getId();
if (!ServerEditorManager.INSTANCE.shouldPlayer(id, Capability.REPLACE))
return;
this.canReplaceExisting = true;
this.placementPos = blockHitResult.getBlockPos();
}
}

View file

@ -0,0 +1,74 @@
/*
* Gimbal
* Copyright (C) 2024 moonleay
*
* 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 net.moonleay.gimbal.mixin;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.block.StairsBlock;
import net.minecraft.item.BlockItem;
import net.minecraft.item.Item;
import net.minecraft.item.ItemPlacementContext;
import net.moonleay.gimbal.editor.ServerEditorManager;
import net.moonleay.gimbal.editor.state.mode.Capability;
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.Redirect;
import java.util.UUID;
import static net.minecraft.block.StairsBlock.*;
@Mixin(BlockItem.class)
public abstract class ReplaceStateUpdaterMixin extends Item {
public ReplaceStateUpdaterMixin(Settings settings) {
super(settings);
}
@Shadow
protected abstract boolean place(ItemPlacementContext context, BlockState state);
@Redirect(method = "place(Lnet/minecraft/item/ItemPlacementContext;)Lnet/minecraft/util/ActionResult;", at = @At(value = "INVOKE", target = "Lnet/minecraft/item/BlockItem;place(Lnet/minecraft/item/ItemPlacementContext;Lnet/minecraft/block/BlockState;)Z"))
private boolean func(BlockItem instance, ItemPlacementContext context, BlockState state) {
if (context.getPlayer() == null)
return this.place(context, state);
UUID id = context.getPlayer().getGameProfile().getId();
if (!ServerEditorManager.INSTANCE.shouldPlayer(id, Capability.REPLACE))
return this.place(context, state);
BlockState oldState = context.getWorld().getBlockState(context.getBlockPos());
Block targetBl = oldState.getBlock();
if (state.getBlock() instanceof StairsBlock && targetBl instanceof StairsBlock targetStair) {
// Block and item is stairs, parse Stairs data
return this.place(context, copyStairState(oldState, instance));
}
return this.place(context, state);
}
@Unique
public BlockState copyStairState(BlockState targetState, BlockItem item) {
BlockState blockState = item.getBlock().getDefaultState()
.with(FACING, targetState.get(FACING))
.with(HALF, targetState.get(HALF))
.with(WATERLOGGED, targetState.get(WATERLOGGED));
return blockState.with(SHAPE, targetState.get(SHAPE));
}
}

View file

@ -2,3 +2,4 @@ accessWidener v2 named
accessible method net/minecraft/client/MinecraftClient handleBlockBreaking (Z)V
accessible method net/minecraft/client/network/ClientPlayerInteractionManager sendSequencedPacket (Lnet/minecraft/client/world/ClientWorld;Lnet/minecraft/client/network/SequencedPacketCreator;)V
accessible field net/minecraft/state/State owner Ljava/lang/Object;

View file

@ -6,15 +6,16 @@
"mixins": [
"ForcePlaceMixin",
"NoBlockUpdatesMixin",
"NoClipMixin"
"NoClipMixin",
"ReplaceModeMixin",
"ReplaceStateUpdaterMixin"
],
"client": [
"BulldozerMixin",
"BulldozerMixin2",
"HudMixin",
"NoClipCameraFixMixin",
"NormalModeMixin",
"ReplaceModeMixin"
"NormalModeMixin"
],
"injectors": {
"defaultRequire": 1