ab18034b98
- Train station can set a new auto-schedule for the train currently at the station - Added CreateLuaTable to add helper functions for working with lua tables - Added StringHelper util class to convert snake case to camel case
800 lines
26 KiB
Java
800 lines
26 KiB
Java
package com.simibubi.create.content.logistics.trains.management.edgePoint.station;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.Arrays;
|
|
import java.util.Collection;
|
|
import java.util.HashMap;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Map.Entry;
|
|
import java.util.Objects;
|
|
import java.util.UUID;
|
|
|
|
import javax.annotation.Nullable;
|
|
|
|
import org.jetbrains.annotations.NotNull;
|
|
|
|
import com.simibubi.create.AllBlocks;
|
|
import com.simibubi.create.AllItems;
|
|
import com.simibubi.create.AllSoundEvents;
|
|
import com.simibubi.create.Create;
|
|
import com.simibubi.create.compat.computercraft.ComputerControllable;
|
|
import com.simibubi.create.compat.computercraft.StationPeripheral;
|
|
import com.simibubi.create.content.contraptions.components.structureMovement.AssemblyException;
|
|
import com.simibubi.create.content.contraptions.components.structureMovement.ITransformableTE;
|
|
import com.simibubi.create.content.contraptions.components.structureMovement.StructureTransform;
|
|
import com.simibubi.create.content.logistics.block.depot.DepotBehaviour;
|
|
import com.simibubi.create.content.logistics.block.display.DisplayLinkBlock;
|
|
import com.simibubi.create.content.logistics.trains.IBogeyBlock;
|
|
import com.simibubi.create.content.logistics.trains.ITrackBlock;
|
|
import com.simibubi.create.content.logistics.trains.TrackEdge;
|
|
import com.simibubi.create.content.logistics.trains.TrackGraph;
|
|
import com.simibubi.create.content.logistics.trains.TrackNode;
|
|
import com.simibubi.create.content.logistics.trains.TrackNodeLocation;
|
|
import com.simibubi.create.content.logistics.trains.TrackNodeLocation.DiscoveredLocation;
|
|
import com.simibubi.create.content.logistics.trains.entity.Carriage;
|
|
import com.simibubi.create.content.logistics.trains.entity.CarriageBogey;
|
|
import com.simibubi.create.content.logistics.trains.entity.CarriageContraption;
|
|
import com.simibubi.create.content.logistics.trains.entity.Train;
|
|
import com.simibubi.create.content.logistics.trains.entity.TrainPacket;
|
|
import com.simibubi.create.content.logistics.trains.entity.TravellingPoint;
|
|
import com.simibubi.create.content.logistics.trains.management.edgePoint.EdgePointType;
|
|
import com.simibubi.create.content.logistics.trains.management.edgePoint.TrackTargetingBehaviour;
|
|
import com.simibubi.create.content.logistics.trains.management.schedule.Schedule;
|
|
import com.simibubi.create.content.logistics.trains.management.schedule.ScheduleItem;
|
|
import com.simibubi.create.foundation.advancement.AllAdvancements;
|
|
import com.simibubi.create.foundation.block.ProperWaterloggedBlock;
|
|
import com.simibubi.create.foundation.config.AllConfigs;
|
|
import com.simibubi.create.foundation.networking.AllPackets;
|
|
import com.simibubi.create.foundation.tileEntity.SmartTileEntity;
|
|
import com.simibubi.create.foundation.tileEntity.TileEntityBehaviour;
|
|
import com.simibubi.create.foundation.utility.Iterate;
|
|
import com.simibubi.create.foundation.utility.Lang;
|
|
import com.simibubi.create.foundation.utility.NBTHelper;
|
|
import com.simibubi.create.foundation.utility.WorldAttached;
|
|
import com.simibubi.create.foundation.utility.animation.LerpedFloat;
|
|
import com.simibubi.create.foundation.utility.animation.LerpedFloat.Chaser;
|
|
|
|
import dan200.computercraft.api.peripheral.IPeripheral;
|
|
import net.minecraft.core.BlockPos;
|
|
import net.minecraft.core.BlockPos.MutableBlockPos;
|
|
import net.minecraft.core.Direction;
|
|
import net.minecraft.core.Direction.Axis;
|
|
import net.minecraft.core.Direction.AxisDirection;
|
|
import net.minecraft.core.particles.ParticleTypes;
|
|
import net.minecraft.nbt.CompoundTag;
|
|
import net.minecraft.network.chat.Component;
|
|
import net.minecraft.server.level.ServerLevel;
|
|
import net.minecraft.sounds.SoundSource;
|
|
import net.minecraft.util.Mth;
|
|
import net.minecraft.world.InteractionHand;
|
|
import net.minecraft.world.entity.player.Player;
|
|
import net.minecraft.world.item.ItemStack;
|
|
import net.minecraft.world.level.block.SoundType;
|
|
import net.minecraft.world.level.block.entity.BlockEntityType;
|
|
import net.minecraft.world.level.block.state.BlockState;
|
|
import net.minecraft.world.level.levelgen.structure.BoundingBox;
|
|
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.common.capabilities.Capability;
|
|
import net.minecraftforge.common.util.LazyOptional;
|
|
import net.minecraftforge.network.PacketDistributor;
|
|
|
|
public class StationTileEntity extends SmartTileEntity implements ITransformableTE, ComputerControllable {
|
|
|
|
public TrackTargetingBehaviour<GlobalStation> edgePoint;
|
|
public LerpedFloat flag;
|
|
|
|
protected int failedCarriageIndex;
|
|
protected AssemblyException lastException;
|
|
protected DepotBehaviour depotBehaviour;
|
|
|
|
// for display
|
|
UUID imminentTrain;
|
|
boolean trainPresent;
|
|
boolean trainBackwards;
|
|
boolean trainCanDisassemble;
|
|
boolean trainHasSchedule;
|
|
boolean trainHasAutoSchedule;
|
|
|
|
int flagYRot = -1;
|
|
boolean flagFlipped;
|
|
|
|
public Component lastDisassembledTrainName;
|
|
private LazyOptional<IPeripheral> peripheral;
|
|
|
|
public StationTileEntity(BlockEntityType<?> type, BlockPos pos, BlockState state) {
|
|
super(type, pos, state);
|
|
setLazyTickRate(20);
|
|
lastException = null;
|
|
failedCarriageIndex = -1;
|
|
flag = LerpedFloat.linear()
|
|
.startWithValue(0);
|
|
}
|
|
|
|
@Override
|
|
public void addBehaviours(List<TileEntityBehaviour> behaviours) {
|
|
behaviours.add(edgePoint = new TrackTargetingBehaviour<>(this, EdgePointType.STATION));
|
|
behaviours.add(depotBehaviour = new DepotBehaviour(this).onlyAccepts(AllItems.SCHEDULE::isIn)
|
|
.withCallback(s -> applyAutoSchedule()));
|
|
depotBehaviour.addSubBehaviours(behaviours);
|
|
registerAwardables(behaviours, AllAdvancements.CONTRAPTION_ACTORS, AllAdvancements.TRAIN,
|
|
AllAdvancements.LONG_TRAIN, AllAdvancements.CONDUCTOR);
|
|
}
|
|
|
|
@Override
|
|
protected void read(CompoundTag tag, boolean clientPacket) {
|
|
lastException = AssemblyException.read(tag);
|
|
failedCarriageIndex = tag.getInt("FailedCarriageIndex");
|
|
super.read(tag, clientPacket);
|
|
invalidateRenderBoundingBox();
|
|
|
|
if (tag.contains("ForceFlag"))
|
|
trainPresent = tag.getBoolean("ForceFlag");
|
|
if (tag.contains("PrevTrainName"))
|
|
lastDisassembledTrainName = Component.Serializer.fromJson(tag.getString("PrevTrainName"));
|
|
|
|
if (!clientPacket)
|
|
return;
|
|
if (!tag.contains("ImminentTrain")) {
|
|
imminentTrain = null;
|
|
trainPresent = false;
|
|
trainCanDisassemble = false;
|
|
trainBackwards = false;
|
|
return;
|
|
}
|
|
|
|
imminentTrain = tag.getUUID("ImminentTrain");
|
|
trainPresent = tag.contains("TrainPresent");
|
|
trainCanDisassemble = tag.contains("TrainCanDisassemble");
|
|
trainBackwards = tag.contains("TrainBackwards");
|
|
trainHasSchedule = tag.contains("TrainHasSchedule");
|
|
trainHasAutoSchedule = tag.contains("TrainHasAutoSchedule");
|
|
}
|
|
|
|
@Override
|
|
protected void write(CompoundTag tag, boolean clientPacket) {
|
|
AssemblyException.write(tag, lastException);
|
|
tag.putInt("FailedCarriageIndex", failedCarriageIndex);
|
|
|
|
if (lastDisassembledTrainName != null)
|
|
tag.putString("PrevTrainName", Component.Serializer.toJson(lastDisassembledTrainName));
|
|
|
|
super.write(tag, clientPacket);
|
|
|
|
if (!clientPacket)
|
|
return;
|
|
if (imminentTrain == null)
|
|
return;
|
|
|
|
tag.putUUID("ImminentTrain", imminentTrain);
|
|
|
|
if (trainPresent)
|
|
NBTHelper.putMarker(tag, "TrainPresent");
|
|
if (trainCanDisassemble)
|
|
NBTHelper.putMarker(tag, "TrainCanDisassemble");
|
|
if (trainBackwards)
|
|
NBTHelper.putMarker(tag, "TrainBackwards");
|
|
if (trainHasSchedule)
|
|
NBTHelper.putMarker(tag, "TrainHasSchedule");
|
|
if (trainHasAutoSchedule)
|
|
NBTHelper.putMarker(tag, "TrainHasAutoSchedule");
|
|
}
|
|
|
|
@Nullable
|
|
public GlobalStation getStation() {
|
|
return edgePoint.getEdgePoint();
|
|
}
|
|
|
|
// Train Assembly
|
|
|
|
public static WorldAttached<Map<BlockPos, BoundingBox>> assemblyAreas = new WorldAttached<>(w -> new HashMap<>());
|
|
|
|
Direction assemblyDirection;
|
|
int assemblyLength;
|
|
int[] bogeyLocations;
|
|
IBogeyBlock[] bogeyTypes;
|
|
int bogeyCount;
|
|
|
|
@Override
|
|
public void lazyTick() {
|
|
if (isAssembling() && !level.isClientSide)
|
|
refreshAssemblyInfo();
|
|
super.lazyTick();
|
|
}
|
|
|
|
@Override
|
|
public void tick() {
|
|
if (isAssembling() && level.isClientSide)
|
|
refreshAssemblyInfo();
|
|
super.tick();
|
|
|
|
if (level.isClientSide) {
|
|
float currentTarget = flag.getChaseTarget();
|
|
if (currentTarget == 0 || flag.settled()) {
|
|
int target = trainPresent || isAssembling() ? 1 : 0;
|
|
if (target != currentTarget) {
|
|
flag.chase(target, 0.1f, Chaser.LINEAR);
|
|
if (target == 1)
|
|
AllSoundEvents.CONTRAPTION_ASSEMBLE.playAt(level, worldPosition, 1, 2, true);
|
|
}
|
|
}
|
|
boolean settled = flag.getValue() > .15f;
|
|
flag.tickChaser();
|
|
if (currentTarget == 0 && settled != flag.getValue() > .15f)
|
|
AllSoundEvents.CONTRAPTION_DISASSEMBLE.playAt(level, worldPosition, 0.75f, 1.5f, true);
|
|
return;
|
|
}
|
|
|
|
GlobalStation station = getStation();
|
|
if (station == null)
|
|
return;
|
|
|
|
Train imminentTrain = station.getImminentTrain();
|
|
boolean trainPresent = imminentTrain != null && imminentTrain.getCurrentStation() == station;
|
|
boolean canDisassemble = trainPresent && imminentTrain.canDisassemble();
|
|
UUID imminentID = imminentTrain != null ? imminentTrain.id : null;
|
|
boolean trainHasSchedule = trainPresent && imminentTrain.runtime.getSchedule() != null;
|
|
boolean trainHasAutoSchedule = trainHasSchedule && imminentTrain.runtime.isAutoSchedule;
|
|
boolean newlyArrived = this.trainPresent != trainPresent;
|
|
|
|
if (trainPresent && imminentTrain.runtime.displayLinkUpdateRequested) {
|
|
DisplayLinkBlock.notifyGatherers(level, worldPosition);
|
|
imminentTrain.runtime.displayLinkUpdateRequested = false;
|
|
}
|
|
|
|
if (newlyArrived)
|
|
applyAutoSchedule();
|
|
|
|
if (newlyArrived || this.trainCanDisassemble != canDisassemble
|
|
|| !Objects.equals(imminentID, this.imminentTrain) || this.trainHasSchedule != trainHasSchedule
|
|
|| this.trainHasAutoSchedule != trainHasAutoSchedule) {
|
|
|
|
this.imminentTrain = imminentID;
|
|
this.trainPresent = trainPresent;
|
|
this.trainCanDisassemble = canDisassemble;
|
|
this.trainBackwards = imminentTrain != null && imminentTrain.currentlyBackwards;
|
|
this.trainHasSchedule = trainHasSchedule;
|
|
this.trainHasAutoSchedule = trainHasAutoSchedule;
|
|
|
|
notifyUpdate();
|
|
}
|
|
}
|
|
|
|
public boolean trackClicked(Player player, InteractionHand hand, ITrackBlock track, BlockState state,
|
|
BlockPos pos) {
|
|
refreshAssemblyInfo();
|
|
BoundingBox bb = assemblyAreas.get(level)
|
|
.get(worldPosition);
|
|
if (bb == null || !bb.isInside(pos))
|
|
return false;
|
|
|
|
BlockPos up = new BlockPos(track.getUpNormal(level, pos, state));
|
|
int bogeyOffset = pos.distManhattan(edgePoint.getGlobalPosition()) - 1;
|
|
if (!isValidBogeyOffset(bogeyOffset)) {
|
|
for (int i = -1; i <= 1; i++) {
|
|
BlockPos bogeyPos = pos.relative(assemblyDirection, i)
|
|
.offset(up);
|
|
BlockState blockState = level.getBlockState(bogeyPos);
|
|
if (blockState.getBlock() instanceof IBogeyBlock bogey) {
|
|
level.setBlock(bogeyPos, bogey.getRotatedBlockState(blockState, Direction.DOWN), 3);
|
|
bogey.playRotateSound(level, bogeyPos);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
ItemStack handItem = player.getItemInHand(hand);
|
|
if (!player.isCreative() && !AllBlocks.RAILWAY_CASING.isIn(handItem)) {
|
|
player.displayClientMessage(Lang.translateDirect("train_assembly.requires_casing"), true);
|
|
return false;
|
|
}
|
|
|
|
BlockPos targetPos = pos.offset(up);
|
|
if (level.getBlockState(targetPos)
|
|
.getDestroySpeed(level, targetPos) == -1) {
|
|
return false;
|
|
}
|
|
|
|
level.destroyBlock(targetPos, true);
|
|
|
|
BlockState bogeyAnchor = ProperWaterloggedBlock.withWater(level, track.getBogeyAnchor(level, pos, state), pos);
|
|
level.setBlock(targetPos, bogeyAnchor, 3);
|
|
player.displayClientMessage(Lang.translateDirect("train_assembly.bogey_created"), true);
|
|
SoundType soundtype = bogeyAnchor.getBlock()
|
|
.getSoundType(state, level, pos, player);
|
|
level.playSound(null, pos, soundtype.getPlaceSound(), SoundSource.BLOCKS, (soundtype.getVolume() + 1.0F) / 2.0F,
|
|
soundtype.getPitch() * 0.8F);
|
|
|
|
if (!player.isCreative()) {
|
|
ItemStack itemInHand = player.getItemInHand(hand);
|
|
itemInHand.shrink(1);
|
|
if (itemInHand.isEmpty())
|
|
player.setItemInHand(hand, ItemStack.EMPTY);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public boolean isAssembling() {
|
|
BlockState state = getBlockState();
|
|
return state.hasProperty(StationBlock.ASSEMBLING) && state.getValue(StationBlock.ASSEMBLING);
|
|
}
|
|
|
|
public boolean tryEnterAssemblyMode() {
|
|
if (!edgePoint.hasValidTrack())
|
|
return false;
|
|
|
|
BlockPos targetPosition = edgePoint.getGlobalPosition();
|
|
BlockState trackState = edgePoint.getTrackBlockState();
|
|
ITrackBlock track = edgePoint.getTrack();
|
|
Vec3 trackAxis = track.getTrackAxes(level, targetPosition, trackState)
|
|
.get(0);
|
|
|
|
boolean axisFound = false;
|
|
for (Axis axis : Iterate.axes) {
|
|
if (trackAxis.get(axis) == 0)
|
|
continue;
|
|
if (axisFound)
|
|
return false;
|
|
axisFound = true;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public void refreshAssemblyInfo() {
|
|
if (!edgePoint.hasValidTrack())
|
|
return;
|
|
|
|
if (!isVirtual()) {
|
|
GlobalStation station = getStation();
|
|
if (station == null || station.getPresentTrain() != null)
|
|
return;
|
|
}
|
|
|
|
int prevLength = assemblyLength;
|
|
BlockPos targetPosition = edgePoint.getGlobalPosition();
|
|
BlockState trackState = edgePoint.getTrackBlockState();
|
|
ITrackBlock track = edgePoint.getTrack();
|
|
getAssemblyDirection();
|
|
|
|
MutableBlockPos currentPos = targetPosition.mutable();
|
|
currentPos.move(assemblyDirection);
|
|
|
|
BlockPos bogeyOffset = new BlockPos(track.getUpNormal(level, targetPosition, trackState));
|
|
|
|
int MAX_LENGTH = AllConfigs.SERVER.trains.maxAssemblyLength.get();
|
|
int MAX_BOGEY_COUNT = AllConfigs.SERVER.trains.maxBogeyCount.get();
|
|
|
|
int bogeyIndex = 0;
|
|
int maxBogeyCount = MAX_BOGEY_COUNT;
|
|
if (bogeyLocations == null)
|
|
bogeyLocations = new int[maxBogeyCount];
|
|
if (bogeyTypes == null)
|
|
bogeyTypes = new IBogeyBlock[maxBogeyCount];
|
|
Arrays.fill(bogeyLocations, -1);
|
|
Arrays.fill(bogeyTypes, null);
|
|
|
|
for (int i = 0; i < MAX_LENGTH; i++) {
|
|
if (i == MAX_LENGTH - 1) {
|
|
assemblyLength = i;
|
|
break;
|
|
}
|
|
if (!track.trackEquals(trackState, level.getBlockState(currentPos))) {
|
|
assemblyLength = Math.max(0, i - 1);
|
|
break;
|
|
}
|
|
|
|
BlockState potentialBogeyState = level.getBlockState(bogeyOffset.offset(currentPos));
|
|
if (potentialBogeyState.getBlock() instanceof IBogeyBlock bogey && bogeyIndex < bogeyLocations.length) {
|
|
bogeyTypes[bogeyIndex] = bogey;
|
|
bogeyLocations[bogeyIndex] = i;
|
|
bogeyIndex++;
|
|
}
|
|
|
|
currentPos.move(assemblyDirection);
|
|
}
|
|
|
|
bogeyCount = bogeyIndex;
|
|
|
|
if (level.isClientSide)
|
|
return;
|
|
if (prevLength == assemblyLength)
|
|
return;
|
|
if (isVirtual())
|
|
return;
|
|
|
|
Map<BlockPos, BoundingBox> map = assemblyAreas.get(level);
|
|
BlockPos startPosition = targetPosition.relative(assemblyDirection);
|
|
BlockPos trackEnd = startPosition.relative(assemblyDirection, assemblyLength - 1);
|
|
map.put(worldPosition, BoundingBox.fromCorners(startPosition, trackEnd));
|
|
}
|
|
|
|
public boolean isValidBogeyOffset(int i) {
|
|
if ((i < 3 || bogeyCount == 0) && i != 0)
|
|
return false;
|
|
for (int j : bogeyLocations) {
|
|
if (j == -1)
|
|
break;
|
|
if (i >= j - 2 && i <= j + 2)
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
public Direction getAssemblyDirection() {
|
|
if (assemblyDirection != null)
|
|
return assemblyDirection;
|
|
if (!edgePoint.hasValidTrack())
|
|
return null;
|
|
BlockPos targetPosition = edgePoint.getGlobalPosition();
|
|
BlockState trackState = edgePoint.getTrackBlockState();
|
|
ITrackBlock track = edgePoint.getTrack();
|
|
AxisDirection axisDirection = edgePoint.getTargetDirection();
|
|
Vec3 axis = track.getTrackAxes(level, targetPosition, trackState)
|
|
.get(0)
|
|
.normalize()
|
|
.scale(axisDirection.getStep());
|
|
return assemblyDirection = Direction.getNearest(axis.x, axis.y, axis.z);
|
|
}
|
|
|
|
@Override
|
|
protected void setRemovedNotDueToChunkUnload() {
|
|
assemblyAreas.get(level)
|
|
.remove(worldPosition);
|
|
|
|
super.setRemovedNotDueToChunkUnload();
|
|
}
|
|
|
|
public void assemble(UUID playerUUID) {
|
|
refreshAssemblyInfo();
|
|
|
|
if (bogeyLocations == null)
|
|
return;
|
|
|
|
if (bogeyLocations[0] != 0) {
|
|
exception(new AssemblyException(Lang.translateDirect("train_assembly.frontmost_bogey_at_station")), -1);
|
|
return;
|
|
}
|
|
|
|
if (!edgePoint.hasValidTrack())
|
|
return;
|
|
|
|
BlockPos trackPosition = edgePoint.getGlobalPosition();
|
|
BlockState trackState = edgePoint.getTrackBlockState();
|
|
ITrackBlock track = edgePoint.getTrack();
|
|
BlockPos bogeyOffset = new BlockPos(track.getUpNormal(level, trackPosition, trackState));
|
|
|
|
TrackNodeLocation location = null;
|
|
Vec3 centre = Vec3.atBottomCenterOf(trackPosition)
|
|
.add(0, track.getElevationAtCenter(level, trackPosition, trackState), 0);
|
|
Collection<DiscoveredLocation> ends = track.getConnected(level, trackPosition, trackState, true, null);
|
|
Vec3 targetOffset = Vec3.atLowerCornerOf(assemblyDirection.getNormal());
|
|
for (DiscoveredLocation end : ends)
|
|
if (Mth.equal(0, targetOffset.distanceToSqr(end.getLocation()
|
|
.subtract(centre)
|
|
.normalize())))
|
|
location = end;
|
|
if (location == null)
|
|
return;
|
|
|
|
List<Double> pointOffsets = new ArrayList<>();
|
|
int iPrevious = -100;
|
|
for (int i = 0; i < bogeyLocations.length; i++) {
|
|
int loc = bogeyLocations[i];
|
|
if (loc == -1)
|
|
break;
|
|
|
|
if (loc - iPrevious < 3) {
|
|
exception(new AssemblyException(Lang.translateDirect("train_assembly.bogeys_too_close", i, i + 1)), -1);
|
|
return;
|
|
}
|
|
|
|
double bogeySize = bogeyTypes[i].getWheelPointSpacing();
|
|
pointOffsets.add(Double.valueOf(loc + .5 - bogeySize / 2));
|
|
pointOffsets.add(Double.valueOf(loc + .5 + bogeySize / 2));
|
|
iPrevious = loc;
|
|
}
|
|
|
|
List<TravellingPoint> points = new ArrayList<>();
|
|
Vec3 directionVec = Vec3.atLowerCornerOf(assemblyDirection.getNormal());
|
|
TrackGraph graph = null;
|
|
TrackNode secondNode = null;
|
|
|
|
for (int j = 0; j < assemblyLength * 2 + 40; j++) {
|
|
double i = j / 2d;
|
|
if (points.size() == pointOffsets.size())
|
|
break;
|
|
|
|
TrackNodeLocation currentLocation = location;
|
|
location = new TrackNodeLocation(location.getLocation()
|
|
.add(directionVec.scale(.5))).in(location.dimension);
|
|
|
|
if (graph == null)
|
|
graph = Create.RAILWAYS.getGraph(level, currentLocation);
|
|
if (graph == null)
|
|
continue;
|
|
TrackNode node = graph.locateNode(currentLocation);
|
|
if (node == null)
|
|
continue;
|
|
|
|
for (int pointIndex = points.size(); pointIndex < pointOffsets.size(); pointIndex++) {
|
|
double offset = pointOffsets.get(pointIndex);
|
|
if (offset > i)
|
|
break;
|
|
double positionOnEdge = i - offset;
|
|
|
|
Map<TrackNode, TrackEdge> connectionsFromNode = graph.getConnectionsFrom(node);
|
|
|
|
if (secondNode == null)
|
|
for (Entry<TrackNode, TrackEdge> entry : connectionsFromNode.entrySet()) {
|
|
TrackEdge edge = entry.getValue();
|
|
TrackNode otherNode = entry.getKey();
|
|
if (edge.isTurn())
|
|
continue;
|
|
Vec3 edgeDirection = edge.getDirection(true);
|
|
if (Mth.equal(edgeDirection.normalize()
|
|
.dot(directionVec), -1d))
|
|
secondNode = otherNode;
|
|
}
|
|
|
|
if (secondNode == null) {
|
|
Create.LOGGER.warn("Cannot assemble: No valid starting node found");
|
|
return;
|
|
}
|
|
|
|
TrackEdge edge = connectionsFromNode.get(secondNode);
|
|
|
|
if (edge == null) {
|
|
Create.LOGGER.warn("Cannot assemble: Missing graph edge");
|
|
return;
|
|
}
|
|
|
|
points.add(new TravellingPoint(node, secondNode, edge, positionOnEdge));
|
|
}
|
|
|
|
secondNode = node;
|
|
}
|
|
|
|
if (points.size() != pointOffsets.size()) {
|
|
Create.LOGGER.warn("Cannot assemble: Not all Points created");
|
|
return;
|
|
}
|
|
|
|
if (points.size() == 0) {
|
|
exception(new AssemblyException(Lang.translateDirect("train_assembly.no_bogeys")), -1);
|
|
return;
|
|
}
|
|
|
|
List<CarriageContraption> contraptions = new ArrayList<>();
|
|
List<Carriage> carriages = new ArrayList<>();
|
|
List<Integer> spacing = new ArrayList<>();
|
|
boolean atLeastOneForwardControls = false;
|
|
|
|
for (int bogeyIndex = 0; bogeyIndex < bogeyCount; bogeyIndex++) {
|
|
int pointIndex = bogeyIndex * 2;
|
|
if (bogeyIndex > 0)
|
|
spacing.add(bogeyLocations[bogeyIndex] - bogeyLocations[bogeyIndex - 1]);
|
|
CarriageContraption contraption = new CarriageContraption(assemblyDirection);
|
|
BlockPos bogeyPosOffset = trackPosition.offset(bogeyOffset);
|
|
|
|
try {
|
|
int offset = bogeyLocations[bogeyIndex] + 1;
|
|
boolean success = contraption.assemble(level, bogeyPosOffset.relative(assemblyDirection, offset));
|
|
atLeastOneForwardControls |= contraption.hasForwardControls();
|
|
contraption.setSoundQueueOffset(offset);
|
|
if (!success) {
|
|
exception(new AssemblyException(Lang.translateDirect("train_assembly.nothing_attached", bogeyIndex + 1)),
|
|
-1);
|
|
return;
|
|
}
|
|
} catch (AssemblyException e) {
|
|
exception(e, contraptions.size() + 1);
|
|
return;
|
|
}
|
|
|
|
IBogeyBlock typeOfFirstBogey = bogeyTypes[bogeyIndex];
|
|
CarriageBogey firstBogey =
|
|
new CarriageBogey(typeOfFirstBogey, points.get(pointIndex), points.get(pointIndex + 1));
|
|
CarriageBogey secondBogey = null;
|
|
BlockPos secondBogeyPos = contraption.getSecondBogeyPos();
|
|
int bogeySpacing = 0;
|
|
|
|
if (secondBogeyPos != null) {
|
|
if (bogeyIndex == bogeyCount - 1 || !secondBogeyPos
|
|
.equals(bogeyPosOffset.relative(assemblyDirection, bogeyLocations[bogeyIndex + 1] + 1))) {
|
|
exception(new AssemblyException(Lang.translateDirect("train_assembly.not_connected_in_order")),
|
|
contraptions.size() + 1);
|
|
return;
|
|
}
|
|
|
|
bogeySpacing = bogeyLocations[bogeyIndex + 1] - bogeyLocations[bogeyIndex];
|
|
secondBogey = new CarriageBogey(bogeyTypes[bogeyIndex + 1], points.get(pointIndex + 2),
|
|
points.get(pointIndex + 3));
|
|
bogeyIndex++;
|
|
|
|
} else if (!typeOfFirstBogey.allowsSingleBogeyCarriage()) {
|
|
exception(new AssemblyException(Lang.translateDirect("train_assembly.single_bogey_carriage")),
|
|
contraptions.size() + 1);
|
|
return;
|
|
}
|
|
|
|
contraptions.add(contraption);
|
|
carriages.add(new Carriage(firstBogey, secondBogey, bogeySpacing));
|
|
}
|
|
|
|
if (!atLeastOneForwardControls) {
|
|
exception(new AssemblyException(Lang.translateDirect("train_assembly.no_controls")), -1);
|
|
return;
|
|
}
|
|
|
|
for (CarriageContraption contraption : contraptions) {
|
|
contraption.removeBlocksFromWorld(level, BlockPos.ZERO);
|
|
contraption.expandBoundsAroundAxis(Axis.Y);
|
|
}
|
|
|
|
Train train = new Train(UUID.randomUUID(), playerUUID, graph, carriages, spacing, contraptions.stream()
|
|
.anyMatch(CarriageContraption::hasBackwardControls));
|
|
|
|
if (lastDisassembledTrainName != null) {
|
|
train.name = lastDisassembledTrainName;
|
|
lastDisassembledTrainName = null;
|
|
}
|
|
|
|
for (int i = 0; i < contraptions.size(); i++) {
|
|
CarriageContraption contraption = contraptions.get(i);
|
|
Carriage carriage = carriages.get(i);
|
|
carriage.setContraption(level, contraption);
|
|
if (contraption.containsBlockBreakers())
|
|
award(AllAdvancements.CONTRAPTION_ACTORS);
|
|
}
|
|
|
|
GlobalStation station = getStation();
|
|
if (station != null) {
|
|
train.setCurrentStation(station);
|
|
station.reserveFor(train);
|
|
}
|
|
|
|
train.collectInitiallyOccupiedSignalBlocks();
|
|
Create.RAILWAYS.addTrain(train);
|
|
AllPackets.channel.send(PacketDistributor.ALL.noArg(), new TrainPacket(train, true));
|
|
clearException();
|
|
|
|
award(AllAdvancements.TRAIN);
|
|
if (contraptions.size() >= 6)
|
|
award(AllAdvancements.LONG_TRAIN);
|
|
}
|
|
|
|
public void cancelAssembly() {
|
|
assemblyLength = 0;
|
|
assemblyAreas.get(level)
|
|
.remove(worldPosition);
|
|
clearException();
|
|
}
|
|
|
|
private void clearException() {
|
|
exception(null, -1);
|
|
}
|
|
|
|
private void exception(AssemblyException exception, int carriage) {
|
|
failedCarriageIndex = carriage;
|
|
lastException = exception;
|
|
sendData();
|
|
}
|
|
|
|
@Override
|
|
@OnlyIn(Dist.CLIENT)
|
|
public AABB getRenderBoundingBox() {
|
|
if (isAssembling())
|
|
return INFINITE_EXTENT_AABB;
|
|
return super.getRenderBoundingBox();
|
|
}
|
|
|
|
@Override
|
|
protected AABB createRenderBoundingBox() {
|
|
return new AABB(worldPosition, edgePoint.getGlobalPosition()).inflate(2);
|
|
}
|
|
|
|
public ItemStack getAutoSchedule() {
|
|
return depotBehaviour.getHeldItemStack();
|
|
}
|
|
|
|
@Override
|
|
public <T> @NotNull LazyOptional<T> getCapability(@NotNull Capability<T> cap, Direction side) {
|
|
LazyOptional<T> peripheralCap = getPeripheralCapability(cap);
|
|
|
|
if (isItemHandlerCap(cap))
|
|
return depotBehaviour.getItemCapability(cap, side);
|
|
|
|
return peripheralCap.isPresent() ? peripheralCap : super.getCapability(cap, side);
|
|
}
|
|
|
|
@Override
|
|
public void invalidateCaps() {
|
|
super.invalidateCaps();
|
|
removePeripheral();
|
|
}
|
|
|
|
private void applyAutoSchedule() {
|
|
ItemStack stack = getAutoSchedule();
|
|
if (!AllItems.SCHEDULE.isIn(stack))
|
|
return;
|
|
Schedule schedule = ScheduleItem.getSchedule(stack);
|
|
if (schedule == null || schedule.entries.isEmpty())
|
|
return;
|
|
GlobalStation station = getStation();
|
|
if (station == null)
|
|
return;
|
|
Train imminentTrain = station.getImminentTrain();
|
|
if (imminentTrain == null || imminentTrain.getCurrentStation() != station)
|
|
return;
|
|
|
|
award(AllAdvancements.CONDUCTOR);
|
|
imminentTrain.runtime.setSchedule(schedule, true);
|
|
AllSoundEvents.CONFIRM.playOnServer(level, worldPosition, 1, 1);
|
|
|
|
if (!(level instanceof ServerLevel server))
|
|
return;
|
|
|
|
Vec3 v = Vec3.atBottomCenterOf(worldPosition.above());
|
|
server.sendParticles(ParticleTypes.HAPPY_VILLAGER, v.x, v.y, v.z, 8, 0.35, 0.05, 0.35, 1);
|
|
server.sendParticles(ParticleTypes.END_ROD, v.x, v.y + .25f, v.z, 10, 0.05, 1, 0.05, 0.005f);
|
|
}
|
|
|
|
public boolean resolveFlagAngle() {
|
|
if (flagYRot != -1)
|
|
return true;
|
|
|
|
BlockState target = edgePoint.getTrackBlockState();
|
|
if (!(target.getBlock() instanceof ITrackBlock def))
|
|
return false;
|
|
|
|
Vec3 axis = null;
|
|
BlockPos trackPos = edgePoint.getGlobalPosition();
|
|
for (Vec3 vec3 : def.getTrackAxes(level, trackPos, target))
|
|
axis = vec3.scale(edgePoint.getTargetDirection()
|
|
.getStep());
|
|
if (axis == null)
|
|
return false;
|
|
|
|
Direction nearest = Direction.getNearest(axis.x, 0, axis.z);
|
|
flagYRot = (int) (-nearest.toYRot() - 90);
|
|
|
|
Vec3 diff = Vec3.atLowerCornerOf(trackPos.subtract(worldPosition))
|
|
.multiply(1, 0, 1);
|
|
if (diff.lengthSqr() == 0)
|
|
return true;
|
|
|
|
flagFlipped = diff.dot(Vec3.atLowerCornerOf(nearest.getClockWise()
|
|
.getNormal())) > 0;
|
|
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
public void transform(StructureTransform transform) {
|
|
edgePoint.transform(transform);
|
|
}
|
|
|
|
@Override
|
|
public IPeripheral createPeripheral() {
|
|
return new StationPeripheral(this);
|
|
}
|
|
|
|
@Override
|
|
public void setPeripheral(LazyOptional<IPeripheral> peripheral) {
|
|
this.peripheral = peripheral;
|
|
}
|
|
|
|
@Override
|
|
public LazyOptional<IPeripheral> getPeripheral() {
|
|
return peripheral;
|
|
}
|
|
|
|
}
|