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.
This commit is contained in:
ElysianCat 2025-09-23 23:32:49 +03:30
parent ece3ed7ff2
commit 3fddd1112b
7 changed files with 162 additions and 17 deletions

View File

@ -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,

View File

@ -68,4 +68,4 @@ public class CarryOnCommonClient
{
return Minecraft.getInstance().player;
}
}
}

View File

@ -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<BlockPos> 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;

View File

@ -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<RegistryFriendlyByteBuf, ClientboundSyncCarryDataPacket> CODEC = StreamCodec.composite(
ByteBufCodecs.INT, ClientboundSyncCarryDataPacket::iden,
CarryOnData.STREAM_CODEC, ClientboundSyncCarryDataPacket::data,
ClientboundSyncCarryDataPacket::new
);
public static final CustomPacketPayload.Type<ClientboundSyncCarryDataPacket> 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<? extends CustomPacketPayload> type() {
return TYPE;
}
}

View File

@ -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

View File

@ -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))

View File

@ -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);
}
}
}