Train crash, game crash

- Added a new signal mode for easier junction & platform management
- Fixed crash during trains' tick after a collision
- Fixed Trains trying to avoid their own carriages in navigation
- Fixed Trains trying to avoid their own station in navigation
- Fixed Trains triggering signals behind their targeted station
- Fixed Trains reversing mid-travel when path cost behind them is lower
- Wait time at signals now affects the tick order of Trains
- Trains no longer change destination when they are close to arriving at their initial choice
- Fixed crashed trains no longer keeping signal blocks occupied
- Fixed crash when a trains' path update is unable to find the original destination
- Improved precision of trains arriving at signals/stations
This commit is contained in:
simibubi 2022-03-17 15:41:09 +01:00
parent 71e18eb505
commit a9a37b313c
48 changed files with 1041 additions and 254 deletions

View file

@ -484,7 +484,7 @@ e815bfd854c2653f10828bb11950f7fb991d7efc assets/create/blockstates/stressometer.
8b0c2c7ac72529565b3339aa8df7565858100afa assets/create/blockstates/tiled_glass.json
a2454400b1cf9889f70aebdc89c52a1be25f543c assets/create/blockstates/tiled_glass_pane.json
85b57776edf426c2f8df6698b2482ea925914a5c assets/create/blockstates/track.json
5a61450b6f6aac63f75580f8dcbc3d3fabfd54d8 assets/create/blockstates/track_signal.json
408ae1009ee8bb2f2b83753d5909c53744f7865f assets/create/blockstates/track_signal.json
aa08785f906d41933e0dd1086ea7b08f5b93aa24 assets/create/blockstates/track_station.json
29af21c8d82891139d48d69f0393f612f2b6f8f1 assets/create/blockstates/tuff_pillar.json
a8094531617e27a545c4815ab2062bf0ffca3633 assets/create/blockstates/turntable.json
@ -541,21 +541,21 @@ bf2b0310500213ff853c748c236eb5d01f61658e assets/create/blockstates/yellow_toolbo
7f39521b211441f5c3e06d60c5978cebe16cacfb assets/create/blockstates/zinc_block.json
b7181bcd8182b2f17088e5aa881f374c9c65470c assets/create/blockstates/zinc_ore.json
255e47ab7894ba35851a2f34c82be3dc9c9e8784 assets/create/lang/en_ud.json
b908dd7c47b286d05a57b8a58c60051363ffff86 assets/create/lang/en_us.json
29ad91d27ee183b9d4de8d152d0fb8ed2a388a0c assets/create/lang/unfinished/de_de.json
8d33ba82d3cca307c5ae0dda41a49ec16f1c7b74 assets/create/lang/unfinished/es_cl.json
ae674553b564a9e09a7d10c9c2d8c1be1307f40a assets/create/lang/unfinished/es_es.json
318149855f383fb90439f2f0818e8e83fee3924e assets/create/lang/unfinished/fr_fr.json
e7321dbe8f98c96e778b5c46823c98500b3001bc assets/create/lang/unfinished/it_it.json
5a2da425fd0dd276c3d7cf66f6bb43f229574306 assets/create/lang/unfinished/ja_jp.json
98fc1956e8b25aec27e889ada3a160800b958dfd assets/create/lang/unfinished/ko_kr.json
67cefb6690f5cccce93d776a760ebdadc7f2631e assets/create/lang/unfinished/nl_nl.json
9a7f4d369d2a54aaf5c422fae740244025a13af4 assets/create/lang/unfinished/pl_pl.json
3656897ffc5c89bf16a32ad11efe33cd5b4bfb23 assets/create/lang/unfinished/pt_br.json
c84995ec524b6469fc42f39574be21b9dbee24e3 assets/create/lang/unfinished/pt_pt.json
bcf41d9fd8454296dc56326b6e26ee41e2ad52e5 assets/create/lang/unfinished/ru_ru.json
d3dfdab017748fa82f9e952a35bd6e82fe063771 assets/create/lang/unfinished/zh_cn.json
dda3aee2ece5503edd53dbd4aa9db7363e4beafc assets/create/lang/unfinished/zh_tw.json
e6698e4672c04cf7b8251e2267b1fe3d6c60e50c assets/create/lang/en_us.json
dfd5234276f508edfb10160fa2b7859363acc313 assets/create/lang/unfinished/de_de.json
c23ca905e7aa9983057c970d9e799b4ba91a9acd assets/create/lang/unfinished/es_cl.json
1781ed3854e04116319528699b4edee9cff4a435 assets/create/lang/unfinished/es_es.json
24d421795a67a19efd3a092b693c2397b3b84d9b assets/create/lang/unfinished/fr_fr.json
50107fc09f422527f20b0a2ddee7e961540e4f6c assets/create/lang/unfinished/it_it.json
570cc561fcb7f1839212e7ba3e1a68886874c846 assets/create/lang/unfinished/ja_jp.json
f5ddd8b98314636c4c763cdd8aee7acd462b00a9 assets/create/lang/unfinished/ko_kr.json
f3696cf3bfcdff92eb11882be9be9fa2759c51c6 assets/create/lang/unfinished/nl_nl.json
3df68a7b3fb44dd666de541d7437c300d596aa7b assets/create/lang/unfinished/pl_pl.json
6a7fb1146f15117abca1a0ea970ab4054a1ee6a7 assets/create/lang/unfinished/pt_br.json
6e993c9e6f5faa55448a8b24456082096cb10935 assets/create/lang/unfinished/pt_pt.json
d8fc231286d4a53e249037905c92513857432c4c assets/create/lang/unfinished/ru_ru.json
3aedca5bf7cf2f08cb53d6e8b5949009cddc1843 assets/create/lang/unfinished/zh_cn.json
30f01dd054c4706d37f6d5ece0fc09ad241a6214 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

@ -1,7 +1,10 @@
{
"variants": {
"": {
"model": "create:block/track_signal/block"
"type=entry_signal": {
"model": "create:block/track_signal/block_entry_signal"
},
"type=cross_signal": {
"model": "create:block/track_signal/block_cross_signal"
}
}
}

View file

@ -1424,6 +1424,10 @@
"create.train.relocate.invalid": "Cannot relocate Train to here",
"create.train.relocate.too_far": "Cannot relocate Train this far away",
"create.track_signal.cannot_change_mode": "Unable to switch mode of this Signal",
"create.track_signal.mode_change.entry_signal": "-> Allow passage if section unoccupied",
"create.track_signal.mode_change.cross_signal": "-> Allow passage if section fully traversable",
"create.contraption.controls.start_controlling": "Now controlling: %1$s",
"create.contraption.controls.stop_controlling": "Stopped controlling contraption",
"create.contraption.controls.approach_station": "Hold %1$s to approach %2$s",

View file

@ -1,5 +1,5 @@
{
"_": "Missing Localizations: 1435",
"_": "Missing Localizations: 1438",
"_": "->------------------------] Game Elements [------------------------<-",
@ -1425,6 +1425,10 @@
"create.train.relocate.invalid": "UNLOCALIZED: Cannot relocate Train to here",
"create.train.relocate.too_far": "UNLOCALIZED: Cannot relocate Train this far away",
"create.track_signal.cannot_change_mode": "UNLOCALIZED: Unable to switch mode of this Signal",
"create.track_signal.mode_change.entry_signal": "UNLOCALIZED: -> Allow passage if section unoccupied",
"create.track_signal.mode_change.cross_signal": "UNLOCALIZED: -> Allow passage if section fully traversable",
"create.contraption.controls.start_controlling": "UNLOCALIZED: Now controlling: %1$s",
"create.contraption.controls.stop_controlling": "UNLOCALIZED: Stopped controlling contraption",
"create.contraption.controls.approach_station": "UNLOCALIZED: Hold %1$s to approach %2$s",

View file

@ -1,5 +1,5 @@
{
"_": "Missing Localizations: 446",
"_": "Missing Localizations: 449",
"_": "->------------------------] Game Elements [------------------------<-",
@ -1425,6 +1425,10 @@
"create.train.relocate.invalid": "UNLOCALIZED: Cannot relocate Train to here",
"create.train.relocate.too_far": "UNLOCALIZED: Cannot relocate Train this far away",
"create.track_signal.cannot_change_mode": "UNLOCALIZED: Unable to switch mode of this Signal",
"create.track_signal.mode_change.entry_signal": "UNLOCALIZED: -> Allow passage if section unoccupied",
"create.track_signal.mode_change.cross_signal": "UNLOCALIZED: -> Allow passage if section fully traversable",
"create.contraption.controls.start_controlling": "UNLOCALIZED: Now controlling: %1$s",
"create.contraption.controls.stop_controlling": "UNLOCALIZED: Stopped controlling contraption",
"create.contraption.controls.approach_station": "UNLOCALIZED: Hold %1$s to approach %2$s",

View file

@ -1,5 +1,5 @@
{
"_": "Missing Localizations: 446",
"_": "Missing Localizations: 449",
"_": "->------------------------] Game Elements [------------------------<-",
@ -1425,6 +1425,10 @@
"create.train.relocate.invalid": "UNLOCALIZED: Cannot relocate Train to here",
"create.train.relocate.too_far": "UNLOCALIZED: Cannot relocate Train this far away",
"create.track_signal.cannot_change_mode": "UNLOCALIZED: Unable to switch mode of this Signal",
"create.track_signal.mode_change.entry_signal": "UNLOCALIZED: -> Allow passage if section unoccupied",
"create.track_signal.mode_change.cross_signal": "UNLOCALIZED: -> Allow passage if section fully traversable",
"create.contraption.controls.start_controlling": "UNLOCALIZED: Now controlling: %1$s",
"create.contraption.controls.stop_controlling": "UNLOCALIZED: Stopped controlling contraption",
"create.contraption.controls.approach_station": "UNLOCALIZED: Hold %1$s to approach %2$s",

View file

@ -1,5 +1,5 @@
{
"_": "Missing Localizations: 1697",
"_": "Missing Localizations: 1700",
"_": "->------------------------] Game Elements [------------------------<-",
@ -1425,6 +1425,10 @@
"create.train.relocate.invalid": "UNLOCALIZED: Cannot relocate Train to here",
"create.train.relocate.too_far": "UNLOCALIZED: Cannot relocate Train this far away",
"create.track_signal.cannot_change_mode": "UNLOCALIZED: Unable to switch mode of this Signal",
"create.track_signal.mode_change.entry_signal": "UNLOCALIZED: -> Allow passage if section unoccupied",
"create.track_signal.mode_change.cross_signal": "UNLOCALIZED: -> Allow passage if section fully traversable",
"create.contraption.controls.start_controlling": "UNLOCALIZED: Now controlling: %1$s",
"create.contraption.controls.stop_controlling": "UNLOCALIZED: Stopped controlling contraption",
"create.contraption.controls.approach_station": "UNLOCALIZED: Hold %1$s to approach %2$s",

View file

@ -1,5 +1,5 @@
{
"_": "Missing Localizations: 1386",
"_": "Missing Localizations: 1389",
"_": "->------------------------] Game Elements [------------------------<-",
@ -1425,6 +1425,10 @@
"create.train.relocate.invalid": "UNLOCALIZED: Cannot relocate Train to here",
"create.train.relocate.too_far": "UNLOCALIZED: Cannot relocate Train this far away",
"create.track_signal.cannot_change_mode": "UNLOCALIZED: Unable to switch mode of this Signal",
"create.track_signal.mode_change.entry_signal": "UNLOCALIZED: -> Allow passage if section unoccupied",
"create.track_signal.mode_change.cross_signal": "UNLOCALIZED: -> Allow passage if section fully traversable",
"create.contraption.controls.start_controlling": "UNLOCALIZED: Now controlling: %1$s",
"create.contraption.controls.stop_controlling": "UNLOCALIZED: Stopped controlling contraption",
"create.contraption.controls.approach_station": "UNLOCALIZED: Hold %1$s to approach %2$s",

View file

@ -1,5 +1,5 @@
{
"_": "Missing Localizations: 116",
"_": "Missing Localizations: 119",
"_": "->------------------------] Game Elements [------------------------<-",
@ -1425,6 +1425,10 @@
"create.train.relocate.invalid": "UNLOCALIZED: Cannot relocate Train to here",
"create.train.relocate.too_far": "UNLOCALIZED: Cannot relocate Train this far away",
"create.track_signal.cannot_change_mode": "UNLOCALIZED: Unable to switch mode of this Signal",
"create.track_signal.mode_change.entry_signal": "UNLOCALIZED: -> Allow passage if section unoccupied",
"create.track_signal.mode_change.cross_signal": "UNLOCALIZED: -> Allow passage if section fully traversable",
"create.contraption.controls.start_controlling": "UNLOCALIZED: Now controlling: %1$s",
"create.contraption.controls.stop_controlling": "UNLOCALIZED: Stopped controlling contraption",
"create.contraption.controls.approach_station": "UNLOCALIZED: Hold %1$s to approach %2$s",

View file

@ -1,5 +1,5 @@
{
"_": "Missing Localizations: 118",
"_": "Missing Localizations: 121",
"_": "->------------------------] Game Elements [------------------------<-",
@ -1425,6 +1425,10 @@
"create.train.relocate.invalid": "UNLOCALIZED: Cannot relocate Train to here",
"create.train.relocate.too_far": "UNLOCALIZED: Cannot relocate Train this far away",
"create.track_signal.cannot_change_mode": "UNLOCALIZED: Unable to switch mode of this Signal",
"create.track_signal.mode_change.entry_signal": "UNLOCALIZED: -> Allow passage if section unoccupied",
"create.track_signal.mode_change.cross_signal": "UNLOCALIZED: -> Allow passage if section fully traversable",
"create.contraption.controls.start_controlling": "UNLOCALIZED: Now controlling: %1$s",
"create.contraption.controls.stop_controlling": "UNLOCALIZED: Stopped controlling contraption",
"create.contraption.controls.approach_station": "UNLOCALIZED: Hold %1$s to approach %2$s",

View file

@ -1,5 +1,5 @@
{
"_": "Missing Localizations: 2050",
"_": "Missing Localizations: 2053",
"_": "->------------------------] Game Elements [------------------------<-",
@ -1425,6 +1425,10 @@
"create.train.relocate.invalid": "UNLOCALIZED: Cannot relocate Train to here",
"create.train.relocate.too_far": "UNLOCALIZED: Cannot relocate Train this far away",
"create.track_signal.cannot_change_mode": "UNLOCALIZED: Unable to switch mode of this Signal",
"create.track_signal.mode_change.entry_signal": "UNLOCALIZED: -> Allow passage if section unoccupied",
"create.track_signal.mode_change.cross_signal": "UNLOCALIZED: -> Allow passage if section fully traversable",
"create.contraption.controls.start_controlling": "UNLOCALIZED: Now controlling: %1$s",
"create.contraption.controls.stop_controlling": "UNLOCALIZED: Stopped controlling contraption",
"create.contraption.controls.approach_station": "UNLOCALIZED: Hold %1$s to approach %2$s",

View file

@ -1,5 +1,5 @@
{
"_": "Missing Localizations: 485",
"_": "Missing Localizations: 488",
"_": "->------------------------] Game Elements [------------------------<-",
@ -1425,6 +1425,10 @@
"create.train.relocate.invalid": "UNLOCALIZED: Cannot relocate Train to here",
"create.train.relocate.too_far": "UNLOCALIZED: Cannot relocate Train this far away",
"create.track_signal.cannot_change_mode": "UNLOCALIZED: Unable to switch mode of this Signal",
"create.track_signal.mode_change.entry_signal": "UNLOCALIZED: -> Allow passage if section unoccupied",
"create.track_signal.mode_change.cross_signal": "UNLOCALIZED: -> Allow passage if section fully traversable",
"create.contraption.controls.start_controlling": "UNLOCALIZED: Now controlling: %1$s",
"create.contraption.controls.stop_controlling": "UNLOCALIZED: Stopped controlling contraption",
"create.contraption.controls.approach_station": "UNLOCALIZED: Hold %1$s to approach %2$s",

View file

@ -1,5 +1,5 @@
{
"_": "Missing Localizations: 1669",
"_": "Missing Localizations: 1672",
"_": "->------------------------] Game Elements [------------------------<-",
@ -1425,6 +1425,10 @@
"create.train.relocate.invalid": "UNLOCALIZED: Cannot relocate Train to here",
"create.train.relocate.too_far": "UNLOCALIZED: Cannot relocate Train this far away",
"create.track_signal.cannot_change_mode": "UNLOCALIZED: Unable to switch mode of this Signal",
"create.track_signal.mode_change.entry_signal": "UNLOCALIZED: -> Allow passage if section unoccupied",
"create.track_signal.mode_change.cross_signal": "UNLOCALIZED: -> Allow passage if section fully traversable",
"create.contraption.controls.start_controlling": "UNLOCALIZED: Now controlling: %1$s",
"create.contraption.controls.stop_controlling": "UNLOCALIZED: Stopped controlling contraption",
"create.contraption.controls.approach_station": "UNLOCALIZED: Hold %1$s to approach %2$s",

View file

@ -1,5 +1,5 @@
{
"_": "Missing Localizations: 1669",
"_": "Missing Localizations: 1672",
"_": "->------------------------] Game Elements [------------------------<-",
@ -1425,6 +1425,10 @@
"create.train.relocate.invalid": "UNLOCALIZED: Cannot relocate Train to here",
"create.train.relocate.too_far": "UNLOCALIZED: Cannot relocate Train this far away",
"create.track_signal.cannot_change_mode": "UNLOCALIZED: Unable to switch mode of this Signal",
"create.track_signal.mode_change.entry_signal": "UNLOCALIZED: -> Allow passage if section unoccupied",
"create.track_signal.mode_change.cross_signal": "UNLOCALIZED: -> Allow passage if section fully traversable",
"create.contraption.controls.start_controlling": "UNLOCALIZED: Now controlling: %1$s",
"create.contraption.controls.stop_controlling": "UNLOCALIZED: Stopped controlling contraption",
"create.contraption.controls.approach_station": "UNLOCALIZED: Hold %1$s to approach %2$s",

View file

@ -1,5 +1,5 @@
{
"_": "Missing Localizations: 490",
"_": "Missing Localizations: 493",
"_": "->------------------------] Game Elements [------------------------<-",
@ -1425,6 +1425,10 @@
"create.train.relocate.invalid": "UNLOCALIZED: Cannot relocate Train to here",
"create.train.relocate.too_far": "UNLOCALIZED: Cannot relocate Train this far away",
"create.track_signal.cannot_change_mode": "UNLOCALIZED: Unable to switch mode of this Signal",
"create.track_signal.mode_change.entry_signal": "UNLOCALIZED: -> Allow passage if section unoccupied",
"create.track_signal.mode_change.cross_signal": "UNLOCALIZED: -> Allow passage if section fully traversable",
"create.contraption.controls.start_controlling": "UNLOCALIZED: Now controlling: %1$s",
"create.contraption.controls.stop_controlling": "UNLOCALIZED: Stopped controlling contraption",
"create.contraption.controls.approach_station": "UNLOCALIZED: Hold %1$s to approach %2$s",

View file

@ -1,5 +1,5 @@
{
"_": "Missing Localizations: 116",
"_": "Missing Localizations: 119",
"_": "->------------------------] Game Elements [------------------------<-",
@ -1425,6 +1425,10 @@
"create.train.relocate.invalid": "UNLOCALIZED: Cannot relocate Train to here",
"create.train.relocate.too_far": "UNLOCALIZED: Cannot relocate Train this far away",
"create.track_signal.cannot_change_mode": "UNLOCALIZED: Unable to switch mode of this Signal",
"create.track_signal.mode_change.entry_signal": "UNLOCALIZED: -> Allow passage if section unoccupied",
"create.track_signal.mode_change.cross_signal": "UNLOCALIZED: -> Allow passage if section fully traversable",
"create.contraption.controls.start_controlling": "UNLOCALIZED: Now controlling: %1$s",
"create.contraption.controls.stop_controlling": "UNLOCALIZED: Stopped controlling contraption",
"create.contraption.controls.approach_station": "UNLOCALIZED: Hold %1$s to approach %2$s",

View file

@ -1,5 +1,5 @@
{
"_": "Missing Localizations: 504",
"_": "Missing Localizations: 507",
"_": "->------------------------] Game Elements [------------------------<-",
@ -1425,6 +1425,10 @@
"create.train.relocate.invalid": "UNLOCALIZED: Cannot relocate Train to here",
"create.train.relocate.too_far": "UNLOCALIZED: Cannot relocate Train this far away",
"create.track_signal.cannot_change_mode": "UNLOCALIZED: Unable to switch mode of this Signal",
"create.track_signal.mode_change.entry_signal": "UNLOCALIZED: -> Allow passage if section unoccupied",
"create.track_signal.mode_change.cross_signal": "UNLOCALIZED: -> Allow passage if section fully traversable",
"create.contraption.controls.start_controlling": "UNLOCALIZED: Now controlling: %1$s",
"create.contraption.controls.stop_controlling": "UNLOCALIZED: Stopped controlling contraption",
"create.contraption.controls.approach_station": "UNLOCALIZED: Hold %1$s to approach %2$s",

View file

@ -151,6 +151,9 @@ public class AllBlockPartials {
SIGNAL_RED_CUBE = block("track_signal/red_cube"),
SIGNAL_RED_GLOW = block("track_signal/red_glow"),
SIGNAL_RED = block("track_signal/red_tube"),
SIGNAL_YELLOW_CUBE = block("track_signal/yellow_cube"),
SIGNAL_YELLOW_GLOW = block("track_signal/yellow_glow"),
SIGNAL_YELLOW = block("track_signal/yellow_tube"),
CRAFTING_BLUEPRINT_1x1 = entity("crafting_blueprint_small"),
CRAFTING_BLUEPRINT_2x2 = entity("crafting_blueprint_medium"),

View file

@ -1336,7 +1336,11 @@ public class AllBlocks {
.initialProperties(SharedProperties::softMetal)
.properties(p -> p.sound(SoundType.NETHERITE_BLOCK))
.transform(pickaxeOnly())
.blockstate((c, p) -> p.simpleBlock(c.get(), AssetLookup.partialBaseModel(c, p)))
.blockstate((c, p) -> p.getVariantBuilder(c.get())
.forAllStates(state -> ConfiguredModel.builder()
.modelFile(AssetLookup.partialBaseModel(c, p, state.getValue(SignalBlock.TYPE)
.getSerializedName()))
.build()))
.lang("Train Signal")
.item(TrackTargetingBlockItem::new)
.transform(customItemModel())

View file

@ -177,10 +177,11 @@ public class NixieTubeRenderer extends SafeTileEntityRenderer<NixieTubeTileEntit
if (first && !te.signalState.isRedLight(renderTime))
continue;
if (!first && !te.signalState.isGreenLight(renderTime))
if (!first && !te.signalState.isGreenLight(renderTime) && !te.signalState.isYellowLight(renderTime))
continue;
boolean flip = first == invertTubes;
boolean yellow = te.signalState.isYellowLight(renderTime);
ms.pushPose();
ms.translate(flip ? 4 / 16f : -4 / 16f, 0, 0);
@ -188,22 +189,29 @@ public class NixieTubeRenderer extends SafeTileEntityRenderer<NixieTubeTileEntit
if (diff.lengthSqr() < 36 * 36) {
boolean vert = first ^ facing.getAxis()
.isHorizontal();
float longSide = yellow ? 1 : 4;
float longSideGlow = yellow ? 2 : 5.125f;
CachedBufferer.partial(AllBlockPartials.SIGNAL_WHITE_CUBE, blockState)
.light(0xf000f0)
.disableDiffuseMult()
.scale(vert ? 4 : 1, vert ? 1 : 4, 1)
.scale(vert ? longSide : 1, vert ? 1 : longSide, 1)
.renderInto(ms, buffer.getBuffer(RenderType.translucent()));
CachedBufferer
.partial(first ? AllBlockPartials.SIGNAL_RED_GLOW : AllBlockPartials.SIGNAL_WHITE_GLOW, blockState)
.partial(
first ? AllBlockPartials.SIGNAL_RED_GLOW
: yellow ? AllBlockPartials.SIGNAL_YELLOW_GLOW : AllBlockPartials.SIGNAL_WHITE_GLOW,
blockState)
.light(0xf000f0)
.disableDiffuseMult()
.scale(vert ? 5.125f : 2, vert ? 2 : 5.125f, 2)
.scale(vert ? longSideGlow : 2, vert ? 2 : longSideGlow, 2)
.renderInto(ms, buffer.getBuffer(RenderTypes.getAdditive()));
}
CachedBufferer.partial(first ? AllBlockPartials.SIGNAL_RED : AllBlockPartials.SIGNAL_WHITE, blockState)
CachedBufferer
.partial(first ? AllBlockPartials.SIGNAL_RED
: yellow ? AllBlockPartials.SIGNAL_YELLOW : AllBlockPartials.SIGNAL_WHITE, blockState)
.light(0xF000F0)
.disableDiffuseMult()
.scale(1 + 1 / 16f)

View file

@ -3,6 +3,8 @@ package com.simibubi.create.content.logistics.trains;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
@ -39,6 +41,9 @@ public class GlobalRailwayManager {
public Map<UUID, Train> trains;
public TrackGraphSync sync;
private List<Train> movingTrains;
private List<Train> waitingTrains;
private RailwaySavedData savedData;
public GlobalRailwayManager() {
@ -75,6 +80,8 @@ public class GlobalRailwayManager {
trains = savedData.getTrains();
trackNetworks = savedData.getTrackNetworks();
signalEdgeGroups = savedData.getSignalBlocks();
trains.values()
.forEach(movingTrains::add);
}
public void cleanUp() {
@ -82,6 +89,8 @@ public class GlobalRailwayManager {
signalEdgeGroups = new HashMap<>();
trains = new HashMap<>();
sync = new TrackGraphSync();
movingTrains = new LinkedList<>();
waitingTrains = new LinkedList<>();
}
public void markTracksDirty() {
@ -89,6 +98,19 @@ public class GlobalRailwayManager {
savedData.setDirty();
}
public void addTrain(Train train) {
trains.put(train.id, train);
movingTrains.add(train);
}
public void removeTrain(UUID id) {
Train removed = trains.remove(id);
if (removed == null)
return;
movingTrains.remove(removed);
waitingTrains.remove(removed);
}
//
public TrackGraph getOrCreateGraph(UUID graphID) {
@ -145,22 +167,51 @@ public class GlobalRailwayManager {
group.trains.clear();
group.reserved = null;
}
for (TrackGraph graph : trackNetworks.values())
graph.tickPoints();
for (Train train : trains.values())
train.earlyTick(level);
for (Train train : trains.values())
train.tick(level);
graph.tickPoints(true);
// if (AllKeys.isKeyDown(GLFW.GLFW_KEY_H) && AllKeys.altDown())
tickTrains(level);
for (TrackGraph graph : trackNetworks.values())
graph.tickPoints(false);
// if (AllKeys.isKeyDown(GLFW.GLFW_KEY_K))
// trackNetworks.values()
// .forEach(TrackGraph::debugViewSignalData);
// .forEach(TrackGraph::debugViewReserved);
// if (AllKeys.isKeyDown(GLFW.GLFW_KEY_J) && AllKeys.altDown())
// trackNetworks.values()
// .forEach(TrackGraph::debugViewNodes);
}
private void tickTrains(Level level) {
// keeping two lists ensures a tick order starting at longest waiting
for (Train train : waitingTrains)
train.earlyTick(level);
for (Train train : movingTrains)
train.earlyTick(level);
for (Train train : waitingTrains)
train.tick(level);
for (Train train : movingTrains)
train.tick(level);
for (Iterator<Train> iterator = waitingTrains.iterator(); iterator.hasNext();) {
Train train = iterator.next();
if (train.navigation.waitingForSignal != null)
continue;
movingTrains.add(train);
iterator.remove();
}
for (Iterator<Train> iterator = movingTrains.iterator(); iterator.hasNext();) {
Train train = iterator.next();
if (train.navigation.waitingForSignal == null)
continue;
waitingTrains.add(train);
iterator.remove();
}
}
public void clientTick() {
if (AllKeys.isKeyDown(GLFW.GLFW_KEY_H) && !AllKeys.altDown())
trackNetworks.values()

View file

@ -98,8 +98,8 @@ public class TrackGraph {
return removed;
}
public void tickPoints() {
edgePoints.tick(this);
public void tickPoints(boolean preTrains) {
edgePoints.tick(this, preTrains);
}
//
@ -425,6 +425,129 @@ public class TrackGraph {
return graph;
}
public void debugViewReserved() {
Entity cameraEntity = Minecraft.getInstance().cameraEntity;
if (cameraEntity == null)
return;
Set<UUID> reserved = new HashSet<>();
Set<UUID> occupied = new HashSet<>();
for (Train train : Create.RAILWAYS.trains.values()) {
reserved.addAll(train.reservedSignalBlocks);
occupied.addAll(train.occupiedSignalBlocks.keySet());
}
reserved.removeAll(occupied);
Vec3 camera = cameraEntity.getEyePosition();
for (Entry<TrackNodeLocation, TrackNode> nodeEntry : nodes.entrySet()) {
TrackNodeLocation nodeLocation = nodeEntry.getKey();
TrackNode node = nodeEntry.getValue();
if (nodeLocation == null)
continue;
Vec3 location = nodeLocation.getLocation();
if (location.distanceTo(camera) > 100)
continue;
Map<TrackNode, TrackEdge> map = connectionsByNode.get(node);
if (map == null)
continue;
int hashCode = node.hashCode();
for (Entry<TrackNode, TrackEdge> entry : map.entrySet()) {
TrackNode other = entry.getKey();
if (other.hashCode() > hashCode && !AllKeys.isKeyDown(GLFW.GLFW_KEY_LEFT_CONTROL))
continue;
Vec3 yOffset = new Vec3(0, (other.hashCode() > hashCode ? 6 : 4) / 16f, 0);
TrackEdge edge = entry.getValue();
EdgeData signalData = edge.getEdgeData();
UUID singleGroup = signalData.singleSignalGroup;
SignalEdgeGroup signalEdgeGroup =
singleGroup == null ? null : Create.RAILWAYS.sided(null).signalEdgeGroups.get(singleGroup);
if (!edge.isTurn()) {
Vec3 p1 = edge.getPosition(node, other, 0);
Vec3 p2 = edge.getPosition(node, other, 1);
if (signalData.hasPoints()) {
double prev = 0;
double length = edge.getLength(node, other);
SignalBoundary prevBoundary = null;
SignalEdgeGroup group = null;
for (TrackEdgePoint trackEdgePoint : signalData.getPoints()) {
if (!(trackEdgePoint instanceof SignalBoundary boundary))
continue;
prevBoundary = boundary;
UUID groupId = boundary.getGroup(node);
group = Create.RAILWAYS.sided(null).signalEdgeGroups.get(groupId);
double start = prev + (prev == 0 ? 0 : 1 / 16f / length);
prev = (boundary.getLocationOn(node, other, edge) / length) - 1 / 16f / length;
if (group != null
&& (group.reserved != null || occupied.contains(groupId) || reserved.contains(groupId)))
CreateClient.OUTLINER
.showLine(Pair.of(boundary, edge), edge.getPosition(node, other, start)
.add(yOffset),
edge.getPosition(node, other, prev)
.add(yOffset))
.colored(occupied.contains(groupId) ? 0xF68989
: group.reserved != null ? 0xC5D8A4 : 0xF6E7D8)
.lineWidth(1 / 16f);
}
if (prevBoundary != null) {
UUID groupId = prevBoundary.getGroup(other);
SignalEdgeGroup lastGroup = Create.RAILWAYS.sided(null).signalEdgeGroups.get(groupId);
if (lastGroup != null && ((lastGroup.reserved != null || occupied.contains(groupId)
|| reserved.contains(groupId))))
CreateClient.OUTLINER
.showLine(edge, edge.getPosition(node, other, prev + 1 / 16f / length)
.add(yOffset), p2.add(yOffset))
.colored(occupied.contains(groupId) ? 0xF68989
: lastGroup.reserved != null ? 0xC5D8A4 : 0xF6E7D8)
.lineWidth(1 / 16f);
continue;
}
}
if (signalEdgeGroup == null || !(signalEdgeGroup.reserved != null || occupied.contains(singleGroup)
|| reserved.contains(singleGroup)))
continue;
CreateClient.OUTLINER.showLine(edge, p1.add(yOffset), p2.add(yOffset))
.colored(occupied.contains(singleGroup) ? 0xF68989
: signalEdgeGroup.reserved != null ? 0xC5D8A4 : 0xF6E7D8)
.lineWidth(1 / 16f);
continue;
}
if (signalEdgeGroup == null || !(signalEdgeGroup.reserved != null || occupied.contains(singleGroup)
|| reserved.contains(singleGroup)))
continue;
int color =
occupied.contains(singleGroup) ? 0xF68989 : signalEdgeGroup.reserved != null ? 0xC5D8A4 : 0xF6E7D8;
Vec3 previous = null;
BezierConnection turn = edge.getTurn();
for (int i = 0; i <= turn.getSegmentCount(); i++) {
Vec3 current = edge.getPosition(node, other, i * 1f / turn.getSegmentCount());
if (previous != null)
CreateClient.OUTLINER
.showLine(Pair.of(edge, previous), previous.add(yOffset), current.add(yOffset))
.colored(color)
.lineWidth(1 / 16f);
previous = current;
}
}
}
}
public void debugViewSignalData() {
Entity cameraEntity = Minecraft.getInstance().cameraEntity;
if (cameraEntity == null)

View file

@ -11,7 +11,7 @@ import javax.annotation.Nullable;
import org.apache.commons.lang3.mutable.MutableDouble;
import com.simibubi.create.content.logistics.trains.TrackGraph;
import com.simibubi.create.content.logistics.trains.entity.TravellingPoint.ISignalBoundaryListener;
import com.simibubi.create.content.logistics.trains.entity.TravellingPoint.IEdgePointListener;
import com.simibubi.create.content.logistics.trains.entity.TravellingPoint.ITrackSelector;
import com.simibubi.create.foundation.utility.Couple;
import com.simibubi.create.foundation.utility.Iterate;
@ -131,9 +131,9 @@ public class Carriage {
boolean atBack =
(type == LAST || type == BOTH) && !actuallyFirstWheel && (!actuallyFirstBogey || !onTwoBogeys);
ISignalBoundaryListener frontListener = train.frontSignalListener();
ISignalBoundaryListener backListener = train.backSignalListener();
ISignalBoundaryListener passiveListener = point.ignoreSignals();
IEdgePointListener frontListener = train.frontSignalListener();
IEdgePointListener backListener = train.backSignalListener();
IEdgePointListener passiveListener = point.ignoreEdgePoints();
toMove += correction + bogeyCorrection;
double moved =

View file

@ -239,7 +239,7 @@ public class CarriageSyncData {
TravellingPoint toApproach = pointsToApproach[index];
point.travel(graph, partial * f,
point.follow(toApproach, b -> success.setValue(success.booleanValue() && b)), point.ignoreSignals(),
point.follow(toApproach, b -> success.setValue(success.booleanValue() && b)), point.ignoreEdgePoints(),
point.ignoreTurns());
// could not pathfind to server location

View file

@ -24,6 +24,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.edgePoint.EdgeData;
import com.simibubi.create.content.logistics.trains.management.edgePoint.EdgePointType;
import com.simibubi.create.content.logistics.trains.management.edgePoint.signal.SignalBlock.SignalType;
import com.simibubi.create.content.logistics.trains.management.edgePoint.signal.SignalBoundary;
import com.simibubi.create.content.logistics.trains.management.edgePoint.signal.SignalEdgeGroup;
import com.simibubi.create.content.logistics.trains.management.edgePoint.signal.TrackEdgePoint;
@ -52,6 +53,7 @@ public class Navigation {
private TravellingPoint signalScout;
public Pair<UUID, Boolean> waitingForSignal;
private Set<UUID> waitingForChainedGroups;
public double distanceToSignal;
public int ticksWaitingForSignal;
@ -59,6 +61,7 @@ public class Navigation {
this.train = train;
currentPath = new ArrayList<>();
signalScout = new TravellingPoint();
waitingForChainedGroups = new HashSet<>();
}
public void tick(Level level) {
@ -97,8 +100,13 @@ public class Navigation {
// Signals
if (train.graph != null) {
if (waitingForSignal != null && checkBlockingSignal())
if (waitingForSignal != null && currentSignalResolved()) {
SignalBoundary signal = train.graph.getPoint(EdgePointType.SIGNAL, waitingForSignal.getFirst());
if (signal.types.get(waitingForSignal.getSecond()) == SignalType.CROSS_SIGNAL)
train.reservedSignalBlocks.addAll(waitingForChainedGroups);
waitingForSignal = null;
waitingForChainedGroups.clear();
}
TravellingPoint leadingPoint = !destinationBehindTrain ? train.carriages.get(0)
.getLeadingPoint()
@ -120,49 +128,103 @@ public class Navigation {
double brakingDistanceNoFlicker =
Math.max(preDepartureLookAhead, brakingDistance + 3 - (brakingDistance % 3));
double scanDistance = Math.min(distanceToDestination - .5f, brakingDistanceNoFlicker);
double scanDistance = Math.min(distanceToDestination, brakingDistanceNoFlicker);
signalScout.travel(train.graph, scanDistance * speedMod, controlSignalScout(), (distance, couple) -> {
UUID entering = couple.getSecond()
.getSecond();
SignalEdgeGroup signalEdgeGroup = Create.RAILWAYS.signalEdgeGroups.get(entering);
if (signalEdgeGroup == null)
return;
SignalBoundary boundary = couple.getFirst();
if (signalEdgeGroup.isOccupiedUnless(train)) {
distanceToSignal = Math.min(distance, distanceToSignal);
waitingForSignal = Pair.of(boundary.id, entering.equals(boundary.groups.getFirst()));
return;
MutableDouble crossSignalDistanceTracker = new MutableDouble(-1);
MutableObject<Pair<UUID, Boolean>> trackingCrossSignal = new MutableObject<>(null);
waitingForChainedGroups.clear();
// train.reservedSignalBlocks.clear();
signalScout.travel(train.graph, distanceToDestination * speedMod, controlSignalScout(),
(distance, couple) -> {
// > scanDistance and not following down a cross signal
boolean crossSignalTracked = trackingCrossSignal.getValue() != null;
if (!crossSignalTracked && distance > scanDistance)
return true;
Couple<TrackNode> nodes = couple.getSecond();
TrackEdgePoint boundary = couple.getFirst();
if (boundary == destination && ((GlobalStation) boundary).canApproachFrom(nodes.getSecond()))
return true;
if (!(boundary instanceof SignalBoundary signal))
return false;
UUID entering = signal.getGroup(nodes.getSecond());
SignalEdgeGroup signalEdgeGroup = Create.RAILWAYS.signalEdgeGroups.get(entering);
if (signalEdgeGroup == null)
return false;
boolean primary = entering.equals(signal.groups.getFirst());
boolean crossSignal = signal.types.get(primary) == SignalType.CROSS_SIGNAL;
boolean occupied = signalEdgeGroup.isOccupiedUnless(train);
if (!occupied && distance < distanceToSignal + .25)
signalEdgeGroup.reserved = signal; // Reserve group for traversal, unless waiting at an
// earlier cross signal
if (!crossSignalTracked) {
if (crossSignal) { // Now entering cross signal path
trackingCrossSignal.setValue(Pair.of(boundary.id, primary));
crossSignalDistanceTracker.setValue(distance);
waitingForChainedGroups.add(entering);
}
if (occupied) { // Section is occupied
waitingForSignal = Pair.of(boundary.id, primary);
distanceToSignal = distance;
if (!crossSignal)
return true; // Standard entry signal, do not collect any further segments
}
} else {
waitingForChainedGroups.add(entering); // Add group to chain
if (occupied) { // Section is occupied, but wait at the cross signal that started the chain
waitingForSignal = trackingCrossSignal.getValue();
distanceToSignal = crossSignalDistanceTracker.doubleValue();
if (!crossSignal)
return true; // Entry signals end a chain
}
if (!crossSignal) {
if (distance < distanceToSignal + .25) {
// Collect and reset the signal chain because none were blocked
trackingCrossSignal.setValue(null);
train.reservedSignalBlocks.addAll(waitingForChainedGroups);
waitingForChainedGroups.clear();
return false;
} else
return true; // End of a blocked signal chain
}
}
return false;
}, (distance, edge) -> {
float current = curveDistanceTracker.floatValue();
if (current == -1 || distance < current)
curveDistanceTracker.setValue(distance);
});
if (trackingCrossSignal.getValue() != null) {
if (waitingForSignal == null) {
train.reservedSignalBlocks.addAll(waitingForChainedGroups);
waitingForChainedGroups.clear();
}
signalEdgeGroup.reserved = boundary;
}, (distance, edge) -> {
float current = curveDistanceTracker.floatValue();
if (current == -1 || distance < current)
curveDistanceTracker.setValue(distance);
});
}
distanceToNextCurve = curveDistanceTracker.floatValue();
} else {
ticksWaitingForSignal++;
// if chain signal try finding new path/destination every x ticks
}
}
double targetDistance = waitingForSignal != null ? distanceToSignal : distanceToDestination;
if (targetDistance < 1 / 32f) {
train.speed = 0;
if (waitingForSignal != null) {
distanceToSignal = 0;
return;
}
distanceToDestination = 0;
currentPath.clear();
train.arriveAt(destination);
destination = null;
return;
} else if (train.getCurrentStation() != null) {
// dont leave until green light
// always overshoot to ensure the travelling point crosses the target
targetDistance += 0.25d;
// dont leave until green light
if (targetDistance > 1 / 32f && train.getCurrentStation() != null) {
if (waitingForSignal != null && distanceToSignal < preDepartureLookAhead)
return;
train.leaveStation();
@ -170,8 +232,13 @@ public class Navigation {
train.currentlyBackwards = destinationBehindTrain;
if (targetDistance < -10) {
cancelNavigation();
return;
}
if (targetDistance - Math.abs(train.speed) < 1 / 32f) {
train.speed = targetDistance * speedMod;
train.speed = Math.max(targetDistance, 1 / 32f) * speedMod;
return;
}
@ -198,12 +265,26 @@ public class Navigation {
train.approachTargetSpeed(1);
}
private boolean checkBlockingSignal() {
private boolean currentSignalResolved() {
if (distanceToDestination < .5f)
return true;
SignalBoundary signal = train.graph.getPoint(EdgePointType.SIGNAL, waitingForSignal.getFirst());
if (signal == null)
return true;
// Cross Signal
if (signal.types.get(waitingForSignal.getSecond()) == SignalType.CROSS_SIGNAL) {
for (UUID groupId : waitingForChainedGroups) {
SignalEdgeGroup signalEdgeGroup = Create.RAILWAYS.signalEdgeGroups.get(groupId);
if (signalEdgeGroup == null)
continue;
if (signalEdgeGroup.isOccupiedUnless(train))
return false;
}
return true;
}
// Entry Signal
UUID groupId = signal.groups.get(waitingForSignal.getSecond());
if (groupId == null)
return true;
@ -255,6 +336,7 @@ public class Navigation {
destination.cancelReservation(train);
destination = null;
train.runtime.transitInterrupted();
train.reservedSignalBlocks.clear();
}
public double startNavigation(GlobalStation destination, double maxCost, boolean simulate) {
@ -267,16 +349,19 @@ public class Navigation {
return cost;
distanceToDestination = distance;
currentPath = pathTo.path;
if (noneFound) {
distanceToDestination = 0;
currentPath = new ArrayList<>();
if (this.destination != null)
cancelNavigation();
return -1;
}
currentPath = pathTo.path;
destinationBehindTrain = pathTo.distance < 0;
train.reservedSignalBlocks.clear();
train.navigation.waitingForSignal = null;
if (this.destination == destination)
return 0;
@ -315,7 +400,9 @@ public class Navigation {
Couple<DiscoveredPath> results = Couple.create(null, null);
for (boolean forward : Iterate.trueAndFalse) {
if (this.destination == destination && destinationBehindTrain == forward)
// When updating destinations midtransit, avoid reversing out of path
if (this.destination != null && destinationBehindTrain == forward)
continue;
TravellingPoint initialPoint = forward ? train.carriages.get(0)
@ -377,6 +464,9 @@ public class Navigation {
if (!canDriveForward)
return back;
// Debug.debugChat("Front: " + front.distance + ", Back: " + back.distance);
// Debug.debugChat("FrontCost: " + front.cost + ", BackCost: " + back.cost);
boolean frontBetter = maxCost == -1 ? -back.distance > front.distance : back.cost > front.cost;
return frontBetter ? front : back;
}
@ -438,6 +528,8 @@ public class Navigation {
for (Train otherTrain : Create.RAILWAYS.trains.values()) {
if (otherTrain.graph != graph)
continue;
if (otherTrain == train)
continue;
int navigationPenalty = otherTrain.getNavigationPenalty();
otherTrain.getEndpointEdges()
.forEach(nodes -> {
@ -474,6 +566,8 @@ public class Navigation {
Search: while (!frontier.isEmpty()) {
FrontierEntry entry = frontier.poll();
if (!visited.add(entry.edge))
continue;
double distance = entry.distance;
int penalty = entry.penalty;
@ -499,12 +593,15 @@ public class Navigation {
if (!point.canNavigateVia(node2))
continue Search;
if (point instanceof GlobalStation station) {
if (station.getPresentTrain() != null)
Train presentTrain = station.getPresentTrain();
boolean isOwnStation = presentTrain == train;
if (presentTrain != null && !isOwnStation)
penalty += Train.Penalties.STATION_WITH_TRAIN;
if (station.canApproachFrom(node2) && stationTest.test(distance, distance + penalty, reachedVia,
Pair.of(Couple.create(node1, node2), edge), station))
return;
penalty += Train.Penalties.STATION;
if (!isOwnStation)
penalty += Train.Penalties.STATION;
}
}
}
@ -521,8 +618,6 @@ public class Navigation {
Vec3 newDirection = newEdge.getDirection(node2, newNode, true);
if (currentDirection.dot(newDirection) < 3 / 4f)
continue;
if (!visited.add(connection.getValue()))
continue;
validTargets.add(connection);
}

View file

@ -1,6 +1,7 @@
package com.simibubi.create.content.logistics.trains.entity;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
@ -23,11 +24,12 @@ import com.simibubi.create.content.logistics.trains.GraphLocation;
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.entity.TravellingPoint.ISignalBoundaryListener;
import com.simibubi.create.content.logistics.trains.entity.TravellingPoint.IEdgePointListener;
import com.simibubi.create.content.logistics.trains.entity.TravellingPoint.ITrackSelector;
import com.simibubi.create.content.logistics.trains.entity.TravellingPoint.SteerDirection;
import com.simibubi.create.content.logistics.trains.management.edgePoint.EdgeData;
import com.simibubi.create.content.logistics.trains.management.edgePoint.EdgePointType;
import com.simibubi.create.content.logistics.trains.management.edgePoint.signal.SignalBlock.SignalType;
import com.simibubi.create.content.logistics.trains.management.edgePoint.signal.SignalBoundary;
import com.simibubi.create.content.logistics.trains.management.edgePoint.signal.SignalEdgeGroup;
import com.simibubi.create.content.logistics.trains.management.edgePoint.station.GlobalStation;
@ -82,7 +84,8 @@ public class Train {
public List<Integer> carriageSpacing;
public boolean updateSignalBlocks;
public List<UUID> occupiedSignalBlocks;
public Map<UUID, UUID> occupiedSignalBlocks;
public Set<UUID> reservedSignalBlocks;
List<TrainMigration> migratingPoints;
public int migrationCooldown;
@ -112,28 +115,35 @@ public class Train {
migratingPoints = new ArrayList<>();
currentStation = null;
manualSteer = SteerDirection.NONE;
occupiedSignalBlocks = new ArrayList<>();
occupiedSignalBlocks = new HashMap<>();
reservedSignalBlocks = new HashSet<>();
}
public void earlyTick(Level level) {
status.tick(level);
if (graph == null && !migratingPoints.isEmpty())
reattachToTracks(level);
addToSignalGroups(occupiedSignalBlocks.keySet());
if (graph == null)
return;
if (updateSignalBlocks) {
updateSignalBlocks = false;
collectInitiallyOccupiedSignalBlocks();
}
for (Iterator<UUID> iterator = occupiedSignalBlocks.iterator(); iterator.hasNext();) {
SignalEdgeGroup signalEdgeGroup = Create.RAILWAYS.signalEdgeGroups.get(iterator.next());
if (signalEdgeGroup == null) {
addToSignalGroups(reservedSignalBlocks);
}
private void addToSignalGroups(Collection<UUID> groups) {
Map<UUID, SignalEdgeGroup> groupMap = Create.RAILWAYS.signalEdgeGroups;
for (Iterator<UUID> iterator = groups.iterator(); iterator.hasNext();) {
SignalEdgeGroup signalEdgeGroup = groupMap.get(iterator.next());
if (signalEdgeGroup == null)
iterator.remove();
continue;
}
signalEdgeGroup.trains.add(this);
else
signalEdgeGroup.trains.add(this);
}
}
@ -212,6 +222,8 @@ public class Train {
if (index == 0) {
distance = actualDistance;
collideWithOtherTrains(level, carriage);
if (graph == null)
return;
}
}
@ -226,23 +238,58 @@ public class Train {
updateNavigationTarget(distance);
}
public ISignalBoundaryListener frontSignalListener() {
public IEdgePointListener frontSignalListener() {
return (distance, couple) -> {
UUID groupId = couple.getSecond()
.getSecond();
SignalEdgeGroup signalEdgeGroup = Create.RAILWAYS.signalEdgeGroups.get(groupId);
SignalBoundary boundary = couple.getFirst();
if (signalEdgeGroup != null) {
signalEdgeGroup.reserved = boundary;
occupiedSignalBlocks.add(groupId);
if (couple.getFirst()instanceof GlobalStation station) {
if (!station.canApproachFrom(couple.getSecond()
.getSecond()) || navigation.destination != station)
return false;
speed = 0;
navigation.distanceToDestination = 0;
navigation.currentPath.clear();
arriveAt(navigation.destination);
navigation.destination = null;
return true;
}
if (!(couple.getFirst()instanceof SignalBoundary signal))
return false;
if (navigation.waitingForSignal != null && navigation.waitingForSignal.getFirst()
.equals(signal.id)) {
speed = 0;
navigation.distanceToSignal = 0;
return true;
}
UUID groupId = signal.getGroup(couple.getSecond()
.getSecond());
SignalEdgeGroup signalEdgeGroup = Create.RAILWAYS.signalEdgeGroups.get(groupId);
if (signalEdgeGroup == null)
return false;
signalEdgeGroup.reserved = signal;
occupy(groupId, signal.id);
return false;
};
}
public ISignalBoundaryListener backSignalListener() {
private boolean occupy(UUID groupId, @Nullable UUID boundaryId) {
reservedSignalBlocks.remove(groupId);
if (boundaryId != null && occupiedSignalBlocks.containsKey(groupId))
if (boundaryId.equals(occupiedSignalBlocks.get(groupId)))
return false;
return occupiedSignalBlocks.put(groupId, boundaryId) == null;
}
public IEdgePointListener backSignalListener() {
return (distance, couple) -> {
occupiedSignalBlocks.remove(couple.getSecond()
if (!(couple.getFirst()instanceof SignalBoundary signal))
return false;
UUID groupId = signal.getGroup(couple.getSecond()
.getFirst());
occupiedSignalBlocks.remove(groupId);
return false;
};
}
@ -250,32 +297,43 @@ public class Train {
if (navigation.destination == null)
return;
boolean recalculate = navigation.distanceToDestination % 100 > 20;
boolean imminentRecalculate = navigation.distanceToDestination > 5;
Pair<UUID, Boolean> blockingSignal = navigation.waitingForSignal;
boolean fullRefresh = navigation.distanceToDestination > 100 && navigation.distanceToDestination % 100 > 20;
boolean signalRefresh = blockingSignal != null && navigation.distanceToSignal % 50 > 5;
boolean partialRefresh = navigation.distanceToDestination < 100 && navigation.distanceToDestination % 50 > 5;
double toSubstract = navigation.destinationBehindTrain ? -distance : distance;
navigation.distanceToDestination -= toSubstract;
boolean signalMode = navigation.waitingForSignal != null;
boolean navigatingManually = runtime.paused;
if (signalMode) {
navigation.distanceToDestination -= toSubstract;
if (blockingSignal != null) {
navigation.distanceToSignal -= toSubstract;
recalculate = navigation.distanceToSignal % 100 > 20;
signalRefresh &= navigation.distanceToSignal % 50 < 5;
}
if (recalculate && (signalMode ? navigation.distanceToSignal : navigation.distanceToDestination) % 100 <= 20
|| imminentRecalculate && navigation.distanceToDestination <= 5) {
if (signalMode) {
navigation.waitingForSignal = null;
return;
}
GlobalStation destination = navigation.destination;
if (!navigatingManually) {
GlobalStation preferredDestination = runtime.findNextStation();
if (preferredDestination != null)
destination = preferredDestination;
}
navigation.startNavigation(destination, navigatingManually ? -1 : Double.MAX_VALUE, false);
fullRefresh &= navigation.distanceToDestination % 100 <= 20;
partialRefresh &= navigation.distanceToDestination % 50 <= 5;
if (blockingSignal != null && navigation.ticksWaitingForSignal % 100 == 50) {
SignalBoundary signal = graph.getPoint(EdgePointType.SIGNAL, blockingSignal.getFirst());
fullRefresh |= signal != null && signal.types.get(blockingSignal.getSecond()) == SignalType.CROSS_SIGNAL;
}
if (signalRefresh)
navigation.waitingForSignal = null;
if (!fullRefresh && !partialRefresh)
return;
if (!reservedSignalBlocks.isEmpty())
return;
GlobalStation destination = navigation.destination;
if (!navigatingManually && fullRefresh) {
GlobalStation preferredDestination = runtime.findNextStation();
if (preferredDestination != null)
destination = preferredDestination;
}
navigation.startNavigation(destination, navigatingManually ? -1 : Double.MAX_VALUE, false);
}
private void tickDerailedSlowdown() {
@ -439,7 +497,7 @@ public class Train {
if (currentStation != null)
currentStation.cancelReservation(this);
Create.RAILWAYS.trains.remove(id);
Create.RAILWAYS.removeTrain(id);
AllPackets.channel.send(PacketDistributor.ALL.noArg(), new TrainPacket(this, false));
return true;
}
@ -598,6 +656,7 @@ public class Train {
public void arriveAt(GlobalStation station) {
setCurrentStation(station);
reservedSignalBlocks.clear();
runtime.destinationReached();
}
@ -645,6 +704,7 @@ public class Train {
EdgeData signalData = edge.getEdgeData();
occupiedSignalBlocks.clear();
reservedSignalBlocks.clear();
TravellingPoint signalScout = new TravellingPoint(node1, node2, edge, position);
Map<UUID, SignalEdgeGroup> allGroups = Create.RAILWAYS.signalEdgeGroups;
@ -664,7 +724,7 @@ public class Train {
if (prev != null) {
UUID group = prev.getGroup(node2);
if (Create.RAILWAYS.signalEdgeGroups.containsKey(group)) {
occupiedSignalBlocks.add(group);
occupy(group, null);
prevGroup.setValue(group);
}
}
@ -672,26 +732,32 @@ public class Train {
} else {
UUID group = nextBoundary.getGroup(node1);
if (Create.RAILWAYS.signalEdgeGroups.containsKey(group)) {
occupiedSignalBlocks.add(group);
occupy(group, null);
prevGroup.setValue(group);
}
}
} else if (signalData.singleSignalGroup != null && allGroups.containsKey(signalData.singleSignalGroup)) {
occupiedSignalBlocks.add(signalData.singleSignalGroup);
occupy(signalData.singleSignalGroup, null);
prevGroup.setValue(signalData.singleSignalGroup);
}
forEachTravellingPointBackwards((tp, d) -> {
signalScout.travel(graph, d, signalScout.follow(tp), (distance, couple) -> couple.getSecond()
.forEach(id -> {
if (!Create.RAILWAYS.signalEdgeGroups.containsKey(id))
return;
if (id.equals(prevGroup.getValue()))
return;
occupiedSignalBlocks.add(id);
prevGroup.setValue(id);
}), signalScout.ignoreTurns());
signalScout.travel(graph, d, signalScout.follow(tp), (distance, couple) -> {
if (!(couple.getFirst()instanceof SignalBoundary signal))
return false;
couple.getSecond()
.map(signal::getGroup)
.forEach(id -> {
if (!Create.RAILWAYS.signalEdgeGroups.containsKey(id))
return;
if (id.equals(prevGroup.getValue()))
return;
occupy(id, null);
prevGroup.setValue(id);
});
return false;
}, signalScout.ignoreTurns());
});
}
@ -740,7 +806,14 @@ public class Train {
tag.putBoolean("StillAssembling", heldForAssembly);
tag.putBoolean("Derailed", derailed);
tag.putBoolean("UpdateSignals", updateSignalBlocks);
tag.put("SignalBlocks", NBTHelper.writeCompoundList(occupiedSignalBlocks, uid -> {
tag.put("SignalBlocks", NBTHelper.writeCompoundList(occupiedSignalBlocks.entrySet(), e -> {
CompoundTag compoundTag = new CompoundTag();
compoundTag.putUUID("Id", e.getKey());
if (e.getValue() != null)
compoundTag.putUUID("Boundary", e.getValue());
return compoundTag;
}));
tag.put("ReservedSignalBlocks", NBTHelper.writeCompoundList(reservedSignalBlocks, uid -> {
CompoundTag compoundTag = new CompoundTag();
compoundTag.putUUID("Id", uid);
return compoundTag;
@ -778,9 +851,10 @@ public class Train {
train.derailed = tag.getBoolean("Derailed");
train.updateSignalBlocks = tag.getBoolean("UpdateSignals");
NBTHelper.iterateCompoundList(tag.getList("SignalBlocks", Tag.TAG_COMPOUND),
c -> train.occupiedSignalBlocks.add(c.getUUID("Id")));
NBTHelper.iterateCompoundList(tag.getList("SignalBlocks", Tag.TAG_COMPOUND), c -> train.occupiedSignalBlocks
.put(c.getUUID("Id"), c.contains("Boundary") ? c.getUUID("Boundary") : null));
NBTHelper.iterateCompoundList(tag.getList("ReservedSignalBlocks", Tag.TAG_COMPOUND),
c -> train.reservedSignalBlocks.add(c.getUUID("Id")));
NBTHelper.iterateCompoundList(tag.getList("MigratingPoints", Tag.TAG_COMPOUND),
c -> train.migratingPoints.add(TrainMigration.read(c)));

View file

@ -22,7 +22,7 @@ import com.simibubi.create.content.logistics.trains.TrackEdge;
import com.simibubi.create.content.logistics.trains.TrackGraph;
import com.simibubi.create.content.logistics.trains.TrackGraphHelper;
import com.simibubi.create.content.logistics.trains.TrackNode;
import com.simibubi.create.content.logistics.trains.entity.TravellingPoint.ISignalBoundaryListener;
import com.simibubi.create.content.logistics.trains.entity.TravellingPoint.IEdgePointListener;
import com.simibubi.create.content.logistics.trains.entity.TravellingPoint.ITrackSelector;
import com.simibubi.create.content.logistics.trains.entity.TravellingPoint.ITurnListener;
import com.simibubi.create.content.logistics.trains.entity.TravellingPoint.SteerDirection;
@ -158,7 +158,7 @@ public class TrainRelocator {
return false;
TravellingPoint probe = new TravellingPoint(node1, node2, edge, graphLocation.position);
ISignalBoundaryListener ignoreSignals = probe.ignoreSignals();
IEdgePointListener ignoreSignals = probe.ignoreEdgePoints();
ITurnListener ignoreTurns = probe.ignoreTurns();
List<Pair<Couple<TrackNode>, Double>> recordedLocations = new ArrayList<>();
List<Vec3> recordedVecs = new ArrayList<>();
@ -206,6 +206,7 @@ public class TrainRelocator {
train.leaveStation();
train.derailed = false;
train.heldForAssembly = false;
train.navigation.waitingForSignal = null;
train.occupiedSignalBlocks.clear();
train.graph = graph;
@ -322,13 +323,12 @@ public class TrainRelocator {
@OnlyIn(Dist.CLIENT)
public static boolean carriageWrenched(Vec3 vec3, CarriageContraptionEntity entity) {
Train train = getTrainFromEntity(entity);
if (train != null && !train.heldForAssembly) {
relocatingOrigin = vec3;
relocatingTrain = train.id;
relocatingEntityId = entity.getId();
return true;
}
return false;
if (train == null)
return false;
relocatingOrigin = vec3;
relocatingTrain = train.id;
relocatingEntityId = entity.getId();
return true;
}
@OnlyIn(Dist.CLIENT)

View file

@ -6,10 +6,10 @@ import java.util.List;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import java.util.Vector;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.BiPredicate;
import java.util.function.Consumer;
import javax.annotation.Nullable;
@ -21,8 +21,7 @@ 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.management.edgePoint.EdgeData;
import com.simibubi.create.content.logistics.trains.management.edgePoint.EdgePointType;
import com.simibubi.create.content.logistics.trains.management.edgePoint.signal.SignalBoundary;
import com.simibubi.create.content.logistics.trains.management.edgePoint.signal.TrackEdgePoint;
import com.simibubi.create.foundation.utility.Couple;
import com.simibubi.create.foundation.utility.Pair;
@ -53,7 +52,7 @@ public class TravellingPoint {
extends BiFunction<TrackGraph, Pair<Boolean, List<Entry<TrackNode, TrackEdge>>>, Entry<TrackNode, TrackEdge>> {
};
public static interface ISignalBoundaryListener extends BiConsumer<Double, Pair<SignalBoundary, Couple<UUID>>> {
public static interface IEdgePointListener extends BiPredicate<Double, Pair<TrackEdgePoint, Couple<TrackNode>>> {
};
public static interface ITurnListener extends BiConsumer<Double, TrackEdge> {
@ -68,9 +67,8 @@ public class TravellingPoint {
this.position = position;
}
public ISignalBoundaryListener ignoreSignals() {
return (d, c) -> {
};
public IEdgePointListener ignoreEdgePoints() {
return (d, c) -> false;
}
public ITurnListener ignoreTurns() {
@ -178,7 +176,7 @@ public class TravellingPoint {
}
public double travel(TrackGraph graph, double distance, ITrackSelector trackSelector,
ISignalBoundaryListener signalListener, ITurnListener turnListener) {
IEdgePointListener signalListener, ITurnListener turnListener) {
blocked = false;
double edgeLength = edge.getLength(node1, node2);
if (distance == 0)
@ -193,7 +191,14 @@ public class TravellingPoint {
boolean forward = distance > 0;
double collectedDistance = forward ? -prevPos : -edgeLength + prevPos;
edgeTraversedFrom(graph, forward, signalListener, turnListener, prevPos, collectedDistance);
Double blockedLocation =
edgeTraversedFrom(graph, forward, signalListener, turnListener, prevPos, collectedDistance);
if (blockedLocation != null) {
position = blockedLocation.doubleValue();
traveled = position - prevPos;
return traveled;
}
if (forward) {
// Moving forward
@ -233,9 +238,17 @@ public class TravellingPoint {
collectedDistance += edgeLength;
if (edge.isTurn())
turnListener.accept(collectedDistance, edge);
edgeTraversedFrom(graph, forward, signalListener, turnListener, 0, collectedDistance);
prevPos = 0;
blockedLocation = edgeTraversedFrom(graph, forward, signalListener, turnListener, 0, collectedDistance);
if (blockedLocation != null) {
traveled -= position;
position = blockedLocation.doubleValue();
traveled += position;
break;
}
prevPos = 0;
edgeLength = edge.getLength(node1, node2);
}
@ -261,7 +274,7 @@ public class TravellingPoint {
}
if (validTargets.isEmpty()) {
traveled -= position;
traveled += position;
position = 0;
blocked = true;
break;
@ -278,7 +291,15 @@ public class TravellingPoint {
edgeLength = edge.getLength(node1, node2);
position += edgeLength;
edgeTraversedFrom(graph, forward, signalListener, turnListener, edgeLength, collectedDistance);
blockedLocation =
edgeTraversedFrom(graph, forward, signalListener, turnListener, edgeLength, collectedDistance);
if (blockedLocation != null) {
traveled -= position;
position = blockedLocation.doubleValue();
traveled += position;
break;
}
}
}
@ -286,42 +307,30 @@ public class TravellingPoint {
return traveled;
}
private void edgeTraversedFrom(TrackGraph graph, boolean forward, ISignalBoundaryListener signalListener,
private Double edgeTraversedFrom(TrackGraph graph, boolean forward, IEdgePointListener edgePointListener,
ITurnListener turnListener, double prevPos, double totalDistance) {
if (edge.isTurn())
turnListener.accept(Math.max(0, totalDistance), edge);
EdgeData signalsOnEdge = edge.getEdgeData();
if (!signalsOnEdge.hasSignalBoundaries())
return;
EdgeData edgeData = edge.getEdgeData();
double from = forward ? prevPos : position;
double to = forward ? position : prevPos;
SignalBoundary nextBoundary = signalsOnEdge.next(EdgePointType.SIGNAL, node1, node2, edge, from);
List<SignalBoundary> discoveredBoundaries = null;
List<TrackEdgePoint> edgePoints = edgeData.getPoints();
while (nextBoundary != null) {
double d = nextBoundary.getLocationOn(node1, node2, edge);
if (d > to)
break;
if (discoveredBoundaries == null)
discoveredBoundaries = new ArrayList<>();
discoveredBoundaries.add(nextBoundary);
nextBoundary = signalsOnEdge.next(EdgePointType.SIGNAL, node1, node2, edge, d);
double length = edge.getLength(node1, node2);
for (int i = 0; i < edgePoints.size(); i++) {
int index = forward ? i : edgePoints.size() - i - 1;
TrackEdgePoint nextBoundary = edgePoints.get(index);
double locationOn = nextBoundary.getLocationOn(node1, node2, edge);
double distance = forward ? locationOn : length - locationOn;
if (forward ? (locationOn < from || locationOn >= to) : (locationOn <= from || locationOn > to))
continue;
Couple<TrackNode> nodes = Couple.create(node1, node2);
if (edgePointListener.test(totalDistance + distance, Pair.of(nextBoundary, forward ? nodes : nodes.swap())))
return locationOn;
}
if (discoveredBoundaries == null)
return;
for (int i = 0; i < discoveredBoundaries.size(); i++) {
int index = forward ? i : discoveredBoundaries.size() - i - 1;
nextBoundary = discoveredBoundaries.get(index);
double d = nextBoundary.getLocationOn(node1, node2, edge);
if (!forward)
d = edge.getLength(node1, node2) - d;
Couple<UUID> nodes = Couple.create(nextBoundary.getGroup(node1), nextBoundary.getGroup(node2));
signalListener.accept(totalDistance + d, Pair.of(nextBoundary, forward ? nodes : nodes.swap()));
}
return null;
}
public void reverse(TrackGraph graph) {

View file

@ -70,6 +70,14 @@ public class EdgeData {
return null;
}
@Nullable
public TrackEdgePoint next(TrackNode node1, TrackNode node2, TrackEdge edge, double minPosition) {
for (TrackEdgePoint point : points)
if (point.getLocationOn(node1, node2, edge) > minPosition)
return point;
return null;
}
@Nullable
public <T extends TrackEdgePoint> T get(EdgePointType<T> type, TrackNode node1, TrackNode node2, TrackEdge edge,
double exactPosition) {

View file

@ -49,10 +49,10 @@ public class EdgePointStorage {
return pointsByType.computeIfAbsent(type, t -> new HashMap<>());
}
public void tick(TrackGraph graph) {
public void tick(TrackGraph graph, boolean preTrains) {
pointsByType.values()
.forEach(map -> map.values()
.forEach(p -> p.tick(graph)));
.forEach(p -> p.tick(graph, preTrains)));
}
public void transferAll(TrackGraph target, EdgePointStorage other) {

View file

@ -143,7 +143,7 @@ public class TrackTargetingBehaviour<T extends TrackEdgePoint> extends TileEntit
}
if (!otherPoint.canMerge())
return null;
otherPoint.tileAdded(getPos(), front);
otherPoint.tileAdded(tileEntity, front);
id = otherPoint.getId();
tileEntity.setChanged();
return (T) otherPoint;
@ -158,7 +158,7 @@ public class TrackTargetingBehaviour<T extends TrackEdgePoint> extends TileEntit
point.setId(id);
point.setLocation(reverseEdge ? loc.edge : loc.edge.swap(), reverseEdge ? loc.position : length - loc.position);
point.tileAdded(getPos(), front);
point.tileAdded(tileEntity, front);
loc.graph.addPoint(edgePointType, point);
return point;
}

View file

@ -3,21 +3,41 @@ package com.simibubi.create.content.logistics.trains.management.edgePoint.signal
import java.util.Random;
import com.simibubi.create.AllTileEntities;
import com.simibubi.create.content.contraptions.wrench.IWrenchable;
import com.simibubi.create.foundation.block.ITE;
import com.simibubi.create.foundation.utility.Lang;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.util.StringRepresentable;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.context.UseOnContext;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.SimpleWaterloggedBlock;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateDefinition.Builder;
import net.minecraft.world.level.block.state.properties.EnumProperty;
public class SignalBlock extends Block implements SimpleWaterloggedBlock, ITE<SignalTileEntity> {
public class SignalBlock extends Block implements ITE<SignalTileEntity>, IWrenchable {
public static final EnumProperty<SignalType> TYPE = EnumProperty.create("type", SignalType.class);
public enum SignalType implements StringRepresentable {
ENTRY_SIGNAL, CROSS_SIGNAL;
@Override
public String getSerializedName() {
return Lang.asId(name());
}
}
public SignalBlock(Properties p_53182_) {
super(p_53182_);
registerDefaultState(defaultBlockState().setValue(TYPE, SignalType.ENTRY_SIGNAL));
}
@Override
@ -25,11 +45,36 @@ public class SignalBlock extends Block implements SimpleWaterloggedBlock, ITE<Si
return SignalTileEntity.class;
}
@Override
protected void createBlockStateDefinition(Builder<Block, BlockState> pBuilder) {
super.createBlockStateDefinition(pBuilder.add(TYPE));
}
@Override
public BlockEntityType<? extends SignalTileEntity> getTileEntityType() {
return AllTileEntities.TRACK_SIGNAL.get();
}
@Override
public InteractionResult onWrenched(BlockState state, UseOnContext context) {
Level level = context.getLevel();
BlockPos pos = context.getClickedPos();
if (level.isClientSide)
return InteractionResult.SUCCESS;
withTileEntityDo(level, pos, ste -> {
SignalBoundary signal = ste.getSignal();
Player player = context.getPlayer();
if (signal != null) {
signal.cycleSignalType(pos);
if (player != null)
player.displayClientMessage(Lang.translate("track_signal.mode_change." + signal.getTypeFor(pos)
.getSerializedName()), true);
} else if (player != null)
player.displayClientMessage(Lang.translate("track_signal.cannot_change_mode"), true);
});
return InteractionResult.SUCCESS;
}
@Override
public boolean canConnectRedstone(BlockState state, BlockGetter world, BlockPos pos, Direction side) {
return side != null;

View file

@ -2,6 +2,7 @@ package com.simibubi.create.content.logistics.trains.management.edgePoint.signal
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.UUID;
@ -9,6 +10,8 @@ import com.google.common.base.Objects;
import com.simibubi.create.Create;
import com.simibubi.create.content.logistics.trains.TrackGraph;
import com.simibubi.create.content.logistics.trains.TrackNode;
import com.simibubi.create.content.logistics.trains.management.edgePoint.EdgePointType;
import com.simibubi.create.content.logistics.trains.management.edgePoint.signal.SignalBlock.SignalType;
import com.simibubi.create.content.logistics.trains.management.edgePoint.signal.SignalTileEntity.OverlayState;
import com.simibubi.create.content.logistics.trains.management.edgePoint.signal.SignalTileEntity.SignalState;
import com.simibubi.create.foundation.utility.Couple;
@ -21,23 +24,32 @@ import net.minecraft.nbt.NbtUtils;
import net.minecraft.nbt.Tag;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.block.entity.BlockEntity;
public class SignalBoundary extends TrackEdgePoint {
public Couple<Set<BlockPos>> signals;
public Couple<Set<BlockPos>> blockEntities;
public Couple<SignalType> types;
public Couple<UUID> groups;
public Couple<Boolean> sidesToUpdate;
public Couple<SignalState> cachedStates;
private Couple<Map<UUID, Boolean>> chainedSignals;
public SignalBoundary() {
signals = Couple.create(HashSet::new);
blockEntities = Couple.create(HashSet::new);
chainedSignals = Couple.create(null, null);
groups = Couple.create(null, null);
sidesToUpdate = Couple.create(true, true);
types = Couple.create(() -> SignalType.ENTRY_SIGNAL);
cachedStates = Couple.create(() -> SignalState.GREEN);
}
public void setGroup(TrackNode side, UUID groupId) {
boolean primary = isPrimary(side);
groups.set(primary, groupId);
sidesToUpdate.set(primary, false);
chainedSignals.set(primary, null);
}
@Override
@ -47,19 +59,23 @@ public class SignalBoundary extends TrackEdgePoint {
@Override
public void invalidate(LevelAccessor level) {
signals.forEach(s -> s.forEach(pos -> invalidateAt(level, pos)));
blockEntities.forEach(s -> s.forEach(pos -> invalidateAt(level, pos)));
}
@Override
public void tileAdded(BlockPos tilePos, boolean front) {
signals.get(front)
.add(tilePos);
public void tileAdded(BlockEntity tile, boolean front) {
Set<BlockPos> tilesOnSide = blockEntities.get(front);
if (tilesOnSide.isEmpty())
tile.getBlockState()
.getOptionalValue(SignalBlock.TYPE)
.ifPresent(type -> types.set(front, type));
tilesOnSide.add(tile.getBlockPos());
}
@Override
public void tileRemoved(BlockPos tilePos, boolean front) {
signals.forEach(s -> s.remove(tilePos));
if (signals.both(Set::isEmpty))
blockEntities.forEach(s -> s.remove(tilePos));
if (blockEntities.both(Set::isEmpty))
removeFromAllGraphs();
}
@ -79,16 +95,16 @@ public class SignalBoundary extends TrackEdgePoint {
@Override
public boolean canNavigateVia(TrackNode side) {
return !signals.get(isPrimary(side))
return !blockEntities.get(isPrimary(side))
.isEmpty();
}
public OverlayState getOverlayFor(BlockPos tile) {
for (boolean first : Iterate.trueAndFalse) {
Set<BlockPos> set = signals.get(first);
Set<BlockPos> set = blockEntities.get(first);
for (BlockPos blockPos : set) {
if (blockPos.equals(tile))
return signals.get(!first)
return blockEntities.get(!first)
.isEmpty() ? OverlayState.RENDER : OverlayState.DUAL;
return OverlayState.SKIP;
}
@ -96,58 +112,123 @@ public class SignalBoundary extends TrackEdgePoint {
return OverlayState.SKIP;
}
public SignalType getTypeFor(BlockPos tile) {
return types.get(blockEntities.getFirst()
.contains(tile));
}
public SignalState getStateFor(BlockPos tile) {
for (boolean first : Iterate.trueAndFalse) {
Set<BlockPos> set = signals.get(first);
if (!set.contains(tile))
continue;
UUID group = groups.get(first);
if (Objects.equal(group, groups.get(!first)))
return SignalState.INVALID;
Map<UUID, SignalEdgeGroup> signalEdgeGroups = Create.RAILWAYS.signalEdgeGroups;
SignalEdgeGroup signalEdgeGroup = signalEdgeGroups.get(group);
if (signalEdgeGroup == null)
return SignalState.INVALID;
return signalEdgeGroup.isOccupiedUnless(this) ? SignalState.RED : SignalState.GREEN;
Set<BlockPos> set = blockEntities.get(first);
if (set.contains(tile))
return cachedStates.get(first);
}
return SignalState.INVALID;
}
@Override
public void tick(TrackGraph graph) {
super.tick(graph);
public void tick(TrackGraph graph, boolean preTrains) {
super.tick(graph, preTrains);
if (!preTrains) {
tickState(graph);
return;
}
for (boolean front : Iterate.trueAndFalse) {
if (!sidesToUpdate.get(front))
continue;
sidesToUpdate.set(front, false);
SignalPropagator.propagateSignalGroup(graph, this, front);
chainedSignals.set(front, null);
}
}
private void tickState(TrackGraph graph) {
for (boolean current : Iterate.trueAndFalse) {
Set<BlockPos> set = blockEntities.get(current);
if (set.isEmpty())
continue;
UUID group = groups.get(current);
if (Objects.equal(group, groups.get(!current))) {
cachedStates.set(current, SignalState.INVALID);
continue;
}
Map<UUID, SignalEdgeGroup> signalEdgeGroups = Create.RAILWAYS.signalEdgeGroups;
SignalEdgeGroup signalEdgeGroup = signalEdgeGroups.get(group);
if (signalEdgeGroup == null) {
cachedStates.set(current, SignalState.INVALID);
continue;
}
boolean occupiedUnlessBySelf = signalEdgeGroup.isOccupiedUnless(this);
cachedStates.set(current, occupiedUnlessBySelf ? SignalState.RED : resolveSignalChain(graph, current));
}
}
private SignalState resolveSignalChain(TrackGraph graph, boolean side) {
if (types.get(side) != SignalType.CROSS_SIGNAL)
return SignalState.GREEN;
if (chainedSignals.get(side) == null)
chainedSignals.set(side, SignalPropagator.collectChainedSignals(graph, this, side));
boolean allPathsFree = true;
boolean noPathsFree = true;
boolean invalid = false;
for (Entry<UUID, Boolean> entry : chainedSignals.get(side)
.entrySet()) {
UUID uuid = entry.getKey();
boolean sideOfOther = entry.getValue();
SignalBoundary otherSignal = graph.getPoint(EdgePointType.SIGNAL, uuid);
if (otherSignal == null) {
invalid = true;
break;
}
SignalState otherState = otherSignal.cachedStates.get(sideOfOther);
allPathsFree &= otherState == SignalState.GREEN || otherState == SignalState.INVALID;
noPathsFree &= otherState == SignalState.RED;
}
if (invalid) {
chainedSignals.set(side, null);
return SignalState.INVALID;
}
if (allPathsFree)
return SignalState.GREEN;
if (noPathsFree)
return SignalState.RED;
return SignalState.YELLOW;
}
@Override
public void read(CompoundTag nbt, boolean migration) {
super.read(nbt, migration);
if (migration)
return;
sidesToUpdate = Couple.create(true, true);
signals = Couple.create(HashSet::new);
blockEntities = Couple.create(HashSet::new);
groups = Couple.create(null, null);
for (int i = 1; i <= 2; i++)
if (nbt.contains("Tiles" + i)) {
boolean first = i == 1;
NBTHelper.iterateCompoundList(nbt.getList("Tiles" + i, Tag.TAG_COMPOUND), c -> signals.get(first)
NBTHelper.iterateCompoundList(nbt.getList("Tiles" + i, Tag.TAG_COMPOUND), c -> blockEntities.get(first)
.add(NbtUtils.readBlockPos(c)));
}
for (int i = 1; i <= 2; i++)
if (nbt.contains("Group" + i))
groups.set(i == 1, nbt.getUUID("Group" + i));
for (int i = 1; i <= 2; i++)
sidesToUpdate.set(i == 1, nbt.contains("Update" + i));
for (int i = 1; i <= 2; i++)
types.set(i == 1, NBTHelper.readEnum(nbt, "Type" + i, SignalType.class));
for (int i = 1; i <= 2; i++)
cachedStates.set(i == 1, NBTHelper.readEnum(nbt, "State" + i, SignalState.class));
}
@Override
public void read(FriendlyByteBuf buffer) {
super.read(buffer);
@ -161,17 +242,21 @@ public class SignalBoundary extends TrackEdgePoint {
public void write(CompoundTag nbt) {
super.write(nbt);
for (int i = 1; i <= 2; i++)
if (!signals.get(i == 1)
if (!blockEntities.get(i == 1)
.isEmpty())
nbt.put("Tiles" + i, NBTHelper.writeCompoundList(signals.get(i == 1), NbtUtils::writeBlockPos));
nbt.put("Tiles" + i, NBTHelper.writeCompoundList(blockEntities.get(i == 1), NbtUtils::writeBlockPos));
for (int i = 1; i <= 2; i++)
if (groups.get(i == 1) != null)
nbt.putUUID("Group" + i, groups.get(i == 1));
for (int i = 1; i <= 2; i++)
if (sidesToUpdate.get(i == 1))
nbt.putBoolean("Update" + i, true);
for (int i = 1; i <= 2; i++)
NBTHelper.writeEnum(nbt, "Type" + i, types.get(i == 1));
for (int i = 1; i <= 2; i++)
NBTHelper.writeEnum(nbt, "State" + i, cachedStates.get(i == 1));
}
@Override
public void write(FriendlyByteBuf buffer) {
super.write(buffer);
@ -183,4 +268,9 @@ public class SignalBoundary extends TrackEdgePoint {
}
}
public void cycleSignalType(BlockPos pos) {
types.set(blockEntities.getFirst()
.contains(pos), SignalType.values()[(getTypeFor(pos).ordinal() + 1) % SignalType.values().length]);
}
}

View file

@ -1,6 +1,7 @@
package com.simibubi.create.content.logistics.trains.management.edgePoint.signal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
@ -23,6 +24,8 @@ 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.phys.Vec3;
public class SignalPropagator {
public static void onSignalRemoved(TrackGraph graph, SignalBoundary signal) {
@ -38,7 +41,7 @@ public class SignalPropagator {
SignalBoundary boundary = pair.getSecond();
boundary.queueUpdate(node1);
return false;
}, Predicates.alwaysFalse());
}, Predicates.alwaysFalse(), false);
}
}
@ -50,7 +53,7 @@ public class SignalPropagator {
SignalBoundary boundary = pair.getSecond();
boundary.queueUpdate(node1);
return false;
}, Predicates.alwaysFalse());
}, Predicates.alwaysFalse(), false);
}
public static void propagateSignalGroup(TrackGraph graph, SignalBoundary signal, boolean front) {
@ -82,11 +85,22 @@ public class SignalPropagator {
signalData.singleSignalGroup = groupId;
return true;
});
}, false);
}
public static Map<UUID, Boolean> collectChainedSignals(TrackGraph graph, SignalBoundary signal, boolean front) {
HashMap<UUID, Boolean> map = new HashMap<>();
walkSignals(graph, signal, front, pair -> {
SignalBoundary boundary = pair.getSecond();
map.put(boundary.id, !boundary.isPrimary(pair.getFirst()));
return false;
}, Predicates.alwaysFalse(), true);
return map;
}
public static void walkSignals(TrackGraph graph, SignalBoundary signal, boolean front,
Predicate<Pair<TrackNode, SignalBoundary>> boundaryCallback, Predicate<EdgeData> nonBoundaryCallback) {
Predicate<Pair<TrackNode, SignalBoundary>> boundaryCallback, Predicate<EdgeData> nonBoundaryCallback,
boolean forCollection) {
Couple<TrackNodeLocation> edgeLocation = signal.edgeLocation;
Couple<TrackNode> startNodes = edgeLocation.map(graph::locateNode);
@ -101,7 +115,8 @@ public class SignalPropagator {
if (startEdge == null)
return;
Create.RAILWAYS.sync.edgeDataChanged(graph, node1, node2, startEdge, oppositeEdge);
if (!forCollection)
Create.RAILWAYS.sync.edgeDataChanged(graph, node1, node2, startEdge, oppositeEdge);
// Check for signal on the same edge
SignalBoundary immediateBoundary = startEdge.getEdgeData()
@ -115,11 +130,12 @@ public class SignalPropagator {
// Search for any connected signals
List<Couple<TrackNode>> frontier = new ArrayList<>();
frontier.add(Couple.create(node2, node1));
walkSignals(graph, frontier, boundaryCallback, nonBoundaryCallback);
walkSignals(graph, frontier, boundaryCallback, nonBoundaryCallback, forCollection);
}
private static void walkSignals(TrackGraph graph, List<Couple<TrackNode>> frontier,
Predicate<Pair<TrackNode, SignalBoundary>> boundaryCallback, Predicate<EdgeData> nonBoundaryCallback) {
Predicate<Pair<TrackNode, SignalBoundary>> boundaryCallback, Predicate<EdgeData> nonBoundaryCallback,
boolean forCollection) {
Set<TrackEdge> visited = new HashSet<>();
while (!frontier.isEmpty()) {
Couple<TrackNode> couple = frontier.remove(0);
@ -138,6 +154,16 @@ public class SignalPropagator {
if (!visited.add(edge))
continue;
// chain signal: check if reachable
if (forCollection) {
Vec3 currentDirection = graph.getConnectionsFrom(prevNode)
.get(currentNode)
.getDirection(prevNode, currentNode, false);
Vec3 newDirection = edge.getDirection(currentNode, nextNode, true);
if (currentDirection.dot(newDirection) < 3 / 4f)
continue;
}
TrackEdge oppositeEdge = graph.getConnectionsFrom(nextNode)
.get(currentNode);
visited.add(oppositeEdge);

View file

@ -6,6 +6,7 @@ import javax.annotation.Nullable;
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.edgePoint.signal.SignalBlock.SignalType;
import com.simibubi.create.foundation.tileEntity.SmartTileEntity;
import com.simibubi.create.foundation.tileEntity.TileEntityBehaviour;
import com.simibubi.create.foundation.utility.NBTHelper;
@ -25,19 +26,23 @@ public class SignalTileEntity extends SmartTileEntity {
}
public static enum SignalState {
RED, GREEN, INVALID, TRAIN_ENTERING;
RED, YELLOW, GREEN, INVALID;
public boolean isRedLight(float renderTime) {
return this == RED || this == INVALID && renderTime % 40 < 3;
}
public boolean isGreenLight(float renderTime) {
return this == GREEN || this == TRAIN_ENTERING;
public boolean isYellowLight(float renderTime) {
return this == YELLOW;
}
public boolean isGreenLight(float renderTime) {
return this == GREEN;
}
}
public TrackTargetingBehaviour<SignalBoundary> edgePoint;
private SignalState state;
private OverlayState overlay;
private int switchToRedAfterTrainEntered;
@ -67,7 +72,7 @@ public class SignalTileEntity extends SmartTileEntity {
public SignalBoundary getSignal() {
return edgePoint.getEdgePoint();
}
public boolean isPowered() {
return state == SignalState.RED;
}
@ -94,12 +99,23 @@ public class SignalTileEntity extends SmartTileEntity {
super.tick();
if (level.isClientSide)
return;
SignalBoundary boundary = getSignal();
if (boundary == null) {
enterState(SignalState.INVALID);
setOverlay(OverlayState.RENDER);
return;
}
getBlockState().getOptionalValue(SignalBlock.TYPE)
.ifPresent(stateType -> {
SignalType targetType = boundary.getTypeFor(worldPosition);
if (stateType != targetType) {
level.setBlock(worldPosition, getBlockState().setValue(SignalBlock.TYPE, targetType), 3);
refreshBlockState();
}
});
enterState(boundary.getStateFor(worldPosition));
setOverlay(boundary.getOverlayFor(worldPosition));
}
@ -127,7 +143,7 @@ public class SignalTileEntity extends SmartTileEntity {
if (state == SignalState.RED && switchToRedAfterTrainEntered > 0)
return;
this.state = state;
switchToRedAfterTrainEntered = state == SignalState.GREEN ? 15 : 0;
switchToRedAfterTrainEntered = state == SignalState.GREEN || state == SignalState.YELLOW ? 15 : 0;
notifyUpdate();
scheduleBlockTick();
}

View file

@ -4,6 +4,7 @@ import net.minecraft.core.BlockPos;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NbtUtils;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.block.entity.BlockEntity;
public abstract class SingleTileEdgePoint extends TrackEdgePoint {
@ -14,8 +15,8 @@ public abstract class SingleTileEdgePoint extends TrackEdgePoint {
}
@Override
public void tileAdded(BlockPos tilePos, boolean front) {
this.tilePos = tilePos;
public void tileAdded(BlockEntity tile, boolean front) {
this.tilePos = tile.getBlockPos();
}
@Override

View file

@ -18,6 +18,7 @@ import net.minecraft.nbt.NbtUtils;
import net.minecraft.nbt.Tag;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.block.entity.BlockEntity;
public abstract class TrackEdgePoint {
@ -59,7 +60,7 @@ public abstract class TrackEdgePoint {
behaviour.invalidateEdgePoint(migrationData);
}
public abstract void tileAdded(BlockPos tilePos, boolean front);
public abstract void tileAdded(BlockEntity tile, boolean front);
public abstract void tileRemoved(BlockPos tilePos, boolean front);
@ -112,7 +113,7 @@ public abstract class TrackEdgePoint {
buffer.writeDouble(position);
}
public void tick(TrackGraph graph) {}
public void tick(TrackGraph graph, boolean preTrains) {}
protected void removeFromAllGraphs() {
for (TrackGraph trackGraph : Create.RAILWAYS.trackNetworks.values())

View file

@ -496,7 +496,7 @@ public class StationTileEntity extends SmartTileEntity {
}
train.collectInitiallyOccupiedSignalBlocks();
Create.RAILWAYS.trains.put(train.id, train);
Create.RAILWAYS.addTrain(train);
AllPackets.channel.send(PacketDistributor.ALL.noArg(), new TrainPacket(train, true));
clearException();
}

View file

@ -648,6 +648,10 @@
"create.train.relocate.invalid": "Cannot relocate Train to here",
"create.train.relocate.too_far": "Cannot relocate Train this far away",
"create.track_signal.cannot_change_mode": "Unable to switch mode of this Signal",
"create.track_signal.mode_change.entry_signal": "-> Allow passage if section unoccupied",
"create.track_signal.mode_change.cross_signal": "-> Allow passage if section fully traversable",
"create.contraption.controls.start_controlling": "Now controlling: %1$s",
"create.contraption.controls.stop_controlling": "Stopped controlling contraption",
"create.contraption.controls.approach_station": "Hold %1$s to approach %2$s",

View file

@ -0,0 +1,85 @@
{
"credit": "Made with Blockbench",
"parent": "block/block",
"textures": {
"0": "create:block/chain_signal_box",
"1": "create:block/chain_signal_box_top",
"particle": "create:block/chain_signal_box"
},
"elements": [
{
"from": [0, 14, 0],
"to": [16, 16, 16],
"faces": {
"north": {"uv": [0, 0, 16, 2], "texture": "#0"},
"east": {"uv": [0, 0, 16, 2], "texture": "#0"},
"south": {"uv": [0, 0, 16, 2], "texture": "#0"},
"west": {"uv": [0, 0, 16, 2], "texture": "#0"},
"up": {"uv": [0, 0, 16, 16], "texture": "#1"},
"down": {"uv": [0, 0, 16, 16], "texture": "#1"}
}
},
{
"from": [0, 0, 0],
"to": [16, 2, 16],
"faces": {
"north": {"uv": [0, 14, 16, 16], "texture": "#0"},
"east": {"uv": [0, 14, 16, 16], "texture": "#0"},
"south": {"uv": [0, 14, 16, 16], "texture": "#0"},
"west": {"uv": [0, 14, 16, 16], "texture": "#0"},
"up": {"uv": [0, 0, 16, 16], "texture": "#1"},
"down": {"uv": [0, 0, 16, 16], "texture": "#1"}
}
},
{
"from": [1, 2, 1],
"to": [15, 14, 15],
"faces": {
"north": {"uv": [1, 2, 15, 14], "texture": "#0"},
"east": {"uv": [1, 2, 15, 14], "texture": "#0"},
"south": {"uv": [1, 2, 15, 14], "texture": "#0"},
"west": {"uv": [1, 2, 15, 14], "texture": "#0"}
}
},
{
"from": [13, 2, 13],
"to": [16, 14, 16],
"faces": {
"north": {"uv": [13, 2, 16, 14], "texture": "#0"},
"east": {"uv": [0, 2, 3, 14], "texture": "#0"},
"south": {"uv": [13, 2, 16, 14], "texture": "#0"},
"west": {"uv": [0, 2, 3, 14], "texture": "#0"}
}
},
{
"from": [0, 2, 0],
"to": [3, 14, 3],
"faces": {
"north": {"uv": [13, 2, 16, 14], "texture": "#0"},
"east": {"uv": [0, 2, 3, 14], "texture": "#0"},
"south": {"uv": [13, 2, 16, 14], "texture": "#0"},
"west": {"uv": [0, 2, 3, 14], "texture": "#0"}
}
},
{
"from": [13, 2, 0],
"to": [16, 14, 3],
"faces": {
"north": {"uv": [0, 2, 3, 14], "texture": "#0"},
"east": {"uv": [13, 2, 16, 14], "texture": "#0"},
"south": {"uv": [0, 2, 3, 14], "texture": "#0"},
"west": {"uv": [13, 2, 16, 14], "texture": "#0"}
}
},
{
"from": [0, 2, 13],
"to": [3, 14, 16],
"faces": {
"north": {"uv": [0, 2, 3, 14], "texture": "#0"},
"east": {"uv": [13, 2, 16, 14], "texture": "#0"},
"south": {"uv": [0, 2, 3, 14], "texture": "#0"},
"west": {"uv": [13, 2, 16, 14], "texture": "#0"}
}
}
]
}

View file

@ -0,0 +1,21 @@
{
"credit": "Made with Blockbench",
"textures": {
"0": "create:block/signal_glow_2",
"particle": "create:block/signal_glow_2"
},
"elements": [
{
"from": [-0.5, -0.5, -0.5],
"to": [0.5, 0.5, 0.5],
"faces": {
"north": {"uv": [1, 1, 2, 2], "texture": "#0"},
"east": {"uv": [1, 1, 2, 2], "texture": "#0"},
"south": {"uv": [1, 1, 2, 2], "texture": "#0"},
"west": {"uv": [1, 1, 2, 2], "texture": "#0"},
"up": {"uv": [1, 1, 2, 2], "texture": "#0"},
"down": {"uv": [1, 1, 2, 2], "texture": "#0"}
}
}
]
}

View file

@ -0,0 +1,21 @@
{
"credit": "Made with Blockbench",
"textures": {
"0": "create:block/signal_glow_2",
"particle": "create:block/signal_glow_2"
},
"elements": [
{
"from": [-0.5, -0.5, -0.5],
"to": [0.5, 0.5, 0.5],
"faces": {
"north": {"uv": [1, 0, 2, 1], "texture": "#0"},
"east": {"uv": [1, 0, 2, 1], "texture": "#0"},
"south": {"uv": [1, 0, 2, 1], "texture": "#0"},
"west": {"uv": [1, 0, 2, 1], "texture": "#0"},
"up": {"uv": [1, 0, 2, 1], "texture": "#0"},
"down": {"uv": [1, 0, 2, 1], "texture": "#0"}
}
}
]
}

View file

@ -0,0 +1,39 @@
{
"credit": "Made with Blockbench",
"parent": "block/block",
"ambientocclusion": false,
"textures": {
"2": "create:block/signal_glow_2",
"particle": "create:block/signal_glow_2"
},
"elements": [
{
"name": "tube3",
"from": [-3, -4.5, -3],
"to": [3, 4.5, 3],
"rotation": {"angle": 0, "axis": "z", "origin": [8, 8, 8]},
"faces": {
"north": {"uv": [10, 7, 16, 16], "texture": "#2"},
"east": {"uv": [10, 7, 16, 16], "texture": "#2"},
"south": {"uv": [10, 7, 16, 16], "texture": "#2"},
"west": {"uv": [10, 7, 16, 16], "texture": "#2"},
"up": {"uv": [10, 0, 16, 6], "rotation": 90, "texture": "#2"},
"down": {"uv": [10, 0, 16, 6], "rotation": 90, "texture": "#2"}
}
}
],
"groups": [
{
"name": "group",
"origin": [17, 14, 13],
"color": 0,
"children": []
},
{
"name": "group",
"origin": [17, 14, 13],
"color": 0,
"children": [0]
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 393 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 340 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 303 B

After

Width:  |  Height:  |  Size: 323 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 469 B