mirror of
https://github.com/Creators-of-Create/Create.git
synced 2024-06-02 18:59:19 +02:00
937 lines
31 KiB
Java
937 lines
31 KiB
Java
package com.simibubi.create.content.trains.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 com.simibubi.create.content.trains.graph.DiscoveredPath;
|
|
|
|
import org.jetbrains.annotations.NotNull;
|
|
|
|
import com.simibubi.create.AllBlocks;
|
|
import com.simibubi.create.AllItems;
|
|
import com.simibubi.create.AllPackets;
|
|
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.AssemblyException;
|
|
import com.simibubi.create.content.contraptions.ITransformableBlockEntity;
|
|
import com.simibubi.create.content.contraptions.StructureTransform;
|
|
import com.simibubi.create.content.decoration.slidingDoor.DoorControlBehaviour;
|
|
import com.simibubi.create.content.logistics.depot.DepotBehaviour;
|
|
import com.simibubi.create.content.redstone.displayLink.DisplayLinkBlock;
|
|
import com.simibubi.create.content.trains.bogey.AbstractBogeyBlock;
|
|
import com.simibubi.create.content.trains.bogey.AbstractBogeyBlockEntity;
|
|
import com.simibubi.create.content.trains.entity.Carriage;
|
|
import com.simibubi.create.content.trains.entity.CarriageBogey;
|
|
import com.simibubi.create.content.trains.entity.CarriageContraption;
|
|
import com.simibubi.create.content.trains.entity.Train;
|
|
import com.simibubi.create.content.trains.entity.TrainPacket;
|
|
import com.simibubi.create.content.trains.entity.TravellingPoint;
|
|
import com.simibubi.create.content.trains.graph.EdgePointType;
|
|
import com.simibubi.create.content.trains.graph.TrackEdge;
|
|
import com.simibubi.create.content.trains.graph.TrackGraph;
|
|
import com.simibubi.create.content.trains.graph.TrackGraphLocation;
|
|
import com.simibubi.create.content.trains.graph.TrackNode;
|
|
import com.simibubi.create.content.trains.graph.TrackNodeLocation;
|
|
import com.simibubi.create.content.trains.graph.TrackNodeLocation.DiscoveredLocation;
|
|
import com.simibubi.create.content.trains.schedule.Schedule;
|
|
import com.simibubi.create.content.trains.schedule.ScheduleItem;
|
|
import com.simibubi.create.content.trains.track.ITrackBlock;
|
|
import com.simibubi.create.content.trains.track.TrackTargetingBehaviour;
|
|
import com.simibubi.create.foundation.advancement.AllAdvancements;
|
|
import com.simibubi.create.foundation.block.ProperWaterloggedBlock;
|
|
import com.simibubi.create.foundation.blockEntity.SmartBlockEntity;
|
|
import com.simibubi.create.foundation.blockEntity.behaviour.BlockEntityBehaviour;
|
|
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 com.simibubi.create.infrastructure.config.AllConfigs;
|
|
|
|
import net.minecraft.ChatFormatting;
|
|
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.BlockEntity;
|
|
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 StationBlockEntity extends SmartBlockEntity implements ITransformableBlockEntity {
|
|
|
|
public TrackTargetingBehaviour<GlobalStation> edgePoint;
|
|
public DoorControlBehaviour doorControls;
|
|
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 StationBlockEntity(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<BlockEntityBehaviour> behaviours) {
|
|
behaviours.add(edgePoint = new TrackTargetingBehaviour<>(this, EdgePointType.STATION));
|
|
behaviours.add(doorControls = new DoorControlBehaviour(this));
|
|
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;
|
|
AbstractBogeyBlock<?>[] bogeyTypes;
|
|
boolean[] upsideDownBogeys;
|
|
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));
|
|
BlockPos down = new BlockPos(track.getUpNormal(level, pos, state).scale(-1));
|
|
int bogeyOffset = pos.distManhattan(edgePoint.getGlobalPosition()) - 1;
|
|
|
|
if (!isValidBogeyOffset(bogeyOffset)) {
|
|
for (boolean upsideDown : Iterate.falseAndTrue) {
|
|
for (int i = -1; i <= 1; i++) {
|
|
BlockPos bogeyPos = pos.relative(assemblyDirection, i)
|
|
.offset(upsideDown ? down : up);
|
|
BlockState blockState = level.getBlockState(bogeyPos);
|
|
if (!(blockState.getBlock() instanceof AbstractBogeyBlock<?> bogey))
|
|
continue;
|
|
BlockEntity be = level.getBlockEntity(bogeyPos);
|
|
if (!(be instanceof AbstractBogeyBlockEntity oldBE))
|
|
continue;
|
|
CompoundTag oldData = oldBE.getBogeyData();
|
|
BlockState newBlock = bogey.getNextSize(oldBE);
|
|
if (newBlock.getBlock() == bogey)
|
|
player.displayClientMessage(Lang.translateDirect("bogey.style.no_other_sizes")
|
|
.withStyle(ChatFormatting.RED), true);
|
|
level.setBlock(bogeyPos, newBlock, 3);
|
|
BlockEntity newEntity = level.getBlockEntity(bogeyPos);
|
|
if (!(newEntity instanceof AbstractBogeyBlockEntity newBE))
|
|
continue;
|
|
newBE.setBogeyData(oldData);
|
|
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;
|
|
}
|
|
|
|
boolean upsideDown = (player.getViewXRot(1.0F) < 0 && (track.getBogeyAnchor(level, pos, state)).getBlock() instanceof AbstractBogeyBlock<?> bogey && bogey.canBeUpsideDown());
|
|
|
|
BlockPos targetPos = upsideDown ? pos.offset(down) : pos.offset(up);
|
|
if (level.getBlockState(targetPos)
|
|
.getDestroySpeed(level, targetPos) == -1) {
|
|
return false;
|
|
}
|
|
|
|
level.destroyBlock(targetPos, true);
|
|
|
|
BlockState bogeyAnchor = track.getBogeyAnchor(level, pos, state);
|
|
if (bogeyAnchor.getBlock() instanceof AbstractBogeyBlock<?> bogey) {
|
|
bogeyAnchor = bogey.getVersion(bogeyAnchor, upsideDown);
|
|
}
|
|
bogeyAnchor = ProperWaterloggedBlock.withWater(level, bogeyAnchor, 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;
|
|
|
|
DiscoveredPath preferredPath = train.runtime.startCurrentInstruction();
|
|
train.navigation.startNavigation(preferredPath != null ? preferredPath : train.navigation.findPathTo(station, Double.MAX_VALUE));
|
|
}
|
|
}
|
|
|
|
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();
|
|
TrackGraphLocation 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 AbstractBogeyBlock[maxBogeyCount];
|
|
if (upsideDownBogeys == null)
|
|
upsideDownBogeys = new boolean[maxBogeyCount];
|
|
Arrays.fill(bogeyLocations, -1);
|
|
Arrays.fill(bogeyTypes, null);
|
|
Arrays.fill(upsideDownBogeys, false);
|
|
|
|
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));
|
|
BlockPos upsideDownBogeyOffset = new BlockPos(bogeyOffset.getX(), bogeyOffset.getY()*-1, bogeyOffset.getZ());
|
|
if (bogeyIndex < bogeyLocations.length) {
|
|
if (potentialBogeyState.getBlock() instanceof AbstractBogeyBlock<?> bogey && !bogey.isUpsideDown(potentialBogeyState)) {
|
|
bogeyTypes[bogeyIndex] = bogey;
|
|
bogeyLocations[bogeyIndex] = i;
|
|
upsideDownBogeys[bogeyIndex] = false;
|
|
bogeyIndex++;
|
|
} else if ((potentialBogeyState = level.getBlockState(upsideDownBogeyOffset.offset(currentPos))).getBlock() instanceof AbstractBogeyBlock<?> bogey && bogey.isUpsideDown(potentialBogeyState)) {
|
|
bogeyTypes[bogeyIndex] = bogey;
|
|
bogeyLocations[bogeyIndex] = i;
|
|
upsideDownBogeys[bogeyIndex] = true;
|
|
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, false));
|
|
}
|
|
|
|
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);
|
|
BlockPos upsideDownBogeyPosOffset = trackPosition.offset(new BlockPos(bogeyOffset.getX(), bogeyOffset.getY() * -1, bogeyOffset.getZ()));
|
|
|
|
try {
|
|
int offset = bogeyLocations[bogeyIndex] + 1;
|
|
boolean success = contraption.assemble(level, upsideDownBogeys[bogeyIndex] ? upsideDownBogeyPosOffset.relative(assemblyDirection, offset) : 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;
|
|
}
|
|
|
|
AbstractBogeyBlock<?> typeOfFirstBogey = bogeyTypes[bogeyIndex];
|
|
boolean firstBogeyIsUpsideDown = upsideDownBogeys[bogeyIndex];
|
|
BlockPos firstBogeyPos = contraption.anchor;
|
|
AbstractBogeyBlockEntity firstBogeyBlockEntity = (AbstractBogeyBlockEntity) level.getBlockEntity(firstBogeyPos);
|
|
CarriageBogey firstBogey =
|
|
new CarriageBogey(typeOfFirstBogey, firstBogeyIsUpsideDown, firstBogeyBlockEntity.getBogeyData(), 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((upsideDownBogeys[bogeyIndex + 1] ? upsideDownBogeyPosOffset : bogeyPosOffset).relative(assemblyDirection, bogeyLocations[bogeyIndex + 1] + 1))) {
|
|
exception(new AssemblyException(Lang.translateDirect("train_assembly.not_connected_in_order")),
|
|
contraptions.size() + 1);
|
|
return;
|
|
}
|
|
AbstractBogeyBlockEntity secondBogeyBlockEntity =
|
|
(AbstractBogeyBlockEntity) level.getBlockEntity(secondBogeyPos);
|
|
bogeySpacing = bogeyLocations[bogeyIndex + 1] - bogeyLocations[bogeyIndex];
|
|
secondBogey = new CarriageBogey(bogeyTypes[bogeyIndex + 1], upsideDownBogeys[bogeyIndex + 1], secondBogeyBlockEntity.getBogeyData(),
|
|
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.getChannel().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);
|
|
}
|
|
|
|
}
|