package com.simibubi.create.content.logistics.trains.entity; import java.lang.ref.WeakReference; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Objects; import java.util.Set; import java.util.UUID; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; import java.util.function.Function; import javax.annotation.Nullable; import org.apache.commons.lang3.mutable.MutableDouble; import com.simibubi.create.content.contraptions.components.structureMovement.Contraption; import com.simibubi.create.content.contraptions.components.structureMovement.train.TrainCargoManager; import com.simibubi.create.content.logistics.trains.DimensionPalette; import com.simibubi.create.content.logistics.trains.TrackGraph; import com.simibubi.create.content.logistics.trains.TrackNodeLocation; import com.simibubi.create.content.logistics.trains.entity.TravellingPoint.IEdgePointListener; import com.simibubi.create.content.logistics.trains.entity.TravellingPoint.ITrackSelector; import com.simibubi.create.foundation.advancement.AllAdvancements; import com.simibubi.create.foundation.utility.Couple; import com.simibubi.create.foundation.utility.Iterate; import com.simibubi.create.foundation.utility.NBTHelper; import com.simibubi.create.foundation.utility.VecHelper; import net.minecraft.core.BlockPos; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.Tag; import net.minecraft.resources.ResourceKey; import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; import net.minecraft.util.Mth; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.EntityType; import net.minecraft.world.entity.player.Player; import net.minecraft.world.level.Level; import net.minecraft.world.phys.Vec3; import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.api.distmarker.OnlyIn; import net.minecraftforge.fml.DistExecutor; public class Carriage { public static final AtomicInteger netIdGenerator = new AtomicInteger(); public Train train; public int id; public boolean blocked; public boolean stalled; public Couple presentConductors; public int bogeySpacing; public Couple bogeys; public TrainCargoManager storage; CompoundTag serialisedEntity; Map serialisedPassengers; private Map, DimensionalCarriageEntity> entities; static final int FIRST = 0, MIDDLE = 1, LAST = 2, BOTH = 3; public Carriage(CarriageBogey bogey1, @Nullable CarriageBogey bogey2, int bogeySpacing) { this.bogeySpacing = bogeySpacing; this.bogeys = Couple.create(bogey1, bogey2); this.id = netIdGenerator.incrementAndGet(); this.serialisedEntity = new CompoundTag(); this.presentConductors = Couple.create(false, false); this.serialisedPassengers = new HashMap<>(); this.entities = new HashMap<>(); this.storage = new TrainCargoManager(); bogey1.setLeading(); bogey1.carriage = this; if (bogey2 != null) bogey2.carriage = this; } public boolean isOnIncompatibleTrack() { return leadingBogey().type.isOnIncompatibleTrack(this, true) || trailingBogey().type.isOnIncompatibleTrack(this, false); } public void setTrain(Train train) { this.train = train; } public boolean presentInMultipleDimensions() { return entities.size() > 1; } public void setContraption(Level level, CarriageContraption contraption) { this.storage = null; CarriageContraptionEntity entity = CarriageContraptionEntity.create(level, contraption); entity.setCarriage(this); contraption.startMoving(level); contraption.onEntityInitialize(level, entity); updateContraptionAnchors(); DimensionalCarriageEntity dimensional = getDimensional(level); dimensional.alignEntity(entity); dimensional.removeAndSaveEntity(entity, false); } public DimensionalCarriageEntity getDimensional(Level level) { return getDimensional(level.dimension()); } public DimensionalCarriageEntity getDimensional(ResourceKey dimension) { return entities.computeIfAbsent(dimension, $ -> new DimensionalCarriageEntity()); } @Nullable public DimensionalCarriageEntity getDimensionalIfPresent(ResourceKey dimension) { return entities.get(dimension); } public double travel(Level level, TrackGraph graph, double distance, TravellingPoint toFollowForward, TravellingPoint toFollowBackward, int type) { Function forwardControl = toFollowForward == null ? train.navigation::control : mp -> mp.follow(toFollowForward); Function backwardControl = toFollowBackward == null ? train.navigation::control : mp -> mp.follow(toFollowBackward); boolean onTwoBogeys = isOnTwoBogeys(); double stress = train.derailed ? 0 : onTwoBogeys ? bogeySpacing - getAnchorDiff() : 0; blocked = false; MutableDouble distanceMoved = new MutableDouble(distance); boolean iterateFromBack = distance < 0; for (boolean firstBogey : Iterate.trueAndFalse) { if (!firstBogey && !onTwoBogeys) continue; boolean actuallyFirstBogey = !onTwoBogeys || (firstBogey ^ iterateFromBack); CarriageBogey bogey = bogeys.get(actuallyFirstBogey); double bogeyCorrection = stress * (actuallyFirstBogey ? 0.5d : -0.5d); double bogeyStress = bogey.getStress(); for (boolean firstWheel : Iterate.trueAndFalse) { boolean actuallyFirstWheel = firstWheel ^ iterateFromBack; TravellingPoint point = bogey.points.get(actuallyFirstWheel); TravellingPoint prevPoint = !actuallyFirstWheel ? bogey.points.getFirst() : !actuallyFirstBogey && onTwoBogeys ? bogeys.getFirst().points.getSecond() : null; TravellingPoint nextPoint = actuallyFirstWheel ? bogey.points.getSecond() : actuallyFirstBogey && onTwoBogeys ? bogeys.getSecond().points.getFirst() : null; double correction = bogeyStress * (actuallyFirstWheel ? 0.5d : -0.5d); double toMove = distanceMoved.getValue(); ITrackSelector frontTrackSelector = prevPoint == null ? forwardControl.apply(point) : point.follow(prevPoint); ITrackSelector backTrackSelector = nextPoint == null ? backwardControl.apply(point) : point.follow(nextPoint); boolean atFront = (type == FIRST || type == BOTH) && actuallyFirstWheel && actuallyFirstBogey; boolean atBack = (type == LAST || type == BOTH) && !actuallyFirstWheel && (!actuallyFirstBogey || !onTwoBogeys); IEdgePointListener frontListener = train.frontSignalListener(); IEdgePointListener backListener = train.backSignalListener(); IEdgePointListener passiveListener = point.ignoreEdgePoints(); toMove += correction + bogeyCorrection; ITrackSelector trackSelector = toMove > 0 ? frontTrackSelector : backTrackSelector; IEdgePointListener signalListener = toMove > 0 ? atFront ? frontListener : atBack ? backListener : passiveListener : atFront ? backListener : atBack ? frontListener : passiveListener; double moved = point.travel(graph, toMove, trackSelector, signalListener, point.ignoreTurns(), c -> { for (DimensionalCarriageEntity dce : entities.values()) if (c.either(tnl -> tnl.equalsIgnoreDim(dce.pivot))) return false; if (entities.size() > 1) { train.status.doublePortal(); return true; } return false; }); blocked |= point.blocked; distanceMoved.setValue(moved); } } updateContraptionAnchors(); manageEntities(level); return distanceMoved.getValue(); } public double getAnchorDiff() { double diff = 0; int entries = 0; TravellingPoint leadingPoint = getLeadingPoint(); TravellingPoint trailingPoint = getTrailingPoint(); if (leadingPoint.node1 != null && trailingPoint.node1 != null) if (!leadingPoint.node1.getLocation().dimension.equals(trailingPoint.node1.getLocation().dimension)) return bogeySpacing; for (DimensionalCarriageEntity dce : entities.values()) if (dce.leadingAnchor() != null && dce.trailingAnchor() != null) { entries++; diff += dce.leadingAnchor() .distanceTo(dce.trailingAnchor()); } if (entries == 0) return bogeySpacing; return diff / entries; } public void updateConductors() { if (anyAvailableEntity() == null || entities.size() > 1 || serialisedPassengers.size() > 0) return; presentConductors.replace($ -> false); for (DimensionalCarriageEntity dimensionalCarriageEntity : entities.values()) { CarriageContraptionEntity entity = dimensionalCarriageEntity.entity.get(); if (entity != null && entity.isAlive()) presentConductors.replaceWithParams((current, checked) -> current || checked, entity.checkConductors()); } } private Set> currentlyTraversedDimensions = new HashSet<>(); public void manageEntities(Level level) { currentlyTraversedDimensions.clear(); bogeys.forEach(cb -> { if (cb == null) return; cb.points.forEach(tp -> { if (tp.node1 == null) return; currentlyTraversedDimensions.add(tp.node1.getLocation().dimension); }); }); for (Iterator, DimensionalCarriageEntity>> iterator = entities.entrySet() .iterator(); iterator.hasNext();) { Entry, DimensionalCarriageEntity> entry = iterator.next(); boolean discard = !currentlyTraversedDimensions.isEmpty() && !currentlyTraversedDimensions.contains(entry.getKey()); MinecraftServer server = level.getServer(); if (server == null) continue; ServerLevel currentLevel = server.getLevel(entry.getKey()); if (currentLevel == null) continue; DimensionalCarriageEntity dimensionalCarriageEntity = entry.getValue(); CarriageContraptionEntity entity = dimensionalCarriageEntity.entity.get(); if (entity == null) { if (discard) iterator.remove(); else if (dimensionalCarriageEntity.positionAnchor != null && CarriageEntityHandler .isActiveChunk(currentLevel, new BlockPos(dimensionalCarriageEntity.positionAnchor))) dimensionalCarriageEntity.createEntity(currentLevel, anyAvailableEntity() == null); } else { if (discard) { discard = dimensionalCarriageEntity.discardTicks > 3; dimensionalCarriageEntity.discardTicks++; } else dimensionalCarriageEntity.discardTicks = 0; CarriageEntityHandler.validateCarriageEntity(entity); if (!entity.isAlive() || entity.leftTickingChunks || discard) { dimensionalCarriageEntity.removeAndSaveEntity(entity, discard); if (discard) iterator.remove(); continue; } } entity = dimensionalCarriageEntity.entity.get(); if (entity != null && dimensionalCarriageEntity.positionAnchor != null) { dimensionalCarriageEntity.alignEntity(entity); entity.syncCarriage(); } } } public void updateContraptionAnchors() { CarriageBogey leadingBogey = leadingBogey(); if (leadingBogey.points.either(t -> t.edge == null)) return; CarriageBogey trailingBogey = trailingBogey(); if (trailingBogey.points.either(t -> t.edge == null)) return; ResourceKey leadingBogeyDim = leadingBogey.getDimension(); ResourceKey trailingBogeyDim = trailingBogey.getDimension(); double leadingWheelSpacing = leadingBogey.type.getWheelPointSpacing(); double trailingWheelSpacing = trailingBogey.type.getWheelPointSpacing(); boolean leadingUpsideDown = leadingBogey.isUpsideDown(); boolean trailingUpsideDown = trailingBogey.isUpsideDown(); for (boolean leading : Iterate.trueAndFalse) { TravellingPoint point = leading ? getLeadingPoint() : getTrailingPoint(); TravellingPoint otherPoint = !leading ? getLeadingPoint() : getTrailingPoint(); ResourceKey dimension = point.node1.getLocation().dimension; ResourceKey otherDimension = otherPoint.node1.getLocation().dimension; if (dimension.equals(otherDimension) && leading) { getDimensional(dimension).discardPivot(); continue; } DimensionalCarriageEntity dce = getDimensional(dimension); dce.positionAnchor = dimension.equals(leadingBogeyDim) ? leadingBogey.getAnchorPosition() : pivoted(dce, dimension, point, leading ? leadingWheelSpacing / 2 : bogeySpacing + trailingWheelSpacing / 2, leadingUpsideDown, trailingUpsideDown); boolean backAnchorFlip = trailingBogey.isUpsideDown() ^ leadingBogey.isUpsideDown(); if (isOnTwoBogeys()) { dce.rotationAnchors.setFirst(dimension.equals(leadingBogeyDim) ? leadingBogey.getAnchorPosition() : pivoted(dce, dimension, point, leading ? leadingWheelSpacing / 2 : bogeySpacing + trailingWheelSpacing / 2, leadingUpsideDown, trailingUpsideDown)); dce.rotationAnchors.setSecond(dimension.equals(trailingBogeyDim) ? trailingBogey.getAnchorPosition(backAnchorFlip) : pivoted(dce, dimension, point, leading ? leadingWheelSpacing / 2 + bogeySpacing : trailingWheelSpacing / 2, leadingUpsideDown, trailingUpsideDown)); } else { if (dimension.equals(otherDimension)) { dce.rotationAnchors = leadingBogey.points.map(TravellingPoint::getPosition); } else { dce.rotationAnchors.setFirst(leadingBogey.points.getFirst() == point ? point.getPosition() : pivoted(dce, dimension, point, leadingWheelSpacing, leadingUpsideDown, trailingUpsideDown)); dce.rotationAnchors.setSecond(leadingBogey.points.getSecond() == point ? point.getPosition() : pivoted(dce, dimension, point, leadingWheelSpacing, leadingUpsideDown, trailingUpsideDown)); } } int prevmin = dce.minAllowedLocalCoord(); int prevmax = dce.maxAllowedLocalCoord(); dce.updateCutoff(leading); if (prevmin != dce.minAllowedLocalCoord() || prevmax != dce.maxAllowedLocalCoord()) { dce.updateRenderedCutoff(); dce.updatePassengerLoadout(); } } } private Vec3 pivoted(DimensionalCarriageEntity dce, ResourceKey dimension, TravellingPoint start, double offset, boolean leadingUpsideDown, boolean trailingUpsideDown) { if (train.graph == null) return dce.pivot == null ? null : dce.pivot.getLocation(); TrackNodeLocation pivot = dce.findPivot(dimension, start == getLeadingPoint()); if (pivot == null) return null; boolean flipped = start != getLeadingPoint() && (leadingUpsideDown != trailingUpsideDown); Vec3 startVec = start.getPosition(flipped); Vec3 portalVec = pivot.getLocation() .add(0, leadingUpsideDown ? -1.0 : 1.0, 0); return VecHelper.lerp((float) (offset / startVec.distanceTo(portalVec)), startVec, portalVec); } public void alignEntity(Level level) { DimensionalCarriageEntity dimensionalCarriageEntity = entities.get(level.dimension()); if (dimensionalCarriageEntity != null) { CarriageContraptionEntity entity = dimensionalCarriageEntity.entity.get(); if (entity != null) dimensionalCarriageEntity.alignEntity(entity); } } public TravellingPoint getLeadingPoint() { return leadingBogey().leading(); } public TravellingPoint getTrailingPoint() { return trailingBogey().trailing(); } public CarriageBogey leadingBogey() { return bogeys.getFirst(); } public CarriageBogey trailingBogey() { return isOnTwoBogeys() ? bogeys.getSecond() : leadingBogey(); } public boolean isOnTwoBogeys() { return bogeys.getSecond() != null; } public CarriageContraptionEntity anyAvailableEntity() { for (DimensionalCarriageEntity dimensionalCarriageEntity : entities.values()) { CarriageContraptionEntity entity = dimensionalCarriageEntity.entity.get(); if (entity != null) return entity; } return null; } public void forEachPresentEntity(Consumer callback) { for (DimensionalCarriageEntity dimensionalCarriageEntity : entities.values()) { CarriageContraptionEntity entity = dimensionalCarriageEntity.entity.get(); if (entity != null) callback.accept(entity); } } public CompoundTag write(DimensionPalette dimensions) { CompoundTag tag = new CompoundTag(); tag.put("FirstBogey", bogeys.getFirst() .write(dimensions)); if (isOnTwoBogeys()) tag.put("SecondBogey", bogeys.getSecond() .write(dimensions)); tag.putInt("Spacing", bogeySpacing); tag.putBoolean("FrontConductor", presentConductors.getFirst()); tag.putBoolean("BackConductor", presentConductors.getSecond()); tag.putBoolean("Stalled", stalled); Map passengerMap = new HashMap<>(); for (DimensionalCarriageEntity dimensionalCarriageEntity : entities.values()) { CarriageContraptionEntity entity = dimensionalCarriageEntity.entity.get(); if (entity == null) continue; serialize(entity); Contraption contraption = entity.getContraption(); if (contraption == null) continue; Map mapping = contraption.getSeatMapping(); for (Entity passenger : entity.getPassengers()) if (mapping.containsKey(passenger.getUUID())) passengerMap.put(mapping.get(passenger.getUUID()), passenger.serializeNBT()); } tag.put("Entity", serialisedEntity.copy()); CompoundTag passengerTag = new CompoundTag(); passengerMap.putAll(serialisedPassengers); passengerMap.forEach((seat, nbt) -> passengerTag.put("Seat" + seat, nbt.copy())); tag.put("Passengers", passengerTag); tag.put("EntityPositioning", NBTHelper.writeCompoundList(entities.entrySet(), e -> { CompoundTag c = e.getValue() .write(); c.putInt("Dim", dimensions.encode(e.getKey())); return c; })); return tag; } private void serialize(Entity entity) { serialisedEntity = entity.serializeNBT(); serialisedEntity.remove("Passengers"); serialisedEntity.getCompound("Contraption") .remove("Passengers"); } public static Carriage read(CompoundTag tag, TrackGraph graph, DimensionPalette dimensions) { CarriageBogey bogey1 = CarriageBogey.read(tag.getCompound("FirstBogey"), graph, dimensions); CarriageBogey bogey2 = tag.contains("SecondBogey") ? CarriageBogey.read(tag.getCompound("SecondBogey"), graph, dimensions) : null; Carriage carriage = new Carriage(bogey1, bogey2, tag.getInt("Spacing")); carriage.stalled = tag.getBoolean("Stalled"); carriage.presentConductors = Couple.create(tag.getBoolean("FrontConductor"), tag.getBoolean("BackConductor")); carriage.serialisedEntity = tag.getCompound("Entity") .copy(); NBTHelper.iterateCompoundList(tag.getList("EntityPositioning", Tag.TAG_COMPOUND), c -> carriage.getDimensional(dimensions.decode(c.getInt("Dim"))) .read(c)); CompoundTag passengersTag = tag.getCompound("Passengers"); passengersTag.getAllKeys() .forEach(key -> carriage.serialisedPassengers.put(Integer.valueOf(key.substring(4)), passengersTag.getCompound(key))); return carriage; } private TravellingPoint portalScout = new TravellingPoint(); public class DimensionalCarriageEntity { public Vec3 positionAnchor; public Couple rotationAnchors; public WeakReference entity; public TrackNodeLocation pivot; int discardTicks; // 0 == whole, 0..1 = fading out, -1..0 = fading in public float cutoff; // client public boolean pointsInitialised; public DimensionalCarriageEntity() { this.entity = new WeakReference<>(null); this.rotationAnchors = Couple.create(null, null); this.pointsInitialised = false; } public void discardPivot() { int prevmin = minAllowedLocalCoord(); int prevmax = maxAllowedLocalCoord(); cutoff = 0; pivot = null; if ((!serialisedPassengers.isEmpty() && entity.get() != null) || prevmin != minAllowedLocalCoord() || prevmax != maxAllowedLocalCoord()) { updatePassengerLoadout(); updateRenderedCutoff(); } } public void updateCutoff(boolean leadingIsCurrent) { Vec3 leadingAnchor = rotationAnchors.getFirst(); Vec3 trailingAnchor = rotationAnchors.getSecond(); if (leadingAnchor == null || trailingAnchor == null) return; if (pivot == null) { cutoff = 0; return; } Vec3 pivotLoc = pivot.getLocation() .add(0, 1, 0); double leadingSpacing = leadingBogey().type.getWheelPointSpacing() / 2; double trailingSpacing = trailingBogey().type.getWheelPointSpacing() / 2; double anchorSpacing = leadingSpacing + bogeySpacing + trailingSpacing; if (isOnTwoBogeys()) { Vec3 diff = trailingAnchor.subtract(leadingAnchor) .normalize(); trailingAnchor = trailingAnchor.add(diff.scale(trailingSpacing)); leadingAnchor = leadingAnchor.add(diff.scale(-leadingSpacing)); } double leadingDiff = leadingAnchor.distanceTo(pivotLoc); double trailingDiff = trailingAnchor.distanceTo(pivotLoc); leadingDiff /= anchorSpacing; trailingDiff /= anchorSpacing; if (leadingIsCurrent && leadingDiff > trailingDiff && leadingDiff > 1) cutoff = 0; else if (leadingIsCurrent && leadingDiff < trailingDiff && trailingDiff > 1) cutoff = 1; else if (!leadingIsCurrent && leadingDiff > trailingDiff && leadingDiff > 1) cutoff = -1; else if (!leadingIsCurrent && leadingDiff < trailingDiff && trailingDiff > 1) cutoff = 0; else cutoff = (float) Mth.clamp(1 - (leadingIsCurrent ? leadingDiff : trailingDiff), 0, 1) * (leadingIsCurrent ? 1 : -1); } public TrackNodeLocation findPivot(ResourceKey dimension, boolean leading) { if (pivot != null) return pivot; TravellingPoint start = leading ? getLeadingPoint() : getTrailingPoint(); TravellingPoint end = !leading ? getLeadingPoint() : getTrailingPoint(); portalScout.node1 = start.node1; portalScout.node2 = start.node2; portalScout.edge = start.edge; portalScout.position = start.position; ITrackSelector trackSelector = portalScout.follow(end); int distance = bogeySpacing + 10; int direction = leading ? -1 : 1; portalScout.travel(train.graph, direction * distance, trackSelector, portalScout.ignoreEdgePoints(), portalScout.ignoreTurns(), nodes -> { for (boolean b : Iterate.trueAndFalse) if (nodes.get(b).dimension.equals(dimension)) pivot = nodes.get(b); return true; }); return pivot; } public CompoundTag write() { CompoundTag tag = new CompoundTag(); tag.putFloat("Cutoff", cutoff); tag.putInt("DiscardTicks", discardTicks); storage.write(tag, false); if (pivot != null) tag.put("Pivot", pivot.write(null)); if (positionAnchor != null) tag.put("PositionAnchor", VecHelper.writeNBT(positionAnchor)); if (rotationAnchors.both(Objects::nonNull)) tag.put("RotationAnchors", rotationAnchors.serializeEach(VecHelper::writeNBTCompound)); return tag; } public void read(CompoundTag tag) { cutoff = tag.getFloat("Cutoff"); discardTicks = tag.getInt("DiscardTicks"); storage.read(tag, null, false); if (tag.contains("Pivot")) pivot = TrackNodeLocation.read(tag.getCompound("Pivot"), null); if (positionAnchor != null) return; if (tag.contains("PositionAnchor")) positionAnchor = VecHelper.readNBT(tag.getList("PositionAnchor", Tag.TAG_DOUBLE)); if (tag.contains("RotationAnchors")) rotationAnchors = Couple.deserializeEach(tag.getList("RotationAnchors", Tag.TAG_COMPOUND), VecHelper::readNBTCompound); } public Vec3 leadingAnchor() { return isOnTwoBogeys() ? rotationAnchors.getFirst() : positionAnchor; } public Vec3 trailingAnchor() { return isOnTwoBogeys() ? rotationAnchors.getSecond() : positionAnchor; } public int minAllowedLocalCoord() { if (cutoff <= 0) return Integer.MIN_VALUE; if (cutoff >= 1) return Integer.MAX_VALUE; return Mth.floor(-bogeySpacing + -1 + (2 + bogeySpacing) * cutoff); } public int maxAllowedLocalCoord() { if (cutoff >= 0) return Integer.MAX_VALUE; if (cutoff <= -1) return Integer.MIN_VALUE; return Mth.ceil(-bogeySpacing + -1 + (2 + bogeySpacing) * (cutoff + 1)); } public void updatePassengerLoadout() { Entity entity = this.entity.get(); if (!(entity instanceof CarriageContraptionEntity cce)) return; if (!(entity.level instanceof ServerLevel sLevel)) return; Set loadedPassengers = new HashSet<>(); int min = minAllowedLocalCoord(); int max = maxAllowedLocalCoord(); for (Entry entry : serialisedPassengers.entrySet()) { Integer seatId = entry.getKey(); List seats = cce.getContraption() .getSeats(); if (seatId >= seats.size()) continue; BlockPos localPos = seats.get(seatId); if (!cce.isLocalCoordWithin(localPos, min, max)) continue; CompoundTag tag = entry.getValue(); Entity passenger = null; if (tag.contains("PlayerPassenger")) { passenger = sLevel.getServer() .getPlayerList() .getPlayer(tag.getUUID("PlayerPassenger")); } else { passenger = EntityType.loadEntityRecursive(tag, entity.level, e -> { e.moveTo(positionAnchor); return e; }); if (passenger != null) sLevel.tryAddFreshEntityWithPassengers(passenger); } if (passenger != null) { ResourceKey passengerDimension = passenger.level.dimension(); if (!passengerDimension.equals(sLevel.dimension()) && passenger instanceof ServerPlayer sp) continue; cce.addSittingPassenger(passenger, seatId); } loadedPassengers.add(seatId); } loadedPassengers.forEach(serialisedPassengers::remove); Map mapping = cce.getContraption() .getSeatMapping(); for (Entity passenger : entity.getPassengers()) { BlockPos localPos = cce.getContraption() .getSeatOf(passenger.getUUID()); if (cce.isLocalCoordWithin(localPos, min, max)) continue; if (!mapping.containsKey(passenger.getUUID())) continue; Integer seat = mapping.get(passenger.getUUID()); if ((passenger instanceof ServerPlayer sp)) { dismountPlayer(sLevel, sp, seat, true); continue; } serialisedPassengers.put(seat, passenger.serializeNBT()); passenger.discard(); } } private void dismountPlayer(ServerLevel sLevel, ServerPlayer sp, Integer seat, boolean portal) { if (!portal) { sp.stopRiding(); return; } CompoundTag tag = new CompoundTag(); tag.putUUID("PlayerPassenger", sp.getUUID()); serialisedPassengers.put(seat, tag); sp.stopRiding(); sp.getPersistentData() .remove("ContraptionDismountLocation"); for (Entry, DimensionalCarriageEntity> other : entities.entrySet()) { DimensionalCarriageEntity otherDce = other.getValue(); if (otherDce == this) continue; if (sp.level.dimension() .equals(other.getKey())) continue; Vec3 loc = otherDce.pivot == null ? otherDce.positionAnchor : otherDce.pivot.getLocation(); if (loc == null) continue; ServerLevel level = sLevel.getServer() .getLevel(other.getKey()); sp.teleportTo(level, loc.x, loc.y, loc.z, sp.getYRot(), sp.getXRot()); sp.setPortalCooldown(); AllAdvancements.TRAIN_PORTAL.awardTo(sp); } } public void updateRenderedCutoff() { Entity entity = this.entity.get(); if (!(entity instanceof CarriageContraptionEntity cce)) return; Contraption contraption = cce.getContraption(); if (!(contraption instanceof CarriageContraption cc)) return; cc.portalCutoffMin = minAllowedLocalCoord(); cc.portalCutoffMax = maxAllowedLocalCoord(); if (!entity.level.isClientSide()) return; DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> () -> invalidate(cce)); } @OnlyIn(Dist.CLIENT) private void invalidate(CarriageContraptionEntity entity) { entity.getContraption().deferInvalidate = true; entity.updateRenderedPortalCutoff(); } private void createEntity(Level level, boolean loadPassengers) { Entity entity = EntityType.create(serialisedEntity, level) .orElse(null); if (!(entity instanceof CarriageContraptionEntity cce)) { train.invalid = true; return; } entity.moveTo(positionAnchor); this.entity = new WeakReference<>(cce); cce.setCarriage(Carriage.this); cce.syncCarriage(); if (level instanceof ServerLevel sl) sl.addFreshEntity(entity); updatePassengerLoadout(); } private void removeAndSaveEntity(CarriageContraptionEntity entity, boolean portal) { Contraption contraption = entity.getContraption(); if (contraption != null) { Map mapping = contraption.getSeatMapping(); for (Entity passenger : entity.getPassengers()) { if (!mapping.containsKey(passenger.getUUID())) continue; Integer seat = mapping.get(passenger.getUUID()); if (passenger instanceof ServerPlayer sp) { dismountPlayer(sp.getLevel(), sp, seat, portal); continue; } serialisedPassengers.put(seat, passenger.serializeNBT()); } } for (Entity passenger : entity.getPassengers()) if (!(passenger instanceof Player)) passenger.discard(); serialize(entity); entity.discard(); this.entity.clear(); } public void alignEntity(CarriageContraptionEntity entity) { if (rotationAnchors.either(Objects::isNull)) return; Vec3 positionVec = rotationAnchors.getFirst(); Vec3 coupledVec = rotationAnchors.getSecond(); double diffX = positionVec.x - coupledVec.x; double diffY = positionVec.y - coupledVec.y; double diffZ = positionVec.z - coupledVec.z; entity.prevYaw = entity.yaw; entity.prevPitch = entity.pitch; if (!entity.level.isClientSide()) { Vec3 lookahead = positionAnchor.add(positionAnchor.subtract(entity.position()) .normalize() .scale(16)); for (Entity e : entity.getPassengers()) { if (!(e instanceof Player)) continue; if (e.distanceToSqr(entity) > 32 * 32) continue; if (CarriageEntityHandler.isActiveChunk(entity.level, new BlockPos(lookahead))) break; train.carriageWaitingForChunks = id; return; } if (entity.getPassengers() .stream() .anyMatch(p -> p instanceof Player) ) { } if (train.carriageWaitingForChunks == id) train.carriageWaitingForChunks = -1; entity.setServerSidePrevPosition(); } entity.setPos(positionAnchor); entity.yaw = (float) (Mth.atan2(diffZ, diffX) * 180 / Math.PI) + 180; entity.pitch = (float) (Math.atan2(diffY, Math.sqrt(diffX * diffX + diffZ * diffZ)) * 180 / Math.PI) * -1; if (!entity.firstPositionUpdate) return; entity.xo = entity.getX(); entity.yo = entity.getY(); entity.zo = entity.getZ(); entity.prevYaw = entity.yaw; entity.prevPitch = entity.pitch; } } }