Skip to content

Commit

Permalink
feat: middle mouse to select blocks in build mode
Browse files Browse the repository at this point in the history
  • Loading branch information
sekwah41 committed Nov 15, 2024
1 parent b1885b3 commit 95683ad
Show file tree
Hide file tree
Showing 5 changed files with 184 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ public static void register(RegisterPayloadHandlersEvent event) {
UpdateFilterMessage.CODEC,
UpdateFilterMessage::handle
);
registrar.playToServer(
SetGamemodeBuildSlotPacket.TYPE,
SetGamemodeBuildSlotPacket.CODEC,
SetGamemodeBuildSlotPacket::handle
);

registrar.playBidirectional(
SetActiveMessage.TYPE,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package com.lovetropics.gamemodebuild.message;

import com.lovetropics.gamemodebuild.GBConfigs;
import com.lovetropics.gamemodebuild.GamemodeBuild;
import com.lovetropics.gamemodebuild.container.BuildContainer;
import com.lovetropics.gamemodebuild.state.GBPlayerStore;
import com.lovetropics.gamemodebuild.state.GBServerState;
import net.minecraft.core.BlockPos;
import net.minecraft.core.RegistryAccess;
import net.minecraft.core.component.DataComponents;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.codec.ByteBufCodecs;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.network.protocol.PacketUtils;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import net.minecraft.network.protocol.game.ServerboundSetCreativeModeSlotPacket;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.SimpleMenuProvider;
import net.minecraft.world.flag.FeatureFlagSet;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.component.CustomData;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.neoforged.neoforge.network.handling.IPayloadContext;

import java.util.List;

public record SetGamemodeBuildSlotPacket(short slotNum, ItemStack itemStack) implements CustomPacketPayload {
public static final Type<SetGamemodeBuildSlotPacket> TYPE = new Type<>(GamemodeBuild.rl("set_slot"));
public static final StreamCodec<RegistryFriendlyByteBuf, SetGamemodeBuildSlotPacket> CODEC;
static {
CODEC = StreamCodec.composite(ByteBufCodecs.SHORT, SetGamemodeBuildSlotPacket::slotNum, ItemStack.validatedStreamCodec(ItemStack.OPTIONAL_STREAM_CODEC), SetGamemodeBuildSlotPacket::itemStack, SetGamemodeBuildSlotPacket::new);
}

public SetGamemodeBuildSlotPacket(short slotNum, ItemStack itemStack) {
this.slotNum = slotNum;
this.itemStack = itemStack;
}

public static void handle(SetGamemodeBuildSlotPacket packet, IPayloadContext ctx) {
var player = ctx.player();

if (player instanceof ServerPlayer serverPlayer) {
ctx.enqueueWork(() -> {
if (GBServerState.isActiveFor(serverPlayer)) {
ItemStack itemstack = packet.itemStack();

FeatureFlagSet featureFlags = player.level().enabledFeatures();
RegistryAccess registryAccess = player.level().registryAccess();
List<ItemStack> itemStacks = GBConfigs.SERVER.getFilter(GBPlayerStore.getList(player)).getAllStacks(featureFlags, registryAccess);

if(!itemStacks.stream().anyMatch(stack -> stack.is(packet.itemStack.getItem()))) {
player.inventoryMenu.setRemoteSlot(packet.slotNum, packet.itemStack);
player.inventoryMenu.broadcastChanges();
return;
}

if (!itemstack.isItemEnabled(player.level().enabledFeatures())) {
return;
}

CustomData customdata = itemstack.getOrDefault(DataComponents.BLOCK_ENTITY_DATA, CustomData.EMPTY);
if (customdata.contains("x") && customdata.contains("y") && customdata.contains("z")) {
BlockPos blockpos = BlockEntity.getPosFromTag(customdata.getUnsafe());
if (player.level().isLoaded(blockpos)) {
BlockEntity blockentity = player.level().getBlockEntity(blockpos);
if (blockentity != null) {
blockentity.saveToItem(itemstack, player.level().registryAccess());
}
}
}

boolean flag1 = packet.slotNum() >= 1 && packet.slotNum() <= 45;
boolean flag2 = itemstack.isEmpty() || itemstack.getCount() <= itemstack.getMaxStackSize();
if (flag1 && flag2) {
player.inventoryMenu.getSlot(packet.slotNum()).setByPlayer(itemstack);
player.inventoryMenu.broadcastChanges();
}
}
});
}
}

@Override
public Type<? extends CustomPacketPayload> type() {
return TYPE;
}


public short slotNum() {
return this.slotNum;
}

public ItemStack itemStack() {
return this.itemStack;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package com.lovetropics.gamemodebuild.mixin;

import com.llamalad7.mixinextras.sugar.Local;
import com.lovetropics.gamemodebuild.container.GBStackMarker;
import com.lovetropics.gamemodebuild.state.GBClientState;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.client.player.LocalPlayer;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.phys.HitResult;
import org.spongepowered.asm.mixin.Debug;
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.ModifyVariable;
import org.spongepowered.asm.mixin.injection.Redirect;

import javax.annotation.Nullable;

@Debug(export = true)
@Mixin(Minecraft.class)
public class MinecraftMixin {

@Shadow @Nullable public LocalPlayer player;

@ModifyVariable(method = "pickBlock", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/phys/HitResult;getType()Lnet/minecraft/world/phys/HitResult$Type;", shift = At.Shift.BY, by = 2))
private boolean flagInsta(boolean value, @Local(ordinal = 0) HitResult.Type type) {
if(GBClientState.isActive() && type == HitResult.Type.BLOCK) {
return true;
}
return value;
}

@ModifyVariable(method = "pickBlock", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/item/ItemStack;isEmpty()Z", ordinal = 0))
private ItemStack addTag(ItemStack itemStack) {
if(GBClientState.isActive()) {
GBStackMarker.mark(itemStack);
}
return itemStack;
}

@Redirect(
method = "pickBlock",
at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screens/Screen;hasControlDown()Z")
)
private boolean redirectHasControlDown() {
if(GBClientState.isActive()) {
return false;
}
return Screen.hasControlDown();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.lovetropics.gamemodebuild.mixin;

import com.lovetropics.gamemodebuild.message.SetGamemodeBuildSlotPacket;
import com.lovetropics.gamemodebuild.state.GBClientState;
import net.minecraft.client.multiplayer.MultiPlayerGameMode;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.GameType;
import net.neoforged.neoforge.network.PacketDistributor;
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(MultiPlayerGameMode.class)
public class MultiPlayerGameModeMixin {

@Shadow private GameType localPlayerMode;

@Inject(method = "handleCreativeModeItemAdd", at = @At(value = "HEAD"))
private void redirectIsCreative(ItemStack stack, int slotId, CallbackInfo ci) {
if(this.localPlayerMode != GameType.CREATIVE && GBClientState.isActive()) {
PacketDistributor.sendToServer(new SetGamemodeBuildSlotPacket((short) slotId, stack));
}
}
}
6 changes: 5 additions & 1 deletion src/main/resources/gamemodebuild.mixins.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,9 @@
"injectors": {
"defaultRequire": 1
},
"minVersion": "0.8"
"minVersion": "0.8",
"client": [
"MinecraftMixin",
"MultiPlayerGameModeMixin"
]
}

0 comments on commit 95683ad

Please sign in to comment.