CreateMod/src/main/java/com/simibubi/create/content/logistics/trains/management/edgePoint/station/StationTileEntity.java
simibubi 3ad4195dd6 Squashed commit of the following:
commit a162e18c9c4d91c4020e86cf718d59d3a33b2817
Merge: 374848f97 beb61708a
Author: simibubi <31564874+simibubi@users.noreply.github.com>
Date:   Fri May 12 13:40:58 2023 +0200

    Merge branch 'mc1.18/dev' into mc1.18/computercraft

commit 374848f978
Author: simibubi <31564874+simibubi@users.noreply.github.com>
Date:   Fri May 12 13:22:29 2023 +0200

    Compilation dependency toggle

commit b2cd60b619
Merge: 8e1e4e8bd ee3a079ba
Author: simibubi <31564874+simibubi@users.noreply.github.com>
Date:   Wed May 10 14:37:32 2023 +0200

    Merge pull request #4650 from ChristianLW/mc1.18/computercraft

    Small tweaks to the wiki pages for ComputerCraft integration

commit ee3a079bac
Author: Christian L.W <bebeu@bebeu.dk>
Date:   Thu Apr 13 00:55:20 2023 +0200

    Small tweaks to the wiki pages

commit 8e1e4e8bd3
Author: caelwarner <caelawarner@gmail.com>
Date:   Mon Mar 13 18:31:56 2023 -0700

    Added computer to display source ponder tag

    - Added advanced computer to display source ponder tag
    - Added missing lang entry for computer display source

commit 952941e5fc
Author: caelwarner <caelawarner@gmail.com>
Date:   Mon Mar 13 16:31:16 2023 -0700

    Added documentation for train station peripherals and train schedules

    - Added in depth documentation for working with train stations and train schedules in Lua
    - Fixed small formatting issues in Lua-Rotation-Speed-Controller.md and Lua-Sequenced-Gearshift.md

commit 7f3ca1cfa0
Author: caelwarner <caelawarner@gmail.com>
Date:   Mon Mar 13 16:29:05 2023 -0700

    Added isTrainEnroute to station peripheral API

    - isTrainEnroute checks if a train is currently navigating to the station
    - Reworded null station exception to "station is not connected to a track"
    - Refactored StationPeripheral#inAssemblyMode to StationPeripheral#isInAssemblyMode
    - Added a check to StationPeripheral#disassemble to make sure the station isn't in assembly mode

commit fac1ebcd3f
Author: caelwarner <caelawarner@gmail.com>
Date:   Sat Mar 11 16:12:58 2023 -0800

    Added documentation for most peripherals

    - Lua documentation has been added for all peripherals except the train station (not looking forward to writing that one)
    - This documentation will be added to the GitHub wiki pages

commit 3e21996984
Author: caelwarner <caelawarner@gmail.com>
Date:   Sat Mar 11 15:54:36 2023 -0800

    Updated DisplayLinkPeripheral#write to move cursor to the end of the text

    - This change was made to be more inline with ComputerCraft's builtin display API

commit 7141c10025
Author: caelwarner <caelawarner@gmail.com>
Date:   Sat Mar 11 11:45:43 2023 -0800

    Added isTrainImminent and hasSchedule to train station API

    - Added isTrainImminent to check if a train is incoming to the station and hasSchedule to check if the currently present train has a schedule
    - Added StationPeripheral#getTrainOrThrow to consolidate repetitive null checks

commit 909484ed5b
Author: caelwarner <caelawarner@gmail.com>
Date:   Sat Mar 11 11:15:58 2023 -0800

    Added getSchedule to train station lua API

    - Added getSchedule which serializes the currently present train's schedule into a lua table
    - Refactored StationPeripheral#setSchedule to use a more generic method of serializing NBT tags to lua tables
    - Moved schedule entry special data from root tag to "Data"
    - Added StringHelper#camelCaseToSnakeCase
    - Added variety of put methods to CreateLuaTable

commit 31ad3aa671
Author: caelwarner <caelawarner@gmail.com>
Date:   Wed Mar 8 18:22:23 2023 -0800

    Extended train station peripheral API

    - Train station peripherals can now assemble and disassemble trains, check if the station is in assembly mode, set the assembly mode of the station, get and change the station name, check if a train is present at the station and get and change the currently present train name.
    - Refactored StationEditPacket. Moved most of the logic that was previously in StationEditPacket to StationTileEntity. This allows us to call this logic without having to send a packet.
    - Made Train#owner nullable. This is needed so that computers can assemble trains. All Train#owner is currently used for is to display the train status to the correct play.

commit 574cd93a89
Author: caelwarner <caelawarner@gmail.com>
Date:   Wed Nov 30 00:37:47 2022 -0800

    Serialize hasAttachedComputer in ComputerBehaviour

    - This eliminates some edge cases were peripherals don't realize they're being controlled by a computer on a world save and load

commit 94e3ed44ad
Author: caelwarner <caelawarner@gmail.com>
Date:   Wed Oct 26 16:57:12 2022 -0700

    Added ComputerScreen

    - ComputerScreen shows that tile entity currently has computers attached and therefore cannot be controlled manually

commit 9afdcaded7
Author: caelwarner <caelawarner@gmail.com>
Date:   Thu Oct 20 10:18:37 2022 -0700

    Refactored PeripheralBase to SyncedPeripheral

commit 7d47fdcd06
Author: caelwarner <caelawarner@gmail.com>
Date:   Wed Oct 19 22:45:47 2022 -0700

    Made LuaFunction's final

commit 56a1210fff
Author: caelwarner <caelawarner@gmail.com>
Date:   Wed Oct 19 22:39:38 2022 -0700

    Created ComputerBehaviour behaviour

    - ComputerBehaviour replaces ComputerControllable and SyncedComputerControllable

commit 19d283b923
Author: caelwarner <caelawarner@gmail.com>
Date:   Wed Oct 19 16:05:48 2022 -0700

    Moved all peripheral classes to computercraft.peripherals package

commit ab18034b98
Author: caelwarner <caelawarner@gmail.com>
Date:   Wed Oct 19 15:58:56 2022 -0700

    Added Train Station as peripheral

    - 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

commit 1091f3227c
Author: caelwarner <caelawarner@gmail.com>
Date:   Thu Oct 6 21:11:24 2022 -0700

    Changed Display Link Peripheral API

    - Changed the Display Link Peripheral API to be more in line with the Monitor API
    - Added write, setCursorPos, getCursorPos, getSize, isColor, isColour, clearLine
    - Removed void writeLine, setLine, writeLines, setLines

commit 18bfb216b1
Author: caelwarner <caelawarner@gmail.com>
Date:   Thu Oct 6 02:50:41 2022 -0700

    Changed method of checking if a computer attached

    - After talking with SquidDev from CC: Tweaked I've changed to monitoring IPeripheral#attach and IPeripheral#detach for changes in the number of computers connected to the network, then updating the client using AttachedComputerPacket
    - This works with wired full modems, wired cabled modems and directly connected computers
    - Added SyncedPeripheralBase and SyncedComputerControllable for TE's and peripherals that want to be aware of attached computers

commit 96dc4db6dc
Author: caelwarner <caelawarner@gmail.com>
Date:   Tue Oct 4 21:11:38 2022 -0700

    Sequenced Gearshift screen "greys out" when being controlled by a computer

    - This is to stop players from trying to using both the builtin sequencing and a computer to control the Sequenced Gearshift at the same time, leading to undefined behaviour
    - The "greyed out" screen should have a message added explaining why it's greyed out.
    - Added ComputerControllable#isComputerControlled to check if a tile entity is connected to a modem

commit 9a80781401
Author: caelwarner <caelawarner@gmail.com>
Date:   Tue Oct 4 19:36:08 2022 -0700

    Added PeripheralBase

commit d404f07319
Author: caelwarner <caelawarner@gmail.com>
Date:   Mon Oct 3 20:46:16 2022 -0700

    Added invalidateCaps

    - Changed setRemoved to invalidateCaps. I don't know why I wasn't just using invalidateCaps from the beginning

commit 654476d9f3
Author: caelwarner <caelawarner@gmail.com>
Date:   Mon Oct 3 20:05:25 2022 -0700

    Added Rotation Speed Controller and Sequenced Gearshift as peripherals

    - Rotation Speed Controller can get and set targetSpeed
    - Sequenced Gearshift can rotate by a certain angle and move a certain distance

commit 1420406ab7
Author: caelwarner <caelawarner@gmail.com>
Date:   Mon Oct 3 16:38:12 2022 -0700

    Added Speedometer and Stressometer as peripherals

    - Speedometer can get current speed
    - Stressometer can get current stress level as well as network stress capacity
    - Made GaugeTileEntity abstract

commit 47b8619d07
Author: caelwarner <caelawarner@gmail.com>
Date:   Mon Oct 3 16:17:05 2022 -0700

    Refactored peripheralHandler to peripheral

    - peripheralHandler was the wrong name. It's just a peripheral.
    - Changed peripheral type from "cdl" to "Create_DisplayLink"
    - Added equals function to DisplayLinkPeripheral

commit 6591c2d46e
Author: caelwarner <caelawarner@gmail.com>
Date:   Mon Oct 3 14:29:04 2022 -0700

    ComputerCraft integration for Display Links

    - CC computers can now control display links through a variety of functions
    - Added ComputerControllable interface to define a tile entity as controllable by CC computers
    - Added CC: Tweaked soft dependency
2023-05-12 13:41:28 +02:00

889 lines
29 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 java.util.function.Consumer;
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.AbstractComputerBehaviour;
import com.simibubi.create.compat.computercraft.ComputerCraftProxy;
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.GraphLocation;
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.VecHelper;
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 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.server.level.ServerPlayer;
import net.minecraft.sounds.SoundSource;
import net.minecraft.util.Mth;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.entity.item.ItemEntity;
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 {
public TrackTargetingBehaviour<GlobalStation> edgePoint;
public LerpedFloat flag;
protected int failedCarriageIndex;
protected AssemblyException lastException;
protected DepotBehaviour depotBehaviour;
public AbstractComputerBehaviour computerBehaviour;
// for display
UUID imminentTrain;
boolean trainPresent;
boolean trainBackwards;
boolean trainCanDisassemble;
boolean trainHasSchedule;
boolean trainHasAutoSchedule;
int flagYRot = -1;
boolean flagFlipped;
public Component lastDisassembledTrainName;
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);
behaviours.add(computerBehaviour = ComputerCraftProxy.behaviour(this));
}
@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 enterAssemblyMode(@Nullable ServerPlayer sender) {
if (isAssembling())
return false;
tryDisassembleTrain(sender);
if (!tryEnterAssemblyMode())
return false;
BlockState newState = getBlockState().setValue(StationBlock.ASSEMBLING, true);
level.setBlock(getBlockPos(), newState, 3);
refreshBlockState();
refreshAssemblyInfo();
updateStationState(station -> station.assembling = true);
GlobalStation station = getStation();
if (station != null) {
for (Train train : Create.RAILWAYS.sided(level).trains.values()) {
if (train.navigation.destination != station)
continue;
GlobalStation preferredDestination = train.runtime.startCurrentInstruction();
train.navigation.startNavigation(preferredDestination != null ? preferredDestination : station, Double.MAX_VALUE, false);
}
}
return true;
}
public boolean exitAssemblyMode() {
if (!isAssembling())
return false;
cancelAssembly();
BlockState newState = getBlockState().setValue(StationBlock.ASSEMBLING, false);
level.setBlock(getBlockPos(), newState, 3);
refreshBlockState();
return updateStationState(station -> station.assembling = false);
}
public boolean tryDisassembleTrain(@Nullable ServerPlayer sender) {
GlobalStation station = getStation();
if (station == null)
return false;
Train train = station.getPresentTrain();
if (train == null)
return false;
BlockPos trackPosition = edgePoint.getGlobalPosition();
if (!train.disassemble(getAssemblyDirection(), trackPosition.above()))
return false;
dropSchedule(sender);
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 dropSchedule(@Nullable ServerPlayer sender) {
GlobalStation station = getStation();
if (station == null)
return;
Train train = station.getPresentTrain();
if (train == null)
return;
ItemStack schedule = train.runtime.returnSchedule();
if (schedule.isEmpty())
return;
if (sender != null && sender.getMainHandItem().isEmpty()) {
sender.getInventory()
.placeItemBackInInventory(schedule);
return;
}
Vec3 v = VecHelper.getCenterOf(getBlockPos());
ItemEntity itemEntity = new ItemEntity(getLevel(), v.x, v.y, v.z, schedule);
itemEntity.setDeltaMovement(Vec3.ZERO);
getLevel().addFreshEntity(itemEntity);
}
private boolean updateStationState(Consumer<GlobalStation> updateState) {
GlobalStation station = getStation();
GraphLocation graphLocation = edgePoint.determineGraphLocation();
if (station == null || graphLocation == null)
return false;
updateState.accept(station);
Create.RAILWAYS.sync.pointAdded(graphLocation.graph, station);
Create.RAILWAYS.markTracksDirty();
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 updateName(String name) {
if (!updateStationState(station -> station.name = name))
return false;
notifyUpdate();
return true;
}
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
public void remove() {
assemblyAreas.get(level)
.remove(worldPosition);
super.remove();
}
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) {
if (isItemHandlerCap(cap))
return depotBehaviour.getItemCapability(cap, side);
if (computerBehaviour.isPeripheralCap(cap))
return computerBehaviour.getPeripheralCapability();
return super.getCapability(cap, side);
}
@Override
public void invalidateCaps() {
super.invalidateCaps();
computerBehaviour.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);
}
}