Seat rework
This commit is contained in:
+1
-1
@@ -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
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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<ResourceKey<Level>, BlockPos> SITTING_POSITIONS = ArrayListMultimap.create();
|
||||
|
||||
private AABB shape;
|
||||
private boolean remove;
|
||||
private boolean canRotate;
|
||||
private Entity pendingRider;
|
||||
private int mountDelayTicks;
|
||||
|
||||
public Seat(EntityType<? extends Entity> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,7 @@ public class ModEntityTypes {
|
||||
|
||||
public static final RegistryEntry<EntityType<Seat>> SEAT = ENTITY_TYPES.register("seat", () ->
|
||||
EntityType.Builder.<Seat>of(Seat::new, MobCategory.MISC)
|
||||
.sized(1, 1)
|
||||
.sized(0, 0)
|
||||
.noSave()
|
||||
.fireImmune()
|
||||
.noSummon()
|
||||
|
||||
Reference in New Issue
Block a user