CreateMod/src/main/java/com/simibubi/create/content/contraptions/AbstractContraptionEntity.java
2023-07-03 17:59:55 +02:00

924 lines
29 KiB
Java

package com.simibubi.create.content.contraptions;
import java.util.Collection;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.UUID;
import javax.annotation.Nullable;
import org.apache.commons.lang3.mutable.MutableInt;
import org.apache.commons.lang3.tuple.MutablePair;
import com.mojang.blaze3d.vertex.PoseStack;
import com.simibubi.create.AllItems;
import com.simibubi.create.AllMovementBehaviours;
import com.simibubi.create.AllPackets;
import com.simibubi.create.AllSoundEvents;
import com.simibubi.create.Create;
import com.simibubi.create.content.contraptions.actors.psi.PortableStorageInterfaceMovement;
import com.simibubi.create.content.contraptions.actors.seat.SeatBlock;
import com.simibubi.create.content.contraptions.actors.seat.SeatEntity;
import com.simibubi.create.content.contraptions.actors.trainControls.ControlsStopControllingPacket;
import com.simibubi.create.content.contraptions.behaviour.MovementBehaviour;
import com.simibubi.create.content.contraptions.behaviour.MovementContext;
import com.simibubi.create.content.contraptions.elevator.ElevatorContraption;
import com.simibubi.create.content.contraptions.glue.SuperGlueEntity;
import com.simibubi.create.content.contraptions.mounted.MountedContraption;
import com.simibubi.create.content.contraptions.render.ContraptionRenderDispatcher;
import com.simibubi.create.content.contraptions.sync.ContraptionSeatMappingPacket;
import com.simibubi.create.content.decoration.slidingDoor.SlidingDoorBlock;
import com.simibubi.create.content.trains.entity.CarriageContraption;
import com.simibubi.create.content.trains.entity.CarriageContraptionEntity;
import com.simibubi.create.content.trains.entity.Train;
import com.simibubi.create.foundation.advancement.AllAdvancements;
import com.simibubi.create.foundation.collision.Matrix3d;
import com.simibubi.create.foundation.mixin.accessor.ServerLevelAccessor;
import com.simibubi.create.foundation.utility.AngleHelper;
import com.simibubi.create.foundation.utility.VecHelper;
import net.minecraft.client.Minecraft;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.Tag;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.syncher.EntityDataAccessor;
import net.minecraft.network.syncher.EntityDataSerializers;
import net.minecraft.network.syncher.SynchedEntityData;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.util.Mth;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.MoverType;
import net.minecraft.world.entity.TamableAnimal;
import net.minecraft.world.entity.decoration.ArmorStand;
import net.minecraft.world.entity.decoration.HangingEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.entity.projectile.Projectile;
import net.minecraft.world.entity.vehicle.AbstractMinecart;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate.StructureBlockInfo;
import net.minecraft.world.level.material.PushReaction;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import net.minecraftforge.entity.IEntityAdditionalSpawnData;
import net.minecraftforge.fml.DistExecutor;
import net.minecraftforge.network.NetworkHooks;
import net.minecraftforge.network.PacketDistributor;
public abstract class AbstractContraptionEntity extends Entity implements IEntityAdditionalSpawnData {
private static final EntityDataAccessor<Boolean> STALLED =
SynchedEntityData.defineId(AbstractContraptionEntity.class, EntityDataSerializers.BOOLEAN);
private static final EntityDataAccessor<Optional<UUID>> CONTROLLED_BY =
SynchedEntityData.defineId(AbstractContraptionEntity.class, EntityDataSerializers.OPTIONAL_UUID);
public final Map<Entity, MutableInt> collidingEntities;
protected Contraption contraption;
protected boolean initialized;
protected boolean prevPosInvalid;
private boolean skipActorStop;
/*
* staleTicks are a band-aid to prevent a frame or two of missing blocks between
* contraption discard and off-thread block placement on disassembly
*
* FIXME this timeout should be longer but then also cancelled early based on a
* chunk rebuild listener
*/
public int staleTicks = 3;
public AbstractContraptionEntity(EntityType<?> entityTypeIn, Level worldIn) {
super(entityTypeIn, worldIn);
prevPosInvalid = true;
collidingEntities = new IdentityHashMap<>();
}
protected void setContraption(Contraption contraption) {
this.contraption = contraption;
if (contraption == null)
return;
if (level.isClientSide)
return;
contraption.onEntityCreated(this);
}
@Override
public void move(MoverType pType, Vec3 pPos) {
if (pType == MoverType.SHULKER)
return;
if (pType == MoverType.SHULKER_BOX)
return;
if (pType == MoverType.PISTON)
return;
super.move(pType, pPos);
}
public boolean supportsTerrainCollision() {
return contraption instanceof TranslatingContraption && !(contraption instanceof ElevatorContraption);
}
protected void contraptionInitialize() {
contraption.onEntityInitialize(level, this);
initialized = true;
}
public boolean collisionEnabled() {
return true;
}
public void registerColliding(Entity collidingEntity) {
collidingEntities.put(collidingEntity, new MutableInt());
}
public void addSittingPassenger(Entity passenger, int seatIndex) {
for (Entity entity : getPassengers()) {
BlockPos seatOf = contraption.getSeatOf(entity.getUUID());
if (seatOf != null && seatOf.equals(contraption.getSeats()
.get(seatIndex))) {
if (entity instanceof Player)
return;
if (!(passenger instanceof Player))
return;
entity.stopRiding();
}
}
passenger.startRiding(this, true);
if (passenger instanceof TamableAnimal ta)
ta.setInSittingPose(true);
if (level.isClientSide)
return;
contraption.getSeatMapping()
.put(passenger.getUUID(), seatIndex);
AllPackets.getChannel().send(PacketDistributor.TRACKING_ENTITY.with(() -> this),
new ContraptionSeatMappingPacket(getId(), contraption.getSeatMapping()));
}
@Override
protected void removePassenger(Entity passenger) {
Vec3 transformedVector = getPassengerPosition(passenger, 1);
super.removePassenger(passenger);
if (passenger instanceof TamableAnimal ta)
ta.setInSittingPose(false);
if (level.isClientSide)
return;
if (transformedVector != null)
passenger.getPersistentData()
.put("ContraptionDismountLocation", VecHelper.writeNBT(transformedVector));
contraption.getSeatMapping()
.remove(passenger.getUUID());
AllPackets.getChannel().send(PacketDistributor.TRACKING_ENTITY.with(() -> this),
new ContraptionSeatMappingPacket(getId(), contraption.getSeatMapping(), passenger.getId()));
}
@Override
public Vec3 getDismountLocationForPassenger(LivingEntity entityLiving) {
Vec3 position = super.getDismountLocationForPassenger(entityLiving);
CompoundTag data = entityLiving.getPersistentData();
if (!data.contains("ContraptionDismountLocation"))
return position;
position = VecHelper.readNBT(data.getList("ContraptionDismountLocation", Tag.TAG_DOUBLE));
data.remove("ContraptionDismountLocation");
entityLiving.setOnGround(false);
if (!data.contains("ContraptionMountLocation"))
return position;
Vec3 prevPosition = VecHelper.readNBT(data.getList("ContraptionMountLocation", Tag.TAG_DOUBLE));
data.remove("ContraptionMountLocation");
if (entityLiving instanceof Player player && !prevPosition.closerThan(position, 5000))
AllAdvancements.LONG_TRAVEL.awardTo(player);
return position;
}
@Override
public void positionRider(Entity passenger, MoveFunction callback) {
if (!hasPassenger(passenger))
return;
Vec3 transformedVector = getPassengerPosition(passenger, 1);
if (transformedVector == null)
return;
callback.accept(passenger, transformedVector.x,
transformedVector.y + SeatEntity.getCustomEntitySeatOffset(passenger) - 1 / 8f, transformedVector.z);
}
public Vec3 getPassengerPosition(Entity passenger, float partialTicks) {
UUID id = passenger.getUUID();
if (passenger instanceof OrientedContraptionEntity) {
BlockPos localPos = contraption.getBearingPosOf(id);
if (localPos != null)
return toGlobalVector(VecHelper.getCenterOf(localPos), partialTicks)
.add(VecHelper.getCenterOf(BlockPos.ZERO))
.subtract(.5f, 1, .5f);
}
AABB bb = passenger.getBoundingBox();
double ySize = bb.getYsize();
BlockPos seat = contraption.getSeatOf(id);
if (seat == null)
return null;
Vec3 transformedVector = toGlobalVector(Vec3.atLowerCornerOf(seat)
.add(.5, passenger.getMyRidingOffset() + ySize - .15f, .5), partialTicks)
.add(VecHelper.getCenterOf(BlockPos.ZERO))
.subtract(0.5, ySize, 0.5);
return transformedVector;
}
@Override
protected boolean canAddPassenger(Entity p_184219_1_) {
if (p_184219_1_ instanceof OrientedContraptionEntity)
return true;
return contraption.getSeatMapping()
.size() < contraption.getSeats()
.size();
}
public Component getContraptionName() {
return getName();
}
public Optional<UUID> getControllingPlayer() {
return entityData.get(CONTROLLED_BY);
}
public void setControllingPlayer(@Nullable UUID playerId) {
entityData.set(CONTROLLED_BY, Optional.ofNullable(playerId));
}
public boolean startControlling(BlockPos controlsLocalPos, Player player) {
return false;
}
public boolean control(BlockPos controlsLocalPos, Collection<Integer> heldControls, Player player) {
return true;
}
public void stopControlling(BlockPos controlsLocalPos) {
getControllingPlayer().map(level::getPlayerByUUID)
.map(p -> (p instanceof ServerPlayer) ? ((ServerPlayer) p) : null)
.ifPresent(p -> AllPackets.getChannel().send(PacketDistributor.PLAYER.with(() -> p),
new ControlsStopControllingPacket()));
setControllingPlayer(null);
}
public boolean handlePlayerInteraction(Player player, BlockPos localPos, Direction side,
InteractionHand interactionHand) {
int indexOfSeat = contraption.getSeats()
.indexOf(localPos);
if (indexOfSeat == -1 || AllItems.WRENCH.isIn(player.getItemInHand(interactionHand))) {
if (contraption.interactors.containsKey(localPos))
return contraption.interactors.get(localPos)
.handlePlayerInteraction(player, interactionHand, localPos, this);
return contraption.storage.handlePlayerStorageInteraction(contraption, player, localPos);
}
if (player.isPassenger())
return false;
// Eject potential existing passenger
Entity toDismount = null;
for (Entry<UUID, Integer> entry : contraption.getSeatMapping()
.entrySet()) {
if (entry.getValue() != indexOfSeat)
continue;
for (Entity entity : getPassengers()) {
if (!entry.getKey()
.equals(entity.getUUID()))
continue;
if (entity instanceof Player)
return false;
toDismount = entity;
}
}
if (toDismount != null && !level.isClientSide) {
Vec3 transformedVector = getPassengerPosition(toDismount, 1);
toDismount.stopRiding();
if (transformedVector != null)
toDismount.teleportTo(transformedVector.x, transformedVector.y, transformedVector.z);
}
if (level.isClientSide)
return true;
addSittingPassenger(SeatBlock.getLeashed(level, player)
.or(player), indexOfSeat);
return true;
}
public Vec3 toGlobalVector(Vec3 localVec, float partialTicks) {
return toGlobalVector(localVec, partialTicks, false);
}
public Vec3 toGlobalVector(Vec3 localVec, float partialTicks, boolean prevAnchor) {
Vec3 anchor = prevAnchor ? getPrevAnchorVec() : getAnchorVec();
Vec3 rotationOffset = VecHelper.getCenterOf(BlockPos.ZERO);
localVec = localVec.subtract(rotationOffset);
localVec = applyRotation(localVec, partialTicks);
localVec = localVec.add(rotationOffset)
.add(anchor);
return localVec;
}
public Vec3 toLocalVector(Vec3 localVec, float partialTicks) {
return toLocalVector(localVec, partialTicks, false);
}
public Vec3 toLocalVector(Vec3 globalVec, float partialTicks, boolean prevAnchor) {
Vec3 anchor = prevAnchor ? getPrevAnchorVec() : getAnchorVec();
Vec3 rotationOffset = VecHelper.getCenterOf(BlockPos.ZERO);
globalVec = globalVec.subtract(anchor)
.subtract(rotationOffset);
globalVec = reverseRotation(globalVec, partialTicks);
globalVec = globalVec.add(rotationOffset);
return globalVec;
}
@Override
public void tick() {
if (contraption == null) {
discard();
return;
}
collidingEntities.entrySet()
.removeIf(e -> e.getValue()
.incrementAndGet() > 3);
xo = getX();
yo = getY();
zo = getZ();
prevPosInvalid = false;
if (!initialized)
contraptionInitialize();
contraption.tickStorage(this);
tickContraption();
super.tick();
if (level.isClientSide())
DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> () -> {
if (!contraption.deferInvalidate)
return;
contraption.deferInvalidate = false;
ContraptionRenderDispatcher.invalidate(contraption);
});
if (!(level instanceof ServerLevelAccessor sl))
return;
for (Entity entity : getPassengers()) {
if (entity instanceof Player)
continue;
if (entity.isAlwaysTicking())
continue;
if (sl.create$getEntityTickList()
.contains(entity))
continue;
positionRider(entity);
}
}
public void alignPassenger(Entity passenger) {
Vec3 motion = getContactPointMotion(passenger.getEyePosition());
if (Mth.equal(motion.length(), 0))
return;
if (passenger instanceof ArmorStand)
return;
if (!(passenger instanceof LivingEntity living))
return;
float prevAngle = living.getYRot();
float angle = AngleHelper.deg(-Mth.atan2(motion.x, motion.z));
angle = AngleHelper.angleLerp(0.4f, prevAngle, angle);
if (level.isClientSide) {
living.lerpTo(0, 0, 0, 0, 0, 0, false);
living.lerpHeadTo(0, 0);
living.setYRot(angle);
living.setXRot(0);
living.yBodyRot = angle;
living.yHeadRot = angle;
} else
living.setYRot(angle);
}
public void setBlock(BlockPos localPos, StructureBlockInfo newInfo) {
contraption.blocks.put(localPos, newInfo);
AllPackets.getChannel().send(PacketDistributor.TRACKING_ENTITY.with(() -> this),
new ContraptionBlockChangedPacket(getId(), localPos, newInfo.state));
}
protected abstract void tickContraption();
public abstract Vec3 applyRotation(Vec3 localPos, float partialTicks);
public abstract Vec3 reverseRotation(Vec3 localPos, float partialTicks);
public void tickActors() {
boolean stalledPreviously = contraption.stalled;
if (!level.isClientSide)
contraption.stalled = false;
skipActorStop = true;
for (MutablePair<StructureBlockInfo, MovementContext> pair : contraption.getActors()) {
MovementContext context = pair.right;
StructureBlockInfo blockInfo = pair.left;
MovementBehaviour actor = AllMovementBehaviours.getBehaviour(blockInfo.state);
if (actor == null)
continue;
Vec3 oldMotion = context.motion;
Vec3 actorPosition = toGlobalVector(VecHelper.getCenterOf(blockInfo.pos)
.add(actor.getActiveAreaOffset(context)), 1);
BlockPos gridPosition = new BlockPos(actorPosition);
boolean newPosVisited =
!context.stall && shouldActorTrigger(context, blockInfo, actor, actorPosition, gridPosition);
context.rotation = v -> applyRotation(v, 1);
context.position = actorPosition;
if (!isActorActive(context, actor) && !actor.mustTickWhileDisabled())
continue;
if (newPosVisited && !context.stall) {
actor.visitNewPosition(context, gridPosition);
if (!isAlive())
break;
context.firstMovement = false;
}
if (!oldMotion.equals(context.motion)) {
actor.onSpeedChanged(context, oldMotion, context.motion);
if (!isAlive())
break;
}
actor.tick(context);
if (!isAlive())
break;
contraption.stalled |= context.stall;
}
if (!isAlive()) {
contraption.stop(level);
return;
}
skipActorStop = false;
for (Entity entity : getPassengers()) {
if (!(entity instanceof OrientedContraptionEntity))
continue;
if (!contraption.stabilizedSubContraptions.containsKey(entity.getUUID()))
continue;
OrientedContraptionEntity orientedCE = (OrientedContraptionEntity) entity;
if (orientedCE.contraption != null && orientedCE.contraption.stalled) {
contraption.stalled = true;
break;
}
}
if (!level.isClientSide) {
if (!stalledPreviously && contraption.stalled)
onContraptionStalled();
entityData.set(STALLED, contraption.stalled);
return;
}
contraption.stalled = isStalled();
}
public void refreshPSIs() {
for (MutablePair<StructureBlockInfo, MovementContext> pair : contraption.getActors()) {
MovementContext context = pair.right;
StructureBlockInfo blockInfo = pair.left;
MovementBehaviour actor = AllMovementBehaviours.getBehaviour(blockInfo.state);
if (actor instanceof PortableStorageInterfaceMovement && isActorActive(context, actor))
if (context.position != null)
actor.visitNewPosition(context, new BlockPos(context.position));
}
}
protected boolean isActorActive(MovementContext context, MovementBehaviour actor) {
return actor.isActive(context);
}
protected void onContraptionStalled() {
AllPackets.getChannel().send(PacketDistributor.TRACKING_ENTITY.with(() -> this),
new ContraptionStallPacket(getId(), getX(), getY(), getZ(), getStalledAngle()));
}
protected boolean shouldActorTrigger(MovementContext context, StructureBlockInfo blockInfo, MovementBehaviour actor,
Vec3 actorPosition, BlockPos gridPosition) {
Vec3 previousPosition = context.position;
if (previousPosition == null)
return false;
context.motion = actorPosition.subtract(previousPosition);
if (!level.isClientSide() && context.contraption.entity instanceof CarriageContraptionEntity cce
&& cce.getCarriage() != null) {
Train train = cce.getCarriage().train;
double actualSpeed = train.speedBeforeStall != null ? train.speedBeforeStall : train.speed;
context.motion = context.motion.normalize()
.scale(Math.abs(actualSpeed));
}
Vec3 relativeMotion = context.motion;
relativeMotion = reverseRotation(relativeMotion, 1);
context.relativeMotion = relativeMotion;
return !new BlockPos(previousPosition).equals(gridPosition)
|| (context.relativeMotion.length() > 0 || context.contraption instanceof CarriageContraption)
&& context.firstMovement;
}
public void move(double x, double y, double z) {
setPos(getX() + x, getY() + y, getZ() + z);
}
public Vec3 getAnchorVec() {
return position();
}
public Vec3 getPrevAnchorVec() {
return getPrevPositionVec();
}
public float getYawOffset() {
return 0;
}
@Override
public void setPos(double x, double y, double z) {
super.setPos(x, y, z);
if (contraption == null)
return;
AABB cbox = contraption.bounds;
if (cbox == null)
return;
Vec3 actualVec = getAnchorVec();
setBoundingBox(cbox.move(actualVec));
}
public static float yawFromVector(Vec3 vec) {
return (float) ((3 * Math.PI / 2 + Math.atan2(vec.z, vec.x)) / Math.PI * 180);
}
public static float pitchFromVector(Vec3 vec) {
return (float) ((Math.acos(vec.y)) / Math.PI * 180);
}
public static EntityType.Builder<?> build(EntityType.Builder<?> builder) {
@SuppressWarnings("unchecked")
EntityType.Builder<AbstractContraptionEntity> entityBuilder =
(EntityType.Builder<AbstractContraptionEntity>) builder;
return entityBuilder.sized(1, 1);
}
@Override
protected void defineSynchedData() {
this.entityData.define(STALLED, false);
this.entityData.define(CONTROLLED_BY, Optional.empty());
}
@Override
public Packet<?> getAddEntityPacket() {
return NetworkHooks.getEntitySpawningPacket(this);
}
@Override
public void writeSpawnData(FriendlyByteBuf buffer) {
CompoundTag compound = new CompoundTag();
writeAdditional(compound, true);
if (ContraptionData.isTooLargeForSync(compound)) {
String info = getContraption().getType().id + " @" + position() + " (" + getStringUUID() + ")";
Create.LOGGER.warn("Could not send Contraption Spawn Data (Packet too big): " + info);
compound = null;
}
buffer.writeNbt(compound);
}
@Override
protected final void addAdditionalSaveData(CompoundTag compound) {
writeAdditional(compound, false);
}
protected void writeAdditional(CompoundTag compound, boolean spawnPacket) {
if (contraption != null)
compound.put("Contraption", contraption.writeNBT(spawnPacket));
compound.putBoolean("Stalled", isStalled());
compound.putBoolean("Initialized", initialized);
}
@Override
public void readSpawnData(FriendlyByteBuf additionalData) {
CompoundTag nbt = additionalData.readAnySizeNbt();
if (nbt != null) {
readAdditional(nbt, true);
}
}
@Override
protected final void readAdditionalSaveData(CompoundTag compound) {
readAdditional(compound, false);
}
protected void readAdditional(CompoundTag compound, boolean spawnData) {
if (compound.isEmpty())
return;
initialized = compound.getBoolean("Initialized");
contraption = Contraption.fromNBT(level, compound.getCompound("Contraption"), spawnData);
contraption.entity = this;
entityData.set(STALLED, compound.getBoolean("Stalled"));
}
public void disassemble() {
if (!isAlive())
return;
if (contraption == null)
return;
StructureTransform transform = makeStructureTransform();
contraption.stop(level);
AllPackets.getChannel().send(PacketDistributor.TRACKING_ENTITY.with(() -> this),
new ContraptionDisassemblyPacket(this.getId(), transform));
contraption.addBlocksToWorld(level, transform);
contraption.addPassengersToWorld(level, transform, getPassengers());
for (Entity entity : getPassengers()) {
if (!(entity instanceof OrientedContraptionEntity))
continue;
UUID id = entity.getUUID();
if (!contraption.stabilizedSubContraptions.containsKey(id))
continue;
BlockPos transformed = transform.apply(contraption.stabilizedSubContraptions.get(id)
.getConnectedPos());
entity.setPos(transformed.getX(), transformed.getY(), transformed.getZ());
((AbstractContraptionEntity) entity).disassemble();
}
skipActorStop = true;
discard();
ejectPassengers();
moveCollidedEntitiesOnDisassembly(transform);
AllSoundEvents.CONTRAPTION_DISASSEMBLE.playOnServer(level, blockPosition());
}
private void moveCollidedEntitiesOnDisassembly(StructureTransform transform) {
for (Entity entity : collidingEntities.keySet()) {
Vec3 localVec = toLocalVector(entity.position(), 0);
Vec3 transformed = transform.apply(localVec);
if (level.isClientSide)
entity.setPos(transformed.x, transformed.y + 1 / 16f, transformed.z);
else
entity.teleportTo(transformed.x, transformed.y + 1 / 16f, transformed.z);
}
}
@Override
public void remove(RemovalReason p_146834_) {
if (!level.isClientSide && !isRemoved() && contraption != null && !skipActorStop)
contraption.stop(level);
if (contraption != null)
contraption.onEntityRemoved(this);
super.remove(p_146834_);
}
protected abstract StructureTransform makeStructureTransform();
@Override
public void kill() {
ejectPassengers();
super.kill();
}
@Override
protected void outOfWorld() {
ejectPassengers();
super.outOfWorld();
}
@Override
public void onRemovedFromWorld() {
super.onRemovedFromWorld();
}
@Override
protected void doWaterSplashEffect() {}
public Contraption getContraption() {
return contraption;
}
public boolean isStalled() {
return entityData.get(STALLED);
}
@OnlyIn(Dist.CLIENT)
static void handleStallPacket(ContraptionStallPacket packet) {
if (Minecraft.getInstance().level.getEntity(packet.entityID) instanceof AbstractContraptionEntity ce)
ce.handleStallInformation(packet.x, packet.y, packet.z, packet.angle);
}
@OnlyIn(Dist.CLIENT)
static void handleBlockChangedPacket(ContraptionBlockChangedPacket packet) {
if (Minecraft.getInstance().level.getEntity(packet.entityID) instanceof AbstractContraptionEntity ce)
ce.handleBlockChange(packet.localPos, packet.newState);
}
@OnlyIn(Dist.CLIENT)
static void handleDisassemblyPacket(ContraptionDisassemblyPacket packet) {
if (Minecraft.getInstance().level.getEntity(packet.entityID) instanceof AbstractContraptionEntity ce)
ce.moveCollidedEntitiesOnDisassembly(packet.transform);
}
protected abstract float getStalledAngle();
protected abstract void handleStallInformation(double x, double y, double z, float angle);
@OnlyIn(Dist.CLIENT)
protected void handleBlockChange(BlockPos localPos, BlockState newState) {
if (contraption == null || !contraption.blocks.containsKey(localPos))
return;
StructureBlockInfo info = contraption.blocks.get(localPos);
contraption.blocks.put(localPos, new StructureBlockInfo(info.pos, newState, info.nbt));
if (info.state != newState && !(newState.getBlock() instanceof SlidingDoorBlock))
contraption.deferInvalidate = true;
contraption.invalidateColliders();
}
@Override
public CompoundTag saveWithoutId(CompoundTag nbt) {
Vec3 vec = position();
List<Entity> passengers = getPassengers();
for (Entity entity : passengers) {
// setPos has world accessing side-effects when removed == null
entity.removalReason = RemovalReason.UNLOADED_TO_CHUNK;
// Gather passengers into same chunk when saving
Vec3 prevVec = entity.position();
entity.setPosRaw(vec.x, prevVec.y, vec.z);
// Super requires all passengers to not be removed in order to write them to the
// tag
entity.removalReason = null;
}
CompoundTag tag = super.saveWithoutId(nbt);
return tag;
}
@Override
// Make sure nothing can move contraptions out of the way
public void setDeltaMovement(Vec3 motionIn) {}
@Override
public PushReaction getPistonPushReaction() {
return PushReaction.IGNORE;
}
public void setContraptionMotion(Vec3 vec) {
super.setDeltaMovement(vec);
}
@Override
public boolean isPickable() {
return false;
}
@Override
public boolean hurt(DamageSource source, float amount) {
return false;
}
public Vec3 getPrevPositionVec() {
return prevPosInvalid ? position() : new Vec3(xo, yo, zo);
}
public abstract ContraptionRotationState getRotationState();
public Vec3 getContactPointMotion(Vec3 globalContactPoint) {
if (prevPosInvalid)
return Vec3.ZERO;
Vec3 contactPoint = toGlobalVector(toLocalVector(globalContactPoint, 0, true), 1, true);
Vec3 contraptionLocalMovement = contactPoint.subtract(globalContactPoint);
Vec3 contraptionAnchorMovement = position().subtract(getPrevPositionVec());
return contraptionLocalMovement.add(contraptionAnchorMovement);
}
public boolean canCollideWith(Entity e) {
if (e instanceof Player && e.isSpectator())
return false;
if (e.noPhysics)
return false;
if (e instanceof HangingEntity)
return false;
if (e instanceof AbstractMinecart)
return !(contraption instanceof MountedContraption);
if (e instanceof SuperGlueEntity)
return false;
if (e instanceof SeatEntity)
return false;
if (e instanceof Projectile)
return false;
if (e.getVehicle() != null)
return false;
Entity riding = this.getVehicle();
while (riding != null) {
if (riding == e)
return false;
riding = riding.getVehicle();
}
return e.getPistonPushReaction() == PushReaction.NORMAL;
}
@Override
public boolean hasExactlyOnePlayerPassenger() {
return false;
}
@OnlyIn(Dist.CLIENT)
public abstract void applyLocalTransforms(PoseStack matrixStack, float partialTicks);
public static class ContraptionRotationState {
public static final ContraptionRotationState NONE = new ContraptionRotationState();
float xRotation = 0;
float yRotation = 0;
float zRotation = 0;
float secondYRotation = 0;
Matrix3d matrix;
public Matrix3d asMatrix() {
if (matrix != null)
return matrix;
matrix = new Matrix3d().asIdentity();
if (xRotation != 0)
matrix.multiply(new Matrix3d().asXRotation(AngleHelper.rad(-xRotation)));
if (yRotation != 0)
matrix.multiply(new Matrix3d().asYRotation(AngleHelper.rad(-yRotation)));
if (zRotation != 0)
matrix.multiply(new Matrix3d().asZRotation(AngleHelper.rad(-zRotation)));
return matrix;
}
public boolean hasVerticalRotation() {
return xRotation != 0 || zRotation != 0;
}
public float getYawOffset() {
return secondYRotation;
}
}
@Override
protected boolean updateInWaterStateAndDoFluidPushing() {
/*
* Override this with an empty method to reduce enormous calculation time when
* contraptions are in water WARNING: THIS HAS A BUNCH OF SIDE EFFECTS! - Fluids
* will not try to change contraption movement direction - this.inWater and
* this.isInWater() will return unreliable data - entities riding a contraption
* will not cause water splashes (seats are their own entity so this should be
* fine) - fall distance is not reset when the contraption is in water -
* this.eyesInWater and this.canSwim() will always be false - swimming state
* will never be updated
*/
return false;
}
@Override
public void setSecondsOnFire(int p_70015_1_) {
// Contraptions no longer catch fire
}
public boolean isReadyForRender() {
return initialized;
}
public boolean isAliveOrStale() {
return isAlive() || level.isClientSide() ? staleTicks > 0 : false;
}
}