diff --git a/gradle.properties b/gradle.properties index a7877c3..744ea27 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,7 +2,7 @@ org.gradle.jvmargs=-Xmx4G org.gradle.parallel=true org.gradle.caching=true -mod_version=4.0.9 +mod_version=4.0.10 maven_group=earth.terrarium.handcrafted archives_base_name=handcrafted diff --git a/src/main/java/earth/terrarium/handcrafted/common/blocks/base/SittableBlock.java b/src/main/java/earth/terrarium/handcrafted/common/blocks/base/SittableBlock.java index 7d077c2..eb9bcfd 100644 --- a/src/main/java/earth/terrarium/handcrafted/common/blocks/base/SittableBlock.java +++ b/src/main/java/earth/terrarium/handcrafted/common/blocks/base/SittableBlock.java @@ -10,29 +10,20 @@ import net.minecraft.world.phys.AABB; public interface SittableBlock { default boolean sitOn(Level level, BlockPos pos, Player player, Direction dir) { - // 26.2 evaluates empty-hand block interaction client-side first. Returning - // success here keeps the interaction authoritative on the server while - // ensuring the use packet is sent for chairs/benches/couches. + // Keep seating server-authoritative. On the client, report a handled + // interaction so Minecraft sends/keeps the block-use interaction path. if (level.isClientSide()) { return true; } - if (!Seat.SITTING_POSITIONS.get(level.dimension()).contains(pos)) { - Seat entity = Seat.of(level, pos, dir); - if (level.addFreshEntity(entity)) { - // Do not mount in the same server tick in which the invisible - // Seat is spawned. In 26.2 the passenger packet can otherwise - // reach the client before the entity-spawn packet, producing - // "Received passengers for unknown entity" and an immediate - // client/server mount desync. Queue the rider and let Seat.tick() - // mount it after the entity has entered normal tracking. - Seat.SITTING_POSITIONS.put(level.dimension(), pos.immutable()); - entity.queueRider(player); - return true; - } else { - entity.removeSeat(); - } - } - return false; + return Seat.sit(player, pos, this.getSeatHeight(level.getBlockState(pos)), dir); + } + + default double getSeatHeight(BlockState state) { + // Preserve Handcrafted's old rider height: 75% of the configured seat + // AABB plus 0.2 blocks, but place the zero-sized vehicle itself there. + // This matches the modern, working furniture-seat pattern and avoids + // custom passenger attachment/bounding-box synchronization. + return this.getSeatSize(state).getYsize() * 0.75D + 0.2D; } AABB getSeatSize(BlockState state); diff --git a/src/main/java/earth/terrarium/handcrafted/common/entities/Seat.java b/src/main/java/earth/terrarium/handcrafted/common/entities/Seat.java index 1a7875b..1a63afa 100644 --- a/src/main/java/earth/terrarium/handcrafted/common/entities/Seat.java +++ b/src/main/java/earth/terrarium/handcrafted/common/entities/Seat.java @@ -1,72 +1,70 @@ package earth.terrarium.handcrafted.common.entities; -import com.google.common.collect.ArrayListMultimap; -import com.google.common.collect.Multimap; import earth.terrarium.handcrafted.common.blocks.base.SittableBlock; import earth.terrarium.handcrafted.common.registry.ModEntityTypes; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; -import net.minecraft.world.level.storage.ValueInput; -import net.minecraft.world.level.storage.ValueOutput; import net.minecraft.network.syncher.SynchedEntityData; -import net.minecraft.resources.ResourceKey; import net.minecraft.server.level.ServerLevel; -import net.minecraft.world.damagesource.DamageSource; import net.minecraft.util.Mth; +import net.minecraft.world.damagesource.DamageSource; import net.minecraft.world.entity.Entity; -import net.minecraft.world.entity.EntityDimensions; import net.minecraft.world.entity.EntityType; import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.player.Player; import net.minecraft.world.entity.vehicle.DismountHelper; import net.minecraft.world.level.Level; -import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.level.entity.EntityInLevelCallback; +import net.minecraft.world.level.storage.ValueInput; +import net.minecraft.world.level.storage.ValueOutput; import net.minecraft.world.phys.AABB; import net.minecraft.world.phys.Vec3; public class Seat extends Entity { - - public static final Multimap, BlockPos> SITTING_POSITIONS = ArrayListMultimap.create(); - - private AABB shape; - private boolean remove; private boolean canRotate; - private Entity pendingRider; - private int mountDelayTicks; public Seat(EntityType entityType, Level level) { super(entityType, level); - this.setLevelCallback(EntityInLevelCallback.NULL); } - public Seat(Level level, AABB shape) { - super(ModEntityTypes.SEAT.get(), level); - this.shape = copyAABB(shape); - } - - public static Seat of(Level level, BlockPos pos, Direction dir) { - BlockState state = level.getBlockState(pos); - AABB shape = new AABB(pos); - if (state.getBlock() instanceof SittableBlock seat) { - shape = seat.getSeatSize(state); - } - Seat entity = new Seat(level, shape); + private Seat(Level level, BlockPos pos, double seatHeight, Direction dir) { + this(ModEntityTypes.SEAT.get(), level); + this.setPos(Vec3.atBottomCenterOf(pos).add(0, seatHeight, 0)); if (dir != null) { - entity.setYRot(dir.toYRot()); + this.setRot(dir.toYRot(), 0); } else { - entity.canRotate = true; + this.setRot(0, 0); + this.canRotate = true; } - entity.setPos(pos.getX() + 0.5, pos.getY(), pos.getZ() + 0.5); - return entity; } - private static AABB copyAABB(AABB aabb) { - return new AABB(aabb.minX, aabb.minY, aabb.minZ, aabb.maxX, aabb.maxY, aabb.maxZ); + public static boolean sit(Player player, BlockPos pos, double seatHeight, Direction dir) { + Level level = player.level(); + if (!level.isClientSide() && availableAt(level, pos)) { + Seat seat = new Seat(level, pos, seatHeight, dir); + level.addFreshEntity(seat); + if (player.startRiding(seat)) { + return true; + } + seat.discard(); + } + return false; } - // Use Minecraft's native entity spawn packet on 26.2. - // The old custom ClientboundAddEntityPacket path from 1.21.x can race the - // passenger packet, leaving the client with an unknown vehicle entity. + public static boolean availableAt(Level level, BlockPos pos) { + return level.getEntitiesOfClass(Seat.class, new AABB(pos)).isEmpty(); + } + + @Override + public void tick() { + super.tick(); + if (!this.level().isClientSide()) { + BlockPos pos = this.blockPosition(); + if (this.getPassengers().isEmpty() || !(this.level().getBlockState(pos).getBlock() instanceof SittableBlock)) { + this.discard(); + this.level().updateNeighbourForOutputSignal(pos, this.level().getBlockState(pos).getBlock()); + } + } + } @Override public boolean hurtServer(ServerLevel level, DamageSource source, float damage) { @@ -79,113 +77,35 @@ public class Seat extends Entity { } @Override - public boolean isVehicle() { - return true; - } - - // Minecraft 26.2 no longer treats a generic Entity subclass as a - // passenger-capable vehicle merely because isVehicle() returns true. - // Explicitly opt this invisible seat entity into accepting one rider. - @Override - protected boolean couldAcceptPassenger() { - return true; + protected void addPassenger(Entity entity) { + super.addPassenger(entity); + if (!canRotate) entity.setYRot(this.getYRot()); } @Override - protected boolean canAddPassenger(Entity passenger) { - return this.getPassengers().isEmpty(); + public void onPassengerTurned(Entity entity) { + if (!canRotate) clampPassengerYaw(entity); } - @Override - public boolean shouldRender(double x, double y, double z) { - return false; + private void clampPassengerYaw(Entity entity) { + entity.setYBodyRot(this.getYRot()); + float wrapped = Mth.wrapDegrees(entity.getYRot() - this.getYRot()); + float clamped = Mth.clamp(wrapped, -105.0F, 105.0F); + entity.yRotO += clamped - wrapped; + entity.setYRot(entity.getYRot() + clamped - wrapped); + entity.setYHeadRot(entity.getYRot()); } @Override public Vec3 getDismountLocationForPassenger(LivingEntity passenger) { - Direction facing = this.getDirection(); - for (Direction offset : new Direction[]{facing, facing.getClockWise(), facing.getCounterClockWise(), facing.getOpposite()}) { - Vec3 safeLocation = DismountHelper.findSafeDismountLocation(passenger.getType(), this.level(), this.blockPosition().relative(offset), false); - if (safeLocation != null) { - return safeLocation.add(0, 0.25, 0); - } + Direction facing = canRotate ? passenger.getDirection() : this.getDirection(); + for (Direction side : new Direction[]{facing, facing.getClockWise(), facing.getCounterClockWise(), facing.getOpposite()}) { + Vec3 safe = DismountHelper.findSafeDismountLocation(passenger.getType(), this.level(), this.blockPosition().relative(side), false); + if (safe != null) return safe.add(0, 0.25, 0); } return super.getDismountLocationForPassenger(passenger); } - public void queueRider(Entity rider) { - this.pendingRider = rider; - // One complete entity tick is enough for ServerEntity tracking to send - // the add-entity packet before we generate a passengers packet. - this.mountDelayTicks = 1; - } - - @Override - public void tick() { - super.tick(); - if (!level().isClientSide()) { - if (!(this.level().getBlockState(this.blockPosition()).getBlock() instanceof SittableBlock) || remove) { - removeSeat(); - return; - } - - if (pendingRider != null) { - if (mountDelayTicks-- <= 0) { - Entity rider = pendingRider; - pendingRider = null; - if (!rider.isRemoved() && rider.level() == this.level() && rider.startRiding(this)) { - return; - } - removeSeat(); - } - } - } - } - - @Override - protected void removePassenger(Entity passenger) { - super.removePassenger(passenger); - if (!level().isClientSide() && this.getPassengers().isEmpty()) { - remove = true; - } - } - - public void removeSeat() { - SITTING_POSITIONS.get(this.level().dimension()).remove(this.blockPosition()); - this.discard(); - } - - - @Override - protected Vec3 getPassengerAttachmentPoint(Entity entity, EntityDimensions dimensions, float partialTick) { - if (shape == null) { - return super.getPassengerAttachmentPoint(entity, dimensions, partialTick); - } - return new Vec3(0, (float) (shape.getYsize() * 0.75) + 0.2f, 0); - } - - protected void clampRotation(Entity entityToUpdate) { - entityToUpdate.setYBodyRot(this.getYRot()); - float f = Mth.wrapDegrees(entityToUpdate.getYRot() - this.getYRot()); - float g = Mth.clamp(f, -105.0F, 105.0F); - entityToUpdate.yRotO += g - f; - entityToUpdate.setYRot(entityToUpdate.getYRot() + g - f); - entityToUpdate.setYHeadRot(entityToUpdate.getYRot()); - } - - @Override - public void onPassengerTurned(Entity entityToUpdate) { - if (!canRotate) { - this.clampRotation(entityToUpdate); - } - } - - @Override - public void setLevelCallback(EntityInLevelCallback levelCallback) { - super.setLevelCallback(new WrappedLevelCallBack(levelCallback)); - - } - @Override protected void defineSynchedData(SynchedEntityData.Builder builder) {} @@ -194,31 +114,4 @@ public class Seat extends Entity { @Override protected void addAdditionalSaveData(ValueOutput output) {} - - private class WrappedLevelCallBack implements EntityInLevelCallback { - private final EntityInLevelCallback callback; - - public WrappedLevelCallBack(EntityInLevelCallback callback) { - this.callback = callback; - } - - @Override - public void onMove() { - if (callback != null) { - callback.onMove(); - if (level().getBlockState(blockPosition()).getBlock() instanceof SittableBlock seat) { - shape = seat.getSeatSize(level().getBlockState(blockPosition())); - } - } else { - Seat.this.shape = null; - } - } - - @Override - public void onRemove(RemovalReason reason) { - if (callback != null) { - callback.onRemove(reason); - } - } - } -} \ No newline at end of file +} diff --git a/src/main/java/earth/terrarium/handcrafted/common/registry/ModEntityTypes.java b/src/main/java/earth/terrarium/handcrafted/common/registry/ModEntityTypes.java index b5d8432..b172be0 100644 --- a/src/main/java/earth/terrarium/handcrafted/common/registry/ModEntityTypes.java +++ b/src/main/java/earth/terrarium/handcrafted/common/registry/ModEntityTypes.java @@ -18,7 +18,7 @@ public class ModEntityTypes { public static final RegistryEntry> SEAT = ENTITY_TYPES.register("seat", () -> EntityType.Builder.of(Seat::new, MobCategory.MISC) - .sized(1, 1) + .sized(0, 0) .noSave() .fireImmune() .noSummon()