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 5d34744..076fb13 100644 --- a/Common/src/main/java/tschipp/carryon/CarryOnCommonClient.java +++ b/Common/src/main/java/tschipp/carryon/CarryOnCommonClient.java @@ -70,4 +70,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); + } } }