Back & Forth

- Reverse steering is no longer inverted
- Fixed previous passengers not being removed from seat when player uses it
- Trains with two mounted controls/conductors can now pathfind backwards out of a station
- Fixed auto-approach not working properly while reversing or controlling a dual powered train the opposite direction
- Fixed icon display order and disassembly location of reversed trains in a station
- Fixed inaccurate train length()
- Blaze burners can now drive a train
- Non player train drivers are now required to sit in front of a controls block
- Schedule now interrupts when no conductors are found
- Fixed reversing trains not iterating carriages in the correct direction
This commit is contained in:
simibubi 2022-02-07 01:54:20 +01:00
parent 962975a09d
commit f64c355816
32 changed files with 587 additions and 169 deletions

View file

@ -536,21 +536,21 @@ bf2b0310500213ff853c748c236eb5d01f61658e assets/create/blockstates/yellow_toolbo
7f39521b211441f5c3e06d60c5978cebe16cacfb assets/create/blockstates/zinc_block.json
b7181bcd8182b2f17088e5aa881f374c9c65470c assets/create/blockstates/zinc_ore.json
9ffe5b3f8a39fa3c3a97a3c534bd82402177e82e assets/create/lang/en_ud.json
9dc50a1957cac1967adba6a02f2708f5a82915b6 assets/create/lang/en_us.json
60e216704dc824ae189af3927ae06e4c70e53478 assets/create/lang/unfinished/de_de.json
c2faed3f8cdc2616dc03d6c9cf6dafd9084c3e14 assets/create/lang/unfinished/es_cl.json
f9632799fdf6278d0b0c0a2bff40df099745b076 assets/create/lang/unfinished/es_es.json
1d7af6b083679766bb542cf61fc6a835983a055c assets/create/lang/unfinished/fr_fr.json
4942f180ef3758a1449108331943e0fa75e1a29b assets/create/lang/unfinished/it_it.json
3249596dacdf06bc727676a5cc8acf85742054d0 assets/create/lang/unfinished/ja_jp.json
ef7601ee26fdaf0356fe5400bc4f230ccb87eea8 assets/create/lang/unfinished/ko_kr.json
6d3914298d06db106d91299729f39cf5406f6768 assets/create/lang/unfinished/nl_nl.json
e21bd53612d2e82e9214b94fd6d2d110377089c1 assets/create/lang/unfinished/pl_pl.json
56b4b8f892bf4442ac2f5140dc97273d1dfe2f27 assets/create/lang/unfinished/pt_br.json
63690bcfce50698d81562fe6cff1d0701a13e23d assets/create/lang/unfinished/pt_pt.json
dfa8dc43216673feac87a2a49d08fd8cc9b1d9f2 assets/create/lang/unfinished/ru_ru.json
dd18a29b4a76752ea033569ebbb07014e9aa3ab0 assets/create/lang/unfinished/zh_cn.json
0b67d5808e2665b5c0414a103b730508e17e01f6 assets/create/lang/unfinished/zh_tw.json
8a5aec9b50def31d67404d0bfe4b44e36f4b92fe assets/create/lang/en_us.json
d9daa020e298d6ab1420c31347e34d1c44b1754b assets/create/lang/unfinished/de_de.json
fb68c11749892f4548065b5a2c9c06d8ea191675 assets/create/lang/unfinished/es_cl.json
2c3dd3ce3babc836ca5d7cf7b6674bc82dacab75 assets/create/lang/unfinished/es_es.json
031fa6be3a6fa11c6f14dc71c96c75e3bfa29172 assets/create/lang/unfinished/fr_fr.json
e683bf4fb4acfb4ba4a545427c9d793905272572 assets/create/lang/unfinished/it_it.json
641d5b5066086679364cbf882722ca75b7a1578c assets/create/lang/unfinished/ja_jp.json
8260c7075f6e9fdb8a97618636854555a0dc2b72 assets/create/lang/unfinished/ko_kr.json
fb7df57cc05f3cf95715b4c1dcf7274beb332c4f assets/create/lang/unfinished/nl_nl.json
697fb5eb49f7316c9932fab43346717695c5e0c5 assets/create/lang/unfinished/pl_pl.json
e007d6c626a1fca61382838f413d0e40ca702017 assets/create/lang/unfinished/pt_br.json
c4b8a2bd6331dd0c703bc70204f9dce24be08cc2 assets/create/lang/unfinished/pt_pt.json
b8acd477bf9bef586fe10e58a4120064581ecabc assets/create/lang/unfinished/ru_ru.json
44e398366683e2d92ebdbbcdf7b6510b7ee62889 assets/create/lang/unfinished/zh_cn.json
0d0016f17bea0cece7b89de092db5517ddfb899e assets/create/lang/unfinished/zh_tw.json
487a511a01b2a4531fb672f917922312db78f958 assets/create/models/block/acacia_window.json
b48060cba1a382f373a05bf0039054053eccf076 assets/create/models/block/acacia_window_pane_noside.json
3066db1bf03cffa1a9c7fbacf47ae586632f4eb3 assets/create/models/block/acacia_window_pane_noside_alt.json

View file

@ -1376,6 +1376,8 @@
"create.schedule.loop1": "Schedule starts over",
"create.schedule.loop2": "when completed",
"create.schedule.train_still_assembling": "Confirm Train Assembly in the Station UI first",
"create.schedule.applied_to_train": "Train is now following this Schedule",
"create.schedule.non_controlling_seat": "Conductor needs to sit in front of a Controls block",
"create.track.selection_cleared": "Selection Cleared",
"create.track.valid_connection": "Can Connect ✔",

View file

@ -1,5 +1,5 @@
{
"_": "Missing Localizations: 1427",
"_": "Missing Localizations: 1429",
"_": "->------------------------] Game Elements [------------------------<-",
@ -1377,6 +1377,8 @@
"create.schedule.loop1": "UNLOCALIZED: Schedule starts over",
"create.schedule.loop2": "UNLOCALIZED: when completed",
"create.schedule.train_still_assembling": "UNLOCALIZED: Confirm Train Assembly in the Station UI first",
"create.schedule.applied_to_train": "UNLOCALIZED: Train is now following this Schedule",
"create.schedule.non_controlling_seat": "UNLOCALIZED: Conductor needs to sit in front of a Controls block",
"create.track.selection_cleared": "UNLOCALIZED: Selection Cleared",
"create.track.valid_connection": "UNLOCALIZED: Can Connect ✔",

View file

@ -1,5 +1,5 @@
{
"_": "Missing Localizations: 438",
"_": "Missing Localizations: 440",
"_": "->------------------------] Game Elements [------------------------<-",
@ -1377,6 +1377,8 @@
"create.schedule.loop1": "UNLOCALIZED: Schedule starts over",
"create.schedule.loop2": "UNLOCALIZED: when completed",
"create.schedule.train_still_assembling": "UNLOCALIZED: Confirm Train Assembly in the Station UI first",
"create.schedule.applied_to_train": "UNLOCALIZED: Train is now following this Schedule",
"create.schedule.non_controlling_seat": "UNLOCALIZED: Conductor needs to sit in front of a Controls block",
"create.track.selection_cleared": "UNLOCALIZED: Selection Cleared",
"create.track.valid_connection": "UNLOCALIZED: Can Connect ✔",

View file

@ -1,5 +1,5 @@
{
"_": "Missing Localizations: 438",
"_": "Missing Localizations: 440",
"_": "->------------------------] Game Elements [------------------------<-",
@ -1377,6 +1377,8 @@
"create.schedule.loop1": "UNLOCALIZED: Schedule starts over",
"create.schedule.loop2": "UNLOCALIZED: when completed",
"create.schedule.train_still_assembling": "UNLOCALIZED: Confirm Train Assembly in the Station UI first",
"create.schedule.applied_to_train": "UNLOCALIZED: Train is now following this Schedule",
"create.schedule.non_controlling_seat": "UNLOCALIZED: Conductor needs to sit in front of a Controls block",
"create.track.selection_cleared": "UNLOCALIZED: Selection Cleared",
"create.track.valid_connection": "UNLOCALIZED: Can Connect ✔",

View file

@ -1,5 +1,5 @@
{
"_": "Missing Localizations: 1689",
"_": "Missing Localizations: 1691",
"_": "->------------------------] Game Elements [------------------------<-",
@ -1377,6 +1377,8 @@
"create.schedule.loop1": "UNLOCALIZED: Schedule starts over",
"create.schedule.loop2": "UNLOCALIZED: when completed",
"create.schedule.train_still_assembling": "UNLOCALIZED: Confirm Train Assembly in the Station UI first",
"create.schedule.applied_to_train": "UNLOCALIZED: Train is now following this Schedule",
"create.schedule.non_controlling_seat": "UNLOCALIZED: Conductor needs to sit in front of a Controls block",
"create.track.selection_cleared": "UNLOCALIZED: Selection Cleared",
"create.track.valid_connection": "UNLOCALIZED: Can Connect ✔",

View file

@ -1,5 +1,5 @@
{
"_": "Missing Localizations: 1378",
"_": "Missing Localizations: 1380",
"_": "->------------------------] Game Elements [------------------------<-",
@ -1377,6 +1377,8 @@
"create.schedule.loop1": "UNLOCALIZED: Schedule starts over",
"create.schedule.loop2": "UNLOCALIZED: when completed",
"create.schedule.train_still_assembling": "UNLOCALIZED: Confirm Train Assembly in the Station UI first",
"create.schedule.applied_to_train": "UNLOCALIZED: Train is now following this Schedule",
"create.schedule.non_controlling_seat": "UNLOCALIZED: Conductor needs to sit in front of a Controls block",
"create.track.selection_cleared": "UNLOCALIZED: Selection Cleared",
"create.track.valid_connection": "UNLOCALIZED: Can Connect ✔",

View file

@ -1,5 +1,5 @@
{
"_": "Missing Localizations: 108",
"_": "Missing Localizations: 110",
"_": "->------------------------] Game Elements [------------------------<-",
@ -1377,6 +1377,8 @@
"create.schedule.loop1": "UNLOCALIZED: Schedule starts over",
"create.schedule.loop2": "UNLOCALIZED: when completed",
"create.schedule.train_still_assembling": "UNLOCALIZED: Confirm Train Assembly in the Station UI first",
"create.schedule.applied_to_train": "UNLOCALIZED: Train is now following this Schedule",
"create.schedule.non_controlling_seat": "UNLOCALIZED: Conductor needs to sit in front of a Controls block",
"create.track.selection_cleared": "UNLOCALIZED: Selection Cleared",
"create.track.valid_connection": "UNLOCALIZED: Can Connect ✔",

View file

@ -1,5 +1,5 @@
{
"_": "Missing Localizations: 110",
"_": "Missing Localizations: 112",
"_": "->------------------------] Game Elements [------------------------<-",
@ -1377,6 +1377,8 @@
"create.schedule.loop1": "UNLOCALIZED: Schedule starts over",
"create.schedule.loop2": "UNLOCALIZED: when completed",
"create.schedule.train_still_assembling": "UNLOCALIZED: Confirm Train Assembly in the Station UI first",
"create.schedule.applied_to_train": "UNLOCALIZED: Train is now following this Schedule",
"create.schedule.non_controlling_seat": "UNLOCALIZED: Conductor needs to sit in front of a Controls block",
"create.track.selection_cleared": "UNLOCALIZED: Selection Cleared",
"create.track.valid_connection": "UNLOCALIZED: Can Connect ✔",

View file

@ -1,5 +1,5 @@
{
"_": "Missing Localizations: 2042",
"_": "Missing Localizations: 2044",
"_": "->------------------------] Game Elements [------------------------<-",
@ -1377,6 +1377,8 @@
"create.schedule.loop1": "UNLOCALIZED: Schedule starts over",
"create.schedule.loop2": "UNLOCALIZED: when completed",
"create.schedule.train_still_assembling": "UNLOCALIZED: Confirm Train Assembly in the Station UI first",
"create.schedule.applied_to_train": "UNLOCALIZED: Train is now following this Schedule",
"create.schedule.non_controlling_seat": "UNLOCALIZED: Conductor needs to sit in front of a Controls block",
"create.track.selection_cleared": "UNLOCALIZED: Selection Cleared",
"create.track.valid_connection": "UNLOCALIZED: Can Connect ✔",

View file

@ -1,5 +1,5 @@
{
"_": "Missing Localizations: 477",
"_": "Missing Localizations: 479",
"_": "->------------------------] Game Elements [------------------------<-",
@ -1377,6 +1377,8 @@
"create.schedule.loop1": "UNLOCALIZED: Schedule starts over",
"create.schedule.loop2": "UNLOCALIZED: when completed",
"create.schedule.train_still_assembling": "UNLOCALIZED: Confirm Train Assembly in the Station UI first",
"create.schedule.applied_to_train": "UNLOCALIZED: Train is now following this Schedule",
"create.schedule.non_controlling_seat": "UNLOCALIZED: Conductor needs to sit in front of a Controls block",
"create.track.selection_cleared": "UNLOCALIZED: Selection Cleared",
"create.track.valid_connection": "UNLOCALIZED: Can Connect ✔",

View file

@ -1,5 +1,5 @@
{
"_": "Missing Localizations: 1661",
"_": "Missing Localizations: 1663",
"_": "->------------------------] Game Elements [------------------------<-",
@ -1377,6 +1377,8 @@
"create.schedule.loop1": "UNLOCALIZED: Schedule starts over",
"create.schedule.loop2": "UNLOCALIZED: when completed",
"create.schedule.train_still_assembling": "UNLOCALIZED: Confirm Train Assembly in the Station UI first",
"create.schedule.applied_to_train": "UNLOCALIZED: Train is now following this Schedule",
"create.schedule.non_controlling_seat": "UNLOCALIZED: Conductor needs to sit in front of a Controls block",
"create.track.selection_cleared": "UNLOCALIZED: Selection Cleared",
"create.track.valid_connection": "UNLOCALIZED: Can Connect ✔",

View file

@ -1,5 +1,5 @@
{
"_": "Missing Localizations: 1661",
"_": "Missing Localizations: 1663",
"_": "->------------------------] Game Elements [------------------------<-",
@ -1377,6 +1377,8 @@
"create.schedule.loop1": "UNLOCALIZED: Schedule starts over",
"create.schedule.loop2": "UNLOCALIZED: when completed",
"create.schedule.train_still_assembling": "UNLOCALIZED: Confirm Train Assembly in the Station UI first",
"create.schedule.applied_to_train": "UNLOCALIZED: Train is now following this Schedule",
"create.schedule.non_controlling_seat": "UNLOCALIZED: Conductor needs to sit in front of a Controls block",
"create.track.selection_cleared": "UNLOCALIZED: Selection Cleared",
"create.track.valid_connection": "UNLOCALIZED: Can Connect ✔",

View file

@ -1,5 +1,5 @@
{
"_": "Missing Localizations: 482",
"_": "Missing Localizations: 484",
"_": "->------------------------] Game Elements [------------------------<-",
@ -1377,6 +1377,8 @@
"create.schedule.loop1": "UNLOCALIZED: Schedule starts over",
"create.schedule.loop2": "UNLOCALIZED: when completed",
"create.schedule.train_still_assembling": "UNLOCALIZED: Confirm Train Assembly in the Station UI first",
"create.schedule.applied_to_train": "UNLOCALIZED: Train is now following this Schedule",
"create.schedule.non_controlling_seat": "UNLOCALIZED: Conductor needs to sit in front of a Controls block",
"create.track.selection_cleared": "UNLOCALIZED: Selection Cleared",
"create.track.valid_connection": "UNLOCALIZED: Can Connect ✔",

View file

@ -1,5 +1,5 @@
{
"_": "Missing Localizations: 108",
"_": "Missing Localizations: 110",
"_": "->------------------------] Game Elements [------------------------<-",
@ -1377,6 +1377,8 @@
"create.schedule.loop1": "UNLOCALIZED: Schedule starts over",
"create.schedule.loop2": "UNLOCALIZED: when completed",
"create.schedule.train_still_assembling": "UNLOCALIZED: Confirm Train Assembly in the Station UI first",
"create.schedule.applied_to_train": "UNLOCALIZED: Train is now following this Schedule",
"create.schedule.non_controlling_seat": "UNLOCALIZED: Conductor needs to sit in front of a Controls block",
"create.track.selection_cleared": "UNLOCALIZED: Selection Cleared",
"create.track.valid_connection": "UNLOCALIZED: Can Connect ✔",

View file

@ -1,5 +1,5 @@
{
"_": "Missing Localizations: 496",
"_": "Missing Localizations: 498",
"_": "->------------------------] Game Elements [------------------------<-",
@ -1377,6 +1377,8 @@
"create.schedule.loop1": "UNLOCALIZED: Schedule starts over",
"create.schedule.loop2": "UNLOCALIZED: when completed",
"create.schedule.train_still_assembling": "UNLOCALIZED: Confirm Train Assembly in the Station UI first",
"create.schedule.applied_to_train": "UNLOCALIZED: Train is now following this Schedule",
"create.schedule.non_controlling_seat": "UNLOCALIZED: Conductor needs to sit in front of a Controls block",
"create.track.selection_cleared": "UNLOCALIZED: Selection Cleared",
"create.track.valid_connection": "UNLOCALIZED: Can Connect ✔",

View file

@ -98,6 +98,7 @@ import com.simibubi.create.content.contraptions.processing.BasinGenerator;
import com.simibubi.create.content.contraptions.processing.BasinMovementBehaviour;
import com.simibubi.create.content.contraptions.processing.burner.BlazeBurnerBlock;
import com.simibubi.create.content.contraptions.processing.burner.BlazeBurnerBlockItem;
import com.simibubi.create.content.contraptions.processing.burner.BlazeBurnerInteractionBehaviour;
import com.simibubi.create.content.contraptions.processing.burner.LitBlazeBurnerBlock;
import com.simibubi.create.content.contraptions.relays.advanced.GantryShaftBlock;
import com.simibubi.create.content.contraptions.relays.advanced.SpeedControllerBlock;
@ -568,6 +569,7 @@ public class AllBlocks {
.tag(AllBlockTags.FAN_TRANSPARENT.tag, AllBlockTags.FAN_HEATERS.tag)
.loot((lt, block) -> lt.add(block, BlazeBurnerBlock.buildLootTable()))
.blockstate((c, p) -> p.simpleBlock(c.getEntry(), AssetLookup.partialBaseModel(c, p)))
.onRegister(addInteractionBehaviour(new BlazeBurnerInteractionBehaviour()))
.item(BlazeBurnerBlockItem::withBlaze)
.model(AssetLookup.<BlazeBurnerBlockItem>customBlockItemModel("blaze_burner", "block_with_blaze"))
.build()

View file

@ -99,6 +99,15 @@ public abstract class AbstractContraptionEntity extends Entity implements IEntit
}
public void addSittingPassenger(Entity passenger, int seatIndex) {
for (Entity entity : getPassengers()) {
BlockPos seatOf = contraption.getSeatOf(entity.getUUID());
if (seatOf != null && seatOf.equals(contraption.getSeats()
.get(seatIndex))) {
if (entity instanceof Player)
return;
entity.stopRiding();
}
}
passenger.startRiding(this, true);
if (level.isClientSide)
return;
@ -168,7 +177,7 @@ public abstract class AbstractContraptionEntity extends Entity implements IEntit
return getName();
}
public boolean startControlling(BlockPos controlsLocalPos) {
public boolean startControlling(BlockPos controlsLocalPos, Player player) {
return false;
}
@ -470,7 +479,7 @@ public abstract class AbstractContraptionEntity extends Entity implements IEntit
contraption.addBlocksToWorld(level, transform);
contraption.addPassengersToWorld(level, transform, getPassengers());
for (Entity entity : getPassengers()) {
if (!(entity instanceof OrientedContraptionEntity))
continue;
@ -482,7 +491,7 @@ public abstract class AbstractContraptionEntity extends Entity implements IEntit
entity.setPos(transformed.getX(), transformed.getY(), transformed.getZ());
((AbstractContraptionEntity) entity).disassemble();
}
discard();
ejectPassengers();

View file

@ -17,6 +17,7 @@ import net.minecraft.client.KeyMapping;
import net.minecraft.client.Minecraft;
import net.minecraft.client.player.LocalPlayer;
import net.minecraft.core.BlockPos;
import net.minecraft.world.entity.player.Player;
public class ControlsHandler {
@ -28,14 +29,14 @@ public class ControlsHandler {
static WeakReference<AbstractContraptionEntity> entityRef = new WeakReference<>(null);
static BlockPos controlsPos;
public static void controllerClicked(AbstractContraptionEntity entity, BlockPos controllerLocalPos) {
public static void controllerClicked(AbstractContraptionEntity entity, BlockPos controllerLocalPos, Player player) {
AbstractContraptionEntity prevEntity = entityRef.get();
if (prevEntity != null) {
stopControlling();
if (prevEntity == entity)
return;
}
if (!entity.startControlling(controllerLocalPos))
if (!entity.startControlling(controllerLocalPos, player))
return;
entityRef = new WeakReference<AbstractContraptionEntity>(entity);
controlsPos = controllerLocalPos;

View file

@ -13,7 +13,7 @@ public class ControlsInteractionBehaviour extends MovingInteractionBehaviour {
public boolean handlePlayerInteraction(Player player, InteractionHand activeHand, BlockPos localPos,
AbstractContraptionEntity contraptionEntity) {
if (player.level.isClientSide)
ControlsHandler.controllerClicked(contraptionEntity, localPos);
ControlsHandler.controllerClicked(contraptionEntity, localPos, player);
return true;
}

View file

@ -0,0 +1,74 @@
package com.simibubi.create.content.contraptions.processing.burner;
import com.simibubi.create.AllItems;
import com.simibubi.create.AllSoundEvents;
import com.simibubi.create.content.contraptions.components.structureMovement.AbstractContraptionEntity;
import com.simibubi.create.content.contraptions.components.structureMovement.Contraption;
import com.simibubi.create.content.contraptions.components.structureMovement.MovingInteractionBehaviour;
import com.simibubi.create.content.contraptions.processing.burner.BlazeBurnerBlock.HeatLevel;
import com.simibubi.create.content.logistics.trains.entity.CarriageContraption;
import com.simibubi.create.content.logistics.trains.entity.CarriageContraptionEntity;
import com.simibubi.create.content.logistics.trains.entity.Train;
import com.simibubi.create.content.logistics.trains.management.ScheduleItem;
import com.simibubi.create.content.logistics.trains.management.schedule.Schedule;
import com.simibubi.create.foundation.utility.Iterate;
import com.simibubi.create.foundation.utility.Lang;
import net.minecraft.ChatFormatting;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate.StructureBlockInfo;
public class BlazeBurnerInteractionBehaviour extends MovingInteractionBehaviour {
@Override
public boolean handlePlayerInteraction(Player player, InteractionHand activeHand, BlockPos localPos,
AbstractContraptionEntity contraptionEntity) {
ItemStack itemInHand = player.getItemInHand(activeHand);
if (!AllItems.SCHEDULE.isIn(itemInHand))
return false;
if (!(contraptionEntity instanceof CarriageContraptionEntity carriage))
return false;
Contraption contraption = carriage.getContraption();
if (!(contraption instanceof CarriageContraption carriageContraption))
return false;
StructureBlockInfo info = carriageContraption.getBlocks()
.get(localPos);
if (info == null || !info.state.hasProperty(BlazeBurnerBlock.HEAT_LEVEL)
|| info.state.getValue(BlazeBurnerBlock.HEAT_LEVEL) == HeatLevel.NONE)
return false;
Direction assemblyDirection = carriageContraption.getAssemblyDirection();
for (Direction direction : Iterate.directionsInAxis(assemblyDirection.getAxis())) {
if (carriageContraption.inControl(localPos, direction)) {
Schedule schedule = ScheduleItem.getSchedule(itemInHand);
if (schedule == null)
return false;
Train train = carriage.getCarriage().train;
if (train == null)
return false;
if (train.heldForAssembly) {
player.displayClientMessage(Lang.translate("schedule.train_still_assembling"), true);
AllSoundEvents.DENY.playOnServer(player.level, player.blockPosition(), 1, 1);
return true;
}
train.runtime.setSchedule(schedule, false);
AllSoundEvents.CONFIRM.playOnServer(player.level, player.blockPosition(), 1, 1);
player.displayClientMessage(Lang.translate("schedule.applied_to_train")
.withStyle(ChatFormatting.GREEN), true);
return true;
}
}
player.displayClientMessage(Lang.translate("schedule.non_controlling_seat"), true);
AllSoundEvents.DENY.playOnServer(player.level, player.blockPosition(), 1, 1);
return true;
}
}

View file

@ -7,7 +7,6 @@ import java.util.function.Function;
import javax.annotation.Nullable;
import org.apache.commons.lang3.mutable.MutableDouble;
import org.apache.commons.lang3.mutable.MutableObject;
import com.simibubi.create.Create;
import com.simibubi.create.content.logistics.trains.IBogeyBlock;
@ -15,6 +14,7 @@ import com.simibubi.create.content.logistics.trains.TrackGraph;
import com.simibubi.create.content.logistics.trains.entity.TravellingPoint.ITrackSelector;
import com.simibubi.create.foundation.utility.AngleHelper;
import com.simibubi.create.foundation.utility.Couple;
import com.simibubi.create.foundation.utility.Iterate;
import com.simibubi.create.foundation.utility.VecHelper;
import com.simibubi.create.foundation.utility.animation.LerpedFloat;
@ -35,6 +35,9 @@ public class Carriage {
public int id;
public boolean blocked;
public boolean hasForwardConductor;
public boolean hasBackwardConductor;
WeakReference<CarriageContraptionEntity> entity;
Couple<CarriageBogey> bogeys;
@ -67,30 +70,27 @@ public class Carriage {
double stress = onTwoBogeys ? bogeySpacing - leadingAnchor.distanceTo(trailingAnchor) : 0;
blocked = false;
// positive stress: points should move apart
// negative stress: points should move closer
double leadingBogeyModifier = 0.5d;
double trailingBogeyModifier = -0.5d;
double leadingPointModifier = 0.5d;
double trailingPointModifier = -0.5d;
MutableObject<TravellingPoint> previous = new MutableObject<>();
MutableDouble distanceMoved = new MutableDouble(distance);
boolean iterateFromBack = distance < 0;
bogeys.forEachWithContext((bogey, firstBogey) -> {
for (boolean firstBogey : Iterate.trueAndFalse) {
if (!firstBogey && !onTwoBogeys)
return;
continue;
double bogeyCorrection = stress * (firstBogey ? leadingBogeyModifier : trailingBogeyModifier);
boolean actuallyFirstBogey = !onTwoBogeys || (firstBogey ^ iterateFromBack);
CarriageBogey bogey = bogeys.get(actuallyFirstBogey);
double bogeyCorrection = stress * (actuallyFirstBogey ? 0.5d : -0.5d);
double bogeyStress = bogey.getStress();
bogey.points.forEachWithContext((point, first) -> {
TravellingPoint prevPoint = previous.getValue();
TravellingPoint nextPoint = first ? bogey.points.getSecond()
: firstBogey && onTwoBogeys ? bogeys.getSecond().points.getFirst() : null;
for (boolean firstWheel : Iterate.trueAndFalse) {
boolean actuallyFirstWheel = firstWheel ^ iterateFromBack;
TravellingPoint point = bogey.points.get(actuallyFirstWheel);
TravellingPoint prevPoint = !actuallyFirstWheel ? bogey.points.getFirst()
: !actuallyFirstBogey && onTwoBogeys ? bogeys.getFirst().points.getSecond() : null;
TravellingPoint nextPoint = actuallyFirstWheel ? bogey.points.getSecond()
: actuallyFirstBogey && onTwoBogeys ? bogeys.getSecond().points.getFirst() : null;
double correction = bogeyStress * (first ? leadingPointModifier : trailingPointModifier);
double correction = bogeyStress * (actuallyFirstWheel ? 0.5d : -0.5d);
double toMove = distanceMoved.getValue();
ITrackSelector frontTrackSelector =
@ -99,21 +99,29 @@ public class Carriage {
nextPoint == null ? backwardControl.apply(point) : point.follow(nextPoint);
double moved = point.travel(graph, toMove, toMove > 0 ? frontTrackSelector : backTrackSelector);
point.travel(graph, correction + bogeyCorrection,
correction + bogeyCorrection > 0 ? frontTrackSelector : backTrackSelector);
double stressCorrection = correction + bogeyCorrection;
point.travel(graph, stressCorrection, stressCorrection > 0 ? frontTrackSelector : backTrackSelector);
blocked |= point.blocked;
distanceMoved.setValue(moved);
previous.setValue(point);
});
}
bogey.updateAnchorPosition();
});
}
tickEntity(level);
return distanceMoved.getValue();
}
public void updateConductors() {
CarriageContraptionEntity entity = this.entity.get();
if (entity == null || !entity.isAlive())
return;
Couple<Boolean> sides = entity.checkConductors();
hasForwardConductor = sides.getFirst();
hasBackwardConductor = sides.getSecond();
}
public void createEntity(Level level) {
contraption.startMoving(level);
CarriageContraptionEntity entity = CarriageContraptionEntity.create(level, contraption);

View file

@ -1,5 +1,10 @@
package com.simibubi.create.content.logistics.trains.entity;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.tuple.Pair;
import com.simibubi.create.AllBlocks;
@ -9,13 +14,19 @@ import com.simibubi.create.content.contraptions.components.structureMovement.Con
import com.simibubi.create.content.contraptions.components.structureMovement.ContraptionType;
import com.simibubi.create.content.contraptions.components.structureMovement.NonStationaryLighter;
import com.simibubi.create.content.contraptions.components.structureMovement.interaction.controls.ControlsBlock;
import com.simibubi.create.content.contraptions.processing.burner.BlazeBurnerBlock;
import com.simibubi.create.content.contraptions.processing.burner.BlazeBurnerBlock.HeatLevel;
import com.simibubi.create.content.logistics.trains.IBogeyBlock;
import com.simibubi.create.foundation.utility.Couple;
import com.simibubi.create.foundation.utility.Iterate;
import com.simibubi.create.foundation.utility.Lang;
import com.simibubi.create.foundation.utility.NBTHelper;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NbtUtils;
import net.minecraft.nbt.Tag;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
@ -24,20 +35,29 @@ import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemp
public class CarriageContraption extends Contraption {
private Direction assemblyDirection;
private boolean forwardControls;
private boolean backwardControls;
private boolean sidewaysControls;
private int bogeys;
private BlockPos secondBogeyPos;
public Couple<Boolean> blazeBurnerConductors;
public Map<BlockPos, Couple<Boolean>> conductorSeats;
// runtime
private Carriage carriage;
public int temporaryCarriageIdHolder = -1;
public CarriageContraption() {}
// for assembly only
private int bogeys;
private boolean sidewaysControls;
private BlockPos secondBogeyPos;
private List<BlockPos> assembledBlazeBurners;
public CarriageContraption() {
conductorSeats = new HashMap<>();
assembledBlazeBurners = new ArrayList<>();
blazeBurnerConductors = Couple.create(false, false);
}
public CarriageContraption(Direction assemblyDirection) {
this();
this.assemblyDirection = assemblyDirection;
this.bogeys = 0;
}
@ -54,9 +74,30 @@ public class CarriageContraption extends Contraption {
throw new AssemblyException(Lang.translate("train_assembly.too_many_bogeys", bogeys));
if (sidewaysControls)
throw new AssemblyException(Lang.translate("train_assembly.sideways_controls"));
for (BlockPos blazePos : assembledBlazeBurners)
for (Direction direction : Iterate.directionsInAxis(assemblyDirection.getAxis()))
if (inControl(blazePos, direction))
blazeBurnerConductors.set(direction != assemblyDirection, true);
for (BlockPos seatPos : getSeats())
for (Direction direction : Iterate.directionsInAxis(assemblyDirection.getAxis()))
if (inControl(seatPos, direction))
conductorSeats.computeIfAbsent(seatPos, p -> Couple.create(false, false))
.set(direction != assemblyDirection, true);
return true;
}
public boolean inControl(BlockPos pos, Direction direction) {
BlockPos controlsPos = pos.relative(direction);
if (!blocks.containsKey(controlsPos))
return false;
StructureBlockInfo info = blocks.get(controlsPos);
if (!AllBlocks.CONTROLS.has(info.state))
return false;
return info.state.getValue(ControlsBlock.FACING) == direction.getOpposite();
}
@Override
protected boolean isAnchoringBlockAt(BlockPos pos) {
return false;
@ -73,14 +114,21 @@ public class CarriageContraption extends Contraption {
return Pair.of(new StructureBlockInfo(pos, blockState, null), null);
}
if (AllBlocks.BLAZE_BURNER.has(blockState)
&& blockState.getValue(BlazeBurnerBlock.HEAT_LEVEL) != HeatLevel.NONE)
assembledBlazeBurners.add(toLocalPos(pos));
if (AllBlocks.CONTROLS.has(blockState)) {
Direction facing = blockState.getValue(ControlsBlock.FACING);
if (facing.getAxis() != assemblyDirection.getAxis())
sidewaysControls = true;
else if (facing == assemblyDirection)
forwardControls = true;
else
backwardControls = true;
else {
boolean forwards = facing == assemblyDirection;
if (forwards)
forwardControls = true;
else
backwardControls = true;
}
}
return super.capture(world, pos);
@ -94,6 +142,17 @@ public class CarriageContraption extends Contraption {
tag.putInt("CarriageId", carriage.id);
tag.putBoolean("FrontControls", forwardControls);
tag.putBoolean("BackControls", backwardControls);
tag.putBoolean("FrontBlazeConductor", blazeBurnerConductors.getFirst());
tag.putBoolean("BackBlazeConductor", blazeBurnerConductors.getSecond());
NBTHelper.writeCompoundList(conductorSeats.entrySet(), e -> {
CompoundTag compoundTag = new CompoundTag();
compoundTag.put("Pos", NbtUtils.writeBlockPos(e.getKey()));
compoundTag.putBoolean("Forward", e.getValue()
.getFirst());
compoundTag.putBoolean("Backward", e.getValue()
.getSecond());
return compoundTag;
});
return tag;
}
@ -104,6 +163,12 @@ public class CarriageContraption extends Contraption {
temporaryCarriageIdHolder = nbt.getInt("CarriageId");
forwardControls = nbt.getBoolean("FrontControls");
backwardControls = nbt.getBoolean("BackControls");
blazeBurnerConductors =
Couple.create(nbt.getBoolean("FrontBlazeConductor"), nbt.getBoolean("BackBlazeConductor"));
conductorSeats.clear();
NBTHelper.iterateCompoundList(nbt.getList("ConductorSeats", Tag.TAG_COMPOUND),
c -> conductorSeats.put(NbtUtils.readBlockPos(c.getCompound("Pos")),
Couple.create(nbt.getBoolean("Forward"), nbt.getBoolean("Backward"))));
super.readNBT(world, nbt, spawnData);
}

View file

@ -13,6 +13,7 @@ import com.simibubi.create.content.contraptions.components.structureMovement.int
import com.simibubi.create.content.logistics.trains.entity.TravellingPoint.SteerDirection;
import com.simibubi.create.content.logistics.trains.management.GlobalStation;
import com.simibubi.create.foundation.utility.Color;
import com.simibubi.create.foundation.utility.Couple;
import com.simibubi.create.foundation.utility.Lang;
import com.simibubi.create.foundation.utility.VecHelper;
@ -24,6 +25,7 @@ import net.minecraft.core.particles.ParticleTypes;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.TextComponent;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.Level;
@ -62,13 +64,39 @@ public class CarriageContraptionEntity extends OrientedContraptionEntity {
return contraptionName;
}
public Couple<Boolean> checkConductors() {
Couple<Boolean> sides = Couple.create(false, false);
if (!(contraption instanceof CarriageContraption cc))
return sides;
sides.setFirst(cc.blazeBurnerConductors.getFirst());
sides.setSecond(cc.blazeBurnerConductors.getSecond());
for (Entity entity : getPassengers()) {
BlockPos seatOf = cc.getSeatOf(entity.getUUID());
if (seatOf == null)
continue;
Couple<Boolean> validSides = cc.conductorSeats.get(seatOf);
if (validSides == null)
continue;
sides.setFirst(sides.getFirst() || validSides.getFirst());
sides.setSecond(sides.getSecond() || validSides.getSecond());
}
return sides;
}
@Override
public boolean startControlling(BlockPos controlsLocalPos) {
public boolean startControlling(BlockPos controlsLocalPos, Player player) {
Carriage carriage = getCarriage();
if (carriage == null)
return false;
if (carriage.train.derailed)
return false;
if (carriage.train.heldForAssembly) {
player.displayClientMessage(Lang.translate("schedule.train_still_assembling"), true);
return false;
}
Train train = carriage.train;
if (train.runtime.getSchedule() != null && !train.runtime.paused)
@ -110,9 +138,10 @@ public class CarriageContraptionEntity extends OrientedContraptionEntity {
if (inverted) {
targetSpeed *= -1;
// targetSteer *= -1;
targetSteer *= -1;
}
boolean slow = inverted ^ targetSpeed < 0;
boolean spaceDown = heldControls.contains(4);
GlobalStation currentStation = carriage.train.getCurrentStation();
if (currentStation != null && spaceDown) {
@ -127,7 +156,7 @@ public class CarriageContraptionEntity extends OrientedContraptionEntity {
.append(new TextComponent(currentStation.name).withStyle(ChatFormatting.WHITE)), true);
}
if (currentStation == null && targetSpeed >= 0) {
if (currentStation == null) {
Navigation nav = carriage.train.navigation;
if (nav.destination != null) {
if (!spaceDown)
@ -148,7 +177,10 @@ public class CarriageContraptionEntity extends OrientedContraptionEntity {
}
}
GlobalStation lookAhead = nav.findNearestApproachable();
double directedSpeed = targetSpeed != 0 ? targetSpeed : carriage.train.speed;
GlobalStation lookAhead = nav.findNearestApproachable(
!carriage.train.doubleEnded || (directedSpeed != 0 ? directedSpeed > 0 : !inverted));
if (lookAhead != null) {
if (spaceDown) {
nav.startNavigation(lookAhead, false);
@ -166,7 +198,7 @@ public class CarriageContraptionEntity extends OrientedContraptionEntity {
carriage.train.manualSteer =
targetSteer < 0 ? SteerDirection.RIGHT : targetSteer > 0 ? SteerDirection.LEFT : SteerDirection.NONE;
carriage.train.targetSpeed = Train.topSpeed * targetSpeed;
if (inverted ^ targetSpeed < 0)
if (slow)
carriage.train.targetSpeed /= 8;
boolean counteringAcceleration = Math.abs(Math.signum(targetSpeed) - Math.signum(carriage.train.speed)) > 1.5f;
carriage.train.manualTick = true;

View file

@ -19,6 +19,7 @@ import com.simibubi.create.content.logistics.trains.TrackNodeLocation;
import com.simibubi.create.content.logistics.trains.entity.TravellingPoint.ITrackSelector;
import com.simibubi.create.content.logistics.trains.management.GlobalStation;
import com.simibubi.create.foundation.utility.Couple;
import com.simibubi.create.foundation.utility.Iterate;
import com.simibubi.create.foundation.utility.Pair;
import net.minecraft.world.level.Level;
@ -29,6 +30,7 @@ public class Navigation {
Train train;
public GlobalStation destination;
public double distanceToDestination;
public boolean destinationBehindTrain;
List<TrackEdge> currentPath;
public Navigation(Train train, TrackGraph graph) {
@ -40,6 +42,27 @@ public class Navigation {
if (destination == null)
return;
if (!train.runtime.paused) {
boolean frontDriver = train.hasForwardConductor();
boolean backDriver = train.hasBackwardConductor();
if (destinationBehindTrain && !backDriver) {
if (frontDriver)
train.status.missingBackwardsConductor();
else
train.status.missingConductor();
cancelNavigation();
return;
}
if (!destinationBehindTrain && !frontDriver) {
train.status.missingConductor();
cancelNavigation();
return;
}
train.status.foundConductor();
}
destination.reserveFor(train);
if (distanceToDestination < 1 / 32f) {
@ -51,21 +74,24 @@ public class Navigation {
return;
}
if (distanceToDestination - train.speed < 1 / 32f) {
train.speed = distanceToDestination;
float speedMod = destinationBehindTrain ? -1 : 1;
train.currentlyBackwards = destinationBehindTrain;
if (distanceToDestination - Math.abs(train.speed) < 1 / 32f) {
train.speed = distanceToDestination * speedMod;
return;
}
if (distanceToDestination < 10) {
double target = Train.topSpeed * ((distanceToDestination) / 10);
if (target < train.speed) {
train.speed += (target - train.speed) * .5f;
if (target < Math.abs(train.speed)) {
train.speed += (target - Math.abs(train.speed)) * .5f * speedMod;
return;
}
}
double brakingDistance = (train.speed * train.speed) / (2 * Train.acceleration);
train.targetSpeed = distanceToDestination > brakingDistance ? Train.topSpeed : 0;
train.targetSpeed = distanceToDestination > brakingDistance ? Train.topSpeed * speedMod : 0;
train.approachTargetSpeed(1);
}
@ -103,13 +129,17 @@ public class Navigation {
public double startNavigation(GlobalStation destination, boolean simulate) {
Pair<Double, List<TrackEdge>> pathTo = findPathTo(destination);
boolean noneFound = pathTo.getFirst() == null;
double distance = noneFound ? -1 : Math.abs(pathTo.getFirst());
if (simulate)
return pathTo.getFirst();
return distance;
distanceToDestination = pathTo.getFirst();
distanceToDestination = distance;
currentPath = pathTo.getSecond();
if (distanceToDestination == -1) {
destinationBehindTrain = pathTo.getFirst() < 0;
if (noneFound) {
distanceToDestination = 0;
if (this.destination != null)
cancelNavigation();
@ -118,6 +148,28 @@ public class Navigation {
if (this.destination == destination)
return 0;
if (!train.runtime.paused) {
boolean frontDriver = train.hasForwardConductor();
boolean backDriver = train.hasBackwardConductor();
if (destinationBehindTrain && !backDriver) {
if (frontDriver)
train.status.missingBackwardsConductor();
else
train.status.missingConductor();
return -1;
}
if (!destinationBehindTrain && !frontDriver) {
if (backDriver)
train.status.missingBackwardsConductor();
else
train.status.missingConductor();
return -1;
}
train.status.foundConductor();
}
train.leaveStation();
this.destination = destination;
@ -129,49 +181,85 @@ public class Navigation {
List<TrackEdge> path = new ArrayList<>();
if (graph == null)
return Pair.of(-1d, path);
return Pair.of(null, path);
Couple<TrackNodeLocation> target = destination.edgeLocation;
TravellingPoint leadingPoint = train.carriages.get(0)
.getLeadingPoint();
TrackEdge initialEdge = leadingPoint.edge;
MutableObject<Pair<Double, List<TrackEdge>>> frontResult = new MutableObject<>(Pair.of(null, path));
MutableObject<Pair<Double, List<TrackEdge>>> backResult = new MutableObject<>(Pair.of(null, path));
MutableObject<Pair<Double, List<TrackEdge>>> result = new MutableObject<>(Pair.of(-1d, path));
for (boolean forward : Iterate.trueAndFalse) {
if (this.destination == destination && destinationBehindTrain == forward)
continue;
search((reachedVia, poll) -> {
double distance = poll.getFirst();
Pair<Couple<TrackNode>, TrackEdge> currentEntry = poll.getSecond();
TrackEdge edge = currentEntry.getSecond();
TrackNode node1 = currentEntry.getFirst()
.getFirst();
TrackNode node2 = currentEntry.getFirst()
.getSecond();
List<TrackEdge> currentPath = new ArrayList<>();
TravellingPoint initialPoint = forward ? train.carriages.get(0)
.getLeadingPoint()
: train.carriages.get(train.carriages.size() - 1)
.getTrailingPoint();
TrackEdge initialEdge = forward ? initialPoint.edge
: graph.getConnectionsFrom(initialPoint.node2)
.get(initialPoint.node1);
TrackNodeLocation loc1 = node1.getLocation();
TrackNodeLocation loc2 = node2.getLocation();
if (!loc1.equals(target.getFirst()) || !loc2.equals(target.getSecond()))
return false;
search(Double.MAX_VALUE, forward, (reachedVia, poll) -> {
Pair<Boolean, TrackEdge> backTrack = reachedVia.get(edge);
TrackEdge toReach = edge;
while (backTrack != null && toReach != initialEdge) {
if (backTrack.getFirst())
path.add(0, toReach);
toReach = backTrack.getSecond();
backTrack = reachedVia.get(backTrack.getSecond());
}
double distance = poll.getFirst();
Pair<Couple<TrackNode>, TrackEdge> currentEntry = poll.getSecond();
TrackEdge edge = currentEntry.getSecond();
TrackNode node1 = currentEntry.getFirst()
.getFirst();
TrackNode node2 = currentEntry.getFirst()
.getSecond();
double distanceToDestination = distance;
double position = edge.getLength(node1, node2) - destination.position;
distanceToDestination -= position;
result.setValue(Pair.of(distanceToDestination, path));
return true;
}, Double.MAX_VALUE);
TrackNodeLocation loc1 = node1.getLocation();
TrackNodeLocation loc2 = node2.getLocation();
if (!loc1.equals(target.getFirst()) || !loc2.equals(target.getSecond()))
return false;
return result.getValue();
Pair<Boolean, TrackEdge> backTrack = reachedVia.get(edge);
TrackEdge toReach = edge;
while (backTrack != null && toReach != initialEdge) {
if (backTrack.getFirst())
currentPath.add(0, toReach);
toReach = backTrack.getSecond();
backTrack = reachedVia.get(backTrack.getSecond());
}
double position = edge.getLength(node1, node2) - destination.position;
double distanceToDestination = distance - position;
if (forward)
frontResult.setValue(Pair.of(distanceToDestination, currentPath));
else
backResult.setValue(Pair.of(-distanceToDestination, currentPath));
return true;
});
if (!train.doubleEnded)
break;
}
Pair<Double, List<TrackEdge>> front = frontResult.getValue();
Pair<Double, List<TrackEdge>> back = backResult.getValue();
boolean frontEmpty = front.getFirst() == null;
boolean backEmpty = back.getFirst() == null;
if (backEmpty)
return front;
if (frontEmpty)
return back;
boolean canDriveForward = train.hasForwardConductor() || train.runtime.paused;
boolean canDriveBackward = train.hasBackwardConductor() || train.runtime.paused;
if (!canDriveBackward)
return front;
if (!canDriveForward)
return back;
boolean frontBetter = -back.getFirst() > front.getFirst();
return frontBetter ? front : back;
}
public GlobalStation findNearestApproachable() {
public GlobalStation findNearestApproachable(boolean forward) {
TrackGraph graph = train.graph;
if (graph == null)
return null;
@ -180,7 +268,7 @@ public class Navigation {
double minDistance = .75f * (train.speed * train.speed) / (2 * Train.acceleration);
double maxDistance = Math.max(32, 1.5f * (train.speed * train.speed) / (2 * Train.acceleration));
search((reachedVia, poll) -> {
search(maxDistance, forward, (reachedVia, poll) -> {
double distance = poll.getFirst();
if (distance < minDistance)
return false;
@ -206,29 +294,34 @@ public class Navigation {
}
return false;
}, maxDistance);
});
return result.getValue();
}
public void search(
BiPredicate<Map<TrackEdge, Pair<Boolean, TrackEdge>>, Pair<Double, Pair<Couple<TrackNode>, TrackEdge>>> condition,
double maxDistance) {
public void search(double maxDistance, boolean forward,
BiPredicate<Map<TrackEdge, Pair<Boolean, TrackEdge>>, Pair<Double, Pair<Couple<TrackNode>, TrackEdge>>> condition) {
TrackGraph graph = train.graph;
if (graph == null)
return;
TravellingPoint leadingPoint = train.carriages.get(0)
.getLeadingPoint();
TravellingPoint startingPoint = forward ? train.carriages.get(0)
.getLeadingPoint()
: train.carriages.get(train.carriages.size() - 1)
.getTrailingPoint();
Set<TrackEdge> visited = new HashSet<>();
Map<TrackEdge, Pair<Boolean, TrackEdge>> reachedVia = new IdentityHashMap<>();
PriorityQueue<Pair<Double, Pair<Couple<TrackNode>, TrackEdge>>> frontier =
new PriorityQueue<>((p1, p2) -> Double.compare(p1.getFirst(), p2.getFirst()));
TrackEdge initialEdge = leadingPoint.edge;
TrackNode initialNode1 = leadingPoint.node1;
TrackNode initialNode2 = leadingPoint.node2;
double distanceToNode2 = initialEdge.getLength(initialNode1, initialNode2) - leadingPoint.position;
TrackNode initialNode1 = forward ? startingPoint.node1 : startingPoint.node2;
TrackNode initialNode2 = forward ? startingPoint.node2 : startingPoint.node1;
TrackEdge initialEdge = graph.getConnectionsFrom(initialNode1)
.get(initialNode2);
double distanceToNode2 = forward ? initialEdge.getLength(initialNode1, initialNode2) - startingPoint.position
: startingPoint.position;
frontier.add(Pair.of(distanceToNode2, Pair.of(Couple.create(initialNode1, initialNode2), initialEdge)));
while (!frontier.isEmpty()) {

View file

@ -61,6 +61,7 @@ public class Train {
public boolean manualTick;
public UUID currentStation;
public boolean currentlyBackwards;
public boolean heldForAssembly;
public boolean doubleEnded;
@ -108,22 +109,13 @@ public class Train {
return;
}
updateConductors();
runtime.tick(level);
navigation.tick(level);
if (!manualTick && navigation.destination == null && speed != 0) {
if (speed > 0)
speed = Math.max(speed - acceleration, 0);
else
speed = Math.min(speed + acceleration, 0);
}
manualTick = false;
if (derailed) {
speed /= 3f;
if (Mth.equal(speed, 0))
speed = 0;
}
tickPassiveSlowdown();
if (derailed)
tickDerailedSlowdown();
double distance = speed;
Carriage previousCarriage = null;
@ -144,18 +136,24 @@ public class Train {
// negative stress: carriages should move closer
boolean approachingStation = navigation.distanceToDestination < 5;
double leadingModifier = approachingStation ? -0.75d : -0.5d;
double leadingModifier = approachingStation ? 0.75d : 0.5d;
double trailingModifier = approachingStation ? 0d : 0.125d;
TravellingPoint previous = null;
boolean blocked = false;
boolean iterateFromBack = speed < 0;
for (int index = 0; index < carriages.size(); index++) {
int i = iterateFromBack ? carriages.size() - 1 - index : index;
double leadingStress = i == 0 ? 0 : stress[i - 1] * -(iterateFromBack ? trailingModifier : leadingModifier);
double trailingStress =
i == stress.length ? 0 : stress[i] * (iterateFromBack ? leadingModifier : trailingModifier);
for (int i = 0; i < carriages.size(); i++) {
double leadingStress = i == 0 ? 0 : stress[i - 1] * leadingModifier;
double trailingStress = i == stress.length ? 0 : stress[i] * trailingModifier;
Carriage carriage = carriages.get(i);
TravellingPoint toFollowForward = previous;
TravellingPoint toFollowForward = i == 0 ? null
: carriages.get(i - 1)
.getTrailingPoint();
TravellingPoint toFollowBackward = i == carriages.size() - 1 ? null
: carriages.get(i + 1)
.getLeadingPoint();
@ -165,15 +163,15 @@ public class Train {
Function<TravellingPoint, ITrackSelector> backwardControl =
toFollowBackward == null ? navigation::control : mp -> mp.follow(toFollowBackward);
double actualDistance = carriage.travel(level, graph, distance + leadingStress + trailingStress,
forwardControl, backwardControl);
double totalStress = leadingStress + trailingStress;
double actualDistance =
carriage.travel(level, graph, distance + totalStress, forwardControl, backwardControl);
blocked |= carriage.blocked;
if (i == 0) {
if (index == 0) {
distance = actualDistance;
collideWithOtherTrains(level, carriage);
}
previous = carriage.getTrailingPoint();
}
if (blocked) {
@ -184,24 +182,63 @@ public class Train {
} else if (speed != 0)
status.trackOK();
updateNavigationTarget(distance);
}
private void updateNavigationTarget(double distance) {
if (navigation.destination != null) {
boolean recalculate = navigation.distanceToDestination % 100 > 20;
navigation.distanceToDestination -= distance;
if (recalculate && navigation.distanceToDestination % 100 <= 20)
boolean imminentRecalculate = navigation.distanceToDestination > 5;
navigation.distanceToDestination -= Math.abs(distance);
if (recalculate && navigation.distanceToDestination % 100 <= 20
|| imminentRecalculate && navigation.distanceToDestination <= 5)
navigation.startNavigation(navigation.destination, false);
}
}
private void tickDerailedSlowdown() {
speed /= 3f;
if (Mth.equal(speed, 0))
speed = 0;
}
private void tickPassiveSlowdown() {
if (!manualTick && navigation.destination == null && speed != 0) {
if (speed > 0)
speed = Math.max(speed - acceleration, 0);
else
speed = Math.min(speed + acceleration, 0);
}
manualTick = false;
}
private void updateConductors() {
for (Carriage carriage : carriages)
carriage.updateConductors();
}
public boolean hasForwardConductor() {
for (Carriage carriage : carriages)
if (carriage.hasForwardConductor)
return true;
return false;
}
public boolean hasBackwardConductor() {
for (Carriage carriage : carriages)
if (carriage.hasBackwardConductor)
return true;
return false;
}
private void collideWithOtherTrains(Level level, Carriage carriage) {
if (derailed)
return;
Collision: for (Train train : Create.RAILWAYS.trains.values()) {
if (train == this)
continue;
Vec3 start = carriage.getLeadingPoint()
.getPosition();
Vec3 end = carriage.getTrailingPoint()
.getPosition();
Vec3 start = (speed < 0 ? carriage.getTrailingPoint() : carriage.getLeadingPoint()).getPosition();
Vec3 end = (speed < 0 ? carriage.getLeadingPoint() : carriage.getTrailingPoint()).getPosition();
Vec3 diff = end.subtract(start);
Vec3 lastPoint = null;
@ -249,7 +286,7 @@ public class Train {
if (intersect[1] < 0)
continue;
double combinedSpeed = speed + train.speed;
double combinedSpeed = Math.abs(speed) + Math.abs(train.speed);
if (combinedSpeed > .2f) {
Vec3 v = start.add(normedDiff.scale(intersect[0]));
level.explode(null, v.x, v.y, v.z, (float) Math.min(3 * combinedSpeed, 5),
@ -284,21 +321,24 @@ public class Train {
}
int offset = 1;
boolean backwards = currentlyBackwards;
for (int i = 0; i < carriages.size(); i++) {
Carriage carriage = carriages.get(i);
Carriage carriage = carriages.get(backwards ? carriages.size() - i - 1 : i);
CarriageContraptionEntity entity = carriage.entity.get();
if (entity == null)
return false;
entity.setPos(Vec3.atLowerCornerOf(pos.relative(assemblyDirection, offset)));
entity.setPos(Vec3
.atLowerCornerOf(pos.relative(assemblyDirection, backwards ? offset + carriage.bogeySpacing : offset)));
entity.disassemble();
Create.RAILWAYS.carriageById.remove(carriage.id);
CreateClient.RAILWAYS.carriageById.remove(carriage.id);
offset += carriage.bogeySpacing;
if (i < carriageSpacing.size())
offset += carriageSpacing.get(i);
offset += carriageSpacing.get(carriageSpacing.size() - i - 1);
}
GlobalStation currentStation = getCurrentStation();
@ -385,7 +425,13 @@ public class Train {
public int getTotalLength() {
int length = 0;
for (int i = 0; i < carriages.size(); i++) {
length += carriages.get(i).bogeySpacing;
Carriage carriage = carriages.get(i);
if (i == 0)
length += carriage.leadingBogey().type.getWheelPointSpacing() / 2;
if (i == carriages.size() - 1)
length += carriage.trailingBogey().type.getWheelPointSpacing() / 2;
length += carriage.bogeySpacing;
if (i < carriageSpacing.size())
length += carriageSpacing.get(i);
}

View file

@ -16,6 +16,7 @@ public class TrainStatus {
boolean navigation;
boolean track;
boolean conductor;
List<Component> queued = new ArrayList<>();
@ -37,6 +38,27 @@ public class TrainStatus {
navigation = false;
}
public void foundConductor() {
if (!conductor)
return;
displayInformation("A new driver has been found", true);
conductor = false;
}
public void missingConductor() {
if (conductor)
return;
displayInformation("Driver has gone missing", false);
conductor = true;
}
public void missingBackwardsConductor() { // missingCorrectConductor
if (conductor)
return;
displayInformation("Path requires driver on the other controls block", false);
conductor = true;
}
public void manualControls() {
displayInformation("Schedule paused for manual controls", true);
}
@ -90,4 +112,9 @@ public class TrainStatus {
.append(new TextComponent(key).withStyle(st -> st.withColor(itsAGoodThing ? 0xD5ECC2 : 0xFFD3B4))));
}
public void newSchedule() {
navigation = false;
conductor = false;
}
}

View file

@ -107,7 +107,6 @@ public class TravellingPoint {
public ITrackSelector steer(SteerDirection direction, Vec3 upNormal) {
return (graph, pair) -> {
List<Entry<TrackNode, TrackEdge>> validTargets = pair.getSecond();
boolean forward = pair.getFirst();
double closest = Double.MAX_VALUE;
Entry<TrackNode, TrackEdge> best = null;
@ -116,7 +115,7 @@ public class TravellingPoint {
Vec3 entryTrajectory = entry.getValue()
.getDirection(node2, entry.getKey(), true);
Vec3 normal = trajectory.cross(upNormal);
double dot = normal.dot(entryTrajectory) * (forward ? 1 : -1);
double dot = normal.dot(entryTrajectory);
double diff = Math.abs(direction.targetDot - dot);
if (diff > closest)
continue;

View file

@ -1,13 +1,17 @@
package com.simibubi.create.content.logistics.trains.management;
import com.simibubi.create.AllContainerTypes;
import com.simibubi.create.AllSoundEvents;
import com.simibubi.create.content.contraptions.components.structureMovement.Contraption;
import com.simibubi.create.content.logistics.trains.entity.CarriageContraption;
import com.simibubi.create.content.logistics.trains.entity.CarriageContraptionEntity;
import com.simibubi.create.content.logistics.trains.entity.Train;
import com.simibubi.create.content.logistics.trains.management.schedule.Schedule;
import com.simibubi.create.foundation.utility.Couple;
import com.simibubi.create.foundation.utility.Lang;
import net.minecraft.ChatFormatting;
import net.minecraft.core.BlockPos;
import net.minecraft.core.NonNullList;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.TranslatableComponent;
@ -60,14 +64,9 @@ public class ScheduleItem extends Item implements MenuProvider {
InteractionHand pUsedHand) {
InteractionResult pass = InteractionResult.PASS;
if (!pStack.hasTag())
Schedule schedule = getSchedule(pStack);
if (schedule == null)
return pass;
if (!pStack.getTag()
.contains("Schedule"))
return pass;
Schedule schedule = Schedule.fromTag(pStack.getTagElement("Schedule"));
if (pInteractionTarget == null)
return pass;
Entity rootVehicle = pInteractionTarget.getRootVehicle();
@ -75,21 +74,51 @@ public class ScheduleItem extends Item implements MenuProvider {
return pass;
if (pPlayer.level.isClientSide)
return InteractionResult.SUCCESS;
CarriageContraptionEntity entity = (CarriageContraptionEntity) rootVehicle;
Contraption contraption = entity.getContraption();
if (contraption instanceof CarriageContraption cc) {
Train train = cc.getCarriage().train;
if (train == null)
return InteractionResult.SUCCESS;
if (train.heldForAssembly) {
pPlayer.displayClientMessage(Lang.translate("schedule.train_still_assembling"), true);
AllSoundEvents.DENY.playOnServer(pPlayer.level, pPlayer.blockPosition(), 1, 1);
return InteractionResult.SUCCESS;
}
Integer seatIndex = contraption.getSeatMapping()
.get(pInteractionTarget.getUUID());
if (seatIndex == null)
return InteractionResult.SUCCESS;
BlockPos seatPos = contraption.getSeats()
.get(seatIndex);
Couple<Boolean> directions = cc.conductorSeats.get(seatPos);
if (directions == null) {
pPlayer.displayClientMessage(Lang.translate("schedule.non_controlling_seat"), true);
AllSoundEvents.DENY.playOnServer(pPlayer.level, pPlayer.blockPosition(), 1, 1);
return InteractionResult.SUCCESS;
}
train.runtime.setSchedule(schedule, false);
AllSoundEvents.CONFIRM.playOnServer(pPlayer.level, pPlayer.blockPosition(), 1, 1);
pPlayer.displayClientMessage(Lang.translate("schedule.applied_to_train")
.withStyle(ChatFormatting.GREEN), true);
}
return InteractionResult.SUCCESS;
}
public static Schedule getSchedule(ItemStack pStack) {
if (!pStack.hasTag())
return null;
if (!pStack.getTag()
.contains("Schedule"))
return null;
return Schedule.fromTag(pStack.getTagElement("Schedule"));
}
@Override
public AbstractContainerMenu createMenu(int id, Inventory inv, Player player) {
ItemStack heldItem = player.getMainHandItem();

View file

@ -152,6 +152,7 @@ public class ScheduleRuntime {
currentEntry = 0;
paused = false;
isAutoSchedule = auto;
train.status.newSchedule();
}
public Schedule getSchedule() {

View file

@ -218,7 +218,7 @@ public class StationScreen extends AbstractStationScreen {
offset += icon.render(TrainIconType.FLIPPED_ENGINE, ms, x + offset, y + 20) + 1;
continue;
}
Carriage carriage = carriages.get(i);
Carriage carriage = carriages.get(train.currentlyBackwards ? carriages.size() - i - 1 : i);
offset += icon.render(carriage.bogeySpacing, ms, x + offset, y + 20) + 1;
}

View file

@ -605,6 +605,8 @@
"create.schedule.loop1": "Schedule starts over",
"create.schedule.loop2": "when completed",
"create.schedule.train_still_assembling": "Confirm Train Assembly in the Station UI first",
"create.schedule.applied_to_train": "Train is now following this Schedule",
"create.schedule.non_controlling_seat": "Conductor needs to sit in front of a Controls block",
"create.track.selection_cleared": "Selection Cleared",
"create.track.valid_connection": "Can Connect \u2714",