mirror of
https://github.com/Creators-of-Create/Create.git
synced 2024-06-02 18:59:19 +02:00
900 lines
30 KiB
Java
900 lines
30 KiB
Java
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<Boolean> presentConductors;
|
|
|
|
public int bogeySpacing;
|
|
public Couple<CarriageBogey> bogeys;
|
|
public TrainCargoManager storage;
|
|
|
|
CompoundTag serialisedEntity;
|
|
Map<Integer, CompoundTag> serialisedPassengers;
|
|
|
|
private Map<ResourceKey<Level>, 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<Level> dimension) {
|
|
return entities.computeIfAbsent(dimension, $ -> new DimensionalCarriageEntity());
|
|
}
|
|
|
|
@Nullable
|
|
public DimensionalCarriageEntity getDimensionalIfPresent(ResourceKey<Level> dimension) {
|
|
return entities.get(dimension);
|
|
}
|
|
|
|
public double travel(Level level, TrackGraph graph, double distance, TravellingPoint toFollowForward,
|
|
TravellingPoint toFollowBackward, int type) {
|
|
|
|
Function<TravellingPoint, ITrackSelector> forwardControl =
|
|
toFollowForward == null ? train.navigation::control : mp -> mp.follow(toFollowForward);
|
|
Function<TravellingPoint, ITrackSelector> 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<ResourceKey<Level>> 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<Entry<ResourceKey<Level>, DimensionalCarriageEntity>> iterator = entities.entrySet()
|
|
.iterator(); iterator.hasNext();) {
|
|
Entry<ResourceKey<Level>, 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<Level> leadingBogeyDim = leadingBogey.getDimension();
|
|
ResourceKey<Level> 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<Level> dimension = point.node1.getLocation().dimension;
|
|
ResourceKey<Level> 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<Level> 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<CarriageContraptionEntity> 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<Integer, CompoundTag> 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<UUID, Integer> 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<Vec3> rotationAnchors;
|
|
public WeakReference<CarriageContraptionEntity> 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<Level> 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<Integer> loadedPassengers = new HashSet<>();
|
|
int min = minAllowedLocalCoord();
|
|
int max = maxAllowedLocalCoord();
|
|
|
|
for (Entry<Integer, CompoundTag> entry : serialisedPassengers.entrySet()) {
|
|
Integer seatId = entry.getKey();
|
|
List<BlockPos> 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<Level> 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<UUID, Integer> 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<ResourceKey<Level>, 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<UUID, Integer> 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;
|
|
}
|
|
}
|
|
|
|
}
|