From 3fddd1112bb30ec18528e0b77d259f9dc4c44726 Mon Sep 17 00:00:00 2001 From: ElysianCat <78264512+CatsSomeCat@users.noreply.github.com> Date: Tue, 23 Sep 2025 23:32:49 +0330 Subject: [PATCH 1/3] Add ClientboundSyncCarryDataPacket for syncing carry data and enhance carry handling on player death - Introduced ClientboundSyncCarryDataPacket to manage carry data synchronization between server and client. - Updated PlacementHandler to handle carry data transfer during player death scenarios, considering keepInventory game rule. - Refactored carry data handling in CommonEvents to ensure proper transfer and clearing of carry data on player respawn. - Registered the new packet in CarryOnCommon for clientbound communication. --- .../java/tschipp/carryon/CarryOnCommon.java | 8 ++ .../tschipp/carryon/CarryOnCommonClient.java | 2 +- .../common/carry/PlacementHandler.java | 74 +++++++++++++++++-- .../ClientboundSyncCarryDataPacket.java | 44 +++++++++++ .../tschipp/carryon/CarryOnFabricMod.java | 2 - .../tschipp/carryon/events/CommonEvents.java | 45 ++++++++--- .../platform/FabricPlatformHelper.java | 4 + 7 files changed, 162 insertions(+), 17 deletions(-) create mode 100644 Common/src/main/java/tschipp/carryon/networking/clientbound/ClientboundSyncCarryDataPacket.java diff --git a/Common/src/main/java/tschipp/carryon/CarryOnCommon.java b/Common/src/main/java/tschipp/carryon/CarryOnCommon.java index 40cba15..9827774 100644 --- a/Common/src/main/java/tschipp/carryon/CarryOnCommon.java +++ b/Common/src/main/java/tschipp/carryon/CarryOnCommon.java @@ -85,6 +85,14 @@ public class CarryOnCommon args ); + Services.PLATFORM.registerClientboundPacket( + tschipp.carryon.networking.clientbound.ClientboundSyncCarryDataPacket.TYPE, + tschipp.carryon.networking.clientbound.ClientboundSyncCarryDataPacket.class, + tschipp.carryon.networking.clientbound.ClientboundSyncCarryDataPacket.CODEC, + tschipp.carryon.networking.clientbound.ClientboundSyncCarryDataPacket::handle, + args + ); + Services.PLATFORM.registerClientboundPacket( ClientboundStartRidingOtherPlayerPacket.TYPE, ClientboundStartRidingOtherPlayerPacket.class, diff --git a/Common/src/main/java/tschipp/carryon/CarryOnCommonClient.java b/Common/src/main/java/tschipp/carryon/CarryOnCommonClient.java index 2cd4463..7b6324b 100644 --- a/Common/src/main/java/tschipp/carryon/CarryOnCommonClient.java +++ b/Common/src/main/java/tschipp/carryon/CarryOnCommonClient.java @@ -68,4 +68,4 @@ public class CarryOnCommonClient { return Minecraft.getInstance().player; } -} +} \ No newline at end of file diff --git a/Common/src/main/java/tschipp/carryon/common/carry/PlacementHandler.java b/Common/src/main/java/tschipp/carryon/common/carry/PlacementHandler.java index e871dcf..5f4948c 100644 --- a/Common/src/main/java/tschipp/carryon/common/carry/PlacementHandler.java +++ b/Common/src/main/java/tschipp/carryon/common/carry/PlacementHandler.java @@ -290,15 +290,46 @@ public class PlacementHandler public static void placeCarriedOnDeath(ServerPlayer oldPlayer, ServerPlayer newPlayer, boolean died) { CarryOnData carry = CarryOnDataManager.getCarryData(oldPlayer); - if (((ServerLevel) oldPlayer.level()).getGameRules().getBoolean(GameRules.RULE_KEEPINVENTORY) || !died) { - if (!carry.isCarrying(CarryType.PLAYER)) { + if (died) { + boolean keepInv = ((ServerLevel) oldPlayer.level()).getGameRules().getBoolean(GameRules.RULE_KEEPINVENTORY); + if (keepInv) { + // With keepInventory, the carried content should persist on the player after respawn + if (!carry.isCarrying(CarryType.PLAYER)) { + CarryOnDataManager.setCarryData(newPlayer, carry); + newPlayer.getInventory().setSelectedSlot(oldPlayer.getInventory().getSelectedSlot()); + } + } else { + // Without keepInventory, place the carried content at the death location (not respawn) + ServerLevel deathLevel = (ServerLevel) oldPlayer.level(); + BlockPos deathPos = oldPlayer.blockPosition(); + if (carry.isCarrying(CarryType.ENTITY)) { + Entity entity = carry.getEntity(deathLevel); + entity.setPos(Vec3.atBottomCenterOf(deathPos)); + deathLevel.addFreshEntity(entity); + if (entity instanceof Mob mob) + mob.playAmbientSound(); + } else if (carry.isCarrying(CarryType.BLOCK)) { + BlockState state = carry.getBlock(); + BlockPlaceContext context = new BlockPlaceContext(oldPlayer, InteractionHand.MAIN_HAND, ItemStack.EMPTY, BlockHitResult.miss(Vec3.atCenterOf(deathPos), Direction.DOWN, deathPos)); + state = getPlacementState(state, oldPlayer, context, deathPos); + BlockPos placePos = getDeathPlacementPosFrom(state, oldPlayer, deathPos); + BlockEntity blockEntity = carry.getBlockEntity(placePos, deathLevel.registryAccess()); + deathLevel.setBlock(placePos, state, 3); + if (blockEntity != null) + deathLevel.setBlockEntity(blockEntity); + } + // Clear carry data on the new player only to avoid syncing an update for the despawned old player + carry.clear(); CarryOnDataManager.setCarryData(newPlayer, carry); - newPlayer.getInventory().setSelectedSlot(oldPlayer.getInventory().getSelectedSlot()); - return; } + return; } - placeCarried(oldPlayer); + // Not a death (e.g., dimension change): transfer carry data to the new player instance + if (!carry.isCarrying(CarryType.PLAYER)) { + CarryOnDataManager.setCarryData(newPlayer, carry); + newPlayer.getInventory().setSelectedSlot(oldPlayer.getInventory().getSelectedSlot()); + } } public static void placeCarried(ServerPlayer player) @@ -357,6 +388,39 @@ public class PlacementHandler return p; } + private static BlockPos getDeathPlacementPosFrom(BlockState state, ServerPlayer player, BlockPos origin) + { + BlockPos p = origin; + + int DISTANCE = 15; + + List potentialPositions = new ArrayList<>(); + + for (int j = 0; j < DISTANCE * 2; j++) { + for (int i = 0; i < DISTANCE * 2; i++) { + for (int k = 0; k < DISTANCE * 2; k++) { + int x = i % 2 == 0 ? i / 2 : -(i / 2); + int y = j % 2 == 0 ? j / 2 : -(j / 2); + int z = k % 2 == 0 ? k / 2 : -(k / 2); + potentialPositions.add(new BlockPos(p.getX() + x, p.getY() + y, p.getZ() + z)); + } + } + } + + potentialPositions.sort(Comparator.comparingDouble(posA -> player.distanceToSqr(posA.getCenter()))); + + for(BlockPos potential : potentialPositions) + { + BlockPlaceContext context = new BlockPlaceContext(player, InteractionHand.MAIN_HAND, ItemStack.EMPTY, BlockHitResult.miss(Vec3.atCenterOf(potential), Direction.DOWN, potential)); + boolean canPlace = state.canSurvive(player.level(), potential) && player.level().getBlockState(potential).canBeReplaced(context) && player.level().isUnobstructed(state, potential, CollisionContext.of(player)); + + if (canPlace) + return potential; + } + + return p; + } + private static int getPassengerCount(Entity entity) { int passengers = 0; diff --git a/Common/src/main/java/tschipp/carryon/networking/clientbound/ClientboundSyncCarryDataPacket.java b/Common/src/main/java/tschipp/carryon/networking/clientbound/ClientboundSyncCarryDataPacket.java new file mode 100644 index 0000000..4895c3a --- /dev/null +++ b/Common/src/main/java/tschipp/carryon/networking/clientbound/ClientboundSyncCarryDataPacket.java @@ -0,0 +1,44 @@ +/* + * GNU Lesser General Public License v3 + * Copyright (C) 2024 Tschipp + * mrtschipp@gmail.com + */ + +package tschipp.carryon.networking.clientbound; + +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.ByteBufCodecs; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.player.Player; +import tschipp.carryon.Constants; +import tschipp.carryon.common.carry.CarryOnData; +import tschipp.carryon.common.carry.CarryOnDataManager; +import tschipp.carryon.networking.PacketBase; + +public record ClientboundSyncCarryDataPacket(int iden, CarryOnData data) implements PacketBase { + + public static final StreamCodec CODEC = StreamCodec.composite( + ByteBufCodecs.INT, ClientboundSyncCarryDataPacket::iden, + CarryOnData.STREAM_CODEC, ClientboundSyncCarryDataPacket::data, + ClientboundSyncCarryDataPacket::new + ); + + public static final CustomPacketPayload.Type TYPE = new Type<>(Constants.PACKET_ID_SYNC_CARRY_ON_DATA); + + @Override + public void handle(Player player) { + Entity e = player.level().getEntity(this.iden); + if(e instanceof Player p) { + CarryOnDataManager.setCarryData(p, data); + } + } + + @Override + public Type type() { + return TYPE; + } +} + + diff --git a/Fabric/src/main/java/tschipp/carryon/CarryOnFabricMod.java b/Fabric/src/main/java/tschipp/carryon/CarryOnFabricMod.java index e5c7c43..d4613e0 100644 --- a/Fabric/src/main/java/tschipp/carryon/CarryOnFabricMod.java +++ b/Fabric/src/main/java/tschipp/carryon/CarryOnFabricMod.java @@ -39,9 +39,7 @@ public class CarryOnFabricMod implements ModInitializer { builder -> builder .initializer(() -> new CarryOnData(new CompoundTag())) .persistent(CarryOnData.CODEC) - .syncWith(CarryOnData.STREAM_CODEC, (t, p) -> p.connection != null) .copyOnDeath() - ); @Override diff --git a/Fabric/src/main/java/tschipp/carryon/events/CommonEvents.java b/Fabric/src/main/java/tschipp/carryon/events/CommonEvents.java index fc7f9ac..3fb1fe0 100644 --- a/Fabric/src/main/java/tschipp/carryon/events/CommonEvents.java +++ b/Fabric/src/main/java/tschipp/carryon/events/CommonEvents.java @@ -44,6 +44,8 @@ import tschipp.carryon.compat.ArchitecturyCompat; import tschipp.carryon.config.ConfigLoader; import tschipp.carryon.scripting.IdentifiableScriptReloadListener; +import java.util.Objects; + public class CommonEvents { public static void registerEvents() { @@ -91,9 +93,6 @@ public class CommonEvents { } }); - - - UseEntityCallback.EVENT.register((player, level, hand, entity, hitResult) -> { if(level.isClientSide) @@ -113,30 +112,58 @@ public class CommonEvents { return InteractionResult.PASS; }); - CommandRegistrationCallback.EVENT.register(((dispatcher, registryAccess, environment) -> { CarryOnCommon.registerCommands(dispatcher); })); - ResourceManagerHelper.get(PackType.SERVER_DATA).registerReloadListener(new IdentifiableScriptReloadListener()); - ServerLifecycleEvents.SYNC_DATA_PACK_CONTENTS.register((player, joined) -> { ScriptReloadListener.syncScriptsWithClient(player); }); - ServerTickEvents.END_SERVER_TICK.register(server -> { for(ServerPlayer player : server.getPlayerList().getPlayers()) CarryOnCommon.onCarryTick(player); }); - + // Handle true deaths during COPY_FROM so placement uses the actual death location/world ServerPlayerEvents.COPY_FROM.register(((oldPlayer, newPlayer, alive) -> { - PlacementHandler.placeCarriedOnDeath(oldPlayer, newPlayer, !alive); + boolean died = !alive; + if (died) { + boolean keepInv = ((net.minecraft.server.level.ServerLevel) oldPlayer.level()).getGameRules().getBoolean(net.minecraft.world.level.GameRules.RULE_KEEPINVENTORY); + if (!keepInv) { + // Place immediately at death location before respawn + PlacementHandler.placeCarriedOnDeath(oldPlayer, newPlayer, true); + } + } })); + // AFTER_RESPAWN: handle non-death transitions; for deaths with keepInventory, transfer after the player fully exists + ServerPlayerEvents.AFTER_RESPAWN.register(((oldPlayer, newPlayer, alive) -> { + boolean died = !alive; + if (!died) { + Objects.requireNonNull(newPlayer.getServer()).execute(() -> PlacementHandler.placeCarriedOnDeath(oldPlayer, newPlayer, false)); + } else { + boolean keepInv = ((net.minecraft.server.level.ServerLevel) oldPlayer.level()).getGameRules().getBoolean(net.minecraft.world.level.GameRules.RULE_KEEPINVENTORY); + if (keepInv) { + // Transfer one tick later so the client has the new entity registered before any attachment sync + Objects.requireNonNull(newPlayer.getServer()).execute(() -> newPlayer.getServer().execute(() -> { + var carry = tschipp.carryon.common.carry.CarryOnDataManager.getCarryData(oldPlayer); + tschipp.carryon.common.carry.CarryOnDataManager.setCarryData(newPlayer, carry); + })); + } else { + // Ensure no residual carried state remains after respawn when keepInventory is off + Objects.requireNonNull(newPlayer.getServer()).execute(() -> newPlayer.getServer().execute(() -> { + var carryNew = tschipp.carryon.common.carry.CarryOnDataManager.getCarryData(newPlayer); + if (carryNew.isCarrying()) { + carryNew.clear(); + tschipp.carryon.common.carry.CarryOnDataManager.setCarryData(newPlayer, carryNew); + } + })); + } + } + })); PlayerBlockBreakEvents.BEFORE.register(((world, player, pos, state, blockEntity) -> { if(!CarryOnCommon.onTryBreakBlock(player)) diff --git a/Fabric/src/main/java/tschipp/carryon/platform/FabricPlatformHelper.java b/Fabric/src/main/java/tschipp/carryon/platform/FabricPlatformHelper.java index 64d4bdf..18b0c10 100644 --- a/Fabric/src/main/java/tschipp/carryon/platform/FabricPlatformHelper.java +++ b/Fabric/src/main/java/tschipp/carryon/platform/FabricPlatformHelper.java @@ -37,6 +37,7 @@ import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.entity.player.Player; import tschipp.carryon.CarryOnFabricClientMod; import tschipp.carryon.CarryOnFabricMod; +import tschipp.carryon.Constants; import tschipp.carryon.common.carry.CarryOnData; import tschipp.carryon.config.BuiltConfig; import tschipp.carryon.config.fabric.ConfigLoaderImpl; @@ -114,5 +115,8 @@ public class FabricPlatformHelper implements IPlatformHelper { @Override public void setCarryData(Player player, CarryOnData data) { player.setAttached(CarryOnFabricMod.CARRY_ON_DATA_ATTACHMENT_TYPE, data); + if (player instanceof ServerPlayer sp) { + sendPacketToPlayer(tschipp.carryon.Constants.PACKET_ID_SYNC_CARRY_ON_DATA, new tschipp.carryon.networking.clientbound.ClientboundSyncCarryDataPacket(player.getId(), data), sp); + } } } From c4346a4c0005738bf3360362bed78c3fbf984b00 Mon Sep 17 00:00:00 2001 From: ElysianCat <78264512+CatsSomeCat@users.noreply.github.com> Date: Tue, 23 Sep 2025 23:43:03 +0330 Subject: [PATCH 2/3] Refactor CarryOn functionality to improve slot selection logic. Ensure that inventory slot changes are only allowed when not carrying an item. --- .../tschipp/carryon/CarryOnCommonClient.java | 14 ++++---- .../tschipp/carryon/mixin/MinecraftMixin.java | 33 +++++++++++++++---- 2 files changed, 34 insertions(+), 13 deletions(-) diff --git a/Common/src/main/java/tschipp/carryon/CarryOnCommonClient.java b/Common/src/main/java/tschipp/carryon/CarryOnCommonClient.java index 2cd4463..5d34744 100644 --- a/Common/src/main/java/tschipp/carryon/CarryOnCommonClient.java +++ b/Common/src/main/java/tschipp/carryon/CarryOnCommonClient.java @@ -52,14 +52,16 @@ public class CarryOnCommonClient return (CarryOnKeybinds.carryKey.matchesMouse(0) && mc.mouseHandler.isLeftPressed()) || (CarryOnKeybinds.carryKey.matchesMouse(1) && mc.mouseHandler.isRightPressed()) || (CarryOnKeybinds.carryKey.matchesMouse(3) && mc.mouseHandler.isMiddlePressed()); } - public static void onCarryClientTick() - { + public static void onCarryClientTick() { Player player = Minecraft.getInstance().player; - if(player != null) { + if (player != null) { CarryOnData carry = CarryOnDataManager.getCarryData(player); - if(carry.isCarrying()) - { - player.getInventory().setSelectedSlot(carry.getSelected()); + if (carry.isCarrying()) { + int wantedSlot = carry.getSelected(); + if (player.getInventory().getSelectedSlot() != wantedSlot) { + // System.out.println("[CarryOn] Forcing slot " + wantedSlot); + player.getInventory().setSelectedSlot(wantedSlot); + } } } } diff --git a/Common/src/main/java/tschipp/carryon/mixin/MinecraftMixin.java b/Common/src/main/java/tschipp/carryon/mixin/MinecraftMixin.java index bbe8a51..dc3bebd 100644 --- a/Common/src/main/java/tschipp/carryon/mixin/MinecraftMixin.java +++ b/Common/src/main/java/tschipp/carryon/mixin/MinecraftMixin.java @@ -29,11 +29,30 @@ import org.spongepowered.asm.mixin.injection.Redirect; import tschipp.carryon.common.carry.CarryOnDataManager; @Mixin(Minecraft.class) -public class MinecraftMixin -{ - @WrapWithCondition(method = "handleKeybinds()V", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/player/Inventory;setSelectedSlot(I)V", ordinal = 0)) - private boolean allowSlotSelection(Inventory inv,int slot) - { - return !CarryOnDataManager.getCarryData(inv.player).isCarrying(); - } +public class MinecraftMixin { + + @WrapWithCondition( + method = "handleKeybinds()V", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/world/entity/player/Inventory;setSelectedSlot(I)V", + ordinal = 0 + ) + ) + private boolean allowSlotSelection(Inventory inv, int slot) { + boolean carrying = CarryOnDataManager.getCarryData(inv.player).isCarrying(); + + // Allow if not carrying + if (!carrying) return true; + + // Block only if trying to switch away + boolean allow = inv.getSelectedSlot() == slot; + + if (!allow) { + // System.out.println("[CarryOn] Blocked hotbar change while carrying. Tried " + slot + ", staying on " + inv.getSelectedSlot()); + } + + return allow; + } } + From 00c0c898586d3237ec256b48e24d31c130009b58 Mon Sep 17 00:00:00 2001 From: ElysianCat <78264512+CatsSomeCat@users.noreply.github.com> Date: Tue, 23 Sep 2025 23:50:26 +0330 Subject: [PATCH 3/3] Bump to 2.7.1: fix slot selection and AttachmentType crash MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fixed slot selection conflict with Sounds mod that caused rapid sound spam. - Resolved crash-on-death bug triggered by AttachmentType handling. - Improved Carry On slot change logic for stability and compatibility. - Updated project version from 2.7.0 → 2.7.1. --- gradle.properties | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 51e46db..dccb0bd 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ # Project -version=2.7.0 +version=2.7.1 group=tschipp.carryon # Common @@ -15,6 +15,7 @@ neo_form_version=1.21.8-20250717.133445 java_version=21 parchment_version=2025.07.20 mod_dev_version=2.0.107 + # Forge forge_version=58.0.10 forge_loader_version_range=[57,)