Seat rework
This commit is contained in:
+1
-1
@@ -2,7 +2,7 @@ org.gradle.jvmargs=-Xmx4G
|
|||||||
org.gradle.parallel=true
|
org.gradle.parallel=true
|
||||||
org.gradle.caching=true
|
org.gradle.caching=true
|
||||||
|
|
||||||
mod_version=4.0.9
|
mod_version=4.0.10
|
||||||
maven_group=earth.terrarium.handcrafted
|
maven_group=earth.terrarium.handcrafted
|
||||||
archives_base_name=handcrafted
|
archives_base_name=handcrafted
|
||||||
|
|
||||||
|
|||||||
@@ -10,29 +10,20 @@ import net.minecraft.world.phys.AABB;
|
|||||||
|
|
||||||
public interface SittableBlock {
|
public interface SittableBlock {
|
||||||
default boolean sitOn(Level level, BlockPos pos, Player player, Direction dir) {
|
default boolean sitOn(Level level, BlockPos pos, Player player, Direction dir) {
|
||||||
// 26.2 evaluates empty-hand block interaction client-side first. Returning
|
// Keep seating server-authoritative. On the client, report a handled
|
||||||
// success here keeps the interaction authoritative on the server while
|
// interaction so Minecraft sends/keeps the block-use interaction path.
|
||||||
// ensuring the use packet is sent for chairs/benches/couches.
|
|
||||||
if (level.isClientSide()) {
|
if (level.isClientSide()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (!Seat.SITTING_POSITIONS.get(level.dimension()).contains(pos)) {
|
return Seat.sit(player, pos, this.getSeatHeight(level.getBlockState(pos)), dir);
|
||||||
Seat entity = Seat.of(level, pos, dir);
|
}
|
||||||
if (level.addFreshEntity(entity)) {
|
|
||||||
// Do not mount in the same server tick in which the invisible
|
default double getSeatHeight(BlockState state) {
|
||||||
// Seat is spawned. In 26.2 the passenger packet can otherwise
|
// Preserve Handcrafted's old rider height: 75% of the configured seat
|
||||||
// reach the client before the entity-spawn packet, producing
|
// AABB plus 0.2 blocks, but place the zero-sized vehicle itself there.
|
||||||
// "Received passengers for unknown entity" and an immediate
|
// This matches the modern, working furniture-seat pattern and avoids
|
||||||
// client/server mount desync. Queue the rider and let Seat.tick()
|
// custom passenger attachment/bounding-box synchronization.
|
||||||
// mount it after the entity has entered normal tracking.
|
return this.getSeatSize(state).getYsize() * 0.75D + 0.2D;
|
||||||
Seat.SITTING_POSITIONS.put(level.dimension(), pos.immutable());
|
|
||||||
entity.queueRider(player);
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
entity.removeSeat();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
AABB getSeatSize(BlockState state);
|
AABB getSeatSize(BlockState state);
|
||||||
|
|||||||
@@ -1,72 +1,70 @@
|
|||||||
package earth.terrarium.handcrafted.common.entities;
|
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.blocks.base.SittableBlock;
|
||||||
import earth.terrarium.handcrafted.common.registry.ModEntityTypes;
|
import earth.terrarium.handcrafted.common.registry.ModEntityTypes;
|
||||||
import net.minecraft.core.BlockPos;
|
import net.minecraft.core.BlockPos;
|
||||||
import net.minecraft.core.Direction;
|
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.network.syncher.SynchedEntityData;
|
||||||
import net.minecraft.resources.ResourceKey;
|
|
||||||
import net.minecraft.server.level.ServerLevel;
|
import net.minecraft.server.level.ServerLevel;
|
||||||
import net.minecraft.world.damagesource.DamageSource;
|
|
||||||
import net.minecraft.util.Mth;
|
import net.minecraft.util.Mth;
|
||||||
|
import net.minecraft.world.damagesource.DamageSource;
|
||||||
import net.minecraft.world.entity.Entity;
|
import net.minecraft.world.entity.Entity;
|
||||||
import net.minecraft.world.entity.EntityDimensions;
|
|
||||||
import net.minecraft.world.entity.EntityType;
|
import net.minecraft.world.entity.EntityType;
|
||||||
import net.minecraft.world.entity.LivingEntity;
|
import net.minecraft.world.entity.LivingEntity;
|
||||||
|
import net.minecraft.world.entity.player.Player;
|
||||||
import net.minecraft.world.entity.vehicle.DismountHelper;
|
import net.minecraft.world.entity.vehicle.DismountHelper;
|
||||||
import net.minecraft.world.level.Level;
|
import net.minecraft.world.level.Level;
|
||||||
import net.minecraft.world.level.block.state.BlockState;
|
import net.minecraft.world.level.storage.ValueInput;
|
||||||
import net.minecraft.world.level.entity.EntityInLevelCallback;
|
import net.minecraft.world.level.storage.ValueOutput;
|
||||||
import net.minecraft.world.phys.AABB;
|
import net.minecraft.world.phys.AABB;
|
||||||
import net.minecraft.world.phys.Vec3;
|
import net.minecraft.world.phys.Vec3;
|
||||||
|
|
||||||
public class Seat extends Entity {
|
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 boolean canRotate;
|
||||||
private Entity pendingRider;
|
|
||||||
private int mountDelayTicks;
|
|
||||||
|
|
||||||
public Seat(EntityType<? extends Entity> entityType, Level level) {
|
public Seat(EntityType<? extends Entity> entityType, Level level) {
|
||||||
super(entityType, level);
|
super(entityType, level);
|
||||||
this.setLevelCallback(EntityInLevelCallback.NULL);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Seat(Level level, AABB shape) {
|
private Seat(Level level, BlockPos pos, double seatHeight, Direction dir) {
|
||||||
super(ModEntityTypes.SEAT.get(), level);
|
this(ModEntityTypes.SEAT.get(), level);
|
||||||
this.shape = copyAABB(shape);
|
this.setPos(Vec3.atBottomCenterOf(pos).add(0, seatHeight, 0));
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
if (dir != null) {
|
if (dir != null) {
|
||||||
entity.setYRot(dir.toYRot());
|
this.setRot(dir.toYRot(), 0);
|
||||||
} else {
|
} 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) {
|
public static boolean sit(Player player, BlockPos pos, double seatHeight, Direction dir) {
|
||||||
return new AABB(aabb.minX, aabb.minY, aabb.minZ, aabb.maxX, aabb.maxY, aabb.maxZ);
|
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.
|
public static boolean availableAt(Level level, BlockPos pos) {
|
||||||
// The old custom ClientboundAddEntityPacket path from 1.21.x can race the
|
return level.getEntitiesOfClass(Seat.class, new AABB(pos)).isEmpty();
|
||||||
// passenger packet, leaving the client with an unknown vehicle entity.
|
}
|
||||||
|
|
||||||
|
@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
|
@Override
|
||||||
public boolean hurtServer(ServerLevel level, DamageSource source, float damage) {
|
public boolean hurtServer(ServerLevel level, DamageSource source, float damage) {
|
||||||
@@ -79,113 +77,35 @@ public class Seat extends Entity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isVehicle() {
|
protected void addPassenger(Entity entity) {
|
||||||
return true;
|
super.addPassenger(entity);
|
||||||
}
|
if (!canRotate) entity.setYRot(this.getYRot());
|
||||||
|
|
||||||
// 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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean canAddPassenger(Entity passenger) {
|
public void onPassengerTurned(Entity entity) {
|
||||||
return this.getPassengers().isEmpty();
|
if (!canRotate) clampPassengerYaw(entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private void clampPassengerYaw(Entity entity) {
|
||||||
public boolean shouldRender(double x, double y, double z) {
|
entity.setYBodyRot(this.getYRot());
|
||||||
return false;
|
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
|
@Override
|
||||||
public Vec3 getDismountLocationForPassenger(LivingEntity passenger) {
|
public Vec3 getDismountLocationForPassenger(LivingEntity passenger) {
|
||||||
Direction facing = this.getDirection();
|
Direction facing = canRotate ? passenger.getDirection() : this.getDirection();
|
||||||
for (Direction offset : new Direction[]{facing, facing.getClockWise(), facing.getCounterClockWise(), facing.getOpposite()}) {
|
for (Direction side : new Direction[]{facing, facing.getClockWise(), facing.getCounterClockWise(), facing.getOpposite()}) {
|
||||||
Vec3 safeLocation = DismountHelper.findSafeDismountLocation(passenger.getType(), this.level(), this.blockPosition().relative(offset), false);
|
Vec3 safe = DismountHelper.findSafeDismountLocation(passenger.getType(), this.level(), this.blockPosition().relative(side), false);
|
||||||
if (safeLocation != null) {
|
if (safe != null) return safe.add(0, 0.25, 0);
|
||||||
return safeLocation.add(0, 0.25, 0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return super.getDismountLocationForPassenger(passenger);
|
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
|
@Override
|
||||||
protected void defineSynchedData(SynchedEntityData.Builder builder) {}
|
protected void defineSynchedData(SynchedEntityData.Builder builder) {}
|
||||||
|
|
||||||
@@ -194,31 +114,4 @@ public class Seat extends Entity {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void addAdditionalSaveData(ValueOutput output) {}
|
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", () ->
|
public static final RegistryEntry<EntityType<Seat>> SEAT = ENTITY_TYPES.register("seat", () ->
|
||||||
EntityType.Builder.<Seat>of(Seat::new, MobCategory.MISC)
|
EntityType.Builder.<Seat>of(Seat::new, MobCategory.MISC)
|
||||||
.sized(1, 1)
|
.sized(0, 0)
|
||||||
.noSave()
|
.noSave()
|
||||||
.fireImmune()
|
.fireImmune()
|
||||||
.noSummon()
|
.noSummon()
|
||||||
|
|||||||
Reference in New Issue
Block a user