diff --git a/README.md b/README.md index 57209af7..48a5a986 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ -# Create Railways Navigator (Minecraft Create Mod Addon) -![Logo](https://github.com/MisterJulsen/Create-Train-Navigator/blob/1.19.2/icon_256px.png) +# 🚅 Create Railways Navigator (Minecraft Create Mod Addon) +![Logo](https://github.com/user-attachments/assets/2ee05cf6-c816-4dd7-8c24-6bb05062758e) Get a list possible train connections in your world from one station to another using the Create Railways Navigator. @@ -7,22 +7,32 @@ Get a list possible train connections in your world from one station to another [![Modrinth](https://i.imgur.com/uLIB4gb.png)](https://modrinth.com/mod/create-railways-navigator) [![CurseForge](https://i.imgur.com/XZYlGVF.png)](https://www.curseforge.com/minecraft/mc-mods/create-railways-navigator) -## Dependencies +## 📚 Dependencies This mod requires the Minecraft [Create Mod v0.5.1e](https://www.curseforge.com/minecraft/mc-mods/create) or newer for [Minecraft Forge](https://files.minecraftforge.net) or later. This mod also uses [DragonLib](https://www.curseforge.com/minecraft/mc-mods/dragonlib), which is already embedded into the built jar, so you don't have to install it manually. -## Features +## ✅ Features This mod adds a new item, the Create Railways Navigator, which is inspired by the [DB Navigator](https://de.wikipedia.org/wiki/DB_Navigator) (an app in Germany where, among other things, you can get possible train connections). Like this app, the navigator in this mod is intended to suggest possible train connections for trains from the Create Mod in your Minecraft world. Various customization options allow you to specify which track stations should be treated as a single station and how your navigation results should be filtered and sorted so that you always receive the best possible route suggestions. -## Supported Languages -- English (100%) -- German (100%) -- Dutch (100%) (by TheSatanicFlame) -- Polish (100%) (by Slasherss) - -## **Please note!** +## đŸ—Łïž Supported Languages +- English +- German +- Dutch (by TheSatanicFlame) +- Polish (by Slasherss) +- Chinese (simplified) (by Mingshuai Zhu, iaddda) +- Saxon (DE) (by PULZ418) +- Bavarian (DE) +- Spanish (by albertosaurio65) +- Russian (by VGamerGroup) +- French (by GeoffreyGx) +- Portuguese (by AlfredoProgramer) +- Korean (by queso-gato1355) +- Swedish (by Geoffrey) + + +## ⚠ **Please note!** To protect your world from damage, you should always create a backup of your world before installing an update of this mod. Alpha versions in particular may contain critical bugs! -## **Dependencies** +## 🐉 **Dependencies** This mod uses **DragonLib** as a library mod that contains useful code shared accross all my mods. DragonLib is embedded in all builds of Create Railways Navigator since version `0.2.0-beta-1.18.2`, so you don't need to install DragonLib manually. If you are developer and are interested in this library you can find more information about it on [GitHub](https://github.com/MisterJulsen/MC-DragonLib "DragonLib on GitHub"). [![DragonLib](https://i.imgur.com/4d8BF5J.png)](https://github.com/MisterJulsen/MC-DragonLib "DragonLib on GitHub") diff --git a/common/src/main/java/de/mrjulsen/crn/CRNPlatformSpecific.java b/common/src/main/java/de/mrjulsen/crn/CRNPlatformSpecific.java index f125577a..5992e2f9 100644 --- a/common/src/main/java/de/mrjulsen/crn/CRNPlatformSpecific.java +++ b/common/src/main/java/de/mrjulsen/crn/CRNPlatformSpecific.java @@ -39,8 +39,10 @@ public static void registerConfig() { throw new AssertionError(); } + @ExpectPlatform public static GlobalStation getStationFromBlockEntity(BlockEntity be) { throw new AssertionError(); } + } diff --git a/common/src/main/java/de/mrjulsen/crn/Constants.java b/common/src/main/java/de/mrjulsen/crn/Constants.java index 6ee63c48..89951cbf 100644 --- a/common/src/main/java/de/mrjulsen/crn/Constants.java +++ b/common/src/main/java/de/mrjulsen/crn/Constants.java @@ -19,8 +19,25 @@ public class Constants { public static final Component TEXT_FALSE = TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".common.false"); public static final Component TEXT_SERVER_ERROR = TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".common.server_error"); public static final Component TEXT_SEARCH = TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".common.search"); - public static final UUID ZERO_UUID = UUID.fromString("00000000-0000-0000-0000-000000000000"); + public static final Component TEXT_HELP = TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".common.help"); + public static final UUID ZERO_UUID = new UUID(0, 0); + public static final int[] DEFAULT_TRAIN_TYPE_COLORS = new int[] { 0xFF393939, 0xFFf0f3f5, 0xFFafb4bb, 0xFF878c96, 0xFF2a7230, 0xFF814997, 0xFF1455c0, 0xFFa9455d, 0xFF55b9e6, 0xFFffd800 }; - public static final int COLOR_ON_TIME = 0x1AEA5F; - public static final int COLOR_DELAYED = 0xFF4242; + public static final int COLOR_ON_TIME = 0xFF1AEA5F; + public static final int COLOR_DELAYED = 0xFFFF4242; + public static final int COLOR_TRAIN_BACKGROUND = 0xFF393939; + + public static final String GITHUB_WIKI = "https://github.com/MisterJulsen/Create-Train-Navigator/wiki/"; + public static final String HELP_PAGE_ADVANCED_DISPLAYS = GITHUB_WIKI + "Advanced-Displays"; + public static final String HELP_PAGE_DYNAMIC_DELAYS = GITHUB_WIKI + "Dynamic-Delays"; + public static final String HELP_PAGE_GLOBAL_SETTINGS = GITHUB_WIKI + "Global-Settings"; + public static final String HELP_PAGE_NAVIGATION_WARNING = GITHUB_WIKI + "Navigation-Warning"; + public static final String HELP_PAGE_SCHEDULE_SECTIONS = GITHUB_WIKI + "Train-Schedule-Sections"; + public static final String HELP_PAGE_SCHEDULED_TIMES_AND_REAL_TIME = GITHUB_WIKI + "Scheduled-Time-and-Real-Time"; + public static final String HELP_PAGE_STATION_BLACKLIST = GITHUB_WIKI + "Station-Blacklist"; + public static final String HELP_PAGE_STATION_TAGS = GITHUB_WIKI + "Station-Tags"; + public static final String HELP_PAGE_TRAIN_BLACKLIST = GITHUB_WIKI + "Train-Blacklist"; + public static final String HELP_PAGE_TRAIN_GROUPS = GITHUB_WIKI + "Train-Groups"; + public static final String HELP_PAGE_TRAIN_INITIALIZATION = GITHUB_WIKI + "Train-Imnitialization"; + public static final String HELP_PAGE_TRAIN_LINES = GITHUB_WIKI + "Train-Lines"; } diff --git a/common/src/main/java/de/mrjulsen/crn/CreateRailwaysNavigator.java b/common/src/main/java/de/mrjulsen/crn/CreateRailwaysNavigator.java index 94629602..0aaca184 100644 --- a/common/src/main/java/de/mrjulsen/crn/CreateRailwaysNavigator.java +++ b/common/src/main/java/de/mrjulsen/crn/CreateRailwaysNavigator.java @@ -8,33 +8,23 @@ import com.simibubi.create.foundation.item.TooltipHelper.Palette; import de.mrjulsen.crn.block.AdvancedDisplayBlock; -import de.mrjulsen.crn.event.ClientEvents; -import de.mrjulsen.crn.event.ModEvents; +import de.mrjulsen.crn.event.CRNClientEventsRegistryEvent; +import de.mrjulsen.crn.event.CRNEventsManager; +import de.mrjulsen.crn.event.ModClientEvents; +import de.mrjulsen.crn.event.ModCommonEvents; import de.mrjulsen.crn.network.packets.cts.AdvancedDisplayUpdatePacket; -import de.mrjulsen.crn.network.packets.cts.GlobalSettingsRequestPacket; -import de.mrjulsen.crn.network.packets.cts.GlobalSettingsUpdatePacket; -import de.mrjulsen.crn.network.packets.cts.NavigationRequestPacket; -import de.mrjulsen.crn.network.packets.cts.NearestStationRequestPacket; -import de.mrjulsen.crn.network.packets.cts.NextConnectionsRequestPacket; -import de.mrjulsen.crn.network.packets.cts.RealtimeRequestPacket; -import de.mrjulsen.crn.network.packets.cts.TrackStationsRequestPacket; -import de.mrjulsen.crn.network.packets.cts.TrainDataRequestPacket; -import de.mrjulsen.crn.network.packets.stc.GlobalSettingsResponsePacket; -import de.mrjulsen.crn.network.packets.stc.NavigationResponsePacket; -import de.mrjulsen.crn.network.packets.stc.NearestStationResponsePacket; -import de.mrjulsen.crn.network.packets.stc.NextConnectionsResponsePacket; -import de.mrjulsen.crn.network.packets.stc.RealtimeResponsePacket; import de.mrjulsen.crn.network.packets.stc.ServerErrorPacket; -import de.mrjulsen.crn.network.packets.stc.TimeCorrectionPacket; -import de.mrjulsen.crn.network.packets.stc.TrackStationResponsePacket; -import de.mrjulsen.crn.network.packets.stc.TrainDataResponsePacket; +import de.mrjulsen.crn.registry.ModAccessorTypes; import de.mrjulsen.crn.registry.ModBlockEntities; import de.mrjulsen.crn.registry.ModBlocks; +import de.mrjulsen.crn.registry.ModDisplayTypes; import de.mrjulsen.crn.registry.ModExtras; import de.mrjulsen.crn.registry.ModItems; +import de.mrjulsen.crn.registry.ModSchedule; +import de.mrjulsen.crn.registry.ModTrainStatusInfos; import de.mrjulsen.mcdragonlib.net.NetworkManagerBase; import dev.architectury.platform.Platform; -import dev.architectury.utils.Env; +import net.fabricmc.api.EnvType; import net.minecraft.world.item.BlockItem; import net.minecraft.world.item.Item; import net.minecraft.world.level.block.Block; @@ -47,7 +37,11 @@ public final class CreateRailwaysNavigator { public static final String MOD_ID = "createrailwaysnavigator"; + public static final String SHORT_MOD_ID = "crn"; public static final Logger LOGGER = LogUtils.getLogger(); + + public static final String DISCORD = "https://discord.gg/hH7YxTrPpk"; + public static final String GITHUB = "https://github.com/MisterJulsen/Create-Train-Navigator"; public static final CreateRegistrate REGISTRATE = CreateRegistrate.create(MOD_ID); @@ -69,52 +63,48 @@ public static KineticStats create(Item item) { return null; } - - private static NetworkManagerBase crnNet; + + public static void load() {} public static void init() { - ModBlocks.register(); - ModItems.register(); - ModBlockEntities.register(); - ModExtras.register(); + ModBlocks.init(); + ModItems.init(); + ModBlockEntities.init(); + ModExtras.init(); + ModSchedule.init(); + ModAccessorTypes.init(); + ModTrainStatusInfos.init(); + ModDisplayTypes.init(); crnNet = new NetworkManagerBase(MOD_ID, "crn_network", List.of( // cts - GlobalSettingsRequestPacket.class, - GlobalSettingsUpdatePacket.class, - NavigationRequestPacket.class, - NearestStationRequestPacket.class, - NextConnectionsRequestPacket.class, - RealtimeRequestPacket.class, - TrackStationsRequestPacket.class, - TrainDataRequestPacket.class, AdvancedDisplayUpdatePacket.class, // stc - GlobalSettingsResponsePacket.class, - NavigationResponsePacket.class, - NearestStationResponsePacket.class, - NextConnectionsResponsePacket.class, - RealtimeResponsePacket.class, - ServerErrorPacket.class, - TrackStationResponsePacket.class, - TrainDataResponsePacket.class, - TimeCorrectionPacket.class + ServerErrorPacket.class )); CRNPlatformSpecific.registerConfig(); - ModEvents.init(); - if (Platform.getEnvironment() == Env.CLIENT) { - ClientEvents.init(); + ModCommonEvents.init(); + if (Platform.getEnv() == EnvType.CLIENT) { + ModClientEvents.init(); } + + CRNEventsManager.getEvent(CRNClientEventsRegistryEvent.class).register(MOD_ID, () -> { + }); + } public static NetworkManagerBase net() { return crnNet; } + + public static boolean isDebug() { + return Platform.isDevelopmentEnvironment(); + } } diff --git a/common/src/main/java/de/mrjulsen/crn/block/AbstractAdvancedDisplayBlock.java b/common/src/main/java/de/mrjulsen/crn/block/AbstractAdvancedDisplayBlock.java index 5a70faf5..3b44a902 100644 --- a/common/src/main/java/de/mrjulsen/crn/block/AbstractAdvancedDisplayBlock.java +++ b/common/src/main/java/de/mrjulsen/crn/block/AbstractAdvancedDisplayBlock.java @@ -8,10 +8,10 @@ import com.simibubi.create.foundation.block.IBE; import com.simibubi.create.foundation.utility.Iterate; -import de.mrjulsen.crn.block.be.AdvancedDisplayBlockEntity; +import de.mrjulsen.crn.block.blockentity.AdvancedDisplayBlockEntity; +import de.mrjulsen.crn.block.blockentity.AdvancedDisplayBlockEntity.EUpdateReason; import de.mrjulsen.crn.client.ClientWrapper; import de.mrjulsen.crn.registry.ModBlockEntities; -import de.mrjulsen.mcdragonlib.client.ber.IBlockEntityRendererInstance.EUpdateReason; import de.mrjulsen.mcdragonlib.data.Pair; import de.mrjulsen.mcdragonlib.data.Tripple; import net.minecraft.core.BlockPos; @@ -46,6 +46,7 @@ import net.minecraft.world.level.block.state.properties.BooleanProperty; import net.minecraft.world.level.block.state.properties.DirectionProperty; import net.minecraft.world.level.block.state.properties.Property; +import net.minecraft.world.level.material.MaterialColor; import net.minecraft.world.phys.BlockHitResult; import net.minecraft.world.ticks.LevelTickAccess; @@ -57,7 +58,7 @@ public abstract class AbstractAdvancedDisplayBlock extends Block implements IWre public static final BooleanProperty DOWN = BooleanProperty.create("down"); public AbstractAdvancedDisplayBlock(Properties properties) { - super(properties); + super(properties.color(MaterialColor.METAL)); this.registerDefaultState(this.stateDefinition.any() .setValue(UP, false) @@ -157,7 +158,7 @@ public void onPlace(BlockState pState, Level pLevel, BlockPos pPos, BlockState p updateNeighbours(pState, pLevel, pPos); if (pLevel.isClientSide) { - withBlockEntityDo(pLevel, pPos, be -> be.getController().getRenderer().update(pLevel, pPos, pState, be, EUpdateReason.BLOCK_CHANGED)); + withBlockEntityDo(pLevel, pPos, be -> be.getController().getRenderer().update(pLevel, pPos, pState, be, EUpdateReason.LAYOUT_CHANGED)); } } @@ -312,7 +313,7 @@ public InteractionResult use(BlockState pState, Level pLevel, BlockPos pPos, Pla }); if (pLevel.isClientSide) { - blockEntity.getRenderer().update(pLevel, pPos, pState, blockEntity, EUpdateReason.BLOCK_CHANGED); + blockEntity.getRenderer().update(pLevel, pPos, pState, blockEntity, EUpdateReason.LAYOUT_CHANGED); } return InteractionResult.SUCCESS; @@ -327,7 +328,7 @@ public InteractionResult use(BlockState pState, Level pLevel, BlockPos pPos, Pla }); if (pLevel.isClientSide) { - blockEntity.getRenderer().update(pLevel, pPos, pState, blockEntity, EUpdateReason.BLOCK_CHANGED); + blockEntity.getRenderer().update(pLevel, pPos, pState, blockEntity, EUpdateReason.LAYOUT_CHANGED); } return InteractionResult.SUCCESS; diff --git a/common/src/main/java/de/mrjulsen/crn/block/AbstractAdvancedSidedDisplayBlock.java b/common/src/main/java/de/mrjulsen/crn/block/AbstractAdvancedSidedDisplayBlock.java index 1e2967c8..66866580 100644 --- a/common/src/main/java/de/mrjulsen/crn/block/AbstractAdvancedSidedDisplayBlock.java +++ b/common/src/main/java/de/mrjulsen/crn/block/AbstractAdvancedSidedDisplayBlock.java @@ -1,6 +1,6 @@ package de.mrjulsen.crn.block; -import de.mrjulsen.crn.data.ESide; +import de.mrjulsen.crn.block.properties.ESide; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.world.item.context.BlockPlaceContext; diff --git a/common/src/main/java/de/mrjulsen/crn/block/AdvancedDisplayBlock.java b/common/src/main/java/de/mrjulsen/crn/block/AdvancedDisplayBlock.java index b7ae7966..12bc4e81 100644 --- a/common/src/main/java/de/mrjulsen/crn/block/AdvancedDisplayBlock.java +++ b/common/src/main/java/de/mrjulsen/crn/block/AdvancedDisplayBlock.java @@ -32,6 +32,7 @@ public Tripple getRenderRotation(Level level, BlockState bl return Tripple.of(0.0F, 0.0F, 0.0F); } + @Override public boolean isSingleLined() { return false; diff --git a/common/src/main/java/de/mrjulsen/crn/block/AdvancedDisplayBoardBlock.java b/common/src/main/java/de/mrjulsen/crn/block/AdvancedDisplayBoardBlock.java index cd0111ad..e3b8945b 100644 --- a/common/src/main/java/de/mrjulsen/crn/block/AdvancedDisplayBoardBlock.java +++ b/common/src/main/java/de/mrjulsen/crn/block/AdvancedDisplayBoardBlock.java @@ -5,7 +5,7 @@ import java.util.Map; import java.util.Objects; -import de.mrjulsen.crn.data.EBlockAlignment; +import de.mrjulsen.crn.block.properties.EBlockAlignment; import de.mrjulsen.mcdragonlib.data.Pair; import de.mrjulsen.mcdragonlib.data.Tripple; import net.minecraft.core.BlockPos; diff --git a/common/src/main/java/de/mrjulsen/crn/block/AdvancedDisplayHalfPanelBlock.java b/common/src/main/java/de/mrjulsen/crn/block/AdvancedDisplayHalfPanelBlock.java index d7ec9408..079bb8e5 100644 --- a/common/src/main/java/de/mrjulsen/crn/block/AdvancedDisplayHalfPanelBlock.java +++ b/common/src/main/java/de/mrjulsen/crn/block/AdvancedDisplayHalfPanelBlock.java @@ -5,7 +5,7 @@ import java.util.Map; import java.util.Objects; -import de.mrjulsen.crn.data.EBlockAlignment; +import de.mrjulsen.crn.block.properties.EBlockAlignment; import de.mrjulsen.mcdragonlib.data.Pair; import de.mrjulsen.mcdragonlib.data.Tripple; import net.minecraft.core.BlockPos; @@ -82,7 +82,7 @@ public class AdvancedDisplayHalfPanelBlock extends AbstractAdvancedSidedDisplayB public AdvancedDisplayHalfPanelBlock(Properties properties) { super(properties); - registerDefaultState(defaultBlockState() + registerDefaultState(defaultBlockState() .setValue(Y_ALIGN, EBlockAlignment.CENTER) .setValue(Z_ALIGN, EBlockAlignment.CENTER) ); diff --git a/common/src/main/java/de/mrjulsen/crn/block/AdvancedDisplayPanelBlock.java b/common/src/main/java/de/mrjulsen/crn/block/AdvancedDisplayPanelBlock.java index ac2094ef..db351e01 100644 --- a/common/src/main/java/de/mrjulsen/crn/block/AdvancedDisplayPanelBlock.java +++ b/common/src/main/java/de/mrjulsen/crn/block/AdvancedDisplayPanelBlock.java @@ -5,7 +5,7 @@ import java.util.Map; import java.util.Objects; -import de.mrjulsen.crn.data.EBlockAlignment; +import de.mrjulsen.crn.block.properties.EBlockAlignment; import de.mrjulsen.mcdragonlib.data.Pair; import de.mrjulsen.mcdragonlib.data.Tripple; import net.minecraft.core.BlockPos; diff --git a/common/src/main/java/de/mrjulsen/crn/block/AdvancedDisplaySlabBlock.java b/common/src/main/java/de/mrjulsen/crn/block/AdvancedDisplaySlabBlock.java new file mode 100644 index 00000000..9449ad8a --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/block/AdvancedDisplaySlabBlock.java @@ -0,0 +1,166 @@ +package de.mrjulsen.crn.block; + +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import de.mrjulsen.crn.block.properties.EBlockAlignment; +import de.mrjulsen.mcdragonlib.data.Pair; +import de.mrjulsen.mcdragonlib.data.Tripple; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.world.item.context.BlockPlaceContext; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.LevelAccessor; +import net.minecraft.world.level.block.Block; +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; +import net.minecraft.world.level.block.state.properties.Property; +import net.minecraft.world.phys.shapes.CollisionContext; +import net.minecraft.world.phys.shapes.VoxelShape; + +public class AdvancedDisplaySlabBlock extends AbstractAdvancedSidedDisplayBlock { + + public static final EnumProperty Y_ALIGN = EnumProperty.create("y_alignment", EBlockAlignment.class); + + private static final Map SHAPES = Map.ofEntries( + Map.entry(new ShapeKey(Direction.SOUTH, EBlockAlignment.NEGATIVE), Block.box(0 , 0 , 0 , 16, 8 , 16)), + Map.entry(new ShapeKey(Direction.NORTH, EBlockAlignment.NEGATIVE), Block.box(0 , 0 , 0 , 16, 8 , 16)), + Map.entry(new ShapeKey(Direction.EAST, EBlockAlignment.NEGATIVE), Block.box(0 , 0 , 0 , 16, 8 , 16)), + Map.entry(new ShapeKey(Direction.WEST, EBlockAlignment.NEGATIVE), Block.box(0 , 0 , 0 , 16, 8 , 16)), + + Map.entry(new ShapeKey(Direction.SOUTH, EBlockAlignment.CENTER), Block.box(0 , 4 , 0 , 16, 12, 16)), + Map.entry(new ShapeKey(Direction.NORTH, EBlockAlignment.CENTER), Block.box(0 , 4 , 0 , 16, 12, 16)), + Map.entry(new ShapeKey(Direction.EAST, EBlockAlignment.CENTER), Block.box(0 , 4 , 0 , 16, 12, 16)), + Map.entry(new ShapeKey(Direction.WEST, EBlockAlignment.CENTER), Block.box(0 , 4 , 0 , 16, 12, 16)), + + Map.entry(new ShapeKey(Direction.SOUTH, EBlockAlignment.POSITIVE), Block.box(0 , 8 , 0 , 16, 16, 16)), + Map.entry(new ShapeKey(Direction.NORTH, EBlockAlignment.POSITIVE), Block.box(0 , 8 , 0 , 16, 16, 16)), + Map.entry(new ShapeKey(Direction.EAST, EBlockAlignment.POSITIVE), Block.box(0 , 8 , 0 , 16, 16, 16)), + Map.entry(new ShapeKey(Direction.WEST, EBlockAlignment.POSITIVE), Block.box(0 , 8 , 0 , 16, 16, 16)) + ); + + public AdvancedDisplaySlabBlock(Properties properties) { + super(properties); + registerDefaultState(defaultBlockState() + .setValue(Y_ALIGN, EBlockAlignment.CENTER) + ); + } + + @Override + public Collection> getExcludedProperties() { + return List.of(Y_ALIGN); + } + + @Override + public VoxelShape getShape(BlockState pState, BlockGetter pLevel, BlockPos pPos, CollisionContext pContext) { + return SHAPES.get(new ShapeKey(pState.getValue(FACING), pState.getValue(Y_ALIGN))); + } + + @Override + public BlockState getDefaultPlacementState(BlockPlaceContext context, BlockState state, BlockState other) { + BlockState stateForPlacement = super.getDefaultPlacementState(context, state, other); + Direction direction = context.getClickedFace(); + + EBlockAlignment yAlign = EBlockAlignment.CENTER; + + if (direction == Direction.UP || (context.getClickLocation().y - context.getClickedPos().getY() < 0.33333333D)) { + yAlign = EBlockAlignment.NEGATIVE; + } else if (direction == Direction.DOWN || (context.getClickLocation().y - context.getClickedPos().getY() > 0.66666666D)) { + yAlign = EBlockAlignment.POSITIVE; + } + + return stateForPlacement + .setValue(Y_ALIGN, yAlign) + ; + } + + @Override + public BlockState appendOnPlace(BlockPlaceContext context, BlockState state, BlockState other) { + return super.appendOnPlace(context, state, other) + .setValue(Y_ALIGN, other.getValue(Y_ALIGN)) + ; + } + + @Override + protected void createBlockStateDefinition(Builder pBuilder) { + super.createBlockStateDefinition(pBuilder.add(Y_ALIGN)); + } + + @Override + public boolean canConnectWithBlock(BlockGetter level, BlockState selfState, BlockState otherState) { + return super.canConnectWithBlock(level, selfState, otherState) && + selfState.getValue(Y_ALIGN) == otherState.getValue(Y_ALIGN) + ; + } + + @Override + protected boolean canConnect(LevelAccessor level, BlockPos pos, BlockState state, BlockState other) { + return super.canConnect(level, pos, state, other) && + state.getValue(Y_ALIGN) == other.getValue(Y_ALIGN) + ; + } + + @Override + public Pair getRenderAspectRatio(Level level, BlockState blockState, BlockPos pos) { + return Pair.of(1.0F, 0.5F); + } + + @Override + public Pair getRenderOffset(Level level, BlockState blockState, BlockPos pos) { + float y; + switch (blockState.getValue(Y_ALIGN)) { + case NEGATIVE: + y = 8.0f; + break; + case POSITIVE: + y = 0.0f; + break; + default: + y = 4.0f; + break; + } + return Pair.of(0.0f, y); + } + + @Override + public Pair getRenderZOffset(Level level, BlockState blockState, BlockPos pos) { + return Pair.of(16.05f, 16.05f); + } + + @Override + public Tripple getRenderRotation(Level level, BlockState blockState, BlockPos pos) { + return Tripple.of(0.0F, 0.0F, 0.0F); + } + + @Override + public boolean isSingleLined() { + return true; + } + + private static final class ShapeKey { + private final Direction facing; + private final EBlockAlignment yAlign; + + public ShapeKey(Direction facing, EBlockAlignment yAlign) { + this.facing = facing; + this.yAlign = yAlign; + } + + @Override + public boolean equals(Object o) { + if (o instanceof ShapeKey other) { + return facing == other.facing && yAlign == other.yAlign; + } + return false; + } + + @Override + public int hashCode() { + return Objects.hash(facing, yAlign); + } + } +} diff --git a/common/src/main/java/de/mrjulsen/crn/block/AdvancedDisplaySmallBlock.java b/common/src/main/java/de/mrjulsen/crn/block/AdvancedDisplaySmallBlock.java index b44d397e..33558e7f 100644 --- a/common/src/main/java/de/mrjulsen/crn/block/AdvancedDisplaySmallBlock.java +++ b/common/src/main/java/de/mrjulsen/crn/block/AdvancedDisplaySmallBlock.java @@ -5,7 +5,7 @@ import java.util.Map; import java.util.Objects; -import de.mrjulsen.crn.data.EBlockAlignment; +import de.mrjulsen.crn.block.properties.EBlockAlignment; import de.mrjulsen.mcdragonlib.data.Pair; import de.mrjulsen.mcdragonlib.data.Tripple; import net.minecraft.core.BlockPos; diff --git a/common/src/main/java/de/mrjulsen/crn/block/TrainStationClockBlock.java b/common/src/main/java/de/mrjulsen/crn/block/TrainStationClockBlock.java index d0726a36..2f2ee6e6 100644 --- a/common/src/main/java/de/mrjulsen/crn/block/TrainStationClockBlock.java +++ b/common/src/main/java/de/mrjulsen/crn/block/TrainStationClockBlock.java @@ -3,11 +3,10 @@ import com.simibubi.create.content.equipment.wrench.IWrenchable; import com.simibubi.create.foundation.block.IBE; -import de.mrjulsen.crn.block.be.TrainStationClockBlockEntity; +import de.mrjulsen.crn.block.blockentity.TrainStationClockBlockEntity; import de.mrjulsen.crn.config.ModClientConfig; import de.mrjulsen.crn.registry.ModBlockEntities; import de.mrjulsen.mcdragonlib.DragonLib; -import de.mrjulsen.mcdragonlib.client.ber.IBlockEntityRendererInstance.EUpdateReason; import de.mrjulsen.mcdragonlib.util.TextUtils; import de.mrjulsen.mcdragonlib.util.TimeUtils; import net.minecraft.core.BlockPos; @@ -65,7 +64,7 @@ public InteractionResult use(BlockState pState, Level pLevel, BlockPos pPos, Pla blockEntity.setColor(dye == DyeColor.ORANGE ? 0xFF9900 : dye.getMaterialColor().col); if (pLevel.isClientSide) { - blockEntity.getRenderer().update(pLevel, pPos, pState, blockEntity, EUpdateReason.BLOCK_CHANGED); + blockEntity.getRenderer().update(pLevel, pPos, pState, blockEntity, null); } return InteractionResult.SUCCESS; @@ -77,7 +76,7 @@ public InteractionResult use(BlockState pState, Level pLevel, BlockPos pPos, Pla blockEntity.setGlowing(true); if (pLevel.isClientSide) { - blockEntity.getRenderer().update(pLevel, pPos, pState, blockEntity, EUpdateReason.BLOCK_CHANGED); + blockEntity.getRenderer().update(pLevel, pPos, pState, blockEntity, null); } return InteractionResult.SUCCESS; diff --git a/common/src/main/java/de/mrjulsen/crn/block/be/AdvancedDisplayBlockEntity.java b/common/src/main/java/de/mrjulsen/crn/block/blockentity/AdvancedDisplayBlockEntity.java similarity index 70% rename from common/src/main/java/de/mrjulsen/crn/block/be/AdvancedDisplayBlockEntity.java rename to common/src/main/java/de/mrjulsen/crn/block/blockentity/AdvancedDisplayBlockEntity.java index 0c94b00d..458f4f6d 100644 --- a/common/src/main/java/de/mrjulsen/crn/block/be/AdvancedDisplayBlockEntity.java +++ b/common/src/main/java/de/mrjulsen/crn/block/blockentity/AdvancedDisplayBlockEntity.java @@ -1,7 +1,6 @@ -package de.mrjulsen.crn.block.be; +package de.mrjulsen.crn.block.blockentity; import java.util.ArrayList; -import java.util.Comparator; import java.util.List; import com.simibubi.create.content.trains.display.FlapDisplayBlock; @@ -11,34 +10,36 @@ import com.simibubi.create.foundation.blockEntity.behaviour.BlockEntityBehaviour; import de.mrjulsen.crn.Constants; -import de.mrjulsen.crn.CreateRailwaysNavigator; import de.mrjulsen.crn.block.AbstractAdvancedDisplayBlock; import de.mrjulsen.crn.block.display.AdvancedDisplaySource.ETimeDisplay; +import de.mrjulsen.crn.block.properties.EDisplayInfo; +import de.mrjulsen.crn.block.properties.EDisplayType; +import de.mrjulsen.crn.block.properties.EDisplayType.EDisplayTypeDataSource; +import de.mrjulsen.crn.client.AdvancedDisplaysRegistry; +import de.mrjulsen.crn.client.AdvancedDisplaysRegistry.DisplayTypeInfo; +import de.mrjulsen.crn.client.AdvancedDisplaysRegistry.DisplayTypeResourceKey; import de.mrjulsen.crn.client.ber.AdvancedDisplayRenderInstance; import de.mrjulsen.crn.data.CarriageData; -import de.mrjulsen.crn.data.EDisplayInfo; -import de.mrjulsen.crn.data.EDisplayType; -import de.mrjulsen.crn.data.IBlockEntitySerializable; -import de.mrjulsen.crn.data.DeparturePrediction.TrainExitSide; -import de.mrjulsen.crn.data.EDisplayType.EDisplayTypeDataSource; -import de.mrjulsen.crn.data.TrainStationAlias.StationInfo; -import de.mrjulsen.crn.data.DeparturePrediction.SimpleDeparturePrediction; -import de.mrjulsen.crn.network.InstanceManager; -import de.mrjulsen.crn.network.packets.cts.TrainDataRequestPacket; -import de.mrjulsen.crn.network.packets.cts.TrainDataRequestPacket.TrainData; +import de.mrjulsen.crn.data.TrainExitSide; +import de.mrjulsen.crn.data.StationTag.StationInfo; +import de.mrjulsen.crn.data.train.portable.StationDisplayData; +import de.mrjulsen.crn.data.train.portable.TrainDisplayData; +import de.mrjulsen.crn.data.train.portable.TrainStopDisplayData; +import de.mrjulsen.crn.registry.ModAccessorTypes; +import de.mrjulsen.crn.registry.ModDisplayTypes; import de.mrjulsen.mcdragonlib.block.IBERInstance; import de.mrjulsen.mcdragonlib.client.ber.IBlockEntityRendererInstance; -import de.mrjulsen.mcdragonlib.client.ber.IBlockEntityRendererInstance.EUpdateReason; import de.mrjulsen.mcdragonlib.data.Cache; import de.mrjulsen.mcdragonlib.data.Pair; import de.mrjulsen.mcdragonlib.data.Tripple; +import de.mrjulsen.mcdragonlib.util.ListUtils; +import de.mrjulsen.mcdragonlib.util.accessor.DataAccessor; import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos.MutableBlockPos; import net.minecraft.core.Direction; import net.minecraft.core.Vec3i; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.ListTag; -import net.minecraft.nbt.StringTag; import net.minecraft.nbt.Tag; import net.minecraft.network.Connection; import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket; @@ -55,7 +56,6 @@ public class AdvancedDisplayBlockEntity extends SmartBlockEntity implements IMultiblockBlockEntity, IContraptionBlockEntity, IBERInstance, - IBlockEntitySerializable, IColorableBlockEntity { private static final String NBT_XSIZE = "XSize"; @@ -63,15 +63,15 @@ public class AdvancedDisplayBlockEntity extends SmartBlockEntity implements private static final String NBT_CONTROLLER = "IsController"; private static final String NBT_COLOR = "Color"; private static final String NBT_GLOWING = "Glowing"; - private static final String NBT_INFO_TYPE = "InfoType"; - private static final String NBT_DISPLAY_TYPE = "DisplayType"; - private static final String NBT_PREDICTIONS = "Predictions"; - private static final String NBT_NEXT_DEPARTURE_STOPOVERS = "NextStopovers"; + private static final String NBT_DISPLAY_TYPE_KEY = "DisplayTypeKey"; private static final String NBT_FILTER = "Filter"; private static final String NBT_LAST_REFRESH_TIME = "LastRefreshed"; private static final String NBT_PLATFORM_WIDTH = "PlatformWidth"; private static final String NBT_TRAIN_NAME_WIDTH = "TrainNameWidth"; private static final String NBT_TIME_DISPLAY = "TimeDisplay"; + private static final String NBT_TRAIN_STOPS = "TrainStops"; + @Deprecated private static final String LEGACY_NBT_INFO_TYPE = "InfoType"; + @Deprecated private static final String LEGACY_NBT_DISPLAY_TYPE = "DisplayType"; public static final byte MAX_XSIZE = 16; public static final byte MAX_YSIZE = 16; @@ -82,8 +82,8 @@ public class AdvancedDisplayBlockEntity extends SmartBlockEntity implements private byte xSize = 1; private byte ySize = 1; private boolean isController; - private List predictions; - private List nextDepartureStopovers; + private List predictions; + private boolean dataOrderChanged = false; private String stationNameFilter; private StationInfo stationInfo; private byte trainNameWidth; @@ -93,13 +93,12 @@ public class AdvancedDisplayBlockEntity extends SmartBlockEntity implements // USER SETTINGS private int color = DyeColor.WHITE.getTextColor(); private boolean glowing = false; - private EDisplayInfo infoType = EDisplayInfo.SIMPLE; - private EDisplayType displayType = EDisplayType.TRAIN_DESTINATION; + private DisplayTypeResourceKey displayTypeKey = ModDisplayTypes.TRAIN_DESTINATION_SIMPLE; // CLIENT DISPLAY ONLY - this data is not saved! private long lastRefreshedTime; - private TrainData trainData = TrainData.empty(false); + private TrainDisplayData trainData = TrainDisplayData.empty(); private CarriageData carriageData = new CarriageData(0, Direction.NORTH, false); // OTHER @@ -110,7 +109,7 @@ public class AdvancedDisplayBlockEntity extends SmartBlockEntity implements if (getCarriageData() == null || !getTrainData().getNextStop().isPresent() || !(getBlockState().getBlock() instanceof AbstractAdvancedDisplayBlock)) { return TrainExitSide.UNKNOWN; } - TrainExitSide side = getTrainData().getNextStop().get().exitSide(); + TrainExitSide side = getTrainData().getNextStopExitSide(); Direction blockFacing = getBlockState().getValue(HorizontalDirectionalBlock.FACING); if (!carriageData.isOppositeDirection()) { blockFacing = blockFacing.getOpposite(); @@ -166,7 +165,7 @@ public AdvancedDisplayBlockEntity(BlockEntityType type, BlockPos pos, BlockSt reset(); } - public TrainData getTrainData() { + public TrainDisplayData getTrainData() { return trainData; } @@ -219,13 +218,9 @@ public int getColor() { public boolean isGlowing() { return glowing; } - - public EDisplayInfo getInfoType() { - return infoType; - } - public EDisplayType getDisplayType() { - return displayType; + public DisplayTypeResourceKey getDisplayTypeKey() { + return displayTypeKey; } @Override @@ -258,19 +253,15 @@ public Class getBlockEntityType() { return AdvancedDisplayBlockEntity.class; } - public List getPredictions() { + public List getStops() { return predictions; } - public List getNextDepartureStopovers() { - return nextDepartureStopovers; - } - public boolean isPlatformFixed() { return !stationNameFilter.contains("*"); } - public StationInfo getStationInfo() { + public StationInfo getStationInfo2() { return stationInfo; } @@ -280,31 +271,19 @@ public String getStationNameFilter() { public boolean isSingleLine() { if (getBlockState().getBlock() instanceof AbstractAdvancedDisplayBlock block) { - return block.isSingleLined() || !( - (getDisplayType() == EDisplayType.PASSENGER_INFORMATION && getInfoType() == EDisplayInfo.INFORMATIVE) || - (getDisplayType() == EDisplayType.PLATFORM && getInfoType() == EDisplayInfo.DETAILED) || - (getDisplayType() == EDisplayType.PLATFORM && getInfoType() == EDisplayInfo.INFORMATIVE) - ); + return block.isSingleLined() || AdvancedDisplaysRegistry.getInfo(displayTypeKey).singleLined(); } return false; } - public int getPlatformInfoLinesCount() { - switch (getInfoType()) { - default: - case SIMPLE: - return 32; - case DETAILED: - return getYSize() * 3 - 1; - case INFORMATIVE: - return getYSize() * 3 - 2; - } + public DisplayTypeInfo getDisplayTypeInfo() { + return AdvancedDisplaysRegistry.getInfo(displayTypeKey); } public void setColor(int color) { this.color = color; if (level.isClientSide) { - getRenderer().update(level, worldPosition, getBlockState(), this, EUpdateReason.BLOCK_CHANGED); + getRenderer().update(level, worldPosition, getBlockState(), this, EUpdateReason.LAYOUT_CHANGED); } } @@ -312,34 +291,29 @@ public void setColor(int color) { public void setGlowing(boolean glowing) { this.glowing = glowing; if (level.isClientSide) { - getRenderer().update(level, worldPosition, getBlockState(), this, EUpdateReason.BLOCK_CHANGED); + getRenderer().update(level, worldPosition, getBlockState(), this, EUpdateReason.LAYOUT_CHANGED); } } - public void setInfoType(EDisplayInfo type) { - this.infoType = type; + public void setDisplayTypeKey(DisplayTypeResourceKey key) { + this.displayTypeKey = key; if (level.isClientSide) { - getRenderer().update(level, worldPosition, getBlockState(), this, EUpdateReason.BLOCK_CHANGED); + getRenderer().update(level, worldPosition, getBlockState(), this, EUpdateReason.LAYOUT_CHANGED); } } - public void setDisplayType(EDisplayType type) { - this.displayType = type; - if (level.isClientSide) { - getRenderer().update(level, worldPosition, getBlockState(), this, EUpdateReason.BLOCK_CHANGED); - } - } - - public void setDepartureData(List predictions, List nextDepartureStopovers, String stationNameFilter, StationInfo staionInfo, long lastRefreshedTime, byte platformWidth, byte trainNameWidth, byte timeDisplayId) { - this.predictions = predictions.stream().sorted(Comparator.comparingInt(x -> x.departureTicks())).toList(); + public void setDepartureData(List predictions, String stationNameFilter, StationInfo staionInfo, long lastRefreshedTime, byte platformWidth, byte trainNameWidth, byte timeDisplayId) { + this.dataOrderChanged = dataOrderChanged || !ListUtils.compareCollections(this.predictions, predictions, StationDisplayData::equals); + + this.predictions = predictions; this.stationNameFilter = stationNameFilter; this.stationInfo = staionInfo; - this.nextDepartureStopovers = nextDepartureStopovers; this.lastRefreshedTime = lastRefreshedTime; this.platformWidth = platformWidth; this.trainNameWidth = trainNameWidth; this.timeDisplay = ETimeDisplay.getById(timeDisplayId); + } @Override @@ -350,8 +324,7 @@ public boolean connectable(BlockGetter getter, BlockPos a, BlockPos b) { if (getter.getBlockEntity(a) instanceof AdvancedDisplayBlockEntity be1 && getter.getBlockEntity(b) instanceof AdvancedDisplayBlockEntity be2 && be1.getBlockState().getBlock() instanceof AbstractAdvancedDisplayBlock block1 && be2.getBlockState().getBlock() instanceof AbstractAdvancedDisplayBlock block2) { return block1 == block2 && - be1.getDisplayType() == be2.getDisplayType() && - be1.getInfoType() == be2.getInfoType() && + be1.getDisplayTypeKey().equals(be2.getDisplayTypeKey()) && block1.canConnectWithBlock(getter, getter.getBlockState(a), getter.getBlockState(b)) && block2.canConnectWithBlock(getter, getter.getBlockState(b), getter.getBlockState(a)) && (!a.above().equals(b) || (be1.getBlockState().getValue(AbstractAdvancedDisplayBlock.UP) && !be1.isSingleLine())) && (!a.below().equals(b) || (be1.getBlockState().getValue(AbstractAdvancedDisplayBlock.DOWN) && !be1.isSingleLine())) @@ -402,8 +375,7 @@ public AdvancedDisplayBlockEntity getController() { public void copyFrom(AdvancedDisplayBlockEntity other) { if (getColor() == other.getColor() && - getInfoType() == other.getInfoType() && - getDisplayType() == other.getDisplayType() && + getDisplayTypeKey().equals(other.getDisplayTypeKey()) && isGlowing() == other.isGlowing() ) { return; @@ -411,17 +383,17 @@ public void copyFrom(AdvancedDisplayBlockEntity other) { color = other.getColor(); glowing = other.isGlowing(); - displayType = other.getDisplayType(); - infoType = other.getInfoType(); + displayTypeKey = other.getDisplayTypeKey(); notifyUpdate(); } public void reset() { + dataOrderChanged = true; + predictions = List.of(); - nextDepartureStopovers = List.of(); stationNameFilter = ""; platformWidth = -1; - trainNameWidth = 16; + trainNameWidth = 14; xSize = 1; ySize = 1; isController = false; @@ -495,17 +467,17 @@ public void tick() { super.tick(); - if (getDisplayType().getSource() != EDisplayTypeDataSource.PLATFORM) { + if (getDisplayTypeKey().category().getSource() != EDisplayTypeDataSource.PLATFORM) { return; } - syncTicks++; + syncTicks++; if ((syncTicks %= REFRESH_FREQUENCY) == 0) { - if (level.isClientSide) { - boolean shouldUpdate = getPredictions().size() > 0; - + if (level.isClientSide) { + boolean shouldUpdate = getStops().size() > 0 || dataOrderChanged; if (shouldUpdate) { - getRenderer().update(level, getBlockPos(), getBlockState(), this, EUpdateReason.DATA_CHANGED); + getRenderer().update(level, getBlockPos(), getBlockState(), this, dataOrderChanged ? EUpdateReason.LAYOUT_CHANGED : EUpdateReason.DATA_CHANGED); + dataOrderChanged = false; } } } @@ -525,41 +497,42 @@ public void contraptionTick(Level level, BlockPos pos, BlockState state, Carriag return; } - if (getDisplayType().getSource() != EDisplayTypeDataSource.TRAIN_INFORMATION) { + if (getDisplayTypeKey().category().getSource() != EDisplayTypeDataSource.TRAIN_INFORMATION) { return; } syncTicks++; - if ((syncTicks %= 100) == 0) { - long id = InstanceManager.registerClientTrainDataResponseAction((data, refreshTime) -> { - if (data == null) { + if ((syncTicks %= 100) == 0 && level.isClientSide) { + DataAccessor.getFromServer(((CarriageContraptionEntity)carriage.entity).trainId, ModAccessorTypes.GET_TRAIN_DISPLAY_DATA, (data) -> { + if (data.isEmpty() && this.trainData.isEmpty()) { return; } + boolean shouldUpdate = false; - if (trainData != null && trainData.getNextStop().isPresent() && data.getNextStop().isPresent()) { - SimpleDeparturePrediction prediction = trainData.getNextStop().get(); - - shouldUpdate = !trainData.trainName().equals(data.trainName()) || - !prediction.scheduleTitle().equals(data.predictions().get(0).scheduleTitle()) || - !prediction.stationTagName().equals(data.predictions().get(0).stationTagName()) || - trainData.getNextStop().get().exitSide() != data.getNextStop().get().exitSide() || - (getInfoType() == EDisplayInfo.INFORMATIVE && getDisplayType() == EDisplayType.PASSENGER_INFORMATION && trainData.getNextStop().get().departureTicks() + lastRefreshedTime != data.getNextStop().get().departureTicks() + refreshTime) // It's not clean but it works ... for now + if (this.trainData != null && this.trainData.getNextStop().isPresent() && data.getNextStop().isPresent()) { + TrainStopDisplayData prediction = this.trainData.getNextStop().get(); + + shouldUpdate = !this.trainData.getTrainData().getName().equals(data.getTrainData().getName()) || + !prediction.getDestination().equals(data.getNextStop().get().getDestination()) || + prediction.getStationEntryIndex() != data.getNextStop().get().getStationEntryIndex() || + this.trainData.getNextStopExitSide() != data.getNextStopExitSide() || + this.trainData.isWaitingAtStation() != data.isWaitingAtStation() + //(getInfoType() == EDisplayInfo.INFORMATIVE && getDisplayType() == EDisplayType.PASSENGER_INFORMATION && trainData.getNextStop().get().departureTicks() + lastRefreshedTime != data.getNextStop().get().departureTicks() + refreshTime) // It's not clean but it works ... for now ; } - boolean oos = trainData != null && !trainData.trainId().equals(Constants.ZERO_UUID) && !data.getNextStop().isPresent(); - if (oos) { + boolean outOfService = this.trainData != null && !this.trainData.getTrainData().getId().equals(Constants.ZERO_UUID) && !data.getNextStop().isPresent(); + if (outOfService) { shouldUpdate = true; } - this.lastRefreshedTime = refreshTime; - this.trainData = oos ? TrainData.empty(true) : data; + //this.lastRefreshedTime = refreshTime; + this.trainData = outOfService ? TrainDisplayData.empty() : data; this.carriageData = new CarriageData(((CarriageContraptionEntity)carriage.entity).carriageIndex, carriage.getAssemblyDirection(), data.isOppositeDirection()); this.relativeExitDirection.clear(); if (shouldUpdate) { - getRenderer().update(level, pos, state, this, EUpdateReason.DATA_CHANGED); } + getRenderer().update(level, pos, state, this, shouldUpdate ? EUpdateReason.LAYOUT_CHANGED : EUpdateReason.DATA_CHANGED); }); - CreateRailwaysNavigator.net().CHANNEL.sendToServer(new TrainDataRequestPacket(id, ((CarriageContraptionEntity)carriage.entity).trainId, true)); } } @@ -570,8 +543,7 @@ protected void write(CompoundTag pTag, boolean clientPacket) { pTag.putByte(NBT_YSIZE, getYSize()); pTag.putInt(NBT_COLOR, getColor()); pTag.putBoolean(NBT_CONTROLLER, isController()); - pTag.putInt(NBT_INFO_TYPE, getInfoType().getId()); - pTag.putInt(NBT_DISPLAY_TYPE, getDisplayType().getId()); + pTag.put(NBT_DISPLAY_TYPE_KEY, getDisplayTypeKey().toNbt()); pTag.putString(NBT_FILTER, getStationNameFilter()); pTag.putBoolean(NBT_GLOWING, isGlowing()); pTag.putLong(NBT_LAST_REFRESH_TIME, getLastRefreshedTime()); @@ -579,21 +551,16 @@ protected void write(CompoundTag pTag, boolean clientPacket) { pTag.putByte(NBT_TRAIN_NAME_WIDTH, getTrainNameWidth()); pTag.putByte(NBT_TIME_DISPLAY, getTimeDisplay().getId()); - getStationInfo().writeNbt(pTag); + getStationInfo2().writeNbt(pTag); - if (getPredictions() != null && !getPredictions().isEmpty()) { + if (getStops() != null && !getStops().isEmpty()) { ListTag list = new ListTag(); - list.addAll(getPredictions().stream().map(x -> x.toNbt()).toList()); - pTag.put(NBT_PREDICTIONS, list); - } - - if (getNextDepartureStopovers() != null && !getNextDepartureStopovers().isEmpty()) { - ListTag stopovers = new ListTag(); - stopovers.addAll(getNextDepartureStopovers().stream().map(x -> StringTag.valueOf(x)).toList()); - pTag.put(NBT_NEXT_DEPARTURE_STOPOVERS, stopovers); + list.addAll(getStops().stream().map(x -> x.toNbt()).toList()); + pTag.put(NBT_TRAIN_STOPS, list); } } + @SuppressWarnings("deprecation") @Override public void read(CompoundTag pTag, boolean clientPacket) { boolean updateClient = false; @@ -604,7 +571,7 @@ public void read(CompoundTag pTag, boolean clientPacket) { getYSize() != pTag.getByte(NBT_YSIZE) || getPlatformWidth() != pTag.getByte(NBT_PLATFORM_WIDTH) || getTrainNameWidth() != pTag.getByte(NBT_TRAIN_NAME_WIDTH) || - (getPredictions().isEmpty() ^ !pTag.contains(NBT_PREDICTIONS)) + (getStops().isEmpty() ^ !pTag.contains(NBT_TRAIN_STOPS)) ) { updateClient = true; } @@ -621,11 +588,13 @@ public void read(CompoundTag pTag, boolean clientPacket) { } glowing = pTag.getBoolean(NBT_GLOWING); isController = pTag.getBoolean(NBT_CONTROLLER); - infoType = EDisplayInfo.getTypeById(pTag.getInt(NBT_INFO_TYPE)); - displayType = EDisplayType.getTypeById(pTag.getInt(NBT_DISPLAY_TYPE)); + if (pTag.contains(LEGACY_NBT_INFO_TYPE) && pTag.contains(LEGACY_NBT_DISPLAY_TYPE)) { + displayTypeKey = ModDisplayTypes.legacy_getKeyForType(EDisplayType.getTypeById(pTag.getInt(LEGACY_NBT_DISPLAY_TYPE)), EDisplayInfo.getTypeById(pTag.getInt(LEGACY_NBT_INFO_TYPE))); + } else { + displayTypeKey = DisplayTypeResourceKey.fromNbt(pTag.getCompound(NBT_DISPLAY_TYPE_KEY)); + } setDepartureData( - pTag.contains(NBT_PREDICTIONS) ? new ArrayList<>(pTag.getList(NBT_PREDICTIONS, Tag.TAG_COMPOUND).stream().map(x -> SimpleDeparturePrediction.fromNbt((CompoundTag)x)).toList()) : new ArrayList<>(), - pTag.contains(NBT_NEXT_DEPARTURE_STOPOVERS) ? pTag.getList(NBT_NEXT_DEPARTURE_STOPOVERS, Tag.TAG_STRING).stream().map(x -> ((StringTag)x).getAsString()).toList() : new ArrayList<>(), + pTag.contains(NBT_TRAIN_STOPS) ? new ArrayList<>(pTag.getList(NBT_TRAIN_STOPS, Tag.TAG_COMPOUND).stream().map(x -> StationDisplayData.fromNbt((CompoundTag)x)).toList()) : new ArrayList<>(), pTag.getString(NBT_FILTER), info, pTag.getLong(NBT_LAST_REFRESH_TIME), @@ -635,22 +604,8 @@ public void read(CompoundTag pTag, boolean clientPacket) { ); if (updateClient) { - getRenderer().update(level, worldPosition, getBlockState(), this, EUpdateReason.BLOCK_CHANGED); + getRenderer().update(level, worldPosition, getBlockState(), this, EUpdateReason.LAYOUT_CHANGED); } - } - - @Override - public CompoundTag serialize() { - CompoundTag nbt = new CompoundTag(); - nbt.putInt(NBT_INFO_TYPE, getInfoType().getId()); - nbt.putInt(NBT_DISPLAY_TYPE, getDisplayType().getId()); - return nbt; - } - - @Override - public void deserialize(CompoundTag nbt) { - infoType = EDisplayInfo.getTypeById(nbt.getInt(NBT_INFO_TYPE)); - displayType = EDisplayType.getTypeById(nbt.getInt(NBT_DISPLAY_TYPE)); } @Override @@ -701,4 +656,9 @@ public void onDataPacket(Connection net, ClientboundBlockEntityDataPacket pkt) { this.level.sendBlockUpdated(this.worldPosition, this.getBlockState(), this.getBlockState(), 512); } + public static enum EUpdateReason { + LAYOUT_CHANGED, + DATA_CHANGED + } + } \ No newline at end of file diff --git a/common/src/main/java/de/mrjulsen/crn/block/be/IColorableBlockEntity.java b/common/src/main/java/de/mrjulsen/crn/block/blockentity/IColorableBlockEntity.java similarity index 68% rename from common/src/main/java/de/mrjulsen/crn/block/be/IColorableBlockEntity.java rename to common/src/main/java/de/mrjulsen/crn/block/blockentity/IColorableBlockEntity.java index 17b5aaad..3bdc918a 100644 --- a/common/src/main/java/de/mrjulsen/crn/block/be/IColorableBlockEntity.java +++ b/common/src/main/java/de/mrjulsen/crn/block/blockentity/IColorableBlockEntity.java @@ -1,4 +1,4 @@ -package de.mrjulsen.crn.block.be; +package de.mrjulsen.crn.block.blockentity; public interface IColorableBlockEntity { int getColor(); diff --git a/common/src/main/java/de/mrjulsen/crn/block/be/IContraptionBlockEntity.java b/common/src/main/java/de/mrjulsen/crn/block/blockentity/IContraptionBlockEntity.java similarity index 92% rename from common/src/main/java/de/mrjulsen/crn/block/be/IContraptionBlockEntity.java rename to common/src/main/java/de/mrjulsen/crn/block/blockentity/IContraptionBlockEntity.java index 0137773a..3a2b9d3d 100644 --- a/common/src/main/java/de/mrjulsen/crn/block/be/IContraptionBlockEntity.java +++ b/common/src/main/java/de/mrjulsen/crn/block/blockentity/IContraptionBlockEntity.java @@ -1,4 +1,4 @@ -package de.mrjulsen.crn.block.be; +package de.mrjulsen.crn.block.blockentity; import com.simibubi.create.content.trains.entity.CarriageContraption; diff --git a/common/src/main/java/de/mrjulsen/crn/block/be/IMultiblockBlockEntity.java b/common/src/main/java/de/mrjulsen/crn/block/blockentity/IMultiblockBlockEntity.java similarity index 98% rename from common/src/main/java/de/mrjulsen/crn/block/be/IMultiblockBlockEntity.java rename to common/src/main/java/de/mrjulsen/crn/block/blockentity/IMultiblockBlockEntity.java index 2561dc4a..0d77cf9b 100644 --- a/common/src/main/java/de/mrjulsen/crn/block/be/IMultiblockBlockEntity.java +++ b/common/src/main/java/de/mrjulsen/crn/block/blockentity/IMultiblockBlockEntity.java @@ -1,4 +1,4 @@ -package de.mrjulsen.crn.block.be; +package de.mrjulsen.crn.block.blockentity; import java.util.function.Consumer; diff --git a/common/src/main/java/de/mrjulsen/crn/block/be/TrainStationClockBlockEntity.java b/common/src/main/java/de/mrjulsen/crn/block/blockentity/TrainStationClockBlockEntity.java similarity index 98% rename from common/src/main/java/de/mrjulsen/crn/block/be/TrainStationClockBlockEntity.java rename to common/src/main/java/de/mrjulsen/crn/block/blockentity/TrainStationClockBlockEntity.java index 73f8cebd..c6ddca2d 100644 --- a/common/src/main/java/de/mrjulsen/crn/block/be/TrainStationClockBlockEntity.java +++ b/common/src/main/java/de/mrjulsen/crn/block/blockentity/TrainStationClockBlockEntity.java @@ -1,4 +1,4 @@ -package de.mrjulsen.crn.block.be; +package de.mrjulsen.crn.block.blockentity; import java.util.List; diff --git a/common/src/main/java/de/mrjulsen/crn/block/connected/AdvancedDisplayCTBehaviour.java b/common/src/main/java/de/mrjulsen/crn/block/connected/AdvancedDisplayCTBehaviour.java index c1d2b669..5603fffa 100644 --- a/common/src/main/java/de/mrjulsen/crn/block/connected/AdvancedDisplayCTBehaviour.java +++ b/common/src/main/java/de/mrjulsen/crn/block/connected/AdvancedDisplayCTBehaviour.java @@ -3,7 +3,7 @@ import com.simibubi.create.foundation.block.connected.CTSpriteShiftEntry; import com.simibubi.create.foundation.block.connected.ConnectedTextureBehaviour; -import de.mrjulsen.crn.block.be.AdvancedDisplayBlockEntity; +import de.mrjulsen.crn.block.blockentity.AdvancedDisplayBlockEntity; import net.minecraft.client.renderer.texture.TextureAtlasSprite; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; diff --git a/common/src/main/java/de/mrjulsen/crn/block/connected/AdvancedDisplaySmallCTBehaviour.java b/common/src/main/java/de/mrjulsen/crn/block/connected/AdvancedDisplaySmallCTBehaviour.java index 599ec36a..1a5c4097 100644 --- a/common/src/main/java/de/mrjulsen/crn/block/connected/AdvancedDisplaySmallCTBehaviour.java +++ b/common/src/main/java/de/mrjulsen/crn/block/connected/AdvancedDisplaySmallCTBehaviour.java @@ -3,7 +3,7 @@ import com.simibubi.create.foundation.block.connected.CTSpriteShiftEntry; import com.simibubi.create.foundation.block.connected.ConnectedTextureBehaviour; -import de.mrjulsen.crn.block.be.AdvancedDisplayBlockEntity; +import de.mrjulsen.crn.block.blockentity.AdvancedDisplayBlockEntity; import net.minecraft.client.renderer.texture.TextureAtlasSprite; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; diff --git a/common/src/main/java/de/mrjulsen/crn/block/display/AdvancedDisplaySource.java b/common/src/main/java/de/mrjulsen/crn/block/display/AdvancedDisplaySource.java index 84b858f9..5be02f7c 100644 --- a/common/src/main/java/de/mrjulsen/crn/block/display/AdvancedDisplaySource.java +++ b/common/src/main/java/de/mrjulsen/crn/block/display/AdvancedDisplaySource.java @@ -8,7 +8,6 @@ import com.simibubi.create.content.redstone.displayLink.source.DisplaySource; import com.simibubi.create.content.redstone.displayLink.target.DisplayTargetStats; import com.simibubi.create.content.trains.station.GlobalStation; -import com.simibubi.create.content.trains.station.StationBlockEntity; import com.simibubi.create.foundation.gui.ModularGuiLineBuilder; import com.simibubi.create.foundation.utility.Lang; diff --git a/common/src/main/java/de/mrjulsen/crn/block/display/AdvancedDisplayTarget.java b/common/src/main/java/de/mrjulsen/crn/block/display/AdvancedDisplayTarget.java index e6cb0193..ac4b0efe 100644 --- a/common/src/main/java/de/mrjulsen/crn/block/display/AdvancedDisplayTarget.java +++ b/common/src/main/java/de/mrjulsen/crn/block/display/AdvancedDisplayTarget.java @@ -1,24 +1,21 @@ package de.mrjulsen.crn.block.display; -import java.util.ArrayList; -import java.util.Comparator; -import java.util.HashMap; import java.util.List; +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.TimeUnit; + import com.simibubi.create.content.redstone.displayLink.DisplayLinkContext; import com.simibubi.create.content.redstone.displayLink.target.DisplayBoardTarget; import com.simibubi.create.content.redstone.displayLink.target.DisplayTargetStats; -import com.simibubi.create.content.trains.display.GlobalTrainDisplayData; -import com.simibubi.create.content.trains.display.GlobalTrainDisplayData.TrainDeparturePrediction; -import com.simibubi.create.content.trains.entity.Train; - -import de.mrjulsen.crn.block.be.AdvancedDisplayBlockEntity; -import de.mrjulsen.crn.data.DeparturePrediction; -import de.mrjulsen.crn.data.GlobalSettingsManager; -import de.mrjulsen.crn.data.TrainStop; -import de.mrjulsen.crn.data.DeparturePrediction.SimpleDeparturePrediction; -import de.mrjulsen.crn.data.SimpleTrainSchedule; -import de.mrjulsen.crn.data.SimulatedTrainSchedule; -import de.mrjulsen.crn.util.TrainUtils; + +import de.mrjulsen.crn.CreateRailwaysNavigator; +import de.mrjulsen.crn.block.blockentity.AdvancedDisplayBlockEntity; +import de.mrjulsen.crn.block.properties.EDisplayType.EDisplayTypeDataSource; +import de.mrjulsen.crn.data.storage.GlobalSettings; +import de.mrjulsen.crn.data.train.TrainUtils; +import de.mrjulsen.crn.data.train.portable.StationDisplayData; +import de.mrjulsen.crn.event.ModCommonEvents; import net.minecraft.core.BlockPos; import net.minecraft.core.Vec3i; import net.minecraft.nbt.CompoundTag; @@ -27,7 +24,52 @@ import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.phys.AABB; -public class AdvancedDisplayTarget extends DisplayBoardTarget { +public class AdvancedDisplayTarget extends DisplayBoardTarget { + + private static boolean running = false; + private static boolean threadRunning = false; + private static final Queue workerTasks = new ConcurrentLinkedQueue<>(); + + public static void start() { + if (running) stop(); + while (running && threadRunning) { + try { + TimeUnit.SECONDS.sleep(1); + } catch (InterruptedException e) {} + } + + workerTasks.clear(); + running = true; + new Thread(() -> { + threadRunning = true; + CreateRailwaysNavigator.LOGGER.info("Advanced Display Data Manager has been started."); + + while (running) { + while (!workerTasks.isEmpty()) { + try { + workerTasks.poll().run(); + } catch (Exception e) { + CreateRailwaysNavigator.LOGGER.info("Error while process Advanced Display Data.", e); + } + } + try { + TimeUnit.SECONDS.sleep(1); + } catch (InterruptedException e) {} + } + workerTasks.clear(); + CreateRailwaysNavigator.LOGGER.info("Advanced Display Data Manager has been stopped."); + threadRunning = false; + }, "Advanced Display Data Manager").start(); + } + + public static void stop() { + CreateRailwaysNavigator.LOGGER.info("Stopping Advanced Display Data Manager..."); + running = false; + } + + private static void queueAdvancedDisplayWorkerTask(Runnable task) { + workerTasks.add(task); + } @Override public void acceptFlapText(int line, List> text, DisplayLinkContext context) { @@ -41,58 +83,31 @@ public void acceptFlapText(int line, List> text, DisplayL if (context.getTargetBlockEntity() instanceof AdvancedDisplayBlockEntity blockEntity) { final AdvancedDisplayBlockEntity controller = blockEntity.getController(); - if (controller != null) { - List preds = prepare(filter, controller.getPlatformInfoLinesCount()).stream().map(x -> new DeparturePrediction(x).simplify()).sorted(Comparator.comparingInt(x -> x.departureTicks())).toList(); - List stopovers = new ArrayList<>(); + long dayTime = context.getTargetBlockEntity().getLevel().getDayTime(); - if (!preds.isEmpty()) { - SimpleDeparturePrediction pred = preds.iterator().next(); - Train train = TrainUtils.getTrain(pred.trainId()); + queueAdvancedDisplayWorkerTask(() -> { + if (controller != null & controller.getDisplayTypeKey().category().getSource() == EDisplayTypeDataSource.PLATFORM) { + List preds = prepare(filter, controller.getDisplayTypeInfo().platformDisplayTrainsCount().apply(controller)); - if (train != null) { - SimulatedTrainSchedule sched = SimpleTrainSchedule.of(TrainUtils.getTrainStopsSorted(pred.trainId(), context.blockEntity().getLevel())).simulate(train, 0, pred.stationName()); - - List stops = new ArrayList<>(sched.getAllStops()); - boolean foundStart = false; - - if (!stops.isEmpty()) { - for (int i = 0; i < stops.size() - 1; i++) { - TrainStop x = stops.get(i); - if (foundStart) { - stopovers.add(x.getStationAlias().getAliasName().get()); - } - foundStart = foundStart || x.getPrediction().getStationName().equals(pred.stationName()); - } - } - } + controller.setDepartureData( + preds, + filter, + GlobalSettings.getInstance().getOrCreateStationTagFor(filter).getInfoForStation(filter), + dayTime, + (byte)context.sourceConfig().getInt(AdvancedDisplaySource.NBT_PLATFORM_WIDTH), + (byte)context.sourceConfig().getInt(AdvancedDisplaySource.NBT_TRAIN_NAME_WIDTH), + context.sourceConfig().getByte(AdvancedDisplaySource.NBT_TIME_DISPLAY_TYPE) + ); + if (ModCommonEvents.hasServer()) { + ModCommonEvents.getCurrentServer().get().executeIfPossible(controller::sendData); + } } - - controller.setDepartureData( - preds, - stopovers, - filter, - GlobalSettingsManager.getInstance().getSettingsData().getAliasFor(filter).getInfoForStation(filter), - context.getTargetBlockEntity().getLevel().getDayTime(), - (byte)context.sourceConfig().getInt(AdvancedDisplaySource.NBT_PLATFORM_WIDTH), - (byte)context.sourceConfig().getInt(AdvancedDisplaySource.NBT_TRAIN_NAME_WIDTH), - context.sourceConfig().getByte(AdvancedDisplaySource.NBT_TIME_DISPLAY_TYPE) - ); - controller.sendData(); - } + }); } } - public static List prepare(String filter, int maxLines) { - String regex = filter.isBlank() ? filter : "\\Q" + filter.replace("*", "\\E.*\\Q") + "\\E"; - return new HashMap<>(GlobalTrainDisplayData.statusByDestination).entrySet() - .stream() - .filter(e -> e.getKey() - .matches(regex)) - .flatMap(e -> e.getValue() - .stream()) - .sorted() - .limit(maxLines) - .toList(); + public static List prepare(String filter, int maxLines) { + return TrainUtils.getDeparturesAtStationName(filter, null).stream().limit(maxLines).map(x -> StationDisplayData.of(x)).toList(); } @Override diff --git a/common/src/main/java/de/mrjulsen/crn/data/EBlockAlignment.java b/common/src/main/java/de/mrjulsen/crn/block/properties/EBlockAlignment.java similarity index 95% rename from common/src/main/java/de/mrjulsen/crn/data/EBlockAlignment.java rename to common/src/main/java/de/mrjulsen/crn/block/properties/EBlockAlignment.java index 739026c0..43f22aab 100644 --- a/common/src/main/java/de/mrjulsen/crn/data/EBlockAlignment.java +++ b/common/src/main/java/de/mrjulsen/crn/block/properties/EBlockAlignment.java @@ -1,4 +1,4 @@ -package de.mrjulsen.crn.data; +package de.mrjulsen.crn.block.properties; import java.util.Arrays; diff --git a/common/src/main/java/de/mrjulsen/crn/data/EDisplayInfo.java b/common/src/main/java/de/mrjulsen/crn/block/properties/EDisplayInfo.java similarity index 95% rename from common/src/main/java/de/mrjulsen/crn/data/EDisplayInfo.java rename to common/src/main/java/de/mrjulsen/crn/block/properties/EDisplayInfo.java index 17e79271..49cf7270 100644 --- a/common/src/main/java/de/mrjulsen/crn/data/EDisplayInfo.java +++ b/common/src/main/java/de/mrjulsen/crn/block/properties/EDisplayInfo.java @@ -1,4 +1,4 @@ -package de.mrjulsen.crn.data; +package de.mrjulsen.crn.block.properties; import java.util.Arrays; @@ -6,6 +6,7 @@ import de.mrjulsen.mcdragonlib.core.ITranslatableEnum; import net.minecraft.util.StringRepresentable; +@Deprecated public enum EDisplayInfo implements StringRepresentable, ITranslatableEnum { SIMPLE(0, "simple", ModGuiIcons.LESS_DETAILS), DETAILED(1, "detailed", ModGuiIcons.DETAILED), diff --git a/common/src/main/java/de/mrjulsen/crn/data/EDisplayType.java b/common/src/main/java/de/mrjulsen/crn/block/properties/EDisplayType.java similarity index 66% rename from common/src/main/java/de/mrjulsen/crn/data/EDisplayType.java rename to common/src/main/java/de/mrjulsen/crn/block/properties/EDisplayType.java index 953daf90..2cb9b15b 100644 --- a/common/src/main/java/de/mrjulsen/crn/data/EDisplayType.java +++ b/common/src/main/java/de/mrjulsen/crn/block/properties/EDisplayType.java @@ -1,4 +1,4 @@ -package de.mrjulsen.crn.data; +package de.mrjulsen.crn.block.properties; import java.util.Arrays; @@ -7,16 +7,16 @@ import net.minecraft.util.StringRepresentable; public enum EDisplayType implements StringRepresentable, ITranslatableEnum { - TRAIN_DESTINATION(0, "train_destination", ModGuiIcons.TRAIN_DESTINATION, EDisplayTypeDataSource.TRAIN_INFORMATION), - PASSENGER_INFORMATION(1, "passenger_information", ModGuiIcons.PASSENGER_INFORMATION, EDisplayTypeDataSource.TRAIN_INFORMATION), - PLATFORM(2, "platform", ModGuiIcons.PLATFORM_INFORMATION, EDisplayTypeDataSource.PLATFORM); + TRAIN_DESTINATION((byte)0, "train_destination", ModGuiIcons.TRAIN_DESTINATION, EDisplayTypeDataSource.TRAIN_INFORMATION), + PASSENGER_INFORMATION((byte)1, "passenger_information", ModGuiIcons.PASSENGER_INFORMATION, EDisplayTypeDataSource.TRAIN_INFORMATION), + PLATFORM((byte)2, "platform", ModGuiIcons.PLATFORM_INFORMATION, EDisplayTypeDataSource.PLATFORM); private String name; - private int id; + private byte id; private ModGuiIcons icon; private EDisplayTypeDataSource source; - private EDisplayType(int id, String name, ModGuiIcons icon, EDisplayTypeDataSource source) { + private EDisplayType(byte id, String name, ModGuiIcons icon, EDisplayTypeDataSource source) { this.name = name; this.id = id; this.icon = icon; @@ -27,7 +27,7 @@ public String getInfoTypeName() { return this.name; } - public int getId() { + public byte getId() { return this.id; } @@ -40,7 +40,7 @@ public EDisplayTypeDataSource getSource() { } public static EDisplayType getTypeById(int id) { - return Arrays.stream(values()).filter(x -> x.getId() == id).findFirst().orElse(EDisplayType.TRAIN_DESTINATION); + return Arrays.stream(values()).filter(x -> x.getId() == (byte)id).findFirst().orElse(EDisplayType.TRAIN_DESTINATION); } @Override diff --git a/common/src/main/java/de/mrjulsen/crn/data/ESide.java b/common/src/main/java/de/mrjulsen/crn/block/properties/ESide.java similarity index 95% rename from common/src/main/java/de/mrjulsen/crn/data/ESide.java rename to common/src/main/java/de/mrjulsen/crn/block/properties/ESide.java index ea0d6009..45fed558 100644 --- a/common/src/main/java/de/mrjulsen/crn/data/ESide.java +++ b/common/src/main/java/de/mrjulsen/crn/block/properties/ESide.java @@ -1,4 +1,4 @@ -package de.mrjulsen.crn.data; +package de.mrjulsen.crn.block.properties; import java.util.Arrays; diff --git a/common/src/main/java/de/mrjulsen/crn/client/AdvancedDisplaysRegistry.java b/common/src/main/java/de/mrjulsen/crn/client/AdvancedDisplaysRegistry.java new file mode 100644 index 00000000..fe56c601 --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/client/AdvancedDisplaysRegistry.java @@ -0,0 +1,101 @@ +package de.mrjulsen.crn.client; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +import de.mrjulsen.crn.CreateRailwaysNavigator; +import de.mrjulsen.crn.block.blockentity.AdvancedDisplayBlockEntity; +import de.mrjulsen.crn.block.properties.EDisplayType; +import de.mrjulsen.crn.client.ber.AdvancedDisplayRenderInstance; +import de.mrjulsen.crn.client.ber.IBERRenderSubtype; +import de.mrjulsen.crn.client.ber.variants.BERError; +import de.mrjulsen.mcdragonlib.data.Pair; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.resources.ResourceLocation; + +public final class AdvancedDisplaysRegistry { + public static record DisplayTypeResourceKey(EDisplayType category, ResourceLocation id) { + private static final String NBT_CATEGORY = "Category"; + private static final String NBT_ID = "Id"; + @Override + public final boolean equals(Object arg) { + return arg instanceof DisplayTypeResourceKey o && category == o.category && id.equals(o.id); + } + @Override + public final int hashCode() { + return Objects.hash(category, id); + } + public CompoundTag toNbt() { + CompoundTag nbt = new CompoundTag(); + nbt.putByte(NBT_CATEGORY, category().getId()); + nbt.putString(NBT_ID, id().toString()); + return nbt; + } + public static DisplayTypeResourceKey fromNbt(CompoundTag nbt) { + return new DisplayTypeResourceKey(EDisplayType.getTypeById(nbt.getByte(NBT_CATEGORY)), new ResourceLocation(nbt.getString(NBT_ID))); + } + public String getTranslationKey() { + return "display." + CreateRailwaysNavigator.MOD_ID + "." + category().getEnumValueName() + "." + id.getPath(); + } + } + + /** + * @param singleLined Whether the display can be connected vertically. + * @param platformDisplayTrainsCount For Platform Displays only! Specifies how many trains can be shown on the display, depending on the properties of the display. If used correctly, this reduces network traffic, as data about trains that do not fit on the display are not transferred from the server. + */ + public static record DisplayTypeInfo(boolean singleLined, Function platformDisplayTrainsCount) {} + + private static final Map>, DisplayTypeInfo>>> displayTypes = new HashMap<>(); + + /** + * Registers a new display type that can then be used in CRN. + * @param category The display category to which the type should be assigned. + * @param id The id of the display type. + * @param displayRenderer The reference of the Renderer class that renders the contents of the display. + * @param info Additional information about this display type + * @return + */ + public static DisplayTypeResourceKey registerDisplayType(EDisplayType category, ResourceLocation id, Supplier> displayRenderer, DisplayTypeInfo info) { + Map>, DisplayTypeInfo>> reg = displayTypes.computeIfAbsent(category, x -> new HashMap<>()); + if (reg.containsKey(id)) { + throw new IllegalArgumentException("A display type with the id '" + id + "' is already registered!"); + } + reg.put(id, Pair.of(displayRenderer, info)); + return new DisplayTypeResourceKey(category, id); + } + + public static boolean isRegietered(DisplayTypeResourceKey key) { + return key != null && displayTypes.containsKey(key.category()) && displayTypes.get(key.category()).containsKey(key.id()); + } + + public static IBERRenderSubtype getRenderer(DisplayTypeResourceKey key) { + if (!isRegietered(key)) { + return new BERError(); + } + return displayTypes.get(key.category()).get(key.id()).getFirst().get(); + } + + public static DisplayTypeInfo getInfo(DisplayTypeResourceKey key) { + if (!isRegietered(key)) { + return new DisplayTypeInfo(true, $ -> 0); + } + return displayTypes.get(key.category()).get(key.id()).getSecond(); + } + + public static Map getAllOfType(EDisplayType type) { + return displayTypes.get(type).entrySet().stream().collect(Collectors.toMap(a -> a.getKey(), b -> b.getValue().getSecond())); + } + + public static List getAllOfTypeAsKey(EDisplayType type) { + return displayTypes.get(type).entrySet().stream().map(x -> new DisplayTypeResourceKey(type, x.getKey())).toList(); + } + + public static List getAllIdsOfType(EDisplayType type) { + return displayTypes.get(type).entrySet().stream().map(x -> x.getKey()).toList(); + } +} diff --git a/common/src/main/java/de/mrjulsen/crn/client/CRNGui.java b/common/src/main/java/de/mrjulsen/crn/client/CRNGui.java new file mode 100644 index 00000000..c6fe395b --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/client/CRNGui.java @@ -0,0 +1,10 @@ +package de.mrjulsen.crn.client; + +import de.mrjulsen.crn.CreateRailwaysNavigator; +import net.minecraft.resources.ResourceLocation; + +public final class CRNGui { + public static final ResourceLocation GUI = new ResourceLocation(CreateRailwaysNavigator.MOD_ID, "textures/gui/gui.png"); + public static final int GUI_WIDTH = 64; + public static final int GUI_HEIGHT = 64; +} diff --git a/common/src/main/java/de/mrjulsen/crn/client/ClientWrapper.java b/common/src/main/java/de/mrjulsen/crn/client/ClientWrapper.java index ff94d66d..7f3637aa 100644 --- a/common/src/main/java/de/mrjulsen/crn/client/ClientWrapper.java +++ b/common/src/main/java/de/mrjulsen/crn/client/ClientWrapper.java @@ -3,27 +3,31 @@ import java.util.List; import java.util.function.Supplier; +import com.mojang.blaze3d.systems.RenderSystem; + import de.mrjulsen.crn.Constants; import de.mrjulsen.crn.CreateRailwaysNavigator; -import de.mrjulsen.crn.block.be.AdvancedDisplayBlockEntity; -import de.mrjulsen.crn.client.gui.overlay.RouteDetailsOverlayScreen; +import de.mrjulsen.crn.block.blockentity.AdvancedDisplayBlockEntity; +import de.mrjulsen.crn.client.gui.NavigatorToast; import de.mrjulsen.crn.client.gui.screen.AdvancedDisplaySettingsScreen; -import de.mrjulsen.crn.client.gui.screen.LoadingScreen; import de.mrjulsen.crn.client.gui.screen.NavigatorScreen; -import de.mrjulsen.crn.client.gui.screen.RouteOverlaySettingsScreen; +import de.mrjulsen.crn.client.gui.screen.TrainDebugScreen; import de.mrjulsen.crn.client.lang.ELanguage; -import de.mrjulsen.crn.data.ClientTrainStationSnapshot; -import de.mrjulsen.crn.data.GlobalSettingsManager; +import de.mrjulsen.crn.config.ModClientConfig; import de.mrjulsen.crn.network.packets.stc.ServerErrorPacket; import de.mrjulsen.mcdragonlib.client.gui.DLScreen; +import de.mrjulsen.mcdragonlib.client.util.Graphics; import de.mrjulsen.mcdragonlib.util.TextUtils; import dev.architectury.networking.NetworkManager.PacketContext; import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Font; +import net.minecraft.client.gui.components.MultiLineLabel; import net.minecraft.client.gui.components.toasts.SystemToast; import net.minecraft.client.gui.components.toasts.SystemToast.SystemToastIds; import net.minecraft.client.resources.language.ClientLanguage; import net.minecraft.client.resources.language.LanguageInfo; import net.minecraft.locale.Language; +import net.minecraft.network.chat.Component; import net.minecraft.world.level.Level; public class ClientWrapper { @@ -31,17 +35,13 @@ public class ClientWrapper { private static ELanguage currentLanguage; private static Language currentClientLanguage; - public static void showNavigatorGui(Level level) { - DLScreen.setScreen(new LoadingScreen()); - GlobalSettingsManager.syncToClient(() -> { - ClientTrainStationSnapshot.syncToClient(() -> { - DLScreen.setScreen(new NavigatorScreen(level)); - }); - }); + public static void showNavigatorGui() { + DLScreen.setScreen(new NavigatorScreen(null)); } - public static void showRouteOverlaySettingsGui(RouteDetailsOverlayScreen overlay) { - DLScreen.setScreen(new RouteOverlaySettingsScreen(overlay)); + @SuppressWarnings("resource") + public static Level getClientLevel() { + return Minecraft.getInstance().level; } public static void handleErrorMessagePacket(ServerErrorPacket packet, Supplier ctx) { @@ -71,4 +71,28 @@ public static void updateLanguage(ELanguage lang, boolean force) { public static Language getCurrentClientLanguage() { return currentClientLanguage == null ? Language.getInstance() : currentClientLanguage; } + + + public static void sendCRNNotification(Component title, Component description) { + if (ModClientConfig.ROUTE_NOTIFICATIONS.get()) { + Minecraft.getInstance().getToasts().addToast(NavigatorToast.multiline(title, description)); + } + } + + public static int renderMultilineLabelSafe(Graphics graphics, int x, int y, Font font, Component text, int maxWidth, int color) { + MultiLineLabel label = MultiLineLabel.create(font, text, maxWidth); + label.renderLeftAlignedNoShadow(graphics.poseStack(), x, y, font.lineHeight, color); + return font.lineHeight * label.getLineCount(); + } + + public static int getTextBlockHeight(Font font, Component text, int maxWidth) { + int lines = font.split(text, maxWidth).size(); + return lines * font.lineHeight; + } + + public static void showTrainDebugScreen() { + RenderSystem.recordRenderCall(() -> { + DLScreen.setScreen(new TrainDebugScreen(null)); + }); + } } diff --git a/common/src/main/java/de/mrjulsen/crn/client/ModGuiUtils.java b/common/src/main/java/de/mrjulsen/crn/client/ModGuiUtils.java index 0af16142..fc1e2f90 100644 --- a/common/src/main/java/de/mrjulsen/crn/client/ModGuiUtils.java +++ b/common/src/main/java/de/mrjulsen/crn/client/ModGuiUtils.java @@ -58,4 +58,12 @@ public static void endStencil() { GL11.glDisable(GL11.GL_STENCIL_TEST); } + public static boolean useWhiteOrBlackForeColor(int color) { + int red = (color >> 16) & 0xFF; + int green = (color >> 8) & 0xFF; + int blue = color & 0xFF; + + double luminance = (0.299 * red + 0.587 * green + 0.114 * blue) / 255; + return luminance < 0.5; + } } diff --git a/common/src/main/java/de/mrjulsen/crn/client/ber/AdvancedDisplayRenderInstance.java b/common/src/main/java/de/mrjulsen/crn/client/ber/AdvancedDisplayRenderInstance.java index 6c2230f7..dc52bc28 100644 --- a/common/src/main/java/de/mrjulsen/crn/client/ber/AdvancedDisplayRenderInstance.java +++ b/common/src/main/java/de/mrjulsen/crn/client/ber/AdvancedDisplayRenderInstance.java @@ -1,140 +1,72 @@ package de.mrjulsen.crn.client.ber; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.function.Supplier; - -import com.mojang.blaze3d.vertex.PoseStack; import com.mojang.math.Vector3f; import de.mrjulsen.crn.block.AbstractAdvancedDisplayBlock; import de.mrjulsen.crn.block.AbstractAdvancedSidedDisplayBlock; -import de.mrjulsen.crn.block.be.AdvancedDisplayBlockEntity; -import de.mrjulsen.crn.client.ber.base.BERText; -import de.mrjulsen.crn.client.ber.variants.BERPassengerInfoDetailed; -import de.mrjulsen.crn.client.ber.variants.BERPassengerInfoInformative; -import de.mrjulsen.crn.client.ber.variants.BERPassengerInfoSimple; -import de.mrjulsen.crn.client.ber.variants.BERPlatformDetailed; -import de.mrjulsen.crn.client.ber.variants.BERPlatformInformative; -import de.mrjulsen.crn.client.ber.variants.BERPlatformSimple; -import de.mrjulsen.crn.client.ber.variants.BERRenderSubtypeBase; -import de.mrjulsen.crn.client.ber.variants.BERTrainDestinationDetailed; -import de.mrjulsen.crn.client.ber.variants.BERTrainDestinationInformative; -import de.mrjulsen.crn.client.ber.variants.BERTrainDestinationSimple; -import de.mrjulsen.crn.client.ber.variants.IBERRenderSubtype; -import de.mrjulsen.crn.data.EDisplayInfo; -import de.mrjulsen.crn.data.EDisplayType; -import de.mrjulsen.crn.data.ESide; -import de.mrjulsen.crn.data.EDisplayType.EDisplayTypeDataSource; +import de.mrjulsen.crn.block.blockentity.AdvancedDisplayBlockEntity; +import de.mrjulsen.crn.block.blockentity.AdvancedDisplayBlockEntity.EUpdateReason; +import de.mrjulsen.crn.block.properties.ESide; +import de.mrjulsen.crn.client.AdvancedDisplaysRegistry; +import de.mrjulsen.crn.client.AdvancedDisplaysRegistry.DisplayTypeResourceKey; import de.mrjulsen.mcdragonlib.client.ber.AbstractBlockEntityRenderInstance; +import de.mrjulsen.mcdragonlib.client.ber.BERGraphics; import de.mrjulsen.mcdragonlib.data.Pair; import de.mrjulsen.mcdragonlib.data.Tripple; -import de.mrjulsen.mcdragonlib.util.TextUtils; +import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.LightTexture; -import net.minecraft.client.renderer.MultiBufferSource; import net.minecraft.core.BlockPos; -import net.minecraft.network.chat.MutableComponent; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.state.BlockState; public class AdvancedDisplayRenderInstance extends AbstractBlockEntityRenderInstance { - private Map>>> renderSubtypes; - - public Collection labels; - public BERText carriageIndexLabel; public IBERRenderSubtype renderSubtype; - + private DisplayTypeResourceKey lastType; private int lastXSize = 0; - private EDisplayType lastType; - private EDisplayInfo lastInfo; public AdvancedDisplayRenderInstance(AdvancedDisplayBlockEntity blockEntity) { super(blockEntity); } @Override - protected void preinit(AdvancedDisplayBlockEntity blockEntity) { - this.labels = new ArrayList<>(); - this.renderSubtypes = Map.of( - EDisplayType.TRAIN_DESTINATION, Map.of( - EDisplayInfo.SIMPLE, () -> new BERTrainDestinationSimple(), - EDisplayInfo.DETAILED, () -> new BERTrainDestinationDetailed(), - EDisplayInfo.INFORMATIVE, () -> new BERTrainDestinationInformative() - ), - EDisplayType.PASSENGER_INFORMATION, Map.of( - EDisplayInfo.SIMPLE, () -> new BERPassengerInfoSimple(), - EDisplayInfo.DETAILED, () -> new BERPassengerInfoDetailed(), - EDisplayInfo.INFORMATIVE, () -> new BERPassengerInfoInformative() - ), - EDisplayType.PLATFORM, Map.of( - EDisplayInfo.SIMPLE, () -> new BERPlatformSimple(), - EDisplayInfo.DETAILED, () -> new BERPlatformDetailed(), - EDisplayInfo.INFORMATIVE, () -> new BERPlatformInformative() - ) - ); - } - - public MutableComponent getStopoversString(AdvancedDisplayBlockEntity blockEntity) { - MutableComponent line = TextUtils.empty(); - - List stopovers = blockEntity.getDisplayType().getSource() == EDisplayTypeDataSource.TRAIN_INFORMATION ? - blockEntity.getTrainData().stopovers().stream().map(x -> x.stationTagName()).toList() : - blockEntity.getNextDepartureStopovers(); - - Iterator i = stopovers.iterator(); - boolean isFirst = true; - while (i.hasNext()) { - if (!isFirst) { - line = line.append(TextUtils.text(" ● ")); - } - line = line.append(TextUtils.text(i.next())); - isFirst = false; - } - return line; - } - - @Override - public void render(BlockEntityRendererContext context, AdvancedDisplayBlockEntity pBlockEntity, float pPartialTicks, PoseStack pPoseStack, MultiBufferSource pBufferSource, int pPackedLight, int pOverlay) { + public void render(BERGraphics graphics, float partialTick) { - if (!pBlockEntity.isController()) { + if (!graphics.blockEntity().isController()) { return; } - final int light = pBlockEntity.isGlowing() ? LightTexture.FULL_BRIGHT : pPackedLight; - - if (pBlockEntity.getBlockState().getBlock() instanceof AbstractAdvancedDisplayBlock) { - - Tripple rotation = pBlockEntity.renderRotation.get(); - Pair offset = pBlockEntity.renderOffset.get(); - Pair zOffset = pBlockEntity.renderZOffset.get(); - float scale = pBlockEntity.renderScale.get(); + final int light = graphics.blockEntity().isGlowing() ? LightTexture.FULL_BRIGHT : graphics.packedLight(); - pPoseStack.pushPose(); - pPoseStack.translate(offset.getFirst(), offset.getSecond(), zOffset.getFirst()); - pPoseStack.mulPose(Vector3f.XP.rotationDegrees(rotation.getFirst())); - pPoseStack.mulPose(Vector3f.YP.rotationDegrees(rotation.getSecond())); - pPoseStack.mulPose(Vector3f.ZP.rotationDegrees(rotation.getThird())); - pPoseStack.scale(scale, scale, 1); - renderSubtype.renderAdditional(context, pBlockEntity, this, pPartialTicks, pPoseStack, pBufferSource, light, pOverlay, false); - labels.forEach(x -> x.render(pPoseStack, pBufferSource, light)); - pPoseStack.popPose(); + if (graphics.blockEntity().getBlockState().getBlock() instanceof AbstractAdvancedDisplayBlock) { - if (!(pBlockEntity.getBlockState().getBlock() instanceof AbstractAdvancedSidedDisplayBlock) || pBlockEntity.getBlockState().getValue(AbstractAdvancedSidedDisplayBlock.SIDE) == ESide.BOTH) { - pPoseStack.pushPose(); - pPoseStack.mulPose(Vector3f.YP.rotationDegrees(180)); - pPoseStack.translate(-pBlockEntity.getXSize() * 16, 0, -16); - pPoseStack.translate(offset.getFirst(), offset.getSecond(), zOffset.getSecond()); - pPoseStack.mulPose(Vector3f.XP.rotationDegrees(rotation.getFirst())); - pPoseStack.mulPose(Vector3f.YP.rotationDegrees(rotation.getSecond())); - pPoseStack.mulPose(Vector3f.ZP.rotationDegrees(rotation.getThird())); - pPoseStack.scale(scale, scale, 1); - renderSubtype.renderAdditional(context, pBlockEntity, this, pPartialTicks, pPoseStack, pBufferSource, light, pOverlay, true); - labels.forEach(x -> x.render(pPoseStack, pBufferSource, light)); - pPoseStack.popPose(); + renderSubtype.renderTick(Minecraft.getInstance().getDeltaFrameTime()); + + Tripple rotation = graphics.blockEntity().renderRotation.get(); + Pair offset = graphics.blockEntity().renderOffset.get(); + Pair zOffset = graphics.blockEntity().renderZOffset.get(); + float scale = graphics.blockEntity().renderScale.get(); + + graphics.poseStack().pushPose(); + graphics.poseStack().translate(offset.getFirst(), offset.getSecond(), zOffset.getFirst()); + graphics.poseStack().mulPose(Vector3f.XP.rotationDegrees(rotation.getFirst())); + graphics.poseStack().mulPose(Vector3f.YP.rotationDegrees(rotation.getSecond())); + graphics.poseStack().mulPose(Vector3f.ZP.rotationDegrees(rotation.getThird())); + graphics.poseStack().scale(scale, scale, 1); + renderSubtype.render(graphics, partialTick, this, light, false); + graphics.poseStack().popPose(); + + if (!(graphics.blockEntity().getBlockState().getBlock() instanceof AbstractAdvancedSidedDisplayBlock) || graphics.blockEntity().getBlockState().getValue(AbstractAdvancedSidedDisplayBlock.SIDE) == ESide.BOTH) { + graphics.poseStack().pushPose(); + graphics.poseStack().mulPose(Vector3f.YP.rotationDegrees(180)); + graphics.poseStack().translate(-graphics.blockEntity().getXSize() * 16, 0, -16); + graphics.poseStack().translate(offset.getFirst(), offset.getSecond(), zOffset.getSecond()); + graphics.poseStack().mulPose(Vector3f.XP.rotationDegrees(rotation.getFirst())); + graphics.poseStack().mulPose(Vector3f.YP.rotationDegrees(rotation.getSecond())); + graphics.poseStack().mulPose(Vector3f.ZP.rotationDegrees(rotation.getThird())); + graphics.poseStack().scale(scale, scale, 1); + renderSubtype.render(graphics, partialTick, this, light, true); + graphics.poseStack().popPose(); } } } @@ -142,35 +74,22 @@ public void render(BlockEntityRendererContext context, AdvancedDisplayBlockEntit @Override public void tick(Level level, BlockPos pos, BlockState state, AdvancedDisplayBlockEntity blockEntity) { renderSubtype.tick(level, pos, state, blockEntity, this); - labels.forEach(x -> x.tick()); if (blockEntity.getXSizeScaled() != lastXSize) { - update(level, pos, state, blockEntity, EUpdateReason.BLOCK_CHANGED); + update(level, pos, state, blockEntity, EUpdateReason.LAYOUT_CHANGED); } lastXSize = blockEntity.getXSizeScaled(); } @Override - public void update(Level level, BlockPos pos, BlockState state, AdvancedDisplayBlockEntity blockEntity, EUpdateReason reason) { - carriageIndexLabel = null; - EDisplayType type = blockEntity.getDisplayType(); - EDisplayInfo info = blockEntity.getInfoType(); - - if (lastType != type || lastInfo != info) { - if (renderSubtypes.containsKey(type)) { - Map>> selectedType = renderSubtypes.get(type); - if (selectedType.containsKey(info)) { - renderSubtype = selectedType.get(info).get(); - } - } - - if (renderSubtype == null) { - renderSubtype = new BERRenderSubtypeBase<>(); - } + public void update(Level level, BlockPos pos, BlockState state, AdvancedDisplayBlockEntity blockEntity, Object data) { + EUpdateReason reason = (EUpdateReason)data; + DisplayTypeResourceKey type = blockEntity.getDisplayTypeKey(); + if (lastType == null || !lastType.equals(type)) { + renderSubtype = AdvancedDisplaysRegistry.getRenderer(type); } lastType = type; - lastInfo = info; renderSubtype.update(level, pos, state, blockEntity, this, reason); } diff --git a/common/src/main/java/de/mrjulsen/crn/client/ber/variants/BERRenderSubtypeBase.java b/common/src/main/java/de/mrjulsen/crn/client/ber/BERRenderSubtypeBase.java similarity index 72% rename from common/src/main/java/de/mrjulsen/crn/client/ber/variants/BERRenderSubtypeBase.java rename to common/src/main/java/de/mrjulsen/crn/client/ber/BERRenderSubtypeBase.java index 57f18669..e20bb7b5 100644 --- a/common/src/main/java/de/mrjulsen/crn/client/ber/variants/BERRenderSubtypeBase.java +++ b/common/src/main/java/de/mrjulsen/crn/client/ber/BERRenderSubtypeBase.java @@ -1,7 +1,7 @@ -package de.mrjulsen.crn.client.ber.variants; +package de.mrjulsen.crn.client.ber; +import de.mrjulsen.crn.block.blockentity.AdvancedDisplayBlockEntity.EUpdateReason; import de.mrjulsen.mcdragonlib.client.ber.AbstractBlockEntityRenderInstance; -import de.mrjulsen.mcdragonlib.client.ber.IBlockEntityRendererInstance.EUpdateReason; import net.minecraft.core.BlockPos; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.entity.BlockEntity; @@ -9,11 +9,6 @@ public final class BERRenderSubtypeBase, U> implements IBERRenderSubtype{ - @Override - public boolean isSingleLined() { - return false; - } - @Override public void update(Level level, BlockPos pos, BlockState state, T blockEntity, S parent, EUpdateReason reason) {} } diff --git a/common/src/main/java/de/mrjulsen/crn/client/ber/IBERRenderSubtype.java b/common/src/main/java/de/mrjulsen/crn/client/ber/IBERRenderSubtype.java new file mode 100644 index 00000000..3a936081 --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/client/ber/IBERRenderSubtype.java @@ -0,0 +1,16 @@ +package de.mrjulsen.crn.client.ber; + +import de.mrjulsen.crn.block.blockentity.AdvancedDisplayBlockEntity.EUpdateReason; +import de.mrjulsen.mcdragonlib.client.ber.AbstractBlockEntityRenderInstance; +import de.mrjulsen.mcdragonlib.client.ber.BERGraphics; +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; + +public interface IBERRenderSubtype, U> { + void update(Level level, BlockPos pos, BlockState state, T blockEntity, S parent, EUpdateReason data); + default void renderTick(float deltaTime) {} + default void render(BERGraphics graphics, float partialTick, S parent, int light, boolean backSide) {} + default void tick(Level level, BlockPos pos, BlockState state, T pBlockEntity, S parent) {} +} diff --git a/common/src/main/java/de/mrjulsen/crn/client/ber/TrainStationClockRenderer.java b/common/src/main/java/de/mrjulsen/crn/client/ber/TrainStationClockRenderer.java index b5d00876..1045c129 100644 --- a/common/src/main/java/de/mrjulsen/crn/client/ber/TrainStationClockRenderer.java +++ b/common/src/main/java/de/mrjulsen/crn/client/ber/TrainStationClockRenderer.java @@ -1,15 +1,15 @@ package de.mrjulsen.crn.client.ber; -import com.mojang.blaze3d.vertex.PoseStack; import com.mojang.math.Vector3f; import de.mrjulsen.crn.CreateRailwaysNavigator; -import de.mrjulsen.crn.block.be.TrainStationClockBlockEntity; +import de.mrjulsen.crn.block.blockentity.TrainStationClockBlockEntity; import de.mrjulsen.crn.util.ModUtils; import de.mrjulsen.mcdragonlib.DragonLib; import de.mrjulsen.mcdragonlib.client.ber.AbstractBlockEntityRenderInstance; +import de.mrjulsen.mcdragonlib.client.ber.BERGraphics; +import de.mrjulsen.mcdragonlib.client.util.BERUtils; import net.minecraft.client.renderer.LightTexture; -import net.minecraft.client.renderer.MultiBufferSource; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.level.block.HorizontalDirectionalBlock; @@ -22,41 +22,40 @@ public TrainStationClockRenderer(TrainStationClockBlockEntity blockEntity) { } @Override - public void render(BlockEntityRendererContext context, TrainStationClockBlockEntity pBlockEntity, float pPartialTicks, PoseStack pPoseStack, MultiBufferSource pBufferSource, int pPackedLight, int pOverlay) { - - context.renderUtils().initRenderEngine(); + public void render(BERGraphics graphics, float partialTick) { + BERUtils.initRenderEngine(); float z = 3.2f; - pPoseStack.translate(8, 8, 8 + z); - context.renderUtils().renderTexture(DIAL_TEXTURE, pBufferSource, pBlockEntity, pPoseStack, !pBlockEntity.isGlowing(), -7, -7, -0.2f, 14, 14, 0, 0, 1, 1, pBlockEntity.getBlockState().getValue(HorizontalDirectionalBlock.FACING), (0xFF << 24) | (pBlockEntity.getColor()), pBlockEntity.isGlowing() ? LightTexture.FULL_BRIGHT : pPackedLight); - - pPoseStack.pushPose(); - pPoseStack.mulPose(Vector3f.ZP.rotationDegrees(-90 + ModUtils.clockHandDegrees(pBlockEntity.getLevel().getDayTime() + DragonLib.DAYTIME_SHIFT, 12000))); - context.renderUtils().fillColor(pBufferSource, pBlockEntity, 0xFF191919, pPoseStack, -0.5f, -0.5f, 0, 6, 1, pBlockEntity.getBlockState().getValue(HorizontalDirectionalBlock.FACING), pPackedLight); - pPoseStack.popPose(); - - pPoseStack.pushPose(); - pPoseStack.mulPose(Vector3f.ZP.rotationDegrees(-90 + ModUtils.clockHandDegrees(pBlockEntity.getLevel().getDayTime() + DragonLib.DAYTIME_SHIFT, 1000))); - context.renderUtils().fillColor(pBufferSource, pBlockEntity, 0xFF222222, pPoseStack, -0.5f, -0.5f, 0.1f, 7, 1, pBlockEntity.getBlockState().getValue(HorizontalDirectionalBlock.FACING), pPackedLight); - pPoseStack.popPose(); - - pPoseStack.translate(0, 0, -z * 2); - pPoseStack.pushPose(); - pPoseStack.mulPose(Vector3f.YP.rotationDegrees(180)); - context.renderUtils().renderTexture(DIAL_TEXTURE, pBufferSource, pBlockEntity, pPoseStack, !pBlockEntity.isGlowing(), -7, -7, -0.2f, 14, 14, 0, 0, 1, 1, pBlockEntity.getBlockState().getValue(HorizontalDirectionalBlock.FACING).getOpposite(), (0xFF << 24) | (pBlockEntity.getColor()), pBlockEntity.isGlowing() ? LightTexture.FULL_BRIGHT : pPackedLight); - pPoseStack.popPose(); - - pPoseStack.pushPose(); - pPoseStack.mulPose(Vector3f.ZN.rotationDegrees(-90 + ModUtils.clockHandDegrees(pBlockEntity.getLevel().getDayTime() + DragonLib.DAYTIME_SHIFT, 12000))); - pPoseStack.mulPose(Vector3f.YP.rotationDegrees(180)); - context.renderUtils().fillColor(pBufferSource, pBlockEntity, 0xFF191919, pPoseStack, -0.5f, -0.5f, 0, 6, 1, pBlockEntity.getBlockState().getValue(HorizontalDirectionalBlock.FACING), pPackedLight); - pPoseStack.popPose(); - - pPoseStack.pushPose(); - pPoseStack.mulPose(Vector3f.ZN.rotationDegrees(-90 + ModUtils.clockHandDegrees(pBlockEntity.getLevel().getDayTime() + DragonLib.DAYTIME_SHIFT, 1000))); - pPoseStack.mulPose(Vector3f.YP.rotationDegrees(180)); - context.renderUtils().fillColor(pBufferSource, pBlockEntity, 0xFF222222, pPoseStack, -0.5f, -0.5f, 0.1f, 7, 1, pBlockEntity.getBlockState().getValue(HorizontalDirectionalBlock.FACING), pPackedLight); - pPoseStack.popPose(); + graphics.poseStack().translate(8, 8, 8 + z); + BERUtils.renderTexture(DIAL_TEXTURE, graphics, !graphics.blockEntity().isGlowing(), -7, -7, -0.2f, 14, 14, 0, 0, 1, 1, graphics.blockEntity().getBlockState().getValue(HorizontalDirectionalBlock.FACING), (0xFF << 24) | (graphics.blockEntity().getColor()), graphics.blockEntity().isGlowing() ? LightTexture.FULL_BRIGHT : graphics.packedLight()); + + graphics.poseStack().pushPose(); + graphics.poseStack().mulPose(Vector3f.ZP.rotationDegrees(-90 + ModUtils.clockHandDegrees(graphics.blockEntity().getLevel().getDayTime() + DragonLib.DAYTIME_SHIFT, 12000))); + BERUtils.fillColor(graphics, -0.5f, -0.5f, 0, 6, 1, 0xFF191919, graphics.blockEntity().getBlockState().getValue(HorizontalDirectionalBlock.FACING)); + graphics.poseStack().popPose(); + + graphics.poseStack().pushPose(); + graphics.poseStack().mulPose(Vector3f.ZP.rotationDegrees(-90 + ModUtils.clockHandDegrees(graphics.blockEntity().getLevel().getDayTime() + DragonLib.DAYTIME_SHIFT, 1000))); + BERUtils.fillColor(graphics, -0.5f, -0.5f, 0.1f, 7, 1, 0xFF222222, graphics.blockEntity().getBlockState().getValue(HorizontalDirectionalBlock.FACING)); + graphics.poseStack().popPose(); + + graphics.poseStack().translate(0, 0, -z * 2); + graphics.poseStack().pushPose(); + graphics.poseStack().mulPose(Vector3f.YP.rotationDegrees(180)); + BERUtils.renderTexture(DIAL_TEXTURE, graphics, !graphics.blockEntity().isGlowing(), -7, -7, -0.2f, 14, 14, 0, 0, 1, 1, graphics.blockEntity().getBlockState().getValue(HorizontalDirectionalBlock.FACING).getOpposite(), (0xFF << 24) | (graphics.blockEntity().getColor()), graphics.blockEntity().isGlowing() ? LightTexture.FULL_BRIGHT : graphics.packedLight()); + graphics.poseStack().popPose(); + + graphics.poseStack().pushPose(); + graphics.poseStack().mulPose(Vector3f.ZN.rotationDegrees(-90 + ModUtils.clockHandDegrees(graphics.blockEntity().getLevel().getDayTime() + DragonLib.DAYTIME_SHIFT, 12000))); + graphics.poseStack().mulPose(Vector3f.YP.rotationDegrees(180)); + BERUtils.fillColor(graphics, -0.5f, -0.5f, 0, 6, 1, 0xFF191919, graphics.blockEntity().getBlockState().getValue(HorizontalDirectionalBlock.FACING)); + graphics.poseStack().popPose(); + + graphics.poseStack().pushPose(); + graphics.poseStack().mulPose(Vector3f.ZN.rotationDegrees(-90 + ModUtils.clockHandDegrees(graphics.blockEntity().getLevel().getDayTime() + DragonLib.DAYTIME_SHIFT, 1000))); + graphics.poseStack().mulPose(Vector3f.YP.rotationDegrees(180)); + BERUtils.fillColor(graphics, -0.5f, -0.5f, 0.1f, 7, 1, 0xFF222222, graphics.blockEntity().getBlockState().getValue(HorizontalDirectionalBlock.FACING)); + graphics.poseStack().popPose(); } } diff --git a/common/src/main/java/de/mrjulsen/crn/client/ber/base/BERText.java b/common/src/main/java/de/mrjulsen/crn/client/ber/base/BERText.java deleted file mode 100644 index a982c025..00000000 --- a/common/src/main/java/de/mrjulsen/crn/client/ber/base/BERText.java +++ /dev/null @@ -1,350 +0,0 @@ -package de.mrjulsen.crn.client.ber.base; - -import java.util.List; -import java.util.function.Consumer; -import java.util.function.Supplier; - -import com.mojang.blaze3d.font.GlyphInfo; -import com.mojang.blaze3d.vertex.PoseStack; - -import de.mrjulsen.mcdragonlib.client.util.FontUtils; -import de.mrjulsen.mcdragonlib.mixin.BakedGlyphAccessor; -import de.mrjulsen.mcdragonlib.util.MathUtils; -import net.minecraft.client.gui.Font; -import net.minecraft.client.renderer.MultiBufferSource; -import net.minecraft.network.chat.Component; -import net.minecraft.util.StringDecomposer; - -/** - * A text component designed for block entity rendering. It supports scissoring and scrolling text, e.g. for display boards. - */ -public class BERText { - - private static final byte CHAR_SIZE = 8; - - private final FontUtils fontUtils; - private final float xOffset; - private Supplier> textData; - - private float minX = 0; - private float maxX = Float.MAX_VALUE; - private float minStretchScale = 1.0f; - private float maxStretchScale = 1.0f; - private float maxWidth = Float.MAX_VALUE; - private boolean forceMaxWidth = false; - private float scrollSpeed = 0.0f; - private boolean centered = false; - private int color = 0xFFFFFFFF; - private int ticksPerPage = 200; - private int refreshRate = 0; - private Consumer onUpdate; - - private TextTransformation predefinedTextTransformation = null; - - // stored data - private TextDataCache cache; - private float scrollXOffset = 0.0f; - private int refreshTimer = 0; - private int currentTicks = 0; - private int currentIndex = 0; - private List texts; - - public BERText(FontUtils fontUtils, Component text, float xOffset) { - this.fontUtils = fontUtils; - this.textData = () -> List.of(text); - this.xOffset = xOffset; - } - - public BERText(FontUtils fontUtils, Supplier> texts, float xOffset) { - this.fontUtils = fontUtils; - this.textData = texts; - this.xOffset = xOffset; - } - - public BERText withStencil(float minX, float maxX) { - this.minX = minX; - this.maxX = maxX; - return this; - } - - public BERText withStretchScale(float minScale, float maxScale) { - this.minStretchScale = minScale; - this.maxStretchScale = maxScale; - return this; - } - - public BERText withMaxWidth(float maxWidth, boolean force) { - this.maxWidth = maxWidth; - this.forceMaxWidth = force; - return this; - } - - public BERText withIsCentered(boolean b) { - this.centered = b; - return this; - } - - public BERText withCanScroll(boolean b, float scrollingSpeed) { - this.scrollSpeed = b ? scrollingSpeed : 0; - return this; - } - - public BERText withColor(int color) { - this.color = color; - return this; - } - - public BERText withTicksPerPage(int ticks) { - this.ticksPerPage = ticks; - return this; - } - - /** - * The displayed text is updated every x ticks. If the value is less than or equal to 0, then the text will not be updated. - */ - public BERText withRefreshRate(int ticks) { - this.refreshRate = ticks; - return this; - } - - public BERText withPredefinedTextTransformation(TextTransformation transformation) { - this.predefinedTextTransformation = transformation; - return this; - } - - public BERText withUpdateFunc(Consumer onUpdate) { - this.onUpdate = onUpdate; - return this; - } - - public BERText build() { - fetchCurrentText(); - calc(false); - scrollXOffset = cache.maxWidthScaled(); - return this; - } - - public FontUtils getFontUtils() { - return fontUtils; - } - - private void fetchCurrentText() { - texts = textData.get(); - } - - public Component getCurrentText() { - return texts.get(currentIndex); - } - - public List getTexts() { - return texts; - } - - public int getTicksPerPage() { - return ticksPerPage; - } - - public float getXOffset() { - return xOffset; - } - - public float getMinX() { - return minX; - } - - public float getMaxX() { - return maxX; - } - - public float getMinStretchScale() { - return minStretchScale; - } - - public float getMaxStretchScale() { - return maxStretchScale; - } - - public float getMaxWidth() { - return maxWidth; - } - - public boolean forceMaxWidth() { - return forceMaxWidth; - } - - public boolean canScroll() { - return scrollSpeed > 0; - } - - public float getScrollSpeed() { - return scrollSpeed; - } - - public boolean isCentered() { - return centered; - } - - public float getTextWidth() { - return getFontUtils().font.width(getCurrentText()); - } - - public float getScaledTextWidth() { - return getTextWidth() * cache.textXScale(); - } - - public int getColor() { - return color; - } - - public void recalc() { - calc(false); - } - - protected void calc(boolean callUpdate) { - float textWidth = getFontUtils().font.width(getCurrentText()); - float rawXScale = getMaxWidth() / textWidth; - float finalXScale = MathUtils.clamp(rawXScale, getMinStretchScale(), getMaxStretchScale()); - boolean mustScroll = rawXScale < getMinStretchScale(); - - if (forceMaxWidth() && mustScroll) { - finalXScale = getMaxStretchScale(); - } - - float minX = getMinX() / finalXScale; - float maxWidthScaled = getMaxWidth() / finalXScale; - float maxX = Math.min(forceMaxWidth() ? maxWidthScaled : Float.MAX_VALUE, getMaxX() / finalXScale); - float xOffset = getXOffset() + (isCentered() ? maxWidthScaled / 2 - textWidth / 2 : 0); - cache = new TextDataCache(finalXScale, minX, maxX, xOffset, maxWidthScaled, textWidth, forceMaxWidth() && mustScroll && canScroll()); - - if (callUpdate && onUpdate != null) { - onUpdate.accept(this); - } - } - - public void render(PoseStack pPoseStack, MultiBufferSource pBufferSource, int pPackedLight) { - getFontUtils().reset(); - pPoseStack.pushPose(); { - if (predefinedTextTransformation != null) { - pPoseStack.translate(predefinedTextTransformation.x(), predefinedTextTransformation.y(), predefinedTextTransformation.z()); - pPoseStack.scale(predefinedTextTransformation.xScale(), predefinedTextTransformation.yScale(), 1); - } - - pPoseStack.pushPose(); { - pPoseStack.scale(cache.textXScale(), 1, 1); - renderTextInBounds(pPoseStack, getFontUtils(), pBufferSource, getCurrentText(), pPackedLight, cache.mustScroll ? scrollXOffset : cache.xOffset(), cache.minX(), cache.maxX(), getColor()); - } - pPoseStack.popPose(); - } - pPoseStack.popPose(); - } - - private void renderTextInBounds(PoseStack pPoseStack, FontUtils fontUtils, MultiBufferSource pBufferSource, Component text, int pPackedLight, float xOffset, float xLeft, float xRight, int color) { - if (xRight <= xLeft) { - return; - } - - pPoseStack.pushPose(); - pPoseStack.translate(xLeft + (xOffset > 0 ? xOffset : 0), 0, 0); - Font.StringRenderOutput sro = fontUtils.font.new StringRenderOutput(pBufferSource, 0, 0, color, false, pPoseStack.last().pose(), Font.DisplayMode.NORMAL, pPackedLight); - - float newX = xOffset; - float glyphTranslation = 0; - final float charSize = CHAR_SIZE + (text.getStyle().isBold() ? 1 : 0); - - for (int i = 0; i < text.getString().length(); i++) { - int charCode = text.getString().charAt(i); - GlyphInfo info = fontUtils.fontSet.getGlyphInfo(charCode, false); - float glyphWidth = info.getAdvance(text.getStyle().isBold()); - float oldX = newX; - newX += glyphWidth; - - if (newX > xLeft && oldX < xLeft) { - float diff = xLeft - oldX; - BakedGlyphAccessor glyph = fontUtils.getGlyphAccessor(charCode); - float glyphUVDiff = glyph.getU1() - glyph.getU0(); - float scale = (1.0f / charSize * diff); - float sub = glyphUVDiff * scale; - - fontUtils.pushUV(charCode); - glyph.setU0(glyph.getU0() + sub); - - pPoseStack.pushPose(); - float invScale = 1.0f - scale; - pPoseStack.scale(invScale, 1, 1); - Font.StringRenderOutput sro2 = fontUtils.font.new StringRenderOutput(pBufferSource, 0 , 0, color, false, pPoseStack.last().pose(), Font.DisplayMode.NORMAL, pPackedLight); - StringDecomposer.iterateFormatted(String.valueOf((char)charCode), text.getStyle(), sro2); - pPoseStack.popPose(); - fontUtils.popUV(charCode); - pPoseStack.translate(glyphWidth - (charSize * scale), 0, 0); - continue; - } else if (newX > xRight) { - float diff = newX - xRight; - float charRightSpace = charSize - glyphWidth; - float totalDiff = diff + charRightSpace; - - BakedGlyphAccessor glyph = fontUtils.getGlyphAccessor(charCode); - float glyphUVDiff = glyph.getU1() - glyph.getU0(); - float scale = (1.0f / charSize * totalDiff); - float sub = glyphUVDiff * scale; - - fontUtils.pushUV(charCode); - glyph.setU1(glyph.getU1() - sub); - pPoseStack.pushPose(); - float invScale = 1.0f - scale; - pPoseStack.scale(invScale, 1, 1); - pPoseStack.translate(glyphTranslation / invScale, 0, 0); - Font.StringRenderOutput sro2 = fontUtils.font.new StringRenderOutput(pBufferSource, 0, 0, color, false, pPoseStack.last().pose(), Font.DisplayMode.NORMAL, pPackedLight); - StringDecomposer.iterateFormatted(String.valueOf((char)charCode), text.getStyle(), sro2); - pPoseStack.popPose(); - fontUtils.popUV(charCode); - break; - } else if (oldX >= xLeft && newX <= xRight) { - StringDecomposer.iterateFormatted(String.valueOf((char)charCode), text.getStyle(), sro); - } else { - continue; - } - - glyphTranslation += glyphWidth; - } - pPoseStack.popPose(); - } - - public void tick() { - - if (refreshRate > 0) { - refreshTimer++; - if ((refreshTimer %= refreshRate) == 0) { - fetchCurrentText(); - calc(true); - } - } - - boolean multiText = getTexts().size() > 1; - - if (cache.mustScroll()) { - scrollXOffset -= getScrollSpeed() / this.getMaxStretchScale(); - if (scrollXOffset < -cache.textWidth()) { - scrollXOffset = cache.maxWidthScaled(); - - if (multiText) { - currentIndex++; - fetchCurrentText(); - currentIndex %= getTexts().size(); - calc(true); - } - } - } else if (multiText) { - currentTicks++; - if ((currentTicks %= getTicksPerPage()) == 0) { - currentIndex++; - fetchCurrentText(); - currentIndex %= getTexts().size(); - calc(true); - } - } - } - - protected static record TextDataCache(float textXScale, float minX, float maxX, float xOffset, float maxWidthScaled, float textWidth, boolean mustScroll) {} - public static record TextTransformation(float x, float y, float z, float xScale, float yScale) {} - -} diff --git a/common/src/main/java/de/mrjulsen/crn/client/ber/variants/BERError.java b/common/src/main/java/de/mrjulsen/crn/client/ber/variants/BERError.java new file mode 100644 index 00000000..9a8af342 --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/client/ber/variants/BERError.java @@ -0,0 +1,41 @@ +package de.mrjulsen.crn.client.ber.variants; + +import de.mrjulsen.crn.block.blockentity.AdvancedDisplayBlockEntity; +import de.mrjulsen.crn.block.blockentity.AdvancedDisplayBlockEntity.EUpdateReason; +import de.mrjulsen.crn.client.ber.AdvancedDisplayRenderInstance; +import de.mrjulsen.crn.client.ber.IBERRenderSubtype; +import de.mrjulsen.mcdragonlib.client.ber.BERGraphics; +import de.mrjulsen.mcdragonlib.client.ber.BERLabel; +import de.mrjulsen.mcdragonlib.client.ber.BERLabel.BoundsHitReaction; +import de.mrjulsen.mcdragonlib.util.TextUtils; +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.state.BlockState; + +public class BERError implements IBERRenderSubtype { + + private final BERLabel label = new BERLabel() + .setPos(3, 3) + .setScale(0.5f, 0.5f) + .setYScale(0.5f) + ; + + @Override + public void renderTick(float deltaTime) { + label.renderTick(); + } + + @Override + public void render(BERGraphics graphics, float partialTick, AdvancedDisplayRenderInstance parent, int light, boolean backSide) { + label.render(graphics); + } + + @Override + public void update(Level level, BlockPos pos, BlockState state, AdvancedDisplayBlockEntity blockEntity, AdvancedDisplayRenderInstance parent, EUpdateReason reason) { + label + .setText(TextUtils.text("Error! Unrecognized display type!")) + .setColor(0xFFFF0000) + .setMaxWidth(blockEntity.getXSizeScaled() * 16 - 6, BoundsHitReaction.SCALE_SCROLL) + ; + } +} diff --git a/common/src/main/java/de/mrjulsen/crn/client/ber/variants/BERPassengerInfoDetailed.java b/common/src/main/java/de/mrjulsen/crn/client/ber/variants/BERPassengerInfoDetailed.java deleted file mode 100644 index df15f40a..00000000 --- a/common/src/main/java/de/mrjulsen/crn/client/ber/variants/BERPassengerInfoDetailed.java +++ /dev/null @@ -1,234 +0,0 @@ -package de.mrjulsen.crn.client.ber.variants; - -import java.util.List; - -import com.mojang.blaze3d.vertex.PoseStack; - -import de.mrjulsen.crn.block.be.AdvancedDisplayBlockEntity; -import de.mrjulsen.crn.client.ber.AdvancedDisplayRenderInstance; -import de.mrjulsen.crn.client.ber.base.BERText; -import de.mrjulsen.crn.client.ber.base.BERText.TextTransformation; -import de.mrjulsen.crn.client.gui.ModGuiIcons; -import de.mrjulsen.crn.client.lang.ELanguage; -import de.mrjulsen.crn.config.ModClientConfig; -import de.mrjulsen.crn.data.DeparturePrediction.TrainExitSide; -import de.mrjulsen.crn.data.GlobalSettingsManager; -import de.mrjulsen.crn.event.listeners.JourneyListener.State; -import de.mrjulsen.crn.util.ModUtils; -import de.mrjulsen.mcdragonlib.DragonLib; -import de.mrjulsen.mcdragonlib.client.ber.IBlockEntityRendererInstance.BlockEntityRendererContext; -import de.mrjulsen.mcdragonlib.client.ber.IBlockEntityRendererInstance.EUpdateReason; -import de.mrjulsen.mcdragonlib.util.DLUtils; -import de.mrjulsen.mcdragonlib.util.TextUtils; -import de.mrjulsen.mcdragonlib.util.TimeUtils; -import net.minecraft.client.renderer.MultiBufferSource; -import net.minecraft.core.BlockPos; -import net.minecraft.network.chat.MutableComponent; -import net.minecraft.world.level.Level; -import net.minecraft.world.level.block.HorizontalDirectionalBlock; -import net.minecraft.world.level.block.state.BlockState; - -public class BERPassengerInfoDetailed implements IBERRenderSubtype { - - private State state = State.WHILE_TRAVELING; - - private static final String keyNextStop = "gui.createrailwaysnavigator.route_overview.next_stop"; - private static final String keyDate = "gui.createrailwaysnavigator.route_overview.date"; - - private BERText announceNextStopLabel; - private BERText whileNextStopLabel; - - @Override - public boolean isSingleLined() { - return true; - } - - @Override - public void tick(Level level, BlockPos pos, BlockState state, AdvancedDisplayBlockEntity pBlockEntity, AdvancedDisplayRenderInstance parent) { - if (pBlockEntity.getTrainData() == null) { - return; - } - - DLUtils.doIfNotNull(announceNextStopLabel, x -> x.tick()); - DLUtils.doIfNotNull(whileNextStopLabel, x -> x.tick()); - - boolean dirty = false; - - if (pBlockEntity.getTrainData().getNextStop().isPresent()) { - if (this.state != State.WHILE_NEXT_STOP && pBlockEntity.getTrainData().getNextStop().get().departureTicks() <= 0) { - this.state = State.WHILE_NEXT_STOP; - dirty = true; - } else if (this.state != State.BEFORE_NEXT_STOP && pBlockEntity.getTrainData().getNextStop().get().departureTicks() <= ModClientConfig.NEXT_STOP_ANNOUNCEMENT.get() && pBlockEntity.getTrainData().getNextStop().get().departureTicks() > 0) { - this.state = State.BEFORE_NEXT_STOP; - dirty = true; - } else if (this.state != State.WHILE_TRAVELING && pBlockEntity.getTrainData().getNextStop().get().departureTicks() > ModClientConfig.NEXT_STOP_ANNOUNCEMENT.get()) { - this.state = State.WHILE_TRAVELING; - dirty = true; - } - } - - if (dirty) { - update(level, pos, state, pBlockEntity, parent, EUpdateReason.DATA_CHANGED); - } - } - - @Override - public void update(Level level, BlockPos pos, BlockState state, AdvancedDisplayBlockEntity blockEntity, AdvancedDisplayRenderInstance parent, EUpdateReason reason) { - if (blockEntity.getTrainData() == null) { - return; - } - - parent.labels.clear(); - announceNextStopLabel = null; - whileNextStopLabel = null; - - switch (this.state) { - case BEFORE_NEXT_STOP: - updateAnnounceNextStop(level, pos, state, blockEntity, parent); - break; - case WHILE_NEXT_STOP: - updateWhileNextStop(level, pos, state, blockEntity, parent); - break; - default: - updateDefault(level, pos, state, blockEntity, parent); - break; - } - } - - @Override - public void renderAdditional(BlockEntityRendererContext context, AdvancedDisplayBlockEntity pBlockEntity, AdvancedDisplayRenderInstance parent, float pPartialTicks, PoseStack pPoseStack, MultiBufferSource pBufferSource, int pPackedLight, int pOverlay, Boolean backSide) { - if (state == State.WHILE_NEXT_STOP || state == State.BEFORE_NEXT_STOP) { - context.renderUtils().initRenderEngine(); - TrainExitSide side = pBlockEntity.relativeExitDirection.get(); - float uv = 1.0f / 256.0f; - - if (backSide) { - side = side.getOpposite(); - } - - switch (side) { - case RIGHT: - context.renderUtils().renderTexture( - ModGuiIcons.ICON_LOCATION, - pBufferSource, - pBlockEntity, - pPoseStack, - false, - pBlockEntity.getXSizeScaled() * 16 - 3 - 8, - 4, - 0, - 8, - 8, - uv * ModGuiIcons.ARROW_RIGHT.getU(), - uv * ModGuiIcons.ARROW_RIGHT.getV(), - uv * (ModGuiIcons.ARROW_RIGHT.getU() + ModGuiIcons.ICON_SIZE), - uv * (ModGuiIcons.ARROW_RIGHT.getV() + ModGuiIcons.ICON_SIZE), - pBlockEntity.getBlockState().getValue(HorizontalDirectionalBlock.FACING), - (0xFF << 24) | (pBlockEntity.getColor()), - pPackedLight - ); - break; - case LEFT: - context.renderUtils().renderTexture( - ModGuiIcons.ICON_LOCATION, - pBufferSource, - pBlockEntity, - pPoseStack, - false, - 3f, - 4, - 0, - 8, - 8, - uv * ModGuiIcons.ARROW_LEFT.getU(), - uv * ModGuiIcons.ARROW_LEFT.getV(), - uv * (ModGuiIcons.ARROW_LEFT.getU() + ModGuiIcons.ICON_SIZE), - uv * (ModGuiIcons.ARROW_LEFT.getV() + ModGuiIcons.ICON_SIZE), - pBlockEntity.getBlockState().getValue(HorizontalDirectionalBlock.FACING), - (0xFF << 24) | (pBlockEntity.getColor()), - pPackedLight - ); - break; - default: - break; - } - - if (backSide) { - switch (side) { - case LEFT: - pPoseStack.translate(10, 0, 0); - break; - case RIGHT: - pPoseStack.translate(-10, 0, 0); - break; - default: - break; - } - } - - DLUtils.doIfNotNull(announceNextStopLabel, x -> x.render(pPoseStack, pBufferSource, pPackedLight)); - DLUtils.doIfNotNull(whileNextStopLabel, x -> x.render(pPoseStack, pBufferSource, pPackedLight)); - } - } - - - private void updateDefault(Level level, BlockPos pos, BlockState state, AdvancedDisplayBlockEntity blockEntity, AdvancedDisplayRenderInstance parent) { - int displayWidth = blockEntity.getXSizeScaled(); - boolean isSingleBlock = blockEntity.getXSizeScaled() <= 1; - - float maxWidth = displayWidth * 16 - 6; - parent.labels.add(new BERText(parent.getFontUtils(), () -> List.of( - TextUtils.text(blockEntity.getTrainData().trainName()).append(" ").append(TextUtils.text(blockEntity.getTrainData().getNextStop().isPresent() ? blockEntity.getTrainData().getNextStop().get().scheduleTitle() : "")), - ModUtils.calcSpeedString(blockEntity.getTrainData().speed(), ModClientConfig.SPEED_UNIT.get()), - isSingleBlock ? - TextUtils.text(TimeUtils.parseTime((int)(blockEntity.getLevel().getDayTime() % DragonLib.TICKS_PER_DAY + DragonLib.DAYTIME_SHIFT), ModClientConfig.TIME_FORMAT.get())) : - ELanguage.translate(keyDate, blockEntity.getLevel().getDayTime() / DragonLib.TICKS_PER_DAY, TimeUtils.parseTime((int)(blockEntity.getLevel().dayTime() % DragonLib.TICKS_PER_DAY + DragonLib.DAYTIME_SHIFT), ModClientConfig.TIME_FORMAT.get())) - ), 0) - .withIsCentered(true) - .withMaxWidth(maxWidth, true) - .withStretchScale(0.75f, 0.75f) - .withStencil(0, maxWidth) - .withCanScroll(true, 1) - .withColor((0xFF << 24) | (blockEntity.getColor())) - .withPredefinedTextTransformation(new TextTransformation(3, 5.5f, 0.0f, 1, 0.75f)) - .build() - ); - } - - private void updateAnnounceNextStop(Level level, BlockPos pos, BlockState state, AdvancedDisplayBlockEntity blockEntity, AdvancedDisplayRenderInstance parent) { - int displayWidth = blockEntity.getXSizeScaled(); - TrainExitSide side = blockEntity.relativeExitDirection.get(); - - MutableComponent line = ELanguage.translate(keyNextStop, GlobalSettingsManager.getInstance().getSettingsData().getAliasFor(blockEntity.getTrainData().getNextStop().isPresent() ? blockEntity.getTrainData().getNextStop().get().stationTagName() : "").getAliasName().get()); - float maxWidth = displayWidth * 16 - 6 - (side != TrainExitSide.UNKNOWN ? 10 : 0); - announceNextStopLabel = (new BERText(parent.getFontUtils(), line, 0) - .withIsCentered(true) - .withMaxWidth(maxWidth, true) - .withStretchScale(0.75f, 0.75f) - .withStencil(0, maxWidth) - .withCanScroll(true, 1) - .withColor((0xFF << 24) | (blockEntity.getColor())) - .withPredefinedTextTransformation(new TextTransformation(3 + (side == TrainExitSide.LEFT ? 10 : 0), 5.5f, 0.0f, 1, 0.75f)) - .build() - ); - } - - private void updateWhileNextStop(Level level, BlockPos pos, BlockState state, AdvancedDisplayBlockEntity blockEntity, AdvancedDisplayRenderInstance parent) { - int displayWidth = blockEntity.getXSizeScaled(); - - TrainExitSide side = blockEntity.relativeExitDirection.get(); - MutableComponent line = TextUtils.text(GlobalSettingsManager.getInstance().getSettingsData().getAliasFor(blockEntity.getTrainData().getNextStop().isPresent() ? blockEntity.getTrainData().getNextStop().get().stationTagName() : null).getAliasName().get()); - - float maxWidth = displayWidth * 16 - 6 - (side != TrainExitSide.UNKNOWN ? 10 : 0); - whileNextStopLabel = (new BERText(parent.getFontUtils(), line, 0) - .withIsCentered(true) - .withMaxWidth(maxWidth, true) - .withStretchScale(0.75f, 0.75f) - .withStencil(0, maxWidth) - .withCanScroll(true, 1) - .withColor((0xFF << 24) | (blockEntity.getColor())) - .withPredefinedTextTransformation(new TextTransformation(3 + (side == TrainExitSide.LEFT ? 10 : 0), 5.5f, 0.0f, 1, 0.75f)) - .build() - ); - } -} diff --git a/common/src/main/java/de/mrjulsen/crn/client/ber/variants/BERPassengerInfoInformative.java b/common/src/main/java/de/mrjulsen/crn/client/ber/variants/BERPassengerInfoInformative.java index 53fe30ce..82449018 100644 --- a/common/src/main/java/de/mrjulsen/crn/client/ber/variants/BERPassengerInfoInformative.java +++ b/common/src/main/java/de/mrjulsen/crn/client/ber/variants/BERPassengerInfoInformative.java @@ -1,35 +1,33 @@ package de.mrjulsen.crn.client.ber.variants; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; - -import com.mojang.blaze3d.vertex.PoseStack; - +import de.mrjulsen.crn.Constants; import de.mrjulsen.crn.CreateRailwaysNavigator; -import de.mrjulsen.crn.block.be.AdvancedDisplayBlockEntity; +import de.mrjulsen.crn.block.blockentity.AdvancedDisplayBlockEntity; +import de.mrjulsen.crn.block.blockentity.AdvancedDisplayBlockEntity.EUpdateReason; +import de.mrjulsen.crn.block.display.AdvancedDisplaySource.ETimeDisplay; +import de.mrjulsen.crn.client.CRNGui; import de.mrjulsen.crn.client.ber.AdvancedDisplayRenderInstance; -import de.mrjulsen.crn.client.ber.base.BERText; -import de.mrjulsen.crn.client.ber.base.BERText.TextTransformation; +import de.mrjulsen.crn.client.ber.IBERRenderSubtype; import de.mrjulsen.crn.client.gui.ModGuiIcons; import de.mrjulsen.crn.client.lang.ELanguage; import de.mrjulsen.crn.config.ModClientConfig; -import de.mrjulsen.crn.data.SimpleTrainConnection; -import de.mrjulsen.crn.data.DeparturePrediction.TrainExitSide; -import de.mrjulsen.crn.data.DeparturePrediction.SimpleDeparturePrediction; -import de.mrjulsen.crn.event.listeners.JourneyListener.State; -import de.mrjulsen.crn.network.InstanceManager; -import de.mrjulsen.crn.network.packets.cts.NextConnectionsRequestPacket; +import de.mrjulsen.crn.data.TrainExitSide; +import de.mrjulsen.crn.data.train.portable.NextConnectionsDisplayData; +import de.mrjulsen.crn.data.train.portable.TrainDisplayData; +import de.mrjulsen.crn.data.train.portable.TrainStopDisplayData; +import de.mrjulsen.crn.registry.ModAccessorTypes; +import de.mrjulsen.crn.registry.data.NextConnectionsRequestData; +import de.mrjulsen.crn.util.ModUtils; import de.mrjulsen.mcdragonlib.DragonLib; -import de.mrjulsen.mcdragonlib.client.ber.IBlockEntityRendererInstance.BlockEntityRendererContext; -import de.mrjulsen.mcdragonlib.client.ber.IBlockEntityRendererInstance.EUpdateReason; +import de.mrjulsen.mcdragonlib.client.ber.BERGraphics; +import de.mrjulsen.mcdragonlib.client.ber.BERLabel; +import de.mrjulsen.mcdragonlib.client.ber.BERLabel.BoundsHitReaction; +import de.mrjulsen.mcdragonlib.client.util.BERUtils; +import de.mrjulsen.mcdragonlib.util.DLUtils; import de.mrjulsen.mcdragonlib.util.TextUtils; -import de.mrjulsen.mcdragonlib.util.TimeUtils; +import de.mrjulsen.mcdragonlib.util.accessor.DataAccessor; import net.minecraft.ChatFormatting; -import net.minecraft.client.renderer.MultiBufferSource; import net.minecraft.core.BlockPos; -import net.minecraft.network.chat.Component; -import net.minecraft.network.chat.MutableComponent; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.HorizontalDirectionalBlock; @@ -37,490 +35,590 @@ public class BERPassengerInfoInformative implements IBERRenderSubtype { - private static final ResourceLocation TEXTURE = new ResourceLocation(CreateRailwaysNavigator.MOD_ID, "textures/gui/overview.png"); - private static final int TEX_ROUTE_PATH_U = 226; - private static final int TEX_ROUTE_PATH_H = 14; - private static final float PANEL_LINE_HEIGHT = 2.0f; - private static final float PANEL_Y_START = 5.75f; - private static final int NEXT_CONNECTIONS_MAX_ENTRIES_PER_PAGE = 3; - private static final int NEXT_CONNECTIONS_PAGE_TIMER = 100; - - private BERText timeLabel; - private BERText titleLabel; - private State state = State.WHILE_TRAVELING; - - // data - private List nextConnections; - private long nextConnectionsRefreshTime = 0; - private int nextConnectionsPage = 0; - private int nextConnectionsMaxPage = 0; - private int nextConnectionsTimer = 0; - - // Cache - private TrainExitSide lastKnownExitSide = TrainExitSide.UNKNOWN; - + private static final ResourceLocation CARRIAGE_ICON = new ResourceLocation("create:textures/gui/assemble.png"); + private static final ResourceLocation ICONS = new ResourceLocation(CreateRailwaysNavigator.MOD_ID, "textures/gui/icons.png"); + private static final String keyDate = "gui.createrailwaysnavigator.route_overview.date"; private static final String keyNextStop = "gui.createrailwaysnavigator.route_overview.next_stop"; - private static final Component textNextConnections = ELanguage.translate("gui.createrailwaysnavigator.route_overview.next_connections").withStyle(ChatFormatting.BOLD); - - @Override - public boolean isSingleLined() { - return false; + private static final String keyNextConnections = "gui.createrailwaysnavigator.route_overview.next_connections"; + private static final int MAX_LINES = 4; + + private NextConnectionsDisplayData nextConnections = null; + private boolean nextStopAnnounced = false; + private TrainExitSide exitSide = TrainExitSide.UNKNOWN; + + private final BERLabel timeLabel = new BERLabel() + .setScale(0.25f, 0.25f) + .setYScale(0.25f) + ; + private final BERLabel carriageLabel = new BERLabel() + .setScale(0.25f, 0.25f) + .setYScale(0.25f) + ; + private final BERLabel trainLineLabel = new BERLabel() + .setPos(3, 2.5f) + .setScale(0.25f, 0.15f) + .setYScale(0.25f) + ; + private final BERLabel speedLabel = new BERLabel() + .setPos(3, 6) + .setScale(0.25f, 0.2f) + .setYScale(0.30f) + .setCentered(true) + ; + private final BERLabel dateLabel = new BERLabel() + .setPos(3, 9) + .setScale(0.2f, 0.15f) + .setYScale(0.2f) + .setCentered(true) + ; + private final BERLabel carriageInfoLabel = new BERLabel() + .setPos(4.5f, 11) + .setScale(0.2f, 0.15f) + .setYScale(0.2f) + .setCentered(true) + ; + private final BERLabel nextConnectionsTitleLabel = new BERLabel(ELanguage.translate(keyNextConnections).withStyle(ChatFormatting.BOLD)) + .setPos(3, 5.5f) + .setScale(0.15f, 0.15f) + .setYScale(0.15f) + ; + private final BERLabel pageIndicatorLabel = new BERLabel() + .setPos(3, 12.5f) + .setScale(0.15f, 0.15f) + .setYScale(0.15f) + .setCentered(true) + ; + + private BERLabel[][] scheduleLines; + private BERLabel[][] nextConnectionsLines = new BERLabel[MAX_LINES - 1][]; + + + private boolean shouldRenderNextConnections() { + return nextConnections != null && !nextConnections.getConnections().isEmpty() && nextStopAnnounced; } - @Override - public void tick(Level level, BlockPos pos, BlockState state, AdvancedDisplayBlockEntity pBlockEntity, AdvancedDisplayRenderInstance parent) { - if (pBlockEntity.getTrainData() == null) { - return; + private String generatePageIndexString(int current, int max) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < current; i++) { + sb.append(" □"); + } + sb.append(" ■"); + for (int i = current + 1; i < max; i++) { + sb.append(" □"); } + return sb.toString(); + } - boolean dirty = false; - - if (pBlockEntity.getTrainData().getNextStop().isPresent()) { - if (this.state != State.WHILE_NEXT_STOP && pBlockEntity.getTrainData().getNextStop().get().departureTicks() <= 0) { - this.state = State.WHILE_NEXT_STOP; - dirty = true; - } else if (this.state != State.BEFORE_NEXT_STOP && pBlockEntity.getTrainData().getNextStop().get().departureTicks() <= ModClientConfig.NEXT_STOP_ANNOUNCEMENT.get() && pBlockEntity.getTrainData().getNextStop().get().departureTicks() > 0) { - this.state = State.BEFORE_NEXT_STOP; - this.nextConnections = null; - long id = InstanceManager.registerClientNextConnectionsResponseAction((data, refreshTime) -> { - this.nextConnections = new ArrayList<>(data); - nextConnectionsPage = 0; - nextConnectionsTimer = 0; - nextConnectionsRefreshTime = refreshTime; - if (data != null && !data.isEmpty()) { - nextConnectionsMaxPage = (int)(nextConnections.size() / NEXT_CONNECTIONS_MAX_ENTRIES_PER_PAGE + (nextConnections.size() % NEXT_CONNECTIONS_MAX_ENTRIES_PER_PAGE == 0 ? 0 : 1)); - parent.labels.clear(); - updateNextConnections(level, pos, state, pBlockEntity, parent); + @Override + public void renderTick(float deltaTime) { + timeLabel.renderTick(); + dateLabel.renderTick(); + trainLineLabel.renderTick(); + speedLabel.renderTick(); + carriageInfoLabel.renderTick(); + nextConnectionsTitleLabel.renderTick(); + pageIndicatorLabel.renderTick(); + DLUtils.doIfNotNull(scheduleLines, x -> { + for (int i = 0; i < x.length; i++) { + DLUtils.doIfNotNull(x[i], y -> { + for (int j = 0; j < y.length; j++) { + DLUtils.doIfNotNull(y[j], z -> z.renderTick()); } }); - CreateRailwaysNavigator.net().CHANNEL.sendToServer(new NextConnectionsRequestPacket(id, pBlockEntity.getTrainData().trainId(), pBlockEntity.getTrainData().getNextStop().get().stationTagName(), pBlockEntity.getTrainData().getNextStop().get().departureTicks())); - dirty = true; - } else if (this.state != State.WHILE_TRAVELING && pBlockEntity.getTrainData().getNextStop().get().departureTicks() > ModClientConfig.NEXT_STOP_ANNOUNCEMENT.get()) { - this.state = State.WHILE_TRAVELING; - dirty = true; } - } - - if (this.state == State.BEFORE_NEXT_STOP && this.nextConnections != null && !this.nextConnections.isEmpty()) { - nextConnectionsTimer++; - if ((nextConnectionsTimer %= NEXT_CONNECTIONS_PAGE_TIMER) == 0) { - nextConnectionsPage++; - nextConnectionsPage %= nextConnectionsMaxPage; - - parent.labels.clear(); - updateNextConnections(level, pos, state, pBlockEntity, parent); + }); + DLUtils.doIfNotNull(nextConnectionsLines, x -> { + for (int i = 0; i < x.length; i++) { + DLUtils.doIfNotNull(x[i], y -> { + for (int j = 0; j < y.length; j++) { + DLUtils.doIfNotNull(y[j], z -> z.renderTick()); + } + }); } - } - - if (lastKnownExitSide != pBlockEntity.relativeExitDirection.get()) { - dirty = true; - } - - lastKnownExitSide = pBlockEntity.relativeExitDirection.get(); - - if (dirty) { - update(level, pos, state, pBlockEntity, parent, EUpdateReason.DATA_CHANGED); - } else { - generateTimeLabel(level, pos, state, pBlockEntity, parent); - } - - if (titleLabel != null) { - titleLabel.tick(); - } + }); } @Override - public void update(Level level, BlockPos pos, BlockState state, AdvancedDisplayBlockEntity blockEntity, AdvancedDisplayRenderInstance parent, EUpdateReason reason) { - if (blockEntity.getTrainData() == null) { - return; - } - - generateTitleBar(level, pos, state, blockEntity, parent, reason); - - if (this.state == State.BEFORE_NEXT_STOP && this.nextConnections != null && !this.nextConnections.isEmpty()) { - return; - } - - parent.labels.clear(); - updateOverview(level, pos, state, blockEntity, parent); + public void tick(Level level, BlockPos pos, BlockState state, AdvancedDisplayBlockEntity blockEntity, AdvancedDisplayRenderInstance parent) { + timeLabel + .setText(blockEntity.getXSizeScaled() > 1 && !nextStopAnnounced ? TextUtils.text(ModUtils.formatTime(DragonLib.getCurrentWorldTime(), blockEntity.getTimeDisplay() == ETimeDisplay.ETA)).withStyle(ChatFormatting.BOLD) : TextUtils.empty()) + .setPos(blockEntity.getXSizeScaled() * 16 - 3 - timeLabel.getTextWidth() - (this.exitSide != TrainExitSide.UNKNOWN ? 4 : 0), 2.5f) + .setColor((0xFF << 24) | (blockEntity.getColor() & 0x00FFFFFF)) + ; } - @Override - public void renderAdditional(BlockEntityRendererContext context, AdvancedDisplayBlockEntity pBlockEntity, AdvancedDisplayRenderInstance parent, float pPartialTicks, PoseStack pPoseStack, MultiBufferSource pBufferSource, int pPackedLight, int pOverlay, Boolean backSide) { - - // render title bar - timeLabel.render(pPoseStack, pBufferSource, pPackedLight); - titleLabel.render(pPoseStack, pBufferSource, pPackedLight); - - context.renderUtils().initRenderEngine(); - context.renderUtils().fillColor(pBufferSource, pBlockEntity, (0xFF << 24) | (pBlockEntity.getColor() & 0x00FFFFFF), pPoseStack, 2.5f, 4.75f, 0.0f, pBlockEntity.getXSizeScaled() * 16 - 5, 0.25f, pBlockEntity.getBlockState().getValue(HorizontalDirectionalBlock.FACING), pPackedLight); - float uv = 1.0f / 256.0f; - float y = 5f; + public void renderHeader(BERGraphics graphics, float partialTick, AdvancedDisplayRenderInstance parent, int light, boolean backSide) { + final float uv255 = 1f / 256f; + TrainExitSide side = exitSide; + if (backSide) { + side = side.getOpposite(); + } - if (notInService(pBlockEntity)) { - return; + graphics.poseStack().pushPose(); + if (side == TrainExitSide.LEFT) { + graphics.poseStack().translate(4, 0, 0); } - // Render route path - if (this.state != State.BEFORE_NEXT_STOP || nextConnections == null || nextConnections.isEmpty()) { - float tempH = PANEL_LINE_HEIGHT - 0.2857142f; - context.renderUtils().renderTexture( - TEXTURE, - pBufferSource, - pBlockEntity, - pPoseStack, + // Render time + if (graphics.blockEntity().getXSizeScaled() > 1 && !nextStopAnnounced) { + timeLabel.render(graphics, light); + BERUtils.renderTexture( + ICONS, + graphics, false, - 8, - y, + timeLabel.getX() - 2.5f, + 2.5f, 0.0f, - 1, - tempH, - uv * TEX_ROUTE_PATH_U, - uv * 2, - uv * (TEX_ROUTE_PATH_U + 7), - uv * (TEX_ROUTE_PATH_H), - pBlockEntity.getBlockState().getValue(HorizontalDirectionalBlock.FACING), - 0xFFFFFFFF, - pPackedLight + 2, + 2, + uv255 * 227, + uv255 * 19, + uv255 * (227 + 10), + uv255 * (19 + 10), + graphics.blockEntity().getBlockState().getValue(HorizontalDirectionalBlock.FACING), + (0xFF << 24) | (graphics.blockEntity().getColor() & 0x00FFFFFF), + light ); - y += tempH; - for (int i = 0; i < 2 && i < pBlockEntity.getTrainData().stopovers().size(); i++) { - context.renderUtils().renderTexture( - TEXTURE, - pBufferSource, - pBlockEntity, - pPoseStack, - false, - 8, - y, - 0.0f, - 1, - 2, - uv * TEX_ROUTE_PATH_U, - uv * TEX_ROUTE_PATH_H * 1, - uv * (TEX_ROUTE_PATH_U + 7), - uv * (TEX_ROUTE_PATH_H * 2), - pBlockEntity.getBlockState().getValue(HorizontalDirectionalBlock.FACING), - 0xFFFFFFFF, - pPackedLight - ); - y += PANEL_LINE_HEIGHT; - } - - if (pBlockEntity.getTrainData().predictions().size() > 1) { - context.renderUtils().renderTexture( - TEXTURE, - pBufferSource, - pBlockEntity, - pPoseStack, - false, - 8, - y, - 0.0f, - 1, - 2, - uv * TEX_ROUTE_PATH_U, - uv * TEX_ROUTE_PATH_H * 2, - uv * (TEX_ROUTE_PATH_U + 7), - uv * (TEX_ROUTE_PATH_H * 3), - pBlockEntity.getBlockState().getValue(HorizontalDirectionalBlock.FACING), - 0xFFFFFFFF, - pPackedLight - ); - } - } - // EXIT ARROW - TrainExitSide side = pBlockEntity.relativeExitDirection.get(); - if (backSide) { - side = side.getOpposite(); + if (graphics.blockEntity().getTrainData() == null || graphics.blockEntity().getTrainData().isEmpty()) { + graphics.poseStack().popPose(); + return; } - if (state != State.WHILE_TRAVELING && side != TrainExitSide.UNKNOWN) { - context.renderUtils().renderTexture( - ModGuiIcons.ICON_LOCATION, - pBufferSource, - pBlockEntity, - pPoseStack, + + trainLineLabel.render(graphics, light); + + // Carriage label + if (graphics.blockEntity().getXSizeScaled() > 2 && !nextStopAnnounced) { + carriageLabel.render(graphics, light); + BERUtils.renderTexture( + CARRIAGE_ICON, + graphics, false, - pBlockEntity.getXSizeScaled() * 16 - 3f - 2, - 2.25f, - 0, + carriageLabel.getX() - 3.5f, 2.5f, - 2.5f, - uv * (side == TrainExitSide.RIGHT ? ModGuiIcons.ARROW_RIGHT : ModGuiIcons.ARROW_LEFT).getU(), - uv * (side == TrainExitSide.RIGHT ? ModGuiIcons.ARROW_RIGHT : ModGuiIcons.ARROW_LEFT).getV(), - uv * ((side == TrainExitSide.RIGHT ? ModGuiIcons.ARROW_RIGHT : ModGuiIcons.ARROW_LEFT).getU() + ModGuiIcons.ICON_SIZE), - uv * ((side == TrainExitSide.RIGHT ? ModGuiIcons.ARROW_RIGHT : ModGuiIcons.ARROW_LEFT).getV() + ModGuiIcons.ICON_SIZE), - pBlockEntity.getBlockState().getValue(HorizontalDirectionalBlock.FACING), - (0xFF << 24) | (pBlockEntity.getColor()), - pPackedLight + 0.0f, + 3, + 2, + uv255 * 22, + uv255 * 231, + uv255 * (22 + 13), + uv255 * (231 + 5), + graphics.blockEntity().getBlockState().getValue(HorizontalDirectionalBlock.FACING), + (0xFF << 24) | (graphics.blockEntity().getColor() & 0x00FFFFFF), + light ); } - } - - private boolean notInService(AdvancedDisplayBlockEntity blockEntity) { - Optional optPred = blockEntity.getTrainData().getNextStop(); - return !optPred.isPresent() || optPred.get().stationTagName() == null || optPred.get().stationTagName().isBlank(); - } - - private float generateTimeLabel(Level level, BlockPos pos, BlockState state, AdvancedDisplayBlockEntity blockEntity, AdvancedDisplayRenderInstance parent) { - float arrowOffset = (this.state != State.WHILE_TRAVELING && blockEntity.relativeExitDirection.get() != TrainExitSide.UNKNOWN ? 4 : 0); - float maxWidth = blockEntity.getXSizeScaled() * 16 - arrowOffset; - MutableComponent line = TextUtils.text(TimeUtils.parseTime((int)(blockEntity.getLevel().getDayTime() % DragonLib.TICKS_PER_DAY + DragonLib.DAYTIME_SHIFT), ModClientConfig.TIME_FORMAT.get())).withStyle(ChatFormatting.BOLD); - float rawTextWidth = Math.min(parent.getFontUtils().font.width(line), maxWidth); - float textWidth = rawTextWidth * 0.25f; - timeLabel = parent.carriageIndexLabel = new BERText(parent.getFontUtils(), textWidth > parent.getFontUtils().font.width(line) * 0.1f ? line : TextUtils.empty(), 0) - .withIsCentered(false) - .withMaxWidth(textWidth, true) - .withStretchScale(0.1f, 0.25f) - .withColor((0xFF << 24) | (blockEntity.getColor() & 0x00FFFFFF)) - .withPredefinedTextTransformation(new TextTransformation(blockEntity.getXSizeScaled() * 16 - 2.5f - textWidth - (this.state != State.WHILE_TRAVELING && blockEntity.relativeExitDirection.get() != TrainExitSide.UNKNOWN ? 4 : 0), 2.5f, 0.0f, 1, 0.25f)) - .build(); - return textWidth; - } - - private void generateTitleBar(Level level, BlockPos pos, BlockState state, AdvancedDisplayBlockEntity blockEntity, AdvancedDisplayRenderInstance parent, EUpdateReason reason) { - int displayWidth = blockEntity.getXSizeScaled(); - - float maxWidth = displayWidth * 16 - 6 - (this.state != State.WHILE_TRAVELING && blockEntity.relativeExitDirection.get() != TrainExitSide.UNKNOWN ? 4 : 0); - //maxWidth *= 2; - float timeWidth = generateTimeLabel(level, pos, state, blockEntity, parent); - - MutableComponent line = TextUtils.text(blockEntity.getTrainData().trainName()).withStyle(ChatFormatting.BOLD); - if (blockEntity.getTrainData().getNextStop().isPresent()) { - switch (this.state) { - case BEFORE_NEXT_STOP: - line = ELanguage.translate(keyNextStop, blockEntity.getTrainData().getNextStop().get().stationTagName()); + graphics.poseStack().popPose(); + + if (nextStopAnnounced || graphics.blockEntity().getTrainData().isWaitingAtStation()) { + switch (side) { + case RIGHT: + BERUtils.renderTexture( + ModGuiIcons.ICON_LOCATION, + graphics, + false, + graphics.blockEntity().getXSizeScaled() * 16 - 3 - 3, + 2.05f, + 0, + 3, + 3, + uv255 * ModGuiIcons.ARROW_RIGHT.getU(), + uv255 * ModGuiIcons.ARROW_RIGHT.getV(), + uv255 * (ModGuiIcons.ARROW_RIGHT.getU() + ModGuiIcons.ICON_SIZE), + uv255 * (ModGuiIcons.ARROW_RIGHT.getV() + ModGuiIcons.ICON_SIZE), + graphics.blockEntity().getBlockState().getValue(HorizontalDirectionalBlock.FACING), + (0xFF << 24) | (graphics.blockEntity().getColor()), + light + ); break; - case WHILE_NEXT_STOP: - line = ELanguage.translate(blockEntity.getTrainData().trainName() + " " + blockEntity.getTrainData().getNextStop().get().stationTagName()).withStyle(ChatFormatting.BOLD); + case LEFT: + BERUtils.renderTexture( + ModGuiIcons.ICON_LOCATION, + graphics, + false, + 3, + 2.05f, + 0, + 3, + 3, + uv255 * ModGuiIcons.ARROW_LEFT.getU(), + uv255 * ModGuiIcons.ARROW_LEFT.getV(), + uv255 * (ModGuiIcons.ARROW_LEFT.getU() + ModGuiIcons.ICON_SIZE), + uv255 * (ModGuiIcons.ARROW_LEFT.getV() + ModGuiIcons.ICON_SIZE), + graphics.blockEntity().getBlockState().getValue(HorizontalDirectionalBlock.FACING), + (0xFF << 24) | (graphics.blockEntity().getColor()), + light + ); break; default: break; } } - - if (titleLabel != null && line.getString().equals(titleLabel.getCurrentText().getString()) && reason == EUpdateReason.DATA_CHANGED) { - return; - } - - titleLabel = new BERText(parent.getFontUtils(), line, 0) - .withIsCentered(false) - .withMaxWidth(maxWidth - timeWidth - 1, true) - .withStretchScale(0.15f, 0.25f) - .withStencil(0, maxWidth - timeWidth - 1) - .withCanScroll(true, 0.5f) - .withColor((0xFF << 24) | (blockEntity.getColor() & 0x00FFFFFF)) - .withPredefinedTextTransformation(new TextTransformation(3.0f, 2.5f, 0.0f, 1, 0.25f)) - .build(); } - private void updateOverview(Level level, BlockPos pos, BlockState state, AdvancedDisplayBlockEntity blockEntity, AdvancedDisplayRenderInstance parent) { - int displayWidth = blockEntity.getXSizeScaled(); + @Override + public void render(BERGraphics graphics, float partialTick, AdvancedDisplayRenderInstance parent, int light, boolean backSide) { + final float uv255 = 1f / 256f; + renderHeader(graphics, partialTick, parent, light, backSide); + BERUtils.fillColor(graphics, 2.5f, 5.0f, 0.01f, graphics.blockEntity().getXSizeScaled() * 16 - 5, 0.25f, (0xFF << 24) | (graphics.blockEntity().getColor() & 0x00FFFFFF), graphics.blockEntity().getBlockState().getValue(HorizontalDirectionalBlock.FACING), light); - // ### CONTENT PANEL - if (notInService(blockEntity) || !blockEntity.getTrainData().getNextStop().isPresent()) { + if (graphics.blockEntity().getTrainData() == null || graphics.blockEntity().getTrainData().isEmpty()) { return; } - float y = PANEL_Y_START; - // DESTINATION - SimpleDeparturePrediction pred = blockEntity.getTrainData().getNextStop().get(); - float maxWidth = displayWidth * 16 - 12.5f; - int rawTime = (int)(blockEntity.getLastRefreshedTime() % 24000 + pred.departureTicks() + DragonLib.DAYTIME_SHIFT); - MutableComponent line = TextUtils.text(TimeUtils.parseTime(rawTime - rawTime % ModClientConfig.REALTIME_PRECISION_THRESHOLD.get(), ModClientConfig.TIME_FORMAT.get())); - parent.labels.add(new BERText(parent.getFontUtils(), line, 0) - .withIsCentered(false) - .withMaxWidth(4, true) - .withStretchScale(0.08f, 0.14f) - .withStencil(0, 7) - .withColor((0xFF << 24) | (blockEntity.getColor())) - .withPredefinedTextTransformation(new TextTransformation(3.0f, y + 0.3f, 0.0f, 1, 0.14f)) - .build() - ); - - - line = TextUtils.text(pred.stationTagName()).withStyle(ChatFormatting.BOLD); - parent.labels.add(new BERText(parent.getFontUtils(), line, 0) - .withIsCentered(false) - .withMaxWidth(maxWidth, true) - .withStretchScale(0.15f, 0.2f) - .withStencil(0, maxWidth) - .withCanScroll(true, 0.5f) - .withColor((0xFF << 24) | (blockEntity.getColor())) - .withPredefinedTextTransformation(new TextTransformation(10.0f, y, 0.0f, 1, 0.2f)) - .build() - ); - y += PANEL_LINE_HEIGHT; - - for (int i = 0; i < 2 && i < blockEntity.getTrainData().stopovers().size(); i++) { - pred = blockEntity.getTrainData().stopovers().get(i); - rawTime = (int)(blockEntity.getLastRefreshedTime() % 24000 + pred.departureTicks() + DragonLib.DAYTIME_SHIFT); - line = TextUtils.text(TimeUtils.parseTime(rawTime - rawTime % ModClientConfig.REALTIME_PRECISION_THRESHOLD.get(), ModClientConfig.TIME_FORMAT.get())); - parent.labels.add(new BERText(parent.getFontUtils(), line, 0) - .withIsCentered(false) - .withMaxWidth(4, true) - .withStretchScale(0.08f, 0.14f) - .withStencil(0, 7) - .withColor((0xFF << 24) | (blockEntity.getColor())) - .withPredefinedTextTransformation(new TextTransformation(3.0f, y + 0.1f, 0.0f, 1, 0.14f)) - .build() - ); - line = TextUtils.text(pred.stationTagName()); - parent.labels.add(new BERText(parent.getFontUtils(), line, 0) - .withIsCentered(false) - .withMaxWidth(maxWidth, true) - .withStretchScale(0.15f, 0.16f) - .withStencil(0, maxWidth) - .withCanScroll(true, 0.5f) - .withColor((0xFF << 24) | (blockEntity.getColor())) - .withPredefinedTextTransformation(new TextTransformation(10.0f, y + 0.2f, 0.0f, 1, 0.16f)) - .build() + if (shouldRenderNextConnections()) { + DLUtils.doIfNotNull(nextConnectionsLines, x -> { + for (int i = 0; i < x.length; i++) { + DLUtils.doIfNotNull(x[i], a -> { + for (int j = 0; j < a.length; j++) { + DLUtils.doIfNotNull(a[j], b -> b.render(graphics, light)); + } + }); + } + }); + nextConnectionsTitleLabel.render(graphics, light); + pageIndicatorLabel.render(graphics, light); + } else if (DragonLib.getCurrentWorldTime() % 500 < 200 && !graphics.blockEntity().getTrainData().isWaitingAtStation()) { + // render stats + speedLabel.render(graphics, light); + dateLabel.render(graphics, light); + carriageInfoLabel.render(graphics, light); + BERUtils.renderTexture( + CARRIAGE_ICON, + graphics, + false, + graphics.blockEntity().getXSizeScaled() * 16 / 2f - carriageInfoLabel.getTextWidth() / 2f - 1.5f, + carriageInfoLabel.getY(), + 0.0f, + 2.25f, + 1.5f, + uv255 * 22, + uv255 * 231, + uv255 * (22 + 13), + uv255 * (231 + 5), + graphics.blockEntity().getBlockState().getValue(HorizontalDirectionalBlock.FACING), + (0xFF << 24) | (graphics.blockEntity().getColor() & 0x00FFFFFF), + light ); - y += PANEL_LINE_HEIGHT; + } else { + // Render schedule + DLUtils.doIfNotNull(scheduleLines, x -> { + for (int i = 0; i < x.length; i++) { + final int idx = i; + DLUtils.doIfNotNull(x[i], a -> { + for (int j = 0; j < a.length; j++) { + DLUtils.doIfNotNull(a[j], b -> b.render(graphics, light)); + } + + final float uv32 = 1f / CRNGui.GUI_WIDTH; + if (idx == 0 && scheduleLines.length > 1) { + BERUtils.renderTexture( + CRNGui.GUI, + graphics, + false, + (a[LineComponent.REAL_TIME.i()] == null ? a[LineComponent.SCHEDULED_TIME.i()].getX() + a[LineComponent.SCHEDULED_TIME.i()].getMaxWidth() : a[LineComponent.REAL_TIME.i()].getX() + a[LineComponent.REAL_TIME.i()].getMaxWidth()) - 1, + a[LineComponent.SCHEDULED_TIME.i()].getY() - 1, + 0.0f, + 1, + 2, + uv32 * 21, + uv32 * 30, + uv32 * (21 + 7), + uv32 * (30 + 14), + graphics.blockEntity().getBlockState().getValue(HorizontalDirectionalBlock.FACING), + (0xFF << 24) | (graphics.blockEntity().getColor() & 0x00FFFFFF), + light + ); + } else if (idx >= MAX_LINES - 1) { + BERUtils.renderTexture( + CRNGui.GUI, + graphics, + false, + (a[LineComponent.REAL_TIME.i()] == null ? a[LineComponent.SCHEDULED_TIME.i()].getX() + a[LineComponent.SCHEDULED_TIME.i()].getMaxWidth() : a[LineComponent.REAL_TIME.i()].getX() + a[LineComponent.REAL_TIME.i()].getMaxWidth()) - 1, + a[LineComponent.SCHEDULED_TIME.i()].getY() - 1, + 0.0f, + 1, + 2, + uv32 * 35, + uv32 * 30, + uv32 * (35 + 7), + uv32 * (30 + 14), + graphics.blockEntity().getBlockState().getValue(HorizontalDirectionalBlock.FACING), + (0xFF << 24) | (graphics.blockEntity().getColor() & 0x00FFFFFF), + light + ); + } else { + BERUtils.renderTexture( + CRNGui.GUI, + graphics, + false, + (a[LineComponent.REAL_TIME.i()] == null ? a[LineComponent.SCHEDULED_TIME.i()].getX() + a[LineComponent.SCHEDULED_TIME.i()].getMaxWidth() : a[LineComponent.REAL_TIME.i()].getX() + a[LineComponent.REAL_TIME.i()].getMaxWidth()) - 1, + a[LineComponent.SCHEDULED_TIME.i()].getY() - 1, + 0.0f, + 1, + 2, + uv32 * 28, + uv32 * 30, + uv32 * (28 + 7), + uv32 * (30 + 14), + graphics.blockEntity().getBlockState().getValue(HorizontalDirectionalBlock.FACING), + (0xFF << 24) | (graphics.blockEntity().getColor() & 0x00FFFFFF), + light + ); + } + }); + } + }); } + + } - if (blockEntity.getTrainData().predictions().size() <= 1) { + @Override + public void update(Level level, BlockPos pos, BlockState state, AdvancedDisplayBlockEntity blockEntity, AdvancedDisplayRenderInstance parent, EUpdateReason reason) { + if (blockEntity.getTrainData() == null || blockEntity.getTrainData().isEmpty()) { return; } + TrainDisplayData data = blockEntity.getTrainData(); + boolean wasNextStopAnnounced = nextStopAnnounced; + nextStopAnnounced = !data.isWaitingAtStation() && data.getNextStop().isPresent() && data.getNextStop().get().getRealTimeArrivalTime() - DragonLib.getCurrentWorldTime() < ModClientConfig.NEXT_STOP_ANNOUNCEMENT.get(); + this.exitSide = !nextStopAnnounced && !data.isWaitingAtStation() ? TrainExitSide.UNKNOWN : (data.isWaitingAtStation() ? exitSide : blockEntity.relativeExitDirection.get()); + + if (blockEntity.getXSizeScaled() > 1 && nextStopAnnounced && !wasNextStopAnnounced && data.getNextStop().isPresent()) { + DataAccessor.getFromServer(new NextConnectionsRequestData(data.getNextStop().get().getName(), data.getTrainData().getId()), ModAccessorTypes.GET_NEXT_CONNECTIONS_DISPLAY_DATA, (res) -> { + nextConnections = res; + updateLayout(blockEntity, data); + updateContent(blockEntity, data); + }); + } - // DESTINATION - pred = blockEntity.getTrainData().getLastStop().get(); - rawTime = (int)(blockEntity.getLastRefreshedTime() % 24000 + pred.departureTicks() + DragonLib.DAYTIME_SHIFT); - line = TextUtils.text(TimeUtils.parseTime(rawTime - rawTime % ModClientConfig.REALTIME_PRECISION_THRESHOLD.get(), ModClientConfig.TIME_FORMAT.get())); - parent.labels.add(new BERText(parent.getFontUtils(), line, 0) - .withIsCentered(false) - .withMaxWidth(4, true) - .withStretchScale(0.08f, 0.14f) - .withStencil(0, 7) - .withColor((0xFF << 24) | (blockEntity.getColor())) - .withPredefinedTextTransformation(new TextTransformation(3.0f, y + 0.3f, 0.0f, 1, 0.14f)) - .build() - ); - line = TextUtils.text(pred.stationTagName()).withStyle(ChatFormatting.BOLD); - parent.labels.add(new BERText(parent.getFontUtils(), line, 0) - .withIsCentered(false) - .withMaxWidth(maxWidth, true) - .withStretchScale(0.2f, 0.2f) - .withStencil(0, maxWidth) - .withCanScroll(true, 0.5f) - .withColor((0xFF << 24) | (blockEntity.getColor())) - .withPredefinedTextTransformation(new TextTransformation(10.0f, y, 0.0f, 1, 0.2f)) - .build() - ); + if (reason == EUpdateReason.LAYOUT_CHANGED || !nextStopAnnounced) { + updateLayout(blockEntity, data); + nextConnections = null; + } + updateContent(blockEntity, data); } - private void updateNextConnections(Level level, BlockPos pos, BlockState state, AdvancedDisplayBlockEntity blockEntity, AdvancedDisplayRenderInstance parent) { - int displayWidth = blockEntity.getXSizeScaled(); - - // ### CONTENT PANEL - if (notInService(blockEntity)) { - return; + private void updateContent(AdvancedDisplayBlockEntity blockEntity, TrainDisplayData data) { + timeLabel + .setText(blockEntity.getXSizeScaled() > 1 && !nextStopAnnounced ? TextUtils.text(ModUtils.formatTime(DragonLib.getCurrentWorldTime(), blockEntity.getTimeDisplay() == ETimeDisplay.ETA)).withStyle(ChatFormatting.BOLD) : TextUtils.empty()) + .setPos(blockEntity.getXSizeScaled() * 16 - 3 - timeLabel.getTextWidth() - (this.exitSide != TrainExitSide.UNKNOWN ? 4 : 0), 2.5f) + .setColor((0xFF << 24) | (blockEntity.getColor() & 0x00FFFFFF)) + ; + carriageLabel + .setText(blockEntity.getXSizeScaled() > 1 && !nextStopAnnounced ? TextUtils.text(String.format("%02d", blockEntity.getCarriageData().index() + 1)).withStyle(ChatFormatting.BOLD) : TextUtils.empty()) + .setPos(timeLabel.getX() - 4 - carriageLabel.getTextWidth(), 2.5f) + .setColor((0xFF << 24) | (blockEntity.getColor() & 0x00FFFFFF)) + ; + trainLineLabel + .setText(nextStopAnnounced ? ELanguage.translate(keyNextStop, data.getNextStop().get().getName()) : TextUtils.text(data.getTrainData().getName()).withStyle(ChatFormatting.BOLD)) + .setMaxWidth(blockEntity.getXSizeScaled() * 16 - 6 - (blockEntity.getXSizeScaled() > 1 && !nextStopAnnounced ? timeLabel.getTextWidth() - 4 : 0) - (blockEntity.getXSizeScaled() > 1 && !nextStopAnnounced ? carriageLabel.getTextWidth() - 5 : 0) - (this.exitSide != TrainExitSide.UNKNOWN ? 4 : 0), BoundsHitReaction.SCALE_SCROLL) + .setColor((0xFF << 24) | (blockEntity.getColor() & 0x00FFFFFF)) + ; + speedLabel + .setText(ModUtils.calcSpeedString(data.getSpeed(), ModClientConfig.SPEED_UNIT.get()).withStyle(ChatFormatting.BOLD)) + .setMaxWidth(blockEntity.getXSizeScaled() * 16 - 6, BoundsHitReaction.CUT_OFF) + .setColor((0xFF << 24) | (blockEntity.getColor() & 0x00FFFFFF)) + ; + dateLabel + .setText(ELanguage.translate(keyDate, blockEntity.getLevel().getDayTime() / Level.TICKS_PER_DAY, ModUtils.formatTime(DragonLib.getCurrentWorldTime(), blockEntity.getTimeDisplay() == ETimeDisplay.ETA))) + .setMaxWidth(blockEntity.getXSizeScaled() * 16 - 6, BoundsHitReaction.CUT_OFF) + .setColor((0xFF << 24) | (blockEntity.getColor() & 0x00FFFFFF)) + ; + carriageInfoLabel + .setText(TextUtils.text(String.format("%02d", blockEntity.getCarriageData().index() + 1))) + .setMaxWidth(blockEntity.getXSizeScaled() * 16 - 6, BoundsHitReaction.CUT_OFF) + .setColor((0xFF << 24) | (blockEntity.getColor() & 0x00FFFFFF)) + ; + + if (shouldRenderNextConnections() && !nextConnections.getConnections().isEmpty()) { + final int pages = (int)Math.ceil((float)nextConnections.getConnections().size() / (MAX_LINES - 1)); + final int page = (int)((DragonLib.getCurrentWorldTime() % (100 * pages)) / 100); + pageIndicatorLabel + .setText(TextUtils.text(generatePageIndexString(page, pages))) + .setMaxWidth(blockEntity.getXSizeScaled() * 16 - 6, BoundsHitReaction.CUT_OFF) + .setColor((0xFF << 24) | (blockEntity.getColor() & 0x00FFFFFF)) + ; + nextConnectionsTitleLabel + .setMaxWidth(blockEntity.getXSizeScaled() * 16 - 6, BoundsHitReaction.CUT_OFF) + .setColor((0xFF << 24) | (blockEntity.getColor() & 0x00FFFFFF)) + ; + DLUtils.doIfNotNull(nextConnectionsLines, x -> { + for (int i = 0; i < MAX_LINES - 1; i++) { + final int k = i; + final int connectionIdx = i + (page * (MAX_LINES - 1)); + DLUtils.doIfNotNull(nextConnectionsLines[i], a -> { + if (connectionIdx >= nextConnections.getConnections().size()) { + a[LineComponent.SCHEDULED_TIME.i()].setText(TextUtils.empty()); + if (a[LineComponent.REAL_TIME.i()] != null) { + a[LineComponent.REAL_TIME.i()].setText(TextUtils.empty()); + } + a[LineComponent.TRAIN_NAME.i()].setText(TextUtils.empty()); + a[LineComponent.DESTINATION.i()].setText(TextUtils.empty()); + a[LineComponent.PLATFORM.i()].setText(TextUtils.empty()); + return; + } + + TrainStopDisplayData stop = nextConnections.getConnections().get(connectionIdx); + a[LineComponent.PLATFORM.i()] + .setText(TextUtils.text(stop.getStationInfo().platform())) + .setPos(blockEntity.getXSizeScaled() * 16 - 3 - a[LineComponent.PLATFORM.i()].getTextWidth(), 7.5f + k * 1.7f) + .setColor((0xFF << 24) | (blockEntity.getColor() & 0x00FFFFFF)) + ; + if (a[LineComponent.REAL_TIME.i()] != null) { + a[LineComponent.SCHEDULED_TIME.i()] + .setPos(3, 7.5f + k * 1.7f) + .setText(TextUtils.text(ModUtils.formatTime(stop.getScheduledDepartureTime(), blockEntity.getTimeDisplay() == ETimeDisplay.ETA))) + .setColor((0xFF << 24) | (blockEntity.getColor() & 0x00FFFFFF)) + ; + a[LineComponent.REAL_TIME.i()] + .setPos(a[LineComponent.SCHEDULED_TIME.i()].getX() + a[LineComponent.SCHEDULED_TIME.i()].getTextWidth() + 1, 7.5f + k * 1.7f) + .setText(TextUtils.text(ModUtils.formatTime(stop.getRealTimeDepartureTime(), blockEntity.getTimeDisplay() == ETimeDisplay.ETA))) + .setColor(stop.isDepartureDelayed() ? Constants.COLOR_DELAYED : Constants.COLOR_ON_TIME) + ; + } else { + a[LineComponent.SCHEDULED_TIME.i()] + .setPos(3, 7.5f + k * 1.7f) + .setText(TextUtils.text(ModUtils.formatTime(stop.getRealTimeDepartureTime(), blockEntity.getTimeDisplay() == ETimeDisplay.ETA))) + .setColor(stop.isDepartureDelayed() ? Constants.COLOR_DELAYED : Constants.COLOR_ON_TIME) + ; + } + float pX = a[LineComponent.SCHEDULED_TIME.i()].getX() + a[LineComponent.SCHEDULED_TIME.i()].getTextWidth() + 1 + (a[LineComponent.REAL_TIME.i()] == null ? 0 : a[LineComponent.REAL_TIME.i()].getTextWidth() + 1); + a[LineComponent.TRAIN_NAME.i()] + .setPos(pX, 7.5f + k * 1.7f) + .setText(TextUtils.text(stop.getTrainName())) + .setMaxWidth(6, BoundsHitReaction.CUT_OFF) + .setColor((0xFF << 24) | (blockEntity.getColor() & 0x00FFFFFF)) + ; + a[LineComponent.DESTINATION.i()] + .setPos(pX + 7, 7.5f + k * 1.7f) + .setText(TextUtils.text(stop.getDestination())) + .setMaxWidth(a[LineComponent.PLATFORM.i()].getX() - 1 - pX - 7, BoundsHitReaction.SCALE_SCROLL) + .setColor((0xFF << 24) | (blockEntity.getColor() & 0x00FFFFFF)) + ; + }); + } + }); + } else { + DLUtils.doIfNotNull(scheduleLines, x -> { + int totalStationsCount = data.getStopsFromCurrentStation().size(); + int linesCount = Math.min(scheduleLines.length, totalStationsCount); + for (int i = 0; i < linesCount; i++) { + final int j = i; + int k = i >= linesCount - 1 ? totalStationsCount - 1 : i; + DLUtils.doIfNotNull(scheduleLines[i], a -> { + TrainStopDisplayData stop = data.getStopsFromCurrentStation().get(k); + if (a[LineComponent.REAL_TIME.i()] != null) { + a[LineComponent.SCHEDULED_TIME.i()] + .setText(TextUtils.text(ModUtils.formatTime(stop.getScheduledArrivalTime(), blockEntity.getTimeDisplay() == ETimeDisplay.ETA))) + .setColor((0xFF << 24) | (blockEntity.getColor() & 0x00FFFFFF)) + ; + a[LineComponent.REAL_TIME.i()] + .setPos(a[LineComponent.SCHEDULED_TIME.i()].getX() + a[LineComponent.SCHEDULED_TIME.i()].getTextWidth() + 1, 6 + j * 2) + .setText(TextUtils.text(ModUtils.formatTime(stop.getRealTimeArrivalTime(), blockEntity.getTimeDisplay() == ETimeDisplay.ETA))) + .setColor(stop.isArrivalDelayed() ? Constants.COLOR_DELAYED : Constants.COLOR_ON_TIME) + ; + } else { + a[LineComponent.SCHEDULED_TIME.i()] + .setText(TextUtils.text(ModUtils.formatTime(stop.getRealTimeArrivalTime(), blockEntity.getTimeDisplay() == ETimeDisplay.ETA))) + .setColor(stop.isArrivalDelayed() ? Constants.COLOR_DELAYED : Constants.COLOR_ON_TIME) + ; + } + float pX = a[LineComponent.SCHEDULED_TIME.i()].getX() + a[LineComponent.SCHEDULED_TIME.i()].getTextWidth() + 3 + (a[LineComponent.REAL_TIME.i()] == null ? 0 : a[LineComponent.REAL_TIME.i()].getTextWidth() + 1); + a[LineComponent.DESTINATION.i()] + .setPos(pX, 6 + j * 2) + .setText(TextUtils.text(stop.getName()).withStyle(j >= linesCount - 1 ? ChatFormatting.BOLD : ChatFormatting.RESET)) + .setMaxWidth(blockEntity.getXSizeScaled() * 16 - 3 - pX, BoundsHitReaction.SCALE_SCROLL) + .setColor((0xFF << 24) | (blockEntity.getColor() & 0x00FFFFFF)) + ; + }); + } + }); } - - float y = PANEL_Y_START; - float maxWidth = displayWidth * 16 - 3; - - MutableComponent ln = TextUtils.text(generatePageIndexString()); - float rawTextWidth = Math.min(parent.getFontUtils().font.width(ln) * 0.2f, maxWidth - 16.0f); - parent.labels.add(new BERText(parent.getFontUtils(), ln, 0) - .withIsCentered(false) - .withMaxWidth(maxWidth - 16.0f, true) - .withStretchScale(0.2f, 0.2f) - .withStencil(0, maxWidth - 16.0f) - .withCanScroll(false, 0.5f) - .withColor((0xFF << 24) | (blockEntity.getColor())) - .withPredefinedTextTransformation(new TextTransformation(maxWidth - rawTextWidth - 0.25f, y - 0.2f, 0.0f, 1, 0.2f)) - .build() - ); - - parent.labels.add(new BERText(parent.getFontUtils(), textNextConnections, 0) - .withIsCentered(false) - .withMaxWidth(maxWidth - 6.0f - rawTextWidth, true) - .withStretchScale(0.1f, 0.15f) - .withStencil(0, maxWidth - 6.0f - rawTextWidth) - .withCanScroll(true, 0.5f) - .withColor((0xFF << 24) | (blockEntity.getColor())) - .withPredefinedTextTransformation(new TextTransformation(3.0f, y, 0.0f, 1, 0.15f)) - .build() - ); - y += PANEL_LINE_HEIGHT; - - if (this.nextConnections == null) { - return; + } + + private BERLabel[] createStationLine(AdvancedDisplayBlockEntity blockEntity, int index) { + BERLabel timeLabel = new BERLabel() + .setPos(3, 6 + index * 2) + .setScale(0.15f, 0.1f) + .setYScale(0.15f) + .setMaxWidth(6, BoundsHitReaction.CUT_OFF) + ; + BERLabel realTimeLabel = null; + if (blockEntity.getXSizeScaled() > 1) { + realTimeLabel = new BERLabel() + .setScale(0.15f, 0.1f) + .setYScale(0.15f) + .setMaxWidth(6, BoundsHitReaction.CUT_OFF) + ; } + BERLabel destinationLabel = new BERLabel() + .setScale(0.15f, 0.08f) + .setYScale(0.15f) + ; - for (int i = nextConnectionsPage * NEXT_CONNECTIONS_MAX_ENTRIES_PER_PAGE; i < (nextConnectionsPage + 1) * NEXT_CONNECTIONS_MAX_ENTRIES_PER_PAGE && i < nextConnections.size(); i++) { - - SimpleTrainConnection connection = nextConnections.get(i); - int rawTime = (int)(nextConnectionsRefreshTime % 24000 + connection.ticks() + DragonLib.DAYTIME_SHIFT); - MutableComponent line = TextUtils.text(TimeUtils.parseTime(rawTime - rawTime % ModClientConfig.REALTIME_PRECISION_THRESHOLD.get(), ModClientConfig.TIME_FORMAT.get())); - - // Time - parent.labels.add(new BERText(parent.getFontUtils(), line, 0) - .withIsCentered(false) - .withMaxWidth(4, true) - .withStretchScale(0.08f, 0.14f) - .withStencil(0, 4) - .withColor((0xFF << 24) | (blockEntity.getColor())) - .withPredefinedTextTransformation(new TextTransformation(4.0f, y + 0.1f, 0.0f, 1, 0.14f)) - .build() - ); - - // Train Name - line = TextUtils.text(connection.trainName()); - parent.labels.add(new BERText(parent.getFontUtils(), line, 0) - .withIsCentered(false) - .withMaxWidth(5, true) - .withStretchScale(0.1f, 0.16f) - .withStencil(0, 5) - .withColor((0xFF << 24) | (blockEntity.getColor())) - .withPredefinedTextTransformation(new TextTransformation(8.5f, y, 0.0f, 1, 0.16f)) - .build() - ); - - // Platform - line = TextUtils.text(connection.stationDetails().platform()); - rawTextWidth = Math.min(parent.getFontUtils().font.width(line) * 0.14f, maxWidth - 16.0f); - parent.labels.add(new BERText(parent.getFontUtils(), line, 0) - .withIsCentered(false) - .withMaxWidth(maxWidth - 16.0f, true) - .withStretchScale(0.14f, 0.16f) - .withStencil(0, maxWidth - 16.0f) - .withCanScroll(false, 0.5f) - .withColor((0xFF << 24) | (blockEntity.getColor())) - .withPredefinedTextTransformation(new TextTransformation(maxWidth - rawTextWidth, y, 0.0f, 1, 0.16f)) - .build() - ); + return new BERLabel[] { timeLabel, realTimeLabel, null, destinationLabel }; + } - // Destination - line = TextUtils.text(connection.scheduleTitle()); - parent.labels.add(new BERText(parent.getFontUtils(), line, 0) - .withIsCentered(false) - .withMaxWidth(maxWidth - 17.0f - rawTextWidth, true) - .withStretchScale(0.1f, 0.16f) - .withStencil(0, maxWidth - 17.0f - rawTextWidth) - .withCanScroll(true, 0.5f) - .withColor((0xFF << 24) | (blockEntity.getColor())) - .withPredefinedTextTransformation(new TextTransformation(14.0f, y, 0.0f, 1, 0.16f)) - .build() - ); - y += PANEL_LINE_HEIGHT; + private BERLabel[] createNextConnectionsLine(AdvancedDisplayBlockEntity blockEntity, int index) { + BERLabel timeLabel = new BERLabel() + .setPos(3, 7 + index * 2) + .setScale(0.15f, 0.1f) + .setYScale(0.15f) + .setMaxWidth(6, BoundsHitReaction.CUT_OFF) + ; + BERLabel realTimeLabel = null; + if (blockEntity.getXSizeScaled() > 2) { + realTimeLabel = new BERLabel() + .setScale(0.15f, 0.1f) + .setYScale(0.15f) + .setMaxWidth(6, BoundsHitReaction.CUT_OFF) + ; } + BERLabel trainNameLabel = new BERLabel() + .setScale(0.15f, 0.08f) + .setYScale(0.15f) + ; + BERLabel destinationLabel = new BERLabel() + .setScale(0.15f, 0.08f) + .setYScale(0.15f) + ; + BERLabel platformLabel = new BERLabel() + .setScale(0.15f, 0.08f) + .setYScale(0.15f) + ; + + return new BERLabel[] { timeLabel, realTimeLabel, trainNameLabel, destinationLabel, platformLabel }; } - private String generatePageIndexString() { - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < nextConnectionsPage; i++) { - sb.append(" □"); + private void updateLayout(AdvancedDisplayBlockEntity blockEntity, TrainDisplayData data) { + if (shouldRenderNextConnections()) { + for (int i = 0; i < MAX_LINES - 1; i++) { + this.nextConnectionsLines[i] = createNextConnectionsLine(blockEntity, i); + } + return; } - sb.append(" ■"); - for (int i = nextConnectionsPage + 1; i < nextConnectionsMaxPage; i++) { - sb.append(" □"); + int totalStationsCount = data.getStopsFromCurrentStation().size(); + int linesCount = Math.min(MAX_LINES, totalStationsCount); + this.scheduleLines = new BERLabel[linesCount][]; + for (int i = 0; i < linesCount; i++) { + this.scheduleLines[i] = createStationLine(blockEntity, i); } + } - return sb.toString(); + private static enum LineComponent { + SCHEDULED_TIME(0), + REAL_TIME(1), + TRAIN_NAME(2), + DESTINATION(3), + PLATFORM(4); + int i; + LineComponent(int i) { + this.i = i; + } + public int i() { + return i; + } } } diff --git a/common/src/main/java/de/mrjulsen/crn/client/ber/variants/BERPassengerInfoSimple.java b/common/src/main/java/de/mrjulsen/crn/client/ber/variants/BERPassengerInfoSimple.java index 9f3e3947..41d13542 100644 --- a/common/src/main/java/de/mrjulsen/crn/client/ber/variants/BERPassengerInfoSimple.java +++ b/common/src/main/java/de/mrjulsen/crn/client/ber/variants/BERPassengerInfoSimple.java @@ -1,220 +1,141 @@ package de.mrjulsen.crn.client.ber.variants; -import com.mojang.blaze3d.vertex.PoseStack; - -import de.mrjulsen.crn.block.be.AdvancedDisplayBlockEntity; +import de.mrjulsen.crn.block.blockentity.AdvancedDisplayBlockEntity; +import de.mrjulsen.crn.block.blockentity.AdvancedDisplayBlockEntity.EUpdateReason; +import de.mrjulsen.crn.block.display.AdvancedDisplaySource.ETimeDisplay; import de.mrjulsen.crn.client.ber.AdvancedDisplayRenderInstance; -import de.mrjulsen.crn.client.ber.base.BERText; -import de.mrjulsen.crn.client.ber.base.BERText.TextTransformation; +import de.mrjulsen.crn.client.ber.IBERRenderSubtype; import de.mrjulsen.crn.client.gui.ModGuiIcons; import de.mrjulsen.crn.client.lang.ELanguage; import de.mrjulsen.crn.config.ModClientConfig; -import de.mrjulsen.crn.data.GlobalSettingsManager; -import de.mrjulsen.crn.data.DeparturePrediction.TrainExitSide; -import de.mrjulsen.crn.event.listeners.JourneyListener.State; -import de.mrjulsen.mcdragonlib.client.ber.IBlockEntityRendererInstance.BlockEntityRendererContext; -import de.mrjulsen.mcdragonlib.client.ber.IBlockEntityRendererInstance.EUpdateReason; -import de.mrjulsen.mcdragonlib.util.DLUtils; +import de.mrjulsen.crn.data.TrainExitSide; +import de.mrjulsen.crn.util.ModUtils; +import de.mrjulsen.mcdragonlib.DragonLib; +import de.mrjulsen.mcdragonlib.client.ber.BERGraphics; +import de.mrjulsen.mcdragonlib.client.ber.BERLabel; +import de.mrjulsen.mcdragonlib.client.ber.BERLabel.BoundsHitReaction; +import de.mrjulsen.mcdragonlib.client.util.BERUtils; import de.mrjulsen.mcdragonlib.util.TextUtils; -import net.minecraft.client.renderer.MultiBufferSource; import net.minecraft.core.BlockPos; -import net.minecraft.network.chat.MutableComponent; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.HorizontalDirectionalBlock; import net.minecraft.world.level.block.state.BlockState; public class BERPassengerInfoSimple implements IBERRenderSubtype { - private State state = State.WHILE_TRAVELING; - private static final String keyNextStop = "gui.createrailwaysnavigator.route_overview.next_stop"; + private static final String keyDate = "gui.createrailwaysnavigator.route_overview.date"; - private BERText announceNextStopLabel; - private BERText whileNextStopLabel; + private static final int TICKS_PER_SLIDE = 100; + + private TrainExitSide exitSide = TrainExitSide.UNKNOWN; + private final BERLabel label = new BERLabel() + .setPos(3, 5.5f) + .setYScale(0.75f) + .setScale(0.75f, 0.75f) + .setCentered(true) + .setScrollingSpeed(2) + ; @Override - public boolean isSingleLined() { - return true; + public void renderTick(float deltaTime) { + label.renderTick(); } + @Override - public void tick(Level level, BlockPos pos, BlockState state, AdvancedDisplayBlockEntity pBlockEntity, AdvancedDisplayRenderInstance parent) { - if (pBlockEntity.getTrainData() == null) { + public void render(BERGraphics graphics, float partialTick, AdvancedDisplayRenderInstance parent, int light, boolean backSide) { + if (graphics.blockEntity().getTrainData() == null || graphics.blockEntity().getTrainData().isEmpty()) { return; } - - DLUtils.doIfNotNull(announceNextStopLabel, x -> x.tick()); - DLUtils.doIfNotNull(whileNextStopLabel, x -> x.tick()); - boolean dirty = false; - if (pBlockEntity.getTrainData().getNextStop().isPresent()) { - if (this.state != State.WHILE_NEXT_STOP && pBlockEntity.getTrainData().getNextStop().get().departureTicks() <= 0) { - this.state = State.WHILE_NEXT_STOP; - dirty = true; - } else if (this.state != State.BEFORE_NEXT_STOP && pBlockEntity.getTrainData().getNextStop().get().departureTicks() <= ModClientConfig.NEXT_STOP_ANNOUNCEMENT.get() && pBlockEntity.getTrainData().getNextStop().get().departureTicks() > 0) { - this.state = State.BEFORE_NEXT_STOP; - dirty = true; - } else if (this.state != State.WHILE_TRAVELING && pBlockEntity.getTrainData().getNextStop().get().departureTicks() > ModClientConfig.NEXT_STOP_ANNOUNCEMENT.get()) { - this.state = State.WHILE_TRAVELING; - dirty = true; - } + float uv = 1.0f / 256.0f; + TrainExitSide side = exitSide; + if (backSide) { + side = side.getOpposite(); } - - if (dirty) { - update(level, pos, state, pBlockEntity, parent, EUpdateReason.DATA_CHANGED); - } - } - - @Override - public void update(Level level, BlockPos pos, BlockState state, AdvancedDisplayBlockEntity blockEntity, AdvancedDisplayRenderInstance parent, EUpdateReason reason) { - if (blockEntity.getTrainData() == null) { - return; + switch (side) { + case RIGHT: + BERUtils.renderTexture( + ModGuiIcons.ICON_LOCATION, + graphics, + false, + graphics.blockEntity().getXSizeScaled() * 16 - 3 - 8, + 4, + 0, + 8, + 8, + uv * ModGuiIcons.ARROW_RIGHT.getU(), + uv * ModGuiIcons.ARROW_RIGHT.getV(), + uv * (ModGuiIcons.ARROW_RIGHT.getU() + ModGuiIcons.ICON_SIZE), + uv * (ModGuiIcons.ARROW_RIGHT.getV() + ModGuiIcons.ICON_SIZE), + graphics.blockEntity().getBlockState().getValue(HorizontalDirectionalBlock.FACING), + (0xFF << 24) | (graphics.blockEntity().getColor()), + light + ); + break; + case LEFT: + BERUtils.renderTexture( + ModGuiIcons.ICON_LOCATION, + graphics, + false, + 3, + 4, + 0, + 8, + 8, + uv * ModGuiIcons.ARROW_LEFT.getU(), + uv * ModGuiIcons.ARROW_LEFT.getV(), + uv * (ModGuiIcons.ARROW_LEFT.getU() + ModGuiIcons.ICON_SIZE), + uv * (ModGuiIcons.ARROW_LEFT.getV() + ModGuiIcons.ICON_SIZE), + graphics.blockEntity().getBlockState().getValue(HorizontalDirectionalBlock.FACING), + (0xFF << 24) | (graphics.blockEntity().getColor()), + light + ); + break; + default: + break; } - - parent.labels.clear(); - announceNextStopLabel = null; - whileNextStopLabel = null; - switch (this.state) { - case BEFORE_NEXT_STOP: - updateAnnounceNextStop(level, pos, state, blockEntity, parent); - break; - case WHILE_NEXT_STOP: - updateWhileNextStop(level, pos, state, blockEntity, parent); + graphics.poseStack().pushPose(); + switch (side) { + case LEFT: + graphics.poseStack().translate(10, 0, 0); break; default: - updateDefault(level, pos, state, blockEntity, parent); break; } + label.render(graphics, light); + graphics.poseStack().popPose(); } - + @Override - public void renderAdditional(BlockEntityRendererContext context, AdvancedDisplayBlockEntity pBlockEntity, AdvancedDisplayRenderInstance parent, float pPartialTicks, PoseStack pPoseStack, MultiBufferSource pBufferSource, int pPackedLight, int pOverlay, Boolean backSide) { - if (state == State.WHILE_NEXT_STOP || state == State.BEFORE_NEXT_STOP) { - context.renderUtils().initRenderEngine(); - TrainExitSide side = pBlockEntity.relativeExitDirection.get(); - float uv = 1.0f / 256.0f; - - if (backSide) { - side = side.getOpposite(); - } + public void update(Level level, BlockPos pos, BlockState state, AdvancedDisplayBlockEntity blockEntity, AdvancedDisplayRenderInstance parent, EUpdateReason data) { + if (blockEntity.getTrainData() == null ||blockEntity.getTrainData().isEmpty()) { + return; + } - switch (side) { - case RIGHT: - context.renderUtils().renderTexture( - ModGuiIcons.ICON_LOCATION, - pBufferSource, - pBlockEntity, - pPoseStack, - false, - pBlockEntity.getXSizeScaled() * 16 - 3 - 8, - 4, - 0, - 8, - 8, - uv * ModGuiIcons.ARROW_RIGHT.getU(), - uv * ModGuiIcons.ARROW_RIGHT.getV(), - uv * (ModGuiIcons.ARROW_RIGHT.getU() + ModGuiIcons.ICON_SIZE), - uv * (ModGuiIcons.ARROW_RIGHT.getV() + ModGuiIcons.ICON_SIZE), - pBlockEntity.getBlockState().getValue(HorizontalDirectionalBlock.FACING), - (0xFF << 24) | (pBlockEntity.getColor()), - pPackedLight - ); - break; - case LEFT: - context.renderUtils().renderTexture( - ModGuiIcons.ICON_LOCATION, - pBufferSource, - pBlockEntity, - pPoseStack, - false, - 3f, - 4, - 0, - 8, - 8, - uv * ModGuiIcons.ARROW_LEFT.getU(), - uv * ModGuiIcons.ARROW_LEFT.getV(), - uv * (ModGuiIcons.ARROW_LEFT.getU() + ModGuiIcons.ICON_SIZE), - uv * (ModGuiIcons.ARROW_LEFT.getV() + ModGuiIcons.ICON_SIZE), - pBlockEntity.getBlockState().getValue(HorizontalDirectionalBlock.FACING), - (0xFF << 24) | (pBlockEntity.getColor()), - pPackedLight - ); - break; - default: - break; + this.exitSide = blockEntity.getTrainData().isWaitingAtStation() ? exitSide : blockEntity.relativeExitDirection.get(); + if (!blockEntity.getTrainData().getNextStop().isPresent()) { + label.setText(TextUtils.text(blockEntity.getTrainData().getTrainData().getName())); + } else if (blockEntity.getTrainData().isWaitingAtStation()) { + label.setText(TextUtils.text(blockEntity.getTrainData().getNextStop().get().getName())); + } else if (blockEntity.getTrainData().getNextStop().get().getRealTimeArrivalTime() - DragonLib.getCurrentWorldTime() < ModClientConfig.NEXT_STOP_ANNOUNCEMENT.get()) { + label.setText(ELanguage.translate(keyNextStop, blockEntity.getTrainData().getNextStop().get().getName())); + } else { + final int slides = 3; + int slide = (int)(DragonLib.getCurrentWorldTime() % (TICKS_PER_SLIDE * slides)) / TICKS_PER_SLIDE; + switch (slide) { + case 0 -> label.setText(TextUtils.text(blockEntity.getTrainData().getTrainData().getName() + " " + blockEntity.getTrainData().getNextStop().get().getDestination())); + case 1 -> label.setText(ELanguage.translate(keyDate, blockEntity.getLevel().getDayTime() / Level.TICKS_PER_DAY, ModUtils.formatTime(DragonLib.getCurrentWorldTime(), blockEntity.getTimeDisplay() == ETimeDisplay.ETA))); + case 2 -> label.setText(ModUtils.calcSpeedString(blockEntity.getTrainData().getSpeed(), ModClientConfig.SPEED_UNIT.get())); } - - if (backSide) { - switch (side) { - case LEFT: - pPoseStack.translate(10, 0, 0); - break; - case RIGHT: - pPoseStack.translate(-10, 0, 0); - break; - default: - break; - } - } - - DLUtils.doIfNotNull(announceNextStopLabel, x -> x.render(pPoseStack, pBufferSource, pPackedLight)); - DLUtils.doIfNotNull(whileNextStopLabel, x -> x.render(pPoseStack, pBufferSource, pPackedLight)); + this.exitSide = TrainExitSide.UNKNOWN; } - } - - private void updateDefault(Level level, BlockPos pos, BlockState state, AdvancedDisplayBlockEntity blockEntity, AdvancedDisplayRenderInstance parent) { - int displayWidth = blockEntity.getXSizeScaled(); - - MutableComponent line = TextUtils.text(blockEntity.getTrainData().trainName()).append(" ").append(TextUtils.text(blockEntity.getTrainData().getNextStop().isPresent() ? blockEntity.getTrainData().getNextStop().get().scheduleTitle() : "")); - float maxWidth = displayWidth * 16 - 6; - parent.labels.add(new BERText(parent.getFontUtils(), line, 0) - .withIsCentered(true) - .withMaxWidth(maxWidth, true) - .withStretchScale(0.75f, 0.75f) - .withStencil(0, maxWidth) - .withCanScroll(true, 1) - .withColor((0xFF << 24) | (blockEntity.getColor())) - .withPredefinedTextTransformation(new TextTransformation(3, 5.5f, 0.0f, 1, 0.75f)) - .build() - ); - } - - private void updateAnnounceNextStop(Level level, BlockPos pos, BlockState state, AdvancedDisplayBlockEntity blockEntity, AdvancedDisplayRenderInstance parent) { - int displayWidth = blockEntity.getXSizeScaled(); - TrainExitSide side = blockEntity.relativeExitDirection.get(); - - MutableComponent line = ELanguage.translate(keyNextStop, GlobalSettingsManager.getInstance().getSettingsData().getAliasFor(blockEntity.getTrainData().getNextStop().isPresent() ? blockEntity.getTrainData().getNextStop().get().stationTagName() : "").getAliasName().get()); - float maxWidth = displayWidth * 16 - 6 - (side != TrainExitSide.UNKNOWN ? 10 : 0); - announceNextStopLabel = (new BERText(parent.getFontUtils(), line, 0) - .withIsCentered(true) - .withMaxWidth(maxWidth, true) - .withStretchScale(0.75f, 0.75f) - .withStencil(0, maxWidth) - .withCanScroll(true, 1) - .withColor((0xFF << 24) | (blockEntity.getColor())) - .withPredefinedTextTransformation(new TextTransformation(3 + (side == TrainExitSide.LEFT ? 10 : 0), 5.5f, 0.0f, 1, 0.75f)) - .build() - ); - } - - private void updateWhileNextStop(Level level, BlockPos pos, BlockState state, AdvancedDisplayBlockEntity blockEntity, AdvancedDisplayRenderInstance parent) { - int displayWidth = blockEntity.getXSizeScaled(); - - TrainExitSide side = blockEntity.relativeExitDirection.get(); - MutableComponent line = TextUtils.text(GlobalSettingsManager.getInstance().getSettingsData().getAliasFor(blockEntity.getTrainData().getNextStop().isPresent() ? blockEntity.getTrainData().getNextStop().get().stationTagName() : "").getAliasName().get()); - float maxWidth = displayWidth * 16 - 6 - (side != TrainExitSide.UNKNOWN ? 10 : 0); - whileNextStopLabel = (new BERText(parent.getFontUtils(), line, 0) - .withIsCentered(true) - .withMaxWidth(maxWidth, true) - .withStretchScale(0.75f, 0.75f) - .withStencil(0, maxWidth) - .withCanScroll(true, 1) - .withColor((0xFF << 24) | (blockEntity.getColor())) - .withPredefinedTextTransformation(new TextTransformation(3 + (side == TrainExitSide.LEFT ? 10 : 0), 5.5f, 0.0f, 1, 0.75f)) - .build() - ); + label + .setMaxWidth(blockEntity.getXSizeScaled() * 16 - 6 - (exitSide == TrainExitSide.UNKNOWN ? 0 : 10), BoundsHitReaction.SCROLL) + .setColor((0xFF << 24) | (blockEntity.getColor() & 0x00FFFFFF)) + ; } } diff --git a/common/src/main/java/de/mrjulsen/crn/client/ber/variants/BERPlatformDetailed.java b/common/src/main/java/de/mrjulsen/crn/client/ber/variants/BERPlatformDetailed.java index fdc502e0..3d560f3b 100644 --- a/common/src/main/java/de/mrjulsen/crn/client/ber/variants/BERPlatformDetailed.java +++ b/common/src/main/java/de/mrjulsen/crn/client/ber/variants/BERPlatformDetailed.java @@ -1,27 +1,30 @@ package de.mrjulsen.crn.client.ber.variants; import java.util.ArrayList; +import java.util.Collection; import java.util.List; -import com.mojang.blaze3d.vertex.PoseStack; - -import de.mrjulsen.crn.block.be.AdvancedDisplayBlockEntity; +import de.mrjulsen.crn.CreateRailwaysNavigator; +import de.mrjulsen.crn.block.blockentity.AdvancedDisplayBlockEntity; +import de.mrjulsen.crn.block.blockentity.AdvancedDisplayBlockEntity.EUpdateReason; +import de.mrjulsen.crn.block.display.AdvancedDisplaySource.ETimeDisplay; import de.mrjulsen.crn.client.ber.AdvancedDisplayRenderInstance; -import de.mrjulsen.crn.client.ber.base.BERText; -import de.mrjulsen.crn.client.ber.base.BERText.TextTransformation; +import de.mrjulsen.crn.client.ber.IBERRenderSubtype; import de.mrjulsen.crn.client.lang.ELanguage; import de.mrjulsen.crn.config.ModClientConfig; -import de.mrjulsen.crn.data.DeparturePrediction.SimpleDeparturePrediction; +import de.mrjulsen.crn.data.train.TrainStatus.CompiledTrainStatus; +import de.mrjulsen.crn.data.train.portable.StationDisplayData; import de.mrjulsen.crn.util.ModUtils; import de.mrjulsen.mcdragonlib.DragonLib; -import de.mrjulsen.mcdragonlib.client.ber.IBlockEntityRendererInstance.BlockEntityRendererContext; -import de.mrjulsen.mcdragonlib.client.ber.IBlockEntityRendererInstance.EUpdateReason; +import de.mrjulsen.mcdragonlib.client.ber.BERGraphics; +import de.mrjulsen.mcdragonlib.client.ber.BERLabel; +import de.mrjulsen.mcdragonlib.client.ber.BERLabel.BoundsHitReaction; import de.mrjulsen.mcdragonlib.util.DLUtils; import de.mrjulsen.mcdragonlib.util.TextUtils; import de.mrjulsen.mcdragonlib.util.TimeUtils; -import net.minecraft.client.renderer.MultiBufferSource; import net.minecraft.core.BlockPos; import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.MutableComponent; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.state.BlockState; @@ -29,202 +32,226 @@ public class BERPlatformDetailed implements IBERRenderSubtype lastPredictions = new ArrayList<>(); + private static final float LINE_HEIGHT = 5.4f; - private BERText timeLabel; - private BERText[][] additionalLabels; - - private int timer; - private static final int MAX_TIMER = 100; - private boolean showTime = false; + private boolean showInfoLine = false; + private MutableComponent infoLineText = TextUtils.empty(); + private int maxLines = 0; + + private final BERLabel timeLabel = new BERLabel(TextUtils.empty()) + .setCentered(true) + .setScale(0.4f, 0.4f) + .setYScale(0.4f); + private final BERLabel statusLabel = new BERLabel(TextUtils.empty()) + .setCentered(true) + .setScale(0.4f, 0.4f) + .setYScale(0.4f) + .setColor(0xFF111111) + .setBackground(0xFFFFFFFF, true) + .setScrollingSpeed(2); + private BERLabel[][] lines = new BERLabel[0][]; - @Override - public boolean isSingleLined() { - return false; - } @Override - public void tick(Level level, BlockPos pos, BlockState state, AdvancedDisplayBlockEntity pBlockEntity, AdvancedDisplayRenderInstance parent) { - if (timeLabel != null) timeLabel.tick(); - - if (additionalLabels != null) { - for (int i = 0; i < additionalLabels.length; i++) { - if (additionalLabels[i] == null) { - continue; - } - - for (int k = 0; k < additionalLabels[i].length; k++) { - if (additionalLabels[i][k] == null) { - continue; - } - - additionalLabels[i][k].tick(); + public void renderTick(float deltaTime) { + timeLabel.renderTick(); + statusLabel.renderTick(); + DLUtils.doIfNotNull(lines, x -> { + for (int i = 0; i < x.length; i++) { + BERLabel[] line = x[i]; + if (line == null) continue; + for (int k = 0; k < line.length; k++) { + DLUtils.doIfNotNull(line[k], y -> y.renderTick()); } } - } + }); + } - timer++; - if ((timer %= MAX_TIMER) == 0) { - showTime = !showTime; - } + @Override + public void tick(Level level, BlockPos pos, BlockState state, AdvancedDisplayBlockEntity blockEntity, AdvancedDisplayRenderInstance parent) { + timeLabel + .setText(ELanguage.translate(keyTime, ModUtils.formatTime(DragonLib.getCurrentWorldTime(), blockEntity.getTimeDisplay() == ETimeDisplay.ETA))) + ; } @Override - public void renderAdditional(BlockEntityRendererContext context, AdvancedDisplayBlockEntity pBlockEntity, AdvancedDisplayRenderInstance parent, float pPartialTicks, PoseStack pPoseStack, MultiBufferSource pBufferSource, int pPackedLight, int pOverlay, Boolean backSide) { - if (additionalLabels != null) { - for (int i = 0; i < additionalLabels.length; i++) { - if (additionalLabels[i] == null) { + public void render(BERGraphics graphics, float pPartialTicks, AdvancedDisplayRenderInstance parent, int light, boolean backSide) { + for (int i = 0; i < lines.length && i < maxLines; i++) { + for (int k = 0; k < lines[i].length; k++) { + if (i >= maxLines - 1 && (DragonLib.getCurrentWorldTime() % 200 > 100)) { + timeLabel.render(graphics, light); continue; } + lines[i][k].render(graphics, light); + } + } - if (i >= pBlockEntity.getPlatformInfoLinesCount() - 1 && showTime) { - break; - } else { - for (int k = 0; k < additionalLabels[i].length; k++) { - if (additionalLabels[i][k] == null) { - continue; - } - - additionalLabels[i][k].render(pPoseStack, pBufferSource, pPackedLight); - } + if (lines.length < maxLines) { + timeLabel.render(graphics, light); + } - if (i >= pBlockEntity.getPlatformInfoLinesCount() - 1) { - return; - } - } - } + if (showInfoLine) { + statusLabel.render(graphics, light); } - timeLabel.render(pPoseStack, pBufferSource, pPackedLight); } @Override public void update(Level level, BlockPos pos, BlockState state, AdvancedDisplayBlockEntity blockEntity, AdvancedDisplayRenderInstance parent, EUpdateReason reason) { - - parent.labels.clear(); - - List preds = blockEntity.getPredictions().stream().filter(x -> x.departureTicks() < ModClientConfig.DISPLAY_LEAD_TIME.get()).toList(); + List preds = blockEntity.getStops().stream().filter(x -> x.getStationData().getScheduledArrivalTime() < DragonLib.getCurrentWorldTime() + ModClientConfig.DISPLAY_LEAD_TIME.get() && (!x.getTrainData().isCancelled() || DragonLib.getCurrentWorldTime() < x.getStationData().getScheduledDepartureTime() + ModClientConfig.DISPLAY_LEAD_TIME.get())).toList(); + + showInfoLine = !preds.isEmpty() && preds.get(0).getStationData().isDepartureDelayed() && preds.get(0).getTrainData().hasStatusInfo(); + if (showInfoLine) { + // Update status label + this.infoLineText = TextUtils.concat(TextUtils.text(" +++ "), preds.stream().limit(maxLines).filter(x -> x.getTrainData().hasStatusInfo() && x.getStationData().isDepartureDelayed()).flatMap(x -> { + Collection content = new ArrayList<>(); + if (x.getTrainData().isCancelled()) { + content.add(ELanguage.translate("block." + CreateRailwaysNavigator.MOD_ID + ".advanced_display.ber.information_about_cancelled", x.getTrainData().getName())); + return content.stream(); + } + content.add(ELanguage.translate("block." + CreateRailwaysNavigator.MOD_ID + ".advanced_display.ber.information_about_delayed", x.getTrainData().getName(), TimeUtils.formatToMinutes(x.getStationData().getDepartureTimeDeviation()))); + for (CompiledTrainStatus status : x.getTrainData().getStatus()) { + content.add(status.text()); + } + return content.stream(); + }).toArray(Component[]::new)); + } else { + infoLineText = TextUtils.empty(); + } - if (preds.size() <= 0) { - additionalLabels = null; - setTimer(level, pos, state, blockEntity, parent, reason, 4f); - return; + int defaultMaxLines = blockEntity.getYSizeScaled() * 3 - 1; + this.maxLines = defaultMaxLines - (showInfoLine ? 1 : 0); + int maxIndices = Math.max(0, Math.min(this.maxLines, preds.size())); + if (reason == EUpdateReason.LAYOUT_CHANGED || this.lines == null || lines.length != maxIndices) { + updateLayout(blockEntity, preds, maxIndices); + } + + for (int i = 0; i < this.lines.length; i++) { + StationDisplayData stop = preds.get(i); + updateContent(blockEntity, stop, i); } - - int maxLines = blockEntity.getPlatformInfoLinesCount(); - boolean refreshAll = reason != EUpdateReason.DATA_CHANGED || !DLUtils.compareCollections(lastPredictions, preds, (a, b) -> a.stationInfo().platform().equals(b.stationInfo().platform()) && a.trainId().equals(b.trainId())); - - lastPredictions = preds; - if (refreshAll) { - additionalLabels = null; - timeLabel = null; - additionalLabels = new BERText[Math.min(preds.size(), maxLines)][]; + statusLabel + .setText(infoLineText) + .setPos(3, blockEntity.getYSizeScaled() * 16 - 12 * statusLabel.getYScale() - 2) + .setMaxWidth(blockEntity.getXSizeScaled() * 16 - 6, BoundsHitReaction.SCALE_SCROLL) + ; + } - for (int i = 0; i < additionalLabels.length; i++) { - additionalLabels[i] = addLine(level, pos, state, blockEntity, parent, reason, i, 4 + (i * 5.4f)); - } - setTimer(level, pos, state, blockEntity, parent, reason, 4 + ((additionalLabels.length < maxLines ? additionalLabels.length : maxLines - 1) * 5.34f)); + private void updateLayout(AdvancedDisplayBlockEntity blockEntity, List preds, int maxIndices) { + this.lines = new BERLabel[maxIndices][]; + for (int i = 0; i < this.lines.length; i++) { + StationDisplayData stop = preds.get(i); + this.lines[i] = createLine(blockEntity, stop, i); + updateContent(blockEntity, stop, i); } - + statusLabel + .setBackground((0xFF << 24) | (blockEntity.getColor() & 0x00FFFFFF), false) + ; + timeLabel + .setPos(3, 3 + (Math.min(lines.length, maxLines) - (lines.length < maxLines ? 0 : 1)) * LINE_HEIGHT) + .setMaxWidth(blockEntity.getXSizeScaled() * 16 - 6, BoundsHitReaction.CUT_OFF) + .setColor((0xFF << 24) | (blockEntity.getColor() & 0x00FFFFFF)) + ; } - - private BERText[] addLine(Level level, BlockPos pos, BlockState state, AdvancedDisplayBlockEntity blockEntity, AdvancedDisplayRenderInstance parent, EUpdateReason reason, int predictionIdx, float y) { - float displayWidth = blockEntity.getXSizeScaled() * 16 - 4; - - BERText[] labels = new BERText[4]; - - // PLATFORM - Component label = TextUtils.text(lastPredictions.get(predictionIdx).stationInfo().platform()); - float labelWidth = blockEntity.getPlatformWidth() < 0 ? parent.getFontUtils().font.width(label) * 0.4f : Math.min(parent.getFontUtils().font.width(label) * 0.4f, blockEntity.getPlatformWidth() - 2); - int platformMaxWidth = blockEntity.getPlatformWidth() < 0 ? (int)(displayWidth - 6) : blockEntity.getPlatformWidth() - 2; - - BERText lastLabel = new BERText(parent.getFontUtils(), label, 0) - .withIsCentered(false) - .withMaxWidth(platformMaxWidth, true) - .withStretchScale(0.2f, 0.4f) - .withStencil(0, platformMaxWidth) - .withColor((0xFF << 24) | (blockEntity.getColor())) - .withPredefinedTextTransformation(new TextTransformation(blockEntity.getXSizeScaled() * 16 - 3 - labelWidth, y, 0.01f, 1, 0.4f)) - .build(); - labels[0] = lastLabel; - - // TIME - labels[1] = new BERText(parent.getFontUtils(), () -> { - List texts = new ArrayList<>(); - switch (blockEntity.getTimeDisplay()) { - case ETA: - texts.add(TextUtils.text(ModUtils.timeRemainingString(lastPredictions.get(predictionIdx).departureTicks()))); - break; - default: - int rawTime = (int)(blockEntity.getLastRefreshedTime() % DragonLib.TICKS_PER_DAY + DragonLib.DAYTIME_SHIFT + lastPredictions.get(predictionIdx).departureTicks()); - texts.add(TextUtils.text(TimeUtils.parseTime(rawTime - rawTime % ModClientConfig.REALTIME_PRECISION_THRESHOLD.get(), ModClientConfig.TIME_FORMAT.get()))); - break; - } - return texts; - }, 0) - .withIsCentered(false) - .withMaxWidth(TIME_LABEL_WIDTH - 4, true) - .withStretchScale(0.2f, 0.4f) - .withStencil(0, TIME_LABEL_WIDTH - 4) - .withColor((0xFF << 24) | (blockEntity.getColor())) - .withRefreshRate(100) - .withPredefinedTextTransformation(new TextTransformation(3, y, 0.01f, 1, 0.4f)) - .build() - ; - - float platformWidth = blockEntity.getPlatformWidth() < 0 ? lastLabel.getScaledTextWidth() + 2 : blockEntity.getPlatformWidth(); - int trainNameWidth = blockEntity.getTrainNameWidth(); - - lastLabel = new BERText(parent.getFontUtils(), () -> { - List texts = new ArrayList<>(); - texts.add(TextUtils.text(lastPredictions.get(predictionIdx).trainName())); - return texts; - }, 0) - .withIsCentered(false) - .withMaxWidth(Math.min(trainNameWidth - 1, displayWidth - TIME_LABEL_WIDTH - platformWidth - 1), false) - .withStretchScale(0.2f, 0.4f) - .withStencil(0, Math.min(trainNameWidth - 1, displayWidth - TIME_LABEL_WIDTH - platformWidth - 1)) - .withColor((0xFF << 24) | (blockEntity.getColor())) - .withPredefinedTextTransformation(new TextTransformation(TIME_LABEL_WIDTH, y, 0.01f, 1, 0.4f)) - .build() - ; - labels[2] = lastLabel; - - lastLabel = new BERText(parent.getFontUtils(), () -> { - List texts = new ArrayList<>(); - texts.add(TextUtils.text(lastPredictions.get(predictionIdx).scheduleTitle())); - return texts; - }, 0) - .withIsCentered(false) - .withMaxWidth(displayWidth - TIME_LABEL_WIDTH - trainNameWidth - platformWidth + 1, true) - .withStretchScale(0.25f, 0.4f) - .withStencil(0, displayWidth - TIME_LABEL_WIDTH - trainNameWidth - platformWidth + 1) - .withCanScroll(true, 1) - .withColor((0xFF << 24) | (blockEntity.getColor())) - .withPredefinedTextTransformation(new TextTransformation(TIME_LABEL_WIDTH + trainNameWidth, y, 0.01f, 1, 0.4f)) - .build() + private void updateContent(AdvancedDisplayBlockEntity blockEntity, StationDisplayData stop, int index) { + boolean isLast = stop.isLastStop(); + BERLabel[] components = lines[index]; + components[LineComponent.TIME.i()] + .setText(TextUtils.text(ModUtils.formatTime(stop.getScheduledTime(), blockEntity.getTimeDisplay() == ETimeDisplay.ETA))) + ; + components[LineComponent.REAL_TIME.i()] + .setText(TextUtils.text(stop.getTrainData().isCancelled() ? + " \u274C " : // X + (stop.getStationData().isDepartureDelayed() ? + (ModUtils.formatTime(stop.getRealTime(), blockEntity.getTimeDisplay() == ETimeDisplay.ETA)) : + ""))) // Nothing (not delayed) + ; + components[LineComponent.TRAIN_NAME.i()] + .setText(TextUtils.text(stop.getTrainData().getName())) ; + components[LineComponent.PLATFORM.i()] + .setText(blockEntity.isPlatformFixed() ? + TextUtils.empty() : + TextUtils.text(stop.getStationData().getStationInfo().platform())) + ; + components[LineComponent.DESTINATION.i()] + .setText(isLast ? + ELanguage.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".schedule_board.train_from", stop.getFirstStopName()) : + TextUtils.text(stop.getStationData().getDestination())) + ; + + int x = 3; + components[LineComponent.TIME.i()].setPos(x, 3 + index * LINE_HEIGHT); + x += components[LineComponent.TIME.i()].getTextWidth() + 2; + components[LineComponent.REAL_TIME.i()].setPos(x, 3 + index * LINE_HEIGHT); + x += components[LineComponent.REAL_TIME.i()].getTextWidth() + 2 + (!components[LineComponent.REAL_TIME.i()].getText().getString().isEmpty() ? 2 : 0); - labels[3] = lastLabel; - return labels; - } + BERLabel trainNameLabel = components[LineComponent.TRAIN_NAME.i()] + .setPos(x, 3 + index * LINE_HEIGHT) + .setMaxWidth(blockEntity.getTrainNameWidth(), BoundsHitReaction.SCALE_SCROLL) + ; + x += trainNameLabel.getMaxWidth() + 2; + + BERLabel platformLabel = components[LineComponent.PLATFORM.i()]; + float platformWidth = platformLabel.getTextWidth(); + platformLabel.setPos(blockEntity.getXSizeScaled() * 16 - 3 - platformWidth, 3 + index * LINE_HEIGHT); + components[LineComponent.DESTINATION.i()].setPos(x, 3 + index * LINE_HEIGHT); + components[LineComponent.DESTINATION.i()].setMaxWidth(blockEntity.getXSizeScaled() * 16 - 3 - x - platformWidth - 3, BoundsHitReaction.SCALE_SCROLL); + } + + private BERLabel[] createLine(AdvancedDisplayBlockEntity blockEntity, StationDisplayData stop, int index) { + BERLabel[] components = new BERLabel[5]; + + components[LineComponent.TIME.i()] = new BERLabel() + .setYScale(0.4f) + .setMaxWidth(12, BoundsHitReaction.SCALE_SCROLL) + .setScale(0.4f, 0.2f) + .setColor((0xFF << 24) | (blockEntity.getColor() & 0x00FFFFFF)) + ; + components[LineComponent.REAL_TIME.i()] = new BERLabel() + .setYScale(0.4f) + .setMaxWidth(12, BoundsHitReaction.SCALE_SCROLL) + .setScale(0.4f, 0.2f) + .setBackground((0xFF << 24) | (blockEntity.getColor() & 0x00FFFFFF), false) + .setColor(0xFF111111) + ; + components[LineComponent.TRAIN_NAME.i()] = new BERLabel() + .setYScale(0.4f) + .setScrollingSpeed(2) + .setScale(0.4f, 0.2f) + .setColor((0xFF << 24) | (blockEntity.getColor() & 0x00FFFFFF)) + ; + + components[LineComponent.PLATFORM.i()] = new BERLabel() + .setYScale(0.4f) + .setScale(0.4f, 0.2f) + .setColor((0xFF << 24) | (blockEntity.getColor() & 0x00FFFFFF)) + ; - public void setTimer(Level level, BlockPos pos, BlockState state, AdvancedDisplayBlockEntity blockEntity, AdvancedDisplayRenderInstance parent, EUpdateReason reason, float y) { - float displayWidth = blockEntity.getXSizeScaled() * 16 - 6; - timeLabel = new BERText(parent.getFontUtils(), () -> List.of(ELanguage.translate(keyTime, TimeUtils.parseTime((int)(blockEntity.getLevel().getDayTime() % DragonLib.TICKS_PER_DAY + DragonLib.DAYTIME_SHIFT), ModClientConfig.TIME_FORMAT.get()))), 0) - .withIsCentered(true) - .withMaxWidth(displayWidth, true) - .withStretchScale(0.4f, 0.4f) - .withStencil(0, displayWidth) - .withCanScroll(true, 1) - .withColor((0xFF << 24) | (blockEntity.getColor())) - .withTicksPerPage(100) - .withRefreshRate(16) - .withPredefinedTextTransformation(new TextTransformation(3, y, 0.0f, 1, 0.4f)) - .build() + components[LineComponent.DESTINATION.i()] = new BERLabel() + .setYScale(0.4f) + .setScrollingSpeed(2) + .setScale(0.4f, 0.2f) + .setColor((0xFF << 24) | (blockEntity.getColor() & 0x00FFFFFF)) ; + + return components; + } + + private static enum LineComponent { + TIME(0), + REAL_TIME(1), + TRAIN_NAME(2), + DESTINATION(3), + PLATFORM(4); + + int index; + LineComponent(int index) { + this.index = index; + } + public int i() { + return index; + } } } diff --git a/common/src/main/java/de/mrjulsen/crn/client/ber/variants/BERPlatformInformative.java b/common/src/main/java/de/mrjulsen/crn/client/ber/variants/BERPlatformInformative.java index 338197da..41a8d1df 100644 --- a/common/src/main/java/de/mrjulsen/crn/client/ber/variants/BERPlatformInformative.java +++ b/common/src/main/java/de/mrjulsen/crn/client/ber/variants/BERPlatformInformative.java @@ -1,26 +1,29 @@ package de.mrjulsen.crn.client.ber.variants; import java.util.ArrayList; +import java.util.Collection; import java.util.List; -import com.mojang.blaze3d.vertex.PoseStack; - -import de.mrjulsen.crn.block.be.AdvancedDisplayBlockEntity; +import de.mrjulsen.crn.CreateRailwaysNavigator; +import de.mrjulsen.crn.block.blockentity.AdvancedDisplayBlockEntity; +import de.mrjulsen.crn.block.blockentity.AdvancedDisplayBlockEntity.EUpdateReason; +import de.mrjulsen.crn.block.display.AdvancedDisplaySource.ETimeDisplay; import de.mrjulsen.crn.client.ber.AdvancedDisplayRenderInstance; -import de.mrjulsen.crn.client.ber.base.BERText; -import de.mrjulsen.crn.client.ber.base.BERText.TextTransformation; +import de.mrjulsen.crn.client.ber.IBERRenderSubtype; import de.mrjulsen.crn.client.lang.ELanguage; import de.mrjulsen.crn.config.ModClientConfig; -import de.mrjulsen.crn.data.DeparturePrediction.SimpleDeparturePrediction; +import de.mrjulsen.crn.data.train.TrainStatus.CompiledTrainStatus; +import de.mrjulsen.crn.data.train.portable.StationDisplayData; import de.mrjulsen.crn.util.ModUtils; import de.mrjulsen.mcdragonlib.DragonLib; -import de.mrjulsen.mcdragonlib.client.ber.IBlockEntityRendererInstance.BlockEntityRendererContext; -import de.mrjulsen.mcdragonlib.client.ber.IBlockEntityRendererInstance.EUpdateReason; +import de.mrjulsen.mcdragonlib.client.ber.BERGraphics; +import de.mrjulsen.mcdragonlib.client.ber.BERLabel; +import de.mrjulsen.mcdragonlib.client.ber.BERLabel.BoundsHitReaction; +import de.mrjulsen.mcdragonlib.client.util.BERUtils; import de.mrjulsen.mcdragonlib.util.DLUtils; import de.mrjulsen.mcdragonlib.util.TextUtils; import de.mrjulsen.mcdragonlib.util.TimeUtils; import net.minecraft.ChatFormatting; -import net.minecraft.client.renderer.MultiBufferSource; import net.minecraft.core.BlockPos; import net.minecraft.network.chat.Component; import net.minecraft.world.level.Level; @@ -28,320 +31,368 @@ import net.minecraft.world.level.block.state.BlockState; public class BERPlatformInformative implements IBERRenderSubtype { - - private List lastPredictions = new ArrayList<>(); - - private static final int TIME_LABEL_WIDTH = 16; - private static final int SPACING = 4; - - private static final String keyPlatform = "gui.createrailwaysnavigator.platform"; - private static final String keyLine = "gui.createrailwaysnavigator.line"; - private static final String keyDestination = "gui.createrailwaysnavigator.destination"; - private static final String keyDeparture = "gui.createrailwaysnavigator.departure"; + private static final String keyFollowingTrains = "gui.createrailwaysnavigator.following_trains"; - - // cache - private boolean wasPlatformFixed; + private static final float LINE_HEIGHT = 5.4f; - @Override - public boolean isSingleLined() { - return false; - } + private int maxLines = 0; + private boolean showInfoLine = false; + private Component infoLineText = TextUtils.empty(); + private BERLabel statusLabel; + private BERLabel[] focusArea; + private BERLabel[][] lines; + private final BERLabel followingTrainsLabel = new BERLabel(ELanguage.translate(keyFollowingTrains)).setPos(3, 16).setScale(0.2f, 0.2f).setYScale(0.2f); + @Override - public void tick(Level level, BlockPos pos, BlockState state, AdvancedDisplayBlockEntity pBlockEntity, AdvancedDisplayRenderInstance parent) { - + public void renderTick(float deltaTime) { + DLUtils.doIfNotNull(statusLabel, x -> x.renderTick()); + DLUtils.doIfNotNull(focusArea, x -> { + for (int i = 0; i < x.length; i++) { + DLUtils.doIfNotNull(x[i], y -> y.renderTick()); + } + }); + DLUtils.doIfNotNull(lines, x -> { + for (int i = 0; i < x.length; i++) { + DLUtils.doIfNotNull(x[i], y -> { + for (int j = 0; j < y.length; j++) { + DLUtils.doIfNotNull(y[j], z -> z.renderTick()); + } + }); + } + }); } - private boolean extendedDisplay(AdvancedDisplayBlockEntity blockEntity) { + private boolean isExtendedDisplay(AdvancedDisplayBlockEntity blockEntity) { return blockEntity.getYSize() > 1; } @Override - public void renderAdditional(BlockEntityRendererContext context, AdvancedDisplayBlockEntity pBlockEntity, AdvancedDisplayRenderInstance parent, float pPartialTicks, PoseStack pPoseStack, MultiBufferSource pBufferSource, int pPackedLight, int pOverlay, Boolean backSide) { - boolean isPlatformFixed = pBlockEntity.isPlatformFixed(); - context.renderUtils().initRenderEngine(); - if (!isPlatformFixed) { - context.renderUtils().fillColor(pBufferSource, pBlockEntity, (0xFF << 24) | (pBlockEntity.getColor() & 0x00FFFFFF), pPoseStack, 2.5f, 6.0f, 0.0f, pBlockEntity.getXSizeScaled() * 16 - 5, 0.25f, pBlockEntity.getBlockState().getValue(HorizontalDirectionalBlock.FACING), pPackedLight); - - int count = pBlockEntity.getPlatformInfoLinesCount(); - for (int i = 0; i < count; i += 2) { - context.renderUtils().fillColor(pBufferSource, pBlockEntity, 0x22FFFFFF, pPoseStack, 2, 4 + ((i + 1) * 5.34f) - 1f, 0.0f, pBlockEntity.getXSizeScaled() * 16 - 4, 5.34f, pBlockEntity.getBlockState().getValue(HorizontalDirectionalBlock.FACING), pPackedLight); + public void render(BERGraphics graphics, float pPartialTicks, AdvancedDisplayRenderInstance parent, int light, boolean backSide) { + if (isExtendedDisplay(graphics.blockEntity())) { + BERUtils.fillColor(graphics, 2.5f, 15.5f, 0.0f, graphics.blockEntity().getXSizeScaled() * 16 - 5, 0.25f, (0xFF << 24) | (graphics.blockEntity().getColor() & 0x00FFFFFF), graphics.blockEntity().getBlockState().getValue(HorizontalDirectionalBlock.FACING), light); + followingTrainsLabel + .setColor((0xFF << 24) | (graphics.blockEntity().getColor() & 0x00FFFFFF)) + ; + followingTrainsLabel.render(graphics, light); + } + + DLUtils.doIfNotNull(focusArea, a -> { + for (int i = 0; i < a.length; i++) { + BERLabel label = a[i]; + if (label == null) continue; + + graphics.poseStack().pushPose(); + if (backSide) { + float maxWidth = graphics.blockEntity().getXSizeScaled() * 16; + if (i == LineComponent.TIME.i()) { + graphics.poseStack().translate(-label.getX() + maxWidth - 3 - label.getTextWidth(), 0, 0); + } else if (i == LineComponent.REAL_TIME.i()) { + graphics.poseStack().translate(-label.getX() + maxWidth - 3 - label.getTextWidth(), 0, 0); + } else if (i == LineComponent.TRAIN_NAME.i()) { + graphics.poseStack().translate(-label.getX() + maxWidth - 3 - label.getTextWidth(), 0, 0); + } else if (i == LineComponent.DESTINATION.i()) { + graphics.poseStack().translate(-label.getX() + 5 + a[LineComponent.PLATFORM.i()].getTextWidth(), 0, 0); + } else if (i == LineComponent.STOPOVERS.i()) { + graphics.poseStack().translate(-label.getX() + 5 + a[LineComponent.PLATFORM.i()].getTextWidth(), 0, 0); + } else if (i == LineComponent.PLATFORM.i()) { + graphics.poseStack().translate(-label.getX() + 3, 0, 0); + } + } + + label.render(graphics, light); + graphics.poseStack().popPose(); } - } else { - if (extendedDisplay(pBlockEntity)) { - context.renderUtils().fillColor(pBufferSource, pBlockEntity, (0xFF << 24) | (pBlockEntity.getColor() & 0x00FFFFFF), pPoseStack, 2.5f, 15.5f, 0.0f, pBlockEntity.getXSizeScaled() * 16 - 5, 0.25f, pBlockEntity.getBlockState().getValue(HorizontalDirectionalBlock.FACING), pPackedLight); + }); + DLUtils.doIfNotNull(lines, x -> { + for (BERLabel[] line : x) { + if (line == null) continue; + for (BERLabel label : line) { + if (label == null) continue; + label.render(graphics, light); + } } + }); + + if (statusLabel != null && !statusLabel.getText().getString().isBlank()) { + graphics.poseStack().pushPose(); + if (backSide && focusArea != null && focusArea[LineComponent.PLATFORM.i()] != null) { + graphics.poseStack().translate(-statusLabel.getX() + 5 + focusArea[LineComponent.PLATFORM.i()].getTextWidth(), 0, 0); + } + DLUtils.doIfNotNull(statusLabel, x -> x.render(graphics, light)); + graphics.poseStack().popPose(); } } @Override public void update(Level level, BlockPos pos, BlockState state, AdvancedDisplayBlockEntity blockEntity, AdvancedDisplayRenderInstance parent, EUpdateReason reason) { - List preds = blockEntity.getPredictions(); - boolean isPlatformFixed = blockEntity.isPlatformFixed(); - /* - if (preds.size() <= 0) { - parent.labels.clear(); + List preds = blockEntity.getStops().stream().filter(x -> !x.getTrainData().isCancelled() || DragonLib.getCurrentWorldTime() < x.getStationData().getScheduledDepartureTime() + ModClientConfig.DISPLAY_LEAD_TIME.get()).toList(); + + if (preds.isEmpty()) { + lines = null; + focusArea = null; + statusLabel = null; return; } - */ - - int maxLines = blockEntity.getPlatformInfoLinesCount() - (isPlatformFixed ? 1 : 0) ; - boolean refreshAll = reason != EUpdateReason.DATA_CHANGED || - !DLUtils.compareCollections(lastPredictions, preds, (a, b) -> a.stationInfo().platform().equals(b.stationInfo().platform()) && a.trainId().equals(b.trainId())) || - wasPlatformFixed != isPlatformFixed - ; - - lastPredictions = preds; - wasPlatformFixed = blockEntity.isPlatformFixed(); + + if (reason == EUpdateReason.LAYOUT_CHANGED || this.lines == null || this.focusArea == null) { + updateLayout(blockEntity, preds); + } - if (refreshAll) { - parent.labels.clear(); - if (isPlatformFixed) { - addNextDeparture(level, pos, state, blockEntity, parent, reason, 0); + showInfoLine = preds.get(0).getStationData().isDepartureDelayed() && preds.get(0).getTrainData().hasStatusInfo(); + if (showInfoLine) { + // Update status label + Collection content = new ArrayList<>(); + if (preds.get(0).getTrainData().isCancelled()) { + content.add(ELanguage.translate("block." + CreateRailwaysNavigator.MOD_ID + ".advanced_display.ber.cancelled")); } else { - addHeader(level, pos, state, blockEntity, parent, reason); - } - - if (isPlatformFixed && preds.size() > 0) { - for (int i = 1; i < maxLines && i < preds.size(); i++) { - addLine(level, pos, state, blockEntity, parent, reason, i, 4 + ((i + 2) * 5.4f)); - } - } else { - for (int i = 0; i < maxLines && i < preds.size(); i++) { - addLine(level, pos, state, blockEntity, parent, reason, i, 4 + ((i + 1) * 5.34f)); + content.add(ELanguage.translate("block." + CreateRailwaysNavigator.MOD_ID + ".advanced_display.ber.delayed", TimeUtils.formatToMinutes(preds.get(0).getStationData().getDepartureTimeDeviation()))); + for (CompiledTrainStatus status : preds.get(0).getTrainData().getStatus()) { + content.add(status.text()); } } + this.infoLineText = TextUtils.concat(TextUtils.text(" +++ "), content); + } else { + infoLineText = TextUtils.empty(); + } + + updateFocusContent(blockEntity, preds.get(0)); + for (int i = 1; i < this.lines.length && i < preds.size(); i++) { + StationDisplayData stop = preds.get(i); + updateTableContent(blockEntity, stop, i); } } - private void addLine(Level level, BlockPos pos, BlockState state, AdvancedDisplayBlockEntity blockEntity, AdvancedDisplayRenderInstance parent, EUpdateReason reason, int predictionIdx, float y) { - float displayWidth = blockEntity.getXSizeScaled() * 16 - 4; - - // PLATFORM - Component label = blockEntity.isPlatformFixed() ? TextUtils.empty() : TextUtils.text(lastPredictions.get(predictionIdx).stationInfo().platform()); - float labelWidth = blockEntity.getPlatformWidth() < 0 ? parent.getFontUtils().font.width(label) * 0.4f : Math.min(parent.getFontUtils().font.width(label) * 0.4f, blockEntity.getPlatformWidth() - 2); - int platformMaxWidth = blockEntity.getPlatformWidth() < 0 ? (int)(displayWidth - 6) : blockEntity.getPlatformWidth() - 2; - - BERText lastLabel = new BERText(parent.getFontUtils(), label, 0) - .withIsCentered(false) - .withMaxWidth(platformMaxWidth, true) - .withStretchScale(0.2f, 0.4f) - .withStencil(0, platformMaxWidth) - .withColor((0xFF << 24) | (blockEntity.getColor())) - .withPredefinedTextTransformation(new TextTransformation(blockEntity.getXSizeScaled() * 16 - 3 - labelWidth, y, 0.01f, 1, 0.4f)) - .build(); - parent.labels.add(lastLabel); - - // TIME - parent.labels.add(new BERText(parent.getFontUtils(), () -> { - List texts = new ArrayList<>(); - switch (blockEntity.getTimeDisplay()) { - case ETA: - texts.add(TextUtils.text(ModUtils.timeRemainingString(lastPredictions.get(predictionIdx).departureTicks()))); - break; - default: - int rawTime = (int)(blockEntity.getLastRefreshedTime() % DragonLib.TICKS_PER_DAY + DragonLib.DAYTIME_SHIFT + lastPredictions.get(predictionIdx).departureTicks()); - texts.add(TextUtils.text(TimeUtils.parseTime(rawTime - rawTime % ModClientConfig.REALTIME_PRECISION_THRESHOLD.get(), ModClientConfig.TIME_FORMAT.get()))); - break; - } - return texts; - }, 0) - .withIsCentered(false) - .withMaxWidth(TIME_LABEL_WIDTH - 4, true) - .withStretchScale(0.2f, 0.4f) - .withStencil(0, TIME_LABEL_WIDTH - 4) - .withColor((0xFF << 24) | (blockEntity.getColor())) - .withRefreshRate(100) - .withPredefinedTextTransformation(new TextTransformation(3, y, 0.01f, 1, 0.4f)) - .build() - ); - - float platformWidth = blockEntity.getPlatformWidth() < 0 ? lastLabel.getScaledTextWidth() + 2 : blockEntity.getPlatformWidth(); - int trainNameWidth = blockEntity.getTrainNameWidth(); - - // TRAIN NAME - lastLabel = new BERText(parent.getFontUtils(), () -> { - List texts = new ArrayList<>(); - texts.add(TextUtils.text(lastPredictions.get(predictionIdx).trainName())); - return texts; - }, 0) - .withIsCentered(false) - .withMaxWidth(Math.min(trainNameWidth - 1, displayWidth - TIME_LABEL_WIDTH - platformWidth - 1), false) - .withStretchScale(0.2f, 0.4f) - .withStencil(0, Math.min(trainNameWidth - 1, displayWidth - TIME_LABEL_WIDTH - platformWidth - 1)) - .withColor((0xFF << 24) | (blockEntity.getColor())) - .withPredefinedTextTransformation(new TextTransformation(TIME_LABEL_WIDTH, y, 0.01f, 1, 0.4f)) - .build() - ; - parent.labels.add(lastLabel); - - // DESTINATION - lastLabel = new BERText(parent.getFontUtils(), () -> { - List texts = new ArrayList<>(); - texts.add(TextUtils.text(lastPredictions.get(predictionIdx).scheduleTitle())); - return texts; - }, 0) - .withIsCentered(false) - .withMaxWidth(displayWidth - TIME_LABEL_WIDTH - trainNameWidth - platformWidth + 1 - SPACING, true) - .withStretchScale(0.25f, 0.4f) - .withStencil(0, displayWidth - TIME_LABEL_WIDTH - trainNameWidth - platformWidth + 1 - SPACING) - .withCanScroll(true, 1) - .withColor((0xFF << 24) | (blockEntity.getColor())) - .withPredefinedTextTransformation(new TextTransformation(TIME_LABEL_WIDTH + trainNameWidth + SPACING, y, 0.01f, 1, 0.4f)) - .build() + private void updateLayout(AdvancedDisplayBlockEntity blockEntity, List preds) { + this.focusArea = new BERLabel[7]; + this.lines = new BERLabel[0][]; + + followingTrainsLabel + .setColor((0xFF << 24) | (blockEntity.getColor() & 0x00FFFFFF)) ; - - parent.labels.add(lastLabel); - } - private void addNextDeparture(Level level, BlockPos pos, BlockState state, AdvancedDisplayBlockEntity blockEntity, AdvancedDisplayRenderInstance parent, EUpdateReason reason, int predictionIdx) { - float displayWidth = blockEntity.getXSizeScaled() * 16 - 6; - int timeLabelWidth = TIME_LABEL_WIDTH - 3; - - // PLATFORM - Component label = TextUtils.text(blockEntity.getStationInfo().platform()).withStyle(ChatFormatting.BOLD); - float labelWidth = parent.getFontUtils().font.width(label) * 0.6f; - BERText lastLabel = new BERText(parent.getFontUtils(), label, 0) - .withIsCentered(false) - .withMaxWidth(TIME_LABEL_WIDTH, true) - .withStretchScale(0.6f, 0.6f) - .withStencil(0, displayWidth) - .withColor((0xFF << 24) | (blockEntity.getColor())) - .withPredefinedTextTransformation(new TextTransformation(displayWidth - labelWidth + 3, 3, 0.0f, 1, 1f)) - .build(); - parent.labels.add(lastLabel); - - if (lastPredictions.size() <= 0) { - return; - } + BERLabel timeLabel = new BERLabel() + .setPos(3, 3) + .setYScale(0.4f) + .setScale(0.4f, 0.2f) + .setColor((0xFF << 24) | (blockEntity.getColor() & 0x00FFFFFF)) + ; + focusArea[LineComponent.TIME.i()] = timeLabel; + + BERLabel realTimeLabel = new BERLabel() + .setPos(3, 7) + .setYScale(0.4f) + .setScale(0.4f, 0.2f) + .setBackground((0xFF << 24) | (blockEntity.getColor() & 0x00FFFFFF), false) + .setColor(0xFF111111) + ; + focusArea[LineComponent.REAL_TIME.i()] = realTimeLabel; + + BERLabel trainNameLabel = new BERLabel() + .setPos(3, 7) + .setYScale(0.3f) + .setMaxWidth(12, BoundsHitReaction.IGNORE) + .setScale(0.3f, 0.15f) + .setColor((0xFF << 24) | (blockEntity.getColor() & 0x00FFFFFF)) + ; + focusArea[LineComponent.TRAIN_NAME.i()] = trainNameLabel; - float platformWidth = lastLabel.getScaledTextWidth(); - - // TIME - parent.labels.add(new BERText(parent.getFontUtils(), () -> { - List texts = new ArrayList<>(); - switch (blockEntity.getTimeDisplay()) { - case ETA: - texts.add(TextUtils.text(ModUtils.timeRemainingString(lastPredictions.get(predictionIdx).departureTicks()))); - break; - default: - int rawTime = (int)(blockEntity.getLevel().getDayTime() % DragonLib.TICKS_PER_DAY + DragonLib.DAYTIME_SHIFT + lastPredictions.get(predictionIdx).departureTicks()); - texts.add(TextUtils.text(TimeUtils.parseTime(rawTime - rawTime % ModClientConfig.REALTIME_PRECISION_THRESHOLD.get(), ModClientConfig.TIME_FORMAT.get()))); - break; + BERLabel platformLabel = new BERLabel() + .setYScale(0.8f) + .setScale(0.6f, 0.5f) + .setColor((0xFF << 24) | (blockEntity.getColor() & 0x00FFFFFF)) + ; + focusArea[LineComponent.PLATFORM.i()] = platformLabel; + + BERLabel destinationLabel = new BERLabel() + .setYScale(0.6f) + .setScale(0.6f, 0.4f) + .setScrollingSpeed(2) + .setColor((0xFF << 24) | (blockEntity.getColor() & 0x00FFFFFF)) + ; + focusArea[LineComponent.DESTINATION.i()] = destinationLabel; + + BERLabel stopoversLabel = new BERLabel() + .setYScale(0.2f) + .setScale(0.2f, 0.1f) + .setScrollingSpeed(2) + .setColor((0xFF << 24) | (blockEntity.getColor() & 0x00FFFFFF)) + ; + focusArea[LineComponent.STOPOVERS.i()] = stopoversLabel; + + statusLabel = new BERLabel() + .setText(infoLineText) + .setYScale(0.3f) + .setScale(0.3f, 0.3f) + .setScrollingSpeed(2) + .setBackground((0xFF << 24) | (blockEntity.getColor() & 0x00FFFFFF), true) + .setColor(0xFF111111) + ; + + if (isExtendedDisplay(blockEntity)) { + maxLines = (blockEntity.getYSizeScaled() - 1) * 3; + int maxIndices = Math.min(this.maxLines, preds.size()); + this.lines = new BERLabel[Math.max(maxIndices, 0)][]; + for (int i = 1; i < this.lines.length; i++) { + StationDisplayData stop = preds.get(i); + this.lines[i] = createTableLine(blockEntity, stop, i); } - return texts; - }, 0) - .withIsCentered(false) - .withMaxWidth(timeLabelWidth, true) - .withStretchScale(0.2f, 0.4f) - .withStencil(0, timeLabelWidth) - .withRefreshRate(100) - .withColor((0xFF << 24) | (blockEntity.getColor())) - .withPredefinedTextTransformation(new TextTransformation(3, 3, 0.0f, 1, 0.4f)) - .build() - ); - - parent.labels.add(new BERText(parent.getFontUtils(), TextUtils.text(lastPredictions.get(predictionIdx).trainName()), 0) - .withIsCentered(false) - .withMaxWidth(timeLabelWidth, true) - .withStretchScale(0.15f, 0.3f) - .withStencil(0, timeLabelWidth) - .withColor((0xFF << 24) | (blockEntity.getColor())) - .withPredefinedTextTransformation(new TextTransformation(3, 7, 0.0f, 1, 0.3f)) - .build() - ); - - parent.labels.add(new BERText(parent.getFontUtils(), TextUtils.text(lastPredictions.get(predictionIdx).scheduleTitle()).withStyle(ChatFormatting.BOLD), 0) - .withIsCentered(false) - .withMaxWidth(displayWidth - timeLabelWidth - platformWidth - 5, true) - .withStretchScale(0.3f, 0.6f) - .withStencil(0, displayWidth - timeLabelWidth - platformWidth - 5) - .withCanScroll(true, 0.5f) - .withColor((0xFF << 24) | (blockEntity.getColor())) - .withPredefinedTextTransformation(new TextTransformation(3 + timeLabelWidth + 1, 8, 0.0f, 1, 0.6f)) - .build() - ); - - parent.labels.add(new BERText(parent.getFontUtils(), parent.getStopoversString(blockEntity), 0) - .withIsCentered(false) - .withMaxWidth(displayWidth - TIME_LABEL_WIDTH - platformWidth - 2, true) - .withStretchScale(0.2f, 0.25f) - .withStencil(0, displayWidth - TIME_LABEL_WIDTH - platformWidth - 2) - .withCanScroll(true, 0.5f) - .withColor((0xFF << 24) | (blockEntity.getColor())) - .withPredefinedTextTransformation(new TextTransformation(TIME_LABEL_WIDTH + 1, 5, 0.0f, 1, 0.25f)) - .build() - ); - - if (extendedDisplay(blockEntity)) { - parent.labels.add(new BERText(parent.getFontUtils(), ELanguage.translate(keyFollowingTrains), 0) - .withIsCentered(false) - .withMaxWidth(displayWidth, true) - .withStretchScale(0.15f, 0.2f) - .withStencil(0, displayWidth) - .withColor((0xFF << 24) | (blockEntity.getColor())) - .withPredefinedTextTransformation(new TextTransformation(3, 17, 0.0f, 1, 0.2f)) - .build() - ); } } - private void addHeader(Level level, BlockPos pos, BlockState state, AdvancedDisplayBlockEntity blockEntity, AdvancedDisplayRenderInstance parent, EUpdateReason reason) { - float displayWidth = blockEntity.getXSizeScaled() * 16 - 4; - float TIME_LABEL_WIDTH = 16; - - // TIME - parent.labels.add(new BERText(parent.getFontUtils(), ELanguage.translate(keyDeparture).withStyle(ChatFormatting.BOLD).withStyle(ChatFormatting.ITALIC), 0) - .withIsCentered(false) - .withMaxWidth(TIME_LABEL_WIDTH - 4, true) - .withStretchScale(0.15f, 0.3f) - .withStencil(0, Math.min(TIME_LABEL_WIDTH - 4, displayWidth - 2)) - .withColor((0xFF << 24) | (blockEntity.getColor())) - .withPredefinedTextTransformation(new TextTransformation(3, 3, 0.0f, 1, 0.3f)) - .build() - ); - - // PLATFORM - Component label = ELanguage.translate(keyPlatform).withStyle(ChatFormatting.BOLD).withStyle(ChatFormatting.ITALIC); - float labelWidth = blockEntity.getPlatformWidth() < 0 ? parent.getFontUtils().font.width(label) * 0.3f : Math.min(parent.getFontUtils().font.width(label) * 0.4f, blockEntity.getPlatformWidth() - 2); - int platformMaxWidth = blockEntity.getPlatformWidth() < 0 ? (int)(displayWidth - 6) : blockEntity.getPlatformWidth() - 2; + private BERLabel[] createTableLine(AdvancedDisplayBlockEntity blockEntity, StationDisplayData stop, int index) { + BERLabel[] components = new BERLabel[5]; + + components[LineComponent.TIME.i()] = new BERLabel() + .setYScale(0.4f) + .setMaxWidth(12, BoundsHitReaction.SCALE_SCROLL) + .setScale(0.4f, 0.2f) + .setColor((0xFF << 24) | (blockEntity.getColor() & 0x00FFFFFF)) + ; + components[LineComponent.REAL_TIME.i()] = new BERLabel() + .setYScale(0.4f) + .setMaxWidth(12, BoundsHitReaction.SCALE_SCROLL) + .setScale(0.4f, 0.2f) + .setBackground((0xFF << 24) | (blockEntity.getColor() & 0x00FFFFFF), false) + .setColor(0xFF111111) + ; + components[LineComponent.TRAIN_NAME.i()] = new BERLabel() + .setYScale(0.4f) + //.setMaxWidth(14, BoundsHitReaction.SCALE_SCROLL) + .setScrollingSpeed(2) + .setScale(0.4f, 0.2f) + .setColor((0xFF << 24) | (blockEntity.getColor() & 0x00FFFFFF)) + ; - BERText lastLabel = new BERText(parent.getFontUtils(), label, 0) - .withIsCentered(false) - .withMaxWidth(platformMaxWidth, true) - .withStretchScale(0.15f, 0.3f) - .withStencil(0, labelWidth) - .withColor((0xFF << 24) | (blockEntity.getColor())) - .withPredefinedTextTransformation(new TextTransformation(blockEntity.getXSizeScaled() * 16 - 3 - labelWidth, 3, 0.0f, 1, 0.3f)) - .build(); - parent.labels.add(lastLabel); - - float platformWidth = blockEntity.getPlatformWidth() < 0 ? lastLabel.getScaledTextWidth() + 2 : blockEntity.getPlatformWidth(); - int trainNameWidth = blockEntity.getTrainNameWidth(); - - lastLabel = new BERText(parent.getFontUtils(), ELanguage.translate(keyLine).withStyle(ChatFormatting.BOLD).withStyle(ChatFormatting.ITALIC), 0) - .withIsCentered(false) - .withMaxWidth(Math.min(trainNameWidth - 1, displayWidth - TIME_LABEL_WIDTH - platformWidth - 1), true) - .withStretchScale(0.15f, 0.3f) - .withStencil(0, Math.min(trainNameWidth - 1, displayWidth - TIME_LABEL_WIDTH - platformWidth - 1)) - .withColor((0xFF << 24) | (blockEntity.getColor())) - .withPredefinedTextTransformation(new TextTransformation(TIME_LABEL_WIDTH, 3, 0.0f, 1, 0.3f)) - .build() - ; - parent.labels.add(lastLabel); - - lastLabel = new BERText(parent.getFontUtils(), ELanguage.translate(keyDestination).withStyle(ChatFormatting.BOLD).withStyle(ChatFormatting.ITALIC), 0) - .withIsCentered(false) - .withMaxWidth(displayWidth - TIME_LABEL_WIDTH - trainNameWidth - platformWidth + 1, true) - .withStretchScale(0.15f, 0.3f) - .withStencil(0, displayWidth - TIME_LABEL_WIDTH - trainNameWidth - platformWidth + 1) - .withColor((0xFF << 24) | (blockEntity.getColor())) - .withPredefinedTextTransformation(new TextTransformation(TIME_LABEL_WIDTH + trainNameWidth, 3, 0.0f, 1, 0.3f)) - .build() + components[LineComponent.PLATFORM.i()] = new BERLabel() + .setYScale(0.4f) + .setScale(0.4f, 0.2f) + .setColor((0xFF << 24) | (blockEntity.getColor() & 0x00FFFFFF)) + ; + + components[LineComponent.DESTINATION.i()] = new BERLabel() + .setYScale(0.4f) + .setScrollingSpeed(2) + .setScale(0.4f, 0.2f) + .setColor((0xFF << 24) | (blockEntity.getColor() & 0x00FFFFFF)) + ; + + return components; + } + + private void updateFocusContent(AdvancedDisplayBlockEntity blockEntity, StationDisplayData stop) { + + followingTrainsLabel + .setColor((0xFF << 24) | (blockEntity.getColor() & 0x00FFFFFF)) + ; + + BERLabel timeLabel = focusArea[LineComponent.TIME.i()] + .setText(TextUtils.text(ModUtils.formatTime(stop.getScheduledTime(), blockEntity.getTimeDisplay() == ETimeDisplay.ETA))) + ; + + BERLabel realTimeLabel = focusArea[LineComponent.REAL_TIME.i()] + .setText(TextUtils.text(stop.isDelayed() ? ModUtils.formatTime(stop.getRealTime(), blockEntity.getTimeDisplay() == ETimeDisplay.ETA) : "")) + ; + + BERLabel trainNameLabel = focusArea[LineComponent.TRAIN_NAME.i()] + .setText(TextUtils.text(stop.getTrainData().getName())) + .setPos(3, 7 + (stop.isDelayed() ? 4.5f : 0)) + .setMaxWidth(blockEntity.getTrainNameWidth(), BoundsHitReaction.SCALE_SCROLL) ; + + BERLabel platformLabel = focusArea[LineComponent.PLATFORM.i()]; + platformLabel + .setText(TextUtils.text(stop.getStationData().getStationInfo().platform()).withStyle(ChatFormatting.BOLD)) + .setPos(blockEntity.getXSizeScaled() * 16 - 3 - platformLabel.getTextWidth(), 3); + ; + float platformWidth = platformLabel.getTextWidth(); + + float x = 5 + Math.max(trainNameLabel.getTextWidth(), Math.max(timeLabel.getTextWidth(), realTimeLabel.getTextWidth())); + float w = blockEntity.getXSizeScaled() * 16 - 5 - platformWidth - x; + focusArea[LineComponent.DESTINATION.i()] + .setText(stop.isLastStop() ? + ELanguage.translate("block." + CreateRailwaysNavigator.MOD_ID + ".advanced_display.ber.arrival") : + TextUtils.text(stop.getStationData().getDestination())) + .setPos(x, 8.5f) + .setMaxWidth(w, BoundsHitReaction.SCALE_SCROLL) + ; + + focusArea[LineComponent.STOPOVERS.i()] + .setText(stop.isLastStop() ? + ELanguage.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".schedule_board.train_from", stop.getFirstStopName()) : + TextUtils.concat(TextUtils.text(" \u25CF "), stop.getStopovers().stream().map(a -> (Component)TextUtils.text(a)).toList())) + .setPos(x, 6) + .setMaxWidth(w, BoundsHitReaction.SCALE_SCROLL) + ; + + statusLabel + .setText(infoLineText) + .setPos(x, 2.5f) + .setMaxWidth(w, BoundsHitReaction.SCALE_SCROLL) + ; + } + + private void updateTableContent(AdvancedDisplayBlockEntity blockEntity, StationDisplayData stop, int index) { + boolean isLast = stop.isLastStop(); + BERLabel[] components = lines[index]; + components[LineComponent.TIME.i()] + .setText(TextUtils.text(ModUtils.formatTime(stop.getScheduledTime(), blockEntity.getTimeDisplay() == ETimeDisplay.ETA))) + ; + components[LineComponent.REAL_TIME.i()] + .setText(TextUtils.text(stop.getTrainData().isCancelled() ? + " \u274C " : // X + (stop.getStationData().isDepartureDelayed() ? + (ModUtils.formatTime(isLast ? stop.getStationData().getRealTimeArrivalTime() : stop.getStationData().getRealTimeDepartureTime(), blockEntity.getTimeDisplay() == ETimeDisplay.ETA)) : + ""))) // Nothing (not delayed) + ; + components[LineComponent.TRAIN_NAME.i()] + .setText(TextUtils.text(stop.getTrainData().getName())) + ; + components[LineComponent.PLATFORM.i()] + .setText(blockEntity.isPlatformFixed() ? + TextUtils.empty() : + TextUtils.text(stop.getStationData().getStationInfo().platform())) + ; + components[LineComponent.DESTINATION.i()] + .setText(isLast ? + ELanguage.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".schedule_board.train_from", stop.getFirstStopName()) : + TextUtils.text(stop.getStationData().getDestination())) + ; + + int x = 3; + components[LineComponent.TIME.i()].setPos(x, 11 + 3 + index * LINE_HEIGHT); + x += components[LineComponent.TIME.i()].getTextWidth() + 2; + components[LineComponent.REAL_TIME.i()].setPos(x, 11 + 3 + index * LINE_HEIGHT); + x += components[LineComponent.REAL_TIME.i()].getTextWidth() + 2 + (!components[LineComponent.REAL_TIME.i()].getText().getString().isEmpty() ? 2 : 0); + + BERLabel trainNameLabel = components[LineComponent.TRAIN_NAME.i()] + .setPos(x, 11 + 3 + index * LINE_HEIGHT) + .setMaxWidth(blockEntity.getTrainNameWidth(), BoundsHitReaction.SCALE_SCROLL) + ; + x += trainNameLabel.getMaxWidth() + 2; - parent.labels.add(lastLabel); + BERLabel platformLabel = components[LineComponent.PLATFORM.i()]; + float platformWidth = platformLabel.getTextWidth(); + platformLabel.setPos(blockEntity.getXSizeScaled() * 16 - 3 - platformWidth, 11 + 3 + index * LINE_HEIGHT); + components[LineComponent.DESTINATION.i()].setPos(x, 11 + 3 + index * LINE_HEIGHT); + components[LineComponent.DESTINATION.i()].setMaxWidth(blockEntity.getXSizeScaled() * 16 - 3 - x - platformWidth - 3, BoundsHitReaction.SCALE_SCROLL); + } + + private static enum LineComponent { + TIME(0), + REAL_TIME(1), + TRAIN_NAME(2), + DESTINATION(3), + PLATFORM(4), + STOPOVERS(5); + + int index; + LineComponent(int index) { + this.index = index; + } + public int i() { + return index; + } } } diff --git a/common/src/main/java/de/mrjulsen/crn/client/ber/variants/BERPlatformSimple.java b/common/src/main/java/de/mrjulsen/crn/client/ber/variants/BERPlatformSimple.java index f529fe8e..9c12a23e 100644 --- a/common/src/main/java/de/mrjulsen/crn/client/ber/variants/BERPlatformSimple.java +++ b/common/src/main/java/de/mrjulsen/crn/client/ber/variants/BERPlatformSimple.java @@ -1,28 +1,27 @@ package de.mrjulsen.crn.client.ber.variants; import java.util.ArrayList; -import java.util.Collection; import java.util.List; -import java.util.UUID; -import com.mojang.blaze3d.vertex.PoseStack; - -import de.mrjulsen.crn.block.be.AdvancedDisplayBlockEntity; +import de.mrjulsen.crn.CreateRailwaysNavigator; +import de.mrjulsen.crn.block.blockentity.AdvancedDisplayBlockEntity; +import de.mrjulsen.crn.block.blockentity.AdvancedDisplayBlockEntity.EUpdateReason; +import de.mrjulsen.crn.block.display.AdvancedDisplaySource.ETimeDisplay; import de.mrjulsen.crn.client.ber.AdvancedDisplayRenderInstance; -import de.mrjulsen.crn.client.ber.base.BERText; -import de.mrjulsen.crn.client.ber.base.BERText.TextTransformation; +import de.mrjulsen.crn.client.ber.IBERRenderSubtype; import de.mrjulsen.crn.client.lang.ELanguage; import de.mrjulsen.crn.config.ModClientConfig; -import de.mrjulsen.crn.data.DeparturePrediction.SimpleDeparturePrediction; +import de.mrjulsen.crn.data.train.portable.StationDisplayData; import de.mrjulsen.crn.util.ModUtils; import de.mrjulsen.mcdragonlib.DragonLib; -import de.mrjulsen.mcdragonlib.client.ber.IBlockEntityRendererInstance.BlockEntityRendererContext; -import de.mrjulsen.mcdragonlib.client.ber.IBlockEntityRendererInstance.EUpdateReason; +import de.mrjulsen.mcdragonlib.client.ber.BERGraphics; +import de.mrjulsen.mcdragonlib.client.ber.BERLabel; +import de.mrjulsen.mcdragonlib.client.ber.BERLabel.BoundsHitReaction; import de.mrjulsen.mcdragonlib.util.TextUtils; import de.mrjulsen.mcdragonlib.util.TimeUtils; -import net.minecraft.client.renderer.MultiBufferSource; import net.minecraft.core.BlockPos; import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.MutableComponent; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.state.BlockState; @@ -32,69 +31,75 @@ public class BERPlatformSimple implements IBERRenderSubtype lastTrainOrder = new ArrayList<>(); + private final BERLabel label = new BERLabel() + .setPos(3, 5.5f) + .setYScale(0.75f) + .setScale(0.75f, 0.75f) + .setCentered(true) + .setScrollingSpeed(2) + ; + private List texts; + boolean updateLabel = false; + + @Override - public boolean isSingleLined() { - return true; + public void renderTick(float deltaTime) { + label.renderTick(); } @Override - public void tick(Level level, BlockPos pos, BlockState state, AdvancedDisplayBlockEntity pBlockEntity, AdvancedDisplayRenderInstance parent) { - + public void tick(Level level, BlockPos pos, BlockState state, AdvancedDisplayBlockEntity blockEntity, AdvancedDisplayRenderInstance parent) { + List textContent = new ArrayList<>(texts); + textContent.add(0, ELanguage.translate(keyTime, TimeUtils.parseTime((int)(blockEntity.getLevel().getDayTime() % DragonLib.TICKS_PER_DAY + DragonLib.DAYTIME_SHIFT), ModClientConfig.TIME_FORMAT.get()))); + MutableComponent txt = TextUtils.concat(textContent); + label + .setText(txt) + ; } @Override - public void renderAdditional(BlockEntityRendererContext context, AdvancedDisplayBlockEntity pBlockEntity, AdvancedDisplayRenderInstance parent, float pPartialTicks, PoseStack pPoseStack, MultiBufferSource pBufferSource, int pPackedLight, int pOverlay, Boolean backSide) { - + public void render(BERGraphics graphics, float pPartialTicks, AdvancedDisplayRenderInstance parent, int light, boolean backSide) { + label.render(graphics, light); } @Override public void update(Level level, BlockPos pos, BlockState state, AdvancedDisplayBlockEntity blockEntity, AdvancedDisplayRenderInstance parent, EUpdateReason reason) { - Collection preds = blockEntity.getPredictions().stream().filter(x -> x.departureTicks() < ModClientConfig.DISPLAY_LEAD_TIME.get()).toList(); - Collection uuidOrder = preds.stream().map(x -> x.trainId()).toList(); - - if (reason == EUpdateReason.DATA_CHANGED && lastTrainOrder.equals(uuidOrder)) { - return; - } + List preds = blockEntity.getStops().stream().filter(x -> x.getStationData().getScheduledArrivalTime() < DragonLib.getCurrentWorldTime() + ModClientConfig.DISPLAY_LEAD_TIME.get() && (!x.getTrainData().isCancelled() || DragonLib.getCurrentWorldTime() < x.getStationData().getScheduledDepartureTime() + ModClientConfig.DISPLAY_LEAD_TIME.get())).toList(); + + label + .setColor((0xFF << 24) | (blockEntity.getColor() & 0x00FFFFFF)) + .setMaxWidth(blockEntity.getXSizeScaled() * 16 - 6, BoundsHitReaction.SCALE_SCROLL) + ; - lastTrainOrder = uuidOrder; - parent.labels.clear(); + texts = new ArrayList<>(); + texts.addAll(preds.stream().map(x -> { + String timeString; + switch (blockEntity.getTimeDisplay()) { + case ETA: + timeString = ModUtils.timeRemainingString(x.getStationData().getScheduledDepartureTime()); + break; + default: + timeString = ModUtils.formatTime(x.getStationData().getScheduledDepartureTime(), blockEntity.getTimeDisplay() == ETimeDisplay.ETA); + break; + } - int displayWidth = blockEntity.getXSizeScaled(); - float maxWidth = displayWidth * 16 - 6; - parent.labels.add(new BERText(parent.getFontUtils(), () -> { - List texts = new ArrayList<>(); - texts.add(ELanguage.translate(keyTime, TimeUtils.parseTime((int)(blockEntity.getLevel().getDayTime() % DragonLib.TICKS_PER_DAY + DragonLib.DAYTIME_SHIFT), ModClientConfig.TIME_FORMAT.get()))); - texts.addAll(preds.stream().map(x -> { - String timeString; - switch (blockEntity.getTimeDisplay()) { - case ETA: - timeString = ModUtils.timeRemainingString(x.departureTicks()); - break; - default: - timeString = TimeUtils.parseTime((int)(blockEntity.getLastRefreshedTime() % DragonLib.TICKS_PER_DAY + DragonLib.DAYTIME_SHIFT + x.departureTicks()), ModClientConfig.TIME_FORMAT.get()); - break; - } + MutableComponent text = TextUtils.empty(); + if (x.getStationData().getStationInfo().platform() == null || x.getStationData().getStationInfo().platform().isBlank()) { + text.append(ELanguage.translate(keyTrainDeparture, x.getTrainData().getName(), x.getStationData().getDestination(), timeString)); + } else { + text.append(ELanguage.translate(keyTrainDepartureWithPlatform, x.getTrainData().getName(), x.getStationData().getDestination(), timeString, x.getStationData().getStationInfo().platform())); + } - if (x.stationInfo().platform() == null || x.stationInfo().platform().isBlank()) { - return ELanguage.translate(keyTrainDeparture, x.trainName(), x.scheduleTitle(), timeString); + if (x.getTrainData().isCancelled()) { + text.append(ELanguage.translate("block." + CreateRailwaysNavigator.MOD_ID + ".advanced_display.ber.cancelled2").getString()); + } else if (x.getStationData().isDepartureDelayed()) { + text.append(ELanguage.translate("block." + CreateRailwaysNavigator.MOD_ID + ".advanced_display.ber.delayed2", TimeUtils.formatToMinutes(x.getStationData().getDepartureTimeDeviation())).getString()); + if (x.getTrainData().hasStatusInfo()) { + text.append(" ").append(ELanguage.translate("block." + CreateRailwaysNavigator.MOD_ID + ".advanced_display.ber.reason").getString()).append(x.getTrainData().getStatus().get(0).text()); } - return ELanguage.translate(keyTrainDepartureWithPlatform, x.trainName(), x.scheduleTitle(), timeString, x.stationInfo().platform()); - }).toList()); - - return List.of(TextUtils.concatWithStarChars(texts.toArray(Component[]::new))); - }, 0) - .withIsCentered(false) - .withMaxWidth(maxWidth, true) - .withStretchScale(0.75f, 0.75f) - .withStencil(0, maxWidth) - .withCanScroll(true, 1) - .withColor((0xFF << 24) | (blockEntity.getColor())) - .withTicksPerPage(100) - .withRefreshRate(16) - .withPredefinedTextTransformation(new TextTransformation(3, 5.5f, 0.0f, 1, 0.75f)) - .build() - ); + } + return text; + }).toList()); } } diff --git a/common/src/main/java/de/mrjulsen/crn/client/ber/variants/BERTrainDestinationDetailed.java b/common/src/main/java/de/mrjulsen/crn/client/ber/variants/BERTrainDestinationDetailed.java index 00f01449..7505ad10 100644 --- a/common/src/main/java/de/mrjulsen/crn/client/ber/variants/BERTrainDestinationDetailed.java +++ b/common/src/main/java/de/mrjulsen/crn/client/ber/variants/BERTrainDestinationDetailed.java @@ -1,96 +1,107 @@ package de.mrjulsen.crn.client.ber.variants; -import de.mrjulsen.crn.block.be.AdvancedDisplayBlockEntity; +import de.mrjulsen.crn.CreateRailwaysNavigator; +import de.mrjulsen.crn.block.blockentity.AdvancedDisplayBlockEntity; +import de.mrjulsen.crn.block.blockentity.AdvancedDisplayBlockEntity.EUpdateReason; import de.mrjulsen.crn.client.ber.AdvancedDisplayRenderInstance; -import de.mrjulsen.crn.client.ber.base.BERText; -import de.mrjulsen.crn.client.ber.base.BERText.TextTransformation; -import de.mrjulsen.mcdragonlib.client.ber.IBlockEntityRendererInstance.EUpdateReason; -import de.mrjulsen.mcdragonlib.util.ColorUtils; +import de.mrjulsen.crn.client.ber.IBERRenderSubtype; +import de.mrjulsen.crn.client.lang.ELanguage; +import de.mrjulsen.mcdragonlib.client.ber.BERGraphics; +import de.mrjulsen.mcdragonlib.client.ber.BERLabel; +import de.mrjulsen.mcdragonlib.client.ber.BERLabel.BoundsHitReaction; import de.mrjulsen.mcdragonlib.util.TextUtils; import net.minecraft.ChatFormatting; import net.minecraft.core.BlockPos; -import net.minecraft.network.chat.MutableComponent; +import net.minecraft.network.chat.Component; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.state.BlockState; public class BERTrainDestinationDetailed implements IBERRenderSubtype { + private final BERLabel outOfServiceLabel = new BERLabel(ELanguage.translate("block." + CreateRailwaysNavigator.MOD_ID + ".advanced_display.ber.not_in_service")) + .setPos(3, 6) + .setScale(0.5f, 0.25f) + .setYScale(0.5f) + .setCentered(true) + .setScrollingSpeed(2) + ; + + private final BERLabel trainLineLabel = new BERLabel() + .setScale(0.5f, 0.3f) + .setYScale(0.5f) + .setMaxWidth(12, BoundsHitReaction.IGNORE) + ; + private final BERLabel destinationLabel = new BERLabel() + .setScale(0.5f, 0.25f) + .setYScale(0.5f) + .setCentered(true) + .setScrollingSpeed(2) + ; + private final BERLabel viaLabel = new BERLabel(ELanguage.translate("gui.createrailwaysnavigator.via").withStyle(ChatFormatting.ITALIC)) + .setScale(0.35f, 0.35f) + .setYScale(0.35f) + ; + private final BERLabel stopoversLabel = new BERLabel() + .setScale(0.35f, 0.35f) + .setYScale(0.35f) + .setScrollingSpeed(2) + ; + + @Override - public boolean isSingleLined() { - return true; + public void renderTick(float deltaTime) { + outOfServiceLabel.renderTick(); + trainLineLabel.renderTick(); + destinationLabel.renderTick(); + viaLabel.renderTick(); + stopoversLabel.renderTick(); } @Override - public void update(Level level, BlockPos pos, BlockState state, AdvancedDisplayBlockEntity blockEntity, AdvancedDisplayRenderInstance parent, EUpdateReason reason) { - if (blockEntity.getTrainData() == null) { + public void render(BERGraphics graphics, float partialTick, AdvancedDisplayRenderInstance parent, int light, boolean backSide) { + if (graphics.blockEntity().getTrainData() == null || graphics.blockEntity().getTrainData().isEmpty()) { + outOfServiceLabel.render(graphics, light); return; } - parent.labels.clear(); - - int displayWidth = blockEntity.getXSizeScaled(); - boolean isSingleBlock = blockEntity.getXSizeScaled() <= 1; - - // TRAIN NAME - float maxWidth = isSingleBlock ? 11.0f : 12.0f; - MutableComponent line = TextUtils.text(blockEntity.getTrainData().trainName()).withStyle(ChatFormatting.BOLD); - BERText lastLabel = new BERText(parent.getFontUtils(), line, 0) - .withIsCentered(isSingleBlock) - .withMaxWidth(maxWidth, isSingleBlock) - .withStretchScale(0.3f, 0.5f) - .withStencil(0, displayWidth * 16 - 5) - .withCanScroll(isSingleBlock, 0.5f) - .withColor((0xFF << 24) | (blockEntity.getColor() & 0x00FFFFFF)) - .withPredefinedTextTransformation(new TextTransformation(isSingleBlock ? 2.5f : 3.0f, 4, 0.0f, 1, 0.5f)) - .build(); - parent.labels.add(lastLabel); + trainLineLabel.render(graphics, light); + destinationLabel.render(graphics, light); + viaLabel.render(graphics, light); + stopoversLabel.render(graphics, light); + } - // DESTINATION - float startX = lastLabel.getScaledTextWidth(); - if (blockEntity.getTrainData().getNextStop().isPresent()) { - line = TextUtils.text(blockEntity.getTrainData().getNextStop().get().scheduleTitle()); - maxWidth = displayWidth * 16 - 7 - startX; - parent.labels.add(new BERText(parent.getFontUtils(), line, 0) - .withIsCentered(true) - .withMaxWidth(maxWidth, true) - .withStretchScale(0.25f, 0.5f) - .withStencil(0, maxWidth) - .withCanScroll(true, 1) - .withColor((0xFF << 24) | (blockEntity.getColor())) - .withPredefinedTextTransformation(new TextTransformation(2 + startX + 2, 4, 0.0f, 1, 0.5f)) - .build() - ); + @Override + public void update(Level level, BlockPos pos, BlockState state, AdvancedDisplayBlockEntity blockEntity, AdvancedDisplayRenderInstance parent, EUpdateReason reason) { + if (blockEntity.getTrainData() == null || blockEntity.getTrainData().isEmpty()) { + outOfServiceLabel + .setMaxWidth(blockEntity.getXSizeScaled() * 16 - 6, BoundsHitReaction.SCALE_SCROLL) + .setColor((0xFF << 24) | (blockEntity.getColor() & 0x00FFFFFF)) + ; + return; } + updateContent(blockEntity); + } - - maxWidth = isSingleBlock ? 11.0f : 12.0f; - line = TextUtils.translate("gui.createrailwaysnavigator.via").withStyle(ChatFormatting.ITALIC); - maxWidth = displayWidth * 16 - 8; - maxWidth /= 0.75f; - lastLabel = new BERText(parent.getFontUtils(), line, 0) - .withIsCentered(false) - .withMaxWidth(maxWidth, true) - .withStretchScale(0.35f, 0.35f) - .withColor(ColorUtils.darkenColor(blockEntity.getColor(), -0.75f)) - .withPredefinedTextTransformation(new TextTransformation(isSingleBlock ? 2.5f : 3.0f, 10, 0.0f, 0.75f, 0.3f)) - .build(); - - parent.labels.add(lastLabel); - - startX = lastLabel.getScaledTextWidth(); - startX *= 0.75f; - line = parent.getStopoversString(blockEntity); - maxWidth = displayWidth * 16 - 7 - startX; - maxWidth /= 0.75f; - - parent.labels.add(new BERText(parent.getFontUtils(), line, 0) - .withIsCentered(false) - .withMaxWidth(maxWidth, true) - .withStretchScale(0.35f, 0.35f) - .withStencil(0, maxWidth) - .withCanScroll(true, 1) - .withColor(ColorUtils.darkenColor(blockEntity.getColor(), -0.75f)) - .withPredefinedTextTransformation(new TextTransformation(2 + startX + 2, 10, 0.0f, 0.75f, 0.3f)) - .build() - ); - } + private void updateContent(AdvancedDisplayBlockEntity blockEntity) { + trainLineLabel + .setPos(3, 4) + .setText(TextUtils.text(blockEntity.getTrainData().getTrainData().getName()).withStyle(ChatFormatting.BOLD)) + .setColor((0xFF << 24) | (blockEntity.getColor() & 0x00FFFFFF)) + ; + destinationLabel + .setPos(trainLineLabel.getTextWidth() + 5, 4) + .setMaxWidth(blockEntity.getXSizeScaled() * 16 - destinationLabel.getX() - 3, BoundsHitReaction.SCALE_SCROLL) + .setText(TextUtils.text(blockEntity.getTrainData().getNextStop().isPresent() ? blockEntity.getTrainData().getNextStop().get().getDestination() : "")) + .setColor((0xFF << 24) | (blockEntity.getColor() & 0x00FFFFFF)) + ; + viaLabel + .setPos(3, 10) + .setColor((0xFF << 24) | (blockEntity.getColor() & 0x00FFFFFF)) + ; + stopoversLabel + .setPos(Math.max(trainLineLabel.getTextWidth(), viaLabel.getTextWidth()) + 5, 10) + .setMaxWidth(blockEntity.getXSizeScaled() * 16 - stopoversLabel.getX() - 3, BoundsHitReaction.SCALE_SCROLL) + .setText(TextUtils.concat(TextUtils.text(" \u25CF "), blockEntity.getTrainData().getStopovers().stream().map(x -> (Component)TextUtils.text(x.getName())).toList())) + .setColor((0xFF << 24) | (blockEntity.getColor() & 0x00FFFFFF)) + ; + } } diff --git a/common/src/main/java/de/mrjulsen/crn/client/ber/variants/BERTrainDestinationInformative.java b/common/src/main/java/de/mrjulsen/crn/client/ber/variants/BERTrainDestinationInformative.java index 443e5d73..dd67781d 100644 --- a/common/src/main/java/de/mrjulsen/crn/client/ber/variants/BERTrainDestinationInformative.java +++ b/common/src/main/java/de/mrjulsen/crn/client/ber/variants/BERTrainDestinationInformative.java @@ -1,18 +1,18 @@ package de.mrjulsen.crn.client.ber.variants; -import com.mojang.blaze3d.vertex.PoseStack; - -import de.mrjulsen.crn.block.be.AdvancedDisplayBlockEntity; +import de.mrjulsen.crn.CreateRailwaysNavigator; +import de.mrjulsen.crn.block.blockentity.AdvancedDisplayBlockEntity; +import de.mrjulsen.crn.block.blockentity.AdvancedDisplayBlockEntity.EUpdateReason; import de.mrjulsen.crn.client.ber.AdvancedDisplayRenderInstance; -import de.mrjulsen.crn.client.ber.base.BERText; -import de.mrjulsen.crn.client.ber.base.BERText.TextTransformation; -import de.mrjulsen.mcdragonlib.client.ber.IBlockEntityRendererInstance.BlockEntityRendererContext; -import de.mrjulsen.mcdragonlib.client.ber.IBlockEntityRendererInstance.EUpdateReason; +import de.mrjulsen.crn.client.ber.IBERRenderSubtype; +import de.mrjulsen.mcdragonlib.client.ber.BERGraphics; +import de.mrjulsen.mcdragonlib.client.ber.BERLabel; +import de.mrjulsen.mcdragonlib.client.ber.BERLabel.BoundsHitReaction; +import de.mrjulsen.mcdragonlib.client.util.BERUtils; import de.mrjulsen.mcdragonlib.util.TextUtils; import net.minecraft.ChatFormatting; -import net.minecraft.client.renderer.MultiBufferSource; import net.minecraft.core.BlockPos; -import net.minecraft.network.chat.MutableComponent; +import net.minecraft.network.chat.Component; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.HorizontalDirectionalBlock; @@ -20,119 +20,134 @@ public class BERTrainDestinationInformative implements IBERRenderSubtype { + private static final ResourceLocation CARRIAGE_ICON = new ResourceLocation("create:textures/gui/assemble.png"); + private static final ResourceLocation ICONS = new ResourceLocation(CreateRailwaysNavigator.MOD_ID, "textures/gui/icons.png"); + + + private final BERLabel carriageIndexLabel = new BERLabel() + .setScale(0.25f, 0.25f) + .setYScale(0.25f) + ; + private final BERLabel trainLineLabel = new BERLabel() + .setScale(0.25f, 0.15f) + .setYScale(0.25f) + ; + private final BERLabel fromLabel = new BERLabel() + .setScale(0.25f, 0.15f) + .setYScale(0.25f) + .setScrollingSpeed(2) + ; + private final BERLabel stopoversLabel = new BERLabel() + .setScale(0.2f, 0.15f) + .setYScale(0.2f) + .setScrollingSpeed(2) + ; + private final BERLabel destinationLabel = new BERLabel() + .setScale(0.25f, 0.15f) + .setYScale(0.25f) + .setScrollingSpeed(2) + ; + + @Override - public boolean isSingleLined() { - return true; + public void renderTick(float deltaTime) { + carriageIndexLabel.renderTick(); + trainLineLabel.renderTick(); + fromLabel.renderTick(); + stopoversLabel.renderTick(); + destinationLabel.renderTick(); } @Override - public void update(Level level, BlockPos pos, BlockState state, AdvancedDisplayBlockEntity blockEntity, AdvancedDisplayRenderInstance parent, EUpdateReason reason) { - if (blockEntity.getTrainData() == null) { + public void render(BERGraphics graphics, float partialTick, AdvancedDisplayRenderInstance parent, int light, boolean backSide) { + float uv = 1.0f / 256.0f; + BERUtils.fillColor(graphics, 2.5f, 5.0f, 0.0f, graphics.blockEntity().getXSizeScaled() * 16 - 5, 0.25f, (0xFF << 24) | (graphics.blockEntity().getColor() & 0x00FFFFFF), graphics.blockEntity().getBlockState().getValue(HorizontalDirectionalBlock.FACING), light); + BERUtils.renderTexture(CARRIAGE_ICON, graphics, false, graphics.blockEntity().getXSizeScaled() * 16 - 7 - carriageIndexLabel.getTextWidth(), 2.5f, 0, 3, 2, uv * 22, uv * 231, uv * 22 + uv * 13, uv * 231 + uv * 5, graphics.blockEntity().getBlockState().getValue(HorizontalDirectionalBlock.FACING).getOpposite(), (0xFF << 24) | (graphics.blockEntity().getColor() & 0x00FFFFFF), light); + carriageIndexLabel.render(graphics, light); + + if (graphics.blockEntity().getTrainData() == null || graphics.blockEntity().getTrainData().isEmpty()) { return; } - parent.labels.clear(); - - int displayWidth = blockEntity.getXSizeScaled(); - boolean isSingleBlock = blockEntity.getXSizeScaled() <= 1; - - float maxWidth = displayWidth * 16 - 6; - maxWidth /= 0.5f; - // TRAIN NAME - MutableComponent line = TextUtils.text(String.format("%02d", blockEntity.getCarriageData().index() + 1)).withStyle(ChatFormatting.BOLD); - float textWidth = parent.getFontUtils().font.width(line) * 0.25f; - BERText lastLabel = parent.carriageIndexLabel = new BERText(parent.getFontUtils(), line, 0) - .withIsCentered(false) - .withMaxWidth(maxWidth, false) - .withStretchScale(0.5f, 0.5f) - .withColor((0xFF << 24) | (blockEntity.getColor() & 0x00FFFFFF)) - .withPredefinedTextTransformation(new TextTransformation(displayWidth * 16 - 2.5f - textWidth, 2.5f, 0.0f, 0.5f, 0.3f)) - .build(); - parent.labels.add(lastLabel); + trainLineLabel.render(graphics, light); + fromLabel.render(graphics, light); + stopoversLabel.render(graphics, light); + destinationLabel.render(graphics, light); - line = TextUtils.text(blockEntity.getTrainData().trainName()).withStyle(ChatFormatting.BOLD); - lastLabel = new BERText(parent.getFontUtils(), line, 0) - .withIsCentered(isSingleBlock) - .withMaxWidth(maxWidth - 10f - textWidth, true) - .withStretchScale(0.3f, 0.6f) - .withStencil(0, maxWidth - 10f - textWidth) - .withCanScroll(true, 0.5f) - .withColor((0xFF << 24) | (blockEntity.getColor() & 0x00FFFFFF)) - .withPredefinedTextTransformation(new TextTransformation(3.0f, 2.5f, 0.0f, 0.5f, 0.3f)) - .build(); - parent.labels.add(lastLabel); - - // DESTINATION - if (blockEntity.getTrainData().getNextStop().isPresent()) { - line = TextUtils.text(blockEntity.getTrainData().getNextStop().get().stationTagName()).withStyle(ChatFormatting.BOLD); - parent.labels.add(new BERText(parent.getFontUtils(), line, 0) - .withIsCentered(false) - .withMaxWidth(maxWidth, true) - .withStretchScale(0.25f, 0.5f) - .withStencil(0, maxWidth) - .withCanScroll(true, 1) - .withColor((0xFF << 24) | (blockEntity.getColor())) - .withPredefinedTextTransformation(new TextTransformation(3.0f, 6.0f, 0.0f, 0.5f, 0.25f)) - .build() - ); - } - - // STOPOVERS - line = parent.getStopoversString(blockEntity); - parent.labels.add(new BERText(parent.getFontUtils(), line, 0) - .withIsCentered(false) - .withMaxWidth(maxWidth, true) - .withStretchScale(0.25f, 0.4f) - .withStencil(0, maxWidth) - .withCanScroll(true, 1) - .withColor((0xFF << 24) | (blockEntity.getColor())) - .withPredefinedTextTransformation(new TextTransformation(3.0f, 8.75f, 0.0f, 0.5f, 0.2f)) - .build() + BERUtils.renderTexture( + ICONS, + graphics, + false, + 3, + 6, + 0.0f, + 2, + 2, + uv * 195, + uv * 19, + uv * (195 + 10), + uv * (19 + 10), + graphics.blockEntity().getBlockState().getValue(HorizontalDirectionalBlock.FACING), + (0xFF << 24) | (graphics.blockEntity().getColor() & 0x00FFFFFF), + light ); - - // DESTINATION - if (blockEntity.getTrainData().getNextStop().isPresent()) { - line = TextUtils.text(blockEntity.getTrainData().getNextStop().get().scheduleTitle()).withStyle(ChatFormatting.BOLD); - parent.labels.add(new BERText(parent.getFontUtils(), line, 0) - .withIsCentered(false) - .withMaxWidth(maxWidth, true) - .withStretchScale(0.25f, 0.5f) - .withStencil(0, maxWidth) - .withCanScroll(true, 1) - .withColor((0xFF << 24) | (blockEntity.getColor())) - .withPredefinedTextTransformation(new TextTransformation(3.0f, 11.0f, 0.0f, 0.5f, 0.25f)) - .build() - ); - } - } - - @Override - public void renderAdditional(BlockEntityRendererContext context, AdvancedDisplayBlockEntity pBlockEntity, AdvancedDisplayRenderInstance parent, float pPartialTicks, PoseStack pPoseStack, MultiBufferSource pBufferSource, int pPackedLight, int pOverlay, Boolean backSide) { - context.renderUtils().initRenderEngine(); - float uv = 1.0f / 256.0f; - context.renderUtils().renderTexture( - new ResourceLocation("create:textures/gui/assemble.png"), - pBufferSource, - pBlockEntity, - pPoseStack, + + BERUtils.renderTexture( + ICONS, + graphics, false, - pBlockEntity.getXSizeScaled() * 16 - 6f - (parent.carriageIndexLabel == null ? 0 : parent.carriageIndexLabel.getScaledTextWidth() * 0.5f), - 2.5f, + 3, + 11, 0.0f, - 3.0f, - 2.0f, - uv * 22, - uv * 231, - uv * 22 + uv * 13, - uv * 231 + uv * 5, - pBlockEntity.getBlockState().getValue(HorizontalDirectionalBlock.FACING), - (0xFF << 24) | (pBlockEntity.getColor() & 0x00FFFFFF), - pPackedLight + 2, + 2, + uv * 211, + uv * 19, + uv * (211 + 10), + uv * (19 + 10), + graphics.blockEntity().getBlockState().getValue(HorizontalDirectionalBlock.FACING), + (0xFF << 24) | (graphics.blockEntity().getColor() & 0x00FFFFFF), + light ); + } - - context.renderUtils().fillColor(pBufferSource, pBlockEntity, (0xFF << 24) | (pBlockEntity.getColor() & 0x00FFFFFF), pPoseStack, 2.5f, 5.0f, 0.0f, pBlockEntity.getXSizeScaled() * 16 - 5, 0.25f, pBlockEntity.getBlockState().getValue(HorizontalDirectionalBlock.FACING), pPackedLight); + @Override + public void update(Level level, BlockPos pos, BlockState state, AdvancedDisplayBlockEntity blockEntity, AdvancedDisplayRenderInstance parent, EUpdateReason reason) { + if (blockEntity.getTrainData() == null || blockEntity.getTrainData().isEmpty()) { + return; + } + updateContent(blockEntity); } - + private void updateContent(AdvancedDisplayBlockEntity blockEntity) { + carriageIndexLabel + .setText(TextUtils.text(String.format("%02d", blockEntity.getCarriageData().index() + 1)).withStyle(ChatFormatting.BOLD)) + .setPos(blockEntity.getXSizeScaled() * 16 - 3 - carriageIndexLabel.getTextWidth(), 2.5f) + .setColor((0xFF << 24) | (blockEntity.getColor() & 0x00FFFFFF)) + ; + trainLineLabel + .setPos(3, 2.5f) + .setMaxWidth(blockEntity.getXSizeScaled() * 16 - 6 - carriageIndexLabel.getTextWidth() - 5, BoundsHitReaction.SCALE_SCROLL) + .setText(TextUtils.text(blockEntity.getTrainData().getTrainData().getName()).withStyle(ChatFormatting.BOLD)) + .setColor((0xFF << 24) | (blockEntity.getColor() & 0x00FFFFFF)) + ; + fromLabel + .setPos(6, 6) + .setMaxWidth(blockEntity.getXSizeScaled() * 16 - 9, BoundsHitReaction.SCALE_SCROLL) + .setText(TextUtils.text(!blockEntity.getTrainData().getAllStops().isEmpty() ? blockEntity.getTrainData().getAllStops().get(0).getName() : "")) + .setColor((0xFF << 24) | (blockEntity.getColor() & 0x00FFFFFF)) + ; + stopoversLabel + .setPos(6, 8.75f) + .setMaxWidth(blockEntity.getXSizeScaled() * 16 - 9, BoundsHitReaction.SCALE_SCROLL) + .setText(TextUtils.concat(TextUtils.text(" \u25CF "), blockEntity.getTrainData().getStopovers().stream().map(x -> (Component)TextUtils.text(x.getName())).toList())) + .setColor((0xFF << 24) | (blockEntity.getColor() & 0x00FFFFFF)) + ; + destinationLabel + .setPos(6, 11) + .setMaxWidth(blockEntity.getXSizeScaled() * 16 - 9, BoundsHitReaction.SCALE_SCROLL) + .setText(TextUtils.text(blockEntity.getTrainData().getNextStop().isPresent() ? blockEntity.getTrainData().getNextStop().get().getDestination() : "").withStyle(ChatFormatting.BOLD)) + .setColor((0xFF << 24) | (blockEntity.getColor() & 0x00FFFFFF)) + ; + } } diff --git a/common/src/main/java/de/mrjulsen/crn/client/ber/variants/BERTrainDestinationSimple.java b/common/src/main/java/de/mrjulsen/crn/client/ber/variants/BERTrainDestinationSimple.java index 0eaa0d57..23881277 100644 --- a/common/src/main/java/de/mrjulsen/crn/client/ber/variants/BERTrainDestinationSimple.java +++ b/common/src/main/java/de/mrjulsen/crn/client/ber/variants/BERTrainDestinationSimple.java @@ -1,64 +1,86 @@ package de.mrjulsen.crn.client.ber.variants; -import de.mrjulsen.crn.block.be.AdvancedDisplayBlockEntity; +import de.mrjulsen.crn.CreateRailwaysNavigator; +import de.mrjulsen.crn.block.blockentity.AdvancedDisplayBlockEntity; +import de.mrjulsen.crn.block.blockentity.AdvancedDisplayBlockEntity.EUpdateReason; import de.mrjulsen.crn.client.ber.AdvancedDisplayRenderInstance; -import de.mrjulsen.crn.client.ber.base.BERText; -import de.mrjulsen.crn.client.ber.base.BERText.TextTransformation; -import de.mrjulsen.mcdragonlib.client.ber.IBlockEntityRendererInstance.EUpdateReason; +import de.mrjulsen.crn.client.ber.IBERRenderSubtype; +import de.mrjulsen.crn.client.lang.ELanguage; +import de.mrjulsen.mcdragonlib.client.ber.BERGraphics; +import de.mrjulsen.mcdragonlib.client.ber.BERLabel; +import de.mrjulsen.mcdragonlib.client.ber.BERLabel.BoundsHitReaction; +import de.mrjulsen.mcdragonlib.util.DLUtils; import de.mrjulsen.mcdragonlib.util.TextUtils; import net.minecraft.ChatFormatting; import net.minecraft.core.BlockPos; -import net.minecraft.network.chat.MutableComponent; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.state.BlockState; public class BERTrainDestinationSimple implements IBERRenderSubtype { + + private final BERLabel outOfServiceLabel = new BERLabel(ELanguage.translate("block." + CreateRailwaysNavigator.MOD_ID + ".advanced_display.ber.not_in_service")) + .setPos(3, 6) + .setScale(0.5f, 0.25f) + .setYScale(0.5f) + .setCentered(true) + .setScrollingSpeed(2) + ; + + private final BERLabel trainLineLabel = new BERLabel() + .setScale(0.6f, 0.3f) + .setYScale(0.8f) + .setMaxWidth(12, BoundsHitReaction.IGNORE) + ; + private final BERLabel destinationLabel = new BERLabel() + .setScale(0.5f, 0.25f) + .setYScale(0.5f) + .setCentered(true) + .setScrollingSpeed(2) + ; + + @Override - public boolean isSingleLined() { - return true; + public void renderTick(float deltaTime) { + trainLineLabel.renderTick(); + destinationLabel.renderTick(); + outOfServiceLabel.renderTick(); + } + + @Override + public void render(BERGraphics graphics, float partialTick, AdvancedDisplayRenderInstance parent, int light, boolean backSide) { + if (graphics.blockEntity().getTrainData() == null || graphics.blockEntity().getTrainData().isEmpty()) { + outOfServiceLabel.render(graphics, light); + return; + } + DLUtils.doIfNotNull(trainLineLabel, x -> x.render(graphics, light)); + DLUtils.doIfNotNull(destinationLabel, x -> x.render(graphics, light)); } @Override public void update(Level level, BlockPos pos, BlockState state, AdvancedDisplayBlockEntity blockEntity, AdvancedDisplayRenderInstance parent, EUpdateReason reason) { - if (blockEntity.getTrainData() == null) { + if (blockEntity.getTrainData() == null || blockEntity.getTrainData().isEmpty()) { + outOfServiceLabel + .setMaxWidth(blockEntity.getXSizeScaled() * 16 - 6, BoundsHitReaction.SCALE_SCROLL) + .setColor((0xFF << 24) | (blockEntity.getColor() & 0x00FFFFFF)) + ; return; } + updateContent(blockEntity); + } - parent.labels.clear(); + private void updateContent(AdvancedDisplayBlockEntity blockEntity) { + trainLineLabel + .setPos(3, 5) + .setText(TextUtils.text(blockEntity.getTrainData().getTrainData().getName()).withStyle(ChatFormatting.BOLD)) + .setColor((0xFF << 24) | (blockEntity.getColor() & 0x00FFFFFF)) + ; + destinationLabel + .setPos(trainLineLabel.getTextWidth() + 5, 6) + .setMaxWidth(blockEntity.getXSizeScaled() * 16 - destinationLabel.getX() - 3, BoundsHitReaction.SCALE_SCROLL) + .setText(TextUtils.text(blockEntity.getTrainData().getNextStop().isPresent() ? blockEntity.getTrainData().getNextStop().get().getDestination() : "")) + .setColor((0xFF << 24) | (blockEntity.getColor() & 0x00FFFFFF)) + ; - int displayWidth = blockEntity.getXSizeScaled(); - boolean isSingleBlock = blockEntity.getXSizeScaled() <= 1; - - // TRAIN NAME - float maxWidth = isSingleBlock ? 11.0f : 12.0f; - MutableComponent line = TextUtils.text(blockEntity.getTrainData().trainName()).withStyle(ChatFormatting.BOLD); - BERText lastLabel = new BERText(parent.getFontUtils(), line, 0) - .withIsCentered(isSingleBlock) - .withMaxWidth(maxWidth, isSingleBlock) - .withStretchScale(0.3f, 0.6f) - .withStencil(0, displayWidth * 16 - 5) - .withCanScroll(isSingleBlock, 0.5f) - .withColor((0xFF << 24) | (blockEntity.getColor() & 0x00FFFFFF)) - .withPredefinedTextTransformation(new TextTransformation(isSingleBlock ? 2.5f : 3.0f, 5f, 0.0f, 1, 0.8f)) - .build(); - parent.labels.add(lastLabel); - - if (!isSingleBlock && blockEntity.getTrainData().getNextStop().isPresent()) { - // DESTINATION - float startX = lastLabel.getScaledTextWidth(); - line = TextUtils.text(blockEntity.getTrainData().getNextStop().get().scheduleTitle()); - maxWidth = displayWidth * 16 - 7 - startX; - parent.labels.add(new BERText(parent.getFontUtils(), line, 0) - .withIsCentered(true) - .withMaxWidth(maxWidth, true) - .withStretchScale(0.25f, 0.5f) - .withStencil(0, maxWidth) - .withCanScroll(true, 1) - .withColor((0xFF << 24) | (blockEntity.getColor())) - .withPredefinedTextTransformation(new TextTransformation(2 + startX + 2, 6, 0.0f, 1, 0.5f)) - .build() - ); - } - } + } } diff --git a/common/src/main/java/de/mrjulsen/crn/client/ber/variants/IBERRenderSubtype.java b/common/src/main/java/de/mrjulsen/crn/client/ber/variants/IBERRenderSubtype.java deleted file mode 100644 index a7a27ffd..00000000 --- a/common/src/main/java/de/mrjulsen/crn/client/ber/variants/IBERRenderSubtype.java +++ /dev/null @@ -1,19 +0,0 @@ -package de.mrjulsen.crn.client.ber.variants; - -import com.mojang.blaze3d.vertex.PoseStack; - -import de.mrjulsen.mcdragonlib.client.ber.AbstractBlockEntityRenderInstance; -import de.mrjulsen.mcdragonlib.client.ber.IBlockEntityRendererInstance.BlockEntityRendererContext; -import de.mrjulsen.mcdragonlib.client.ber.IBlockEntityRendererInstance.EUpdateReason; -import net.minecraft.client.renderer.MultiBufferSource; -import net.minecraft.core.BlockPos; -import net.minecraft.world.level.Level; -import net.minecraft.world.level.block.entity.BlockEntity; -import net.minecraft.world.level.block.state.BlockState; - -public interface IBERRenderSubtype, U> { - void update(Level level, BlockPos pos, BlockState state, T blockEntity, S parent, EUpdateReason reason); - boolean isSingleLined(); - default void renderAdditional(BlockEntityRendererContext context, T pBlockEntity, S parent, float pPartialTicks, PoseStack pPoseStack, MultiBufferSource pBufferSource, int pPackedLight, int pOverlay, U renderData) {} - default void tick(Level level, BlockPos pos, BlockState state, T pBlockEntity, S parent) {} -} diff --git a/common/src/main/java/de/mrjulsen/crn/client/gui/Animator.java b/common/src/main/java/de/mrjulsen/crn/client/gui/Animator.java new file mode 100644 index 00000000..b1205e65 --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/client/gui/Animator.java @@ -0,0 +1,93 @@ +package de.mrjulsen.crn.client.gui; + +import com.mojang.blaze3d.vertex.PoseStack; + +import de.mrjulsen.mcdragonlib.client.gui.widgets.DLRenderable; +import de.mrjulsen.mcdragonlib.client.util.Graphics; +import de.mrjulsen.mcdragonlib.util.DLUtils; +import net.minecraft.client.Minecraft; + +public class Animator extends DLRenderable { + + private int maxTicks; + private int currentTicks; + private float currentTicksSmooth; + private boolean running; + + private IAnimatorRenderCallback onAnimateRender; + private IAnimatorTickCallback onAnimateTick; + private Runnable onCompleted; + + public Animator() { + super(0, 0, 0, 0); + } + + public boolean isRunning() { + return running; + } + + public int getTotalTicks() { + return maxTicks; + } + + public int getCurrentTicks() { + return currentTicks; + } + + public float getCurrentTicksSmooth() { + return currentTicksSmooth; + } + + public float getPercentage() { + return 1F / (float)maxTicks * currentTicksSmooth; + } + + @Override + public void renderMainLayer(Graphics graphics, int mouseX, int mouseY, float partialTicks) { + partialTicks = Minecraft.getInstance().getFrameTime(); + currentTicksSmooth += partialTicks; + if (running) { + DLUtils.doIfNotNull(onAnimateRender, x -> x.execute(graphics.poseStack(), getCurrentTicks(), getTotalTicks(), getPercentage())); + } + } + + @Override + public void tick() { + if (running) { + DLUtils.doIfNotNull(onAnimateTick, x -> x.execute(getCurrentTicks(), getTotalTicks(), getPercentage())); + currentTicks++; + currentTicksSmooth = currentTicks; + if (currentTicks >= maxTicks) { + stop(); + DLUtils.doIfNotNull(onCompleted, x -> x.run()); + } + } + } + + public void start(int ticks, IAnimatorRenderCallback renderCallback, IAnimatorTickCallback tickCallback, Runnable onCompleted) { + this.currentTicks = 0; + this.currentTicksSmooth = 0; + this.maxTicks = ticks; + this.onAnimateRender = renderCallback; + this.onAnimateTick = tickCallback; + this.onCompleted = onCompleted; + this.running = true; + } + + public void stop() { + this.running = false; + this.currentTicks = 1; + this.currentTicksSmooth = 1; + this.maxTicks = 1; + } + + @FunctionalInterface + public static interface IAnimatorRenderCallback { + void execute(PoseStack poseStack, int currentTicks, int totalTicks, double percentage); + } + + @FunctionalInterface + public static interface IAnimatorTickCallback { + void execute(int currentTicks, int totalTicks, double percentage); + } +} diff --git a/common/src/main/java/de/mrjulsen/crn/client/gui/CreateDynamicWidgets.java b/common/src/main/java/de/mrjulsen/crn/client/gui/CreateDynamicWidgets.java index 13410e83..d2ef0910 100644 --- a/common/src/main/java/de/mrjulsen/crn/client/gui/CreateDynamicWidgets.java +++ b/common/src/main/java/de/mrjulsen/crn/client/gui/CreateDynamicWidgets.java @@ -1,10 +1,25 @@ package de.mrjulsen.crn.client.gui; +import com.mojang.blaze3d.systems.RenderSystem; +import com.mojang.blaze3d.vertex.BufferBuilder; +import com.mojang.blaze3d.vertex.DefaultVertexFormat; +import com.mojang.blaze3d.vertex.Tesselator; +import com.mojang.blaze3d.vertex.VertexFormat.Mode; + +import de.mrjulsen.crn.CreateRailwaysNavigator; +import de.mrjulsen.crn.client.CRNGui; +import de.mrjulsen.crn.client.ModGuiUtils; import de.mrjulsen.mcdragonlib.client.util.Graphics; import de.mrjulsen.mcdragonlib.client.util.GuiUtils; +import de.mrjulsen.mcdragonlib.core.EAlignment; +import net.minecraft.client.gui.Font; +import net.minecraft.client.renderer.GameRenderer; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; public class CreateDynamicWidgets { - private static final int BORDER_WEIGHT = 2; + + private static final int BORDER_HEIGHT = 2; private static final int TEXTBOX_HEIGHT = 18; private static final int COLOR_BORDER = 0xFF393939; private static final int COLOR_3D_SHADOW = 0xFF373737; @@ -30,19 +45,21 @@ public static void renderWidgetInner(Graphics graphics, int x, int y, int w, int } public static void renderWidgetTopBorder(Graphics graphics, int x, int y, int w) { - GuiUtils.fill(graphics, x, y, w, BORDER_WEIGHT, ColorShade.LIGHT.getColor()); // left border - GuiUtils.fill(graphics, x + 1, y + 1, w - 2, BORDER_WEIGHT - 1, COLOR_BORDER); // left border + GuiUtils.fill(graphics, x + 1, y, w - 2, 1, ColorShade.LIGHT.getColor()); // left border + GuiUtils.fill(graphics, x, y + 1, w, 1, ColorShade.LIGHT.getColor()); // left border + GuiUtils.fill(graphics, x + 1, y + 1, w - 2, BORDER_HEIGHT - 1, COLOR_BORDER); // left border } public static void renderWidgetBottomBorder(Graphics graphics, int x, int y, int w) { - GuiUtils.fill(graphics, x, y, w, BORDER_WEIGHT, ColorShade.LIGHT.getColor()); // left border + GuiUtils.fill(graphics, x, y, w, 1, ColorShade.LIGHT.getColor()); // left border + GuiUtils.fill(graphics, x + 1, y + 1, w - 2, 1, ColorShade.LIGHT.getColor()); // left border GuiUtils.fill(graphics, x + 1, y, w - 2, 1, COLOR_BORDER); // left border } public static void renderSingleShadeWidget(Graphics graphics, int x, int y, int w, int h, int color) { - renderWidgetInner(graphics, x, y, w, h, color); + renderWidgetInner(graphics, x, y + 2, w, h - 4, color); renderWidgetTopBorder(graphics, x, y, w); - renderWidgetBottomBorder(graphics, x, y + h - BORDER_WEIGHT, w); + renderWidgetBottomBorder(graphics, x, y + h - BORDER_HEIGHT, w); } public static void renderSingleShadeWidget(Graphics graphics, int x, int y, int w, int h, ColorShade color) { @@ -50,10 +67,10 @@ public static void renderSingleShadeWidget(Graphics graphics, int x, int y, int } public static void renderDuoShadeWidget(Graphics graphics, int x, int y, int w, int h1, int color1, int h2, int color2) { - renderWidgetInner(graphics, x, y, w, h1, color1); - renderWidgetInner(graphics, x, y + h1, w, h2, color2); + renderWidgetInner(graphics, x, y + 2, w, h1 - 2, color1); + renderWidgetInner(graphics, x, y + h1, w, h2 - 2, color2); renderWidgetTopBorder(graphics, x, y, w); - renderWidgetBottomBorder(graphics, x, y + h1 + h2 - BORDER_WEIGHT, w); + renderWidgetBottomBorder(graphics, x, y + h1 + h2 - BORDER_HEIGHT, w); } public static void renderDuoShadeWidget(Graphics graphics, int x, int y, int w, int h1, ColorShade color1, int h2, ColorShade color2) { @@ -90,11 +107,74 @@ public static void renderHorizontalSeparator(Graphics graphics, int x, int y, in GuiUtils.fill(graphics, x, y + 1, w - 1, 1, COLOR_BORDER); } + public static void renderContainerBackground(Graphics graphics, int x, int y, int w, int h, ContainerColor color) { + Tesselator tesselator = Tesselator.getInstance(); + BufferBuilder bufferbuilder = tesselator.getBuilder(); + RenderSystem.setShader(GameRenderer::getPositionTexColorShader); + RenderSystem.setShaderTexture(0, color.res); + RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F); + float f = 2f; + bufferbuilder.begin(Mode.QUADS, DefaultVertexFormat.POSITION_TEX_COLOR); + bufferbuilder.vertex(x, y + (double)h, 0.0).uv(0.0F, (float)h / f).color(1f, 1f, 1f, 1f).endVertex(); + bufferbuilder.vertex(x + (double)w, y + (double)h, 0.0).uv((float)w / f, (float)h / f).color(1f, 1f, 1f, 1f).endVertex(); + bufferbuilder.vertex(x + (double)w, y, 0.0).uv((float)w / f, 0).color(1f, 1f, 1f, 1f).endVertex(); + bufferbuilder.vertex(x, y, 0.0).uv(0.0F, 0).color(1f, 1f, 1f, 1f).endVertex(); + tesselator.end(); + } + + protected static void renderNineSliced(Graphics graphics, int x, int y, int w, int h, int u, int v, int textureWidth, int textureHeight, int cornerSliceSize, ResourceLocation location, boolean renderCenter) { + GuiUtils.drawTexture(location, graphics, x, y, cornerSliceSize, cornerSliceSize, u, v, cornerSliceSize, cornerSliceSize, textureWidth, textureHeight); // Top left + GuiUtils.drawTexture(location, graphics, x + w - cornerSliceSize, y, cornerSliceSize, cornerSliceSize, u + 1 + cornerSliceSize, v, cornerSliceSize, cornerSliceSize, textureWidth, textureHeight); // Top right + GuiUtils.drawTexture(location, graphics, x, y + h - cornerSliceSize, cornerSliceSize, cornerSliceSize, u, v + 1 + cornerSliceSize, cornerSliceSize, cornerSliceSize, textureWidth, textureHeight); // bottom left + GuiUtils.drawTexture(location, graphics, x + w - cornerSliceSize, y + h - cornerSliceSize, cornerSliceSize, cornerSliceSize, u + 1 + cornerSliceSize, v + 1 + cornerSliceSize, cornerSliceSize, cornerSliceSize, textureWidth, textureHeight); // bottom right + + GuiUtils.drawTexture(location, graphics, x + cornerSliceSize, y, w - cornerSliceSize * 2, cornerSliceSize, u + cornerSliceSize, v, 1, cornerSliceSize, textureWidth, textureHeight); // top + GuiUtils.drawTexture(location, graphics, x + cornerSliceSize, y + h - cornerSliceSize, w - cornerSliceSize * 2, cornerSliceSize, u + cornerSliceSize, v + 1 + cornerSliceSize, 1, cornerSliceSize, textureWidth, textureHeight); // bottom + GuiUtils.drawTexture(location, graphics, x, y + cornerSliceSize, cornerSliceSize, h - cornerSliceSize * 2, u, v + cornerSliceSize, cornerSliceSize, 1, textureWidth, textureHeight); // left + GuiUtils.drawTexture(location, graphics, x + w - cornerSliceSize, y + cornerSliceSize, cornerSliceSize, h - cornerSliceSize * 2, u + 1 + cornerSliceSize, v + cornerSliceSize, cornerSliceSize, 1, textureWidth, textureHeight); // right + + if (renderCenter) { + GuiUtils.drawTexture(location, graphics, x + cornerSliceSize, y + cornerSliceSize, w - cornerSliceSize * 2, h - cornerSliceSize * 2, u + cornerSliceSize, v + cornerSliceSize, 1, 1, textureWidth, textureHeight); + } + } + + public static void renderContainer(Graphics graphics, int x, int y, int w, int h, ContainerColor color) { + renderContainerBackground(graphics, x + 2, y + 2, w - 4, h - 4, color); + renderNineSliced(graphics, x, y, w, h, 0, 7, CRNGui.GUI_WIDTH, CRNGui.GUI_HEIGHT, 2, CRNGui.GUI, false); + } + + public static void renderTitleBar(Graphics graphics, int x, int y, int w, int h, BarColor color) { + renderNineSliced(graphics, x, y, w, h, color.u, color.v, CRNGui.GUI_WIDTH, CRNGui.GUI_HEIGHT, 3, CRNGui.GUI, true); + } + + + public static void renderWindow(Graphics graphics, int x, int y, int w, int h, ContainerColor color, BarColor bar, int headerSize, int footerSize, boolean renderContent) { + renderTitleBar(graphics, x, y, w, headerSize, bar); + renderTitleBar(graphics, x, y + h - footerSize, w, footerSize, bar); + + if (renderContent) { + renderContainer(graphics, x + 1, y + headerSize - 1, w - 2, h - headerSize - footerSize + 2, color); + } + } + + public static void renderShadow(Graphics graphics, int x, int y, int w, int h) { + renderNineSliced(graphics, x - 5, y - 5, w + 10, h + 10, 0, 0, 11, 11, 5, new ResourceLocation(CreateRailwaysNavigator.MOD_ID, "textures/gui/shadow.png"), true); + } + + public static void renderTextHighlighted(Graphics graphics, int x, int y, Font font, Component text, int color) { + int width = font.width(text) + 6; + int height = font.lineHeight + 6; + GuiUtils.fill(graphics, x, y + 1, width, height - 3, color); + GuiUtils.fill(graphics, x + 1, y, width - 2, 1, color); + GuiUtils.fill(graphics, x + 1, y + height - 2, width - 2, 1, color); + GuiUtils.drawString(graphics, font, x + 3, y + 3, text, ModGuiUtils.useWhiteOrBlackForeColor(color) ? 0xFFFFFFFF : 0xFF000000, EAlignment.LEFT, false); + } + public static enum ColorShade { LIGHT(0xFF6f6f6f), DARK(0xFF575757); - private int color; + private final int color; ColorShade(int color) { this.color = color; @@ -105,4 +185,50 @@ public int getColor() { } } + + public static enum ContainerColor { + GRAY(new ResourceLocation(CreateRailwaysNavigator.MOD_ID, "textures/gui/container_gray.png")), + PURPLE(new ResourceLocation(CreateRailwaysNavigator.MOD_ID, "textures/gui/container_purple.png")), + BLUE(new ResourceLocation(CreateRailwaysNavigator.MOD_ID, "textures/gui/container_blue.png")), + GOLD(new ResourceLocation(CreateRailwaysNavigator.MOD_ID, "textures/gui/container_gold.png")); + + private ResourceLocation res; + + ContainerColor(ResourceLocation res) { + this.res = res; + } + } + + public static enum BarColor { + GRAY(7, 0), + PURPLE(14, 0), + GOLD(0, 0); + + private final int u; + private final int v; + + BarColor(int u, int v) { + this.u = u; + this.v = v; + } + } + + + + public static enum FooterSize { + DEFAULT(15), + SMALL(30), + EXTENDED(36); + + private final int size; + + FooterSize(int size) { + this.size = size; + } + + public int size() { + return size; + } + } + } diff --git a/common/src/main/java/de/mrjulsen/crn/client/gui/screen/CustomIconScreenElement.java b/common/src/main/java/de/mrjulsen/crn/client/gui/CustomIconScreenElement.java similarity index 95% rename from common/src/main/java/de/mrjulsen/crn/client/gui/screen/CustomIconScreenElement.java rename to common/src/main/java/de/mrjulsen/crn/client/gui/CustomIconScreenElement.java index 672a5930..85e111e8 100644 --- a/common/src/main/java/de/mrjulsen/crn/client/gui/screen/CustomIconScreenElement.java +++ b/common/src/main/java/de/mrjulsen/crn/client/gui/CustomIconScreenElement.java @@ -1,4 +1,4 @@ -package de.mrjulsen.crn.client.gui.screen; +package de.mrjulsen.crn.client.gui; import com.mojang.blaze3d.vertex.PoseStack; import com.simibubi.create.foundation.gui.element.ScreenElement; diff --git a/common/src/main/java/de/mrjulsen/crn/client/gui/ModGuiIcons.java b/common/src/main/java/de/mrjulsen/crn/client/gui/ModGuiIcons.java index 002b24c0..bef0e660 100644 --- a/common/src/main/java/de/mrjulsen/crn/client/gui/ModGuiIcons.java +++ b/common/src/main/java/de/mrjulsen/crn/client/gui/ModGuiIcons.java @@ -6,6 +6,7 @@ import com.simibubi.create.foundation.gui.AllIcons; import de.mrjulsen.crn.CreateRailwaysNavigator; +import de.mrjulsen.mcdragonlib.client.render.Sprite; import de.mrjulsen.mcdragonlib.client.util.Graphics; import de.mrjulsen.mcdragonlib.client.util.GuiUtils; import net.minecraft.resources.ResourceLocation; @@ -15,6 +16,7 @@ public enum ModGuiIcons { CHECK("check", 1, 0), CROSS("cross", 2, 0), WARN("warn", 3, 0), + IMPORTANT("important", 4, 0), SETTINGS("settings", 0, 1), FILTER("filter", 1, 1), @@ -46,14 +48,29 @@ public enum ModGuiIcons { DETAILED("detailed", 10, 2), VERY_DETAILED("very_detailed", 11, 2), ARROW_RIGHT("arrow_right", 12, 2), - ARROW_LEFT("arrow_left", 13, 2); + ARROW_LEFT("arrow_left", 13, 2), + REFRESH("refresh", 14, 2), + POPUP("refresh", 15, 2), + + USER("user", 0, 3), + MAP_PATH("map_path", 1, 3), + BOOKMARK_FILLED("bookmark_filled", 2, 3), + HELP("help", 3, 3), + ROUTE_START("route_start", 4, 3), + ROUTE_END("route_end", 5, 3), + CALENDAR("calendar", 6, 3), + BELL("bell", 7, 3), + COLOR_PALETTE("color_palette", 8, 3), + TRAIN("train", 9, 3), + X("x", 10, 3), + CHECKMARK("checkmark", 11, 3); private String id; private int u; private int v; public static final int ICON_SIZE = 16; - public static final ResourceLocation ICON_LOCATION = new ResourceLocation(CreateRailwaysNavigator.MOD_ID, "textures/gui/icons.png");; + public static final ResourceLocation ICON_LOCATION = new ResourceLocation(CreateRailwaysNavigator.MOD_ID, "textures/gui/icons.png"); ModGuiIcons(String id, int u, int v) { this.id = id; @@ -92,6 +109,10 @@ public AllIcons getAsCreateIcon() { public void render(Graphics graphics, int x, int y) { GuiUtils.drawTexture(ModGuiIcons.ICON_LOCATION, graphics, x, y, getU(), getV(), ICON_SIZE, ICON_SIZE); } + + public Sprite getAsSprite(int renderWidth, int renderHeight) { + return new Sprite(ICON_LOCATION, 256, 256, getU(), getV(), ICON_SIZE, ICON_SIZE, renderWidth, renderHeight); + } public static class ModAllIcons extends AllIcons { diff --git a/common/src/main/java/de/mrjulsen/crn/client/gui/overlay/RouteDetailsOverlay.java b/common/src/main/java/de/mrjulsen/crn/client/gui/overlay/RouteDetailsOverlay.java new file mode 100644 index 00000000..c209148c --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/client/gui/overlay/RouteDetailsOverlay.java @@ -0,0 +1,296 @@ +package de.mrjulsen.crn.client.gui.overlay; + +import java.util.Set; +import com.mojang.blaze3d.platform.InputConstants; +import com.mojang.blaze3d.systems.RenderSystem; +import com.simibubi.create.foundation.gui.UIRenderHelper; +import com.simibubi.create.foundation.utility.animation.LerpedFloat; +import com.simibubi.create.foundation.utility.animation.LerpedFloat.Chaser; + +import de.mrjulsen.crn.CreateRailwaysNavigator; +import de.mrjulsen.crn.client.ModGuiUtils; +import de.mrjulsen.crn.client.gui.overlay.pages.AbstractRouteDetailsPage; +import de.mrjulsen.crn.client.gui.overlay.pages.ConnectionMissedPage; +import de.mrjulsen.crn.client.gui.overlay.pages.JourneyCompletedPage; +import de.mrjulsen.crn.client.gui.overlay.pages.NextConnectionsPage; +import de.mrjulsen.crn.client.gui.overlay.pages.RouteOverviewPage; +import de.mrjulsen.crn.client.gui.overlay.pages.TrainCancelledInfo; +import de.mrjulsen.crn.client.gui.overlay.pages.TransferPage; +import de.mrjulsen.crn.client.gui.overlay.pages.WelcomePage; +import de.mrjulsen.crn.client.gui.screen.RouteOverlaySettingsScreen; +import de.mrjulsen.crn.client.input.ModKeys; +import de.mrjulsen.crn.client.lang.ELanguage; +import de.mrjulsen.crn.config.ModClientConfig; +import de.mrjulsen.crn.data.StationTag.StationInfo; +import de.mrjulsen.crn.data.navigation.ClientRoute; +import de.mrjulsen.crn.data.navigation.TransferConnection; +import de.mrjulsen.crn.registry.ModItems; +import de.mrjulsen.mcdragonlib.DragonLib; +import de.mrjulsen.mcdragonlib.client.gui.DLOverlayScreen; +import de.mrjulsen.mcdragonlib.client.gui.DLScreen; +import de.mrjulsen.mcdragonlib.client.util.Graphics; +import de.mrjulsen.mcdragonlib.client.util.GuiUtils; +import de.mrjulsen.mcdragonlib.core.EAlignment; +import de.mrjulsen.mcdragonlib.util.DLUtils; +import de.mrjulsen.mcdragonlib.util.TextUtils; +import de.mrjulsen.mcdragonlib.util.TimeUtils; +import net.minecraft.ChatFormatting; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Font; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.Level; + +public class RouteDetailsOverlay extends DLOverlayScreen { + + private static final ResourceLocation GUI = new ResourceLocation(CreateRailwaysNavigator.MOD_ID, "textures/gui/overview.png"); + private static final Component title = TextUtils.translate("gui.createrailwaysnavigator.route_overview.title"); + private static final int GUI_WIDTH = 226; + private static final int GUI_HEIGHT = 118; + private static final int SLIDING_TEXT_AREA_WIDTH = 220; + + private final Level level; + + private Component slidingText = TextUtils.empty(); + private float slidingTextOffset = 0; + private int slidingTextWidth = 0; + + private LerpedFloat xPos; + private LerpedFloat yPos; + + private static final String keyTrainDetails = "gui.createrailwaysnavigator.route_overview.train_details"; + private static final String keyTransfer = "gui.createrailwaysnavigator.route_overview.transfer"; + private static final String keyTransferWithPlatform = "gui.createrailwaysnavigator.route_overview.transfer_with_platform"; + private static final String keyAfterJourney = "gui.createrailwaysnavigator.route_overview.after_journey"; + private static final String keyOptionsText = "gui.createrailwaysnavigator.route_overview.options"; + private static final String keyKeybindOptions = "key.createrailwaysnavigator.route_overlay_options"; + private static final String keyJourneyBegins = "gui.createrailwaysnavigator.route_overview.journey_begins"; + private static final String keyJourneyBeginsWithPlatform = "gui.createrailwaysnavigator.route_overview.journey_begins_with_platform"; + private static final String keyNextStop = "gui.createrailwaysnavigator.route_overview.next_stop"; + private static final String keyConnectionMissedInfo = "gui.createrailwaysnavigator.route_overview.connection_missed_info"; + private static final String keyTrainCancelledInfo = "gui.createrailwaysnavigator.route_overview.train_cancelled_info"; + + + private final Font font = Minecraft.getInstance().font; + private final ClientRoute route; + private boolean journeyCompleted = false; + + private AbstractRouteDetailsPage currentPage; + + public RouteDetailsOverlay(Level level, ClientRoute route, int width, int height) { + this.level = level; + this.route = route; + route.addListener(); + + { + currentPage = new WelcomePage(this.route); + String terminus = route.getStart().getDisplayTitle(); + StationInfo info = route.getStart().getRealTimeStationTag().info(); + setSlidingText(info.platform().isEmpty() ? ELanguage.translate(keyJourneyBegins, route.getStart().getTrainDisplayName(), terminus, TimeUtils.parseTime(route.getStart().getScheduledDepartureTime(), ModClientConfig.TIME_FORMAT.get())) : ELanguage.translate(keyJourneyBeginsWithPlatform, route.getStart().getTrainDisplayName(), terminus, TimeUtils.parseTime(route.getStart().getScheduledDepartureTime(), ModClientConfig.TIME_FORMAT.get()), info.platform())); + } + + xPos = LerpedFloat.linear().startWithValue(width / 2 - (ModClientConfig.OVERLAY_SCALE.get() * (GUI_WIDTH / 2))); + yPos = LerpedFloat.linear().startWithValue(height / 2 - (ModClientConfig.OVERLAY_SCALE.get() * (GUI_HEIGHT / 2))); + + if (route.isClosed()) return; + + route.listen(ClientRoute.EVENT_DEPARTURE_FROM_ANY_STOP, this, x -> { + currentPage = new RouteOverviewPage(this.route); + String terminus = x.part().getNextStop().getTerminusText(); + setSlidingText(ELanguage.translate(keyTrainDetails, x.part().getNextStop().getTrainDisplayName(), terminus == null || terminus.isEmpty() ? x.part().getNextStop().getScheduleTitle() : terminus)); + }); + route.listen(ClientRoute.EVENT_FIRST_STOP_STATION_CHANGED, this, x -> { + setSlidingText(x.trainStop().getRealTimeStationTag().info().platform().isEmpty() ? ELanguage.translate(keyJourneyBegins) : ELanguage.translate(keyJourneyBeginsWithPlatform, x.trainStop().getRealTimeStationTag().info().platform())); + }); + route.listen(ClientRoute.EVENT_ARRIVAL_AT_ANY_STOP, this, x -> { + setSlidingText(TextUtils.text(x.trainStop().getClientTag().tagName())); + }); + route.listen(ClientRoute.EVENT_ANY_STOP_ANNOUNCED, this, x -> { + NextConnectionsPage page = new NextConnectionsPage(this.route, null); + if (page.hasConnections()) { + currentPage = page; + } + }); + route.listen(ClientRoute.EVENT_ANNOUNCE_STOPOVER, this, x -> { + setSlidingText(ELanguage.translate(keyNextStop, x.trainStop().getClientTag().tagName())); + }); + route.listen(ClientRoute.EVENT_ANNOUNCE_LAST_STOP, this, x -> { + setSlidingText(ELanguage.translate(keyNextStop, x.trainStop().getClientTag().tagName())); + }); + route.listen(ClientRoute.EVENT_ANNOUNCE_TRANSFER_ARRIVAL_STATION, this, x -> { + if (x.connection().isConnectionMissed()) { + connectionMissed(); + return; + } + setSlidingText(ELanguage.translate(keyNextStop, x.trainStop().getClientTag().tagName()).append(" *** ").append(getTransferSlidingText(x.connection()))); + currentPage = new TransferPage(this.route, x.connection()); + }); + route.listen(ClientRoute.EVENT_PART_CHANGED, this, x -> { + if (x.connection().isConnectionMissed()) { + connectionMissed(); + } + }); + route.listen(ClientRoute.EVENT_DEPARTURE_FROM_TRANSFER_ARRIVAL_STATION, this, x -> { + setSlidingText(TextUtils.text(x.connection().getArrivalStation().getClientTag().tagName()).append(" *** ").append(getTransferSlidingText(x.connection()))); + currentPage = new TransferPage(this.route, x.connection()); + }); + route.listen(ClientRoute.EVENT_ARRIVAL_AT_LAST_STOP, this, x -> { + setSlidingText(ELanguage.translate(keyAfterJourney, x.trainStop().getClientTag().tagName())); + currentPage = new JourneyCompletedPage(this.route, () -> currentPage = new NextConnectionsPage(route, () -> {} /*InstanceManager::removeRouteOverlay*/)); + route.close(); + }); + route.listen(ClientRoute.EVENT_DEPARTURE_FROM_LAST_STOP, this, x -> { + if (journeyCompleted) { + return; + } + setSlidingText(ELanguage.translate(keyAfterJourney, x.trainStop().getClientTag().tagName())); + currentPage = new JourneyCompletedPage(this.route, () -> currentPage = new NextConnectionsPage(route, () -> {} /*InstanceManager::removeRouteOverlay*/)); + route.close(); + }); + route.listen(ClientRoute.EVENT_ANY_TRANSFER_MISSED, this, x -> { + connectionMissed(); + }); + route.listen(ClientRoute.EVENT_ANY_TRAIN_CANCELLED, this, x -> { + trainCancelled(x.part().getLastStop().getTrainDisplayName()); + }); + } + + private Component getTransferSlidingText(TransferConnection connection) { + StationInfo info = connection.getDepartureStation().getRealTimeStationTag().info(); + String terminus = connection.getDepartureStation().getDisplayTitle(); + return (info == null || info.platform().isBlank() ? ELanguage.translate(keyTransfer, connection.getDepartureStation().getTrainDisplayName(), terminus) : ELanguage.translate(keyTransferWithPlatform, connection.getDepartureStation().getTrainDisplayName(), terminus, info.platform())); + } + + private void connectionMissed() { + setSlidingText(ELanguage.translate(keyConnectionMissedInfo)); + currentPage = new ConnectionMissedPage(this.route); + route.close(); + } + + private void trainCancelled(String trainName) { + setSlidingText(ELanguage.translate(keyTrainCancelledInfo, trainName)); + currentPage = new TrainCancelledInfo(this.route, trainName); + route.close(); + } + + private float getUIScale() { + return (float)ModClientConfig.OVERLAY_SCALE.get().doubleValue(); + } + + @Override + public void onClose() { + route.close(); + journeyCompleted = true; + } + + + @SuppressWarnings("resource") + @Override + public void tick() { + if (Screen.hasControlDown() && ModKeys.KEY_OVERLAY_SETTINGS.isDown() && Minecraft.getInstance().player.getInventory().hasAnyOf(Set.of(ModItems.NAVIGATOR.get()))) { + DLScreen.setScreen(new RouteOverlaySettingsScreen(this)); + } + + xPos.tickChaser(); + yPos.tickChaser(); + + DLUtils.doIfNotNull(currentPage, x -> x.tick()); + } + + protected void tickSlidingText(float delta) { + // Sliding text + if (slidingTextWidth > SLIDING_TEXT_AREA_WIDTH * 0.75f) { + slidingTextOffset -= delta; + if (slidingTextOffset < -(slidingTextWidth / 2)) { + slidingTextOffset = (int)((SLIDING_TEXT_AREA_WIDTH + slidingTextWidth / 2) + 20); + } + } + } + + //#region FUNCTIONS + + private void startStencil(Graphics graphics, int x, int y, int w, int h) { + UIRenderHelper.swapAndBlitColor(Minecraft.getInstance().getMainRenderTarget(), UIRenderHelper.framebuffer); + ModGuiUtils.startStencil(graphics, x, y, w, h); + } + + private void endStencil() { + ModGuiUtils.endStencil(); + UIRenderHelper.swapAndBlitColor(UIRenderHelper.framebuffer, Minecraft.getInstance().getMainRenderTarget()); + } + + private void setSlidingText(Component component) { + slidingText = component; + slidingTextWidth = font.width(component); + + if (slidingTextWidth > SLIDING_TEXT_AREA_WIDTH * 0.75f) { + slidingTextOffset = (int)((SLIDING_TEXT_AREA_WIDTH + slidingTextWidth / 2) + 20); + } else { + slidingTextOffset = (int)(SLIDING_TEXT_AREA_WIDTH * 0.75f / 2); + } + } + //#endregion + + //#region RENDERING + @Override + public void render(Graphics graphics, float partialTicks, int width, int height) { + width = Minecraft.getInstance().getWindow().getGuiScaledWidth(); + height = Minecraft.getInstance().getWindow().getGuiScaledHeight(); + partialTicks = Minecraft.getInstance().getFrameTime(); + OverlayPosition pos = ModClientConfig.ROUTE_OVERLAY_POSITION.get(); + final int x = pos == OverlayPosition.TOP_LEFT || pos == OverlayPosition.BOTTOM_LEFT ? 8 : (int)(width - GUI_WIDTH * getUIScale() - 10); + final int y = pos == OverlayPosition.TOP_LEFT || pos == OverlayPosition.TOP_RIGHT ? 8 : (int)(height - GUI_HEIGHT * getUIScale() - 10); + + xPos.chase(x, 0.2f, Chaser.EXP); + yPos.chase(y, 0.2f, Chaser.EXP); + + graphics.poseStack().pushPose(); + graphics.poseStack().translate((int)xPos.getValue(partialTicks), (int)yPos.getValue(partialTicks), 0); + renderInternal(graphics, 0, 0, width, height, partialTicks, (int)xPos.getValue(partialTicks), (int)yPos.getValue(partialTicks)); + graphics.poseStack().popPose(); + + tickSlidingText(2 * Minecraft.getInstance().getDeltaFrameTime()); + } + + public void renderSlidingText(Graphics graphics, int x, int y, int transX, int transY) { + startStencil(graphics, x + 3, y + 14, 220, 21); + graphics.poseStack().pushPose(); + graphics.poseStack().scale(1.0f / 0.75f, 1.0f / 0.75f, 1.0f / 0.75f); + GuiUtils.drawString(graphics, font, (int)((x + 3) + slidingTextOffset), y + 14, slidingText, 0xFF9900, EAlignment.CENTER, false); + graphics.poseStack().popPose(); + endStencil(); + } + + private void renderInternal(Graphics graphics, int x, int y, int width, int height, float partialTicks, int transX, int transY) { + graphics.poseStack().pushPose(); + graphics.poseStack().scale(getUIScale(), getUIScale(), getUIScale()); + RenderSystem.setShaderTexture(0, GUI); + GuiUtils.drawTexture(GUI, graphics, x, y, GUI_WIDTH, GUI_HEIGHT, 0, currentPage != null && currentPage.isImportant() ? 138 : 0, 256, 256); + + GuiUtils.drawString(graphics, font, x + 6, y + 4, title, 0x4F4F4F, EAlignment.LEFT, false); + GuiUtils.drawString(graphics, font, x + 6, y + GUI_HEIGHT - 2 - font.lineHeight, TextUtils.translate(keyOptionsText, TextUtils.translate(InputConstants.getKey(Minecraft.ON_OSX ? InputConstants.KEY_LWIN : InputConstants.KEY_LCONTROL, 0).getName()).append(" + ").append(Component.keybind(keyKeybindOptions)).withStyle(ChatFormatting.BOLD)), 0x4F4F4F, EAlignment.LEFT, false); + + String timeString = TimeUtils.parseTime((int)((level.getDayTime() + DragonLib.DAYTIME_SHIFT) % DragonLib.TICKS_PER_DAY), ModClientConfig.TIME_FORMAT.get()); + GuiUtils.drawString(graphics, font, x + GUI_WIDTH - 4 - font.width(timeString), y + 4, timeString, 0x4F4F4F, EAlignment.LEFT, false); + + renderSlidingText(graphics, x, y + 2, transX, transY); + + startStencil(graphics, x + 3, y + 40, 220, 62); + graphics.poseStack().pushPose(); + graphics.poseStack().translate(3, 40, 0); + DLUtils.doIfNotNull(currentPage, a -> { + a.renderBackLayer(graphics, 0, 0, partialTicks); + a.renderMainLayer(graphics, 0, 0, partialTicks); + }); + graphics.poseStack().popPose(); + endStencil(); + DLUtils.doIfNotNull(currentPage, a -> a.renderFrontLayer(graphics, 0, 0, partialTicks)); + if (CreateRailwaysNavigator.isDebug()) GuiUtils.drawString(graphics, font, 5, GUI_HEIGHT + 10, "State: " + route.getState() + ", " + route.getCurrentPartIndex() + ", " + route.getCurrentPart().getNextStop().getClientTag().tagName(), 0xFFFF0000, EAlignment.LEFT, false); + graphics.poseStack().popPose(); + } + + public ClientRoute getRoute() { + return route; + } +} diff --git a/common/src/main/java/de/mrjulsen/crn/client/gui/overlay/RouteDetailsOverlayScreen.java b/common/src/main/java/de/mrjulsen/crn/client/gui/overlay/RouteDetailsOverlayScreen.java deleted file mode 100644 index 76371718..00000000 --- a/common/src/main/java/de/mrjulsen/crn/client/gui/overlay/RouteDetailsOverlayScreen.java +++ /dev/null @@ -1,787 +0,0 @@ -package de.mrjulsen.crn.client.gui.overlay; - -import java.util.Collection; -import java.util.Optional; -import java.util.Set; -import java.util.UUID; - -import com.mojang.blaze3d.platform.InputConstants; -import com.mojang.blaze3d.systems.RenderSystem; -import com.mojang.text2speech.Narrator; -import com.simibubi.create.content.trains.station.NoShadowFontWrapper; -import com.simibubi.create.foundation.gui.UIRenderHelper; -import com.simibubi.create.foundation.utility.animation.LerpedFloat; -import com.simibubi.create.foundation.utility.animation.LerpedFloat.Chaser; - -import de.mrjulsen.crn.CreateRailwaysNavigator; -import de.mrjulsen.crn.client.ClientWrapper; -import de.mrjulsen.crn.client.ModGuiUtils; -import de.mrjulsen.crn.client.gui.ModGuiIcons; -import de.mrjulsen.crn.client.gui.NavigatorToast; -import de.mrjulsen.crn.client.input.ModKeys; -import de.mrjulsen.crn.client.lang.ELanguage; -import de.mrjulsen.crn.config.ModClientConfig; -import de.mrjulsen.crn.data.SimpleRoute; -import de.mrjulsen.crn.data.SimpleTrainConnection; -import de.mrjulsen.crn.data.SimpleRoute.StationEntry; -import de.mrjulsen.crn.data.SimpleRoute.StationTag; -import de.mrjulsen.crn.event.listeners.IJourneyListenerClient; -import de.mrjulsen.crn.event.listeners.JourneyListener; -import de.mrjulsen.crn.event.listeners.JourneyListenerManager; -import de.mrjulsen.crn.event.listeners.JourneyListener.AnnounceNextStopData; -import de.mrjulsen.crn.event.listeners.JourneyListener.ContinueData; -import de.mrjulsen.crn.event.listeners.JourneyListener.FinishJourneyData; -import de.mrjulsen.crn.event.listeners.JourneyListener.JourneyBeginData; -import de.mrjulsen.crn.event.listeners.JourneyListener.JourneyInterruptData; -import de.mrjulsen.crn.event.listeners.JourneyListener.NotificationData; -import de.mrjulsen.crn.event.listeners.JourneyListener.ReachNextStopData; -import de.mrjulsen.crn.event.listeners.JourneyListener.State; -import de.mrjulsen.crn.network.InstanceManager; -import de.mrjulsen.crn.network.packets.cts.NextConnectionsRequestPacket; -import de.mrjulsen.crn.network.packets.cts.TrainDataRequestPacket; -import de.mrjulsen.crn.registry.ModItems; -import de.mrjulsen.crn.util.ModUtils; -import de.mrjulsen.mcdragonlib.DragonLib; -import de.mrjulsen.mcdragonlib.client.gui.DLOverlayScreen; -import de.mrjulsen.mcdragonlib.client.util.Graphics; -import de.mrjulsen.mcdragonlib.client.util.GuiUtils; -import de.mrjulsen.mcdragonlib.core.EAlignment; -import de.mrjulsen.mcdragonlib.util.TextUtils; -import de.mrjulsen.mcdragonlib.util.TimeUtils; -import de.mrjulsen.mcdragonlib.util.TimeUtils.TimeFormat; -import net.minecraft.ChatFormatting; -import net.minecraft.Util; -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.Font; -import net.minecraft.client.gui.components.MultiLineLabel; -import net.minecraft.client.gui.screens.Screen; -import net.minecraft.network.chat.Component; -import net.minecraft.network.chat.MutableComponent; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.util.Mth; -import net.minecraft.world.level.Level; - -public class RouteDetailsOverlayScreen extends DLOverlayScreen implements IJourneyListenerClient { - - private static final ResourceLocation GUI = new ResourceLocation(CreateRailwaysNavigator.MOD_ID, "textures/gui/overview.png"); - private static final Component title = TextUtils.translate("gui.createrailwaysnavigator.route_overview.title"); - private static final int GUI_WIDTH = 226; - private static final int GUI_HEIGHT = 118; - private static final int SLIDING_TEXT_AREA_WIDTH = 220; - - private static final int ON_TIME = 0x1AEA5F; - private static final int DELAYED = 0xFF4242; - private static final int COLOR_WARN = 16755200; - - private long fadeStart = 0L; - private boolean fading = false; - private boolean fadeInvert = true; - private Runnable fadeDoneAction; - - private static final int ROUTE_LINE_HEIGHT = 14; - - private final Level level; - - private static final int MAX_STATION_PER_PAGE = 4; - - private Page currentPage = Page.NONE; - - private Collection connections; - private long connectionsRefreshTime; - private static final int CONNECTION_ENTRIES_PER_PAGE = 3; - private static final int TIME_PER_CONNECTIONS_SUBPAGE = 200; - private int connectionsSubPageTime = 0; - private int connectionsSubPageIndex = 0; - private int connectionsSubPagesCount = 0; - - private static final int TIME_PER_TRAIN_DATA_SUBPAGE = 200; - private static final int TRAIN_DATA_PAGES = 2; - private int trainDataSubPageTime = 0; - private int trainDataSubPageIndex = 0; - - private final Font shadowlessFont; - - private Component slidingText = TextUtils.empty(); - private float slidingTextOffset = 0; - private int slidingTextWidth = 0; - - private MultiLineLabel messageLabel; - private MutableComponent interruptedText; - - private LerpedFloat xPos; - private LerpedFloat yPos; - - private static final String keyTrainDetails = "gui.createrailwaysnavigator.route_overview.train_details"; - private static final String keyTransfer = "gui.createrailwaysnavigator.route_overview.transfer"; - private static final String keyTransferWithPlatform = "gui.createrailwaysnavigator.route_overview.transfer_with_platform"; - private static final String keyTransferCount = "gui.createrailwaysnavigator.navigator.route_entry.transfer"; - private static final String keyTrainCanceled = "gui.createrailwaysnavigator.route_overview.stop_canceled"; - private static final String keyAfterJourney = "gui.createrailwaysnavigator.route_overview.after_journey"; - private static final String keyJourneyCompleted = "gui.createrailwaysnavigator.route_overview.journey_completed"; - private static final String keyNextConnections = "gui.createrailwaysnavigator.route_overview.next_connections"; - private static final String keyScheduleTransfer = "gui.createrailwaysnavigator.route_overview.schedule_transfer"; - private static final String keyConnectionEndangered = "gui.createrailwaysnavigator.route_overview.connection_endangered"; - private static final String keyConnectionMissed = "gui.createrailwaysnavigator.route_overview.connection_missed"; - private static final String keyConnectionCanceled = "gui.createrailwaysnavigator.route_overview.connection_canceled"; - private static final String keyConnectionMissedPageText = "gui.createrailwaysnavigator.route_overview.journey_interrupted_info"; - private static final String keyDepartureIn = "gui.createrailwaysnavigator.route_details.departure"; - private static final String keyTimeNow = "gui.createrailwaysnavigator.time.now"; - private static final String keyOptionsText = "gui.createrailwaysnavigator.route_overview.options"; - private static final String keyKeybindOptions = "key.createrailwaysnavigator.route_overlay_options"; - - private final UUID listenerId; - private JourneyListener listener; - private final UUID clientId = UUID.randomUUID(); - - - - @SuppressWarnings("resource") - public RouteDetailsOverlayScreen(Level level, SimpleRoute route, int width, int height) { - this.level = level; - this.shadowlessFont = new NoShadowFontWrapper(Minecraft.getInstance().font); - this.listenerId = route.getListenerId(); - - xPos = LerpedFloat.linear().startWithValue(width / 2 - (ModClientConfig.OVERLAY_SCALE.get() * (GUI_WIDTH / 2))); - yPos = LerpedFloat.linear().startWithValue(height / 2 - (ModClientConfig.OVERLAY_SCALE.get() * (GUI_HEIGHT / 2))); - - getListener() - .registerOnNarratorAnnounce(this, this::narratorAnnouncement) - .registerOnAnnounceNextStop(this, this::announceNextStop) - .registerOnContinueWithJourneyAfterStop(this, this::nextStop) - .registerOnFinishJourney(this, this::finishJourney) - .registerOnReachNextStop(this, this::reachNextStop) - .registerOnJourneyBegin(this, this::journeyBegin) - .registerOnNotification(this, this::notificationReceive) - .registerOnJourneyInterrupt(this, this::journeyInterrupt) - ; - - setSlidingText(getListener().getLastInfoText()); - if (getListener().getCurrentState() == State.BEFORE_JOURNEY) { - if (getListener().getLastNotification() != null) { - notificationReceive(getListener().getLastNotification()); - } - if (getListener().lastNarratorText() != null) { - narratorAnnouncement(getListener().lastNarratorText()); - } - setPageJourneyStart(); - } else { - setPageRouteOverview(); - } - } - - private float getUIScale() { - return (float)ModClientConfig.OVERLAY_SCALE.get().doubleValue(); - } - - public JourneyListener getListener() { - return listener == null ? listener = JourneyListenerManager.getInstance().get(listenerId, this) : listener; - } - - public UUID getListenerId() { - return listenerId; - } - - @Override - public UUID getJourneyListenerClientId() { - return clientId; - } - - @Override - public void onClose() { - getListener().unregister(this); - JourneyListenerManager.getInstance().removeClientListenerForAll(this); - } - - - @SuppressWarnings("resource") - @Override - public void tick() { - if (Screen.hasControlDown() && ModKeys.KEY_OVERLAY_SETTINGS.isDown() && Minecraft.getInstance().player.getInventory().hasAnyOf(Set.of(ModItems.NAVIGATOR.get()))) { - ClientWrapper.showRouteOverlaySettingsGui(this); - } - - xPos.tickChaser(); - yPos.tickChaser(); - - //tickSlidingText(2); - - // train info while traveling - if (getListener() != null && getListener().getCurrentState() == JourneyListener.State.WHILE_TRAVELING) { - trainDataSubPageTime++; - if ((slidingTextWidth <= SLIDING_TEXT_AREA_WIDTH && trainDataSubPageTime > TIME_PER_TRAIN_DATA_SUBPAGE) || slidingTextOffset < -(slidingTextWidth / 2)) { - setTrainDataSubPage(false); - } - } - - switch (currentPage) { - case NEXT_CONNECTIONS: // Next connections animation - if (fading) { - break; - } - - connectionsSubPageTime++; - if (connectionsSubPageTime > TIME_PER_CONNECTIONS_SUBPAGE) { - setNextConnectionsSubPage(); - } - break; - case ROUTE_OVERVIEW: - default: - break; - } - } - - public void tickSlidingText(float delta) { - // Sliding text - if (slidingTextWidth > SLIDING_TEXT_AREA_WIDTH * 0.75f) { - slidingTextOffset -= delta; - if (slidingTextOffset < -(slidingTextWidth / 2)) { - slidingTextOffset = (int)((SLIDING_TEXT_AREA_WIDTH + slidingTextWidth / 2) + 20); - } - } - } - - //#region FUNCTIONS - - private void fadeIn(Runnable andThen) { - fadeInternal(andThen, false); - } - - private void fadeOut(Runnable andThen) { - fadeInternal(andThen, true); - } - - private void fadeInternal(Runnable andThen, boolean fadeOut) { - fadeDoneAction = andThen; - fadeInvert = fadeOut; - this.fadeStart = Util.getMillis(); - fading = true; - } - - private void setSlidingText(Component component) { - slidingText = component; - slidingTextWidth = shadowlessFont.width(component); - - if (slidingTextWidth > SLIDING_TEXT_AREA_WIDTH * 0.75f) { - slidingTextOffset = (int)((SLIDING_TEXT_AREA_WIDTH + slidingTextWidth / 2) + 20); - } else { - slidingTextOffset = (int)(SLIDING_TEXT_AREA_WIDTH * 0.75f / 2); - } - } - - private void startStencil(Graphics graphics, int x, int y, int w, int h) { - UIRenderHelper.swapAndBlitColor(Minecraft.getInstance().getMainRenderTarget(), UIRenderHelper.framebuffer); - ModGuiUtils.startStencil(graphics, x, y, w, h); - } - - private void endStencil() { - ModGuiUtils.endStencil(); - UIRenderHelper.swapAndBlitColor(UIRenderHelper.framebuffer, Minecraft.getInstance().getMainRenderTarget()); - } - //#endregion - - //#region ACTIONS - - private void notificationReceive(NotificationData data) { - if (ModClientConfig.ROUTE_NOTIFICATIONS.get()) { - Minecraft.getInstance().getToasts().addToast(NavigatorToast.multiline(data.title(), data.text())); - } - } - - private void journeyBegin(JourneyBeginData data) { - setSlidingText(data.infoText()); - setPageJourneyStart(); - } - - private void nextStop(ContinueData data) { - setTrainDataSubPage(true); - setPageRouteOverview(); - } - - private void finishJourney(FinishJourneyData data) { - setSlidingText(data.infoText()); - setPageJourneyCompleted(); - } - - private void announceNextStop(AnnounceNextStopData data) { - setSlidingText(data.infoText()); - setPageNextConnections(); - } - - private void reachNextStop(ReachNextStopData data) { - setSlidingText(data.infoText()); - - switch (data.transferState()) { - case CONNECTION_MISSED: - setPageConnectionMissed(); - break; - case DEFAULT: - setPageTransfer(); - break; - default: - break; - } - } - - private void journeyInterrupt(JourneyInterruptData data) { - setSlidingText(data.text()); - setPageJourneyInterrupted(data); - } - - private void narratorAnnouncement(String text) { - if (ModClientConfig.ROUTE_NARRATOR.get()) { - Narrator.getNarrator().say(text, true); - } - } - - //#endregion - - //#region PAGE MANAGEMENT - private void setPageRouteOverview() { - fadeOut(() -> { - currentPage = Page.ROUTE_OVERVIEW; - fadeIn(null); - }); - } - - private void setPageJourneyStart() { - fadeOut(() -> { - currentPage = Page.JOURNEY_START; - fadeIn(null); - }); - } - - private void setPageTransfer() { - fadeOut(() -> { - currentPage = Page.TRANSFER; - StationEntry station = getListener().currentStation(); - if (station != null) { - this.messageLabel = MultiLineLabel.create(shadowlessFont, - station.getInfo().platform() == null || station.getInfo().platform().isBlank() ? - ELanguage.translate(keyTransfer, - station.getTrain().trainName(), - station.getTrain().scheduleTitle() - ) : - ELanguage.translate(keyTransferWithPlatform, - station.getTrain().trainName(), - station.getTrain().scheduleTitle(), - station.getInfo().platform() - ), SLIDING_TEXT_AREA_WIDTH - (15 + ModGuiIcons.ICON_SIZE)); - } - fadeIn(null); - }); - } - - private void setPageConnectionMissed() { - fadeOut(() -> { - currentPage = Page.JOURNEY_INTERRUPTED; - StationEntry station = getListener().lastStation(); - if (station != null) { - Component text = TextUtils.translate(keyConnectionMissedPageText, station.getStationName()); - this.messageLabel = MultiLineLabel.create(shadowlessFont, text, SLIDING_TEXT_AREA_WIDTH - 10); - interruptedText = TextUtils.translate(keyConnectionMissed); - } - fadeIn(null); - }); - } - - private void setPageJourneyInterrupted(JourneyInterruptData data) { - fadeOut(() -> { - currentPage = Page.JOURNEY_INTERRUPTED; - this.messageLabel = MultiLineLabel.create(shadowlessFont, data.text(), SLIDING_TEXT_AREA_WIDTH - 10); - interruptedText = TextUtils.text(data.title().getString()); - fadeIn(null); - }); - } - - private void setPageJourneyCompleted() { - fadeOut(() -> { - currentPage = Page.JOURNEY_END; - StationEntry station = getListener().currentStation(); - if (station != null) { - this.messageLabel = MultiLineLabel.create(shadowlessFont, TextUtils.translate(keyAfterJourney, station.getStationName()), SLIDING_TEXT_AREA_WIDTH - 10); - } - fadeIn(null); - }); - } - - private void setPageNextConnections() { - long id = InstanceManager.registerClientNextConnectionsResponseAction((connections, time) -> { - this.connections = connections; - this.connectionsRefreshTime = time; - if (!connections.isEmpty()) { - fadeOut(() -> { - currentPage = Page.NEXT_CONNECTIONS; - connectionsSubPagesCount = connections.size() / CONNECTION_ENTRIES_PER_PAGE + (connections.size() % CONNECTION_ENTRIES_PER_PAGE == 0 ? 0 : 1); - connectionsSubPageIndex = 0; - connectionsSubPageTime = 0; - fadeIn(null); - }); - } - }); - CreateRailwaysNavigator.net().CHANNEL.sendToServer(new NextConnectionsRequestPacket(id, getListener().currentStation().getTrain().trainId(), getListener().currentStation().getStationName(), getListener().currentStation().getCurrentTicks() + ModClientConfig.TRANSFER_TIME.get())); - } - - private void setNextConnectionsSubPage() { - if (connectionsSubPagesCount > 1) { - fadeOut(() -> { - connectionsSubPageTime = 0; - connectionsSubPageIndex++; - if (connectionsSubPageIndex >= connectionsSubPagesCount) { - connectionsSubPageIndex = 0; - } - fadeIn(null); - }); - } else { - connectionsSubPageTime = 0; - } - } - - private void setTrainDataSubPage(boolean reset) { - if (reset || trainDataSubPageIndex >= TRAIN_DATA_PAGES) { - trainDataSubPageIndex = 0; - } else { - trainDataSubPageIndex++; - } - - switch (trainDataSubPageIndex) { - default: - case 0: - setSlidingText(ELanguage.translate(keyTrainDetails, - getListener().currentStation().getTrain().trainName(), - getListener().currentStation().getTrain().scheduleTitle() - )); - trainDataSubPageTime = 0; - break; - case 1: - long id = InstanceManager.registerClientTrainDataResponseAction((data, time) -> { - setSlidingText(ModUtils.calcSpeedString(data.speed(), ModClientConfig.SPEED_UNIT.get())); - trainDataSubPageTime = 0; - }); - CreateRailwaysNavigator.net().CHANNEL.sendToServer(new TrainDataRequestPacket(id, getListener().currentStation().getTrain().trainId(), false)); - break; - } - } - //#endregion - - //#region RENDERING - @Override - public void render(Graphics graphics, float partialTicks, int width, int height) { - width = Minecraft.getInstance().getWindow().getGuiScaledWidth(); - height = Minecraft.getInstance().getWindow().getGuiScaledHeight(); - partialTicks = Minecraft.getInstance().getFrameTime(); - OverlayPosition pos = ModClientConfig.ROUTE_OVERLAY_POSITION.get(); - final int x = pos == OverlayPosition.TOP_LEFT || pos == OverlayPosition.BOTTOM_LEFT ? 8 : (int)(width - GUI_WIDTH * getUIScale() - 10); - final int y = pos == OverlayPosition.TOP_LEFT || pos == OverlayPosition.TOP_RIGHT ? 8 : (int)(height - GUI_HEIGHT * getUIScale() - 10); - - xPos.chase(x, 0.2f, Chaser.EXP); - yPos.chase(y, 0.2f, Chaser.EXP); - - graphics.poseStack().pushPose(); - graphics.poseStack().translate((int)xPos.getValue(partialTicks), (int)yPos.getValue(partialTicks), 0); - renderInternal(graphics, 0, 0, width, height, partialTicks); - graphics.poseStack().popPose(); - - tickSlidingText(2 * Minecraft.getInstance().getDeltaFrameTime()); - } - - private void renderInternal(Graphics graphics, int x, int y, int width, int height, float partialTicks) { - graphics.poseStack().pushPose(); - float fadePercentage = this.fading ? Mth.clamp((float)(Util.getMillis() - this.fadeStart) / 500.0F, 0.0F, 1.0F) : 1.0F; - float alpha = fadeInvert ? Mth.clamp(1.0f - fadePercentage, 0, 1) : Mth.clamp(fadePercentage, 0, 1); - int fontAlpha = Mth.ceil(alpha * 255.0F) << 24; // | fontAlpha - - graphics.poseStack().scale(getUIScale(), getUIScale(), getUIScale()); - RenderSystem.setShaderTexture(0, GUI); - GuiUtils.drawTexture(GUI, graphics, x, y, GUI_WIDTH, GUI_HEIGHT, 0, getListener().getCurrentState().important() ? 138 : 0, 256, 256); - - GuiUtils.drawString(graphics, shadowlessFont, x + 6, y + 4, title, 0x4F4F4F, EAlignment.LEFT, false); - GuiUtils.drawString(graphics, shadowlessFont, x + 6, y + GUI_HEIGHT - 2 - shadowlessFont.lineHeight, TextUtils.translate(keyOptionsText, TextUtils.translate(InputConstants.getKey(Minecraft.ON_OSX ? InputConstants.KEY_LWIN : InputConstants.KEY_LCONTROL, 0).getName()).append(" + ").append(TextUtils.keybind(keyKeybindOptions)).withStyle(ChatFormatting.BOLD)), 0x4F4F4F, EAlignment.LEFT, false); - - String timeString = TimeUtils.parseTime((int)((level.getDayTime() + DragonLib.DAYTIME_SHIFT) % DragonLib.TICKS_PER_DAY), ModClientConfig.TIME_FORMAT.get()); - GuiUtils.drawString(graphics, shadowlessFont, x + GUI_WIDTH - 4 - shadowlessFont.width(timeString), y + 4, timeString, 0x4F4F4F, EAlignment.LEFT, false); - - // Test - renderSlidingText(graphics, x, y + 2); - - startStencil(graphics, x + 3, y + 40, 220, 62); - graphics.poseStack().pushPose(); - - graphics.poseStack().translate((fadeInvert ? 0 : -20) + fadePercentage * 20, 0, 0); - if (alpha > 0.1f && (fontAlpha & -67108864) != 0) { - - switch (currentPage) { - case JOURNEY_START: - renderPageJourneyStart(graphics, x, y + 40, fadePercentage, fontAlpha); - break; - case NEXT_CONNECTIONS: - renderNextConnections(graphics, x, y + 40, fadePercentage, fontAlpha, null); - break; - case TRANSFER: - renderPageTransfer(graphics, x, y + 40, fadePercentage, fontAlpha, null); - break; - case JOURNEY_INTERRUPTED: - renderPageJourneyInterrupted(graphics, x, y + 40, fadePercentage, fontAlpha); - break; - case JOURNEY_END: - renderPageJourneyCompleted(graphics, x, y + 40, fadePercentage, fontAlpha); - break; - case ROUTE_OVERVIEW: - final int[] yOffset = new int[] { y + 40 - 1 }; - for (int i = getListener().getIndex(); i < Math.min(getListener().getIndex() + MAX_STATION_PER_PAGE, getListener().getListeningRoute().getStationCount(true)); i++) { - final int k = i; - yOffset[0] += renderRouteOverview(graphics, k, x, yOffset[0], alpha, fontAlpha); - } - break; - default: - break; - } - } - - graphics.poseStack().popPose(); - endStencil(); - graphics.poseStack().popPose(); - - if (fadePercentage >= 1.0f) { - fading = false; - if (fadeDoneAction != null) { - fadeDoneAction.run(); - } - } - } - - public void renderSlidingText(Graphics graphics, int x, int y) { - startStencil(graphics, x + 3, y + 16, 220, 21); - graphics.poseStack().pushPose(); - graphics.poseStack().scale(1.0f / 0.75f, 1.0f / 0.75f, 1.0f / 0.75f); - GuiUtils.drawString(graphics, shadowlessFont, (int)((x + 3) + slidingTextOffset), y + 14, slidingText, 0xFF9900, EAlignment.CENTER, false); - graphics.poseStack().popPose(); - endStencil(); - } - - public int renderRouteOverview(Graphics graphics, int index, int x, int y, float alphaPercentage, int fontAlpha) { - RenderSystem.setShaderTexture(0, GUI); - RenderSystem.setShaderColor(1, 1, 1, alphaPercentage); - RenderSystem.enableBlend(); - RenderSystem.defaultBlendFunc(); - RenderSystem.enableDepthTest(); - - Optional stationOptional = getListener().getEntryAt(index); - //Optional lastStation = index > getListener().getIndex() ? getListener().getEntryAt(index - 1) : Optional.empty(); - Optional nextStation = getListener().getEntryAt(index + 1); - - if (!stationOptional.isPresent()) { - return y; - } - StationEntry station = stationOptional.get(); - - boolean reachable = station.reachable(false); - - // Icon - int dY = index <= 0 ? 0 : ROUTE_LINE_HEIGHT; - final int transferY = ROUTE_LINE_HEIGHT * 3; - switch (station.getTag()) { - case PART_START: - dY = ROUTE_LINE_HEIGHT * 2; - break; - case START: - dY = ROUTE_LINE_HEIGHT * 4; - break; - default: - break; - } - GuiUtils.drawTexture(GUI, graphics, x + 75, y, 7, ROUTE_LINE_HEIGHT, 226, dY, 256, 256); - if (index >= getListener().getIndex() + MAX_STATION_PER_PAGE - 1 && station.getTag() != StationTag.END) { - GuiUtils.drawTexture(GUI, graphics, x + 75, y + ROUTE_LINE_HEIGHT, 7, ROUTE_LINE_HEIGHT, 226, ROUTE_LINE_HEIGHT, 256, 256); - } - - // time display - if (station.isTrainCanceled()) { - GuiUtils.drawString(graphics, shadowlessFont, x + 10, y + ROUTE_LINE_HEIGHT - 2 - shadowlessFont.lineHeight / 2, ELanguage.translate(keyTrainCanceled), DELAYED | fontAlpha, EAlignment.LEFT, false); - } else { - long timeDiff = station.getDifferenceTime(); - MutableComponent timeText = TextUtils.text(TimeUtils.parseTime((int)((station.getScheduleTime() + DragonLib.DAYTIME_SHIFT) % DragonLib.TICKS_PER_DAY), ModClientConfig.TIME_FORMAT.get())).withStyle(reachable ? ChatFormatting.RESET : ChatFormatting.STRIKETHROUGH); - - float scale = shadowlessFont.width(timeText) >= 30 ? 0.7F : 1; - graphics.poseStack().pushPose(); - graphics.poseStack().scale(scale, 1, 1); - GuiUtils.drawString(graphics, shadowlessFont, (int)((x + 10) / scale), y + ROUTE_LINE_HEIGHT - 2 - shadowlessFont.lineHeight / 2, timeText, reachable ? (index <= getListener().getIndex() ? 0xFFFFFF | fontAlpha : 0xDBDBDB | fontAlpha) : DELAYED | fontAlpha, EAlignment.LEFT, false); - graphics.poseStack().popPose(); - - if (station.reachable(false) && station.shouldRenderRealtime()) { - MutableComponent realtimeText = TextUtils.text(TimeUtils.parseTime((int)((station.getEstimatedTimeWithThreshold() + DragonLib.DAYTIME_SHIFT) % DragonLib.TICKS_PER_DAY), ModClientConfig.TIME_FORMAT.get())).withStyle(reachable ? ChatFormatting.RESET : ChatFormatting.STRIKETHROUGH); - - float realtimeScale = shadowlessFont.width(realtimeText) >= 30 ? 0.7F : 1; - graphics.poseStack().pushPose(); - graphics.poseStack().scale(realtimeScale, 1, 1); - GuiUtils.drawString(graphics, shadowlessFont, (int)((x + 40) / realtimeScale), y + ROUTE_LINE_HEIGHT - 2 - shadowlessFont.lineHeight / 2, realtimeText, timeDiff < ModClientConfig.DEVIATION_THRESHOLD.get() && reachable ? ON_TIME | fontAlpha : DELAYED | fontAlpha, EAlignment.LEFT, false); - graphics.poseStack().popPose(); - } - } - - // station name display - Component platformText = TextUtils.text(station.getUpdatedInfo().platform()); - int platformTextWidth = shadowlessFont.width(platformText); - - final int maxStationNameWidth = SLIDING_TEXT_AREA_WIDTH - platformTextWidth - 105; - MutableComponent stationText = TextUtils.text(station.getStationName()); - if (index == getListener().getIndex()) stationText = stationText.withStyle(ChatFormatting.BOLD); - if (!reachable) stationText = stationText.withStyle(ChatFormatting.STRIKETHROUGH); - if (shadowlessFont.width(stationText) > maxStationNameWidth) { - stationText = TextUtils.text(shadowlessFont.substrByWidth(stationText, maxStationNameWidth).getString()).append(TextUtils.text("...")).withStyle(stationText.getStyle()); - } - - GuiUtils.drawString(graphics, shadowlessFont, x + 90, y + ROUTE_LINE_HEIGHT - 2 - shadowlessFont.lineHeight / 2, stationText, reachable ? (index <= getListener().getIndex() ? 0xFFFFFF | fontAlpha : 0xDBDBDB | fontAlpha) : DELAYED | fontAlpha, EAlignment.LEFT, false); - GuiUtils.drawString(graphics, shadowlessFont, x + SLIDING_TEXT_AREA_WIDTH - platformTextWidth, y + ROUTE_LINE_HEIGHT - 2 - shadowlessFont.lineHeight / 2, platformText, reachable && !station.stationInfoChanged() ? (index <= getListener().getIndex() ? 0xFFFFFF | fontAlpha : 0xDBDBDB | fontAlpha) : DELAYED | fontAlpha, EAlignment.LEFT, false); - - // render transfer - if (station.getTag() == StationTag.PART_END) { - y += ROUTE_LINE_HEIGHT; - RenderSystem.setShaderColor(1, 1, 1, alphaPercentage); - RenderSystem.enableBlend(); - RenderSystem.defaultBlendFunc(); - RenderSystem.enableDepthTest(); - GuiUtils.drawTexture(GUI, graphics, x + 75, y, 7, ROUTE_LINE_HEIGHT, 226, transferY,256, 256); - if (nextStation.isPresent() && !nextStation.get().reachable(true)) { - if (nextStation.get().isDeparted() || nextStation.get().isTrainCanceled()) { - ModGuiIcons.CROSS.render(graphics, x + 10, y + ROUTE_LINE_HEIGHT - 2 - ModGuiIcons.ICON_SIZE / 2); - GuiUtils.drawString(graphics, shadowlessFont, x + 90, y + ROUTE_LINE_HEIGHT - 2 - shadowlessFont.lineHeight / 2, ELanguage.translate(nextStation.get().isTrainCanceled() ? keyConnectionCanceled : keyConnectionMissed).withStyle(ChatFormatting.BOLD), DELAYED | fontAlpha, EAlignment.LEFT, false); - } else { - ModGuiIcons.WARN.render(graphics, x + 10, y + ROUTE_LINE_HEIGHT - 2 - ModGuiIcons.ICON_SIZE / 2); - GuiUtils.drawString(graphics, shadowlessFont, x + 90, y + ROUTE_LINE_HEIGHT - 2 - shadowlessFont.lineHeight / 2, ELanguage.translate(keyConnectionEndangered).withStyle(ChatFormatting.BOLD), COLOR_WARN | fontAlpha, EAlignment.LEFT, false); - } - } else { - GuiUtils.drawString(graphics, shadowlessFont, x + 10, y + ROUTE_LINE_HEIGHT - 2 - shadowlessFont.lineHeight / 2, TextUtils.text(TimeUtils.parseDurationShort((int)(getListener().getTransferTime(index)))).withStyle(ChatFormatting.ITALIC), 0xDBDBDB | fontAlpha, EAlignment.LEFT, false); - GuiUtils.drawString(graphics, shadowlessFont, x + 90, y + ROUTE_LINE_HEIGHT - 2 - shadowlessFont.lineHeight / 2, ELanguage.translate(keyScheduleTransfer).withStyle(ChatFormatting.ITALIC), 0xDBDBDB | fontAlpha, EAlignment.LEFT, false); - } - - return ROUTE_LINE_HEIGHT * 2; - } - return ROUTE_LINE_HEIGHT; - - } - - public void renderNextConnections(Graphics graphics, int x, int y, float alphaPercentage, int fontAlpha, StationEntry station) { - GuiUtils.drawString(graphics, shadowlessFont, x + 10, y + 4, ELanguage.translate(keyNextConnections).withStyle(ChatFormatting.BOLD), 0xFFFFFF | fontAlpha, EAlignment.LEFT, false); - - SimpleTrainConnection[] conns = connections.toArray(SimpleTrainConnection[]::new); - for (int i = connectionsSubPageIndex * CONNECTION_ENTRIES_PER_PAGE, k = 0; i < Math.min((connectionsSubPageIndex + 1) * CONNECTION_ENTRIES_PER_PAGE, connections.size()); i++, k++) { - MutableComponent time = TextUtils.text(TimeUtils.parseTime((int)((connectionsRefreshTime + conns[i].ticks() + DragonLib.DAYTIME_SHIFT) % DragonLib.TICKS_PER_DAY), ModClientConfig.TIME_FORMAT.get())); - MutableComponent platform = TextUtils.text(conns[i].stationDetails().platform()); - - int x1 = x + 10; - int x2 = x + 55; - int x3 = x + 100; - int x4 = x + SLIDING_TEXT_AREA_WIDTH - shadowlessFont.width(platform); - - final int maxTrainNameWidth = x3 - x2 - 5; - MutableComponent trainName = TextUtils.text(conns[i].trainName()); - if (shadowlessFont.width(trainName) > maxTrainNameWidth) { - trainName = TextUtils.text(shadowlessFont.substrByWidth(trainName, maxTrainNameWidth).getString()).append(TextUtils.text("...")); - } - - final int maxDestinationWidth = x4 - x3 - 5; - MutableComponent destination = TextUtils.text(conns[i].scheduleTitle()); - if (shadowlessFont.width(destination) > maxDestinationWidth) { - destination = TextUtils.text(shadowlessFont.substrByWidth(destination, maxDestinationWidth).getString()).append(TextUtils.text("...")); - } - - GuiUtils.drawString(graphics, shadowlessFont, x1, y + 15 + 12 * k, time, 0xDBDBDB | fontAlpha, EAlignment.LEFT, false); - GuiUtils.drawString(graphics, shadowlessFont, x2, y + 15 + 12 * k, trainName, 0xDBDBDB | fontAlpha, EAlignment.LEFT, false); - GuiUtils.drawString(graphics, shadowlessFont, x3, y + 15 + 12 * k, destination, 0xDBDBDB | fontAlpha, EAlignment.LEFT, false); - GuiUtils.drawString(graphics, shadowlessFont, x4, y + 15 + 12 * k, platform, 0xDBDBDB | fontAlpha, EAlignment.LEFT, false); - } - - // page - final int dotSize = 4; - final int dotY = y + 62 - 10; - final int startX = x + GUI_WIDTH / 2 - connectionsSubPagesCount * dotSize - dotSize; - - for (int i = 0; i < connectionsSubPagesCount; i++) { - int s = dotSize + (i == connectionsSubPageIndex ? 2 : 0); - int dX = startX + i * dotSize * 3 - (i == connectionsSubPageIndex ? 1 : 0); - int dY = dotY - (i == connectionsSubPageIndex ? 1 : 0); - GuiUtils.fill(graphics, dX, dY, s, s, i == connectionsSubPageIndex ? 0xFFFFFF | fontAlpha : 0xDBDBDB | fontAlpha); - GuiUtils.fill(graphics, dX + 1, dY + 1, s - 2, s - 2, i == connectionsSubPageIndex ? 0xAAAAAA | fontAlpha : 0x888888 | fontAlpha); - } - - } - - public void renderPageJourneyStart(Graphics graphics, int x, int y, float alphaPercentage, int fontAlpha) { - y += 3 + renderRouteOverview(graphics, getListener().getIndex(), x, y - 3, alphaPercentage, fontAlpha); - GuiUtils.fill(graphics, x + 3, y, SLIDING_TEXT_AREA_WIDTH, 1, 0xDBDBDB | fontAlpha); - - // Title - ModGuiIcons.TIME.render(graphics, x + 10, y + 3); - long time = getListener().currentStation().getEstimatedTimeWithThreshold() - level.getDayTime(); - GuiUtils.drawString(graphics, shadowlessFont, x + 15 + ModGuiIcons.ICON_SIZE, y + 3 + ModGuiIcons.ICON_SIZE / 2 - shadowlessFont.lineHeight / 2, ELanguage.translate(keyDepartureIn).append(" ").append(time > 0 ? TextUtils.text(TimeUtils.parseTime((int)(time % DragonLib.TICKS_PER_DAY), TimeFormat.HOURS_24)) : ELanguage.translate(keyTimeNow)).withStyle(ChatFormatting.BOLD), 0xFFFFFF | fontAlpha, EAlignment.LEFT, false); - y += 5 + ModGuiIcons.ICON_SIZE; - - // Details - final int detailsLineHeight = 12; - //StationEntry station = taggedRoute[0]; - StationEntry endStation = getListener().lastStation(); - - Component platformText = TextUtils.text(endStation.getInfo().platform()); - int platformTextWidth = shadowlessFont.width(platformText); - final int maxStationNameWidth = SLIDING_TEXT_AREA_WIDTH - platformTextWidth - 10 - 5; - MutableComponent stationText = TextUtils.text(TimeUtils.parseTime((int)((endStation.getEstimatedTimeWithThreshold() + DragonLib.DAYTIME_SHIFT) % DragonLib.TICKS_PER_DAY), ModClientConfig.TIME_FORMAT.get())).append(TextUtils.text(" " + endStation.getStationName())); - if (shadowlessFont.width(stationText) > maxStationNameWidth) { - stationText = TextUtils.text(shadowlessFont.substrByWidth(stationText, maxStationNameWidth).getString()).append(TextUtils.text("...")).withStyle(stationText.getStyle()); - } - - ModGuiIcons.TARGET.render(graphics, x + 10, y + shadowlessFont.lineHeight / 2 - ModGuiIcons.ICON_SIZE / 2); - GuiUtils.drawString(graphics, shadowlessFont, x + 15 + ModGuiIcons.ICON_SIZE, y, stationText, 0xDBDBDB | fontAlpha, EAlignment.LEFT, false); - GuiUtils.drawString(graphics, shadowlessFont, x + SLIDING_TEXT_AREA_WIDTH - platformTextWidth, y, platformText, endStation.stationInfoChanged() ? DELAYED | fontAlpha : 0xDBDBDB | fontAlpha, EAlignment.LEFT, false); - ModGuiIcons.INFO.render(graphics, x + 10, y + detailsLineHeight + shadowlessFont.lineHeight / 2 - ModGuiIcons.ICON_SIZE / 2); - GuiUtils.drawString(graphics, shadowlessFont, x + 15 + ModGuiIcons.ICON_SIZE, y + detailsLineHeight, TextUtils.text(String.format("%s %s | %s", - getListener().getListeningRoute().getTransferCount(), - ELanguage.translate(keyTransferCount).getString(), - TimeUtils.parseDurationShort(getListener().getListeningRoute().getTotalDuration()) - )), 0xDBDBDB | fontAlpha, EAlignment.LEFT, false); - } - - public void renderPageTransfer(Graphics graphics, int x, int y, float alphaPercentage, int fontAlpha, StationEntry station) { - y += 3 + renderRouteOverview(graphics, getListener().getIndex(), x, y - 3, alphaPercentage, fontAlpha); - GuiUtils.fill(graphics, x + 3, y, SLIDING_TEXT_AREA_WIDTH, 1, 0xDBDBDB | fontAlpha); - - // Title - ModGuiIcons.WALK.render(graphics, x + 10, y + 3); - long transferTime = getListener().currentStation().getEstimatedTimeWithThreshold() - level.getDayTime();//getListener().getTransferTime(getListener().getIndex()); - GuiUtils.drawString(graphics, shadowlessFont, x + 15 + ModGuiIcons.ICON_SIZE, y + 3 + ModGuiIcons.ICON_SIZE / 2 - shadowlessFont.lineHeight / 2, ELanguage.translate(keyScheduleTransfer).append(" ").append(transferTime > 0 ? TextUtils.text(TimeUtils.parseTime((int)(transferTime % DragonLib.TICKS_PER_DAY), TimeFormat.HOURS_24)) : ELanguage.translate(keyTimeNow)).withStyle(ChatFormatting.BOLD), 0xFFFFFF | fontAlpha, EAlignment.LEFT, false); - y += 5 + ModGuiIcons.ICON_SIZE; - - // Details - this.messageLabel.renderLeftAligned(graphics.poseStack(), x + 15 + ModGuiIcons.ICON_SIZE, y, 12, 0xDBDBDB | fontAlpha); - } - - public void renderPageJourneyInterrupted(Graphics graphics, int x, int y, float alphaPercentage, int fontAlpha) { - // Title - ModGuiIcons.CROSS.render(graphics, x + 10, y + 3); - GuiUtils.drawString(graphics, shadowlessFont, x + 15 + ModGuiIcons.ICON_SIZE, y + 3 + ModGuiIcons.ICON_SIZE / 2 - shadowlessFont.lineHeight / 2, interruptedText.withStyle(ChatFormatting.BOLD), DELAYED | fontAlpha, EAlignment.LEFT, false); - y += 5 + ModGuiIcons.ICON_SIZE; - - // Details - this.messageLabel.renderLeftAligned(graphics.poseStack(), x + 10, y, 10, 0xDBDBDB | fontAlpha); - } - - public void renderPageJourneyCompleted(Graphics graphics, int x, int y, float alphaPercentage, int fontAlpha) { - // Title - ModGuiIcons.CHECK.render(graphics, x + 10, y + 3); - GuiUtils.drawString(graphics, shadowlessFont, x + 15 + ModGuiIcons.ICON_SIZE, y + 3 + ModGuiIcons.ICON_SIZE / 2 - shadowlessFont.lineHeight / 2, TextUtils.translate(keyJourneyCompleted).withStyle(ChatFormatting.BOLD), ON_TIME | fontAlpha, EAlignment.LEFT, false); - y += 5 + ModGuiIcons.ICON_SIZE; - - // Details - this.messageLabel.renderLeftAligned(graphics.poseStack(), x + 10, y, 10, 0xDBDBDB | fontAlpha); - } - //#endregion - - protected static enum Page { - NONE, - ROUTE_OVERVIEW, - NEXT_CONNECTIONS, - JOURNEY_START, - JOURNEY_END, - TRANSFER, - JOURNEY_INTERRUPTED; - } -} diff --git a/common/src/main/java/de/mrjulsen/crn/client/gui/overlay/pages/AbstractRouteDetailsPage.java b/common/src/main/java/de/mrjulsen/crn/client/gui/overlay/pages/AbstractRouteDetailsPage.java new file mode 100644 index 00000000..27b46901 --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/client/gui/overlay/pages/AbstractRouteDetailsPage.java @@ -0,0 +1,20 @@ +package de.mrjulsen.crn.client.gui.overlay.pages; + +import de.mrjulsen.crn.data.navigation.ClientRoute; +import de.mrjulsen.mcdragonlib.client.gui.widgets.DLRenderable; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Font; + +public abstract class AbstractRouteDetailsPage extends DLRenderable { + + protected final Font font = Minecraft.getInstance().font; + protected final ClientRoute route; + + public AbstractRouteDetailsPage(ClientRoute route) { + super(0, 0, 220, 62); + this.route = route; + } + + public abstract boolean isImportant(); + +} diff --git a/common/src/main/java/de/mrjulsen/crn/client/gui/overlay/pages/ConnectionMissedPage.java b/common/src/main/java/de/mrjulsen/crn/client/gui/overlay/pages/ConnectionMissedPage.java new file mode 100644 index 00000000..71d947da --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/client/gui/overlay/pages/ConnectionMissedPage.java @@ -0,0 +1,42 @@ +package de.mrjulsen.crn.client.gui.overlay.pages; + +import de.mrjulsen.crn.Constants; +import de.mrjulsen.crn.client.gui.ModGuiIcons; +import de.mrjulsen.crn.data.navigation.ClientRoute; +import de.mrjulsen.mcdragonlib.client.util.Graphics; +import de.mrjulsen.mcdragonlib.client.util.GuiUtils; +import de.mrjulsen.mcdragonlib.core.EAlignment; +import de.mrjulsen.mcdragonlib.util.TextUtils; +import net.minecraft.ChatFormatting; +import net.minecraft.client.gui.components.MultiLineLabel; + +public class ConnectionMissedPage extends AbstractRouteDetailsPage { + + private static final String keyConnectionMissed = "gui.createrailwaysnavigator.route_overview.connection_missed"; + private static final String keyConnectionMissedPageText = "gui.createrailwaysnavigator.route_overview.journey_interrupted_info"; + + private MultiLineLabel messageLabel; + + public ConnectionMissedPage(ClientRoute route) { + super(route); + this.messageLabel = MultiLineLabel.create(font, TextUtils.translate(keyConnectionMissedPageText, route.getEnd().getClientTag().tagName()), width() - 10); + } + + @Override + public boolean isImportant() { + return false; + } + + @Override + public void renderMainLayer(Graphics graphics, int mouseX, int mouseY, float partialTicks) { + int y = 3; + // Title + ModGuiIcons.CROSS.render(graphics, 5, y); + GuiUtils.drawString(graphics, font, 10 + ModGuiIcons.ICON_SIZE, y + ModGuiIcons.ICON_SIZE / 2 - font.lineHeight / 2, TextUtils.translate(keyConnectionMissed).withStyle(ChatFormatting.BOLD), Constants.COLOR_DELAYED, EAlignment.LEFT, false); + y += 5 + ModGuiIcons.ICON_SIZE; + + // Details + this.messageLabel.renderLeftAligned(graphics.poseStack(), 10, y, font.lineHeight, 0xFFDBDBDB); + } + +} diff --git a/common/src/main/java/de/mrjulsen/crn/client/gui/overlay/pages/JourneyCompletedPage.java b/common/src/main/java/de/mrjulsen/crn/client/gui/overlay/pages/JourneyCompletedPage.java new file mode 100644 index 00000000..41ae88b7 --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/client/gui/overlay/pages/JourneyCompletedPage.java @@ -0,0 +1,58 @@ +package de.mrjulsen.crn.client.gui.overlay.pages; + +import de.mrjulsen.crn.Constants; +import de.mrjulsen.crn.client.gui.ModGuiIcons; +import de.mrjulsen.crn.data.navigation.ClientRoute; +import de.mrjulsen.mcdragonlib.client.util.Graphics; +import de.mrjulsen.mcdragonlib.client.util.GuiUtils; +import de.mrjulsen.mcdragonlib.core.EAlignment; +import de.mrjulsen.mcdragonlib.util.DLUtils; +import de.mrjulsen.mcdragonlib.util.TextUtils; +import net.minecraft.ChatFormatting; +import net.minecraft.client.gui.components.MultiLineLabel; + +public class JourneyCompletedPage extends AbstractRouteDetailsPage { + + private static final String keyAfterJourney = "gui.createrailwaysnavigator.route_overview.after_journey"; + private static final String keyJourneyCompleted = "gui.createrailwaysnavigator.route_overview.journey_completed"; + + private static final int MAX_TICK_TIME = 200; + private int ticks; + private final Runnable after; + + private MultiLineLabel messageLabel; + + public JourneyCompletedPage(ClientRoute route, Runnable after) { + super(route); + this.messageLabel = MultiLineLabel.create(font, TextUtils.translate(keyAfterJourney, route.getEnd().getClientTag().tagName()), width() - 10); + this.after = after; + } + + @Override + public boolean isImportant() { + return false; + } + + @Override + public void tick() { + super.tick(); + ticks++; + + if (ticks == MAX_TICK_TIME) { + DLUtils.doIfNotNull(after, x -> x.run()); + } + } + + @Override + public void renderMainLayer(Graphics graphics, int mouseX, int mouseY, float partialTicks) { + int y = 3; + // Title + ModGuiIcons.CHECK.render(graphics, 5, y); + GuiUtils.drawString(graphics, font, 10 + ModGuiIcons.ICON_SIZE, y + ModGuiIcons.ICON_SIZE / 2 - font.lineHeight / 2, TextUtils.translate(keyJourneyCompleted).withStyle(ChatFormatting.BOLD), Constants.COLOR_ON_TIME, EAlignment.LEFT, false); + y += 5 + ModGuiIcons.ICON_SIZE; + + // Details + this.messageLabel.renderLeftAligned(graphics.poseStack(), 10, y, font.lineHeight, 0xFFDBDBDB); + } + +} diff --git a/common/src/main/java/de/mrjulsen/crn/client/gui/overlay/pages/NextConnectionsPage.java b/common/src/main/java/de/mrjulsen/crn/client/gui/overlay/pages/NextConnectionsPage.java new file mode 100644 index 00000000..3a62722c --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/client/gui/overlay/pages/NextConnectionsPage.java @@ -0,0 +1,108 @@ +package de.mrjulsen.crn.client.gui.overlay.pages; + +import java.util.ArrayList; +import java.util.List; + +import de.mrjulsen.crn.client.lang.ELanguage; +import de.mrjulsen.crn.config.ModClientConfig; +import de.mrjulsen.crn.data.train.TrainStop; +import de.mrjulsen.crn.registry.ModAccessorTypes; +import de.mrjulsen.crn.registry.ModAccessorTypes.DeparturesData; +import de.mrjulsen.crn.data.navigation.ClientRoute; +import de.mrjulsen.mcdragonlib.client.util.Graphics; +import de.mrjulsen.mcdragonlib.client.util.GuiAreaDefinition; +import de.mrjulsen.mcdragonlib.client.util.GuiUtils; +import de.mrjulsen.mcdragonlib.core.EAlignment; +import de.mrjulsen.mcdragonlib.util.DLUtils; +import de.mrjulsen.mcdragonlib.util.TextUtils; +import de.mrjulsen.mcdragonlib.util.TimeUtils; +import de.mrjulsen.mcdragonlib.util.accessor.DataAccessor; +import net.minecraft.ChatFormatting; + +public class NextConnectionsPage extends AbstractRouteDetailsPage { + + private final List nextConnections = new ArrayList<>(); + private static final String keyNextConnections = "gui.createrailwaysnavigator.route_overview.next_connections"; + + private static final int CONNECTION_ENTRIES_PER_PAGE = 3; + private static final int TIME_PER_CONNECTIONS_SUBPAGE = 200; + private int connectionsSubPageTime = 0; + private int connectionsSubPageIndex = 0; + private int connectionsSubPagesCount = 0; + private final Runnable afterFirstCycle; + private int cycles; + + public NextConnectionsPage(ClientRoute route, Runnable afterFirstCycle) { + super(route); + this.afterFirstCycle = afterFirstCycle; + + DataAccessor.getFromServer(new DeparturesData(route.getCurrentPart().getNextStop().getClientTag().tagId(), route.getCurrentPart().getNextStop().getTrainId()), ModAccessorTypes.GET_DEPARTURES_AT, (stops) -> { + if (stops.isEmpty()) { + afterFirstCycle.run(); + return; + } + nextConnections.addAll(stops); + connectionsSubPagesCount = nextConnections.size() / CONNECTION_ENTRIES_PER_PAGE + (nextConnections.size() % CONNECTION_ENTRIES_PER_PAGE == 0 ? 0 : 1); + }); + } + + @Override + public boolean isImportant() { + return false; + } + + public boolean hasConnections() { + return !nextConnections.isEmpty(); + } + + @Override + public void tick() { + super.tick(); + if (nextConnections.isEmpty()) { + return; + } + connectionsSubPageTime++; + if ((connectionsSubPageTime %= TIME_PER_CONNECTIONS_SUBPAGE) == 0) { + connectionsSubPageIndex++; + if ((connectionsSubPageIndex %= connectionsSubPagesCount) == 0) { + cycles++; + if (cycles == 1) { + DLUtils.doIfNotNull(afterFirstCycle, x -> x.run()); + } + } + } + } + + @Override + public void renderMainLayer(Graphics graphics, int mouseX, int mouseY, float partialTicks) { + GuiUtils.drawString(graphics, font, 5, 4, ELanguage.translate(keyNextConnections).withStyle(ChatFormatting.BOLD), 0xFFFFFFFF, EAlignment.LEFT, false); + + int y = 16; + final int spacing = 5; + final int timeWidth = 30; + final int trainNameWidth = 40; + for (int i = connectionsSubPageIndex * CONNECTION_ENTRIES_PER_PAGE; i < (connectionsSubPageIndex + 1) * CONNECTION_ENTRIES_PER_PAGE && i < nextConnections.size(); i++) { + TrainStop stop = nextConnections.get(i); + String terminus = stop.getDisplayTitle(); + GuiUtils.drawString(graphics, font, 5, y, TimeUtils.parseTime(stop.getScheduledDepartureTime(), ModClientConfig.TIME_FORMAT.get()), 0xFFDBDBDB, EAlignment.LEFT, false); + GuiUtils.drawString(graphics, font, 5 + timeWidth + spacing, y, GuiUtils.ellipsisString(font, TextUtils.text(stop.getTrainName()), trainNameWidth), 0xFFDBDBDB, EAlignment.LEFT, false); + GuiUtils.drawString(graphics, font, width() - 5, y, stop.getRealTimeStationTag().info().platform(), 0xFFDBDBDB, EAlignment.RIGHT, false); + int terminusWidth = width() - 10 + timeWidth + trainNameWidth + spacing * 3 - font.width(stop.getRealTimeStationTag().info().platform()); + GuiUtils.drawString(graphics, font, 5 + timeWidth + trainNameWidth + spacing * 2, y, GuiUtils.ellipsisString(font, TextUtils.text(terminus), terminusWidth), 0xFFDBDBDB, EAlignment.LEFT, false); + y += 12; + } + + y = 52; + final int dotSize = 4; + final int center = width() / 2 - dotSize / 2; + final int startX = center - (dotSize * 2) * (connectionsSubPagesCount - 1); + + for (int i = 0; i < connectionsSubPagesCount; i++) { + if (connectionsSubPageIndex == i) { + GuiUtils.drawBox(graphics, new GuiAreaDefinition((startX + (dotSize * 2 * i)) - 1, y - 1, dotSize + 2, dotSize + 2), 0xFFAAAAAA, 0xFFFFFFFF); + } else { + GuiUtils.drawBox(graphics, new GuiAreaDefinition((startX + (dotSize * 2 * i)), y, dotSize, dotSize), 0xFF888888, 0xFFDBDBDB); + } + } + } +} diff --git a/common/src/main/java/de/mrjulsen/crn/client/gui/overlay/pages/RouteOverviewPage.java b/common/src/main/java/de/mrjulsen/crn/client/gui/overlay/pages/RouteOverviewPage.java new file mode 100644 index 00000000..89c2899e --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/client/gui/overlay/pages/RouteOverviewPage.java @@ -0,0 +1,122 @@ +package de.mrjulsen.crn.client.gui.overlay.pages; + +import java.util.Arrays; +import java.util.List; +import java.util.Optional; + +import de.mrjulsen.crn.Constants; +import de.mrjulsen.crn.client.CRNGui; +import de.mrjulsen.crn.client.gui.ModGuiIcons; +import de.mrjulsen.crn.config.ModClientConfig; +import de.mrjulsen.crn.data.train.TrainStop; +import de.mrjulsen.crn.data.navigation.ClientRoute; +import de.mrjulsen.crn.data.navigation.ClientRoutePart; +import de.mrjulsen.crn.data.navigation.TransferConnection; +import de.mrjulsen.mcdragonlib.DragonLib; +import de.mrjulsen.mcdragonlib.client.render.Sprite; +import de.mrjulsen.mcdragonlib.client.util.Graphics; +import de.mrjulsen.mcdragonlib.client.util.GuiUtils; +import de.mrjulsen.mcdragonlib.core.EAlignment; +import de.mrjulsen.mcdragonlib.util.TextUtils; +import de.mrjulsen.mcdragonlib.util.TimeUtils; +import net.minecraft.ChatFormatting; +import net.minecraft.client.gui.Font; +import net.minecraft.network.chat.MutableComponent; + +public class RouteOverviewPage extends AbstractRouteDetailsPage { + + public static final int ENTRY_HEIGHT = 14; + + private static final MutableComponent textTransfer = TextUtils.translate("gui.createrailwaysnavigator.route_overview.schedule_transfer"); + private static final MutableComponent textConnectionEndangered = TextUtils.translate("gui.createrailwaysnavigator.route_overview.connection_endangered"); + private static final MutableComponent textConnectionMissed = TextUtils.translate("gui.createrailwaysnavigator.route_overview.connection_missed"); + + public RouteOverviewPage(ClientRoute route) { + super(route); + } + + @Override + public boolean isImportant() { + return false; + } + + @Override + public void renderMainLayer(Graphics graphics, int mouseX, int mouseY, float partialTicks) { + int y = -2; + int n = 0; + List parts = route.getClientParts().stream().skip(Math.max(route.getCurrentPartIndex(), 0)).toList(); + for (int i = 0; i < parts.size(); i++) { + ClientRoutePart part = parts.get(i); + int toSkip = Math.max(part.getNextStopIndex(), 0); + List stops = part.getAllStops().stream().skip(toSkip).toList(); + for (int k = 0; k < stops.size() && n < 5; k++, n++) { + TrainStop stop = stops.get(k); + renderStation(graphics, y, width(), font, stop, toSkip <= 0 && i > 0 && k == 0 ? RoutePathIcons.TRANSFER_STOP : RoutePathIcons.STOP, stop == part.getFirstStop(), !route.isPartReachable(part)); + y += RoutePathIcons.SPRITE_HEIGHT; + if (i < parts.size() - 1 && k >= stops.size() - 1) { + Optional connection = route.getConnectionWith(stop); + if (connection.isPresent()) { + renderTransfer(graphics, y, width(), font, connection.get()); + } + y += RoutePathIcons.SPRITE_HEIGHT; + } + } + } + } + + public static void renderStation(Graphics graphics, int y, int width, Font font, TrainStop stop, RoutePathIcons icon, boolean isStart, boolean isMissed) { + final int precision = ModClientConfig.REALTIME_PRECISION_THRESHOLD.get(); + GuiUtils.drawString(graphics, font, 7, y + ENTRY_HEIGHT - 2 - font.lineHeight / 2, TextUtils.text(TimeUtils.parseTime((isStart ? stop.getScheduledDepartureTime() : stop.getScheduledArrivalTime()) + DragonLib.DAYTIME_SHIFT, ModClientConfig.TIME_FORMAT.get())).withStyle(isMissed ? ChatFormatting.STRIKETHROUGH : ChatFormatting.RESET), isMissed ? Constants.COLOR_DELAYED : 0xFFDBDBDB, EAlignment.LEFT, false); + if (stop.shouldRenderRealTime() && !isMissed) { + GuiUtils.drawString(graphics, font, 7 + 32, y + ENTRY_HEIGHT - 2 - font.lineHeight / 2, TimeUtils.parseTime((isStart ? stop.getScheduledDepartureTime() + (stop.getDepartureTimeDeviation() / precision * precision) : stop.getScheduledArrivalTime() + (stop.getArrivalTimeDeviation() / precision * precision)) + DragonLib.DAYTIME_SHIFT, ModClientConfig.TIME_FORMAT.get()), stop.isArrivalDelayed() ? Constants.COLOR_DELAYED : Constants.COLOR_ON_TIME, EAlignment.LEFT, false); + } + icon.getAsSprite().render(graphics, 10 + 64, y); + GuiUtils.drawString(graphics, font, 17 + 64 + RoutePathIcons.SPRITE_WIDTH, y + ENTRY_HEIGHT - 2 - font.lineHeight / 2, GuiUtils.ellipsisString(font, TextUtils.text(stop.getClientTag().tagName()), width - (17 + 64 + RoutePathIcons.SPRITE_WIDTH) - font.width(stop.getRealTimeStationTag().info().platform()) - 10), 0xFFDBDBDB, EAlignment.LEFT, false); + GuiUtils.drawString(graphics, font, width - 4, y + ENTRY_HEIGHT - 2 - font.lineHeight / 2, stop.getRealTimeStationTag().info().platform(), stop.isStationInfoChanged() ? Constants.COLOR_DELAYED : 0xFFDBDBDB, EAlignment.RIGHT, false); + + } + + public static void renderTransfer(Graphics graphics, int y, int width, Font font, TransferConnection connection) { + if (connection.isConnectionMissed()) { + ModGuiIcons.CROSS.getAsSprite(16, 16).render(graphics, 5, y + ENTRY_HEIGHT - 2 - ModGuiIcons.ICON_SIZE / 2); + GuiUtils.drawString(graphics, font, 17 + 64 + RoutePathIcons.SPRITE_WIDTH, y + ENTRY_HEIGHT - 2 - font.lineHeight / 2, textConnectionMissed.withStyle(ChatFormatting.BOLD).withStyle(ChatFormatting.RED), 0xFFFFFFFF, EAlignment.LEFT, false); + } else if (connection.isConnectionEndangered()) { + ModGuiIcons.WARN.getAsSprite(16, 16).render(graphics, 5, y + ENTRY_HEIGHT - 2 - ModGuiIcons.ICON_SIZE / 2); + GuiUtils.drawString(graphics, font, 17 + 64 + RoutePathIcons.SPRITE_WIDTH, y + ENTRY_HEIGHT - 2 - font.lineHeight / 2, textConnectionEndangered.withStyle(ChatFormatting.BOLD).withStyle(ChatFormatting.GOLD), 0xFFFFFFFF, EAlignment.LEFT, false); + } else { + GuiUtils.drawString(graphics, font, 7, y + ENTRY_HEIGHT - 2 - font.lineHeight / 2, TextUtils.text(TimeUtils.parseDurationShort((int)connection.getRealTimeTransferTime())).withStyle(ChatFormatting.ITALIC), 0xFFDBDBDB, EAlignment.LEFT, false); + GuiUtils.drawString(graphics, font, 17 + 64 + RoutePathIcons.SPRITE_WIDTH, y + ENTRY_HEIGHT - 2 - font.lineHeight / 2, textTransfer.withStyle(ChatFormatting.ITALIC), 0xFFDBDBDB, EAlignment.LEFT, false); + } + RoutePathIcons.TRANSFER.getAsSprite().render(graphics, 10 + 64, y); + } + + public static enum RoutePathIcons { + CURRENT(0), + STOP(1), + TRANSFER_STOP(2), + TRANSFER(3), + START(4); + + private static final int V = 30; + private static final int START_U = 21; + private static final int SPRITE_WIDTH = 7; + private static final int SPRITE_HEIGHT = 14; + private int index; + + private RoutePathIcons(int index) { + this.index = index; + } + + public int getIndex() { + return index; + } + + public static RoutePathIcons getByIndex(int index) { + return Arrays.stream(values()).filter(x -> x.getIndex() == index).findFirst().orElse(START); + } + + public Sprite getAsSprite() { + return new Sprite(CRNGui.GUI, CRNGui.GUI_WIDTH, CRNGui.GUI_HEIGHT, START_U + getIndex() * SPRITE_WIDTH, V, SPRITE_WIDTH, SPRITE_HEIGHT, SPRITE_WIDTH, SPRITE_HEIGHT, 0, 0); + } + } +} diff --git a/common/src/main/java/de/mrjulsen/crn/client/gui/overlay/pages/TrainCancelledInfo.java b/common/src/main/java/de/mrjulsen/crn/client/gui/overlay/pages/TrainCancelledInfo.java new file mode 100644 index 00000000..e86eb570 --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/client/gui/overlay/pages/TrainCancelledInfo.java @@ -0,0 +1,42 @@ +package de.mrjulsen.crn.client.gui.overlay.pages; + +import de.mrjulsen.crn.Constants; +import de.mrjulsen.crn.client.gui.ModGuiIcons; +import de.mrjulsen.crn.data.navigation.ClientRoute; +import de.mrjulsen.mcdragonlib.client.util.Graphics; +import de.mrjulsen.mcdragonlib.client.util.GuiUtils; +import de.mrjulsen.mcdragonlib.core.EAlignment; +import de.mrjulsen.mcdragonlib.util.TextUtils; +import net.minecraft.ChatFormatting; +import net.minecraft.client.gui.components.MultiLineLabel; + +public class TrainCancelledInfo extends AbstractRouteDetailsPage { + + private static final String keyTrainCancelled = "gui.createrailwaysnavigator.route_overview.train_cancelled_title"; + private static final String keyTrainCancelledInfoText = "gui.createrailwaysnavigator.route_overview.train_cancelled_info"; + + private MultiLineLabel messageLabel; + + public TrainCancelledInfo(ClientRoute route, String trainName) { + super(route); + this.messageLabel = MultiLineLabel.create(font, TextUtils.translate(keyTrainCancelledInfoText, trainName), width() - 10); + } + + @Override + public boolean isImportant() { + return false; + } + + @Override + public void renderMainLayer(Graphics graphics, int mouseX, int mouseY, float partialTicks) { + int y = 3; + // Title + ModGuiIcons.CROSS.render(graphics, 5, y); + GuiUtils.drawString(graphics, font, 10 + ModGuiIcons.ICON_SIZE, y + ModGuiIcons.ICON_SIZE / 2 - font.lineHeight / 2, TextUtils.translate(keyTrainCancelled).withStyle(ChatFormatting.BOLD), Constants.COLOR_DELAYED, EAlignment.LEFT, false); + y += 5 + ModGuiIcons.ICON_SIZE; + + // Details + this.messageLabel.renderLeftAligned(graphics.poseStack(), 10, y, font.lineHeight, 0xFFDBDBDB); + } + +} diff --git a/common/src/main/java/de/mrjulsen/crn/client/gui/overlay/pages/TransferPage.java b/common/src/main/java/de/mrjulsen/crn/client/gui/overlay/pages/TransferPage.java new file mode 100644 index 00000000..3ff0b2ea --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/client/gui/overlay/pages/TransferPage.java @@ -0,0 +1,69 @@ +package de.mrjulsen.crn.client.gui.overlay.pages; + +import de.mrjulsen.crn.client.gui.ModGuiIcons; +import de.mrjulsen.crn.client.gui.overlay.pages.RouteOverviewPage.RoutePathIcons; +import de.mrjulsen.crn.client.lang.ELanguage; +import de.mrjulsen.crn.data.StationTag.StationInfo; +import de.mrjulsen.crn.data.navigation.ClientRoute; +import de.mrjulsen.crn.data.navigation.TransferConnection; +import de.mrjulsen.mcdragonlib.DragonLib; +import de.mrjulsen.mcdragonlib.client.util.Graphics; +import de.mrjulsen.mcdragonlib.client.util.GuiUtils; +import de.mrjulsen.mcdragonlib.core.EAlignment; +import de.mrjulsen.mcdragonlib.util.TextUtils; +import de.mrjulsen.mcdragonlib.util.TimeUtils; +import net.minecraft.ChatFormatting; +import net.minecraft.client.gui.components.MultiLineLabel; + +public class TransferPage extends AbstractRouteDetailsPage { + + private static final String keyScheduleTransfer = "gui.createrailwaysnavigator.route_overview.schedule_transfer"; + private static final String keyTransfer = "gui.createrailwaysnavigator.route_overview.transfer"; + private static final String keyTransferWithPlatform = "gui.createrailwaysnavigator.route_overview.transfer_with_platform"; + private static final String keyTimeNow = "gui.createrailwaysnavigator.time.now"; + + private final TransferConnection connection; + private MultiLineLabel messageLabel; + + public TransferPage(ClientRoute route, TransferConnection connection) { + super(route); + this.connection = connection; + + String terminus = connection.getDepartureStation().getDisplayTitle(); + StationInfo info = connection.getDepartureStation().getRealTimeStationTag().info(); + this.messageLabel = MultiLineLabel.create(font, + info.platform() == null || info.platform().isBlank() ? + ELanguage.translate(keyTransfer, + connection.getDepartureStation().getTrainName(), + terminus + ) : + ELanguage.translate(keyTransferWithPlatform, + connection.getDepartureStation().getTrainName(), + terminus, + info.platform() + ), width - (15 + ModGuiIcons.ICON_SIZE)); + } + + @Override + public boolean isImportant() { + return true; + } + + @Override + public void renderMainLayer(Graphics graphics, int mouseX, int mouseY, float partialTicks) { + int y = 0; + RouteOverviewPage.renderStation(graphics, -4, width(), font, connection.getDepartureStation(), RoutePathIcons.START, true, connection.isConnectionMissed()); + y += 16; + GuiUtils.fill(graphics, 0, y, width(), 1, 0xFFDBDBDB); + + // Title + ModGuiIcons.WALK.render(graphics, 5, y + 3); + long transferTime = connection.getDepartureStation().getRealTimeDepartureTime() - DragonLib.getCurrentWorldTime(); + GuiUtils.drawString(graphics, font, 10 + ModGuiIcons.ICON_SIZE, y + 3 + ModGuiIcons.ICON_SIZE / 2 - font.lineHeight / 2, ELanguage.translate(keyScheduleTransfer).append(" ").append(transferTime > 0 ? TextUtils.text(TimeUtils.parseDurationShort((int)transferTime)) : ELanguage.translate(keyTimeNow)).withStyle(ChatFormatting.BOLD), 0xFFFFFFFF, EAlignment.LEFT, false); + y += 5 + ModGuiIcons.ICON_SIZE; + + // Details + this.messageLabel.renderLeftAligned(graphics.poseStack(), 10 + ModGuiIcons.ICON_SIZE, y, font.lineHeight, 0xFFDBDBDB); + } + +} diff --git a/common/src/main/java/de/mrjulsen/crn/client/gui/overlay/pages/WelcomePage.java b/common/src/main/java/de/mrjulsen/crn/client/gui/overlay/pages/WelcomePage.java new file mode 100644 index 00000000..b70b04ef --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/client/gui/overlay/pages/WelcomePage.java @@ -0,0 +1,69 @@ +package de.mrjulsen.crn.client.gui.overlay.pages; + +import de.mrjulsen.crn.Constants; +import de.mrjulsen.crn.client.gui.ModGuiIcons; +import de.mrjulsen.crn.client.gui.overlay.pages.RouteOverviewPage.RoutePathIcons; +import de.mrjulsen.crn.client.lang.ELanguage; +import de.mrjulsen.crn.data.train.ClientTrainStop; +import de.mrjulsen.crn.util.ModUtils; +import de.mrjulsen.crn.data.navigation.ClientRoute; +import de.mrjulsen.mcdragonlib.client.util.Graphics; +import de.mrjulsen.mcdragonlib.client.util.GuiUtils; +import de.mrjulsen.mcdragonlib.core.EAlignment; +import de.mrjulsen.mcdragonlib.util.TextUtils; +import de.mrjulsen.mcdragonlib.util.TimeUtils; +import net.minecraft.ChatFormatting; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.MutableComponent; + +public class WelcomePage extends AbstractRouteDetailsPage { + + private static final String keyDepartureIn = "gui.createrailwaysnavigator.route_details.departure"; + private static final String keyTimeNow = "gui.createrailwaysnavigator.time.now"; + private static final String keyTransferCount = "gui.createrailwaysnavigator.navigator.route_entry.transfer"; + + public WelcomePage(ClientRoute route) { + super(route); + } + + @Override + public boolean isImportant() { + return true; + } + + @Override + public void renderMainLayer(Graphics graphics, int mouseX, int mouseY, float partialTicks) { + int y = 16; + RouteOverviewPage.renderStation(graphics, -4, width(), font, route.getStart(), RoutePathIcons.START, true, false); + GuiUtils.fill(graphics, 0, y, width(), 1, 0xFFDBDBDB); + + // Title + ModGuiIcons.TIME.render(graphics, 5, y + 3); + long time = route.getCurrentPart().departureIn(); + GuiUtils.drawString(graphics, font, 10 + ModGuiIcons.ICON_SIZE, y + 3 + ModGuiIcons.ICON_SIZE / 2 - font.lineHeight / 2, ELanguage.translate(keyDepartureIn).append(" ").append(time > 0 ? TextUtils.text(TimeUtils.parseDurationShort((int)time)) : ELanguage.translate(keyTimeNow)).withStyle(ChatFormatting.BOLD), 0xFFFFFFFF, EAlignment.LEFT, false); + y += 5 + ModGuiIcons.ICON_SIZE; + + // Details + final int detailsLineHeight = 12; + //StationEntry station = taggedRoute[0]; + ClientTrainStop endStation = route.getLastClientPart().getLastClientStop(); + + Component platformText = TextUtils.text(endStation.getRealTimeStationTag().info().platform()); + int platformTextWidth = font.width(platformText); + final int maxStationNameWidth = width() - platformTextWidth - 10 - 5; + MutableComponent stationText = TextUtils.text(ModUtils.formatTime(endStation.getRoundedRealTimeArrivalTime(), false)).append(TextUtils.text(" " + endStation.getClientTag().tagName())); + if (font.width(stationText) > maxStationNameWidth) { + stationText = TextUtils.text(font.substrByWidth(stationText, maxStationNameWidth).getString()).append(TextUtils.text("...")).withStyle(stationText.getStyle()); + } + + ModGuiIcons.TARGET.render(graphics, 5, y + font.lineHeight / 2 - ModGuiIcons.ICON_SIZE / 2); + GuiUtils.drawString(graphics, font, 10 + ModGuiIcons.ICON_SIZE, y, stationText, 0xFFDBDBDB, EAlignment.LEFT, false); + GuiUtils.drawString(graphics, font, width() - 5, y, platformText, endStation.isStationInfoChanged() ? Constants.COLOR_DELAYED : 0xFFDBDBDB, EAlignment.RIGHT, false); + ModGuiIcons.INFO.render(graphics, 5, y + detailsLineHeight + font.lineHeight / 2 - ModGuiIcons.ICON_SIZE / 2); + GuiUtils.drawString(graphics, font, 10 + ModGuiIcons.ICON_SIZE, y + detailsLineHeight, TextUtils.text(String.format("%s %s | %s", + route.getTransferCount(), + ELanguage.translate(keyTransferCount).getString(), + TimeUtils.parseDurationShort((int)route.travelTime()) + )), 0xFFDBDBDB, EAlignment.LEFT, false); + } +} diff --git a/common/src/main/java/de/mrjulsen/crn/client/gui/screen/AbstractBlacklistScreen.java b/common/src/main/java/de/mrjulsen/crn/client/gui/screen/AbstractBlacklistScreen.java deleted file mode 100644 index 1230317a..00000000 --- a/common/src/main/java/de/mrjulsen/crn/client/gui/screen/AbstractBlacklistScreen.java +++ /dev/null @@ -1,357 +0,0 @@ -package de.mrjulsen.crn.client.gui.screen; - -import java.util.Collection; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; - -import com.simibubi.create.content.trains.station.NoShadowFontWrapper; -import com.simibubi.create.foundation.gui.AllIcons; -import com.simibubi.create.foundation.utility.animation.LerpedFloat; -import com.simibubi.create.foundation.utility.animation.LerpedFloat.Chaser; - -import de.mrjulsen.crn.Constants; -import de.mrjulsen.crn.CreateRailwaysNavigator; -import de.mrjulsen.crn.client.gui.widgets.DLCreateIconButton; -import de.mrjulsen.crn.client.gui.widgets.ModStationSuggestions; -import de.mrjulsen.crn.config.ModClientConfig; -import de.mrjulsen.mcdragonlib.DragonLib; -import de.mrjulsen.mcdragonlib.client.gui.DLScreen; -import de.mrjulsen.mcdragonlib.client.gui.widgets.DLEditBox; -import de.mrjulsen.mcdragonlib.client.gui.widgets.DLTooltip; -import de.mrjulsen.mcdragonlib.client.util.Graphics; -import de.mrjulsen.mcdragonlib.client.util.GuiAreaDefinition; -import de.mrjulsen.mcdragonlib.client.util.GuiUtils; -import de.mrjulsen.mcdragonlib.core.EAlignment; -import de.mrjulsen.mcdragonlib.util.TextUtils; -import de.mrjulsen.mcdragonlib.util.TimeUtils; -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.Font; -import net.minecraft.client.gui.screens.Screen; -import net.minecraft.network.chat.Component; -import net.minecraft.network.chat.MutableComponent; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.util.Mth; -import net.minecraft.world.level.Level; - -public abstract class AbstractBlacklistScreen extends DLScreen { - - private static final ResourceLocation GUI = new ResourceLocation(CreateRailwaysNavigator.MOD_ID, "textures/gui/settings.png"); - public static final ResourceLocation WIDGETS = new ResourceLocation(CreateRailwaysNavigator.MOD_ID, "textures/gui/settings_widgets.png"); - private static final int GUI_WIDTH = 255; - private static final int GUI_HEIGHT = 247; - - private static final int DEFAULT_ICON_BUTTON_WIDTH = 18; - private static final int DEFAULT_ICON_BUTTON_HEIGHT = 18; - private static final int ENTRIES_START_Y_OFFSET = 10; - private static final int ENTRY_HEIGHT = 20; - - private final int AREA_X = 16; - private final int AREA_Y = 54 + 18; - private final int AREA_W = 220; - private final int AREA_H = 156 - 18; - - - private int guiLeft, guiTop; - private LerpedFloat scroll = LerpedFloat.linear().startWithValue(0); - - // Data - private final Level level; - private final Font shadowlessFont; - private final Screen lastScreen; - - private boolean initialized; - - // Controls - private DLCreateIconButton backButton; - private DLEditBox newEntryBox; - private DLEditBox searchBox; - private GuiAreaDefinition addButton; - private ModStationSuggestions suggestions; - private final Map blacklistEntryButton = new HashMap<>(); - private final Map entryAreas = new HashMap<>(); - - // Tooltips - private final MutableComponent tooltipAdd = TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".blacklist.add.tooltip"); - private final MutableComponent tooltipRemove = TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".blacklist.delete.tooltip"); - - @SuppressWarnings("resource") - public AbstractBlacklistScreen(Level level, Screen lastScreen, Component title) { - super(title); - this.level = level; - this.lastScreen = lastScreen; - this.shadowlessFont = new NoShadowFontWrapper(Minecraft.getInstance().font); - } - - protected abstract Collection getSuggestions(); - protected abstract boolean checkIsBlacklisted(String entry); - protected abstract String[] getBlacklistedNames(String searchText); - protected abstract void addToBlacklist(String name, Runnable andThen); - protected abstract void removeFromBlacklist(String name, Runnable andThen); - - @Override - protected void init() { - initialized = false; - super.init(); - guiLeft = this.width / 2 - GUI_WIDTH / 2; - guiTop = this.height / 2 - GUI_HEIGHT / 2; - - backButton = this.addRenderableWidget(new DLCreateIconButton(guiLeft + 21, guiTop + 222, DEFAULT_ICON_BUTTON_WIDTH, DEFAULT_ICON_BUTTON_HEIGHT, AllIcons.I_CONFIG_BACK) { - @Override - public void onClick(double mouseX, double mouseY) { - super.onClick(mouseX, mouseY); - onClose(); - } - }); - addTooltip(DLTooltip.of(Constants.TOOLTIP_GO_BACK).assignedTo(backButton)); - - newEntryBox = addEditBox(guiLeft + AREA_X + 5 + 35, guiTop + AREA_Y - 28 + 10, 129, 12, "", TextUtils.empty(), false, (s) -> { - updateEditorSubwidgets(newEntryBox); - }, (box, focus) -> { - if (!focus) { - clearSuggestions(); - } - }, null); - newEntryBox.setMaxLength(25); - newEntryBox.setTextColor(0xFFFFFF); - - addButton = new GuiAreaDefinition(guiLeft + AREA_X + 165 + 10, guiTop + AREA_Y - 28 + 6, 16, 16); - addTooltip(DLTooltip.of(tooltipAdd).assignedTo(addButton)); - - searchBox = addEditBox(guiLeft + AREA_X + 1, guiTop + 16 + 1, AREA_W - 2, 16, "", Constants.TEXT_SEARCH, true, (s) -> { - initStationDeleteButtons(); - }, null, null); - - initialized = true; - initStationDeleteButtons(); - } - - private void initStationDeleteButtons() { - if (!initialized) { - return; - } - - blacklistEntryButton.clear(); - entryAreas.clear(); - - String[] names = getBlacklistedNames(searchBox.getValue()); - for (int i = 0; i < names.length; i++) { - String name = names[i]; - blacklistEntryButton.put(name, new GuiAreaDefinition(guiLeft + AREA_X + 165 + 10, guiTop + AREA_Y + (i * ENTRY_HEIGHT) + 2, 16, 16)); - entryAreas.put(name, new GuiAreaDefinition(guiLeft + AREA_X + 35, guiTop + AREA_Y + (i * ENTRY_HEIGHT) + 2, 129, 16)); - } - } - - private void addToBlacklistInternal() { - String value = newEntryBox.getValue(); - newEntryBox.setValue(""); - - if (value.isBlank()) { - return; - } - - addToBlacklist(value, this::initStationDeleteButtons); - } - - private int getMaxScrollHeight() { - return ENTRIES_START_Y_OFFSET + ENTRY_HEIGHT * getBlacklistedNames(searchBox.getValue()).length; - } - - private void clearSuggestions() { - if (suggestions != null) { - suggestions.getEditBox().setSuggestion(""); - } - suggestions = null; - } - - protected void updateEditorSubwidgets(DLEditBox field) { - if (!initialized) { - return; - } - - clearSuggestions(); - - suggestions = new ModStationSuggestions(minecraft, this, field, minecraft.font, getViableStations(field), field.getHeight() + 2 + field.y); - suggestions.setAllowSuggestions(true); - suggestions.updateCommandInfo(); - } - - - private List getViableStations(DLEditBox field) { - return getSuggestions().stream() - .distinct() - .filter(x -> !checkIsBlacklisted(x)) - .sorted((a, b) -> a.compareTo(b)) - .toList(); - } - - @Override - public void onClose() { - minecraft.setScreen(lastScreen); - } - - @Override - public void tick() { - super.tick(); - scroll.tickChaser(); - - float scrollMax = getMaxScrollHeight(); - if (scroll.getValue() > 0 && scroll.getValue() > scrollMax) { - scroll.chase(Math.max(0, scrollMax), 0.7f, Chaser.EXP); - } - - if (suggestions != null) { - suggestions.tick(); - - if (!newEntryBox.canConsumeInput()) { - clearSuggestions(); - } - } - } - - @Override - public void renderMainLayer(Graphics graphics, int pMouseX, int pMouseY, float pPartialTick) { - pPartialTick = Minecraft.getInstance().getFrameTime(); - float scrollOffset = -scroll.getValue(pPartialTick); - - renderScreenBackground(graphics); - GuiUtils.drawTexture(GUI, graphics, guiLeft, guiTop, 0, 0, GUI_WIDTH, GUI_HEIGHT); - - GuiUtils.drawTexture(WIDGETS, graphics, guiLeft + AREA_X + 10, guiTop + AREA_Y - 28, 0, 110, 200, 28); - GuiUtils.drawTexture(WIDGETS, graphics, guiLeft + AREA_X + 35, guiTop + AREA_Y - 28 + 5, 0, 92, 139, 18); - GuiUtils.drawTexture(WIDGETS, graphics, addButton.getX(), addButton.getY(), 200, 16, 16, 16); // add button - if (addButton.isInBounds(pMouseX, pMouseY)) { - GuiUtils.fill(graphics, addButton.getX(), addButton.getY(), addButton.getWidth(), addButton.getHeight(), 0x1AFFFFFF); - } - - GuiUtils.enableScissor(graphics, guiLeft + AREA_X, guiTop + AREA_Y, AREA_W, AREA_H); - graphics.poseStack().translate(0, scrollOffset, 0); - - String[] blacklist = getBlacklistedNames(searchBox.getValue()); - for (int i = 0; i < blacklist.length; i++) { - GuiUtils.drawTexture(WIDGETS, graphics, guiLeft + AREA_X + 10, guiTop + AREA_Y + (i * ENTRY_HEIGHT), 0, 4, 200, ENTRY_HEIGHT); - } - GuiUtils.drawTexture(WIDGETS, graphics, guiLeft + AREA_X + 10, guiTop + AREA_Y + (blacklist.length * ENTRY_HEIGHT), 0, 23, 200, 3); - GuiUtils.drawTexture(WIDGETS, graphics, guiLeft + AREA_X + 10, guiTop + AREA_Y + (blacklist.length * ENTRY_HEIGHT) + 3, 0, 46, 200, 2); - - for (GuiAreaDefinition def : blacklistEntryButton.values()) { - GuiUtils.drawTexture(WIDGETS, graphics, def.getX(), def.getY(), 232, 0, 16, 16); // delete button - - if (def.isInBounds(pMouseX, pMouseY - scrollOffset)) { - GuiUtils.fill(graphics, def.getX(), def.getY(), def.getWidth(), def.getHeight(), 0x1AFFFFFF); - } - } - - for (int i = 0; i < blacklist.length; i++) { - MutableComponent name = TextUtils.text(blacklist[i]); - int maxTextWidth = 129; - if (shadowlessFont.width(name) > maxTextWidth) { - name = TextUtils.text(shadowlessFont.substrByWidth(name, maxTextWidth).getString()).append(Constants.ELLIPSIS_STRING); - } - GuiUtils.drawString(graphics, shadowlessFont, guiLeft + AREA_X + 40, guiTop + AREA_Y + (i * ENTRY_HEIGHT) + 6, name, 0xFFFFFF, EAlignment.LEFT, false); - } - - GuiUtils.disableScissor(graphics); - GuiUtils.fillGradient(graphics, guiLeft + AREA_X, guiTop + AREA_Y - 38, 0, AREA_W, 10, 0x77000000, 0x00000000); - GuiUtils.fillGradient(graphics, guiLeft + AREA_X, guiTop + AREA_Y + AREA_H - 10, 0, AREA_W, 10, 0x00000000, 0x77000000); - GuiUtils.fillGradient(graphics, guiLeft + AREA_X + 10, guiTop + AREA_Y, 0, AREA_W - 20, 10, 0x77000000, 0x00000000); - - super.renderMainLayer(graphics, pMouseX, pMouseY, pPartialTick); - - // Scrollbar - double maxHeight = getMaxScrollHeight(); - double aH = AREA_H + 1; - if (aH / maxHeight < 1) { - int scrollerHeight = Math.max(10, (int)(aH * (aH / maxHeight))); - int startY = guiTop + AREA_Y + (int)((AREA_H) * (Math.abs(scrollOffset) / maxHeight)); - - GuiUtils.fill(graphics, guiLeft + AREA_X + AREA_W - 3, startY, 3, scrollerHeight, 0x7FFFFFFF); - } - - GuiUtils.drawString(graphics, shadowlessFont, guiLeft + 19, guiTop + 4, title, 0xFF4F4F4F, EAlignment.LEFT, false); - String timeString = TimeUtils.parseTime((int)((level.getDayTime() + DragonLib.DAYTIME_SHIFT) % DragonLib.TICKS_PER_DAY), ModClientConfig.TIME_FORMAT.get()); - GuiUtils.drawString(graphics, shadowlessFont, guiLeft + GUI_WIDTH - 22 - shadowlessFont.width(timeString), guiTop + 4, TextUtils.text(timeString), 0xFF4F4F4F, EAlignment.LEFT, false); - } - - - @Override - public void renderFrontLayer(Graphics graphics, int mouseX, int mouseY, float partialTick) { - if (suggestions != null) { - graphics.poseStack().pushPose(); - graphics.poseStack().translate(0, 0, 500); - suggestions.render(graphics.poseStack(), mouseX, mouseY); - graphics.poseStack().popPose(); - } - - float scrollOffset = scroll.getValue(partialTick); - - if (mouseX > guiLeft + AREA_X && mouseX < guiLeft + AREA_X + AREA_W && mouseY > guiTop + AREA_Y && mouseY < guiTop + AREA_Y + AREA_H) { - for (Entry entry : blacklistEntryButton.entrySet()) { - if (GuiUtils.renderTooltipWithOffset(this, entry.getValue(), List.of(tooltipRemove), width, graphics, mouseX, mouseY, 0, (int)scrollOffset)) { - break; - } - } - - for (Entry entry : entryAreas.entrySet()) { - if (shadowlessFont.width(entry.getKey()) > 129 && GuiUtils.renderTooltipAt(this, entry.getValue(), List.of(TextUtils.text(entry.getKey())), width, graphics, entry.getValue().getLeft() + 1, (int)(entry.getValue().getTop() - scrollOffset), mouseX, mouseY, 0, (int)scrollOffset)) { - break; - } - } - } - super.renderFrontLayer(graphics, mouseX, mouseY, partialTick); - } - - @Override - public boolean mouseClicked(double pMouseX, double pMouseY, int pButton) { - - if (suggestions != null && suggestions.mouseClicked((int) pMouseX, (int) pMouseY, pButton)) { - GuiUtils.playButtonSound(); - return super.mouseClicked(pMouseX, pMouseY, pButton); - } - - float scrollOffset = scroll.getValue(); - - if (addButton.isInBounds(pMouseX, pMouseY) && !newEntryBox.getValue().isBlank()) { - addToBlacklistInternal(); - GuiUtils.playButtonSound(); - return super.mouseClicked(pMouseX, pMouseY, pButton); - } else if (pMouseX > guiLeft + AREA_X && pMouseX < guiLeft + AREA_X + AREA_W && pMouseY > guiTop + AREA_Y && pMouseY < guiTop + AREA_Y + AREA_H && blacklistEntryButton.values().stream().anyMatch(x -> x.isInBounds(pMouseX, pMouseY + scrollOffset))) { - for (Entry entry : blacklistEntryButton.entrySet()) { - if (entry.getValue().isInBounds(pMouseX, pMouseY + scrollOffset)) { - removeFromBlacklist(entry.getKey(), this::initStationDeleteButtons); - GuiUtils.playButtonSound(); - return super.mouseClicked(pMouseX, pMouseY + scrollOffset, pButton); - } - } - } - - return super.mouseClicked(pMouseX, pMouseY, pButton); - } - - @Override - public boolean keyPressed(int pKeyCode, int pScanCode, int pModifiers) { - if (suggestions != null && suggestions.keyPressed(pKeyCode, pScanCode, pModifiers)) - return true; - - return super.keyPressed(pKeyCode, pScanCode, pModifiers); - } - - @Override - public boolean mouseScrolled(double pMouseX, double pMouseY, double pDelta) { - if (suggestions != null && suggestions.mouseScrolled(pMouseX, pMouseY, Mth.clamp(pDelta, -1.0D, 1.0D))) - return true; - - float chaseTarget = scroll.getChaseTarget(); - float max = -AREA_H + getMaxScrollHeight(); - - if (max > 0) { - chaseTarget -= pDelta * 12; - chaseTarget = Mth.clamp(chaseTarget, 0, max); - scroll.chase((int) chaseTarget, 0.7f, Chaser.EXP); - } else - scroll.chase(0, 0.7f, Chaser.EXP); - - return super.mouseScrolled(pMouseX, pMouseY, pDelta); - } -} diff --git a/common/src/main/java/de/mrjulsen/crn/client/gui/screen/AbstractEntryListSettingsScreen.java b/common/src/main/java/de/mrjulsen/crn/client/gui/screen/AbstractEntryListSettingsScreen.java deleted file mode 100644 index 343f763f..00000000 --- a/common/src/main/java/de/mrjulsen/crn/client/gui/screen/AbstractEntryListSettingsScreen.java +++ /dev/null @@ -1,355 +0,0 @@ -package de.mrjulsen.crn.client.gui.screen; - -import java.util.ArrayList; -import java.util.List; - -import com.simibubi.create.content.trains.station.NoShadowFontWrapper; -import com.simibubi.create.foundation.gui.AllIcons; -import com.simibubi.create.foundation.utility.animation.LerpedFloat; -import com.simibubi.create.foundation.utility.animation.LerpedFloat.Chaser; - -import de.mrjulsen.crn.Constants; -import de.mrjulsen.crn.CreateRailwaysNavigator; -import de.mrjulsen.crn.client.gui.widgets.AbstractEntryListOptionWidget; -import de.mrjulsen.crn.client.gui.widgets.DLCreateIconButton; -import de.mrjulsen.crn.client.gui.widgets.WidgetContainerCollection; -import de.mrjulsen.crn.config.ModClientConfig; -import de.mrjulsen.mcdragonlib.DragonLib; -import de.mrjulsen.mcdragonlib.client.gui.DLScreen; -import de.mrjulsen.mcdragonlib.client.gui.widgets.DLEditBox; -import de.mrjulsen.mcdragonlib.client.gui.widgets.DLTooltip; -import de.mrjulsen.mcdragonlib.client.gui.widgets.WidgetContainer; -import de.mrjulsen.mcdragonlib.client.util.Graphics; -import de.mrjulsen.mcdragonlib.client.util.GuiUtils; -import de.mrjulsen.mcdragonlib.client.util.WidgetsCollection; -import de.mrjulsen.mcdragonlib.core.EAlignment; -import de.mrjulsen.mcdragonlib.util.MathUtils; -import de.mrjulsen.mcdragonlib.util.TextUtils; -import de.mrjulsen.mcdragonlib.util.TimeUtils; -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.Font; -import net.minecraft.client.gui.screens.Screen; -import net.minecraft.network.chat.Component; -import net.minecraft.network.chat.MutableComponent; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.level.Level; - -public abstract class AbstractEntryListSettingsScreen extends DLScreen { - - private static final ResourceLocation GUI = new ResourceLocation(CreateRailwaysNavigator.MOD_ID, "textures/gui/settings.png"); - private static final int GUI_WIDTH = 255; - private static final int GUI_HEIGHT = 247; - - private static final int DEFAULT_ICON_BUTTON_WIDTH = 18; - private static final int DEFAULT_ICON_BUTTON_HEIGHT = 18; - - private static final int ENTRIES_START_Y_OFFSET = 10; - private final int ENTRY_SPACING = 4; - - private final int AREA_X = 16; - private final int AREA_Y = 16 + 18; // + searchbar height - private final int AREA_W = 220; - private final int AREA_H = 194 - 18; // + searchbar height - - private int guiLeft, guiTop; - - private boolean initialized = false; - - // Data - private final Level level; - private final Font shadowlessFont; - private final Screen lastScreen; - private boolean initEntries = false; - private boolean renderingEntries = false; - - // Widgets - private final WidgetContainerCollection entriesCollection = new WidgetContainerCollection(); - private final WidgetsCollection newEntryCollection = new WidgetsCollection(); - private DLEditBox newEntryInputBox; - private DLCreateIconButton backButton; - private DLCreateIconButton addButton; - private DLEditBox searchBox; - private LerpedFloat scroll = LerpedFloat.linear().startWithValue(0); - - // Tooltips - private final MutableComponent tooltipAdd = TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".alias_settings.add.tooltip"); - private final MutableComponent textEnterName = TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".alias_settings.enter_name"); - - public static record WidgetCreationData>(W parent, int x, int y, List previousEntries) {} - - @SuppressWarnings("resource") - public AbstractEntryListSettingsScreen(Level level, Screen lastScreen, Component title) { - super(title); - this.level = level; - this.lastScreen = lastScreen; - this.shadowlessFont = new NoShadowFontWrapper(Minecraft.getInstance().font); - } - - protected abstract D[] getData(String searchText); - protected abstract W createWidget(WidgetCreationData> widgetData, D data); - - @Override - protected void init() { - initialized = false; - super.init(); - guiLeft = this.width / 2 - GUI_WIDTH / 2; - guiTop = this.height / 2 - GUI_HEIGHT / 2; - - - backButton = this.addRenderableWidget(new DLCreateIconButton(guiLeft + 21, guiTop + 222, DEFAULT_ICON_BUTTON_WIDTH, DEFAULT_ICON_BUTTON_HEIGHT, AllIcons.I_CONFIG_BACK) - .withCallback((x, y) -> { - onClose(); - })); - addTooltip(DLTooltip.of(Constants.TOOLTIP_GO_BACK).assignedTo(backButton)); - - addButton = this.addRenderableWidget(new DLCreateIconButton(guiLeft + 43, guiTop + 222, DEFAULT_ICON_BUTTON_WIDTH, DEFAULT_ICON_BUTTON_HEIGHT, AllIcons.I_ADD)); - addButton.withCallback((x, y) -> { - addButton.visible = false; - newEntryCollection.setVisible(true); - newEntryInputBox.setValue(""); - }); - addTooltip(DLTooltip.of(tooltipAdd).assignedTo(addButton)); - - searchBox = addEditBox(guiLeft + AREA_X + 1, guiTop + 16 + 1, AREA_W - 2, 16, "", Constants.TEXT_SEARCH, true, (b) -> { - refreshEntries(); - }, NO_EDIT_BOX_FOCUS_CHANGE_ACTION, null); - - // Add new Entry - newEntryCollection.add(addRenderableWidget(new DLCreateIconButton(guiLeft + 145, guiTop + 222, DEFAULT_ICON_BUTTON_WIDTH, DEFAULT_ICON_BUTTON_HEIGHT, AllIcons.I_CONFIRM)) - .withCallback((x, y) -> { - if (newEntryInputBox.getValue().isBlank()) { - return; - } - - onCreateNewEntry(newEntryInputBox.getValue(), () -> { - refreshEntries(); - scroll.chase(getScrollMax(), 0.7f, Chaser.EXP); - }); - - cancelNewEntryCreation(null); - })); - - newEntryCollection.add(addRenderableWidget(new DLCreateIconButton(guiLeft + 145 + DEFAULT_ICON_BUTTON_WIDTH, guiTop + 222, DEFAULT_ICON_BUTTON_WIDTH, DEFAULT_ICON_BUTTON_HEIGHT, AllIcons.I_DISABLE)) - .withCallback((x, y) -> { - cancelNewEntryCreation(null); - })); - - newEntryCollection.add(newEntryInputBox = addEditBox(guiLeft + 44, guiTop + 223, 100, 16, "", textEnterName, true, (b) -> {}, (box, focus) -> {}, null)); - - initialized = true; - newEntryCollection.setVisible(false); - refreshEntries(); - } - - protected abstract void onCreateNewEntry(String value, Runnable refreshAction); - - private boolean cancelFocus = false; - private void cancelNewEntryCreation(DLEditBox excluded) { - if (cancelFocus) { - return; - } - cancelFocus = true; - addButton.visible = true; - newEntryCollection.setVisible(false); - //renderables.stream().filter(x -> x instanceof DLEditBox && excluded != x).forEach(x -> ((DLEditBox)x).setFocus(false)); - cancelFocus = false; - } - - protected void refreshEntries() { - if (!initialized) { - return; - } - initEntries = true; - - while (renderingEntries) { - try { - Thread.sleep(1); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - - int startY = guiTop + AREA_X + ENTRIES_START_Y_OFFSET; - List previousComponents = new ArrayList<>(entriesCollection.components); - entriesCollection.components.forEach(x -> removeWidget(x)); - entriesCollection.components.clear(); - - // Entries - D[] data = getData(searchBox.getValue()); - for (int i = 0; i < data.length; i++) { - W w = createWidget(new WidgetCreationData>(this, guiLeft + AREA_X + 10, startY, previousComponents), data[i]); - entriesCollection.components.add(w); - w.calcHeight(); - } - initEntries = false; - } - - @Override - public void onClose() { - minecraft.setScreen(lastScreen); - } - - @Override - public void tick() { - searchBox.tick(); - scroll.tickChaser(); - - float scrollMax = getScrollMax(); - if (scroll.getValue() > 0 && scroll.getValue() > scrollMax) { - scroll.chase(Math.max(0, getScrollMax()), 0.7f, Chaser.EXP); - } - - entriesCollection.performForEachOfType(AbstractEntryListOptionWidget.class, x -> x.tick()); - newEntryCollection.performForEachOfType(DLEditBox.class, x -> x.tick()); - } - - public int getScrollOffset(float pPartialTicks) { - return (int)scroll.getValue(pPartialTicks); - } - - private float getScrollMax() { - float max = -AREA_H + (ENTRIES_START_Y_OFFSET * 2); - for (WidgetContainer w : entriesCollection.components) { - max += 4 + w.getHeight(); - } - return max; - } - - @Override - public void renderMainLayer(Graphics graphics, int pMouseX, int pMouseY, float pPartialTick) { - pPartialTick = Minecraft.getInstance().getFrameTime(); - float scrollOffset = -scroll.getValue(pPartialTick); - - renderScreenBackground(graphics); - GuiUtils.drawTexture(GUI, graphics, guiLeft, guiTop, 0, 0, GUI_WIDTH, GUI_HEIGHT); - GuiUtils.drawString(graphics, shadowlessFont, guiLeft + 19, guiTop + 4, title, 0x4F4F4F, EAlignment.LEFT, false); - String timeString = TimeUtils.parseTime((int)((level.getDayTime() + DragonLib.DAYTIME_SHIFT) % DragonLib.TICKS_PER_DAY), ModClientConfig.TIME_FORMAT.get()); - GuiUtils.drawString(graphics, shadowlessFont, guiLeft + GUI_WIDTH - 22 - shadowlessFont.width(timeString), guiTop + 4, TextUtils.text(timeString), 0x4F4F4F, EAlignment.LEFT, false); - - GuiUtils.enableScissor(graphics, guiLeft + AREA_X, guiTop + AREA_Y, AREA_W, AREA_H); - graphics.poseStack().translate(0, scrollOffset, 0); - - renderingEntries = true; - while (initEntries) { - try { - Thread.sleep(1); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - - // render entries - int currentY = guiTop + AREA_Y + ENTRIES_START_Y_OFFSET; - for (int i = 0; i < entriesCollection.components.size(); i++) { - - if (entriesCollection.components.get(i) instanceof AbstractEntryListOptionWidget widget) { - widget.setYPos(currentY); - widget.calcHeight(); - if (currentY < guiTop + AREA_Y + AREA_H - scrollOffset && currentY + widget.getHeight() > guiTop + AREA_Y - scrollOffset) { - widget.render(graphics.poseStack(), pMouseX, (int)(pMouseY - scrollOffset), pPartialTick); - } - currentY += widget.getHeight() + ENTRY_SPACING; - } - - } - - //super.renderMainLayer(graphics, pMouseX, pMouseY, pPartialTick); - renderingEntries = false; - - GuiUtils.disableScissor(graphics); - GuiUtils.fillGradient(graphics, guiLeft + AREA_X, guiTop + AREA_Y, 0, AREA_W, 10, 0x77000000, 0x00000000); - GuiUtils.fillGradient(graphics, guiLeft + AREA_X, guiTop + AREA_Y + AREA_H - 10, 0, AREA_W, 10, 0x00000000, 0x77000000); - - // Scrollbar - int componentHeight = entriesCollection.components.stream().mapToInt(x -> x.getHeight() + ENTRY_SPACING).sum(); - double maxHeight = ENTRIES_START_Y_OFFSET * 2 + componentHeight; - double aH = AREA_H + 1; - if (aH / maxHeight < 1) { - int scrollerHeight = Math.max(10, (int)(aH * (aH / maxHeight))); - int startY = guiTop + AREA_Y + (int)((AREA_H) * (Math.abs(scrollOffset) / maxHeight)); - - GuiUtils.fill(graphics, guiLeft + AREA_X + AREA_W - 3, startY, 3, scrollerHeight, 0x7FFFFFFF); - } - - super.renderMainLayer(graphics, pMouseX, pMouseY, pPartialTick); - } - - @Override - public void renderFrontLayer(Graphics graphics, int pMouseX, int pMouseY, float pPartialTicks) { - entriesCollection.performForEachOfType(AbstractEntryListOptionWidget.class, x -> { - if (pMouseX > guiLeft + AREA_X && pMouseX < guiLeft + AREA_X + AREA_W && pMouseY > guiTop + AREA_Y && pMouseY < guiTop + AREA_Y + AREA_H) { - x.renderFrontLayer(graphics, pMouseX, pMouseY, pPartialTicks); - } - x.renderSuggestions(graphics.poseStack(), pMouseX, pMouseY, pPartialTicks); - }); - - super.renderFrontLayer(graphics, pMouseX, pMouseY, pPartialTicks); - } - - @Override - public boolean mouseClicked(double pMouseX, double pMouseY, int pButton) { - float scrollOffset = scroll.getValue(); - for (WidgetContainer w : entriesCollection.components) { - if (pMouseX > guiLeft + AREA_X && pMouseX < guiLeft + AREA_X + AREA_W && pMouseY > guiTop + AREA_Y && pMouseY < guiTop + AREA_Y + AREA_H) { - if (w.mouseClicked(pMouseX, pMouseY + scrollOffset, pButton)) { - break; - } - } - - if (w instanceof AbstractEntryListOptionWidget entry && entry.mouseClickedLoop(pMouseX, pMouseY + scrollOffset, pButton)) { - break; - } - } - - return super.mouseClicked(pMouseX, pMouseY, pButton); - } - - @Override - public boolean mouseScrolled(double pMouseX, double pMouseY, double pDelta) { - float scrollOffset = scroll.getValue(); - - for (WidgetContainer w : entriesCollection.components) { - if (w instanceof AbstractEntryListOptionWidget entry && entry.mouseScrolledLoop(pMouseX, pMouseY + scrollOffset, pDelta)) { - return true; - } - - if (pMouseX > guiLeft + AREA_X && pMouseX < guiLeft + AREA_X + AREA_W && pMouseY > guiTop + AREA_Y && pMouseY < guiTop + AREA_Y + AREA_H) { - if (w.mouseScrolled(pMouseX, pMouseY + scrollOffset, pDelta)) { - return true; - } - } - } - - float chaseTarget = scroll.getChaseTarget(); - float max = getScrollMax(); - - if (max > 0) { - chaseTarget -= pDelta * 12; - chaseTarget = MathUtils.clamp(chaseTarget, 0, max); - scroll.chase((int) chaseTarget, 0.7f, Chaser.EXP); - } else - scroll.chase(0, 0.7f, Chaser.EXP); - - return super.mouseScrolled(pMouseX, pMouseY, pDelta); - } - - @Override - public boolean keyPressed(int pKeyCode, int pScanCode, int pModifiers) { - for (WidgetContainer w : entriesCollection.components) { - if (w.keyPressed(pKeyCode, pScanCode, pModifiers)) { - return true; - } - } - return super.keyPressed(pKeyCode, pScanCode, pModifiers); - } - - @Override - public boolean charTyped(char pCodePoint, int pModifiers) { - for (WidgetContainer w : entriesCollection.components) { - if (w.charTyped(pCodePoint, pModifiers)) { - return true; - } - } - return super.charTyped(pCodePoint, pModifiers); - } -} diff --git a/common/src/main/java/de/mrjulsen/crn/client/gui/screen/AbstractNavigatorScreen.java b/common/src/main/java/de/mrjulsen/crn/client/gui/screen/AbstractNavigatorScreen.java new file mode 100644 index 00000000..b94e1931 --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/client/gui/screen/AbstractNavigatorScreen.java @@ -0,0 +1,68 @@ +package de.mrjulsen.crn.client.gui.screen; + +import com.simibubi.create.foundation.gui.AllIcons; + +import de.mrjulsen.crn.Constants; +import de.mrjulsen.crn.client.gui.CreateDynamicWidgets; +import de.mrjulsen.crn.client.gui.CreateDynamicWidgets.BarColor; +import de.mrjulsen.crn.client.gui.CreateDynamicWidgets.ContainerColor; +import de.mrjulsen.crn.client.gui.CreateDynamicWidgets.FooterSize; +import de.mrjulsen.crn.client.gui.widgets.DLCreateIconButton; +import de.mrjulsen.crn.config.ModClientConfig; +import de.mrjulsen.mcdragonlib.DragonLib; +import de.mrjulsen.mcdragonlib.client.gui.DLScreen; +import de.mrjulsen.mcdragonlib.client.gui.widgets.DLIconButton; +import de.mrjulsen.mcdragonlib.client.gui.widgets.DLTooltip; +import de.mrjulsen.mcdragonlib.client.util.Graphics; +import de.mrjulsen.mcdragonlib.client.util.GuiUtils; +import de.mrjulsen.mcdragonlib.core.EAlignment; +import de.mrjulsen.mcdragonlib.util.TextUtils; +import de.mrjulsen.mcdragonlib.util.TimeUtils; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.network.chat.Component; + +public abstract class AbstractNavigatorScreen extends DLScreen { + + protected static final int GUI_WIDTH = 240;//255; + protected static final int GUI_HEIGHT = 247; + + protected BarColor primaryColoring; + protected int guiLeft, guiTop; + + protected DLCreateIconButton backButton; + + protected final Screen lastScreen; + + protected AbstractNavigatorScreen(Screen lastScreen, Component title, BarColor primaryColoring) { + super(title); + this.lastScreen = lastScreen; + this.primaryColoring = primaryColoring; + } + + @Override + protected void init() { + super.init(); + guiLeft = this.width / 2 - GUI_WIDTH / 2; + guiTop = this.height / 2 - GUI_HEIGHT / 2; + + backButton = this.addRenderableWidget(new DLCreateIconButton(guiLeft + 8, guiTop + 223, DLIconButton.DEFAULT_BUTTON_WIDTH, DLIconButton.DEFAULT_BUTTON_HEIGHT, AllIcons.I_CONFIG_BACK) { + @Override + public void onClick(double mouseX, double mouseY) { + super.onClick(mouseX, mouseY); + onClose(); + } + }); + addTooltip(DLTooltip.of(Constants.TOOLTIP_GO_BACK).assignedTo(backButton)); + } + + @SuppressWarnings("resource") + public void renderNavigatorBackground(Graphics graphics, int mouseX, int mouseY, float partialTicks) { + renderScreenBackground(graphics); + CreateDynamicWidgets.renderWindow(graphics, guiLeft, guiTop, GUI_WIDTH, GUI_HEIGHT, ContainerColor.GRAY, primaryColoring, FooterSize.DEFAULT.size(), FooterSize.SMALL.size(), false); + GuiUtils.drawString(graphics, font, guiLeft + 6, guiTop + 4, getTitle(), 0x4F4F4F, EAlignment.LEFT, false); + String timeString = TimeUtils.parseTime((int)((Minecraft.getInstance().level.getDayTime() + DragonLib.DAYTIME_SHIFT) % DragonLib.TICKS_PER_DAY), ModClientConfig.TIME_FORMAT.get()); + GuiUtils.drawString(graphics, font, guiLeft + GUI_WIDTH - 6, guiTop + 4, TextUtils.text(timeString), 0x4F4F4F, EAlignment.RIGHT, false); + } + +} diff --git a/common/src/main/java/de/mrjulsen/crn/client/gui/screen/AdvancedDisplaySettingsScreen.java b/common/src/main/java/de/mrjulsen/crn/client/gui/screen/AdvancedDisplaySettingsScreen.java index e4fbd1e4..7dc93f06 100644 --- a/common/src/main/java/de/mrjulsen/crn/client/gui/screen/AdvancedDisplaySettingsScreen.java +++ b/common/src/main/java/de/mrjulsen/crn/client/gui/screen/AdvancedDisplaySettingsScreen.java @@ -11,19 +11,19 @@ import com.simibubi.create.foundation.gui.widget.ScrollInput; import com.simibubi.create.foundation.utility.Components; +import de.mrjulsen.crn.Constants; import de.mrjulsen.crn.CreateRailwaysNavigator; import de.mrjulsen.crn.block.AbstractAdvancedSidedDisplayBlock; -import de.mrjulsen.crn.block.be.AdvancedDisplayBlockEntity; +import de.mrjulsen.crn.block.blockentity.AdvancedDisplayBlockEntity; +import de.mrjulsen.crn.block.properties.EDisplayType; +import de.mrjulsen.crn.block.properties.ESide; +import de.mrjulsen.crn.client.AdvancedDisplaysRegistry; +import de.mrjulsen.crn.client.AdvancedDisplaysRegistry.DisplayTypeResourceKey; import de.mrjulsen.crn.client.gui.ModGuiIcons; import de.mrjulsen.crn.client.gui.widgets.DLCreateIconButton; import de.mrjulsen.crn.client.gui.widgets.DLCreateLabel; import de.mrjulsen.crn.client.gui.widgets.DLCreateSelectionScrollInput; import de.mrjulsen.crn.config.ModCommonConfig; -import de.mrjulsen.crn.data.ClientTrainStationSnapshot; -import de.mrjulsen.crn.data.EDisplayInfo; -import de.mrjulsen.crn.data.EDisplayType; -import de.mrjulsen.crn.data.ESide; -import de.mrjulsen.crn.data.GlobalSettingsManager; import de.mrjulsen.crn.network.packets.cts.AdvancedDisplayUpdatePacket; import de.mrjulsen.mcdragonlib.DragonLib; import de.mrjulsen.mcdragonlib.client.gui.DLScreen; @@ -32,7 +32,9 @@ import de.mrjulsen.mcdragonlib.client.util.Graphics; import de.mrjulsen.mcdragonlib.client.util.GuiUtils; import de.mrjulsen.mcdragonlib.core.EAlignment; +import de.mrjulsen.mcdragonlib.data.Cache; import de.mrjulsen.mcdragonlib.util.TextUtils; +import net.minecraft.Util; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.Font; import net.minecraft.client.gui.components.Widget; @@ -40,6 +42,7 @@ import net.minecraft.core.BlockPos; import net.minecraft.network.chat.Component; import net.minecraft.network.chat.MutableComponent; +import net.minecraft.network.chat.Style; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.Level; @@ -59,7 +62,7 @@ public class AdvancedDisplaySettingsScreen extends DLScreen { // Settings private final Level level; private final BlockPos pos; - private EDisplayInfo info; + private DisplayTypeResourceKey typeKey; private EDisplayType type; private final boolean canBeDoubleSided; private boolean doubleSided; @@ -72,14 +75,14 @@ public class AdvancedDisplaySettingsScreen extends DLScreen { private DLCreateIconButton globalSettingsButton; private final MutableComponent tooltipGlobalSettings = TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".navigator.global_settings.tooltip"); private final MutableComponent tooltipDisplayType = TextUtils.translate("gui.createrailwaysnavigator.advanced_display_settings.display_type"); - private final MutableComponent tooltipDisplayTypeDescription = TextUtils.translate("gui.createrailwaysnavigator.advanced_display_settings.display_type.description"); private final MutableComponent tooltipInfoType = TextUtils.translate("gui.createrailwaysnavigator.advanced_display_settings.info_type"); - private final MutableComponent tooltipInfoTypeDescription = TextUtils.translate("gui.createrailwaysnavigator.advanced_display_settings.info_type.description"); private final MutableComponent textDoubleSided = TextUtils.translate("gui.createrailwaysnavigator.advanced_display_settings.double_sided"); private int guiLeft, guiTop; private DLCreateIconButton backButton; + + private final Cache> displayTypes = new Cache<>(() -> AdvancedDisplaysRegistry.getAllOfTypeAsKey(type)); @SuppressWarnings("resource") public AdvancedDisplaySettingsScreen(AdvancedDisplayBlockEntity blockEntity) { @@ -87,8 +90,8 @@ public AdvancedDisplaySettingsScreen(AdvancedDisplayBlockEntity blockEntity) { this.shadowlessFont = new NoShadowFontWrapper(Minecraft.getInstance().font); this.pos = blockEntity.getBlockPos(); this.level = blockEntity.getLevel(); - this.info = blockEntity.getInfoType(); - this.type = blockEntity.getDisplayType(); + this.type = blockEntity.getDisplayTypeKey().category(); + this.typeKey = blockEntity.getDisplayTypeKey(); this.renderedItem = new ItemStack(blockEntity.getBlockState().getBlock()); this.canBeDoubleSided = blockEntity.getBlockState().getBlock() instanceof AbstractAdvancedSidedDisplayBlock; this.doubleSided = !canBeDoubleSided || blockEntity.getBlockState().getValue(AbstractAdvancedSidedDisplayBlock.SIDE) == ESide.BOTH; @@ -96,7 +99,7 @@ public AdvancedDisplaySettingsScreen(AdvancedDisplayBlockEntity blockEntity) { @Override public void onClose() { - CreateRailwaysNavigator.net().CHANNEL.sendToServer(new AdvancedDisplayUpdatePacket(level, pos, type, info, doubleSided)); + CreateRailwaysNavigator.net().CHANNEL.sendToServer(new AdvancedDisplayUpdatePacket(level, pos, typeKey, doubleSided)); super.onClose(); } @@ -110,6 +113,15 @@ protected void init() { backButton.withCallback(() -> { onClose(); }); + + DLCreateIconButton helpButton = this.addRenderableWidget(new DLCreateIconButton(guiLeft + 179 - DEFAULT_ICON_BUTTON_WIDTH - 10, guiTop + 99, DEFAULT_ICON_BUTTON_WIDTH, DEFAULT_ICON_BUTTON_HEIGHT, ModGuiIcons.HELP.getAsCreateIcon()) { + @Override + public void onClick(double mouseX, double mouseY) { + super.onClick(mouseX, mouseY); + Util.getPlatform().openUri(Constants.HELP_PAGE_ADVANCED_DISPLAYS); + } + }); + addTooltip(DLTooltip.of(Constants.TEXT_HELP).assignedTo(helpButton)); displayTypeLabel = addRenderableWidget(new DLCreateLabel(guiLeft + 45 + 5, guiTop + 23 + 5, Components.immutableEmpty()).withShadow()); displayTypeInput = addRenderableWidget(new DLCreateSelectionScrollInput(guiLeft + 45, guiTop + 23, 138, 18) @@ -118,23 +130,16 @@ protected void init() { .writingTo(displayTypeLabel) .calling((i) -> { type = EDisplayType.getTypeById(i); + displayTypes.clear(); + createDisplayBrowser(); + displayTypeInput.addHint(displayTypeHint()); }) - .addHint(tooltipDisplayTypeDescription) + .addHint(displayTypeHint()) .setState(type.getId())); displayTypeInput.onChanged(); infoTypeLabel = addRenderableWidget(new DLCreateLabel(guiLeft + 45 + 5, guiTop + 45 + 5, Components.immutableEmpty()).withShadow()); - infoTypeInput = addRenderableWidget(new DLCreateSelectionScrollInput(guiLeft + 45, guiTop + 45, 138, 18) - .forOptions(Arrays.stream(EDisplayInfo.values()).map(x -> TextUtils.translate(x.getValueTranslationKey(CreateRailwaysNavigator.MOD_ID))).toList()) - .titled(tooltipInfoType) - .writingTo(infoTypeLabel) - .calling((i) -> { - info = EDisplayInfo.getTypeById(i); - }) - .addHint(tooltipInfoTypeDescription) - .setState(info.getId())); - infoTypeInput.onChanged(); - + createDisplayBrowser(); addRenderableWidget(new DLCheckBox(guiLeft + 45, guiTop + 67 + 1, 138, textDoubleSided.getString(), doubleSided, (box) -> { this.doubleSided = box.isChecked(); @@ -147,18 +152,37 @@ protected void init() { @Override public void onClick(double mouseX, double mouseY) { super.onClick(mouseX, mouseY); - minecraft.setScreen(new LoadingScreen()); - GlobalSettingsManager.syncToClient(() -> { - ClientTrainStationSnapshot.syncToClient(() -> { - minecraft.setScreen(new GlobalSettingsScreen(level, instance)); - }); - }); + DLScreen.setScreen(new GlobalSettingsScreen(instance)); } }); addTooltip(DLTooltip.of(tooltipGlobalSettings).assignedTo(globalSettingsButton)); } } + private void createDisplayBrowser() { + if (infoTypeInput != null) { + removeWidget(infoTypeInput); + } + infoTypeInput = new DLCreateSelectionScrollInput(guiLeft + 45, guiTop + 45, 138, 18) + .forOptions(displayTypes.get().stream().map(x -> TextUtils.translate(x.getTranslationKey())).toList()) + .titled(tooltipInfoType) + .writingTo(infoTypeLabel) + .calling((i) -> { + typeKey = displayTypes.get().get(i); + }) + .setState(displayTypes.get().indexOf(typeKey)); + infoTypeInput.onChanged(); + addRenderableWidget(infoTypeInput); + } + + private MutableComponent displayTypeHint() { + StringBuilder sb = new StringBuilder(); + font.getSplitter().splitLines(TextUtils.translate(typeKey.category().getValueInfoTranslationKey(CreateRailwaysNavigator.MOD_ID)), width() / 3, Style.EMPTY).forEach(x -> { + sb.append("\n" + x.getString()); + }); + return TextUtils.text(sb.toString()); + } + @Override public boolean isPauseScreen() { return false; @@ -183,7 +207,7 @@ public void renderMainLayer(Graphics graphics, int pMouseX, int pMouseY, float p .render(graphics.poseStack()); type.getIcon().render(graphics, guiLeft + 22, guiTop + 24); - info.getIcon().render(graphics, guiLeft + 22, guiTop + 46); + ModGuiIcons.VERY_DETAILED.render(graphics, guiLeft + 22, guiTop + 46); ModGuiIcons.DOUBLE_SIDED.render(graphics, guiLeft + 22, guiTop + 68); super.renderMainLayer(graphics, pMouseX, pMouseY, pPartialTick); diff --git a/common/src/main/java/de/mrjulsen/crn/client/gui/screen/AliasSettingsScreen.java b/common/src/main/java/de/mrjulsen/crn/client/gui/screen/AliasSettingsScreen.java deleted file mode 100644 index 47c6b56f..00000000 --- a/common/src/main/java/de/mrjulsen/crn/client/gui/screen/AliasSettingsScreen.java +++ /dev/null @@ -1,34 +0,0 @@ -package de.mrjulsen.crn.client.gui.screen; - -import java.util.Collection; - -import de.mrjulsen.crn.CreateRailwaysNavigator; -import de.mrjulsen.crn.client.gui.widgets.AliasEntryWidget; -import de.mrjulsen.crn.data.AliasName; -import de.mrjulsen.crn.data.GlobalSettingsManager; -import de.mrjulsen.crn.data.TrainStationAlias; -import de.mrjulsen.mcdragonlib.util.TextUtils; -import net.minecraft.client.gui.screens.Screen; -import net.minecraft.world.level.Level; - -public class AliasSettingsScreen extends AbstractEntryListSettingsScreen { - public AliasSettingsScreen(Level level, Screen lastScreen) { - super(level, lastScreen, TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".alias_settings.title")); - } - - @Override - protected AliasEntryWidget createWidget(WidgetCreationData> widgetData, TrainStationAlias data) { - Collection expandedAliasNames = widgetData.previousEntries().stream().filter(x -> x instanceof AliasEntryWidget && ((AliasEntryWidget)x).isExpanded()).map(x -> ((AliasEntryWidget)x).getAlias().getAliasName().get()).toList(); - return new AliasEntryWidget(widgetData.parent(), widgetData.x(), widgetData.y(), data, () -> refreshEntries(), expandedAliasNames.contains(data.getAliasName().get())); - } - - @Override - protected TrainStationAlias[] getData(String searchText) { - return GlobalSettingsManager.getInstance().getSettingsData().getAliasList().stream().filter(x -> x.getAliasName().get().toLowerCase().contains(searchText.toLowerCase())).toArray(TrainStationAlias[]::new); - } - - @Override - protected void onCreateNewEntry(String value, Runnable refreshAction) { - GlobalSettingsManager.getInstance().getSettingsData().registerAlias(new TrainStationAlias(AliasName.of(value)), refreshAction); - } -} diff --git a/common/src/main/java/de/mrjulsen/crn/client/gui/screen/GlobalSettingsScreen.java b/common/src/main/java/de/mrjulsen/crn/client/gui/screen/GlobalSettingsScreen.java index 5c021f05..092a0b0d 100644 --- a/common/src/main/java/de/mrjulsen/crn/client/gui/screen/GlobalSettingsScreen.java +++ b/common/src/main/java/de/mrjulsen/crn/client/gui/screen/GlobalSettingsScreen.java @@ -1,208 +1,438 @@ package de.mrjulsen.crn.client.gui.screen; -import com.simibubi.create.content.trains.station.NoShadowFontWrapper; -import com.simibubi.create.foundation.gui.AllIcons; -import com.simibubi.create.foundation.gui.widget.IconButton; -import com.simibubi.create.foundation.utility.animation.LerpedFloat; -import com.simibubi.create.foundation.utility.animation.LerpedFloat.Chaser; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Locale; +import java.util.Optional; import de.mrjulsen.crn.Constants; import de.mrjulsen.crn.CreateRailwaysNavigator; +import de.mrjulsen.crn.client.gui.CreateDynamicWidgets; +import de.mrjulsen.crn.client.gui.CreateDynamicWidgets.BarColor; +import de.mrjulsen.crn.client.gui.CreateDynamicWidgets.ContainerColor; +import de.mrjulsen.crn.client.gui.CreateDynamicWidgets.FooterSize; +import de.mrjulsen.crn.client.gui.ModGuiIcons; import de.mrjulsen.crn.client.gui.widgets.DLCreateIconButton; -import de.mrjulsen.crn.client.gui.widgets.SettingsOptionWidget; -import de.mrjulsen.crn.config.ModClientConfig; -import de.mrjulsen.mcdragonlib.DragonLib; -import de.mrjulsen.mcdragonlib.client.gui.DLScreen; +import de.mrjulsen.crn.client.gui.widgets.ModStationSuggestions; +import de.mrjulsen.crn.client.gui.widgets.ModernVerticalScrollBar; +import de.mrjulsen.crn.client.gui.widgets.flyouts.FlyoutColorPicker; +import de.mrjulsen.crn.client.gui.widgets.options.DLOptionsList; +import de.mrjulsen.crn.client.gui.widgets.options.DataListContainer; +import de.mrjulsen.crn.client.gui.widgets.options.OptionEntry; +import de.mrjulsen.crn.client.gui.widgets.options.SimpleDataListNewEntry; +import de.mrjulsen.crn.data.StationTag; +import de.mrjulsen.crn.data.TrainGroup; +import de.mrjulsen.crn.data.TrainLine; +import de.mrjulsen.crn.data.storage.GlobalSettingsClient; +import de.mrjulsen.crn.registry.ModAccessorTypes; +import de.mrjulsen.mcdragonlib.client.gui.widgets.DLEditBox; +import de.mrjulsen.mcdragonlib.client.gui.widgets.DLIconButton; import de.mrjulsen.mcdragonlib.client.gui.widgets.DLTooltip; +import de.mrjulsen.mcdragonlib.client.gui.widgets.DLVerticalScrollBar; import de.mrjulsen.mcdragonlib.client.util.Graphics; import de.mrjulsen.mcdragonlib.client.util.GuiAreaDefinition; -import de.mrjulsen.mcdragonlib.client.util.GuiUtils; -import de.mrjulsen.mcdragonlib.client.util.WidgetsCollection; -import de.mrjulsen.mcdragonlib.core.EAlignment; +import de.mrjulsen.mcdragonlib.util.DLUtils; +import de.mrjulsen.mcdragonlib.util.MathUtils; import de.mrjulsen.mcdragonlib.util.TextUtils; -import de.mrjulsen.mcdragonlib.util.TimeUtils; +import de.mrjulsen.mcdragonlib.util.accessor.DataAccessor; +import net.minecraft.Util; import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.Font; import net.minecraft.client.gui.screens.Screen; import net.minecraft.network.chat.Component; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.util.Mth; -import net.minecraft.world.level.Level; - -public class GlobalSettingsScreen extends DLScreen { - - private static final ResourceLocation GUI = new ResourceLocation(CreateRailwaysNavigator.MOD_ID, "textures/gui/settings.png"); - private static final int GUI_WIDTH = 255; - private static final int GUI_HEIGHT = 247; +public class GlobalSettingsScreen extends AbstractNavigatorScreen { + private static final int DEFAULT_ICON_BUTTON_WIDTH = 18; private static final int DEFAULT_ICON_BUTTON_HEIGHT = 18; - private static final int ENTRIES_START_Y_OFFSET = 10; - private final int ENTRY_SPACING = 4; + private DLOptionsList viewer; + private ModStationSuggestions destinationSuggestions; - private final int AREA_X = 16; - private final int AREA_Y = 16; - private final int AREA_W = 220; - private final int AREA_H = 194; - private GuiAreaDefinition workingArea; - - private int guiLeft, guiTop; - private LerpedFloat scroll = LerpedFloat.linear().startWithValue(0); - - // Data - private final Level level; - private final Font shadowlessFont; - private final Screen lastScreen; - private final GlobalSettingsScreen instance; - - // Controls - private IconButton backButton; - private final WidgetsCollection optionsCollection = new WidgetsCollection(); - - // Tooltips - private final Component optionAliasTitle = TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".global_settings.option_alias.title"); - private final Component optionAliasDescription = TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".global_settings.option_alias.description"); + private final Component optionTagTitle = TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".global_settings.option_alias.title"); + private final Component optionTagDescription = TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".global_settings.option_alias.description"); private final Component optionBlacklistTitle = TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".global_settings.option_blacklist.title"); private final Component optionBlacklistDescription = TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".global_settings.option_blacklist.description"); private final Component optionTrainGroupTitle = TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".global_settings.train_group.title"); private final Component optionTrainGroupDescription = TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".global_settings.train_group.description"); private final Component optionTrainBlacklistTitle = TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".global_settings.train_blacklist.title"); private final Component optionTrainBlacklistDescription = TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".global_settings.train_blacklist.description"); + private final Component optionTrainLineTitle = TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".global_settings.train_line.title"); + private final Component optionTrainLineDescription = TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".global_settings.train_line.description"); + private final Component textAdd = TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".common.add"); + private final Component textColor = TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".global_settings.train_line.color"); + + private final List stationNames = new ArrayList<>(); + private final List trainNames = new ArrayList<>(); - @SuppressWarnings("resource") - public GlobalSettingsScreen(Level level, Screen lastScreen) { - super(TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".global_settings.title")); - this.level = level; - this.lastScreen = lastScreen; - this.shadowlessFont = new NoShadowFontWrapper(Minecraft.getInstance().font); - this.instance = this; + public GlobalSettingsScreen(Screen lastScreen) { + super(lastScreen, TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".global_settings.title"), BarColor.GRAY); } @Override - protected void init() { - super.init(); - guiLeft = this.width / 2 - GUI_WIDTH / 2; - guiTop = this.height / 2 - GUI_HEIGHT / 2; - int startY = guiTop + AREA_X + ENTRIES_START_Y_OFFSET; - workingArea = new GuiAreaDefinition(guiLeft + AREA_X, guiTop + AREA_Y, AREA_W, AREA_H); + public void onClose() { + Minecraft.getInstance().setScreen(lastScreen); + } - optionsCollection.components.clear(); + @Override + public void tick() { + super.tick(); - optionsCollection.components.add(new SettingsOptionWidget(this, guiLeft + 26, startY, optionAliasTitle, optionAliasDescription, (btn) -> { - minecraft.setScreen(new AliasSettingsScreen(level, instance)); - })); + DLUtils.doIfNotNull(destinationSuggestions, x -> { + x.tick(); + if (!destinationSuggestions.getEditBox().canConsumeInput()) { + clearSuggestions(); + } + }); + } - optionsCollection.components.add(new SettingsOptionWidget(this, guiLeft + 26, startY + SettingsOptionWidget.HEIGHT + ENTRY_SPACING, optionBlacklistTitle, optionBlacklistDescription, (btn) -> { - minecraft.setScreen(new StationBlacklistScreen(level, instance)); - })); + @Override + protected void init() { + super.init(); + setAllowedLayer(0); - optionsCollection.components.add(new SettingsOptionWidget(this, guiLeft + 26, startY + (SettingsOptionWidget.HEIGHT + ENTRY_SPACING) * 2, optionTrainGroupTitle, optionTrainGroupDescription, (btn) -> { - minecraft.setScreen(new TrainGroupScreen(level, instance)); - })); - - optionsCollection.components.add(new SettingsOptionWidget(this, guiLeft + 26, startY + (SettingsOptionWidget.HEIGHT + ENTRY_SPACING) * 3, optionTrainBlacklistTitle, optionTrainBlacklistDescription, (btn) -> { - minecraft.setScreen(new TrainBlacklistScreen(level, instance)); - })); - - backButton = this.addRenderableWidget(new DLCreateIconButton(guiLeft + 21, guiTop + 222, DEFAULT_ICON_BUTTON_WIDTH, DEFAULT_ICON_BUTTON_HEIGHT, AllIcons.I_CONFIG_BACK) { + DLCreateIconButton helpButton = this.addRenderableWidget(new DLCreateIconButton(guiLeft + GUI_WIDTH - DEFAULT_ICON_BUTTON_WIDTH - 8, guiTop + 223, DEFAULT_ICON_BUTTON_WIDTH, DEFAULT_ICON_BUTTON_HEIGHT, ModGuiIcons.HELP.getAsCreateIcon()) { @Override public void onClick(double mouseX, double mouseY) { super.onClick(mouseX, mouseY); - onClose(); + Util.getPlatform().openUri(Constants.HELP_PAGE_GLOBAL_SETTINGS); } }); - addTooltip(DLTooltip.of(Constants.TOOLTIP_GO_BACK).assignedTo(backButton)); + addTooltip(DLTooltip.of(Constants.TEXT_HELP).assignedTo(helpButton)); + + int dy = FooterSize.DEFAULT.size() + 1; + ModernVerticalScrollBar scrollBar = new ModernVerticalScrollBar(this, guiLeft + GUI_WIDTH - 8, guiTop + dy, GUI_HEIGHT - dy - FooterSize.SMALL.size() - 1, null); + viewer = new DLOptionsList(this, guiLeft + 3, guiTop + dy, GUI_WIDTH - 6, GUI_HEIGHT - dy - FooterSize.SMALL.size() - 1, scrollBar); + addRenderableWidget(viewer); + addRenderableWidget(scrollBar); + + viewer.addOption(null, optionTagTitle, optionTagDescription, (a, b) -> Minecraft.getInstance().setScreen(new StationTagSettingsScreen(this)), null); + //viewer.addOption(null, optionTrainGroupTitle, optionTrainGroupDescription, (a, b) -> Minecraft.getInstance().setScreen(new TrainGroupScreen(this)), null); + + GlobalSettingsClient.getBlacklistedStations((datalist) -> { + addBlacklistedStationsWidget(datalist.stream().sorted((a, b) -> a.compareToIgnoreCase(b)).toList(), scrollBar); + GlobalSettingsClient.getBlacklistedTrains((datalist2) -> { + addBlacklistedTrainsWidget(datalist2.stream().sorted((a, b) -> a.compareToIgnoreCase(b)).toList(), scrollBar); + GlobalSettingsClient.getTrainGroups((datalist3) -> { + addTrainGroupsWidget(datalist3.stream().sorted((a, b) -> a.getGroupName().compareToIgnoreCase(b.getGroupName())).toList(), scrollBar); + GlobalSettingsClient.getTrainLines((datalist4) -> { + addTrainLinesWidget(datalist4.stream().sorted((a, b) -> a.getLineName().compareToIgnoreCase(b.getLineName())).toList(), scrollBar); + }); + }); + }); + }); + + DataAccessor.getFromServer(null, ModAccessorTypes.GET_ALL_TRAIN_NAMES, (names) -> { + this.trainNames.clear(); + this.trainNames.addAll(names); + + DataAccessor.getFromServer(null, ModAccessorTypes.GET_ALL_STATION_NAMES, (names2) -> { + this.stationNames.clear(); + this.stationNames.addAll(names2); + }); + }); + + //viewer.addOption(null, optionTrainBlacklistTitle, optionTrainBlacklistDescription, (a, b) -> OptionEntry.expandOrCollapse(a), null); } - - private int getMaxScrollHeight() { - return (SettingsOptionWidget.HEIGHT + ENTRY_SPACING) * 4 + ENTRIES_START_Y_OFFSET * 2; + + private void addBlacklistedStationsWidget(List datalist, DLVerticalScrollBar scrollBar) { + OptionEntry opt = viewer.addOption((option) -> { + GuiAreaDefinition workspace = option.getContentSpace(); + DataListContainer, String> cont = new DataListContainer<>(option, workspace.getX(), workspace.getY(), workspace.getWidth(), datalist, + (list) -> { + return list.iterator(); + }, (data, entryWidget) -> { + entryWidget.addDeleteButton((btn, tg, entry, refreshAction) -> { + GlobalSettingsClient.removeStationFromBlacklist(entry, (res) -> { + refreshAction.accept(Optional.ofNullable(res)); + }); + }); + return data; + }, (data, entryWidget) -> { + entryWidget.addAddButton(ModGuiIcons.ADD.getAsSprite(16, 16), textAdd, + (btn, tg, inputValues, refreshAction) -> { + String name = inputValues.get(SimpleDataListNewEntry.MAIN_INPUT_KEY).get(); + if (name == null || name.isBlank()) { + return false; + } + GlobalSettingsClient.addStationToBlacklist(name,(res) -> { + refreshAction.accept(Optional.ofNullable(res)); + }); + return true; + }); + entryWidget.editNameEditBox((box) -> { + box.setResponder((b) -> { + this.updateEditorSubwidgetsStations(box, data); + }); + box.setMaxLength(StationTag.MAX_NAME_LENGTH); + }); + }, (self) -> { + option.notifyContentSizeChanged(); + } + ); + cont.setPadding(3, 0, 3, 18); + cont.setFilter((entry, searchText) -> { + return entry.toLowerCase(Locale.ROOT).contains(searchText.get().toLowerCase(Locale.ROOT)); + }); + cont.setBordered(false); + + return cont; + }, optionBlacklistTitle, optionBlacklistDescription, (a, b) -> OptionEntry.expandOrCollapse(a), null); + opt.addAdditionalButton(ModGuiIcons.HELP.getAsSprite(16, 16), Constants.TEXT_HELP, (entry, btn) -> Util.getPlatform().openUri(Constants.HELP_PAGE_STATION_BLACKLIST)); + } - @Override - public void onClose() { - minecraft.setScreen(lastScreen); + private void addBlacklistedTrainsWidget(List datalist, DLVerticalScrollBar scrollBar) { + OptionEntry opt = viewer.addOption((option) -> { + GuiAreaDefinition workspace = option.getContentSpace(); + DataListContainer, String> cont = new DataListContainer<>(option, workspace.getX(), workspace.getY(), workspace.getWidth(), datalist, + (list) -> { + return list.iterator(); + }, (data, entryWidget) -> { + entryWidget.addDeleteButton((btn, tg, entry, refreshAction) -> { + GlobalSettingsClient.removeTrainFromBlacklist(entry, (res) -> { + refreshAction.accept(Optional.ofNullable(res)); + }); + }); + return data; + }, (data, entryWidget) -> { + entryWidget.addAddButton(ModGuiIcons.ADD.getAsSprite(16, 16), textAdd, + (btn, tg, inputValues, refreshAction) -> { + String name = inputValues.get(SimpleDataListNewEntry.MAIN_INPUT_KEY).get(); + if (name == null || name.isBlank()) { + return false; + } + GlobalSettingsClient.addTrainToBlacklist(name, (res) -> { + refreshAction.accept(Optional.ofNullable(res)); + }); + return true; + }); + entryWidget.editNameEditBox((box) -> { + box.setResponder((b) -> { + this.updateEditorSubwidgetsTrains(box, data); + }); + box.setMaxLength(StationTag.MAX_NAME_LENGTH); + }); + }, (self) -> { + option.notifyContentSizeChanged(); + } + ); + cont.setPadding(3, 0, 3, 18); + cont.setFilter((entry, searchText) -> { + return entry.toLowerCase(Locale.ROOT).contains(searchText.get().toLowerCase(Locale.ROOT)); + }); + cont.setBordered(false); + + return cont; + }, optionTrainBlacklistTitle, optionTrainBlacklistDescription, (a, b) -> OptionEntry.expandOrCollapse(a), null); + opt.addAdditionalButton(ModGuiIcons.HELP.getAsSprite(16, 16), Constants.TEXT_HELP, (entry, btn) -> Util.getPlatform().openUri(Constants.HELP_PAGE_TRAIN_BLACKLIST)); + } - @Override - public void tick() { - super.tick(); - - scroll.tickChaser(); + private void addTrainGroupsWidget(List datalist, DLVerticalScrollBar scrollBar) { + OptionEntry opt = viewer.addOption((option) -> { + GuiAreaDefinition workspace = option.getContentSpace(); + DataListContainer, TrainGroup> cont = new DataListContainer<>(option, workspace.getX(), workspace.getY(), workspace.getWidth(), datalist, + (list) -> { + return list.iterator(); + }, (data, entryWidget) -> { + entryWidget.addDeleteButton((btn, tg, entry, refreshAction) -> { + GlobalSettingsClient.deleteTrainGroup(entry.getGroupName(), () -> { + GlobalSettingsClient.getTrainGroups((res) -> { + refreshAction.accept(Optional.ofNullable(res)); + }); + }); + }); + DLIconButton colorBtn = entryWidget.addButton(ModGuiIcons.COLOR_PALETTE.getAsSprite(16, 16), textColor, + (btn, tg, entry, refreshAction) -> { + final TrainGroup e = entry; + FlyoutColorPicker flyout = new FlyoutColorPicker<>(this, e.getColor(), this::addRenderableWidget, (w) -> { + GlobalSettingsClient.updateTrainGroupColor(e.getGroupName(), ((FlyoutColorPicker)w).getColorPicker().getSelectedColor(), () -> { + GlobalSettingsClient.getTrainGroups((res) -> { + refreshAction.accept(Optional.ofNullable(res)); + }); + }); + removeWidget(w); + }); + flyout.setYOffset((int)-scrollBar.getScrollValue()); + flyout.open(btn); + }); + colorBtn.setBackColor(data.getColor()); + return data.getGroupName(); + }, (data, entryWidget) -> { + entryWidget.addAddButton(ModGuiIcons.ADD.getAsSprite(16, 16), textAdd, + (btn, tg, inputValues, refreshAction) -> { + String name = inputValues.get(SimpleDataListNewEntry.MAIN_INPUT_KEY).get(); + if (name == null || name.isBlank()) { + return false; + } + GlobalSettingsClient.createTrainGroup(name, (res) -> { + GlobalSettingsClient.getTrainGroups((r) -> { + refreshAction.accept(Optional.ofNullable(r)); + }); + }); + return true; + }); + entryWidget.editNameEditBox((box) -> { + box.setResponder((b) -> { + //this.updateEditorSubwidgetsTrains(box, data); + }); + box.setMaxLength(StationTag.MAX_NAME_LENGTH); + }); + }, (self) -> { + option.notifyContentSizeChanged(); + } + ); + cont.setPadding(3, 0, 3, 18); + cont.setFilter((entry, searchText) -> { + return entry.getGroupName().toLowerCase(Locale.ROOT).contains(searchText.get().toLowerCase(Locale.ROOT)); + }); + cont.setBordered(false); + return cont; + }, optionTrainGroupTitle, optionTrainGroupDescription, (a, b) -> OptionEntry.expandOrCollapse(a), null); + opt.addAdditionalButton(ModGuiIcons.HELP.getAsSprite(16, 16), Constants.TEXT_HELP, (entry, btn) -> Util.getPlatform().openUri(Constants.HELP_PAGE_TRAIN_GROUPS)); + } - @Override - public void renderMainLayer(Graphics graphics, int pMouseX, int pMouseY, float pPartialTick) { - final float partialTicks = Minecraft.getInstance().getFrameTime(); - float scrollOffset = -scroll.getValue(partialTicks); - - renderScreenBackground(graphics); - GuiUtils.drawTexture(GUI, graphics, guiLeft, guiTop, 0, 0, GUI_WIDTH, GUI_HEIGHT); - GuiUtils.drawString(graphics, shadowlessFont, guiLeft + 19, guiTop + 4, title, 0x4F4F4F, EAlignment.LEFT, false); - String timeString = TimeUtils.parseTime((int)((level.getDayTime() + DragonLib.DAYTIME_SHIFT) % DragonLib.TICKS_PER_DAY), ModClientConfig.TIME_FORMAT.get()); - GuiUtils.drawString(graphics, shadowlessFont, guiLeft + GUI_WIDTH - 22 - shadowlessFont.width(timeString), guiTop + 4, TextUtils.text(timeString), 0x4F4F4F, EAlignment.LEFT, false); - - // Scrollbar - double maxHeight = getMaxScrollHeight(); - double aH = AREA_H + 1; - if (aH / maxHeight < 1) { - int scrollerHeight = Math.max(10, (int)(aH * (aH / maxHeight))); - int startY = guiTop + AREA_Y + (int)((AREA_H) * (Math.abs(scrollOffset) / maxHeight)); - - GuiUtils.fill(graphics, guiLeft + AREA_X + AREA_W - 3, startY, 3, scrollerHeight, 0x7FFFFFFF); - } + private void addTrainLinesWidget(List datalist, DLVerticalScrollBar scrollBar) { + OptionEntry opt = viewer.addOption((option) -> { + GuiAreaDefinition workspace = option.getContentSpace(); + DataListContainer, TrainLine> cont = new DataListContainer<>(option, workspace.getX(), workspace.getY(), workspace.getWidth(), datalist, + (list) -> { + return list.iterator(); + }, (data, entryWidget) -> { + entryWidget.addDeleteButton((btn, tg, entry, refreshAction) -> { + GlobalSettingsClient.deleteTrainLine(entry.getLineName(), () -> { + GlobalSettingsClient.getTrainLines((res) -> { + refreshAction.accept(Optional.ofNullable(res)); + }); + }); + }); + DLIconButton colorBtn = entryWidget.addButton(ModGuiIcons.COLOR_PALETTE.getAsSprite(16, 16), textColor, + (btn, tg, entry, refreshAction) -> { + final TrainLine e = entry; + FlyoutColorPicker flyout = new FlyoutColorPicker<>(this, e.getColor(), this::addRenderableWidget, (w) -> { + GlobalSettingsClient.updateTrainLineColor(e.getLineName(), ((FlyoutColorPicker)w).getColorPicker().getSelectedColor(), () -> { + GlobalSettingsClient.getTrainLines((res) -> { + refreshAction.accept(Optional.ofNullable(res)); + }); + }); + removeWidget(w); + }); + flyout.setYOffset((int)-scrollBar.getScrollValue()); + flyout.open(btn); + }); + colorBtn.setBackColor(data.getColor()); + return data.getLineName(); + }, (data, entryWidget) -> { + entryWidget.addAddButton(ModGuiIcons.ADD.getAsSprite(16, 16), textAdd, + (btn, tg, inputValues, refreshAction) -> { + String name = inputValues.get(SimpleDataListNewEntry.MAIN_INPUT_KEY).get(); + if (name == null || name.isBlank()) { + return false; + } + GlobalSettingsClient.createTrainLine(name, (res) -> { + GlobalSettingsClient.getTrainLines((r) -> { + refreshAction.accept(Optional.ofNullable(r)); + }); + }); + return true; + }); + entryWidget.editNameEditBox((box) -> { + box.setResponder((b) -> { + //this.updateEditorSubwidgetsTrains(box, data); + }); + box.setMaxLength(StationTag.MAX_NAME_LENGTH); + }); + }, (self) -> { + option.notifyContentSizeChanged(); + } + ); + cont.setPadding(3, 0, 3, 18); + cont.setFilter((entry, searchText) -> { + return entry.getLineName().toLowerCase(Locale.ROOT).contains(searchText.get().toLowerCase(Locale.ROOT)); + }); + cont.setBordered(false); + return cont; + }, optionTrainLineTitle, optionTrainLineDescription, (a, b) -> OptionEntry.expandOrCollapse(a), null); + opt.addAdditionalButton(ModGuiIcons.HELP.getAsSprite(16, 16), Constants.TEXT_HELP, (entry, btn) -> Util.getPlatform().openUri(Constants.HELP_PAGE_TRAIN_LINES)); + } - // CONTENT - GuiUtils.enableScissor(graphics, guiLeft + AREA_X, guiTop + AREA_Y, AREA_W, AREA_H); - graphics.poseStack().translate(0, scrollOffset, 0); + @Override + public void renderMainLayer(Graphics graphics, int mouseX, int mouseY, float partialTicks) { + renderNavigatorBackground(graphics, mouseX, mouseY, partialTicks); - optionsCollection.performForEachOfType(SettingsOptionWidget.class, x -> x.renderMainLayer(graphics, pMouseX, (int)(pMouseY - scrollOffset), partialTicks/*, workingArea.isInBounds(pMouseX, pMouseY)*/)); - - GuiUtils.disableScissor(graphics); - GuiUtils.fillGradient(graphics, guiLeft + AREA_X, guiTop + AREA_Y, 200, AREA_W, 10, 0x77000000, 0x00000000); - GuiUtils.fillGradient(graphics, guiLeft + AREA_X, guiTop + AREA_Y + AREA_H - 10, 200, AREA_W, 10, 0x00000000, 0x77000000); + int y = FooterSize.DEFAULT.size() - 1; + int h = GUI_HEIGHT - y - FooterSize.SMALL.size(); + CreateDynamicWidgets.renderContainer(graphics, guiLeft + 1, guiTop + y, GUI_WIDTH - 2, h + 1, ContainerColor.PURPLE); - super.renderMainLayer(graphics, pMouseX, pMouseY, partialTicks); + super.renderMainLayer(graphics, mouseX, mouseY, partialTicks); } @Override - public void renderFrontLayer(Graphics graphics, int pMouseX, int pMouseY, float pPartialTicks) { - float scrollOffset = -scroll.getValue(pPartialTicks); - optionsCollection.performForEachOfType(SettingsOptionWidget.class, x -> workingArea.isInBounds(pMouseX, pMouseY), x -> x.renderFrontLayer(graphics, pMouseX, pMouseY, -scrollOffset)); - super.renderFrontLayer(graphics, pMouseX, pMouseY, pPartialTicks); + public void renderFrontLayer(Graphics graphics, int mouseX, int mouseY, float partialTick) { + super.renderFrontLayer(graphics, mouseX, mouseY, partialTick); + DLUtils.doIfNotNull(destinationSuggestions, x -> { + graphics.poseStack().pushPose(); + graphics.poseStack().translate(-viewer.getXScrollOffset(), -viewer.getYScrollOffset(), 0); + x.render(graphics.poseStack(), (int)(mouseX + viewer.getXScrollOffset()), (int)(mouseY + viewer.getYScrollOffset())); + graphics.poseStack().popPose(); + }); } - + @Override - public void mouseSelectEvent(int mouseX, int mouseY) { - - super.mouseSelectEvent(mouseX, mouseY); + public boolean mouseScrolled(double mouseX, double mouseY, double delta) { + if (destinationSuggestions != null && destinationSuggestions.mouseScrolled(mouseX + viewer.getXScrollOffset(), mouseY + viewer.getYScrollOffset(), MathUtils.clamp(delta, -1.0D, 1.0D))) + return true; + + return super.mouseScrolled(mouseX, mouseY, delta); } @Override - public boolean mouseClicked(double pMouseX, double pMouseY, int pButton) { - float scrollOffset = -scroll.getValue(); - - optionsCollection.performForEachOfType(SettingsOptionWidget.class, - x -> workingArea.isInBounds(pMouseX, pMouseY) && x.isMouseOver(pMouseX, (int)(pMouseY - scrollOffset)), - x -> x.mouseClicked(pMouseX, (int)(pMouseY - scrollOffset), pButton) - ); - return super.mouseClicked(pMouseX, pMouseY, pButton); + public boolean mouseClicked(double mouseX, double mouseY, int button) { + if (destinationSuggestions != null && destinationSuggestions.mouseClicked(mouseX + viewer.getXScrollOffset(), mouseY + viewer.getYScrollOffset(), button)) + return true; + + return super.mouseClicked(mouseX, mouseY, button); + } + + private void clearSuggestions() { + if (destinationSuggestions != null) { + destinationSuggestions.getEditBox().setSuggestion(""); + } + destinationSuggestions = null; } - @Override - public boolean mouseScrolled(double pMouseX, double pMouseY, double pDelta) { - float chaseTarget = scroll.getChaseTarget(); - float max = -AREA_H + getMaxScrollHeight(); + public void updateEditorSubwidgetsTrains(DLEditBox field, Collection blacklisted) { + updateEditorSubwidgetsInternal(field, getViableTrains(trainNames, blacklisted)); + } - if (max > 0) { - chaseTarget -= pDelta * 12; - chaseTarget = Mth.clamp(chaseTarget, 0, max); - scroll.chase((int) chaseTarget, 0.7f, Chaser.EXP); - } else - scroll.chase(0, 0.7f, Chaser.EXP); + public void updateEditorSubwidgetsStations(DLEditBox field, Collection blacklisted) { + updateEditorSubwidgetsInternal(field, getViableStations(stationNames, blacklisted)); + } - return super.mouseScrolled(pMouseX, pMouseY, pDelta); + private void updateEditorSubwidgetsInternal(DLEditBox field, List data) { + clearSuggestions(); + destinationSuggestions = new ModStationSuggestions(Minecraft.getInstance(), this, field, font, data, field.getHeight() + 2 + field.y); + destinationSuggestions.setAllowSuggestions(true); + destinationSuggestions.updateCommandInfo(); } + + private List getViableStations(Collection src, Collection blacklisted) { + return src.stream() + .distinct() + .filter(x -> !blacklisted.contains(x)) + .sorted((a, b) -> a.compareTo(b)) + .toList(); + } + + private List getViableTrains(Collection src, Collection blacklisted) { + return src.stream() + .distinct() + .filter(x -> !blacklisted.contains(x)) + .sorted((a, b) -> a.compareTo(b)) + .toList(); + } } diff --git a/common/src/main/java/de/mrjulsen/crn/client/gui/screen/LoadingScreen.java b/common/src/main/java/de/mrjulsen/crn/client/gui/screen/LoadingScreen.java deleted file mode 100644 index d03b0065..00000000 --- a/common/src/main/java/de/mrjulsen/crn/client/gui/screen/LoadingScreen.java +++ /dev/null @@ -1,45 +0,0 @@ -package de.mrjulsen.crn.client.gui.screen; - -import com.simibubi.create.foundation.gui.AllIcons; -import de.mrjulsen.crn.CreateRailwaysNavigator; -import de.mrjulsen.mcdragonlib.client.gui.DLScreen; -import de.mrjulsen.mcdragonlib.client.util.Graphics; -import de.mrjulsen.mcdragonlib.client.util.GuiUtils; -import de.mrjulsen.mcdragonlib.core.EAlignment; -import de.mrjulsen.mcdragonlib.util.TextUtils; - -public class LoadingScreen extends DLScreen { - - int angle = 0; - - public LoadingScreen() { - super(TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".loading.title")); - } - - @Override - protected void init() { - super.init(); - } - - @Override - public void tick() { - angle += 6; - if (angle > 360) { - angle = 0; - } - super.tick(); - } - - @Override - public void renderMainLayer(Graphics graphics, int mouseX, int mouseY, float partialTicks) { - super.renderMainLayer(graphics, mouseX, mouseY, partialTicks); - - renderScreenBackground(graphics); - - double offsetX = Math.sin(Math.toRadians(angle)) * 5; - double offsetY = Math.cos(Math.toRadians(angle)) * 5; - - GuiUtils.drawString(graphics, font, width / 2, height / 2, title, 0xFFFFFF, EAlignment.CENTER, true); - AllIcons.I_MTD_SCAN.render(graphics.poseStack(), (int)(width / 2 + offsetX), (int)(height / 2 - 50 + offsetY)); - } -} diff --git a/common/src/main/java/de/mrjulsen/crn/client/gui/screen/NavigatorScreen.java b/common/src/main/java/de/mrjulsen/crn/client/gui/screen/NavigatorScreen.java index d571c410..d4d62821 100644 --- a/common/src/main/java/de/mrjulsen/crn/client/gui/screen/NavigatorScreen.java +++ b/common/src/main/java/de/mrjulsen/crn/client/gui/screen/NavigatorScreen.java @@ -1,101 +1,86 @@ package de.mrjulsen.crn.client.gui.screen; +import java.util.ArrayList; +import java.util.Collection; import java.util.List; -import java.util.UUID; - -import org.lwjgl.opengl.GL11; -import org.lwjgl.opengl.GL30; - -import com.mojang.blaze3d.systems.RenderSystem; -import com.mojang.blaze3d.vertex.PoseStack; -import com.simibubi.create.content.trains.station.NoShadowFontWrapper; +import com.google.common.collect.ImmutableList; import com.simibubi.create.foundation.gui.AllIcons; import com.simibubi.create.foundation.utility.animation.LerpedFloat; -import com.simibubi.create.foundation.utility.animation.LerpedFloat.Chaser; - -import de.mrjulsen.crn.Constants; import de.mrjulsen.crn.CreateRailwaysNavigator; +import de.mrjulsen.crn.client.CRNGui; +import de.mrjulsen.crn.client.gui.CreateDynamicWidgets; import de.mrjulsen.crn.client.gui.ModGuiIcons; +import de.mrjulsen.crn.client.gui.CreateDynamicWidgets.BarColor; +import de.mrjulsen.crn.client.gui.CreateDynamicWidgets.ColorShade; +import de.mrjulsen.crn.client.gui.CreateDynamicWidgets.ContainerColor; +import de.mrjulsen.crn.client.gui.CreateDynamicWidgets.FooterSize; +import de.mrjulsen.crn.client.gui.widgets.AbstractNotificationPopup; import de.mrjulsen.crn.client.gui.widgets.DLCreateIconButton; import de.mrjulsen.crn.client.gui.widgets.ModDestinationSuggestions; -import de.mrjulsen.crn.client.gui.widgets.RouteEntryOverviewWidget; -import de.mrjulsen.crn.config.ModClientConfig; +import de.mrjulsen.crn.client.gui.widgets.ModernVerticalScrollBar; +import de.mrjulsen.crn.client.gui.widgets.RouteViewer; +import de.mrjulsen.crn.client.gui.widgets.SearchOptionButton; +import de.mrjulsen.crn.client.gui.widgets.flyouts.FlyoutAdvancedSearchsettingsWidget; +import de.mrjulsen.crn.client.gui.widgets.flyouts.FlyoutDepartureInWidget; +import de.mrjulsen.crn.client.gui.widgets.flyouts.FlyoutTrainGroupsWidget; +import de.mrjulsen.crn.client.gui.widgets.flyouts.FlyoutTransferTimeWidget; +import de.mrjulsen.crn.client.gui.widgets.notifications.NotificationTrainInitialization; +import de.mrjulsen.crn.client.gui.widgets.AbstractFlyoutWidget.FlyoutPointer; import de.mrjulsen.crn.config.ModCommonConfig; -import de.mrjulsen.crn.data.ClientTrainStationSnapshot; -import de.mrjulsen.crn.data.GlobalSettingsManager; -import de.mrjulsen.crn.data.SimpleRoute; -import de.mrjulsen.crn.data.TrainStationAlias; -import de.mrjulsen.crn.event.listeners.IJourneyListenerClient; -import de.mrjulsen.crn.event.listeners.JourneyListenerManager; -import de.mrjulsen.crn.network.InstanceManager; -import de.mrjulsen.crn.network.packets.cts.NavigationRequestPacket; -import de.mrjulsen.crn.network.packets.cts.NearestStationRequestPacket; -import de.mrjulsen.mcdragonlib.DragonLib; -import de.mrjulsen.mcdragonlib.client.gui.DLScreen; +import de.mrjulsen.crn.data.StationTag; +import de.mrjulsen.crn.data.UserSettings; +import de.mrjulsen.crn.data.navigation.ClientRoute; +import de.mrjulsen.crn.registry.ModAccessorTypes; +import de.mrjulsen.crn.registry.ModAccessorTypes.NavigationData; import de.mrjulsen.mcdragonlib.client.gui.widgets.DLEditBox; +import de.mrjulsen.mcdragonlib.client.gui.widgets.DLIconButton; import de.mrjulsen.mcdragonlib.client.gui.widgets.DLTooltip; +import de.mrjulsen.mcdragonlib.client.gui.widgets.DLAbstractImageButton.ButtonType; +import de.mrjulsen.mcdragonlib.client.render.GuiIcons; +import de.mrjulsen.mcdragonlib.client.render.Sprite; +import de.mrjulsen.mcdragonlib.client.render.DynamicGuiRenderer.AreaStyle; import de.mrjulsen.mcdragonlib.client.util.Graphics; import de.mrjulsen.mcdragonlib.client.util.GuiAreaDefinition; import de.mrjulsen.mcdragonlib.client.util.GuiUtils; -import de.mrjulsen.mcdragonlib.client.util.WidgetsCollection; import de.mrjulsen.mcdragonlib.core.EAlignment; +import de.mrjulsen.mcdragonlib.util.DLUtils; import de.mrjulsen.mcdragonlib.util.TextUtils; -import de.mrjulsen.mcdragonlib.util.TimeUtils; +import de.mrjulsen.mcdragonlib.util.accessor.DataAccessor; import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.Font; -import net.minecraft.client.gui.components.AbstractWidget; import net.minecraft.client.gui.components.toasts.SystemToast; import net.minecraft.client.gui.components.toasts.SystemToast.SystemToastIds; -import net.minecraft.client.resources.sounds.SimpleSoundInstance; +import net.minecraft.client.gui.screens.Screen; import net.minecraft.network.chat.MutableComponent; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.sounds.SoundEvents; import net.minecraft.util.Mth; -import net.minecraft.world.level.Level; -public class NavigatorScreen extends DLScreen implements IJourneyListenerClient { - - private static final ResourceLocation GUI = new ResourceLocation(CreateRailwaysNavigator.MOD_ID, "textures/gui/navigator.png"); - private static final int GUI_WIDTH = 255; - private static final int GUI_HEIGHT = 247; +public class NavigatorScreen extends AbstractNavigatorScreen { private static final int DEFAULT_ICON_BUTTON_WIDTH = 18; private static final int DEFAULT_ICON_BUTTON_HEIGHT = 18; - private static final int ENTRIES_START_Y_OFFSET = 10; - private static final int ENTRY_SPACING = 4; - - private final int AREA_X = 16; - private final int AREA_Y = 67; - private final int AREA_W = 220; - private final int AREA_H = 143; - - private int guiLeft, guiTop; private boolean initialized = false; private int angle = 0; // Controls private DLCreateIconButton locationButton; private DLCreateIconButton searchButton; - private DLCreateIconButton goToTopButton; - private DLCreateIconButton globalSettingsButton; - private DLCreateIconButton searchSettingsButton; + private DLCreateIconButton globalSettingsButton; private DLEditBox fromBox; private DLEditBox toBox; + private RouteViewer routeViewer; private LerpedFloat scroll = LerpedFloat.linear().startWithValue(0); - private ModDestinationSuggestions destinationSuggestions; - private GuiAreaDefinition switchButtonsArea; - private final WidgetsCollection routesCollection = new WidgetsCollection(); + private ModDestinationSuggestions destinationSuggestions; + private AbstractNotificationPopup notificationPopup; + + @SuppressWarnings("resource") + private UserSettings userSettings = new UserSettings(Minecraft.getInstance().player.getUUID(), false); // Data - private SimpleRoute[] routes; + private final Collection routes = new ArrayList<>(); + private final List stationNames = new ArrayList<>(); private String stationFrom = ""; private String stationTo = ""; - private long lastRefreshedTime; private final NavigatorScreen instance; - private final Level level; - private final Font shadowlessFont; - private final UUID clientId = UUID.randomUUID(); // var private boolean isLoadingRoutes = false; @@ -113,47 +98,20 @@ public class NavigatorScreen extends DLScreen implements IJourneyListenerClient private final MutableComponent tooltipLocation = TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".navigator.location.tooltip"); private final MutableComponent tooltipSwitch = TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".navigator.switch.tooltip"); private final MutableComponent tooltipGlobalSettings = TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".navigator.global_settings.tooltip"); - private final MutableComponent tooltipSearchSettings = TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".navigator.search_settings.tooltip"); + //private final MutableComponent tooltipUserProfile = TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".navigator.my_profile"); + private final MutableComponent tooltipSavedRoutes = TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".saved_routes.title"); + private final MutableComponent tooltipScheduleViewer = TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".schedule_board.title"); - @SuppressWarnings("resource") - public NavigatorScreen(Level level) { - super(TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".navigator.title")); + public NavigatorScreen(Screen lastScreen) { + super(lastScreen, TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".navigator.title"), BarColor.GRAY); this.instance = this; - this.level = level; - this.shadowlessFont = new NoShadowFontWrapper(Minecraft.getInstance().font); - } - - @Override - public UUID getJourneyListenerClientId() { - return clientId; } private void generateRouteEntries() { - generatingRouteEntries = true; - routesCollection.components.clear(); - - if (routes != null && routes.length > 0) { - for (int i = 0; i < routes.length; i++) { - SimpleRoute route = routes[i]; - AbstractWidget w = new RouteEntryOverviewWidget(instance, level, lastRefreshedTime, guiLeft + 26, guiTop + 67 + ENTRIES_START_Y_OFFSET + (i * (RouteEntryOverviewWidget.HEIGHT + ENTRY_SPACING)), route, (btn) -> {}); - routesCollection.components.add(w); - } - } - generatingRouteEntries = false; + } - private void clearSuggestions() { - if (destinationSuggestions != null) { - destinationSuggestions.getEditBox().setSuggestion(""); - } - destinationSuggestions = null; - } - - private void setLastRefreshedTime() { - lastRefreshedTime = level.getDayTime(); - } - @Override public boolean isPauseScreen() { return false; @@ -161,12 +119,21 @@ public boolean isPauseScreen() { @Override public void onClose() { - JourneyListenerManager.getInstance().removeClientListenerForAll(this); super.onClose(); + clearRoutes(); + } + + @Override + public void removed() { + super.removed(); + } + + public synchronized void clearRoutes() { + routes.stream().forEach(x -> x.close()); + routes.clear(); } private void switchButtonClick() { - minecraft.getSoundManager().play(SimpleSoundInstance.forUI(SoundEvents.UI_BUTTON_CLICK, 1.0F)); String fromInput = fromBox.getValue(); String toInput = toBox.getValue(); @@ -174,254 +141,251 @@ private void switchButtonClick() { toBox.setValue(fromInput); } + @SuppressWarnings("resource") @Override protected void init() { super.init(); + setAllowedLayer(0); initialized = false; - guiLeft = this.width / 2 - GUI_WIDTH / 2; - guiTop = this.height / 2 - GUI_HEIGHT / 2; - switchButtonsArea = new GuiAreaDefinition(guiLeft + 190, guiTop + 34, 11, 12); - addTooltip(DLTooltip.of(tooltipSwitch).assignedTo(switchButtonsArea)); - - locationButton = this.addRenderableWidget(new DLCreateIconButton(guiLeft + 208, guiTop + 20, DEFAULT_ICON_BUTTON_WIDTH, DEFAULT_ICON_BUTTON_HEIGHT, ModGuiIcons.POSITION.getAsCreateIcon()) { - @Override - public void onClick(double mouseX, double mouseY) { - super.onClick(mouseX, mouseY); - long id = InstanceManager.registerClientNearestStationResponseAction((result) -> { - if (result.aliasName.isPresent()) { - fromBox.setValue(result.aliasName.get().getAliasName().get()); - } - }); - CreateRailwaysNavigator.net().CHANNEL.sendToServer(new NearestStationRequestPacket(id, minecraft.player.position())); - } + DataAccessor.getFromServer(true, ModAccessorTypes.GET_ALL_STATIONS_AS_TAGS, (names) -> { + this.stationNames.clear(); + this.stationNames.addAll(names); }); - addTooltip(DLTooltip.of(tooltipLocation).assignedTo(locationButton)); - searchButton = this.addRenderableWidget(new DLCreateIconButton(guiLeft + 208, guiTop + 42, DEFAULT_ICON_BUTTON_WIDTH, DEFAULT_ICON_BUTTON_HEIGHT, AllIcons.I_MTD_SCAN) { + locationButton = this.addRenderableWidget(new DLCreateIconButton(guiLeft + 195, guiTop + 20, DEFAULT_ICON_BUTTON_WIDTH, DEFAULT_ICON_BUTTON_HEIGHT, ModGuiIcons.POSITION.getAsCreateIcon()) { @Override public void onClick(double mouseX, double mouseY) { super.onClick(mouseX, mouseY); - - if (stationFrom == null || stationTo == null || stationFrom.isBlank() || stationTo.isBlank()) { - Minecraft.getInstance().getToasts().addToast(new SystemToast(SystemToastIds.PERIODIC_NOTIFICATION, errorTitle, startEndNullText)); - return; - } - - if (stationFrom.equals(stationTo)) { - Minecraft.getInstance().getToasts().addToast(new SystemToast(SystemToastIds.PERIODIC_NOTIFICATION, errorTitle, startEndEqualText)); - return; - } - - isLoadingRoutes = true; - - long id = InstanceManager.registerClientNavigationResponseAction((routes, data) -> { - JourneyListenerManager.getInstance().removeClientListenerForAll(instance); - - instance.routes = routes.toArray(SimpleRoute[]::new); - setLastRefreshedTime(); - generateRouteEntries(); - isLoadingRoutes = false; - - for (SimpleRoute route : instance.routes) { - UUID listenerId = route.listen(instance); - JourneyListenerManager.getInstance().get(listenerId, instance).start(); + DataAccessor.getFromServer(minecraft.player.blockPosition(), ModAccessorTypes.GET_NEAREST_STATION, (result) -> { + if (result.tagName.isPresent()) { + fromBox.setValue(result.tagName.get().get()); } }); - scroll.chase(0, 0.7f, Chaser.EXP); - CreateRailwaysNavigator.net().CHANNEL.sendToServer(new NavigationRequestPacket(id, stationFrom, stationTo)); - } }); - addTooltip(DLTooltip.of(tooltipSearch).assignedTo(searchButton)); + addTooltip(DLTooltip.of(tooltipLocation).assignedTo(locationButton)); - fromBox = addEditBox(guiLeft + 50, guiTop + 25, 157, 12, stationFrom, TextUtils.empty(), false, (v) -> { + fromBox = addEditBox(guiLeft + 32 + 5, guiTop + 25, 157, 12, stationFrom, TextUtils.empty(), false, (v) -> { if (!initialized) { return; } stationFrom = v; updateEditorSubwidgets(fromBox); }, NO_EDIT_BOX_FOCUS_CHANGE_ACTION, null); - fromBox.setMaxLength(25); - fromBox.setTextColor(0xFFFFFF); + fromBox.setMaxLength(StationTag.MAX_NAME_LENGTH); - toBox = addEditBox(guiLeft + 50, guiTop + 47, 157, 12, stationTo, TextUtils.empty(), false, (v) -> { + toBox = addEditBox(guiLeft + 32 + 5, guiTop + 47, 157, 12, stationTo, TextUtils.empty(), false, (v) -> { if (!initialized) { return; } stationTo = v; updateEditorSubwidgets(toBox); }, NO_EDIT_BOX_FOCUS_CHANGE_ACTION, null); - toBox.setMaxLength(25); - toBox.setTextColor(0xFFFFFF); + toBox.setMaxLength(StationTag.MAX_NAME_LENGTH); - goToTopButton = this.addRenderableWidget(new DLCreateIconButton(guiLeft + GUI_WIDTH - 10, guiTop + AREA_Y, DEFAULT_ICON_BUTTON_WIDTH, DEFAULT_ICON_BUTTON_HEIGHT, AllIcons.I_PRIORITY_VERY_HIGH) { - @Override - public void onClick(double mouseX, double mouseY) { - super.onClick(mouseX, mouseY); - scroll.chase(0, 0.7f, Chaser.EXP); - } - }); - addTooltip(DLTooltip.of(Constants.TOOLTIP_GO_TO_TOP).assignedTo(goToTopButton)); // Global Options Button if (minecraft.player.hasPermissions(ModCommonConfig.GLOBAL_SETTINGS_PERMISSION_LEVEL.get())) { - globalSettingsButton = this.addRenderableWidget(new DLCreateIconButton(guiLeft + 43, guiTop + 222, DEFAULT_ICON_BUTTON_WIDTH, DEFAULT_ICON_BUTTON_HEIGHT, ModGuiIcons.SETTINGS.getAsCreateIcon()) { + globalSettingsButton = this.addRenderableWidget(new DLCreateIconButton(guiLeft + 30, guiTop + 223, DEFAULT_ICON_BUTTON_WIDTH, DEFAULT_ICON_BUTTON_HEIGHT, ModGuiIcons.SETTINGS.getAsCreateIcon()) { @Override public void onClick(double mouseX, double mouseY) { super.onClick(mouseX, mouseY); - minecraft.setScreen(new GlobalSettingsScreen(level, instance)); + minecraft.setScreen(new GlobalSettingsScreen(instance)); } }); addTooltip(DLTooltip.of(tooltipGlobalSettings).assignedTo(globalSettingsButton)); } - searchSettingsButton = this.addRenderableWidget(new DLCreateIconButton(guiLeft + 21, guiTop + 222, DEFAULT_ICON_BUTTON_WIDTH, DEFAULT_ICON_BUTTON_HEIGHT, ModGuiIcons.FILTER.getAsCreateIcon()) { + /* + DLCreateIconButton userProfileBtn = this.addRenderableWidget(new DLCreateIconButton(guiLeft + GUI_WIDTH - DEFAULT_ICON_BUTTON_WIDTH - 8, guiTop + 223, DEFAULT_ICON_BUTTON_WIDTH, DEFAULT_ICON_BUTTON_HEIGHT, ModGuiIcons.USER.getAsCreateIcon()) { + @Override + public void onClick(double mouseX, double mouseY) { + super.onClick(mouseX, mouseY); + } + }); + addTooltip(DLTooltip.of(tooltipUserProfile).assignedTo(userProfileBtn)); + */ + + DLCreateIconButton savedRoutes = this.addRenderableWidget(new DLCreateIconButton(guiLeft + GUI_WIDTH - DEFAULT_ICON_BUTTON_WIDTH - 8, guiTop + 223, DEFAULT_ICON_BUTTON_WIDTH, DEFAULT_ICON_BUTTON_HEIGHT, ModGuiIcons.MAP_PATH.getAsCreateIcon()) { + @Override + public void onClick(double mouseX, double mouseY) { + super.onClick(mouseX, mouseY); + minecraft.setScreen(new SavedRoutesScreen(instance)); + } + }); + addTooltip(DLTooltip.of(tooltipSavedRoutes).assignedTo(savedRoutes)); + + DLCreateIconButton scheduleBoardBtn = this.addRenderableWidget(new DLCreateIconButton(guiLeft + GUI_WIDTH - DEFAULT_ICON_BUTTON_WIDTH - 30, guiTop + 223, DEFAULT_ICON_BUTTON_WIDTH, DEFAULT_ICON_BUTTON_HEIGHT, ModGuiIcons.VERY_DETAILED.getAsCreateIcon()) { @Override public void onClick(double mouseX, double mouseY) { super.onClick(mouseX, mouseY); - minecraft.setScreen(new SearchSettingsScreen(level, instance)); + minecraft.setScreen(new ScheduleBoardScreen(instance, null)); } }); - addTooltip(DLTooltip.of(tooltipSearchSettings).assignedTo(searchSettingsButton)); + addTooltip(DLTooltip.of(tooltipScheduleViewer).assignedTo(scheduleBoardBtn)); + - this.addRenderableWidget(new DLCreateIconButton(guiLeft + GUI_WIDTH - 42, guiTop + 222, DEFAULT_ICON_BUTTON_WIDTH, DEFAULT_ICON_BUTTON_HEIGHT, AllIcons.I_MTD_CLOSE) { + DLIconButton btn = addRenderableWidget((new DLIconButton(ButtonType.DEFAULT, AreaStyle.FLAT, new Sprite(CRNGui.GUI, CRNGui.GUI_WIDTH, CRNGui.GUI_HEIGHT, 55, 0, 9, 12), guiLeft + 176, guiTop + 33, 13, 14, TextUtils.empty(), (b) -> switchButtonClick()))); + addTooltip(DLTooltip.of(tooltipSwitch).assignedTo(btn)); + btn.setBackColor(0x00000000); + + ModernVerticalScrollBar scrollBar = new ModernVerticalScrollBar(this, guiLeft + GUI_WIDTH - 8, guiTop + 88, 128, GuiAreaDefinition.empty()); + routeViewer = addRenderableWidget(new RouteViewer(this, guiLeft + 3, guiTop + 88, GUI_WIDTH - 6, 128, scrollBar)); + addRenderableWidget(scrollBar); + DLUtils.doIfNotNull(routeViewer, x -> x.displayRoutes(ImmutableList.copyOf(routes))); + + searchButton = this.addRenderableWidget(new DLCreateIconButton(guiLeft + 195, guiTop + 42, DEFAULT_ICON_BUTTON_WIDTH, DEFAULT_ICON_BUTTON_HEIGHT, AllIcons.I_MTD_SCAN) { @Override public void onClick(double mouseX, double mouseY) { super.onClick(mouseX, mouseY); - onClose(); + + if (stationFrom == null || stationTo == null || stationFrom.isBlank() || stationTo.isBlank()) { + Minecraft.getInstance().getToasts().addToast(new SystemToast(SystemToastIds.PERIODIC_NOTIFICATION, errorTitle, startEndNullText)); + return; + } + + if (stationFrom.equals(stationTo)) { + Minecraft.getInstance().getToasts().addToast(new SystemToast(SystemToastIds.PERIODIC_NOTIFICATION, errorTitle, startEndEqualText)); + return; + } + + isLoadingRoutes = true; + clearRoutes(); + routeViewer.clear(); + + DataAccessor.getFromServer(new NavigationData(stationFrom, stationTo, Minecraft.getInstance().player.getUUID()), ModAccessorTypes.NAVIGATE, (routeList) -> { + routes.addAll(routeList); + routeViewer.displayRoutes(ImmutableList.copyOf(routes)); + routeViewer.displayRoutes(routeList); + isLoadingRoutes = false; + + DataAccessor.getFromServer(null, ModAccessorTypes.ALL_TRAINS_INITIALIZED, (result) -> { + DLUtils.doIfNotNull(notificationPopup, x -> x.close()); + if (!result) notificationPopup = addRenderableWidget(new NotificationTrainInitialization(instance, guiLeft + 10, guiTop + GUI_HEIGHT - FooterSize.DEFAULT.size() - 20, GUI_WIDTH - 20, instance::removeWidget)); + }); + }); } }); + addTooltip(DLTooltip.of(tooltipSearch).assignedTo(searchButton)); + + // Search Options + final int btnCount = 3; + int btnWidth = (GUI_WIDTH - 6 - 16) / btnCount; + addRenderableWidget(new SearchOptionButton(guiLeft + 3, guiTop + 54 + FooterSize.DEFAULT.size() - 2, btnWidth, 18, TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".search_options.departure_in"), () -> userSettings.navigationDepartureInTicks.toString(), (b) -> { + new FlyoutDepartureInWidget<>(this, FlyoutPointer.UP, ColorShade.DARK, this::addRenderableWidget, userSettings, () -> { + return userSettings.navigationDepartureInTicks; + }, (w) -> { + removeWidget(w); + reloadUserSettings(); + }).open(b); + })); + addRenderableWidget(new SearchOptionButton(guiLeft + 3 + btnWidth, guiTop + 54 + FooterSize.DEFAULT.size() - 2, btnWidth, 18, TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".search_options.transfer_time"), () -> userSettings.navigationTransferTime.toString(), (b) -> { + new FlyoutTransferTimeWidget<>(this, FlyoutPointer.UP, ColorShade.DARK, this::addRenderableWidget, userSettings, () -> { + return userSettings.navigationTransferTime; + }, (w) -> { + removeWidget(w); + reloadUserSettings(); + }).open(b); + })); + addRenderableWidget(new SearchOptionButton(guiLeft + 3 + btnWidth * 2, guiTop + 54 + FooterSize.DEFAULT.size() - 2, btnWidth, 18, TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".search_options.train_groups"), () -> userSettings.navigationExcludedTrainGroups.toString(), (b) -> { + new FlyoutTrainGroupsWidget<>(this, FlyoutPointer.UP, ColorShade.DARK, this::addRenderableWidget, userSettings, () -> { + return userSettings.navigationExcludedTrainGroups; + }, (w) -> { + removeWidget(w); + reloadUserSettings(); + }).open(b); + })); + DLIconButton moreSearchOptionsBtn = addRenderableWidget(new DLIconButton(ButtonType.DEFAULT, AreaStyle.FLAT, GuiIcons.ARROW_RIGHT.getAsSprite(16, 16), guiLeft + GUI_WIDTH - 3 - (GUI_WIDTH - btnWidth * 3 - 6), guiTop + 54 + FooterSize.DEFAULT.size() - 2, (GUI_WIDTH - btnWidth * 3 - 6), 18, TextUtils.empty(), + (b) -> { + new FlyoutAdvancedSearchsettingsWidget<>(this, FlyoutPointer.UP, ColorShade.DARK, this::addRenderableWidget, (w) -> { + removeWidget(w); + }).open(b); + })); + moreSearchOptionsBtn.setBackColor(0x00000000); generateRouteEntries(); + reloadUserSettings(); initialized = true; } - protected void updateEditorSubwidgets(DLEditBox field) { - clearSuggestions(); + @SuppressWarnings("resource") + private void reloadUserSettings() { + DataAccessor.getFromServer(Minecraft.getInstance().player.getUUID(), ModAccessorTypes.GET_USER_SETTINGS, settings -> this.userSettings = settings); + } + + protected void updateEditorSubwidgets(DLEditBox field) { + updateEditorSubwidgetsInternal(field, getViableStations(stationNames)); + } - destinationSuggestions = new ModDestinationSuggestions(this.minecraft, this, field, this.font, getViableStations(field), field.getHeight() + 2 + field.y); + protected void updateEditorSubwidgetsInternal(DLEditBox field, List list) { + clearSuggestions(); + destinationSuggestions = new ModDestinationSuggestions(this.minecraft, this, field, this.font, list, field.getHeight() + 2 + field.y()); destinationSuggestions.setAllowSuggestions(true); destinationSuggestions.updateCommandInfo(); } - private List getViableStations(DLEditBox field) { - return ClientTrainStationSnapshot.getInstance().getAllTrainStations().stream() - .map(x -> GlobalSettingsManager.getInstance().getSettingsData().getAliasFor(x)) + private List getViableStations(Collection src) { + return src.stream() .distinct() - .filter(x -> !GlobalSettingsManager.getInstance().getSettingsData().isBlacklisted(x)) - .sorted((a, b) -> a.getAliasName().get().compareTo(b.getAliasName().get())) + .sorted((a, b) -> a.getTagName().get().compareToIgnoreCase(b.getTagName().get())) .toList(); } + private void clearSuggestions() { + if (destinationSuggestions != null) { + destinationSuggestions.getEditBox().setSuggestion(""); + } + destinationSuggestions = null; + } + @Override public void tick() { - angle += 6; - if (angle > 360) { - angle = 0; - } scroll.tickChaser(); - if (destinationSuggestions != null) { - destinationSuggestions.tick(); + DLUtils.doIfNotNull(destinationSuggestions, x -> { + x.tick(); if (!toBox.canConsumeInput() && !fromBox.canConsumeInput()) { clearSuggestions(); } - } - - if (goToTopButton != null) { - this.goToTopButton.visible = routes != null && scroll.getValue() > 0; - } + }); - searchButton.active = !isLoadingRoutes; + DLUtils.doIfNotNull(searchButton, x -> x.set_active(!isLoadingRoutes)); super.tick(); } - protected void startStencil(PoseStack matrixStack, float x, float y, float w, float h) { - RenderSystem.clear(GL30.GL_STENCIL_BUFFER_BIT | GL30.GL_DEPTH_BUFFER_BIT, Minecraft.ON_OSX); - - GL11.glDisable(GL11.GL_STENCIL_TEST); - RenderSystem.stencilMask(~0); - RenderSystem.clear(GL11.GL_STENCIL_BUFFER_BIT, Minecraft.ON_OSX); - GL11.glEnable(GL11.GL_STENCIL_TEST); - RenderSystem.stencilOp(GL11.GL_REPLACE, GL11.GL_KEEP, GL11.GL_KEEP); - RenderSystem.stencilMask(0xFF); - RenderSystem.stencilFunc(GL11.GL_NEVER, 1, 0xFF); - - matrixStack.pushPose(); - matrixStack.translate(x, y, 0); - matrixStack.scale(w, h, 1); - io.github.fabricators_of_create.porting_lib.util.client.GuiUtils.drawGradientRect(matrixStack.last().pose(), -100, 0, 0, 1, 1, 0xff000000, 0xff000000); - matrixStack.popPose(); - - GL11.glEnable(GL11.GL_STENCIL_TEST); - RenderSystem.stencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_KEEP); - RenderSystem.stencilFunc(GL11.GL_EQUAL, 1, 0xFF); - } - - protected void endStencil() { - GL11.glDisable(GL11.GL_STENCIL_TEST); - } - @Override public void renderMainLayer(Graphics graphics, int pMouseX, int pMouseY, float pPartialTick) { pPartialTick = minecraft.getFrameTime(); - float scrollOffset = -scroll.getValue(pPartialTick); + angle += 6 * pPartialTick; + if (angle > 360) { + angle = 0; + } + + renderNavigatorBackground(graphics, pMouseX, pMouseY, pPartialTick); - renderScreenBackground(graphics); - GuiUtils.drawTexture(GUI, graphics, guiLeft, guiTop, 0, 0, GUI_WIDTH, GUI_HEIGHT); - - //routesCollection.performForEach(x -> x.render(graphics.poseStack(), pMouseX, pMouseY, pPartialTick)); - super.renderMainLayer(graphics, pMouseX, pMouseY, pPartialTick); + int y = FooterSize.DEFAULT.size() - 1; + CreateDynamicWidgets.renderContainer(graphics, guiLeft + 1, guiTop + y, GUI_WIDTH - 2, 52, ContainerColor.BLUE); + CreateDynamicWidgets.renderContainer(graphics, guiLeft + 1, guiTop + y + 52 - 1, GUI_WIDTH - 2, 22, ContainerColor.GOLD); + y += 52 + 22 - 2; + CreateDynamicWidgets.renderContainer(graphics, guiLeft + 1, guiTop + y, GUI_WIDTH - 2, GUI_HEIGHT - y - FooterSize.SMALL.size() + 1, ContainerColor.GRAY); - GuiUtils.drawString(graphics, shadowlessFont, guiLeft + 19, guiTop + 4, title, 0x4F4F4F, EAlignment.LEFT, false); - String timeString = TimeUtils.parseTime((int)((level.getDayTime() + DragonLib.DAYTIME_SHIFT) % DragonLib.TICKS_PER_DAY), ModClientConfig.TIME_FORMAT.get()); - GuiUtils.drawString(graphics, shadowlessFont, guiLeft + GUI_WIDTH - 22 - shadowlessFont.width(timeString), guiTop + 4, TextUtils.text(timeString), 0x4F4F4F, EAlignment.LEFT, false); + CreateDynamicWidgets.renderTextBox(graphics, guiLeft + 32, guiTop + 20, 159); + CreateDynamicWidgets.renderTextBox(graphics, guiLeft + 32, guiTop + 42, 159); + GuiUtils.drawTexture(CRNGui.GUI, graphics, guiLeft + 16, guiTop + 16, 7, 24, 0, 30, 7, 24, CRNGui.GUI_WIDTH, CRNGui.GUI_HEIGHT); + GuiUtils.drawTexture(CRNGui.GUI, graphics, guiLeft + 16, guiTop + 16 + 24, 7, 24, 7, 30, 7, 24, CRNGui.GUI_WIDTH, CRNGui.GUI_HEIGHT); + if (!isLoadingRoutes && !generatingRouteEntries) { if (routes == null) { GuiUtils.drawString(graphics, font, guiLeft + GUI_WIDTH / 2, guiTop + 32 + GUI_HEIGHT / 2, notSearchedText, 0xFFFFFF, EAlignment.CENTER, false); ModGuiIcons.INFO.render(graphics, (int)(guiLeft + GUI_WIDTH / 2 - 8), (int)(guiTop + GUI_HEIGHT / 2)); - } else if (routes.length <= 0) { + } else if (routes.isEmpty()) { GuiUtils.drawString(graphics, font, guiLeft + GUI_WIDTH / 2, guiTop + 32 + GUI_HEIGHT / 2, noConnectionsText, 0xFFFFFF, EAlignment.CENTER, false); AllIcons.I_ACTIVE.render(graphics.poseStack(), (int)(guiLeft + GUI_WIDTH / 2 - 8), (int)(guiTop + GUI_HEIGHT / 2)); - } else { - //GuiUtils.swapAndBlitColor(minecraft.getMainRenderTarget(), GuiUtils.getFramebuffer()); - //GuiUtils.startStencil(graphics, guiLeft + AREA_X, guiTop + AREA_Y, AREA_W, AREA_H); - - //UIRenderHelper.swapAndBlitColor(minecraft.getMainRenderTarget(), UIRenderHelper.framebuffer); - //startStencil(graphics.poseStack(), guiLeft + AREA_X, guiTop + AREA_Y, AREA_W, AREA_H); - //graphics.poseStack().pushPose(); - GuiUtils.enableScissor(graphics, guiLeft + AREA_X, guiTop + AREA_Y, AREA_W, AREA_H); - graphics.poseStack().translate(0, scrollOffset, 0); - - int start = (int)(Math.abs(scrollOffset + ENTRIES_START_Y_OFFSET) / (ENTRY_SPACING + RouteEntryOverviewWidget.HEIGHT)); - int end = Math.min(routesCollection.components.size(), start + 2 + (int)(AREA_H / (ENTRY_SPACING + RouteEntryOverviewWidget.HEIGHT))); - for (int i = start; i < end; i++) { - routesCollection.components.get(i).render(graphics.poseStack(), (int)(pMouseX), (int)(pMouseY - scrollOffset), pPartialTick); - } - - //graphics.poseStack().popPose(); - //GuiUtils.endStencil(); - //GuiUtils.swapAndBlitColor(GuiUtils.getFramebuffer(), minecraft.getMainRenderTarget()); - //endStencil(); - //UIRenderHelper.swapAndBlitColor(UIRenderHelper.framebuffer, minecraft.getMainRenderTarget()); - GuiUtils.disableScissor(graphics); - GuiUtils.fillGradient(graphics, guiLeft + AREA_X, guiTop + AREA_Y, 0, AREA_W, 10, 0x77000000, 0x00000000); - GuiUtils.fillGradient(graphics, guiLeft + AREA_X, guiTop + AREA_Y + AREA_H - 10, 0, AREA_W, 10, 0x00000000, 0x77000000); - - // Scrollbar - double maxHeight = ENTRIES_START_Y_OFFSET + routes.length * (RouteEntryOverviewWidget.HEIGHT + 4) + ENTRIES_START_Y_OFFSET; - double aH = AREA_H + 1; - if (aH / maxHeight < 1) { - int scrollerHeight = Math.max(10, (int)(aH * (aH / maxHeight))); - int startY = guiTop + AREA_Y + (int)((AREA_H) * (Math.abs(scrollOffset) / maxHeight)); - - GuiUtils.fill(graphics, guiLeft + AREA_X + AREA_W - 3, startY, 3, scrollerHeight, 0x7FFFFFFF); - } } } else { double offsetX = Math.sin(Math.toRadians(angle)) * 5; @@ -430,10 +394,8 @@ public void renderMainLayer(Graphics graphics, int pMouseX, int pMouseY, float p GuiUtils.drawString(graphics, font, guiLeft + GUI_WIDTH / 2, guiTop + 32 + GUI_HEIGHT / 2, searchingText, 0xFFFFFF, EAlignment.CENTER, false); AllIcons.I_MTD_SCAN.render(graphics.poseStack(), (int)(guiLeft + GUI_WIDTH / 2 - 8 + offsetX), (int)(guiTop + GUI_HEIGHT / 2 + offsetY)); } - - if (switchButtonsArea.isInBounds(pMouseX, pMouseY)) { - GuiUtils.fill(graphics, switchButtonsArea.getLeft(), switchButtonsArea.getTop(), switchButtonsArea.getWidth(), switchButtonsArea.getHeight(), 0x3FFFFFFF); - } + + super.renderMainLayer(graphics, pMouseX, pMouseY, pPartialTick); } @Override @@ -452,20 +414,7 @@ public boolean mouseClicked(double pMouseX, double pMouseY, int pButton) { if (destinationSuggestions != null && destinationSuggestions.mouseClicked((int) pMouseX, (int) pMouseY, pButton)) return true; - float scrollOffset = scroll.getValue(); - - if (switchButtonsArea.isInBounds(pMouseX, pMouseY)) { - switchButtonClick(); - } - if (super.mouseClicked(pMouseX, pMouseY, pButton)) { - return true; - } - - if (pMouseX > guiLeft + AREA_X && pMouseX < guiLeft + AREA_X + AREA_W && pMouseY > guiTop + AREA_Y && pMouseY < guiTop + AREA_Y + AREA_H) { - routesCollection.performForEach(x -> x.mouseClicked(pMouseX, pMouseY + scrollOffset, pButton)); - } - - return false; + return super.mouseClicked(pMouseX, pMouseY, pButton); } @Override @@ -481,19 +430,6 @@ public boolean mouseScrolled(double pMouseX, double pMouseY, double pDelta) { if (destinationSuggestions != null && destinationSuggestions.mouseScrolled(pMouseX, pMouseY, Mth.clamp(pDelta, -1.0D, 1.0D))) return true; - float chaseTarget = scroll.getChaseTarget(); - float max = -AREA_H; - if (routes != null && routes.length > 0) { - max += ENTRIES_START_Y_OFFSET + routes.length * (RouteEntryOverviewWidget.HEIGHT + 4) + ENTRIES_START_Y_OFFSET; - } - - if (max > 0) { - chaseTarget -= pDelta * 12; - chaseTarget = Mth.clamp(chaseTarget, 0, max); - scroll.chase((int) chaseTarget, 0.7f, Chaser.EXP); - } else - scroll.chase(0, 0.7f, Chaser.EXP); - return super.mouseScrolled(pMouseX, pMouseY, pDelta); } diff --git a/common/src/main/java/de/mrjulsen/crn/client/gui/screen/RouteDetailsScreen.java b/common/src/main/java/de/mrjulsen/crn/client/gui/screen/RouteDetailsScreen.java index 8d5bb394..c376fc77 100644 --- a/common/src/main/java/de/mrjulsen/crn/client/gui/screen/RouteDetailsScreen.java +++ b/common/src/main/java/de/mrjulsen/crn/client/gui/screen/RouteDetailsScreen.java @@ -1,127 +1,72 @@ package de.mrjulsen.crn.client.gui.screen; -import java.util.UUID; +import java.util.LinkedHashMap; +import java.util.Map; -import com.simibubi.create.content.trains.entity.TrainIconType; -import com.simibubi.create.content.trains.station.NoShadowFontWrapper; -import com.simibubi.create.foundation.gui.AllIcons; -import com.simibubi.create.foundation.utility.animation.LerpedFloat; -import com.simibubi.create.foundation.utility.animation.LerpedFloat.Chaser; +import com.simibubi.create.foundation.gui.widget.IconButton; +import com.simibubi.create.foundation.gui.widget.Indicator.State; +import com.simibubi.create.foundation.item.TooltipHelper; +import com.simibubi.create.foundation.item.TooltipHelper.Palette; +import com.simibubi.create.foundation.utility.Components; -import de.mrjulsen.crn.Constants; import de.mrjulsen.crn.CreateRailwaysNavigator; +import de.mrjulsen.crn.client.gui.CreateDynamicWidgets; +import de.mrjulsen.crn.client.gui.CreateDynamicWidgets.BarColor; +import de.mrjulsen.crn.client.gui.CreateDynamicWidgets.ContainerColor; +import de.mrjulsen.crn.client.gui.CreateDynamicWidgets.FooterSize; +import de.mrjulsen.crn.client.gui.overlay.RouteDetailsOverlay; import de.mrjulsen.crn.client.gui.ModGuiIcons; -import de.mrjulsen.crn.client.gui.overlay.RouteDetailsOverlayScreen; import de.mrjulsen.crn.client.gui.widgets.DLCreateIconButton; -import de.mrjulsen.crn.client.gui.widgets.ExpandButton; +import de.mrjulsen.crn.client.gui.widgets.DLCreateIndicator; +import de.mrjulsen.crn.client.gui.widgets.ModernVerticalScrollBar; +import de.mrjulsen.crn.client.gui.widgets.RouteDetailsViewer; import de.mrjulsen.crn.client.lang.ELanguage; -import de.mrjulsen.crn.config.ModClientConfig; -import de.mrjulsen.crn.data.SimpleRoute; -import de.mrjulsen.crn.data.SimpleRoute.SimpleRoutePart; -import de.mrjulsen.crn.data.SimpleRoute.StationEntry; -import de.mrjulsen.crn.data.SimpleRoute.StationTag; -import de.mrjulsen.crn.event.listeners.IJourneyListenerClient; -import de.mrjulsen.crn.event.listeners.JourneyListener; -import de.mrjulsen.crn.event.listeners.JourneyListenerManager; +import de.mrjulsen.crn.data.SavedRoutesManager; +import de.mrjulsen.crn.data.navigation.ClientRoute; +import de.mrjulsen.crn.event.ModCommonEvents; import de.mrjulsen.crn.network.InstanceManager; import de.mrjulsen.mcdragonlib.DragonLib; import de.mrjulsen.mcdragonlib.client.OverlayManager; -import de.mrjulsen.mcdragonlib.client.gui.DLScreen; +import de.mrjulsen.mcdragonlib.client.gui.widgets.DLIconButton; import de.mrjulsen.mcdragonlib.client.gui.widgets.DLTooltip; import de.mrjulsen.mcdragonlib.client.util.Graphics; import de.mrjulsen.mcdragonlib.client.util.GuiUtils; import de.mrjulsen.mcdragonlib.client.util.WidgetsCollection; import de.mrjulsen.mcdragonlib.core.EAlignment; import de.mrjulsen.mcdragonlib.data.Pair; -import de.mrjulsen.mcdragonlib.util.MathUtils; import de.mrjulsen.mcdragonlib.util.TextUtils; import de.mrjulsen.mcdragonlib.util.TimeUtils; -import de.mrjulsen.mcdragonlib.util.TimeUtils.TimeFormat; import net.minecraft.ChatFormatting; import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.Font; import net.minecraft.client.gui.screens.Screen; import net.minecraft.network.chat.Component; import net.minecraft.network.chat.MutableComponent; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.level.Level; -public class RouteDetailsScreen extends DLScreen implements IJourneyListenerClient { +public class RouteDetailsScreen extends AbstractNavigatorScreen { - private static final ResourceLocation GUI = new ResourceLocation(CreateRailwaysNavigator.MOD_ID, "textures/gui/route_details.png"); - private static final int GUI_WIDTH = 255; - private static final int GUI_HEIGHT = 247; - - private static final int DEFAULT_ICON_BUTTON_WIDTH = 18; - private static final int DEFAULT_ICON_BUTTON_HEIGHT = 18; - - private static final int ENTRIES_START_Y_OFFSET = 18; - private static final int ENTRY_WIDTH = 220; - private static final int ENTRY_TIME_X = 28; - private static final int ENTRY_DEST_X = 66; - - - private final int AREA_X = 16; - private final int AREA_Y = 53; - private final int AREA_W = 220; - private final int AREA_H = 157; - - private int guiLeft, guiTop; - private int scrollMax = 0; - - // Controls - private DLCreateIconButton backButton; - private DLCreateIconButton saveButton; - private LerpedFloat scroll = LerpedFloat.linear().startWithValue(0); - private final ExpandButton[] expandButtons; - private final WidgetsCollection expandButtonCollection = new WidgetsCollection(); - - // Data - private final SimpleRoute route; - private final Screen lastScreen; - private final Font font; - private final Font shadowlessFont; - private final Level level; - - // Tooltips private final MutableComponent textDeparture = ELanguage.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".route_details.departure"); - private final MutableComponent textTransferIn = ELanguage.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".route_details.next_transfer_time"); - private final MutableComponent transferText = ELanguage.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".route_details.transfer"); - private final MutableComponent textJourneyCompleted = ELanguage.translate("gui.createrailwaysnavigator.route_overview.journey_completed"); - private final MutableComponent timeNowText = ELanguage.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".time.now"); - private final MutableComponent textConnectionEndangered = ELanguage.translate("gui.createrailwaysnavigator.route_overview.connection_endangered").withStyle(ChatFormatting.GOLD).withStyle(ChatFormatting.BOLD); - private final MutableComponent textConnectionMissed = ELanguage.translate("gui.createrailwaysnavigator.route_overview.connection_missed").withStyle(ChatFormatting.RED).withStyle(ChatFormatting.BOLD); - private final MutableComponent textTrainCanceled = ELanguage.translate("gui.createrailwaysnavigator.route_overview.train_canceled").withStyle(ChatFormatting.RED).withStyle(ChatFormatting.BOLD); - private final MutableComponent textSaveRoute = TextUtils.translate("gui.createrailwaysnavigator.route_details.save_route"); - private final String keyTrainCancellationReason = "gui.createrailwaysnavigator.route_overview.train_cancellation_info"; - - private final UUID clientId = UUID.randomUUID(); - - @SuppressWarnings("resource") - public RouteDetailsScreen(Screen lastScreen, Level level, SimpleRoute route, UUID listenerId) { - super(TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".route_details.title")); - this.lastScreen = lastScreen; + private final MutableComponent timeNowText = ELanguage.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".time.now"); + private final MutableComponent tooltipSaveRoute = TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".route_details.save_route.tooltip"); + private final MutableComponent tooltipRemoveRoute = TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".route_details.remove_route.tooltip"); + private final MutableComponent tooltipShowPopup = TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".route_details.show_popup.tooltip"); + private final MutableComponent tooltipShowNotifications = TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".route_overlay_settings.notifications"); + private final MutableComponent tooltipShowNotificationsDescription = TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".route_overlay_settings.notifications.description").withStyle(ChatFormatting.GRAY); + + private final ClientRoute route; + + private RouteDetailsViewer viewer; + private DLCreateIconButton notificationButton; + private DLCreateIndicator notificationIndicator; + private DLCreateIconButton saveRouteBtn; + private DLTooltip saveBtnTooltip; + private DLCreateIconButton popupBtn; + + private final Map> buttonTooltips = new LinkedHashMap<>(); + private final WidgetsCollection buttons = new WidgetsCollection(); + + public RouteDetailsScreen(Screen lastScreen, ClientRoute route) { + super(lastScreen, TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".route_details.title"), BarColor.GOLD); this.route = route; - this.font = Minecraft.getInstance().font; - this.shadowlessFont = new NoShadowFontWrapper(font); - this.level = level; - JourneyListenerManager.getInstance().get(listenerId, this); - - int count = route.getParts().size(); - expandButtons = new ExpandButton[count]; - for (int i = 0; i < count; i++) { - expandButtons[i] = new ExpandButton(0, 0, false, (btn) -> {}); - expandButtonCollection.components.add(expandButtons[i]); - } - } - - @Override - public UUID getJourneyListenerClientId() { - return clientId; - } - - public int getCurrentTime() { - return (int)(level.getDayTime() % DragonLib.TICKS_PER_DAY); } @Override @@ -131,339 +76,119 @@ public boolean isPauseScreen() { @Override public void onClose() { - this.minecraft.setScreen(lastScreen); - JourneyListenerManager.getInstance().removeClientListenerForAll(this); + Minecraft.getInstance().setScreen(lastScreen); } @Override protected void init() { - super.init(); - guiLeft = this.width / 2 - GUI_WIDTH / 2; - guiTop = this.height / 2 - GUI_HEIGHT / 2; - + super.init(); + buttons.clear(); + buttonTooltips.clear(); final int fWidth = width; final int fHeight = height; + int dy = FooterSize.DEFAULT.size() + 38; + ModernVerticalScrollBar scrollBar = new ModernVerticalScrollBar(this, guiLeft + GUI_WIDTH - 8, guiTop + dy, GUI_HEIGHT - dy - FooterSize.SMALL.size() - 1, null); + viewer = new RouteDetailsViewer(this, guiLeft + 3, guiTop + dy, GUI_WIDTH - 6, GUI_HEIGHT - dy - FooterSize.SMALL.size() - 1, scrollBar); + addRenderableWidget(viewer); + addRenderableWidget(scrollBar); + viewer.displayRoute(route); + - backButton = this.addRenderableWidget(new DLCreateIconButton(guiLeft + 21, guiTop + 222, DEFAULT_ICON_BUTTON_WIDTH, DEFAULT_ICON_BUTTON_HEIGHT, AllIcons.I_CONFIG_BACK) { + saveRouteBtn = this.addRenderableWidget(new DLCreateIconButton(guiLeft + 30, guiTop + 223, DLIconButton.DEFAULT_BUTTON_WIDTH, DLIconButton.DEFAULT_BUTTON_HEIGHT, SavedRoutesManager.isSaved(route) ? ModGuiIcons.BOOKMARK_FILLED.getAsCreateIcon() : ModGuiIcons.BOOKMARK.getAsCreateIcon()) { @Override public void onClick(double mouseX, double mouseY) { super.onClick(mouseX, mouseY); - onClose(); + removeTooltips(x -> x == saveBtnTooltip); + addTooltip(DLTooltip.of(SavedRoutesManager.isSaved(route) ? tooltipRemoveRoute : tooltipSaveRoute).assignedTo(saveRouteBtn)); + if (SavedRoutesManager.isSaved(route)) { + SavedRoutesManager.removeRoute(route); + this.setIcon(ModGuiIcons.BOOKMARK.getAsCreateIcon()); + } else { + SavedRoutesManager.saveRoute(route); + this.setIcon(ModGuiIcons.BOOKMARK_FILLED.getAsCreateIcon()); + } + SavedRoutesManager.push(true, null); + boolean isSaved = SavedRoutesManager.isSaved(route); + notificationButton.set_visible(isSaved); + notificationIndicator.set_visible(isSaved); + route.setShowNotifications(isSaved); + } }); - addTooltip(DLTooltip.of(Constants.TOOLTIP_GO_BACK).assignedTo(backButton)); - - saveButton = this.addRenderableWidget(new DLCreateIconButton(guiLeft + 21 + DEFAULT_ICON_BUTTON_WIDTH + 4, guiTop + 222, DEFAULT_ICON_BUTTON_WIDTH, DEFAULT_ICON_BUTTON_HEIGHT, ModGuiIcons.BOOKMARK.getAsCreateIcon()) { + addTooltip(DLTooltip.of(SavedRoutesManager.isSaved(route) ? tooltipRemoveRoute : tooltipSaveRoute).assignedTo(saveRouteBtn)); + + popupBtn = this.addRenderableWidget(new DLCreateIconButton(guiLeft + 52, guiTop + 223, DLIconButton.DEFAULT_BUTTON_WIDTH, DLIconButton.DEFAULT_BUTTON_HEIGHT, ModGuiIcons.POPUP.getAsCreateIcon()) { @Override public void onClick(double mouseX, double mouseY) { super.onClick(mouseX, mouseY); - if (JourneyListenerManager.getInstance().exists(route.getListenerId())) { - InstanceManager.setRouteOverlay(OverlayManager.add(new RouteDetailsOverlayScreen(level, route, fWidth, fHeight))); - } + InstanceManager.setRouteOverlay(OverlayManager.add(new RouteDetailsOverlay(ModCommonEvents.getPhysicalLevel(), route, fWidth, fHeight))); } }); - addTooltip(DLTooltip.of(textSaveRoute).assignedTo(saveButton)); - } - - @Override - public void tick() { - super.tick(); - scroll.tickChaser(); + addTooltip(DLTooltip.of(tooltipShowPopup).assignedTo(popupBtn)); - saveButton.visible = JourneyListenerManager.getInstance().exists(route.getListenerId()); - } - - private Pair getStationInfo(StationEntry station) { - final boolean reachable = station.reachable(false); - MutableComponent timeText = TextUtils.text(TimeUtils.parseTime((int)((route.getRefreshTime() + DragonLib.DAYTIME_SHIFT) % 24000 + station.getTicks()), ModClientConfig.TIME_FORMAT.get())); - MutableComponent stationText = TextUtils.text(station.getStationName()); - if (!reachable) { - timeText = timeText.withStyle(ChatFormatting.RED).withStyle(ChatFormatting.STRIKETHROUGH); - stationText = stationText.withStyle(ChatFormatting.RED).withStyle(ChatFormatting.STRIKETHROUGH); - } - - return Pair.of(timeText, stationText); - } - - private int renderRouteStart(Graphics graphics, int x, int y, StationEntry stop) { - final int HEIGHT = 30; - final int V = 48; - - GuiUtils.drawTexture(Constants.GUI_WIDGETS, graphics, x, y, 0, V, ENTRY_WIDTH, HEIGHT); - - Pair text = getStationInfo(stop); - float scale = shadowlessFont.width(text.getFirst()) > 30 ? 0.75f : 1; - graphics.poseStack().pushPose(); - graphics.poseStack().scale(scale, 1, 1); - int pY = y + 15; - if (stop.shouldRenderRealtime() && stop.reachable(false)) { - pY -= (stop.shouldRenderRealtime() ? 5 : 0); - GuiUtils.drawString(graphics, shadowlessFont, (int)((x + ENTRY_TIME_X) / scale), pY + 10, TextUtils.text(TimeUtils.parseTime((int)(stop.getEstimatedTimeWithThreshold() % 24000 + DragonLib.DAYTIME_SHIFT), ModClientConfig.TIME_FORMAT.get())), stop.getDifferenceTime() > ModClientConfig.DEVIATION_THRESHOLD.get() ? Constants.COLOR_DELAYED : Constants.COLOR_ON_TIME, EAlignment.LEFT, false); - } - GuiUtils.drawString(graphics, shadowlessFont, (int)((x + ENTRY_TIME_X) / scale), pY, text.getFirst(), 0xFFFFFF, EAlignment.LEFT, false); - graphics.poseStack().popPose(); - - Component platformText = TextUtils.text(stop.getUpdatedInfo().platform()); - GuiUtils.drawString(graphics, shadowlessFont, x + ENTRY_DEST_X + 129 - shadowlessFont.width(platformText), y + 15, platformText, stop.stationInfoChanged() ? Constants.COLOR_DELAYED : 0xFFFFFF, EAlignment.LEFT, false); - - MutableComponent name = text.getSecond(); - int maxTextWidth = 135 - 12 - shadowlessFont.width(platformText); - if (shadowlessFont.width(name) > maxTextWidth) { - name = TextUtils.text(shadowlessFont.substrByWidth(name, maxTextWidth - 3).getString()).append(Constants.ELLIPSIS_STRING); - } - GuiUtils.drawString(graphics, shadowlessFont, x + ENTRY_DEST_X, y + 15, name, 0xFFFFFF, EAlignment.LEFT, false); - - return HEIGHT; - } - - private int renderTrainDetails(Graphics graphics, int x, int y, SimpleRoutePart part) { - final int HEIGHT = 43; - final int V = 99; - final float scale = 0.75f; - final float mul = 1 / scale; - - GuiUtils.drawTexture(Constants.GUI_WIDGETS, graphics, x, y, 0, V, ENTRY_WIDTH, HEIGHT); - part.getTrainIcon().render(TrainIconType.ENGINE, graphics.poseStack(), x + ENTRY_DEST_X, y + 7); - - graphics.poseStack().pushPose(); - graphics.poseStack().scale(scale, scale, scale); - GuiUtils.drawString(graphics, shadowlessFont, (int)((x + ENTRY_DEST_X + 24) / scale), (int)((y + 7) / scale), TextUtils.text(String.format("%s (%s)", part.getTrainName(), part.getTrainID().toString().split("-")[0])), 0xDBDBDB, EAlignment.LEFT, false); - GuiUtils.drawString(graphics, shadowlessFont, (int)((x + ENTRY_DEST_X + 24) / scale), (int)((y + 17) / scale), TextUtils.text(String.format("→ %s", part.getScheduleTitle())), 0xDBDBDB, EAlignment.LEFT, false); - graphics.poseStack().scale(mul, mul, mul); - graphics.poseStack().popPose(); - - return HEIGHT; - } - - private int renderStop(Graphics graphics, int x, int y, StationEntry stop) { - final int HEIGHT = 21; - final int V = 78; - - GuiUtils.drawTexture(Constants.GUI_WIDGETS, graphics, x, y, 0, V, ENTRY_WIDTH, HEIGHT); - - Pair text = getStationInfo(stop); - float scale = shadowlessFont.width(text.getFirst()) > 30 ? 0.75f : 1; - graphics.poseStack().pushPose(); - graphics.poseStack().scale(scale, 1, 1); - int pY = y + 6; - if (stop.shouldRenderRealtime() && stop.reachable(false)) { - pY -= (stop.shouldRenderRealtime() ? 5 : 0); - GuiUtils.drawString(graphics, shadowlessFont, (int)((x + ENTRY_TIME_X) / scale), pY + 10, TextUtils.text(TimeUtils.parseTime((int)(stop.getEstimatedTimeWithThreshold() % 24000 + DragonLib.DAYTIME_SHIFT), ModClientConfig.TIME_FORMAT.get())), stop.getDifferenceTime() > ModClientConfig.DEVIATION_THRESHOLD.get() ? Constants.COLOR_DELAYED : Constants.COLOR_ON_TIME, EAlignment.LEFT, false); - } - GuiUtils.drawString(graphics, shadowlessFont, (int)((x + ENTRY_TIME_X) / scale), pY, text.getFirst(), 0xFFFFFF, EAlignment.LEFT, false); - graphics.poseStack().popPose(); - - Component platformText = TextUtils.text(stop.getUpdatedInfo().platform()); - GuiUtils.drawString(graphics, shadowlessFont, x + ENTRY_DEST_X + 129 - shadowlessFont.width(platformText), y + 6, platformText, stop.stationInfoChanged() ? Constants.COLOR_DELAYED : 0xFFFFFF, EAlignment.LEFT, false); - - MutableComponent name = text.getSecond(); - int maxTextWidth = 135 - 12 - shadowlessFont.width(platformText); - if (shadowlessFont.width(name) > maxTextWidth) { - name = TextUtils.text(shadowlessFont.substrByWidth(name, maxTextWidth - 3).getString()).append(Constants.ELLIPSIS_STRING); - } - GuiUtils.drawString(graphics, shadowlessFont, x + ENTRY_DEST_X, y + 6, name, 0xFFFFFF, EAlignment.LEFT, false); - - return HEIGHT; - } - - private int renderRouteEnd(Graphics graphics, int x, int y, StationEntry stop) { - final int HEIGHT = 44; - final int V = 142; - - GuiUtils.drawTexture(Constants.GUI_WIDGETS, graphics, x, y, 0, V, ENTRY_WIDTH, HEIGHT); - - Pair text = getStationInfo(stop); - float scale = shadowlessFont.width(text.getFirst()) > 30 ? 0.75f : 1; - graphics.poseStack().pushPose(); - graphics.poseStack().scale(scale, 1, 1); - int pY = y + 21; - if (stop.shouldRenderRealtime() && stop.reachable(false)) { - pY -= (stop.shouldRenderRealtime() ? 5 : 0); - GuiUtils.drawString(graphics, shadowlessFont, (int)((x + ENTRY_TIME_X) / scale), pY + 10, TextUtils.text(TimeUtils.parseTime((int)(stop.getEstimatedTimeWithThreshold() % 24000 + DragonLib.DAYTIME_SHIFT), ModClientConfig.TIME_FORMAT.get())), stop.getDifferenceTime() > ModClientConfig.DEVIATION_THRESHOLD.get() ? Constants.COLOR_DELAYED : Constants.COLOR_ON_TIME, EAlignment.LEFT, false); - } - GuiUtils.drawString(graphics, shadowlessFont, (int)((x + ENTRY_TIME_X) / scale), pY, text.getFirst(), 0xFFFFFF, EAlignment.LEFT, false); - graphics.poseStack().popPose(); - - Component platformText = TextUtils.text(stop.getUpdatedInfo().platform()); - GuiUtils.drawString(graphics, shadowlessFont, x + ENTRY_DEST_X + 129 - shadowlessFont.width(platformText), y + 21, platformText, stop.stationInfoChanged() ? Constants.COLOR_DELAYED : 0xFFFFFF, EAlignment.LEFT, false); - - MutableComponent name = text.getSecond(); - int maxTextWidth = 135 - 12 - shadowlessFont.width(platformText); - if (shadowlessFont.width(name) > maxTextWidth) { - name = TextUtils.text(shadowlessFont.substrByWidth(name, maxTextWidth - 3).getString()).append(Constants.ELLIPSIS_STRING); - } - GuiUtils.drawString(graphics, shadowlessFont, x + ENTRY_DEST_X, y + 21, name, 0xFFFFFF, EAlignment.LEFT, false); - - return HEIGHT; - } - - private void renderHeadline(Graphics graphics, int pMouseX, int pMouseY) { - Component titleInfo = TextUtils.empty(); - Component headline = TextUtils.empty(); - JourneyListener listener = JourneyListenerManager.getInstance().get(route.getListenerId(), null); - - // Title - if (!route.isValid()) { - titleInfo = TextUtils.translate(keyTrainCancellationReason, route.getInvalidationTrainName()).withStyle(ChatFormatting.RED); - headline = textTrainCanceled; - } else if (listener != null && listener.getIndex() > 0) { - titleInfo = textTransferIn; - long arrivalTime = listener.currentStation().getParent().getEndStation().getEstimatedTimeWithThreshold(); - int time = (int)(arrivalTime % 24000 - getCurrentTime()); - headline = time < 0 || listener.currentStation().getTag() == StationTag.PART_START ? timeNowText : TextUtils.text(TimeUtils.parseTime(time, TimeFormat.HOURS_24)); - } else if (listener == null) { - titleInfo = TextUtils.empty(); - headline = textJourneyCompleted.withStyle(ChatFormatting.GREEN); - } else { - titleInfo = textDeparture; - int departureTicks = route.getStartStation().getTicks(); - int departureTime = (int)(route.getRefreshTime() % 24000 + departureTicks); - headline = departureTime - getCurrentTime() < 0 ? timeNowText : TextUtils.text(TimeUtils.parseTime(departureTime - getCurrentTime(), TimeFormat.HOURS_24)); - } - - GuiUtils.drawString(graphics, font, guiLeft + GUI_WIDTH / 2, guiTop + 19, titleInfo, 0xFFFFFF, EAlignment.CENTER, false); - graphics.poseStack().pushPose(); - graphics.poseStack().scale(2, 2, 2); - GuiUtils.drawString(graphics, font, (guiLeft + GUI_WIDTH / 2) / 2, (guiTop + 31) / 2, headline, 0xFFFFFF, EAlignment.CENTER, false); - graphics.poseStack().popPose(); - } - - private int renderTransfer(Graphics graphics, int x, int y, long a, long b, StationEntry nextStation) { - final int HEIGHT = 24; - final int V = 186; - - GuiUtils.drawTexture(Constants.GUI_WIDGETS, graphics, x, y, 0, V, ENTRY_WIDTH, HEIGHT); - - long time = -1; - if (a < 0 || b < 0) { - time = -1; - } else { - time = b - a; - } - - if (nextStation != null && !nextStation.reachable(true)) { - if (nextStation.isTrainCanceled()) { - ModGuiIcons.CROSS.render(graphics, x + ENTRY_TIME_X, y + 13 - ModGuiIcons.ICON_SIZE / 2); - GuiUtils.drawString(graphics, shadowlessFont, x + ENTRY_TIME_X + ModGuiIcons.ICON_SIZE + 4, y + 8, textTrainCanceled, 0xFFFFFF, EAlignment.LEFT, false); - } else if (nextStation.isDeparted()) { - ModGuiIcons.CROSS.render(graphics, x + ENTRY_TIME_X, y + 13 - ModGuiIcons.ICON_SIZE / 2); - GuiUtils.drawString(graphics, shadowlessFont, x + ENTRY_TIME_X + ModGuiIcons.ICON_SIZE + 4, y + 8, textConnectionMissed, 0xFFFFFF, EAlignment.LEFT, false); - } else { - ModGuiIcons.WARN.render(graphics, x + ENTRY_TIME_X, y + 13 - ModGuiIcons.ICON_SIZE / 2); - GuiUtils.drawString(graphics, shadowlessFont, x + ENTRY_TIME_X + ModGuiIcons.ICON_SIZE + 4, y + 8, textConnectionEndangered, 0xFFFFFF, EAlignment.LEFT, false); - } - } else { - GuiUtils.drawString(graphics, shadowlessFont, x + ENTRY_TIME_X, y + 8, TextUtils.text(transferText.getString() + " " + (time < 0 ? "" : "(" + TimeUtils.parseDuration(time) + ")")), 0xFFFFFF, EAlignment.LEFT, false); - } - - return HEIGHT; + notificationIndicator = this.addRenderableWidget(new DLCreateIndicator(guiLeft + GUI_WIDTH - DLIconButton.DEFAULT_BUTTON_WIDTH - 8, guiTop + 220, Components.immutableEmpty())); + notificationButton = this.addRenderableWidget(new DLCreateIconButton(guiLeft + GUI_WIDTH - DLIconButton.DEFAULT_BUTTON_WIDTH - 8, guiTop + 225, DLIconButton.DEFAULT_BUTTON_WIDTH, DLIconButton.DEFAULT_BUTTON_HEIGHT, ModGuiIcons.INFO.getAsCreateIcon())); + notificationButton.withCallback(() -> { + route.setShowNotifications(!route.shouldShowNotifications()); + }); + buttonTooltips.put(notificationButton, Pair.of(tooltipShowNotifications, tooltipShowNotificationsDescription)); + boolean isSaved = SavedRoutesManager.isSaved(route); + notificationButton.set_visible(isSaved); + notificationIndicator.set_visible(isSaved); } @Override - public void renderMainLayer(Graphics graphics, int pMouseX, int pMouseY, float pPartialTick) { - pPartialTick = Minecraft.getInstance().getFrameTime(); - float scrollOffset = -scroll.getValue(pPartialTick); - - renderScreenBackground(graphics); - GuiUtils.drawTexture(GUI, graphics, guiLeft, guiTop, 0, 0, GUI_WIDTH, GUI_HEIGHT); - - /* - for (Widget widget : this.renderables) - widget.render(graphics, pMouseX, pMouseY, pPartialTick); - */ - - super.renderMainLayer(graphics, pMouseX, pMouseY, pPartialTick); - - GuiUtils.drawString(graphics, shadowlessFont, guiLeft + 19, guiTop + 4, title, 0x4F4F4F, EAlignment.LEFT, false); - String timeString = TimeUtils.parseTime((int)((level.getDayTime() + DragonLib.DAYTIME_SHIFT) % DragonLib.TICKS_PER_DAY), ModClientConfig.TIME_FORMAT.get()); - GuiUtils.drawString(graphics, shadowlessFont, guiLeft + GUI_WIDTH - 22 - shadowlessFont.width(timeString), guiTop + 4, TextUtils.text(timeString), 0x4F4F4F, EAlignment.LEFT, false); + public void tick() { + super.tick(); + notificationIndicator.state = route.shouldShowNotifications() ? State.ON : State.OFF; + saveRouteBtn.set_visible(!route.getEnd().isDeparted() && !route.isClosed()); + popupBtn.set_visible(!route.getEnd().isDeparted() && !route.isClosed()); - renderHeadline(graphics, pMouseX, pMouseY); - - GuiUtils.enableScissor(graphics, guiLeft + AREA_X, guiTop + AREA_Y, AREA_W, AREA_H); - graphics.poseStack().translate(0, scrollOffset, 0); - - int yOffs = guiTop + 45; - GuiUtils.drawTexture(Constants.GUI_WIDGETS, graphics, guiLeft + 16, yOffs, 22, ENTRIES_START_Y_OFFSET, 0, 48, 22, 1, 256, 256); - yOffs += + ENTRIES_START_Y_OFFSET; - SimpleRoutePart[] partsArray = route.getParts().toArray(SimpleRoutePart[]::new); - for (int i = 0; i < partsArray.length; i++) { - SimpleRoutePart part = partsArray[i]; - - yOffs += renderRouteStart(graphics, guiLeft + 16, yOffs, part.getStartStation()); - yOffs += renderTrainDetails(graphics, guiLeft + 16, yOffs, part); - - ExpandButton btn = expandButtons[i]; - btn.active = part.getStopovers().size() > 0; - - if (btn.active) { - btn.x = guiLeft + 78; - btn.y = yOffs - 14; - - btn.render(graphics.poseStack(), pMouseX, (int)(pMouseY - scrollOffset), pPartialTick); - } - - if (btn.isExpanded()) { - for (StationEntry stop : part.getStopovers()) { - yOffs += renderStop(graphics, guiLeft + 16, yOffs, stop); - } + buttons.performForEachOfType(IconButton.class, x -> { + if (!buttonTooltips.containsKey(x)) { + return; } - yOffs += renderRouteEnd(graphics, guiLeft + 16, yOffs, part.getEndStation()); + x.setToolTip(buttonTooltips.get(x).getFirst()); + x.getToolTip().add(TooltipHelper.holdShift(Palette.YELLOW, hasShiftDown())); - if (i < partsArray.length - 1) { - StationEntry currentStation = part.getEndStation(); - StationEntry nextStation = partsArray[i + 1].getStartStation(); - long a = currentStation.shouldRenderRealtime() ? currentStation.getCurrentTime() : currentStation.getScheduleTime(); - long b = nextStation.shouldRenderRealtime() ? nextStation.getCurrentTime() : nextStation.getScheduleTime(); - yOffs += renderTransfer(graphics, guiLeft + 16, yOffs, a, b, nextStation); + if (hasShiftDown()) { + x.getToolTip().add(buttonTooltips.get(x).getSecond()); } - } - scrollMax = yOffs - guiTop - 45; - GuiUtils.drawTexture(Constants.GUI_WIDGETS, graphics, guiLeft + 16, yOffs, 22, AREA_H, 0, 48, 22, 1, 256, 256); - - GuiUtils.disableScissor(graphics); - GuiUtils.fillGradient(graphics, guiLeft + AREA_X, guiTop + AREA_Y, 0, AREA_W, 10, 0x77000000, 0x00000000); - GuiUtils.fillGradient(graphics, guiLeft + AREA_X, guiTop + AREA_Y + AREA_H - 10, 0, AREA_W, 10, 0x00000000, 0x77000000); - - // Scrollbar - double maxHeight = scrollMax; - double aH = AREA_H + 1; - if (aH / maxHeight < 1) { - int scrollerHeight = Math.max(10, (int)(aH * (aH / maxHeight))); - int startY = guiTop + AREA_Y + (int)((AREA_H) * (Math.abs(scrollOffset) / maxHeight)); - - GuiUtils.fill(graphics, guiLeft + AREA_X + AREA_W - 3, startY, 3, scrollerHeight, 0x7FFFFFFF); - } + }); } @Override - public boolean mouseScrolled(double pMouseX, double pMouseY, double pDelta) { + public void renderMainLayer(Graphics graphics, int mouseX, int mouseY, float partialTicks) { + renderNavigatorBackground(graphics, mouseX, mouseY, partialTicks); - float chaseTarget = scroll.getChaseTarget(); - float max = -AREA_H; - max += scrollMax; - - if (max > 0) { - chaseTarget -= pDelta * 12; - chaseTarget = MathUtils.clamp(chaseTarget, 0, max); - scroll.chase((int) chaseTarget, 0.7f, Chaser.EXP); - } else - scroll.chase(0, 0.7f, Chaser.EXP); + int y = FooterSize.DEFAULT.size() - 1; + CreateDynamicWidgets.renderContainer(graphics, guiLeft + 1, guiTop + y, GUI_WIDTH - 2, 38, ContainerColor.BLUE); + y += 38 - 1; + CreateDynamicWidgets.renderContainer(graphics, guiLeft + 1, guiTop + y, GUI_WIDTH - 2, GUI_HEIGHT - y - FooterSize.SMALL.size() + 1, ContainerColor.GOLD); - return super.mouseScrolled(pMouseX, pMouseY, pDelta); - } + super.renderMainLayer(graphics, mouseX, mouseY, partialTicks); - @Override - public boolean mouseClicked(double pMouseX, double pMouseY, int pButton) { - float scrollOffset = scroll.getValue(); - - if (pMouseX > guiLeft + AREA_X && pMouseX < guiLeft + AREA_X + AREA_W && pMouseY > guiTop + AREA_Y && pMouseY < guiTop + AREA_Y + AREA_H) { - expandButtonCollection.performForEach(x -> x.active, x -> x.mouseClicked(pMouseX, pMouseY + scrollOffset, pButton)); + if (!route.isAnyCancelled()) { + if (route.getStart().isDeparted()) { + GuiUtils.drawString(graphics, font, guiLeft + GUI_WIDTH / 2, guiTop + 19, "Ankunft in", 0xFFFFFF, EAlignment.CENTER, false); + } else { + GuiUtils.drawString(graphics, font, guiLeft + GUI_WIDTH / 2, guiTop + 19, textDeparture, 0xFFFFFF, EAlignment.CENTER, false); + } + graphics.poseStack().pushPose(); + graphics.poseStack().scale(2, 2, 2); + long time = 0; + if (route.getStart().isDeparted()) { + time = route.getEnd().getRealTimeArrivalTime() - DragonLib.getCurrentWorldTime(); + GuiUtils.drawString(graphics, font, (guiLeft + GUI_WIDTH / 2) / 2, (guiTop + 31) / 2, time < 0 ? timeNowText : TextUtils.text(TimeUtils.parseDurationShort(time)), 0xFFFFFF, EAlignment.CENTER, false); + } else { + time = route.getStart().getRealTimeDepartureTime() - DragonLib.getCurrentWorldTime(); + GuiUtils.drawString(graphics, font, (guiLeft + GUI_WIDTH / 2) / 2, (guiTop + 31) / 2, time < 0 ? timeNowText : TextUtils.text(TimeUtils.parseDurationShort(time)), 0xFFFFFF, EAlignment.CENTER, false); + } + graphics.poseStack().popPose(); } - return super.mouseClicked(pMouseX, pMouseY, pButton); + //GuiUtils.drawString(graphics, font, 5, 5, "State: " + route.getState(), 0xFFFF0000, EAlignment.LEFT, false); } - } diff --git a/common/src/main/java/de/mrjulsen/crn/client/gui/screen/RouteOverlaySettingsScreen.java b/common/src/main/java/de/mrjulsen/crn/client/gui/screen/RouteOverlaySettingsScreen.java index 24df4dac..285a3ce5 100644 --- a/common/src/main/java/de/mrjulsen/crn/client/gui/screen/RouteOverlaySettingsScreen.java +++ b/common/src/main/java/de/mrjulsen/crn/client/gui/screen/RouteOverlaySettingsScreen.java @@ -4,7 +4,6 @@ import java.util.List; import java.util.Map; -import com.mojang.text2speech.Narrator; import com.simibubi.create.content.trains.station.NoShadowFontWrapper; import com.simibubi.create.foundation.gui.AllIcons; import com.simibubi.create.foundation.gui.element.GuiGameElement; @@ -19,9 +18,8 @@ import de.mrjulsen.crn.CreateRailwaysNavigator; import de.mrjulsen.crn.client.gui.CreateDynamicWidgets; import de.mrjulsen.crn.client.gui.ModGuiIcons; -import de.mrjulsen.crn.client.gui.NavigatorToast; +import de.mrjulsen.crn.client.gui.overlay.RouteDetailsOverlay; import de.mrjulsen.crn.client.gui.overlay.OverlayPosition; -import de.mrjulsen.crn.client.gui.overlay.RouteDetailsOverlayScreen; import de.mrjulsen.crn.client.gui.widgets.DLCreateIconButton; import de.mrjulsen.crn.client.gui.widgets.DLCreateIndicator; import de.mrjulsen.crn.config.ModClientConfig; @@ -29,6 +27,7 @@ import de.mrjulsen.crn.registry.ModItems; import de.mrjulsen.mcdragonlib.DragonLib; import de.mrjulsen.mcdragonlib.client.gui.DLScreen; +import de.mrjulsen.mcdragonlib.client.gui.widgets.IDragonLibWidget; import de.mrjulsen.mcdragonlib.client.util.Graphics; import de.mrjulsen.mcdragonlib.client.util.GuiUtils; import de.mrjulsen.mcdragonlib.client.util.WidgetsCollection; @@ -45,7 +44,6 @@ public class RouteOverlaySettingsScreen extends DLScreen { - private static final Component title = TextUtils.translate("gui.createrailwaysnavigator.overlay_settings.title"); private static final ResourceLocation GUI = new ResourceLocation(CreateRailwaysNavigator.MOD_ID, "textures/gui/route_overlay_settings.png"); private static final int GUI_WIDTH = 213; private static final int GUI_HEIGHT = 79; @@ -56,14 +54,12 @@ public class RouteOverlaySettingsScreen extends DLScreen { private final ItemStack renderedItem = new ItemStack(ModItems.NAVIGATOR.get()); private int guiLeft, guiTop; - private final RouteDetailsOverlayScreen overlay; + private final RouteDetailsOverlay overlay; private DLCreateIconButton backButton; private DLCreateIconButton detailsButton; private IconButton removeOverlayButton; - private DLCreateIconButton soundButton; private DLCreateIconButton notificationsButton; - private DLCreateIndicator soundIndicator; private DLCreateIndicator notificationsIndicator; private ScrollInput scaleInput; private Component scaleLabel; @@ -87,8 +83,8 @@ public class RouteOverlaySettingsScreen extends DLScreen { private static final MutableComponent textNotificationsDescription = TextUtils.translate("gui.createrailwaysnavigator.route_overlay_settings.notifications.description").withStyle(ChatFormatting.GRAY); @SuppressWarnings("resource") - public RouteOverlaySettingsScreen(RouteDetailsOverlayScreen overlay) { - super(title); + public RouteOverlaySettingsScreen(RouteDetailsOverlay overlay) { + super(TextUtils.translate("gui.createrailwaysnavigator.overlay_settings.title")); this.shadowlessFont = new NoShadowFontWrapper(Minecraft.getInstance().font); this.overlay = overlay; } @@ -100,7 +96,6 @@ public void onClose() { super.onClose(); } - @SuppressWarnings("resource") @Override protected void init() { super.init(); @@ -118,16 +113,15 @@ protected void init() { detailsButton = this.addRenderableWidget(new DLCreateIconButton(guiLeft + 7, guiTop + 55, DEFAULT_ICON_BUTTON_WIDTH, DEFAULT_ICON_BUTTON_HEIGHT, AllIcons.I_VIEW_SCHEDULE)); detailsButton.withCallback(() -> { Minecraft minecraft = Minecraft.getInstance(); - minecraft.setScreen(new RouteDetailsScreen(this, Minecraft.getInstance().level, overlay.getListener().getListeningRoute(), overlay.getListenerId())); + minecraft.setScreen(new RouteDetailsScreen(null, overlay.getRoute())); }); detailsButton.setToolTip(textShowDetails); buttons.add(detailsButton); removeOverlayButton = this.addRenderableWidget(new DLCreateIconButton(guiLeft + 27, guiTop + 55, DEFAULT_ICON_BUTTON_WIDTH, DEFAULT_ICON_BUTTON_HEIGHT, AllIcons.I_CONFIG_DISCARD)); removeOverlayButton.withCallback(() -> { - Minecraft minecraft = Minecraft.getInstance(); - minecraft.setScreen(new RouteDetailsScreen(null, Minecraft.getInstance().level, overlay.getListener().getListeningRoute(), overlay.getListenerId())); InstanceManager.removeRouteOverlay(); + onClose(); }); removeOverlayButton.setToolTip(textUnpin); buttons.add(removeOverlayButton); @@ -152,34 +146,13 @@ protected void init() { buttons.add(remOverlayButton); } - // On/Off Buttons - soundButton = this.addRenderableWidget(new DLCreateIconButton(guiLeft + 10, guiTop + 26, DEFAULT_ICON_BUTTON_WIDTH, DEFAULT_ICON_BUTTON_HEIGHT, ModGuiIcons.SOUND_ON.getAsCreateIcon())); - soundButton.withCallback(() -> { - ModClientConfig.ROUTE_NARRATOR.set(!ModClientConfig.ROUTE_NARRATOR.get()); - - if (ModClientConfig.ROUTE_NARRATOR.get()) { - Narrator.getNarrator().say(narratorOn.getString(), true); - } else { - Narrator.getNarrator().say(narratorOff.getString(), true); - } - }); - buttons.add(soundButton); - buttonTooltips.put(soundButton, Pair.of(textNarrator, textNarratorDescription)); - soundIndicator = this.addRenderableWidget(new DLCreateIndicator(guiLeft + 10, guiTop + 20, Components.immutableEmpty())); - - notificationsButton = this.addRenderableWidget(new DLCreateIconButton(guiLeft + 28, guiTop + 26, DEFAULT_ICON_BUTTON_WIDTH, DEFAULT_ICON_BUTTON_HEIGHT, ModGuiIcons.INFO.getAsCreateIcon())); + notificationsButton = this.addRenderableWidget(new DLCreateIconButton(guiLeft + 20, guiTop + 26, DEFAULT_ICON_BUTTON_WIDTH, DEFAULT_ICON_BUTTON_HEIGHT, ModGuiIcons.INFO.getAsCreateIcon())); notificationsButton.withCallback(() -> { - ModClientConfig.ROUTE_NOTIFICATIONS.set(!ModClientConfig.ROUTE_NOTIFICATIONS.get()); - - if (ModClientConfig.ROUTE_NOTIFICATIONS.get()) { - Minecraft.getInstance().getToasts().addToast(NavigatorToast.multiline(notificationsOn, TextUtils.empty())); - } else { - Minecraft.getInstance().getToasts().addToast(NavigatorToast.multiline(notificationsOff, TextUtils.empty())); - } + overlay.getRoute().setShowNotifications(!overlay.getRoute().shouldShowNotifications()); }); buttons.add(notificationsButton); buttonTooltips.put(notificationsButton, Pair.of(textNotifications, textNotificationsDescription)); - notificationsIndicator = this.addRenderableWidget(new DLCreateIndicator(guiLeft + 28, guiTop + 20, Components.immutableEmpty())); + notificationsIndicator = this.addRenderableWidget(new DLCreateIndicator(guiLeft + 20, guiTop + 20, Components.immutableEmpty())); // scale scaleInput = addRenderableWidget(new ScrollInput(guiLeft + 63, guiTop + 23, 43, 18) @@ -204,8 +177,7 @@ public boolean isPauseScreen() { @Override public void tick() { super.tick(); - soundIndicator.state = ModClientConfig.ROUTE_NARRATOR.get() ? State.ON : State.OFF; - notificationsIndicator.state = ModClientConfig.ROUTE_NOTIFICATIONS.get() ? State.ON : State.OFF; + notificationsIndicator.state = overlay.getRoute().shouldShowNotifications() ? State.ON : State.OFF; buttons.performForEachOfType(IconButton.class, x -> { if (!buttonTooltips.containsKey(x)) { @@ -220,6 +192,20 @@ public void tick() { } }); } + + @Override + public boolean mouseScrolled(double mouseX, double mouseY, double pDelta) { + + if (scaleInput.mouseScrolled(mouseX, mouseY, pDelta)) { + return true; + } + + if (super.mouseScrolled(mouseX, mouseY, pDelta)) { + return true; + } + + return false; + } @Override public void renderMainLayer(Graphics graphics, int pMouseX, int pMouseY, float pPartialTick) { @@ -241,8 +227,9 @@ public void renderMainLayer(Graphics graphics, int pMouseX, int pMouseY, float p @Override public void renderFrontLayer(Graphics graphics, int pMouseX, int pMouseY, float pPartialTick) { + super.renderFrontLayer(graphics, pMouseX, pMouseY, pPartialTick); buttons.performForEach(widget -> { - if (widget instanceof AbstractSimiWidget simiWidget && simiWidget.isHoveredOrFocused()) { + if (widget instanceof AbstractSimiWidget simiWidget && simiWidget instanceof IDragonLibWidget dlw && dlw.isMouseSelected()) { List tooltip = simiWidget.getToolTip(); if (tooltip.isEmpty()) return; @@ -251,6 +238,5 @@ public void renderFrontLayer(Graphics graphics, int pMouseX, int pMouseY, float renderComponentTooltip(graphics.poseStack(), tooltip, ttx, tty); } }); - super.renderFrontLayer(graphics, pMouseX, pMouseY, pPartialTick); } } \ No newline at end of file diff --git a/common/src/main/java/de/mrjulsen/crn/client/gui/screen/SavedRoutesScreen.java b/common/src/main/java/de/mrjulsen/crn/client/gui/screen/SavedRoutesScreen.java new file mode 100644 index 00000000..d22da03b --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/client/gui/screen/SavedRoutesScreen.java @@ -0,0 +1,58 @@ +package de.mrjulsen.crn.client.gui.screen; + +import de.mrjulsen.crn.CreateRailwaysNavigator; +import de.mrjulsen.crn.client.gui.CreateDynamicWidgets; +import de.mrjulsen.crn.client.gui.CreateDynamicWidgets.BarColor; +import de.mrjulsen.crn.client.gui.CreateDynamicWidgets.ContainerColor; +import de.mrjulsen.crn.client.gui.CreateDynamicWidgets.FooterSize; +import de.mrjulsen.crn.client.gui.widgets.ModernVerticalScrollBar; +import de.mrjulsen.crn.client.gui.widgets.SavedRoutesViewer; +import de.mrjulsen.crn.data.SavedRoutesManager; +import de.mrjulsen.mcdragonlib.client.util.Graphics; +import de.mrjulsen.mcdragonlib.client.util.GuiAreaDefinition; +import de.mrjulsen.mcdragonlib.util.TextUtils; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.screens.Screen; + +public class SavedRoutesScreen extends AbstractNavigatorScreen { + + private SavedRoutesViewer viewer; + + private GuiAreaDefinition workingArea; + + public SavedRoutesScreen(Screen lastScreen) { + super(lastScreen, TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".saved_routes.title"), BarColor.GOLD); + } + + @Override + public boolean isPauseScreen() { + return false; + } + + @Override + public void onClose() { + Minecraft.getInstance().setScreen(lastScreen); + } + + @Override + protected void init() { + super.init(); + int wY = FooterSize.DEFAULT.size() - 1; + int wH = GUI_HEIGHT - wY - FooterSize.SMALL.size(); + workingArea = new GuiAreaDefinition(guiLeft + 3, guiTop + wY + 2, GUI_WIDTH - 6, wH - 3); + ModernVerticalScrollBar scrollBar = new ModernVerticalScrollBar(this, workingArea.getRight() - 5, workingArea.getY(), workingArea.getHeight(), null); + this.viewer = new SavedRoutesViewer(this, workingArea.getX(), workingArea.getY(), workingArea.getWidth(), workingArea.getHeight(), scrollBar); + this.viewer.displayRoutes(SavedRoutesManager.getAllSavedRoutes()); + + addRenderableWidget(viewer); + addRenderableWidget(scrollBar); + } + + @Override + public void renderMainLayer(Graphics graphics, int mouseX, int mouseY, float partialTicks) { + renderNavigatorBackground(graphics, mouseX, mouseY, partialTicks); + CreateDynamicWidgets.renderContainer(graphics, workingArea.getX() - 2, workingArea.getY() - 2, workingArea.getWidth() + 4, workingArea.getHeight() + 4, ContainerColor.PURPLE); + + super.renderMainLayer(graphics, mouseX, mouseY, partialTicks); + } +} diff --git a/common/src/main/java/de/mrjulsen/crn/client/gui/screen/ScheduleBoardScreen.java b/common/src/main/java/de/mrjulsen/crn/client/gui/screen/ScheduleBoardScreen.java new file mode 100644 index 00000000..b774c47b --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/client/gui/screen/ScheduleBoardScreen.java @@ -0,0 +1,268 @@ +package de.mrjulsen.crn.client.gui.screen; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import com.simibubi.create.foundation.gui.AllIcons; + +import de.mrjulsen.crn.CreateRailwaysNavigator; +import de.mrjulsen.crn.client.gui.CreateDynamicWidgets; +import de.mrjulsen.crn.client.gui.ModGuiIcons; +import de.mrjulsen.crn.client.gui.widgets.DLCreateIconButton; +import de.mrjulsen.crn.client.gui.widgets.ModDestinationSuggestions; +import de.mrjulsen.crn.client.gui.widgets.ModernVerticalScrollBar; +import de.mrjulsen.crn.client.gui.widgets.SearchOptionButton; +import de.mrjulsen.crn.client.gui.widgets.StationDeparturesViewer; +import de.mrjulsen.crn.client.gui.widgets.flyouts.FlyoutDepartureInWidget; +import de.mrjulsen.crn.client.gui.widgets.flyouts.FlyoutTrainGroupsWidget; +import de.mrjulsen.crn.data.StationTag; +import de.mrjulsen.crn.data.TagName; +import de.mrjulsen.crn.data.UserSettings; +import de.mrjulsen.crn.data.StationTag.ClientStationTag; +import de.mrjulsen.crn.client.gui.widgets.AbstractFlyoutWidget.FlyoutPointer; +import de.mrjulsen.crn.client.gui.CreateDynamicWidgets.BarColor; +import de.mrjulsen.crn.client.gui.CreateDynamicWidgets.ColorShade; +import de.mrjulsen.crn.client.gui.CreateDynamicWidgets.ContainerColor; +import de.mrjulsen.crn.client.gui.CreateDynamicWidgets.FooterSize; +import de.mrjulsen.crn.registry.ModAccessorTypes; +import de.mrjulsen.mcdragonlib.client.gui.widgets.DLAbstractImageButton.ButtonType; +import de.mrjulsen.mcdragonlib.client.gui.widgets.DLEditBox; +import de.mrjulsen.mcdragonlib.client.gui.widgets.DLIconButton; +import de.mrjulsen.mcdragonlib.client.gui.widgets.DLTooltip; +import de.mrjulsen.mcdragonlib.client.render.DynamicGuiRenderer.AreaStyle; +import de.mrjulsen.mcdragonlib.client.util.Graphics; +import de.mrjulsen.mcdragonlib.client.util.GuiAreaDefinition; +import de.mrjulsen.mcdragonlib.client.util.GuiUtils; +import de.mrjulsen.mcdragonlib.core.EAlignment; +import de.mrjulsen.mcdragonlib.util.DLUtils; +import de.mrjulsen.mcdragonlib.util.TextUtils; +import de.mrjulsen.mcdragonlib.util.accessor.DataAccessor; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.network.chat.MutableComponent; +import net.minecraft.util.Mth; + +public class ScheduleBoardScreen extends AbstractNavigatorScreen { + + private StationDeparturesViewer viewer; + + @SuppressWarnings("resource") + private UserSettings userSettings = new UserSettings(Minecraft.getInstance().player.getUUID(), false); + + private DLEditBox stationBox; + private String stationFrom; + private ModDestinationSuggestions destinationSuggestions; + + private GuiAreaDefinition workingArea; + private String stationTagName; + private final boolean fixedStation; + + private final List stationNames = new ArrayList<>(); + + private final MutableComponent tooltipSearch = TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".navigator.search.tooltip"); + private final MutableComponent tooltipLocation = TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".navigator.location.tooltip"); + private final MutableComponent tooltipRefresh = TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".navigator.refresh.tooltip"); + + public ScheduleBoardScreen(Screen lastScreen, ClientStationTag tag) { + super(lastScreen, TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".schedule_board.title"), BarColor.GOLD); + this.fixedStation = tag != null; + if (fixedStation) { + this.stationTagName = tag.tagName(); + } + } + + @Override + public boolean isPauseScreen() { + return false; + } + + @Override + public void onClose() { + Minecraft.getInstance().setScreen(lastScreen); + } + + @Override + public void tick() { + + DLUtils.doIfNotNull(destinationSuggestions, x -> { + x.tick(); + + if (!stationBox.canConsumeInput()) { + clearSuggestions(); + } + }); + super.tick(); + } + + @Override + protected void init() { + super.init(); + + DataAccessor.getFromServer(true, ModAccessorTypes.GET_ALL_STATIONS_AS_TAGS, (names) -> { + this.stationNames.clear(); + this.stationNames.addAll(names); + }); + + setAllowedLayer(0); + int wY = FooterSize.DEFAULT.size() - 1; + int wH = GUI_HEIGHT - wY - FooterSize.SMALL.size(); + workingArea = new GuiAreaDefinition(guiLeft + 3, guiTop + wY + 2, GUI_WIDTH - 6, wH - 3); + + if (!fixedStation) { + stationBox = addEditBox(guiLeft + 32 + 5, guiTop + 25, 152, 12, stationFrom, TextUtils.empty(), false, (v) -> { + stationFrom = v; + updateEditorSubwidgets(stationBox); + }, NO_EDIT_BOX_FOCUS_CHANGE_ACTION, null); + stationBox.setMaxLength(StationTag.MAX_NAME_LENGTH); + + DLCreateIconButton searchButton = this.addRenderableWidget(new DLCreateIconButton(guiLeft + 190, guiTop + 20, 18, 18, AllIcons.I_MTD_SCAN) { + @Override + public void onClick(double mouseX, double mouseY) { + super.onClick(mouseX, mouseY); + if (stationFrom == null || stationFrom.isBlank()) { + viewer.displayRoutes(null, userSettings); + return; + } + DataAccessor.getFromServer(TagName.of(stationFrom), ModAccessorTypes.GET_STATION_TAG_BY_TAG_NAME, (result) -> { + stationTagName = result.getTagName().get(); + viewer.displayRoutes(stationTagName, userSettings); + }); + } + }); + addTooltip(DLTooltip.of(tooltipSearch).assignedTo(searchButton)); + + DLCreateIconButton locationButton = this.addRenderableWidget(new DLCreateIconButton(guiLeft + 212, guiTop + 20, 18, 18, ModGuiIcons.POSITION.getAsCreateIcon()) { + @Override + public void onClick(double mouseX, double mouseY) { + super.onClick(mouseX, mouseY); + DataAccessor.getFromServer(minecraft.player.blockPosition(), ModAccessorTypes.GET_NEAREST_STATION, (result) -> { + if (result.tagName.isPresent()) { + stationBox.setValue(result.tagName.get().get()); + } + }); + } + }); + addTooltip(DLTooltip.of(tooltipLocation).assignedTo(locationButton)); + } + + // Search Options + final int btnCount = 2; + int btnWidth = (workingArea.getWidth() - 16) / btnCount; + addRenderableWidget(new SearchOptionButton(workingArea.getLeft(), workingArea.getTop() + 16 + FooterSize.DEFAULT.size() - 2, btnWidth, 18, TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".search_options.departure_in"), () -> userSettings.searchDepartureInTicks.toString(), (b) -> { + new FlyoutDepartureInWidget<>(this, FlyoutPointer.UP, ColorShade.DARK, this::addRenderableWidget, userSettings, () -> { + return userSettings.searchDepartureInTicks; + }, (w) -> { + removeWidget(w); + reloadUserSettings(() -> this.viewer.displayRoutes(stationTagName, userSettings)); + }).open(b); + })); + addRenderableWidget(new SearchOptionButton(workingArea.getLeft() + btnWidth, workingArea.getTop() + 16 + FooterSize.DEFAULT.size() - 2, btnWidth, 18, TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".search_options.train_groups"), () -> userSettings.searchExcludedTrainGroups.toString(), (b) -> { + new FlyoutTrainGroupsWidget<>(this, FlyoutPointer.UP, ColorShade.DARK, this::addRenderableWidget, userSettings, () -> { + return userSettings.searchExcludedTrainGroups; + }, (w) -> { + removeWidget(w); + reloadUserSettings(() -> this.viewer.displayRoutes(stationTagName, userSettings)); + }).open(b); + })); + DLIconButton refreshBtn = addRenderableWidget(new DLIconButton(ButtonType.DEFAULT, AreaStyle.FLAT, ModGuiIcons.REFRESH.getAsSprite(16, 16), workingArea.getRight() - (workingArea.getWidth() - btnWidth * btnCount), workingArea.getTop() + 16 + FooterSize.DEFAULT.size() - 2, (workingArea.getWidth() - btnWidth * btnCount), 18, TextUtils.empty(), + (b) -> { + reloadUserSettings(() -> this.viewer.displayRoutes(stationTagName, userSettings)); + })); + refreshBtn.setBackColor(0x00000000); + addTooltip(DLTooltip.of(tooltipRefresh).assignedTo(refreshBtn)); + + ModernVerticalScrollBar scrollBar = new ModernVerticalScrollBar(this, workingArea.getRight() - 5, workingArea.getY() + 50, workingArea.getHeight() - 50, GuiAreaDefinition.of(lastScreen)); + this.viewer = new StationDeparturesViewer(this, workingArea.getX(), workingArea.getY() + 50, workingArea.getWidth(), workingArea.getHeight() - 50, scrollBar); + + addRenderableWidget(viewer); + addRenderableWidget(scrollBar); + reloadUserSettings(() -> this.viewer.displayRoutes(stationTagName, userSettings)); + } + + @SuppressWarnings("resource") + private void reloadUserSettings(Runnable andThen) { + DataAccessor.getFromServer(Minecraft.getInstance().player.getUUID(), ModAccessorTypes.GET_USER_SETTINGS, settings -> { + this.userSettings = settings; + DLUtils.doIfNotNull(andThen, Runnable::run); + }); + } + + @Override + public void renderMainLayer(Graphics graphics, int mouseX, int mouseY, float partialTicks) { + renderNavigatorBackground(graphics, mouseX, mouseY, partialTicks); + CreateDynamicWidgets.renderContainer(graphics, workingArea.getX() - 2, workingArea.getY() - 2, workingArea.getWidth() + 4, 30, ContainerColor.BLUE); + CreateDynamicWidgets.renderContainer(graphics, workingArea.getX() - 2, workingArea.getY() - 2 + 29, workingArea.getWidth() + 4, 22, ContainerColor.GOLD); + CreateDynamicWidgets.renderContainer(graphics, workingArea.getX() - 2, workingArea.getY() - 2 + 50, workingArea.getWidth() + 4, workingArea.getHeight() + 4 - 50, ContainerColor.PURPLE); + + if (fixedStation) { + graphics.poseStack().pushPose(); + graphics.poseStack().scale(2, 2, 2); + GuiUtils.drawString(graphics, font, (guiLeft + GUI_WIDTH / 2) / 2, (guiTop + 22) / 2, GuiUtils.ellipsisString(font, TextUtils.text(stationTagName), GUI_WIDTH / 2), 0xFFFFFF, EAlignment.CENTER, false); + graphics.poseStack().popPose(); + } else { + ModGuiIcons.POSITION.render(graphics, workingArea.getX() + 5, workingArea.getY() + 4); + CreateDynamicWidgets.renderTextBox(graphics, guiLeft + 32, guiTop + 20, 154); + } + + super.renderMainLayer(graphics, mouseX, mouseY, partialTicks); + } + + @Override + public void renderFrontLayer(Graphics graphics, int mouseX, int mouseY, float partialTicks) { + if (destinationSuggestions != null) { + graphics.poseStack().pushPose(); + graphics.poseStack().translate(0, 0, 500); + destinationSuggestions.render(graphics.poseStack(), mouseX, mouseY); + graphics.poseStack().popPose(); + } + super.renderFrontLayer(graphics, mouseX, mouseY, partialTicks); + } + + protected void updateEditorSubwidgets(DLEditBox field) { + updateEditorSubwidgetsInternal(field, getViableStations(stationNames)); + } + + protected void updateEditorSubwidgetsInternal(DLEditBox field, List list) { + clearSuggestions(); + destinationSuggestions = new ModDestinationSuggestions(this.minecraft, this, field, this.font, list, field.getHeight() + 2 + field.y()); + destinationSuggestions.setAllowSuggestions(true); + destinationSuggestions.updateCommandInfo(); + } + + private List getViableStations(Collection src) { + return src.stream() + .distinct() + .sorted((a, b) -> a.getTagName().get().compareToIgnoreCase(b.getTagName().get())) + .toList(); + } + + private void clearSuggestions() { + if (destinationSuggestions != null) { + destinationSuggestions.getEditBox().setSuggestion(""); + } + destinationSuggestions = null; + } @Override + public boolean mouseClicked(double pMouseX, double pMouseY, int pButton) { + if (destinationSuggestions != null && destinationSuggestions.mouseClicked((int) pMouseX, (int) pMouseY, pButton)) + return true; + + return super.mouseClicked(pMouseX, pMouseY, pButton); + } + + @Override + public boolean keyPressed(int pKeyCode, int pScanCode, int pModifiers) { + if (destinationSuggestions != null && destinationSuggestions.keyPressed(pKeyCode, pScanCode, pModifiers)) + return true; + + return super.keyPressed(pKeyCode, pScanCode, pModifiers); + } + + @Override + public boolean mouseScrolled(double pMouseX, double pMouseY, double pDelta) { + if (destinationSuggestions != null && destinationSuggestions.mouseScrolled(pMouseX, pMouseY, Mth.clamp(pDelta, -1.0D, 1.0D))) + return true; + + return super.mouseScrolled(pMouseX, pMouseY, pDelta); + } +} diff --git a/common/src/main/java/de/mrjulsen/crn/client/gui/screen/SearchSettingsScreen.java b/common/src/main/java/de/mrjulsen/crn/client/gui/screen/SearchSettingsScreen.java deleted file mode 100644 index 95679c9e..00000000 --- a/common/src/main/java/de/mrjulsen/crn/client/gui/screen/SearchSettingsScreen.java +++ /dev/null @@ -1,432 +0,0 @@ -package de.mrjulsen.crn.client.gui.screen; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Map.Entry; - -import com.simibubi.create.content.trains.station.NoShadowFontWrapper; -import com.simibubi.create.foundation.gui.AllIcons; -import com.simibubi.create.foundation.gui.widget.AbstractSimiWidget; -import com.simibubi.create.foundation.gui.widget.ScrollInput; -import com.simibubi.create.foundation.utility.animation.LerpedFloat; -import com.simibubi.create.foundation.utility.animation.LerpedFloat.Chaser; - -import de.mrjulsen.crn.Constants; -import de.mrjulsen.crn.CreateRailwaysNavigator; -import de.mrjulsen.crn.client.gui.CreateDynamicWidgets; -import de.mrjulsen.crn.client.gui.ModGuiIcons; -import de.mrjulsen.crn.client.gui.MutableGuiAreaDefinition; -import de.mrjulsen.crn.client.gui.CreateDynamicWidgets.ColorShade; -import de.mrjulsen.crn.client.gui.widgets.DLCreateIconButton; -import de.mrjulsen.mcdragonlib.DragonLib; -import de.mrjulsen.mcdragonlib.client.gui.DLScreen; -import de.mrjulsen.mcdragonlib.client.gui.widgets.DLTooltip; -import de.mrjulsen.mcdragonlib.client.util.Graphics; -import de.mrjulsen.mcdragonlib.client.util.GuiAreaDefinition; -import de.mrjulsen.mcdragonlib.client.util.GuiUtils; -import de.mrjulsen.mcdragonlib.core.EAlignment; -import de.mrjulsen.crn.config.ModClientConfig; -import de.mrjulsen.crn.data.GlobalSettingsManager; -import de.mrjulsen.crn.data.TrainGroup; -import de.mrjulsen.mcdragonlib.util.TextUtils; -import de.mrjulsen.mcdragonlib.util.TimeUtils; -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.Font; -import net.minecraft.client.gui.components.MultiLineLabel; -import net.minecraft.client.gui.components.Widget; -import net.minecraft.client.gui.screens.Screen; -import net.minecraft.network.chat.Component; -import net.minecraft.network.chat.MutableComponent; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.util.Mth; -import net.minecraft.world.level.Level; - -public class SearchSettingsScreen extends DLScreen { - - private static final ResourceLocation GUI = new ResourceLocation(CreateRailwaysNavigator.MOD_ID, "textures/gui/settings.png"); - private static final int GUI_WIDTH = 255; - private static final int GUI_HEIGHT = 247; - - private static final int DEFAULT_ICON_BUTTON_WIDTH = 18; - private static final int DEFAULT_ICON_BUTTON_HEIGHT = 18; - private static final int ENTRIES_START_Y_OFFSET = 10; - private static final int ENTRY_HEIGHT = 62; - private static final int ENTRY_SPACING = 4; - private static final int DISPLAY_WIDTH = 164; - - private static final int ARRAY_ENTRY_HEIGHT = 20; - - private final int AREA_X = 16; - private final int AREA_Y = 16; - private final int AREA_W = 220; - private final int AREA_H = 194; - private de.mrjulsen.mcdragonlib.client.util.GuiAreaDefinition workingArea; - - - private int guiLeft, guiTop; - private LerpedFloat scroll = LerpedFloat.linear().startWithValue(0); - private int maxY = 0; - - // Data - private final Level level; - private final Font shadowlessFont; - private final Screen lastScreen; - private final TrainGroup[] trainGroups; - private final Map areaByTrainGroup = new HashMap<>(); - private int transferTimeInputInitialY = 0; - private int transferTimeLabelInitialY = 0; - - private boolean trainGroupsExpanded; - - // Widgets - private DLCreateIconButton backButton; - private DLCreateIconButton defaultsButton; - private ScrollInput transferTimeInput; - private Component transferLabel; - private MultiLineLabel transferOptionLabel; - private MultiLineLabel trainGroupsOptionLabel; - - private MutableGuiAreaDefinition trainGroupResetButton; - private MutableGuiAreaDefinition trainGroupExpandButton; - - // Tooltips - private final MutableComponent transferTimeBoxText = TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".search_settings.transfer_time"); - private final MutableComponent transferTimeBoxDescription = TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".search_settings.transfer_time.description"); - private final MutableComponent trainGroupsText = TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".search_settings.train_groups"); - private final MutableComponent trainGroupsDescription = TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".search_settings.train_groups.description"); - private final MutableComponent trainGroupsOverviewAll = TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".search_settings.train_groups.overview.all"); - private final MutableComponent trainGroupsOverviewNone = TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".search_settings.train_groups.overview.none"); - private final MutableComponent tooltipTrainGroupsReset = TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".search_settings.train_groups.tooltip.reset"); - private final String trainGroupsOverviewKey = "gui." + CreateRailwaysNavigator.MOD_ID + ".search_settings.train_groups.overview"; - - - @SuppressWarnings("resource") - public SearchSettingsScreen(Level level, Screen lastScreen) { - super(TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".search_settings.title")); - this.level = level; - this.lastScreen = lastScreen; - this.shadowlessFont = new NoShadowFontWrapper(Minecraft.getInstance().font); - this.trainGroups = GlobalSettingsManager.getInstance().getSettingsData().getTrainGroupsList().toArray(TrainGroup[]::new); - } - - @Override - protected void init() { - super.init(); - guiLeft = this.width / 2 - GUI_WIDTH / 2; - guiTop = this.height / 2 - GUI_HEIGHT / 2; - - workingArea = new GuiAreaDefinition(guiLeft + AREA_X, guiTop + AREA_Y, AREA_W, AREA_H); - - backButton = this.addRenderableWidget(new DLCreateIconButton(guiLeft + 21, guiTop + 222, DEFAULT_ICON_BUTTON_WIDTH, DEFAULT_ICON_BUTTON_HEIGHT, AllIcons.I_CONFIG_BACK) { - @Override - public void onClick(double mouseX, double mouseY) { - super.onClick(mouseX, mouseY); - onClose(); - } - }); - addTooltip(DLTooltip.of(Constants.TOOLTIP_GO_BACK).assignedTo(backButton)); - - defaultsButton = this.addRenderableWidget(new DLCreateIconButton(guiLeft + 43, guiTop + 222, DEFAULT_ICON_BUTTON_WIDTH, DEFAULT_ICON_BUTTON_HEIGHT, AllIcons.I_REFRESH) { - @Override - public void onClick(double mouseX, double mouseY) { - super.onClick(mouseX, mouseY); - ModClientConfig.resetSearchSettings(); - clearWidgets(); - init(); - } - }); - addTooltip(DLTooltip.of(Constants.TOOLTIP_RESET_DEFAULTS).assignedTo(defaultsButton)); - - transferTimeLabelInitialY = guiTop + AREA_Y + ENTRIES_START_Y_OFFSET + (0 * (ENTRY_HEIGHT + ENTRY_SPACING)) + 44; - transferTimeInputInitialY = guiTop + AREA_Y + ENTRIES_START_Y_OFFSET + (0 * (ENTRY_HEIGHT + ENTRY_SPACING)) + 39; - transferTimeInput = addRenderableWidget(new ScrollInput(guiLeft + AREA_X + 10 + 25, transferTimeInputInitialY, 60, 18) - .withRange(0, ModClientConfig.MAX_TRANSFER_TIME + 1) - .withStepFunction(x -> 500 * (x.shift ? 2 : 1)) - .titled(transferTimeBoxText.copy()) - .calling((i) -> { - ModClientConfig.TRANSFER_TIME.set(i); - ModClientConfig.TRANSFER_TIME.save(); - ModClientConfig.SPEC.afterReload(); - transferLabel = TextUtils.text(TimeUtils.parseDurationShort(transferTimeInput.getState())); - }) - .setState(ModClientConfig.TRANSFER_TIME.get())); - transferTimeInput.onChanged(); - transferOptionLabel = MultiLineLabel.create(shadowlessFont, transferTimeBoxDescription, (int)((DISPLAY_WIDTH) / 0.75f)); - - - trainGroupExpandButton = new MutableGuiAreaDefinition(0, 0, 16, 16); - trainGroupResetButton = new MutableGuiAreaDefinition(0, 0, 16, 16); - areaByTrainGroup.clear(); - for (int i = 0; i < trainGroups.length; i++) { - TrainGroup group = trainGroups[i]; - areaByTrainGroup.put(group, new MutableGuiAreaDefinition(2, 0, 200 - 4, ARRAY_ENTRY_HEIGHT)); - } - - trainGroupsOptionLabel = MultiLineLabel.create(shadowlessFont, trainGroupsDescription, (int)((DISPLAY_WIDTH - 32) / 0.75f)); - } - - @Override - public void onClose() { - ModClientConfig.SPEC.save(); - ModClientConfig.SPEC.afterReload(); - minecraft.setScreen(lastScreen); - } - - @Override - public void tick() { - super.tick(); - scroll.tickChaser(); - transferTimeInput.tick(); - } - - private void renderDefaultOptionWidget(Graphics graphics, int x, int y, String text, MultiLineLabel label) { - graphics.poseStack().pushPose(); - GuiUtils.drawString(graphics, shadowlessFont, x + 25, y + 6, TextUtils.text(text), 0xFFFFFF, EAlignment.LEFT, false); - graphics.poseStack().scale(0.75f, 0.75f, 0.75f); - label.renderLeftAligned(graphics.poseStack(), (int)((x + 25) / 0.75f), (int)((y + 19) / 0.75f), 10, 0xDBDBDB); - graphics.poseStack().popPose(); - } - - private void modifyTrainGroupFilter(TrainGroup group) { - List current = new ArrayList<>(ModClientConfig.TRAIN_GROUP_FILTER_BLACKLIST.get()); - if (current.contains(group.getGroupName())) { - current.removeIf(x -> x.equals(group.getGroupName())); - } else { - current.add(group.getGroupName()); - } - ModClientConfig.TRAIN_GROUP_FILTER_BLACKLIST.set(current); - ModClientConfig.TRAIN_GROUP_FILTER_BLACKLIST.save(); - ModClientConfig.SPEC.afterReload(); - } - - private void resetTrainGroupFilter() { - ModClientConfig.TRAIN_GROUP_FILTER_BLACKLIST.set(new ArrayList<>()); - ModClientConfig.TRAIN_GROUP_FILTER_BLACKLIST.save(); - ModClientConfig.SPEC.afterReload(); - } - - private int getMaxScrollHeight() { - return maxY; - } - - - @Override - public void renderMainLayer(Graphics graphics, int pMouseX, int pMouseY, float pPartialTick) { - pPartialTick = Minecraft.getInstance().getFrameTime(); - renderScreenBackground(graphics); - GuiUtils.drawTexture(GUI, graphics, guiLeft, guiTop, 0, 0, GUI_WIDTH, GUI_HEIGHT); - float scrollOffset = -scroll.getValue(pPartialTick); - - // SCROLLABLE AREA START - graphics.poseStack().pushPose(); - GuiUtils.enableScissor(graphics, guiLeft + AREA_X, guiTop + AREA_Y, AREA_W, AREA_H); - graphics.poseStack().translate(0, scrollOffset, 0); - - final int defaultWidth = 200; - final int defaultDescriptionHeight = 36; - final int defaultOptionHeight = 26; - int wX = guiLeft + AREA_X + 10; - int wY = guiTop + AREA_Y + ENTRIES_START_Y_OFFSET; - - // transfer time - CreateDynamicWidgets.renderDuoShadeWidget(graphics, wX, wY, defaultWidth, defaultDescriptionHeight, ColorShade.LIGHT, defaultOptionHeight, ColorShade.DARK); - CreateDynamicWidgets.renderTextSlotOverlay(graphics, wX + 25, wY + 39, 163, 18); - CreateDynamicWidgets.renderTextBox(graphics, wX + 25, wY + 39, 66); - renderDefaultOptionWidget(graphics, wX, wY, transferTimeBoxText.getString(), transferOptionLabel); - GuiUtils.drawString(graphics, font, guiLeft + AREA_X + 10 + 30, transferTimeLabelInitialY, transferLabel, 0xFFFFFF, EAlignment.LEFT, true); - wY += ENTRY_SPACING + defaultOptionHeight + defaultDescriptionHeight; - - // train groups - int dY = wY; - if (trainGroupsExpanded) { - CreateDynamicWidgets.renderWidgetInner(graphics, wX, dY, defaultWidth, defaultDescriptionHeight, ColorShade.LIGHT); - CreateDynamicWidgets.renderWidgetTopBorder(graphics, wX, dY, defaultWidth); - dY += defaultDescriptionHeight; - CreateDynamicWidgets.renderWidgetInner(graphics, wX, dY, defaultWidth, 2, ColorShade.DARK); - dY += 2; - - for (int i = 0; i < trainGroups.length; i++) { - if (dY + (i * ARRAY_ENTRY_HEIGHT) > workingArea.getTop() + workingArea.getHeight() - scrollOffset || dY + (i * ARRAY_ENTRY_HEIGHT) < workingArea.getTop() - ARRAY_ENTRY_HEIGHT - scrollOffset) { - continue; - } - - TrainGroup group = trainGroups[i]; - MutableGuiAreaDefinition area = areaByTrainGroup.get(group); - area.setXOffset(wX); - area.setYOffset(dY + (i * ARRAY_ENTRY_HEIGHT)); - - CreateDynamicWidgets.renderWidgetInner(graphics, wX, dY + (i * ARRAY_ENTRY_HEIGHT), defaultWidth, 20, ColorShade.DARK); - CreateDynamicWidgets.renderTextSlotOverlay(graphics, wX + 25, dY + (i * ARRAY_ENTRY_HEIGHT) + 1, 163, ARRAY_ENTRY_HEIGHT - 2); - - MutableComponent name = TextUtils.text(group.getGroupName()); - int maxTextWidth = 163 - 12; - if (shadowlessFont.width(name) > maxTextWidth) { - name = TextUtils.text(shadowlessFont.substrByWidth(name, maxTextWidth).getString()).append(Constants.ELLIPSIS_STRING); - } - GuiUtils.drawString(graphics, shadowlessFont, wX + 30, dY + (i * ARRAY_ENTRY_HEIGHT) + 1 + 5, name, 0xFFFFFF, EAlignment.LEFT, false); - - CreateDynamicWidgets.renderTextSlotOverlay(graphics, wX + 6, dY + (i * ARRAY_ENTRY_HEIGHT) + 1, 16, ARRAY_ENTRY_HEIGHT - 2); - - if (ModClientConfig.TRAIN_GROUP_FILTER_BLACKLIST.get().stream().noneMatch(x -> x.equals(group.getGroupName()))) { - AllIcons.I_CONFIRM.render(graphics.poseStack(), wX + 6, dY + (i * ARRAY_ENTRY_HEIGHT) + 2); - } - - if (workingArea.isInBounds(pMouseX, pMouseY) && area.isInBounds(pMouseX, pMouseY - scrollOffset)) { - GuiUtils.fill(graphics, area.getX(), area.getY(), area.getWidth(), area.getHeight(), 0x1AFFFFFF); - } - } - dY += trainGroups.length * ARRAY_ENTRY_HEIGHT; - CreateDynamicWidgets.renderWidgetInner(graphics, wX, dY, defaultWidth, 2, ColorShade.DARK); - dY += 2; - CreateDynamicWidgets.renderWidgetBottomBorder(graphics, wX, dY, defaultWidth); - } else { - CreateDynamicWidgets.renderDuoShadeWidget(graphics, wX, wY, defaultWidth, defaultDescriptionHeight, ColorShade.LIGHT, defaultOptionHeight, ColorShade.DARK); - int amount = trainGroups.length - ModClientConfig.TRAIN_GROUP_FILTER_BLACKLIST.get().size(); - String text = String.valueOf(amount); - if (amount <= 0) { - text = trainGroupsOverviewNone.getString(); - } else if (amount >= trainGroups.length) { - text = trainGroupsOverviewAll.getString(); - } - GuiUtils.drawString(graphics, font, wX + 25, wY + defaultDescriptionHeight + defaultOptionHeight / 2 - font.lineHeight / 2, TextUtils.translate(trainGroupsOverviewKey, text), amount <= 0 ? 0xFF8888 : 0xFFFF88, EAlignment.LEFT, false); - } - - renderDefaultOptionWidget(graphics, wX, wY, trainGroupsText.getString(), trainGroupsOptionLabel); - trainGroupExpandButton.setXOffset(wX + defaultWidth - 2 - 16); - trainGroupExpandButton.setYOffset(wY + defaultDescriptionHeight / 2 - 7); - trainGroupResetButton.setXOffset(wX + defaultWidth - 2 - 32); - trainGroupResetButton.setYOffset(wY + defaultDescriptionHeight / 2 - 7); - - AllIcons.I_REFRESH.render(graphics.poseStack(), trainGroupResetButton.getX(), trainGroupResetButton.getY()); - if (trainGroupsExpanded) { - ModGuiIcons.COLLAPSE.render(graphics, trainGroupExpandButton.getX(), trainGroupExpandButton.getY()); - } else { - ModGuiIcons.EXPAND.render(graphics, trainGroupExpandButton.getX(), trainGroupExpandButton.getY()); - } - - // Button highlight - if (workingArea.isInBounds(pMouseX, pMouseY)) { - if (trainGroupExpandButton.isInBounds(pMouseX, pMouseY - scrollOffset)) { - GuiUtils.fill(graphics, trainGroupExpandButton.getX(), trainGroupExpandButton.getY(), trainGroupExpandButton.getWidth(), trainGroupExpandButton.getHeight(), 0x1AFFFFFF); - } else if (trainGroupResetButton.isInBounds(pMouseX, pMouseY - scrollOffset)) { - GuiUtils.fill(graphics, trainGroupResetButton.getX(), trainGroupResetButton.getY(), trainGroupResetButton.getWidth(), trainGroupResetButton.getHeight(), 0x1AFFFFFF); - } - } - - wY += ENTRY_SPACING + dY; - - GuiUtils.disableScissor(graphics); - GuiUtils.fillGradient(graphics, guiLeft + AREA_X, guiTop + AREA_Y, 0, AREA_W, 10, 0x77000000, 0x00000000); - GuiUtils.fillGradient(graphics, guiLeft + AREA_X, guiTop + AREA_Y + AREA_H - 10, 0, AREA_W, 10, 0x00000000, 0x77000000); - - // widgets y offset - transferTimeInput.y = (int)(transferTimeInputInitialY + scrollOffset); - - // set scrollbar values - maxY = wY - AREA_H; - graphics.poseStack().popPose(); - // SCROLLABLE AREA END - - GuiUtils.drawString(graphics, shadowlessFont, guiLeft + 19, guiTop + 4, title, 0x4F4F4F, EAlignment.LEFT, false); - String timeString = TimeUtils.parseTime((int)((level.getDayTime() + DragonLib.DAYTIME_SHIFT) % DragonLib.TICKS_PER_DAY), ModClientConfig.TIME_FORMAT.get()); - GuiUtils.drawString(graphics, shadowlessFont, guiLeft + GUI_WIDTH - 22 - shadowlessFont.width(timeString), guiTop + 4, TextUtils.text(timeString), 0x4F4F4F, EAlignment.LEFT, false); - - double maxHeight = getMaxScrollHeight(); - double aH = AREA_H + 1; - if (aH / maxHeight < 1) { - int scrollerHeight = Math.max(10, (int)(aH * (aH / maxHeight))); - int startY = guiTop + AREA_Y + (int)((AREA_H) * (Math.abs(scrollOffset) / maxHeight)); - - GuiUtils.fill(graphics, guiLeft + AREA_X + AREA_W - 3, startY, 3, scrollerHeight, 0x7FFFFFFF); - } - - super.renderMainLayer(graphics, pMouseX, pMouseY, pPartialTick); - } - - @Override - public void renderFrontLayer(Graphics graphics, int pMouseX, int pMouseY, float pPartialTick) { - int scrollOffset = (int)scroll.getValue(pPartialTick); - - if (workingArea.isInBounds(pMouseX, pMouseY)) { - GuiUtils.renderTooltipWithOffset(this, trainGroupResetButton, List.of(tooltipTrainGroupsReset), width, graphics, pMouseX, pMouseY, 0, scrollOffset); - } - - for (Widget widget : renderables) { - if (widget instanceof AbstractSimiWidget simiWidget && simiWidget.isHoveredOrFocused() - && simiWidget.visible) { - List tooltip = simiWidget.getToolTip(); - if (tooltip.isEmpty()) - continue; - int ttx = simiWidget.lockedTooltipX == -1 ? pMouseX : simiWidget.lockedTooltipX + simiWidget.x; - int tty = simiWidget.lockedTooltipY == -1 ? pMouseY : simiWidget.lockedTooltipY + simiWidget.y; - renderComponentTooltip(graphics.poseStack(), tooltip, ttx, tty); - } - } - - for (Entry entry : areaByTrainGroup.entrySet()) { - if (!workingArea.isInBounds(pMouseX, pMouseY)) { - continue; - } - - if (shadowlessFont.width(entry.getKey().getGroupName()) > 163 - 12 && GuiUtils.renderTooltipAt(this, entry.getValue(), List.of(TextUtils.text(entry.getKey().getGroupName())), width, graphics, entry.getValue().getLeft() + 24, (int)(entry.getValue().getTop() + 2 - scrollOffset), pMouseX, pMouseY, 0, (int)scrollOffset)) { - break; - } - } - - super.renderFrontLayer(graphics, pMouseX, pMouseY, pPartialTick); - } - - @Override - public boolean mouseClicked(double pMouseX, double pMouseY, int pButton) { - float scrollOffset = -scroll.getValue(0); - if (workingArea.isInBounds(pMouseX, pMouseY)) { - if (trainGroupResetButton.isInBounds(pMouseX, pMouseY - scrollOffset)) { - resetTrainGroupFilter(); - GuiUtils.playButtonSound(); - return true; - } else if (trainGroupExpandButton.isInBounds(pMouseX, pMouseY - scrollOffset)) { - trainGroupsExpanded = !trainGroupsExpanded; - GuiUtils.playButtonSound(); - return true; - } - - if (trainGroupsExpanded) { - Optional> area = areaByTrainGroup.entrySet().stream().filter(x -> x.getValue().isInBounds(pMouseX, pMouseY - scrollOffset)).findFirst(); - if (area.isPresent()) { - modifyTrainGroupFilter(area.get().getKey()); - GuiUtils.playButtonSound(); - return true; - } - } - } - return super.mouseClicked(pMouseX, pMouseY, pButton); - } - - -@Override -public boolean mouseScrolled(double pMouseX, double pMouseY, double pDelta) { - - if (transferTimeInput.isHoveredOrFocused()) { - boolean b = transferTimeInput.mouseScrolled(pMouseX, pMouseY, pDelta); - if (b) { - return b; - } - } - - float chaseTarget = scroll.getChaseTarget(); - float max = -AREA_H + getMaxScrollHeight(); - - if (max > 0) { - chaseTarget -= pDelta * 12; - chaseTarget = Mth.clamp(chaseTarget, 0, max); - scroll.chase((int) chaseTarget, 0.7f, Chaser.EXP); - } else { - scroll.chase(0, 0.7f, Chaser.EXP); - } - - return super.mouseScrolled(pMouseX, pMouseY, pDelta); -} -} diff --git a/common/src/main/java/de/mrjulsen/crn/client/gui/screen/StationBlacklistScreen.java b/common/src/main/java/de/mrjulsen/crn/client/gui/screen/StationBlacklistScreen.java deleted file mode 100644 index f2aaeee5..00000000 --- a/common/src/main/java/de/mrjulsen/crn/client/gui/screen/StationBlacklistScreen.java +++ /dev/null @@ -1,47 +0,0 @@ -package de.mrjulsen.crn.client.gui.screen; - -import java.util.Collection; - -import de.mrjulsen.crn.CreateRailwaysNavigator; -import de.mrjulsen.crn.data.ClientTrainStationSnapshot; -import de.mrjulsen.crn.data.GlobalSettingsManager; -import de.mrjulsen.mcdragonlib.util.TextUtils; -import net.minecraft.client.gui.screens.Screen; -import net.minecraft.world.level.Level; - -public class StationBlacklistScreen extends AbstractBlacklistScreen { - - public StationBlacklistScreen(Level level, Screen lastScreen) { - super(level, lastScreen, TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".blacklist.title")); - } - - @Override - protected Collection getSuggestions() { - return ClientTrainStationSnapshot.getInstance().getAllTrainStations(); - } - - @Override - protected boolean checkIsBlacklisted(String entry) { - return GlobalSettingsManager.getInstance().getSettingsData().isBlacklisted(entry); - } - - @Override - protected String[] getBlacklistedNames(String searchText) { - return GlobalSettingsManager.getInstance().getSettingsData().getBlacklist().stream().filter(x -> x.toLowerCase().contains(searchText.toLowerCase())).toArray(String[]::new); - } - - @Override - protected void addToBlacklist(String name, Runnable andThen) { - if (GlobalSettingsManager.getInstance().getSettingsData().isBlacklisted(name) || ClientTrainStationSnapshot.getInstance().getAllTrainStations().stream().noneMatch(x -> x.toLowerCase().equals(name.toLowerCase()))) { - return; - } - - GlobalSettingsManager.getInstance().getSettingsData().addToBlacklist(name, andThen); - } - - @Override - protected void removeFromBlacklist(String name, Runnable andThen) { - GlobalSettingsManager.getInstance().getSettingsData().removeFromBlacklist(name, andThen); - } - -} diff --git a/common/src/main/java/de/mrjulsen/crn/client/gui/screen/StationTagSettingsScreen.java b/common/src/main/java/de/mrjulsen/crn/client/gui/screen/StationTagSettingsScreen.java new file mode 100644 index 00000000..aa3402db --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/client/gui/screen/StationTagSettingsScreen.java @@ -0,0 +1,289 @@ +package de.mrjulsen.crn.client.gui.screen; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import org.lwjgl.glfw.GLFW; + +import de.mrjulsen.crn.Constants; +import de.mrjulsen.crn.CreateRailwaysNavigator; +import de.mrjulsen.crn.client.gui.CreateDynamicWidgets; +import de.mrjulsen.crn.client.gui.ModGuiIcons; +import de.mrjulsen.crn.client.gui.widgets.DLCreateIconButton; +import de.mrjulsen.crn.client.gui.widgets.ModStationSuggestions; +import de.mrjulsen.crn.client.gui.widgets.ModernVerticalScrollBar; +import de.mrjulsen.crn.client.gui.widgets.options.DLOptionsList; +import de.mrjulsen.crn.client.gui.widgets.options.DataListContainer; +import de.mrjulsen.crn.client.gui.widgets.options.NewEntryWidget; +import de.mrjulsen.crn.client.gui.widgets.options.OptionEntry; +import de.mrjulsen.crn.client.gui.widgets.options.SimpleDataListNewEntry; +import de.mrjulsen.crn.data.StationTag; +import de.mrjulsen.crn.data.StationTag.StationInfo; +import de.mrjulsen.crn.client.gui.CreateDynamicWidgets.BarColor; +import de.mrjulsen.crn.client.gui.CreateDynamicWidgets.ContainerColor; +import de.mrjulsen.crn.client.gui.CreateDynamicWidgets.FooterSize; +import de.mrjulsen.crn.data.storage.GlobalSettingsClient; +import de.mrjulsen.crn.registry.ModAccessorTypes; +import de.mrjulsen.mcdragonlib.DragonLib; +import de.mrjulsen.mcdragonlib.client.gui.widgets.DLEditBox; +import de.mrjulsen.mcdragonlib.client.gui.widgets.DLTooltip; +import de.mrjulsen.mcdragonlib.client.util.Graphics; +import de.mrjulsen.mcdragonlib.client.util.GuiAreaDefinition; +import de.mrjulsen.mcdragonlib.core.EAlignment; +import de.mrjulsen.mcdragonlib.data.Pair; +import de.mrjulsen.mcdragonlib.util.DLUtils; +import de.mrjulsen.mcdragonlib.util.MathUtils; +import de.mrjulsen.mcdragonlib.util.TextUtils; +import de.mrjulsen.mcdragonlib.util.accessor.DataAccessor; +import net.minecraft.Util; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.components.EditBox; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.network.chat.MutableComponent; + +public class StationTagSettingsScreen extends AbstractNavigatorScreen { + + private static final int DEFAULT_ICON_BUTTON_WIDTH = 18; + private static final int DEFAULT_ICON_BUTTON_HEIGHT = 18; + + private DLOptionsList viewer; + private DLEditBox searchBox; + + private final MutableComponent tooltipDeleteTag = TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".station_tags.delete_alias.tooltip"); + private final MutableComponent textAdd = TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".common.add"); + private final MutableComponent textStationName = TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".station_tags.hint.station_name"); + private final MutableComponent textPlatformName = TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".station_tags.hint.platform"); + + private ModStationSuggestions destinationSuggestions; + private StationTag selectedTag; + private String searchText = ""; + + private final List stationNames = new ArrayList<>(); + + public StationTagSettingsScreen(Screen lastScreen) { + super(lastScreen, TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".station_tags.title"), BarColor.GRAY); + } + + @Override + public void tick() { + super.tick(); + + DLUtils.doIfNotNull(destinationSuggestions, x -> { + x.tick(); + if (!destinationSuggestions.getEditBox().canConsumeInput()) { + clearSuggestions(); + } + }); + } + + @Override + public void onClose() { + Minecraft.getInstance().setScreen(lastScreen); + } + + public void reload() { + clearWidgets(); + init(); + } + + @Override + protected void init() { + super.init(); + + DataAccessor.getFromServer(null, ModAccessorTypes.GET_ALL_STATION_NAMES, (names) -> { + this.stationNames.clear(); + this.stationNames.addAll(names); + }); + + DLCreateIconButton helpButton = this.addRenderableWidget(new DLCreateIconButton(guiLeft + GUI_WIDTH - DEFAULT_ICON_BUTTON_WIDTH - 8, guiTop + 223, DEFAULT_ICON_BUTTON_WIDTH, DEFAULT_ICON_BUTTON_HEIGHT, ModGuiIcons.HELP.getAsCreateIcon()) { + @Override + public void onClick(double mouseX, double mouseY) { + super.onClick(mouseX, mouseY); + Util.getPlatform().openUri(Constants.HELP_PAGE_STATION_TAGS); + } + }); + addTooltip(DLTooltip.of(Constants.TEXT_HELP).assignedTo(helpButton)); + + int dy = FooterSize.DEFAULT.size() + 1; + + searchBox = addRenderableWidget(new DLEditBox(font, guiLeft + 4, guiTop + dy + 1, GUI_WIDTH - 8, 16, TextUtils.empty()) { + @Override + public boolean keyPressed(int code, int p_keyPressed_2_, int p_keyPressed_3_) { + if (code == GLFW.GLFW_KEY_ENTER) { + searchText = getValue(); + reload(); + return true; + } + return super.keyPressed(code, p_keyPressed_2_, p_keyPressed_3_); + } + }); + searchBox.setValue(searchText); + searchBox.withHint(DragonLib.TEXT_SEARCH); + dy += 18; + + ModernVerticalScrollBar scrollBar = new ModernVerticalScrollBar(this, guiLeft + GUI_WIDTH - 8, guiTop + dy, GUI_HEIGHT - dy - FooterSize.SMALL.size() - 1, null); + viewer = new DLOptionsList(this, guiLeft + 3, guiTop + dy, GUI_WIDTH - 6, GUI_HEIGHT - dy - FooterSize.SMALL.size() - 1, scrollBar); + addRenderableWidget(viewer); + addRenderableWidget(scrollBar); + + DataAccessor.getFromServer(null, ModAccessorTypes.GET_ALL_STATION_TAGS, (tgs) -> { + List tags = tgs.stream().sorted((a, b) -> a.getTagName().get().compareToIgnoreCase(b.getTagName().get())).toList(); + for (StationTag tag : tags) { + if (!tag.getTagName().get().toLowerCase(Locale.ROOT).contains(searchText.toLowerCase(Locale.ROOT))) { + continue; + } + final StationTag stationTag = tag; + OptionEntry>> opt = viewer.addOption((option) -> { + GuiAreaDefinition workspace = option.getContentSpace(); + + DataListContainer> cont = new DataListContainer<>(option, workspace.getX(), workspace.getY(), workspace.getWidth(), stationTag, + (tg) -> { + return tg.getAllStations().entrySet().stream().sorted((a, b) -> a.getKey().compareToIgnoreCase(b.getKey())).iterator(); + }, (data, entryWidget) -> { + entryWidget.addDeleteButton((btn, tg, entry, refreshAction) -> { + GlobalSettingsClient.removeStationTagEntry(tag.getId(), entry.getKey(), + (newTag) -> { + newTag.ifPresent(a -> refreshAction.accept(newTag)); + }); + }); + entryWidget.addDataSection(40, (entry) -> entry.getValue().platform(), EAlignment.RIGHT, + (tg, entry, newValue, refreshAction) -> { + if (!newValue.isBlank() && !entry.getValue().platform().equals(newValue)) { + GlobalSettingsClient.updateStationTagEntry(tg.getId(), entry.getKey(), new StationInfo(newValue), + (newTag) -> { + newTag.ifPresent(a -> refreshAction.accept(newTag)); + }); + } + }); + return data.getKey(); + }, (data, entryWidget) -> { + entryWidget.addAddButton(ModGuiIcons.ADD.getAsSprite(16, 16), textAdd, + (btn, tg, inputValues, refreshAction) -> { + String name = inputValues.get(SimpleDataListNewEntry.MAIN_INPUT_KEY).get(); + String platform = inputValues.get("platform").get(); + if (name == null || platform == null || name.isBlank() || platform.isBlank()) { + return false; + } + GlobalSettingsClient.addStationTagEntry(tg.getId(), name, new StationInfo(platform), + (newTag) -> { + newTag.ifPresent(a -> refreshAction.accept(newTag)); + }); + return true; + }); + entryWidget.editNameEditBox((box) -> { + box.setResponder((b) -> { + this.updateEditorSubwidgets(box, data); + }); + box.setMaxLength(StationTag.MAX_NAME_LENGTH); + }); + entryWidget.setNameEditBoxTooltip((box) -> textStationName); + entryWidget.addDataSection(40, "platform", textPlatformName, (box) -> box.setMaxLength(StationInfo.MAX_PLATFORM_NAME_LENGTH)); + }, (self) -> { + option.notifyContentSizeChanged(); + } + ); + cont.setPadding(3, 0, 3, 18); + cont.setFilter((entry, searchText) -> { + return entry.getKey().toLowerCase(Locale.ROOT).contains(searchText.get().toLowerCase(Locale.ROOT)); + }); + cont.setBordered(false); + + return cont; + }, TextUtils.text(tag.getTagName().get()), TextUtils.empty(), (a, b) -> OptionEntry.expandOrCollapse(a), + (str) -> { + if (!str.isBlank()) { + GlobalSettingsClient.updateStationTagNameData(stationTag.getId(), str, () -> {}); + return true; + } + return false; + }); + opt.addAdditionalButton(ModGuiIcons.DELETE.getAsSprite(16, 16), tooltipDeleteTag, + (entry, btn) -> { + GlobalSettingsClient.deleteStationTag(entry.getContentContainer().getData().getId(), () -> { + reload(); + }); + }); + + opt.setTooltip(List.of( + TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".station_tags.summary", stationTag.getAllStationNames().size()), + TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".station_tags.editor", stationTag.getLastEditorName(), stationTag.getLastEditedTimeFormatted()) + )); + } + + viewer.addRenderableWidget(new NewEntryWidget(this, () -> Pair.of(-viewer.getXScrollOffset(), -viewer.getYScrollOffset()), (val) -> { + GlobalSettingsClient.createStationTag(val, (tag) -> { + reload(); + }); + return true; + }, 0, 0, viewer.getContentWidth())); + + }); + } + + @Override + public void renderMainLayer(Graphics graphics, int mouseX, int mouseY, float partialTicks) { + renderNavigatorBackground(graphics, mouseX, mouseY, partialTicks); + + int y = FooterSize.DEFAULT.size() - 1; + int h = GUI_HEIGHT - y - FooterSize.SMALL.size(); + CreateDynamicWidgets.renderContainer(graphics, guiLeft + 1, guiTop + y, GUI_WIDTH - 2, h + 1, ContainerColor.PURPLE); + + super.renderMainLayer(graphics, mouseX, mouseY, partialTicks); + } + + @Override + public void renderFrontLayer(Graphics graphics, int mouseX, int mouseY, float partialTick) { + super.renderFrontLayer(graphics, mouseX, mouseY, partialTick); + + DLUtils.doIfNotNull(destinationSuggestions, x -> { + graphics.poseStack().pushPose(); + graphics.poseStack().translate(-viewer.getXScrollOffset(), -viewer.getYScrollOffset(), 0); + x.render(graphics.poseStack(), (int)(mouseX + viewer.getXScrollOffset()), (int)(mouseY + viewer.getYScrollOffset())); + graphics.poseStack().popPose(); + }); + } + + @Override + public boolean mouseScrolled(double mouseX, double mouseY, double delta) { + if (destinationSuggestions != null && destinationSuggestions.mouseScrolled(mouseX + viewer.getXScrollOffset(), mouseY + viewer.getYScrollOffset(), MathUtils.clamp(delta, -1.0D, 1.0D))) + return true; + + return super.mouseScrolled(mouseX, mouseY, delta); + } + + @Override + public boolean mouseClicked(double mouseX, double mouseY, int button) { + if (destinationSuggestions != null && destinationSuggestions.mouseClicked(mouseX + viewer.getXScrollOffset(), mouseY + viewer.getYScrollOffset(), button)) + return true; + + return super.mouseClicked(mouseX, mouseY, button); + } + + private void clearSuggestions() { + if (destinationSuggestions != null) { + destinationSuggestions.getEditBox().setSuggestion(""); + } + destinationSuggestions = null; + } + + + public void updateEditorSubwidgets(EditBox field, StationTag tag) { + clearSuggestions(); + this.selectedTag = tag; + + destinationSuggestions = new ModStationSuggestions(Minecraft.getInstance(), this, field, font, getViableStations(stationNames, field), field.getHeight() + 2 + field.y); + destinationSuggestions.setAllowSuggestions(true); + destinationSuggestions.updateCommandInfo(); + } + + private List getViableStations(Collection src, EditBox field) { + return src.stream() + .distinct() + .filter(x -> !selectedTag.contains(x)) + .sorted((a, b) -> a.compareTo(b)) + .toList(); + } +} diff --git a/common/src/main/java/de/mrjulsen/crn/client/gui/screen/TrainBlacklistScreen.java b/common/src/main/java/de/mrjulsen/crn/client/gui/screen/TrainBlacklistScreen.java deleted file mode 100644 index 1bbcc6ca..00000000 --- a/common/src/main/java/de/mrjulsen/crn/client/gui/screen/TrainBlacklistScreen.java +++ /dev/null @@ -1,47 +0,0 @@ -package de.mrjulsen.crn.client.gui.screen; - -import java.util.Collection; - -import de.mrjulsen.crn.CreateRailwaysNavigator; -import de.mrjulsen.crn.data.ClientTrainStationSnapshot; -import de.mrjulsen.crn.data.GlobalSettingsManager; -import de.mrjulsen.mcdragonlib.util.TextUtils; -import net.minecraft.client.gui.screens.Screen; -import net.minecraft.world.level.Level; - -public class TrainBlacklistScreen extends AbstractBlacklistScreen { - - public TrainBlacklistScreen(Level level, Screen lastScreen) { - super(level, lastScreen, TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".train_blacklist.title")); - } - - @Override - protected Collection getSuggestions() { - return ClientTrainStationSnapshot.getInstance().getAllTrainNames(); - } - - @Override - protected boolean checkIsBlacklisted(String entry) { - return GlobalSettingsManager.getInstance().getSettingsData().isTrainBlacklisted(entry); - } - - @Override - protected String[] getBlacklistedNames(String searchText) { - return GlobalSettingsManager.getInstance().getSettingsData().getTrainBlacklist().stream().filter(x -> x.toLowerCase().contains(searchText.toLowerCase())).toArray(String[]::new); - } - - @Override - protected void addToBlacklist(String name, Runnable andThen) { - if (GlobalSettingsManager.getInstance().getSettingsData().isTrainBlacklisted(name) || ClientTrainStationSnapshot.getInstance().getAllTrainNames().stream().noneMatch(x -> x.equals(name))) { - return; - } - - GlobalSettingsManager.getInstance().getSettingsData().addTrainToBlacklist(name, andThen); - } - - @Override - protected void removeFromBlacklist(String name, Runnable andThen) { - GlobalSettingsManager.getInstance().getSettingsData().removeTrainFromBlacklist(name, andThen); - } - -} diff --git a/common/src/main/java/de/mrjulsen/crn/client/gui/screen/TrainDebugScreen.java b/common/src/main/java/de/mrjulsen/crn/client/gui/screen/TrainDebugScreen.java new file mode 100644 index 00000000..3b9bffaf --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/client/gui/screen/TrainDebugScreen.java @@ -0,0 +1,66 @@ +package de.mrjulsen.crn.client.gui.screen; + +import de.mrjulsen.crn.client.gui.CreateDynamicWidgets; +import de.mrjulsen.crn.client.gui.CreateDynamicWidgets.BarColor; +import de.mrjulsen.crn.client.gui.CreateDynamicWidgets.ContainerColor; +import de.mrjulsen.crn.client.gui.CreateDynamicWidgets.FooterSize; +import de.mrjulsen.crn.client.gui.ModGuiIcons; +import de.mrjulsen.crn.client.gui.widgets.DLCreateIconButton; +import de.mrjulsen.crn.client.gui.widgets.ModernVerticalScrollBar; +import de.mrjulsen.crn.client.gui.widgets.TrainDebugViewer; +import de.mrjulsen.mcdragonlib.client.util.Graphics; +import de.mrjulsen.mcdragonlib.client.util.GuiAreaDefinition; +import de.mrjulsen.mcdragonlib.util.TextUtils; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.screens.Screen; + +public class TrainDebugScreen extends AbstractNavigatorScreen { + + private TrainDebugViewer viewer; + + private GuiAreaDefinition workingArea; + + public TrainDebugScreen(Screen lastScreen) { + super(lastScreen, TextUtils.text("Train Status Viewer"), BarColor.PURPLE); + } + + @Override + public boolean isPauseScreen() { + return false; + } + + @Override + public void onClose() { + Minecraft.getInstance().setScreen(lastScreen); + } + + @Override + protected void init() { + super.init(); + int wY = FooterSize.DEFAULT.size() - 1; + int wH = GUI_HEIGHT - wY - FooterSize.SMALL.size(); + workingArea = new GuiAreaDefinition(guiLeft + 3, guiTop + wY + 2, GUI_WIDTH - 6, wH - 3); + ModernVerticalScrollBar scrollBar = new ModernVerticalScrollBar(this, workingArea.getRight() - 5, workingArea.getY(), workingArea.getHeight(), null); + this.viewer = new TrainDebugViewer(this, workingArea.getX(), workingArea.getY(), workingArea.getWidth(), workingArea.getHeight(), scrollBar); + this.viewer.reload(); + + addRenderableWidget(viewer); + addRenderableWidget(scrollBar); + + this.addRenderableWidget(new DLCreateIconButton(guiLeft + GUI_WIDTH - 18 - 8, guiTop + 223, 18, 18, ModGuiIcons.REFRESH.getAsCreateIcon()) { + @Override + public void onClick(double mouseX, double mouseY) { + super.onClick(mouseX, mouseY); + viewer.reload(); + } + }); + } + + @Override + public void renderMainLayer(Graphics graphics, int mouseX, int mouseY, float partialTicks) { + renderNavigatorBackground(graphics, mouseX, mouseY, partialTicks); + CreateDynamicWidgets.renderContainer(graphics, workingArea.getX() - 2, workingArea.getY() - 2, workingArea.getWidth() + 4, workingArea.getHeight() + 4, ContainerColor.PURPLE); + + super.renderMainLayer(graphics, mouseX, mouseY, partialTicks); + } +} diff --git a/common/src/main/java/de/mrjulsen/crn/client/gui/screen/TrainGroupScreen.java b/common/src/main/java/de/mrjulsen/crn/client/gui/screen/TrainGroupScreen.java deleted file mode 100644 index e3033d9c..00000000 --- a/common/src/main/java/de/mrjulsen/crn/client/gui/screen/TrainGroupScreen.java +++ /dev/null @@ -1,34 +0,0 @@ -package de.mrjulsen.crn.client.gui.screen; - -import java.util.Collection; - -import de.mrjulsen.crn.CreateRailwaysNavigator; -import de.mrjulsen.crn.client.gui.widgets.TrainGroupEntryWidget; -import de.mrjulsen.crn.data.GlobalSettingsManager; -import de.mrjulsen.crn.data.TrainGroup; -import de.mrjulsen.mcdragonlib.util.TextUtils; -import net.minecraft.client.gui.screens.Screen; -import net.minecraft.world.level.Level; - -public class TrainGroupScreen extends AbstractEntryListSettingsScreen { - - public TrainGroupScreen(Level level, Screen lastScreen) { - super(level, lastScreen, TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".train_group_settings.title")); - } - - @Override - protected TrainGroup[] getData(String searchText) { - return GlobalSettingsManager.getInstance().getSettingsData().getTrainGroupsList().stream().filter(x -> x.getGroupName().toLowerCase().contains(searchText.toLowerCase())).toArray(TrainGroup[]::new); - } - - @Override - protected TrainGroupEntryWidget createWidget(WidgetCreationData> widgetData, TrainGroup data) { - Collection expandedAliasNames = widgetData.previousEntries().stream().filter(x -> x instanceof TrainGroupEntryWidget w && w.isExpanded()).map(x -> ((TrainGroupEntryWidget)x).getTrainGroup().getGroupName()).toList(); - return new TrainGroupEntryWidget(widgetData.parent(), widgetData.x(), widgetData.y(), data, () -> refreshEntries(), expandedAliasNames.contains(data.getGroupName())); - } - - @Override - protected void onCreateNewEntry(String value, Runnable refreshAction) { - GlobalSettingsManager.getInstance().getSettingsData().registerTrainGroup(new TrainGroup(value), refreshAction); - } -} diff --git a/common/src/main/java/de/mrjulsen/crn/client/gui/screen/TrainJourneySreen.java b/common/src/main/java/de/mrjulsen/crn/client/gui/screen/TrainJourneySreen.java new file mode 100644 index 00000000..23d26ab2 --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/client/gui/screen/TrainJourneySreen.java @@ -0,0 +1,76 @@ +package de.mrjulsen.crn.client.gui.screen; + +import java.util.UUID; + +import de.mrjulsen.crn.CreateRailwaysNavigator; +import de.mrjulsen.crn.client.gui.CreateDynamicWidgets; +import de.mrjulsen.crn.client.gui.CreateDynamicWidgets.BarColor; +import de.mrjulsen.crn.client.gui.CreateDynamicWidgets.ContainerColor; +import de.mrjulsen.crn.client.gui.CreateDynamicWidgets.FooterSize; +import de.mrjulsen.crn.client.gui.widgets.ModernVerticalScrollBar; +import de.mrjulsen.crn.client.gui.widgets.RouteDetailsViewer; +import de.mrjulsen.crn.data.navigation.ClientRoute; +import de.mrjulsen.crn.data.navigation.ClientRoutePart; +import de.mrjulsen.crn.event.ModCommonEvents; +import de.mrjulsen.mcdragonlib.DragonLib; +import de.mrjulsen.mcdragonlib.client.util.Graphics; +import de.mrjulsen.mcdragonlib.client.util.GuiUtils; +import de.mrjulsen.mcdragonlib.core.EAlignment; +import de.mrjulsen.mcdragonlib.util.TextUtils; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.world.level.Level; + +public class TrainJourneySreen extends AbstractNavigatorScreen { + + private final ClientRoute route; + private final ClientRoutePart part; + + private RouteDetailsViewer viewer; + + public TrainJourneySreen(Screen lastScreen, ClientRoute route, UUID trainId) { + super(lastScreen, TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".journey_info.title"), BarColor.GOLD); + this.route = route; + this.part = route.getClientParts().stream().filter(x -> x.getTrainId().equals(trainId)).findFirst().orElse(route.getFirstClientPart()); + } + + @Override + public boolean isPauseScreen() { + return false; + } + + @Override + public void onClose() { + Minecraft.getInstance().setScreen(lastScreen); + } + + @Override + protected void init() { + super.init(); + int dy = FooterSize.DEFAULT.size() + 32; + ModernVerticalScrollBar scrollBar = new ModernVerticalScrollBar(this, guiLeft + GUI_WIDTH - 8, guiTop + dy, GUI_HEIGHT - dy - FooterSize.SMALL.size() - 1, null); + viewer = new RouteDetailsViewer(this, guiLeft + 3, guiTop + dy, GUI_WIDTH - 6, GUI_HEIGHT - dy - FooterSize.SMALL.size() - 1, scrollBar); + viewer.setShowTrainDetails(false); + viewer.setCanExpandCollapse(false); + viewer.setInitialExpanded(true); + viewer.setShowJourney(true); + addRenderableWidget(viewer); + addRenderableWidget(scrollBar); + viewer.displayPart(route, x -> x == part); + } + + @Override + public void renderMainLayer(Graphics graphics, int mouseX, int mouseY, float partialTicks) { + renderNavigatorBackground(graphics, mouseX, mouseY, partialTicks); + + int y = FooterSize.DEFAULT.size() - 1; + CreateDynamicWidgets.renderContainer(graphics, guiLeft + 1, guiTop + y, GUI_WIDTH - 2, 32, ContainerColor.BLUE); + GuiUtils.drawString(graphics, font, guiLeft + 8, guiTop + y + 7, TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".journey_info.date", (ModCommonEvents.getPhysicalLevel().dayTime() / Level.TICKS_PER_DAY)), DragonLib.NATIVE_BUTTON_FONT_COLOR_ACTIVE, EAlignment.LEFT, false); + GuiUtils.drawString(graphics, font, guiLeft + 8, guiTop + y + 18, TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".journey_info.train", part.getFirstStop().getTrainName(), part.getFirstStop().getTrainId().toString().split("-")[0], part.getFirstStop().getDisplayTitle()), DragonLib.NATIVE_BUTTON_FONT_COLOR_ACTIVE, EAlignment.LEFT, false); + y += 32 - 1; + CreateDynamicWidgets.renderContainer(graphics, guiLeft + 1, guiTop + y, GUI_WIDTH - 2, GUI_HEIGHT - y - FooterSize.SMALL.size() + 1, ContainerColor.GOLD); + + super.renderMainLayer(graphics, mouseX, mouseY, partialTicks); + + } +} diff --git a/common/src/main/java/de/mrjulsen/crn/client/gui/screen/TrainSectionSettingsScreen.java b/common/src/main/java/de/mrjulsen/crn/client/gui/screen/TrainSectionSettingsScreen.java new file mode 100644 index 00000000..ec310b75 --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/client/gui/screen/TrainSectionSettingsScreen.java @@ -0,0 +1,225 @@ +package de.mrjulsen.crn.client.gui.screen; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import com.simibubi.create.AllItems; +import com.simibubi.create.foundation.gui.AllIcons; +import com.simibubi.create.foundation.gui.element.GuiGameElement; +import com.simibubi.create.foundation.gui.widget.AbstractSimiWidget; +import com.simibubi.create.foundation.gui.widget.Label; +import com.simibubi.create.foundation.gui.widget.ScrollInput; +import com.simibubi.create.foundation.utility.Components; + +import de.mrjulsen.crn.Constants; +import de.mrjulsen.crn.CreateRailwaysNavigator; +import de.mrjulsen.crn.client.gui.ModGuiIcons; +import de.mrjulsen.crn.client.gui.widgets.DLCreateIconButton; +import de.mrjulsen.crn.client.gui.widgets.DLCreateLabel; +import de.mrjulsen.crn.client.gui.widgets.DLCreateSelectionScrollInput; +import de.mrjulsen.crn.config.ModCommonConfig; +import de.mrjulsen.crn.data.TrainGroup; +import de.mrjulsen.crn.data.TrainLine; +import de.mrjulsen.crn.data.schedule.instruction.TravelSectionInstruction; +import de.mrjulsen.crn.data.storage.GlobalSettingsClient; +import de.mrjulsen.mcdragonlib.DragonLib; +import de.mrjulsen.mcdragonlib.client.gui.DLScreen; +import de.mrjulsen.mcdragonlib.client.gui.widgets.DLCheckBox; +import de.mrjulsen.mcdragonlib.client.gui.widgets.DLTooltip; +import de.mrjulsen.mcdragonlib.client.util.Graphics; +import de.mrjulsen.mcdragonlib.client.util.GuiUtils; +import de.mrjulsen.mcdragonlib.core.EAlignment; +import de.mrjulsen.mcdragonlib.util.DLUtils; +import de.mrjulsen.mcdragonlib.util.TextUtils; +import net.minecraft.Util; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.components.Widget; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.MutableComponent; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.ItemStack; + +public class TrainSectionSettingsScreen extends DLScreen { + + private static final ResourceLocation TEXTURE = new ResourceLocation(CreateRailwaysNavigator.MOD_ID, "textures/gui/section_settings.png"); + private static final int GUI_WIDTH = 212; + private static final int GUI_HEIGHT = 143; + private static final int DEFAULT_ICON_BUTTON_WIDTH = 18; + private static final int DEFAULT_ICON_BUTTON_HEIGHT = 18; + private static final ItemStack DISPLAY_ITEM = new ItemStack(AllItems.SCHEDULE.get()); + + private final CompoundTag nbt; + private final Screen lastScreen; + + // Settings + private boolean includePreviousStation = false; + private boolean usable = true; + private String trainGroupId; + private String trainLineId; + + private Map groupsById; + private Map linesById; + + // GUI + private int guiLeft; + private int guiTop; + + private ScrollInput infoTypeInput; + private Label infoTypeLabel; + private ScrollInput displayTypeInput; + private Label displayTypeLabel; + private DLCreateIconButton backButton; + private DLCreateIconButton globalSettingsButton; + + private final MutableComponent tooltipGlobalSettings = TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".navigator.global_settings.tooltip"); + private final MutableComponent tooltipTrainGroup = TextUtils.translate("gui.createrailwaysnavigator.section_settings.train_groups"); + private final MutableComponent tooltipTrainLine = TextUtils.translate("gui.createrailwaysnavigator.section_settings.train_lines"); + private final MutableComponent textIncludePreviousStation = TextUtils.translate("gui.createrailwaysnavigator.section_settings.include_previous_station"); + private final MutableComponent textUsable = TextUtils.translate("gui.createrailwaysnavigator.section_settings.usable"); + private final MutableComponent textNone = TextUtils.translate("gui.createrailwaysnavigator.section_settings.none"); + + public TrainSectionSettingsScreen(Screen lastScreen, CompoundTag nbt) { + super(TextUtils.translate("gui.createrailwaysnavigator.section_settings.title")); + this.lastScreen = lastScreen; + this.nbt = nbt; + + this.includePreviousStation = nbt.contains(TravelSectionInstruction.NBT_INCLUDE_PREVIOUS_STATION) ? nbt.getBoolean(TravelSectionInstruction.NBT_INCLUDE_PREVIOUS_STATION) : false; + this.usable = nbt.contains(TravelSectionInstruction.NBT_USABLE) ? nbt.getBoolean(TravelSectionInstruction.NBT_USABLE) : true; + this.trainGroupId = nbt.contains(TravelSectionInstruction.NBT_TRAIN_GROUP) ? nbt.getString(TravelSectionInstruction.NBT_TRAIN_GROUP) : null; + this.trainLineId = nbt.contains(TravelSectionInstruction.NBT_TRAIN_LINE) ? nbt.getString(TravelSectionInstruction.NBT_TRAIN_LINE) : null; + } + + @Override + public boolean isPauseScreen() { + return false; + } + + @Override + public void onClose() { + nbt.putString(TravelSectionInstruction.NBT_TRAIN_GROUP, trainGroupId == null ? "" : trainGroupId); + nbt.putString(TravelSectionInstruction.NBT_TRAIN_LINE, trainLineId == null ? "" : trainLineId); + nbt.putBoolean(TravelSectionInstruction.NBT_INCLUDE_PREVIOUS_STATION, includePreviousStation); + nbt.putBoolean(TravelSectionInstruction.NBT_USABLE, usable); + Minecraft.getInstance().setScreen(lastScreen); + } + + @Override + protected void init() { + super.init(); + guiLeft = width() / 2 - GUI_WIDTH / 2; + guiTop = height() / 2 - GUI_HEIGHT / 2; + + backButton = this.addRenderableWidget(new DLCreateIconButton(guiLeft + 179, guiTop + 119, DEFAULT_ICON_BUTTON_WIDTH, DEFAULT_ICON_BUTTON_HEIGHT, AllIcons.I_CONFIRM)); + backButton.withCallback(() -> { + onClose(); + }); + + DLCreateIconButton helpButton = this.addRenderableWidget(new DLCreateIconButton(guiLeft + 179 - DEFAULT_ICON_BUTTON_WIDTH - 10, guiTop + 119, DEFAULT_ICON_BUTTON_WIDTH, DEFAULT_ICON_BUTTON_HEIGHT, ModGuiIcons.HELP.getAsCreateIcon()) { + @Override + public void onClick(double mouseX, double mouseY) { + super.onClick(mouseX, mouseY); + Util.getPlatform().openUri(Constants.HELP_PAGE_SCHEDULE_SECTIONS); + } + }); + addTooltip(DLTooltip.of(Constants.TEXT_HELP).assignedTo(helpButton)); + + // Global Options Button + if (minecraft.player.hasPermissions(ModCommonConfig.GLOBAL_SETTINGS_PERMISSION_LEVEL.get())) { + final Screen instance = this; + globalSettingsButton = this.addRenderableWidget(new DLCreateIconButton(guiLeft + 7, guiTop + 119, DEFAULT_ICON_BUTTON_WIDTH, DEFAULT_ICON_BUTTON_HEIGHT, ModGuiIcons.SETTINGS.getAsCreateIcon()) { + @Override + public void onClick(double mouseX, double mouseY) { + super.onClick(mouseX, mouseY); + DLScreen.setScreen(new GlobalSettingsScreen(instance)); + } + }); + addTooltip(DLTooltip.of(tooltipGlobalSettings).assignedTo(globalSettingsButton)); + } + + GlobalSettingsClient.getTrainGroups((trainGroups) -> { + this.groupsById = trainGroups.stream().collect(Collectors.toMap(x -> x.getGroupName(), x -> x)); + GlobalSettingsClient.getTrainLines((trainLines) -> { + this.linesById = trainLines.stream().collect(Collectors.toMap(x -> x.getLineName(), x -> x)); + + List groupsList = new ArrayList<>(trainGroups.stream().map(x -> TextUtils.text(x.getGroupName())).toList()); + groupsList.add(0, textNone); + displayTypeLabel = addRenderableWidget(new DLCreateLabel(guiLeft + 45 + 5, guiTop + 23 + 5, Components.immutableEmpty()).withShadow()); + displayTypeInput = addRenderableWidget(new DLCreateSelectionScrollInput(guiLeft + 45, guiTop + 23, 138, 18) + .forOptions(groupsList) + .titled(tooltipTrainGroup) + .writingTo(displayTypeLabel) + .calling((i) -> { + this.trainGroupId = i <= 0 ? null : trainGroups.get(i - 1).getGroupName(); + }) + .setState(trainGroupId != null && groupsById.containsKey(trainGroupId) ? trainGroups.indexOf(groupsById.get(trainGroupId)) + 1 : 0) + ); + displayTypeInput.onChanged(); + + List linesList = new ArrayList<>(trainLines.stream().map(x -> TextUtils.text(x.getLineName())).toList()); + linesList.add(0, textNone); + infoTypeLabel = addRenderableWidget(new DLCreateLabel(guiLeft + 45 + 5, guiTop + 45 + 5, Components.immutableEmpty()).withShadow()); + infoTypeInput = addRenderableWidget(new DLCreateSelectionScrollInput(guiLeft + 45, guiTop + 45, 138, 18) + .forOptions(linesList) + .titled(tooltipTrainLine) + .writingTo(infoTypeLabel) + .calling((i) -> { + this.trainLineId = i <= 0 ? null : trainLines.get(i - 1).getLineName(); + }) + .setState(trainLineId != null && linesById.containsKey(trainLineId) ? trainLines.indexOf(linesById.get(trainLineId)) + 1 : 0) + ); + infoTypeInput.onChanged(); + + addRenderableWidget(new DLCheckBox(guiLeft + 21, guiTop + 67 + 1, 165, textIncludePreviousStation.getString(), includePreviousStation, (box) -> { + this.includePreviousStation = box.isChecked(); + })); + addRenderableWidget(new DLCheckBox(guiLeft + 21, guiTop + 87 + 1, 165, textUsable.getString(), usable, (box) -> { + this.usable = box.isChecked(); + })); + }); + }); + + } + + @Override + public void tick() { + super.tick(); + DLUtils.doIfNotNull(displayTypeInput, x -> x.tick()); + DLUtils.doIfNotNull(infoTypeInput, x -> x.tick()); + } + + @Override + public void renderMainLayer(Graphics graphics, int mouseX, int mouseY, float partialTicks) { + renderScreenBackground(graphics); + GuiUtils.drawTexture(TEXTURE, graphics, guiLeft, guiTop, GUI_WIDTH, GUI_HEIGHT, 0, 0, 256, 256); + GuiUtils.drawString(graphics, font, guiLeft + 6, guiTop + 4, getTitle(), DragonLib.NATIVE_UI_FONT_COLOR, EAlignment.LEFT, false); + + ModGuiIcons.TRAIN.render(graphics, guiLeft + 22, guiTop + 24); + ModGuiIcons.MAP_PATH.render(graphics, guiLeft + 22, guiTop + 46); + + GuiGameElement.of(DISPLAY_ITEM).at(guiLeft + GUI_WIDTH, guiTop + GUI_HEIGHT - 48, -200) + .scale(4f) + .render(graphics.poseStack()); + + super.renderMainLayer(graphics, mouseX, mouseY, partialTicks); + } + + @Override + public void renderFrontLayer(Graphics graphics, int pMouseX, int pMouseY, float pPartialTick) { + super.renderFrontLayer(graphics, pMouseX, pMouseY, pPartialTick); + for (Widget widget : renderables) { + if (widget instanceof AbstractSimiWidget simiWidget && simiWidget.isHoveredOrFocused() && simiWidget.visible) { + List tooltip = simiWidget.getToolTip(); + if (tooltip.isEmpty()) + continue; + int ttx = simiWidget.lockedTooltipX == -1 ? pMouseX : simiWidget.lockedTooltipX + simiWidget.x; + int tty = simiWidget.lockedTooltipY == -1 ? pMouseY : simiWidget.lockedTooltipY + simiWidget.y; + renderComponentTooltip(graphics.poseStack(), tooltip, ttx, tty); + } + } + } +} diff --git a/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/AbstractEntryListOptionWidget.java b/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/AbstractEntryListOptionWidget.java deleted file mode 100644 index d31eba5f..00000000 --- a/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/AbstractEntryListOptionWidget.java +++ /dev/null @@ -1,16 +0,0 @@ -package de.mrjulsen.crn.client.gui.widgets; - -import de.mrjulsen.mcdragonlib.client.gui.widgets.WidgetContainer; - -public abstract class AbstractEntryListOptionWidget extends WidgetContainer implements IEntryListSettingsOption { - - public AbstractEntryListOptionWidget(int x, int y, int width, int height) { - super(x, y, width, height); - } - - @Override - public boolean consumeScrolling(double mouseX, double mouseY) { - return false; - } - -} diff --git a/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/AbstractFlyoutWidget.java b/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/AbstractFlyoutWidget.java new file mode 100644 index 00000000..92b7faeb --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/AbstractFlyoutWidget.java @@ -0,0 +1,223 @@ +package de.mrjulsen.crn.client.gui.widgets; + +import java.util.function.Consumer; + +import com.mojang.blaze3d.systems.RenderSystem; + +import de.mrjulsen.crn.client.CRNGui; +import de.mrjulsen.crn.client.gui.Animator; +import de.mrjulsen.crn.client.gui.CreateDynamicWidgets; +import de.mrjulsen.crn.client.gui.CreateDynamicWidgets.ColorShade; +import de.mrjulsen.mcdragonlib.client.gui.DLScreen; +import de.mrjulsen.mcdragonlib.client.gui.widgets.IDragonLibWidget; +import de.mrjulsen.mcdragonlib.client.gui.widgets.WidgetContainer; +import de.mrjulsen.mcdragonlib.client.util.Graphics; +import de.mrjulsen.mcdragonlib.client.util.GuiAreaDefinition; +import de.mrjulsen.mcdragonlib.client.util.GuiUtils; +import de.mrjulsen.mcdragonlib.data.Cache; +import de.mrjulsen.mcdragonlib.util.MathUtils; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.components.Widget; +import net.minecraft.client.gui.components.events.GuiEventListener; +import net.minecraft.client.gui.narration.NarratableEntry; +import net.minecraft.client.gui.narration.NarrationElementOutput; + +public abstract class AbstractFlyoutWidget extends WidgetContainer { + + protected final DLScreen screen; + protected final FlyoutPointer pointer; + protected final ColorShade pointerShade; + protected final int distanceToParent = 0; + protected final Animator animator = addRenderableOnly(new Animator()); + private final Consumer addRenderableWidgetFunc; + private final Consumer removeWidgetFunc; + private int xOffset; + private int yOffset; + + private boolean isClosing = false; + + private final Cache contentArea = new Cache<>(() -> new GuiAreaDefinition(x() + (FlyoutPointer.WIDTH - 2), y() + (FlyoutPointer.HEIGHT - 2), width() - (FlyoutPointer.WIDTH - 2) * 2, height() - (FlyoutPointer.HEIGHT - 2) * 2)); + + public AbstractFlyoutWidget(DLScreen screen, int width, int height, FlyoutPointer pointer, ColorShade pointerShade, Consumer addRenderableWidgetFunc, Consumer removeWidgetFunc) { + super(0, 0, width, height); + this.screen = screen; + this.pointerShade = pointerShade; + this.pointer = pointer; + this.addRenderableWidgetFunc = addRenderableWidgetFunc; + this.removeWidgetFunc = removeWidgetFunc; + } + + public GuiAreaDefinition getContentArea() { + return contentArea.get(); + } + + @SuppressWarnings({ "resource", "unchecked" }) + public void open(IDragonLibWidget parent) { + screen.setAllowedLayer(screen.getAllowedLayer() + 1); + setWidgetLayerIndex(screen.getAllowedLayer()); + + switch (pointer) { + case UP -> { + set_x(MathUtils.clamp(xOffset + parent.x() + parent.width() / 2 - width() / 2, 0, Minecraft.getInstance().screen.width - width())); + set_y(MathUtils.clamp(yOffset + parent.y() + parent.height() + distanceToParent, 0, Minecraft.getInstance().screen.height - height() - distanceToParent)); + } + case DOWN -> { + set_x(MathUtils.clamp(xOffset + parent.x() + parent.width() / 2 - width() / 2, 0, Minecraft.getInstance().screen.width - width())); + set_y(MathUtils.clamp(yOffset + parent.y() - height() - distanceToParent, 0, Minecraft.getInstance().screen.height - height() - distanceToParent)); + } + case RIGHT -> { + set_x(MathUtils.clamp(xOffset + parent.x() - width() - distanceToParent, 0, Minecraft.getInstance().screen.width - width() - distanceToParent)); + set_y(MathUtils.clamp(yOffset + parent.y() + parent.height() / 2 - height() / 2, 0, Minecraft.getInstance().screen.height - height())); + } + case LEFT -> { + set_x(MathUtils.clamp(xOffset + parent.x() + parent.width() + distanceToParent, 0, Minecraft.getInstance().screen.width - width() - distanceToParent)); + set_y(MathUtils.clamp(yOffset + parent.y() + parent.height() / 2 - height() / 2, 0, Minecraft.getInstance().screen.height - height())); + } + } + contentArea.clear(); + animator.start(3, null, null, null); + addRenderableWidgetFunc.accept((T)this); + } + + public void close() { + isClosing = true; + screen.setAllowedLayer(getWidgetLayerIndex() - 1); + animator.start(3, null, null, () -> { + screen.setAllowedLayer(getWidgetLayerIndex() - 1); + removeWidgetFunc.accept(this); + }); + } + + public void closeImmediately() { + isClosing = true; + screen.setAllowedLayer(getWidgetLayerIndex() - 1); + removeWidgetFunc.accept(this); + } + + @Override + public void renderMainLayer(Graphics graphics, int mouseX, int mouseY, float partialTicks) { + graphics.poseStack().pushPose(); + RenderSystem.enableBlend(); + RenderSystem.defaultBlendFunc(); + RenderSystem.enableDepthTest(); + if (animator.isRunning()) { + if (isClosing) { + switch (pointer) { + case UP -> graphics.poseStack().translate(0, -animator.getCurrentTicksSmooth() * 2, 0); + case DOWN -> graphics.poseStack().translate(0, animator.getTotalTicks() * 2 - animator.getCurrentTicksSmooth() * 2, 0); + case LEFT -> graphics.poseStack().translate(animator.getTotalTicks() * 2 - animator.getCurrentTicksSmooth() * 2, 0, 0); + case RIGHT -> graphics.poseStack().translate(animator.getCurrentTicksSmooth() * 2, 0, 0); + } + GuiUtils.setTint(1, 1, 1, 1f - animator.getPercentage()); + } else { + switch (pointer) { + case UP -> graphics.poseStack().translate(0, -animator.getTotalTicks() * 2 + animator.getCurrentTicksSmooth() * 2, 0); + case DOWN -> graphics.poseStack().translate(0, animator.getCurrentTicksSmooth() * 2, 0); + case LEFT -> graphics.poseStack().translate(-animator.getCurrentTicksSmooth() * 2, 0, 0); + case RIGHT -> graphics.poseStack().translate(animator.getTotalTicks() * 2 - animator.getCurrentTicksSmooth() * 2, 0, 0); + } + GuiUtils.setTint(1, 1, 1, animator.getPercentage()); + } + } + + renderFlyout(graphics, mouseX, mouseY, partialTicks, getContentArea()); + renderFlyoutContent(graphics, mouseX, mouseY, partialTicks, getContentArea()); + graphics.poseStack().popPose(); + } + + public void renderFlyoutContent(Graphics graphics, int mouseX, int mouseY, float partialTicks, GuiAreaDefinition contentArea) { + super.renderMainLayer(graphics, mouseX, mouseY, partialTicks); + } + + public void renderFlyout(Graphics graphics, int mouseX, int mouseY, float partialTicks, GuiAreaDefinition contentArea) { + CreateDynamicWidgets.renderShadow(graphics, contentArea.getX(), contentArea.getY(), contentArea.getWidth(), contentArea.getHeight()); + CreateDynamicWidgets.renderSingleShadeWidget(graphics, contentArea.getX(), contentArea.getY(), contentArea.getWidth(), contentArea.getHeight(), pointerShade); + RenderSystem.enableBlend(); + RenderSystem.defaultBlendFunc(); + RenderSystem.enableDepthTest(); + switch (pointer) { + case UP: + pointer.render(graphics, x() + width() / 2 - FlyoutPointer.WIDTH / 2, y(), pointerShade); + break; + case DOWN: + pointer.render(graphics, x() + width() / 2 - FlyoutPointer.WIDTH / 2, y() + height() - FlyoutPointer.HEIGHT, pointerShade); + break; + case LEFT: + pointer.render(graphics, x(), y() + height() / 2 - FlyoutPointer.HEIGHT / 2, pointerShade); + break; + case RIGHT: + pointer.render(graphics, x() + width() - FlyoutPointer.WIDTH, y() + height() / 2 - FlyoutPointer.HEIGHT / 2, pointerShade); + break; + default: + break; + } + } + + @Override + public NarrationPriority narrationPriority() { + return NarrationPriority.HOVERED; + } + + @Override + public boolean mouseClicked(double mouseX, double mouseY, int button) { + if (!isMouseOver(mouseX, mouseY)) { + close(); + return true; + } + return super.mouseClicked(mouseX, mouseY, button); + } + + public int getXOffset() { + return xOffset; + } + + public void setXOffset(int xOffset) { + this.xOffset = xOffset; + } + + public int getYOffset() { + return yOffset; + } + + public void setYOffset(int yOffset) { + this.yOffset = yOffset; + } + + @Override + public void updateNarration(NarrationElementOutput narrationElementOutput) {} + + @Override + public boolean consumeScrolling(double mouseX, double mouseY) { + return true; + } + + public static enum FlyoutPointer { + UP(0, 54), + DOWN(14, 54), + RIGHT(28, 54), + LEFT(42, 54); + + private final int u; + private final int v; + public static final int WIDTH = 7; + public static final int HEIGHT = 7; + + private FlyoutPointer(int u, int v) { + this.u = u; + this.v = v; + } + + public int getU() { + return u; + } + + public int getV() { + return v; + } + + public void render(Graphics graphics, int x, int y, ColorShade shade) { + int u = shade == ColorShade.LIGHT ? getU() : getU() + WIDTH; + GuiUtils.drawTexture(CRNGui.GUI, graphics, x, y, WIDTH, HEIGHT, u, v, CRNGui.GUI_WIDTH, CRNGui.GUI_HEIGHT); + } + } +} diff --git a/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/AbstractNotificationPopup.java b/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/AbstractNotificationPopup.java new file mode 100644 index 00000000..9e10c75a --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/AbstractNotificationPopup.java @@ -0,0 +1,115 @@ +package de.mrjulsen.crn.client.gui.widgets; + +import java.util.function.Consumer; + +import com.mojang.blaze3d.systems.RenderSystem; + +import de.mrjulsen.crn.client.gui.Animator; +import de.mrjulsen.crn.client.gui.CreateDynamicWidgets; +import de.mrjulsen.crn.client.gui.CreateDynamicWidgets.ColorShade; +import de.mrjulsen.mcdragonlib.client.gui.DLScreen; +import de.mrjulsen.mcdragonlib.client.gui.widgets.WidgetContainer; +import de.mrjulsen.mcdragonlib.client.util.Graphics; +import de.mrjulsen.mcdragonlib.client.util.GuiAreaDefinition; +import de.mrjulsen.mcdragonlib.client.util.GuiUtils; +import net.minecraft.client.gui.components.events.GuiEventListener; +import net.minecraft.client.gui.narration.NarrationElementOutput; + +public abstract class AbstractNotificationPopup extends WidgetContainer { + + protected final DLScreen screen; + protected final ColorShade shade; + protected final Animator animator = addRenderableOnly(new Animator()); + private final Consumer removeWidgetFunc; + private int xOffset; + private int yOffset; + + private boolean isClosing = false; + + public AbstractNotificationPopup(DLScreen screen, int x, int y, int width, int height, ColorShade shade, Consumer removeWidgetFunc) { + super(x, y, width, height); + this.screen = screen; + this.shade = shade; + this.removeWidgetFunc = removeWidgetFunc; + animator.start(3, null, null, null); + } + + public void close() { + isClosing = true; + animator.start(3, null, null, () -> { + removeWidgetFunc.accept(this); + }); + } + + public void closeImmediately() { + isClosing = true; + removeWidgetFunc.accept(this); + } + + @Override + public void renderMainLayer(Graphics graphics, int mouseX, int mouseY, float partialTicks) { + graphics.poseStack().pushPose(); + RenderSystem.enableBlend(); + RenderSystem.defaultBlendFunc(); + RenderSystem.enableDepthTest(); + if (animator.isRunning()) { + if (isClosing) { + graphics.poseStack().translate(0, animator.getCurrentTicksSmooth() * 2, 0); + GuiUtils.setTint(1, 1, 1, 1f - animator.getPercentage()); + } else {graphics.poseStack().translate(0, animator.getTotalTicks() * 2 - animator.getCurrentTicksSmooth() * 2, 0); + GuiUtils.setTint(1, 1, 1, animator.getPercentage()); + } + } + + renderFlyout(graphics, mouseX, mouseY, partialTicks, GuiAreaDefinition.of(this)); + renderFlyoutContent(graphics, mouseX, mouseY, partialTicks, GuiAreaDefinition.of(this)); + graphics.poseStack().popPose(); + } + + public void renderFlyoutContent(Graphics graphics, int mouseX, int mouseY, float partialTicks, GuiAreaDefinition contentArea) { + super.renderMainLayer(graphics, mouseX, mouseY, partialTicks); + } + + public void renderFlyout(Graphics graphics, int mouseX, int mouseY, float partialTicks, GuiAreaDefinition contentArea) { + CreateDynamicWidgets.renderShadow(graphics, contentArea.getX(), contentArea.getY(), contentArea.getWidth(), contentArea.getHeight()); + CreateDynamicWidgets.renderSingleShadeWidget(graphics, contentArea.getX(), contentArea.getY(), contentArea.getWidth(), contentArea.getHeight(), shade); + } + + @Override + public NarrationPriority narrationPriority() { + return NarrationPriority.HOVERED; + } + + @Override + public boolean mouseClicked(double mouseX, double mouseY, int button) { + if (!isMouseOver(mouseX, mouseY)) { + close(); + return true; + } + return super.mouseClicked(mouseX, mouseY, button); + } + + public int getXOffset() { + return xOffset; + } + + public void setXOffset(int xOffset) { + this.xOffset = xOffset; + } + + public int getYOffset() { + return yOffset; + } + + public void setYOffset(int yOffset) { + this.yOffset = yOffset; + } + + @Override + public void updateNarration(NarrationElementOutput narrationElementOutput) {} + + @Override + public boolean consumeScrolling(double mouseX, double mouseY) { + return true; + } +} diff --git a/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/AliasEntryWidget.java b/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/AliasEntryWidget.java deleted file mode 100644 index 2fa32994..00000000 --- a/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/AliasEntryWidget.java +++ /dev/null @@ -1,501 +0,0 @@ -package de.mrjulsen.crn.client.gui.widgets; - -import java.util.List; -import java.util.Map; -import java.util.HashMap; -import java.util.Map.Entry; - -import com.mojang.blaze3d.vertex.PoseStack; -import com.simibubi.create.content.trains.station.NoShadowFontWrapper; - -import de.mrjulsen.crn.Constants; -import de.mrjulsen.crn.CreateRailwaysNavigator; -import de.mrjulsen.crn.client.gui.screen.AbstractEntryListSettingsScreen; -import de.mrjulsen.crn.data.AliasName; -import de.mrjulsen.crn.data.ClientTrainStationSnapshot; -import de.mrjulsen.crn.data.GlobalSettingsManager; -import de.mrjulsen.crn.data.TrainStationAlias; -import de.mrjulsen.crn.data.TrainStationAlias.StationInfo; -import de.mrjulsen.mcdragonlib.client.gui.widgets.DLEditBox; -import de.mrjulsen.mcdragonlib.client.util.Graphics; -import de.mrjulsen.mcdragonlib.client.util.GuiAreaDefinition; -import de.mrjulsen.mcdragonlib.client.util.GuiUtils; -import de.mrjulsen.mcdragonlib.core.EAlignment; -import de.mrjulsen.mcdragonlib.util.MathUtils; -import de.mrjulsen.mcdragonlib.util.TextUtils; -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.Font; -import net.minecraft.client.gui.narration.NarrationElementOutput; -import net.minecraft.network.chat.MutableComponent; -import net.minecraft.resources.ResourceLocation; - -public class AliasEntryWidget extends AbstractEntryListOptionWidget { - - private static final ResourceLocation GUI_WIDGETS = new ResourceLocation(CreateRailwaysNavigator.MOD_ID, "textures/gui/settings_widgets.png"); - public static final int WIDTH = 200; - public static final int HEIGHT = 48; - private static final int STATION_ENTRY_HEIGHT = 20; - - private final AbstractEntryListSettingsScreen parent; - private final Font shadowlessFont; - private final Minecraft minecraft; - - private final Runnable onUpdate; - - // Data - private TrainStationAlias alias; - private boolean expanded = false; - - // Controls - private final DLEditBox titleBox; - private final DLEditBox newEntryBox; - private final DLEditBox newEntryPlatformBox; - //private final WidgetsCollection controls = new WidgetsCollection(); - private final Map removeStationButtons = new HashMap<>(); - private final Map stationInfoAreas = new HashMap<>(); - private final Map stationNameAreas = new HashMap<>(); - - private GuiAreaDefinition titleBarArea; - - private GuiAreaDefinition deleteButton; - private GuiAreaDefinition expandButton; - private GuiAreaDefinition addButton; - - private ModStationSuggestions destinationSuggestions; - - // Edit station info - private String selectedStationName = null; - private final DLEditBox editAliasPlatform; - - // Tooltips - private final MutableComponent tooltipDeleteAlias = TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".alias_settings.delete_alias.tooltip"); - private final MutableComponent tooltipDeleteStation = TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".alias_settings.delete_station.tooltip"); - private final MutableComponent tooltipAddStation = TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".alias_settings.add_station.tooltip"); - private final MutableComponent tooltipStationName = TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".alias_settings.hint.station_name"); - private final MutableComponent tooltipPlatform = TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".alias_settings.hint.platform"); - - - public AliasEntryWidget(AbstractEntryListSettingsScreen parent, int pX, int pY, TrainStationAlias alias, Runnable onUpdate, boolean expanded) { - super(pX, pY, 200, 48); - - Minecraft minecraft = Minecraft.getInstance(); - shadowlessFont = new NoShadowFontWrapper(minecraft.font); - - this.minecraft = Minecraft.getInstance(); - this.parent = parent; - this.alias = alias; - this.expanded = expanded; - this.onUpdate = onUpdate; - - titleBox = new DLEditBox(minecraft.font, pX + 30, pY + 10, 129, 12, TextUtils.empty()); - titleBox.setBordered(false); - titleBox.setMaxLength(32); - titleBox.setTextColor(0xFFFFFF); - titleBox.setValue(alias.getAliasName().get()); - titleBox.withOnFocusChanged((box, focused) -> { - if (!focused) { - if (!setAliasName(box.getValue())) { - titleBox.setValue(alias.getAliasName().get()); - } - } - }); - titleBox.visible = expanded; - addRenderableWidget(titleBox); - - - newEntryBox = new DLEditBox(minecraft.font, pX + 30, pY + 30, 95, 12, TextUtils.empty()); - newEntryBox.setBordered(false); - newEntryBox.setMaxLength(32); - newEntryBox.setTextColor(0xFFFFFF); - newEntryBox.visible = expanded; - newEntryBox.setResponder(x -> { - updateEditorSubwidgets(newEntryBox); - }); - addRenderableWidget(newEntryBox); - - newEntryPlatformBox = new DLEditBox(minecraft.font, pX + 134, pY + 30, 25, 12, TextUtils.empty()); - newEntryPlatformBox.setBordered(false); - newEntryPlatformBox.setMaxLength(10); - newEntryPlatformBox.setTextColor(0xFFFFFF); - newEntryPlatformBox.visible = expanded; - addRenderableWidget(newEntryPlatformBox); - - editAliasPlatform = new DLEditBox(minecraft.font, pX + 134, 0, 33, 14, TextUtils.empty()); - editAliasPlatform.setBordered(true); - editAliasPlatform.setMaxLength(10); - editAliasPlatform.setTextColor(0xFFFFFF); - editAliasPlatform.visible = false; - editAliasPlatform.withOnFocusChanged((box, focus) -> { - if (!focus) { - if (selectedStationName != null && !selectedStationName.isBlank()) { - alias.updateInfoForStation(selectedStationName, new StationInfo(box.getValue())); - alias.updateLastEdited(minecraft.player.getName().getString()); - GlobalSettingsManager.getInstance().getSettingsData().updateAlias(alias.getAliasName(), alias, () -> { - onUpdate.run(); - initStationDeleteButtons(); - }); - } - box.visible = false; - selectedStationName = null; - box.setValue(""); - } - }); - addRenderableWidget(editAliasPlatform); - - setYPos(pY); - } - - public TrainStationAlias getAlias() { - return alias; - } - - public boolean isExpanded() { - return expanded; - } - - @Override - public void setYPos(int y) { - this.y = y; - deleteButton = new GuiAreaDefinition(x + 165, y + 6, 16, 16); - expandButton = new GuiAreaDefinition(x + 182, y + 6, 16, 16); - addButton = new GuiAreaDefinition(x + 165, y + 26 + (alias.getAllStationNames().size() * STATION_ENTRY_HEIGHT) + 2, 16, 16); - titleBox.y = y + 10; - initStationDeleteButtons(); - } - - private void initStationDeleteButtons() { - removeStationButtons.clear(); - stationInfoAreas.clear(); - stationNameAreas.clear(); - - String[] names = alias.getAllStationNames().toArray(String[]::new); - for (int i = 0; i < names.length; i++) { - String name = names[i]; - removeStationButtons.put(name, new GuiAreaDefinition(x + 165, y + 26 + (i * STATION_ENTRY_HEIGHT) + 2, 16, 16)); - stationNameAreas.put(name, new GuiAreaDefinition(x + 25, y + 26 + (i * STATION_ENTRY_HEIGHT) + 2, 104, 16)); - stationInfoAreas.put(name, new GuiAreaDefinition(x + 129, y + 26 + (i * STATION_ENTRY_HEIGHT) + 2, 35, 16)); - } - - titleBarArea = new GuiAreaDefinition(x + 25, y + 6, 129, 16); - } - - @Override - public void tick() { - super.tick(); - - if (destinationSuggestions != null) { - destinationSuggestions.tick(); - - if (!newEntryBox.canConsumeInput()) { - clearSuggestions(); - } - } - } - - private void toggleExpanded() { - this.expanded = !expanded; - titleBox.visible = expanded; - newEntryBox.visible = expanded; - newEntryPlatformBox.visible = expanded; - } - - private void deleteAlias() { - GlobalSettingsManager.getInstance().getSettingsData().unregisterAlias(alias, onUpdate); - } - - private void addStation(String name, StationInfo info) { - AliasName prevName = alias.getAliasName(); - if (ClientTrainStationSnapshot.getInstance().getAllTrainStations().stream().noneMatch(x -> x.equals(name)) || newEntryPlatformBox.getValue().isBlank()) { - return; - } - - alias.add(name, info); - alias.updateLastEdited(minecraft.player.getName().getString()); - GlobalSettingsManager.getInstance().getSettingsData().updateAlias(prevName, alias, () -> { - onUpdate.run(); - initStationDeleteButtons(); - }); - - newEntryBox.setValue(""); - newEntryBox.setFocus(false); - newEntryPlatformBox.setValue(""); - newEntryPlatformBox.setFocus(false); - } - - private boolean setAliasName(String name) { - AliasName prevName = alias.getAliasName(); - - if (name == null || name.isBlank()) { - return false; - } - - if (GlobalSettingsManager.getInstance().getSettingsData().getAliasList().stream().anyMatch(x -> x.getAliasName().get().toLowerCase().equals(name.toLowerCase()))) { - return false; - } - - alias.setName(AliasName.of(name)); - alias.updateLastEdited(minecraft.player.getName().getString()); - GlobalSettingsManager.getInstance().getSettingsData().updateAlias(prevName, alias, onUpdate); - return true; - } - - - @Override - public int getHeight() { - return height; - } - - private void removeStation(String name) { - AliasName prevName = alias.getAliasName(); - alias.remove(name); - alias.updateLastEdited(minecraft.player.getName().getString()); - GlobalSettingsManager.getInstance().getSettingsData().updateAlias(prevName, alias, () -> { - onUpdate.run(); - initStationDeleteButtons(); - }); - initStationDeleteButtons(); - onUpdate.run(); - } - - @Override - public int calcHeight() { - if (expanded) { - height = STATION_ENTRY_HEIGHT * alias.getAllStationNames().size() + 50; - } else { - height = HEIGHT; - } - return height; - } - - private void editStationInfo(String stationName, GuiAreaDefinition buttonArea) { - //parent.unfocusAllWidgets(); - //parent.unfocusAllEntries(); - - selectedStationName = stationName; - editAliasPlatform.setValue(alias.getInfoForStation(selectedStationName).platform()); - editAliasPlatform.x = buttonArea.getLeft() + 1; - editAliasPlatform.y = buttonArea.getTop() + 1; - editAliasPlatform.visible = true; - editAliasPlatform.setFocus(true); - } - - @Override - public void renderMainLayer(Graphics graphics, int pMouseX, int pMouseY, float pPartialTick) { - GuiUtils.drawTexture(GUI_WIDGETS, graphics, x, y, 0, 0, WIDTH, HEIGHT); - - GuiUtils.drawTexture(GUI_WIDGETS, graphics, deleteButton.getX(), deleteButton.getY(), 232, 0, 16, 16); // delete button - GuiUtils.drawTexture(GUI_WIDGETS, graphics, expandButton.getX(), expandButton.getY(), expanded ? 216 : 200, 0, 16, 16); // expand button - - if (expanded) { - Map names = alias.getAllStations(); - GuiUtils.drawTexture(GUI_WIDGETS, graphics, x + 25, y + 5, 0, 92, 139, 18); // textbox - newEntryBox.y = y + 26 + (names.size() * STATION_ENTRY_HEIGHT) + 6; - newEntryPlatformBox.y = y + 26 + (names.size() * STATION_ENTRY_HEIGHT) + 6; - - for (int i = 0; i < names.size(); i++) { - GuiUtils.drawTexture(GUI_WIDGETS, graphics, x, y + 26 + (i * STATION_ENTRY_HEIGHT), 0, 48, 200, STATION_ENTRY_HEIGHT); - } - - GuiUtils.drawTexture(GUI_WIDGETS, graphics, x, y + 26 + (names.size() * STATION_ENTRY_HEIGHT), 0, 68, 200, 24); - GuiUtils.drawTexture(GUI_WIDGETS, graphics, x + 25, y + 26 + (names.size() * STATION_ENTRY_HEIGHT) + 1, 0, 92, 103, 18); // textbox - GuiUtils.drawTexture(GUI_WIDGETS, graphics, x + 25 + 102, y + 26 + (names.size() * STATION_ENTRY_HEIGHT) + 1, 138, 92, 1, 18); // textbox - - GuiUtils.drawTexture(GUI_WIDGETS, graphics, x + 129, y + 26 + (names.size() * STATION_ENTRY_HEIGHT) + 1, 0, 92, 35, 18); // textbox - GuiUtils.drawTexture(GUI_WIDGETS, graphics, x + 129 + 34, y + 26 + (names.size() * STATION_ENTRY_HEIGHT) + 1, 138, 92, 1, 18); // textbox - GuiUtils.drawTexture(GUI_WIDGETS, graphics, addButton.getX(), addButton.getY(), 200, 16, 16, 16); // add button - - for (GuiAreaDefinition def : removeStationButtons.values()) { - GuiUtils.drawTexture(GUI_WIDGETS, graphics, def.getX(), def.getY(), 232, 0, 16, 16); // delete button - } - - int i = 0; - for (Entry entry : names.entrySet()) { - MutableComponent name = TextUtils.text(entry.getKey()); - int maxTextWidth = 104 - 12; - if (shadowlessFont.width(name) > maxTextWidth) { - name = TextUtils.text(shadowlessFont.substrByWidth(name, maxTextWidth).getString()).append(Constants.ELLIPSIS_STRING); - } - GuiUtils.drawString(graphics, shadowlessFont, x + 30, y + 26 + (i * STATION_ENTRY_HEIGHT) + 6, name, 0xFFFFFF, EAlignment.LEFT, false); - - StationInfo info = entry.getValue(); - MutableComponent platform = TextUtils.text(info.platform()); - int maxPlatformWidth = 35 - 7; - if (shadowlessFont.width(platform) > maxPlatformWidth) { - platform = TextUtils.text(shadowlessFont.substrByWidth(platform, maxPlatformWidth - 3).getString()).append(Constants.ELLIPSIS_STRING); - } - int platformTextWidth = shadowlessFont.width(platform); - GuiUtils.drawString(graphics, shadowlessFont, x + 30 + 130 - platformTextWidth, y + 26 + (i * STATION_ENTRY_HEIGHT) + 6, platform, 0xFFFFFF, EAlignment.LEFT, false); - i++; - } - - } else { - MutableComponent name = TextUtils.text(alias.getAliasName().get()); - int maxTextWidth = 129; - if (shadowlessFont.width(name) > maxTextWidth) { - name = TextUtils.text(shadowlessFont.substrByWidth(name, maxTextWidth).getString()).append(Constants.ELLIPSIS_STRING); - } - GuiUtils.drawString(graphics, shadowlessFont, x + 30, y + 10, name, 0xFFFFFF, EAlignment.LEFT, false); - - graphics.poseStack().scale(0.75f, 0.75f, 0.75f); - GuiUtils.drawString(graphics, shadowlessFont, (int)((x + 5) / 0.75f), (int)((y + 30) / 0.75f), TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".alias_settings.summary", alias.getAllStationNames().size()), 0xDBDBDB, EAlignment.LEFT, false); - if (alias.getLastEditorName() != null && !alias.getLastEditorName().isBlank()) { - GuiUtils.drawString(graphics, shadowlessFont, (int)((x + 5) / 0.75f), (int)((y + 38) / 0.75f), TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".alias_settings.editor", alias.getLastEditorName(), alias.getLastEditedTimeFormatted()), 0xDBDBDB, EAlignment.LEFT, false); - } - float s = 1 / 0.75f; - graphics.poseStack().scale(s, s, s); - } - - super.renderMainLayer(graphics, pMouseX, pMouseY, pPartialTick); - - // Button highlight - if (deleteButton.isInBounds(pMouseX, pMouseY)) { - GuiUtils.fill(graphics, deleteButton.getX(), deleteButton.getY(), deleteButton.getWidth(), deleteButton.getHeight(), 0x1AFFFFFF); - } else if (expandButton.isInBounds(pMouseX, pMouseY)) { - GuiUtils.fill(graphics, expandButton.getX(), expandButton.getY(), expandButton.getWidth(), expandButton.getHeight(), 0x1AFFFFFF); - } else if (expanded && addButton.isInBounds(pMouseX, pMouseY)) { - GuiUtils.fill(graphics, addButton.getX(), addButton.getY(), addButton.getWidth(), addButton.getHeight(), 0x1AFFFFFF); - } else if (expanded && removeStationButtons.values().stream().anyMatch(x -> x.isInBounds(pMouseX, pMouseY))) { - for (Entry entry : removeStationButtons.entrySet()) { - if (entry.getValue().isInBounds(pMouseX, pMouseY)) { - GuiUtils.fill(graphics, entry.getValue().getX(), entry.getValue().getY(), entry.getValue().getWidth(), entry.getValue().getHeight(), 0x1AFFFFFF); - } - } - } else if (expanded && stationInfoAreas.values().stream().anyMatch(x -> x.isInBounds(pMouseX, pMouseY))) { - for (Entry entry : stationInfoAreas.entrySet()) { - if (entry.getValue().isInBounds(pMouseX, pMouseY)) { - GuiUtils.fill(graphics, entry.getValue().getX(), entry.getValue().getY(), entry.getValue().getWidth(), entry.getValue().getHeight(), 0x1AFFFFFF); - } - } - } - } - - @Override - public void renderFrontLayer(Graphics graphics, int mouseX, int mouseY, float partialTicks) { - GuiUtils.renderTooltipWithOffset(parent, newEntryBox, List.of(tooltipStationName), width / 2, graphics, mouseX, mouseY, 0, parent.getScrollOffset(partialTicks)); - GuiUtils.renderTooltipWithOffset(parent, newEntryPlatformBox, List.of(tooltipPlatform), width / 2, graphics, mouseX, mouseY, 0, parent.getScrollOffset(partialTicks)); - GuiUtils.renderTooltipWithOffset(parent, deleteButton, List.of(tooltipDeleteAlias), width / 2, graphics, mouseX, mouseY, 0, parent.getScrollOffset(partialTicks)); - GuiUtils.renderTooltipWithOffset(parent, expandButton, List.of(expanded ? Constants.TOOLTIP_COLLAPSE : Constants.TOOLTIP_EXPAND), width / 2, graphics, mouseX, mouseY, 0, parent.getScrollOffset(partialTicks)); - - if (expanded) { - GuiUtils.renderTooltipWithOffset(parent, addButton, List.of(tooltipAddStation), width / 2, graphics, mouseX, mouseY, 0, parent.getScrollOffset(partialTicks)); - for (Entry entry : removeStationButtons.entrySet()) { - if (GuiUtils.renderTooltipWithOffset(parent, entry.getValue(), List.of(tooltipDeleteStation), width / 2, graphics, mouseX, mouseY, 0, parent.getScrollOffset(partialTicks))) { - break; - } - } - - for (Entry entry : stationNameAreas.entrySet()) { - if (shadowlessFont.width(entry.getKey()) > 104 - 12 && GuiUtils.renderTooltipAt(parent, entry.getValue(), List.of(TextUtils.text(entry.getKey())), width, graphics, entry.getValue().getLeft() + 1, entry.getValue().getTop() - parent.getScrollOffset(partialTicks), mouseX, mouseY, 0, parent.getScrollOffset(partialTicks))) { - break; - } - } - - for (Entry entry : stationInfoAreas.entrySet()) { - MutableComponent text = TextUtils.text(alias.getInfoForStation(entry.getKey()).platform()); - if ((selectedStationName == null || !entry.getKey().equals(selectedStationName)) && shadowlessFont.width(text) > 35 - 7 && GuiUtils.renderTooltipAt(parent, entry.getValue(), List.of(text), width, graphics, entry.getValue().getLeft(), entry.getValue().getTop() - parent.getScrollOffset(partialTicks), mouseX, mouseY, 0, parent.getScrollOffset(partialTicks))) { - break; - } - } - } else { - if (titleBarArea.isInBounds(mouseX, mouseY + parent.getScrollOffset(partialTicks)) && shadowlessFont.width(alias.getAliasName().get()) > 129) { - GuiUtils.renderTooltipAt(parent, titleBarArea, List.of(TextUtils.text(alias.getAliasName().get())), width, graphics, titleBarArea.getLeft() + 1, titleBarArea.getTop() - parent.getScrollOffset(partialTicks), mouseX, mouseY, 0, parent.getScrollOffset(partialTicks)); - } - } - } - - @Override - public void renderSuggestions(PoseStack matrixStack, int mouseX, int mouseY, float partialTicks) { - if (destinationSuggestions != null) { - matrixStack.pushPose(); - matrixStack.translate(0, -parent.getScrollOffset(partialTicks), 500); - destinationSuggestions.render(matrixStack, mouseX, mouseY + parent.getScrollOffset(partialTicks)); - matrixStack.popPose(); - } - } - - @Override - public boolean mouseClickedLoop(double pMouseX, double pMouseY, int pButton) { - //parent.unfocusAllEntries(); - - if (destinationSuggestions != null && destinationSuggestions.mouseClicked((int) pMouseX, (int) pMouseY, pButton)) - return super.mouseClicked(pMouseX, pMouseY, pButton); - - return false; - } - - @Override - public boolean mouseClicked(double pMouseX, double pMouseY, int pButton) { - - if (expanded && stationInfoAreas.values().stream().anyMatch(x -> x.isInBounds(pMouseX, pMouseY))) { - for (Entry entry : stationInfoAreas.entrySet()) { - if (entry.getValue().isInBounds(pMouseX, pMouseY)) { - editStationInfo(entry.getKey(), entry.getValue()); - return super.mouseClicked(pMouseX, pMouseY, pButton); - } - } - } - - editAliasPlatform.setFocus(false); - - if (deleteButton.isInBounds(pMouseX, pMouseY)) { - deleteAlias(); - return super.mouseClicked(pMouseX, pMouseY, pButton); - } else if (expandButton.isInBounds(pMouseX, pMouseY)) { - toggleExpanded(); - return super.mouseClicked(pMouseX, pMouseY, pButton); - } else if (expanded && addButton.isInBounds(pMouseX, pMouseY)) { - addStation(newEntryBox.getValue(), new StationInfo(newEntryPlatformBox.getValue())); - return super.mouseClicked(pMouseX, pMouseY, pButton); - } else if (expanded && removeStationButtons.values().stream().anyMatch(x -> x.isInBounds(pMouseX, pMouseY))) { - for (Entry entry : removeStationButtons.entrySet()) { - if (entry.getValue().isInBounds(pMouseX, pMouseY)) { - removeStation(entry.getKey()); - return super.mouseClicked(pMouseX, pMouseY, pButton); - } - } - } - - return super.mouseClicked(pMouseX, pMouseY, pButton); - } - - @Override - public boolean mouseScrolledLoop(double pMouseX, double pMouseY, double pDelta) { - if (destinationSuggestions != null && destinationSuggestions.mouseScrolled(pMouseX, pMouseY, MathUtils.clamp(pDelta, -1.0D, 1.0D))) - return true; - - return false; - } - - private void clearSuggestions() { - if (destinationSuggestions != null) { - destinationSuggestions.getEditBox().setSuggestion(""); - } - destinationSuggestions = null; - } - - - protected void updateEditorSubwidgets(DLEditBox field) { - clearSuggestions(); - - destinationSuggestions = new ModStationSuggestions(minecraft, parent, field, minecraft.font, getViableStations(field), field.getHeight() + 2 + field.y); - destinationSuggestions.setAllowSuggestions(true); - destinationSuggestions.updateCommandInfo(); - } - - private List getViableStations(DLEditBox field) { - return ClientTrainStationSnapshot.getInstance().getAllTrainStations().stream() - .distinct() - .filter(x -> !GlobalSettingsManager.getInstance().getSettingsData().isBlacklisted(x) && !alias.contains(x)) - .sorted((a, b) -> a.compareTo(b)) - .toList(); - } - - @Override - public NarrationPriority narrationPriority() { - return NarrationPriority.HOVERED; - } - - @Override - public void updateNarration(NarrationElementOutput narrationElementOutput) { - } -} diff --git a/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/CRNListBox.java b/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/CRNListBox.java new file mode 100644 index 00000000..277dbf12 --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/CRNListBox.java @@ -0,0 +1,87 @@ +package de.mrjulsen.crn.client.gui.widgets; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.Map.Entry; +import java.util.function.Function; + +import de.mrjulsen.mcdragonlib.client.gui.widgets.DLAbstractScrollBar; +import de.mrjulsen.mcdragonlib.client.gui.widgets.DLButton; +import de.mrjulsen.mcdragonlib.client.gui.widgets.ScrollableWidgetContainer; +import de.mrjulsen.mcdragonlib.client.util.Graphics; +import de.mrjulsen.mcdragonlib.client.util.GuiUtils; +import net.minecraft.client.gui.narration.NarrationElementOutput; +import net.minecraft.client.gui.screens.Screen; + +public class CRNListBox extends ScrollableWidgetContainer { + + private final Screen parent; + private final DLAbstractScrollBar scrollBar; + private int contentHeight = 0; + private final Map values = new HashMap<>(); + + public CRNListBox(Screen parent, int x, int y, int width, int height, DLAbstractScrollBar scrollBar) { + super(x, y, width, height); + this.parent = parent; + this.scrollBar = scrollBar; + + scrollBar.setAutoScrollerSize(true); + scrollBar.setScreenSize(height()); + scrollBar.updateMaxScroll(0); + scrollBar.withOnValueChanged((sb) -> setYScrollOffset(sb.getScrollValue())); + scrollBar.setStepSize(10); + } + + public Screen getParent() { + return parent; + } + + public void displayData(List data, Function createItem) { + clearWidgets(); + values.clear(); + contentHeight = 0; + for (int i = 0; i < data.size(); i++) { + T entry = data.get(i); + W widget = createItem.apply(entry); + widget.set_x(x()); + widget.set_width(width()); + widget.set_y(y() + contentHeight); + addRenderableWidget(widget); + values.put(widget, entry); + contentHeight += widget.height(); + } + scrollBar.updateMaxScroll(contentHeight); + } + + public Set> getEntries() { + return values.entrySet(); + } + + @Override + public void renderMainLayer(Graphics graphics, int mouseX, int mouseY, float partialTicks) { + super.renderMainLayer(graphics, mouseX, mouseY, partialTicks); + + if (scrollBar.getScrollValue() > 0) { + GuiUtils.fillGradient(graphics, x(), y(), 0, width(), 10, 0x77000000, 0x00000000); + } + if (scrollBar.getScrollValue() < scrollBar.getMaxScroll()) { + GuiUtils.fillGradient(graphics, x(), y() + height() - 10, 0, width(), 10, 0x00000000, 0x77000000); + } + } + + @Override + public NarrationPriority narrationPriority() { + return NarrationPriority.HOVERED; + } + + @Override + public void updateNarration(NarrationElementOutput narrationElementOutput) {} + + @Override + public boolean consumeScrolling(double mouseX, double mouseY) { + return false; + } + +} diff --git a/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/ColorPickerWidget.java b/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/ColorPickerWidget.java new file mode 100644 index 00000000..94ac0b66 --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/ColorPickerWidget.java @@ -0,0 +1,109 @@ +package de.mrjulsen.crn.client.gui.widgets; + +import java.util.function.Consumer; +import java.util.function.Supplier; + +import de.mrjulsen.crn.CreateRailwaysNavigator; +import de.mrjulsen.mcdragonlib.DragonLib; +import de.mrjulsen.mcdragonlib.client.gui.DLColorPickerScreen; +import de.mrjulsen.mcdragonlib.client.gui.DLScreen; +import de.mrjulsen.mcdragonlib.client.gui.widgets.DLButton; +import de.mrjulsen.mcdragonlib.client.gui.widgets.WidgetContainer; +import de.mrjulsen.mcdragonlib.client.util.Graphics; +import de.mrjulsen.mcdragonlib.client.util.GuiAreaDefinition; +import de.mrjulsen.mcdragonlib.client.util.GuiUtils; +import de.mrjulsen.mcdragonlib.core.EAlignment; +import de.mrjulsen.mcdragonlib.util.TextUtils; +import net.minecraft.client.gui.narration.NarrationElementOutput; +import net.minecraft.client.gui.screens.Screen; + +public class ColorPickerWidget extends WidgetContainer { + + private int selectedColor = 0; + + public ColorPickerWidget(Screen parent, int px, int py, int[] sampleColors, int maxColorsPerLine, int preselectedColor, Consumer onPick) { + super(px, py, 1, 1); + this.selectedColor = preselectedColor; + + int lines = (int)Math.ceil((double)sampleColors.length / (double)maxColorsPerLine); + set_height(18 * 2 + lines * 13 - 1); + set_width(maxColorsPerLine * 13 - 1); + addRenderableWidget(new ColorBrowserButton(x(), y(), width(), () -> selectedColor, (btn) -> { + DLScreen.setScreen(new DLColorPickerScreen(parent, selectedColor, c -> { + selectedColor = c.toInt(); + onPick.accept(this); + }, true)); + })); + for (int i = 0, y = 0; y < lines && i < sampleColors.length; y++) { + for (int x = 0; x < maxColorsPerLine && i < sampleColors.length; x++, i++) { + final int j = i; + addRenderableWidget(new ColorButton(x() + x * 13, y() + 18 + y * 13, sampleColors[j], btn -> { + selectedColor = sampleColors[j]; + onPick.accept(this); + })); + } + } + addRenderableWidget(new NoColorButton(x(), y() + height() - 16, width(), (btn) -> { + selectedColor = 0; + onPick.accept(this); + })); + } + + public int getSelectedColor() { + return selectedColor; + } + + @Override + public NarrationPriority narrationPriority() { + return NarrationPriority.HOVERED; + } + + @Override + public void updateNarration(NarrationElementOutput narrationElementOutput) {} + + @Override + public boolean consumeScrolling(double mouseX, double mouseY) { + return false; + } + + + private static class ColorBrowserButton extends DLButton { + private final Supplier color; + public ColorBrowserButton(int pX, int pY, int pWidth, Supplier color, Consumer pOnPress) { + super(pX, pY, pWidth, 16, TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".color_picker.custom"), pOnPress); + this.color = color; + } + + @Override + public void renderMainLayer(Graphics graphics, int mouseX, int mouseY, float partialTick) { + GuiUtils.drawBox(graphics, GuiAreaDefinition.of(this), color.get(), isMouseSelected() ? DragonLib.NATIVE_BUTTON_FONT_COLOR_ACTIVE : DragonLib.NATIVE_BUTTON_FONT_COLOR_DISABLED); + GuiUtils.drawString(graphics, font, x() + width() / 2, y() + height() / 2 - font.lineHeight / 2, getMessage(), DragonLib.NATIVE_BUTTON_FONT_COLOR_ACTIVE, EAlignment.CENTER, true); + } + } + + private static class ColorButton extends DLButton { + private final int color; + public ColorButton(int pX, int pY, int color, Consumer pOnPress) { + super(pX, pY, 12, 12, TextUtils.empty(), pOnPress); + this.color = color; + } + + @Override + public void renderMainLayer(Graphics graphics, int mouseX, int mouseY, float partialTick) { + GuiUtils.drawBox(graphics, GuiAreaDefinition.of(this), color, isMouseSelected() ? DragonLib.NATIVE_BUTTON_FONT_COLOR_ACTIVE : DragonLib.NATIVE_BUTTON_FONT_COLOR_DISABLED); + GuiUtils.drawString(graphics, font, x() + width() / 2, y() + height() / 2 - font.lineHeight / 2, getMessage(), DragonLib.NATIVE_BUTTON_FONT_COLOR_ACTIVE, EAlignment.CENTER, true); + } + } + + private static class NoColorButton extends DLButton { + public NoColorButton(int pX, int pY, int pWidth, Consumer pOnPress) { + super(pX, pY, pWidth, 16, TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".color_picker.no_color"), pOnPress); + } + + @Override + public void renderMainLayer(Graphics graphics, int mouseX, int mouseY, float partialTick) { + GuiUtils.drawBox(graphics, GuiAreaDefinition.of(this), 0, isMouseSelected() ? DragonLib.NATIVE_BUTTON_FONT_COLOR_ACTIVE : DragonLib.NATIVE_BUTTON_FONT_COLOR_DISABLED); + GuiUtils.drawString(graphics, font, x() + width() / 2, y() + height() / 2 - font.lineHeight / 2, getMessage(), DragonLib.NATIVE_BUTTON_FONT_COLOR_ACTIVE, EAlignment.CENTER, true); + } + } +} diff --git a/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/DLCreateIconButton.java b/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/DLCreateIconButton.java index 4aac7a96..b7cf048e 100644 --- a/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/DLCreateIconButton.java +++ b/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/DLCreateIconButton.java @@ -1,5 +1,10 @@ package de.mrjulsen.crn.client.gui.widgets; +import javax.annotation.Nonnull; + +import com.mojang.blaze3d.systems.RenderSystem; +import com.mojang.blaze3d.vertex.PoseStack; +import com.simibubi.create.foundation.gui.AllGuiTextures; import com.simibubi.create.foundation.gui.element.ScreenElement; import com.simibubi.create.foundation.gui.widget.IconButton; @@ -18,6 +23,20 @@ public DLCreateIconButton(int x, int y, ScreenElement icon) { super(x, y, icon); } + @Override + public void renderButton(@Nonnull PoseStack matrixStack, int mouseX, int mouseY, float partialTicks) { + if (visible) { + isHovered = mouseX >= x && mouseY >= y && mouseX < x + width && mouseY < y + height; + + AllGuiTextures button = !isActive() ? AllGuiTextures.BUTTON_DOWN + : isMouseSelected() ? AllGuiTextures.BUTTON_HOVER : AllGuiTextures.BUTTON; + + RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F); + drawBg(matrixStack, button); + icon.render(matrixStack, x + 1, y + 1); + } + } + @Override public void onFocusChangeEvent(boolean focus) {} diff --git a/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/DLCreateLabel.java b/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/DLCreateLabel.java index db758ce3..952896b8 100644 --- a/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/DLCreateLabel.java +++ b/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/DLCreateLabel.java @@ -33,7 +33,7 @@ public boolean isMouseSelected() { @Override public void setMouseSelected(boolean selected) { this.mouseSelected = selected; - } + } @Override public int x() { diff --git a/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/DLCreateSelectionScrollInput.java b/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/DLCreateSelectionScrollInput.java index 8a5718fe..a9fd0c27 100644 --- a/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/DLCreateSelectionScrollInput.java +++ b/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/DLCreateSelectionScrollInput.java @@ -32,7 +32,7 @@ public boolean isMouseSelected() { @Override public void setMouseSelected(boolean selected) { this.mouseSelected = selected; - } + } @Override public int x() { diff --git a/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/ExpandButton.java b/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/ExpandButton.java deleted file mode 100644 index 2518f7b5..00000000 --- a/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/ExpandButton.java +++ /dev/null @@ -1,59 +0,0 @@ -package de.mrjulsen.crn.client.gui.widgets; - -import java.util.function.Consumer; - -import de.mrjulsen.crn.CreateRailwaysNavigator; -import de.mrjulsen.mcdragonlib.client.gui.widgets.DLButton; -import de.mrjulsen.mcdragonlib.client.util.Graphics; -import de.mrjulsen.mcdragonlib.client.util.GuiUtils; -import de.mrjulsen.mcdragonlib.core.EAlignment; -import de.mrjulsen.mcdragonlib.util.TextUtils; -import net.minecraft.ChatFormatting; - -public class ExpandButton extends DLButton { - - public static final int WIDTH = 200; - public static final int HEIGHT = 48; - - // Data - private boolean expanded; - - private static final String expandText = "â–Œ " + TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".common.expand").getString(); - private static final String collapseText = "â–Č " + TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".common.collapse").getString(); - - - public ExpandButton(int pX, int pY, boolean initialState, Consumer onClick) { - super(pX, pY, 20, 20, TextUtils.empty(), onClick); - this.expanded = initialState; - - int w1 = font.width(expandText) + 10; - int w2 = font.width(collapseText) + 10; - int h = font.lineHeight + 6; - - width = w1 > w2 ? w1 : w2; - height = h; - } - - @Override - public boolean mouseClicked(double pMouseX, double pMouseY, int pButton) { - if (isMouseOver(pMouseX, pMouseY)) { - expanded = !expanded; - } - return super.mouseClicked(pMouseX, pMouseY, pButton); - } - - @Override - public void renderMainLayer(Graphics graphics, int pMouseX, int pMouseY, float pPartialTick) { - if (isMouseOver(pMouseX, pMouseY)) { - GuiUtils.fill(graphics, x, y, width, height, 0x1AFFFFFF); - GuiUtils.drawString(graphics, font, x + width / 2, y + height / 2 - font.lineHeight / 2, expanded ? TextUtils.text(collapseText).withStyle(ChatFormatting.UNDERLINE) : TextUtils.text(expandText).withStyle(ChatFormatting.UNDERLINE), 0xFFFFFF, EAlignment.CENTER, true); - } else { - GuiUtils.drawString(graphics, font, x + width / 2, y + height / 2 - font.lineHeight / 2, expanded ? TextUtils.text(collapseText) : TextUtils.text(expandText), 0xFFFFFF, EAlignment.CENTER, true); - } - } - - public boolean isExpanded() { - return expanded; - } - -} diff --git a/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/FlatCheckBox.java b/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/FlatCheckBox.java new file mode 100644 index 00000000..3eb558a3 --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/FlatCheckBox.java @@ -0,0 +1,34 @@ +package de.mrjulsen.crn.client.gui.widgets; + +import java.util.function.Consumer; + +import de.mrjulsen.mcdragonlib.DragonLib; +import de.mrjulsen.mcdragonlib.client.gui.widgets.DLCheckBox; +import de.mrjulsen.mcdragonlib.client.render.GuiIcons; +import de.mrjulsen.mcdragonlib.client.util.Graphics; +import de.mrjulsen.mcdragonlib.client.util.GuiUtils; +import de.mrjulsen.mcdragonlib.core.EAlignment; + +public class FlatCheckBox extends DLCheckBox { + + public FlatCheckBox(int pX, int pY, int pWidth, String pMessage, boolean checked, Consumer onCheckedChanged) { + super(pX, pY, pWidth, pMessage, checked, onCheckedChanged); + set_height(GuiIcons.ICON_SIZE); + } + + @Override + public void renderMainLayer(Graphics graphics, int pMouseX, int pMouseY, float pPartialTick) { + if (isChecked()) { + GuiIcons.CHECKMARK.render(graphics, x(), y()); + } + GuiUtils.drawString(graphics, font, x() + GuiIcons.ICON_SIZE + 2, y() + height() / 2 - font.lineHeight / 2, getMessage(), DragonLib.NATIVE_BUTTON_FONT_COLOR_ACTIVE, EAlignment.LEFT, false); + + if (isMouseSelected()) { + GuiUtils.fill(graphics, x(), y(), width(), height(), 0x44FFFFFF); + } + } + + @Override + public void renderFrontLayer(Graphics graphics, int mouseX, int mouseY, float partialTicks) { + } +} diff --git a/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/IEntryListSettingsOption.java b/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/IEntryListSettingsOption.java deleted file mode 100644 index ccc91eab..00000000 --- a/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/IEntryListSettingsOption.java +++ /dev/null @@ -1,29 +0,0 @@ -package de.mrjulsen.crn.client.gui.widgets; - -import com.mojang.blaze3d.vertex.PoseStack; - -public interface IEntryListSettingsOption { - void setYPos(int y); - void tick(); - int calcHeight(); - void renderSuggestions(PoseStack poseStack, int mouseX, int mouseY, float partialTicks); - /** - * This method is always called, even if the used clicked outside the working area of the container window. - * This additional method should fix the usage of the suggestions popup, which can be rendered outside of the working area. - * @param pMouseX - * @param pMouseY - * @param pButton - * @return - */ - boolean mouseClickedLoop(double pMouseX, double pMouseY, int pButton); - - /** - * This method is always called, even if the used scrolled outside the working area of the container window. - * This additional method should fix the usage of the suggestions popup, which can be rendered outside of the working area. - * @param pMouseX - * @param pMouseY - * @param pDelta - * @return - */ - boolean mouseScrolledLoop(double pMouseX, double pMouseY, double pDelta); -} diff --git a/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/ModDestinationSuggestions.java b/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/ModDestinationSuggestions.java index f1df5aef..d9ed0959 100644 --- a/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/ModDestinationSuggestions.java +++ b/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/ModDestinationSuggestions.java @@ -6,7 +6,7 @@ import com.mojang.brigadier.context.StringRange; import com.mojang.brigadier.suggestion.Suggestion; -import de.mrjulsen.crn.data.TrainStationAlias; +import de.mrjulsen.crn.data.StationTag; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.Font; import net.minecraft.client.gui.components.EditBox; @@ -15,7 +15,7 @@ public class ModDestinationSuggestions extends ModCommandSuggestions { - private List viableStations; + private List viableStations; private String previous = "<>"; private Font font; private boolean active; @@ -23,7 +23,7 @@ public class ModDestinationSuggestions extends ModCommandSuggestions { private List currentSuggestions; private int yOffset; - public ModDestinationSuggestions(Minecraft pMinecraft, Screen pScreen, EditBox pInput, Font pFont, List viableStations, int yOffset) { + public ModDestinationSuggestions(Minecraft pMinecraft, Screen pScreen, EditBox pInput, Font pFont, List viableStations, int yOffset) { super(pMinecraft, pScreen, pInput, pFont, true, true, 0, 7, false, 0xee_303030); this.font = pFont; this.viableStations = viableStations; @@ -53,8 +53,8 @@ public void updateCommandInfo() { previous = value; currentSuggestions = viableStations.stream() - .filter(ia -> !ia.getAliasName().get().equals(value) && ia.getAliasName().get().toLowerCase().startsWith(value.toLowerCase())) - .map(s -> new Suggestion(new StringRange(0, s.getAliasName().get().length()), s.getAliasName().get())) + .filter(ia -> !ia.getTagName().get().equals(value) && ia.getTagName().get().toLowerCase().startsWith(value.toLowerCase())) + .map(s -> new Suggestion(new StringRange(0, s.getTagName().get().length()), s.getTagName().get())) .toList(); showSuggestions(false); diff --git a/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/ModernVerticalScrollBar.java b/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/ModernVerticalScrollBar.java new file mode 100644 index 00000000..31816928 --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/ModernVerticalScrollBar.java @@ -0,0 +1,46 @@ +package de.mrjulsen.crn.client.gui.widgets; + +import de.mrjulsen.mcdragonlib.client.ITickable; +import de.mrjulsen.mcdragonlib.client.gui.widgets.DLVerticalScrollBar; +import de.mrjulsen.mcdragonlib.client.util.Graphics; +import de.mrjulsen.mcdragonlib.client.util.GuiAreaDefinition; +import de.mrjulsen.mcdragonlib.client.util.GuiUtils; +import net.minecraft.client.gui.screens.Screen; + +public class ModernVerticalScrollBar extends DLVerticalScrollBar implements ITickable { + + private final Screen parent; + + public ModernVerticalScrollBar(Screen parent, int x, int y, int h, GuiAreaDefinition scrollArea) { + super(x, y, 5, h, null); + this.parent = parent; + setAutoScrollerSize(true); + set_width(5); + } + + @Override + public void renderMainLayer(Graphics graphics, int pMouseX, int pMouseY, float pPartialTick) { + // Render background + if (isMouseSelected() || (parent.isDragging() && isScrolling)) { + GuiUtils.fill(graphics, x(), y(), width(), height(), 0xFF444444); + } + + // Render scrollbar + int x1 = x + (isMouseSelected() || (parent.isDragging() && isScrolling) ? 0 : 2); + int y1 = y + (int)(scrollPercentage * (height - scrollerSize)); + int w = isMouseSelected() || (parent.isDragging() && isScrolling) ? width : 1; + int h = scrollerSize; + + if (canScroll()) { + GuiUtils.fill(graphics, x1, y1 + (isMouseSelected() || (parent.isDragging() && isScrolling) ? 0 : 2), w, h - (isMouseSelected() || (parent.isDragging() && isScrolling) ? 0 : 4), 0x88FFFFFF); + } + } + + @Override + public void tick() { + if (!parent.isDragging()) { + isScrolling = false; + } + } + +} diff --git a/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/ResizableButton.java b/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/ResizableButton.java new file mode 100644 index 00000000..8f35fc30 --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/ResizableButton.java @@ -0,0 +1,35 @@ +package de.mrjulsen.crn.client.gui.widgets; + +import com.mojang.blaze3d.vertex.PoseStack; + +import de.mrjulsen.mcdragonlib.DragonLib; +import de.mrjulsen.mcdragonlib.client.render.DynamicGuiRenderer; +import de.mrjulsen.mcdragonlib.client.render.DynamicGuiRenderer.AreaStyle; +import de.mrjulsen.mcdragonlib.client.render.DynamicGuiRenderer.ButtonState; +import de.mrjulsen.mcdragonlib.client.util.Graphics; +import de.mrjulsen.mcdragonlib.client.util.GuiAreaDefinition; +import de.mrjulsen.mcdragonlib.client.util.GuiUtils; +import de.mrjulsen.mcdragonlib.core.EAlignment; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.components.Button; +import net.minecraft.network.chat.Component; + +public class ResizableButton extends Button { + + public ResizableButton(int x, int y, int width, int height, Component message, OnPress onPress, OnTooltip onTooltip) { + super(x, y, width, height, message, onPress, onTooltip); + } + + public ResizableButton(int x, int y, int width, int height, Component message, OnPress onPress) { + super(x, y, width, height, message, onPress, NO_TOOLTIP); + } + + @SuppressWarnings("resource") + @Override + public void renderButton(PoseStack poseStack, int mouseX, int mouseY, float partialTick) { + Graphics graphics = new Graphics(poseStack); + DynamicGuiRenderer.renderArea(graphics, GuiAreaDefinition.of(this), AreaStyle.NATIVE, isActive() ? (isHoveredOrFocused() ? ButtonState.SELECTED : ButtonState.BUTTON) : ButtonState.DISABLED); + int j = isActive() ? DragonLib.NATIVE_BUTTON_FONT_COLOR_ACTIVE : DragonLib.NATIVE_BUTTON_FONT_COLOR_DISABLED; + GuiUtils.drawString(graphics, Minecraft.getInstance().font, this.x + this.width / 2, this.y + (this.height - 8) / 2, this.getMessage(), j, EAlignment.CENTER, true); + } +} diff --git a/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/RouteDetailsViewer.java b/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/RouteDetailsViewer.java new file mode 100644 index 00000000..0bef955b --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/RouteDetailsViewer.java @@ -0,0 +1,146 @@ +package de.mrjulsen.crn.client.gui.widgets; + +import java.util.Set; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.function.Predicate; +import java.util.HashSet; +import java.util.List; +import java.util.Queue; + +import de.mrjulsen.crn.Constants; +import de.mrjulsen.crn.client.gui.widgets.routedetails.RouteDetailsTransferWidget; +import de.mrjulsen.crn.client.gui.widgets.routedetails.RoutePartWidget; +import de.mrjulsen.crn.data.navigation.ClientRoute; +import de.mrjulsen.crn.data.navigation.ClientRoutePart; +import de.mrjulsen.crn.data.navigation.RoutePart; +import de.mrjulsen.crn.data.navigation.TransferConnection; +import de.mrjulsen.mcdragonlib.client.gui.widgets.DLAbstractScrollBar; +import de.mrjulsen.mcdragonlib.client.gui.widgets.ScrollableWidgetContainer; +import de.mrjulsen.mcdragonlib.client.util.Graphics; +import de.mrjulsen.mcdragonlib.client.util.GuiUtils; +import net.minecraft.client.gui.narration.NarrationElementOutput; +import net.minecraft.client.gui.screens.Screen; + +public class RouteDetailsViewer extends ScrollableWidgetContainer { + + private final DLAbstractScrollBar scrollBar; + private int contentHeight = 0; + private Set expandedParts = new HashSet<>(); + private boolean canExpandCollapse = true; + private boolean showTrainDetails = true; + private boolean initialExpanded = false; + private boolean showJourney = false; + + private final Screen parent; + + public RouteDetailsViewer(Screen parent, int x, int y, int width, int height, DLAbstractScrollBar scrollBar) { + super(x, y, width, height); + this.scrollBar = scrollBar; + this.parent = parent; + + scrollBar.setAutoScrollerSize(true); + scrollBar.setScreenSize(height()); + scrollBar.updateMaxScroll(0); + scrollBar.withOnValueChanged((sb) -> setYScrollOffset(sb.getScrollValue())); + scrollBar.setStepSize(10); + } + + public void displayRoute(ClientRoute route) { + displayRouteInternal(route, route.getClientParts(), true); + } + + public void displayPart(ClientRoute route, Predicate verifiedSelector) { + displayRouteInternal(route, route.getClientParts().stream().filter(verifiedSelector).toList(), false); + } + + public void displayRouteInternal(ClientRoute route, List parts, boolean displayConnections) { + clearWidgets(); + contentHeight = 10; + Queue connections = new ConcurrentLinkedQueue<>(route.getConnections()); + for (int i = 0; i < parts.size(); i++) { + ClientRoutePart part = parts.get(i); + RoutePartWidget widget = new RoutePartWidget(parent, x(), y() + contentHeight, width(), route, part); + widget.setShowTrainDetails(showTrainDetails); + widget.setCanExpandCollapse(canExpandCollapse); + widget.setShowJourney(showJourney); + widget.setExpanded(expandedParts.contains(part) || initialExpanded); + widget.withOnGuiChangedEvent((w) -> { + if (w.isExpanded()) expandedParts.add(part); else expandedParts.remove(part); + displayRoute(route); + }); + addRenderableWidget(widget); + contentHeight += widget.height(); + + if (!connections.isEmpty() && displayConnections) { + RouteDetailsTransferWidget transfer = addRenderableOnly(new RouteDetailsTransferWidget(x(), y() + contentHeight, width(), connections.poll())); + contentHeight += transfer.height(); + } + } + + contentHeight += 10; + scrollBar.updateMaxScroll(contentHeight); + } + + @Override + public void renderMainLayer(Graphics graphics, int mouseX, int mouseY, float partialTicks) { + super.renderMainLayer(graphics, mouseX, mouseY, partialTicks); + + GuiUtils.fillGradient(graphics, x(), y(), 0, width(), 10, 0x77000000, 0x00000000); + GuiUtils.fillGradient(graphics, x(), y() + height() - 10, 0, width(), 10, 0x00000000, 0x77000000); + + //DLUtils.doIfNotNull(route, r -> GuiUtils.drawString(graphics, font, x(), y(), r.getState().name() + ", Running: " + !r.isClosed(), 0xFFFF0000, EAlignment.LEFT, false)); + } + + @Override + public void renderMainLayerScrolled(Graphics graphics, int mouseX, int mouseY, float partialTicks) { + GuiUtils.drawTexture(Constants.GUI_WIDGETS, graphics, x(), y(), 22, 10, 0, 179, 22, 1, 256, 256); + GuiUtils.drawTexture(Constants.GUI_WIDGETS, graphics, x(), y() + contentHeight - 10, 22, Math.max(10, height() - contentHeight + 10), 0, 179, 22, 1, 256, 256); + super.renderMainLayerScrolled(graphics, mouseX, mouseY, partialTicks); + } + + public boolean canExpandCollapse() { + return canExpandCollapse; + } + + public void setCanExpandCollapse(boolean canExpandCollapse) { + this.canExpandCollapse = canExpandCollapse; + } + + public boolean showTrainDetails() { + return showTrainDetails; + } + + public void setShowTrainDetails(boolean showTrainDetails) { + this.showTrainDetails = showTrainDetails; + } + + public boolean isInitialExpanded() { + return initialExpanded; + } + + public void setInitialExpanded(boolean b) { + this.initialExpanded = b; + } + + public boolean isShowingJourney() { + return showJourney; + } + + public void setShowJourney(boolean b) { + this.showJourney = b; + } + + @Override + public NarrationPriority narrationPriority() { + return NarrationPriority.HOVERED; + } + + @Override + public void updateNarration(NarrationElementOutput narrationElementOutput) {} + + @Override + public boolean consumeScrolling(double mouseX, double mouseY) { + return false; + } + +} diff --git a/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/RouteEntryOverviewWidget.java b/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/RouteEntryOverviewWidget.java deleted file mode 100644 index 3a966afa..00000000 --- a/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/RouteEntryOverviewWidget.java +++ /dev/null @@ -1,141 +0,0 @@ -package de.mrjulsen.crn.client.gui.widgets; - -import java.util.function.Consumer; - -import com.simibubi.create.content.trains.station.NoShadowFontWrapper; - -import de.mrjulsen.crn.Constants; -import de.mrjulsen.crn.CreateRailwaysNavigator; -import de.mrjulsen.crn.client.gui.CreateDynamicWidgets; -import de.mrjulsen.crn.client.gui.CreateDynamicWidgets.ColorShade; -import de.mrjulsen.crn.client.gui.screen.NavigatorScreen; -import de.mrjulsen.crn.client.gui.screen.RouteDetailsScreen; -import de.mrjulsen.crn.client.lang.ELanguage; -import de.mrjulsen.crn.config.ModClientConfig; -import de.mrjulsen.crn.data.SimpleRoute; -import de.mrjulsen.crn.data.SimpleRoute.SimpleRoutePart; -import de.mrjulsen.crn.event.listeners.JourneyListenerManager; -import de.mrjulsen.crn.event.listeners.JourneyListener.State; -import de.mrjulsen.mcdragonlib.DragonLib; -import de.mrjulsen.mcdragonlib.client.gui.widgets.DLButton; -import de.mrjulsen.mcdragonlib.client.util.Graphics; -import de.mrjulsen.mcdragonlib.client.util.GuiUtils; -import de.mrjulsen.mcdragonlib.core.EAlignment; -import de.mrjulsen.mcdragonlib.util.ColorUtils; -import de.mrjulsen.mcdragonlib.util.TextUtils; -import de.mrjulsen.mcdragonlib.util.TimeUtils; -import net.minecraft.ChatFormatting; -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.Font; -import net.minecraft.network.chat.MutableComponent; -import net.minecraft.world.level.Level; - -public class RouteEntryOverviewWidget extends DLButton { - - public static final int WIDTH = 200; - public static final int HEIGHT = 54; - - private static final int DISPLAY_WIDTH = 190; - - private final SimpleRoute route; - private final NavigatorScreen parent; - private final Level level; - private final long lastRefreshedTime; - - private final MutableComponent transferText = ELanguage.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".navigator.route_entry.transfer"); - private final MutableComponent connectionInPast = ELanguage.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".navigator.route_entry.connection_in_past"); - private final MutableComponent trainCanceled = ELanguage.translate("gui.createrailwaysnavigator.route_overview.stop_canceled"); - - public RouteEntryOverviewWidget(NavigatorScreen parent, Level level, long lastRefreshedTime, int pX, int pY, SimpleRoute route, Consumer onClick) { - super(pX, pY, WIDTH, HEIGHT, TextUtils.text(route.getName()), onClick); // 48 - this.route = route; - this.parent = parent; - this.level = level; - this.lastRefreshedTime = lastRefreshedTime; - } - - - @Override - public void onClick(double pMouseX, double pMouseY) { - super.onClick(pMouseX, pMouseY); - Minecraft minecraft = Minecraft.getInstance(); - minecraft.setScreen(new RouteDetailsScreen(parent, level, route, route.getListenerId())); - } - - @Override - public void renderMainLayer(Graphics graphics, int pMouseX, int pMouseY, float pPartialTick) { - - final float scale = 0.75f; - float l = isMouseOver(pMouseX, pMouseY) ? 0.1f : 0; - boolean isActive = JourneyListenerManager.getInstance().get(route.getListenerId(), null) != null; - boolean beforeJourney = isActive && JourneyListenerManager.getInstance().get(route.getListenerId(), null).getCurrentState() == State.BEFORE_JOURNEY; - - int color = ColorUtils.lightenColor(ColorShade.DARK.getColor(), l); - if (!beforeJourney) { - color = ColorUtils.applyTint(color, 0x663300); - } - CreateDynamicWidgets.renderSingleShadeWidget(graphics, x, y, WIDTH, HEIGHT, color); - CreateDynamicWidgets.renderHorizontalSeparator(graphics, x + 6, y + 22, 188); - - Minecraft minecraft = Minecraft.getInstance(); - SimpleRoutePart[] parts = route.getParts().toArray(SimpleRoutePart[]::new); - Font shadowlessFont = new NoShadowFontWrapper(minecraft.font); - - String timeStart = TimeUtils.parseTime((int)((lastRefreshedTime + DragonLib.DAYTIME_SHIFT) % DragonLib.TICKS_PER_DAY) + route.getStartStation().getTicks(), ModClientConfig.TIME_FORMAT.get()); - String timeEnd = TimeUtils.parseTime((int)((lastRefreshedTime + DragonLib.DAYTIME_SHIFT) % DragonLib.TICKS_PER_DAY) + route.getEndStation().getTicks(), ModClientConfig.TIME_FORMAT.get()); - String dash = " - "; - MutableComponent line = TextUtils.text(String.format("%s%s%s | %s %s | %s", - timeStart, - dash, - timeEnd, - route.getTransferCount(), - transferText.getString(), - TimeUtils.parseDurationShort(route.getTotalDuration()) - )); - - if (!route.isValid()) { - line = line.withStyle(ChatFormatting.RED).withStyle(ChatFormatting.STRIKETHROUGH); - } - - float localScale = shadowlessFont.width(line) > WIDTH - 12 ? scale : 1; - graphics.poseStack().pushPose(); - graphics.poseStack().scale(localScale, 1, 1); - GuiUtils.drawString(graphics, minecraft.font, (int)((x + 6) / localScale), y + 5, line, 0xFFFFFF, EAlignment.LEFT, false); - graphics.poseStack().popPose(); - - int routePartWidth = DISPLAY_WIDTH / parts.length; - String end = route.getEndStation().getStationName(); - int textW = shadowlessFont.width(end); - - for (int i = 0; i < parts.length; i++) { - GuiUtils.fill(graphics, x + 5 + (i * routePartWidth) + 1, y + 27, routePartWidth - 2, 11, 0xFF393939); - } - - graphics.poseStack().pushPose(); - graphics.poseStack().scale(scale, scale, scale); - - if (route.getStartStation().shouldRenderRealtime()) { - GuiUtils.drawString(graphics, shadowlessFont, (int)((x + 6 + shadowlessFont.width(timeStart) * localScale / 2.0f) / scale) - shadowlessFont.width(timeStart) / 2, (int)((y + 15) / scale), TextUtils.text(TimeUtils.parseTime((int)(route.getStartStation().getEstimatedTimeWithThreshold() % 24000 + DragonLib.DAYTIME_SHIFT), ModClientConfig.TIME_FORMAT.get())), route.getStartStation().isDelayed() ? Constants.COLOR_DELAYED : Constants.COLOR_ON_TIME, EAlignment.LEFT, false); - } - if (route.getEndStation().shouldRenderRealtime()) { - GuiUtils.drawString(graphics, shadowlessFont, (int)((x + 6 + shadowlessFont.width(timeEnd) * localScale * 1.5f + (shadowlessFont.width(dash)) * localScale) / scale) - shadowlessFont.width(timeEnd) / 2, (int)((y + 15) / scale), TextUtils.text(TimeUtils.parseTime((int)(route.getEndStation().getEstimatedTimeWithThreshold() % 24000 + DragonLib.DAYTIME_SHIFT), ModClientConfig.TIME_FORMAT.get())), route.getEndStation().isDelayed() ? Constants.COLOR_DELAYED : Constants.COLOR_ON_TIME, EAlignment.LEFT, false); - } - - if (!route.isValid()) { - GuiUtils.drawString(graphics, shadowlessFont, (int)((x + WIDTH - 5) / scale) - shadowlessFont.width(trainCanceled), (int)((y + 15) / scale), trainCanceled, Constants.COLOR_DELAYED, EAlignment.LEFT, false); - } else if (!beforeJourney) { - GuiUtils.drawString(graphics, shadowlessFont, (int)((x + WIDTH - 5) / scale) - shadowlessFont.width(connectionInPast), (int)((y + 15) / scale), connectionInPast, Constants.COLOR_DELAYED, EAlignment.LEFT, false); - } - - - for (int i = 0; i < parts.length; i++) { - GuiUtils.drawString(graphics, shadowlessFont, (int)((x + 5 + (i * routePartWidth) + (routePartWidth / 2)) / 0.75f), (int)((y + 30) / 0.75f), TextUtils.text(parts[i].getTrainName()), 0xFFFFFF, EAlignment.CENTER, false); - } - - GuiUtils.drawString(graphics, shadowlessFont, (int)((x + 6) / scale), (int)((y + 43) / scale), TextUtils.text(route.getStartStation().getStationName()), 0xDBDBDB, EAlignment.LEFT, false); - GuiUtils.drawString(graphics, shadowlessFont, (int)((x + WIDTH - 6) / scale) - textW, (int)((y + 43) / scale), TextUtils.text(end), 0xDBDBDB, EAlignment.LEFT, false); - - graphics.poseStack().popPose(); - } - -} diff --git a/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/RouteViewer.java b/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/RouteViewer.java new file mode 100644 index 00000000..0c13edad --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/RouteViewer.java @@ -0,0 +1,72 @@ +package de.mrjulsen.crn.client.gui.widgets; + +import java.util.List; + +import de.mrjulsen.crn.data.navigation.ClientRoute; +import de.mrjulsen.mcdragonlib.client.gui.widgets.DLAbstractScrollBar; +import de.mrjulsen.mcdragonlib.client.gui.widgets.ScrollableWidgetContainer; +import de.mrjulsen.mcdragonlib.client.util.Graphics; +import de.mrjulsen.mcdragonlib.client.util.GuiUtils; +import net.minecraft.client.gui.narration.NarrationElementOutput; +import net.minecraft.client.gui.screens.Screen; + +public class RouteViewer extends ScrollableWidgetContainer { + + private final Screen parent; + private final DLAbstractScrollBar scrollBar; + private int contentHeight = 0; + + public RouteViewer(Screen parent, int x, int y, int width, int height, DLAbstractScrollBar scrollBar) { + super(x, y, width, height); + this.parent = parent; + this.scrollBar = scrollBar; + + scrollBar.setAutoScrollerSize(true); + scrollBar.setScreenSize(height()); + scrollBar.updateMaxScroll(0); + scrollBar.withOnValueChanged((sb) -> setYScrollOffset(sb.getScrollValue())); + scrollBar.setStepSize(10); + } + + public Screen getParent() { + return parent; + } + + public void displayRoutes(List routes) { + clearWidgets(); + contentHeight = 5; + for (int i = 0; i < routes.size(); i++) { + RouteWidget widget = new RouteWidget(this, routes.get(i), x + 10, y + contentHeight); + addRenderableWidget(widget); + contentHeight += (RouteWidget.HEIGHT + 3); + } + contentHeight += 2; + scrollBar.updateMaxScroll(contentHeight); + } + + @Override + public void renderMainLayer(Graphics graphics, int mouseX, int mouseY, float partialTicks) { + super.renderMainLayer(graphics, mouseX, mouseY, partialTicks); + + GuiUtils.fillGradient(graphics, x(), y(), 0, width(), 10, 0x77000000, 0x00000000); + GuiUtils.fillGradient(graphics, x(), y() + height() - 10, 0, width(), 10, 0x00000000, 0x77000000); + } + + @Override + public NarrationPriority narrationPriority() { + return NarrationPriority.HOVERED; + } + + @Override + public void updateNarration(NarrationElementOutput narrationElementOutput) {} + + @Override + public boolean consumeScrolling(double mouseX, double mouseY) { + return false; + } + + public void clear() { + clearWidgets(); + } + +} diff --git a/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/RouteWidget.java b/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/RouteWidget.java new file mode 100644 index 00000000..8ba80431 --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/RouteWidget.java @@ -0,0 +1,146 @@ +package de.mrjulsen.crn.client.gui.widgets; + +import com.google.common.collect.ImmutableList; +import com.simibubi.create.content.trains.station.NoShadowFontWrapper; + +import de.mrjulsen.crn.Constants; +import de.mrjulsen.crn.CreateRailwaysNavigator; +import de.mrjulsen.crn.client.ModGuiUtils; +import de.mrjulsen.crn.client.gui.CreateDynamicWidgets; +import de.mrjulsen.crn.client.gui.CreateDynamicWidgets.ColorShade; +import de.mrjulsen.crn.client.gui.screen.RouteDetailsScreen; +import de.mrjulsen.crn.client.lang.ELanguage; +import de.mrjulsen.crn.config.ModClientConfig; +import de.mrjulsen.crn.data.SavedRoutesManager; +import de.mrjulsen.crn.data.navigation.ClientRoute; +import de.mrjulsen.crn.data.navigation.RoutePart; +import de.mrjulsen.mcdragonlib.DragonLib; +import de.mrjulsen.mcdragonlib.client.gui.widgets.DLButton; +import de.mrjulsen.mcdragonlib.client.gui.widgets.DLContextMenu; +import de.mrjulsen.mcdragonlib.client.gui.widgets.DLContextMenuItem; +import de.mrjulsen.mcdragonlib.client.gui.widgets.DLContextMenuItem.ContextMenuItemData; +import de.mrjulsen.mcdragonlib.client.render.Sprite; +import de.mrjulsen.mcdragonlib.client.render.DynamicGuiRenderer.AreaStyle; +import de.mrjulsen.mcdragonlib.client.util.Graphics; +import de.mrjulsen.mcdragonlib.client.util.GuiAreaDefinition; +import de.mrjulsen.mcdragonlib.client.util.GuiUtils; +import de.mrjulsen.mcdragonlib.core.EAlignment; +import de.mrjulsen.mcdragonlib.util.TextUtils; +import de.mrjulsen.mcdragonlib.util.TimeUtils; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Font; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.MutableComponent; + +public class RouteWidget extends DLButton { + + public static final int WIDTH = 214; + public static final int HEIGHT = 54; + + private static final int DISPLAY_WIDTH = WIDTH - 10; + + private final ClientRoute route; + + private final MutableComponent transferText = ELanguage.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".navigator.route_entry.transfer"); + private final MutableComponent connectionInPast = ELanguage.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".navigator.route_entry.connection_in_past"); + private final MutableComponent trainCanceled = ELanguage.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".route_overview.stop_cancelled"); + private final MutableComponent textShowDetails = TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".route_widget.show_details"); + private final MutableComponent textSave = TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".route_widget.save"); + private final MutableComponent textShare = TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".route_widget.share"); + private final MutableComponent textRemove = TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".route_widget.remove"); + + public RouteWidget(RouteViewer parent, ClientRoute route, int x, int y) { + super(x, y, WIDTH, HEIGHT, TextUtils.empty(), (b) -> Minecraft.getInstance().setScreen(new RouteDetailsScreen(parent.getParent(), route))); + this.route = route; + + setRenderStyle(AreaStyle.FLAT); + setMenu(new DLContextMenu(() -> GuiAreaDefinition.of(this), () -> new DLContextMenuItem.Builder() + .add(new ContextMenuItemData(textShowDetails, Sprite.empty(), true, (b) -> onPress.onPress(b), null)) + .addSeparator() + .add(new ContextMenuItemData(SavedRoutesManager.isSaved(route) ? textRemove : textSave, Sprite.empty(), true, (b) -> { + if (SavedRoutesManager.isSaved(route)) { + SavedRoutesManager.removeRoute(route); + } else { + SavedRoutesManager.saveRoute(route); + } + }, null)) + //.add(new ContextMenuItemData(textShare, Sprite.empty(), true, (b) -> {}, null)) + )); + } + + + @Override + public void renderMainLayer(Graphics graphics, int pMouseX, int pMouseY, float pPartialTick) { + final int precision = ModClientConfig.REALTIME_PRECISION_THRESHOLD.get(); + + CreateDynamicWidgets.renderSingleShadeWidget(graphics, x, y, WIDTH, HEIGHT, ColorShade.DARK.getColor()); + CreateDynamicWidgets.renderHorizontalSeparator(graphics, x + 6, y + 22, WIDTH - 12); + + if (isMouseSelected()) { + GuiUtils.fill(graphics, x(), y(), width(), height(), 0x22FFFFFF); + } + + Minecraft minecraft = Minecraft.getInstance(); + ImmutableList parts = route.getParts(); + Font shadowlessFont = new NoShadowFontWrapper(minecraft.font); + + String timeStart = TimeUtils.parseTime((int)((route.getStart().getScheduledDepartureTime() + DragonLib.DAYTIME_SHIFT) % DragonLib.TICKS_PER_DAY), ModClientConfig.TIME_FORMAT.get()); + String timeEnd = TimeUtils.parseTime((int)((route.getEnd().getScheduledArrivalTime() + DragonLib.DAYTIME_SHIFT) % DragonLib.TICKS_PER_DAY), ModClientConfig.TIME_FORMAT.get()); + String dash = " - "; + MutableComponent summary = TextUtils.text(String.format("%s%s%s | %s %s | %s", + timeStart, + dash, + timeEnd, + route.getTransferCount(), + transferText.getString(), + TimeUtils.parseDurationShort((int)route.travelTime()) + )); + + final float scale = 0.75f; + + float localScale = shadowlessFont.width(summary) > WIDTH - 12 ? scale : 1; + graphics.poseStack().pushPose(); + graphics.poseStack().scale(localScale, 1, 1); + GuiUtils.drawString(graphics, minecraft.font, (int)((x + 6) / localScale), y + 5, summary, 0xFFFFFF, EAlignment.LEFT, false); + graphics.poseStack().popPose(); + + int routePartWidth = DISPLAY_WIDTH / parts.size(); + String endStationName = route.getEnd().getClientTag().tagName(); + int textW = shadowlessFont.width(endStationName); + + for (int i = 0; i < parts.size(); i++) { + int color = parts.get(i).getFirstStop().getTrainDisplayColor(); + GuiUtils.fill(graphics, x + 6 + (i * routePartWidth) + 1, y + 27, routePartWidth - 4, 1, color); + GuiUtils.fill(graphics, x + 5 + (i * routePartWidth) + 1, y + 28, routePartWidth - 2, 9, color); + GuiUtils.fill(graphics, x + 6 + (i * routePartWidth) + 1, y + 37, routePartWidth - 4, 1, color); + } + + graphics.poseStack().pushPose(); + graphics.poseStack().scale(scale, scale, scale); + + for (int i = 0; i < parts.size(); i++) { + int color = parts.get(i).getFirstStop().getTrainDisplayColor(); + int fontColor = ModGuiUtils.useWhiteOrBlackForeColor(color) ? 0xFFFFFFFF: 0xFF000000; + Component trainName = GuiUtils.ellipsisString(font, TextUtils.text(parts.get(i).getFirstStop().getTrainDisplayName()), (int)((routePartWidth - 10) / 0.75f)); + GuiUtils.drawString(graphics, font, (int)((x + 5 + (i * routePartWidth) + (routePartWidth / 2)) / 0.75f), (int)((y + 30) / 0.75f), trainName, fontColor, EAlignment.CENTER, false); + } + + GuiUtils.drawString(graphics, font, (int)((x + 6) / scale), (int)((y + 43) / scale), TextUtils.text(route.getStart().getClientTag().tagName()), 0xDBDBDB, EAlignment.LEFT, false); + GuiUtils.drawString(graphics, font, (int)((x + WIDTH - 6) / scale) - textW, (int)((y + 43) / scale), TextUtils.text(endStationName), 0xDBDBDB, EAlignment.LEFT, false); + if (route.getStart().shouldRenderRealTime()) { + GuiUtils.drawString(graphics, font, (int)((x + 6 + font.width(timeStart) * localScale / 2.0f) / scale) - font.width(timeStart) / 2, (int)((y + 15) / scale), TextUtils.text(TimeUtils.parseTime((int)((route.getStart().getScheduledDepartureTime() + (route.getStart().getDepartureTimeDeviation() / precision * precision)) % 24000 + DragonLib.DAYTIME_SHIFT), ModClientConfig.TIME_FORMAT.get())), route.getStart().isDepartureDelayed() ? Constants.COLOR_DELAYED : Constants.COLOR_ON_TIME, EAlignment.LEFT, false); + } + if (route.getEnd().shouldRenderRealTime()) { + GuiUtils.drawString(graphics, font, (int)((x + 6 + font.width(timeEnd) * localScale * 1.5f + (font.width(dash)) * localScale) / scale) - font.width(timeEnd) / 2, (int)((y + 15) / scale), TextUtils.text(TimeUtils.parseTime((int)((route.getEnd().getScheduledArrivalTime() + (route.getEnd().getArrivalTimeDeviation() / precision * precision)) % 24000 + DragonLib.DAYTIME_SHIFT), ModClientConfig.TIME_FORMAT.get())), route.getEnd().isArrivalDelayed() ? Constants.COLOR_DELAYED : Constants.COLOR_ON_TIME, EAlignment.LEFT, false); + } + + if (route.isAnyCancelled()) { + GuiUtils.drawString(graphics, shadowlessFont, (int)((x + WIDTH - 5) / scale), (int)((y + 15) / scale), trainCanceled, Constants.COLOR_DELAYED, EAlignment.RIGHT, false); + } else if (route.getStart().isDeparted()) { + GuiUtils.drawString(graphics, shadowlessFont, (int)((x + WIDTH - 5) / scale), (int)((y + 15) / scale), connectionInPast, Constants.COLOR_DELAYED, EAlignment.RIGHT, false); + } + + graphics.poseStack().popPose(); + } + +} diff --git a/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/SavedRouteWidget.java b/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/SavedRouteWidget.java new file mode 100644 index 00000000..55618ca5 --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/SavedRouteWidget.java @@ -0,0 +1,105 @@ +package de.mrjulsen.crn.client.gui.widgets; + +import de.mrjulsen.crn.CreateRailwaysNavigator; +import de.mrjulsen.crn.client.ClientWrapper; +import de.mrjulsen.crn.client.gui.CreateDynamicWidgets; +import de.mrjulsen.crn.client.gui.CreateDynamicWidgets.ColorShade; +import de.mrjulsen.crn.client.gui.screen.RouteDetailsScreen; +import de.mrjulsen.crn.client.lang.ELanguage; +import de.mrjulsen.crn.data.ISaveableNavigatorData; +import de.mrjulsen.crn.data.SavedRoutesManager; +import de.mrjulsen.crn.data.ISaveableNavigatorData.SaveableNavigatorDataLine; +import de.mrjulsen.crn.data.navigation.ClientRoute; +import de.mrjulsen.crn.data.navigation.Route; +import de.mrjulsen.mcdragonlib.client.gui.widgets.DLButton; +import de.mrjulsen.mcdragonlib.client.gui.widgets.DLContextMenu; +import de.mrjulsen.mcdragonlib.client.gui.widgets.DLContextMenuItem; +import de.mrjulsen.mcdragonlib.client.gui.widgets.DLContextMenuItem.ContextMenuItemData; +import de.mrjulsen.mcdragonlib.client.render.GuiIcons; +import de.mrjulsen.mcdragonlib.client.render.Sprite; +import de.mrjulsen.mcdragonlib.client.render.DynamicGuiRenderer.AreaStyle; +import de.mrjulsen.mcdragonlib.client.util.Graphics; +import de.mrjulsen.mcdragonlib.client.util.GuiAreaDefinition; +import de.mrjulsen.mcdragonlib.client.util.GuiUtils; +import de.mrjulsen.mcdragonlib.core.EAlignment; +import de.mrjulsen.mcdragonlib.util.TextUtils; +import net.minecraft.client.Minecraft; +import net.minecraft.network.chat.MutableComponent; + +public class SavedRouteWidget extends DLButton { + + public static final int WIDTH = 180; + public static final int HEADER_HEIGHT = 20; + public static final int DEFAULT_LINE_HEIGHT = 12; + public static final float DEFAULT_SCALE = 0.75f; + + private static final int DISPLAY_WIDTH = WIDTH - 20; + + private final ISaveableNavigatorData data; + + private final MutableComponent transferText = ELanguage.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".navigator.route_entry.transfer"); + private final MutableComponent connectionInPast = ELanguage.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".navigator.route_entry.connection_in_past"); + private final MutableComponent trainCanceled = ELanguage.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".route_overview.stop_cancelled"); + private final MutableComponent textShowDetails = TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".saved_route_widget.show_details"); + private final MutableComponent textRemove = TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".route_widget.remove"); + private final MutableComponent textShare = TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".saved_route_widget.share"); + private final MutableComponent textShowNotifications = TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".saved_route_widget.notifications"); + + public SavedRouteWidget(SavedRoutesViewer parent, int x, int y, ISaveableNavigatorData data) { + super(x, y, WIDTH, 50, TextUtils.empty(), (b) -> clickAction(parent, data)); + this.data = data; + set_height(HEADER_HEIGHT + 10 + data.getOverviewData().stream().mapToInt(a -> (int)(Math.max(DEFAULT_LINE_HEIGHT, ClientWrapper.getTextBlockHeight(font, a.text(), (int)(DISPLAY_WIDTH / DEFAULT_SCALE))) * DEFAULT_SCALE)).sum()); + + setRenderStyle(AreaStyle.FLAT); + setMenu(new DLContextMenu(() -> GuiAreaDefinition.of(this), () -> new DLContextMenuItem.Builder() + .add(new ContextMenuItemData(textShowDetails, Sprite.empty(), true, (b) -> onPress.onPress(b), null)) + .addSeparator() + .add(new ContextMenuItemData(textRemove, Sprite.empty(), true, (b) -> { + SavedRoutesManager.removeRoute((ClientRoute)data); + SavedRoutesManager.push(true, null); + parent.displayRoutes(SavedRoutesManager.getAllSavedRoutes()); + }, null)) + //.add(new ContextMenuItemData(textShare, Sprite.empty(), true, (b) -> {}, null)) + .addSeparator() + .add(new ContextMenuItemData(textShowNotifications, data instanceof ClientRoute route && route.shouldShowNotifications() ? GuiIcons.CHECKMARK.getAsSprite(8, 8) : Sprite.empty(), data instanceof Route, (b) -> { + if (data instanceof ClientRoute route) { + route.setShowNotifications(!route.shouldShowNotifications()); + } + }, null)) + )); + } + + private static void clickAction(SavedRoutesViewer parent, ISaveableNavigatorData data) { + if (data instanceof ClientRoute route) { + Minecraft.getInstance().setScreen(new RouteDetailsScreen(parent.getParent(), route)); + } + } + + @Override + public void renderMainLayer(Graphics graphics, int pMouseX, int pMouseY, float pPartialTick) { + + CreateDynamicWidgets.renderSingleShadeWidget(graphics, x(), y(), width(), height(), ColorShade.DARK.getColor()); + CreateDynamicWidgets.renderHorizontalSeparator(graphics, x() + 6, y() + 16, width() - 12 - data.getTitle().icon().getWidth()); + + if (isMouseSelected()) { + GuiUtils.fill(graphics, x(), y(), width(), height(), 0x22FFFFFF); + } + + GuiUtils.drawString(graphics, font, x() + 6, y() + 5, data.getTitle().text(), 0xFFFFFFFF, EAlignment.LEFT, false); + data.getTitle().icon().render(graphics, x() + width() - data.getTitle().icon().getWidth() - 3, y() + 3); + + graphics.poseStack().pushPose(); + graphics.poseStack().translate(x() + 10, y() + HEADER_HEIGHT, 0); + for (SaveableNavigatorDataLine line : data.getOverviewData()) { + graphics.poseStack().pushPose(); + graphics.poseStack().scale(DEFAULT_SCALE, DEFAULT_SCALE, 1); + line.icon().render(graphics, 0, -2); + int height = (int)(ClientWrapper.renderMultilineLabelSafe(graphics, (int)(16 / DEFAULT_SCALE), (int)(2 / DEFAULT_SCALE), font, line.text(), (int)(DISPLAY_WIDTH / DEFAULT_SCALE), 0xFFFFFFFF) * DEFAULT_SCALE); + graphics.poseStack().popPose(); + graphics.poseStack().translate(0, Math.max((int)(DEFAULT_LINE_HEIGHT * DEFAULT_SCALE), height + 4), 0); + } + + graphics.poseStack().popPose(); + } + +} diff --git a/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/SavedRoutesViewer.java b/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/SavedRoutesViewer.java new file mode 100644 index 00000000..7c20b4f3 --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/SavedRoutesViewer.java @@ -0,0 +1,132 @@ +package de.mrjulsen.crn.client.gui.widgets; + +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +import de.mrjulsen.crn.CreateRailwaysNavigator; +import de.mrjulsen.crn.data.ISaveableNavigatorData; +import de.mrjulsen.mcdragonlib.DragonLib; +import de.mrjulsen.mcdragonlib.client.gui.widgets.DLAbstractScrollBar; +import de.mrjulsen.mcdragonlib.client.gui.widgets.DLRenderable; +import de.mrjulsen.mcdragonlib.client.gui.widgets.ScrollableWidgetContainer; +import de.mrjulsen.mcdragonlib.client.util.Graphics; +import de.mrjulsen.mcdragonlib.client.util.GuiUtils; +import de.mrjulsen.mcdragonlib.core.EAlignment; +import de.mrjulsen.mcdragonlib.util.TextUtils; +import net.minecraft.ChatFormatting; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.narration.NarrationElementOutput; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.network.chat.Component; + +public class SavedRoutesViewer extends ScrollableWidgetContainer { + + private final Screen parent; + private final DLAbstractScrollBar scrollBar; + private int contentHeight = 0; + + private List data = List.of(); + + public SavedRoutesViewer(Screen parent, int x, int y, int width, int height, DLAbstractScrollBar scrollBar) { + super(x, y, width, height); + this.parent = parent; + this.scrollBar = scrollBar; + + scrollBar.setAutoScrollerSize(true); + scrollBar.setScreenSize(height()); + scrollBar.updateMaxScroll(0); + scrollBar.withOnValueChanged((sb) -> setYScrollOffset(sb.getScrollValue())); + scrollBar.setStepSize(10); + } + + public Screen getParent() { + return parent; + } + + public void refresh() { + displayRoutes(data); + } + + public void displayRoutes(List data) { + this.data = data; + Collections.sort(data, Comparator + .comparing(x -> ((ISaveableNavigatorData)x).customGroup() == null ? null : ((ISaveableNavigatorData)x).customGroup().getFirst(), Comparator.nullsLast(Comparator.naturalOrder())) + .thenComparingLong(x -> ((ISaveableNavigatorData)x).dayOrderValue()) + .thenComparingLong(x -> ((ISaveableNavigatorData)x).timeOrderValue())); + + clearWidgets(); + contentHeight = 5; + ISaveableNavigatorData lastData = null; + for (int i = 0; i < data.size(); i++) { + ISaveableNavigatorData d = data.get(i); + + if (lastData != null && lastData.customGroup() != d.customGroup()) { + contentHeight += addRenderableOnly(new GroupingHeader(x(), y() + contentHeight, width(), (d.customGroup() == null ? TextUtils.empty() : d.customGroup().getSecond()).withStyle(ChatFormatting.BOLD))).height(); + } + if (lastData == null || lastData.dayOrderValue() != d.dayOrderValue()) { + Component text; + long worldTime = DragonLib.getCurrentWorldTime(); + long dayDiff = d.dayOrderValue() - (worldTime + DragonLib.DAYTIME_SHIFT) / DragonLib.TICKS_PER_DAY; + if (d.timeOrderValue() < worldTime) text = TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".saved_routes.in_the_past"); + else if (dayDiff == 0) text = TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".saved_routes.today"); + else if (dayDiff == 1) text = TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".saved_routes.tomorrow"); + else text = TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".saved_routes.in_days", dayDiff); + contentHeight += addRenderableOnly(new GroupingHeader(x(), y() + contentHeight, width(), text)).height(); + } + + lastData = d; + SavedRouteWidget widget = new SavedRouteWidget(this, x(), y() + contentHeight, d); + addRenderableWidget(widget); + widget.set_x(x() + width() / 2 - widget.width() / 2); + contentHeight += (widget.height() + 3); + + } + contentHeight += 10; + scrollBar.updateMaxScroll(contentHeight); + } + + @Override + public void renderMainLayer(Graphics graphics, int mouseX, int mouseY, float partialTicks) { + super.renderMainLayer(graphics, mouseX, mouseY, partialTicks); + + if (children().isEmpty()) { + GuiUtils.drawString(graphics, font, x() + width() / 2, y() + height() / 2, TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".empty_list"), 0xFFDBDBDB, EAlignment.CENTER, false); + } + + GuiUtils.fillGradient(graphics, x(), y(), 0, width(), 10, 0x77000000, 0x00000000); + GuiUtils.fillGradient(graphics, x(), y() + height() - 10, 0, width(), 10, 0x00000000, 0x77000000); + } + + @Override + public NarrationPriority narrationPriority() { + return NarrationPriority.HOVERED; + } + + @Override + public void updateNarration(NarrationElementOutput narrationElementOutput) {} + + @Override + public boolean consumeScrolling(double mouseX, double mouseY) { + return false; + } + + private static final class GroupingHeader extends DLRenderable { + + private static final int HEIGHT = 24; + + private final Component text; + + public GroupingHeader(int x, int y, int width, Component text) { + super(x, y, width, HEIGHT); + this.text = text == null ? TextUtils.empty() : text; + } + + @SuppressWarnings("resource") + @Override + public void renderMainLayer(Graphics graphics, int mouseX, int mouseY, float partialTicks) { + GuiUtils.drawString(graphics, Minecraft.getInstance().font, x() + 10, y() + height() / 2 - Minecraft.getInstance().font.lineHeight / 2, text, DragonLib.NATIVE_BUTTON_FONT_COLOR_ACTIVE, EAlignment.LEFT, true); + } + } + +} diff --git a/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/SearchOptionButton.java b/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/SearchOptionButton.java new file mode 100644 index 00000000..28016b7f --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/SearchOptionButton.java @@ -0,0 +1,47 @@ +package de.mrjulsen.crn.client.gui.widgets; + +import java.util.function.Consumer; +import java.util.function.Supplier; + +import de.mrjulsen.mcdragonlib.DragonLib; +import de.mrjulsen.mcdragonlib.client.gui.widgets.DLButton; +import de.mrjulsen.mcdragonlib.client.render.DynamicGuiRenderer; +import de.mrjulsen.mcdragonlib.client.render.DynamicGuiRenderer.AreaStyle; +import de.mrjulsen.mcdragonlib.client.render.DynamicGuiRenderer.ButtonState; +import de.mrjulsen.mcdragonlib.client.util.Graphics; +import de.mrjulsen.mcdragonlib.client.util.GuiUtils; +import de.mrjulsen.mcdragonlib.core.EAlignment; +import de.mrjulsen.mcdragonlib.util.TextUtils; +import net.minecraft.ChatFormatting; +import net.minecraft.network.chat.Component; + +public class SearchOptionButton extends DLButton { + + private final Supplier value; + + public SearchOptionButton(int pX, int pY, int pWidth, int pHeight, Component text, Supplier value, Consumer clickAction) { + super(pX, pY, pWidth, pHeight, text, clickAction); + this.value = value; + setRenderStyle(AreaStyle.FLAT); + setBackColor(0x00000000); + } + + @Override + public void renderMainLayer(Graphics graphics, int pMouseX, int pMouseY, float pPartialTick) { + DynamicGuiRenderer.renderArea(graphics, x, y, width, height, getBackColor(), style, isActive() ? (isFocused() || isMouseSelected() ? ButtonState.SELECTED : ButtonState.BUTTON) : ButtonState.DISABLED); + + int j = active ? getFontColor() : DragonLib.NATIVE_BUTTON_FONT_COLOR_DISABLED; + + GuiUtils.fill(graphics, x() + width() - 1, y() + 2, 1, height() - 4, DragonLib.NATIVE_BUTTON_FONT_COLOR_DISABLED); + + final float scale = 0.75f; + graphics.poseStack().pushPose(); + graphics.poseStack().scale(scale, scale, 1); + graphics.poseStack().translate(x() / scale, y() / scale, 0); + GuiUtils.drawString(graphics, font, 5, 3, TextUtils.empty().append(getMessage()).withStyle(ChatFormatting.BOLD), j, EAlignment.LEFT, false); + GuiUtils.drawString(graphics, font, 5, 3 + font.lineHeight + 1, TextUtils.text(value.get()).withStyle(ChatFormatting.GRAY), j, EAlignment.LEFT, false); + graphics.poseStack().popPose(); + //GuiIcons.ARROW_DOWN.render(graphics, x() + width() - DROP_DOWN_BUTTON_WIDTH + 3, y() + height() / 2 - GuiIcons.ICON_SIZE / 2 + 5); + } + +} diff --git a/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/SettingsOptionWidget.java b/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/SettingsOptionWidget.java deleted file mode 100644 index 408a1afe..00000000 --- a/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/SettingsOptionWidget.java +++ /dev/null @@ -1,65 +0,0 @@ -package de.mrjulsen.crn.client.gui.widgets; - -import java.util.List; -import java.util.function.Consumer; - -import com.simibubi.create.content.trains.station.NoShadowFontWrapper; - -import de.mrjulsen.crn.Constants; -import de.mrjulsen.crn.CreateRailwaysNavigator; -import de.mrjulsen.mcdragonlib.client.gui.widgets.DLButton; -import de.mrjulsen.mcdragonlib.client.util.Graphics; -import de.mrjulsen.mcdragonlib.client.util.GuiUtils; -import de.mrjulsen.mcdragonlib.core.EAlignment; -import de.mrjulsen.mcdragonlib.util.TextUtils; -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.Font; -import net.minecraft.client.gui.components.MultiLineLabel; -import net.minecraft.client.gui.screens.Screen; -import net.minecraft.network.chat.Component; -import net.minecraft.network.chat.MutableComponent; - -public class SettingsOptionWidget extends DLButton { - - public static final int WIDTH = 200; - public static final int HEIGHT = 48; - - private static final int DISPLAY_WIDTH = 190; - - private final Screen parent; - private final Font shadowlessFont; - - // Controls - private final MultiLineLabel messageLabel; - private final MutableComponent tooltipOptionText = TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".global_settings.option.tooltip"); - - - public SettingsOptionWidget(Screen parent, int pX, int pY, Component title, Component description, Consumer onClick) { - super(pX, pY, 200, 48, title, onClick); - - Minecraft minecraft = Minecraft.getInstance(); - shadowlessFont = new NoShadowFontWrapper(minecraft.font); - - this.parent = parent; - this.messageLabel = MultiLineLabel.create(shadowlessFont, description, (int)((DISPLAY_WIDTH) / 0.75f)); - } - - @Override - public void renderMainLayer(Graphics graphics, int pMouseX, int pMouseY, float pPartialTick) { - float l = isMouseOver(pMouseX, pMouseY) && isHovered ? 0.2f : 0; - GuiUtils.setTint(1 + l, 1 + l, 1 + l, 1); - GuiUtils.drawTexture(Constants.GUI_WIDGETS, graphics, x, y, 0, 0, WIDTH, HEIGHT); - - GuiUtils.drawString(graphics, shadowlessFont, x + 6, y + 5, getMessage(), 0xFFFFFF, EAlignment.LEFT, false); - graphics.poseStack().scale(0.75f, 0.75f, 0.75f); - this.messageLabel.renderLeftAligned(graphics.poseStack(), (int)((x + 6) / 0.75f), (int)((y + 20) / 0.75f), 10, 0xDBDBDB); - float s = 1 / 0.75f; - graphics.poseStack().scale(s, s, s); - } - - @Override - public void renderFrontLayer(Graphics graphics, int pMouseX, int pMouseY, float pPartialTicks) { - GuiUtils.renderTooltipWithOffset(parent, this, List.of(tooltipOptionText), parent.width, graphics, pMouseX, pMouseY, 0, (int)pPartialTicks); - } - -} diff --git a/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/StationDeparturesViewer.java b/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/StationDeparturesViewer.java new file mode 100644 index 00000000..14995f92 --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/StationDeparturesViewer.java @@ -0,0 +1,86 @@ +package de.mrjulsen.crn.client.gui.widgets; + +import de.mrjulsen.crn.CreateRailwaysNavigator; +import de.mrjulsen.crn.data.UserSettings; +import de.mrjulsen.crn.registry.ModAccessorTypes; +import de.mrjulsen.crn.registry.ModAccessorTypes.DepartureRoutesData; +import de.mrjulsen.crn.data.navigation.ClientRoute; +import de.mrjulsen.mcdragonlib.client.gui.widgets.DLAbstractScrollBar; +import de.mrjulsen.mcdragonlib.client.gui.widgets.ScrollableWidgetContainer; +import de.mrjulsen.mcdragonlib.client.util.Graphics; +import de.mrjulsen.mcdragonlib.client.util.GuiUtils; +import de.mrjulsen.mcdragonlib.core.EAlignment; +import de.mrjulsen.mcdragonlib.data.Pair; +import de.mrjulsen.mcdragonlib.util.TextUtils; +import de.mrjulsen.mcdragonlib.util.accessor.DataAccessor; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.narration.NarrationElementOutput; +import net.minecraft.client.gui.screens.Screen; + +public class StationDeparturesViewer extends ScrollableWidgetContainer { + + private final Screen parent; + private final DLAbstractScrollBar scrollBar; + private int contentHeight = 0; + + public StationDeparturesViewer(Screen parent, int x, int y, int width, int height, DLAbstractScrollBar scrollBar) { + super(x, y, width, height); + this.parent = parent; + this.scrollBar = scrollBar; + + scrollBar.setAutoScrollerSize(true); + scrollBar.setScreenSize(height()); + scrollBar.updateMaxScroll(0); + scrollBar.withOnValueChanged((sb) -> setYScrollOffset(sb.getScrollValue())); + scrollBar.setStepSize(10); + } + + public Screen getParent() { + return parent; + } + + @SuppressWarnings("resource") + public void displayRoutes(String stationTagName, UserSettings settings) { + clearWidgets(); + contentHeight = 0; + if (stationTagName == null) { + return; + } + + DataAccessor.getFromServer(new DepartureRoutesData(stationTagName, Minecraft.getInstance().player.getUUID()), ModAccessorTypes.GET_DEPARTURE_AND_ARRIVAL_ROUTES_AT, (routesL) -> { + for (int i = 0; i < routesL.size(); i++) { + Pair route = routesL.get(i); + StationDeparturesWidget widget = new StationDeparturesWidget(parent, this, x() + 10, y() + 5 + contentHeight, width() - 20, route.getSecond(), route.getFirst()); + addRenderableWidget(widget); + contentHeight += (widget.height() + 3); + } + contentHeight += 7; + scrollBar.updateMaxScroll(contentHeight); + }); + } + + @Override + public void renderMainLayer(Graphics graphics, int mouseX, int mouseY, float partialTicks) { + super.renderMainLayer(graphics, mouseX, mouseY, partialTicks); + + if (children().isEmpty()) { + GuiUtils.drawString(graphics, font, x() + width() / 2, y() + height() / 2, TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".empty_list"), 0xFFDBDBDB, EAlignment.CENTER, false); + } + + GuiUtils.fillGradient(graphics, x(), y(), 0, width(), 10, 0x77000000, 0x00000000); + GuiUtils.fillGradient(graphics, x(), y() + height() - 10, 0, width(), 10, 0x00000000, 0x77000000); + } + + @Override + public NarrationPriority narrationPriority() { + return NarrationPriority.HOVERED; + } + + @Override + public void updateNarration(NarrationElementOutput narrationElementOutput) {} + + @Override + public boolean consumeScrolling(double mouseX, double mouseY) { + return false; + } +} diff --git a/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/StationDeparturesWidget.java b/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/StationDeparturesWidget.java new file mode 100644 index 00000000..2f3317cc --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/StationDeparturesWidget.java @@ -0,0 +1,108 @@ +package de.mrjulsen.crn.client.gui.widgets; + +import com.simibubi.create.foundation.gui.AllIcons; + +import de.mrjulsen.crn.Constants; +import de.mrjulsen.crn.CreateRailwaysNavigator; +import de.mrjulsen.crn.client.gui.CreateDynamicWidgets; +import de.mrjulsen.crn.client.gui.CreateDynamicWidgets.ColorShade; +import de.mrjulsen.crn.client.gui.screen.TrainJourneySreen; +import de.mrjulsen.crn.client.lang.ELanguage; +import de.mrjulsen.crn.util.ModUtils; +import de.mrjulsen.crn.data.navigation.ClientRoute; +import de.mrjulsen.mcdragonlib.client.gui.DLScreen; +import de.mrjulsen.mcdragonlib.client.gui.widgets.DLButton; +import de.mrjulsen.mcdragonlib.client.gui.widgets.DLContextMenu; +import de.mrjulsen.mcdragonlib.client.gui.widgets.DLContextMenuItem; +import de.mrjulsen.mcdragonlib.client.gui.widgets.DLContextMenuItem.ContextMenuItemData; +import de.mrjulsen.mcdragonlib.client.render.Sprite; +import de.mrjulsen.mcdragonlib.client.render.DynamicGuiRenderer.AreaStyle; +import de.mrjulsen.mcdragonlib.client.util.Graphics; +import de.mrjulsen.mcdragonlib.client.util.GuiAreaDefinition; +import de.mrjulsen.mcdragonlib.client.util.GuiUtils; +import de.mrjulsen.mcdragonlib.core.EAlignment; +import de.mrjulsen.mcdragonlib.util.TextUtils; +import net.minecraft.ChatFormatting; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.MutableComponent; + +public class StationDeparturesWidget extends DLButton implements AutoCloseable { + + public static final int HEADER_HEIGHT = 20; + public static final int DEFAULT_LINE_HEIGHT = 12; + public static final float DEFAULT_SCALE = 0.75f; + + + private final MutableComponent connectionInPast = ELanguage.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".navigator.route_entry.connection_in_past"); + private final MutableComponent trainCanceled = ELanguage.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".route_overview.stop_cancelled"); + + private final ClientRoute route; + private final boolean arrival; + + public StationDeparturesWidget(Screen parent, StationDeparturesViewer viewer, int x, int y, int width, ClientRoute route, boolean arrival) { + super(x, y, width, 32, TextUtils.empty(), (b) -> { + DLScreen.setScreen(new TrainJourneySreen(parent, route, route.getStart().getTrainId())); + }); + this.route = route; + this.arrival = arrival; + + setRenderStyle(AreaStyle.FLAT); + setMenu(new DLContextMenu(() -> GuiAreaDefinition.of(this), () -> new DLContextMenuItem.Builder() + .add(new ContextMenuItemData(TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".schedule_board.view_details"), Sprite.empty(), true, (b) -> onPress.onPress(b), null)) + )); + } + + @Override + public void renderMainLayer(Graphics graphics, int pMouseX, int pMouseY, float pPartialTick) { + + CreateDynamicWidgets.renderSingleShadeWidget(graphics, x(), y(), width(), height(), ColorShade.DARK.getColor()); + + if (isMouseSelected()) { + GuiUtils.fill(graphics, x(), y(), width(), height(), 0x22FFFFFF); + } + + final float scale = 0.75f; + Component trainName = TextUtils.text(route.getStart().getTrainDisplayName()).withStyle(ChatFormatting.BOLD); + graphics.poseStack().pushPose(); + graphics.poseStack().translate(x(), y(), 0); + graphics.poseStack().scale(scale, scale, scale); + if (arrival) { + AllIcons.I_CONFIG_OPEN.render(graphics.poseStack(), 8, 5); + } else { + AllIcons.I_CONFIG_BACK.render(graphics.poseStack(), 8, 5); + } + + if (route.isAnyCancelled()) { + GuiUtils.drawString(graphics, font, (int)((x + width() - 5) / scale), (int)((y() + 15) / scale), trainCanceled, Constants.COLOR_DELAYED, EAlignment.RIGHT, false); + } else if (route.getStart().isDeparted()) { + GuiUtils.drawString(graphics, font, (int)((x + width() - 5) / scale), (int)((y() + 15) / scale), connectionInPast, Constants.COLOR_DELAYED, EAlignment.RIGHT, false); + } + + CreateDynamicWidgets.renderTextHighlighted(graphics, 30, 6, font, trainName, route.getStart().getTrainDisplayColor()); + graphics.poseStack().popPose(); + + Component platformText = TextUtils.text(route.getStart().getRealTimeStationTag().info().platform()); + int platformTextWidth = font.width(platformText); + final int maxStationNameWidth = width() - platformTextWidth - 15 - (int)((45 + font.width(trainName)) * scale); + MutableComponent stationText = arrival ? TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".schedule_board.train_from", route.getEnd().getClientTag().tagName()) : TextUtils.text(route.getStart().getDisplayTitle()); + if (font.width(stationText) > maxStationNameWidth) { + stationText = TextUtils.text(font.substrByWidth(stationText, maxStationNameWidth).getString()).append(TextUtils.text("...")).withStyle(stationText.getStyle()); + } + + GuiUtils.drawString(graphics, font, x() + (int)((45 + font.width(trainName)) * scale), y() + 6, stationText, 0xFFFFFF, EAlignment.LEFT, false); + GuiUtils.drawString(graphics, font, x() + width() - 6, y() + 6, platformText, 0xFFFFFF, EAlignment.RIGHT, false); + GuiUtils.drawString(graphics, font, x() + (int)(30 * scale), y() + 20, ModUtils.formatTime(arrival ? route.getStart().getScheduledArrivalTime() : route.getStart().getScheduledDepartureTime(), false), 0xFFFFFF, EAlignment.LEFT, false); + GuiUtils.drawString(graphics, font, x() + (int)(30 * scale) + 40, y() + 20, ModUtils.formatTime(arrival ? route.getStart().getRealTimeArrivalTime() : route.getStart().getRealTimeDepartureTime(), false), (arrival ? route.getStart().isArrivalDelayed() : route.getStart().isDepartureDelayed()) ? Constants.COLOR_DELAYED : Constants.COLOR_ON_TIME, EAlignment.LEFT, false); + + + //GuiUtils.drawString(graphics, font, x() + 6, y() + 5, route.getStart().getTag().getTagName().get(), 0xFFFFFFFF, EAlignment.LEFT, false); + + } + + @Override + public void close() { + route.closeAll(); + } + +} diff --git a/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/TrainDebugViewer.java b/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/TrainDebugViewer.java new file mode 100644 index 00000000..94c138e0 --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/TrainDebugViewer.java @@ -0,0 +1,83 @@ +package de.mrjulsen.crn.client.gui.widgets; + +import de.mrjulsen.crn.CreateRailwaysNavigator; +import de.mrjulsen.crn.registry.ModAccessorTypes; +import de.mrjulsen.crn.debug.TrainDebugData; +import de.mrjulsen.mcdragonlib.client.gui.widgets.DLAbstractScrollBar; +import de.mrjulsen.mcdragonlib.client.gui.widgets.ScrollableWidgetContainer; +import de.mrjulsen.mcdragonlib.client.util.Graphics; +import de.mrjulsen.mcdragonlib.client.util.GuiUtils; +import de.mrjulsen.mcdragonlib.core.EAlignment; +import de.mrjulsen.mcdragonlib.util.TextUtils; +import de.mrjulsen.mcdragonlib.util.accessor.DataAccessor; +import net.minecraft.client.gui.narration.NarrationElementOutput; +import net.minecraft.client.gui.screens.Screen; + +public class TrainDebugViewer extends ScrollableWidgetContainer { + + private final Screen parent; + private final DLAbstractScrollBar scrollBar; + private int contentHeight = 0; + + public TrainDebugViewer(Screen parent, int x, int y, int width, int height, DLAbstractScrollBar scrollBar) { + super(x, y, width, height); + this.parent = parent; + this.scrollBar = scrollBar; + + scrollBar.setAutoScrollerSize(true); + scrollBar.setScreenSize(height()); + scrollBar.updateMaxScroll(0); + scrollBar.withOnValueChanged((sb) -> setYScrollOffset(sb.getScrollValue())); + scrollBar.setStepSize(10); + } + + public Screen getParent() { + return parent; + } + + @Override + protected void clearWidgets() { + super.clearWidgets(); + } + + public void reload() { + clearWidgets(); + contentHeight = 0; + + DataAccessor.getFromServer(null, ModAccessorTypes.GET_ALL_TRAINS_DEBUG_DATA, (debug) -> { + for (int i = 0; i < debug.size(); i++) { + TrainDebugData debugData = debug.get(i); + TrainDebugWidget widget = new TrainDebugWidget(parent, this, x() + 10, y() + 5 + contentHeight, width() - 20, debugData); + addRenderableWidget(widget); + contentHeight += (widget.height() + 3); + } + contentHeight += 7; + scrollBar.updateMaxScroll(contentHeight); + }); + } + + @Override + public void renderMainLayer(Graphics graphics, int mouseX, int mouseY, float partialTicks) { + super.renderMainLayer(graphics, mouseX, mouseY, partialTicks); + + if (children().isEmpty()) { + GuiUtils.drawString(graphics, font, x() + width() / 2, y() + height() / 2, TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".empty_list"), 0xFFDBDBDB, EAlignment.CENTER, false); + } + + GuiUtils.fillGradient(graphics, x(), y(), 0, width(), 10, 0x77000000, 0x00000000); + GuiUtils.fillGradient(graphics, x(), y() + height() - 10, 0, width(), 10, 0x00000000, 0x77000000); + } + + @Override + public NarrationPriority narrationPriority() { + return NarrationPriority.HOVERED; + } + + @Override + public void updateNarration(NarrationElementOutput narrationElementOutput) {} + + @Override + public boolean consumeScrolling(double mouseX, double mouseY) { + return false; + } +} diff --git a/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/TrainDebugWidget.java b/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/TrainDebugWidget.java new file mode 100644 index 00000000..a4b98bf4 --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/TrainDebugWidget.java @@ -0,0 +1,74 @@ +package de.mrjulsen.crn.client.gui.widgets; + +import de.mrjulsen.crn.Constants; +import de.mrjulsen.crn.client.gui.CreateDynamicWidgets; +import de.mrjulsen.crn.client.gui.CreateDynamicWidgets.ColorShade; +import de.mrjulsen.crn.debug.TrainDebugData; +import de.mrjulsen.crn.registry.ModAccessorTypes; +import de.mrjulsen.mcdragonlib.client.gui.widgets.DLButton; +import de.mrjulsen.mcdragonlib.client.gui.widgets.DLContextMenu; +import de.mrjulsen.mcdragonlib.client.gui.widgets.DLContextMenuItem; +import de.mrjulsen.mcdragonlib.client.gui.widgets.DLContextMenuItem.ContextMenuItemData; +import de.mrjulsen.mcdragonlib.client.render.DynamicGuiRenderer.AreaStyle; +import de.mrjulsen.mcdragonlib.client.render.Sprite; +import de.mrjulsen.mcdragonlib.client.util.Graphics; +import de.mrjulsen.mcdragonlib.client.util.GuiAreaDefinition; +import de.mrjulsen.mcdragonlib.client.util.GuiUtils; +import de.mrjulsen.mcdragonlib.core.EAlignment; +import de.mrjulsen.mcdragonlib.util.TextUtils; +import de.mrjulsen.mcdragonlib.util.accessor.DataAccessor; +import net.minecraft.ChatFormatting; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.MutableComponent; + +public class TrainDebugWidget extends DLButton { + + public static final int HEADER_HEIGHT = 20; + public static final int DEFAULT_LINE_HEIGHT = 12; + public static final float DEFAULT_SCALE = 0.75f; + + private final TrainDebugData data; + + public TrainDebugWidget(Screen parent, TrainDebugViewer viewer, int x, int y, int width, TrainDebugData data) { + super(x, y, width, 32, TextUtils.empty(), (b) -> {}); + this.data = data; + setRenderStyle(AreaStyle.FLAT); + setMenu(new DLContextMenu(() -> GuiAreaDefinition.of(this), () -> new DLContextMenuItem.Builder() + .add(new ContextMenuItemData(TextUtils.text("Reset Predictions"), Sprite.empty(), true, (b) -> DataAccessor.getFromServer(data.trainId(), ModAccessorTypes.TRAIN_SOFT_RESET, $ -> viewer.reload()), null)) + .addSeparator() + .add(new ContextMenuItemData(TextUtils.text("Hard Reset"), Sprite.empty(), true, (b) -> DataAccessor.getFromServer(data.trainId(), ModAccessorTypes.TRAIN_HARD_RESET, $ -> viewer.reload()), null)) + )); + } + + @Override + public void renderMainLayer(Graphics graphics, int pMouseX, int pMouseY, float pPartialTick) { + + CreateDynamicWidgets.renderSingleShadeWidget(graphics, x(), y(), width(), height(), ColorShade.DARK.getColor()); + + if (isMouseSelected()) { + GuiUtils.fill(graphics, x(), y(), width(), height(), 0x22FFFFFF); + } + + final float scale = 0.75f; + Component trainName = TextUtils.text(data.trainName()).withStyle(ChatFormatting.BOLD); + graphics.poseStack().pushPose(); + graphics.poseStack().translate(x(), y(), 0); + graphics.poseStack().scale(scale, scale, scale); + + Component predictionsText = TextUtils.text(data.predictionsInitialized() + " / " + data.predictionsCount()); + int platformTextWidth = font.width(predictionsText); + CreateDynamicWidgets.renderTextHighlighted(graphics, 5, 4, font, trainName, Constants.COLOR_TRAIN_BACKGROUND); + final int maxIdWidth = (int)(width() - platformTextWidth * scale - 15 - (45 + font.width(trainName)) * scale); + MutableComponent idText = TextUtils.text(data.trainId().toString()); + if (font.width(idText) > maxIdWidth) { + idText = TextUtils.text(font.substrByWidth(idText, maxIdWidth).getString()).append(TextUtils.text("...")).withStyle(idText.getStyle()); + } + GuiUtils.drawString(graphics, font, 20 + font.width(trainName), 6, idText, 0xFFFFFF, EAlignment.LEFT, false); + GuiUtils.drawString(graphics, font, (int)(width() / scale) - 6, 6, predictionsText, 0xFFFFFF, EAlignment.RIGHT, false); + GuiUtils.drawString(graphics, font, 5, 20, TextUtils.text("Session: " + data.sessionId()), 0xFFDBDBDB, EAlignment.LEFT, false); + GuiUtils.drawString(graphics, font, 5, 30, TextUtils.text("Status: " + data.state().getName()), data.state().getColor(), EAlignment.LEFT, false); + graphics.poseStack().popPose(); + } + +} diff --git a/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/TrainGroupEntryWidget.java b/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/TrainGroupEntryWidget.java deleted file mode 100644 index e0a17fe7..00000000 --- a/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/TrainGroupEntryWidget.java +++ /dev/null @@ -1,408 +0,0 @@ -package de.mrjulsen.crn.client.gui.widgets; - -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.HashMap; -import java.util.Map.Entry; -import com.mojang.blaze3d.systems.RenderSystem; -import com.mojang.blaze3d.vertex.PoseStack; -import com.simibubi.create.content.trains.station.NoShadowFontWrapper; - -import de.mrjulsen.crn.Constants; -import de.mrjulsen.crn.CreateRailwaysNavigator; -import de.mrjulsen.crn.client.gui.CreateDynamicWidgets; -import de.mrjulsen.crn.client.gui.screen.AbstractEntryListSettingsScreen; -import de.mrjulsen.crn.data.ClientTrainStationSnapshot; -import de.mrjulsen.crn.data.GlobalSettingsManager; -import de.mrjulsen.crn.data.TrainGroup; -import de.mrjulsen.mcdragonlib.client.gui.widgets.DLEditBox; -import de.mrjulsen.mcdragonlib.client.util.Graphics; -import de.mrjulsen.mcdragonlib.client.util.GuiAreaDefinition; -import de.mrjulsen.mcdragonlib.client.util.GuiUtils; -import de.mrjulsen.mcdragonlib.core.EAlignment; -import de.mrjulsen.mcdragonlib.util.MathUtils; -import de.mrjulsen.mcdragonlib.util.TextUtils; -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.Font; -import net.minecraft.client.gui.narration.NarrationElementOutput; -import net.minecraft.network.chat.MutableComponent; -import net.minecraft.resources.ResourceLocation; - -public class TrainGroupEntryWidget extends AbstractEntryListOptionWidget { - - private static final ResourceLocation GUI_WIDGETS = new ResourceLocation(CreateRailwaysNavigator.MOD_ID, "textures/gui/settings_widgets.png"); - public static final int WIDTH = 200; - public static final int HEIGHT = 48; - private static final int STATION_ENTRY_HEIGHT = 20; - - private final AbstractEntryListSettingsScreen parent; - private final Font shadowlessFont; - private final Minecraft minecraft; - - private final Runnable onUpdate; - - // Data - private TrainGroup trainGroup; - private boolean expanded = false; - - // Controls - private final DLEditBox titleBox; - private final DLEditBox newEntryBox; - private final Map removeStationButtons = new HashMap<>(); - private final Map trainGroupNames = new HashMap<>(); - - private GuiAreaDefinition titleBarArea; - - private GuiAreaDefinition deleteButton; - private GuiAreaDefinition expandButton; - private GuiAreaDefinition addButton; - - private ModStationSuggestions suggestions; - - // Tooltips - private final MutableComponent tooltipDeleteAlias = TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".train_group_settings.delete_alias.tooltip"); - private final MutableComponent tooltipDeleteStation = TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".train_group_settings.delete_station.tooltip"); - private final MutableComponent tooltipAddStation = TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".train_group_settings.add_station.tooltip"); - - - public TrainGroupEntryWidget(AbstractEntryListSettingsScreen parent, int pX, int pY, TrainGroup trainGroup, Runnable onUpdate, boolean expanded) { - super(pX, pY, 200, 48); - - Minecraft minecraft = Minecraft.getInstance(); - shadowlessFont = new NoShadowFontWrapper(minecraft.font); - - this.minecraft = Minecraft.getInstance(); - this.parent = parent; - this.trainGroup = trainGroup; - this.expanded = expanded; - this.onUpdate = onUpdate; - - titleBox = new DLEditBox(minecraft.font, pX + 30, pY + 10, 129, 12, TextUtils.empty()); - titleBox.setBordered(false); - titleBox.setMaxLength(32); - titleBox.setTextColor(0xFFFFFF); - titleBox.setValue(trainGroup.getGroupName()); - titleBox.withOnFocusChanged((box, focused) -> { - if (!focused) { - if (!setGroupName(box.getValue())) { - titleBox.setValue(trainGroup.getGroupName()); - } - } - }); - titleBox.visible = expanded; - addRenderableWidget(titleBox); - - - newEntryBox = new DLEditBox(minecraft.font, pX + 30, pY + 30, 129, 12, TextUtils.empty()); - newEntryBox.setBordered(false); - newEntryBox.setMaxLength(32); - newEntryBox.setTextColor(0xFFFFFF); - newEntryBox.visible = expanded; - newEntryBox.setResponder(x -> { - updateEditorSubwidgets(newEntryBox); - }); - addRenderableWidget(newEntryBox); - - setYPos(pY); - } - - public TrainGroup getTrainGroup() { - return trainGroup; - } - - public boolean isExpanded() { - return expanded; - } - - private void initStationDeleteButtons() { - removeStationButtons.clear(); - trainGroupNames.clear(); - - String[] names = trainGroup.getTrainNames().toArray(String[]::new); - for (int i = 0; i < names.length; i++) { - String name = names[i]; - removeStationButtons.put(name, new GuiAreaDefinition(x + 165, y + 26 + (i * STATION_ENTRY_HEIGHT) + 2, 16, 16)); - trainGroupNames.put(name, new GuiAreaDefinition(x + 25, y + 26 + (i * STATION_ENTRY_HEIGHT) + 2, 129, 16)); - } - - titleBarArea = new GuiAreaDefinition(x + 25, y + 6, 129, 16); - } - - @Override - public void tick() { - super.tick(); - - if (suggestions != null) { - suggestions.tick(); - - if (!newEntryBox.canConsumeInput()) { - clearSuggestions(); - } - } - } - - private void toggleExpanded() { - this.expanded = !expanded; - titleBox.visible = expanded; - newEntryBox.visible = expanded; - } - - private void deleteTrainGroup() { - GlobalSettingsManager.getInstance().getSettingsData().unregisterTrainGroup(trainGroup, onUpdate); - } - - private void addTrain(String name) { - String prevName = trainGroup.getGroupName(); - if (ClientTrainStationSnapshot.getInstance().getAllTrainNames().stream().noneMatch(x -> x.equals(name)) || newEntryBox.getValue().isBlank()) { - return; - } - - trainGroup.add(name); - trainGroup.updateLastEdited(minecraft.player.getName().getString()); - GlobalSettingsManager.getInstance().getSettingsData().updateTrainGroup(prevName, trainGroup, () -> { - onUpdate.run(); - initStationDeleteButtons(); - }); - - newEntryBox.setValue(""); - newEntryBox.setFocus(false); - } - - private boolean setGroupName(String name) { - String prevName = trainGroup.getGroupName(); - - if (name == null || name.isBlank()) { - return false; - } - - if (GlobalSettingsManager.getInstance().getSettingsData().getTrainGroupsList().stream().anyMatch(x -> x.getGroupName().equals(name))) { - return false; - } - - trainGroup.setGroupName(name); - trainGroup.updateLastEdited(minecraft.player.getName().getString()); - GlobalSettingsManager.getInstance().getSettingsData().updateTrainGroup(prevName, trainGroup, onUpdate); - return true; - } - - private void removeTrain(String name) { - String prevName = trainGroup.getGroupName(); - trainGroup.remove(name); - trainGroup.updateLastEdited(minecraft.player.getName().getString()); - GlobalSettingsManager.getInstance().getSettingsData().updateTrainGroup(prevName, trainGroup, () -> { - onUpdate.run(); - initStationDeleteButtons(); - }); - initStationDeleteButtons(); - onUpdate.run(); - } - - @Override - public int getHeight() { - return height; - } - - public int calcHeight() { - if (expanded) { - height = STATION_ENTRY_HEIGHT * trainGroup.getTrainNames().size() + 50; - } else { - height = HEIGHT; - } - return height; - } - - @Override - public void setYPos(int y) { - this.y = y; - deleteButton = new GuiAreaDefinition(x + 165, y + 6, 16, 16); - expandButton = new GuiAreaDefinition(x + 182, y + 6, 16, 16); - addButton = new GuiAreaDefinition(x + 165, y + 26 + (trainGroup.getTrainNames().size() * STATION_ENTRY_HEIGHT) + 2, 16, 16); - titleBox.y = y + 10; - initStationDeleteButtons(); - } - - @Override - public void renderSuggestions(PoseStack poseStack, int mouseX, int mouseY, float partialTicks) { - if (suggestions != null) { - poseStack.pushPose(); - poseStack.translate(0, -parent.getScrollOffset(partialTicks), 500); - suggestions.render(poseStack, mouseX, mouseY + parent.getScrollOffset(partialTicks)); - poseStack.popPose(); - } - } - - @Override - public void renderMainLayer(Graphics graphics, int pMouseX, int pMouseY, float pPartialTick) { - RenderSystem.setShaderTexture(0, GUI_WIDGETS); - GuiUtils.drawTexture(GUI_WIDGETS, graphics, x, y, 0, 0, WIDTH, HEIGHT); - - GuiUtils.drawTexture(GUI_WIDGETS, graphics, deleteButton.getX(), deleteButton.getY(), 232, 0, 16, 16); // delete button - GuiUtils.drawTexture(GUI_WIDGETS, graphics, expandButton.getX(), expandButton.getY(), expanded ? 216 : 200, 0, 16, 16); // expand button - - if (expanded) { - Set names = trainGroup.getTrainNames(); - GuiUtils.drawTexture(GUI_WIDGETS, graphics, x + 25, y + 5, 0, 92, 139, 18); // textbox - newEntryBox.y = y + 26 + (names.size() * STATION_ENTRY_HEIGHT) + 6; - - for (int i = 0; i < names.size(); i++) { - GuiUtils.drawTexture(GUI_WIDGETS, graphics, x, y + 26 + (i * STATION_ENTRY_HEIGHT), 0, 48, 200, STATION_ENTRY_HEIGHT); - } - - GuiUtils.drawTexture(GUI_WIDGETS, graphics, x, y + 26 + (names.size() * STATION_ENTRY_HEIGHT), 0, 68, 200, 24); - CreateDynamicWidgets.renderTextBox(graphics, x + 25, y + 26 + (names.size() * STATION_ENTRY_HEIGHT) + 1, 139); - GuiUtils.drawTexture(GUI_WIDGETS, graphics, addButton.getX(), addButton.getY(), 200, 16, 16, 16); // add button - - for (GuiAreaDefinition def : removeStationButtons.values()) { - GuiUtils.drawTexture(GUI_WIDGETS, graphics, def.getX(), def.getY(), 232, 0, 16, 16); // delete button - } - - int i = 0; - for (String entry : names) { - GuiUtils.drawString(graphics, shadowlessFont, x + 30, y + 26 + (i * STATION_ENTRY_HEIGHT) + 6, TextUtils.text(entry), 0xFFFFFF, EAlignment.LEFT, false); - i++; - } - - } else { - MutableComponent name = TextUtils.text(trainGroup.getGroupName()); - int maxTextWidth = 129; - if (shadowlessFont.width(name) > maxTextWidth) { - name = TextUtils.text(shadowlessFont.substrByWidth(name, maxTextWidth).getString()).append(Constants.ELLIPSIS_STRING); - } - GuiUtils.drawString(graphics, shadowlessFont, x + 30, y + 10, name, 0xFFFFFF, EAlignment.LEFT, false); - - graphics.poseStack().scale(0.75f, 0.75f, 0.75f); - GuiUtils.drawString(graphics, shadowlessFont, (int)((x + 5) / 0.75f), (int)((y + 30) / 0.75f), TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".train_group_settings.summary", trainGroup.getTrainNames().size()), 0xDBDBDB, EAlignment.LEFT, false); - if (trainGroup.getLastEditorName() != null && !trainGroup.getLastEditorName().isBlank()) { - GuiUtils.drawString(graphics, shadowlessFont, (int)((x + 5) / 0.75f), (int)((y + 38) / 0.75f), TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".train_group_settings.editor", trainGroup.getLastEditorName(), trainGroup.getLastEditedTimeFormatted()), 0xDBDBDB, EAlignment.LEFT, false); - } - float s = 1 / 0.75f; - graphics.poseStack().scale(s, s, s); - } - - super.renderMainLayer(graphics, pMouseX, pMouseY, pPartialTick); - - // Button highlight - if (deleteButton.isInBounds(pMouseX, pMouseY)) { - GuiUtils.fill(graphics, deleteButton.getX(), deleteButton.getY(), deleteButton.getWidth(), deleteButton.getHeight(), 0x1AFFFFFF); - } else if (expandButton.isInBounds(pMouseX, pMouseY)) { - GuiUtils.fill(graphics, expandButton.getX(), expandButton.getY(), expandButton.getWidth(), expandButton.getHeight(), 0x1AFFFFFF); - } else if (expanded && addButton.isInBounds(pMouseX, pMouseY)) { - GuiUtils.fill(graphics, addButton.getX(), addButton.getY(), addButton.getWidth(), addButton.getHeight(), 0x1AFFFFFF); - } else if (expanded && removeStationButtons.values().stream().anyMatch(x -> x.isInBounds(pMouseX, pMouseY))) { - for (Entry entry : removeStationButtons.entrySet()) { - if (entry.getValue().isInBounds(pMouseX, pMouseY)) { - GuiUtils.fill(graphics, entry.getValue().getX(), entry.getValue().getY(), entry.getValue().getWidth(), entry.getValue().getHeight(), 0x1AFFFFFF); - } - } - } - } - - @Override - public void renderFrontLayer(Graphics graphics, int mouseX, int mouseY, float partialTicks) { - if (suggestions != null) { - graphics.poseStack().pushPose(); - graphics.poseStack().translate(0, -parent.getScrollOffset(partialTicks), 500); - suggestions.render(graphics.poseStack(), mouseX, mouseY + parent.getScrollOffset(partialTicks)); - graphics.poseStack().popPose(); - } - - GuiUtils.renderTooltipWithOffset(parent, deleteButton, List.of(tooltipDeleteAlias), width, graphics, mouseX, mouseY, 0, parent.getScrollOffset(partialTicks)); - GuiUtils.renderTooltipWithOffset(parent, expandButton, List.of(expanded ? Constants.TOOLTIP_COLLAPSE : Constants.TOOLTIP_EXPAND), width, graphics, mouseX, mouseY, 0, parent.getScrollOffset(partialTicks)); - if (expanded) { - GuiUtils.renderTooltipWithOffset(parent, addButton, List.of(tooltipAddStation), width, graphics, mouseX, mouseY, 0, parent.getScrollOffset(partialTicks)); - for (Entry entry : removeStationButtons.entrySet()) { - if (GuiUtils.renderTooltipWithOffset(parent, entry.getValue(), List.of(tooltipDeleteStation), width, graphics, mouseX, mouseY, 0, parent.getScrollOffset(partialTicks))) { - break; - } - } - - for (Entry entry : trainGroupNames.entrySet()) { - if (shadowlessFont.width(entry.getKey()) > 129 && GuiUtils.renderTooltipAt(parent, entry.getValue(), List.of(TextUtils.text(entry.getKey())), width, graphics, entry.getValue().getLeft() + 1, entry.getValue().getTop() - parent.getScrollOffset(partialTicks), mouseX, mouseY, 0, parent.getScrollOffset(partialTicks))) { - break; - } - } - } else { - if (titleBarArea.isInBounds(mouseX, mouseY + parent.getScrollOffset(partialTicks)) && shadowlessFont.width(trainGroup.getGroupName()) > 129) { - GuiUtils.renderTooltipAt(parent, titleBarArea, List.of(TextUtils.text(trainGroup.getGroupName())), width, graphics, titleBarArea.getLeft() + 1, titleBarArea.getTop() - parent.getScrollOffset(partialTicks), mouseX, mouseY, 0, parent.getScrollOffset(partialTicks)); - } - } - } - - @Override - public boolean mouseClickedLoop(double pMouseX, double pMouseY, int pButton) { - //parent.unfocusAllEntries(); - - if (suggestions != null && suggestions.mouseClicked((int) pMouseX, (int) pMouseY, pButton)) - return super.mouseClicked(pMouseX, pMouseY, pButton); - - return false; - } - - @Override - public boolean mouseClicked(double pMouseX, double pMouseY, int pButton) { - if (suggestions != null && suggestions.mouseClicked((int) pMouseX, (int) pMouseY, pButton)) - return super.mouseClicked(pMouseX, pMouseY, pButton); - - if (deleteButton.isInBounds(pMouseX, pMouseY)) { - deleteTrainGroup(); - return super.mouseClicked(pMouseX, pMouseY, pButton); - } else if (expandButton.isInBounds(pMouseX, pMouseY)) { - toggleExpanded(); - return super.mouseClicked(pMouseX, pMouseY, pButton); - } else if (expanded && addButton.isInBounds(pMouseX, pMouseY)) { - addTrain(newEntryBox.getValue()); - return super.mouseClicked(pMouseX, pMouseY, pButton); - } else if (expanded && removeStationButtons.values().stream().anyMatch(x -> x.isInBounds(pMouseX, pMouseY))) { - for (Entry entry : removeStationButtons.entrySet()) { - if (entry.getValue().isInBounds(pMouseX, pMouseY)) { - removeTrain(entry.getKey()); - return super.mouseClicked(pMouseX, pMouseY, pButton); - } - } - } - return super.mouseClicked(pMouseX, pMouseY, pButton); - } - - @Override - public boolean mouseScrolledLoop(double pMouseX, double pMouseY, double pDelta) { - if (suggestions != null && suggestions.mouseScrolled(pMouseX, pMouseY, MathUtils.clamp(pDelta, -1.0D, 1.0D))) - return true; - - return false; - } - - private void clearSuggestions() { - if (suggestions != null) { - suggestions.getEditBox().setSuggestion(""); - } - suggestions = null; - } - - - protected void updateEditorSubwidgets(DLEditBox field) { - clearSuggestions(); - - suggestions = new ModStationSuggestions(minecraft, parent, field, minecraft.font, getViableTrains(field), field.getHeight() + 2 + field.y); - suggestions.setAllowSuggestions(true); - suggestions.updateCommandInfo(); - } - - private List getViableTrains(DLEditBox field) { - return ClientTrainStationSnapshot.getInstance().getAllTrainNames().stream() - .distinct() - .filter(x -> !GlobalSettingsManager.getInstance().getSettingsData().isTrainBlacklisted(x) && !trainGroup.contains(x)) - .sorted((a, b) -> a.compareTo(b)) - .toList(); - } - - @Override - public NarrationPriority narrationPriority() { - return NarrationPriority.HOVERED; - } - - @Override - public void updateNarration(NarrationElementOutput narrationElementOutput) { - - } -} diff --git a/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/WidgetContainerCollection.java b/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/WidgetContainerCollection.java deleted file mode 100644 index c8a79d65..00000000 --- a/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/WidgetContainerCollection.java +++ /dev/null @@ -1,67 +0,0 @@ -package de.mrjulsen.crn.client.gui.widgets; - -import java.util.ArrayList; -import java.util.List; -import java.util.function.Consumer; -import java.util.function.Predicate; - -import de.mrjulsen.mcdragonlib.client.gui.widgets.WidgetContainer; - -public class WidgetContainerCollection { - - public final List components = new ArrayList<>(); - - private boolean enabled = true; - private boolean visible = true; - - public void performForEach(Predicate filter, Consumer consumer) { - components.stream().filter(filter).forEach(consumer); - } - - public void performForEach(Consumer consumer) { - performForEach(x -> true, consumer); - } - - public void performForEachOfType(Class clazz, Predicate filter, Consumer consumer) { - components.stream().filter(clazz::isInstance).map(clazz::cast).filter(filter).forEach(consumer); - } - - public void performForEachOfType(Class clazz, Consumer consumer) { - performForEachOfType(clazz, x -> true, consumer); - } - - - - public boolean isVisible() { - return visible; - } - - public boolean isEnabled() { - return enabled; - } - - public void setVisible(boolean v) { - this.visible = v; - performForEach(x -> x.set_visible(v)); - } - - public void setEnabled(boolean e) { - this.enabled = e; - performForEach(x -> x.set_active(e)); - } - - public void add(W widget) { - widget.set_active(enabled); - widget.set_visible(visible); - components.add(widget); - } - - public void clear() { - components.clear(); - } - - public void clear(Consumer onRemove) { - performForEach(x -> onRemove.accept(x)); - clear(); - } -} \ No newline at end of file diff --git a/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/create/CreateTimeSelectionWidget.java b/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/create/CreateTimeSelectionWidget.java new file mode 100644 index 00000000..8e30a997 --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/create/CreateTimeSelectionWidget.java @@ -0,0 +1,82 @@ +package de.mrjulsen.crn.client.gui.widgets.create; + +import com.simibubi.create.foundation.gui.widget.ScrollInput; + +import de.mrjulsen.crn.CreateRailwaysNavigator; +import de.mrjulsen.crn.client.gui.CreateDynamicWidgets; +import de.mrjulsen.mcdragonlib.DragonLib; +import de.mrjulsen.mcdragonlib.client.gui.widgets.WidgetContainer; +import de.mrjulsen.mcdragonlib.client.util.Graphics; +import de.mrjulsen.mcdragonlib.client.util.GuiUtils; +import de.mrjulsen.mcdragonlib.core.EAlignment; +import de.mrjulsen.mcdragonlib.util.DLUtils; +import de.mrjulsen.mcdragonlib.util.TextUtils; +import de.mrjulsen.mcdragonlib.util.TimeUtils; +import net.minecraft.client.gui.narration.NarrationElementOutput; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.MutableComponent; + +public class CreateTimeSelectionWidget extends WidgetContainer { + + public static final int WIDHT = 66; + public static final int HEIGHT = 18; + + private final MutableComponent transferTimeBoxText = TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".search_settings.transfer_time"); + + protected ScrollInput transferTimeInput; + private Component transferLabel = TextUtils.empty(); + + private int value = 0; + + public CreateTimeSelectionWidget(int x, int y, int max) { + super(x, y, WIDHT, HEIGHT); + + transferTimeInput = addRenderableWidget(new ScrollInput(x() + 3, y(), width() - 6, height()) + .withRange(0, max) + .withStepFunction(a -> a.shift ? 1000 : 500) + .titled(transferTimeBoxText.copy()) + .calling((i) -> { + if (transferTimeInput == null) return; + value = transferTimeInput.getState(); + transferLabel = TextUtils.text(TimeUtils.parseDurationShort(value)); + }) + ); + setValue(0); + } + + @Override + public void renderMainLayer(Graphics graphics, int mouseX, int mouseY, float partialTicks) { + CreateDynamicWidgets.renderTextBox(graphics, x(), y(), WIDHT); + GuiUtils.drawString(graphics, font, x() + 5, y() + 5, transferLabel, DragonLib.NATIVE_BUTTON_FONT_COLOR_ACTIVE, EAlignment.LEFT, true); + } + + @Override + public void tick() { + super.tick(); + transferTimeInput.tick(); + } + + public int getValue() { + return value; + } + + public void setValue(int value) { + this.value = value; + DLUtils.doIfNotNull(transferTimeInput, x -> x.setState(value)); + transferLabel = TextUtils.text(TimeUtils.parseDurationShort(value)); + } + + @Override + public NarrationPriority narrationPriority() { + return NarrationPriority.HOVERED; + } + + @Override + public void updateNarration(NarrationElementOutput narrationElementOutput) {} + + @Override + public boolean consumeScrolling(double mouseX, double mouseY) { + return false; + } + +} diff --git a/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/flyouts/FlyoutAdvancedSearchsettingsWidget.java b/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/flyouts/FlyoutAdvancedSearchsettingsWidget.java new file mode 100644 index 00000000..4a1fe2a0 --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/flyouts/FlyoutAdvancedSearchsettingsWidget.java @@ -0,0 +1,63 @@ +package de.mrjulsen.crn.client.gui.widgets.flyouts; + +import java.util.function.Consumer; +import de.mrjulsen.crn.client.gui.ModGuiIcons; +import de.mrjulsen.crn.CreateRailwaysNavigator; +import de.mrjulsen.crn.client.gui.CreateDynamicWidgets.ColorShade; +import de.mrjulsen.crn.client.gui.widgets.AbstractFlyoutWidget; +import de.mrjulsen.mcdragonlib.DragonLib; +import de.mrjulsen.mcdragonlib.client.gui.DLScreen; +import de.mrjulsen.mcdragonlib.client.gui.widgets.DLIconButton; +import de.mrjulsen.mcdragonlib.client.gui.widgets.DLAbstractImageButton.ButtonType; +import de.mrjulsen.mcdragonlib.client.render.DynamicGuiRenderer.AreaStyle; +import de.mrjulsen.mcdragonlib.client.util.Graphics; +import de.mrjulsen.mcdragonlib.client.util.GuiAreaDefinition; +import de.mrjulsen.mcdragonlib.client.util.GuiUtils; +import de.mrjulsen.mcdragonlib.core.EAlignment; +import de.mrjulsen.mcdragonlib.util.TextUtils; +import net.minecraft.ChatFormatting; +import net.minecraft.client.gui.components.Widget; +import net.minecraft.client.gui.components.events.GuiEventListener; +import net.minecraft.client.gui.narration.NarratableEntry; +import net.minecraft.network.chat.MutableComponent; + +public class FlyoutAdvancedSearchsettingsWidget extends AbstractFlyoutWidget { + + private final MutableComponent textTrainGroups = TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".search_options.advanced_options").withStyle(ChatFormatting.BOLD); + //private UserSettings settings; + + public FlyoutAdvancedSearchsettingsWidget(DLScreen screen, FlyoutPointer pointer, ColorShade pointerShade, Consumer addRenderableWidgetFunc, Consumer removeWidgetFunc) { + super(screen, 1, 120, pointer, pointerShade, addRenderableWidgetFunc, removeWidgetFunc); + set_width(Math.max(150, font.width(textTrainGroups) + DLIconButton.DEFAULT_BUTTON_WIDTH + 16 + 10 + FlyoutPointer.WIDTH * 2)); + + //int top = getContentArea().getY() + 21; + //int contentHeight = getContentArea().getHeight() - 21 - 2; + + /* + addRenderableWidget(new SearchOptionButton(getContentArea().getX() + 2, top, getContentArea().getWidth() - 4, 18, TextUtils.text("Train Groups"), () -> "Here be Dragons!", (b) -> { + new FlyoutTrainGroupsWidget<>(screen, FlyoutPointer.UP, ColorShade.DARK, addRenderableWidgetFunc, (settings) -> { + return settings.navigationExcludedTrainGroups; + }, (w) -> { + removeWidgetFunc.accept(w); + }).open(b); + })); + */ + + DLIconButton resetBtn = addRenderableWidget(new DLIconButton(ButtonType.DEFAULT, AreaStyle.FLAT, ModGuiIcons.REFRESH.getAsSprite(16, 16), getContentArea().getX() + getContentArea().getWidth() - DLIconButton.DEFAULT_BUTTON_WIDTH - 2, getContentArea().getY() + 2, TextUtils.empty(), (b) -> { + + })); + + resetBtn.setBackColor(0x00000000); + } + + @Override + public void renderFlyoutContent(Graphics graphics, int mouseX, int mouseY, float partialTicks, GuiAreaDefinition contentArea) { + super.renderFlyoutContent(graphics, mouseX, mouseY, partialTicks, contentArea); + GuiUtils.drawString(graphics, font, contentArea.getX() + 8, contentArea.getY() + 8, textTrainGroups, DragonLib.NATIVE_BUTTON_FONT_COLOR_ACTIVE, EAlignment.LEFT, false); + } + + @Override + public void close() { + super.close(); + } +} diff --git a/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/flyouts/FlyoutColorPicker.java b/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/flyouts/FlyoutColorPicker.java new file mode 100644 index 00000000..a544c841 --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/flyouts/FlyoutColorPicker.java @@ -0,0 +1,30 @@ +package de.mrjulsen.crn.client.gui.widgets.flyouts; + +import java.util.function.Consumer; + +import de.mrjulsen.crn.Constants; +import de.mrjulsen.crn.client.gui.CreateDynamicWidgets.ColorShade; +import de.mrjulsen.crn.client.gui.widgets.AbstractFlyoutWidget; +import de.mrjulsen.crn.client.gui.widgets.ColorPickerWidget; +import de.mrjulsen.mcdragonlib.client.gui.DLScreen; +import net.minecraft.client.gui.components.Widget; +import net.minecraft.client.gui.components.events.GuiEventListener; +import net.minecraft.client.gui.narration.NarratableEntry; + +public class FlyoutColorPicker extends AbstractFlyoutWidget { + + private final ColorPickerWidget colorPicker; + + public FlyoutColorPicker(DLScreen screen, int initialColor, Consumer addRenderableWidgetFunc, Consumer removeWidgetFunc) { + super(screen, 100, 50, FlyoutPointer.RIGHT, ColorShade.LIGHT, addRenderableWidgetFunc, removeWidgetFunc); + colorPicker = addRenderableWidget(new ColorPickerWidget(screen, x() + 10, y() + 10, Constants.DEFAULT_TRAIN_TYPE_COLORS, 5, initialColor, (picker) -> { + closeImmediately(); + })); + set_width(colorPicker.width() + 20); + set_height(colorPicker.height() + 20); + } + + public ColorPickerWidget getColorPicker() { + return colorPicker; + } +} diff --git a/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/flyouts/FlyoutDepartureInWidget.java b/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/flyouts/FlyoutDepartureInWidget.java new file mode 100644 index 00000000..efb50ddc --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/flyouts/FlyoutDepartureInWidget.java @@ -0,0 +1,76 @@ +package de.mrjulsen.crn.client.gui.widgets.flyouts; + +import java.util.function.Consumer; +import java.util.function.Supplier; + +import de.mrjulsen.crn.client.gui.ModGuiIcons; +import de.mrjulsen.crn.CreateRailwaysNavigator; +import de.mrjulsen.crn.client.gui.CreateDynamicWidgets.ColorShade; +import de.mrjulsen.crn.client.gui.widgets.AbstractFlyoutWidget; +import de.mrjulsen.crn.client.gui.widgets.create.CreateTimeSelectionWidget; +import de.mrjulsen.crn.data.UserSettings; +import de.mrjulsen.crn.data.UserSettings.UserSetting; +import de.mrjulsen.mcdragonlib.DragonLib; +import de.mrjulsen.mcdragonlib.client.gui.DLScreen; +import de.mrjulsen.mcdragonlib.client.gui.widgets.DLIconButton; +import de.mrjulsen.mcdragonlib.client.gui.widgets.IDragonLibWidget; +import de.mrjulsen.mcdragonlib.client.gui.widgets.DLAbstractImageButton.ButtonType; +import de.mrjulsen.mcdragonlib.client.render.DynamicGuiRenderer.AreaStyle; +import de.mrjulsen.mcdragonlib.client.util.Graphics; +import de.mrjulsen.mcdragonlib.client.util.GuiAreaDefinition; +import de.mrjulsen.mcdragonlib.client.util.GuiUtils; +import de.mrjulsen.mcdragonlib.core.EAlignment; +import de.mrjulsen.mcdragonlib.util.DLUtils; +import de.mrjulsen.mcdragonlib.util.TextUtils; +import net.minecraft.ChatFormatting; +import net.minecraft.client.gui.components.Widget; +import net.minecraft.client.gui.components.events.GuiEventListener; +import net.minecraft.client.gui.narration.NarratableEntry; +import net.minecraft.network.chat.MutableComponent; + +public class FlyoutDepartureInWidget extends AbstractFlyoutWidget { + + private final MutableComponent textDepartureIn = TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".search_options.departure_in").withStyle(ChatFormatting.BOLD); + private final UserSettings settings; + + private final CreateTimeSelectionWidget timeSelection; + private final Supplier> getUserSetting; + + public FlyoutDepartureInWidget(DLScreen screen, FlyoutPointer pointer, ColorShade pointerShade, Consumer addRenderableWidgetFunc, UserSettings settings, Supplier> getUserSetting, Consumer removeWidgetFunc) { + super(screen, 1, 60, pointer, pointerShade, addRenderableWidgetFunc, removeWidgetFunc); + set_width(Math.max(CreateTimeSelectionWidget.WIDHT + 16, font.width(textDepartureIn) + DLIconButton.DEFAULT_BUTTON_WIDTH + 16 + 10 + FlyoutPointer.WIDTH * 2)); + this.settings = settings; + this.getUserSetting = getUserSetting; + + this.timeSelection = addRenderableWidget(new CreateTimeSelectionWidget(getContentArea().getX() + 8, getContentArea().getY() + 24, DragonLib.TICKS_PER_DAY * 10)); + DLIconButton resetBtn = addRenderableWidget(new DLIconButton(ButtonType.DEFAULT, AreaStyle.FLAT, ModGuiIcons.REFRESH.getAsSprite(16, 16), getContentArea().getX() + getContentArea().getWidth() - DLIconButton.DEFAULT_BUTTON_WIDTH - 2, getContentArea().getY() + 2, TextUtils.empty(), (b) -> { + getUserSetting.get().setToDefault(); + timeSelection.setValue(getUserSetting.get().getValue()); + })); + + resetBtn.setBackColor(0x00000000); + + } + + @Override + public void renderFlyoutContent(Graphics graphics, int mouseX, int mouseY, float partialTicks, GuiAreaDefinition contentArea) { + super.renderFlyoutContent(graphics, mouseX, mouseY, partialTicks, contentArea); + GuiUtils.drawString(graphics, font, contentArea.getX() + 8, contentArea.getY() + 8, textDepartureIn, DragonLib.NATIVE_BUTTON_FONT_COLOR_ACTIVE, EAlignment.LEFT, false); + } + + @SuppressWarnings("resource") + @Override + public void open(IDragonLibWidget parent) { + this.timeSelection.setValue(getUserSetting.get().getValue()); + super.open(parent); + } + + @Override + public void close() { + DLUtils.doIfNotNull(settings, x -> { + getUserSetting.get().setValue(timeSelection.getValue()); + x.clientSave(super::close); + }); + } + +} diff --git a/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/flyouts/FlyoutTrainGroupsWidget.java b/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/flyouts/FlyoutTrainGroupsWidget.java new file mode 100644 index 00000000..b7ca3265 --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/flyouts/FlyoutTrainGroupsWidget.java @@ -0,0 +1,96 @@ +package de.mrjulsen.crn.client.gui.widgets.flyouts; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Set; +import java.util.function.Consumer; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +import de.mrjulsen.crn.client.gui.ModGuiIcons; +import de.mrjulsen.crn.CreateRailwaysNavigator; +import de.mrjulsen.crn.client.gui.CreateDynamicWidgets.ColorShade; +import de.mrjulsen.crn.client.gui.widgets.AbstractFlyoutWidget; +import de.mrjulsen.crn.client.gui.widgets.CRNListBox; +import de.mrjulsen.crn.client.gui.widgets.FlatCheckBox; +import de.mrjulsen.crn.client.gui.widgets.ModernVerticalScrollBar; +import de.mrjulsen.crn.data.TrainGroup; +import de.mrjulsen.crn.data.UserSettings; +import de.mrjulsen.crn.data.UserSettings.UserSetting; +import de.mrjulsen.crn.registry.ModAccessorTypes; +import de.mrjulsen.mcdragonlib.DragonLib; +import de.mrjulsen.mcdragonlib.client.gui.DLScreen; +import de.mrjulsen.mcdragonlib.client.gui.widgets.DLIconButton; +import de.mrjulsen.mcdragonlib.client.gui.widgets.IDragonLibWidget; +import de.mrjulsen.mcdragonlib.client.gui.widgets.DLAbstractImageButton.ButtonType; +import de.mrjulsen.mcdragonlib.client.render.DynamicGuiRenderer.AreaStyle; +import de.mrjulsen.mcdragonlib.client.util.Graphics; +import de.mrjulsen.mcdragonlib.client.util.GuiAreaDefinition; +import de.mrjulsen.mcdragonlib.client.util.GuiUtils; +import de.mrjulsen.mcdragonlib.core.EAlignment; +import de.mrjulsen.mcdragonlib.util.DLUtils; +import de.mrjulsen.mcdragonlib.util.TextUtils; +import de.mrjulsen.mcdragonlib.util.accessor.DataAccessor; +import net.minecraft.ChatFormatting; +import net.minecraft.client.gui.components.Widget; +import net.minecraft.client.gui.components.events.GuiEventListener; +import net.minecraft.client.gui.narration.NarratableEntry; +import net.minecraft.network.chat.MutableComponent; + +public class FlyoutTrainGroupsWidget extends AbstractFlyoutWidget { + + private final MutableComponent textTrainGroups = TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".search_options.train_groups").withStyle(ChatFormatting.BOLD); + private final UserSettings settings; + + private final CRNListBox trainGroups; + private final Supplier>> getUserSetting; + + public FlyoutTrainGroupsWidget(DLScreen screen, FlyoutPointer pointer, ColorShade pointerShade, Consumer addRenderableWidgetFunc, UserSettings settings, Supplier>> getUserSetting, Consumer removeWidgetFunc) { + super(screen, 1, 120, pointer, pointerShade, addRenderableWidgetFunc, removeWidgetFunc); + set_width(Math.max(150, font.width(textTrainGroups) + DLIconButton.DEFAULT_BUTTON_WIDTH + 16 + 10 + FlyoutPointer.WIDTH * 2)); + this.settings = settings; + this.getUserSetting = getUserSetting; + + int top = getContentArea().getY() + 21; + int contentHeight = getContentArea().getHeight() - 21 - 2; + + ModernVerticalScrollBar scrollBar = new ModernVerticalScrollBar(screen, getContentArea().getX() + getContentArea().getWidth() - 7, top, contentHeight, GuiAreaDefinition.of(screen)); + this.trainGroups = addRenderableWidget(new CRNListBox<>(screen, getContentArea().getX() + 2, top, getContentArea().getWidth() - 4, contentHeight, scrollBar)); + addRenderableWidget(scrollBar); + DLIconButton resetBtn = addRenderableWidget(new DLIconButton(ButtonType.DEFAULT, AreaStyle.FLAT, ModGuiIcons.REFRESH.getAsSprite(16, 16), getContentArea().getX() + getContentArea().getWidth() - DLIconButton.DEFAULT_BUTTON_WIDTH - 2, getContentArea().getY() + 2, TextUtils.empty(), (b) -> { + getUserSetting.get().setToDefault(); + settings.clientSave(() -> reload(null)); + })); + + resetBtn.setBackColor(0x00000000); + } + + @Override + public void renderFlyoutContent(Graphics graphics, int mouseX, int mouseY, float partialTicks, GuiAreaDefinition contentArea) { + super.renderFlyoutContent(graphics, mouseX, mouseY, partialTicks, contentArea); + GuiUtils.drawString(graphics, font, contentArea.getX() + 8, contentArea.getY() + 8, textTrainGroups, DragonLib.NATIVE_BUTTON_FONT_COLOR_ACTIVE, EAlignment.LEFT, false); + } + + @Override + public void open(IDragonLibWidget parent) { + reload(() -> super.open(parent)); + } + + private void reload(Runnable andThen) { + DataAccessor.getFromServer(null, ModAccessorTypes.GET_ALL_TRAIN_GROUPS, (groups) -> { + trainGroups.displayData(new ArrayList<>(groups.stream().sorted((a, b) -> a.getGroupName().compareToIgnoreCase(b.getGroupName())).toList()), (group) -> { + FlatCheckBox cb = new FlatCheckBox(0, 0, 0, group.getGroupName(), getUserSetting.get().getValue().stream().noneMatch(x -> x.equals(group.getGroupName())), (b) -> {}); + return cb; + }); + DLUtils.doIfNotNull(andThen, x -> x.run()); + }); + } + + @Override + public void close() { + DLUtils.doIfNotNull(settings, x -> { + getUserSetting.get().setValue(new HashSet<>(trainGroups.getEntries().stream().filter(a -> !a.getKey().isChecked()).map(a -> a.getValue()).map(a -> a.getGroupName()).collect(Collectors.toSet()))); + x.clientSave(super::close); + }); + } +} diff --git a/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/flyouts/FlyoutTransferTimeWidget.java b/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/flyouts/FlyoutTransferTimeWidget.java new file mode 100644 index 00000000..01f6eb44 --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/flyouts/FlyoutTransferTimeWidget.java @@ -0,0 +1,75 @@ +package de.mrjulsen.crn.client.gui.widgets.flyouts; + +import java.util.function.Consumer; +import java.util.function.Supplier; + +import de.mrjulsen.crn.client.gui.ModGuiIcons; +import de.mrjulsen.crn.CreateRailwaysNavigator; +import de.mrjulsen.crn.client.gui.CreateDynamicWidgets.ColorShade; +import de.mrjulsen.crn.client.gui.widgets.AbstractFlyoutWidget; +import de.mrjulsen.crn.client.gui.widgets.create.CreateTimeSelectionWidget; +import de.mrjulsen.crn.data.UserSettings; +import de.mrjulsen.crn.data.UserSettings.UserSetting; +import de.mrjulsen.mcdragonlib.DragonLib; +import de.mrjulsen.mcdragonlib.client.gui.DLScreen; +import de.mrjulsen.mcdragonlib.client.gui.widgets.DLIconButton; +import de.mrjulsen.mcdragonlib.client.gui.widgets.IDragonLibWidget; +import de.mrjulsen.mcdragonlib.client.gui.widgets.DLAbstractImageButton.ButtonType; +import de.mrjulsen.mcdragonlib.client.render.DynamicGuiRenderer.AreaStyle; +import de.mrjulsen.mcdragonlib.client.util.Graphics; +import de.mrjulsen.mcdragonlib.client.util.GuiAreaDefinition; +import de.mrjulsen.mcdragonlib.client.util.GuiUtils; +import de.mrjulsen.mcdragonlib.core.EAlignment; +import de.mrjulsen.mcdragonlib.util.DLUtils; +import de.mrjulsen.mcdragonlib.util.TextUtils; +import net.minecraft.ChatFormatting; +import net.minecraft.client.gui.components.Widget; +import net.minecraft.client.gui.components.events.GuiEventListener; +import net.minecraft.client.gui.narration.NarratableEntry; +import net.minecraft.network.chat.MutableComponent; + +public class FlyoutTransferTimeWidget extends AbstractFlyoutWidget { + + private final MutableComponent textTransferTime = TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".search_options.transfer_time").withStyle(ChatFormatting.BOLD); + private final UserSettings settings; + + private final Supplier> getUserSetting; + private final CreateTimeSelectionWidget timeSelection; + + public FlyoutTransferTimeWidget(DLScreen screen, FlyoutPointer pointer, ColorShade pointerShade, Consumer addRenderableWidgetFunc, UserSettings settings, Supplier> getUserSetting, Consumer removeWidgetFunc) { + super(screen, 1, 60, pointer, pointerShade, addRenderableWidgetFunc, removeWidgetFunc); + set_width(Math.max(CreateTimeSelectionWidget.WIDHT + 16, font.width(textTransferTime) + DLIconButton.DEFAULT_BUTTON_WIDTH + 16 + 10 + FlyoutPointer.WIDTH * 2)); + this.settings = settings; + this.getUserSetting = getUserSetting; + + this.timeSelection = addRenderableWidget(new CreateTimeSelectionWidget(getContentArea().getX() + 8, getContentArea().getY() + 24, DragonLib.TICKS_PER_DAY)); + DLIconButton resetBtn = addRenderableWidget(new DLIconButton(ButtonType.DEFAULT, AreaStyle.FLAT, ModGuiIcons.REFRESH.getAsSprite(16, 16), getContentArea().getX() + getContentArea().getWidth() - DLIconButton.DEFAULT_BUTTON_WIDTH - 2, getContentArea().getY() + 2, TextUtils.empty(), (b) -> { + getUserSetting.get().setToDefault(); + timeSelection.setValue(getUserSetting.get().getValue()); + })); + + resetBtn.setBackColor(0x00000000); + + } + + @Override + public void renderFlyoutContent(Graphics graphics, int mouseX, int mouseY, float partialTicks, GuiAreaDefinition contentArea) { + super.renderFlyoutContent(graphics, mouseX, mouseY, partialTicks, contentArea); + GuiUtils.drawString(graphics, font, contentArea.getX() + 8, contentArea.getY() + 8, textTransferTime, DragonLib.NATIVE_BUTTON_FONT_COLOR_ACTIVE, EAlignment.LEFT, false); + } + + @Override + public void open(IDragonLibWidget parent) { + this.timeSelection.setValue(getUserSetting.get().getValue()); + super.open(parent); + } + + @Override + public void close() { + DLUtils.doIfNotNull(settings, x -> { + getUserSetting.get().setValue(timeSelection.getValue()); + x.clientSave(super::close); + }); + } + +} diff --git a/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/notifications/NotificationTrainInitialization.java b/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/notifications/NotificationTrainInitialization.java new file mode 100644 index 00000000..44b44499 --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/notifications/NotificationTrainInitialization.java @@ -0,0 +1,54 @@ +package de.mrjulsen.crn.client.gui.widgets.notifications; + +import java.util.function.Consumer; + +import de.mrjulsen.crn.Constants; +import de.mrjulsen.crn.CreateRailwaysNavigator; +import de.mrjulsen.crn.client.ClientWrapper; +import de.mrjulsen.crn.client.gui.ModGuiIcons; +import de.mrjulsen.crn.client.gui.CreateDynamicWidgets.ColorShade; +import de.mrjulsen.crn.client.gui.widgets.AbstractNotificationPopup; +import de.mrjulsen.mcdragonlib.client.gui.DLScreen; +import de.mrjulsen.mcdragonlib.client.gui.widgets.DLAbstractImageButton.ButtonType; +import de.mrjulsen.mcdragonlib.client.gui.widgets.DLIconButton; +import de.mrjulsen.mcdragonlib.client.render.DynamicGuiRenderer.AreaStyle; +import de.mrjulsen.mcdragonlib.client.util.Graphics; +import de.mrjulsen.mcdragonlib.client.util.GuiAreaDefinition; +import de.mrjulsen.mcdragonlib.util.TextUtils; +import net.minecraft.Util; +import net.minecraft.client.gui.components.events.GuiEventListener; +import net.minecraft.network.chat.MutableComponent; + +public class NotificationTrainInitialization extends AbstractNotificationPopup { + + private final float scale = 0.75f; + private final int maxTextWidth; + private final MutableComponent text = TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".navigator.train_initialization_warning"); + + public NotificationTrainInitialization(DLScreen screen, int x, int y, int width, Consumer removeWidgetFunc) { + super(screen, x, y, width, 1, ColorShade.LIGHT, removeWidgetFunc); + this.maxTextWidth = (int)(width / scale) - 10 - ModGuiIcons.ICON_SIZE - 12 - DLIconButton.DEFAULT_BUTTON_WIDTH * 2; + set_height((int)((ClientWrapper.getTextBlockHeight(font, text, maxTextWidth)) * scale) + 10); + set_y(y() - height()); + + DLIconButton closeBtn = addRenderableWidget(new DLIconButton(ButtonType.DEFAULT, AreaStyle.FLAT, ModGuiIcons.X.getAsSprite(16, 16), x() + width() - DLIconButton.DEFAULT_BUTTON_WIDTH - 2, y() + 2, DLIconButton.DEFAULT_BUTTON_WIDTH, height() - 4, TextUtils.empty(), (b) -> { + close(); + })); + DLIconButton helpBtn = addRenderableWidget(new DLIconButton(ButtonType.DEFAULT, AreaStyle.FLAT, ModGuiIcons.HELP.getAsSprite(16, 16), x() + width() - DLIconButton.DEFAULT_BUTTON_WIDTH * 2 - 2, y() + 2, DLIconButton.DEFAULT_BUTTON_WIDTH, height() - 4, TextUtils.empty(), (b) -> { + Util.getPlatform().openUri(Constants.HELP_PAGE_NAVIGATION_WARNING); + })); + closeBtn.setBackColor(0); + helpBtn.setBackColor(0); + } + + @Override + public void renderFlyoutContent(Graphics graphics, int mouseX, int mouseY, float partialTicks, GuiAreaDefinition contentArea) { + super.renderFlyoutContent(graphics, mouseX, mouseY, partialTicks, contentArea); + ModGuiIcons.WARN.render(graphics, x() + 4, y() + 4); + graphics.poseStack().pushPose(); + graphics.poseStack().translate(x() + 5 + 2 + ModGuiIcons.ICON_SIZE, y() + 5, 0); + graphics.poseStack().scale(scale, scale, scale); + ClientWrapper.renderMultilineLabelSafe(graphics, 0, 0, font, text, maxTextWidth, 0xFFFF9999); + graphics.poseStack().popPose(); + } +} diff --git a/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/options/AbstractDataListEntry.java b/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/options/AbstractDataListEntry.java new file mode 100644 index 00000000..82c18591 --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/options/AbstractDataListEntry.java @@ -0,0 +1,166 @@ +package de.mrjulsen.crn.client.gui.widgets.options; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.function.Consumer; + +import com.google.common.collect.ImmutableList; + +import de.mrjulsen.crn.CreateRailwaysNavigator; +import de.mrjulsen.crn.client.gui.ModGuiIcons; +import de.mrjulsen.mcdragonlib.client.gui.widgets.DLButton; +import de.mrjulsen.mcdragonlib.client.gui.widgets.DLIconButton; +import de.mrjulsen.mcdragonlib.client.gui.widgets.DLTooltip; +import de.mrjulsen.mcdragonlib.client.gui.widgets.IDragonLibWidget; +import de.mrjulsen.mcdragonlib.client.gui.widgets.WidgetContainer; +import de.mrjulsen.mcdragonlib.client.gui.widgets.DLAbstractImageButton.ButtonType; +import de.mrjulsen.mcdragonlib.client.render.Sprite; +import de.mrjulsen.mcdragonlib.client.render.DynamicGuiRenderer.AreaStyle; +import de.mrjulsen.mcdragonlib.client.util.Graphics; +import de.mrjulsen.mcdragonlib.client.util.GuiAreaDefinition; +import de.mrjulsen.mcdragonlib.util.TextUtils; +import net.minecraft.client.gui.components.Widget; +import net.minecraft.client.gui.components.events.GuiEventListener; +import net.minecraft.client.gui.narration.NarrationElementOutput; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.MutableComponent; + +public abstract class AbstractDataListEntry> extends WidgetContainer { + + protected final DataListContainer parent; + + public final int CONTENT_POS_LEFT = 0; + public final int CONTENT_POS_RIGHT = 0; + public final int CONTENT_SPACING = 3; + public final int TEXT_OFFSET = 3; + + private boolean wasBuild = false; + + private int buttonsXOffset = 0; + private int sectionsXOffset = 0; + private List sections = new ArrayList<>(); + + protected final S data; + private String text; + + private final MutableComponent textDelete = TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".common.delete"); + + public AbstractDataListEntry(DataListContainer parent, int x, int y, int width, S data) { + super(x, y, width, 20); + this.parent = parent; + this.data = data; + } + + public void setText(String text) { + this.text = text; + } + + public String getText() { + return text; + } + + public ImmutableList getSections() { + return ImmutableList.copyOf(sections); + } + + public int getCurrentButtonsXOffset() { + return buttonsXOffset; + } + + public int getCurrentSectionsXOffset() { + return sectionsXOffset; + } + + public W addWidget(W widget) { + if (wasBuild) { + throw new IllegalStateException("Cannot add elements to this widget after finishing creation."); + } + addRenderableWidget(widget); + buttonsXOffset += widget.width(); + return widget; + } + + public DLIconButton addButton(Sprite icon, Component text, DataListEntryContext onClick) { + if (wasBuild) { + throw new IllegalStateException("Cannot add elements to this widget after finishing creation."); + } + DLIconButton btn = addRenderableWidget(new DLIconButton(ButtonType.DEFAULT, AreaStyle.FLAT, icon, x() + width() - DLIconButton.DEFAULT_BUTTON_WIDTH - buttonsXOffset - CONTENT_POS_RIGHT, y() + 1, TextUtils.empty(), + (b) -> { + onClick.run(b, this.parent.getData(), data, (newData) -> newData.ifPresent(a -> this.parent.displayData(a))); + })); + btn.setBackColor(0x00000000); + buttonsXOffset += btn.width(); + DLTooltip tooltip = DLTooltip.of(text).assignedTo(btn); + tooltip.setDynamicOffset(() -> (int)parent.getParentEntry().getParentList().getXScrollOffset(), () -> (int)parent.getParentEntry().getParentList().getYScrollOffset()); + parent.getTooltips().add(tooltip); + return btn; + } + + public DLIconButton addDeleteButton(DataListEntryContext onClick) { + return addButton(ModGuiIcons.DELETE.getAsSprite(16, 16), textDelete, onClick); + } + + protected E createSection(E section) { + if (wasBuild) { + throw new IllegalStateException("Cannot add elements to this widget after finishing creation."); + } + sections.add(section); + sectionsXOffset += section.width + CONTENT_SPACING; + return section; + } + + /** + * Called after creating this entry. Can be used to create edit boxes for editable sections and more. + */ + protected abstract void build(); + public final void buildInternal() { + wasBuild = true; + build(); + } + + @Override + public void renderMainLayer(Graphics graphics, int mouseX, int mouseY, float partialTicks) { + renderWidgetBase(graphics, mouseX, mouseY, partialTicks); + for (E section : sections) { + int xCoord = x() + width() - CONTENT_POS_RIGHT - CONTENT_SPACING - buttonsXOffset - section.xOffset - section.width; + renderSection(graphics, mouseX, mouseY, partialTicks, section, new GuiAreaDefinition(xCoord, y() + 1, section.width, 18)); + } + int remainingWidth = width() - CONTENT_POS_LEFT - CONTENT_POS_RIGHT - CONTENT_SPACING - buttonsXOffset - sectionsXOffset; + int xCoord = x() + CONTENT_POS_LEFT; + renderMainSection(graphics, mouseX, mouseY, partialTicks, text, new GuiAreaDefinition(xCoord, y() + 1, remainingWidth, 18)); + super.renderMainLayer(graphics, mouseX, mouseY, partialTicks); + } + + protected abstract void renderWidgetBase(Graphics graphics, int mouseX, int mouseY, float partialTicks); + protected abstract void renderSection(Graphics graphics, int mouseX, int mouseY, float partialTicks, E section, GuiAreaDefinition area); + protected abstract void renderMainSection(Graphics graphics, int mouseX, int mouseY, float partialTicks, String text, GuiAreaDefinition area); + + @Override + public NarrationPriority narrationPriority() { + return NarrationPriority.HOVERED; + } + + @Override + public void updateNarration(NarrationElementOutput narrationElementOutput) {} + + @Override + public boolean consumeScrolling(double mouseX, double mouseY) { + return false; + } + + @FunctionalInterface + public static interface DataListEntryContext { + void run(B btn, T data, S entry, Consumer> refreshAction); + } + + public static abstract class AbstractDataSectionDefinition { + public final int xOffset; + public final int width; + + public AbstractDataSectionDefinition(int xOffset, int width) { + this.xOffset = xOffset; + this.width = width; + } + } +} diff --git a/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/options/DLOptionsList.java b/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/options/DLOptionsList.java new file mode 100644 index 00000000..26075911 --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/options/DLOptionsList.java @@ -0,0 +1,114 @@ +package de.mrjulsen.crn.client.gui.widgets.options; + +import java.util.function.BiConsumer; +import java.util.function.Function; + +import de.mrjulsen.crn.CreateRailwaysNavigator; +import de.mrjulsen.mcdragonlib.DragonLib; +import de.mrjulsen.mcdragonlib.client.gui.widgets.DLAbstractScrollBar; +import de.mrjulsen.mcdragonlib.client.gui.widgets.IDragonLibWidget; +import de.mrjulsen.mcdragonlib.client.gui.widgets.ScrollableWidgetContainer; +import de.mrjulsen.mcdragonlib.client.gui.widgets.WidgetContainer; +import de.mrjulsen.mcdragonlib.client.util.Graphics; +import de.mrjulsen.mcdragonlib.client.util.GuiUtils; +import de.mrjulsen.mcdragonlib.core.EAlignment; +import de.mrjulsen.mcdragonlib.util.TextUtils; +import net.minecraft.client.gui.components.AbstractWidget; +import net.minecraft.client.gui.components.Widget; +import net.minecraft.client.gui.components.events.GuiEventListener; +import net.minecraft.client.gui.narration.NarrationElementOutput; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.network.chat.Component; + +public class DLOptionsList extends ScrollableWidgetContainer { + + private final DLAbstractScrollBar scrollBar; + private int contentHeight = 0; + + private final Screen parent; + + public DLOptionsList(Screen parent, int x, int y, int width, int height, DLAbstractScrollBar scrollBar) { + super(x, y, width, height); + this.scrollBar = scrollBar; + this.parent = parent; + + scrollBar.setAutoScrollerSize(true); + scrollBar.setScreenSize(height()); + scrollBar.updateMaxScroll(0); + scrollBar.withOnValueChanged((sb) -> setYScrollOffset(sb.getScrollValue())); + scrollBar.setStepSize(10); + } + + public void clearOptions() { + clearWidgets(); + rearrangeContent(); + } + + public int getContentWidth() { + return width() - 20; + } + + public OptionEntry addOption(Function, T> contentContainer, Component text, Component description, BiConsumer, OptionEntryHeader> onHeaderClick, Function onTitleEdited) { + OptionEntry entry = new OptionEntry(parent, this, 0, y(), width() - 20, contentContainer, text, description, x -> rearrangeContent(), onHeaderClick, onTitleEdited); + addRenderableWidget(entry); + return entry; + } + + @Override + public T addRenderableWidget(T guiEventListener) { + T t = super.addRenderableWidget(guiEventListener); + if (t instanceof IDragonLibWidget wgt) { + wgt.set_x(x() + 10); + wgt.set_width(Math.min(width() - 20, wgt.width())); + } else if (t instanceof AbstractWidget wgt) { + wgt.x = x() + 10; + wgt.setWidth(Math.min(width() - 20, wgt.getWidth())); + } + rearrangeContent(); + return t; + } + + public void rearrangeContent() { + contentHeight = 10; + for (GuiEventListener listener : children()) { + if (listener instanceof OptionEntry wgt) { + wgt.set_y(y() + contentHeight); + contentHeight += wgt.height() + (wgt.isExpanded() ? 6 : 3); + } else if (listener instanceof IDragonLibWidget wgt) { + wgt.set_y(y() + contentHeight); + contentHeight += wgt.height() + 3; + } else if (listener instanceof AbstractWidget wgt) { + wgt.y = y() + contentHeight; + contentHeight += wgt.getHeight() + 3; + } + } + contentHeight += 10; + scrollBar.updateMaxScroll(contentHeight); + if (!scrollBar.canScroll()) { + scrollBar.scrollTo(0); + } + } + + @Override + public void renderMainLayer(Graphics graphics, int mouseX, int mouseY, float partialTicks) { + super.renderMainLayer(graphics, mouseX, mouseY, partialTicks); + GuiUtils.fillGradient(graphics, x(), y(), 0, width(), 10, 0x77000000, 0x00000000); + GuiUtils.fillGradient(graphics, x(), y() + height() - 10, 0, width(), 10, 0x00000000, 0x77000000); + if (children().isEmpty()) { + GuiUtils.drawString(graphics, font, x() + width() / 2, y() + height() / 2 - font.lineHeight / 2, TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".empty_list"), DragonLib.NATIVE_BUTTON_FONT_COLOR_DISABLED, EAlignment.CENTER, false); + } + } + + @Override + public NarrationPriority narrationPriority() { + return NarrationPriority.HOVERED; + } + + @Override + public void updateNarration(NarrationElementOutput narrationElementOutput) {} + + @Override + public boolean consumeScrolling(double mouseX, double mouseY) { + return false; + } +} diff --git a/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/options/DataListContainer.java b/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/options/DataListContainer.java new file mode 100644 index 00000000..42e4e0e5 --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/options/DataListContainer.java @@ -0,0 +1,247 @@ +package de.mrjulsen.crn.client.gui.widgets.options; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.function.BiPredicate; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; + +import org.lwjgl.glfw.GLFW; + +import de.mrjulsen.crn.client.gui.CreateDynamicWidgets; +import de.mrjulsen.crn.client.gui.CreateDynamicWidgets.ColorShade; +import de.mrjulsen.mcdragonlib.DragonLib; +import de.mrjulsen.mcdragonlib.client.gui.widgets.DLEditBox; +import de.mrjulsen.mcdragonlib.client.gui.widgets.DLTooltip; +import de.mrjulsen.mcdragonlib.client.gui.widgets.WidgetContainer; +import de.mrjulsen.mcdragonlib.client.util.Graphics; +import de.mrjulsen.mcdragonlib.client.util.GuiUtils; +import de.mrjulsen.mcdragonlib.data.Single.MutableSingle; +import de.mrjulsen.mcdragonlib.util.DLUtils; +import de.mrjulsen.mcdragonlib.util.TextUtils; +import net.minecraft.client.gui.narration.NarrationElementOutput; +import net.minecraft.client.gui.screens.Screen; + +public class DataListContainer extends WidgetContainer { + + private static final int BORDER_WIDTH = 2; + + private final Screen parent; + private final OptionEntry parentEntry; + private T data; + private int entriesHeight; + private int addNewEntryHeight; + private int contentHeight; + + private int paddingLeft; + private int paddingRight; + private int paddingTop; + private int paddingBottom; + private boolean renderBackground = true; + private boolean bordered = true; + + private final Collection tooltips = new ArrayList<>(); + + private final Function> onGetData; + private final BiFunction, String> onCreateEntry; + private final Consumer> onContainerSizeChanged; + private final BiConsumer> createNewEntry; + + private String filterText = ""; + private BiPredicate> filter; + + + + /** + * @param parent The parent screen. + * @param x The x position. + * @param y The y position. + * @param width The width of this widget. + * @param initialData The data which this widget should display and process. + * @param dataIterator The iterator of the contents of the data. + * @param onCreateEntry Called when creating the entries. + * @param createNewEntry Called when creating the "Add New Entry" widget. Pass {@code null} if no "Add New Entry" widget should be created. + * @param onContainerSizeChanged Called when the size of the container changes to adjust the gui surrounding it. + */ + public DataListContainer(OptionEntry parentEntry, int x, int y, int width, T initialData, Function> dataIterator, BiFunction, String> onCreateEntry, BiConsumer> createNewEntry, Consumer> onContainerSizeChanged) { + super(x, y, width, 100); + this.parent = parentEntry.getParentScreen(); + this.parentEntry = parentEntry; + + this.onGetData = dataIterator; + this.onCreateEntry = onCreateEntry; + this.onContainerSizeChanged = onContainerSizeChanged; + this.createNewEntry = createNewEntry; + + displayData(initialData, false); + } + + public void setPadding(int top, int right, int bottom, int left) { + this.paddingTop = top; + this.paddingRight = right; + this.paddingBottom = bottom; + this.paddingLeft = left; + displayData(data, false); + } + + public void setFilter(BiPredicate> filter) { + this.filter = filter; + displayData(data, false); + } + + public int getPaddingLeft() { + return paddingLeft; + } + + public int getPaddingRight() { + return paddingRight; + } + + public int getPaddingTop() { + return paddingTop; + } + + public int getPaddingBottom() { + return paddingBottom; + } + + public boolean isRenderBackground() { + return renderBackground; + } + + public void setRenderBackground(boolean renderBackground) { + this.renderBackground = renderBackground; + } + + public boolean isBordered() { + return bordered; + } + + public void setBordered(boolean bordered) { + this.bordered = bordered; + } + + public int displayData(T data) { + return displayData(data, true); + } + + private int displayData(T data, boolean notifySizeChanged) { + clearWidgets(); + tooltips.clear(); + this.data = data; + contentHeight = paddingTop + BORDER_WIDTH; + entriesHeight = 0; + addNewEntryHeight = 0; + + MutableSingle searchBox = new MutableSingle<>(null); + DLUtils.doIfNotNull(filter, x -> { + searchBox.setFirst(addRenderableWidget(new DLEditBox(font, x() + BORDER_WIDTH + paddingLeft + 1, y() + 2 + contentHeight, width() - BORDER_WIDTH * 2 - paddingLeft - paddingRight - 2, 14, TextUtils.empty()) { + @Override + public boolean keyPressed(int code, int p_keyPressed_2_, int p_keyPressed_3_) { + if (code == GLFW.GLFW_KEY_ENTER) { + filterText = getValue(); + displayData(data, true); + return true; + } + return super.keyPressed(code, p_keyPressed_2_, p_keyPressed_3_); + } + })); + searchBox.getFirst().withHint(DragonLib.TEXT_SEARCH); + searchBox.getFirst().setValue(filterText); + int h = searchBox.getFirst().height() + 4; + contentHeight += h; + entriesHeight += h; + addNewEntryHeight += h; + }); + + Iterator content = onGetData.apply(data); + while (content.hasNext()) { + final S current = content.next(); + if (filter != null) { + if (!filter.test(current, () -> searchBox.getFirst() == null ? "" : searchBox.getFirst().getValue())) { + continue; + } + } + SimpleDataListEntry entry = addRenderableWidget(new SimpleDataListEntry<>(this, x() + BORDER_WIDTH + paddingLeft, y() + contentHeight, width() - BORDER_WIDTH * 2 - paddingLeft - paddingRight, current)); + entry.setText(onCreateEntry.apply(current, entry)); + entry.buildInternal(); + contentHeight += entry.height(); + entriesHeight += entry.height(); + } + + DLUtils.doIfNotNull(createNewEntry, x -> { + SimpleDataListNewEntry entry = addRenderableWidget(new SimpleDataListNewEntry<>(this, x() + BORDER_WIDTH + paddingLeft, y() + contentHeight, width() - BORDER_WIDTH * 2 - paddingLeft - paddingRight)); + createNewEntry.accept(data, entry); + entry.buildInternal(); + contentHeight += entry.height(); + addNewEntryHeight = entry.height(); + }); + + contentHeight += paddingBottom + BORDER_WIDTH; + set_height(contentHeight); + + if (notifySizeChanged) { + onContainerSizeChanged.accept(this); + } + return height(); + } + + public Screen getParentScreen() { + return parent; + } + + public OptionEntry getParentEntry() { + return parentEntry; + } + + public T getData() { + return data; + } + + @Override + public void renderMainLayer(Graphics graphics, int mouseX, int mouseY, float partialTicks) { + if (isRenderBackground()) { + if (isBordered()) { + if (createNewEntry == null) { + CreateDynamicWidgets.renderSingleShadeWidget(graphics, x(), y(), width(), height(), ColorShade.LIGHT); + } else { + CreateDynamicWidgets.renderDuoShadeWidget(graphics, x(), y(), width(), BORDER_WIDTH + paddingTop + entriesHeight, ColorShade.LIGHT, BORDER_WIDTH + paddingBottom + addNewEntryHeight, ColorShade.DARK); + } + } else { + GuiUtils.fill(graphics, x(), y(), width(), height(), ColorShade.LIGHT.getColor()); + if (createNewEntry != null) { + GuiUtils.fill(graphics, x(), y() + BORDER_WIDTH + paddingTop + entriesHeight, width(), BORDER_WIDTH + paddingBottom + addNewEntryHeight, ColorShade.DARK.getColor()); + } + } + } + super.renderMainLayer(graphics, mouseX, mouseY, partialTicks); + } + + public Collection getTooltips() { + return tooltips; + } + + @Override + public void renderFrontLayer(Graphics graphics, int mouseX, int mouseY, float partialTicks) { + super.renderFrontLayer(graphics, mouseX, mouseY, partialTicks); + tooltips.stream().forEach(x -> x.render(parent, graphics, mouseX, mouseY)); + + } + + @Override + public NarrationPriority narrationPriority() { + return NarrationPriority.HOVERED; + } + + @Override + public void updateNarration(NarrationElementOutput narrationElementOutput) {} + + @Override + public boolean consumeScrolling(double mouseX, double mouseY) { + return false; + } +} diff --git a/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/options/NewEntryWidget.java b/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/options/NewEntryWidget.java new file mode 100644 index 00000000..8412ca17 --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/options/NewEntryWidget.java @@ -0,0 +1,132 @@ +package de.mrjulsen.crn.client.gui.widgets.options; + +import java.util.function.Function; +import java.util.List; +import java.util.function.Supplier; + +import de.mrjulsen.crn.CreateRailwaysNavigator; +import de.mrjulsen.crn.client.gui.CreateDynamicWidgets; +import de.mrjulsen.crn.client.gui.CreateDynamicWidgets.ColorShade; +import de.mrjulsen.crn.data.StationTag; +import de.mrjulsen.crn.client.gui.ModGuiIcons; +import de.mrjulsen.mcdragonlib.DragonLib; +import de.mrjulsen.mcdragonlib.client.gui.widgets.DLAbstractImageButton.ButtonType; +import de.mrjulsen.mcdragonlib.client.gui.widgets.DLEditBox; +import de.mrjulsen.mcdragonlib.client.gui.widgets.DLIconButton; +import de.mrjulsen.mcdragonlib.client.gui.widgets.WidgetContainer; +import de.mrjulsen.mcdragonlib.client.render.DynamicGuiRenderer.AreaStyle; +import de.mrjulsen.mcdragonlib.client.util.DLWidgetsCollection; +import de.mrjulsen.mcdragonlib.client.util.Graphics; +import de.mrjulsen.mcdragonlib.client.util.GuiAreaDefinition; +import de.mrjulsen.mcdragonlib.client.util.GuiUtils; +import de.mrjulsen.mcdragonlib.core.EAlignment; +import de.mrjulsen.mcdragonlib.data.Pair; +import de.mrjulsen.mcdragonlib.util.DLUtils; +import de.mrjulsen.mcdragonlib.util.TextUtils; +import net.minecraft.client.gui.narration.NarrationElementOutput; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.network.chat.MutableComponent; + +public class NewEntryWidget extends WidgetContainer { + + private final DLEditBox nameBox; + private final DLIconButton addBtn; + private final DLWidgetsCollection collection = new DLWidgetsCollection(); + private final Screen parent; + private final Supplier> scrollOffset; + + private final MutableComponent textAdd = TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".new_entry.add"); + private final MutableComponent textNew = TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".new_entry.new").append(" "); + + private boolean expanded = false; + + public NewEntryWidget(Screen parent, Supplier> scrollOffset, Function onAcceptNewName, int x, int y, int width) { + super(x, y, width, 26); + this.parent = parent; + this.scrollOffset = scrollOffset; + + int w = width() - 12 - DLIconButton.DEFAULT_BUTTON_WIDTH * 2 - font.width(textNew); + nameBox = addRenderableWidget(new DLEditBox(font, x() + 10 + font.width(textNew), y() + 9, w - 8, font.lineHeight, TextUtils.empty())); + nameBox.setBordered(false); + nameBox.setMaxLength(StationTag.MAX_NAME_LENGTH); + + addBtn = addRenderableWidget(new DLIconButton(ButtonType.DEFAULT, AreaStyle.FLAT, ModGuiIcons.ADD.getAsSprite(16, 16), x(), y() + height() / 2 - DLIconButton.DEFAULT_BUTTON_WIDTH / 2, TextUtils.empty(), + (b) -> { + expanded = true; + b.set_visible(false); + collection.setVisible(true); + nameBox.setValue(""); + }) { + @Override + public void renderMainLayer(Graphics graphics, int mouseX, int mouseY, float partialTicks) { + CreateDynamicWidgets.renderSingleShadeWidget(graphics, x(), y(), width(), height(), ColorShade.DARK); + super.renderMainLayer(graphics, mouseX, mouseY, partialTicks); + } + + }); + addBtn.setBackColor(0x00000000); + + DLIconButton acceptButton = addRenderableWidget(new DLIconButton(ButtonType.DEFAULT, AreaStyle.FLAT, ModGuiIcons.CHECKMARK.getAsSprite(16, 16), x() + width() - DLIconButton.DEFAULT_BUTTON_WIDTH * 2 - 5, y() + 4, TextUtils.empty(), + (b) -> { + if (nameBox.getValue() == null || nameBox.getValue().isBlank()) { + return; + } + DLUtils.doIfNotNull(onAcceptNewName, a -> { + if (a.apply(nameBox.getValue())) { + collapse(); + } + }); + })); + acceptButton.setBackColor(0x00000000); + + DLIconButton denyButton = addRenderableWidget(new DLIconButton(ButtonType.DEFAULT, AreaStyle.FLAT, ModGuiIcons.X.getAsSprite(16, 16), x() + width() - DLIconButton.DEFAULT_BUTTON_WIDTH - 4, y() + 4, TextUtils.empty(), + (b) -> { + collapse(); + })); + denyButton.setBackColor(0x00000000); + + collection.add(nameBox); + collection.add(acceptButton); + collection.add(denyButton); + collection.setVisible(false); + } + + private void collapse() { + expanded = false; + collection.setVisible(false); + addBtn.set_visible(true); + } + + @Override + public void renderMainLayer(Graphics graphics, int mouseX, int mouseY, float partialTicks) { + if (expanded) { + CreateDynamicWidgets.renderSingleShadeWidget(graphics, x(), y(), width(), height(), ColorShade.DARK); + int w = width() - 12 - DLIconButton.DEFAULT_BUTTON_WIDTH * 2 - font.width(textNew); + CreateDynamicWidgets.renderTextBox(graphics, x() + 5 + font.width(textNew), y() + 4, w); + GuiUtils.drawString(graphics, font, x() + 5, y() + height() / 2 - font.lineHeight / 2, textNew, DragonLib.NATIVE_BUTTON_FONT_COLOR_ACTIVE, EAlignment.LEFT, false); + } + + super.renderMainLayer(graphics, mouseX, mouseY, partialTicks); + } + + @Override + public void renderFrontLayer(Graphics graphics, int mouseX, int mouseY, float partialTicks) { + super.renderFrontLayer(graphics, mouseX, mouseY, partialTicks); + if (addBtn.visible() && addBtn.isMouseSelected()) { + GuiUtils.renderTooltipAt(parent, GuiAreaDefinition.of(parent), List.of(textAdd), parent.width / 4, graphics, addBtn.x() + addBtn.width() + 5 + scrollOffset.get().getFirst().intValue(), addBtn.y() + 1 + scrollOffset.get().getSecond().intValue(), mouseX, mouseY, 0, 0); + } + } + + @Override + public NarrationPriority narrationPriority() { + return NarrationPriority.HOVERED; + } + + @Override + public void updateNarration(NarrationElementOutput narrationElementOutput) {} + + @Override + public boolean consumeScrolling(double mouseX, double mouseY) { + return false; + } +} diff --git a/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/options/OptionEntry.java b/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/options/OptionEntry.java new file mode 100644 index 00000000..8af99032 --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/options/OptionEntry.java @@ -0,0 +1,245 @@ +package de.mrjulsen.crn.client.gui.widgets.options; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Function; + +import com.mojang.blaze3d.systems.RenderSystem; + +import java.util.List; + +import de.mrjulsen.crn.client.gui.Animator; +import de.mrjulsen.crn.client.gui.CreateDynamicWidgets; +import de.mrjulsen.crn.client.gui.CreateDynamicWidgets.ColorShade; +import de.mrjulsen.crn.data.StationTag; +import de.mrjulsen.mcdragonlib.client.gui.widgets.DLButton; +import de.mrjulsen.mcdragonlib.client.gui.widgets.DLEditBox; +import de.mrjulsen.mcdragonlib.client.gui.widgets.DLIconButton; +import de.mrjulsen.mcdragonlib.client.gui.widgets.DLTooltip; +import de.mrjulsen.mcdragonlib.client.gui.widgets.WidgetContainer; +import de.mrjulsen.mcdragonlib.client.gui.widgets.DLAbstractImageButton.ButtonType; +import de.mrjulsen.mcdragonlib.client.render.Sprite; +import de.mrjulsen.mcdragonlib.client.render.DynamicGuiRenderer.AreaStyle; +import de.mrjulsen.mcdragonlib.client.util.Graphics; +import de.mrjulsen.mcdragonlib.client.util.GuiAreaDefinition; +import de.mrjulsen.mcdragonlib.client.util.GuiUtils; +import de.mrjulsen.mcdragonlib.util.DLUtils; +import de.mrjulsen.mcdragonlib.util.TextUtils; +import net.minecraft.client.gui.narration.NarrationElementOutput; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.FormattedText; +import net.minecraft.network.chat.Style; + +public class OptionEntry extends WidgetContainer { + + public static void expandOrCollapse(OptionEntry entry) { + if (entry.isExpanded()) + entry.collapse(); + else + entry.expand(); + } + + private int btnX = 0; + private final Collection additionalButtons = new ArrayList<>(); + private final Collection tooltips = new ArrayList<>(); + private List descriptionTooltips; + + private final int initialHeight = OptionEntryHeader.DEFAULT_HEIGHT; + private final Component description; + private Component text; + + private final Screen parent; + private final DLOptionsList parentList; + private final T contentContainer; + private final Consumer> onSizeChanged; + private final OptionEntryHeader header; + private DLEditBox editBox; + private final Animator animator = addRenderableOnly(new Animator()); + + private boolean expanded; + + public OptionEntry(Screen parent, DLOptionsList parentList, int x, int y, int width, Function, T> contentContainer, Component text, Component description, Consumer> onSizeChanged, BiConsumer, OptionEntryHeader> onHeaderClick, Function onTitleEdited) { + super(x, y, width, OptionEntryHeader.DEFAULT_HEIGHT); + this.parent = parent; + this.parentList = parentList; + this.text = text; + this.contentContainer = contentContainer != null ? contentContainer.apply(this) : null; + this.description = description; + this.onSizeChanged = onSizeChanged; + this.setTooltip(font.getSplitter().splitLines(description, width, Style.EMPTY)); + + header = addRenderableWidget(new OptionEntryHeader(this, x(), y(), width(), text, (b) -> onHeaderClick.accept(this, b))); + DLUtils.doIfNotNull(this.contentContainer, a -> { + addRenderableWidget(a); + a.set_visible(false); + }); + + if (onTitleEdited != null) { + editBox = addRenderableWidget(new DLEditBox(font, x() + 5, y() + 6, width() - 5 - 25, font.lineHeight, text) { + @Override + public void setMouseSelected(boolean selected) { + super.setMouseSelected(selected); + if (selected) { + header.setMouseSelected(true); + } + } + }); + editBox.setValue(text.getString()); + editBox.setBordered(false); + editBox.setMaxLength(StationTag.MAX_NAME_LENGTH); + editBox.set_visible(false); + editBox.withOnFocusChanged((box, focus) -> { + if (!focus) { + if (onTitleEdited.apply(box.getValue())) { + this.text = TextUtils.text(box.getValue()); + header.setMessage(this.text); + } + } + }); + + } + + animator.start(4, null, null, null); + } + + public void addAdditionalButton(Sprite icon, Component text, BiConsumer, DLIconButton> onClick) { + DLIconButton btn = new DLIconButton(ButtonType.DEFAULT, AreaStyle.FLAT, icon, x() + width() - 2 - 18 - btnX - 16, y() + 2, 16, OptionEntryHeader.DEFAULT_HEIGHT - 4, TextUtils.empty(), x -> onClick.accept(this, x)) { + @Override + public void setMouseSelected(boolean selected) { + super.setMouseSelected(selected); + if (selected) { + header.setMouseSelected(true); + } + } + }; + btn.setBackColor(0x00000000); + DLTooltip tooltip = DLTooltip.of(text).assignedTo(btn); + tooltip.setDynamicOffset(() -> (int)parentList.getXScrollOffset(), () -> (int)parentList.getYScrollOffset()); + tooltips.add(tooltip); + btnX += btn.width(); + addRenderableWidget(btn); + additionalButtons.add(btn); + } + + public OptionEntry(Screen parent, DLOptionsList parentList, int x, int y, int width, Function, T> contentContainer, Component text, Component description, Consumer> onExpandedChanged, BiConsumer, OptionEntryHeader> onHeaderClick) { + this(parent, parentList, x, y, width, contentContainer, text, description, onExpandedChanged, onHeaderClick, null); + } + + public GuiAreaDefinition getContentSpace() { + return new GuiAreaDefinition(x() + 3, y() + initialHeight, width() - 6, height() - initialHeight - 2); + } + + public void collapse() { + expanded = false; + set_height(initialHeight); + DLUtils.doIfNotNull(editBox, a -> { + a.setVisible(false); + a.setWidth(width() - 5 - 5 - 18 - btnX); + }); + DLUtils.doIfNotNull(contentContainer, a -> a.set_visible(false)); + onSizeChanged.accept(this); + } + + public void expand() { + expanded = true; + DLUtils.doIfNotNull(contentContainer, a -> { + set_height(initialHeight + a.height() + 2); + a.set_visible(true); + }); + DLUtils.doIfNotNull(editBox, a -> a.setVisible(true)); + onSizeChanged.accept(this); + } + + public void notifyContentSizeChanged() { + DLUtils.doIfNotNull(contentContainer, a -> { + set_height(initialHeight + a.height() + 2); + }); + onSizeChanged.accept(this); + } + + public int getInitialHeight() { + return initialHeight; + } + + public Component getText() { + return text; + } + + public Component getDescription() { + return description; + } + + public boolean isExpanded() { + return expanded; + } + + public T getContentContainer() { + return contentContainer; + } + + public void setTooltip(List tooltip) { + this.descriptionTooltips = tooltip; + } + + public List getTooltips() { + return descriptionTooltips; + } + + public DLOptionsList getParentList() { + return parentList; + } + + public Screen getParentScreen() { + return parent; + } + + @Override + public NarrationPriority narrationPriority() { + return NarrationPriority.HOVERED; + } + + @Override + public void updateNarration(NarrationElementOutput narrationElementOutput) {} + + @Override + public boolean consumeScrolling(double mouseX, double mouseY) { + return false; + } + + @Override + public void renderMainLayer(Graphics graphics, int mouseX, int mouseY, float partialTicks) { + graphics.poseStack().pushPose(); + RenderSystem.enableBlend(); + RenderSystem.defaultBlendFunc(); + RenderSystem.enableDepthTest(); + + if (animator.isRunning()) { + graphics.poseStack().translate(-animator.getTotalTicks() * 5 + animator.getCurrentTicksSmooth() * 5, 0, 0); + GuiUtils.setTint(1, 1, 1, animator.getPercentage()); + } + + CreateDynamicWidgets.renderSingleShadeWidget(graphics, x() + 1, y(), width() - 2, height(), ColorShade.LIGHT); + super.renderMainLayer(graphics, mouseX, mouseY, partialTicks); + DLUtils.doIfNotNull(editBox, a -> { + if (a.visible()) { + CreateDynamicWidgets.renderTextBox(graphics, x() + 1, y() + 1, width() - 2 - 2 - 20 - btnX); + //GuiUtils.fill(graphics, x() + 2, y() + 2, width() - 2 - 2 - 18 - btnX, OptionEntryHeader.DEFAULT_HEIGHT - 4, 0xFF000000); + editBox.render(graphics.poseStack(), mouseX, mouseY, partialTicks); + } + }); + if (isExpanded() && contentContainer != null) { + GuiUtils.fillGradient(graphics, x() + 3, y() + 20, 0, width() - 6, 10, 0x77000000, 0x00000000); + } + graphics.poseStack().popPose(); + } + + @Override + public void renderFrontLayer(Graphics graphics, int mouseX, int mouseY, float partialTicks) { + super.renderFrontLayer(graphics, mouseX, mouseY, partialTicks); + tooltips.stream().forEach(x -> x.render(parent, graphics, mouseX, mouseY)); + + } +} diff --git a/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/options/OptionEntryHeader.java b/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/options/OptionEntryHeader.java new file mode 100644 index 00000000..89bb6d0a --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/options/OptionEntryHeader.java @@ -0,0 +1,76 @@ +package de.mrjulsen.crn.client.gui.widgets.options; + +import java.util.function.Consumer; + +import de.mrjulsen.crn.client.gui.CreateDynamicWidgets; +import de.mrjulsen.crn.client.gui.CreateDynamicWidgets.ColorShade; +import de.mrjulsen.mcdragonlib.DragonLib; +import de.mrjulsen.mcdragonlib.client.gui.widgets.DLButton; +import de.mrjulsen.mcdragonlib.client.render.DynamicGuiRenderer; +import de.mrjulsen.mcdragonlib.client.render.GuiIcons; +import de.mrjulsen.mcdragonlib.client.render.DynamicGuiRenderer.AreaStyle; +import de.mrjulsen.mcdragonlib.client.render.DynamicGuiRenderer.ButtonState; +import de.mrjulsen.mcdragonlib.client.util.Graphics; +import de.mrjulsen.mcdragonlib.client.util.GuiUtils; +import de.mrjulsen.mcdragonlib.core.EAlignment; +import de.mrjulsen.mcdragonlib.util.DLUtils; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.FormattedText; + +public class OptionEntryHeader extends DLButton { + +public static final int DEFAULT_HEIGHT = 20; + + private final OptionEntry parent; + + public OptionEntryHeader(OptionEntry parent, int pX, int pY, int pWidth, Component pMessage, Consumer pOnPress) { + super(pX, pY, pWidth, DEFAULT_HEIGHT, pMessage, pOnPress); + this.parent = parent; + setRenderStyle(AreaStyle.FLAT); + setBackColor(0x00000000); + } + + @Override + public void renderMainLayer(Graphics graphics, int mouseX, int mouseY, float partialTick) { + CreateDynamicWidgets.renderSingleShadeWidget(graphics, x(), y(), width(), height(), ColorShade.LIGHT); + CreateDynamicWidgets.renderSingleShadeWidget(graphics, x(), y(), width(), 20, ColorShade.DARK); + DynamicGuiRenderer.renderArea(graphics, x(), y(), width, height, getBackColor(), style, isActive() ? (isFocused() || isMouseSelected() ? ButtonState.SELECTED : ButtonState.BUTTON) : ButtonState.DISABLED); + int j = active ? (isMouseSelected() ? DragonLib.NATIVE_BUTTON_FONT_COLOR_HIGHLIGHT : DragonLib.NATIVE_BUTTON_FONT_COLOR_ACTIVE) : DragonLib.NATIVE_BUTTON_FONT_COLOR_DISABLED; + GuiUtils.drawString(graphics, font, x() + 5, this.y() + (20 - 8) / 2, this.getMessage(), j, EAlignment.LEFT, false); + if (parent.isExpanded()) { + GuiIcons.ARROW_UP.render(graphics, x() + width() - 2 - GuiIcons.ICON_SIZE, y() + 2); + } else { + if (parent.getContentContainer() == null) { + GuiIcons.ARROW_RIGHT.render(graphics, x() + width() - 2 - GuiIcons.ICON_SIZE, y() + 2); + } else { + GuiIcons.ARROW_DOWN.render(graphics, x() + width() - 2 - GuiIcons.ICON_SIZE, y() + 2); + } + } + } + + @Override + public void renderFrontLayer(Graphics graphics, int mouseX, int mouseY, float partialTicks) { + super.renderFrontLayer(graphics, mouseX, mouseY, partialTicks); + + if (isMouseSelected()) { + renderDescriptionTooltip(graphics, mouseX, mouseY, partialTicks); + } + } + + protected void renderDescriptionTooltip(Graphics graphics, int mouseX, int mouseY, float partialTicks) { + DLUtils.doIfNotNull(parent.getTooltips(), t -> { + final float scale = 0.75f; + graphics.poseStack().pushPose(); + graphics.poseStack().translate(mouseX, mouseY, 0); + graphics.poseStack().scale(scale, scale, 1); + int maxLineWidth = t.stream().mapToInt(x -> font.width(x)).max().orElse(0); + GuiUtils.fill(graphics, 8, 12, maxLineWidth + 6, (font.lineHeight + 2) * t.size() + 4, 0xAA000000); + for (int i = 0; i < t.size(); i++) { + FormattedText line = t.get(i); + GuiUtils.drawString(graphics, font, 12, 16 + (font.lineHeight + 2) * i, line, DragonLib.NATIVE_BUTTON_FONT_COLOR_ACTIVE, EAlignment.LEFT, false); + } + graphics.poseStack().popPose(); + }); + } + +} diff --git a/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/options/SimpleDataListEntry.java b/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/options/SimpleDataListEntry.java new file mode 100644 index 00000000..2801d8a8 --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/options/SimpleDataListEntry.java @@ -0,0 +1,113 @@ +package de.mrjulsen.crn.client.gui.widgets.options; + +import java.util.Optional; +import java.util.function.Consumer; +import java.util.function.Function; + +import de.mrjulsen.crn.client.gui.CreateDynamicWidgets; +import de.mrjulsen.crn.client.gui.widgets.options.AbstractDataListEntry.AbstractDataSectionDefinition; +import de.mrjulsen.mcdragonlib.DragonLib; +import de.mrjulsen.mcdragonlib.client.gui.widgets.DLButton; +import de.mrjulsen.mcdragonlib.client.gui.widgets.DLEditBox; +import de.mrjulsen.mcdragonlib.client.render.DynamicGuiRenderer.AreaStyle; +import de.mrjulsen.mcdragonlib.client.util.Graphics; +import de.mrjulsen.mcdragonlib.client.util.GuiAreaDefinition; +import de.mrjulsen.mcdragonlib.client.util.GuiUtils; +import de.mrjulsen.mcdragonlib.core.EAlignment; +import de.mrjulsen.mcdragonlib.util.TextUtils; + +public class SimpleDataListEntry extends AbstractDataListEntry> { + + public SimpleDataListEntry(DataListContainer parent, int x, int y, int width, S data) { + super(parent, x, y, width, data); + } + + /** + * Adds a new data section. + * @param width The width of the sections + * @param displayName The display text of the section. + * @param onEdit Called when editing this value. {@code null} if this value should not be editable. + */ + public void addDataSection(int width, Function displayName, EAlignment alignment, DataListEntryEditContext onEdit) { + createSection(new DisplayableDataSectionDefinition<>(getCurrentSectionsXOffset(), width, displayName.apply(data), alignment, onEdit)); + } + + @Override + protected void build() { + for (DisplayableDataSectionDefinition section : getSections()) { + if (section.onEdit == null) { + continue; + } + + final DisplayableDataSectionDefinition dataSection = section; + int xCoord = x() + width() - CONTENT_POS_RIGHT - CONTENT_SPACING - getCurrentButtonsXOffset() - section.xOffset - section.width; + DLEditBox modifyPlatformInput = addRenderableWidget(new DLEditBox(font, xCoord + 1, y() + 2, section.width - 2, height() - 4, TextUtils.empty())); + DLButton modifyPlatformBtn = addRenderableWidget(new DLButton(xCoord, y() + 1, section.width, height() - 2, TextUtils.empty(), + (b) -> { + b.set_visible(false); + modifyPlatformInput.setValue(dataSection.displayName); + modifyPlatformInput.set_visible(true); + })); + modifyPlatformBtn.setRenderStyle(AreaStyle.FLAT); + modifyPlatformBtn.setBackColor(0x00000000); + + modifyPlatformInput.setValue(""); + modifyPlatformInput.set_visible(false); + modifyPlatformInput.withOnFocusChanged((box, focus) -> { + if (box.visible() && !focus) { + dataSection.onEdit.run(parent.getData(), data, box.getValue(), (newData) -> newData.ifPresent(a -> this.parent.displayData(a))); + modifyPlatformInput.setValue(""); + modifyPlatformInput.set_visible(false); + modifyPlatformBtn.set_visible(true); + } + }); + } + } + + @Override + protected void renderWidgetBase(Graphics graphics, int mouseX, int mouseY, float partialTicks) { + } + + @Override + protected void renderSection(Graphics graphics, int mouseX, int mouseY, float partialTicks, DisplayableDataSectionDefinition section, GuiAreaDefinition area) { + CreateDynamicWidgets.renderTextSlotOverlay(graphics, area.getX(), area.getY(), area.getWidth(), area.getHeight()); + switch (section.alignment) { + case RIGHT: + GuiUtils.drawString(graphics, font, area.getX() + area.getWidth() - TEXT_OFFSET, area.getY() + area.getHeight() / 2 - font.lineHeight / 2, GuiUtils.ellipsisString(font, TextUtils.text(section.displayName), area.getWidth() - TEXT_OFFSET * 2), DragonLib.NATIVE_BUTTON_FONT_COLOR_ACTIVE, EAlignment.RIGHT, false); + break; + case CENTER: + GuiUtils.drawString(graphics, font, area.getX() + TEXT_OFFSET + (area.getWidth() - TEXT_OFFSET * 2) / 2, area.getY() + area.getHeight() / 2 - font.lineHeight / 2, GuiUtils.ellipsisString(font, TextUtils.text(section.displayName), area.getWidth() - TEXT_OFFSET * 2), DragonLib.NATIVE_BUTTON_FONT_COLOR_ACTIVE, EAlignment.CENTER, false); + break; + default: + case LEFT: + GuiUtils.drawString(graphics, font, area.getX() + TEXT_OFFSET, area.getY() + area.getHeight() / 2 - font.lineHeight / 2, GuiUtils.ellipsisString(font, TextUtils.text(section.displayName), area.getWidth() - TEXT_OFFSET * 2), DragonLib.NATIVE_BUTTON_FONT_COLOR_ACTIVE, EAlignment.LEFT, false); + break; + } + } + + @Override + protected void renderMainSection(Graphics graphics, int mouseX, int mouseY, float partialTicks, String text, GuiAreaDefinition area) { + CreateDynamicWidgets.renderTextSlotOverlay(graphics, area.getX(), area.getY(), area.getWidth(), area.getHeight()); + GuiUtils.drawString(graphics, font, area.getX() + TEXT_OFFSET, area.getY() + area.getHeight() / 2 - font.lineHeight / 2, GuiUtils.ellipsisString(font, TextUtils.text(text), area.getWidth() - TEXT_OFFSET * 2), DragonLib.NATIVE_BUTTON_FONT_COLOR_ACTIVE, EAlignment.LEFT, false); + } + + + + @FunctionalInterface + public static interface DataListEntryEditContext { + void run(T data, S entry, String newValue, Consumer> refreshAction); + } + + public static class DisplayableDataSectionDefinition extends AbstractDataSectionDefinition { + private final String displayName; + private final EAlignment alignment; + private final DataListEntryEditContext onEdit; + + public DisplayableDataSectionDefinition(int xOffset, int width, String displayName, EAlignment alignment, DataListEntryEditContext onEdit) { + super(xOffset, width); + this.displayName = displayName; + this.alignment = alignment; + this.onEdit = onEdit; + } + } +} diff --git a/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/options/SimpleDataListNewEntry.java b/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/options/SimpleDataListNewEntry.java new file mode 100644 index 00000000..27f525b5 --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/options/SimpleDataListNewEntry.java @@ -0,0 +1,128 @@ +package de.mrjulsen.crn.client.gui.widgets.options; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; + +import com.google.common.collect.ImmutableMap; + +import de.mrjulsen.crn.client.gui.CreateDynamicWidgets; +import de.mrjulsen.crn.client.gui.widgets.options.AbstractDataListEntry.AbstractDataSectionDefinition; +import de.mrjulsen.mcdragonlib.client.gui.widgets.DLButton; +import de.mrjulsen.mcdragonlib.client.gui.widgets.DLEditBox; +import de.mrjulsen.mcdragonlib.client.gui.widgets.DLIconButton; +import de.mrjulsen.mcdragonlib.client.gui.widgets.DLTooltip; +import de.mrjulsen.mcdragonlib.client.render.Sprite; +import de.mrjulsen.mcdragonlib.client.util.Graphics; +import de.mrjulsen.mcdragonlib.client.util.GuiAreaDefinition; +import de.mrjulsen.mcdragonlib.util.DLUtils; +import de.mrjulsen.mcdragonlib.util.TextUtils; +import net.minecraft.client.gui.components.EditBox; +import net.minecraft.network.chat.Component; + +public class SimpleDataListNewEntry extends AbstractDataListEntry> { + + public static final String MAIN_INPUT_KEY = "name"; + + public Map> values = new HashMap<>(); + + private Consumer onCreateMainEditBox; + private Function onSetNameEditBoxTooltip; + + public SimpleDataListNewEntry(DataListContainer parent, int x, int y, int width) { + super(parent, x, y, width, null); + } + + /** + * Adds a new data section. + * @param width The width of the sections + * @param displayName The display text of the section. + * @param onEdit Called when editing this value. {@code null} if this value should not be editable. + */ + public void addDataSection(int width, String key, Component text, Consumer onCreateEditBox) { + createSection(new InputDataSectionDefinition<>(getCurrentSectionsXOffset(), width, key, text, onCreateEditBox)); + } + + public void addAddButton(Sprite icon, Component description, DataListAddNewEntryContext onClick) { + addButton(icon, description, + (btn, data, entry, refreshAction) -> { + if (onClick.run(btn, parent.getData(), ImmutableMap.copyOf(values), refreshAction)) { + children().stream().filter(x -> x instanceof EditBox).map(x -> (EditBox)x).forEach(x -> x.setValue("")); + } + }); + } + + public void editNameEditBox(Consumer onCreateMainEditBox) { + this.onCreateMainEditBox = onCreateMainEditBox; + } + + public void setNameEditBoxTooltip(Function onSetNameEditBoxTooltip) { + this.onSetNameEditBoxTooltip = onSetNameEditBoxTooltip; + } + + @Override + protected void build() { + values.clear(); + for (InputDataSectionDefinition section : getSections()) { + int xCoord = x() + width() - CONTENT_POS_RIGHT - CONTENT_SPACING - getCurrentButtonsXOffset() - section.xOffset - section.width; + DLEditBox inputBox = addRenderableWidget(new DLEditBox(font, xCoord + 4, y() + 5, section.width - 8, height() - 10, TextUtils.empty())); + inputBox.setValue(""); + inputBox.setBordered(false); + DLUtils.doIfNotNull(section.onCreateEditBox, x -> x.accept(inputBox)); + values.put(section.key, () -> inputBox.getValue()); + DLTooltip tooltip = DLTooltip.of(section.text).assignedTo(inputBox); + tooltip.setDynamicOffset(() -> (int)parent.getParentEntry().getParentList().getXScrollOffset(), () -> (int)parent.getParentEntry().getParentList().getYScrollOffset()); + parent.getTooltips().add(tooltip); + } + + int remainingWidth = width() - CONTENT_POS_LEFT - CONTENT_POS_RIGHT - CONTENT_SPACING - getCurrentButtonsXOffset() - getCurrentSectionsXOffset(); + int xCoord = x() + CONTENT_POS_LEFT; + DLEditBox inputBox = addRenderableWidget(new DLEditBox(font, xCoord + 4, y() + 5, remainingWidth - 8, height() - 10, TextUtils.empty())); + inputBox.setValue(""); + inputBox.setBordered(false); + DLUtils.doIfNotNull(onCreateMainEditBox, x -> x.accept(inputBox)); + DLUtils.doIfNotNull(onSetNameEditBoxTooltip, x -> { + Component text = x.apply(inputBox); + DLTooltip tooltip = DLTooltip.of(text).assignedTo(inputBox); + tooltip.setDynamicOffset(() -> (int)parent.getParentEntry().getParentList().getXScrollOffset(), () -> (int)parent.getParentEntry().getParentList().getYScrollOffset()); + parent.getTooltips().add(tooltip); + }); + values.put(MAIN_INPUT_KEY, () -> inputBox.getValue()); + } + + @Override + protected void renderWidgetBase(Graphics graphics, int mouseX, int mouseY, float partialTicks) { + } + + @Override + protected void renderSection(Graphics graphics, int mouseX, int mouseY, float partialTicks, InputDataSectionDefinition section, GuiAreaDefinition area) { + CreateDynamicWidgets.renderTextBox(graphics, area.getX(), area.getY(), area.getWidth()); + } + + @Override + protected void renderMainSection(Graphics graphics, int mouseX, int mouseY, float partialTicks, String text, GuiAreaDefinition area) { + CreateDynamicWidgets.renderTextBox(graphics, area.getX(), area.getY(), area.getWidth()); + } + + + @FunctionalInterface + public static interface DataListAddNewEntryContext { + boolean run(B btn, T data, ImmutableMap> inputValues, Consumer> refreshAction); + } + + public static class InputDataSectionDefinition extends AbstractDataSectionDefinition { + private final String key; + private final Component text; + private final Consumer onCreateEditBox; + + public InputDataSectionDefinition(int xOffset, int width, String key, Component text, Consumer onCreateEditBox) { + super(xOffset, width); + this.key = key; + this.text = text; + this.onCreateEditBox = onCreateEditBox; + } + } +} diff --git a/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/routedetails/RouteDetailsTransferWidget.java b/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/routedetails/RouteDetailsTransferWidget.java new file mode 100644 index 00000000..f9fb08a7 --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/routedetails/RouteDetailsTransferWidget.java @@ -0,0 +1,55 @@ +package de.mrjulsen.crn.client.gui.widgets.routedetails; + +import de.mrjulsen.crn.CreateRailwaysNavigator; +import de.mrjulsen.crn.client.gui.ModGuiIcons; +import de.mrjulsen.crn.client.lang.ELanguage; +import de.mrjulsen.crn.data.navigation.TransferConnection; +import de.mrjulsen.mcdragonlib.client.gui.widgets.DLRenderable; +import de.mrjulsen.mcdragonlib.client.util.Graphics; +import de.mrjulsen.mcdragonlib.client.util.GuiUtils; +import de.mrjulsen.mcdragonlib.core.EAlignment; +import de.mrjulsen.mcdragonlib.util.TextUtils; +import de.mrjulsen.mcdragonlib.util.TimeUtils; +import net.minecraft.ChatFormatting; +import net.minecraft.client.Minecraft; +import net.minecraft.network.chat.MutableComponent; +import net.minecraft.resources.ResourceLocation; + +public class RouteDetailsTransferWidget extends DLRenderable { + + private final MutableComponent transferText = ELanguage.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".route_details.transfer"); + + protected static final ResourceLocation GUI = new ResourceLocation(CreateRailwaysNavigator.MOD_ID, "textures/gui/widgets.png"); + protected static final int GUI_TEXTURE_WIDTH = 256; + protected static final int GUI_TEXTURE_HEIGHT = 256; + protected static final int ENTRY_WIDTH = 225; + + private final MutableComponent textConnectionEndangered = ELanguage.translate("gui.createrailwaysnavigator.route_overview.connection_endangered").withStyle(ChatFormatting.GOLD).withStyle(ChatFormatting.BOLD); + private final MutableComponent textConnectionMissed = ELanguage.translate("gui.createrailwaysnavigator.route_overview.connection_missed").withStyle(ChatFormatting.RED).withStyle(ChatFormatting.BOLD); + + private final TransferConnection connection; + + public RouteDetailsTransferWidget(int x, int y, int width, TransferConnection connection) { + super(x, y, width, 24); + this.connection = connection; + } + + @SuppressWarnings("resource") + @Override + public void renderMainLayer(Graphics graphics, int mouseX, int mouseY, float partialTicks) { + super.renderMainLayer(graphics, mouseX, mouseY, partialTicks); + + long time = connection.getDepartureStation().getScheduledDepartureTime() - connection.getArrivalStation().getScheduledArrivalTime(); + GuiUtils.drawTexture(GUI, graphics, x(), y(), ENTRY_WIDTH, height(), 0, 155, ENTRY_WIDTH, height(), GUI_TEXTURE_WIDTH, GUI_TEXTURE_HEIGHT); + + if (connection.isConnectionMissed()) { + ModGuiIcons.CROSS.render(graphics, x() + 24, y + 4); + GuiUtils.drawString(graphics, Minecraft.getInstance().font, x() + 28 + ModGuiIcons.ICON_SIZE + 2, y + 8, textConnectionMissed, 0xFFFFFFFF, EAlignment.LEFT, false); + } else if (connection.isConnectionEndangered()) { + ModGuiIcons.WARN.render(graphics, x() + 24, y + 4); + GuiUtils.drawString(graphics, Minecraft.getInstance().font, x() + 28 + ModGuiIcons.ICON_SIZE + 2, y + 8, textConnectionEndangered, 0xFFFFFFFF, EAlignment.LEFT, false); + } else { + GuiUtils.drawString(graphics, Minecraft.getInstance().font, x() + 32, y + 8, TextUtils.text(transferText.getString() + " " + (time < 0 ? "" : "(" + TimeUtils.parseDuration(time) + ")")), 0xFFFFFF, EAlignment.LEFT, false); + } + } +} diff --git a/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/routedetails/RoutePartEntryWidget.java b/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/routedetails/RoutePartEntryWidget.java new file mode 100644 index 00000000..14117750 --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/routedetails/RoutePartEntryWidget.java @@ -0,0 +1,114 @@ +package de.mrjulsen.crn.client.gui.widgets.routedetails; + +import de.mrjulsen.crn.Constants; +import de.mrjulsen.crn.CreateRailwaysNavigator; +import de.mrjulsen.crn.client.gui.screen.ScheduleBoardScreen; +import de.mrjulsen.crn.config.ModClientConfig; +import de.mrjulsen.crn.data.train.ClientTrainStop; +import de.mrjulsen.crn.data.navigation.ClientRoutePart; +import de.mrjulsen.mcdragonlib.DragonLib; +import de.mrjulsen.mcdragonlib.client.gui.DLScreen; +import de.mrjulsen.mcdragonlib.client.gui.widgets.DLButton; +import de.mrjulsen.mcdragonlib.client.util.Graphics; +import de.mrjulsen.mcdragonlib.client.util.GuiUtils; +import de.mrjulsen.mcdragonlib.core.EAlignment; +import de.mrjulsen.mcdragonlib.util.TextUtils; +import de.mrjulsen.mcdragonlib.util.TimeUtils; +import net.minecraft.ChatFormatting; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.resources.ResourceLocation; + +public class RoutePartEntryWidget extends DLButton { + + protected static final ResourceLocation GUI = new ResourceLocation(CreateRailwaysNavigator.MOD_ID, "textures/gui/widgets.png"); + protected static final int GUI_TEXTURE_WIDTH = 256; + protected static final int GUI_TEXTURE_HEIGHT = 256; + protected static final int ENTRY_WIDTH = 225; + + private final ClientRoutePart part; + private final ClientTrainStop stop; + private final TrainStopType type; + private boolean valid; + + public RoutePartEntryWidget(Screen parent, ClientRoutePart part, ClientTrainStop stop, int pX, int pY, int width, TrainStopType type, boolean valid) { + super(pX, pY, width, type.h, TextUtils.empty(), (b) -> { + DLScreen.setScreen(new ScheduleBoardScreen(parent, stop.getClientTag())); + }); + this.part = part; + this.stop = stop; + this.type = type; + this.valid = valid; + } + + public void setValid(boolean b) { + this.valid = b; + } + + @Override + public void renderMainLayer(Graphics graphics, int mouseX, int mouseY, float partialTick) { + GuiUtils.drawTexture(GUI, graphics, x(), y(), ENTRY_WIDTH, height(), 0, type.v, ENTRY_WIDTH, height(), GUI_TEXTURE_WIDTH, GUI_TEXTURE_HEIGHT); + renderData(graphics, y() + type.dy); + + if (isMouseSelected()) { + GuiUtils.fill(graphics, x() + 24, y() + type.dy - 1, 199, 20, 0x22FFFFFF); + } + } + + protected void renderData(Graphics graphics, int y) { + final float scale = 0.75f; + String platformText = stop.getClientTag().info().platform(); + String nameText = stop.getClientTag().tagName(); + int maxStationNameWidth = 138 - 8 - font.width(platformText) - 6; + + if (font.width(nameText) > maxStationNameWidth) { + GuiUtils.drawString(graphics, font, x() + 80, y + 5, TextUtils.text(font.substrByWidth(TextUtils.text(stop.getClientTag().tagName()), maxStationNameWidth).getString()).append(Constants.ELLIPSIS_STRING), 0xFFFFFFFF, EAlignment.LEFT, false); + } else { + GuiUtils.drawString(graphics, font, x() + 80, y + 5, nameText, 0xFFFFFFFF, EAlignment.LEFT, false); + } + GuiUtils.drawString(graphics, font, x() + 213, y + 5, platformText, 0xFFFFFFFF, EAlignment.RIGHT, false); + + graphics.poseStack().pushPose(); + graphics.poseStack().scale(scale, scale, 1); + + int precision = ModClientConfig.REALTIME_PRECISION_THRESHOLD.get(); + + if (this.type == TrainStopType.TRANSIT) { + graphics.poseStack().translate((x() + 28) / scale, (y + 2) / scale, 0); + GuiUtils.drawString(graphics, font, 00, 00, TextUtils.text(TimeUtils.parseTime(stop.getScheduledArrivalTime() + DragonLib.DAYTIME_SHIFT, ModClientConfig.TIME_FORMAT.get())).withStyle(valid ? ChatFormatting.RESET : ChatFormatting.STRIKETHROUGH), valid ? 0xFFFFFFFF : Constants.COLOR_DELAYED, EAlignment.LEFT, false); + GuiUtils.drawString(graphics, font, 00, 12, TextUtils.text(TimeUtils.parseTime(stop.getScheduledDepartureTime() + DragonLib.DAYTIME_SHIFT, ModClientConfig.TIME_FORMAT.get())).withStyle(valid ? ChatFormatting.RESET : ChatFormatting.STRIKETHROUGH), valid ? 0xFFFFFFFF : Constants.COLOR_DELAYED, EAlignment.LEFT, false); + + if (stop.shouldRenderRealTime() && !part.isCancelled() && valid) { + GuiUtils.drawString(graphics, font, 30, 00, TimeUtils.parseTime(stop.getScheduledArrivalTime() + (stop.getArrivalTimeDeviation() / precision * precision) + DragonLib.DAYTIME_SHIFT, ModClientConfig.TIME_FORMAT.get()), stop.isArrivalDelayed() ? Constants.COLOR_DELAYED : Constants.COLOR_ON_TIME, EAlignment.LEFT, false); + GuiUtils.drawString(graphics, font, 30, 12, TimeUtils.parseTime(stop.getScheduledDepartureTime() + (stop.getDepartureTimeDeviation() / precision * precision) + DragonLib.DAYTIME_SHIFT, ModClientConfig.TIME_FORMAT.get()), stop.isDepartureDelayed() ? Constants.COLOR_DELAYED : Constants.COLOR_ON_TIME, EAlignment.LEFT, false); + } + } else { + graphics.poseStack().translate((x() + 28) / scale, (y + 6) / scale, 0); + GuiUtils.drawString(graphics, font, 00, 00, TextUtils.text(TimeUtils.parseTime((type == TrainStopType.START ? stop.getScheduledDepartureTime() : stop.getScheduledArrivalTime()) + DragonLib.DAYTIME_SHIFT, ModClientConfig.TIME_FORMAT.get())).withStyle(valid ? ChatFormatting.RESET : ChatFormatting.STRIKETHROUGH), valid ? 0xFFFFFFFF : Constants.COLOR_DELAYED, EAlignment.LEFT, false); + if (stop.shouldRenderRealTime() && !part.isCancelled() && valid) { + long realTime = type == TrainStopType.START ? stop.getScheduledDepartureTime() + (stop.getDepartureTimeDeviation() / precision * precision) : stop.getScheduledArrivalTime() + (stop.getArrivalTimeDeviation() / precision * precision); + GuiUtils.drawString(graphics, font, 30, 00, TimeUtils.parseTime(realTime + DragonLib.DAYTIME_SHIFT, ModClientConfig.TIME_FORMAT.get()), (type == TrainStopType.START ? stop.isDepartureDelayed() : stop.isArrivalDelayed()) ? Constants.COLOR_DELAYED : Constants.COLOR_ON_TIME, EAlignment.LEFT, false); + } + } + + graphics.poseStack().popPose(); + //GuiUtils.drawString(graphics, font, x(), y(), stop.getState().name() + ", Running: " + !stop.isClosed(), 0xFFFF0000, EAlignment.LEFT, false); + //GuiUtils.drawString(graphics, font, x(), y() + height() - font.lineHeight, "" + stop.getScheduleIndex() + ": " + stop.getScheduledCycle() + " / " + stop.getSimulationTime() + " / " + stop.getScheduledArrivalTime() + " / " + stop.debug_test + (stop.isSimulated() ? "s" : ""), 0xFFFF0000, EAlignment.LEFT, false); + //GuiUtils.drawString(graphics, font, x(), y() + height() - font.lineHeight, String.valueOf(stop.getTag().getId()), 0xFFFF0000, EAlignment.LEFT, false); + } + + public static enum TrainStopType { + START(48, 24, 4), + TRANSIT(72, 21, 1), + END(122, 33, 11); + + private int v; + private int h; + private int dy; + + TrainStopType(int v, int h, int dy) { + this.v = v; + this.h = h; + this.dy = dy; + } + } +} diff --git a/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/routedetails/RoutePartTrainDetailsWidget.java b/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/routedetails/RoutePartTrainDetailsWidget.java new file mode 100644 index 00000000..40b6c797 --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/routedetails/RoutePartTrainDetailsWidget.java @@ -0,0 +1,192 @@ +package de.mrjulsen.crn.client.gui.widgets.routedetails; + +import java.io.Closeable; +import java.util.Set; + +import com.simibubi.create.content.trains.entity.TrainIconType; + +import de.mrjulsen.crn.Constants; +import de.mrjulsen.crn.CreateRailwaysNavigator; +import de.mrjulsen.crn.client.ClientWrapper; +import de.mrjulsen.crn.client.gui.CreateDynamicWidgets; +import de.mrjulsen.crn.client.gui.screen.TrainJourneySreen; +import de.mrjulsen.crn.client.gui.widgets.routedetails.RoutePartWidget.RoutePartDetailsActionBuilder; +import de.mrjulsen.crn.data.train.ClientTrainStop; +import de.mrjulsen.crn.data.train.TrainStatus.CompiledTrainStatus; +import de.mrjulsen.crn.data.navigation.ClientRoute; +import de.mrjulsen.crn.data.navigation.ClientRoutePart; +import de.mrjulsen.crn.event.CRNEventsManager; +import de.mrjulsen.crn.event.events.RouteDetailsActionsEvent; +import de.mrjulsen.mcdragonlib.DragonLib; +import de.mrjulsen.mcdragonlib.client.gui.widgets.DLAbstractImageButton.ButtonType; +import de.mrjulsen.mcdragonlib.client.gui.widgets.DLIconButton; +import de.mrjulsen.mcdragonlib.client.gui.widgets.WidgetContainer; +import de.mrjulsen.mcdragonlib.client.render.DynamicGuiRenderer.AreaStyle; +import de.mrjulsen.mcdragonlib.client.render.GuiIcons; +import de.mrjulsen.mcdragonlib.client.render.Sprite; +import de.mrjulsen.mcdragonlib.client.util.DLWidgetsCollection; +import de.mrjulsen.mcdragonlib.client.util.Graphics; +import de.mrjulsen.mcdragonlib.client.util.GuiUtils; +import de.mrjulsen.mcdragonlib.core.EAlignment; +import de.mrjulsen.mcdragonlib.data.Single; +import de.mrjulsen.mcdragonlib.util.TextUtils; +import net.minecraft.ChatFormatting; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.narration.NarrationElementOutput; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; + +public class RoutePartTrainDetailsWidget extends WidgetContainer implements Closeable { + + protected static final ResourceLocation GUI = new ResourceLocation(CreateRailwaysNavigator.MOD_ID, "textures/gui/widgets.png"); + protected static final int GUI_TEXTURE_WIDTH = 256; + protected static final int GUI_TEXTURE_HEIGHT = 256; + protected static final int ENTRY_WIDTH = 225; + protected static final int DEFAULT_HEIGHT = 28; + protected static final int REASON_WIDTH = RoutePartWidget.ACTION_BTN_WIDTH - 8; + protected static final int V = 92; + + private final ClientTrainStop stop; + private final ClientRoutePart part; + private Set status = Set.of(); + + public static final int ACTION_BTN_WIDTH = 140; + public static final int ACTION_BTN_HEIGHT = 14; + private int actionIndex; + private int currentHeight; + private final DLWidgetsCollection actionButtons = new DLWidgetsCollection(); + private final RoutePartWidget container; + + public RoutePartTrainDetailsWidget(Screen parent, RoutePartWidget container, ClientRoute route, ClientRoutePart part, ClientTrainStop firstStop, int pX, int pY, int width) { + super(pX, pY, width, DEFAULT_HEIGHT); + this.stop = firstStop; + this.part = part; + this.container = container; + + part.listen(ClientRoutePart.EVENT_UPDATE, this, (data) -> { + int oldHeight = currentHeight; + currentHeight = DEFAULT_HEIGHT; + updateStatus(); + currentHeight += actionIndex * (ACTION_BTN_HEIGHT + 1); + int diff = currentHeight - oldHeight; + actionButtons.performForEach(x -> x.set_y(x.y() + diff)); + set_height(currentHeight); + }); + + currentHeight = DEFAULT_HEIGHT; + updateStatus(); + + addAction(new RoutePartDetailsActionBuilder(TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".journey_info.title"), Sprite.empty(), (b) -> Minecraft.getInstance().setScreen(new TrainJourneySreen(parent, route, part.getTrainId())))); + CRNEventsManager.getEventOptional(RouteDetailsActionsEvent.class).ifPresent(x -> x.run(route, part, container.isExpanded()).forEach(this::addAction)); + if (!part.getStopovers().isEmpty()) { + addAction(new RoutePartDetailsActionBuilder(container.isExpanded() ? Constants.TOOLTIP_COLLAPSE : Constants.TOOLTIP_EXPAND, (container.isExpanded() ? GuiIcons.ARROW_UP : GuiIcons.ARROW_DOWN).getAsSprite(16, 16), (b) -> container.setExpanded(!container.isExpanded()))); + } + + currentHeight += actionIndex * (ACTION_BTN_HEIGHT + 1); + set_height(currentHeight); + } + + private void updateStatus() { + this.status = part.getStatus(); + currentHeight += status.stream().mapToInt(x -> Math.max(9, (int)(ClientWrapper.getTextBlockHeight(font, x.text(), (int)(REASON_WIDTH / 0.75f)) * 0.75f) + 2)).sum() /* TODO */ + 4; + } + + public void addAction(RoutePartDetailsActionBuilder builder) { + DLIconButton btn2 = addRenderableWidget(new DLIconButton(ButtonType.DEFAULT, AreaStyle.FLAT, builder.icon(), x() + 76, y() + currentHeight + (actionIndex * (ACTION_BTN_HEIGHT + 1)), ACTION_BTN_WIDTH, ACTION_BTN_HEIGHT, builder.text(), builder.onClick()) { + @Override + public void mouseMoved(double mouseX, double mouseY) { + setFontColor(isInBounds(mouseX, mouseY) ? DragonLib.NATIVE_BUTTON_FONT_COLOR_HIGHLIGHT : DragonLib.NATIVE_BUTTON_FONT_COLOR_ACTIVE); + super.mouseMoved(mouseX, mouseY); + } + }); + btn2.setFontColor(DragonLib.NATIVE_BUTTON_FONT_COLOR_ACTIVE); + btn2.setBackColor(0x00000000); + actionButtons.add(btn2); + actionIndex++; + } + + @Override + public void renderMainLayer(Graphics graphics, int mouseX, int mouseY, float partialTick) { + GuiUtils.drawTexture(GUI, graphics, x(), y(), ENTRY_WIDTH, DEFAULT_HEIGHT, 0, V, ENTRY_WIDTH, DEFAULT_HEIGHT, GUI_TEXTURE_WIDTH, GUI_TEXTURE_HEIGHT); + renderData(graphics, y() + 1); + super.renderMainLayer(graphics, mouseX, mouseY, partialTick); + } + + protected void renderData(Graphics graphics, int y) { + final float scale = 0.75f; + final float mul = 1 / scale; + final float maxWidth = 140; + + //GuiUtils.drawTexture(Constants.GUI_WIDGETS, graphics, x, y, 0, V, ENTRY_WIDTH, DEFAULT_HEIGHT); + GuiUtils.drawTexture(Constants.GUI_WIDGETS, graphics, x, y + DEFAULT_HEIGHT - 1, ENTRY_WIDTH, height() - DEFAULT_HEIGHT, 0, V + DEFAULT_HEIGHT, ENTRY_WIDTH, 1, GUI_TEXTURE_WIDTH, GUI_TEXTURE_HEIGHT); + stop.getTrainIcon().render(TrainIconType.ENGINE, graphics.poseStack(), x + 80, y + 7); + + graphics.poseStack().pushPose(); + graphics.poseStack().scale(scale, scale, scale); + Component trainName = TextUtils.text(part.getLastStop().getTrainDisplayName()).withStyle(ChatFormatting.BOLD); + CreateDynamicWidgets.renderTextHighlighted(graphics, (int)((x() + 80 + 24) / scale), (int)((y + 4) / scale), font, trainName, part.getLastStop().getTrainDisplayColor()); + GuiUtils.drawString(graphics, font, (int)((x() + 80 + 24) / scale) + font.width(trainName) + 10, (int)((y + 6) / scale), GuiUtils.ellipsisString(font, TextUtils.text(String.format("%s (%s)", stop.getTrainName(), stop.getTrainId().toString().split("-")[0])), (int)((maxWidth - font.width(trainName) - 15) / scale)), 0xFFDBDBDB, EAlignment.LEFT, false); + GuiUtils.drawString(graphics, font, (int)((x() + 80 + 24) / scale), (int)((y + 18) / scale), GuiUtils.ellipsisString(font, TextUtils.text(stop.getDisplayTitle()), (int)((maxWidth - 24) / scale)), 0xFFDBDBDB, EAlignment.LEFT, false); + graphics.poseStack().scale(mul, mul, mul); + graphics.poseStack().popPose(); + + // render reasons + int reasonsY = 0; + for (CompiledTrainStatus trainInformation : status) { + reasonsY += trainInformation.render(graphics, Single.of(font), x() + 76, y() + DEFAULT_HEIGHT + 2 + reasonsY, REASON_WIDTH); + } + } + + @Override + public void set_height(int h) { + super.set_height(h); + container.updateHeight(); + } + + public static enum TrainStopType { + START(48, 30, 8), + TRANSIT(78, 21, 1), + END(142, 44, 14); + + private int v; + private int h; + private int dy; + + TrainStopType(int v, int h, int dy) { + this.v = v; + this.h = h; + this.dy = dy; + } + + public int getV() { + return v; + } + + public int getH() { + return h; + } + + public int getDy() { + return dy; + } + } + + @Override + public void close() { + part.stopListeningAll(this); + } + + @Override + public NarrationPriority narrationPriority() { + return NarrationPriority.HOVERED; + } + + @Override + public void updateNarration(NarrationElementOutput narrationElementOutput) {} + + @Override + public boolean consumeScrolling(double mouseX, double mouseY) { + return false; + } +} diff --git a/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/routedetails/RoutePartWidget.java b/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/routedetails/RoutePartWidget.java new file mode 100644 index 00000000..5daab598 --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/client/gui/widgets/routedetails/RoutePartWidget.java @@ -0,0 +1,163 @@ +package de.mrjulsen.crn.client.gui.widgets.routedetails; + +import java.io.Closeable; +import java.io.IOException; +import java.util.List; +import java.util.function.Consumer; + +import de.mrjulsen.crn.client.gui.widgets.routedetails.RoutePartEntryWidget.TrainStopType; +import de.mrjulsen.crn.data.train.ClientTrainStop; +import de.mrjulsen.crn.data.navigation.ClientRoute; +import de.mrjulsen.crn.data.navigation.ClientRoutePart; +import de.mrjulsen.mcdragonlib.client.gui.widgets.DLIconButton; +import de.mrjulsen.mcdragonlib.client.gui.widgets.IDragonLibWidget; +import de.mrjulsen.mcdragonlib.client.gui.widgets.WidgetContainer; +import de.mrjulsen.mcdragonlib.client.render.Sprite; +import de.mrjulsen.mcdragonlib.client.util.DLWidgetsCollection; +import de.mrjulsen.mcdragonlib.client.util.Graphics; +import de.mrjulsen.mcdragonlib.util.DLUtils; +import net.minecraft.client.gui.components.Widget; +import net.minecraft.client.gui.components.events.GuiEventListener; +import net.minecraft.client.gui.narration.NarrationElementOutput; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.network.chat.Component; + +public class RoutePartWidget extends WidgetContainer { + + private final ClientRoutePart part; + private final ClientRoute route; + private int stackLayoutY; + + private boolean expanded; + private boolean canExpandCollapse = true; + private boolean showTrainDetails = true; + private boolean showJourney = false; + private Consumer onGuiChanged; + private final DLWidgetsCollection stationWidgets = new DLWidgetsCollection(); + + private final Screen parent; + + public static final int ACTION_BTN_WIDTH = 140; + public static final int ACTION_BTN_HEIGHT = 14; + + public RoutePartWidget(Screen parent, int x, int y, int width, ClientRoute route, ClientRoutePart part) { + super(x, y, width, 1); + this.part = part; + this.route = route; + this.parent = parent; + initGui(); + } + + public void initGui() { + children().stream().filter(x -> x instanceof Closeable).forEach(x -> { + try { + ((Closeable)x).close(); + } catch (IOException e) {} + }); + clearWidgets(); + stackLayoutY = 0; + stationWidgets.clear(); + boolean valid = route.isPartReachable(part); + + List stops = showJourney ? part.getAllJourneyClientStops() : part.getAllClientStops(); + + addToStackLayout(new RoutePartEntryWidget(parent, part, stops.get(0), x(), y() + stackLayoutY, width(), TrainStopType.START, valid)); + if (showTrainDetails()) { + RoutePartTrainDetailsWidget details = new RoutePartTrainDetailsWidget(parent, this, route, part, stops.get(0), x(), y() + stackLayoutY, width()); + addToStackLayout(details); + } + + if (this.expanded) { + for (int i = 1; i < stops.size() - 1; i++) { + ClientTrainStop stop = stops.get(i); + addToStackLayout(new RoutePartEntryWidget(parent, part, stop, x(), y() + stackLayoutY, width(), TrainStopType.TRANSIT, valid)); + } + } + addToStackLayout(new RoutePartEntryWidget(parent, part, stops.get(stops.size() - 1), x(), y() + stackLayoutY, width(), TrainStopType.END, valid)); + + set_height(stackLayoutY); + + DLUtils.doIfNotNull(onGuiChanged, x -> x.accept(this)); + } + + public RoutePartWidget withOnGuiChangedEvent(Consumer onGuiChanged) { + this.onGuiChanged = onGuiChanged; + return this; + } + + public void updateHeight() { + stackLayoutY = 0; + for (IDragonLibWidget c : stationWidgets.components) { + c.set_y(y() + stackLayoutY); + stackLayoutY += c.height(); + } + } + + + private void addToStackLayout(T widget) { + addRenderableWidget(widget); + stationWidgets.add(widget); + stackLayoutY += widget.height(); + } + + public boolean isExpanded() { + return expanded; + } + + public void setExpanded(boolean b) { + this.expanded = b; + initGui(); + } + + public boolean canExpandCollapse() { + return canExpandCollapse; + } + + public void setCanExpandCollapse(boolean canExpandCollapse) { + this.canExpandCollapse = canExpandCollapse; + } + + public boolean showTrainDetails() { + return showTrainDetails; + } + + public void setShowTrainDetails(boolean showTrainDetails) { + this.showTrainDetails = showTrainDetails; + } + + public boolean isShowingJourney() { + return showJourney; + } + + public void setShowJourney(boolean showJourney) { + this.showJourney = showJourney; + } + + @Override + public void renderMainLayer(Graphics graphics, int mouseX, int mouseY, float partialTicks) { + super.renderMainLayer(graphics, mouseX, mouseY, partialTicks); + //GuiUtils.drawString(graphics, font, x() + 22, y(), "State: " + part.getProgressState() + ", " + part.getNextStop().getTag().getTagName().get(), 0xFFFF0000, EAlignment.LEFT, false); + } + + @Override + public void tick() { + super.tick(); + boolean valid = route.isPartReachable(part); + stationWidgets.performForEach(x -> x instanceof RoutePartEntryWidget, x -> ((RoutePartEntryWidget)x).setValid(valid)); + } + + @Override + public NarrationPriority narrationPriority() { + return NarrationPriority.HOVERED; + } + + @Override + public void updateNarration(NarrationElementOutput narrationElementOutput) {} + + @Override + public boolean consumeScrolling(double mouseX, double mouseY) { + return false; + } + + public static record RoutePartDetailsActionBuilder(Component text, Sprite icon, Consumer onClick) {} +} diff --git a/common/src/main/java/de/mrjulsen/crn/client/lang/ELanguage.java b/common/src/main/java/de/mrjulsen/crn/client/lang/ELanguage.java index a0c4970d..3bc57dab 100644 --- a/common/src/main/java/de/mrjulsen/crn/client/lang/ELanguage.java +++ b/common/src/main/java/de/mrjulsen/crn/client/lang/ELanguage.java @@ -17,7 +17,11 @@ public enum ELanguage implements StringRepresentable { SAXON("saxon", "sxu"), BAVARIAN("bavarian", "bar"), SPANISH("spanish", "es_es"), - RUSSIAN("russian", "ru_ru"); + RUSSIAN("russian", "ru_ru"), + FRENCH("french", "fr_fr"), + KOREAN("korean", "ko_kr"), + SWEDISH("swedish", "sv_se"), + PORTUGUESE("portuguese", "pt_pt"); private String name; private String code; diff --git a/common/src/main/java/de/mrjulsen/crn/cmd/DebugCommand.java b/common/src/main/java/de/mrjulsen/crn/cmd/DebugCommand.java new file mode 100644 index 00000000..dffbc443 --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/cmd/DebugCommand.java @@ -0,0 +1,105 @@ +package de.mrjulsen.crn.cmd; + +import com.mojang.brigadier.CommandDispatcher; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import de.mrjulsen.crn.CreateRailwaysNavigator; +import de.mrjulsen.crn.data.train.TrainListener; +import de.mrjulsen.crn.debug.DebugOverlay; +import de.mrjulsen.crn.registry.ModAccessorTypes; +import de.mrjulsen.mcdragonlib.util.TextUtils; +import de.mrjulsen.mcdragonlib.util.accessor.DataAccessor; +import net.minecraft.Util; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.Commands; +import net.minecraft.commands.Commands.CommandSelection; + +public class DebugCommand { + + private static final String CMD_NAME = CreateRailwaysNavigator.MOD_ID; + + private static final String SUB_DEBUG = "debug"; + private static final String SUB_DISCORD = "discord"; + private static final String SUB_GITHUB = "github"; + + private static final String SUB_RESET = "resetTrainPredictions"; + private static final String SUB_HARD_RESET = "hardResetTrainPredictions"; + private static final String SUB_TRAIN_DEBUG_OVERLAY = "trainDebugOverlay"; + private static final String SUB_TRAIN_OVERVIEW = "trainOverview"; + private static final String SUB_TEST = "test"; + + @SuppressWarnings("all") + public static void register(CommandDispatcher dispatcher, CommandSelection selection) { + + LiteralArgumentBuilder builder = Commands.literal(CMD_NAME) + .then(Commands.literal(SUB_DEBUG) + .requires(x -> x.hasPermission(3)) + .then(Commands.literal(SUB_HARD_RESET) + .executes(x -> hardReset(x.getSource())) + ) + .then(Commands.literal(SUB_RESET) + .executes(x -> reset(x.getSource())) + ) + .then(Commands.literal(SUB_TRAIN_DEBUG_OVERLAY) + .executes(x -> showTrainObservationOverlay(x.getSource())) + ) + .then(Commands.literal(SUB_TRAIN_OVERVIEW) + .executes(x -> showTrainDebugScreen(x.getSource())) + ) + .then(Commands.literal(SUB_TEST) + .executes(x -> printAllSignals(x.getSource())) + ) + ) + .then(Commands.literal(SUB_DISCORD) + .executes(x -> discord(x.getSource())) + ) + .then(Commands.literal(SUB_GITHUB) + .executes(x -> github(x.getSource())) + ) + ; + + dispatcher.register(builder); + } + + private static int discord(CommandSourceStack cmd) throws CommandSyntaxException { + cmd.sendSuccess(TextUtils.text("Redirecting to the discord server..."), false); + Util.getPlatform().openUri(CreateRailwaysNavigator.DISCORD); + return 1; + } + + private static int github(CommandSourceStack cmd) throws CommandSyntaxException { + cmd.sendSuccess(TextUtils.text("Redirecting to the github repository..."), false); + Util.getPlatform().openUri(CreateRailwaysNavigator.GITHUB); + return 1; + } + + private static int hardReset(CommandSourceStack cmd) throws CommandSyntaxException { + cmd.sendSuccess(TextUtils.text("All train predictions have been deleted."), false); + TrainListener.data.clear(); + TrainListener.data.values().forEach(x -> x.hardResetPredictions()); + return 1; + } + + private static int reset(CommandSourceStack cmd) throws CommandSyntaxException { + cmd.sendSuccess(TextUtils.text("All train predictions have been reset."), false); + TrainListener.data.values().forEach(x -> x.resetPredictions()); + return 1; + } + + private static int showTrainObservationOverlay(CommandSourceStack cmd) throws CommandSyntaxException { + cmd.sendSuccess(TextUtils.text("Visibility of the train debug overlay has been toggled."), false); + DebugOverlay.toggle(); + return 1; + } + + private static int showTrainDebugScreen(CommandSourceStack cmd) throws CommandSyntaxException { + cmd.sendSuccess(TextUtils.empty(), false); + DataAccessor.getFromClient(cmd.getPlayerOrException(), null, ModAccessorTypes.SHOW_TRAIN_DEBUG_SCREEN, $ -> {}); + return 1; + } + + private static int printAllSignals(CommandSourceStack cmd) throws CommandSyntaxException { + cmd.sendSuccess(TextUtils.empty(), false); + return 1; + } +} \ No newline at end of file diff --git a/common/src/main/java/de/mrjulsen/crn/config/ModClientConfig.java b/common/src/main/java/de/mrjulsen/crn/config/ModClientConfig.java index 0afb01e2..d347c20e 100644 --- a/common/src/main/java/de/mrjulsen/crn/config/ModClientConfig.java +++ b/common/src/main/java/de/mrjulsen/crn/config/ModClientConfig.java @@ -1,8 +1,5 @@ package de.mrjulsen.crn.config; -import java.util.ArrayList; -import java.util.List; - import de.mrjulsen.crn.client.gui.overlay.OverlayPosition; import de.mrjulsen.crn.client.lang.ELanguage; import de.mrjulsen.crn.util.ESpeedUnit; @@ -15,21 +12,15 @@ public class ModClientConfig { public static final ForgeConfigSpec.ConfigValue REALTIME_PRECISION_THRESHOLD; - public static final ForgeConfigSpec.ConfigValue DEVIATION_THRESHOLD; public static final ForgeConfigSpec.ConfigValue NEXT_STOP_ANNOUNCEMENT; public static final ForgeConfigSpec.ConfigValue DISPLAY_LEAD_TIME; - public static final ForgeConfigSpec.ConfigValue REALTIME_EARLY_ARRIVAL_THRESHOLD; public static final ForgeConfigSpec.ConfigValue OVERLAY_SCALE; - public static final ForgeConfigSpec.ConfigValue TRANSFER_TIME; - public static final ForgeConfigSpec.ConfigValue> TRAIN_GROUP_FILTER_BLACKLIST; - public static final ForgeConfigSpec.ConfigValue ROUTE_NARRATOR; public static final ForgeConfigSpec.ConfigValue ROUTE_NOTIFICATIONS; public static final ForgeConfigSpec.ConfigValue ROUTE_OVERLAY_POSITION; public static final ForgeConfigSpec.ConfigValue TIME_FORMAT; public static final ForgeConfigSpec.ConfigValue LANGUAGE; public static final ForgeConfigSpec.ConfigValue SPEED_UNIT; - public static final int MAX_TRANSFER_TIME = 24000; public static final double MIN_SCALE = 0.25f; public static final double MAX_SCALE = 2.0f; @@ -37,28 +28,18 @@ public class ModClientConfig { BUILDER.push("Create Railways Navigator Config"); /* CONFIGS */ - DEVIATION_THRESHOLD = BUILDER.comment("The value indicates how much deviation from the schedule a train should be considered delayed. Delayed trains are marked red in the real-time display. (Default: 500, 30 in-game minutes)") - .defineInRange("general.deviation_threshold", 500, 1, MAX_TRANSFER_TIME); - REALTIME_EARLY_ARRIVAL_THRESHOLD = BUILDER.comment("If a train departs earlier from the scheduled time than specified here, no real-time data will be displayed. Trains that depart earlier than the scheduled departure time minus the minimum transfer time are intentionally \"missed\" in order to continue to ensure the connection. (Default: 500, 30 in-game minutes)") - .defineInRange("general.realtime_early_arrival_threshold", 500, 1, MAX_TRANSFER_TIME); - NEXT_STOP_ANNOUNCEMENT = BUILDER.comment("The next stop or information about the start of the journey is announced in the specified number of ticks before the scheduled arrival at the next station. (Default: 500, 30 real life seconds)") + NEXT_STOP_ANNOUNCEMENT = BUILDER.comment(new String[] {"[in Ticks]", "The next stop or information about the start of the journey is announced in the specified number of ticks before the scheduled arrival at the next station. (Default: 500, 30 real life seconds)"}) .defineInRange("general.next_stop_announcement", 500, 100, 1000); - REALTIME_PRECISION_THRESHOLD = BUILDER.comment("This value (in ticks) indicates how accurately the real-time data should be displayed. By default, only deviations over 10 in-game minutes (167 ticks, approx. 8 real life seconds) are displayed. The lower the value, the more accurate the real-time data but also the more often deviations from the schedule occur. (Default: 167, 10 in-game minutes)") - .defineInRange("general.realtime_precision_threshold", 167, 1, 1000); - DISPLAY_LEAD_TIME = BUILDER.comment("This value indicates when departing trains should be displayed on advanced displays. By default 1000 ticks (1 in-game hour, 50 real life seconds) before departure is imminent.") - .defineInRange("general.display_lead_time", 1000, 200, MAX_TRANSFER_TIME); + REALTIME_PRECISION_THRESHOLD = BUILDER.comment(new String[] {"[in Ticks]", "This value indicates how accurately the real-time data should be displayed. By default, only deviations above 10 in-game minutes (167 ticks, approx. 8 real life seconds) are displayed. The lower the value, the more accurate the real-time data but also the more often deviations from the schedule occur. (Default: 167, 10 in-game minutes)"}) + .defineInRange("general.realtime_precision_threshold", 167, 1, 1000); + DISPLAY_LEAD_TIME = BUILDER.comment(new String[] {"[in Ticks]", "How early a train should be shown on the display. (Default: 1000, 1 in-game hour)"}) + .defineInRange("general.display_lead_time", 1000, 100, 24000); OVERLAY_SCALE = BUILDER.comment("Scale of the route overlay UI. (Default: 0.75)") .defineInRange("route_overlay.scale", 0.75f, MIN_SCALE, MAX_SCALE); - ROUTE_NARRATOR = BUILDER.comment("If active, events during the journey (e.g. the next stop) are announced. (Default: OFF)") - .define("route_overlay.narrator", false); ROUTE_NOTIFICATIONS = BUILDER.comment("If active, you will receive short toasts about important events on your trip, e.g. delays, changes, ... (Default: ON)") .define("route_overlay.notifications", true); ROUTE_OVERLAY_POSITION = BUILDER.comment("The position on your screen where you want the overlay to appear. (Default: Top Left)") .defineEnum("route_overlay.position", OverlayPosition.TOP_LEFT); - TRANSFER_TIME = BUILDER.comment("Specifies the minimum amount of time (in ticks) that must be available for changing the train. Only trains that depart later than the specified value at the transfer station will be selected. (Default: 1000, 1 in-game hour, approx. 50 real life seconds)") - .defineInRange("search_settings.transfer_time", 1000, 0, MAX_TRANSFER_TIME); - TRAIN_GROUP_FILTER_BLACKLIST = BUILDER.comment("List of train groups that should NOT be used in navigation. (Default: )") - .defineList("search_settings.train_group_blacklist", new ArrayList(), x -> x instanceof String); LANGUAGE = BUILDER.comment("The language that should be used for announcements of the navigator. Can be different from the game's language settings. (Default: Default)") .defineEnum("language", ELanguage.DEFAULT); @@ -72,7 +53,5 @@ public class ModClientConfig { } public static void resetSearchSettings() { - TRANSFER_TIME.set(1000); - TRAIN_GROUP_FILTER_BLACKLIST.set(List.of()); } } diff --git a/common/src/main/java/de/mrjulsen/crn/config/ModCommonConfig.java b/common/src/main/java/de/mrjulsen/crn/config/ModCommonConfig.java index 62192b0c..150ae845 100644 --- a/common/src/main/java/de/mrjulsen/crn/config/ModCommonConfig.java +++ b/common/src/main/java/de/mrjulsen/crn/config/ModCommonConfig.java @@ -8,17 +8,39 @@ public class ModCommonConfig { public static final ForgeConfigSpec SPEC; public static final ForgeConfigSpec.ConfigValue GLOBAL_SETTINGS_PERMISSION_LEVEL; - public static final ForgeConfigSpec.ConfigValue TRAIN_WATCHER_INTERVALL; + public static final ForgeConfigSpec.ConfigValue TOTAL_DURATION_BUFFER_SIZE; + public static final ForgeConfigSpec.ConfigValue SCHEDULE_DEVIATION_THRESHOLD; + public static final ForgeConfigSpec.ConfigValue AUTO_RESET_TIMINGS; + public static final ForgeConfigSpec.ConfigValue TRANSFER_COST; + public static final ForgeConfigSpec.ConfigValue TOTAL_DURATION_DEVIATION_THRESHOLD; + public static final ForgeConfigSpec.ConfigValue CUSTOM_TRANSIT_TIME_CALCULATION; + public static final ForgeConfigSpec.ConfigValue EXCLUDE_TRAINS; static { BUILDER.push(CreateRailwaysNavigator.MOD_ID + "_common_config"); - - TRAIN_WATCHER_INTERVALL = BUILDER.comment("The time interval (in ticks) in which the Train Listener collects tick data of all trains. Higher values may result in less precise time information in the route details view, while lower values may result in higher CPU usage. Default: 100 (5s)") - .defineInRange("train_watcher_intervall", 100, 100, 1000); - GLOBAL_SETTINGS_PERMISSION_LEVEL = BUILDER.comment("Minimum permission level required to edit the global navigator settings. 0 allows everyone to edit these settings.") - .defineInRange("global_settings_permission_level", 0, 0, 4); + GLOBAL_SETTINGS_PERMISSION_LEVEL = BUILDER.comment("Minimum permission level required to edit the global navigator settings. 0 allows everyone to edit these settings. (Default: 0)") + .defineInRange("permissions.global_settings_permission_level", 0, 0, 4); + + + EXCLUDE_TRAINS = BUILDER.comment("If activated, used trains are excluded from the route search for all following route parts. This prevents the same train from being suggested multiple times in the same route and forces the navigator to use other trains instead. Normally, however, there are no problems, so this option can be left off if in doubt. (Default: OFF)") + .define("navigation.exclude_trains", false); + TRANSFER_COST = BUILDER.comment("How much transfers should be avoided. Higher values try to use fewer transfers, even if this increases the travel time. (Default: 5000)") + .defineInRange("navigation.transfer_cost", 10000, 1000, Integer.MAX_VALUE); + + + CUSTOM_TRANSIT_TIME_CALCULATION = BUILDER.comment("When activated, CRN calculates the transit times of the trains and does not use the calculations from Create. CRN is much more accurate, while Create calculates an average. (Default: ON)") + .define("train_data_calculation.custom_transit_time_calculation", true); + TOTAL_DURATION_BUFFER_SIZE = BUILDER.comment(new String[] {"[in Cycles]", "How often the calculated time for a route section between two stations must deviate from the current reference value before the reference value is updated. (Default: 3)"}) + .defineInRange("train_data_calculation.total_duration_deviation_buffer_size", 3, 1, 16); + TOTAL_DURATION_DEVIATION_THRESHOLD = BUILDER.comment(new String[] {"[in Ticks]", "Deviations of the calculated time for a route section between two stations from the reference value that are smaller than the threshold value are not taken into account. (Default: 50)"}) + .defineInRange("train_data_calculation.total_duration_deviation_threshold", 50, 0, 1000); + SCHEDULE_DEVIATION_THRESHOLD = BUILDER.comment(new String[] {"[in Ticks]", "How many ticks the real-time can deviate from the scheduled time before the train is considered delayed. (Default: 500)"}) + .defineInRange("train_data_calculation.schedule_deviation_threshold", 500, 100, 24000); + AUTO_RESET_TIMINGS = BUILDER.comment(new String[] {"[In Cycles]", "(ONLY WORKS FOR TRAINS WITH DYNAMIC DELAYS! Trains without dynamic delays do this every new schedule section by default.)", " ", "Every X cycles the scheduled times are updated to the current real-time data. (Default: 2; Disabled: 0)"}) + .defineInRange("train_data_calculation.auto_reset_timings", 2, 0, Integer.MAX_VALUE); + BUILDER.pop(); SPEC = BUILDER.build(); } diff --git a/common/src/main/java/de/mrjulsen/crn/core/navigation/Edge.java b/common/src/main/java/de/mrjulsen/crn/core/navigation/Edge.java deleted file mode 100644 index eecbb174..00000000 --- a/common/src/main/java/de/mrjulsen/crn/core/navigation/Edge.java +++ /dev/null @@ -1,70 +0,0 @@ -package de.mrjulsen.crn.core.navigation; - -import java.util.Objects; -import java.util.UUID; - -public class Edge { - - private final UUID id; - private UUID node1Id; - private UUID node2Id; - - private int cost = -1; - private UUID scheduleId; - - public Edge(Node node1, Node node2, UUID id, UUID scheduleId) { - this.id = id; - this.node1Id = node1.getId(); - this.node2Id = node2.getId(); - this.scheduleId = scheduleId; - } - - public Edge withCost(int cost, boolean overwrite) { - this.cost = cost < 0 || !overwrite ? cost : (cost + cost) / 2; - return this; - } - - public UUID getId() { - return id; - } - - public UUID getFirstNodeId() { - return node1Id; - } - - public UUID getSecondNodeId() { - return node2Id; - } - - public UUID getScheduleId() { - return scheduleId; - } - - public int getCost() { - return cost; - } - - @Override - public boolean equals(Object obj) { - if (obj instanceof Edge other) { - return getFirstNodeId().equals(other.getFirstNodeId()) && getSecondNodeId().equals(other.getSecondNodeId()) && getCost() == other.getCost(); - } - - return false; - } - - public boolean similar(Edge other) { - return getFirstNodeId().equals(other.getFirstNodeId()) && getSecondNodeId().equals(other.getSecondNodeId()); - } - - @Override - public int hashCode() { - return 43 * Objects.hash(getFirstNodeId(), getSecondNodeId()); - } - - @Override - public String toString() { - return String.format("%s [%s -> %s, Cost: %s]", getId(), getFirstNodeId(), getSecondNodeId(), getCost()); - } - -} diff --git a/common/src/main/java/de/mrjulsen/crn/core/navigation/Graph.java b/common/src/main/java/de/mrjulsen/crn/core/navigation/Graph.java deleted file mode 100644 index 1dabf768..00000000 --- a/common/src/main/java/de/mrjulsen/crn/core/navigation/Graph.java +++ /dev/null @@ -1,423 +0,0 @@ -package de.mrjulsen.crn.core.navigation; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Comparator; -import java.util.HashMap; -import java.util.HashSet; -import java.util.IdentityHashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.PriorityQueue; -import java.util.Set; -import java.util.UUID; -import java.util.stream.Collectors; - -import com.simibubi.create.content.trains.entity.Train; - -import de.mrjulsen.crn.CreateRailwaysNavigator; -import de.mrjulsen.crn.data.GlobalSettings; -import de.mrjulsen.crn.data.GlobalSettingsManager; -import de.mrjulsen.crn.data.GlobalTrainData; -import de.mrjulsen.crn.data.Route; -import de.mrjulsen.crn.data.RoutePart; -import de.mrjulsen.crn.data.SimpleTrainSchedule; -import de.mrjulsen.crn.data.SimulatedTrainSchedule; -import de.mrjulsen.crn.data.TrainStationAlias; -import de.mrjulsen.crn.data.UserSettings; -import de.mrjulsen.crn.event.listeners.TrainListener; -import de.mrjulsen.crn.util.TrainUtils; -import net.minecraft.world.level.Level; - -public class Graph { - - protected static final int MIN_START_TIME = 200; - - private Map nodesById; - private Map nodesByStation; - - private Map edgesById; - private Map>> edgesByNode; - - private Map schedulesById; - private Map schedulesByTrainId; - private Map> trainIdsBySchedule; - private Map scheduleIdByTrainId; - - private final GlobalSettings globalSettings; - private final UserSettings settings; - - private final long lastUpdated; - private final Level level; - - public Graph(Level level, UserSettings settings) { - long startTime = System.currentTimeMillis(); - lastUpdated = level.getDayTime(); - this.settings = settings; - this.level = level; - GlobalTrainData.makeSnapshot(lastUpdated); - - this.nodesById = new HashMap<>(); - this.nodesByStation = new HashMap<>(); - this.edgesById = new HashMap<>(); - this.edgesByNode = new HashMap<>(); - this.schedulesById = new HashMap<>(); - this.schedulesByTrainId = new HashMap<>(); - this.trainIdsBySchedule = new HashMap<>(); - this.scheduleIdByTrainId = new HashMap<>(); - - final int[] trainCounter = new int[] { 0 }; - globalSettings = GlobalSettingsManager.getInstance().getSettingsData(); - - TrainUtils.getAllTrains().stream().filter(x -> TrainUtils.isTrainValid(x) && !globalSettings.isTrainBlacklisted(x) && !settings.isTrainExcluded(x, globalSettings)).forEach(x -> { - addTrain(x, globalSettings); - trainCounter[0]++; - }); - - long estimatedTime = System.currentTimeMillis() - startTime; - CreateRailwaysNavigator.LOGGER.info(String.format("Graph generated. Took %sms. Contains %s nodes, %s edges and %s schedules. %s train processed.", - estimatedTime, - nodesById.size(), - edgesById.size(), - schedulesById.size(), - trainCounter.length - )); - } - - public UserSettings getSettings() { - return settings; - } - - protected Node addNode(TrainStationAlias alias, Train train) { - if (nodesByStation.containsKey(alias)) { - Node node = nodesByStation.get(alias); - node.addTrain(train.id); - return node; - } - - UUID id = UUID.randomUUID(); - while (nodesById.containsKey(id)) { - id = UUID.randomUUID(); - } - - Node node = new Node(alias, id); - node.addTrain(train.id); - nodesById.put(id, node); - nodesByStation.put(alias, node); - return node; - } - - protected Edge addEdge(Node node1, Node node2, UUID scheduleId) { - UUID id = UUID.randomUUID(); - while (edgesById.containsKey(id)) { - id = UUID.randomUUID(); - } - - Edge edge = new Edge(node1, node2, id, scheduleId); - if (putConnection(node1, node2, edge)) { - edgesById.put(id, edge); - } - return edge; - } - - protected TrainSchedule addTrain(Train train, GlobalSettings settingsInstance) { - - if (schedulesByTrainId.containsKey(train.id)) { - return schedulesByTrainId.get(train.id); - } - - UUID id = UUID.randomUUID(); - while (edgesById.containsKey(id)) { - id = UUID.randomUUID(); - } - - TrainSchedule schedule = new TrainSchedule(train, id, settingsInstance); - if (!schedule.addToGraph(this, train)) { - return null; - } - - if (trainIdsBySchedule.containsKey(schedule)) { - TrainSchedule sched = schedulesByTrainId.get(trainIdsBySchedule.get(schedule).stream().findFirst().get()); - trainIdsBySchedule.get(schedule).add(train.id); - schedulesByTrainId.put(train.id, sched); - scheduleIdByTrainId.put(train.id, sched.getId()); - - sched.getEdges().forEach(x -> { - Optional matchingEdge = schedule.getEdges().stream().filter(a -> a.equals(x)).findFirst(); - - if (matchingEdge.isPresent()) { - x.withCost(matchingEdge.get().getCost(), false); - } - }); - - return sched; - } - - schedulesById.put(id, schedule); - schedulesByTrainId.put(train.id, schedule); - trainIdsBySchedule.put(schedule, new HashSet<>(Set.of(train.id))); - scheduleIdByTrainId.put(train.id, schedule.getId()); - - return schedule; - } - - public boolean putConnection(Node node1, Node node2, Edge edge) { - Map> connections = edgesByNode.computeIfAbsent(node1, n -> new IdentityHashMap<>()); - if (connections.containsKey(node2)) { - if (connections.get(node2).contains(edge)) { - return false; - } - return connections.get(node2).add(edge); - } - - return connections.put(node2, new HashSet<>(Set.of(edge))) == null; - } - - public Set getNodes() { - return new HashSet<>(nodesById.values()); - } - - public Node getNode(TrainStationAlias alias) { - return nodesByStation.get(alias); - } - - public Node getNode(UUID id) { - return nodesById.get(id); - } - - public Map> getEdges(Node node) { - return edgesByNode.get(node); - } - - public Set getEdges() { - return new HashSet<>(edgesById.values()); - } - - public Edge getEdge(UUID id) { - return edgesById.get(id); - } - - public Map> getConnectionsFrom(Node node) { - if (node == null) { - return null; - } - return edgesByNode.getOrDefault(node, new HashMap<>()); - } - - public Collection navigate(TrainStationAlias start, TrainStationAlias end, boolean avoidTransfers) { - return searchTrains(searchRoute(start, end, avoidTransfers)).stream().filter(x -> !x.isEmpty()).sorted(Comparator.comparingInt(x -> x.getStartStation().getPrediction().getTicks())).toList(); - } - - public List searchRoute(TrainStationAlias start, TrainStationAlias end, boolean avoidTransfers) { - - if (!nodesByStation.containsKey(start) || !nodesByStation.containsKey(end)) { - return List.of(); - } - - Map nodes = dijkstra(start, avoidTransfers); - Node endNode = nodes.get(end); - - if (endNode == null) { - return List.of(); - } - - endNode.setIsTransferPoint(true); - List route = new ArrayList<>(); - - Node currentNode = endNode; - while (!currentNode.getStationAlias().equals(start)) { - route.add(0, currentNode); - - if (currentNode.getPreviousEdge() != null) { - if (currentNode.getPreviousNode().getPreviousEdge() == null) { - currentNode.getPreviousNode().setIsTransferPoint(false); - } else { - currentNode.getPreviousNode().setIsTransferPoint(!currentNode.getPreviousEdge().getScheduleId().equals(currentNode.getPreviousNode().getPreviousEdge().getScheduleId())); - } - } - currentNode = currentNode.getPreviousNode(); - } - currentNode.getPreviousNode().setIsTransferPoint(true); - route.add(0, currentNode.getPreviousNode()); - - return route; - } - - private Map generateTrainSchedules() { - return GlobalTrainData.getInstance().getAllTrains().stream().filter(x -> - TrainUtils.isTrainValid(x) && - !globalSettings.isTrainBlacklisted(x) && - !settings.isTrainExcluded(x, globalSettings) - ).collect(Collectors.toMap(x -> x.id, x -> new SimpleTrainSchedule(x))); - } - - public Collection searchTrains(List routeNodes) { - Map schedulesByTrain = generateTrainSchedules(); - Collection routes = new ArrayList<>(); - routes.add(new Route(lastUpdated)); - - int timer = MIN_START_TIME; - final Node[] filteredTransferNodes = routeNodes.stream().filter(x -> x.isTransferPoint()).toArray(Node[]::new); - - if (filteredTransferNodes.length < 2) { - return routes; - } - - - final Node lastTransfer = filteredTransferNodes[0]; - final Map lastTransferByTrain = new HashMap<>(); - final Node lastNode = lastTransfer; - final int simulationTime = timer; - - Collection trainPredictions = GlobalTrainData.getInstance().getDepartingTrainsAt(lastNode.getStationAlias()).stream() - .filter(x -> { - if (globalSettings.isTrainBlacklisted(x.getTrain()) || settings.isTrainExcluded(x.getTrain(), globalSettings)) { - return false; - } - - SimpleTrainSchedule schedule = schedulesByTrain.get(x.getTrain().id); - for (int i = filteredTransferNodes.length - 1; i > 0; i--) { - Node nde = filteredTransferNodes[i]; - final int j = i; - if (nde.getTrainIds().contains(x.getTrain().id)) { - lastTransferByTrain.put(x.getTrain().id, j); - break; - } - } - - boolean b = lastTransferByTrain.containsKey(x.getTrain().id) && - schedule.hasStationAlias(filteredTransferNodes[lastTransferByTrain.get(x.getTrain().id)].getStationAlias()) && - TrainUtils.isTrainValid(x.getTrain()); - - return b; - }).map(x -> { - int t = x.getTicks() + TrainListener.getInstance().getDepartmentTime(level, x.getTrain()); - return schedulesByTrain.get(x.getTrain().id).simulate(x.getTrain(), t > simulationTime ? 0 : simulationTime, lastNode.getStationAlias()); - }).sorted(Comparator.comparingInt(x -> x.getSimulationData().simulationCorrection())).toList(); - - SimulatedTrainSchedule selectedPrediction = trainPredictions.stream().filter(x -> x.isInDirection(lastNode.getStationAlias(), filteredTransferNodes[lastTransferByTrain.get(x.getSimulationData().train().id)].getStationAlias())).findFirst().orElse(trainPredictions.stream().findFirst().orElse(null)); - - if (selectedPrediction == null) { - CreateRailwaysNavigator.LOGGER.warn("Unable to find route from " + lastNode.getStationAlias().getAliasName()); - return routes; - } - - Collection filteredSchedules = trainPredictions.stream().collect(Collectors.toMap(x -> x.getSimulationData().train().id, x -> x, (o, n) -> { - if (n.isInDirection(lastNode.getStationAlias(), filteredTransferNodes[lastTransferByTrain.get(o.getSimulationData().train().id)].getStationAlias())) { - return n; - } else if (o.isInDirection(lastNode.getStationAlias(), filteredTransferNodes[lastTransferByTrain.get(o.getSimulationData().train().id)].getStationAlias())) { - return o; - } - return o.getSimulationData().simulationCorrection() < n.getSimulationData().simulationCorrection() ? o : n; - })).values(); - if (filteredSchedules.isEmpty()) { - filteredSchedules = List.of(selectedPrediction); - } - - for (SimulatedTrainSchedule sched : filteredSchedules) { - Route r = new Route(lastUpdated); - int t = sched.getFirstStopOf(lastNode.getStationAlias()).get().getPrediction().getTicks() + TrainListener.getInstance().getDepartmentTime(level, sched.getSimulationData().train()); - RoutePart part = new RoutePart(level, sched.getSimulationData().train(), lastNode.getStationAlias(), filteredTransferNodes[lastTransferByTrain.get(sched.getSimulationData().train().id)].getStationAlias(), t > simulationTime ? 0 : simulationTime); - r.addPart(part); - timer = part.getEndStation().getPrediction().getTicks() + settings.getTransferTime(); - Set excludedSchedules = new HashSet<>(); - excludedSchedules.add(schedulesByTrain.get(part.getTrain().id)); - - Collection parts = searchTrainsInternal(schedulesByTrain, new HashSet<>(excludedSchedules), filteredTransferNodes, lastTransferByTrain.get(sched.getSimulationData().train().id) + 1, timer, filteredTransferNodes[lastTransferByTrain.get(sched.getSimulationData().train().id)]); - parts.forEach(x -> r.addPart(x)); - routes.add(r); - } - - return routes; - } - - public Collection searchTrainsInternal(Map schedulesByTrain, Set excludedSchedules, Node[] filteredTransferNodes, int startIdx, int timer, Node lastTransfer) { - List routes = new ArrayList<>(); - - final int len = filteredTransferNodes.length; - for (int i = startIdx; i < len; i++) { - Node node = filteredTransferNodes[i]; - - if (lastTransfer != null) { - final Node lastNode = lastTransfer; - final int simulationTime = timer; - - Collection trainPredictions = GlobalTrainData.getInstance().getDepartingTrainsAt(lastNode.getStationAlias()).stream() - .filter(x -> { - if (globalSettings.isTrainBlacklisted(x.getTrain()) || settings.isTrainExcluded(x.getTrain(), globalSettings)) { - return false; - } - - SimpleTrainSchedule schedule = schedulesByTrain.get(x.getTrain().id); - - boolean b = !excludedSchedules.contains(schedule) && - schedule.hasStationAlias(node.getStationAlias()) && - TrainUtils.isTrainValid(x.getTrain()); - return b; - }).map(x -> { - return schedulesByTrain.get(x.getTrain().id).simulate(x.getTrain(), simulationTime, lastNode.getStationAlias()); - }).sorted(Comparator.comparingInt(x -> x.getSimulationData().simulationCorrection())).toList(); - - SimulatedTrainSchedule selectedPrediction = trainPredictions.stream().filter(x -> x.isInDirection(lastNode.getStationAlias(), node.getStationAlias())).findFirst().orElse(trainPredictions.stream().findFirst().orElse(null)); - - if (selectedPrediction == null) { - CreateRailwaysNavigator.LOGGER.warn("Route aborted! No train was found at " + lastNode.getStationAlias().getAliasName()); - return routes; - } - - RoutePart part = new RoutePart(level, selectedPrediction.getSimulationData().train(), lastNode.getStationAlias(), node.getStationAlias(), simulationTime); - routes.add(part); - timer = part.getEndStation().getPrediction().getTicks() + settings.getTransferTime(); - excludedSchedules.add(schedulesByTrain.get(part.getTrain().id)); - } - - lastTransfer = node; - } - - return routes; - } - - - protected Map dijkstra(TrainStationAlias start, boolean avoidTransfers) { - nodesById.values().forEach(x -> x.init()); - Node startNode = nodesByStation.get(start); - startNode.setCost(0); - startNode.setPrevious(startNode, null); - startNode.setIsTransferPoint(true); - - PriorityQueue queue = new PriorityQueue<>(); - Map excludedNodes = new HashMap<>(); - queue.add(startNode); - - while (!queue.isEmpty()) { - Node currentNode = queue.poll(); - Map> reachableNodes = edgesByNode.get(currentNode); - - reachableNodes.entrySet().stream().filter(x -> !excludedNodes.containsKey(x.getKey().getStationAlias())).forEach(y -> { - final Node node = y.getKey(); - y.getValue().forEach(x -> { - Edge edge = x; - boolean isTransfer = currentNode.getPreviousEdge() != null && !currentNode.getPreviousEdge().getScheduleId().equals(edge.getScheduleId()); - - TrainSchedule sched = schedulesById.get(edge.getScheduleId()); - int avgTransferTime = (int)trainIdsBySchedule.get(sched).stream().mapToInt(a -> TrainListener.getInstance().getApproximatedTrainDuration(a)).average().getAsDouble(); - - long newCost = currentNode.getCost() + edge.getCost() + (isTransfer && avoidTransfers ? avgTransferTime + 1000 : 0); - if (newCost > node.getCost()) { - return; - } - - node.setCost(newCost); - node.setPrevious(currentNode, edge); - - queue.add(node); - }); - }); - - excludedNodes.put(currentNode.getStationAlias(), currentNode); - } - - return excludedNodes; - } -} diff --git a/common/src/main/java/de/mrjulsen/crn/core/navigation/Node.java b/common/src/main/java/de/mrjulsen/crn/core/navigation/Node.java deleted file mode 100644 index cba74922..00000000 --- a/common/src/main/java/de/mrjulsen/crn/core/navigation/Node.java +++ /dev/null @@ -1,101 +0,0 @@ -package de.mrjulsen.crn.core.navigation; - -import java.util.HashSet; -import java.util.Objects; -import java.util.Set; -import java.util.UUID; - -import de.mrjulsen.crn.data.TrainStationAlias; - -public class Node implements Comparable { - private TrainStationAlias name; - private final UUID id; - private final Set trainIds = new HashSet<>(); - - // calc - private long cost = Long.MAX_VALUE; - private Node previousNode = null; - private Edge previousEdge = null; - private boolean isTransferPoint = false; - - public Node(TrainStationAlias alias, UUID id) { - this.id = id; - this.name = alias; - } - - public UUID getId() { - return id; - } - - public Set getTrainIds() { - return trainIds; - } - - public void addTrain(UUID id) { - trainIds.add(id); - } - - public TrainStationAlias getStationAlias() { - return name; - } - - public void init() { - this.cost = Long.MAX_VALUE; - this.previousNode = null; - this.previousEdge = null; - this.isTransferPoint = false; - } - - public long getCost() { - return cost; - } - - public void setCost(long cost) { - this.cost = cost; - } - - public Node getPreviousNode() { - return previousNode; - } - - public Edge getPreviousEdge() { - return previousEdge; - } - - public boolean isTransferPoint() { - return isTransferPoint; - } - - public void setPrevious(Node node, Edge viaEdge) { - this.previousNode = node; - this.previousEdge = viaEdge; - } - - public void setIsTransferPoint(boolean b) { - this.isTransferPoint = b; - } - - - @Override - public boolean equals(Object obj) { - if (obj instanceof Node other) { - return getStationAlias().equals(other.getStationAlias()); - } - return false; - } - - @Override - public String toString() { - return String.format("%s (%s) %s", getStationAlias(), getId(), isTransferPoint() ? "(Transfer)" : ""); - } - - @Override - public int hashCode() { - return 37 + Objects.hash(getStationAlias()); - } - - @Override - public int compareTo(Node o) { - return Long.compare(getCost(), o.getCost()); - } -} diff --git a/common/src/main/java/de/mrjulsen/crn/core/navigation/TrainData.java b/common/src/main/java/de/mrjulsen/crn/core/navigation/TrainData.java deleted file mode 100644 index ec2930e6..00000000 --- a/common/src/main/java/de/mrjulsen/crn/core/navigation/TrainData.java +++ /dev/null @@ -1,25 +0,0 @@ -package de.mrjulsen.crn.core.navigation; - -import java.util.UUID; - -import com.simibubi.create.content.trains.entity.Train; - -public class TrainData { - private UUID trainId; - private String name; - - public TrainData(Train train) { - this.trainId = train.id; - this.name = train.name.getString(); - } - - public UUID getId() { - return trainId; - } - - public String getName() { - return name; - } - - -} diff --git a/common/src/main/java/de/mrjulsen/crn/core/navigation/TrainSchedule.java b/common/src/main/java/de/mrjulsen/crn/core/navigation/TrainSchedule.java deleted file mode 100644 index b08e0bda..00000000 --- a/common/src/main/java/de/mrjulsen/crn/core/navigation/TrainSchedule.java +++ /dev/null @@ -1,105 +0,0 @@ -package de.mrjulsen.crn.core.navigation; - -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; -import java.util.Set; -import java.util.UUID; -import java.util.concurrent.ConcurrentHashMap; - -import com.simibubi.create.content.trains.entity.Train; - -import de.mrjulsen.crn.data.GlobalSettings; -import de.mrjulsen.crn.data.GlobalTrainData; -import de.mrjulsen.crn.data.TrainStop; -import de.mrjulsen.crn.event.listeners.TrainListener; - -public class TrainSchedule { - - private final UUID id; - private Set nodes; - private Set edges; - - private List stops; - - public TrainSchedule(Train train, UUID id, GlobalSettings settingsInstance) { - this.id = id; - nodes = ConcurrentHashMap.newKeySet(); - edges = ConcurrentHashMap.newKeySet(); - makeSchedule(train, settingsInstance); - } - - private void makeSchedule(Train train, GlobalSettings settingsInstance) { - this.stops = new ArrayList<>(GlobalTrainData.getInstance().getAllStopsSorted(train).stream().filter(x -> !settingsInstance.isBlacklisted(x.getPrediction().getStationName())).toList()); - } - - public boolean addToGraph(Graph graph, Train train) { - if (stops.isEmpty()) { - return false; - } - - final int cycleDuration = TrainListener.getInstance().getApproximatedTrainDuration(train); - - final int size = stops.size(); - TrainStop lastStop = stops.get(size - 1); - - for (int i = 0; i < size; i++) { - TrainStop stop = stops.get(i); - - int duration = i == 0 ? cycleDuration - lastStop.getPrediction().getTicks() + stop.getPrediction().getTicks() : stop.getPrediction().getTicks() - lastStop.getPrediction().getTicks(); - Node node1 = graph.addNode(lastStop.getStationAlias(), train); - Node node2 = graph.addNode(stop.getStationAlias(), train); - Edge edge = graph.addEdge(node1, node2, getId()).withCost(duration, false); - - nodes.add(node1); - nodes.add(node2); - edges.add(edge); - - lastStop = stop; - } - - return true; - } - - public UUID getId() { - return id; - } - - public Set getNodes() { - return nodes; - } - - public Set getEdges() { - return edges; - } - - @Override - public boolean equals(Object obj) { - if (obj instanceof TrainSchedule other) { - boolean b = getNodes().size() == other.getNodes().size() && getEdges().size() == other.getEdges().size(); - return b && getNodes().containsAll(other.getNodes()) && getEdges().containsAll(other.getEdges()); - } - - return false; - } - - @Override - public int hashCode() { - return 41 * Objects.hash(getEdges(), getNodes()); - } - - public void debugPrint() { - System.out.println(String.format("TRAIN SCHEDULE DETAILS (%s nodes, %s edges)", nodes.size(), edges.size())); - System.out.println("Nodes"); - for (Node node : nodes) { - System.out.println(" -> " + node); - } - - System.out.println("Edges"); - for (Edge edge : edges) { - System.out.println(" -> " + edge); - } - - System.out.println("--- END OF SCHEDULE ---"); - } -} diff --git a/common/src/main/java/de/mrjulsen/crn/data/ClientTrainStationSnapshot.java b/common/src/main/java/de/mrjulsen/crn/data/ClientTrainStationSnapshot.java deleted file mode 100644 index b104669e..00000000 --- a/common/src/main/java/de/mrjulsen/crn/data/ClientTrainStationSnapshot.java +++ /dev/null @@ -1,66 +0,0 @@ -package de.mrjulsen.crn.data; - -import java.util.ArrayList; -import java.util.Collection; - -import de.mrjulsen.crn.CreateRailwaysNavigator; -import de.mrjulsen.crn.network.InstanceManager; -import de.mrjulsen.crn.network.packets.cts.TrackStationsRequestPacket; - -public class ClientTrainStationSnapshot { - private static ClientTrainStationSnapshot instance; - - private final Collection stationNames; - private final Collection trainNames; - - private final int listeningTrainCount; - private final int totalTrainCount; - - private ClientTrainStationSnapshot(Collection stationNames, Collection trainNames, int listeningTrainCount, int totalTrainCount) { - this.stationNames = stationNames; - this.trainNames = trainNames; - this.listeningTrainCount = listeningTrainCount; - this.totalTrainCount = totalTrainCount; - } - - public static ClientTrainStationSnapshot makeNew(Collection stationNames, Collection trainNames, int listeningTrainCount, int totalTrainCount) { - return instance = new ClientTrainStationSnapshot(stationNames, trainNames, listeningTrainCount, totalTrainCount); - } - - public static ClientTrainStationSnapshot getInstance() { - if (instance == null) { - makeNew(new ArrayList<>(), new ArrayList<>(), 0, 0); - } - return instance; - } - - public Collection getAllTrainStations() { - return stationNames; - } - - public Collection getAllTrainNames() { - return trainNames; - } - - public static void syncToClient(Runnable then) { - long id = InstanceManager.registerClientResponseReceievedAction(then); - CreateRailwaysNavigator.net().CHANNEL.sendToServer(new TrackStationsRequestPacket(id)); - } - - - public int getListeningTrainCount() { - return listeningTrainCount; - } - - public int getTrainCount() { - return totalTrainCount; - } - - public int getStationCount() { - return stationNames.size(); - } - - public void dispose() { - instance = null; - } -} diff --git a/common/src/main/java/de/mrjulsen/crn/data/DeparturePrediction.java b/common/src/main/java/de/mrjulsen/crn/data/DeparturePrediction.java deleted file mode 100644 index 68aac1ea..00000000 --- a/common/src/main/java/de/mrjulsen/crn/data/DeparturePrediction.java +++ /dev/null @@ -1,185 +0,0 @@ -package de.mrjulsen.crn.data; - -import java.util.Arrays; -import java.util.Objects; -import java.util.UUID; - -import com.simibubi.create.content.trains.display.GlobalTrainDisplayData.TrainDeparturePrediction; -import com.simibubi.create.content.trains.entity.Train; - -import de.mrjulsen.crn.data.TrainStationAlias.StationInfo; -import de.mrjulsen.crn.event.listeners.TrainListener; -import net.minecraft.nbt.CompoundTag; - -public class DeparturePrediction { - - private Train train; - private int ticks; - private String scheduleTitle; - private String nextStop; - private StationInfo info; - - // Special data - private TrainExitSide exit = TrainExitSide.UNKNOWN; - - private int cycle; - - public DeparturePrediction(Train train, int ticks, String scheduleTitle, String nextStop, int cycle, StationInfo info) { - this.train = train; - this.ticks = ticks; - this.scheduleTitle = scheduleTitle; - this.nextStop = nextStop; - this.cycle = cycle; - this.info = info; - } - - public DeparturePrediction(TrainDeparturePrediction prediction) { - this(prediction.train, prediction.ticks, prediction.scheduleTitle.getString(), prediction.destination, 0, GlobalSettingsManager.getInstance().getSettingsData().getAliasFor(prediction.destination).getInfoForStation(prediction.destination)); - } - - public DeparturePrediction copy() { - return new DeparturePrediction(getTrain(), getTicks(), getScheduleTitle(), getStationName(), getCycle(), getInfo()); - } - - public static DeparturePrediction withNextCycleTicks(DeparturePrediction current) { - int cycle = current.getCycle() + 1; - return new DeparturePrediction(current.getTrain(), (getTrainCycleDuration(current.getTrain()) * cycle) + current.getTicks(), current.getScheduleTitle(), current.getStationName(), cycle, current.getInfo()); - } - - public static DeparturePrediction withCycleTicks(DeparturePrediction current, int cycles) { - return new DeparturePrediction(current.getTrain(), (getTrainCycleDuration(current.getTrain()) * cycles) + current.getTicks(), current.getScheduleTitle(), current.getStationName(), cycles, current.getInfo()); - } - - public Train getTrain() { - return train; - } - - public int getTicks() { - return ticks; - } - - public String getScheduleTitle() { - return scheduleTitle; - } - - public String getStationName() { - return nextStop; - } - - public StationInfo getInfo() { - return info; - } - - public TrainStationAlias getNextStop() { - return GlobalSettingsManager.getInstance().getSettingsData().getAliasFor(nextStop); - } - - public int getCycle() { - return cycle; - } - - public int getTrainCycleDuration() { - return getTrainCycleDuration(getTrain()); - } - - public static int getTrainCycleDuration(Train train) { - return TrainListener.getInstance().getApproximatedTrainDuration(train); - } - - public TrainExitSide getExitSide() { - return this.exit; - } - - public void setExit(TrainExitSide exit) { - this.exit = exit; - } - - public SimpleDeparturePrediction simplify() { - return new SimpleDeparturePrediction(getNextStop().getAliasName().get(), nextStop, getTicks(), getScheduleTitle(), getTrain().name.getString(), getTrain().id, getInfo(), getExitSide()); - } - - - @Override - public boolean equals(Object obj) { - if (obj instanceof DeparturePrediction other) { - return getTrain().id == other.getTrain().id && getTicks() == other.getTicks() && getScheduleTitle().equals(other.getScheduleTitle()) && getStationName().equals(other.getStationName()); - } - - return false; - } - - @Override - public int hashCode() { - return 19 * Objects.hash(getTrain().id, getTicks(), getScheduleTitle(), getStationName()); - } - - public boolean similar(DeparturePrediction other) { - return getTicks() == other.getTicks() && getStationName().equals(other.getStationName()); - } - - @Override - public String toString() { - return String.format("%s, Next stop: %s in %st", getTrain().name.getString(), getNextStop().getAliasName(), getTicks()); - } - - public static enum TrainExitSide { - UNKNOWN((byte)0), - RIGHT((byte)1), - LEFT((byte)-1); - - private byte side; - - TrainExitSide(byte side) { - this.side = side; - } - - public byte getAsByte() { - return side; - } - - public static TrainExitSide getFromByte(byte side) { - return Arrays.stream(values()).filter(x -> x.getAsByte() == side).findFirst().orElse(UNKNOWN); - } - - public TrainExitSide getOpposite() { - return getFromByte((byte)-getAsByte()); - } - } - - public static record SimpleDeparturePrediction(String stationTagName, String stationName, int departureTicks, String scheduleTitle, String trainName, UUID trainId, StationInfo stationInfo, TrainExitSide exitSide) { - - private static final String NBT_STATION = "station"; - private static final String NBT_STATION_NAME = "stationName"; - private static final String NBT_TICKS = "ticks"; - private static final String NBT_SCHEDULE_TITLE = "title"; - private static final String NBT_TRAIN_NAME = "tname"; - private static final String NBT_ID = "id"; - private static final String NBT_EXIT_SIDE = "exit"; - - public CompoundTag toNbt() { - CompoundTag nbt = new CompoundTag(); - nbt.putString(NBT_STATION, stationTagName); - nbt.putString(NBT_STATION_NAME, stationName); - nbt.putInt(NBT_TICKS, departureTicks); - nbt.putString(NBT_SCHEDULE_TITLE, scheduleTitle); - nbt.putString(NBT_TRAIN_NAME, trainName); - nbt.putUUID(NBT_ID, trainId); - stationInfo().writeNbt(nbt); - nbt.putByte(NBT_EXIT_SIDE, exitSide.getAsByte()); - return nbt; - } - - public static SimpleDeparturePrediction fromNbt(CompoundTag nbt) { - return new SimpleDeparturePrediction( - nbt.getString(NBT_STATION), - nbt.getString(NBT_STATION_NAME), - nbt.getInt(NBT_TICKS), - nbt.getString(NBT_SCHEDULE_TITLE), - nbt.getString(NBT_TRAIN_NAME), - nbt.getUUID(NBT_ID), - StationInfo.fromNbt(nbt), - TrainExitSide.getFromByte(nbt.getByte(NBT_EXIT_SIDE)) - ); - } - } -} diff --git a/common/src/main/java/de/mrjulsen/crn/data/EFilterCriteria.java b/common/src/main/java/de/mrjulsen/crn/data/EFilterCriteria.java deleted file mode 100644 index 0919e98c..00000000 --- a/common/src/main/java/de/mrjulsen/crn/data/EFilterCriteria.java +++ /dev/null @@ -1,62 +0,0 @@ -package de.mrjulsen.crn.data; - -import de.mrjulsen.mcdragonlib.core.ITranslatableEnum; -import net.minecraft.util.StringRepresentable; - -public enum EFilterCriteria implements StringRepresentable, ITranslatableEnum { - TRANSFER_COUNT(0, "transfer_count"), - DURATION(1, "duration"), - STOPOVERS(2, "stopovers"); - - private String name; - private int count; - - private EFilterCriteria(int count, String name) { - this.name = name; - this.count = count; - } - - public String getCriteriaName() { - return this.name; - } - - public int getId() { - return this.count; - } - - public static EFilterCriteria getCriteriaById(int count) { - for (EFilterCriteria shape : EFilterCriteria.values()) { - if (shape.getId() == count) { - return shape; - } - } - return EFilterCriteria.TRANSFER_COUNT; - } - - @Override - public String getSerializedName() { - return name; - } - - public static int getDataFromRoute(EFilterCriteria criteria, Route route) { - switch (criteria) { - case DURATION: - return route.getTotalDuration(); - case STOPOVERS: - return route.getStationCount(); - case TRANSFER_COUNT: - default: - return route.getTransferCount(); - } - } - - @Override - public String getEnumName() { - return "filter_criteria"; - } - - @Override - public String getEnumValueName() { - return this.name; - } -} diff --git a/common/src/main/java/de/mrjulsen/crn/data/EResultCount.java b/common/src/main/java/de/mrjulsen/crn/data/EResultCount.java deleted file mode 100644 index 745e5bb5..00000000 --- a/common/src/main/java/de/mrjulsen/crn/data/EResultCount.java +++ /dev/null @@ -1,50 +0,0 @@ -package de.mrjulsen.crn.data; - -import de.mrjulsen.mcdragonlib.core.ITranslatableEnum; -import net.minecraft.util.StringRepresentable; - -public enum EResultCount implements StringRepresentable, ITranslatableEnum { - ALL(0, "all"), - BEST(1, "best"), - FIXED_AMOUNT(2, "fixed_amount"); - - private String name; - private int count; - - private EResultCount(int count, String name) { - this.name = name; - this.count = count; - } - - public String getCriteriaName() { - return this.name; - } - - public int getId() { - return this.count; - } - - public static EResultCount getCriteriaById(int count) { - for (EResultCount shape : EResultCount.values()) { - if (shape.getId() == count) { - return shape; - } - } - return EResultCount.ALL; - } - - @Override - public String getSerializedName() { - return name; - } - - @Override - public String getEnumName() { - return "result_count"; - } - - @Override - public String getEnumValueName() { - return this.name; - } -} diff --git a/common/src/main/java/de/mrjulsen/crn/data/GlobalSettings.java b/common/src/main/java/de/mrjulsen/crn/data/GlobalSettings.java deleted file mode 100644 index 78f8648f..00000000 --- a/common/src/main/java/de/mrjulsen/crn/data/GlobalSettings.java +++ /dev/null @@ -1,358 +0,0 @@ -package de.mrjulsen.crn.data; - -import java.util.Collection; -import java.util.Map; -import java.util.ArrayList; -import java.util.Optional; -import java.util.concurrent.CopyOnWriteArrayList; -import java.util.stream.Collectors; - -import com.simibubi.create.content.trains.entity.Train; -import com.simibubi.create.content.trains.station.GlobalStation; - -import de.mrjulsen.crn.data.TrainStationAlias.StationInfo; -import de.mrjulsen.crn.network.packets.cts.GlobalSettingsUpdatePacket; -import de.mrjulsen.crn.network.packets.cts.GlobalSettingsUpdatePacket.EGlobalSettingsAction; -import net.minecraft.nbt.CompoundTag; -import net.minecraft.nbt.ListTag; -import net.minecraft.nbt.StringTag; -import net.minecraft.nbt.Tag; - -public class GlobalSettings { - - private static final String NBT_ALIAS_REGISTRY = "RegisteredAliasData"; - private static final String NBT_BLACKLIST = "StationBlacklist"; - private static final String NBT_TRAIN_BLACKLIST = "TrainBlacklist"; - private static final String NBT_TRAIN_GROUP_REGISTRY = "RegisteredTrainGroups"; - - private final Collection registeredAlias = new CopyOnWriteArrayList<>(); - private final Collection registeredTrainGroups = new CopyOnWriteArrayList<>(); - private final Collection blacklist = new CopyOnWriteArrayList<>(); - private final Collection trainsBlacklist = new CopyOnWriteArrayList<>(); - - - protected GlobalSettings() { - } - - public CompoundTag toNbt(CompoundTag pCompoundTag) { - if (registeredAlias != null && !registeredAlias.isEmpty()) { - ListTag aliasTag = new ListTag(); - aliasTag.addAll(registeredAlias.stream().map(x -> x.toNbt()).toList()); - pCompoundTag.put(NBT_ALIAS_REGISTRY, aliasTag); - } - - if (registeredTrainGroups != null && !registeredTrainGroups.isEmpty()) { - ListTag aliasTag = new ListTag(); - aliasTag.addAll(registeredTrainGroups.stream().map(x -> x.toNbt()).toList()); - pCompoundTag.put(NBT_TRAIN_GROUP_REGISTRY, aliasTag); - } - - if (blacklist != null && !blacklist.isEmpty()) { - ListTag blacklistTag = new ListTag(); - blacklistTag.addAll(blacklist.stream().map(x -> StringTag.valueOf(x)).toList()); - pCompoundTag.put(NBT_BLACKLIST, blacklistTag); - } - - if (trainsBlacklist != null && !trainsBlacklist.isEmpty()) { - ListTag blacklistTag = new ListTag(); - blacklistTag.addAll(trainsBlacklist.stream().map(x -> StringTag.valueOf(x)).toList()); - pCompoundTag.put(NBT_TRAIN_BLACKLIST, blacklistTag); - } - - return pCompoundTag; - } - - public static GlobalSettings fromNbt(CompoundTag tag) { - Collection aliasData = new ArrayList<>(); - Collection trainGroupData = new ArrayList<>(); - Collection blacklistData = new ArrayList<>(); - Collection trainBlacklistData = new ArrayList<>(); - - if (tag.contains(NBT_ALIAS_REGISTRY)) { - aliasData = tag.getList(NBT_ALIAS_REGISTRY, Tag.TAG_COMPOUND).stream().map(x -> TrainStationAlias.fromNbt((CompoundTag)x)).toList(); - } - - if (tag.contains(NBT_TRAIN_GROUP_REGISTRY)) { - trainGroupData = tag.getList(NBT_TRAIN_GROUP_REGISTRY, Tag.TAG_COMPOUND).stream().map(x -> TrainGroup.fromNbt((CompoundTag)x)).toList(); - } - - if (tag.contains(NBT_BLACKLIST)) { - blacklistData = tag.getList(NBT_BLACKLIST, Tag.TAG_STRING).stream().map(x -> ((StringTag)x).getAsString()).toList(); - } - - if (tag.contains(NBT_TRAIN_BLACKLIST)) { - trainBlacklistData = tag.getList(NBT_TRAIN_BLACKLIST, Tag.TAG_STRING).stream().map(x -> ((StringTag)x).getAsString()).toList(); - } - - GlobalSettings instance = new GlobalSettings(); - instance.registeredAlias.addAll(aliasData); - instance.blacklist.addAll(blacklistData); - instance.trainsBlacklist.addAll(trainBlacklistData); - instance.registeredTrainGroups.addAll(trainGroupData); - - return instance; - } - - - - // Setter methods for client - public boolean registerAlias(TrainStationAlias alias, Runnable then) { - GlobalSettingsUpdatePacket.send(alias, EGlobalSettingsAction.REGISTER_ALIAS, then); - return true; - } - - public boolean updateAlias(AliasName name, TrainStationAlias newData, Runnable then) { - GlobalSettingsUpdatePacket.send(new Object[] { name.get(), newData }, EGlobalSettingsAction.UPDATE_ALIAS, then); - return true; - } - - public boolean unregisterAlias(String name, Runnable then) { - GlobalSettingsUpdatePacket.send(name, EGlobalSettingsAction.UNREGISTER_ALIAS_STRING, then); - return true; - } - - public boolean unregisterAlias(TrainStationAlias alias, Runnable then) { - GlobalSettingsUpdatePacket.send(alias, EGlobalSettingsAction.UNREGISTER_ALIAS, then); - return true; - } - - - - public boolean registerTrainGroup(TrainGroup group, Runnable then) { - GlobalSettingsUpdatePacket.send(group, EGlobalSettingsAction.REGISTER_TRAIN_GROUP, then); - return true; - } - - public boolean updateTrainGroup(String name, TrainGroup newData, Runnable then) { - GlobalSettingsUpdatePacket.send(new Object[] { name, newData }, EGlobalSettingsAction.UPDATE_TRAIN_GROUP, then); - return true; - } - - public boolean unregisterTrainGroupTrain(String trainName, Runnable then) { - GlobalSettingsUpdatePacket.send(trainName, EGlobalSettingsAction.UNREGISTER_TRAIN_GROUP_TRAIN, then); - return true; - } - - public boolean unregisterTrainGroup(TrainGroup group, Runnable then) { - GlobalSettingsUpdatePacket.send(group, EGlobalSettingsAction.UNREGISTER_TRAIN_GROUP, then); - return true; - } - - - - public boolean addToBlacklist(String station, Runnable then) { - GlobalSettingsUpdatePacket.send(station, EGlobalSettingsAction.ADD_TO_BLACKLIST, then); - return true; - } - - public boolean removeFromBlacklist(String name, Runnable then) { - GlobalSettingsUpdatePacket.send(name, EGlobalSettingsAction.REMOVE_FROM_BLACKLIST, then); - return true; - } - - public boolean addTrainToBlacklist(String trainName, Runnable then) { - GlobalSettingsUpdatePacket.send(trainName, EGlobalSettingsAction.ADD_TRAIN_TO_BLACKLIST, then); - return true; - } - - public boolean removeTrainFromBlacklist(String name, Runnable then) { - GlobalSettingsUpdatePacket.send(name, EGlobalSettingsAction.REMOVE_TRAIN_FROM_BLACKLIST, then); - return true; - } - - - public boolean registerAliasForStationNames(String name, Collection stations, Runnable then) { - //TODO - return registerAlias(new TrainStationAlias(AliasName.of(name), stations.stream().collect(Collectors.toMap(x -> x, x -> StationInfo.empty()))), then); - } - - public boolean registerAlias(String name, Collection stations, Runnable then) { - //TODO - return registerAlias(new TrainStationAlias(AliasName.of(name), stations.stream().collect(Collectors.toMap(x -> x.name, x -> StationInfo.empty()))), then); - } - - - // Setter methods for server - public boolean registerAliasServer(TrainStationAlias alias) { - if (!registeredAlias.contains(alias)) { - registeredAlias.add(alias); - return true; - } - return false; - } - - public boolean updateAliasServer(AliasName name, TrainStationAlias newData) { - if (!registeredAlias.stream().anyMatch(x -> x.getAliasName().equals(name))) { - return false; - } - registeredAlias.stream().filter(x -> x.getAliasName().equals(name)).forEach(x -> x.update(newData)); - return true; - } - - public boolean unregisterAliasServer(String name) { - boolean b = registeredAlias.removeIf(x -> compareAliasAndString(x, name)); - return b; - } - - public boolean unregisterAliasServer(TrainStationAlias alias) { - boolean b = registeredAlias.removeIf(x -> x.equals(alias)); - return b; - } - - public boolean registerAliasForStationNamesServer(String name, Collection stations) { - //TODO - return registerAliasServer(new TrainStationAlias(AliasName.of(name), stations.stream().collect(Collectors.toMap(x -> x, x -> StationInfo.empty())))); - } - - public boolean registerAliasServer(String name, Collection stations) { - //TODO - return registerAliasServer(new TrainStationAlias(AliasName.of(name), stations.stream().collect(Collectors.toMap(x -> x.name, x -> StationInfo.empty())))); - } - - - - public boolean registerTrainGroupServer(TrainGroup group) { - if (!registeredTrainGroups.contains(group)) { - registeredTrainGroups.add(group); - return true; - } - return false; - } - - public boolean updateTrainGroupServer(String name, TrainGroup newData) { - if (!registeredTrainGroups.stream().anyMatch(x -> x.getGroupName().equals(name))) { - return false; - } - registeredTrainGroups.stream().filter(x -> x.getGroupName().equals(name)).forEach(x -> x.update(newData)); - return true; - } - - public boolean unregisterTrainGroupServer(String name) { - boolean b = registeredTrainGroups.removeIf(x -> x.getGroupName().equals(name)); - return b; - } - - public boolean unregisterAliasServer(TrainGroup group) { - boolean b = registeredTrainGroups.removeIf(x -> x.equals(group)); - return b; - } - - - public boolean addToBlacklistServer(String station) { - if (!blacklist.contains(station)) { - blacklist.add(station); - return true; - } - return false; - } - - public boolean removeFromBlacklistServer(String name) { - boolean b = blacklist.removeIf(x -> x.equals(name)); - return b; - } - - public boolean addTrainToBlacklistServer(String trainName) { - if (!trainsBlacklist.contains(trainName)) { - trainsBlacklist.add(trainName); - return true; - } - return false; - } - - public boolean removeTrainFromBlacklistServer(String trainName) { - boolean b = trainsBlacklist.removeIf(x -> x.equals(trainName)); - return b; - } - - - //### Getters and testers - // tags - public boolean isAliasRegistered(String stationName) { - return registeredAlias.stream().anyMatch(x -> x.contains(stationName)); - } - - public boolean isAliasRegistered(GlobalStation station) { - return isAliasRegistered(station.name); - } - - private TrainStationAlias getOrCreateAliasFor(String stationName) { - if (stationName.contains("*")) { - return getOrCreateAliasForWildcard(stationName); - } - - Optional a = registeredAlias.stream().filter(x -> x.contains(stationName)).findFirst(); - if (a.isPresent()) { - return a.get(); - } - - return new TrainStationAlias(AliasName.of(stationName), Map.of(stationName, StationInfo.empty())); - } - - private TrainStationAlias getOrCreateAliasForWildcard(String stationName) { - String regex = stationName.isBlank() ? stationName : "\\Q" + stationName.replace("*", "\\E.*\\Q") + "\\E"; - Optional a = registeredAlias.stream().filter(x -> x.getAllStationNames().stream().anyMatch(y -> y.matches(regex))).findFirst(); - if (a.isPresent()) { - return a.get(); - } - - return new TrainStationAlias(AliasName.of(stationName), Map.of(stationName, StationInfo.empty())); - - } - - private Optional getAlias(String stationName) { - Optional a = registeredAlias.stream().filter(x -> x.getAliasName().equals(AliasName.of(stationName))).findFirst(); - return a; - } - - public Collection getAliasList() { - return registeredAlias; - } - - public Collection getTrainGroupsList() { - return registeredTrainGroups; - } - - public TrainStationAlias getAliasFor(String stationName) { - Optional a = getAlias(stationName); - - if (!a.isPresent()) { - return getOrCreateAliasFor(stationName); - } - - return a.get(); - } - - private boolean compareAliasAndString(TrainStationAlias alias, String name) { - return alias.getAliasName().get().equals(name); - } - - - - // station blacklist - public boolean isBlacklisted(String stationName) { - return blacklist.stream().anyMatch(x -> x.equals(stationName)); - } - - public boolean isBlacklisted(TrainStationAlias station) { - return station.getAllStationNames().stream().allMatch(x -> isBlacklisted(x)); - } - - public Collection getBlacklist() { - return blacklist; - } - - - // train blacklist - public boolean isTrainBlacklisted(Train train) { - return trainsBlacklist.stream().anyMatch(x -> x.equals(train.name.getString())); - } - - public boolean isTrainBlacklisted(String trainName) { - return trainsBlacklist.stream().anyMatch(x -> x.equals(trainName)); - } - - public Collection getTrainBlacklist() { - return trainsBlacklist; - } - -} diff --git a/common/src/main/java/de/mrjulsen/crn/data/GlobalSettingsManager.java b/common/src/main/java/de/mrjulsen/crn/data/GlobalSettingsManager.java deleted file mode 100644 index 7115758f..00000000 --- a/common/src/main/java/de/mrjulsen/crn/data/GlobalSettingsManager.java +++ /dev/null @@ -1,82 +0,0 @@ -package de.mrjulsen.crn.data; - -import de.mrjulsen.crn.CRNPlatformSpecific; -import de.mrjulsen.crn.CreateRailwaysNavigator; -import de.mrjulsen.crn.network.InstanceManager; -import de.mrjulsen.crn.network.packets.cts.GlobalSettingsRequestPacket; -import net.minecraft.nbt.CompoundTag; -import net.minecraft.server.MinecraftServer; -import net.minecraft.server.level.ServerLevel; -import net.minecraft.world.level.saveddata.SavedData; - -public class GlobalSettingsManager extends SavedData { - - private static final String FILE_ID = CreateRailwaysNavigator.MOD_ID + "_global_settings"; - private static volatile GlobalSettingsManager instance; - - private GlobalSettings settingsData; - - private GlobalSettingsManager() { - settingsData = new GlobalSettings(); - CreateRailwaysNavigator.LOGGER.info("Created new Create Railways Navigator settings instance."); - } - - private GlobalSettingsManager(GlobalSettings settings) { - instance = this; - settingsData = settings; - } - - - public static GlobalSettingsManager createClientInstance() { - if (instance == null) { - instance = new GlobalSettingsManager(); - } - return instance; - } - - public static GlobalSettingsManager getInstance() { - if (instance == null) { - MinecraftServer server = CRNPlatformSpecific.getServer(); - if (server == null) { - // execute on client - instance = new GlobalSettingsManager(); - } else { - // execute on server - CreateRailwaysNavigator.LOGGER.debug("Create Instance"); - ServerLevel level = server.overworld(); - instance = level.getDataStorage().computeIfAbsent(GlobalSettingsManager::load, GlobalSettingsManager::new, FILE_ID); - } - } - return instance; - } - - @Override - public CompoundTag save(CompoundTag pCompoundTag) { - return settingsData.toNbt(pCompoundTag); - } - - private static GlobalSettingsManager load(CompoundTag tag) { - GlobalSettings settings = GlobalSettings.fromNbt(tag); - return new GlobalSettingsManager(settings); - - } - - public final GlobalSettings getSettingsData() { - return settingsData; - } - - public void updateSettingsData(GlobalSettings settings) { - this.settingsData = settings; - setDirty(); - } - - public static void syncToClient(Runnable then) { - long id = InstanceManager.registerClientResponseReceievedAction(then); - CreateRailwaysNavigator.net().CHANNEL.sendToServer(new GlobalSettingsRequestPacket(id)); - } - - public static void close() { - instance = null; - CreateRailwaysNavigator.LOGGER.info("Closed current Create Railways Navigator settings instance."); - } -} diff --git a/common/src/main/java/de/mrjulsen/crn/data/GlobalTrainData.java b/common/src/main/java/de/mrjulsen/crn/data/GlobalTrainData.java deleted file mode 100644 index dce7e60e..00000000 --- a/common/src/main/java/de/mrjulsen/crn/data/GlobalTrainData.java +++ /dev/null @@ -1,268 +0,0 @@ -package de.mrjulsen.crn.data; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; -import java.util.Map; -import java.util.HashMap; -import java.util.Optional; -import java.util.Set; -import java.util.UUID; -import java.util.stream.Collectors; - -import com.simibubi.create.content.trains.entity.Train; -import com.simibubi.create.content.trains.station.GlobalStation; - -import de.mrjulsen.crn.util.TrainUtils; - -public class GlobalTrainData { - - private final Collection trains; - private final Map> stationByName; - private final Map> aliasPredictions = new HashMap<>(); - private final Map> trainPredictions = new HashMap<>(); - private final long updateTime; - - private static GlobalTrainData instance = null; - - private GlobalTrainData(long updateTime) { - instance = this; - trains = TrainUtils.getAllTrains(); - - stationByName = TrainUtils.getAllStations().stream().collect(Collectors.groupingBy(x -> x.name, Collectors.toSet())); - /* - for (GlobalStation sta : TrainUtils.getAllStations()) { - stationByName.computeIfAbsent(sta.name, x -> new HashSet<>()).add(sta); - if (stationByName.containsKey(sta.name)) { - stationByName.get(sta.name).add(sta); - } else { - stationByName.put(sta.name, new HashSet<>(Set.of(sta))); - } - } - */ - TrainUtils.getMappedDeparturePredictions(aliasPredictions, trainPredictions); - this.updateTime = updateTime; - } - - public static GlobalTrainData makeSnapshot(long updateTime) { - return new GlobalTrainData(updateTime); - } - - public static GlobalTrainData getInstance() { - return instance; - } - - public long getUpdateTime() { - return updateTime; - } - - - public boolean stationHasDepartingTrains(TrainStationAlias alias) { - return aliasPredictions.containsKey(alias.getAliasName().get()); - } - - public final Collection getAllTrains() { - return trains; - } - - public final Set getAllStations() { - return stationByName.keySet(); - } - - public final Set getStationData(String stationName) { - return stationByName.get(stationName); - } - - - - - public Collection getPredictionsOfTrain(Train train) { - return trainPredictions.get(train.id).stream().filter(x -> !GlobalSettingsManager.getInstance().getSettingsData().isBlacklisted(x.getStationName())).toList(); - } - - public Collection getPredictionsOfTrainChronologically(Train train) { - return getPredictionsOfTrain(train).parallelStream().sorted(Comparator.comparingInt(x -> x.getTicks())).toList(); - } - - public Optional getNextStop(Train train) { - return getPredictionsOfTrainChronologically(train).stream().findFirst(); - } - - - public boolean trainStopsAt(Train train, TrainStationAlias station) { - return getPredictionsOfTrain(train).parallelStream().anyMatch(x -> x.getNextStop().equals(station)); - } - - - public Collection getTrainStopDataAt(Train train, TrainStationAlias station) { - Collection predictions = getPredictionsOfTrain(train); - return predictions.parallelStream().filter(x -> x.getNextStop().equals(station)).toList(); - } - - public Collection getSortedTrainStopDataAt(Train train, TrainStationAlias station) { - return getTrainStopDataAt(train, station).parallelStream().sorted(Comparator.comparingInt(x -> x.getTicks())).toList(); - } - - public Optional getNextTrainStopDataAt(Train train, TrainStationAlias station) { - return getTrainStopDataAt(train, station).stream().findFirst(); - } - - public Collection getAllStops(Train train) { - return getPredictionsOfTrain(train).parallelStream().map(x -> new TrainStop(x.getNextStop(), x)).toList(); - } - - public List getAllStopsSorted(Train train) { - return getAllStops(train).parallelStream().sorted(Comparator.comparingInt(x -> x.getPrediction().getTicks())).toList(); - } - - public SimpleTrainSchedule getTrainSimpleSchedule(Train train) { - return new SimpleTrainSchedule(train); - } - - public List getAllStopoversOfTrainSortedNew(Train train, TrainStationAlias start, TrainStationAlias end, boolean includeStartEnd, boolean correctStart) { - Collection stops = getAllStopsFrom(train, start, false, true).getAllStops(); - - if (stops.parallelStream().noneMatch(x -> x.isStationAlias(start) || x.isStationAlias(end))) { - return new ArrayList<>(); - } - - DeparturePrediction startPrediction = null; - DeparturePrediction endPrediction = null; - int ticksStart = -1; - int ticksStop = -1; - - Collection startStopDatas = getSortedTrainStopDataAt(train, start); - Collection endStopDatas = getSortedTrainStopDataAt(train, end); - - Optional firstStartData = startStopDatas.stream().findFirst(); - - if (firstStartData.isPresent()) { - ticksStart = firstStartData.get().getTicks(); - } - final int ftmpTicksStart = ticksStart; - - if (endStopDatas != null && endStopDatas.size() > 0) { - // Die erste Endstation, die nach der ersten Startstation kommt. - Optional endStopData = endStopDatas.stream() - .filter(x -> x.getTicks() >= ftmpTicksStart) - .findFirst(); - - if (endStopData.isPresent()) { - ticksStop = (endPrediction = endStopData.get()).getTicks(); - } else { - endStopData = endStopDatas.stream().findFirst(); - if (endStopData.isPresent()) { - ticksStop = (endPrediction = endStopData.get()).getTicks(); - } - } - } - final int fTicksStop = ticksStop; - - if (startStopDatas != null && startStopDatas.size() > 0) { - DeparturePrediction startStopData = correctStart ? - startStopDatas.stream() - .filter(x -> x.getTicks() <= fTicksStop) - .reduce((a, b) -> b).orElse(null) - : startPrediction; - - if (startStopData != null) { - ticksStart = (startPrediction = startStopData).getTicks(); - } else { - startStopData = startStopDatas.stream().reduce((a, b) -> b).orElse(null); - if (startStopData != null) { - ticksStart = (startPrediction = startStopData).getTicks(); - } - } - } - - if (correctStart) { - - } - final int fTicksStart = ticksStart; - - List filteredStops = new ArrayList<>(); - if (fTicksStart <= fTicksStop) { - filteredStops.addAll(stops.stream().filter(x -> (x.getPrediction().getTicks() > fTicksStart && x.getPrediction().getTicks() < fTicksStop)).toList()); - } else { - filteredStops.addAll(stops.stream().filter(x -> (x.getPrediction().getTicks() < fTicksStop || x.getPrediction().getTicks() > fTicksStart)).map(x -> { - if (x.getPrediction().getTicks() < fTicksStop) { - return new TrainStop(x.getStationAlias(), DeparturePrediction.withNextCycleTicks(x.getPrediction())); - } - return x; - }).toList()); - } - - if (includeStartEnd) { - filteredStops.add(new TrainStop(end, fTicksStop < fTicksStart ? DeparturePrediction.withNextCycleTicks(endPrediction) : endPrediction)); - filteredStops.add(0, new TrainStop(start, startPrediction)); - } - - return filteredStops; - } - - // TODO unused - /** - * Creates a {@code SimpleTrainSchedule} which contains all stations the train will arrive. - * @param train The train of this schedule. - * @param alias The station alias to start at. This is the first stop in the schedule. - * @param preventDuplicates If {@code true}, every station exists once. - * @param noLoop If {@code true}, the schedule will continue beyond the last stop. - * @return A new {@code SimpleTrainSchedule} - */ - public SimpleTrainSchedule getAllStopsFrom(Train train, TrainStationAlias alias, boolean preventDuplicates, boolean loop) { - List newList = new ArrayList<>(); - int idx = 0; - for (TrainStop stop : getAllStopsSorted(train)) { - if (preventDuplicates && newList.contains(stop)) { - if (loop) { - continue; - } else { - break; - } - } - - if (stop.getStationAlias().equals(alias)) { - idx = 0; - } - - newList.add(idx, stop); - idx++; - } - return SimpleTrainSchedule.of(newList); - } - - - public SimpleTrainSchedule getDirectionalSchedule(Train train) { - List newList = new ArrayList<>(); - boolean isRepeating = false; - for (TrainStop stop : getAllStopsSorted(train)) { - if (newList.contains(stop)) { - isRepeating = true; - continue; - } - - if (isRepeating) { - newList.add(0, stop); - } else { - newList.add(stop); - } - } - return SimpleTrainSchedule.of(newList); - } - - public Collection getDepartingTrainsAt(TrainStationAlias station) { - return aliasPredictions.getOrDefault(station.getAliasName().get(), Collections.emptyList()).parallelStream().sorted(Comparator.comparingInt(x -> x.getTicks())).toList(); - } - - public Optional getNextDepartingTrainAt(TrainStationAlias station) { - Collection predictions = getDepartingTrainsAt(station); - - if (predictions == null || predictions.isEmpty()) { - return Optional.empty(); - } - - return predictions.stream().findFirst(); - } -} diff --git a/common/src/main/java/de/mrjulsen/crn/data/ISaveableNavigatorData.java b/common/src/main/java/de/mrjulsen/crn/data/ISaveableNavigatorData.java new file mode 100644 index 00000000..781efc63 --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/data/ISaveableNavigatorData.java @@ -0,0 +1,28 @@ +package de.mrjulsen.crn.data; + +import java.util.List; + +import de.mrjulsen.mcdragonlib.DragonLib; +import de.mrjulsen.mcdragonlib.client.render.Sprite; +import de.mrjulsen.mcdragonlib.data.Pair; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.MutableComponent; + +public interface ISaveableNavigatorData { + + /** All lines with information to be displayed in the overview. */ + List getOverviewData(); + /** Content of the title line. */ + SaveableNavigatorDataLine getTitle(); + /** The value (usually the time at which the corresponding entry is relevant) by which the items are sorted and grouped. */ + long timeOrderValue(); + default long dayOrderValue() { + return (timeOrderValue() + DragonLib.DAYTIME_SHIFT) / DragonLib.TICKS_PER_DAY; + } + /** Custom value used for grouping with custom label. Default: {@code null} (grouped by time) */ + default Pair customGroup() { + return null; + } + + public static record SaveableNavigatorDataLine(Component text, Sprite icon) {} +} diff --git a/common/src/main/java/de/mrjulsen/crn/data/ITranslatableEnum.java b/common/src/main/java/de/mrjulsen/crn/data/ITranslatableEnum.java deleted file mode 100644 index 4752e76f..00000000 --- a/common/src/main/java/de/mrjulsen/crn/data/ITranslatableEnum.java +++ /dev/null @@ -1,18 +0,0 @@ -package de.mrjulsen.crn.data; - -import de.mrjulsen.crn.CreateRailwaysNavigator; - -public interface ITranslatableEnum { - String getNameOfEnum(); - String getValue(); - - default String getTranslationKey() { - return String.format("gui.%s.%s.%s", CreateRailwaysNavigator.MOD_ID, this.getNameOfEnum(), this.getValue()); - } - default String getDescriptionTranslationKey() { - return String.format("gui.%s.%s.description", CreateRailwaysNavigator.MOD_ID, this.getNameOfEnum()); - } - default String getInfoTranslationKey() { - return String.format("gui.%s.%s.info.%s", CreateRailwaysNavigator.MOD_ID, this.getNameOfEnum(), this.getValue()); - } -} diff --git a/common/src/main/java/de/mrjulsen/crn/data/NearestTrackStationResult.java b/common/src/main/java/de/mrjulsen/crn/data/NearestTrackStationResult.java index 4de841f8..5c979c42 100644 --- a/common/src/main/java/de/mrjulsen/crn/data/NearestTrackStationResult.java +++ b/common/src/main/java/de/mrjulsen/crn/data/NearestTrackStationResult.java @@ -4,40 +4,42 @@ import com.simibubi.create.content.trains.station.GlobalStation; -import net.minecraft.network.FriendlyByteBuf; +import de.mrjulsen.crn.data.storage.GlobalSettings; +import net.minecraft.nbt.CompoundTag; public class NearestTrackStationResult { + + public static final String NBT_DISTANCE = "Distance"; + public static final String NBT_TAG = "TagName"; + + public final double distance; - public final Optional aliasName; + public final Optional tagName; public NearestTrackStationResult(Optional station, double distance) { - this(station.isPresent() ? GlobalSettingsManager.getInstance().getSettingsData().getAliasFor(station.get().name) : null, distance); + this(station.isPresent() ? GlobalSettings.getInstance().getOrCreateStationTagFor(station.get().name).getTagName() : null, distance); } - private NearestTrackStationResult(TrainStationAlias station, double distance) { + private NearestTrackStationResult(TagName tagName, double distance) { this.distance = distance; - this.aliasName = station != null ? Optional.of(station) : Optional.empty(); + this.tagName = Optional.ofNullable(tagName); } public static NearestTrackStationResult empty() { return new NearestTrackStationResult(Optional.empty(), 0); } - public void serialize(FriendlyByteBuf buffer) { - buffer.writeBoolean(aliasName.isPresent()); - if (aliasName.isPresent()) { - buffer.writeNbt(aliasName.get().toNbt()); - } - buffer.writeDouble(distance); + public CompoundTag toNbt() { + CompoundTag nbt = new CompoundTag(); + nbt.putDouble(NBT_DISTANCE, distance); + tagName.ifPresent(x -> nbt.putString(NBT_TAG, tagName.get().get())); + return nbt; } - public static NearestTrackStationResult deserialize(FriendlyByteBuf buffer) { - boolean valid = buffer.readBoolean(); - TrainStationAlias alias = null; - if (valid) { - alias = TrainStationAlias.fromNbt(buffer.readNbt()); - } - double distance = buffer.readDouble(); - return new NearestTrackStationResult(alias, distance); + public static NearestTrackStationResult fromNbt(CompoundTag nbt) { + return new NearestTrackStationResult( + nbt.contains(NBT_TAG) ? TagName.of(nbt.getString(NBT_TAG)) : null, + nbt.getDouble(NBT_DISTANCE) + ); } } diff --git a/common/src/main/java/de/mrjulsen/crn/data/Route.java b/common/src/main/java/de/mrjulsen/crn/data/Route.java deleted file mode 100644 index 4dd6a081..00000000 --- a/common/src/main/java/de/mrjulsen/crn/data/Route.java +++ /dev/null @@ -1,101 +0,0 @@ -package de.mrjulsen.crn.data; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; - -public class Route { - private Collection parts = new ArrayList<>(); - private final long refreshTime; - - public Route(Collection initialValues, long refreshTime) { - this(refreshTime); - this.parts.addAll(initialValues); - } - - public Route(long refreshTime) { - this.refreshTime = refreshTime; - } - - public void addPart(RoutePart part) { - if (!contains(part)) { - parts.add(part); - } - } - - public boolean isEmpty() { - return parts.size() <= 0; - } - - public long getRefreshTime() { - return refreshTime; - } - - public boolean contains(RoutePart part) { - return parts.contains(part); - } - - public Collection getParts() { - return parts; - } - - public int getStationCount() { - return parts.stream().mapToInt(x -> x.getStationCount(false)).sum() + parts.size() + 1; - } - - public int getTransferCount() { - return getParts().size() - 1; - } - - public int getTotalDuration() { - return getEndStation().getPrediction().getTicks() - getStartStation().getPrediction().getTicks(); - } - - public TrainStop getStartStation() { - return parts.stream().findFirst().get().getStartStation(); - } - - public TrainStop getEndStation() { - return parts.stream().reduce((a, b) -> b).get().getEndStation(); - } - - @Override - public boolean equals(Object obj) { - if (obj instanceof Route other) { - return getParts().size() == other.getParts().size() && getParts().stream().allMatch(x -> other.getParts().stream().anyMatch(y -> y.equals(x))); - } - return false; - } - - public boolean exactEquals(Object obj, boolean respectOrder, boolean respectTrains) { - if (obj instanceof Route other) { - if (getParts().size() != other.getParts().size()) { - return false; - } - - RoutePart[] a = getParts().toArray(RoutePart[]::new); - RoutePart[] b = other.getParts().toArray(RoutePart[]::new); - - for (int i = 0; i < a.length; i++) { - if ((respectOrder && !a[i].exactEquals(b[i], respectTrains)) || (!respectOrder && !a[i].equals(b[i]))) { - return false; - } - } - return true; - } - return false; - } - - public String toName() { - return String.format("%s - %s", - parts.stream().findFirst().get().getStartStation().getStationAlias().getAliasName(), - parts.stream().reduce((a, b) -> b).get().getEndStation().getStationAlias().getAliasName() - ); - } - - @Override - public String toString() { - return Arrays.toString(getParts().toArray()); - } - -} diff --git a/common/src/main/java/de/mrjulsen/crn/data/RoutePart.java b/common/src/main/java/de/mrjulsen/crn/data/RoutePart.java deleted file mode 100644 index c9787e3c..00000000 --- a/common/src/main/java/de/mrjulsen/crn/data/RoutePart.java +++ /dev/null @@ -1,126 +0,0 @@ -package de.mrjulsen.crn.data; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Optional; -import java.util.List; - -import com.simibubi.create.content.trains.entity.Train; - -import net.minecraft.world.level.Level; - -public class RoutePart { - private Train train; - private TrainStop start; - private TrainStop end; - private Collection stops; - - public RoutePart(Level level, Train train, TrainStationAlias start, TrainStationAlias end, int startTicks) { - this.train = train; - List stops = new ArrayList<>(GlobalTrainData.getInstance().getAllStopoversOfTrainSortedNew(train, start, end, true, true)); - - TrainStop startStop = stops.get(0); - if (startStop.getPrediction().getTicks() < startTicks && startStop.getPrediction().getTrainCycleDuration() > 0) { - int diffTicks = startTicks - startStop.getPrediction().getTicks(); - int mul = diffTicks / startStop.getPrediction().getTrainCycleDuration() + 1; - - stops = new ArrayList<>(stops.stream().map(x -> new TrainStop(x.getStationAlias(), DeparturePrediction.withCycleTicks(x.getPrediction(), mul))).toList()); - } - - this.end = stops.remove(stops.size() - 1); - this.start = stops.remove(0); - - this.stops = stops; - } - - public Train getTrain() { - return train; - } - - public TrainStop getStartStation() { - return start; - } - - public TrainStop getEndStation() { - return end; - } - - public Collection getStopovers() { - return stops; - } - - public int arrivesAtStartStationIn() { - Optional data = GlobalTrainData.getInstance().getNextTrainStopDataAt(getTrain(), getStartStation().getStationAlias()); - if (data.isPresent()) { - return data.get().getTicks(); - } - return 0; - } - - public int arrivesAtEndStationIn() { - Optional data = GlobalTrainData.getInstance().getNextTrainStopDataAt(getTrain(), getEndStation().getStationAlias()); - if (data.isPresent()) { - return data.get().getTicks(); - } - return 0; - } - - public int getStationCount(boolean includeStartEnd) { - return getStopovers().size() + (includeStartEnd ? 2 : 0); - } - - @Override - public boolean equals(Object obj) { - if (obj instanceof RoutePart other) { - return getStartStation().equals(other.getStartStation()) && - getEndStation().equals(other.getEndStation()) && - getStopovers().size() == other.getStopovers().size() && - getStopovers().stream().allMatch(x -> other.getStopovers().stream().anyMatch(y -> y.equals(x))); - } - return false; - } - - public boolean exactEquals(Object obj, boolean respectTrains) { - if (obj instanceof RoutePart other) { - if (!getStartStation().equals(other.getStartStation()) || - !getEndStation().equals(other.getEndStation()) || - getStopovers().size() != other.getStopovers().size()) - return false; - - TrainStop[] a = getStopovers().toArray(TrainStop[]::new); - TrainStop[] b = other.getStopovers().toArray(TrainStop[]::new); - - for (int i = 0; i < a.length; i++) { - if (!a[i].equals(b[i])) - return false; - } - return !respectTrains || train.equals(other.train); - } - return false; - } - - @Override - public String toString() { - return String.format("%s, From: %s (%s), To: %s (%s), via: %s", - getTrain().name.getString(), - getStartStation().getStationAlias().getAliasName(), - arrivesAtStartStationIn(), - getEndStation().getStationAlias().getAliasName(), - arrivesAtEndStationIn(), - Arrays.toString(stops.toArray()) - ); - - - } - - public String getString() { - return String.format("Train: %s, From: %s in %st, To: %s in %s, Stops: %s", - getTrain().name.getString(), - getStartStation().getStationAlias().getAliasName(), - arrivesAtStartStationIn(), - getEndStation().getStationAlias().getAliasName(), - arrivesAtEndStationIn(), - Arrays.toString(stops.toArray())); - } -} diff --git a/common/src/main/java/de/mrjulsen/crn/data/SavedRoutesManager.java b/common/src/main/java/de/mrjulsen/crn/data/SavedRoutesManager.java new file mode 100644 index 00000000..55bc325e --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/data/SavedRoutesManager.java @@ -0,0 +1,76 @@ +package de.mrjulsen.crn.data; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import de.mrjulsen.crn.data.navigation.ClientRoute; +import de.mrjulsen.crn.registry.ModAccessorTypes; +import de.mrjulsen.mcdragonlib.data.Single.MutableSingle; +import de.mrjulsen.mcdragonlib.util.DLUtils; +import de.mrjulsen.mcdragonlib.util.accessor.DataAccessor; +import net.minecraft.client.Minecraft; +import net.minecraft.nbt.CompoundTag; + +public final class SavedRoutesManager { + private static final LinkedHashSet savedRoutes = new LinkedHashSet<>(); + private static MutableSingle isSynchronizing = new MutableSingle(false); + + public static void saveRoute(ClientRoute route) { + route.addListener(); + savedRoutes.add(route); + } + + public static void removeRoute(ClientRoute route) { + route.close(); + savedRoutes.remove(route); + } + + public static void removeAllRoutes() { + savedRoutes.forEach(x -> x.closeAll()); + savedRoutes.clear(); + } + + public static boolean isSaved(ClientRoute route) { + return savedRoutes.contains(route); + } + + public static List getAllSavedRoutes() { + return new ArrayList<>(savedRoutes); + } + + @SuppressWarnings("resource") + public static void push(boolean clear, Runnable andThen) { + isSynchronizing.setFirst(true); + DataAccessor.getFromServer(Minecraft.getInstance().player.getUUID(), ModAccessorTypes.GET_USER_SETTINGS, (settings) -> { + Set currentValue = clear ? new HashSet<>() : settings.savedRoutes.getValue(); + currentValue.addAll(savedRoutes.stream().map(x -> x.toNbt()).toList()); + settings.savedRoutes.setValue(currentValue); + settings.clientSave(() -> { + isSynchronizing.setFirst(false); + DLUtils.doIfNotNull(andThen, Runnable::run); + }); + }); + } + + @SuppressWarnings("resource") + public static void pull(boolean clear, Runnable andThen) { + isSynchronizing.setFirst(true); + DataAccessor.getFromServer(Minecraft.getInstance().player.getUUID(), ModAccessorTypes.GET_USER_SETTINGS, (settings) -> { + Set currentValue = settings.savedRoutes.getValue().stream().map(x -> ClientRoute.fromNbt(x, true)).collect(Collectors.toSet()); + if (clear) { + savedRoutes.clear(); + } + savedRoutes.addAll(currentValue); + isSynchronizing.setFirst(false); + DLUtils.doIfNotNull(andThen, Runnable::run); + }); + } + + public static boolean isSynchronizing() { + return isSynchronizing.getFirst(); + } +} diff --git a/common/src/main/java/de/mrjulsen/crn/data/SimpleRoute.java b/common/src/main/java/de/mrjulsen/crn/data/SimpleRoute.java deleted file mode 100644 index 05f2d764..00000000 --- a/common/src/main/java/de/mrjulsen/crn/data/SimpleRoute.java +++ /dev/null @@ -1,508 +0,0 @@ -package de.mrjulsen.crn.data; - -import java.util.UUID; - -import com.simibubi.create.content.trains.entity.TrainIconType; - -import de.mrjulsen.crn.config.ModClientConfig; -import de.mrjulsen.crn.data.TrainStationAlias.StationInfo; -import de.mrjulsen.crn.event.listeners.IJourneyListenerClient; -import de.mrjulsen.crn.event.listeners.JourneyListenerManager; -import net.minecraft.nbt.CompoundTag; -import net.minecraft.nbt.ListTag; -import net.minecraft.nbt.Tag; -import net.minecraft.resources.ResourceLocation; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - -public class SimpleRoute { - - private static final String NBT_PARTS = "Parts"; - private static final String NBT_REFRESH_TIME = "RefreshTime"; - - protected long refreshTime; - protected final List parts; - - // Cache - protected int stationCount = -1; - protected StationEntry startStation = null; - protected StationEntry endStation = null; - protected StationEntry[] stationArray = null; - protected boolean valid = true; - protected String invalidationReason = ""; - protected String invalidationTrainName = ""; - - // listener - protected UUID listenerId; - - public SimpleRoute(Route route) { - this(route.getParts().stream().map(x -> new SimpleRoutePart(x, route.getRefreshTime())).toList(), route.getRefreshTime()); - } - - protected SimpleRoute(List parts, long refreshTime) { - this.parts = new ArrayList<>(parts); - this.refreshTime = refreshTime; - - parts.forEach(x -> x.setParent(this)); - tagAll(); - } - - public UUID listen(IJourneyListenerClient initialListener) { - return listenerId = JourneyListenerManager.getInstance().create(this, initialListener); - } - - public UUID getListenerId() { - return listenerId; - } - - public List getParts() { - return parts; - } - - public long getRefreshTime() { - return refreshTime; - } - - protected void tagAll() { - final int maxIndex = getParts().size() - 1; - int partIndex = 0; - int stationIndex = 0; - - for (SimpleRoutePart part : getParts()) { - final int idx = partIndex; - final StationTrainDetails trainDetails = new StationTrainDetails(part.getTrainName(), part.getTrainID(), part.getScheduleTitle()); - - part.getStartStation().train = trainDetails; - part.getStartStation().tag = idx <= 0 ? StationTag.START : StationTag.PART_START; - part.getStartStation().index = stationIndex; - stationIndex++; - - for (StationEntry station : part.getStopovers()) { - station.train = trainDetails; - station.tag = StationTag.TRANSIT; - station.index = stationIndex; - stationIndex++; - } - - part.getEndStation().train = trainDetails; - part.getEndStation().tag = idx >= maxIndex ? StationTag.END : StationTag.PART_END; - part.getEndStation().index = stationIndex; - - stationIndex++; - partIndex++; - } - } - - public void shiftTime(int amount) { - parts.forEach(x -> x.shiftTime(amount)); - refreshTime += amount; - } - - public int getStationCount(boolean countTransfersTwice) { - return stationCount < 0 ? stationCount = parts.stream().mapToInt(x -> x.getStationCount(false)).sum() + (countTransfersTwice ? parts.size() * 2 : parts.size() + 1) : stationCount; - } - - public int getTransferCount() { - return getParts().size() - 1; - } - - public int getTotalDuration() { - return getEndStation().getTicks() - getStartStation().getTicks(); - } - - protected void invalidate(String reason, String trainName) { - this.valid = false; - this.invalidationReason = reason; - this.invalidationTrainName = trainName; - } - - public boolean isValid() { - return valid; - } - - public String getInvalidationReason() { - return invalidationReason; - } - - public String getInvalidationTrainName() { - return invalidationTrainName; - } - - public StationEntry getStartStation() { - return startStation == null ? startStation = getParts().stream().findFirst().get().getStartStation() : startStation; - } - - public StationEntry getEndStation() { - return endStation == null ? endStation = getParts().stream().reduce((a, b) -> b).get().getEndStation() : endStation; - } - - public String getName() { - return String.format("%s - %s", getStartStation().getStationName(), getEndStation().getStationName()); - } - - public StationEntry[] getStationArray() { - return stationArray == null ? stationArray = getParts().stream().flatMap(x -> x.getStations().stream()).toArray(StationEntry[]::new) : stationArray; - } - - public CompoundTag toNbt() { - CompoundTag nbt = new CompoundTag(); - nbt.putLong(NBT_REFRESH_TIME, getRefreshTime()); - ListTag partsTag = new ListTag(); - partsTag.addAll(getParts().stream().map(x -> x.toNbt()).toList()); - nbt.put(NBT_PARTS, partsTag); - return nbt; - } - - public static SimpleRoute fromNbt(CompoundTag nbt) { - long refreshTime = nbt.getLong(NBT_REFRESH_TIME); - List parts = new ArrayList<>(nbt.getList(NBT_PARTS, Tag.TAG_COMPOUND).stream().map(x -> SimpleRoutePart.fromNbt((CompoundTag)x, refreshTime)).toList()); - SimpleRoute route = new SimpleRoute(parts, refreshTime); - parts.forEach(x -> x.setParent(route)); - return route; - } - - public static class SimpleRoutePart { - private static final String NBT_TRAIN_NAME = "TrainName"; - private static final String NBT_TRAIN_ID = "TrainId"; - private static final String NBT_TRAIN_ICON_ID = "TrainIconId"; - private static final String NBT_SCHEDULE_TITLE = "ScheduleTitle"; - private static final String NBT_START_STATION = "StartStation"; - private static final String NBT_END_STATION = "EndStation"; - private static final String NBT_STOPOVERS = "Stopovers"; - - protected SimpleRoute parent; - - protected final String trainName; - protected final UUID trainId; - protected final ResourceLocation trainIconId; - protected final String scheduleTitle; - protected final StationEntry start; - protected final StationEntry end; - protected final Collection stopovers; - - // Cache - protected List allStations = null; - - public SimpleRoutePart(RoutePart part, long refreshTime) { - this( - part.getTrain().name.getString(), - part.getTrain().id, - part.getTrain().icon.getId(), - part.getStartStation().getPrediction().getScheduleTitle(), - new StationEntry(part.getStartStation(), refreshTime), - new StationEntry(part.getEndStation(), refreshTime), - part.getStopovers().stream().map(x -> new StationEntry(x, refreshTime)).toList() - ); - } - - protected SimpleRoutePart(String trainName, UUID trainId, ResourceLocation trainIconId, String scheduleTitle, StationEntry start, StationEntry end, Collection stopovers) { - this.trainName = trainName; - this.trainId = trainId; - this.trainIconId = trainIconId; - this.scheduleTitle = scheduleTitle; - this.start = start; - this.end = end; - this.stopovers = stopovers; - - getStations().forEach(x -> x.setParent(this)); - } - - public void shiftTime(int amount) { - getStations().forEach(x -> x.shiftTime(amount)); - } - - protected void setParent(SimpleRoute parent) { - this.parent = parent; - } - - protected SimpleRoute getParent() { - return parent; - } - - public String getTrainName() { - return trainName; - } - - public UUID getTrainID() { - return trainId; - } - - public TrainIconType getTrainIcon() { - return TrainIconType.byId(trainIconId); - } - - public String getScheduleTitle() { - return scheduleTitle; - } - - public StationEntry getStartStation() { - return start; - } - - public StationEntry getEndStation() { - return end; - } - - public Collection getStopovers() { - return stopovers; - } - - public List getStations() { - if (allStations != null) { - return allStations; - } - allStations = new ArrayList<>(stopovers); - allStations.add(0, getStartStation()); - allStations.add(getEndStation()); - return allStations; - } - - public int getStationCount(boolean includeStartEnd) { - return getStopovers().size() + (includeStartEnd ? 2 : 0); - } - - public CompoundTag toNbt() { - CompoundTag nbt = new CompoundTag(); - nbt.putString(NBT_TRAIN_NAME, getTrainName()); - nbt.putString(NBT_SCHEDULE_TITLE, getScheduleTitle()); - nbt.putUUID(NBT_TRAIN_ID, getTrainID()); - nbt.putString(NBT_TRAIN_ICON_ID, trainIconId.toString()); - nbt.put(NBT_START_STATION, getStartStation().toNbt()); - nbt.put(NBT_END_STATION, getEndStation().toNbt()); - ListTag stopoversTag = new ListTag(); - stopoversTag.addAll(stopovers.stream().map(x -> x.toNbt()).toList()); - nbt.put(NBT_STOPOVERS, stopoversTag); - return nbt; - } - - public static SimpleRoutePart fromNbt(CompoundTag nbt, long refreshTime) { - String trainName = nbt.getString(NBT_TRAIN_NAME); - String scheduleTitle = nbt.getString(NBT_SCHEDULE_TITLE); - UUID trainId = nbt.getUUID(NBT_TRAIN_ID); - ResourceLocation trainIconId = new ResourceLocation(nbt.getString(NBT_TRAIN_ICON_ID)); - StationEntry start = StationEntry.fromNbt(nbt.getCompound(NBT_START_STATION), refreshTime); - StationEntry end = StationEntry.fromNbt(nbt.getCompound(NBT_END_STATION), refreshTime); - Collection stopovers = nbt.getList(NBT_STOPOVERS, Tag.TAG_COMPOUND).stream().map(x -> StationEntry.fromNbt((CompoundTag)x, refreshTime)).toList(); - - SimpleRoutePart part = new SimpleRoutePart(trainName, trainId, trainIconId, scheduleTitle, start, end, stopovers); - start.setParent(part); - end.setParent(part); - stopovers.forEach(x -> x.setParent(part)); - return part; - } - } - - public static class StationEntry { - private static final String NBT_NAME = "Name"; - private static final String NBT_TICKS = "Ticks"; - - protected final String stationName; - protected final StationInfo info; - protected int ticks; - protected long refreshTime; - - protected int index; - protected StationTrainDetails train; - protected StationTag tag; - - protected boolean departed = false; - protected boolean willMiss = false; - protected boolean trainCanceled = false; - - protected int timeShift = 0; - - protected int currentTicks; - protected long currentRefreshTime; - protected StationInfo updatedStationInfo; - protected boolean wasUpdated = false; - - protected SimpleRoutePart parent; - - public StationEntry(TrainStop stop, long refreshTime) { - this( - stop.getStationAlias().getAliasName().get(), - stop.getStationAlias().getInfoForStation(stop.getPrediction().getStationName()), - stop.getPrediction().getTicks(), refreshTime - ); - } - - protected StationEntry(String stationName, StationInfo info, int ticks, long refreshTime) { - this.stationName = stationName; - this.info = info; - this.ticks = ticks; - this.refreshTime = refreshTime; - this.currentTicks = ticks; - this.currentRefreshTime = refreshTime; - this.updatedStationInfo = info; - } - - public void shiftTime(int amount) { - timeShift += amount; - refreshTime += amount; - currentRefreshTime += amount; - - } - - public int getTimeShift() { - return timeShift; - } - - protected void setParent(SimpleRoutePart parent) { - this.parent = parent; - } - - public SimpleRoutePart getParent() { - return parent; - } - - public String getStationName() { - return stationName; - } - - public int getTicks() { - return ticks; - } - - public long getRefreshTime() { - return refreshTime; - } - - public int getCurrentTicks() { - return currentTicks; - } - - public long getCurrentRefreshTime() { - return currentRefreshTime; - } - - public long getCurrentTime() { - return currentRefreshTime + currentTicks; - } - - public StationInfo getInfo() { - return info; - } - - public boolean stationInfoChanged() { - return !getInfo().equals(getUpdatedInfo()); - } - - public StationInfo getUpdatedInfo() { - return updatedStationInfo; - } - - public void updateRealtimeData(int ticks, long refreshTime, StationInfo info, Runnable onDelayed) { - this.wasUpdated = true; - - this.currentRefreshTime = refreshTime; - this.currentTicks = ticks; - this.updatedStationInfo = info; - } - - public long getScheduleTime() { - return getRefreshTime() + getTicks(); - } - - public long getEstimatedTime() { - return getCurrentRefreshTime() + getCurrentTicks(); - } - - public long getDifferenceTime() { - return getEstimatedTime() - getScheduleTime(); - } - - public long getEstimatedTimeWithThreshold() { - return getScheduleTime() + ((long)(getDifferenceTime() / ModClientConfig.REALTIME_PRECISION_THRESHOLD.get()) * ModClientConfig.REALTIME_PRECISION_THRESHOLD.get()); - } - - public StationTrainDetails getTrain() { - return train; - } - - public int getIndex() { - return index; - } - - public StationTag getTag() { - return tag; - } - - public boolean isDelayed() { - return getEstimatedTime() - ModClientConfig.DEVIATION_THRESHOLD.get() > getScheduleTime(); - } - - - - public void setDeparted(boolean b) { - this.departed = this.departed || b; - } - - public void setWillMiss(boolean b) { - this.willMiss = b; - } - - public void setTrainCanceled(boolean b, String reason, String trainName) { - if (b) { - getParent().getParent().invalidate(reason, trainName); - } - this.trainCanceled = b; - } - - public boolean isDeparted() { - return this.departed; - } - - public boolean willMissStop() { - return willMiss; - } - - public boolean isTrainCanceled() { - return trainCanceled; - } - - /** - * Returns true if this station is reachable. Returns false if the train at this station already departed. - * @param safeConnection If true this station will no longer be considered as reachable if it may not be reachable due to a delay. But this doesn't mean that the train already departed. - * @return - */ - public boolean reachable(boolean safeConnection) { - return (!safeConnection || !willMissStop()) && !isDeparted() && !isTrainCanceled(); - } - - public boolean shouldRenderRealtime() { - return !isDeparted() && !isTrainCanceled() && relatimeWasUpdated() && (getEstimatedTime() + ModClientConfig.TRANSFER_TIME.get() + ModClientConfig.REALTIME_EARLY_ARRIVAL_THRESHOLD.get() > getScheduleTime()); - } - - public boolean relatimeWasUpdated() { - return wasUpdated; - } - - public CompoundTag toNbt() { - CompoundTag nbt = new CompoundTag(); - nbt.putString(NBT_NAME, getStationName()); - nbt.putInt(NBT_TICKS, getTicks()); - getInfo().writeNbt(nbt); - return nbt; - } - - public static StationEntry fromNbt(CompoundTag nbt, long refreshTime) { - String stationName = nbt.getString(NBT_NAME); - int ticks = nbt.getInt(NBT_TICKS); - StationInfo info = StationInfo.fromNbt(nbt); - return new StationEntry(stationName, info, ticks, refreshTime); - } - } - - public record StationTrainDetails(String trainName, UUID trainId, String scheduleTitle) {} - - public enum StationTag { - TRANSIT, - START, - PART_START, - PART_END, - END; - } -} - diff --git a/common/src/main/java/de/mrjulsen/crn/data/SimpleTrainSchedule.java b/common/src/main/java/de/mrjulsen/crn/data/SimpleTrainSchedule.java deleted file mode 100644 index 7b2279ef..00000000 --- a/common/src/main/java/de/mrjulsen/crn/data/SimpleTrainSchedule.java +++ /dev/null @@ -1,265 +0,0 @@ -package de.mrjulsen.crn.data; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Comparator; -import java.util.HashSet; -import java.util.List; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; - -import com.simibubi.create.content.trains.entity.Train; -import com.simibubi.create.content.trains.station.GlobalStation; - -import de.mrjulsen.crn.data.SimulatedTrainSchedule.SimulationData; -import de.mrjulsen.crn.event.listeners.TrainListener; - -public class SimpleTrainSchedule { - private Collection stops; - - public SimpleTrainSchedule(Train train) { - this(GlobalTrainData.getInstance().getAllStopsSorted(train)); - } - - private SimpleTrainSchedule(Collection stations) { - this.stops = stations; - } - - public static SimpleTrainSchedule of(Collection stations) { - return new SimpleTrainSchedule(stations); - } - - public Collection getAllStops() { - return stops; - } - - public Collection getAllStopsFrom(TrainStationAlias alias) { - final boolean[] startFound = new boolean[] { false }; - - return stops.stream().dropWhile(x -> { - if (x.getStationAlias().equals(alias)) { - startFound[0] = true; - } - return !startFound[0]; - }).toList(); - } - - public SimpleTrainSchedule copy() { - return new SimpleTrainSchedule(stops.stream().map(x -> x.copy()).toList()); - } - - public SimpleTrainSchedule makeScheduleUntilNextRepeat() { - List newList = new ArrayList<>(); - for (TrainStop stop : getAllStops()) { - if (newList.contains(stop)) { - break; - } - newList.add(stop); - } - return SimpleTrainSchedule.of(newList); - } - - public SimpleTrainSchedule makeScheduleFrom(TrainStationAlias alias, boolean preventDuplicates) { - List newList = new ArrayList<>(); - int idx = 0; - for (TrainStop stop : getAllStops()) { - if (preventDuplicates && newList.contains(stop)) { - continue; - } - - if (stop.getStationAlias().equals(alias)) { - idx = 0; - } - - newList.add(idx, stop); - idx++; - } - return SimpleTrainSchedule.of(newList); - } - - public SimpleTrainSchedule makeDirectionalScheduleFrom(TrainStationAlias alias) { - List newList = new ArrayList<>(); - for (TrainStop stop : makeScheduleFrom(alias, false).getAllStops()) { - if (newList.contains(stop)) { - break; - } - - newList.add(stop); - } - return SimpleTrainSchedule.of(newList); - } - - public SimpleTrainSchedule makeDirectionalSchedule() { - List newList = new ArrayList<>(); - boolean isRepeating = false; - for (TrainStop stop : getAllStops()) { - if (newList.contains(stop)) { - isRepeating = true; - continue; - } - - if (isRepeating) { - newList.add(0, stop); - } else { - newList.add(stop); - } - } - return SimpleTrainSchedule.of(newList); - } - - public boolean hasStation(GlobalStation station) { - return getAllStops().stream().anyMatch(x -> x.isStation(station)); - } - - public boolean hasStationAlias(TrainStationAlias station) { - return getAllStops().stream().anyMatch(x -> x.isStationAlias(station)); - } - - public Optional getLastStop(TrainStationAlias start) { - Optional lastStop = this.getAllStops().stream().reduce((a, b) -> b); - return lastStop.get().getStationAlias().equals(start) ? getFirstStop() : lastStop; - } - - public Optional getFirstStop() { - return this.getAllStops().stream().findFirst(); - } - - public Optional getNextStop() { - return this.getAllStops().stream().min((a, b) -> a.getPrediction().getTicks()); - } - - public Optional getNextStopOf(TrainStationAlias alias) { - return this.getAllStops().stream().filter(x -> x.getStationAlias().equals(alias)).min(Comparator.comparingInt(x -> x.getPrediction().getTicks())); - } - - public List getAllStopsOf(TrainStationAlias alias) { - return this.getAllStops().stream().filter(x -> x.getStationAlias().equals(alias)).toList(); - } - - public List getAllStopsOf(String station) { - return this.getAllStops().stream().filter(x -> x.getPrediction().getStationName().equals(station)).toList(); - } - - public boolean isInDirection(TrainStationAlias start, TrainStationAlias end) { - for (TrainStop stop : getAllStops()) { - if (stop.getStationAlias().equals(start)) { - return true; - } else if (stop.getStationAlias().equals(end)) { - return false; - } - } - return false; - } - - @Override - public boolean equals(Object obj) { - if (obj instanceof SimpleTrainSchedule other) { - Set thisStops = new HashSet<>(getAllStops()); - Set otherStops = new HashSet<>(other.getAllStops()); - - if (thisStops.size() != otherStops.size()) { - return false; - } - - return thisStops.containsAll(otherStops); - } - return false; - } - - public boolean exactEquals(Object obj) { - if (obj instanceof SimpleTrainSchedule other) { - if (getAllStops().size() != other.getAllStops().size()) { - return false; - } - - TrainStop[] a = getAllStops().toArray(TrainStop[]::new); - TrainStop[] b = other.getAllStops().toArray(TrainStop[]::new); - for (int i = 0; i < a.length; i++) { - if (!a[i].equals(b[i])) - return false; - } - return true; - } - return false; - } - - @Override - public int hashCode() { - return 17 * Objects.hash(stops); - } - - @Override - public String toString() { - return Arrays.toString(stops.toArray()); - } - - public static int getTrainCycleDuration(Train train) { - return TrainListener.getInstance().getApproximatedTrainDuration(train); - } - - public SimulatedTrainSchedule simulate(Train train, int simulationTime, TrainStationAlias simulationTarget) { - final int cycleDuration = getTrainCycleDuration(train); - - int timeToTargetAfterSim = getAllStopsOf(simulationTarget).stream().mapToInt(x -> { - int v = (int)((double)(x.getPrediction().getTicks() - simulationTime) % cycleDuration); - if (v < 0) { - v += cycleDuration; - } - return v; - }).min().orElse(0); - int simToTargetTime = simulationTime + timeToTargetAfterSim; - - return new SimulatedTrainSchedule(getAllStops().parallelStream().map(x -> { - int cycle = (int)((double)(x.getPrediction().getTicks() - simToTargetTime) / cycleDuration); - int estimatedTicks = (x.getPrediction().getTicks() - simToTargetTime) % cycleDuration; - while (estimatedTicks < 0) { - estimatedTicks += cycleDuration; - cycle++; - } - cycle += x.getPrediction().getCycle(); - return new TrainStop(x.getStationAlias(), new DeparturePrediction(x.getPrediction().getTrain(), estimatedTicks, x.getPrediction().getScheduleTitle(), x.getPrediction().getStationName(), cycle, x.getPrediction().getInfo())); - }).sorted(Comparator.comparingInt(x -> x.getPrediction().getTicks())).toList(), new SimulationData(train, simulationTime, timeToTargetAfterSim)); - } - - public SimulatedTrainSchedule simulate(Train train, int simulationTime, String simulationTarget) { - final int cycleDuration = getTrainCycleDuration(train); - - int timeToTargetAfterSim = getAllStopsOf(simulationTarget).stream().mapToInt(x -> { - int v = (int)((double)(x.getPrediction().getTicks() - simulationTime) % cycleDuration); - if (v < 0) { - v += cycleDuration; - } - return v; - }).min().orElse(0); - int simToTargetTime = simulationTime + timeToTargetAfterSim; - - return new SimulatedTrainSchedule(getAllStops().parallelStream().map(x -> { - int cycle = (int)((double)(x.getPrediction().getTicks() - simToTargetTime) / cycleDuration); - int estimatedTicks = (x.getPrediction().getTicks() - simToTargetTime) % cycleDuration; - while (estimatedTicks < 0) { - estimatedTicks += cycleDuration; - cycle++; - } - cycle += x.getPrediction().getCycle(); - return new TrainStop(x.getStationAlias(), new DeparturePrediction(x.getPrediction().getTrain(), estimatedTicks, x.getPrediction().getScheduleTitle(), x.getPrediction().getStationName(), cycle, x.getPrediction().getInfo())); - }).sorted(Comparator.comparingInt(x -> x.getPrediction().getTicks())).toList(), new SimulationData(train, simulationTime, timeToTargetAfterSim)); - } - - public SimpleTrainSchedule simulate(Train train, int simulationTime) { - final int cycleDuration = getTrainCycleDuration(train); - - return new SimpleTrainSchedule(getAllStops().parallelStream().map(x -> { - int cycle = (int)((double)(x.getPrediction().getTicks() - simulationTime) / cycleDuration); - int estimatedTicks = (x.getPrediction().getTicks() - simulationTime) % cycleDuration; - while (estimatedTicks < 0) { - estimatedTicks += cycleDuration; - cycle++; - } - cycle += x.getPrediction().getCycle(); - return new TrainStop(x.getStationAlias(), new DeparturePrediction(x.getPrediction().getTrain(), estimatedTicks, x.getPrediction().getScheduleTitle(), x.getPrediction().getStationName(), cycle, x.getPrediction().getInfo())); - }).sorted(Comparator.comparingInt(x -> x.getPrediction().getTicks())).toList()); - } - -} diff --git a/common/src/main/java/de/mrjulsen/crn/data/SimulatedTrainSchedule.java b/common/src/main/java/de/mrjulsen/crn/data/SimulatedTrainSchedule.java deleted file mode 100644 index 67b821a2..00000000 --- a/common/src/main/java/de/mrjulsen/crn/data/SimulatedTrainSchedule.java +++ /dev/null @@ -1,122 +0,0 @@ -package de.mrjulsen.crn.data; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.HashSet; -import java.util.List; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; - -import com.simibubi.create.content.trains.entity.Train; -import com.simibubi.create.content.trains.station.GlobalStation; - -import de.mrjulsen.crn.event.listeners.TrainListener; - -public class SimulatedTrainSchedule { - private final Collection stationOrder; - private final SimulationData data; - - public SimulatedTrainSchedule(Collection stations, SimulationData data) { - this.stationOrder = makeDiractional(stations); - this.data = data; - } - - private List makeDiractional(Collection raw) { - List newList = new ArrayList<>(); - boolean isRepeating = false; - for (TrainStop stop : raw) { - if (newList.contains(stop)) { - isRepeating = true; - continue; - } - - if (isRepeating) { - newList.add(0, stop); - } else { - newList.add(stop); - } - } - return newList; - } - - public Collection getAllStops() { - return stationOrder; - } - - public Optional getFirstStopOf(TrainStationAlias station) { - return getAllStops().stream().filter(x -> x.getStationAlias().equals(station)).findFirst(); - } - - public boolean hasStation(GlobalStation station) { - return getAllStops().stream().anyMatch(x -> x.getStationAlias().contains(station.name)); - } - - public boolean hasStationAlias(TrainStationAlias station) { - return getAllStops().stream().anyMatch(x -> x.getStationAlias().equals(station)); - } - - public SimulationData getSimulationData() { - return data; - } - - public boolean isInDirection(TrainStationAlias start, TrainStationAlias end) { - for (TrainStop stop : getAllStops()) { - if (stop.getStationAlias().equals(start)) { - return true; - } else if (stop.getStationAlias().equals(end)) { - return false; - } - } - return false; - } - - @Override - public boolean equals(Object obj) { - if (obj instanceof SimulatedTrainSchedule other) { - Set thisStops = new HashSet<>(getAllStops()); - Set otherStops = new HashSet<>(other.getAllStops()); - - if (thisStops.size() != otherStops.size()) { - return false; - } - - return thisStops.containsAll(otherStops); - } - return false; - } - - public boolean exactEquals(Object obj) { - if (obj instanceof SimulatedTrainSchedule other) { - if (getAllStops().size() != other.getAllStops().size()) { - return false; - } - - TrainStop[] a = getAllStops().toArray(TrainStop[]::new); - TrainStop[] b = other.getAllStops().toArray(TrainStop[]::new); - for (int i = 0; i < a.length; i++) { - if (!a[i].equals(b[i])) - return false; - } - return true; - } - return false; - } - - @Override - public int hashCode() { - return 17 * Objects.hash(getAllStops()); - } - - @Override - public String toString() { - return Arrays.toString(getAllStops().toArray()); - } - - public static int getTrainCycleDuration(Train train) { - return TrainListener.getInstance().getApproximatedTrainDuration(train); - } - - public static record SimulationData(Train train, int simulationTime, int simulationCorrection) {} -} diff --git a/common/src/main/java/de/mrjulsen/crn/data/StationTag.java b/common/src/main/java/de/mrjulsen/crn/data/StationTag.java new file mode 100644 index 00000000..40afc206 --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/data/StationTag.java @@ -0,0 +1,295 @@ +package de.mrjulsen.crn.data; + +import java.util.Date; +import java.util.HashMap; +import java.util.IdentityHashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; + +import de.mrjulsen.mcdragonlib.DragonLib; +import de.mrjulsen.mcdragonlib.util.DLUtils; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.ListTag; +import net.minecraft.nbt.StringTag; +import net.minecraft.nbt.Tag; + +public class StationTag { + + public static record ClientStationTag(String tagName, String stationName, StationInfo info, UUID tagId) { + public static final String NBT_TAG_NAME = "TagName"; + public static final String NBT_STATION_NAME = "StationName"; + public static final String NBT_STATION_INFO = "StationInfo"; + public static final String NBT_TAG_ID = "Id"; + + public CompoundTag toNbt() { + CompoundTag nbt = new CompoundTag(); + nbt.putString(NBT_TAG_NAME, tagName()); + nbt.putString(NBT_STATION_NAME, stationName()); + nbt.put(NBT_STATION_INFO, info().toNbt()); + nbt.putUUID(NBT_TAG_ID, tagId() == null ? new UUID(0, 0) : tagId()); + return nbt; + } + + public static ClientStationTag fromNbt(CompoundTag nbt) { + return new ClientStationTag( + nbt.getString(NBT_TAG_NAME), + nbt.getString(NBT_STATION_NAME), + StationInfo.fromNbt(nbt.getCompound(NBT_STATION_INFO)), + nbt.getUUID(NBT_TAG_ID) + ); + } + } + + public static final int MAX_NAME_LENGTH = 32; + + private static final String LEGACY_NBT_TAG_NAME = "AliasName"; + + private static final String NBT_ID = "Id"; + private static final String NBT_TAG_NAME = "TagName"; + private static final String NBT_STATION_LIST = "Stations"; + private static final String NBT_STATION_MAP = "StationData"; + private static final String NBT_LAST_EDITOR = "LastEditor"; + private static final String NBT_LAST_EDITED_TIME = "LastEditedTimestamp"; + + private static final String NBT_STATION_ENTRY_NAME = "Name"; + + protected UUID id; + protected TagName tagName; + protected Map stations = new HashMap<>(); + + // History + protected String lastEditorName = null; + protected long lastEditedTime = 0; + + protected StationTag(UUID id, TagName tagName, Map initialValues, String lastEditorName, long lastEditedTime) { + this(id, tagName, initialValues); + this.lastEditorName = lastEditorName; + this.lastEditedTime = lastEditedTime; + } + + public StationTag(UUID id, TagName tagName, Map initialValues) { + this(id, tagName); + stations.putAll(initialValues); + } + + public StationTag(UUID id, TagName tagName) { + this.id = id; + this.tagName = tagName; + updateLastEdited("Server"); + } + + /** + * Returns the Id of the tag. May be {@code null}, which means that this tag is temporary or not registered properly. + * @return The id of the tag or null. + */ + public UUID getId() { + return id; + } + + public void setId(UUID id) { + this.id = id; + } + + public StationTag copy() { + return new StationTag(null, new TagName(getTagName().get()), new HashMap<>(getAllStations())); + } + + public CompoundTag toNbt() { + CompoundTag nbt = new CompoundTag(); + if (tagName == null) { + return nbt; + } + + DLUtils.doIfNotNull(id, (i) -> nbt.putUUID(NBT_ID, i)); + nbt.put(NBT_TAG_NAME, getTagName().toNbt()); + if (lastEditorName != null) { + nbt.putString(NBT_LAST_EDITOR, getLastEditorName()); + } + nbt.putLong(NBT_LAST_EDITED_TIME, lastEditedTime); + + ListTag stationsList = new ListTag(); + stations.forEach((key, value) -> { + CompoundTag entry = new CompoundTag(); + entry.putString(NBT_STATION_ENTRY_NAME, key); + value.writeNbt(entry); + stationsList.add(entry); + }); + nbt.put(NBT_STATION_MAP, stationsList); + + return nbt; + } + + public static StationTag fromNbt(CompoundTag nbt, UUID overwriteId) { + UUID id = overwriteId == null ? (nbt.contains(NBT_ID) ? nbt.getUUID(NBT_ID) : null) : overwriteId; + TagName name = TagName.fromNbt(nbt.getCompound(!nbt.contains(NBT_TAG_NAME) ? LEGACY_NBT_TAG_NAME : NBT_TAG_NAME)); + String lastEditorName = nbt.contains(NBT_LAST_EDITOR) ? nbt.getString(NBT_LAST_EDITOR) : null; + long lastEditedTime = nbt.getLong(NBT_LAST_EDITED_TIME); + Map stations; + if (nbt.contains(NBT_STATION_LIST)) { + stations = new HashMap<>(nbt.getList(NBT_STATION_LIST, Tag.TAG_STRING).stream().map(x -> ((StringTag)x).getAsString()).collect(Collectors.toMap(x -> x, x -> StationInfo.empty()))); + } else if (nbt.contains(NBT_STATION_MAP)) { + stations = new HashMap<>(nbt.getList(NBT_STATION_MAP, Tag.TAG_COMPOUND).stream().map(x -> (CompoundTag)x).collect(Collectors.toMap(x -> { + return x.getString(NBT_STATION_ENTRY_NAME); + }, x -> { + return StationInfo.fromNbt(x); + }))); + } else { + stations = new IdentityHashMap<>(); + } + + return new StationTag(id, name, stations, lastEditorName, lastEditedTime); + } + + public String getLastEditorName() { + return lastEditorName; + } + + public void updateLastEdited(String name) { + this.lastEditorName = name; + this.lastEditedTime = new Date().getTime(); + } + + public Date getLastEditedTime() { + return new Date(lastEditedTime); + } + + public String getLastEditedTimeFormatted() { + return DragonLib.DATE_FORMAT.format(getLastEditedTime()); + } + + + public TagName getTagName() { + return this.tagName; + } + + public void updateInfoForStation(String station, StationInfo info) { + if (stations.containsKey(station)) { + stations.replace(station, info); + } + } + + public void add(String station, StationInfo info) { + if (!stations.containsKey(station)) { + stations.put(station, info); + } + } + + public void addAll(Map stations) { + stations.forEach((key, value) -> { + if (!this.stations.containsKey(key)) { + this.stations.put(key, value); + } + }); + } + + /** + * @param stationName The name of the train station. + * @return {@code true} if the station is part of this tag. + */ + public boolean contains(String stationName) { + String regex = stationName.isBlank() ? stationName : "\\Q" + stationName.replace("*", "\\E.*\\Q"); + return stations.keySet().stream().anyMatch(x -> x.matches(regex)); + } + + public Set getAllStationNames() { + return Set.copyOf(stations.keySet()); + } + + public Map getAllStations() { + return Map.copyOf(stations); + } + + public StationInfo getInfoForStation(String stationName) { + return stations.containsKey(stationName) ? stations.get(stationName) : StationInfo.empty(); + } + + public void setName(TagName name) { + this.tagName = name; + } + + public void remove(String station) { + stations.remove(station); + } + + public ClientStationTag getClientTag(String station) { + return new ClientStationTag(getTagName().get(), station, getInfoForStation(station), getId()); + } + + @Override + public String toString() { + return getTagName().toString(); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof StationTag alias) { + return getTagName().equals(alias.getTagName()) && getAllStationNames().size() == alias.getAllStationNames().size() && getAllStationNames().stream().allMatch(x -> alias.contains(x)); + } + return false; + } + + @Override + public int hashCode() { + return 7 * Objects.hash(tagName); + } + + public void applyFrom(StationTag newData) { + this.tagName = newData.tagName; + this.stations.clear(); + this.stations.putAll(newData.stations); + this.lastEditedTime = newData.lastEditedTime; + this.lastEditorName = newData.lastEditorName; + } + + /** + * Information about one specific train station (not station tag!) + */ + public static record StationInfo(String platform) { + + public static final int MAX_PLATFORM_NAME_LENGTH = 8; + + private static final String NBT_PLATFORM = "Platform"; + + public static StationInfo empty() { + return new StationInfo(""); + } + + public boolean isPlatformKnown() { + return platform() != null && !platform().isBlank(); + } + + public CompoundTag toNbt() { + CompoundTag nbt = new CompoundTag(); + nbt.putString(NBT_PLATFORM, platform()); + return nbt; + } + + public void writeNbt(CompoundTag nbt) { + nbt.putString(NBT_PLATFORM, platform()); + } + + public static StationInfo fromNbt(CompoundTag nbt) { + return new StationInfo( + nbt.getString(NBT_PLATFORM) + ); + } + + @Override + public final boolean equals(Object obj) { + if (obj instanceof StationInfo other) { + return + platform().equals(other.platform()) + ; + } + return false; + } + + @Override + public final int hashCode() { + return Objects.hash(platform()); + } + } +} diff --git a/common/src/main/java/de/mrjulsen/crn/data/AliasName.java b/common/src/main/java/de/mrjulsen/crn/data/TagName.java similarity index 61% rename from common/src/main/java/de/mrjulsen/crn/data/AliasName.java rename to common/src/main/java/de/mrjulsen/crn/data/TagName.java index a3538a33..13ddcda4 100644 --- a/common/src/main/java/de/mrjulsen/crn/data/AliasName.java +++ b/common/src/main/java/de/mrjulsen/crn/data/TagName.java @@ -2,12 +2,13 @@ import net.minecraft.nbt.CompoundTag; -public class AliasName { +public class TagName { + public static final TagName EMPTY = TagName.of(""); private static final String NBT_NAME = "Name"; - private String name; + private String name = ""; - public AliasName(String name) { + public TagName(String name) { this.name = name; } @@ -17,8 +18,8 @@ public CompoundTag toNbt() { return nbt; } - public static AliasName fromNbt(CompoundTag nbt) { - return new AliasName(nbt.getString(NBT_NAME)); + public static TagName fromNbt(CompoundTag nbt) { + return new TagName(nbt.getString(NBT_NAME)); } public String get() { @@ -29,8 +30,12 @@ public String set(String name) { return this.name = name; } - public static AliasName of(String name) { - return new AliasName(name); + public static TagName of(String name) { + return new TagName(name); + } + + public boolean isEmpty() { + return name == null || name.isBlank(); } @Override @@ -40,10 +45,9 @@ public String toString() { @Override public boolean equals(Object obj) { - if (obj instanceof AliasName aliasName) { + if (obj instanceof TagName aliasName) { return name.equals(aliasName.get()); } - return false; } diff --git a/common/src/main/java/de/mrjulsen/crn/data/SimpleTrainConnection.java b/common/src/main/java/de/mrjulsen/crn/data/TrainConnection.java similarity index 77% rename from common/src/main/java/de/mrjulsen/crn/data/SimpleTrainConnection.java rename to common/src/main/java/de/mrjulsen/crn/data/TrainConnection.java index 16fb15fb..66a41afd 100644 --- a/common/src/main/java/de/mrjulsen/crn/data/SimpleTrainConnection.java +++ b/common/src/main/java/de/mrjulsen/crn/data/TrainConnection.java @@ -2,11 +2,11 @@ import java.util.UUID; -import de.mrjulsen.crn.data.TrainStationAlias.StationInfo; +import de.mrjulsen.crn.data.StationTag.StationInfo; import net.minecraft.nbt.CompoundTag; import net.minecraft.resources.ResourceLocation; -public record SimpleTrainConnection(String trainName, UUID trainId, ResourceLocation trainIconId, int ticks, String scheduleTitle, StationInfo stationDetails) { +public record TrainConnection(String trainName, UUID trainId, ResourceLocation trainIconId, int ticks, String scheduleTitle, StationInfo stationDetails) { private static final String NBT_TRAIN_NAME = "TrainName"; private static final String NBT_TRAIN_ID = "Id"; @@ -27,8 +27,8 @@ public CompoundTag toNbt() { return nbt; } - public static SimpleTrainConnection fromNbt(CompoundTag nbt) { - return new SimpleTrainConnection( + public static TrainConnection fromNbt(CompoundTag nbt) { + return new TrainConnection( nbt.getString(NBT_TRAIN_NAME), nbt.getUUID(NBT_TRAIN_ID), new ResourceLocation(nbt.getString(NBT_TRAIN_ICON)), diff --git a/common/src/main/java/de/mrjulsen/crn/data/TrainData.java b/common/src/main/java/de/mrjulsen/crn/data/TrainData.java deleted file mode 100644 index 18b5c5d3..00000000 --- a/common/src/main/java/de/mrjulsen/crn/data/TrainData.java +++ /dev/null @@ -1,49 +0,0 @@ -package de.mrjulsen.crn.data; - -import java.util.UUID; - -import com.simibubi.create.content.trains.entity.Train; - -import de.mrjulsen.mcdragonlib.data.INBTSerializable; -import net.minecraft.nbt.CompoundTag; - -public class TrainData implements INBTSerializable { - - private static final String NBT_NAME = "Name"; - private static final String NBT_UUID = "UUID"; - - - private String name; - private UUID id; - - public TrainData() { - - } - - public TrainData(Train train) { - this.name = train.name.getString(); - this.id = train.id; - } - - public String getName() { - return name; - } - - public UUID getId() { - return id; - } - - @Override - public CompoundTag serializeNbt() { - CompoundTag nbt = new CompoundTag(); - nbt.putString(NBT_NAME, name); - nbt.putUUID(NBT_UUID, id); - return nbt; - } - - @Override - public void deserializeNbt(CompoundTag nbt) { - name = nbt.getString(NBT_NAME); - id = nbt.getUUID(NBT_UUID); - } -} diff --git a/common/src/main/java/de/mrjulsen/crn/data/TrainExitSide.java b/common/src/main/java/de/mrjulsen/crn/data/TrainExitSide.java new file mode 100644 index 00000000..7f167b5a --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/data/TrainExitSide.java @@ -0,0 +1,27 @@ +package de.mrjulsen.crn.data; + +import java.util.Arrays; + +public enum TrainExitSide { + UNKNOWN((byte)0), + RIGHT((byte)1), + LEFT((byte)-1); + + private byte side; + + TrainExitSide(byte side) { + this.side = side; + } + + public byte getAsByte() { + return side; + } + + public static TrainExitSide getFromByte(byte side) { + return Arrays.stream(values()).filter(x -> x.getAsByte() == side).findFirst().orElse(UNKNOWN); + } + + public TrainExitSide getOpposite() { + return getFromByte((byte)-getAsByte()); + } +} diff --git a/common/src/main/java/de/mrjulsen/crn/data/TrainGroup.java b/common/src/main/java/de/mrjulsen/crn/data/TrainGroup.java index dad5dc09..9a78398b 100644 --- a/common/src/main/java/de/mrjulsen/crn/data/TrainGroup.java +++ b/common/src/main/java/de/mrjulsen/crn/data/TrainGroup.java @@ -1,98 +1,40 @@ package de.mrjulsen.crn.data; import java.util.Date; -import java.util.HashSet; import java.util.Objects; -import java.util.Set; -import java.util.stream.Collectors; - -import com.simibubi.create.content.trains.entity.Train; - import de.mrjulsen.mcdragonlib.DragonLib; import net.minecraft.nbt.CompoundTag; -import net.minecraft.nbt.ListTag; -import net.minecraft.nbt.StringTag; -import net.minecraft.nbt.Tag; public class TrainGroup { + public static int MAX_NAME_LENGTH = 32; + private static final String NBT_NAME = "Name"; - private static final String NBT_TRAIN_NAMES = "Trains"; + private static final String NBT_COLOR = "Color"; private static final String NBT_LAST_EDITOR = "LastEditor"; private static final String NBT_LAST_EDITED_TIME = "LastEditedTimestamp"; - private String name; - private Set trainNames = new HashSet<>(); + private final String name; + private int color; - protected String lastEditorName = null; - protected long lastEditedTime = 0; + protected String lastEditorName; + protected long lastEditedTime; public TrainGroup(String name) { this.name = name; - } - - public TrainGroup(String name, Set initialValues) { - this.name = name; - this.trainNames = initialValues; - } - - public void addTrain(Train train) { - addTrain(train.name.getString()); - } - - public void addTrain(String trainName) { - trainNames.add(trainName); - } - - public Set getTrainNames() { - return trainNames; - } - - public boolean contains(Train train) { - return trainNames.stream().anyMatch(x -> x.equals(train.name.getString())); - } - - public boolean contains(String trainName) { - return trainNames.stream().anyMatch(x -> x.equals(trainName)); - } - - public void setGroupName(String name) { - this.name = name; + updateLastEdited("Server"); } public String getGroupName() { return name; } - public void update(TrainGroup newData) { - this.name = newData.name; - this.trainNames = newData.trainNames; - this.lastEditedTime = newData.lastEditedTime; - this.lastEditorName = newData.lastEditorName; + public int getColor() { + return color; } - public void add(String trainName) { - trainNames.add(trainName); - } - - public void addAll(Set trainNames) { - this.trainNames.addAll(trainNames); - } - - public void add(Train train) { - add(train.name.getString()); - } - - public void addAllTrains(Set trains) { - this.trainNames.addAll(trains.stream().map(x -> x.name.getString()).toList()); - } - - public void remove(Train train) { - remove(train.name.getString()); - } - - public void remove(String trainName) { - trainNames.removeIf(x -> x.equals(trainName)); + public void setColor(int color) { + this.color = color; } public String getLastEditorName() { @@ -114,7 +56,15 @@ public String getLastEditedTimeFormatted() { @Override public int hashCode() { - return Objects.hash(name, trainNames); + return Objects.hash(name); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof TrainGroup o) { + return name.equals(o.name); + } + return false; } public CompoundTag toNbt() { @@ -123,24 +73,25 @@ public CompoundTag toNbt() { if (lastEditorName != null) { nbt.putString(NBT_LAST_EDITOR, getLastEditorName()); } - nbt.putLong(NBT_LAST_EDITED_TIME, lastEditedTime); + if (lastEditedTime > 0) { + nbt.putLong(NBT_LAST_EDITED_TIME, lastEditedTime); + } nbt.putString(NBT_NAME, getGroupName()); - ListTag tag = new ListTag(); - tag.addAll(getTrainNames().stream().map(x -> StringTag.valueOf(x)).toList()); - nbt.put(NBT_TRAIN_NAMES, tag); + nbt.putInt(NBT_COLOR, getColor()); return nbt; } public static TrainGroup fromNbt(CompoundTag nbt) { String groupName = nbt.getString(NBT_NAME); - Set trainNames = new HashSet<>(nbt.getList(NBT_TRAIN_NAMES, Tag.TAG_STRING).stream().map(x -> ((StringTag)x).getAsString()).collect(Collectors.toSet())); - String lastEditorName = nbt.contains(NBT_LAST_EDITOR) ? nbt.getString(NBT_LAST_EDITOR) : null; - long lastEditedTime = nbt.getLong(NBT_LAST_EDITED_TIME); - TrainGroup group = new TrainGroup(groupName, trainNames); - group.lastEditedTime = lastEditedTime; - group.lastEditorName = lastEditorName; - + TrainGroup group = new TrainGroup(groupName); + group.setColor(nbt.getInt(NBT_COLOR)); + if (nbt.contains(NBT_LAST_EDITOR)) { + group.lastEditorName = nbt.getString(NBT_LAST_EDITOR); + } + if (nbt.contains(NBT_LAST_EDITED_TIME)) { + group.lastEditedTime = nbt.getLong(NBT_LAST_EDITED_TIME); + } return group; } } diff --git a/common/src/main/java/de/mrjulsen/crn/data/TrainInfo.java b/common/src/main/java/de/mrjulsen/crn/data/TrainInfo.java new file mode 100644 index 00000000..2e07e66b --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/data/TrainInfo.java @@ -0,0 +1,28 @@ +package de.mrjulsen.crn.data; + +import net.minecraft.nbt.CompoundTag; + +public record TrainInfo(TrainLine line, TrainGroup group) { + + private static final String NBT_TRAIN_GROUP = "Group"; + private static final String NBT_TRAIN_LINE = "Line"; + + public CompoundTag toNbt() { + CompoundTag nbt = new CompoundTag(); + if (group != null) nbt.put(NBT_TRAIN_GROUP, group.toNbt()); + if (line != null) nbt.put(NBT_TRAIN_LINE, line.toNbt()); + + return nbt; + } + + public static TrainInfo fromNbt(CompoundTag nbt) { + return new TrainInfo( + nbt.contains(NBT_TRAIN_LINE) ? TrainLine.fromNbt(nbt.getCompound(NBT_TRAIN_LINE)) : null, + nbt.contains(NBT_TRAIN_GROUP) ? TrainGroup.fromNbt(nbt.getCompound(NBT_TRAIN_GROUP)) : null + ); + } + + public static TrainInfo empty() { + return new TrainInfo(null, null); + } +} diff --git a/common/src/main/java/de/mrjulsen/crn/data/TrainLine.java b/common/src/main/java/de/mrjulsen/crn/data/TrainLine.java new file mode 100644 index 00000000..1e101c95 --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/data/TrainLine.java @@ -0,0 +1,47 @@ +package de.mrjulsen.crn.data; + +import net.minecraft.nbt.CompoundTag; + +public class TrainLine { + + public static int MAX_NAME_LENGTH = 32; + + private static final String NBT_NAME = "Name"; + private static final String NBT_COLOR = "Color"; + + private final String name; + private int lineColor = 0; + protected String lastEditorName; + protected long lastEditedTime; + + public TrainLine(String name) { + this.name = name; + } + + public String getLineName() { + return name; + } + + public int getColor() { + return lineColor; + } + + public void setColor(int color) { + this.lineColor = color; + } + + public CompoundTag toNbt() { + CompoundTag nbt = new CompoundTag(); + nbt.putString(NBT_NAME, name); + nbt.putInt(NBT_COLOR, lineColor); + return nbt; + } + + public static TrainLine fromNbt(CompoundTag nbt) { + TrainLine line = new TrainLine( + nbt.getString(NBT_NAME) + ); + line.setColor(nbt.getInt(NBT_COLOR)); + return line; + } +} diff --git a/common/src/main/java/de/mrjulsen/crn/data/TrainStationAlias.java b/common/src/main/java/de/mrjulsen/crn/data/TrainStationAlias.java deleted file mode 100644 index e6c1e424..00000000 --- a/common/src/main/java/de/mrjulsen/crn/data/TrainStationAlias.java +++ /dev/null @@ -1,217 +0,0 @@ -package de.mrjulsen.crn.data; - -import java.util.Date; -import java.util.HashMap; -import java.util.IdentityHashMap; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.stream.Collectors; - -import de.mrjulsen.mcdragonlib.DragonLib; -import net.minecraft.nbt.CompoundTag; -import net.minecraft.nbt.ListTag; -import net.minecraft.nbt.StringTag; -import net.minecraft.nbt.Tag; - -public class TrainStationAlias { - - private static final String NBT_ALIAS_NAME = "AliasName"; - private static final String NBT_STATION_LIST = "Stations"; - private static final String NBT_STATION_MAP = "StationData"; - private static final String NBT_LAST_EDITOR = "LastEditor"; - private static final String NBT_LAST_EDITED_TIME = "LastEditedTimestamp"; - - private static final String NBT_STATION_ENTRY_NAME = "Name"; - - protected AliasName aliasName; - protected Map stations = new HashMap<>(); - // log - protected String lastEditorName = null; - protected long lastEditedTime = 0; - - protected TrainStationAlias(AliasName aliasName, Map initialValues, String lastEditorName, long lastEditedTime) { - this(aliasName, initialValues); - this.lastEditorName = lastEditorName; - this.lastEditedTime = lastEditedTime; - } - - public TrainStationAlias(AliasName aliasName, Map initialValues) { - this(aliasName); - stations.putAll(initialValues); - } - - public TrainStationAlias(AliasName aliasName) { - this.aliasName = aliasName; - } - - public TrainStationAlias copy() { - return new TrainStationAlias(new AliasName(getAliasName().get()), new HashMap<>(getAllStations())); - } - - public CompoundTag toNbt() { - CompoundTag nbt = new CompoundTag(); - if (aliasName == null) { - return nbt; - } - - nbt.put(NBT_ALIAS_NAME, getAliasName().toNbt()); - if (lastEditorName != null) { - nbt.putString(NBT_LAST_EDITOR, getLastEditorName()); - } - nbt.putLong(NBT_LAST_EDITED_TIME, lastEditedTime); - - ListTag stationsList = new ListTag(); - stations.forEach((key, value) -> { - CompoundTag entry = new CompoundTag(); - entry.putString(NBT_STATION_ENTRY_NAME, key); - value.writeNbt(entry); - stationsList.add(entry); - }); - nbt.put(NBT_STATION_MAP, stationsList); - - return nbt; - } - - public static TrainStationAlias fromNbt(CompoundTag nbt) { - if (!nbt.contains(NBT_ALIAS_NAME)) { - return new TrainStationAlias(AliasName.of("null")); - } - AliasName name = AliasName.fromNbt(nbt.getCompound(NBT_ALIAS_NAME)); - String lastEditorName = nbt.contains(NBT_LAST_EDITOR) ? nbt.getString(NBT_LAST_EDITOR) : null; - long lastEditedTime = nbt.getLong(NBT_LAST_EDITED_TIME); - Map stations; - if (nbt.contains(NBT_STATION_LIST)) { - stations = nbt.getList(NBT_STATION_LIST, Tag.TAG_STRING).stream().map(x -> ((StringTag)x).getAsString()).collect(Collectors.toMap(x -> x, x -> StationInfo.empty())); - } else if (nbt.contains(NBT_STATION_MAP)) { - stations = nbt.getList(NBT_STATION_MAP, Tag.TAG_COMPOUND).stream().map(x -> (CompoundTag)x).collect(Collectors.toMap(x -> { - return x.getString(NBT_STATION_ENTRY_NAME); - }, x -> { - return StationInfo.fromNbt(x); - })); - } else { - stations = new IdentityHashMap<>(); - } - - return new TrainStationAlias(name, stations, lastEditorName, lastEditedTime); - } - - public String getLastEditorName() { - return lastEditorName; - } - - public void updateLastEdited(String name) { - this.lastEditorName = name; - this.lastEditedTime = new Date().getTime(); - } - - public Date getLastEditedTime() { - return new Date(lastEditedTime); - } - - public String getLastEditedTimeFormatted() { - return DragonLib.DATE_FORMAT.format(getLastEditedTime()); - } - - - public AliasName getAliasName() { - return this.aliasName; - } - - public void updateInfoForStation(String station, StationInfo info) { - if (stations.containsKey(station)) { - stations.replace(station, info); - } - } - - public void add(String station, StationInfo info) { - if (!stations.containsKey(station)) { - stations.put(station, info); - } - } - - public void addAll(Map stations) { - stations.forEach((key, value) -> { - if (!this.stations.containsKey(key)) { - this.stations.put(key, value); - } - }); - } - - public boolean contains(String station) { - String regex = station.isBlank() ? station : "\\Q" + station.replace("*", "\\E.*\\Q"); - return stations.keySet().stream().anyMatch(x -> x.matches(regex)); - } - - public Set getAllStationNames() { - return stations.keySet(); - } - - public Map getAllStations() { - return stations; - } - - public StationInfo getInfoForStation(String stationName) { - return stations.containsKey(stationName) ? stations.get(stationName) : StationInfo.empty(); - } - - public void setName(AliasName name) { - this.aliasName = name; - } - - public void remove(String station) { - stations.remove(station); - } - - @Override - public String toString() { - return String.format("%s", getAliasName()); - } - - @Override - public boolean equals(Object obj) { - if (obj instanceof TrainStationAlias alias) { - return getAliasName().equals(alias.getAliasName()) && getAllStationNames().size() == alias.getAllStationNames().size() && getAllStationNames().stream().allMatch(x -> alias.contains(x)); - } - return false; - } - - @Override - public int hashCode() { - return 7 * Objects.hash(aliasName); - } - - public void update(TrainStationAlias newData) { - this.aliasName = newData.aliasName; - this.stations = newData.stations; - this.lastEditedTime = newData.lastEditedTime; - this.lastEditorName = newData.lastEditorName; - } - - public static record StationInfo(String platform) { - private static final String NBT_PLATFORM = "Platform"; - - public static StationInfo empty() { - return new StationInfo(""); - } - - public void writeNbt(CompoundTag nbt) { - nbt.putString(NBT_PLATFORM, platform()); - } - - public static StationInfo fromNbt(CompoundTag nbt) { - return new StationInfo( - nbt.getString(NBT_PLATFORM) - ); - } - - @Override - public final boolean equals(Object obj) { - if (obj instanceof StationInfo other) { - return platform().equals(other.platform()); - } - return false; - } - } - -} diff --git a/common/src/main/java/de/mrjulsen/crn/data/TrainStop.java b/common/src/main/java/de/mrjulsen/crn/data/TrainStop.java deleted file mode 100644 index a0b11bfb..00000000 --- a/common/src/main/java/de/mrjulsen/crn/data/TrainStop.java +++ /dev/null @@ -1,58 +0,0 @@ -package de.mrjulsen.crn.data; - -import java.util.Objects; - -import com.simibubi.create.content.trains.station.GlobalStation; - -public class TrainStop { - - private TrainStationAlias station; - private DeparturePrediction prediction; - - public TrainStop(TrainStationAlias station, DeparturePrediction prediction) { - this.station = station; - this.prediction = prediction; - } - - public TrainStop copy() { - return new TrainStop(getStationAlias().copy(), getPrediction().copy()); - } - - public TrainStationAlias getStationAlias() { - return station; - } - - public DeparturePrediction getPrediction() { - return prediction; - } - - public boolean isStation(GlobalStation station) { - return getStationAlias().contains(station.name); - } - - public boolean isStationAlias(TrainStationAlias alias) { - return getStationAlias().equals(alias); - } - - @Override - public boolean equals(Object obj) { - if (obj instanceof TrainStop other) { - return getStationAlias().equals(other.getStationAlias()); - } - return false; - } - - public boolean identical(TrainStop other) { - return getStationAlias().equals(other.getStationAlias()) && prediction.getTicks() == other.getPrediction().getTicks(); - } - - @Override - public int hashCode() { - return 13 * Objects.hash(station); - } - - @Override - public String toString() { - return String.format("%s (%s)", getStationAlias(), prediction.getTicks()); - } -} diff --git a/common/src/main/java/de/mrjulsen/crn/data/UserSettings.java b/common/src/main/java/de/mrjulsen/crn/data/UserSettings.java index 15433dbe..e4a8a550 100644 --- a/common/src/main/java/de/mrjulsen/crn/data/UserSettings.java +++ b/common/src/main/java/de/mrjulsen/crn/data/UserSettings.java @@ -1,57 +1,232 @@ package de.mrjulsen.crn.data; -import java.util.List; +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.function.BiFunction; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Collectors; -import com.simibubi.create.content.trains.entity.Train; - -import de.mrjulsen.crn.config.ModClientConfig; +import de.mrjulsen.crn.CreateRailwaysNavigator; +import de.mrjulsen.crn.data.storage.GlobalSettings; +import de.mrjulsen.crn.event.ModCommonEvents; +import de.mrjulsen.crn.exceptions.RuntimeSideException; +import de.mrjulsen.crn.registry.ModAccessorTypes; +import de.mrjulsen.mcdragonlib.util.DLUtils; +import de.mrjulsen.mcdragonlib.util.TextUtils; +import de.mrjulsen.mcdragonlib.util.TimeUtils; +import de.mrjulsen.mcdragonlib.util.accessor.DataAccessor; +import dev.architectury.platform.Platform; +import net.fabricmc.api.EnvType; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.ListTag; +import net.minecraft.nbt.NbtIo; import net.minecraft.nbt.StringTag; import net.minecraft.nbt.Tag; +import net.minecraft.world.level.storage.LevelResource; public class UserSettings { + + private static final String FILENAME = CreateRailwaysNavigator.SHORT_MOD_ID + "_usersettings_"; + private static final int VERSION = 1; + + private static final String NBT_VERSION = "Version"; + private static final String NBT_DEPARTURE_IN = "DepartureIn"; private static final String NBT_TRANSFER_TIME = "TransferTime"; - private static final String NBT_TRAIN_GROUPS = "TrainGroupBlacklist"; + private static final String NBT_TRAIN_GROUPS = "ExcludedTrainGroups"; + private static final String NBT_SAVED_ROUTES = "SavedRoutes"; + private static final String NBT_SEARCH_DEPARTURE_TIME = "SearchDepartureIn"; + private static final String NBT_SEARCH_TRAIN_GROUPS = "SearchExcludedTrainGroups"; + + private static final Map settingsInstances = new LinkedHashMap<>(); + + private final Collection> allSettings = new ArrayList<>(); + private final UUID owner; + private final boolean readOnly; + + // Settings + public final UserSetting navigationDepartureInTicks = registerSetting(new UserSetting<>(() -> 0, NBT_DEPARTURE_IN, (nbt, val, name) -> nbt.putInt(name, val), (nbt, name) -> nbt.getInt(name), (val) -> TimeUtils.parseDurationShort(val))); + public final UserSetting navigationTransferTime = registerSetting(new UserSetting<>(() -> 1000, NBT_TRANSFER_TIME, (nbt, val, name) -> nbt.putInt(name, val), (nbt, name) -> nbt.getInt(name), (val) -> TimeUtils.parseDurationShort(val))); + public final UserSetting> navigationExcludedTrainGroups = registerSetting(new UserSetting<>(() -> new HashSet<>(), NBT_TRAIN_GROUPS, + (nbt, val, name) -> { + ListTag list = new ListTag(); + list.addAll(val.stream().map(x -> StringTag.valueOf(x)).toList()); + nbt.put(name, list); + }, (nbt, name) -> { + return nbt.getList(name, Tag.TAG_STRING).stream().filter(x -> GlobalSettings.hasInstance() ? GlobalSettings.getInstance().trainGroupExists(x.getAsString()) : true).map(x -> x.getAsString()).collect(Collectors.toSet()); + },(val) -> val.isEmpty() ? TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".search_options.train_groups.all").getString() : TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".search_options.train_groups.excluded", val.size()).getString())); + + public final UserSetting> savedRoutes = registerSetting(new UserSetting<>(() -> new HashSet<>(), NBT_SAVED_ROUTES, (nbt, val, name) -> { + ListTag list = new ListTag(); + list.addAll(val); + nbt.put(name, list); + }, (nbt, name) -> { + return nbt.getList(name, Tag.TAG_COMPOUND).stream().map(x -> (CompoundTag)x).collect(Collectors.toSet()); + },(val) -> TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".saved_routes.saved", val.size()).getString())); + + public final UserSetting searchDepartureInTicks = registerSetting(new UserSetting<>(() -> 0, NBT_SEARCH_DEPARTURE_TIME, (nbt, val, name) -> nbt.putInt(name, val), (nbt, name) -> nbt.getInt(name), (val) -> TimeUtils.parseDurationShort(val))); + public final UserSetting> searchExcludedTrainGroups = registerSetting(new UserSetting<>(() -> new HashSet<>(), NBT_SEARCH_TRAIN_GROUPS, + (nbt, val, name) -> { + ListTag list = new ListTag(); + list.addAll(val.stream().map(x -> StringTag.valueOf(x)).toList()); + nbt.put(name, list); + }, (nbt, name) -> { + return nbt.getList(name, Tag.TAG_STRING).stream().filter(x -> GlobalSettings.hasInstance() ? GlobalSettings.getInstance().trainGroupExists(x.getAsString()) : true).map(x -> x.getAsString()).collect(Collectors.toSet()); + },(val) -> val.isEmpty() ? TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".search_options.train_groups.all").getString() : TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".search_options.train_groups.excluded", val.size()).getString())); + + public UserSettings(UUID playerId, boolean readOnly) { + this.owner = playerId; + this.readOnly = readOnly; + } + + public UUID getOwnerId() { + return owner; + } + + public boolean isReadOnly() { + return readOnly; + } + + private void checkReadOnly() { + if (isReadOnly()) { + throw new IllegalAccessError("This instance of the user settings is read-only!"); + } + } - private final int transferTime; - private final List trainGroupBlacklist; + private static void update(UserSettings settings) { + if (settingsInstances.containsKey(settings.getOwnerId())) { + settingsInstances.get(settings.getOwnerId()).checkReadOnly(); + } + settingsInstances.put(settings.getOwnerId(), settings); + } - public UserSettings() { - this(ModClientConfig.TRANSFER_TIME.get(), ModClientConfig.TRAIN_GROUP_FILTER_BLACKLIST.get()); + protected > T registerSetting(T setting) { + allSettings.add(setting); + return setting; } - private UserSettings(int transferTime, List trainGroupBlacklist) { - this.transferTime = transferTime; - this.trainGroupBlacklist = trainGroupBlacklist; + + public static UserSettings getSettingsFor(UUID playerId, boolean readOnly) { + return settingsInstances.computeIfAbsent(playerId, x -> UserSettings.load(x, readOnly)); } - public CompoundTag toNbt() { + /** Client-side only! */ + public final void clientSave(Runnable andThen) throws RuntimeSideException { + if (Platform.getEnv() == EnvType.SERVER) { + throw new RuntimeSideException(true); + } + checkReadOnly(); + DataAccessor.getFromServer(this, ModAccessorTypes.SAVE_USER_SETTINGS, $ -> DLUtils.doIfNotNull(andThen, x -> x.run())); + } + + /** Server-side only! */ + public final synchronized void save() throws RuntimeSideException { + if (!ModCommonEvents.hasServer()) { + throw new RuntimeSideException(false); + } + checkReadOnly(); + UserSettings.update(this); + CompoundTag nbt = this.toNbt(); + try { + NbtIo.writeCompressed(nbt, new File(ModCommonEvents.getCurrentServer().get().getWorldPath(new LevelResource("data/" + FILENAME + getOwnerId() + ".nbt")).toString())); + CreateRailwaysNavigator.LOGGER.info("Saved user settings."); + } catch (IOException e) { + CreateRailwaysNavigator.LOGGER.error("Unable to save user settings.", e); + } + } + + /** Server-side only! */ + public static UserSettings load(UUID playerId, boolean readOnly) throws RuntimeSideException { + if (!ModCommonEvents.hasServer()) { + throw new RuntimeSideException(false); + } + + File settingsFile = new File(ModCommonEvents.getCurrentServer().get().getWorldPath(new LevelResource("data/" + FILENAME + playerId + ".nbt")).toString()); + + if (settingsFile.exists()) { + try { + return UserSettings.fromNbt(NbtIo.readCompressed(settingsFile), playerId, readOnly); + } catch (IOException e) { + CreateRailwaysNavigator.LOGGER.error("Cannot load user settings for player: " + playerId, e); + } + } + return new UserSettings(playerId, readOnly); + } + + public final CompoundTag toNbt() { CompoundTag nbt = new CompoundTag(); - nbt.putInt(NBT_TRANSFER_TIME, getTransferTime()); - ListTag list = new ListTag(); - list.addAll(getTrainGroupBlacklist().stream().map(x -> StringTag.valueOf(x)).toList()); - nbt.put(NBT_TRAIN_GROUPS, list); + allSettings.forEach(x -> x.serialize(nbt)); + nbt.putInt(NBT_VERSION, VERSION); return nbt; } - public static UserSettings fromNbt(CompoundTag nbt) { - return new UserSettings( - nbt.getInt(NBT_TRANSFER_TIME), - nbt.getList(NBT_TRAIN_GROUPS, Tag.TAG_STRING).stream().map(x -> ((StringTag)x).getAsString()).toList() - ); + public final static UserSettings fromNbt(CompoundTag nbt, UUID playerId, boolean readOnly) { + UserSettings settings = new UserSettings(playerId, readOnly); + @SuppressWarnings("unused") final int version = nbt.getInt(NBT_VERSION); + settings.allSettings.forEach(x -> x.deserialize(nbt)); + return settings; } - public int getTransferTime() { - return transferTime; - } + public static class UserSetting { + private T value; + private final String serializationName; + private final Supplier defaultValue; + private final ISerializationContext serializer; + private final BiFunction deserializer; + private final Function stringRepresentation; + + public UserSetting(Supplier defaultValue, String serializationName, ISerializationContext serializer, BiFunction deserializer, Function stringRepresentation) { + this.defaultValue = defaultValue; + this.serializer = serializer; + this.serializationName = serializationName; + this.deserializer = deserializer; + this.stringRepresentation = stringRepresentation; + this.value = defaultValue.get(); + } + + public T getValue() { + return value; + } + + public void setValue(T value) { + this.value = value; + } + + public void setToDefault() { + this.value = getDefault(); + } + + public T getDefault() { + return defaultValue.get(); + } + + private void serialize(CompoundTag nbt) { + serializer.execute(nbt, value, getSerializationName()); + } + + private T deserialize(CompoundTag nbt) { + return value = deserializer.apply(nbt, getSerializationName()); + } + + private String getSerializationName() { + return serializationName; + } - public List getTrainGroupBlacklist() { - return trainGroupBlacklist; + @Override + public String toString() { + return stringRepresentation.apply(value); + } } - public boolean isTrainExcluded(Train train, GlobalSettings settingsInstance) { - boolean b = settingsInstance.getTrainGroupsList().stream().filter(x -> getTrainGroupBlacklist().contains(x.getGroupName())).anyMatch(x -> x.contains(train)); - return b; + @FunctionalInterface + private static interface ISerializationContext { + void execute(CompoundTag nbt, T value, String serializationName); } } diff --git a/common/src/main/java/de/mrjulsen/crn/data/navigation/ClientRoute.java b/common/src/main/java/de/mrjulsen/crn/data/navigation/ClientRoute.java new file mode 100644 index 00000000..4feba82a --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/data/navigation/ClientRoute.java @@ -0,0 +1,673 @@ +package de.mrjulsen.crn.data.navigation; + +import java.util.Collection; +import java.util.HashMap; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Map; +import java.util.Queue; +import java.util.UUID; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.function.Consumer; + +import java.lang.StringBuilder; + +import de.mrjulsen.crn.CreateRailwaysNavigator; +import de.mrjulsen.crn.client.ClientWrapper; +import de.mrjulsen.crn.client.lang.ELanguage; +import de.mrjulsen.crn.data.SavedRoutesManager; +import de.mrjulsen.crn.data.train.ClientTrainStop; +import de.mrjulsen.crn.data.train.RoutePartProgressState; +import de.mrjulsen.crn.data.train.RouteProgressState; +import de.mrjulsen.crn.util.ModUtils; +import de.mrjulsen.crn.event.CRNEventsManager; +import de.mrjulsen.crn.event.events.DefaultTrainDataRefreshEvent; +import de.mrjulsen.crn.util.IListenable; +import de.mrjulsen.mcdragonlib.data.Cache; +import de.mrjulsen.mcdragonlib.util.TimeUtils; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.Tag; +import net.minecraft.network.chat.Component; + +public class ClientRoute extends Route implements AutoCloseable, IListenable { + + public static record ListenerNotificationData(ClientRoute route, ClientRoutePart part, ClientTrainStop trainStop, TransferConnection connection) {} + public static record QueuedAnnouncementEvent(Runnable callback, ClientRoutePart part, ClientTrainStop trainStop) {} + + /** Called every time the real-time data updates. */ + public static final String EVENT_UPDATE = "update"; + + /** Called when announcing the start of the journey. */ + public static final String EVENT_ANNOUNCE_START = "announce_start"; + /** Called when arriving at the first station of the journey. */ + public static final String EVENT_ARRIVAL_AT_START = "arrival_at_start"; + /** Called when departing from the first station of the journey. */ + public static final String EVENT_DEPARTURE_FROM_START = "departure_from_start"; + + /** Called while travelling between two stations. */ + public static final String EVENT_WHILE_TRANSIT = "while_transit"; + + /** Called when announcing a stopover station with no special role on the route. */ + public static final String EVENT_ANNOUNCE_STOPOVER = "announce_stopover"; + /** Called when arriving at a stopover station with no special role on the route. */ + public static final String EVENT_ARRIVAL_AT_STOPOVER = "arrival_at_stopover"; + /** Called when departing from a stopover station with no special role on the route. */ + public static final String EVENT_DEPARTURE_AT_STOPOVER = "departure_from_stopover"; + + /** Called when announcing the arrival at a transfer station. */ + public static final String EVENT_ANNOUNCE_TRANSFER_ARRIVAL_STATION = "announce_transfer_arrival_station"; + /** Called when arraving at a transfer station. */ + public static final String EVENT_ARRIVAL_AT_TRANSFER_ARRIVAL_STATION = "arrival_at_transfer_arrival_station"; + /** Called when the train that brought you to the transfer station departs. */ + public static final String EVENT_DEPARTURE_FROM_TRANSFER_ARRIVAL_STATION = "departure_from_transfer_arrival_station"; + /** Called while waiting for the connecting train. */ + public static final String EVENT_WHILE_TRANSFER = "while_transfer"; + /** Called when the connecting train announces arrival at a transfer station. */ + public static final String EVENT_ANNOUNCE_TRANSFER_DEPARTURE_STATION = "announce_transfer_departure_station"; + /** Called when the connecting train arrives at a transfer station. */ + public static final String EVENT_ARRIVAL_AT_TRANSFER_DEPARTURE_STATION = "arrival_at_transfer_departure_station"; + /** Called when the connecting train departs at a transfer station. */ + public static final String EVENT_DEPARTURE_FROM_TRANSFER_DEPARTURE_STATION = "departure_from_transfer_departure_station"; + + /** Called when announcing the end of the journey. */ + public static final String EVENT_ANNOUNCE_LAST_STOP = "announce_last_stop"; + /** Called when arriving at the last station of the journey. */ + public static final String EVENT_ARRIVAL_AT_LAST_STOP = "arrival_at_last_stop"; + /** Called when departing from the last station of the journey. */ + public static final String EVENT_DEPARTURE_FROM_LAST_STOP = "departure_from_last_stop"; + + /** Called when the first stop changes. */ + public static final String EVENT_FIRST_STOP_STATION_CHANGED = "first_stop_station_changed"; + /** Called when the first stop is delayed. */ + public static final String EVENT_FIRST_STOP_DELAYED = "first_stop_delayed"; + + /** Called when the transfer arrival station changes. */ + public static final String EVENT_TRANSFER_ARRIVAL_STATION_CHANGED = "transfer_arrival_station_changed"; + /** Called when the transfer arrival station is delayed. */ + public static final String EVENT_TRANSFER_ARRIVAL_DELAYED = "transfer_arrival_delayed"; + /** Called when the transfer departure station changes. */ + public static final String EVENT_TRANSFER_DEPARTURE_STATION_CHANGED = "transfer_departure_station_changed"; + /** Called when the transfer departure station is delayed. */ + public static final String EVENT_TRANSFER_DEPARTURE_DELAYED = "transfer_departure_delayed"; + + /** Called when the last stop changes. */ + public static final String EVENT_LAST_STOP_STATION_CHANGED = "last_stop_station_changed"; + /** Called when the last stop is delayed. */ + public static final String EVENT_LAST_STOP_DELAYED = "last_stop_delayed"; + + /** Called when any stop is announced. */ + public static final String EVENT_ANY_STOP_ANNOUNCED = "any_stop_announced"; + /** Called when arriving at any stop. */ + public static final String EVENT_ARRIVAL_AT_ANY_STOP = "arrival_at_any_stop"; + /** Called when departing from any stop. */ + public static final String EVENT_DEPARTURE_FROM_ANY_STOP = "departure_from_any_stop"; + /** Called when announcing any important station (start, transfers and end) */ + public static final String EVENT_ANNOUNCE_ANY_IMPORTANT_STATION = "announce_any_important_station"; + /** Called when arriving at any important station (start, transfers and end) */ + public static final String EVENT_ARRIVAL_AT_ANY_IMPORTANT_STATION = "arrival_at_any_important_station"; + /** Called when departing from any important station (start, transfers and end) */ + public static final String EVENT_DEPARTURE_FROM_ANY_IMPORTANT_STATION = "departure_from_any_important_station"; + /** Called when any important station (start, transfers and end) reports delays. */ + public static final String EVENT_ANY_STATION_DELAYED = "any_station_delayed"; + /** Called when any important station (start, transfers and end) reports changes. */ + public static final String EVENT_ANY_STATION_CHANGED = "any_station_changed"; + + /** Called when a transfer connection is endangered. */ + public static final String EVENT_ANY_TRANSFER_ENDANGERED = "any_transfer_endangered"; + /** Called when a transfer connection is missed. */ + public static final String EVENT_ANY_TRANSFER_MISSED = "any_transfer_missed"; + /** Called when the route part changes. */ + public static final String EVENT_PART_CHANGED = "part_changed"; + public static final String EVENT_SCHEDULE_CHANGED = "schedule_changed"; + public static final String EVENT_ANY_TRAIN_CANCELLED = "train_cancelled"; + + + // Texts + private static final String keyNotificationJourneyBeginsTitle = "gui.createrailwaysnavigator.route_overview.notification.journey_begins.title"; + private static final String keyNotificationJourneyBegins = "gui.createrailwaysnavigator.route_overview.notification.journey_begins"; + private static final String keyNotificationJourneyBeginsWithPlatform = "gui.createrailwaysnavigator.route_overview.notification.journey_begins_with_platform"; + private static final String keyNotificationPlatformChangedTitle = "gui.createrailwaysnavigator.route_overview.notification.platform_changed.title"; + private static final String keyNotificationPlatformChanged = "gui.createrailwaysnavigator.route_overview.notification.platform_changed"; + private static final String keyNotificationTrainDelayedTitle = "gui.createrailwaysnavigator.route_overview.notification.train_delayed.title"; + private static final String keyNotificationTrainDelayed = "gui.createrailwaysnavigator.route_overview.notification.train_delayed"; + private static final String keyNotificationTransferTitle = "gui.createrailwaysnavigator.route_overview.notification.transfer.title"; + private static final String keyNotificationTransfer = "gui.createrailwaysnavigator.route_overview.notification.transfer"; + private static final String keyNotificationTransferWithPlatform = "gui.createrailwaysnavigator.route_overview.notification.transfer_with_platform"; + private static final String keyNotificationConnectionEndangeredTitle = "gui.createrailwaysnavigator.route_overview.notification.connection_endangered.title"; + private static final String keyNotificationConnectionEndangered = "gui.createrailwaysnavigator.route_overview.notification.connection_endangered"; + private static final String keyNotificationConnectionMissedTitle = "gui.createrailwaysnavigator.route_overview.notification.connection_missed.title"; + private static final String keyNotificationConnectionMissed = "gui.createrailwaysnavigator.route_overview.notification.connection_missed"; + private static final String keyNotificationJourneyCompletedTitle = "gui.createrailwaysnavigator.route_overview.notification.journey_completed.title"; + private static final String keyNotificationJourneyCompleted = "gui.createrailwaysnavigator.route_overview.notification.journey_completed"; + private static final String keyNotificationConnectionCanceledTitle = "gui.createrailwaysnavigator.route_overview.connection_cancelled"; + private static final String keyNotificationConnectionCanceled = "gui.createrailwaysnavigator.route_overview.journey_interrupted"; + + private final Map>> listeners = new HashMap<>(); + private final Map> queuedNotifications = new HashMap<>(); + + private final long id = System.nanoTime(); + private final Map listenerIds = new HashMap<>(); + private int listenersCount = 0; + + private boolean isClosed; + + // State + private RouteProgressState progressState = RouteProgressState.BEFORE; + private final Queue queuedAnnouncements = new ConcurrentLinkedQueue<>(); + private ClientRoutePart currentPart; + private int currentPartIndex; + private final Cache> clientParts = new Cache<>(() -> getParts().stream().filter(x -> x instanceof ClientRoutePart).map(x -> (ClientRoutePart)x).toList()); + + // User settings + private boolean savedRouteRemoved = false; + private boolean showNotifications = false; + + // spam blocker + private boolean stationChangedSent = false; + private boolean scheduleChangedSent = false; + private boolean stationDelayedSent = false; + private boolean connectionWarningSent = false; + private boolean cancelledSent = false; + + private void resetSpamBlockers() { + stationChangedSent = false; + scheduleChangedSent = false; + connectionWarningSent = false; + stationDelayedSent = false; + cancelledSent = false; + } + + public ClientRoute(List parts, boolean realTimeTracker) { + super(parts, realTimeTracker); + this.currentPart = getFirstClientPart(); + + if (!realTimeTracker) return; + getClientParts().stream().forEach(x -> listenerIds.put(ClientTrainListener.register(x.getSessionId(), x.getTrainId(), x::update), x)); + CRNEventsManager.getEvent(DefaultTrainDataRefreshEvent.class).register(CreateRailwaysNavigator.MOD_ID + "_" + id, this::update); + addListener(); + + createEvent(EVENT_UPDATE); + createEvent(EVENT_ANNOUNCE_START); + createEvent(EVENT_ARRIVAL_AT_START); + createEvent(EVENT_DEPARTURE_FROM_START); + createEvent(EVENT_WHILE_TRANSIT); + createEvent(EVENT_ANNOUNCE_STOPOVER); + createEvent(EVENT_ARRIVAL_AT_STOPOVER); + createEvent(EVENT_DEPARTURE_AT_STOPOVER); + createEvent(EVENT_ANNOUNCE_TRANSFER_ARRIVAL_STATION); + createEvent(EVENT_ARRIVAL_AT_TRANSFER_ARRIVAL_STATION); + createEvent(EVENT_DEPARTURE_FROM_TRANSFER_ARRIVAL_STATION); + createEvent(EVENT_WHILE_TRANSFER); + createEvent(EVENT_ANNOUNCE_TRANSFER_DEPARTURE_STATION); + createEvent(EVENT_ARRIVAL_AT_TRANSFER_DEPARTURE_STATION); + createEvent(EVENT_DEPARTURE_FROM_TRANSFER_DEPARTURE_STATION); + createEvent(EVENT_ANNOUNCE_LAST_STOP); + createEvent(EVENT_ARRIVAL_AT_LAST_STOP); + createEvent(EVENT_DEPARTURE_FROM_LAST_STOP); + createEvent(EVENT_FIRST_STOP_STATION_CHANGED); + createEvent(EVENT_FIRST_STOP_DELAYED); + createEvent(EVENT_TRANSFER_ARRIVAL_STATION_CHANGED); + createEvent(EVENT_TRANSFER_ARRIVAL_DELAYED); + createEvent(EVENT_TRANSFER_DEPARTURE_STATION_CHANGED); + createEvent(EVENT_TRANSFER_DEPARTURE_DELAYED); + createEvent(EVENT_LAST_STOP_STATION_CHANGED); + createEvent(EVENT_LAST_STOP_DELAYED); + createEvent(EVENT_ANY_STOP_ANNOUNCED); + createEvent(EVENT_ARRIVAL_AT_ANY_STOP); + createEvent(EVENT_DEPARTURE_FROM_ANY_STOP); + createEvent(EVENT_ANNOUNCE_ANY_IMPORTANT_STATION); + createEvent(EVENT_ARRIVAL_AT_ANY_IMPORTANT_STATION); + createEvent(EVENT_DEPARTURE_FROM_ANY_IMPORTANT_STATION); + createEvent(EVENT_ANY_STATION_DELAYED); + createEvent(EVENT_ANY_STATION_CHANGED); + createEvent(EVENT_ANY_TRANSFER_ENDANGERED); + createEvent(EVENT_ANY_TRANSFER_MISSED); + createEvent(EVENT_PART_CHANGED); + createEvent(EVENT_SCHEDULE_CHANGED); + createEvent(EVENT_ANY_TRAIN_CANCELLED); + + + getFirstClientPart().listen(ClientRoutePart.EVENT_ANNOUNCE_START, this, x -> { + if (currentPartIndex > 0) return; + + sendNotification( + ELanguage.translate(keyNotificationJourneyBeginsTitle, getEnd().getClientTag().tagName()), + getStart().getRealTimeStationTag().info().isPlatformKnown() ? + ELanguage.translate(keyNotificationJourneyBeginsWithPlatform, getStart().getTrainDisplayName(), getStart().getDisplayTitle(), ModUtils.formatTime(getStart().getScheduledDepartureTime(), false), getStart().getRealTimeStationTag().info().platform()) : + ELanguage.translate(keyNotificationJourneyBegins, getStart().getTrainDisplayName(), getStart().getDisplayTitle(), ModUtils.formatTime(getStart().getScheduledDepartureTime(), false)) + ); + + queuedAnnouncements.add(new QueuedAnnouncementEvent(() -> { + notifyListeners(EVENT_ANY_STOP_ANNOUNCED, new ListenerNotificationData(this, x.part(), x.trainStop(), null)); + notifyListeners(EVENT_ANNOUNCE_ANY_IMPORTANT_STATION, new ListenerNotificationData(this, x.part(), x.trainStop(), null)); + notifyListeners(EVENT_ANNOUNCE_START, new ListenerNotificationData(this, x.part(), x.trainStop(), null)); + + }, x.part(), x.trainStop())); + }); + getFirstClientPart().listen(ClientRoutePart.EVENT_ARRIVAL_AT_START, this, x -> { + if (currentPartIndex != 0) return; + this.progressState = RouteProgressState.AT_START; + notifyListeners(EVENT_ARRIVAL_AT_ANY_STOP, new ListenerNotificationData(this, x.part(), x.trainStop(), null)); + notifyListeners(EVENT_ARRIVAL_AT_ANY_IMPORTANT_STATION, new ListenerNotificationData(this, x.part(), x.trainStop(), null)); + notifyListeners(EVENT_ARRIVAL_AT_START, new ListenerNotificationData(this, x.part(), x.trainStop(), null)); + }); + getFirstClientPart().listen(ClientRoutePart.EVENT_DEPARTURE_FROM_START, this, x -> { + if (currentPartIndex != 0) return; + this.progressState = RouteProgressState.TRAVELING; + notifyListeners(EVENT_DEPARTURE_FROM_ANY_STOP, new ListenerNotificationData(this, x.part(), x.trainStop(), null)); + notifyListeners(EVENT_DEPARTURE_FROM_ANY_IMPORTANT_STATION, new ListenerNotificationData(this, x.part(), x.trainStop(), null)); + notifyListeners(EVENT_DEPARTURE_FROM_START, new ListenerNotificationData(this, x.part(), x.trainStop(), null)); + }); + getFirstClientPart().listen(ClientRoutePart.EVENT_FIRST_STOP_STATION_CHANGED, this, x -> { + if (currentPartIndex > 0) return; + notifyListeners(EVENT_ANY_STATION_CHANGED, new ListenerNotificationData(this, x.part(), x.trainStop(), null)); + notifyListeners(EVENT_FIRST_STOP_STATION_CHANGED, new ListenerNotificationData(this, x.part(), x.trainStop(), null)); + }); + getFirstClientPart().listen(ClientRoutePart.EVENT_FIRST_STOP_DELAYED, this, x -> { + if (currentPartIndex > 0) return; + notifyListeners(EVENT_ANY_STATION_DELAYED, new ListenerNotificationData(this, x.part(), x.trainStop(), null)); + notifyListeners(EVENT_FIRST_STOP_DELAYED, new ListenerNotificationData(this, x.part(), x.trainStop(), null)); + }); + + for (int i = 0; i < getParts().size(); i++) { + ClientRoutePart part = getClientParts().get(i); + final int k = i; + + if (i > 0) { + part.listen(ClientRoutePart.EVENT_ANNOUNCE_START, this, x -> { + if (currentPartIndex > k) return; + queuedAnnouncements.add(new QueuedAnnouncementEvent(() -> { + notifyListeners(EVENT_ANY_STOP_ANNOUNCED, new ListenerNotificationData(this, x.part(), x.trainStop(), null)); + notifyListeners(EVENT_ANNOUNCE_ANY_IMPORTANT_STATION, new ListenerNotificationData(this, x.part(), x.trainStop(), null)); + notifyListeners(EVENT_ANNOUNCE_TRANSFER_DEPARTURE_STATION, new ListenerNotificationData(this, x.part(), x.trainStop(), getConnectionWith(x.trainStop()).orElse(null))); + }, x.part(), x.trainStop())); + }); + part.listen(ClientRoutePart.EVENT_ARRIVAL_AT_START, this, x -> { + if (currentPartIndex != k) return; + this.progressState = RouteProgressState.BEFORE_CONTINUATION; + notifyListeners(EVENT_ARRIVAL_AT_ANY_STOP, new ListenerNotificationData(this, x.part(), x.trainStop(), null)); + notifyListeners(EVENT_ARRIVAL_AT_ANY_IMPORTANT_STATION, new ListenerNotificationData(this, x.part(), x.trainStop(), null)); + notifyListeners(EVENT_ARRIVAL_AT_TRANSFER_DEPARTURE_STATION, new ListenerNotificationData(this, x.part(), x.trainStop(), getConnectionWith(x.trainStop()).orElse(null))); + }); + part.listen(ClientRoutePart.EVENT_DEPARTURE_FROM_START, this, x -> { + if (currentPartIndex != k) return; + this.progressState = RouteProgressState.TRAVELING; + notifyListeners(EVENT_DEPARTURE_FROM_ANY_STOP, new ListenerNotificationData(this, x.part(), x.trainStop(), null)); + notifyListeners(EVENT_DEPARTURE_FROM_ANY_IMPORTANT_STATION, new ListenerNotificationData(this, x.part(), x.trainStop(), null)); + notifyListeners(EVENT_DEPARTURE_FROM_TRANSFER_DEPARTURE_STATION, new ListenerNotificationData(this, x.part(), x.trainStop(), getConnectionWith(x.trainStop()).orElse(null))); + }); + part.listen(ClientRoutePart.EVENT_FIRST_STOP_STATION_CHANGED, this, x -> { + if (currentPartIndex > k) return; + notifyListeners(EVENT_ANY_STATION_CHANGED, new ListenerNotificationData(this, x.part(), x.trainStop(), null)); + notifyListeners(EVENT_TRANSFER_DEPARTURE_STATION_CHANGED, new ListenerNotificationData(this, x.part(), x.trainStop(), getConnectionWith(x.trainStop()).orElse(null))); + }); + part.listen(ClientRoutePart.EVENT_FIRST_STOP_DELAYED, this, x -> { + if (currentPartIndex > k) return; + notifyListeners(EVENT_ANY_STATION_DELAYED, new ListenerNotificationData(this, x.part(), x.trainStop(), null)); + notifyListeners(EVENT_TRANSFER_DEPARTURE_DELAYED, new ListenerNotificationData(this, x.part(), x.trainStop(), getConnectionWith(x.trainStop()).orElse(null))); + }); + } + + part.listen(ClientRoutePart.EVENT_NEXT_STOP_ANNOUNCED, this, x -> { + if (currentPartIndex > k) return; + queuedAnnouncements.add(new QueuedAnnouncementEvent(() -> { + this.progressState = RouteProgressState.NEXT_STOP_ANNOUNCED; + notifyListeners(EVENT_ANY_STOP_ANNOUNCED, new ListenerNotificationData(this, x.part(), x.trainStop(), null)); + notifyListeners(EVENT_ANNOUNCE_STOPOVER, new ListenerNotificationData(this, x.part(), x.trainStop(), null)); + }, x.part(), x.trainStop())); + }); + part.listen(ClientRoutePart.EVENT_ARRIVAL_AT_STOPOVER, this, x -> { + if (currentPartIndex != k) return; + this.progressState = RouteProgressState.AT_STOPOVER; + notifyListeners(EVENT_ARRIVAL_AT_ANY_STOP, new ListenerNotificationData(this, x.part(), x.trainStop(), null)); + notifyListeners(EVENT_ARRIVAL_AT_STOPOVER, new ListenerNotificationData(this, x.part(), x.trainStop(), null)); + }); + part.listen(ClientRoutePart.EVENT_DEPARTURE_FROM_STOPOVER, this, x -> { + if (currentPartIndex != k) return; + this.progressState = RouteProgressState.TRAVELING; + notifyListeners(EVENT_DEPARTURE_FROM_ANY_STOP, new ListenerNotificationData(this, x.part(), x.trainStop(), null)); + notifyListeners(EVENT_DEPARTURE_AT_STOPOVER, new ListenerNotificationData(this, x.part(), x.trainStop(), null)); + }); + + part.listen(ClientRoutePart.EVENT_SCHEDULE_CHANGED, this, x -> { + if (scheduleChangedSent) return; + sendNotification(ELanguage.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".route_overview.notification.schedule_changed.title"), ELanguage.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".route_overview.notification.schedule_changed")); + notifyListeners(EVENT_SCHEDULE_CHANGED, new ListenerNotificationData(this, x.part(), x.trainStop(), null)); + scheduleChangedSent = true; + }); + + part.listen(ClientRoutePart.EVENT_TRAIN_CANCELLED, this, x -> { + if (cancelledSent) return; + sendNotification(ELanguage.translate(keyNotificationConnectionCanceledTitle), ELanguage.translate(keyNotificationConnectionCanceled, x.part().getFirstStop().getTrainDisplayName())); + notifyListeners(EVENT_ANY_TRAIN_CANCELLED, new ListenerNotificationData(this, x.part(), x.trainStop(), null)); + cancelledSent = true; + }); + + if (i < parts.size() - 1) { + part.listen(ClientRoutePart.EVENT_LAST_STOP_ANNOUNCED, this, x -> { + if (currentPartIndex > k) return; + + queuedAnnouncements.add(new QueuedAnnouncementEvent(() -> { + this.progressState = RouteProgressState.TRANSFER_ANNOUNCED; + notifyListeners(EVENT_ANY_STOP_ANNOUNCED, new ListenerNotificationData(this, x.part(), x.trainStop(), null)); + notifyListeners(EVENT_ANNOUNCE_ANY_IMPORTANT_STATION, new ListenerNotificationData(this, x.part(), x.trainStop(), null)); + notifyListeners(EVENT_ANNOUNCE_TRANSFER_ARRIVAL_STATION, new ListenerNotificationData(this, x.part(), x.trainStop(), getConnectionWith(x.trainStop()).orElse(null))); + }, x.part(), x.trainStop())); + }); + part.listen(ClientRoutePart.EVENT_ARRIVAL_AT_LAST_STOP, this, x -> { + if (currentPartIndex != k) return; + this.progressState = RouteProgressState.AT_TRANSFER; + notifyListeners(EVENT_ARRIVAL_AT_ANY_STOP, new ListenerNotificationData(this, x.part(), x.trainStop(), null)); + notifyListeners(EVENT_ARRIVAL_AT_ANY_IMPORTANT_STATION, new ListenerNotificationData(this, x.part(), x.trainStop(), null)); + notifyListeners(EVENT_ARRIVAL_AT_TRANSFER_ARRIVAL_STATION, new ListenerNotificationData(this, x.part(), x.trainStop(), getConnectionWith(x.trainStop()).orElse(null))); + }); + part.listen(ClientRoutePart.EVENT_DEPARTURE_FROM_LAST_STOP, this, x -> { + if (currentPartIndex != k) return; + this.progressState = RouteProgressState.WHILE_TRANSFER; + notifyListeners(EVENT_DEPARTURE_FROM_ANY_STOP, new ListenerNotificationData(this, x.part(), x.trainStop(), null)); + notifyListeners(EVENT_DEPARTURE_FROM_ANY_IMPORTANT_STATION, new ListenerNotificationData(this, x.part(), x.trainStop(), null)); + notifyListeners(EVENT_DEPARTURE_FROM_TRANSFER_ARRIVAL_STATION, new ListenerNotificationData(this, x.part(), x.trainStop(), getConnectionWith(x.trainStop()).orElse(null))); + }); + part.listen(ClientRoutePart.EVENT_LAST_STOP_STATION_CHANGED, this, x -> { + if (currentPartIndex > k) return; + notifyListeners(EVENT_ANY_STATION_CHANGED, new ListenerNotificationData(this, x.part(), x.trainStop(), null)); + notifyListeners(EVENT_TRANSFER_ARRIVAL_STATION_CHANGED, new ListenerNotificationData(this, x.part(), x.trainStop(), getConnectionWith(x.trainStop()).orElse(null))); + }); + part.listen(ClientRoutePart.EVENT_LAST_STOP_DELAYED, this, x -> { + if (currentPartIndex > k) return; + notifyListeners(EVENT_ANY_STATION_DELAYED, new ListenerNotificationData(this, x.part(), x.trainStop(), null)); + notifyListeners(EVENT_TRANSFER_ARRIVAL_DELAYED, new ListenerNotificationData(this, x.part(), x.trainStop(), getConnectionWith(x.trainStop()).orElse(null))); + }); + } + } + + getLastClientPart().listen(ClientRoutePart.EVENT_LAST_STOP_ANNOUNCED, this, x -> { + if (currentPartIndex > parts.size() - 1) return; + queuedAnnouncements.add(new QueuedAnnouncementEvent(() -> { + this.progressState = RouteProgressState.END_ANNOUNCED; + notifyListeners(EVENT_ANY_STOP_ANNOUNCED, new ListenerNotificationData(this, x.part(), x.trainStop(), null)); + notifyListeners(EVENT_ANNOUNCE_ANY_IMPORTANT_STATION, new ListenerNotificationData(this, x.part(), x.trainStop(), null)); + notifyListeners(EVENT_ANNOUNCE_LAST_STOP, new ListenerNotificationData(this, x.part(), x.trainStop(), null)); + }, x.part(), x.trainStop())); + }); + getLastClientPart().listen(ClientRoutePart.EVENT_ARRIVAL_AT_LAST_STOP, this, x -> { + if (currentPartIndex != parts.size() - 1) return; + this.progressState = RouteProgressState.AT_END; + notifyListeners(EVENT_ARRIVAL_AT_ANY_STOP, new ListenerNotificationData(this, x.part(), x.trainStop(), null)); + notifyListeners(EVENT_ARRIVAL_AT_ANY_IMPORTANT_STATION, new ListenerNotificationData(this, x.part(), x.trainStop(), null)); + notifyListeners(EVENT_ARRIVAL_AT_LAST_STOP, new ListenerNotificationData(this, x.part(), x.trainStop(), null)); + }); + getLastClientPart().listen(ClientRoutePart.EVENT_DEPARTURE_FROM_LAST_STOP, this, x -> { + if (currentPartIndex < parts.size() - 1) return; + this.progressState = RouteProgressState.AFTER; + sendNotification(ELanguage.translate(keyNotificationJourneyCompletedTitle), ELanguage.translate(keyNotificationJourneyCompleted)); + if (!savedRouteRemoved) { + savedRouteRemoved = true; + SavedRoutesManager.removeRoute(this); + SavedRoutesManager.push(true, null); + } + queuedAnnouncements.clear(); + notifyListeners(EVENT_DEPARTURE_FROM_ANY_STOP, new ListenerNotificationData(this, x.part(), x.trainStop(), null)); + notifyListeners(EVENT_DEPARTURE_FROM_ANY_IMPORTANT_STATION, new ListenerNotificationData(this, x.part(), x.trainStop(), null)); + notifyListeners(EVENT_DEPARTURE_FROM_LAST_STOP, new ListenerNotificationData(this, x.part(), x.trainStop(), null)); + }); + getLastClientPart().listen(ClientRoutePart.EVENT_LAST_STOP_STATION_CHANGED, this, x -> { + if (currentPartIndex > parts.size() - 1) return; + notifyListeners(EVENT_ANY_STATION_CHANGED, new ListenerNotificationData(this, x.part(), x.trainStop(), null)); + notifyListeners(EVENT_LAST_STOP_STATION_CHANGED, new ListenerNotificationData(this, x.part(), x.trainStop(), null)); + }); + getLastClientPart().listen(ClientRoutePart.EVENT_LAST_STOP_DELAYED, this, x -> { + if (currentPartIndex > parts.size() - 1) return; + notifyListeners(EVENT_ANY_STATION_DELAYED, new ListenerNotificationData(this, x.part(), x.trainStop(), null)); + notifyListeners(EVENT_LAST_STOP_DELAYED, new ListenerNotificationData(this, x.part(), x.trainStop(), null)); + }); + + for (TransferConnection connection : getConnections()) { + connection.listen(TransferConnection.EVENT_CONNECTION_ENDANGERED, this, x -> { + if (connectionWarningSent) return; + queueConnectionEndangeredNotification(x); + notifyListeners(EVENT_ANY_TRANSFER_ENDANGERED, new ListenerNotificationData(this, null, null, x)); + connectionWarningSent = true; + }); + connection.listen(TransferConnection.EVENT_CONNECTION_MISSED, this, x -> { + if (connectionWarningSent) return; + queueConnectionMissedNotification(x); + notifyListeners(EVENT_ANY_TRANSFER_MISSED, new ListenerNotificationData(this, null, null, x)); + closeAll(); + connectionWarningSent = true; + }); + } + + listen(EVENT_DEPARTURE_FROM_TRANSFER_ARRIVAL_STATION, this, (p) -> { + int idx = parts.indexOf(p.part()); + if (idx >= 0) { + if (idx < parts.size() - 1) { + currentPart = getClientParts().get(idx + 1); + currentPartIndex = idx + 1; + ClientRoutePart part = getClientParts().get(currentPartIndex); + notifyListeners(EVENT_PART_CHANGED, new ListenerNotificationData(this, part, part.getFirstClientStop(), p.connection())); + + if (part.getProgressState() != RoutePartProgressState.BEFORE && part.getProgressState() != RoutePartProgressState.AT_START) { + queueConnectionMissedNotification(p.connection()); + notifyListeners(EVENT_ANY_TRANSFER_MISSED, new ListenerNotificationData(this, null, null, p.connection())); + closeAll(); + } + return; + } + } + currentPart = p.part(); + currentPartIndex = idx; + }); + + listen(EVENT_ANY_STATION_CHANGED, this, (p) -> { + if (stationChangedSent) return; + sendNotification(ELanguage.translate(keyNotificationPlatformChangedTitle), ELanguage.translate(keyNotificationPlatformChanged, + p.trainStop().getTrainDisplayName(), + p.trainStop().getRealTimeStationTag().info().platform() + )); + stationChangedSent = true; + }); + + listen(EVENT_ANY_STATION_DELAYED, this, (p) -> { + if (stationDelayedSent) return; + queueDelayNotification(p.trainStop(), p.part().getFirstStop() == p.trainStop()); + stationDelayedSent = true; + }); + + listen(EVENT_ANNOUNCE_TRANSFER_ARRIVAL_STATION, this, (p) -> { + sendNotification(ELanguage.translate(keyNotificationTransferTitle), getStart().getRealTimeStationTag().info().isPlatformKnown() ? ELanguage.translate(keyNotificationTransferWithPlatform, + p.connection().getDepartureStation().getTrainDisplayName(), + p.connection().getDepartureStation().getDisplayTitle(), + p.connection().getDepartureStation().getRealTimeStationTag().info().platform() + ) : ELanguage.translate(keyNotificationTransfer, + p.connection().getDepartureStation().getTrainDisplayName(), + p.connection().getDepartureStation().getDisplayTitle() + ) + ); + }); + } + + private void sendNotification(Component title, Component description) { + if (shouldShowNotifications()) { + ClientWrapper.sendCRNNotification(title, description); + } + } + + private void queueDelayNotification(ClientTrainStop stop, boolean start) { + if (shouldShowNotifications()) { + ClientWrapper.sendCRNNotification( + ELanguage.translate(keyNotificationTrainDelayedTitle, stop.getTrainDisplayName(), TimeUtils.parseDurationShort((int)(start ? stop.getDepartureTimeDeviation() : stop.getArrivalTimeDeviation()))), + ELanguage.translate(keyNotificationTrainDelayed, + ModUtils.formatTime(start ? stop.getRoundedRealTimeDepartureTime() : stop.getRoundedRealTimeArrivalTime(), false), + ModUtils.formatTime(start ? stop.getScheduledDepartureTime() : stop.getScheduledArrivalTime(), false), + stop.getClientTag().tagName() + )); + } + } + + private void queueConnectionEndangeredNotification(TransferConnection connection) { + if (shouldShowNotifications()) { + ClientWrapper.sendCRNNotification(ELanguage.translate(keyNotificationConnectionEndangeredTitle), ELanguage.translate(keyNotificationConnectionEndangered, connection.getDepartureStation().getTrainDisplayName(), connection.getDepartureStation().getDisplayTitle())); + } + } + + private void queueConnectionMissedNotification(TransferConnection connection) { + if (shouldShowNotifications()) { + ClientWrapper.sendCRNNotification(ELanguage.translate(keyNotificationConnectionMissedTitle), ELanguage.translate(keyNotificationConnectionMissed, connection.getDepartureStation().getTrainDisplayName(), connection.getDepartureStation().getDisplayTitle())); + } + } + + public static ClientRoute empty(boolean realTimeTracker) { + return new ClientRoute(List.of(), realTimeTracker); + } + + public RouteProgressState getState() { + return progressState; + } + + @Override + public Map>> getListeners() { + return listeners; + } + + public boolean shouldShowNotifications() { + return showNotifications; + } + + public void setShowNotifications(boolean b) { + this.showNotifications = b; + } + + public List getClientParts() { + return clientParts.get(); + } + + public ClientRoutePart getFirstClientPart() { + return getClientParts().get(0); + } + + public ClientRoutePart getLastClientPart() { + return getClientParts().get(getClientParts().size() - 1); + } +/* + public TrainStop getStart() { + return getFirstPart().getFirstStop(); + } + + public TrainStop getEnd() { + return getLastPart().getLastStop(); + } + + public ImmutableList getParts() { + return ImmutableList.copyOf(parts); + } + */ + + public ClientRoutePart getCurrentPart() { + return currentPart; + } + + public int getCurrentPartIndex() { + return parts.indexOf(currentPart); + } + + public void update() { + if (isClosed) { + return; + } + + if (isAnyCancelled()) { + closeAll(); + return; + } + + notifyListeners(EVENT_UPDATE, new ListenerNotificationData(this, getCurrentPart(), getCurrentPart().getNextStop(), null)); + if (getState() == RouteProgressState.TRAVELING) { + notifyListeners(EVENT_WHILE_TRANSIT, new ListenerNotificationData(this, getCurrentPart(), getCurrentPart().getNextStop(), null)); + } else if (getState() == RouteProgressState.WHILE_TRANSFER) { + notifyListeners(EVENT_WHILE_TRANSFER, new ListenerNotificationData(this, getCurrentPart(), getCurrentPart().getNextStop(), null)); + } + + if (getState() == RouteProgressState.TRAVELING || getState() == RouteProgressState.WHILE_TRANSFER) { + while (!queuedAnnouncements.isEmpty()) { + QueuedAnnouncementEvent event = queuedAnnouncements.poll(); + if (getCurrentPart().getNextStop() != event.trainStop() || getCurrentPart() != event.part()) { + continue; + } + event.callback().run(); + break; + } + } + + getConnections().stream().forEach(x -> x.update()); + + // process notifications + queuedNotifications.entrySet().forEach(x -> { + if (x.getValue().isEmpty()) { + return; + } + }); + queuedNotifications.clear(); + isCancelled.clear(); + resetSpamBlockers(); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("ROUTE[" + getStart().getClientTag().tagName() + " -> " + getEnd().getClientTag().tagName() + "]"); + return builder.toString(); + } + + public void addListener() { + listenersCount++; + } + + @Override + public void close() { + listenersCount--; + if (listenersCount <= 0) { + closeAll(); + } + } + + public void closeAll() { + listenersCount = 0; + listenerIds.entrySet().stream().forEach(x -> ClientTrainListener.unregister(x.getValue().getTrainId(), x.getKey())); + getClientParts().stream().forEach(x -> { + x.stopListeningAll(this); + x.close(); + }); + getConnections().stream().forEach(x -> { + x.stopListeningAll(this); + x.close(); + }); + stopListeningAll(this); + CRNEventsManager.getEvent(DefaultTrainDataRefreshEvent.class).unregister(CreateRailwaysNavigator.MOD_ID + "_" + id); + clearEvents(); + isClosed = true; + CreateRailwaysNavigator.LOGGER.info("Route listener closed."); + } + + public static ClientRoute fromNbt(CompoundTag nbt, boolean realTimeTracker) { + return new ClientRoute( + nbt.getList(NBT_PARTS, Tag.TAG_COMPOUND).stream().map(x -> ClientRoutePart.fromNbt((CompoundTag)x)).toList(), + realTimeTracker + ); + } + + @Override + public long timeOrderValue() { + return getStart().getScheduledDepartureTime(); + } + + private static enum NotificationType { + DELAY, + CONNECTION_ENDANGERED, + CONNECTION_MISSED + } + + public boolean isClosed() { + return isClosed; + } +} diff --git a/common/src/main/java/de/mrjulsen/crn/data/navigation/ClientRoutePart.java b/common/src/main/java/de/mrjulsen/crn/data/navigation/ClientRoutePart.java new file mode 100644 index 00000000..ec2d8296 --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/data/navigation/ClientRoutePart.java @@ -0,0 +1,323 @@ +package de.mrjulsen.crn.data.navigation; + +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.function.Consumer; +import java.util.stream.Collectors; +import java.util.Map; +import java.util.Queue; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.HashMap; + +import com.google.common.collect.ImmutableList; + +import de.mrjulsen.crn.CreateRailwaysNavigator; +import de.mrjulsen.crn.data.train.ClientTrainStop; +import de.mrjulsen.crn.data.train.RoutePartProgressState; +import de.mrjulsen.crn.data.train.TrainStop; +import de.mrjulsen.crn.data.train.ClientTrainStop.TrainStopRealTimeData; +import de.mrjulsen.crn.data.train.TrainStatus.CompiledTrainStatus; +import de.mrjulsen.crn.util.IListenable; +import de.mrjulsen.mcdragonlib.data.Cache; +import de.mrjulsen.mcdragonlib.data.Single.MutableSingle; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.ListTag; +import net.minecraft.nbt.Tag; + +public class ClientRoutePart extends RoutePart implements ITrainListenerClient, IListenable { + + public static record ListenerNotificationData(ClientRoutePart part, ClientTrainStop trainStop) {} + public static record QueuedAnnouncementEvent(Runnable callback, ClientTrainStop trainStop) {} + + public static final String EVENT_UPDATE = "update"; + public static final String EVENT_ANNOUNCE_START = "announce_start"; + public static final String EVENT_ARRIVAL_AT_START = "arrival_at_start"; + public static final String EVENT_DEPARTURE_FROM_START = "departure_at_start"; + public static final String EVENT_WHILE_TRANSIT = "while_transit"; + public static final String EVENT_NEXT_STOP_ANNOUNCED = "next_stop_announced"; + public static final String EVENT_ARRIVAL_AT_STOPOVER = "arrival_at_stopover"; + public static final String EVENT_DEPARTURE_FROM_STOPOVER = "departure_at_stopover"; + public static final String EVENT_LAST_STOP_ANNOUNCED = "last_stop_announced"; + public static final String EVENT_ARRIVAL_AT_LAST_STOP = "arrival_at_last_stop"; + public static final String EVENT_DEPARTURE_FROM_LAST_STOP = "departure_at_last_stop"; + public static final String EVENT_FIRST_STOP_STATION_CHANGED = "first_stop_station_changed"; + public static final String EVENT_FIRST_STOP_DELAYED = "first_stop_delayed"; + public static final String EVENT_LAST_STOP_STATION_CHANGED = "last_stop_station_changed"; + public static final String EVENT_LAST_STOP_DELAYED = "last_stop_delayed"; + public static final String EVENT_ANY_STOP_ANNOUNCED = "any_stop_announced"; + public static final String EVENT_ARRIVAL_AT_ANY_STOP = "arrival_at_any_stop"; + public static final String EVENT_DEPARTURE_FROM_ANY_STOP = "departure_at_any_stop"; + public static final String EVENT_SCHEDULE_CHANGED = "schedule_changed"; + public static final String EVENT_TRAIN_CANCELLED = "train_cancelled"; + + private final Map>> listeners = new HashMap<>(); + + // state + private RoutePartProgressState progressState = RoutePartProgressState.BEFORE; + private ClientTrainStop nextStop; + private final Queue queuedAnnouncements = new ConcurrentLinkedQueue<>(); + + private final Cache> clientTrainStops = new Cache<>(() -> getAllStops().stream().filter(x -> x instanceof ClientTrainStop).map(x -> (ClientTrainStop)x).toList()); + private final Cache> clientJourneyStops = new Cache<>(() -> getAllJourneyStops().stream().filter(x -> x instanceof ClientTrainStop).map(x -> (ClientTrainStop)x).toList()); + + + public ClientRoutePart(UUID sessionId, UUID trainId, List routeStops, List allStops) { + super(sessionId, trainId, routeStops, allStops); + this.nextStop = getFirstClientStop(); + + createEvent(EVENT_UPDATE); + createEvent(EVENT_ANNOUNCE_START); + createEvent(EVENT_ARRIVAL_AT_START); + createEvent(EVENT_DEPARTURE_FROM_START); + createEvent(EVENT_WHILE_TRANSIT); + createEvent(EVENT_NEXT_STOP_ANNOUNCED); + createEvent(EVENT_ARRIVAL_AT_STOPOVER); + createEvent(EVENT_DEPARTURE_FROM_STOPOVER); + createEvent(EVENT_LAST_STOP_ANNOUNCED); + createEvent(EVENT_ARRIVAL_AT_LAST_STOP); + createEvent(EVENT_DEPARTURE_FROM_LAST_STOP); + createEvent(EVENT_FIRST_STOP_STATION_CHANGED); + createEvent(EVENT_FIRST_STOP_DELAYED); + createEvent(EVENT_LAST_STOP_STATION_CHANGED); + createEvent(EVENT_LAST_STOP_DELAYED); + createEvent(EVENT_ANY_STOP_ANNOUNCED); + createEvent(EVENT_ARRIVAL_AT_ANY_STOP); + createEvent(EVENT_DEPARTURE_FROM_ANY_STOP); + createEvent(EVENT_SCHEDULE_CHANGED); + createEvent(EVENT_TRAIN_CANCELLED); + + getFirstClientStop().listen(ClientTrainStop.EVENT_ANNOUNCE_NEXT_STOP, this, x -> { + notifyListeners(EVENT_ANNOUNCE_START, new ListenerNotificationData(this, x)); + }); + getFirstClientStop().listen(ClientTrainStop.EVENT_STATION_REACHED, this, x -> { + progressState = RoutePartProgressState.AT_START; + notifyListeners(EVENT_ARRIVAL_AT_START, new ListenerNotificationData(this, x)); + notifyListeners(EVENT_ARRIVAL_AT_ANY_STOP, new ListenerNotificationData(this, x)); + }); + getFirstClientStop().listen(ClientTrainStop.EVENT_STATION_LEFT, this, x -> { + progressState = RoutePartProgressState.TRAVELING; + notifyListeners(EVENT_DEPARTURE_FROM_START, new ListenerNotificationData(this, x)); + notifyListeners(EVENT_DEPARTURE_FROM_ANY_STOP, new ListenerNotificationData(this, x)); + }); + getFirstClientStop().listen(ClientTrainStop.EVENT_DELAY, this, x -> { + notifyListeners(EVENT_FIRST_STOP_DELAYED, new ListenerNotificationData(this, x)); + }); + getFirstClientStop().listen(ClientTrainStop.EVENT_STATION_CHANGED, this, x -> { + notifyListeners(EVENT_FIRST_STOP_STATION_CHANGED, new ListenerNotificationData(this, x)); + }); + + for (ClientTrainStop stop : getClientStopovers()) { + stop.listen(ClientTrainStop.EVENT_ANNOUNCE_NEXT_STOP, this, x -> { + queuedAnnouncements.add(new QueuedAnnouncementEvent(() -> { + progressState = RoutePartProgressState.NEXT_STOP_ANNOUNCED; + notifyListeners(EVENT_NEXT_STOP_ANNOUNCED, new ListenerNotificationData(this, x)); + notifyListeners(EVENT_ANY_STOP_ANNOUNCED, new ListenerNotificationData(this, x)); + }, x)); + }); + stop.listen(ClientTrainStop.EVENT_STATION_REACHED, this, x -> { + progressState = RoutePartProgressState.AT_STOPOVER; + notifyListeners(EVENT_ARRIVAL_AT_STOPOVER, new ListenerNotificationData(this, x)); + notifyListeners(EVENT_ARRIVAL_AT_ANY_STOP, new ListenerNotificationData(this, x)); + }); + stop.listen(ClientTrainStop.EVENT_STATION_LEFT, this, x -> { + progressState = RoutePartProgressState.TRAVELING; + notifyListeners(EVENT_DEPARTURE_FROM_STOPOVER, new ListenerNotificationData(this, x)); + notifyListeners(EVENT_DEPARTURE_FROM_ANY_STOP, new ListenerNotificationData(this, x)); + }); + } + + getLastClientStop().listen(ClientTrainStop.EVENT_ANNOUNCE_NEXT_STOP, this, x -> { + queuedAnnouncements.add(new QueuedAnnouncementEvent(() -> { + progressState = RoutePartProgressState.END_ANNOUNCED; + notifyListeners(EVENT_LAST_STOP_ANNOUNCED, new ListenerNotificationData(this, x)); + notifyListeners(EVENT_ANY_STOP_ANNOUNCED, new ListenerNotificationData(this, x)); + }, x)); + }); + getLastClientStop().listen(ClientTrainStop.EVENT_STATION_REACHED, this, x -> { + progressState = RoutePartProgressState.AT_END; + queuedAnnouncements.clear(); + notifyListeners(EVENT_ARRIVAL_AT_LAST_STOP, new ListenerNotificationData(this, x)); + notifyListeners(EVENT_ARRIVAL_AT_ANY_STOP, new ListenerNotificationData(this, x)); + }); + getLastClientStop().listen(ClientTrainStop.EVENT_STATION_LEFT, this, x -> { + progressState = RoutePartProgressState.AFTER; + queuedAnnouncements.clear(); + notifyListeners(EVENT_DEPARTURE_FROM_LAST_STOP, new ListenerNotificationData(this, x)); + notifyListeners(EVENT_DEPARTURE_FROM_ANY_STOP, new ListenerNotificationData(this, x)); + close(); + }); + getLastClientStop().listen(ClientTrainStop.EVENT_DELAY, this, x -> { + notifyListeners(EVENT_LAST_STOP_DELAYED, new ListenerNotificationData(this, x)); + }); + getLastClientStop().listen(ClientTrainStop.EVENT_STATION_CHANGED, this, x -> { + notifyListeners(EVENT_LAST_STOP_STATION_CHANGED, new ListenerNotificationData(this, x)); + }); + + getAllClientStops().stream().forEach(x -> { + x.listen(ClientTrainStop.EVENT_SCHEDULE_CHANGED, this, a -> { + notifyListeners(EVENT_SCHEDULE_CHANGED, new ListenerNotificationData(this, a)); + }); + }); + + + // TEST + listen(EVENT_DEPARTURE_FROM_ANY_STOP, this, (p) -> { + int idx = routeStops.indexOf(p.trainStop()); + if (idx < 0 || idx >= routeStops.size() - 1) { + nextStop = p.trainStop(); + } else { + nextStop = (ClientTrainStop)routeStops.get(idx + 1); + } + }); + } + + @Override + public Map>> getListeners() { + return listeners; + } + + public List getAllJourneyClientStops() { + return clientJourneyStops.get(); + } + + public List getAllClientStops() { + return clientTrainStops.get(); + } + + public List getClientStopovers() { + return getAllClientStops().size() <= 2 ? List.of() : ImmutableList.copyOf(getAllClientStops().subList(1, routeStops.size() - 1)); + } + + public ClientTrainStop getFirstClientStop() { + return getAllClientStops().get(0); + } + + public ClientTrainStop getLastClientStop() { + return getAllClientStops().get(getAllClientStops().size() - 1); + } + + public ClientTrainStop getNextStop() { + return nextStop; + } + + public int getNextStopIndex() { + return routeStops.indexOf(nextStop); + } + + public RoutePartProgressState getProgressState() { + return progressState; + } + + @Override + public void update(TrainRealTimeData data) { + if (isCancelled()) { + return; + } + + status.clear(); + if (!data.sessionId().equals(getSessionId())) { + cancelled = true; + } + + MutableSingle shouldRenderStatus = new MutableSingle<>(false); + + getAllClientStops().stream().forEach(x -> { + if (data.stationData().containsKey(x.getScheduleIndex())) { + x.update(data.stationData().get(x.getScheduleIndex())); + if (x.shouldRenderRealTime()) { + shouldRenderStatus.setFirst(true); + } + } + }); + getAllJourneyClientStops().stream().forEach(x -> { + if (data.stationData().containsKey(x.getScheduleIndex())) { + x.update(data.stationData().get(x.getScheduleIndex())); + } + }); + + if (shouldRenderStatus.getFirst() || data.cancelled()) { + status.addAll(data.statusInfo()); + } + + this.cancelled = this.cancelled || data.cancelled(); + + notifyListeners(EVENT_UPDATE, new ListenerNotificationData(this, nextStop)); + if (getProgressState() == RoutePartProgressState.TRAVELING) { + notifyListeners(EVENT_WHILE_TRANSIT, new ListenerNotificationData(this, nextStop)); + + while (!queuedAnnouncements.isEmpty()) { + QueuedAnnouncementEvent event = queuedAnnouncements.peek(); + if (routeStops.indexOf(event.trainStop()) > getNextStopIndex()) { + break; + } + event = queuedAnnouncements.poll(); + if (getNextStop() != event.trainStop()) { + continue; + } + event.callback().run(); + break; + } + } + + if (isCancelled()) { + CreateRailwaysNavigator.LOGGER.info("Train got cancelled. Closing route..."); + notifyListeners(EVENT_TRAIN_CANCELLED, new ListenerNotificationData(this, nextStop)); + close(); + } + } + + public static RoutePart fromNbt(CompoundTag nbt) { + return new ClientRoutePart( + nbt.contains(NBT_SESSION_ID) ? nbt.getUUID(NBT_SESSION_ID) : new UUID(0, 0), + nbt.getUUID(NBT_TRAIN_ID), + nbt.getList(NBT_STOPS, Tag.TAG_COMPOUND).stream().map(x -> ClientTrainStop.fromNbt((CompoundTag)x)).toList(), + nbt.getList(NBT_JOURNEY, Tag.TAG_COMPOUND).stream().map(x -> ClientTrainStop.fromNbt((CompoundTag)x)).toList() + ); + } + + @Override + public void close() { + getAllClientStops().stream().forEach(x -> { + x.stopListeningAll(this); + x.close(); + }); + getAllJourneyClientStops().stream().forEach(x -> { + x.stopListeningAll(this); + x.close(); + }); + stopListeningAll(this); + } + + + public static record TrainRealTimeData(UUID sessionId, Map stationData, Set statusInfo, boolean cancelled) { + + private static final String NBT_SESSION_ID = "SessionId"; + private static final String NBT_STATUS_INFOS = "Status"; + private static final String NBT_CANCELLED = "Cancelled"; + + public CompoundTag toNbt() { + CompoundTag nbt = new CompoundTag(); + nbt.putUUID(NBT_SESSION_ID, sessionId); + ListTag status = new ListTag(); + status.addAll(statusInfo().stream().map(x -> x.toNbt()).toList()); + nbt.put(NBT_STATUS_INFOS, status); + nbt.putBoolean(NBT_CANCELLED, cancelled); + + for (Map.Entry e : stationData.entrySet()) { + nbt.put("" + e.getKey(), e.getValue().toNbt()); + } + return nbt; + } + + public static TrainRealTimeData fromNbt(CompoundTag nbt) { + return new TrainRealTimeData( + nbt.getUUID(NBT_SESSION_ID), + nbt.getAllKeys().stream().filter(x -> { try { Integer.parseInt(x); return true; } catch (Exception e) { return false; } }).collect(Collectors.toMap(x -> Integer.parseInt(x), x -> TrainStopRealTimeData.fromNbt(nbt.getCompound(x)))), + nbt.getList(NBT_STATUS_INFOS, Tag.TAG_COMPOUND).stream().map(x -> CompiledTrainStatus.fromNbt((CompoundTag)x)).collect(Collectors.toSet()), + nbt.getBoolean(NBT_CANCELLED) + ); + } + } +} diff --git a/common/src/main/java/de/mrjulsen/crn/data/navigation/ClientTrainListener.java b/common/src/main/java/de/mrjulsen/crn/data/navigation/ClientTrainListener.java new file mode 100644 index 00000000..7f291924 --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/data/navigation/ClientTrainListener.java @@ -0,0 +1,67 @@ +package de.mrjulsen.crn.data.navigation; + +import java.util.ArrayList; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Consumer; + +import de.mrjulsen.crn.registry.ModAccessorTypes; +import de.mrjulsen.mcdragonlib.data.Pair; +import de.mrjulsen.mcdragonlib.util.DLUtils; +import de.mrjulsen.mcdragonlib.util.accessor.DataAccessor; + +public final class ClientTrainListener { + + private static final ConcurrentHashMap>>> callbacks = new ConcurrentHashMap<>(); + + public static int debug_registeredListenersCount() { + return callbacks.values().stream().mapToInt(x -> x.size()).sum(); + } + + public static UUID register(UUID sessionId, UUID trainId, Consumer callback) { + Map>> trainCallbacks = callbacks.computeIfAbsent(trainId, x -> new ConcurrentHashMap<>()); + + UUID id = null; + do { + id = UUID.randomUUID(); + } while (trainCallbacks.containsKey(id)); + + trainCallbacks.put(id, Pair.of(sessionId, callback)); + return id; + } + + public static void unregister(UUID trainId, UUID callbackId) { + if (callbacks.containsKey(trainId)) { + Map>> trainCallbacks = callbacks.get(trainId); + if (trainCallbacks.containsKey(callbackId)) { + trainCallbacks.remove(callbackId); + } + + if (trainCallbacks.isEmpty()) { + callbacks.remove(trainId); + } + } + } + + public static void tick(Runnable andThen) { + callbacks.entrySet().stream().forEach(x -> { + if (x.getValue().isEmpty()) { + callbacks.remove(x.getKey()); + return; + } + + final Map>> listeners = x.getValue(); + DataAccessor.getFromServer(x.getKey(), ModAccessorTypes.UPDATE_REALTIME, res -> { + if (res != null) { + new ArrayList<>(listeners.values()).stream().forEach(a -> a.getSecond().accept(res)); + } + DLUtils.doIfNotNull(andThen, a -> a.run()); + }); + }); + } + + public static void clear() { + callbacks.clear(); + } +} diff --git a/common/src/main/java/de/mrjulsen/crn/data/navigation/EdgeData.java b/common/src/main/java/de/mrjulsen/crn/data/navigation/EdgeData.java new file mode 100644 index 00000000..2a6b09e5 --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/data/navigation/EdgeData.java @@ -0,0 +1,63 @@ +package de.mrjulsen.crn.data.navigation; + +import java.util.UUID; + +import de.mrjulsen.crn.data.train.TrainPrediction; + +public class EdgeData implements Comparable { + + private final TrainPrediction prediction; + private final Node node1; + private final Node node2; + + // Calc + private long cost = -1; + + protected EdgeData(Node node1, Node node2, TrainPrediction prediction, long cost) { + this.prediction = prediction; + this.node1 = node1; + this.node2 = node2; + this.cost = cost; + } + + public EdgeData(Node node1, Node node2, TrainPrediction prediction) { + this(node1, node2, prediction, prediction.getLastTransitTime()); + } + + public Node getFirstNode() { + return node1; + } + + public Node getSecondNode() { + return node2; + } + + public UUID getTrainId() { + return prediction.getData().getTrainId(); + } + + public boolean connected(EdgeData other) { + return getTrainId().equals(other.getTrainId()) && (getSectionIndex() == other.getSectionIndex()); + } + + public int getSectionIndex() { + return prediction.getSection().getScheduleIndex(); + } + + public TrainPrediction getPrediction() { + return prediction; + } + + public long getCost() { + return cost; + } + + public EdgeData invert() { + return new EdgeData(node2, node1, prediction); + } + + @Override + public int compareTo(EdgeData o) { + return Long.compare(cost, o.cost); + } +} diff --git a/common/src/main/java/de/mrjulsen/crn/data/navigation/ITrainListenerClient.java b/common/src/main/java/de/mrjulsen/crn/data/navigation/ITrainListenerClient.java new file mode 100644 index 00000000..ea5dc4d9 --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/data/navigation/ITrainListenerClient.java @@ -0,0 +1,5 @@ +package de.mrjulsen.crn.data.navigation; + +public interface ITrainListenerClient extends AutoCloseable { + void update(T data); +} diff --git a/common/src/main/java/de/mrjulsen/crn/data/navigation/NavigatableGraph.java b/common/src/main/java/de/mrjulsen/crn/data/navigation/NavigatableGraph.java new file mode 100644 index 00000000..9320b6bf --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/data/navigation/NavigatableGraph.java @@ -0,0 +1,430 @@ +package de.mrjulsen.crn.data.navigation; + +import java.util.ArrayList; +import java.util.Deque; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.PriorityQueue; +import java.util.Queue; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.ConcurrentLinkedDeque; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.stream.Collectors; + +import com.google.common.collect.ImmutableMap; +import com.simibubi.create.content.trains.entity.Train; + +import de.mrjulsen.crn.CreateRailwaysNavigator; +import de.mrjulsen.crn.config.ModCommonConfig; +import de.mrjulsen.crn.data.StationTag; +import de.mrjulsen.crn.data.UserSettings; +import de.mrjulsen.crn.data.storage.GlobalSettings; +import de.mrjulsen.crn.data.train.TrainData; +import de.mrjulsen.crn.data.train.TrainListener; +import de.mrjulsen.crn.data.train.TrainPrediction; +import de.mrjulsen.crn.data.train.TrainTravelSection; +import de.mrjulsen.crn.data.train.TrainUtils; +import de.mrjulsen.crn.event.ModCommonEvents; +import de.mrjulsen.crn.data.navigation.Node.EdgeConnection; +import de.mrjulsen.crn.exceptions.RuntimeSideException; +import de.mrjulsen.mcdragonlib.data.Single.MutableSingle; + +/* ####################################################### + * + * § 1: Das Navigationssystem ist unantastbar! + * + * ####################################################### + */ + +public class NavigatableGraph { + + protected final UserSettings userSettings; + + protected final Map nodesByTag = new HashMap<>(); + protected final Map> nodesByTrain = new HashMap<>(); + protected final Map /* connection */>> edgesByTag = new HashMap<>(); + + //#region GRAPH GENERATION + + /** Server-side only! */ + public NavigatableGraph(UserSettings userSettings) throws RuntimeSideException { + if (!ModCommonEvents.hasServer()) { + throw new RuntimeSideException(false); + } + this.userSettings = userSettings; + long startTime = System.currentTimeMillis(); + Set trains = TrainListener.getAllTrains().stream().filter(x -> + !globalSettings().isTrainBlacklisted(x) && + !globalSettings().isTrainExcludedByUser(x, userSettings) && + TrainUtils.isTrainUsable(x) + ).collect(Collectors.toSet()); + for (Train train : trains) { + addTrain(train, TrainListener.data.get(train.id)); + } + + CreateRailwaysNavigator.LOGGER.info(String.format("Graph generated. Took %sms. Contains %s nodes and %s edges. %s train processed.", + System.currentTimeMillis() - startTime, + nodesByTag.size(), + edgesByTag.values().stream().flatMap(x -> x.values().stream().flatMap(y -> y.stream())).count(), + trains.size() + )); + } + + protected GlobalSettings globalSettings() { + return GlobalSettings.getInstance(); + } + + protected void addTrain(Train train, TrainData data) { + Deque predictions = new ConcurrentLinkedDeque<>(data.getPredictions()); + boolean singleSection = data.isSingleSection(); + if (predictions.isEmpty()) { + return; + } + + if (CreateRailwaysNavigator.isDebug()) CreateRailwaysNavigator.LOGGER.info("\nEDGES FOR TRAIN: " + train.name.getString() + ", Single Section: " + singleSection + ", Sections Count: " + data.getSections().size()); + StringBuilder sb = new StringBuilder(); + + TrainPrediction lastPrediction = null; // The last prediction in the list + boolean nothingFound = true; + boolean stationsRemoved = false; + while (!predictions.isEmpty()) { + TrainPrediction prediction = predictions.peekLast(); + TrainTravelSection section = prediction.getSection(); + if ((globalSettings().isStationBlacklisted(prediction.getStationName())) || + (!section.isUsable() && (!section.isFirstStop(prediction) || !section.previousSection().isUsable() || !section.previousSection().shouldIncludeNextStationOfNextSection())) + ) { + predictions.removeLast(); + stationsRemoved = true; + continue; + } + + nothingFound = false; + lastPrediction = prediction; + break; + } + if (nothingFound || lastPrediction == null) { + return; + } + + Node lastNode = addNode(lastPrediction); + TrainPrediction lastTrainPrediction = lastPrediction; + if (CreateRailwaysNavigator.isDebug()) sb.append(lastTrainPrediction.getStationTag().getTagName().get()); + boolean noEdgePossible = false; + while (!predictions.isEmpty()) { + TrainPrediction prediction = predictions.poll(); + if (!isPredictionAllowed(prediction)) { + noEdgePossible = true; + continue; + } + Node node = addNode(prediction); + if ((!noEdgePossible && !stationsRemoved && lastTrainPrediction.getSection().getScheduleIndex() == prediction.getSection().getScheduleIndex()) || lastTrainPrediction.getSection().shouldIncludeNextStationOfNextSection()) { + addEdge(lastNode, node, prediction); + if (CreateRailwaysNavigator.isDebug()) sb.append(" ----- "); + } else { + if (CreateRailwaysNavigator.isDebug()) sb.append(" > < "); + } + lastNode = node; + lastTrainPrediction = prediction; + noEdgePossible = false; + stationsRemoved = false; + if (CreateRailwaysNavigator.isDebug()) sb.append(prediction.getStationTag().getTagName().get()); + } + if (CreateRailwaysNavigator.isDebug()) CreateRailwaysNavigator.LOGGER.info(sb.toString()); + } + + protected boolean isPredictionAllowed(TrainPrediction prediction) { + TrainTravelSection section = prediction.getSection(); + boolean usable = section.isUsable() || (section.isFirstStop(prediction) && section.previousSection().isUsable() && section.previousSection().shouldIncludeNextStationOfNextSection()); + return !globalSettings().isStationBlacklisted(prediction.getStationName()) && (prediction.getSection().getTrainGroup() == null || !userSettings.navigationExcludedTrainGroups.getValue().contains(prediction.getSection().getTrainGroup().getGroupName())) && usable; + } + + protected Node addNode(TrainPrediction prediction) { + StationTag tag = prediction.getStationTag(); + Node node = nodesByTag.computeIfAbsent(tag, x -> new Node(prediction)); + nodesByTrain.computeIfAbsent(prediction.getData().getTrainId(), x -> new HashSet<>()).add(node); + node.addTrain(prediction); + return node; + } + + protected void addEdge(Node first, Node second, TrainPrediction prediction) { + if (first == second) { + return; + } + edgesByTag.computeIfAbsent(first, x -> new HashMap<>()).computeIfAbsent(second, x -> new HashSet<>()).add(new EdgeData(first, second, prediction)); + } + +//#endregion +//#region GRAPH ACCESSORS + + protected Map dijkstra(StationTag start, boolean avoidTransfers) { + nodesByTag.values().forEach(x -> x.init()); + Node startNode = nodesByTag.get(start); + startNode.setConnection(startNode, null, 0); + startNode.setTransferPoint(true); + + PriorityQueue queue = new PriorityQueue<>(); + Map excludedNodes = new HashMap<>(); + queue.add(startNode); + + while (!queue.isEmpty()) { + Node currentNode = queue.poll(); + Map /* via */> reachableNodes = edgesByTag.get(currentNode); + + if (reachableNodes != null) { + reachableNodes.entrySet().stream().filter(x -> !excludedNodes.containsKey(x.getKey().getStationTag())).forEach(y -> { + final Node targetNode = y.getKey(); + y.getValue().forEach(x -> { + final EdgeData viaEdge = x; + + EdgeConnection connection = currentNode.selectBestConnectionFor(targetNode, viaEdge); + boolean isTransfer = connection != null && !connection.edge().connected(viaEdge); + + // TODO: Evtl die kĂŒrzeste Umstiegszeit (also Kosten fĂŒr den Umstieg) berechnen lassen anhand der aktuellen Verkehrslage + + long newCost = currentNode.getCost() + viaEdge.getCost() + (isTransfer && avoidTransfers ? ModCommonConfig.TRANSFER_COST.get() : 0); + targetNode.setConnection(currentNode, viaEdge, newCost); + }); + queue.add(targetNode); + }); + } + + excludedNodes.put(currentNode.getStationTag(), currentNode); + } + + return excludedNodes; + } + + protected Node dijkstraProcessor(Map nodes, StationTag start, StationTag end) { + if (nodes.size() <= 1 || !nodes.containsKey(end) || !nodes.containsKey(start)) { + return null; + } + + Node currentNode = nodes.get(end); + while (!currentNode.getStationTag().equals(start)) { + Node prevNode = currentNode.getPreviousNode(); + if (prevNode == null || currentNode == prevNode) break; + prevNode.setNextNode(currentNode); + currentNode = prevNode; + } + return currentNode; + } + + public List searchRoute(StationTag start, StationTag end, boolean avoidTransfers) { + if (!nodesByTag.containsKey(start) || !nodesByTag.containsKey(end)) { + return List.of(); + } + + Node startNode = dijkstraProcessor(dijkstra(start, avoidTransfers), start, end); + + if (CreateRailwaysNavigator.isDebug()) { + StringBuilder sb = new StringBuilder("Dijkstra Nodes: "); + Node node = startNode; + while (node != null) { + sb.append(node.getStationTag().getTagName().get() + " > "); + node = node.getNextNode(); + } + CreateRailwaysNavigator.LOGGER.info(sb.toString()); + } + + if (startNode == null) { + return List.of(); + } + startNode.setTransferPoint(true); + + List route = new ArrayList<>(); + Node currentNode = startNode; + while (!currentNode.getStationTag().equals(end)) { + route.add(currentNode); + MutableSingle nextNode = new MutableSingle(currentNode.getNextNode()); + List connections = new ArrayList<>(currentNode.getNextConnections().stream().filter(x -> x.edge().getSecondNode().equals(nextNode.getFirst())).toList()); + while (nextNode.getFirst() != null && connections != null && !connections.isEmpty()) { + Node pNode = nextNode.getFirst().getNextNode(); + if (pNode == null) { + break; + } + List pConnections = nextNode.getFirst().getNextConnections().stream().filter(x -> x.edge().getSecondNode().equals(pNode)).toList(); + connections.removeIf(x -> pConnections.stream().noneMatch(y -> x.edge().connected(y.edge()))); + if (connections.isEmpty()) break; + nextNode.setFirst(pNode); + } + + currentNode = nextNode.getFirst(); + currentNode.setTransferPoint(true); + } + currentNode.setTransferPoint(true); + route.add(currentNode); + + return route; + } + + public ImmutableMap createTrainSchedules() { + return ImmutableMap.copyOf(TrainUtils.getTrains(true).stream() + .filter(x -> { + return TrainListener.data.containsKey(x.id) && + TrainUtils.isTrainUsable(x) && + !globalSettings().isTrainBlacklisted(x) && + !globalSettings().isTrainExcludedByUser(x, userSettings) + ; + }) + .map(x -> new TrainSchedule(TrainListener.data.get(x.id).getSessionId(), x)) + .collect(Collectors.toMap(x -> x.getTrain().id, x -> x))); + } + + public List searchTrainsForRoute(List nodes) { + ImmutableMap schedules = createTrainSchedules(); + + StringBuilder sb = new StringBuilder("Transfer points: "); + Queue transferNodes = new ConcurrentLinkedQueue<>(nodes.stream().filter(x -> x.isTransferPoint()).peek(x -> { + if (CreateRailwaysNavigator.isDebug()) sb.append(x.getStationTag().getTagName().get() + " > "); + }).toList()); + if (CreateRailwaysNavigator.isDebug()) CreateRailwaysNavigator.LOGGER.info(sb.toString()); + + if (transferNodes.size() <= 1) { + return List.of(); + } + + Node start = transferNodes.poll(); + Node end = transferNodes.poll(); + + Set excludedTrainIds = new HashSet<>(); + List departingTrains = TrainUtils.getDepartingTrainsAt(start.getStationTag()).stream().filter(train -> + TrainListener.data.containsKey(train.id) && + TrainUtils.isTrainUsable(train) && + !excludedTrainIds.contains(train.id) && + !globalSettings().isTrainBlacklisted(train) && + schedules.containsKey(train.id) && + schedules.get(train.id).stopsAt(start.getStationTag()) && + schedules.get(train.id).stopsAt(end.getStationTag()) + ).toList(); + + List routes = new ArrayList<>(); + + for (Train train : departingTrains) { + TrainData trainData = TrainListener.data.get(train.id); + int simulationTime = userSettings.navigationDepartureInTicks.getValue(); + Queue tempTransferNodes = new ConcurrentLinkedQueue<>(transferNodes); + Node tempEnd = end; + + RoutePart part = RoutePart.get(schedules.get(train.id).getSessionId(), schedules.get(train.id).simulate(simulationTime), start.getStationTag(), end.getStationTag(), userSettings); + if (!RoutePart.validate(part, trainData)) { + continue; + } + + while (!tempTransferNodes.isEmpty()) { + RoutePart tempPart = RoutePart.get(schedules.get(train.id).getSessionId(), schedules.get(train.id).simulate(simulationTime), start.getStationTag(), tempTransferNodes.peek().getStationTag(), userSettings); + if (!RoutePart.validate(tempPart, trainData)) { + break; + } + part = tempPart; + tempEnd = tempTransferNodes.poll(); + } + + + if (ModCommonConfig.EXCLUDE_TRAINS.get()) excludedTrainIds.add(train.id); + + // Step 2 + List parts = new ArrayList<>(); + parts.add(part); + if (!tempTransferNodes.isEmpty()) { + Set exclTrns = new HashSet<>(excludedTrainIds); + if (ModCommonConfig.EXCLUDE_TRAINS.get()) exclTrns.add(part.getTrainId()); + List res = searchForTrainsInternal(tempEnd, schedules, new ConcurrentLinkedQueue<>(tempTransferNodes), exclTrns, part); + if (res == null) continue; + parts.addAll(res); + } + routes.add(new Route(parts, false)); + } + + return routes; + } + + public List searchForTrainsInternal(Node start, ImmutableMap schedules, Queue transferNodes, Set excludedTrainIds, RoutePart previousPart) { + Node end = transferNodes.poll(); + + List departingTrains = TrainUtils.getDepartingTrainsAt(start.getStationTag()).stream().filter(train -> + TrainListener.data.containsKey(train.id) && + TrainUtils.isTrainUsable(train) && + !excludedTrainIds.contains(train.id) && + !globalSettings().isTrainBlacklisted(train) && + schedules.containsKey(train.id) && + schedules.get(train.id).stopsAt(start.getStationTag()) && + schedules.get(train.id).stopsAt(end.getStationTag()) + ).toList(); + + RoutePart bestPart = null; + Node bestPartEnd = null; + Queue bestPartRemainingTransfers = null; + + for (Train train : departingTrains) { + TrainData trainData = TrainListener.data.get(train.id); + long simulationTime = previousPart.timeUntilEnd() - 1 + userSettings.navigationTransferTime.getValue(); + Queue tempTransferNodes = new ConcurrentLinkedQueue<>(transferNodes); + Node tempEnd = end; + + RoutePart part = RoutePart.get(schedules.get(train.id).getSessionId(), schedules.get(train.id).simulate(simulationTime), start.getStationTag(), tempEnd.getStationTag(), userSettings); + if (!RoutePart.validate(part, trainData)) { + continue; + } + + while (!tempTransferNodes.isEmpty()) { + RoutePart tempPart = RoutePart.get(schedules.get(train.id).getSessionId(), schedules.get(train.id).simulate(simulationTime), start.getStationTag(), tempTransferNodes.peek().getStationTag(), userSettings); + if (!RoutePart.validate(tempPart, trainData)) { + break; + } + part = tempPart; + tempEnd = tempTransferNodes.poll(); + } + + if (bestPart == null || part.compareTo(bestPart) <= 0) { + bestPart = part; + bestPartEnd = tempEnd; + bestPartRemainingTransfers = tempTransferNodes; + } + } + + if (bestPart == null || bestPart.isEmpty()) { + return null; + } + + if (ModCommonConfig.EXCLUDE_TRAINS.get()) excludedTrainIds.add(bestPart.getTrainId()); + List parts = new ArrayList<>(); + parts.add(bestPart); + if (bestPartRemainingTransfers != null && !bestPartRemainingTransfers.isEmpty()) { + List res = searchForTrainsInternal(bestPartEnd, schedules, bestPartRemainingTransfers, excludedTrainIds, bestPart); + if (res == null) return null; + parts.addAll(res); + } + return parts; + } + + /** + * Generates possible routes from the start station to the destination station. + * @param start The start station. + * @param destination The destination station. + * @param playerId The UUID of the player to use its user settings, or {@code null} to use the default settings. + * @param avoidTransfers Tries to avoid transfers as much as possible. + * @return The possible routes. + */ + public static List searchRoutes(StationTag start, StationTag destination, UUID playerId, boolean avoidTransfers) { + long startTime = System.currentTimeMillis(); + UserSettings userSettings = UserSettings.getSettingsFor(playerId, true); + NavigatableGraph graph = new NavigatableGraph(userSettings); + + List nodes = graph.searchRoute(start, destination, avoidTransfers); + List routes = graph.searchTrainsForRoute(nodes); + + int minNumber = routes.stream().mapToInt(x -> x.getTransferCount()).min().orElse(0); + routes = routes.stream().filter(x -> x.getTransferCount() == minNumber).toList(); + + CreateRailwaysNavigator.LOGGER.info(String.format("%s route(s) calculated. Took %sms.", + routes.size(), + System.currentTimeMillis() - startTime + )); + return routes.stream().sorted((a, b) -> Long.compare(a.getStart().getScheduledDepartureTime(), b.getStart().getScheduledDepartureTime())).toList(); + } + + //#endregion +} diff --git a/common/src/main/java/de/mrjulsen/crn/data/navigation/Node.java b/common/src/main/java/de/mrjulsen/crn/data/navigation/Node.java new file mode 100644 index 00000000..759ad40c --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/data/navigation/Node.java @@ -0,0 +1,185 @@ +package de.mrjulsen.crn.data.navigation; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +import com.google.common.base.Objects; + +import de.mrjulsen.crn.data.StationTag; +import de.mrjulsen.crn.data.train.TrainPrediction; + +public class Node implements Comparable { + private final StationTag tag; + private final Map departingTrains = new HashMap<>(); + + // Dijkstra + private long cost = Long.MAX_VALUE; + private Node previousNode = null; + private Node nextNode = null; + //private EdgeData previousEdge = null; + private final Set connections = new HashSet<>(); + private final Set nextConnections = new HashSet<>(); + private final Map preferredConnectionForNode = new HashMap<>(); + + private boolean isTransferPoint = false; + + + + public Node(TrainPrediction prediction) { + this.tag = prediction.getStationTag(); + } + + public void addTrain(TrainPrediction prediction) { + this.departingTrains.put(prediction.getData().getTrainId(), prediction); + } + + public Map getIdsOfDepartingTrains() { + return departingTrains; + } + + public StationTag getStationTag() { + return tag; + } + + public long getCost() { + return cost; + } + + public boolean setCost(long cost) { + boolean b = this.cost != cost; + if (b) { + connections.clear(); + preferredConnectionForNode.clear(); + } + this.cost = cost; + return b; + } + + public Node getPreviousNode() { + return previousNode; + } + + public void setConnection(Node previousNode, EdgeData viaEdge, long cost) { + if (cost > this.cost) { + return; + } + setCost(cost); + if (viaEdge != null) { + connections.add(new EdgeConnection(previousNode, viaEdge)); + } + setPreviousNode(previousNode); + } + + public void setPreviousNode(Node previousNode) { + this.previousNode = previousNode; + } + + public void setNextNode(Node nextNode) { + this.nextNode = nextNode; + nextNode.getConnections().stream().filter(x -> x.target() == this).forEach(x -> { + nextConnections.add(new EdgeConnection(nextNode, x.edge().invert())); + }); + } + + public Node getNextNode() { + return nextNode; + } + + public boolean isTransferPoint() { + return isTransferPoint; + } + + public void setTransferPoint(boolean isTransferPoint) { + this.isTransferPoint = isTransferPoint; + } + + public boolean hasConnections() { + return !this.connections.isEmpty(); + } + + @Override + public int compareTo(Node o) { + return Long.compare(cost, o.cost); + } + + @Override + public boolean equals(Object obj) { + return obj instanceof Node o && + tag.equals(o.tag) + ; + } + + @Override + public int hashCode() { + return Objects.hashCode(tag); + } + + public void init() { + this.cost = Long.MAX_VALUE; + this.previousNode = null; + this.isTransferPoint = false; + this.connections.clear(); + this.preferredConnectionForNode.clear(); + } + + public EdgeConnection selectBestConnectionFor(Node nextNode, EdgeData nextEdge) { // Sucht die beste Edge zu diesem Knoten unter Beachtung des nĂ€chsten Knotens + if (!hasConnections()) { // Keine Alternativen + return null; + } + + EdgeConnection possibleConnection = getPreviousNode() == null ? null : (getPreviousNode().getPreferredConnectionFor(this)); + + for (EdgeConnection connection : connections) { + if (connection.edge() == null) continue; + + if (connection.edge().connected(nextEdge)) { // Wenn es zuvor eine Edge gab, die mit Zug X erreichbar ist, und Zug X auch auf der neuen Edge benötigt wird, wĂ€hle diese. + possibleConnection = connection; + break; + } + } + + if (possibleConnection == null) { + return null; + } + + preferredConnectionForNode.put(nextNode, possibleConnection); // Speichere diese PrĂ€ferenz. Wenn man mit der spĂ€teren Node dann hier sucht, bekommt man die Conenction zurĂŒck. + return possibleConnection; + } + + public EdgeConnection getPreferredConnectionFor(Node node) { + return preferredConnectionForNode.getOrDefault(node, connections.stream().findFirst().orElse(null)); + } + + + public Set getConnections() { + return connections; + } + + public Set getNextConnections() { + return nextConnections; + } + + + public static class EdgeConnection { + + private final Node target; + private final EdgeData edge; + + public EdgeConnection(Node target, EdgeData edge) { + this.target = target; + this.edge = edge; + } + + public Node target() { + return target; + } + + public EdgeData edge() { + return edge; + } + } + +} diff --git a/common/src/main/java/de/mrjulsen/crn/data/navigation/Route.java b/common/src/main/java/de/mrjulsen/crn/data/navigation/Route.java new file mode 100644 index 00000000..678e4d62 --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/data/navigation/Route.java @@ -0,0 +1,146 @@ +package de.mrjulsen.crn.data.navigation; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import com.google.common.collect.ImmutableList; +import java.lang.StringBuilder; + +import de.mrjulsen.crn.CreateRailwaysNavigator; +import de.mrjulsen.crn.client.gui.ModGuiIcons; +import de.mrjulsen.crn.data.ISaveableNavigatorData; +import de.mrjulsen.crn.data.train.TrainStop; +import de.mrjulsen.crn.util.ModUtils; +import de.mrjulsen.mcdragonlib.DragonLib; +import de.mrjulsen.mcdragonlib.data.Cache; +import de.mrjulsen.mcdragonlib.util.TextUtils; +import de.mrjulsen.mcdragonlib.util.TimeUtils; +import net.minecraft.ChatFormatting; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.ListTag; +import net.minecraft.nbt.Tag; + +public class Route implements ISaveableNavigatorData { + + protected static final String NBT_PARTS = "Parts"; + + protected final List parts; + protected final List connections; + protected final Cache isCancelled = new Cache<>(() -> getParts().stream().anyMatch(x -> x.isCancelled())); + + public Route(List parts, boolean realTimeTracker) { + this.parts = parts; + this.connections = TransferConnection.getConnections(parts); + } + + public static Route empty(boolean realTimeTracker) { + return new Route(List.of(), realTimeTracker); + } + + public List getConnections() { + return connections; + } + + public Optional getConnectionWith(TrainStop stop) { + return getConnections().stream().filter(x -> x.getArrivalStation() == stop || x.getDepartureStation() == stop).findFirst(); + } + + public RoutePart getFirstPart() { + return parts.get(0); + } + + public RoutePart getLastPart() { + return parts.get(parts.size() - 1); + } + + public TrainStop getStart() { + return getFirstPart().getFirstStop(); + } + + public TrainStop getEnd() { + return getLastPart().getLastStop(); + } + + public ImmutableList getParts() { + return ImmutableList.copyOf(parts); + } + + public int getTransferCount() { + return parts.size() - 1; + } + + public long departureIn() { + return getFirstPart().departureIn(); + } + + public long arrivalAtDestinationIn() { + return getLastPart().timeUntilEnd(); + } + + public long travelTime() { + return arrivalAtDestinationIn() - departureIn(); + } + + public boolean isAnyCancelled() { + return isCancelled.get(); + } + + /** Checks whether the connection to this part of the route is still reachable or not. The first part is always reachable because there is no transfer there. A part is marked as reachable if the train has not yet departed there. */ + public boolean isPartReachable(RoutePart part) { + int idx = parts.indexOf(part); + if (idx <= 0) { + return true; + } + for (int i = 0; i < connections.size(); i++) { + if (connections.get(i).isConnectionMissed()) { + return !(i < idx); + } + } + return true; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("ROUTE[" + getStart().getClientTag().tagName() + " -> " + getEnd().getClientTag().tagName() + "]"); + return builder.toString(); + } + + public CompoundTag toNbt() { + CompoundTag nbt = new CompoundTag(); + ListTag list = new ListTag(); + list.addAll(parts.stream().map(x -> x.toNbt()).toList()); + nbt.put(NBT_PARTS, list); + return nbt; + } + + public static Route fromNbt(CompoundTag nbt, boolean realTimeTracker) { + return new Route( + nbt.getList(NBT_PARTS, Tag.TAG_COMPOUND).stream().map(x -> RoutePart.fromNbt((CompoundTag)x)).toList(), + realTimeTracker + ); + } + + @Override + public List getOverviewData() { + List lines = new ArrayList<>(); + lines.add(new SaveableNavigatorDataLine(TextUtils.text(ModUtils.formatTime(getStart().getScheduledDepartureTime(), false) + " " + getStart().getClientTag().tagName()), ModGuiIcons.ROUTE_START.getAsSprite(ModGuiIcons.ICON_SIZE, ModGuiIcons.ICON_SIZE))); + lines.add(new SaveableNavigatorDataLine(TextUtils.text(ModUtils.formatTime(getEnd().getScheduledArrivalTime(), false) + " " + getEnd().getClientTag().tagName()), ModGuiIcons.ROUTE_END.getAsSprite(ModGuiIcons.ICON_SIZE, ModGuiIcons.ICON_SIZE))); + lines.add(new SaveableNavigatorDataLine(TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".route_overview.date", (getStart().getScheduledDepartureTime() + DragonLib.DAYTIME_SHIFT) / DragonLib.TICKS_PER_DAY, ModUtils.formatTime(getStart().getScheduledDepartureTime(), false)).append(" | ").append(TimeUtils.parseDurationShort(departureIn())), ModGuiIcons.CALENDAR.getAsSprite(ModGuiIcons.ICON_SIZE, ModGuiIcons.ICON_SIZE))); + lines.add(new SaveableNavigatorDataLine(TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".route_overview.transfers", getTransferCount()).append(TextUtils.text(" | " + TimeUtils.parseDurationShort(travelTime()))), ModGuiIcons.INFO.getAsSprite(ModGuiIcons.ICON_SIZE, ModGuiIcons.ICON_SIZE))); + if (isAnyCancelled()) { + lines.add(new SaveableNavigatorDataLine(TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".route_overview.cancelled").withStyle(ChatFormatting.RED), ModGuiIcons.IMPORTANT.getAsSprite(ModGuiIcons.ICON_SIZE, ModGuiIcons.ICON_SIZE))); + } + return lines; + } + + @Override + public SaveableNavigatorDataLine getTitle() { + return new SaveableNavigatorDataLine(TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".saved_routes.saved_route"), ModGuiIcons.BOOKMARK.getAsSprite(ModGuiIcons.ICON_SIZE, ModGuiIcons.ICON_SIZE)); + } + + @Override + public long timeOrderValue() { + return getStart().getScheduledDepartureTime(); + } +} diff --git a/common/src/main/java/de/mrjulsen/crn/data/navigation/RoutePart.java b/common/src/main/java/de/mrjulsen/crn/data/navigation/RoutePart.java new file mode 100644 index 00000000..9e95601f --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/data/navigation/RoutePart.java @@ -0,0 +1,217 @@ +package de.mrjulsen.crn.data.navigation; + +import java.util.Set; +import java.util.UUID; +import java.util.HashSet; +import java.util.List; +import java.util.ArrayList; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import de.mrjulsen.crn.data.StationTag; +import de.mrjulsen.crn.data.UserSettings; +import de.mrjulsen.crn.data.storage.GlobalSettings; +import de.mrjulsen.crn.data.train.TrainData; +import de.mrjulsen.crn.data.train.TrainStop; +import de.mrjulsen.crn.data.train.TrainTravelSection; +import de.mrjulsen.crn.data.train.TrainStatus.CompiledTrainStatus; +import de.mrjulsen.mcdragonlib.DragonLib; +import de.mrjulsen.mcdragonlib.data.Pair; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.ListTag; +import net.minecraft.nbt.Tag; + +public class RoutePart implements Comparable { + + protected static final String NBT_SESSION_ID = "SessionId"; + protected static final String NBT_TRAIN_ID = "TrainId"; + protected static final String NBT_STOPS = "Stops"; + protected static final String NBT_JOURNEY = "EntireJourney"; + + protected final UUID sessionId; + protected final UUID trainId; + protected final List routeStops; + protected final List allStops; + protected boolean cancelled; + protected final Set status = new HashSet<>(); + + public static RoutePart get(UUID sessionId, TrainSchedule schedule, StationTag from, StationTag to, UserSettings settings) { + List stops = getBetween(schedule, from, to, settings).stream().filter(x -> !x.isEmpty() && x.stream().limit(x.size() - 1).allMatch(y -> y.getSectionIndex() == x.get(0).getSectionIndex())).findFirst().orElse(List.of()); + List entireJourney = List.of(); + if (stops.isEmpty()) { + return null; + } else { + TrainStop firstStop = stops.get(0); + entireJourney = TrainSchedule.ofSectionForIndex(sessionId, schedule.getTrain(), firstStop.getScheduleIndex(), firstStop.getScheduleIndex(), firstStop.getSimulationTime()).getAllStops(); + } + return new RoutePart(sessionId, schedule.getTrain().id, stops, entireJourney); + } + + public static boolean validate(RoutePart part, TrainData trainData) { + if (part == null || part.isEmpty()) { + return false; + } + int startSectionIndex = part.getFirstStop().getSectionIndex(); + int endSectionIndex = part.getLastStop().getSectionIndex(); + TrainTravelSection startSection = trainData.getSectionByIndex(startSectionIndex); + TrainTravelSection endSection = trainData.getSectionByIndex(endSectionIndex); + if (startSectionIndex != endSectionIndex && !(endSection.isFirstStop(part.getLastStop().getScheduleIndex()) && endSection.previousSection() == startSection && startSection.shouldIncludeNextStationOfNextSection() && startSection.isUsable())) { + return false; + } + return true; + } + + public RoutePart(UUID sessionId, TrainSchedule schedule) { + this( + sessionId, + schedule.getTrain().id, + schedule.getAllStops(), + TrainSchedule.ofSectionForIndex( + sessionId, + schedule.getTrain(), + schedule.getAllStops().isEmpty() ? 0 : schedule.getAllStops().get(schedule.getAllStops().size() - 1).getScheduleIndex(), + schedule.getAllStops().isEmpty() ? 0 : schedule.getAllStops().get(0).getScheduleIndex(), + schedule.getAllStops().isEmpty() ? 0 : schedule.getAllStops().get(0).getSimulationTime() + ).getAllStops() + ); + } + + public RoutePart(UUID sessionId, UUID trainId, List routeStops, List allStops) { + this.sessionId = sessionId; + this.routeStops = routeStops; + this.trainId = trainId; + this.allStops = allStops; + } + + public boolean isEmpty() { + return routeStops.isEmpty(); + } + + public TrainStop getFirstStop() { + return routeStops.get(0); + } + + public TrainStop getLastStop() { + return routeStops.get(routeStops.size() - 1); + } + + public UUID getSessionId() { + return sessionId; + } + + public UUID getTrainId() { + return trainId; + } + + public boolean isCancelled() { + return cancelled; + } + + public List getStopovers() { + return routeStops.size() <= 2 ? List.of() : ImmutableList.copyOf(routeStops.subList(1, routeStops.size() - 1)); + } + + public ImmutableList getAllStops() { + return ImmutableList.copyOf(routeStops); + } + + public ImmutableList getAllJourneyStops() { + return ImmutableList.copyOf(allStops); + } + + public long departureIn() { + return getFirstStop().getScheduledDepartureTime() - DragonLib.getCurrentWorldTime(); + } + + public long arrivalIn() { + return getFirstStop().getScheduledArrivalTime() - DragonLib.getCurrentWorldTime(); + } + + public long travelTime() { + return getLastStop().getScheduledArrivalTime() - getFirstStop().getScheduledDepartureTime(); + } + + public long timeUntilEnd() { + return departureIn() + travelTime(); + } + + public Set getStatus() { + return ImmutableSet.copyOf(status); + } + + private static Set> getBetween(TrainSchedule schedule, StationTag start, StationTag end, UserSettings settings) { + List stops = schedule.getAllStops().stream().sorted((a, b) -> Long.compare(a.getScheduledDepartureTime(), b.getScheduledDepartureTime())).toList(); + + if (stops.stream().noneMatch(x -> x.getTag().equals(start)) || stops.stream().noneMatch(x -> x.getTag().equals(end))) { + return Set.of(); + } + + // Step 1: Select ALL possible pairs of start and end indices + Set> sections = new HashSet<>(); + int firstStartIndex = -1; + int startIndex = -1; + + for (int i = 0, index = 0; i < stops.size() + (firstStartIndex < 0 ? stops.size() : firstStartIndex); i++, index = i % stops.size()) { + TrainStop stop = stops.get(index); + if (stop.getTag().equals(start)) { + startIndex = index; + if (firstStartIndex < 0) firstStartIndex = index; + } + if (startIndex >= 0 && stop.getTag().equals(end)) { + sections.add(new Pair<>(startIndex, index)); + startIndex = -1; + } + } + + if (sections.isEmpty()) { + return Set.of(); + } + + // Step 2: Gather all stations between those indices. + Set> routeParts = new HashSet<>(); + for (Pair startStopPair : sections) { + if (GlobalSettings.getInstance().isTrainStationExcludedByUser(schedule.getTrain(), stops.get(startStopPair.getFirst()), settings) || GlobalSettings.getInstance().isTrainStationExcludedByUser(schedule.getTrain(), stops.get(startStopPair.getSecond()), settings)) { + continue; + } + + List stopovers = new ArrayList<>(); + + int index = startStopPair.getFirst() - 1; + do { + index++; + TrainStop stop = stops.get(index % stops.size()); + stop.simulateCycles(index / stops.size()); + stopovers.add(stop); + } while (index % stops.size() != startStopPair.getSecond()); + + routeParts.add(stopovers); + } + return routeParts; + } + + public CompoundTag toNbt() { + CompoundTag nbt = new CompoundTag(); + nbt.putUUID(NBT_SESSION_ID, sessionId); + nbt.putUUID(NBT_TRAIN_ID, trainId); + ListTag stopsList = new ListTag(); + stopsList.addAll(routeStops.stream().map(x -> x.toNbt(true)).toList()); + nbt.put(NBT_STOPS, stopsList); + ListTag journeyList = new ListTag(); + journeyList.addAll(allStops.stream().map(x -> x.toNbt(true)).toList()); + nbt.put(NBT_JOURNEY, journeyList); + return nbt; + } + + public static RoutePart fromNbt(CompoundTag nbt) { + return new RoutePart( + nbt.contains(NBT_SESSION_ID) ? nbt.getUUID(NBT_SESSION_ID) : new UUID(0, 0), + nbt.getUUID(NBT_TRAIN_ID), + nbt.getList(NBT_STOPS, Tag.TAG_COMPOUND).stream().map(x -> TrainStop.fromNbt((CompoundTag)x)).toList(), + nbt.getList(NBT_JOURNEY, Tag.TAG_COMPOUND).stream().map(x -> TrainStop.fromNbt((CompoundTag)x)).toList() + ); + } + + @Override + public int compareTo(RoutePart o) { + return Long.compare(departureIn(), o.departureIn()); + } +} diff --git a/common/src/main/java/de/mrjulsen/crn/data/navigation/TrainSchedule.java b/common/src/main/java/de/mrjulsen/crn/data/navigation/TrainSchedule.java new file mode 100644 index 00000000..43b8c06a --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/data/navigation/TrainSchedule.java @@ -0,0 +1,89 @@ +package de.mrjulsen.crn.data.navigation; + +import java.util.List; +import java.util.Set; +import java.util.UUID; +import java.util.HashSet; + +import com.simibubi.create.content.trains.entity.Train; + +import de.mrjulsen.crn.data.StationTag; +import de.mrjulsen.crn.data.train.TrainListener; +import de.mrjulsen.crn.data.train.TrainStop; +import de.mrjulsen.mcdragonlib.data.Cache; + +public class TrainSchedule { + private final UUID sessionId; + private final Train train; + private final List stops; + private final Cache> stopsChronologically = new Cache<>(() -> getAllStops().stream().sorted((a, b) -> Long.compare(a.getScheduledArrivalTime(), b.getScheduledArrivalTime())).toList()); + + private boolean simulated; + private long simulationTime; + + private TrainSchedule(UUID sessionId, Train train, List stops) { + this.sessionId = sessionId; + this.train = train; + this.stops = stops; + } + + public TrainSchedule(UUID sessionId, Train train) { + this(TrainListener.data.get(train.id).getSessionId(), train, TrainListener.data.get(train.id).getPredictions().stream().map(x -> new TrainStop(x)).toList()); + } + + public static TrainSchedule empty() { + return new TrainSchedule(new UUID(0, 0), null, List.of()); + } + + public static TrainSchedule ofSectionForIndex(UUID sessionId, Train train, int stationSectionIndex, int targetStationIndex, long simulationTime) { + return new TrainSchedule(sessionId, train, TrainListener.data.get(train.id).getSectionForIndex(stationSectionIndex).getAllStops(simulationTime, targetStationIndex)); + } + + public List getAllStops() { + return stops; + } + + public List getAllStopsChronologically() { + return stopsChronologically.get(); + } + + public UUID getSessionId() { + return sessionId; + } + + public Train getTrain() { + return train; + } + + public boolean stopsAt(StationTag tag) { + return stops.stream().anyMatch(x -> x.getTag().equals(tag)); + } + + public TrainSchedule simulate(long ticks) { + simulated = true; + simulationTime += ticks; + return new TrainSchedule(sessionId, train, stops.stream().map(x -> x.copy()).peek(x -> x.simulateTicks(ticks)).toList()); + } + + public boolean isSimulated() { + return simulated; + } + + public long getSimulationTime() { + return simulationTime; + } + + public boolean isEqual(TrainSchedule other) { + if (other == null) { + return false; + } + if (getAllStops().size() != other.getAllStops().size()) { + return false; + } + Set tagsA = new HashSet<>(); + Set tagsB = new HashSet<>(); + getAllStops().stream().forEach(x -> tagsA.add(x.getTag().getTagName().get())); + other.getAllStops().stream().forEach(x -> tagsB.add(x.getTag().getTagName().get())); + return tagsA.size() == tagsB.size() && tagsA.stream().allMatch(x -> tagsB.contains(x)); + } +} diff --git a/common/src/main/java/de/mrjulsen/crn/data/navigation/TransferConnection.java b/common/src/main/java/de/mrjulsen/crn/data/navigation/TransferConnection.java new file mode 100644 index 00000000..732410f3 --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/data/navigation/TransferConnection.java @@ -0,0 +1,110 @@ +package de.mrjulsen.crn.data.navigation; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; + +import com.google.common.collect.ImmutableList; + +import de.mrjulsen.crn.data.train.TrainState; +import de.mrjulsen.crn.data.train.TrainStop; +import de.mrjulsen.crn.util.IListenable; + +public class TransferConnection implements AutoCloseable, IListenable { + + public static final String EVENT_CONNECTION_ENDANGERED = "connection_endangered"; + public static final String EVENT_CONNECTION_MISSED = "connection_missed"; + private final Map>> listeners = new HashMap<>(); + + private final TrainStop arrival; + private final TrainStop departure; + + private boolean isPossible = true; + + private boolean wasConnectionEndangered = false; + private boolean wasConnectionMissed = false; + + public TransferConnection(TrainStop arrival, TrainStop departure) { + this.arrival = arrival; + this.departure = departure; + + createEvent(EVENT_CONNECTION_ENDANGERED); + createEvent(EVENT_CONNECTION_MISSED); + } + + public static ImmutableList getConnections(List parts) { + List connections = new ArrayList<>(); + for (int i = 0; i < parts.size() - 1; i++) { + connections.add(new TransferConnection(parts.get(i).getLastStop(), parts.get(i + 1).getFirstStop())); + } + return ImmutableList.copyOf(connections); + } + + public TrainStop getArrivalStation() { + return arrival; + } + + public TrainStop getDepartureStation() { + return departure; + } + + public long getScheduledTransferTime() { + return departure.getScheduledDepartureTime() - arrival.getScheduledArrivalTime(); + } + + public long getRealTimeTransferTime() { + return (departure.shouldRenderRealTime() ? departure.getRealTimeDepartureTime() : departure.getScheduledDepartureTime()) - (arrival.shouldRenderRealTime() ? arrival.getRealTimeArrivalTime() : arrival.getScheduledArrivalTime()); + } + + public boolean isPossible() { + return isPossible; + } + + public boolean isConnectionEndangered() { + return !isPossible() || getRealTimeTransferTime() <= 0; + } + + public boolean isConnectionMissed() { + if (!isPossible) { + return true; + } + + boolean possible = !(arrival.getState() == TrainState.BEFORE && departure.getState() == TrainState.AFTER); + if (!possible) { + isPossible = false; + } + return !possible; + } + + @Override + public Map>> getListeners() { + return listeners; + } + + public void update() { + if (!isPossible()) { + return; + } + + boolean endangered = isConnectionEndangered(); + boolean missed = isConnectionMissed(); + + if (endangered != wasConnectionEndangered) { + notifyListeners(EVENT_CONNECTION_ENDANGERED, this); + } + if (missed != wasConnectionMissed) { + notifyListeners(EVENT_CONNECTION_MISSED, this); + } + + wasConnectionEndangered = endangered; + wasConnectionMissed = missed; + } + + @Override + public void close() { + stopListeningAll(this); + } +} diff --git a/common/src/main/java/de/mrjulsen/crn/data/schedule/condition/DynamicDelayCondition.java b/common/src/main/java/de/mrjulsen/crn/data/schedule/condition/DynamicDelayCondition.java new file mode 100644 index 00000000..b8889efc --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/data/schedule/condition/DynamicDelayCondition.java @@ -0,0 +1,147 @@ +package de.mrjulsen.crn.data.schedule.condition; + +import java.util.List; +import java.util.Optional; + +import com.google.common.collect.ImmutableList; +import com.mojang.blaze3d.vertex.PoseStack; +import com.simibubi.create.content.trains.entity.Train; +import com.simibubi.create.content.trains.schedule.condition.ScheduledDelay; +import com.simibubi.create.foundation.gui.ModularGuiLineBuilder; +import com.simibubi.create.foundation.utility.Components; +import com.simibubi.create.foundation.utility.Lang; +import com.simibubi.create.foundation.utility.Pair; + +import de.mrjulsen.crn.Constants; +import de.mrjulsen.crn.CreateRailwaysNavigator; +import de.mrjulsen.crn.client.gui.ModGuiIcons; +import de.mrjulsen.crn.client.gui.widgets.ResizableButton; +import de.mrjulsen.crn.data.train.TrainData; +import de.mrjulsen.crn.data.train.TrainListener; +import de.mrjulsen.crn.data.train.TrainPrediction; +import de.mrjulsen.crn.mixin.ModularGuiLineBuilderAccessor; +import de.mrjulsen.mcdragonlib.DragonLib; +import de.mrjulsen.mcdragonlib.client.render.DynamicGuiRenderer; +import de.mrjulsen.mcdragonlib.client.render.DynamicGuiRenderer.AreaStyle; +import de.mrjulsen.mcdragonlib.client.render.DynamicGuiRenderer.ButtonState; +import de.mrjulsen.mcdragonlib.client.util.Graphics; +import de.mrjulsen.mcdragonlib.util.TextUtils; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.minecraft.ChatFormatting; +import net.minecraft.Util; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; +import net.minecraft.world.level.Level; + +public class DynamicDelayCondition extends ScheduledDelay { + + private static final String NBT_MIN = "Min"; + + public DynamicDelayCondition() { + super(); + data.putInt(NBT_MIN, 5); + } + + @Override + public Pair getSummary() { + return Pair.of(new ItemStack(Items.COMPARATOR), TextUtils.translate(CreateRailwaysNavigator.MOD_ID + ".schedule.condition." + getId().getPath() + ".title", formatCustomTime(getMinValue(), true), formatTime(true))); + } + + protected Component formatCustomTime(int time, boolean compact) { + if (compact) + return Components.literal(time + getUnit().suffix); + return Components.literal(time + " ").append(Lang.translateDirect(getUnit().key)); + } + + @Override + public List getTitleAs(String type) { + return ImmutableList.of( + TextUtils.translate(CreateRailwaysNavigator.MOD_ID + ".schedule." + type + "." + getId().getPath()), + Lang.translateDirect("schedule.condition.for_x_time", formatTime(false)).withStyle(ChatFormatting.DARK_AQUA), + TextUtils.translate(CreateRailwaysNavigator.MOD_ID + ".schedule." + type + "." + getId().getPath() + ".at_least", formatCustomTime(getMinValue(), false)).withStyle(ChatFormatting.DARK_AQUA) + ); + } + + @Override + public boolean tickCompletion(Level level, Train train, CompoundTag context) { + int time = context.getInt("Time"); + + long currentDelay = 0; + long scheduledDepartureTime = 0; + boolean initialized = false; + + if (TrainListener.data.containsKey(train.id)) { + TrainData data = TrainListener.data.get(train.id); + Optional pred = data.getNextStopPrediction(); + if (pred.isPresent()) { + currentDelay = pred.get().getArrivalTimeDeviation(); + initialized = data.isInitialized() && !data.isPreparing(); + scheduledDepartureTime = pred.get().getScheduledDepartureTime(); + } + } + + if (time >= (initialized ? Math.max(totalWaitTicks() - currentDelay, minWaitTicks()) : totalWaitTicks()) && (!initialized || DragonLib.getCurrentWorldTime() >= scheduledDepartureTime)) + return true; + + context.putInt("Time", time + 1); + requestDisplayIfNecessary(context, time); + return false; + } + + @Override + public ResourceLocation getId() { + return new ResourceLocation(CreateRailwaysNavigator.MOD_ID, "dynamic_delay"); + } + + public int getMinValue() { + return intData(NBT_MIN); + } + + public int minWaitTicks() { + return getMinValue() * getUnit().ticksPer; + } + + @Override + @Environment(EnvType.CLIENT) + public void initConfigurationWidgets(ModularGuiLineBuilder builder) { + builder.addScrollInput(0, 26, (i, l) -> { + i.titled(Lang.translateDirect("generic.duration")) + .withShiftStep(15) + .withRange(0, 121); + i.lockedTooltipX = -15; + i.lockedTooltipY = 35; + }, "Value"); + + builder.addScrollInput(26, 26, (i, l) -> { + i.titled(TextUtils.translate(CreateRailwaysNavigator.MOD_ID + ".schedule.condition." + getId().getPath() + ".min_duration")) + .withShiftStep(15) + .withRange(0, 121); + i.lockedTooltipX = -15; + i.lockedTooltipY = 35; + }, NBT_MIN); + + builder.addSelectionScrollInput(52, 58, (i, l) -> { + i.forOptions(TimeUnit.translatedOptions()) + .titled(Lang.translateDirect("generic.timeUnit")); + }, "TimeUnit"); + + + ModularGuiLineBuilderAccessor accessor = (ModularGuiLineBuilderAccessor)builder; + ResizableButton btn = new ResizableButton(accessor.crn$getX() + 110, accessor.crn$getY() - 4, 16, 16, TextUtils.empty(), + (b) -> { + Util.getPlatform().openUri(Constants.HELP_PAGE_DYNAMIC_DELAYS); + }) { + @Override + public void renderButton(PoseStack poseStack, int mouseX, int mouseY, float partialTick) { + Graphics graphics = new Graphics(poseStack); + DynamicGuiRenderer.renderArea(graphics, x, y, width, height, AreaStyle.GRAY, isActive() ? (isFocused() || isMouseOver(mouseX, mouseY) ? ButtonState.SELECTED : ButtonState.BUTTON) : ButtonState.DISABLED); + ModGuiIcons.HELP.render(graphics, x, y); + } + }; + accessor.crn$getTarget().add(Pair.of(btn, "help_btn")); + } +} diff --git a/common/src/main/java/de/mrjulsen/crn/data/schedule/instruction/ICustomSuggestionsInstruction.java b/common/src/main/java/de/mrjulsen/crn/data/schedule/instruction/ICustomSuggestionsInstruction.java new file mode 100644 index 00000000..406f3579 --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/data/schedule/instruction/ICustomSuggestionsInstruction.java @@ -0,0 +1,10 @@ +package de.mrjulsen.crn.data.schedule.instruction; + +import com.simibubi.create.content.trains.entity.Train; +import com.simibubi.create.content.trains.schedule.ScheduleRuntime; + +import de.mrjulsen.crn.data.train.TrainData; + +public interface ICustomSuggestionsInstruction { + void run(ScheduleRuntime runtime, TrainData data, Train train, int currentScheduleIndex); +} diff --git a/common/src/main/java/de/mrjulsen/crn/data/schedule/instruction/IPredictableInstruction.java b/common/src/main/java/de/mrjulsen/crn/data/schedule/instruction/IPredictableInstruction.java new file mode 100644 index 00000000..b52a99e6 --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/data/schedule/instruction/IPredictableInstruction.java @@ -0,0 +1,11 @@ +package de.mrjulsen.crn.data.schedule.instruction; + +import com.simibubi.create.content.trains.entity.Train; +import com.simibubi.create.content.trains.schedule.ScheduleRuntime; + +import de.mrjulsen.crn.data.train.TrainData; + +@FunctionalInterface +public interface IPredictableInstruction { + void predict(TrainData data, ScheduleRuntime runtime, int indexInSchedule, Train train); +} diff --git a/common/src/main/java/de/mrjulsen/crn/data/schedule/instruction/IStationPredictableInstruction.java b/common/src/main/java/de/mrjulsen/crn/data/schedule/instruction/IStationPredictableInstruction.java new file mode 100644 index 00000000..52508254 --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/data/schedule/instruction/IStationPredictableInstruction.java @@ -0,0 +1,12 @@ +package de.mrjulsen.crn.data.schedule.instruction; + +import com.simibubi.create.content.trains.entity.Train; +import com.simibubi.create.content.trains.schedule.ScheduleRuntime; + +import de.mrjulsen.crn.data.train.TrainData; +import de.mrjulsen.crn.data.train.TrainPrediction; + +@FunctionalInterface +public interface IStationPredictableInstruction { + void predictForStation(TrainData data, TrainPrediction prediction, ScheduleRuntime runtime, int indexInSchedule, Train train); +} diff --git a/common/src/main/java/de/mrjulsen/crn/data/schedule/instruction/IStationTagInstruction.java b/common/src/main/java/de/mrjulsen/crn/data/schedule/instruction/IStationTagInstruction.java new file mode 100644 index 00000000..3086ddd5 --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/data/schedule/instruction/IStationTagInstruction.java @@ -0,0 +1,3 @@ +package de.mrjulsen.crn.data.schedule.instruction; + +public interface IStationTagInstruction extends ICustomSuggestionsInstruction {} diff --git a/common/src/main/java/de/mrjulsen/crn/data/schedule/instruction/ITrainNameInstruction.java b/common/src/main/java/de/mrjulsen/crn/data/schedule/instruction/ITrainNameInstruction.java new file mode 100644 index 00000000..86cc72bb --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/data/schedule/instruction/ITrainNameInstruction.java @@ -0,0 +1,3 @@ +package de.mrjulsen.crn.data.schedule.instruction; + +public interface ITrainNameInstruction extends ICustomSuggestionsInstruction {} diff --git a/common/src/main/java/de/mrjulsen/crn/data/schedule/instruction/ResetTimingsInstruction.java b/common/src/main/java/de/mrjulsen/crn/data/schedule/instruction/ResetTimingsInstruction.java new file mode 100644 index 00000000..ca894572 --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/data/schedule/instruction/ResetTimingsInstruction.java @@ -0,0 +1,88 @@ +package de.mrjulsen.crn.data.schedule.instruction; + +import java.util.List; + +import com.mojang.blaze3d.vertex.PoseStack; +import com.simibubi.create.content.trains.entity.Train; +import com.simibubi.create.content.trains.schedule.ScheduleRuntime; +import com.simibubi.create.content.trains.schedule.destination.ScheduleInstruction; +import com.simibubi.create.foundation.gui.ModularGuiLineBuilder; +import com.simibubi.create.foundation.utility.Pair; + +import de.mrjulsen.crn.Constants; +import de.mrjulsen.crn.CreateRailwaysNavigator; +import de.mrjulsen.crn.client.gui.ModGuiIcons; +import de.mrjulsen.crn.client.gui.widgets.ResizableButton; +import de.mrjulsen.crn.data.train.TrainData; +import de.mrjulsen.crn.mixin.ModularGuiLineBuilderAccessor; +import de.mrjulsen.mcdragonlib.client.render.DynamicGuiRenderer; +import de.mrjulsen.mcdragonlib.client.render.DynamicGuiRenderer.AreaStyle; +import de.mrjulsen.mcdragonlib.client.render.DynamicGuiRenderer.ButtonState; +import de.mrjulsen.mcdragonlib.client.util.Graphics; +import de.mrjulsen.mcdragonlib.util.DLUtils; +import de.mrjulsen.mcdragonlib.util.TextUtils; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.minecraft.ChatFormatting; +import net.minecraft.Util; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.block.Blocks; + +public class ResetTimingsInstruction extends ScheduleInstruction implements IStationTagInstruction, IPredictableInstruction { + + + public ResetTimingsInstruction() { + } + + @Override + public Pair getSummary() { + return Pair.of(new ItemStack(Blocks.BARRIER), TextUtils.translate(CreateRailwaysNavigator.MOD_ID + ".schedule.instruction." + getId().getPath()).withStyle(ChatFormatting.AQUA)); + } + + @Override + public ResourceLocation getId() { + return new ResourceLocation(CreateRailwaysNavigator.MOD_ID, "reset_timings"); + } + + @Override + public boolean supportsConditions() { + return false; + } + + @Override + public List getTitleAs(String type) { + return List.of(TextUtils.translate(CreateRailwaysNavigator.MOD_ID + ".schedule." + type + "." + getId().getPath()).withStyle(ChatFormatting.GOLD)); + } + + @Override + @Environment(EnvType.CLIENT) + public void initConfigurationWidgets(ModularGuiLineBuilder builder) { + + ModularGuiLineBuilderAccessor accessor = (ModularGuiLineBuilderAccessor)builder; + ResizableButton btn = new ResizableButton(accessor.crn$getX(), accessor.crn$getY() - 4, 16, 16, TextUtils.empty(), + (b) -> { + Util.getPlatform().openUri(Constants.HELP_PAGE_SCHEDULED_TIMES_AND_REAL_TIME); + }) { + @Override + public void renderButton(PoseStack poseStack, int mouseX, int mouseY, float partialTick) { + Graphics graphics = new Graphics(poseStack); + DynamicGuiRenderer.renderArea(graphics, x, y, width, height, AreaStyle.GRAY, isActive() ? (isFocused() || isMouseOver(mouseX, mouseY) ? ButtonState.SELECTED : ButtonState.BUTTON) : ButtonState.DISABLED); + ModGuiIcons.HELP.render(graphics, x, y); + } + }; + accessor.crn$getTarget().add(Pair.of(btn, "help_btn")); + } + + @Override + public void run(ScheduleRuntime runtime, TrainData data, Train train, int index) { + DLUtils.doIfNotNull(data, x -> { + data.resetPredictions(); + }); + } + + @Override + public void predict(TrainData data, ScheduleRuntime runtime, int indexInSchedule, Train train) { + } +} \ No newline at end of file diff --git a/common/src/main/java/de/mrjulsen/crn/data/schedule/instruction/TravelSectionInstruction.java b/common/src/main/java/de/mrjulsen/crn/data/schedule/instruction/TravelSectionInstruction.java new file mode 100644 index 00000000..86fd2467 --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/data/schedule/instruction/TravelSectionInstruction.java @@ -0,0 +1,140 @@ +package de.mrjulsen.crn.data.schedule.instruction; + +import java.util.ArrayList; +import java.util.List; + +import com.mojang.blaze3d.vertex.PoseStack; +import com.simibubi.create.content.trains.entity.Train; +import com.simibubi.create.content.trains.schedule.ScheduleRuntime; +import com.simibubi.create.content.trains.schedule.ScheduleScreen; +import com.simibubi.create.content.trains.schedule.destination.ScheduleInstruction; +import com.simibubi.create.foundation.gui.ModularGuiLineBuilder; +import com.simibubi.create.foundation.utility.Pair; + +import de.mrjulsen.crn.CreateRailwaysNavigator; +import de.mrjulsen.crn.client.gui.screen.TrainSectionSettingsScreen; +import de.mrjulsen.crn.client.gui.widgets.ResizableButton; +import de.mrjulsen.crn.data.storage.GlobalSettings; +import de.mrjulsen.crn.data.train.TrainData; +import de.mrjulsen.crn.data.train.TrainTravelSection; +import de.mrjulsen.crn.mixin.ModularGuiLineBuilderAccessor; +import de.mrjulsen.crn.mixin.ScheduleScreenAccessor; +import de.mrjulsen.crn.registry.ModBlocks; +import de.mrjulsen.mcdragonlib.DragonLib; +import de.mrjulsen.mcdragonlib.client.render.DynamicGuiRenderer; +import de.mrjulsen.mcdragonlib.client.render.DynamicGuiRenderer.AreaStyle; +import de.mrjulsen.mcdragonlib.client.render.DynamicGuiRenderer.ButtonState; +import de.mrjulsen.mcdragonlib.client.util.Graphics; +import de.mrjulsen.mcdragonlib.client.util.GuiUtils; +import de.mrjulsen.mcdragonlib.core.EAlignment; +import de.mrjulsen.mcdragonlib.util.DLUtils; +import de.mrjulsen.mcdragonlib.util.TextUtils; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.minecraft.ChatFormatting; +import net.minecraft.client.Minecraft; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.chat.CommonComponents; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.ItemStack; + +public class TravelSectionInstruction extends ScheduleInstruction implements IStationTagInstruction, IPredictableInstruction { + + public static final String NBT_TRAIN_GROUP = "TrainGroup"; + public static final String NBT_TRAIN_LINE = "TrainLine"; + public static final String NBT_INCLUDE_PREVIOUS_STATION = "IncludePreviousStation"; + public static final String NBT_USABLE = "Usable"; + + public TravelSectionInstruction() { + } + + @Override + protected void readAdditional(CompoundTag tag) { + super.readAdditional(tag); + if (!tag.contains(NBT_TRAIN_GROUP)) tag.putString(NBT_TRAIN_GROUP, ""); + if (!tag.contains(NBT_TRAIN_LINE)) tag.putString(NBT_TRAIN_LINE, ""); + if (!tag.contains(NBT_INCLUDE_PREVIOUS_STATION)) tag.putBoolean(NBT_INCLUDE_PREVIOUS_STATION, false); + if (!tag.contains(NBT_USABLE)) tag.putBoolean(NBT_USABLE, true); + } + + @Override + public Pair getSummary() { + return Pair.of(new ItemStack(ModBlocks.ADVANCED_DISPLAY.get()), TextUtils.translate(CreateRailwaysNavigator.MOD_ID + ".schedule.instruction." + getId().getPath()).withStyle(ChatFormatting.AQUA)); + } + + @Override + public ResourceLocation getId() { + return new ResourceLocation(CreateRailwaysNavigator.MOD_ID, "travel_section"); + } + + @Override + public boolean supportsConditions() { + return false; + } + + @Override + public List getTitleAs(String type) { + String noneText = TextUtils.translate("gui.createrailwaysnavigator.section_settings.none").getString(); + List lines = new ArrayList<>(); + lines.add(TextUtils.translate(CreateRailwaysNavigator.MOD_ID + ".schedule." + type + "." + getId().getPath()).withStyle(ChatFormatting.GOLD)); + lines.add(TextUtils.translate(CreateRailwaysNavigator.MOD_ID + ".schedule." + type + "." + getId().getPath() + ".description").withStyle(ChatFormatting.GRAY)); + if (data.contains(NBT_TRAIN_GROUP)) lines.add(TextUtils.translate(CreateRailwaysNavigator.MOD_ID + ".schedule." + type + "." + getId().getPath() + ".train_group").withStyle(ChatFormatting.DARK_AQUA).append(TextUtils.text(data.getString(NBT_TRAIN_GROUP).isBlank() ? noneText : data.getString(NBT_TRAIN_GROUP).toString()).withStyle(ChatFormatting.WHITE))); + if (data.contains(NBT_TRAIN_LINE)) lines.add(TextUtils.translate(CreateRailwaysNavigator.MOD_ID + ".schedule." + type + "." + getId().getPath() + ".train_line").withStyle(ChatFormatting.DARK_AQUA).append(TextUtils.text(data.getString(NBT_TRAIN_LINE).isBlank() ? noneText : data.getString(NBT_TRAIN_LINE).toString()).withStyle(ChatFormatting.WHITE))); + if (data.contains(NBT_INCLUDE_PREVIOUS_STATION)) lines.add(TextUtils.translate(CreateRailwaysNavigator.MOD_ID + ".schedule." + type + "." + getId().getPath() + ".include_previous_station").withStyle(ChatFormatting.DARK_AQUA).append((data.getBoolean(NBT_INCLUDE_PREVIOUS_STATION) ? CommonComponents.GUI_YES : CommonComponents.GUI_NO))); + if (data.contains(NBT_USABLE)) lines.add(TextUtils.translate(CreateRailwaysNavigator.MOD_ID + ".schedule." + type + "." + getId().getPath() + ".usable").withStyle(ChatFormatting.DARK_AQUA).append((data.getBoolean(NBT_USABLE) ? CommonComponents.GUI_YES : CommonComponents.GUI_NO))); + return lines; + } + + /** HERE BE DRAGONS! This code is very illegal, but it works... */ + @Override + @Environment(EnvType.CLIENT) + @SuppressWarnings("resource") + public void initConfigurationWidgets(ModularGuiLineBuilder builder) { + ModularGuiLineBuilderAccessor accessor = (ModularGuiLineBuilderAccessor)builder; + + ResizableButton btn = new ResizableButton(accessor.crn$getX(), accessor.crn$getY() - 4, 121, 16, TextUtils.translate(CreateRailwaysNavigator.MOD_ID + ".schedule.instruction." + getId().getPath() + ".configure"), + (b) -> { + if (Minecraft.getInstance().screen instanceof ScheduleScreen scheduleScreen) { + ((ScheduleScreenAccessor)scheduleScreen).crn$getOnEditorClose().accept(true); + builder.customArea(0, 0).speechBubble(); + Minecraft.getInstance().setScreen(new TrainSectionSettingsScreen(scheduleScreen, data)); + } + }) { + @Override + public void renderButton(PoseStack poseStack, int mouseX, int mouseY, float partialTick) { + Graphics graphics = new Graphics(poseStack); + DynamicGuiRenderer.renderArea(graphics, x, y, width, height, AreaStyle.GRAY, isActive() ? (isFocused() || isMouseOver(mouseX, mouseY) ? ButtonState.SELECTED : ButtonState.BUTTON) : ButtonState.DISABLED); + int j = isActive() ? DragonLib.NATIVE_BUTTON_FONT_COLOR_ACTIVE : DragonLib.NATIVE_BUTTON_FONT_COLOR_DISABLED; + GuiUtils.drawString(graphics, Minecraft.getInstance().font, x + width / 2, y + (height - 8) / 2, this.getMessage(), j, EAlignment.CENTER, true); + } + }; + accessor.crn$getTarget().add(Pair.of(btn, "config_btn")); + } + + @Override + public void run(ScheduleRuntime runtime, TrainData data, Train train, int index) { + DLUtils.doIfNotNull(data, x -> { + x.addTravelSection(getSectionData(x, index)); + x.changeCurrentSection(index); + }); + } + + private TrainTravelSection getSectionData(TrainData data, int index) { + return new TrainTravelSection( + data, + index, + GlobalSettings.getInstance().getTrainGroup(this.data.getString(NBT_TRAIN_GROUP)).orElse(null), + GlobalSettings.getInstance().getTrainLine(this.data.getString(NBT_TRAIN_LINE)).orElse(null), + this.data.getBoolean(NBT_INCLUDE_PREVIOUS_STATION), + this.data.getBoolean(NBT_USABLE) + ); + } + + @Override + public void predict(TrainData data, ScheduleRuntime runtime, int indexInSchedule, Train train) { + DLUtils.doIfNotNull(data, x -> { + x.addTravelSection(getSectionData(x, indexInSchedule)); + }); + } +} \ No newline at end of file diff --git a/common/src/main/java/de/mrjulsen/crn/data/storage/GlobalSettings.java b/common/src/main/java/de/mrjulsen/crn/data/storage/GlobalSettings.java new file mode 100644 index 00000000..91113968 --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/data/storage/GlobalSettings.java @@ -0,0 +1,496 @@ +package de.mrjulsen.crn.data.storage; + +import java.io.File; +import java.io.IOException; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; +import java.util.ArrayList; + +import com.google.common.collect.ImmutableList; +import com.simibubi.create.content.trains.entity.Train; +import com.simibubi.create.content.trains.station.GlobalStation; + +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.Optional; +import java.util.Map; + +import de.mrjulsen.crn.CreateRailwaysNavigator; +import de.mrjulsen.crn.data.StationTag; +import de.mrjulsen.crn.data.TagName; +import de.mrjulsen.crn.data.TrainGroup; +import de.mrjulsen.crn.data.TrainLine; +import de.mrjulsen.crn.data.UserSettings; +import de.mrjulsen.crn.data.StationTag.StationInfo; +import de.mrjulsen.crn.data.train.TrainListener; +import de.mrjulsen.crn.data.train.TrainPrediction; +import de.mrjulsen.crn.data.train.TrainStop; +import de.mrjulsen.crn.data.train.TrainTravelSection; +import de.mrjulsen.crn.event.ModCommonEvents; +import de.mrjulsen.mcdragonlib.data.INBTSerializable; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.ListTag; +import net.minecraft.nbt.NbtIo; +import net.minecraft.nbt.StringTag; +import net.minecraft.nbt.Tag; +import net.minecraft.server.MinecraftServer; +import net.minecraft.world.level.storage.LevelResource; + +public class GlobalSettings implements INBTSerializable { + + /** @deprecated For data migration only. Use {@code FILENAME} instead. */ + @Deprecated + public static final String LEGACY_FILENAME = "createrailwaysnavigator_global_settings.dat"; + + public static final String FILENAME = CreateRailwaysNavigator.MOD_ID + "_global_settings.nbt"; + + public static final int DATA_VERSION = 1; + + private static final String NBT_VERSION = "Version"; + private static final String NBT_STATION_TAGS = "StationTags"; + private static final String NBT_TRAIN_GROUPS = "TrainGroups"; + private static final String NBT_STATION_BLACKLIST = "StationBlacklist"; + private static final String NBT_TRAIN_BLACKLIST = "TrainBlacklist"; + private static final String NBT_TRAIN_LINES = "TrainLines"; + + private final MinecraftServer server; + + private final Map stationTags = new HashMap<>(); + private final Map trainGroups = new HashMap<>(); + private final Set stationBlacklist = new HashSet<>(); + private final Set trainBlacklist = new HashSet<>(); + private final Map trainLines = new HashMap<>(); + + private static GlobalSettings instance; + + + private GlobalSettings(MinecraftServer server) { + this.server = server; + } + + public synchronized static GlobalSettings getInstance() { + if (instance == null) { + try { + instance = GlobalSettings.open(ModCommonEvents.getCurrentServer().get()); + } catch (IOException e) { + CreateRailwaysNavigator.LOGGER.error("Unable to open settings file.", e); + instance = new GlobalSettings(ModCommonEvents.getCurrentServer().get()); + } + } + return instance; + } + + public static boolean hasInstance() { + return instance != null; + } + + public static void clearInstance() { + if (instance != null) { + instance.close(); + } + instance = null; + } + + public synchronized void save() { + CompoundTag nbt = this.serializeNbt(); + + try { + NbtIo.writeCompressed(nbt, new File(server.getWorldPath(new LevelResource("data/" + FILENAME)).toString())); + CreateRailwaysNavigator.LOGGER.info("Saved global settings."); + } catch (IOException e) { + CreateRailwaysNavigator.LOGGER.error("Unable to save global settings.", e); + } + } + + public synchronized static GlobalSettings open(MinecraftServer server) throws IOException { + File legacyFile = new File(server.getWorldPath(new LevelResource("data/" + LEGACY_FILENAME)).toString()); + File settingsFile = new File(server.getWorldPath(new LevelResource("data/" + FILENAME)).toString()); + + GlobalSettings file = new GlobalSettings(server); + + if (legacyFile.exists()) { + CreateRailwaysNavigator.LOGGER.warn("A legacy global settings file was found. Try to load it."); + file.deserializeNbtLegacy(NbtIo.readCompressed(legacyFile).getCompound("data")); + legacyFile.delete(); + } else if (settingsFile.exists()) { + file.deserializeNbt(NbtIo.readCompressed(settingsFile)); + } + return file; + } + + public CompoundTag serializeNbt() { + CompoundTag nbt = new CompoundTag(); + nbt.putInt(NBT_VERSION, DATA_VERSION); + + CompoundTag stationsComp = new CompoundTag(); + this.stationTags.entrySet().forEach(x -> stationsComp.put(x.getKey().toString(), x.getValue().toNbt())); + nbt.put(NBT_STATION_TAGS, stationsComp); + + CompoundTag trainGroupComp = new CompoundTag(); + this.trainGroups.entrySet().forEach(x -> trainGroupComp.put(x.getKey().toString(), x.getValue().toNbt())); + nbt.put(NBT_TRAIN_GROUPS, trainGroupComp); + + ListTag stationsBlacklist = new ListTag(); + this.stationBlacklist.stream().forEach(x -> stationsBlacklist.add(StringTag.valueOf(x))); + nbt.put(NBT_STATION_BLACKLIST, stationsBlacklist); + + ListTag trainsBlacklist = new ListTag(); + this.trainBlacklist.stream().forEach(x -> trainsBlacklist.add(StringTag.valueOf(x))); + nbt.put(NBT_TRAIN_BLACKLIST, trainsBlacklist); + + CompoundTag trainLinesComp = new CompoundTag(); + this.trainLines.entrySet().forEach(x -> trainLinesComp.put(x.getKey().toString(), x.getValue().toNbt())); + nbt.put(NBT_TRAIN_LINES, trainLinesComp); + + return nbt; + } + + public void deserializeNbt(CompoundTag nbt) { + @SuppressWarnings("unused") int version = nbt.getInt(NBT_VERSION); + + CompoundTag stationsComp = nbt.getCompound(NBT_STATION_TAGS); + this.stationTags.putAll(stationsComp.getAllKeys().stream().map(x -> StationTag.fromNbt(stationsComp.getCompound(x), UUID.fromString(x))).collect(Collectors.toMap(x -> x.getId(), x -> x))); + CompoundTag trainGroupsComp = nbt.getCompound(NBT_TRAIN_GROUPS); + this.trainGroups.putAll(trainGroupsComp.getAllKeys().stream().map(x -> TrainGroup.fromNbt(trainGroupsComp.getCompound(x))).collect(Collectors.toMap(x -> x.getGroupName(), x -> x))); + this.stationBlacklist.addAll(nbt.getList(NBT_STATION_BLACKLIST, Tag.TAG_STRING).stream().map(x -> ((StringTag)x).getAsString()).toList()); + this.trainBlacklist.addAll(nbt.getList(NBT_TRAIN_BLACKLIST, Tag.TAG_STRING).stream().map(x -> ((StringTag)x).getAsString()).toList()); + CompoundTag trainLinesComp = nbt.getCompound(NBT_TRAIN_LINES); + this.trainLines.putAll(trainLinesComp.getAllKeys().stream().map(x -> TrainLine.fromNbt(trainLinesComp.getCompound(x))).collect(Collectors.toMap(x -> x.getLineName(), x -> x))); + + } + + /** + * @deprecated For data migration only. Use {@code deserializeNbt} instead. + */ + @Deprecated + public void deserializeNbtLegacy(CompoundTag nbt) { + final String NBT_ALIAS_REGISTRY = "RegisteredAliasData"; + final String NBT_BLACKLIST = "StationBlacklist"; + final String NBT_TRAIN_BLACKLIST = "TrainBlacklist"; + final String NBT_TRAIN_GROUP_REGISTRY = "RegisteredTrainGroups"; + + Collection aliasData = new ArrayList<>(); + Collection trainGroupData = new ArrayList<>(); + Collection blacklistData = new ArrayList<>(); + Collection trainBlacklistData = new ArrayList<>(); + + if (nbt.contains(NBT_ALIAS_REGISTRY)) { + aliasData = nbt.getList(NBT_ALIAS_REGISTRY, Tag.TAG_COMPOUND).stream().map(x -> (CompoundTag)x).toList(); + } + + if (nbt.contains(NBT_TRAIN_GROUP_REGISTRY)) { + trainGroupData = nbt.getList(NBT_TRAIN_GROUP_REGISTRY, Tag.TAG_COMPOUND).stream().map(x -> (CompoundTag)x).toList(); + } + + if (nbt.contains(NBT_BLACKLIST)) { + blacklistData = nbt.getList(NBT_BLACKLIST, Tag.TAG_STRING).stream().map(x -> ((StringTag)x).getAsString()).toList(); + } + + if (nbt.contains(NBT_TRAIN_BLACKLIST)) { + trainBlacklistData = nbt.getList(NBT_TRAIN_BLACKLIST, Tag.TAG_STRING).stream().map(x -> ((StringTag)x).getAsString()).toList(); + } + + Set usedIds = new LinkedHashSet<>(); + stationTags.putAll(aliasData.stream().map(x -> { + UUID id; + do { + id = UUID.randomUUID(); + } while (usedIds.contains(id)); + usedIds.add(id); + return StationTag.fromNbt(x, id); + }).collect(Collectors.toMap(x -> x.getId(), x -> x))); + usedIds.clear(); + trainGroups.putAll(trainGroupData.stream().map(x -> TrainGroup.fromNbt(x)).collect(Collectors.toMap(x -> x.getGroupName(), x -> x))); + stationBlacklist.addAll(blacklistData); + trainBlacklist.addAll(trainBlacklistData); + + save(); + } + + public void close() { + this.save(); + } + +//#region +++ STATION TAGS +++ + + public boolean hasStationTag(GlobalStation station) { + return hasStationTag(station.name); + } + + public boolean hasStationTag(String stationName) { + return stationTags.values().stream().anyMatch(x -> x.contains(stationName)); + } + + public boolean stationTagExists(String tagName) { + return stationTagExists(TagName.of(tagName)); + } + + public boolean stationTagExists(TagName tagName) { + return stationTags.values().stream().anyMatch(x -> x.getTagName().equals(tagName)); + } + + public boolean stationTagExists(UUID id) { + return stationTags.containsKey(id); + } + + public StationTag getOrCreateStationTagFor(GlobalStation station) { + return getOrCreateStationTagFor(station.name); + } + + public StationTag getOrCreateStationTagFor(TagName tagName) { + return getTagByName(tagName).orElse(getOrCreateStationTagFor(tagName.get())); + } + + /** + * @param stationName The name of the train station. + * @return Returns the station tag for the given train station. + */ + public StationTag getOrCreateStationTagFor(String stationName) { + if (stationName.contains("*")) { + return getOrCreateTagForWildcard(stationName); + } + + Optional a = stationTags.values().stream().filter(x -> x.contains(stationName)).findFirst(); + if (a.isPresent()) { + return a.get(); + } + + return new StationTag(null, TagName.of(stationName), Map.of(stationName, StationInfo.empty())); + } + + private StationTag getOrCreateTagForWildcard(String stationName) { + String regex = stationName.isBlank() ? stationName : "\\Q" + stationName.replace("*", "\\E.*\\Q") + "\\E"; + Optional a = stationTags.values().stream().filter(x -> x.getAllStationNames().stream().anyMatch(y -> y.matches(regex))).findFirst(); + if (a.isPresent()) { + return a.get(); + } + + return new StationTag(null, TagName.of(stationName), Map.of(stationName, StationInfo.empty())); + } + + /** + * Get the station tag with the given name or create and register a new one, if no tag exists. + * @param name The name of the station tag. + * @return The station tag for the name. + */ + public StationTag createOrGetStationTag(String name) { + return createOrGetStationTag(TagName.of(name)); + } + + /** + * Get the station tag with the given name or create and register a new one, if no tag exists. + * @param name The name of the station tag. + * @return The station tag for the name. + */ + public StationTag createOrGetStationTag(TagName name) { + Optional tag = getTagByName(name); + if (tag.isPresent()) { + return tag.get(); + } + UUID newId; + do { + newId = UUID.randomUUID(); + } while (stationTags.containsKey(newId)); + StationTag newTag = new StationTag(newId, name); + stationTags.put(newId, newTag); + return newTag; + } + + public StationTag registerStationTag(StationTag tag) { + UUID newId; + do { + newId = UUID.randomUUID(); + } while (stationTags.containsKey(newId)); + tag.setId(newId); + stationTags.put(newId, tag); + return tag; + } + + public Optional getTagByName(TagName name) { + return stationTags.values().stream().filter(x -> x.getTagName().equals(name)).findFirst(); + } + + public Optional getStationTag(UUID id) { + return Optional.ofNullable(stationTagExists(id) ? stationTags.get(id) : null); + } + + public boolean removeStationTag(String name) { + return removeStationTag(TagName.of(name)); + } + + public boolean removeStationTag(TagName name) { + return stationTags.values().removeIf(x -> x.getTagName().equals(name)); + } + + public StationTag removeStationTag(UUID id) { + return stationTags.remove(id); + } + + public ImmutableList getAllStationTags() { + return ImmutableList.copyOf(stationTags.values()); + } + +//#endregion +//#region +++ TRAIN GROUPS +++ + + public boolean trainGroupExists(String name) { + return trainGroups.containsKey(name); + } + + /** + * Get the train group with the given name or create and register a new one, if no group exists. + * @param name The name of the train group. + * @return The train group for the name. + */ + public TrainGroup createOrGetTrainGroup(String name) { + Optional tag = getTrainGroup(name); + if (tag.isPresent()) { + return tag.get(); + } + TrainGroup newGroup = new TrainGroup(name); + trainGroups.put(name, newGroup); + return newGroup; + } + + public Optional getTrainGroup(String name) { + return Optional.ofNullable(trainGroupExists(name) ? trainGroups.get(name) : null); + } + + public TrainGroup removeTrainGroup(String name) { + return trainGroups.remove(name); + } + + public ImmutableList getAllTrainGroups() { + return ImmutableList.copyOf(trainGroups.values()); + } + + public boolean isTrainExcludedByUser(Train train, UserSettings settings) { + return !TrainListener.data.get(train.id).getSections().isEmpty() && TrainListener.data.get(train.id).getSections().stream().allMatch(x -> !x.isUsable() || (x.getTrainGroup() != null && settings.navigationExcludedTrainGroups.getValue().contains(x.getTrainGroup().getGroupName()))); + //List groupsOfTrain = getTrainGroupsOfTrain(train); + //Set excludedGroups = settings.navigationExcludedTrainGroups.getValue(); + //return !groupsOfTrain.isEmpty() && !excludedGroups.isEmpty() && groupsOfTrain.stream().allMatch(a -> excludedGroups.stream().anyMatch(b -> a.getId().equals(b.getId()))); + } + + public boolean isTrainStationExcludedByUser(Train train, TrainPrediction at, UserSettings settings) { + return at.getSection().getTrainGroup() != null && (!at.getSection().isUsable() || (at.getSection().getTrainGroup() != null && settings.navigationExcludedTrainGroups.getValue().contains(at.getSection().getTrainGroup().getGroupName()))); + } + + public boolean isTrainStationExcludedByUser(Train train, TrainStop at, UserSettings settings) { + TrainTravelSection section = TrainListener.data.get(train.id).getSectionByIndex(at.getSectionIndex()); + return section.getTrainGroup() != null && (!section.isUsable() || (section.getTrainGroup() != null && settings.navigationExcludedTrainGroups.getValue().contains(section.getTrainGroup().getGroupName()))); + } + +//#endregion +//#region +++ STATION BLACKLIST +++ + + public boolean isStationBlacklisted(GlobalStation station) { + return isStationBlacklisted(station.name); + } + + public boolean isStationBlacklisted(String name) { + return stationBlacklist.contains(name); + } + + public void blacklistStation(GlobalStation station) { + blacklistStation(station.name); + } + + public void blacklistStation(String stationName) { + stationBlacklist.add(stationName); + } + + public boolean removeStationFromBlacklist(GlobalStation station) { + return removeStationFromBlacklist(station.name); + } + + public boolean removeStationFromBlacklist(String stationName) { + return stationBlacklist.removeIf(x -> x.equals(stationName)); + } + + public boolean isEntireStationTagBlacklisted(StationTag tag) { + if (tag == null) { + return true; + } + return tag.getAllStationNames().stream().allMatch(x -> isStationBlacklisted(x)); + } + + public ImmutableList getAllBlacklistedStations() { + return ImmutableList.copyOf(stationBlacklist); + } + +//#endregion +//#region +++ TRAIN BLACKLIST +++ + + public boolean isTrainBlacklisted(Train train) { + return isTrainBlacklisted(train.name.getString()); + } + + public boolean isTrainBlacklisted(String trainName) { + return trainBlacklist.contains(trainName); + } + + public void blacklistTrain(Train train) { + blacklistTrain(train.name.getString()); + } + + public void blacklistTrain(String trainName) { + trainBlacklist.add(trainName); + } + + public boolean removeTrainFromBlacklist(Train train) { + return removeTrainFromBlacklist(train.name.getString()); + } + + public boolean removeTrainFromBlacklist(String trainName) { + return trainBlacklist.removeIf(x -> x.equals(trainName)); + } + + /* TODO + public boolean isEntireTrainGroupBlacklisted(TrainGroup tag) { + if (tag == null) { + return true; + } + return tag.getTrainNames().stream().allMatch(x -> isTrainBlacklisted(x)); + } + */ + + public ImmutableList getAllBlacklistedTrains() { + return ImmutableList.copyOf(trainBlacklist); + } + +//#endregion + +//#region +++ TRAIN LINES +++ + + public boolean trainLineExists(String name) { + return trainLines.containsKey(name); + } + + public TrainLine createOrGetTrainLine(String name) { + Optional tag = getTrainLine(name); + if (tag.isPresent()) { + return tag.get(); + } + TrainLine newGroup = new TrainLine(name); + trainLines.put(newGroup.getLineName(), newGroup); + return newGroup; + } + + public Optional getTrainLine(String name) { + return Optional.ofNullable(trainLineExists(name) ? trainLines.get(name) : null); + } + + public TrainLine removeTrainLine(String name) { + return trainLines.remove(name); + } + + public ImmutableList getAllTrainLines() { + return ImmutableList.copyOf(trainLines.values()); + } + +//#endregion + +} diff --git a/common/src/main/java/de/mrjulsen/crn/data/storage/GlobalSettingsClient.java b/common/src/main/java/de/mrjulsen/crn/data/storage/GlobalSettingsClient.java new file mode 100644 index 00000000..c354d600 --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/data/storage/GlobalSettingsClient.java @@ -0,0 +1,124 @@ +package de.mrjulsen.crn.data.storage; + +import java.util.function.Consumer; +import java.util.Collection; +import java.util.List; +import java.util.UUID; +import java.util.Optional; + +import de.mrjulsen.crn.data.StationTag; +import de.mrjulsen.crn.data.TrainGroup; +import de.mrjulsen.crn.data.TrainLine; +import de.mrjulsen.crn.data.StationTag.StationInfo; +import de.mrjulsen.crn.registry.ModAccessorTypes; +import de.mrjulsen.mcdragonlib.util.accessor.DataAccessor; + +/** + * Client access for global settings, which are only present on the server side. + */ +public class GlobalSettingsClient { + + public static void getStationTags(Consumer> result) { + DataAccessor.getFromServer(null, ModAccessorTypes.GET_ALL_STATION_TAGS, result); + } + + public static void getStationTag(String name, Consumer result) { + DataAccessor.getFromServer(name, ModAccessorTypes.GET_STATION_TAG, result); + } + + public static void createStationTag(String name, Consumer result) { + DataAccessor.getFromServer(name, ModAccessorTypes.CREATE_STATION_TAG, result); + } + + public static void registerNewStationTag(StationTag tag, Runnable callback) { + DataAccessor.getFromServer(tag, ModAccessorTypes.REGISTER_STATION_TAG, x -> callback.run()); + } + + public static void deleteStationTag(UUID tagId, Runnable callback) { + DataAccessor.getFromServer(tagId, ModAccessorTypes.DELETE_STATION_TAG, x -> callback.run()); + } + + public static record UpdateStationTagNameData(UUID tagId, String name) {} + public static void updateStationTagNameData(UUID tagId, String name, Runnable callback) { + DataAccessor.getFromServer(new UpdateStationTagNameData(tagId, name), ModAccessorTypes.UPDATE_STATION_TAG_NAME, x -> callback.run()); + } + + public static record AddStationTagEntryData(UUID tagId, String station, StationInfo info) {} + public static void addStationTagEntry(UUID tagId, String station, StationInfo info, Consumer> callback) { + DataAccessor.getFromServer(new AddStationTagEntryData(tagId, station, info), ModAccessorTypes.ADD_STATION_TAG_ENTRY, callback); + } + + public static void updateStationTagEntry(UUID tagId, String station, StationInfo info, Consumer> callback) { + DataAccessor.getFromServer(new AddStationTagEntryData(tagId, station, info), ModAccessorTypes.UPDATE_STATION_TAG_ENTRY, callback); + } + + public static record RemoveStationTagEntryData(UUID tagId, String station) {} + public static void removeStationTagEntry(UUID tagId, String station, Consumer> callback) { + DataAccessor.getFromServer(new RemoveStationTagEntryData(tagId, station), ModAccessorTypes.REMOVE_STATION_TAG_ENTRY, callback); + } + + + + public static void getTrainGroups(Consumer> result) { + DataAccessor.getFromServer(null, ModAccessorTypes.GET_ALL_TRAIN_GROUPS, result); + } + + public static void deleteTrainGroup(String name, Runnable callback) { + DataAccessor.getFromServer(name, ModAccessorTypes.DELETE_TRAIN_GROUP, x -> callback.run()); + } + + public static record UpdateTrainGroupColorData(String name, int color) {} + public static void updateTrainGroupColor(String name, int color, Runnable callback) { + DataAccessor.getFromServer(new UpdateTrainGroupColorData(name, color), ModAccessorTypes.UPDATE_TRAIN_GROUP_COLOR, x -> callback.run()); + } + + public static void createTrainGroup(String name, Consumer result) { + DataAccessor.getFromServer(name, ModAccessorTypes.CREATE_TRAIN_GROUP, result); + } + + + + public static void getBlacklistedStations(Consumer> result) { + DataAccessor.getFromServer(null, ModAccessorTypes.GET_BLACKLISTED_STATIONS, result); + } + + public static void addStationToBlacklist(String name, Consumer> result) { + DataAccessor.getFromServer(name, ModAccessorTypes.ADD_STATION_TO_BLACKLIST, result); + } + + public static void removeStationFromBlacklist(String name, Consumer> result) { + DataAccessor.getFromServer(name, ModAccessorTypes.REMOVE_STATION_FROM_BLACKLIST, result); + } + + + + public static void getBlacklistedTrains(Consumer> result) { + DataAccessor.getFromServer(null, ModAccessorTypes.GET_BLACKLISTED_TRAINS, result); + } + + public static void addTrainToBlacklist(String name, Consumer> result) { + DataAccessor.getFromServer(name, ModAccessorTypes.ADD_TRAIN_TO_BLACKLIST, result); + } + + public static void removeTrainFromBlacklist(String name, Consumer> result) { + DataAccessor.getFromServer(name, ModAccessorTypes.REMOVE_TRAIN_FROM_BLACKLIST, result); + } + + + + public static void getTrainLines(Consumer> result) { + DataAccessor.getFromServer(null, ModAccessorTypes.GET_ALL_TRAIN_LINES, result); + } + + public static void deleteTrainLine(String lineId, Runnable callback) { + DataAccessor.getFromServer(lineId, ModAccessorTypes.DELETE_TRAIN_LINE, x -> callback.run()); + } + public static record UpdateTrainLineColorData(String name, int color) {} + public static void updateTrainLineColor(String name, int color, Runnable callback) { + DataAccessor.getFromServer(new UpdateTrainLineColorData(name, color), ModAccessorTypes.UPDATE_TRAIN_LINE_COLOR, x -> callback.run()); + } + + public static void createTrainLine(String name, Consumer result) { + DataAccessor.getFromServer(name, ModAccessorTypes.CREATE_TRAIN_LINE, result); + } +} diff --git a/common/src/main/java/de/mrjulsen/crn/data/train/ClientTrainStop.java b/common/src/main/java/de/mrjulsen/crn/data/train/ClientTrainStop.java new file mode 100644 index 00000000..6b367a38 --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/data/train/ClientTrainStop.java @@ -0,0 +1,215 @@ +package de.mrjulsen.crn.data.train; + +import java.util.HashMap; +import java.util.IdentityHashMap; +import java.util.Map; +import java.util.UUID; +import java.util.function.Consumer; + +import com.simibubi.create.content.trains.entity.TrainIconType; + +import de.mrjulsen.crn.config.ModClientConfig; +import de.mrjulsen.crn.data.StationTag.ClientStationTag; +import de.mrjulsen.crn.data.TrainInfo; +import de.mrjulsen.crn.data.navigation.ITrainListenerClient; +import de.mrjulsen.crn.exceptions.RuntimeSideException; +import de.mrjulsen.crn.util.IListenable; +import dev.architectury.platform.Platform; +import dev.architectury.utils.Env; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.resources.ResourceLocation; + +/** + * A small variant of the {@code NewTrainPrediction} class, representing a stop of a train on its route with some important information. + */ +public class ClientTrainStop extends TrainStop implements ITrainListenerClient, IListenable { + + public static final String EVENT_UPDATE = "update"; + public static final String EVENT_DELAY = "delayed"; + public static final String EVENT_SCHEDULE_CHANGED = "schedule_changed"; + public static final String EVENT_STATION_CHANGED = "station_changed"; + public static final String EVENT_STATION_REACHED = "station_reached"; + public static final String EVENT_STATION_LEFT = "station_left"; + public static final String EVENT_ANNOUNCE_NEXT_STOP = "announce_next_stop"; + private final Map>> listeners = new HashMap<>(); + + private boolean isClosed = false; + + public ClientTrainStop(int scheduleIndex, int sectionIndex, UUID trainId, String trainName, TrainIconType trainIcon, TrainInfo trainInfo, + String scheduleTitle, boolean isCustomTitle, String terminusText, int stayDuration, boolean simulated, + long scheduledDepartureTime, long scheduledArrivalTime, int cycle, ClientStationTag tag, long realTimeArrivalTime, + long realTimeDepartureTime, int realTimeCycle, ClientStationTag realTimeTag, long arrivalTimeDeviation, + long departureTimeDeviation, int realTimeTicksUntilArrival, TrainState trainPosition) + { + super(scheduleIndex, sectionIndex, trainId, trainName, trainIcon, trainInfo, scheduleTitle, isCustomTitle, + terminusText, stayDuration, simulated, scheduledDepartureTime, scheduledArrivalTime, cycle, tag, + realTimeArrivalTime, realTimeDepartureTime, realTimeCycle, realTimeTag, arrivalTimeDeviation, + departureTimeDeviation, realTimeTicksUntilArrival, trainPosition); + initEvents(); + } + + private void initEvents() { + this.createEvent(EVENT_UPDATE); + this.createEvent(EVENT_DELAY); + this.createEvent(EVENT_SCHEDULE_CHANGED); + this.createEvent(EVENT_STATION_CHANGED); + this.createEvent(EVENT_STATION_REACHED); + this.createEvent(EVENT_STATION_LEFT); + this.createEvent(EVENT_ANNOUNCE_NEXT_STOP); + } + + @Override + public Map>> getListeners() { + return listeners; + } + + /** Client-side only! */ + public long getRoundedRealTimeArrivalTime() throws RuntimeSideException { + if (Platform.getEnvironment() != Env.CLIENT) { + throw new RuntimeSideException(true); + } + return (getScheduledArrivalTime() + getArrivalTimeDeviation()) / ModClientConfig.REALTIME_PRECISION_THRESHOLD.get() * ModClientConfig.REALTIME_PRECISION_THRESHOLD.get(); + } + + /** Client-side only! */ + public long getRoundedRealTimeDepartureTime() throws RuntimeSideException { + if (Platform.getEnvironment() != Env.CLIENT) { + throw new RuntimeSideException(true); + } + return (getScheduledDepartureTime() + getDepartureTimeDeviation()) / ModClientConfig.REALTIME_PRECISION_THRESHOLD.get() * ModClientConfig.REALTIME_PRECISION_THRESHOLD.get(); + } + + @Override + public void update(TrainStopRealTimeData data) { + if (isClosed) { + return; + } + + if (data.cycle() != getScheduledCycle()) { + if (data.cycle() > getScheduledCycle() && trainState != TrainState.AFTER) { + trainState = TrainState.AFTER; + notifyListeners(EVENT_STATION_LEFT, this); + close(); + } + return; + } + + boolean wasDelayed = isDepartureDelayed(); + String oldRealTimeStation = getRealTimeStationTag().stationName(); + int oldTimeUntilArrival = getTicksUntilArrival(); + + if (scheduledArrivalTime != data.scheduledArrivalTime() || scheduledDepartureTime != data.scheduledDepartureTime()) { + notifyListeners(EVENT_SCHEDULE_CHANGED, this); + } + + this.scheduledArrivalTime = data.scheduledArrivalTime(); + this.scheduledDepartureTime = data.scheduledDepartureTime(); + this.realTimeArrivalTime = data.realTimeArrivalTime(); + this.realTimeDepartureTime = data.realTimeDepartureTime(); + this.arrivalTimeDeviation = data.deltaArrivalTime(); + this.departureTimeDeviation = data.deltaDepartureTime(); + this.realTimeCycle = data.cycle(); + this.realTimeTag = data.station(); + this.realTimeTicksUntilArrival = data.ticksUntilArrival(); + + if (!wasDelayed && isAnyDelayed()) { + notifyListeners(EVENT_DELAY, this); + } + if (!oldRealTimeStation.equals(getRealTimeStationTag().stationName())) { + notifyListeners(EVENT_STATION_CHANGED, this); + } + if (trainState == TrainState.BEFORE && oldTimeUntilArrival > getTicksUntilArrival() && getTicksUntilArrival() <= ModClientConfig.NEXT_STOP_ANNOUNCEMENT.get()) { + trainState = TrainState.ANNOUNCED; + notifyListeners(EVENT_ANNOUNCE_NEXT_STOP, this); + } + + if (trainState.getPositionMultiplier() < 0 && getTicksUntilArrival() <= 0) { + trainState = TrainState.STAYING; + notifyListeners(EVENT_STATION_REACHED, this); + } + + notifyListeners(EVENT_UPDATE, this); + } + + public static TrainStop fromNbt(CompoundTag nbt) { + return new ClientTrainStop( + nbt.getInt(NBT_SCHEDULE_INDEX), + nbt.getInt(NBT_SECTION_INDEX), + nbt.getUUID(NBT_TRAIN_ID), + nbt.getString(NBT_TRAIN_NAME), + TrainIconType.byId(new ResourceLocation(nbt.getString(NBT_TRAIN_ICON))), + TrainInfo.fromNbt(nbt.getCompound(NBT_TRAIN_INFO)), + nbt.getString(NBT_SCHEDULE_TITLE), + nbt.getBoolean(NBT_IS_CUSTOM_TITLE), + nbt.getString(NBT_TERMINUS_TEXT), + nbt.getInt(NBT_STAY_DURATION), + nbt.getLong(NBT_SIMULATED_TIME) != 0, + nbt.getLong(NBT_SCHEDULED_DEPARTURE_TIME), + nbt.getLong(NBT_SCHEDULED_ARRIVAL_TIME), + nbt.getInt(NBT_CYCLE), + ClientStationTag.fromNbt(nbt.getCompound(NBT_TAG)), + nbt.getLong(NBT_REAL_TIME_ARRIVAL_TIME), + nbt.getLong(NBT_REAL_TIME_DEPARTURE_TIME), + nbt.getInt(NBT_REAL_CYCLE), + ClientStationTag.fromNbt(nbt.getCompound(NBT_REAL_TIME_TAG)), + nbt.contains(NBT_REAL_TIME_ARRIVAL_TIME) ? nbt.getLong(NBT_REAL_TIME_ARRIVAL_TIME) - nbt.getLong(NBT_SCHEDULED_ARRIVAL_TIME) : 0, + nbt.contains(NBT_REAL_TIME_DEPARTURE_TIME) ? nbt.getLong(NBT_REAL_TIME_DEPARTURE_TIME) - nbt.getLong(NBT_SCHEDULED_DEPARTURE_TIME) : 0, + 0, + TrainState.BEFORE + ); + } + + @Override + public void close() { + clearEvents(); + isClosed = true; + } + + public static record TrainStopRealTimeData(ClientStationTag station, int entryIndex, long scheduledArrivalTime, long scheduledDepartureTime, long realTimeArrivalTime, long realTimeDepartureTime, long deltaArrivalTime, long deltaDepartureTime, int ticksUntilArrival, int cycle) { + private static final String NBT_INDEX = "Index"; + private static final String NBT_STATION = "Station"; + private static final String NBT_SCHEDULED_ARRIVAL = "Arrival"; + private static final String NBT_SCHEDULED_DEPARTURE = "Departure"; + private static final String NBT_REAL_TIME_ARRIVAL = "RealArrival"; + private static final String NBT_REAL_TIME_DEPARTURE = "RealDeparture"; + private static final String NBT_DELTA_ARRIVAL = "DeltaArrival"; + private static final String NBT_DELTA_DEPARTURE = "DeltaDeparture"; + private static final String NBT_CYCLE = "Cycle"; + private static final String NBT_TICKS_UNTIL_ARRIVAL = "TUA"; + + public CompoundTag toNbt() { + CompoundTag nbt = new CompoundTag(); + nbt.put(NBT_STATION, station.toNbt()); + nbt.putInt(NBT_INDEX, entryIndex); + nbt.putLong(NBT_SCHEDULED_ARRIVAL, scheduledArrivalTime); + nbt.putLong(NBT_SCHEDULED_DEPARTURE, scheduledDepartureTime); + nbt.putLong(NBT_REAL_TIME_ARRIVAL, realTimeArrivalTime); + nbt.putLong(NBT_REAL_TIME_DEPARTURE, realTimeDepartureTime); + nbt.putLong(NBT_DELTA_ARRIVAL, deltaArrivalTime); + nbt.putLong(NBT_DELTA_DEPARTURE, deltaDepartureTime); + nbt.putInt(NBT_CYCLE, cycle); + nbt.putInt(NBT_TICKS_UNTIL_ARRIVAL, ticksUntilArrival); + + return nbt; + } + + public static TrainStopRealTimeData fromNbt(CompoundTag nbt) { + return new TrainStopRealTimeData( + ClientStationTag.fromNbt(nbt.getCompound(NBT_STATION)), + nbt.getInt(NBT_INDEX), + nbt.getLong(NBT_SCHEDULED_ARRIVAL), + nbt.getLong(NBT_SCHEDULED_DEPARTURE), + nbt.getLong(NBT_REAL_TIME_ARRIVAL), + nbt.getLong(NBT_REAL_TIME_DEPARTURE), + nbt.getLong(NBT_DELTA_ARRIVAL), + nbt.getLong(NBT_DELTA_DEPARTURE), + nbt.getInt(NBT_TICKS_UNTIL_ARRIVAL), + nbt.getInt(NBT_CYCLE) + ); + } + } + + public boolean isClosed() { + return isClosed; + } +} diff --git a/common/src/main/java/de/mrjulsen/crn/data/train/RoutePartProgressState.java b/common/src/main/java/de/mrjulsen/crn/data/train/RoutePartProgressState.java new file mode 100644 index 00000000..8d8466de --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/data/train/RoutePartProgressState.java @@ -0,0 +1,40 @@ +package de.mrjulsen.crn.data.train; + +import java.util.Arrays; + +public enum RoutePartProgressState { + BEFORE(0), + AT_START(1), + TRAVELING(2), + NEXT_STOP_ANNOUNCED(3), + AT_STOPOVER(4), + END_ANNOUNCED(4), + AT_END(5), + AFTER(6); + + private int index; + + private RoutePartProgressState(int index) { + this.index = index; + } + + public int getIndex() { + return index; + } + + public static RoutePartProgressState getByIndex(int index) { + return Arrays.stream(values()).filter(x -> x.getIndex() == index).findFirst().orElse(BEFORE); + } + + public boolean isAnyStopAnnounced() { + return this == NEXT_STOP_ANNOUNCED || this == END_ANNOUNCED; + } + + public boolean isAtAnyStop() { + return this == AT_START || this == AT_STOPOVER || this == AT_END; + } + + public boolean isOutOfBounds() { + return this == BEFORE || this == AFTER; + } +} diff --git a/common/src/main/java/de/mrjulsen/crn/data/train/RouteProgressState.java b/common/src/main/java/de/mrjulsen/crn/data/train/RouteProgressState.java new file mode 100644 index 00000000..072713bb --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/data/train/RouteProgressState.java @@ -0,0 +1,52 @@ +package de.mrjulsen.crn.data.train; + +import java.util.Arrays; + +public enum RouteProgressState { + BEFORE(0), + AT_START(1), + TRAVELING(2), + NEXT_STOP_ANNOUNCED(3), + AT_STOPOVER(4), + TRANSFER_ANNOUNCED(5), + AT_TRANSFER(6), + WHILE_TRANSFER(7), + BEFORE_CONTINUATION(8), + END_ANNOUNCED(9), + AT_END(10), + AFTER(11); + + private int index; + + private RouteProgressState(int index) { + this.index = index; + } + + public int getIndex() { + return index; + } + + public static RouteProgressState getByIndex(int index) { + return Arrays.stream(values()).filter(x -> x.getIndex() == index).findFirst().orElse(BEFORE); + } + + public boolean isAnyStopAnnounced() { + return this == NEXT_STOP_ANNOUNCED || this == END_ANNOUNCED || this == TRANSFER_ANNOUNCED; + } + + public boolean isAtAnyStop() { + return this == AT_START || this == AT_STOPOVER || this == AT_END || this == AT_TRANSFER || this == BEFORE_CONTINUATION; + } + + public boolean isOutOfBounds() { + return this == BEFORE || this == AFTER; + } + + public boolean isTransferring() { + return this == AT_TRANSFER || this == WHILE_TRANSFER || this == BEFORE_CONTINUATION; + } + + public boolean isWaiting() { + return isOutOfBounds() || this == WHILE_TRANSFER; + } +} diff --git a/common/src/main/java/de/mrjulsen/crn/data/train/TrainData.java b/common/src/main/java/de/mrjulsen/crn/data/train/TrainData.java new file mode 100644 index 00000000..48ff4576 --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/data/train/TrainData.java @@ -0,0 +1,672 @@ +package de.mrjulsen.crn.data.train; + +import java.util.List; +import java.util.Optional; +import java.util.PriorityQueue; +import java.util.Queue; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Consumer; +import java.util.stream.Collectors; +import java.util.Map.Entry; +import java.util.Map; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.IdentityHashMap; + +import com.google.common.collect.ImmutableMap; +import com.simibubi.create.content.trains.display.GlobalTrainDisplayData.TrainDeparturePrediction; +import com.simibubi.create.content.trains.entity.Train; + +import de.mrjulsen.crn.CreateRailwaysNavigator; +import de.mrjulsen.crn.config.ModCommonConfig; +import de.mrjulsen.crn.util.ModUtils; +import de.mrjulsen.crn.event.CRNEventsManager; +import de.mrjulsen.crn.event.events.TotalDurationTimeChangedEvent; +import de.mrjulsen.crn.data.TrainInfo; +import de.mrjulsen.crn.data.schedule.condition.DynamicDelayCondition; +import de.mrjulsen.crn.data.train.TrainStatus.CompiledTrainStatus; +import de.mrjulsen.crn.data.train.TrainStatus.TrainStatusType; +import de.mrjulsen.crn.util.IListenable; +import de.mrjulsen.crn.util.LockedList; +import de.mrjulsen.mcdragonlib.DragonLib; +import de.mrjulsen.mcdragonlib.data.Cache; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.resources.ResourceLocation; + +/** Contains general data about a specific train (but not about the individual stations) */ +public class TrainData implements IListenable { + + private transient static final int VERSION = 1; + + public transient static final String EVENT_TOTAL_DURATION_CHANGED = "total_duration_changed"; + public transient static final String EVENT_SECTION_CHANGED = "section_changed"; + public transient static final String EVENT_DESTINATION_CHANGED = "destination_changed"; + public transient static final String EVENT_STATION_REACHED = "station_reached"; + + private transient static final String NBT_VERSION = "Version"; + private transient static final String NBT_ID = "SessionId"; + private transient static final String NBT_TRAIN_ID = "TrainId"; + private transient static final String NBT_PREDICTIONS = "Predictions"; + private transient static final String NBT_CURRENT_SCHEDULE_INDEX = "CurrentScheduleIndex"; + private transient static final String NBT_LINE_ID = "LineId"; + private transient static final String NBT_LAST_DELAY_OFFSET = "LastDelay"; + private transient static final String NBT_CANCELLED = "Cancelled"; + private transient static final String NBT_TRANSIT_TIMES = "TransitTimes"; + + private transient static final int INVALID = -1; + + + private transient final Map>> listeners = new HashMap<>(); // Events + + private transient final Train train; + private UUID sessionId; + private final ConcurrentHashMap predictionsByIndex = new ConcurrentHashMap<>(); + private transient final ConcurrentHashMap sectionsByIndex = new ConcurrentHashMap<>(); + private transient final Cache defaultSection = new Cache<>(() -> TrainTravelSection.def(this)); + private transient final List predictionsChronologically = new LockedList<>(); + private transient final Set validPredictionEntries = new HashSet<>(); + private transient final Cache isDynamic = new Cache<>(() -> getTrain().runtime.getSchedule().entries.stream().anyMatch(x -> x.conditions.stream().flatMap(y -> y.stream()).anyMatch(y -> y instanceof DynamicDelayCondition c && c.minWaitTicks() < c.totalWaitTicks()))); + + private int currentScheduleIndex = INVALID; + private transient int currentTravelSectionIndex = INVALID; + private transient int lastScheduleIndex = INVALID; + private String lineId; + + private transient int totalDuration = INVALID; + private transient long destinationReachTime; + private transient boolean isAtStation = false; + + private transient boolean wasWaitingForSignal = false; + public transient UUID waitingForSignalId; + public transient final Set occupyingTrains = new HashSet<>(); + public transient int waitingForSignalTicks; + public transient boolean isManualControlled; + + /** Last measured ransit time (mem) */ + public transient int transitTime = 0; + /** Contains the last (single!) measured transit time that can be used for the calculation. */ + private transient final Map measuredTransitTimes = new HashMap<>(); + /** Contains the x last measured transit times that can be used for the calculation. */ + public final Map /* last x transit times */> transitTimeHistory = new HashMap<>(); + /** The current valid and used transit time. */ + public final Map currentTransitTime = new HashMap<>(); + + // Delays + private long lastSectionDelayOffset; + private boolean cancelled = false; + private transient final Map delaysBySignal = new HashMap<>(); + private final Set currentStatusInfos = new HashSet<>(); // Reasons for delays, etc. + + private int refreshTimingsCounter = 0; + + // Queued Tasks + /** whether all predictions should be deleted */ + private transient boolean hardResetPredictions = false; + /** whether the initialization should now be completed */ + private transient boolean initializationFinishTask = false; + /** whether the initialization has already been completed */ + private transient boolean initializationCompleted = false; + private boolean hasStarted = false; + + // temp mem + private boolean sectionChanged; + private boolean destinationChanged; + + /* PLEASE NOTE! + * Chronologically update order (once every ~5 seconds): + * refreshPre() (once) + * setPredictionData() (x times) + * refreshPost() (once) + * + * tick() (every tick) + */ + + private TrainData(Train train, UUID sessionId) { + this.train = train; + this.sessionId = sessionId; + this.totalDuration = INVALID; + + createEvent(EVENT_TOTAL_DURATION_CHANGED); + createEvent(EVENT_DESTINATION_CHANGED); + createEvent(EVENT_SECTION_CHANGED); + createEvent(EVENT_STATION_REACHED); + } + + public static Optional of(UUID trainId) { + Optional train = TrainUtils.getTrain(trainId); + if (train.isPresent()) { + return Optional.of(new TrainData(train.get(), UUID.randomUUID())); + } + return Optional.empty(); + } + + public static TrainData of(Train train) { + return new TrainData(train, UUID.randomUUID()); + } + + public UUID getSessionId() { + return sessionId; + } + + public UUID getTrainId() { + return getTrain().id; + } + + public Train getTrain() { + return train; + } + + public TrainInfo getTrainInfo(int scheduleIndex) { + return new TrainInfo(getSectionForIndex(scheduleIndex).getTrainLine(), getSectionForIndex(scheduleIndex).getTrainGroup()); + } + + /** + * Checks if this train uses dynamic wait times to catch up for delays. + * If there are no dynamic waiting times, a train cannot catch up for delays, resulting in permanent delays. + * Even if everything goes according to plan, a train tends to be delayed due to calculation inaccuracies. + */ + public boolean isDynamic() { + return isDynamic.get(); + } + + private int getHistoryBufferSize() { + return ModCommonConfig.TOTAL_DURATION_BUFFER_SIZE.get() * 2 + 1; + } + + /** {@code true} when the train is currently waiting at a station. */ + public boolean isAtStation() { + return train.navigation.destination == null; + } + + /** The time in ticks the train is waiting at the current station. */ + public long waitingAtStationTicks() { + return isAtStation() ? DragonLib.getCurrentWorldTime() - destinationReachTime : 0; + } + + public boolean isCancelled() { + return cancelled; + } + + public int getTotalDuration() { + return totalDuration; + } + + public int getTransitTicks() { + return transitTime; + } + + public int getTransitTimeOf(int scheduleIndex) { + return currentTransitTime.containsKey(scheduleIndex) ? currentTransitTime.get(scheduleIndex) : INVALID; + } + + public boolean isWaitingAtStation() { + return isAtStation; + } + + public TrainTravelSection getCurrentTravelSection() { + return currentTravelSectionIndex < 0 || !sectionsByIndex.containsKey(currentTravelSectionIndex) ? defaultSection.get() : sectionsByIndex.get(currentTravelSectionIndex); + } + + public TrainTravelSection getSectionByIndex(int scheduleIndex) { + return sectionsByIndex.isEmpty() ? defaultSection.get() : sectionsByIndex.get(scheduleIndex); + } + + public void addTravelSection(TrainTravelSection section) { + this.sectionsByIndex.put(section.getScheduleIndex(), section); + } + + public String getCurrentTitle() { + return this.predictionsByIndex.containsKey(currentScheduleIndex) ? this.predictionsByIndex.get(currentScheduleIndex).getTitle() : ""; + } + + public String getTrainName() { + return train.name.getString(); + } + + public int getCurrentScheduleIndex() { + return currentScheduleIndex; + } + + public boolean hasCustomTravelSections() { + return !sectionsByIndex.isEmpty(); + } + + public boolean isSingleSection() { + return sectionsByIndex.size() <= 1; + } + + public List getSections() { + return sectionsByIndex.isEmpty() ? List.of(defaultSection.get()) : sectionsByIndex.values().stream().sorted((a, b) -> Integer.compare(a.getScheduleIndex(), b.getScheduleIndex())).toList(); + } + + public TrainTravelSection getSectionForIndex(int anyIndex) { + if (isSingleSection()) { + return getSections().get(0); + } + TrainTravelSection selectedSection = getSections().get(getSections().size() - 1); + for (TrainTravelSection section : getSections()) { + if (section.getScheduleIndex() > anyIndex) { + break; + } + selectedSection = section; + } + return selectedSection; + } + + public synchronized List getPredictions() { + return new ArrayList<>(predictionsByIndex.values()); + } + + public synchronized Map getPredictionsRaw() { + return new HashMap<>(predictionsByIndex); + } + + public synchronized List getPredictionsChronologically() { + return new ArrayList<>(predictionsChronologically); + } + + public synchronized Optional getNextStopPrediction() { + return predictionsChronologically.isEmpty() ? Optional.empty() : Optional.ofNullable(predictionsChronologically.get(0)); + } + + public void resetPredictions() { + predictionsByIndex.values().stream().forEach(x -> x.reset()); + lastSectionDelayOffset = 0; + refreshTimingsCounter = 0; + resetStatus(true); + isDynamic.clear(); + if (CreateRailwaysNavigator.isDebug()) CreateRailwaysNavigator.LOGGER.info(getTrainName() + " has reset their scheduled times."); + } + + public void hardResetPredictions() { + hardResetPredictions = true; + } + + public synchronized boolean isDelayed() { + return predictionsChronologically.stream().anyMatch(TrainPrediction::isAnyDelayed); + } + + public boolean isCurrentSectionDelayed() { + return isDelayed() && getHighestDeviation() - lastSectionDelayOffset > ModCommonConfig.SCHEDULE_DEVIATION_THRESHOLD.get(); + } + + public long getHighestDeviation() { + return predictionsByIndex.values().stream().mapToLong(x -> Math.max(x.getArrivalTimeDeviation(), x.getDepartureTimeDeviation())).max().orElse(0); + } + + public long getDeviationDelayOffset() { + return lastSectionDelayOffset; + } + + public TrainTravelSection getCurrentSection() { + return currentTravelSectionIndex < 0 || !hasCustomTravelSections() || !sectionsByIndex.containsKey(currentTravelSectionIndex) ? TrainTravelSection.def(this) : sectionsByIndex.get(currentTravelSectionIndex); + } + + public Map getWaitingForSignalsTime() { + return ImmutableMap.copyOf(delaysBySignal); + } + + public Set getStatus() { + return currentStatusInfos.stream().map(x -> TrainStatus.Registry.getRegisteredStatus().get(x).compile(this)).collect(Collectors.toSet()); + } + + public int debug_statusInfoCount() { + return currentStatusInfos.size(); + } + + private void resetStatus(boolean keepPreviousDelays) { + currentStatusInfos.clear(); + if (keepPreviousDelays && isDelayed()) { + currentStatusInfos.add(TrainStatus.DELAY_FROM_PREVIOUS_JOURNEY.getLocation()); + } + } + + public void applyStatus() { + if (isCancelled()) { + currentStatusInfos.clear(); + currentStatusInfos.add(TrainStatus.CANCELLED.getLocation()); + return; + } + + for (Entry x : TrainStatus.Registry.getRegisteredStatus().entrySet()) { + if (x.getValue().isTriggerd(this)) { + currentStatusInfos.add(x.getKey()); + } + } + + + if (currentStatusInfos.stream().noneMatch(x -> TrainStatus.Registry.getRegisteredStatus().get(x).getImportance() == TrainStatusType.DELAY && !x.equals(TrainStatus.DEFAULT_DELAY.getLocation())) && isCurrentSectionDelayed()) { + currentStatusInfos.add(TrainStatus.DEFAULT_DELAY.getLocation()); + } else { + currentStatusInfos.remove(TrainStatus.DEFAULT_DELAY.getLocation()); + } + } + + public boolean hasSectionChanged() { + return sectionChanged; + } + + /** + * Indicates whether there is enough data about this train and whether it has already been initialized. + * Trains that have not yet been initialized do not yet contain any reliable data to make any predictions. + */ + public boolean isInitialized() { + return !currentTransitTime.isEmpty() && + currentTransitTime.values().stream().noneMatch(x -> x < 0) + ; + } + + public int debug_initializedStationsCount() { + return (int)currentTransitTime.values().stream().filter(x -> x > 0).count(); + } + + /** + * Indicates whether any preparations need to be made before the initialization phase can begin. + * This is especially the case after starting the world, when the train was still in the middle of its journey. + */ + public boolean isPreparing() { + return !hasStarted; + } + + public synchronized TrainPrediction setPredictionData(int entryIndex, int currentIndex, int maxEntries, int stayDuration, int minStayDuration, int transitTime, TrainDeparturePrediction predictionData) { + // keep track of current schedule index + this.destinationChanged = destinationChanged || this.currentScheduleIndex != currentIndex; + this.currentScheduleIndex = currentIndex; + + // Update CRN predictions with data from Create + TrainPrediction pred = predictionsByIndex.computeIfAbsent(entryIndex, i -> new TrainPrediction(this, entryIndex, predictionData, stayDuration, minStayDuration)); + currentTransitTime.computeIfAbsent(entryIndex, x -> -1); + validPredictionEntries.add(entryIndex); + + pred.updateRealTime( + predictionData.destination, + predictionData.ticks + ); + predictionsChronologically.add(pred); + return pred; + } + + public void changeCurrentSection(int sectionEntryIndex) { + this.currentTravelSectionIndex = sectionsByIndex.containsKey(sectionEntryIndex) ? sectionEntryIndex : INVALID; + sectionChanged = true; + lastSectionDelayOffset = Math.max(0, getHighestDeviation()); + this.refreshTimingsCounter++; + } + + private void clearAll() { + predictionsByIndex.clear(); + sectionsByIndex.clear(); + defaultSection.clear(); + predictionsChronologically.clear(); + validPredictionEntries.clear(); + currentStatusInfos.clear(); + measuredTransitTimes.clear(); + transitTimeHistory.clear(); + currentTransitTime.clear(); + lastScheduleIndex = INVALID; + hasStarted = false; + } + + /** Called every ~5 seconds */ + public synchronized void refreshPre() { + if (train.runtime.paused) { + return; + } + + if (hardResetPredictions) { + hardResetPredictions = false; + clearAll(); + } + + validPredictionEntries.clear(); + predictionsChronologically.clear(); + } + + /** Called every ~5 seconds */ + public synchronized void refreshPost() { + + // [] Remove invalid prediction data + if (hasStarted && !train.runtime.paused) { + predictionsByIndex.keySet().retainAll(validPredictionEntries); + measuredTransitTimes.keySet().retainAll(validPredictionEntries); + transitTimeHistory.keySet().retainAll(validPredictionEntries); + currentTransitTime.keySet().retainAll(validPredictionEntries); + } + + // [] Called after the schedule index has been changed. + if (lastScheduleIndex >= 0 && lastScheduleIndex != currentScheduleIndex && predictionsByIndex.containsKey(lastScheduleIndex)) { + predictionsByIndex.get(lastScheduleIndex).nextCycle(); + } + if (!hasCustomTravelSections() && lastScheduleIndex > currentScheduleIndex) { // Manually call section change event if there are no sections defined. + changeCurrentSection(currentTravelSectionIndex); + } + lastScheduleIndex = currentScheduleIndex; + + // [] train cancelled manager + boolean isNowCancelled = !(TrainUtils.isTrainValid(train) && isInitialized()) || train.runtime.paused; + if (this.cancelled && !isNowCancelled) { // Train should no longer be cancelled -> restart + hasStarted = false; + initializationCompleted = false; + initializationFinishTask = false; + sessionId = UUID.randomUUID(); + resetPredictions(); + } + this.cancelled = isNowCancelled; + + applyStatus(); + + if (destinationChanged) { + destinationChanged = false; + notifyListeners(EVENT_DESTINATION_CHANGED, this); + } + + if (initializationFinishTask) { + initializationFinishTask = false; + onInitialize(); + } + } + + /** Called every tick */ + public void tick() { + if (train.runtime.paused) { + return; + } + + if (!isAtStation()) { + transitTime++; // CRN transit time measurement + } + + // Waiting for signal processor + boolean isWaitingForSignal = train.navigation.waitingForSignal != null; + if (wasWaitingForSignal != isWaitingForSignal) { // The moment in which the state has been changed + if (isWaitingForSignal) { // currently waiting + waitingForSignalId = train.navigation.waitingForSignal.getFirst(); + occupyingTrains.clear(); + occupyingTrains.addAll(TrainUtils.isSignalOccupied(waitingForSignalId, Set.of(train.id))); + } else { // no longer waiting + delaysBySignal.put(waitingForSignalId, waitingForSignalTicks); + waitingForSignalTicks = 0; + occupyingTrains.clear(); + } + } + + if (isWaitingForSignal) { + waitingForSignalTicks++; + } + + this.wasWaitingForSignal = isWaitingForSignal; + } + + public void updateTotalDuration() { + // measuredTransitTimes + int newDuration = currentTransitTime.values().stream().mapToInt(x -> x).sum() + getPredictions().stream().mapToInt(x -> x.getStayDuration()).sum(); + int oldTotalDuration = this.totalDuration; + if (CRNEventsManager.isRegistered(TotalDurationTimeChangedEvent.class) && this.totalDuration > 0 && this.totalDuration != newDuration) { + CRNEventsManager.getEvent(TotalDurationTimeChangedEvent.class).run(train, this.totalDuration, newDuration); + } + this.totalDuration = newDuration; + if (oldTotalDuration != INVALID) { + notifyListeners(EVENT_TOTAL_DURATION_CHANGED, this); + } + resetPredictions(); + } + + /** + * Called when the train reaches a station. + * @param createTicksInTransit Ticks measured by Create. + */ + public void reachDestination(long destinationReachTime, int createTicksInTransit) { + this.destinationReachTime = destinationReachTime; + + if (hasStarted) { + processTransitHistory(transitTimeHistory.computeIfAbsent(currentScheduleIndex, x -> new PriorityQueue<>())); + this.measuredTransitTimes.put(currentScheduleIndex, ModCommonConfig.CUSTOM_TRANSIT_TIME_CALCULATION.get() ? createTicksInTransit : transitTime); + } + this.transitTime = 0; + this.waitingForSignalTicks = 0; + this.waitingForSignalId = null; + this.delaysBySignal.clear(); + this.hasStarted = true; + this.isAtStation = true; + + if (!initializationCompleted && isInitialized()) { + initializationCompleted = true; + initializationFinishTask = true; + } + + if (sectionChanged) { + sectionChanged = false; + if (!isDynamic() || (ModCommonConfig.AUTO_RESET_TIMINGS.get() > 0 && refreshTimingsCounter >= ModCommonConfig.AUTO_RESET_TIMINGS.get())) { + resetPredictions(); + } else { + resetStatus(true); + } + notifyListeners(EVENT_SECTION_CHANGED, this); + } + + notifyListeners(EVENT_STATION_REACHED, this); + } + + public void leaveDestination() { + this.currentScheduleIndex = getTrain().runtime.currentEntry; + this.isAtStation = false; + } + + public void onInitialize() { + updateTotalDuration(); + } + + /** Checks and calculates a new total duration time if necessary. */ + private void processTransitHistory(Queue history) { + // First initialization + if (!currentTransitTime.containsKey(currentScheduleIndex) || currentTransitTime.get(currentScheduleIndex) < 0) { + fillHistory(history, transitTime); + currentTransitTime.put(currentScheduleIndex, transitTime); // Set initial reference transit time + } + + // remove excess elements + while (history.size() >= getHistoryBufferSize()) { + history.poll(); + } + history.offer(transitTime); // add current transit time to the history + + int refCurrentTransitTime = currentTransitTime.get(currentScheduleIndex); + double median = ModUtils.calculateMedian(history, ModCommonConfig.TOTAL_DURATION_DEVIATION_THRESHOLD.get(), x -> true); + + if (Math.abs(refCurrentTransitTime - median) > ModCommonConfig.TOTAL_DURATION_DEVIATION_THRESHOLD.get()) { // Deviation is too large -> change transit time for this section + int newValue = ModUtils.calculateMedian(history, ModCommonConfig.TOTAL_DURATION_DEVIATION_THRESHOLD.get(), x -> Math.abs(refCurrentTransitTime - x) > ModCommonConfig.TOTAL_DURATION_DEVIATION_THRESHOLD.get()); + currentTransitTime.put(currentScheduleIndex, newValue); // save transit time for this section + fillHistory(history, newValue); // Reset the history + updateTotalDuration(); + } else if (Math.abs(refCurrentTransitTime - transitTime) < ModCommonConfig.TOTAL_DURATION_DEVIATION_THRESHOLD.get()) { // new value is smaller than current -> reset history (no changes needed) + fillHistory(history, refCurrentTransitTime); + } + } + + private void fillHistory(Queue history, int value) { + history.clear(); + for (int i = 0; i < getHistoryBufferSize(); i++) { + history.add(value); + } + } + + @Override + public Map>> getListeners() { + return listeners; + } + + public CompoundTag toNbt() { + CompoundTag nbt = new CompoundTag(); + nbt.putInt(NBT_VERSION, VERSION); + + CompoundTag predictions = new CompoundTag(); + for (Entry entry : predictionsByIndex.entrySet()) { + predictions.put(String.valueOf(entry.getKey()), entry.getValue().toNbt()); + } + + CompoundTag transitTimes = new CompoundTag(); + for (Entry entry : this.currentTransitTime.entrySet()) { + transitTimes.putInt(String.valueOf(entry.getKey()), entry.getValue()); + } + + nbt.putUUID(NBT_ID, getSessionId()); + nbt.putUUID(NBT_TRAIN_ID, getTrainId()); + nbt.put(NBT_PREDICTIONS, predictions); + nbt.put(NBT_TRANSIT_TIMES, transitTimes); + nbt.putInt(NBT_CURRENT_SCHEDULE_INDEX, currentScheduleIndex); + nbt.putLong(NBT_LAST_DELAY_OFFSET, lastSectionDelayOffset); + nbt.putBoolean(NBT_CANCELLED, cancelled); + nbt.putString(NBT_LINE_ID, lineId == null ? "" : lineId); + return nbt; + } + + public static TrainData fromNbt(CompoundTag nbt) { + UUID trainId = nbt.getUUID(NBT_TRAIN_ID); + UUID sessionId = nbt.getUUID(NBT_ID); + TrainData data = new TrainData(TrainUtils.getTrain(trainId).get(), sessionId); // TODO + data.deserializeNbt(nbt); + return data; + } + + protected void deserializeNbt(CompoundTag nbt) { + CompoundTag predictions = nbt.getCompound(NBT_PREDICTIONS); + for (String key : predictions.getAllKeys()) { + try { + int idx = Integer.parseInt(key); + this.predictionsByIndex.put(idx, TrainPrediction.fromNbt(this, predictions.getCompound(key))); + } catch (Exception e) { + CreateRailwaysNavigator.LOGGER.warn("Unable to load prediction with index '" + key + "': The value is not an integer.", e); + } + } + + CompoundTag transitTimes = nbt.getCompound(NBT_TRANSIT_TIMES); + for (String key : transitTimes.getAllKeys()) { + try { + int idx = Integer.parseInt(key); + int time = transitTimes.getInt(key); + if (time > 0) { + fillHistory(transitTimeHistory.computeIfAbsent(idx, x -> new PriorityQueue<>()), time); + this.measuredTransitTimes.put(idx, time); + this.currentTransitTime.put(idx, time); + } + } catch (Exception e) { + CreateRailwaysNavigator.LOGGER.warn("Unable to load transit time with index '" + key + "': The value is not an integer.", e); + } + } + + this.currentScheduleIndex = nbt.getInt(NBT_CURRENT_SCHEDULE_INDEX); + this.lastScheduleIndex = currentScheduleIndex; + this.currentTravelSectionIndex = getSectionForIndex(currentScheduleIndex).getScheduleIndex(); + this.lineId = nbt.getString(NBT_LINE_ID); + this.lastSectionDelayOffset = nbt.getLong(NBT_LAST_DELAY_OFFSET); + this.cancelled = nbt.getBoolean(NBT_CANCELLED); + } + + public synchronized void shiftTime(long l) { + this.destinationReachTime += l; + predictionsByIndex.values().forEach(x -> x.shiftTime(l)); + } +} diff --git a/common/src/main/java/de/mrjulsen/crn/data/train/TrainListener.java b/common/src/main/java/de/mrjulsen/crn/data/train/TrainListener.java new file mode 100644 index 00000000..54da4195 --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/data/train/TrainListener.java @@ -0,0 +1,225 @@ +package de.mrjulsen.crn.data.train; + +import java.io.File; +import java.io.IOException; +import java.util.UUID; +import java.util.Queue; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; +import java.util.Set; + +import com.simibubi.create.content.trains.entity.Train; +import de.mrjulsen.crn.CreateRailwaysNavigator; +import de.mrjulsen.crn.data.storage.GlobalSettings; +import de.mrjulsen.crn.event.CRNEventsManager; +import de.mrjulsen.crn.event.ModCommonEvents; +import de.mrjulsen.crn.event.events.CreateTrainPredictionEvent; +import de.mrjulsen.crn.event.events.GlobalTrainDisplayDataRefreshEventPost; +import de.mrjulsen.crn.event.events.GlobalTrainDisplayDataRefreshEventPre; +import de.mrjulsen.crn.event.events.ScheduleResetEvent; +import de.mrjulsen.crn.event.events.SubmitTrainPredictionsEvent; +import de.mrjulsen.crn.event.events.TotalDurationTimeChangedEvent; +import de.mrjulsen.crn.event.events.TrainArrivalAndDepartureEvent; +import de.mrjulsen.crn.event.events.TrainDestinationChangedEvent; +import de.mrjulsen.crn.mixin.ScheduleRuntimeAccessor; +import de.mrjulsen.mcdragonlib.DragonLib; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.NbtIo; +import net.minecraft.world.level.storage.LevelResource; + +/** Monitors all trains in the world and processes their data and information to make it available for use. */ +public final class TrainListener { + + private transient static final String FILENAME = CreateRailwaysNavigator.MOD_ID + "_train_data.nbt"; + + public static final ConcurrentHashMap data = new ConcurrentHashMap<>(); + + private transient static boolean trainDataListenerActive = false; + private transient static long currentTrainDataListenerId = 0L; + private transient static final Queue trainDataHookTasks = new ConcurrentLinkedQueue<>(); + + + public static void init() { + // Register Event Listeners + CRNEventsManager.getEvent(GlobalTrainDisplayDataRefreshEventPre.class).register(CreateRailwaysNavigator.MOD_ID, () -> { + queueTrainListenerTask(TrainListener::refreshPre); + }); + + CRNEventsManager.getEvent(GlobalTrainDisplayDataRefreshEventPost.class).register(CreateRailwaysNavigator.MOD_ID, () -> { + queueTrainListenerTask(TrainListener::refreshPost); + }); + + CRNEventsManager.getEvent(TrainDestinationChangedEvent.class).register(CreateRailwaysNavigator.MOD_ID, (train, current, next, nextIndex) -> { + }); + + CRNEventsManager.getEvent(TotalDurationTimeChangedEvent.class).register(CreateRailwaysNavigator.MOD_ID, (train, old, newDuration) -> { + CreateRailwaysNavigator.LOGGER.warn("The total duration of the train " + train.name.getString() + " (" + train.id + ") has changed from " + old + " Ticks to " + newDuration + " Ticks. This will result in changes to the scheduled departure times!"); + }); + + CRNEventsManager.getEvent(TrainArrivalAndDepartureEvent.class).register(CreateRailwaysNavigator.MOD_ID, (train, station, isArrival) -> { + queueTrainListenerTask(() -> { + if (data.containsKey(train.id)) { + if (isArrival) { + data.get(train.id).reachDestination(DragonLib.getCurrentWorldTime(), ((ScheduleRuntimeAccessor)train.runtime).crn$getTicksInTransit()); + } else { + data.get(train.id).leaveDestination(); + } + } + }); + }); + + CRNEventsManager.getEvent(ScheduleResetEvent.class).register(CreateRailwaysNavigator.MOD_ID, (train, soft) -> { + queueTrainListenerTask(() -> { + if (data.containsKey(train.id)) { + TrainData trainData = data.get(train.id); + if (soft) { + trainData.resetPredictions(); + } else { + trainData.hardResetPredictions(); + } + } + }); + }); + + CRNEventsManager.getEvent(SubmitTrainPredictionsEvent.class).register(CreateRailwaysNavigator.MOD_ID, (train, predictions, entryCount, accumulatedTime, current) -> { + + }); + + CRNEventsManager.getEvent(CreateTrainPredictionEvent.class).register(CreateRailwaysNavigator.MOD_ID, (train, schedule, predictables, index, stayDuration, minStayDuration, prediction) -> { + queueTrainListenerTask(() -> { + ScheduleRuntimeAccessor accessor = (ScheduleRuntimeAccessor)(Object)schedule; + UUID trainId = accessor.crn$getTrain().id; + if (data.containsKey(trainId) && prediction != null) { + TrainData trainData = data.get(trainId); + TrainPrediction pred = trainData.setPredictionData(index, schedule.currentEntry, schedule.getSchedule().entries.size(), stayDuration, minStayDuration, accessor.crn$predictionTicks().get(index), prediction); + predictables.values().forEach(x -> x.predictForStation(trainData, pred, schedule, index, accessor.crn$getTrain())); + } + }); + }); + } + + public static Set getAllTrains() { + return data.values().stream().map(x -> x.getTrain()).collect(Collectors.toSet()); + } + + public static boolean allTrainsInitialized() { + return data.values().stream().filter(x -> + !GlobalSettings.getInstance().isTrainBlacklisted(x.getTrain()) && + !x.getPredictionsRaw().isEmpty() && + !x.getTrain().runtime.paused && + !x.getTrain().derailed && + !x.getTrain().runtime.completed && + TrainUtils.isTrainValid(x.getTrain()) + ).allMatch(x -> x.isInitialized() && !x.isPreparing()); + } + + public static void start() { + new Thread(() -> { + init(); + long id; + do { + id = System.nanoTime(); + } while (currentTrainDataListenerId == id); + + currentTrainDataListenerId = id; + trainDataListenerActive = true; + trainDataHookTasks.clear(); + TrainListener.data.clear(); + try { + TrainListener.load(); + } catch (Exception e) { + CreateRailwaysNavigator.LOGGER.error("Unable to load train listener data.", e); + } + + final long threadId = id; + new Thread(() -> { + try { + while (currentTrainDataListenerId == threadId && trainDataListenerActive) { + while (!trainDataHookTasks.isEmpty()) { + try { + trainDataHookTasks.poll().run(); + } catch (Exception e) { + CreateRailwaysNavigator.LOGGER.error("Error while executing train listener task.", e); + } + } + + try { + TimeUnit.SECONDS.sleep(1); + } catch (InterruptedException e) { + CreateRailwaysNavigator.LOGGER.error("Error while waiting for next task.", e); + } + } + save(); + TrainListener.data.clear(); + trainDataHookTasks.clear(); + CreateRailwaysNavigator.LOGGER.info("Train listener has been stopped."); + } catch (Exception e) { + CreateRailwaysNavigator.LOGGER.error("Error while executing Train Listener.", e); + } + }, "CRN Train Listener").start(); + CreateRailwaysNavigator.LOGGER.info("Train listener has been started."); + }, "CRN Train Listener Launcher").start(); + } + + public static void stop() { + trainDataListenerActive = false; + CreateRailwaysNavigator.LOGGER.info("Stopping train listener..."); + } + + public static synchronized void save() { + if (!trainDataListenerActive) { + return; + } + + CompoundTag nbt = new CompoundTag(); + data.entrySet().forEach(x -> nbt.put(x.getKey().toString(), x.getValue().toNbt())); + + try { + NbtIo.writeCompressed(nbt, new File(ModCommonEvents.getCurrentServer().get().getWorldPath(new LevelResource("data/" + FILENAME)).toString())); + CreateRailwaysNavigator.LOGGER.debug("Saved train listener data."); + } catch (IOException e) { + CreateRailwaysNavigator.LOGGER.error("Unable to save train listener data.", e); + } + } + + private static void load() throws IOException { + File settingsFile = new File(ModCommonEvents.getCurrentServer().get().getWorldPath(new LevelResource("data/" + FILENAME)).toString()); + if (!settingsFile.exists()) { + return; + } + CompoundTag nbt = NbtIo.readCompressed(settingsFile); + for (String key : nbt.getAllKeys()) { + try { + UUID id = UUID.fromString(key); + data.put(id, TrainData.fromNbt(nbt.getCompound(key))); + } catch (Exception e) { + CreateRailwaysNavigator.LOGGER.warn("Unable to read train listener train data with ID '" + key + "'.", e); + } + } + } + + private static void queueTrainListenerTask(Runnable task) { + trainDataHookTasks.add(task); + } + + public synchronized static void refreshPre() { + if (!trainDataListenerActive) return; + Set trains = TrainUtils.getTrains(true); + data.keySet().retainAll(trains.stream().filter(x -> !GlobalSettings.getInstance().isTrainBlacklisted(x)).map(x -> x.id).toList()); + trains.forEach(x -> { + data.computeIfAbsent(x.id, a -> TrainData.of(x)).refreshPre(); + }); + } + + public synchronized static void refreshPost() { + if (!trainDataListenerActive) return; + data.values().forEach(x -> x.refreshPost()); + } + + public synchronized static void tick() { + if (!trainDataListenerActive) return; + data.values().forEach(x -> x.tick()); + } +} diff --git a/common/src/main/java/de/mrjulsen/crn/data/train/TrainPrediction.java b/common/src/main/java/de/mrjulsen/crn/data/train/TrainPrediction.java new file mode 100644 index 00000000..5ebd35bb --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/data/train/TrainPrediction.java @@ -0,0 +1,509 @@ +package de.mrjulsen.crn.data.train; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import com.google.common.base.Objects; +import com.simibubi.create.content.trains.display.GlobalTrainDisplayData.TrainDeparturePrediction; + +import de.mrjulsen.crn.CreateRailwaysNavigator; +import de.mrjulsen.crn.config.ModCommonConfig; +import de.mrjulsen.crn.data.StationTag; +import de.mrjulsen.crn.data.storage.GlobalSettings; +import de.mrjulsen.crn.event.ModCommonEvents; +import de.mrjulsen.crn.exceptions.RuntimeSideException; +import de.mrjulsen.mcdragonlib.DragonLib; +import de.mrjulsen.mcdragonlib.data.Cache; +import de.mrjulsen.mcdragonlib.util.TextUtils; +import net.minecraft.ChatFormatting; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.ListTag; +import net.minecraft.nbt.StringTag; +import net.minecraft.nbt.Tag; +import net.minecraft.network.chat.Component; + +/** Data about one single station of a train. */ +public class TrainPrediction implements Comparable { + + private static final String NBT_ENTRY_INDEX = "EntryIndex"; + private static final String NBT_STATION_NAME = "StationName"; + private static final String NBT_TITLE = "Title"; + private static final String NBT_SCHEDULED_TICKS = "ScheduledTicks"; + private static final String NBT_SCHEDULED_REFRESH_TIME = "ScheduledRefreshTime"; + private static final String NBT_REAL_TIME_TICKS = "RealTimeTicks"; + private static final String NBT_REAL_TIME_REFRESH_TIME = "RealTimeRefreshTime"; + private static final String NBT_CURRENT_TICKS_CORRECTION = "CurrentTicksCorrection"; + private static final String NBT_STOPOVERS = "Stopovers"; + private static final String NBT_CYCLE = "Cycle"; + private static final String NBT_CYCLE_TIME = "CycleTime"; + private static final String NBT_STAY_DURATION = "StayDuration"; + private static final String NBT_MIN_STAY_DURATION = "MinStayDuration"; + + private transient final TrainData data; + + private final int entryIndex; + private final String title; + private String stationName; + + private int scheduledTicks; + private long scheduleRefreshTime; + private int realTimeTicks; + private long realTimeRefreshTime; + + private long arrivalTicksCorrection; + private long departureTicksCorrection; + private List stopovers = new ArrayList<>(); + private int cycle; + private long cycleTime; + private final int stayDuration; + private final int minStayDuration; + + // History + private long previousScheduledArrivalTime; + private long previousScheduledDepartureTime; + private long previousRealTimeArrivalTime; + private long previousRealTimeDepartureTime; + + private final Cache isCustomTitle = new Cache<>(() -> { + if (this.getData().getPredictionsChronologically().isEmpty()) { + return false; + } + TrainPrediction nextPrediction = this.getData().getPredictionsChronologically().get((this.getData().getPredictionsChronologically().indexOf(this) + 1) % this.getData().getPredictionsChronologically().size()); + return !getTitle().matches(nextPrediction.getStationName()); + }); + private final Cache isLastStopOfSection = new Cache<>(() -> { + TrainTravelSection section = getSection(); + return section.isFinalStop(this); + }); + private final Cache section; + + private TrainPrediction(TrainData data, int entryIndex, String stationName, String title, int ticks, int stayDuration, int minStayDuration) { + this.entryIndex = entryIndex; + this.data = data; + this.title = title; + this.stayDuration = stayDuration; + this.minStayDuration = minStayDuration; + this.stationName = stationName; + this.realTimeTicks = ticks; + this.realTimeRefreshTime = DragonLib.getCurrentWorldTime(); + this.section = new Cache<>(() -> data.getSectionForIndex(entryIndex)); + + reset(); + } + + public TrainPrediction(TrainData data, int entryIndex, TrainDeparturePrediction prediction, int stayDuration, int minStayDuration) { + this(data, entryIndex, prediction.destination, prediction.scheduleTitle.getString(), prediction.ticks, stayDuration, minStayDuration); + } + + public static TrainPrediction unpredictable(TrainData data) { + CreateRailwaysNavigator.LOGGER.warn("Train " + data.getTrain().name.getString() + " (" + data.getTrain().id + ") is unpredictable!"); + return new TrainPrediction(data, -1, "", "", 0, 0, 0); + } + + /** Resets the scheduled time to the current real time. Called when the total duration changes to prevent deviations. */ + public void reset() { + this.cycleTime = 0; + this.departureTicksCorrection = 0; + this.arrivalTicksCorrection = 0; + this.scheduledTicks = realTimeTicks; + this.scheduleRefreshTime = realTimeRefreshTime; + } + + /** General data about the train. */ + public TrainData getData() { + return data; + } + + /** The index of this entry in the train schedule. */ + public int getEntryIndex() { + return entryIndex; + } + + /** The name of the station. */ + public String getStationName() { + return stationName; + } + + /** The title, the train has when arriving at this station. */ + public String getTitle() { + return title; + } + + public boolean hasCustomTitle() { + return isCustomTitle.get(); + } + + /** The scheduled time the train will stay at this station. */ + public int getStayDuration() { + return stayDuration; + } + + public int getMinStayDuration() { + return minStayDuration; + } + + public List getStopovers() { + return stopovers; + } + + /** The world time when the scheduled time was calculated. */ + public long getScheduleRefreshTime() { + return scheduleRefreshTime; + } + + + + + /** The time in ticks of all cycles that have elapsed since the last update. */ + public long getCycleTime() { + return cycleTime; + } + + /** The scheduled time until the train stops here. */ + public int getScheduledArrivalTicks() { + return scheduledTicks; + } + + /** The scheduled world time when the train arrives at this station. */ + public long getScheduledArrivalTime() { + return getScheduleRefreshTime() + (long)getScheduledArrivalTicks() + getCycleTime(); + } + + + + + /** The world time when the real time data was last refreshed. */ + public long getRealTimeRefreshTime() { + return realTimeRefreshTime; + } + + /** The current time until the train stops here. */ + public int getRealTimeArrivalTicks() { + return realTimeTicks; + } + + private long getRealTimeArrivalTimeRaw() { + return getRealTimeRefreshTime() + getRealTimeArrivalTicks(); + } + + /** The actual deviation from real time and schedule time. Cycles are not taken into account! */ + private long getArrivalTimeRawDeviation() { + return getRealTimeArrivalTimeRaw() - getScheduledArrivalTime(); + } + + + /** The current world time the train will arrive at this station. */ + public long getRealTimeArrivalTime() { + return getRealTimeArrivalTimeRaw() - arrivalTicksCorrection; + } + + /** The actual deviation from real time and schedule time. */ + public long getArrivalTimeDeviation() { + return getArrivalTimeRawDeviation() - arrivalTicksCorrection; + } + + + + + + + + + + + + + /** The departure time from this stop when the schedule was updated. */ + public int getScheduledDepartureTicks() { + return getScheduledArrivalTicks() + getStayDuration(); + } + + /** The scheduled world time when the train departs from this station. */ + public long getScheduledDepartureTime() { + return getScheduledArrivalTime() + getStayDuration(); + } + + /** The current world time at which the train will depart. */ + public long getRealTimeDepartureTime() { + return getRealTimeArrivalTime() + getStayDuration() - departureTicksCorrection;// - Math.min(getBufferTime(), getDeviationArrivalTime()); + } + + /** The deviation of the departure time from the schedule. */ + public long getDepartureTimeDeviation() { + return getArrivalTimeDeviation() - departureTicksCorrection; + } + + + + + /** The time it took the train to get here from the last station. */ + public int getLastTransitTime() { + return data.getTransitTicks(); + } + + public long getBufferTime() { + return Math.max(getStayDuration() - getMinStayDuration(), 0); + } + + /** The remaining buffer time that the train can use to catch up for delays. */ + public long getBufferTimeLeft() { + return getBufferTime() - data.waitingAtStationTicks(); + } + + public long getScheduledArrivalDay() { + return getScheduledArrivalTime() / DragonLib.TICKS_PER_DAY; + } + + public long getScheduledDepartureDay() { + return getScheduledDepartureDay() / DragonLib.TICKS_PER_DAY; + } + + public long getRealTimeArrivalDay() { + return getRealTimeArrivalTime() / DragonLib.TICKS_PER_DAY; + } + + public long getRealTimeDepartureDay() { + return getRealTimeDepartureTime() / DragonLib.TICKS_PER_DAY; + } + + public void setStopovers(List stopovers) { + this.stopovers = stopovers; + } + + + /** Change this stop to the next cycle. */ + public void nextCycle() { + this.previousScheduledArrivalTime = getScheduledArrivalTime(); + this.previousScheduledDepartureTime = getScheduledDepartureTime(); + this.previousRealTimeArrivalTime = getRealTimeArrivalTime(); + this.previousRealTimeDepartureTime = getRealTimeDepartureTime(); + + cycle++; + this.cycleTime += data.getTotalDuration(); + } + + /** The cycle the train is currently in. */ + public int getCurrentCycle() { + return cycle; + } + + + public long getPreviousScheduledArrivalTime() { + return previousScheduledArrivalTime; + } + + public long getPreviousScheduledDepartureTime() { + return previousScheduledDepartureTime; + } + + public long getPreviousRealTimeArrivalTime() { + return previousRealTimeArrivalTime; + } + + public long getPreviousRealTimeDepartureTime() { + return previousRealTimeDepartureTime; + } + + + + /** Calculates in which cycle the train will be when it arrives back here in the specified time.*/ + public int estimateCycleIn(int ticks) { + return getCurrentCycle() + ticks / data.getTotalDuration(); + } + + /** Time since start of recording. */ + public long getRuntime() { + return DragonLib.getCurrentWorldTime() - getScheduleRefreshTime(); + } + + public boolean hasDepartedOnce() { + return getCurrentCycle() > 0; + } + + public boolean isArrivalDelayed() { + return getRealTimeArrivalTime() - ModCommonConfig.SCHEDULE_DEVIATION_THRESHOLD.get() > getScheduledArrivalTime(); + } + + public boolean isDepartureDelayed() { + return getRealTimeDepartureTime() - ModCommonConfig.SCHEDULE_DEVIATION_THRESHOLD.get() > getScheduledDepartureTime(); + } + + public boolean isAnyDelayed() { + return isArrivalDelayed() || isDepartureDelayed(); + } + + /** + * Get the station tag for this station. Server-side only! + * @return The StationTag for this stop. + * @throws RuntimeSideException Thrown when called on the wrong logical side. + */ + public StationTag getStationTag() throws RuntimeSideException { + if (!ModCommonEvents.hasServer()) { + throw new RuntimeSideException(false); + } + return GlobalSettings.getInstance().getOrCreateStationTagFor(stationName); + } + + public TrainTravelSection getSection() { + TrainTravelSection sec = section.get(); + if (sec.isDefault()) { + section.clear(); + } + return sec; + } + + public String getSectionDestinationText() { + TrainTravelSection sec = section.get(); + if (sec.isDefault()) { + section.clear(); + } + return isLastStopOfSection.get() ? sec.nextSection().getDisplayText() : sec.getDisplayText(); + } + + + + public void updateRealTime(String stationName, int realTimeTicks) { + isCustomTitle.clear(); + this.stationName = stationName == null ? this.stationName : stationName; + this.realTimeRefreshTime = DragonLib.getCurrentWorldTime(); + this.realTimeTicks = realTimeTicks; + + List prevPreds = data.getPredictionsChronologically(); + Optional currentPrediction = data.getNextStopPrediction(); + this.arrivalTicksCorrection = 0; + this.departureTicksCorrection = 0; + + if (data.isWaitingAtStation() && data.getCurrentScheduleIndex() == getEntryIndex()) { + this.arrivalTicksCorrection = Math.min(data.waitingAtStationTicks(), getStayDuration()); + //this.arrivalTicksCorrection = data.waitingAtStationTicks(); + } + + if (currentPrediction.isPresent()) { + this.arrivalTicksCorrection = currentPrediction.get().arrivalTicksCorrection; + } + + long tempDepartureCorrection = getBufferTime(); + long tempArrivalCorrection = 0;//getBufferTime(); + for (int i = 0; i < prevPreds.size(); i++) { + final TrainPrediction pred = prevPreds.get(i); + //tempArrivalCorrection += getBufferTime(); + tempArrivalCorrection += pred.getBufferTime(); + if (pred == this) break; + } + this.arrivalTicksCorrection += Math.min(tempArrivalCorrection, getArrivalTimeDeviation()); + this.departureTicksCorrection += Math.min(tempDepartureCorrection, getArrivalTimeDeviation()); + } + + @Override + public boolean equals(Object obj) { + return + obj instanceof TrainPrediction o && + scheduledTicks == o.scheduledTicks && + scheduleRefreshTime == o.scheduleRefreshTime && + entryIndex == o.entryIndex && + stationName.equals(o.stationName) + ; + } + + @Override + public int hashCode() { + return Objects.hashCode(scheduledTicks, scheduleRefreshTime, entryIndex, stationName); + } + + public boolean similarTo(Object obj) { + return + obj instanceof TrainPrediction o && + stationName.equals(o.stationName) && + entryIndex == o.entryIndex + ; + } + + @Override + public String toString() { + return formattedText().getString(); + } + + public CompoundTag toNbt() { + CompoundTag nbt = new CompoundTag(); + + ListTag stopovers = new ListTag(); + stopovers.addAll(this.stopovers.stream().map(x -> StringTag.valueOf(x)).toList()); + + nbt.putInt(NBT_ENTRY_INDEX, entryIndex); + nbt.putString(NBT_STATION_NAME, stationName == null ? "" : stationName); + nbt.putString(NBT_TITLE, title == null ? "" : title); + nbt.putInt(NBT_SCHEDULED_TICKS, scheduledTicks); + nbt.putLong(NBT_SCHEDULED_REFRESH_TIME, scheduleRefreshTime); + nbt.putInt(NBT_REAL_TIME_TICKS, realTimeTicks); + nbt.putLong(NBT_REAL_TIME_REFRESH_TIME, realTimeRefreshTime); + nbt.putLong(NBT_CURRENT_TICKS_CORRECTION, departureTicksCorrection); + nbt.put(NBT_STOPOVERS, stopovers); + nbt.putInt(NBT_CYCLE, cycle); + nbt.putLong(NBT_CYCLE_TIME, cycleTime); + nbt.putInt(NBT_STAY_DURATION, stayDuration); + nbt.putInt(NBT_MIN_STAY_DURATION, minStayDuration); + return nbt; + } + + public static TrainPrediction fromNbt(TrainData data, CompoundTag nbt) { + TrainPrediction pred = new TrainPrediction( + data, + nbt.getInt(NBT_ENTRY_INDEX), + nbt.getString(NBT_STATION_NAME), + nbt.getString(NBT_TITLE), + nbt.getInt(NBT_SCHEDULED_TICKS), + nbt.getInt(NBT_STAY_DURATION), + nbt.getInt(NBT_MIN_STAY_DURATION) + ); + pred.deserializeNbt(nbt); + return pred; + } + + protected void deserializeNbt(CompoundTag nbt) { + this.scheduledTicks = nbt.getInt(NBT_SCHEDULED_TICKS); + this.scheduleRefreshTime = nbt.getLong(NBT_SCHEDULED_REFRESH_TIME); + this.realTimeTicks = nbt.getInt(NBT_REAL_TIME_TICKS); + this.realTimeRefreshTime = nbt.getLong(NBT_REAL_TIME_REFRESH_TIME); + this.departureTicksCorrection = nbt.getLong(NBT_CURRENT_TICKS_CORRECTION); + this.stopovers = new ArrayList<>(nbt.getList(NBT_STOPOVERS, Tag.TAG_STRING).stream().map(x -> x.getAsString()).toList()); + this.cycle = nbt.getInt(NBT_CYCLE); + this.cycleTime = nbt.getLong(NBT_CYCLE_TIME); + } + + /** + * DEBUG ONLY! + */ + public Component formattedText() { + return TextUtils.text("[ " + entryIndex + " ]: ").withStyle(ChatFormatting.WHITE) + .append(TextUtils.text(stationName).withStyle(ChatFormatting.WHITE)) + .append(TextUtils.text(", ").withStyle(ChatFormatting.WHITE)) + .append(TextUtils.text("*" + getCurrentCycle()).withStyle(ChatFormatting.YELLOW)) + .append(TextUtils.text(", ").withStyle(ChatFormatting.WHITE)) + .append(TextUtils.text("CT: " + getRealTimeArrivalTime()).withStyle(ChatFormatting.GREEN)) + .append(TextUtils.text(", ").withStyle(ChatFormatting.WHITE)) + .append(TextUtils.text("D: " + (getArrivalTimeDeviation() + " / " + getDepartureTimeDeviation())).withStyle(ChatFormatting.GOLD)) + .append(TextUtils.text(", ").withStyle(ChatFormatting.WHITE)) + .append(TextUtils.text("B: " + (departureTicksCorrection)).withStyle(ChatFormatting.DARK_GREEN)) + .append(TextUtils.text(", ").withStyle(ChatFormatting.WHITE)) + .append(TextUtils.text("P: " + (getScheduledArrivalTime())).withStyle(ChatFormatting.BLUE)) + .append(TextUtils.text(", ").withStyle(ChatFormatting.WHITE)) + .append(TextUtils.text("S: " + getStayDuration()).withStyle(ChatFormatting.AQUA)) + .append(TextUtils.text(", ").withStyle(ChatFormatting.WHITE)) + .append(TextUtils.text("Tr: " + getLastTransitTime()).withStyle(ChatFormatting.DARK_RED)) + .append(TextUtils.text(", ").withStyle(ChatFormatting.WHITE)) + .append(TextUtils.text("T: " + getSection()).withStyle(ChatFormatting.RED)) + .append(TextUtils.text(", ").withStyle(ChatFormatting.WHITE)) + .append(TextUtils.text("Ti: " + title).withStyle(ChatFormatting.LIGHT_PURPLE)) + ; + } + + public void shiftTime(long l) { + this.scheduleRefreshTime += l; + this.realTimeRefreshTime += l; + } + + @Override + public int compareTo(TrainPrediction o) { + return Long.compare(getScheduledArrivalTime(), o.getScheduledArrivalTime()); + } +} diff --git a/common/src/main/java/de/mrjulsen/crn/data/train/TrainState.java b/common/src/main/java/de/mrjulsen/crn/data/train/TrainState.java new file mode 100644 index 00000000..76ebb0f1 --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/data/train/TrainState.java @@ -0,0 +1,35 @@ +package de.mrjulsen.crn.data.train; + +import java.util.Arrays; + +/** The status of the train measured at the current station. */ +public enum TrainState { + /** The train will arrive at this station in the future. */ + BEFORE((byte)-2, '-'), + /** The next stop was announced. */ + ANNOUNCED((byte)-1, '.'), + /** The train is waiting at this station. */ + STAYING((byte)0, '~'), + /** The train has already departed from this station. */ + AFTER((byte)1, '+'); + + private byte position; + private char indicator; + + private TrainState(byte position, char indicator) { + this.position = position; + this.indicator = indicator; + } + + public byte getPositionMultiplier() { + return position; + } + + public char getIndicator() { + return indicator; + } + + public static TrainState getByPositionInt(int position) { + return Arrays.stream(values()).filter(x -> x.getPositionMultiplier() == position).findFirst().orElse(STAYING); + } +} diff --git a/common/src/main/java/de/mrjulsen/crn/data/train/TrainStatus.java b/common/src/main/java/de/mrjulsen/crn/data/train/TrainStatus.java new file mode 100644 index 00000000..0c19d13d --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/data/train/TrainStatus.java @@ -0,0 +1,226 @@ +package de.mrjulsen.crn.data.train; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Function; +import java.util.function.Predicate; + +import com.google.common.collect.ImmutableMap; + +import de.mrjulsen.crn.Constants; +import de.mrjulsen.crn.CreateRailwaysNavigator; +import de.mrjulsen.crn.client.ClientWrapper; +import de.mrjulsen.crn.client.gui.ModGuiIcons; +import de.mrjulsen.mcdragonlib.client.util.Graphics; +import de.mrjulsen.mcdragonlib.client.util.GuiUtils; +import de.mrjulsen.mcdragonlib.data.Single; +import de.mrjulsen.mcdragonlib.util.TextUtils; +import net.minecraft.ChatFormatting; +import net.minecraft.client.gui.Font; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.MutableComponent; +import net.minecraft.resources.ResourceLocation; + +public class TrainStatus { + + public static final MutableComponent textOperationalDisruption = TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".delay_reason.operational_disruption"); // Betriebsstörung + public static final MutableComponent textTooFewTracks = TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".delay_reason.too_few_tracks"); // VerfĂŒgbarkeit der Gleise eingeschrĂ€nkt + public static final MutableComponent textOperationalStabilization = TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".delay_reason.operational_stabilization"); // Betriebsstabilisierung + public static final MutableComponent textStaffShortage = TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".delay_reason.staff_shortage"); // Kurzfristiger Personalausfall + public static final MutableComponent textTrackClosed = TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".delay_reason.track_closed"); + + private static final Registry REGISTRY = Registry.create(CreateRailwaysNavigator.MOD_ID); + public static final TrainStatus DEFAULT_DELAY = REGISTRY.registerDefault("default_delay", new TrainStatus(TrainStatusCategory.TRAIN, TrainStatusType.DELAY, (data) -> TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".train_status.unknown_delay"), null)); + public static final TrainStatus DELAY_FROM_PREVIOUS_JOURNEY = REGISTRY.registerDefault("delay_from_previous_journey", new TrainStatus(TrainStatusCategory.TRAIN, TrainStatusType.DELAY, (data) -> TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".train_status.delay_previous_journey"), null)); + public static final TrainStatus CANCELLED = REGISTRY.registerDefault("cancelled", new TrainStatus(TrainStatusCategory.TRAIN, TrainStatusType.DELAY, (data) -> TextUtils.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".route_overview.cancelled"), null)); + + public static final int HEIGHT = 9; + + private final TrainStatusType importance; + private final TrainStatusCategory category; + private final Function text; + //private final Function reason; + private final Predicate trigger; + + private ResourceLocation location; + + public TrainStatus(TrainStatusCategory category, TrainStatusType importance, Function text, /*Function reason,*/ Predicate trigger) { + this.importance = importance; + this.category = category; + this.text = text; + //this.reason = reason; + this.trigger = trigger; + } + + public TrainStatusType getImportance() { + return importance; + } + + public MutableComponent getText(TrainData data) { + return text.apply(data); + } + + /* + public MutableComponent getReason(NewTrainData data) { + return reason.apply(data); + } + */ + + public boolean isTriggerd(TrainData data) { + return trigger != null && trigger.test(data); + } + + public CompiledTrainStatus compile(TrainData data) { + return new CompiledTrainStatus(category, importance, getText(data));//, getReason(data)); + } + + public ResourceLocation getLocation() { + return location; + } + + + private void setLocation(ResourceLocation location) { + this.location = location; + } + + + public static record CompiledTrainStatus(TrainStatusCategory category, TrainStatusType type, Component text/*, Component reason*/) { + + public static final String NBT_CATEGORY = "Category"; + public static final String NBT_TYPE = "Type"; + public static final String NBT_TEXT = "Text"; + public static final String NBT_REASON = "Reason"; + + public CompoundTag toNbt() { + CompoundTag nbt = new CompoundTag(); + nbt.putByte(NBT_CATEGORY, category().getIndex()); + nbt.putByte(NBT_TYPE, type().getIndex()); + nbt.putString(NBT_TEXT, text().getString()); + //nbt.putString(NBT_REASON, reason().getString()); + return nbt; + } + + public static CompiledTrainStatus fromNbt(CompoundTag nbt) { + return new CompiledTrainStatus( + TrainStatusCategory.getByIndex(nbt.getByte(NBT_CATEGORY)), + TrainStatusType.getByIndex(nbt.getByte(NBT_TYPE)), + TextUtils.text(nbt.getString(NBT_TEXT)) + //TextUtils.text(nbt.getString(NBT_REASON)) + ); + } + + public int render(Graphics graphics, Single font, int x, int y, int maxWidth) { + final int color = type().getColor(); + final float scale = 0.75f; + graphics.poseStack().pushPose(); + graphics.poseStack().translate(x, y, 0); + GuiUtils.setTint(color); + ModGuiIcons.IMPORTANT.render(graphics, -4, -3); + graphics.poseStack().scale(scale, scale, 1); + int height = (int)(ClientWrapper.renderMultilineLabelSafe(graphics, (int)(10 / scale), (int)(2 / scale), font.getFirst(), text(), (int)(maxWidth / scale), color) * scale); + graphics.poseStack().popPose(); + + return Math.max(HEIGHT, height + 2); + } + } + + + public static class Registry { + + private static final Map registeredStatusInfos = new HashMap<>(); + + private final String modid; + + private Registry(String modid) { + this.modid = modid; + } + + public static Registry create(String modid) { + return new Registry(modid); + } + + public static ImmutableMap getRegisteredStatus() { + return ImmutableMap.copyOf(registeredStatusInfos); + } + + public TrainStatus register(String name, TrainStatus statusPattern) { + ResourceLocation loc = new ResourceLocation(modid, name); + statusPattern.setLocation(loc); + registeredStatusInfos.put(loc, statusPattern); + return statusPattern; + } + + public TrainStatus registerDefault(String name, TrainStatus statusPattern) { + ResourceLocation loc = new ResourceLocation(modid, name); + statusPattern.setLocation(loc); + registeredStatusInfos.put(loc, statusPattern); + return statusPattern; + } + + public TrainStatus unregister(ResourceLocation location) { + return registeredStatusInfos.remove(location); + } + + public TrainStatus get(ResourceLocation location) { + return registeredStatusInfos.get(location); + } + + public boolean isRegistered(ResourceLocation location) { + return registeredStatusInfos.containsKey(location); + } + + public void delete(String modid) { + registeredStatusInfos.keySet().removeIf(x -> x.getNamespace().equals(modid)); + } + } + + public static enum TrainStatusType { + MESSAGE_DEFAULT((byte)0, 0xFFFFFFFF), + MESSAGE_WARN((byte)1, ChatFormatting.GOLD.getColor()), + MESSAGE_IMPORTANT((byte)2, Constants.COLOR_DELAYED), + DELAY((byte)3, Constants.COLOR_DELAYED); + + private final byte index; + private final int color; + + private TrainStatusType(byte index, int color) { + this.index = index; + this.color = color; + } + + public byte getIndex() { + return index; + } + + public int getColor() { + return color; + } + + public static TrainStatusType getByIndex(int index) { + return Arrays.stream(values()).filter(x -> x.getIndex() == index).findFirst().orElse(MESSAGE_DEFAULT); + } + } + + public static enum TrainStatusCategory { + /** Information about a train. */ + TRAIN((byte)0), + /** Information about a single station. */ + STATION((byte)1); + + private final byte index; + + private TrainStatusCategory(byte index) { + this.index = index; + } + + public byte getIndex() { + return index; + } + + public static TrainStatusCategory getByIndex(int index) { + return Arrays.stream(values()).filter(x -> x.getIndex() == index).findFirst().orElse(TRAIN); + } + } +} diff --git a/common/src/main/java/de/mrjulsen/crn/data/train/TrainStop.java b/common/src/main/java/de/mrjulsen/crn/data/train/TrainStop.java new file mode 100644 index 00000000..907a6e9e --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/data/train/TrainStop.java @@ -0,0 +1,417 @@ +package de.mrjulsen.crn.data.train; + +import java.util.UUID; +import com.simibubi.create.content.trains.entity.TrainIconType; + +import de.mrjulsen.crn.Constants; +import de.mrjulsen.crn.config.ModCommonConfig; +import de.mrjulsen.crn.data.StationTag; +import de.mrjulsen.crn.data.TagName; +import de.mrjulsen.crn.data.StationTag.ClientStationTag; +import de.mrjulsen.crn.data.storage.GlobalSettings; +import de.mrjulsen.crn.data.TrainInfo; +import de.mrjulsen.mcdragonlib.DragonLib; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.resources.ResourceLocation; + +/** + * A small variant of the {@code NewTrainPrediction} class, representing a stop of a train on its route with some important information. + */ +public class TrainStop implements Comparable { + + protected static final String NBT_SCHEDULE_INDEX = "ScheduleIndex"; + protected static final String NBT_SECTION_INDEX = "SectionIndex"; + protected static final String NBT_TRAIN_ID = "TrainId"; + protected static final String NBT_TRAIN_NAME = "TrainName"; + protected static final String NBT_TRAIN_ICON = "TrainIcon"; + protected static final String NBT_TRAIN_INFO = "TrainInfo"; + protected static final String NBT_SCHEDULE_TITLE = "ScheduleTitle"; + protected static final String NBT_TERMINUS_TEXT = "TerminusText"; + protected static final String NBT_STAY_DURATION = "StayDuration"; + protected static final String NBT_IS_CUSTOM_TITLE = "IsCustomTitle"; + protected static final String NBT_SIMULATED_TIME = "SimulationTime"; + + protected static final String NBT_SCHEDULED_DEPARTURE_TIME = "ScheduledDeparture"; + protected static final String NBT_SCHEDULED_ARRIVAL_TIME = "ScheduledArrival"; + protected static final String NBT_CYCLE = "Cycle"; + protected static final String NBT_TAG = "StationTag"; + + protected static final String NBT_REAL_TIME_ARRIVAL_TIME = "RealArrival"; + protected static final String NBT_REAL_TIME_DEPARTURE_TIME = "RealDeparture"; + protected static final String NBT_REAL_CYCLE = "RealCycle"; + protected static final String NBT_REAL_TIME_TAG = "RealTimeTag"; + protected static final String NBT_STATE = "State"; + + protected final int scheduleIndex; + protected final int sectionIndex; + protected final UUID trainId; + protected final String trainName; + protected final TrainIconType trainIcon; + protected final TrainInfo trainInfo; + protected final String scheduleTitle; + protected final String terminusText; + protected final int stayDuration; + protected final boolean isCustomTitle; + + protected boolean simulated; + protected long simulationTime; + + protected long scheduledDepartureTime; + protected long scheduledArrivalTime; + protected int cycle; // Der Zyklus, fĂŒr den dieser Eintrag gĂŒltig ist. Wenn der real-time Zyklus kleiner ist, dann ist der Zug noch nicht vorhersagbar, falls grĂ¶ĂŸer, ist er abgefahren. + protected ClientStationTag tag; + + protected long realTimeArrivalTime; + protected long realTimeDepartureTime; + protected int realTimeCycle = -1; + protected ClientStationTag realTimeTag; + + protected long arrivalTimeDeviation; + protected long departureTimeDeviation; + protected int realTimeTicksUntilArrival = -1; + + + // state + protected TrainState trainState = TrainState.BEFORE; + + + public TrainStop(int scheduleIndex, int sectionIndex, UUID trainId, String trainName, TrainIconType trainIcon, TrainInfo trainInfo, + String scheduleTitle, boolean isCustomTitle, String terminusText, int stayDuration, boolean simulated, + long scheduledDepartureTime, long scheduledArrivalTime, int cycle, ClientStationTag tag, long realTimeArrivalTime, + long realTimeDepartureTime, int realTimeCycle, ClientStationTag realTimeTag, long arrivalTimeDeviation, + long departureTimeDeviation, int realTimeTicksUntilArrival, TrainState trainPosition) { + this.scheduleIndex = scheduleIndex; + this.sectionIndex = sectionIndex; + this.trainId = trainId; + this.trainName = trainName; + this.trainIcon = trainIcon; + this.trainInfo = trainInfo; + this.scheduleTitle = scheduleTitle; + this.isCustomTitle = isCustomTitle; + this.terminusText = terminusText; + this.stayDuration = stayDuration; + this.simulated = simulated; + this.scheduledDepartureTime = scheduledDepartureTime; + this.scheduledArrivalTime = scheduledArrivalTime; + this.cycle = cycle; + this.tag = tag; + this.realTimeArrivalTime = realTimeArrivalTime; + this.realTimeDepartureTime = realTimeDepartureTime; + this.realTimeCycle = realTimeCycle; + this.realTimeTag = realTimeTag; + this.arrivalTimeDeviation = arrivalTimeDeviation; + this.departureTimeDeviation = departureTimeDeviation; + this.realTimeTicksUntilArrival = realTimeTicksUntilArrival; + this.trainState = trainPosition; + } + + public TrainStop(TrainPrediction prediction) { + this(prediction.getStationTag(), prediction, false); + } + + public TrainStop(StationTag tag, TrainPrediction prediction, boolean lastCycle) { + this( + prediction.getEntryIndex(), + prediction.getSection().getScheduleIndex(), + prediction.getData().getTrainId(), + prediction.getData().getTrain().name.getString(), + prediction.getData().getTrain().icon, + prediction.getData().getTrainInfo(prediction.getEntryIndex()), + prediction.getTitle(), + prediction.hasCustomTitle(), + prediction.getSectionDestinationText(), + prediction.getStayDuration(), + false, + lastCycle ? prediction.getPreviousScheduledDepartureTime() : prediction.getScheduledDepartureTime(), + lastCycle ? prediction.getPreviousScheduledArrivalTime() : prediction.getScheduledArrivalTime(), + prediction.getCurrentCycle() - (lastCycle ? 1 : 0), + prediction.getStationTag().getClientTag(prediction.getStationName()), + lastCycle ? prediction.getPreviousRealTimeArrivalTime() : prediction.getRealTimeArrivalTime(), + lastCycle ? prediction.getPreviousRealTimeDepartureTime() : prediction.getRealTimeDepartureTime(), + prediction.getCurrentCycle() - (lastCycle ? 1 : 0), + prediction.getStationTag().getClientTag(prediction.getStationName()), + prediction.getArrivalTimeDeviation(), + prediction.getDepartureTimeDeviation(), + prediction.getRealTimeArrivalTicks(), + TrainState.BEFORE + ); + //updateRealTime(prediction); + } + + public TrainStop copy() { + return new TrainStop( + this.scheduleIndex, + this.sectionIndex, + this.trainId, + this.trainName, + this.trainIcon, + this.trainInfo, + this.scheduleTitle, + this.isCustomTitle, + this.terminusText, + this.stayDuration, + this.simulated, + this.scheduledDepartureTime, + this.scheduledArrivalTime, + this.cycle, + this.tag, + this.realTimeArrivalTime, + this.realTimeDepartureTime, + this.realTimeCycle, + this.realTimeTag, + this.arrivalTimeDeviation, + this.departureTimeDeviation, + this.realTimeTicksUntilArrival, + this.trainState + ); + } + + public void simulateTicks(long ticks) { + this.simulated = true; + int totalDuration = TrainListener.data.get(getTrainId()).getTotalDuration(); + long scheduledTimeUntilArrival = getScheduledArrivalTime() - DragonLib.getCurrentWorldTime(); + int simulationCycles = (int)(ticks / totalDuration); + long simulationRemaining = ticks % totalDuration; + if (simulationRemaining > 0 && simulationRemaining >= scheduledTimeUntilArrival) { + simulationCycles++; + } + + this.cycle += simulationCycles; + this.scheduledArrivalTime += simulationCycles * totalDuration; + this.scheduledDepartureTime += simulationCycles * totalDuration; + + this.realTimeArrivalTime += simulationCycles * totalDuration; + this.realTimeDepartureTime += simulationCycles * totalDuration; + this.realTimeTicksUntilArrival = -1; + this.simulationTime += ticks; + } + + public void simulateCycles(int cycles) { + if (cycles == 0) { + return; + } + simulateTicks(cycles * TrainListener.data.get(getTrainId()).getTotalDuration()); + } + + + public static TrainStop simulateCyclesBack(TrainPrediction prediction) { + return new TrainStop(prediction.getStationTag(), prediction, true); + } + + public boolean isSimulated() { + return simulated; + } + + public long getSimulationTime() { + return simulationTime; + } + + public UUID getTrainId() { + return trainId; + } + + public String getTrainName() { + return trainName; + } + + public TrainIconType getTrainIcon() { + return trainIcon; + } + + public TrainInfo getTrainInfo() { + return trainInfo; + } + + public int getScheduleIndex() { + return scheduleIndex; + } + + public int getSectionIndex() { + return sectionIndex; + } + + public String getTerminusText() { + return terminusText; + } + + public boolean hasCustomTitle() { + return isCustomTitle; + } + + public String getScheduleTitle() { + return scheduleTitle; + } + + public String getDisplayTitle() { + return hasCustomTitle() || getTerminusText() == null || getTerminusText().isEmpty() ? getScheduleTitle() : getTerminusText(); + } + + public String getTrainDisplayName() { + return getTrainInfo() == null || getTrainInfo().line() == null || getTrainInfo().line().getLineName().isEmpty() ? getTrainName() : getTrainInfo().line().getLineName(); + } + + public int getTrainDisplayColor() { + if (getTrainInfo() != null && getTrainInfo().line() != null && getTrainInfo().line().getColor() != 0) { + return getTrainInfo().line().getColor(); + } else if (getTrainInfo() != null && getTrainInfo().group() != null && getTrainInfo().group().getColor() != 0) { + return getTrainInfo().group().getColor(); + } + return Constants.COLOR_TRAIN_BACKGROUND; + } + + public int getStayTime() { + return stayDuration; + } + + public StationTag getTag() { + return GlobalSettings.getInstance().getStationTag(getClientTag().tagId()).orElse(GlobalSettings.getInstance().getOrCreateStationTagFor(TagName.of(getClientTag().tagName()))); + } + + public ClientStationTag getClientTag() { + return tag; + } + + public int getScheduledCycle() { + return cycle; + } + + public int getRealTimeCycle() { + return realTimeCycle; + } + + public ClientStationTag getRealTimeStationTag() { + return realTimeTag; + } + + public long getScheduledDepartureTime() { + return scheduledDepartureTime; + } + + public long getScheduledArrivalTime() { + return scheduledArrivalTime; + } + + public long getRealTimeArrivalTime() { + return realTimeArrivalTime; + } + + public long getRealTimeDepartureTime() { + return realTimeDepartureTime; + } + + public long getArrivalTimeDeviation() { + return arrivalTimeDeviation; + } + + public long getDepartureTimeDeviation() { + return departureTimeDeviation; + } + + public int getTicksUntilArrival() { + return realTimeTicksUntilArrival; + } + + public long getScheduledArrivalDay() { + return getScheduledArrivalTime() / DragonLib.TICKS_PER_DAY; + } + + public long getScheduledDepartureDay() { + return getScheduledDepartureDay() / DragonLib.TICKS_PER_DAY; + } + + public long getRealTimeArrivalDay() { + return getRealTimeArrivalTime() / DragonLib.TICKS_PER_DAY; + } + + public long getRealTimeDepartureDay() { + return getRealTimeDepartureTime() / DragonLib.TICKS_PER_DAY; + } + + /** + * The state of this train at this station. + */ + public TrainState getState() { + return trainState; + } + + public boolean isArrivalDelayed() { + return getArrivalTimeDeviation() >= ModCommonConfig.SCHEDULE_DEVIATION_THRESHOLD.get(); + } + + public boolean isDepartureDelayed() { + return getDepartureTimeDeviation() >= ModCommonConfig.SCHEDULE_DEVIATION_THRESHOLD.get(); + } + + public boolean isAnyDelayed() { + return isArrivalDelayed() || isDepartureDelayed(); + } + + public boolean shouldRenderRealTime() { + return getRealTimeCycle() >= getScheduledCycle(); + } + + public boolean isStationInfoChanged() { + return !getClientTag().info().equals(getRealTimeStationTag().info()); + } + + public boolean isDeparted() { + return trainState == TrainState.AFTER; + } + + @Override + public int compareTo(TrainStop o) { + return Long.compare(getScheduledArrivalTime(), o.getScheduledArrivalTime()); + } + + public CompoundTag toNbt(boolean includeRealTime) { + CompoundTag nbt = new CompoundTag(); + nbt.putInt(NBT_SCHEDULE_INDEX, scheduleIndex); + nbt.putInt(NBT_SECTION_INDEX, sectionIndex); + nbt.putUUID(NBT_TRAIN_ID, trainId); + nbt.putString(NBT_TRAIN_NAME, trainName); + nbt.putString(NBT_TRAIN_ICON, trainIcon.getId().toString()); + nbt.put(NBT_TRAIN_INFO, trainInfo.toNbt()); + nbt.putString(NBT_SCHEDULE_TITLE, scheduleTitle); + nbt.putString(NBT_TERMINUS_TEXT, terminusText); + nbt.putInt(NBT_STAY_DURATION, stayDuration); + nbt.putBoolean(NBT_IS_CUSTOM_TITLE, isCustomTitle); + nbt.put(NBT_TAG, tag.toNbt()); + nbt.putLong(NBT_SIMULATED_TIME, simulationTime); + nbt.putLong(NBT_SCHEDULED_ARRIVAL_TIME, scheduledArrivalTime); + nbt.putLong(NBT_SCHEDULED_DEPARTURE_TIME, scheduledDepartureTime); + nbt.putInt(NBT_CYCLE, cycle); + nbt.putLong(NBT_REAL_TIME_ARRIVAL_TIME, realTimeArrivalTime); + nbt.putLong(NBT_REAL_TIME_DEPARTURE_TIME, realTimeDepartureTime); + nbt.putInt(NBT_REAL_CYCLE, realTimeCycle); + nbt.put(NBT_REAL_TIME_TAG, realTimeTag.toNbt()); + return nbt; + } + + public static TrainStop fromNbt(CompoundTag nbt) { + return new TrainStop( + nbt.getInt(NBT_SCHEDULE_INDEX), + nbt.getInt(NBT_SECTION_INDEX), + nbt.getUUID(NBT_TRAIN_ID), + nbt.getString(NBT_TRAIN_NAME), + TrainIconType.byId(new ResourceLocation(nbt.getString(NBT_TRAIN_ICON))), + TrainInfo.fromNbt(nbt.getCompound(NBT_TRAIN_INFO)), + nbt.getString(NBT_SCHEDULE_TITLE), + nbt.getBoolean(NBT_IS_CUSTOM_TITLE), + nbt.getString(NBT_TERMINUS_TEXT), + nbt.getInt(NBT_STAY_DURATION), + nbt.getLong(NBT_SIMULATED_TIME) != 0, + nbt.getLong(NBT_SCHEDULED_DEPARTURE_TIME), + nbt.getLong(NBT_SCHEDULED_ARRIVAL_TIME), + nbt.getInt(NBT_CYCLE), + ClientStationTag.fromNbt(nbt.getCompound(NBT_TAG)), + nbt.getLong(NBT_REAL_TIME_ARRIVAL_TIME), + nbt.getLong(NBT_REAL_TIME_DEPARTURE_TIME), + nbt.getInt(NBT_REAL_CYCLE), + ClientStationTag.fromNbt(nbt.getCompound(NBT_REAL_TIME_TAG)), + nbt.contains(NBT_REAL_TIME_ARRIVAL_TIME) ? nbt.getLong(NBT_REAL_TIME_ARRIVAL_TIME) - nbt.getLong(NBT_SCHEDULED_ARRIVAL_TIME) : 0, + nbt.contains(NBT_REAL_TIME_DEPARTURE_TIME) ? nbt.getLong(NBT_REAL_TIME_DEPARTURE_TIME) - nbt.getLong(NBT_SCHEDULED_DEPARTURE_TIME) : 0, + 0, + TrainState.BEFORE + ); + } +} diff --git a/common/src/main/java/de/mrjulsen/crn/data/train/TrainTravelSection.java b/common/src/main/java/de/mrjulsen/crn/data/train/TrainTravelSection.java new file mode 100644 index 00000000..6c2a6f25 --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/data/train/TrainTravelSection.java @@ -0,0 +1,264 @@ +package de.mrjulsen.crn.data.train; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; + +import de.mrjulsen.crn.CreateRailwaysNavigator; +import de.mrjulsen.crn.client.lang.ELanguage; +import de.mrjulsen.crn.data.StationTag; +import de.mrjulsen.crn.data.TrainGroup; +import de.mrjulsen.crn.data.TrainLine; +import de.mrjulsen.crn.data.storage.GlobalSettings; +import de.mrjulsen.mcdragonlib.data.Cache; + +public class TrainTravelSection { + + private static final int INVALID = -1; + + private transient final TrainData data; + private transient final int scheduleIndex; + private transient final boolean isDefault; + + private final boolean includeLastStationOfLastSection; + private final boolean usable; + private final TrainGroup trainGroup; + private final TrainLine trainLine; + + private final Cache> predictions = new Cache<>(() -> getPredictions(INVALID, false)); + private final Cache nextSection; + private final Cache previousSection; + + public TrainTravelSection(TrainData data, int indexInSchedule, TrainGroup group, TrainLine line, boolean includePreviousStation, boolean usable) { + this(false, data, indexInSchedule, group, line, includePreviousStation, usable); + } + + private TrainTravelSection(boolean isDefault, TrainData data, int indexInSchedule, TrainGroup group, TrainLine line, boolean includePreviousStation, boolean usable) { + this.data = data; + this.scheduleIndex = indexInSchedule; + this.isDefault = isDefault; + this.trainGroup = group; + this.trainLine = line; + this.includeLastStationOfLastSection = includePreviousStation; + this.usable = usable; + + nextSection = new Cache<>(() -> { + if (data.isSingleSection()) { + return this; + } + + List sections = data.getSections(); + if (sections.isEmpty()) { + return this; + } + int selfIndex = sections.indexOf(this); + if (selfIndex < 0 || selfIndex >= sections.size()) { + return sections.get(0); + } + return sections.get((selfIndex + 1) % sections.size()); + }); + + + previousSection = new Cache<>(() -> { + if (data.isSingleSection()) { + return this; + } + + List sections = data.getSections(); + if (sections.isEmpty()) { + return this; + } + int selfIndex = sections.indexOf(this); + if (selfIndex < 0 || selfIndex >= sections.size()) { + return sections.get(0); + } + int prevIndex = selfIndex - 1; + return sections.get(prevIndex < 0 ? sections.size() - 1 : prevIndex); + }); + } + + public static final TrainTravelSection def(TrainData data) { + return new TrainTravelSection(true, data, 0, null, null, true, true); + } + + public boolean isDefault() { + return isDefault; + } + + public TrainData getData() { + return data; + } + + public int getScheduleIndex() { + return scheduleIndex; + } + + public boolean shouldIncludeNextStationOfNextSection() { + return includeLastStationOfLastSection; + } + + public boolean isUsable() { + return usable; + } + + public TrainGroup getTrainGroup() { + return trainGroup; + } + + public TrainLine getTrainLine() { + return trainLine; + } + + public TrainTravelSection nextSection() { + return nextSection.get(); + } + + public TrainTravelSection previousSection() { + return previousSection.get(); + } + + /** + * Creates a list of all stops assigned to this section. + * @param startingAtIndex The schedule index from which found stops should be added, or {@code < 0} to get all elements of the section. + * @return A list of all stops assigned to this section. + */ + public List getPredictions(int startingAtIndex, boolean ignoreIncludeLastStationRule) { + if (data.getTrain() == null || data.getTrain().runtime == null || data.getTrain().runtime.getSchedule() == null) { + return List.of(); + } + List result = new ArrayList<>(); + TrainTravelSection nextSection = nextSection(); + Map predictions = data.getPredictionsRaw().entrySet().stream().filter(x -> !GlobalSettings.getInstance().isStationBlacklisted(x.getValue().getStationName())).collect(Collectors.toMap(x -> x.getKey(), x -> x.getValue())); + final int startIndex = getScheduleIndex(); + final int stopIndex = nextSection.getScheduleIndex(); + final int count = data.getTrain().runtime.getSchedule().entries.size(); + + boolean customStartFound = false; + boolean endReached = false; + TrainPrediction pred = null; + for (int i = 0; i < count * 2; i++) { + final int j = (startIndex + i) % count; + if (i != 0 && j == stopIndex) { + if (!ignoreIncludeLastStationRule && shouldIncludeNextStationOfNextSection()) { + endReached = true; + } else return result; + } + customStartFound = customStartFound || startingAtIndex < 0 || j == startingAtIndex; + if (!predictions.containsKey(j) || !customStartFound) continue; + pred = predictions.get(j); + result.add(pred); + if (endReached) break; + } + return result; + } + + public int getFirstIndexFor(StationTag tag) { + return getPredictions(INVALID, false).stream().filter(x -> x.getStationTag().equals(tag)).map(x -> x.getEntryIndex()).findFirst().orElse(0); + } + + /** + * Creates a list as a route with all stations in this section. + * @param simulationTime How far ahead the predictions should be calculated. + * @param currentIndex The schedule index of the current station as a reference point. The route is calculated so that the station with the specified index fits with it's timing into the generated route. + * @return A list as a route with all stations in this section + */ + public List getAllStops(long simulationTime, int currentIndex) { + List result = new ArrayList<>(); + List predictions = getPredictions(INVALID, false); + + TrainStop lastStop = null; + for (TrainPrediction prediction : predictions) { + TrainStop stop = new TrainStop(prediction); + stop.simulateTicks(simulationTime); + if (lastStop != null && lastStop.getScheduledArrivalTime() > stop.getScheduledArrivalTime()) { + if (prediction.getEntryIndex() == currentIndex) { + result.forEach(x -> x.simulateCycles(-1)); + } else { + stop.simulateCycles(1); + } + } + result.add(stop); + lastStop = stop; + } + return result; + } + + public List getStopovers() { + List predictions = this.predictions.get(); + return predictions.stream().limit(predictions.size() - 1).skip(1).map(x -> x.getStationTag().getTagName().get()).toList(); /* TODO */ + } + + public List getStopoversFrom(int startIndex) { + List predictions = new ArrayList<>(); + boolean startFound = false; + for (int i = 0; i < this.predictions.get().size() - 1; i++) { + TrainPrediction prediction = this.predictions.get().get(i); + boolean wasStartFound = startFound; + if (prediction.getEntryIndex() == startIndex) startFound = true; + if (!wasStartFound) continue; + predictions.add(prediction.getStationTag().getTagName().get()); + } + return predictions; + } + + public Optional getFinalStop() { + List predictions = this.predictions.get(); + return predictions.isEmpty() ? Optional.empty() : Optional.ofNullable(predictions.get(predictions.size() - 1)); + } + + public boolean isFinalStop(TrainPrediction prediction) { + Optional pred = getFinalStop(); + return pred.isPresent() && pred.get() == prediction; + } + + public boolean isFirstStop(TrainPrediction prediction) { + Optional pred = getFirstStop(); + return pred.isPresent() && pred.get() == prediction; + } + + public boolean isFinalStop(int scheduleIndex) { + Optional pred = getFinalStop(); + return pred.isPresent() && pred.get().getEntryIndex() == scheduleIndex; + } + + public boolean isFirstStop(int scheduleIndex) { + Optional pred = getFirstStop(); + return pred.isPresent() && pred.get().getEntryIndex() == scheduleIndex; + } + + public Optional getFirstStop() { + List predictions = this.predictions.get(); + return predictions.isEmpty() ? Optional.empty() : Optional.ofNullable(predictions.get(0)); + } + + public Optional getNextStop() { + List predictions = this.predictions.get(); + return predictions.isEmpty() ? Optional.empty() : Optional.ofNullable(predictions.get(0)); + } + + public String getDisplayText() { + if (!isUsable()) { + return ELanguage.translate("block." + CreateRailwaysNavigator.MOD_ID + ".advanced_display.ber.not_in_service").getString(); + } + return getFinalStop().map(x -> GlobalSettings.getInstance().getOrCreateStationTagFor(x.getStationName()).getTagName().get()).orElse("?"); + } + + public String getDisplayTextStart() { + return !isUsable() ? ELanguage.translate("block." + CreateRailwaysNavigator.MOD_ID + ".advanced_display.ber.not_in_service").getString() : getFirstStop().map(x -> GlobalSettings.getInstance().getOrCreateStationTagFor(x.getStationName()).getTagName().get()).orElse("?"); + } + + public String getStartStationName() { + return getFirstStop().map(x -> x.getStationName()).orElse("?"); + } + + public String getDestinationStationName() { + return getFinalStop().map(x -> x.getStationName()).orElse("?"); + } + + @Override + public String toString() { + return getDisplayText(); + } +} diff --git a/common/src/main/java/de/mrjulsen/crn/data/train/TrainUtils.java b/common/src/main/java/de/mrjulsen/crn/data/train/TrainUtils.java new file mode 100644 index 00000000..a6936c5c --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/data/train/TrainUtils.java @@ -0,0 +1,253 @@ +package de.mrjulsen.crn.data.train; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.UUID; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +import com.google.common.collect.ImmutableMap; +import com.simibubi.create.Create; +import com.simibubi.create.content.decoration.slidingDoor.DoorControlBehaviour; +import com.simibubi.create.content.trains.GlobalRailwayManager; +import com.simibubi.create.content.trains.display.GlobalTrainDisplayData; +import com.simibubi.create.content.trains.display.GlobalTrainDisplayData.TrainDeparturePrediction; +import com.simibubi.create.content.trains.entity.Train; +import com.simibubi.create.content.trains.graph.EdgePointType; +import com.simibubi.create.content.trains.graph.TrackEdge; +import com.simibubi.create.content.trains.signal.SignalBoundary; +import com.simibubi.create.content.trains.signal.TrackEdgePoint; +import com.simibubi.create.content.trains.station.GlobalStation; +import com.simibubi.create.content.trains.station.StationBlockEntity; +import com.simibubi.create.foundation.utility.Couple; + +import de.mrjulsen.crn.data.NearestTrackStationResult; +import de.mrjulsen.crn.data.StationTag; +import de.mrjulsen.crn.data.TrainExitSide; +import de.mrjulsen.crn.data.storage.GlobalSettings; +import de.mrjulsen.crn.event.ModCommonEvents; +import de.mrjulsen.crn.data.navigation.TrainSchedule; +import de.mrjulsen.mcdragonlib.data.Single.MutableSingle; +import de.mrjulsen.mcdragonlib.util.MathUtils; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.core.Vec3i; +import net.minecraft.world.level.Level; +import net.minecraft.world.phys.Vec3; + +public final class TrainUtils { + + public static GlobalRailwayManager getRailwayManager() { + return Create.RAILWAYS; + } + + /** + * Get data about all trains and when they arrive where. + * @return a Map where the key is the station name and the value is a list of data from all trains that will arrive at this stations. + */ + public static Map> allPredictionsRaw() { + return new HashMap<>(GlobalTrainDisplayData.statusByDestination); + } + + public static boolean isStationKnown(String station) { + return allPredictionsRaw().keySet().stream().anyMatch(x -> TrainUtils.stationMatches(station, x)); + } + + /** + * A list of all stations in the world. + * @return a list containing all track stations. + */ + public static Collection getAllStations() { + final Collection stations = new ArrayList<>(); + getRailwayManager().trackNetworks.forEach((uuid, graph) -> { + Collection foundStations = graph.getPoints(EdgePointType.STATION); + stations.addAll(foundStations); + }); + return stations; + } + + public static Optional getTrain(UUID trainId) { + return Optional.ofNullable(getRailwayManager().trains.get(trainId)); + } + + public static Set getTrainIds() { + return new HashSet<>(getRailwayManager().trains.keySet()); + } + + public static Set getTrains(boolean onlyValid) { + return new HashSet<>(getRailwayManager().trains.values().stream().filter(x -> !onlyValid || isTrainValid(x)).toList()); + } + + public static Set getAllSignals() { + return new HashSet<>(getRailwayManager().trackNetworks.values().stream().flatMap(x -> x.getPoints(EdgePointType.SIGNAL).stream()).toList()); + } + + public static Set getDepartingTrainsAt(StationTag station) { + return ImmutableMap.copyOf(GlobalTrainDisplayData.statusByDestination).entrySet().stream().filter(x -> station.contains(x.getKey())).flatMap(x -> x.getValue().stream()).map(x -> x.train).collect(Collectors.toSet()); + } + + public static Set getDepartingTrainsAt(String station) { + return ImmutableMap.copyOf(GlobalTrainDisplayData.statusByDestination).entrySet().stream().filter(x -> station.equals(x.getKey())).flatMap(x -> x.getValue().stream()).map(x -> x.train).collect(Collectors.toSet()); + } + + public static List getDeparturesAt(StationTag station, UUID selfTrain) { + return getDeparturesAt(x -> x.getStationTag().equals(station), selfTrain); + } + + public static List getDeparturesAtStationName(String stationName, UUID selfTrain) { + return getDeparturesAt(x -> TrainUtils.stationMatches(x.getStationName(), stationName), selfTrain); + } + + public static List getDeparturesAt(Predicate stationFilter, UUID selfTrain) { + + MutableSingle selfSchedule = new MutableSingle(null); + TrainUtils.getTrain(selfTrain).ifPresent(x -> { + selfSchedule.setFirst(new TrainSchedule(TrainListener.data.containsKey(x.id) ? TrainListener.data.get(x.id).getSessionId() : new UUID(0, 0), x)); + }); + + List list = TrainListener.data.values().stream() + .filter(x -> !x.getTrainId().equals(selfTrain) && TrainUtils.isTrainUsable(x.getTrain())) + .flatMap(x -> x.getPredictions().stream().filter(stationFilter)) + .map(TrainStop::new) + .filter(x -> { + if (selfSchedule.getFirst() != null) { + return true; + } + Optional train = TrainUtils.getTrain(x.getTrainId()); + if (!train.isPresent()) { + return false; + } + TrainSchedule sched = new TrainSchedule(TrainListener.data.containsKey(train.get().id) ? TrainListener.data.get(train.get().id).getSessionId() : new UUID(0, 0), train.get()); + return !sched.isEqual(selfSchedule.getFirst()); + }) + .sorted((a, b) -> Long.compare(a.getScheduledDepartureTime(), b.getScheduledDepartureTime())) + .toList(); + + List results = new ArrayList<>(); + Set usedTrains = new HashSet<>(); + usedTrains.add(selfTrain); + for (TrainStop stop : list) { + if (!TrainListener.data.containsKey(stop.getTrainId())) continue; + TrainData data = TrainListener.data.get(stop.getTrainId()); + TrainTravelSection section = data.getSectionByIndex(stop.getSectionIndex()); + if (!section.isUsable() && !(section.isFirstStop(stop.getScheduleIndex()) && section.previousSection().isUsable() && section.previousSection().shouldIncludeNextStationOfNextSection())) { + continue; + } + + if (!usedTrains.contains(stop.getTrainId())) { + usedTrains.add(stop.getTrainId()); + results.add(stop); + } + } + + return results; + } + + public static Set isSignalOccupied(UUID signalId, Set excludedTrains) { + Optional signal = getAllSignals().stream().filter(x -> x.getId().equals(signalId)).findFirst(); + if (!signal.isPresent()) { + return Set.of(); + } + + Set occupyingTrains = getTrains(false).stream().filter(x -> !excludedTrains.contains(x.id) && x.occupiedSignalBlocks.keySet().stream().anyMatch(y -> y.equals(signal.get().groups.getFirst()) || y.equals(signal.get().groups.getSecond()))).collect(Collectors.toSet()); + return occupyingTrains; + } + + + public static NearestTrackStationResult getNearestTrackStation(Level level, Vec3i pos) { + Optional station = getAllStations().stream().filter(x -> + isStationKnown(x.name) && + x.getBlockEntityDimension().equals(level.dimension()) && + !GlobalSettings.getInstance().isStationBlacklisted(x.name) + ).min((a, b) -> Double.compare(a.getBlockEntityPos().distSqr(pos), b.getBlockEntityPos().distSqr(pos))); + + double distance = station.isPresent() ? station.get().getBlockEntityPos().distSqr(pos) : 0; + return new NearestTrackStationResult(station, distance); + } + + public static TrainExitSide getTrainStationExit(GlobalStation station, Direction stationDirection, Level level) { + DoorControlBehaviour dcb = getTrainStationDoorControl(station, level); + if (dcb == null) { + return TrainExitSide.UNKNOWN; + } + + if (dcb.mode.matches(stationDirection.getClockWise())) { + return TrainExitSide.RIGHT; + } else if (dcb.mode.matches(stationDirection.getCounterClockWise())) { + return TrainExitSide.LEFT; + } + return TrainExitSide.UNKNOWN; + } + + public static DoorControlBehaviour getTrainStationDoorControl(GlobalStation station, Level level) { + BlockPos stationPos = station.getBlockEntityPos(); + if (level == null || !level.isLoaded(stationPos)) { + return null; + } + if (level.getBlockEntity(stationPos) instanceof StationBlockEntity be) { + return be.doorControls; + } + return null; + } + + + public static Optional getEdge(GlobalStation station) { + MutableSingle edge = new MutableSingle(null); + Create.RAILWAYS.trackNetworks.forEach((uuid, graph) -> { + if (edge.getFirst() != null) return; + TrackEdge e = graph.getConnection(Couple.create(graph.locateNode(station.edgeLocation.getFirst()), graph.locateNode(station.edgeLocation.getSecond()))); + if (e == null) return; + edge.setFirst(e); + }); + return Optional.ofNullable(edge.getFirst()); + } + + public static double angleOn(TrackEdgePoint point, TrackEdge edge) { + double basePos = point.isPrimary(edge.node1) ? edge.getLength() - point.position : point.position; + Vec3 vec = edge.getDirectionAt(basePos); + return point.isPrimary(edge.node1) ? MathUtils.getVectorAngle(vec) : MathUtils.getVectorAngle(vec.reverse()); + } + + public static TrainExitSide getExitSide(GlobalStation station) { + Level level = ModCommonEvents.getPhysicalLevel(); + if (level == null || station == null || !level.isLoaded(station.getBlockEntityPos())) { + return TrainExitSide.UNKNOWN; + } + final Optional edge = level != null ? getEdge(station) : Optional.empty(); + if (!edge.isPresent()) { + return TrainExitSide.UNKNOWN; + } + TrainExitSide side = getTrainStationExit(station, Direction.fromYRot(angleOn(station, edge.get())), level); + + return side; + } + + + public static boolean stationMatches(String stationName, String filter) { + String regex = filter.isBlank() ? filter : "\\Q" + filter.replace("*", "\\E.*\\Q"); + return stationName.matches(regex); + } + + public static boolean isTrainValid(Train train) { + return //!train.derailed && + !train.invalid && + //!train.runtime.paused && + train.runtime.getSchedule() != null && + train.graph != null + ; + } + + public static boolean isTrainUsable(Train train) { + return isTrainValid(train) && + TrainListener.data.containsKey(train.id) && + TrainListener.data.get(train.id).isInitialized() && + !TrainListener.data.get(train.id).isPreparing() + ; + } +} diff --git a/common/src/main/java/de/mrjulsen/crn/data/train/portable/BasicTrainDisplayData.java b/common/src/main/java/de/mrjulsen/crn/data/train/portable/BasicTrainDisplayData.java new file mode 100644 index 00000000..862b2af4 --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/data/train/portable/BasicTrainDisplayData.java @@ -0,0 +1,144 @@ +package de.mrjulsen.crn.data.train.portable; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.UUID; + +import com.simibubi.create.content.trains.entity.TrainIconType; + +import de.mrjulsen.crn.exceptions.RuntimeSideException; +import de.mrjulsen.crn.data.train.TrainData; +import de.mrjulsen.crn.data.train.TrainListener; +import de.mrjulsen.crn.data.train.TrainStop; +import de.mrjulsen.crn.data.train.TrainStatus.CompiledTrainStatus; +import de.mrjulsen.crn.event.ModCommonEvents; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.ListTag; +import net.minecraft.nbt.Tag; +import net.minecraft.resources.ResourceLocation; + +/** Contains data about one train arrival at a specific station. This data is used by displays and does not provide any additional functionality. */ +public class BasicTrainDisplayData { + private final UUID id; + private final String name; + private final TrainIconType icon; + private final List status; + private final boolean cancelled; + + private static final String NBT_ID = "Id"; + private static final String NBT_NAME = "Name"; + private static final String NBT_ICON = "Icon"; + private static final String NBT_STATUS = "Status"; + private static final String NBT_CANCELLED = "Cancelled"; + + public BasicTrainDisplayData( + UUID id, + String name, + TrainIconType icon, + List status, + boolean cancelled + ) { + this.id = id; + this.name = name; + this.icon = icon; + this.status = status; + this.cancelled = cancelled; + } + + public static BasicTrainDisplayData empty() { + return new BasicTrainDisplayData(new UUID(0, 0), "", TrainIconType.getDefault(), List.of(), true); + } + + /** Server-side only! */ + public static BasicTrainDisplayData of(UUID train) throws RuntimeSideException { + if (!ModCommonEvents.hasServer()) { + throw new RuntimeSideException(false); + } + if (!TrainListener.data.containsKey(train)) { + return empty(); + } + TrainData data = TrainListener.data.get(train); + return new BasicTrainDisplayData( + data.getTrainId(), + data.getTrainName(), + TrainIconType.getDefault(), // TODO + new ArrayList<>(data.getStatus()), + data.isCancelled() + ); + } + + public static BasicTrainDisplayData of(TrainStop stop) throws RuntimeSideException { + if (!ModCommonEvents.hasServer()) { + throw new RuntimeSideException(false); + } + if (!TrainListener.data.containsKey(stop.getTrainId())) { + return empty(); + } + TrainData data = TrainListener.data.get(stop.getTrainId()); + return new BasicTrainDisplayData( + stop.getTrainId(), + stop.getTrainName(), + stop.getTrainIcon(), + new ArrayList<>(data.getStatus()), + data.isCancelled() + ); + } + + public UUID getId() { + return id; + } + + public String getName() { + return name; + } + + public TrainIconType getIcon() { + return icon; + } + + public List getStatus() { + return status; + } + + public boolean isCancelled() { + return cancelled; + } + + public boolean hasStatusInfo() { + return !getStatus().isEmpty(); + } + public CompoundTag toNbt() { + CompoundTag nbt = new CompoundTag(); + + ListTag statusList = new ListTag(); + statusList.addAll(status.stream().map(x -> x.toNbt()).toList()); + + nbt.putUUID(NBT_ID, id); + nbt.putString(NBT_NAME, name); + nbt.putString(NBT_ICON, icon.getId().toString()); + nbt.put(NBT_STATUS, statusList); + nbt.putBoolean(NBT_CANCELLED, cancelled); + return nbt; + } + + public static BasicTrainDisplayData fromNbt(CompoundTag nbt) { + return new BasicTrainDisplayData( + nbt.getUUID(NBT_ID), + nbt.getString(NBT_NAME), + TrainIconType.byId(new ResourceLocation(nbt.getString(NBT_ICON))), + nbt.getList(NBT_STATUS, Tag.TAG_COMPOUND).stream().map(x -> CompiledTrainStatus.fromNbt(((CompoundTag)x))).toList(), + nbt.getBoolean(NBT_CANCELLED) + ); + } + + @Override + public final boolean equals(Object obj) { + return obj instanceof BasicTrainDisplayData o && o.getId().equals(getId()); + } + + @Override + public final int hashCode() { + return Objects.hash(getId()); + } +} diff --git a/common/src/main/java/de/mrjulsen/crn/data/train/portable/NextConnectionsDisplayData.java b/common/src/main/java/de/mrjulsen/crn/data/train/portable/NextConnectionsDisplayData.java new file mode 100644 index 00000000..641bacd6 --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/data/train/portable/NextConnectionsDisplayData.java @@ -0,0 +1,60 @@ +package de.mrjulsen.crn.data.train.portable; + +import java.util.List; +import java.util.UUID; + +import de.mrjulsen.crn.data.TagName; +import de.mrjulsen.crn.data.storage.GlobalSettings; +import de.mrjulsen.crn.exceptions.RuntimeSideException; +import de.mrjulsen.crn.data.train.TrainUtils; +import de.mrjulsen.crn.event.ModCommonEvents; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.ListTag; +import net.minecraft.nbt.Tag; + +/** Contains data about one train arrival at a specific station. This data is used by displays and does not provide any additional functionality. */ +public class NextConnectionsDisplayData { + private final List stops; + + private static final String NBT_STOPS = "Stops"; + + public NextConnectionsDisplayData( + List stops + ) { + this.stops = stops; + } + + public static NextConnectionsDisplayData empty() { + return new NextConnectionsDisplayData(List.of()); + } + + /** Server-side only! */ + public static NextConnectionsDisplayData at(String stationName, UUID selfTrainId) throws RuntimeSideException { + if (!ModCommonEvents.hasServer()) { + throw new RuntimeSideException(false); + } + + return new NextConnectionsDisplayData( + TrainUtils.getDeparturesAt(GlobalSettings.getInstance().getOrCreateStationTagFor(TagName.of(stationName)), selfTrainId).stream().map(x -> TrainStopDisplayData.of(x)).toList() + ); + } + public List getConnections() { + return stops; + } + + public CompoundTag toNbt() { + CompoundTag nbt = new CompoundTag(); + + ListTag list = new ListTag(); + list.addAll(stops.stream().map(x -> x.toNbt()).toList()); + + nbt.put(NBT_STOPS, list); + return nbt; + } + + public static NextConnectionsDisplayData fromNbt(CompoundTag nbt) { + return new NextConnectionsDisplayData( + nbt.getList(NBT_STOPS, Tag.TAG_COMPOUND).stream().map(x -> TrainStopDisplayData.fromNbt((CompoundTag)x)).toList() + ); + } +} diff --git a/common/src/main/java/de/mrjulsen/crn/data/train/portable/StationDisplayData.java b/common/src/main/java/de/mrjulsen/crn/data/train/portable/StationDisplayData.java new file mode 100644 index 00000000..dece556a --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/data/train/portable/StationDisplayData.java @@ -0,0 +1,149 @@ +package de.mrjulsen.crn.data.train.portable; + +import java.util.List; +import java.util.Objects; + +import de.mrjulsen.crn.exceptions.RuntimeSideException; +import de.mrjulsen.crn.data.train.TrainData; +import de.mrjulsen.crn.data.train.TrainListener; +import de.mrjulsen.crn.data.train.TrainStop; +import de.mrjulsen.crn.data.train.TrainTravelSection; +import de.mrjulsen.crn.event.ModCommonEvents; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.ListTag; +import net.minecraft.nbt.StringTag; +import net.minecraft.nbt.Tag; + +public class StationDisplayData { + private final BasicTrainDisplayData trainData; + private final TrainStopDisplayData stationData; + private final String firstStopName; + private final boolean isLastStop; + private final List stopovers; + + private static final String NBT_TRAIN = "Train"; + private static final String NBT_STATION = "Station"; + private static final String NBT_STOPOVERS = "Stopovers"; + private static final String NBT_FIRST_STOP = "FirstStop"; + private static final String NBT_IS_LAST = "IsLast"; + + + + public StationDisplayData( + BasicTrainDisplayData trainData, + TrainStopDisplayData stationData, + String firstStopName, + boolean isLastStop, + List stopovers + ) { + this.trainData = trainData; + this.stationData = stationData; + this.stopovers = stopovers; + this.firstStopName = firstStopName; + this.isLastStop = isLastStop; + } + + public static StationDisplayData empty() { + return new StationDisplayData(BasicTrainDisplayData.empty(), TrainStopDisplayData.empty(), "", false, List.of()); + } + + /** Server-side only! */ + public static StationDisplayData of(TrainStop stop) throws RuntimeSideException { + if (!ModCommonEvents.hasServer()) { + throw new RuntimeSideException(false); + } + if (!TrainListener.data.containsKey(stop.getTrainId())) { + return empty(); + } + TrainData data = TrainListener.data.get(stop.getTrainId()); + TrainTravelSection section = data.getSectionByIndex(stop.getSectionIndex()); + TrainTravelSection previousSection = section.previousSection(); + String firstStop = section.getFirstStop().isPresent() ? section.getFirstStop().get().getStationTag().getTagName().get() : ""; + boolean isLastStopOfSection = section.getFinalStop().isPresent() && (previousSection.shouldIncludeNextStationOfNextSection() && previousSection.getFinalStop().isPresent() ? previousSection.getFinalStop().get() : section.getFinalStop().get()).getEntryIndex() == stop.getScheduleIndex(); + if (isLastStopOfSection) { + if (previousSection.shouldIncludeNextStationOfNextSection() && previousSection.getFinalStop().isPresent() && previousSection.getFinalStop().get().getEntryIndex() == stop.getScheduleIndex()) { + firstStop = previousSection.getFirstStop().isPresent() ? previousSection.getFirstStop().get().getStationTag().getTagName().get() : ""; + if ((data.isWaitingAtStation() && data.getCurrentScheduleIndex() == stop.getScheduleIndex()) || !previousSection.isUsable()) { + isLastStopOfSection = false; + } + if (!section.isUsable()) { + isLastStopOfSection = true; + } + } + } + return new StationDisplayData( + BasicTrainDisplayData.of(stop), + TrainStopDisplayData.of(stop), + firstStop, + isLastStopOfSection, + section.getStopoversFrom(stop.getScheduleIndex()) + ); + } + + public BasicTrainDisplayData getTrainData() { + return trainData; + } + + public TrainStopDisplayData getStationData() { + return stationData; + } + + public List getStopovers() { + return stopovers; + } + + public String getFirstStopName() { + return firstStopName; + } + + public boolean isLastStop() { + return isLastStop; + } + + public boolean isDelayed() { + return isLastStop() ? getStationData().isArrivalDelayed() : getStationData().isDepartureDelayed(); + } + + public long getScheduledTime() { + return isLastStop() ? getStationData().getScheduledArrivalTime() : getStationData().getScheduledDepartureTime(); + } + + public long getRealTime() { + return isLastStop() ? getStationData().getRealTimeArrivalTime() : getStationData().getRealTimeDepartureTime(); + } + + + public CompoundTag toNbt() { + CompoundTag nbt = new CompoundTag(); + + ListTag stopoversList = new ListTag(); + stopoversList.addAll(getStopovers().stream().map(x -> StringTag.valueOf(x)).toList()); + + nbt.put(NBT_TRAIN, trainData.toNbt()); + nbt.put(NBT_STATION, stationData.toNbt()); + nbt.putString(NBT_FIRST_STOP, firstStopName); + nbt.putBoolean(NBT_IS_LAST, isLastStop); + nbt.put(NBT_STOPOVERS, stopoversList); + return nbt; + } + + public static StationDisplayData fromNbt(CompoundTag nbt) { + return new StationDisplayData( + BasicTrainDisplayData.fromNbt(nbt.getCompound(NBT_TRAIN)), + TrainStopDisplayData.fromNbt(nbt.getCompound(NBT_STATION)), + nbt.getString(NBT_FIRST_STOP), + nbt.getBoolean(NBT_IS_LAST), + nbt.getList(NBT_STOPOVERS, Tag.TAG_STRING).stream().map(x -> ((StringTag)x).getAsString()).toList() + ); + } + + @Override + public final boolean equals(Object obj) { + return obj instanceof StationDisplayData o && o.getTrainData().equals(getTrainData()) && o.getStationData().equals(getStationData()); + } + + @Override + public final int hashCode() { + return Objects.hash(getTrainData(), getStationData()); + } +} diff --git a/common/src/main/java/de/mrjulsen/crn/data/train/portable/TrainDisplayData.java b/common/src/main/java/de/mrjulsen/crn/data/train/portable/TrainDisplayData.java new file mode 100644 index 00000000..f26a097d --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/data/train/portable/TrainDisplayData.java @@ -0,0 +1,208 @@ +package de.mrjulsen.crn.data.train.portable; + +import java.util.List; +import java.util.ArrayList; +import java.util.Optional; +import java.util.concurrent.TimeUnit; + +import com.simibubi.create.content.trains.entity.Train; + +import de.mrjulsen.crn.data.TrainExitSide; +import de.mrjulsen.crn.exceptions.RuntimeSideException; +import de.mrjulsen.crn.data.train.TrainData; +import de.mrjulsen.crn.data.train.TrainListener; +import de.mrjulsen.crn.data.train.TrainStop; +import de.mrjulsen.crn.data.train.TrainTravelSection; +import de.mrjulsen.crn.data.train.TrainUtils; +import de.mrjulsen.crn.event.ModCommonEvents; +import de.mrjulsen.mcdragonlib.data.Cache; +import de.mrjulsen.mcdragonlib.data.Single.MutableSingle; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.ListTag; +import net.minecraft.nbt.Tag; + +public class TrainDisplayData { + private final BasicTrainDisplayData trainData; + private final List stops; + private final int currentScheduleIndex; + private final double speed; + private final boolean oppositeDirection; + private final TrainExitSide exitSide; + private final boolean isWaitingAtStation; + private final boolean empty; + + private final Cache> stopsFromHere; + private final Cache> stopovers; + + private static final String NBT_TRAIN = "Train"; + private static final String NBT_STOPS = "Stops"; + private static final String NBT_INDEX = "CurrentIndex"; + private static final String NBT_SPEED = "Speed"; + private static final String NBT_OPPOSITE_DIRECTION = "Opposite"; + private static final String NBT_EXIT_SIDE = "ExitSide"; + private static final String NBT_AT_STATION = "AtStation"; + private static final String NBT_IS_EMPTY = "Empty"; + + + private TrainDisplayData() { + this.trainData = BasicTrainDisplayData.empty(); + this.stops = List.of(); + this.currentScheduleIndex = -1; + this.speed = 0; + this.oppositeDirection = false; + this.exitSide = TrainExitSide.UNKNOWN; + this.stopsFromHere = new Cache<>(() -> List.of()); + this.stopovers = new Cache<>(() -> List.of()); + this.isWaitingAtStation = false; + this.empty = true; + } + + public TrainDisplayData( + BasicTrainDisplayData trainData, + List stops, + int currentScheduleIndex, + TrainExitSide exitSide, + double speed, + boolean oppositeDirection, + boolean isWaitingAtStation, + boolean empty + ) { + this.trainData = trainData; + this.stops = stops; + this.currentScheduleIndex = currentScheduleIndex; + this.speed = speed; + this.oppositeDirection = oppositeDirection; + this.exitSide = exitSide; + this.isWaitingAtStation = isWaitingAtStation; + this.stopsFromHere = new Cache<>(() -> { + boolean startFound = false; + List list = new ArrayList<>(); + for (TrainStopDisplayData stop : getAllStops()) { + if (stop.getStationEntryIndex() == getCurrentScheduleIndex()) startFound = true; + if (!startFound) continue; + list.add(stop); + } + return list; + }); + this.stopovers = new Cache<>(() -> getStopsFromCurrentStation().size() > 2 ? getStopsFromCurrentStation().stream().limit(getStopsFromCurrentStation().size() - 1).skip(1).toList() : List.of()); + this.empty = empty; + } + + public static TrainDisplayData empty() { + return new TrainDisplayData(); + } + + /** Server-side only! */ + public static TrainDisplayData of(Train train) throws RuntimeSideException { + if (!ModCommonEvents.hasServer()) { + throw new RuntimeSideException(false); + } + if (!TrainListener.data.containsKey(train.id) || train.runtime.getSchedule() == null) { + return empty(); + } + + MutableSingle sideHolder = new MutableSingle<>(null); + ModCommonEvents.getCurrentServer().ifPresent(x -> { + x.execute(() -> sideHolder.setFirst(TrainUtils.getExitSide(train.navigation.destination))); + while (sideHolder.getFirst() == null) { + try { TimeUnit.MILLISECONDS.sleep(10); } catch (InterruptedException e) {} + } + }); + TrainExitSide side = sideHolder.getFirst() == null ? TrainExitSide.UNKNOWN : sideHolder.getFirst(); + + TrainData data = TrainListener.data.get(train.id); + TrainTravelSection section = data.getCurrentSection(); + return new TrainDisplayData( + BasicTrainDisplayData.of(train.id), + section.isUsable() ? data.getCurrentSection().getPredictions(-1, false).stream().map(x -> TrainStopDisplayData.of(new TrainStop(x))).toList() : List.of(), + data.getCurrentScheduleIndex(), + side, + train.speed, + train.currentlyBackwards, + data.isWaitingAtStation(), + !section.isUsable() + ); + } + + public BasicTrainDisplayData getTrainData() { + return trainData; + } + + public List getAllStops() { + return stops; + } + + public List getStopsFromCurrentStation() { + return stopsFromHere.get(); + } + + public List getStopovers() { + return stopovers.get(); + } + + public double getSpeed() { + return speed; + } + + public boolean isOppositeDirection() { + return oppositeDirection; + } + + public TrainExitSide getNextStopExitSide() { + return exitSide; + } + + public boolean isWaitingAtStation() { + return isWaitingAtStation; + } + + public boolean isEmpty() { + return empty; + } + + public int getCurrentScheduleIndex() { + return currentScheduleIndex; + } + + public Optional getNextStop() { + return !getStopsFromCurrentStation().isEmpty() ? Optional.of(getStopsFromCurrentStation().get(0)) : Optional.empty(); + } + + public Optional getLastStop() { + return !getStopsFromCurrentStation().isEmpty() ? Optional.of(getStopsFromCurrentStation().get(getStopsFromCurrentStation().size() - 1)) : Optional.empty(); + } + + + public CompoundTag toNbt() { + CompoundTag nbt = new CompoundTag(); + + ListTag stopsList = new ListTag(); + stopsList.addAll(getAllStops().stream().map(x -> x.toNbt()).toList()); + + nbt.put(NBT_TRAIN, trainData.toNbt()); + nbt.put(NBT_STOPS, stopsList); + nbt.putInt(NBT_INDEX, currentScheduleIndex); + nbt.putDouble(NBT_SPEED, speed); + nbt.putBoolean(NBT_OPPOSITE_DIRECTION, oppositeDirection); + nbt.putByte(NBT_EXIT_SIDE, exitSide.getAsByte()); + nbt.putBoolean(NBT_AT_STATION, isWaitingAtStation); + nbt.putBoolean(NBT_IS_EMPTY, isEmpty()); + return nbt; + } + + public static TrainDisplayData fromNbt(CompoundTag nbt) { + if (nbt.getBoolean(NBT_IS_EMPTY)) { + return new TrainDisplayData(); + } + return new TrainDisplayData( + BasicTrainDisplayData.fromNbt(nbt.getCompound(NBT_TRAIN)), + nbt.getList(NBT_STOPS, Tag.TAG_COMPOUND).stream().map(x -> TrainStopDisplayData.fromNbt((CompoundTag)x)).toList(), + nbt.getInt(NBT_INDEX), + TrainExitSide.getFromByte(nbt.getByte(NBT_EXIT_SIDE)), + nbt.getDouble(NBT_SPEED), + nbt.getBoolean(NBT_OPPOSITE_DIRECTION), + nbt.getBoolean(NBT_AT_STATION), + nbt.getBoolean(NBT_IS_EMPTY) + ); + } +} diff --git a/common/src/main/java/de/mrjulsen/crn/data/train/portable/TrainStopDisplayData.java b/common/src/main/java/de/mrjulsen/crn/data/train/portable/TrainStopDisplayData.java new file mode 100644 index 00000000..7414ba06 --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/data/train/portable/TrainStopDisplayData.java @@ -0,0 +1,170 @@ +package de.mrjulsen.crn.data.train.portable; + +import java.util.Objects; +import de.mrjulsen.crn.config.ModCommonConfig; +import de.mrjulsen.crn.data.StationTag.StationInfo; +import de.mrjulsen.crn.exceptions.RuntimeSideException; +import de.mrjulsen.crn.data.train.TrainStop; +import de.mrjulsen.crn.event.ModCommonEvents; +import net.minecraft.nbt.CompoundTag; + +/** Contains data about one train arrival at a specific station. This data is used by displays and does not provide any additional functionality. */ +public class TrainStopDisplayData { + private final int stationEntryIndex; + private final String name; + private final long scheduledDepartureTime; + private final long scheduledArrivalTime; + private final long realTimeDepartureTime; + private final long realTimeArrivalTime; + private final String destination; + private final String trainName; + private final StationInfo stationInfo; + + private static final String NBT_STATION_INDEX = "Index"; + private static final String NBT_NAME = "Name"; + private static final String NBT_SCHEDULED_DEPARTURE_TIME = "ScheduledDeparture"; + private static final String NBT_SCHEDULED_ARRIVAL_TIME = "ScheduledArrival"; + private static final String NBT_REAL_TIME_DEPARTURE_TIME = "RealTimeArrival"; + private static final String NBT_REAL_TIME_ARRIVAL_TIME = "RealTimeDeparture"; + private static final String NBT_DESTINATION = "Destination"; + private static final String NBT_TRAIN_NAME = "TrainName"; + private static final String NBT_STATION_INFO = "StationInfo"; + + public TrainStopDisplayData( + int stationEntryIndex, + String name, + long scheduledDepartureTime, + long scheduledArrivalTime, + long realTimeDepartureTime, + long realTimeArrivalTime, + String destination, + String trainName, + StationInfo stationInfo + ) { + this.stationEntryIndex = stationEntryIndex; + this.name = name; + this.scheduledDepartureTime = scheduledDepartureTime; + this.scheduledArrivalTime = scheduledArrivalTime; + this.realTimeDepartureTime = realTimeDepartureTime; + this.realTimeArrivalTime = realTimeArrivalTime; + this.destination = destination; + this.trainName = trainName; + this.stationInfo = stationInfo; + } + + public static TrainStopDisplayData empty() { + return new TrainStopDisplayData(-1, "", 0, 0, 0, 0, "", "", StationInfo.empty()); + } + + /** Server-side only! */ + public static TrainStopDisplayData of(TrainStop stop) throws RuntimeSideException { + if (!ModCommonEvents.hasServer()) { + throw new RuntimeSideException(false); + } + return new TrainStopDisplayData( + stop.getScheduleIndex(), + stop.getRealTimeStationTag().tagName(), + stop.getScheduledDepartureTime(), + stop.getScheduledArrivalTime(), + stop.getRealTimeDepartureTime(), + stop.getRealTimeArrivalTime(), + stop.getDisplayTitle(),//stop.getRealTimeStationTag().stationName(), + stop.getTrainName(), + stop.getRealTimeStationTag().info() + ); + } + + public int getStationEntryIndex() { + return stationEntryIndex; + } + + public String getName() { + return name; + } + + public long getScheduledDepartureTime() { + return scheduledDepartureTime; + } + + public long getScheduledArrivalTime() { + return scheduledArrivalTime; + } + + public long getRealTimeDepartureTime() { + return realTimeDepartureTime; + } + + public long getRealTimeArrivalTime() { + return realTimeArrivalTime; + } + + public String getDestination() { + return destination; + } + + public StationInfo getStationInfo() { + return stationInfo; + } + + public String getTrainName() { + return trainName; + } + + + + + public long getDepartureTimeDeviation() { + return getRealTimeDepartureTime() - getScheduledDepartureTime(); + } + + public long getArrivalTimeDeviation() { + return getRealTimeArrivalTime() - getScheduledArrivalTime(); + } + + public boolean isDepartureDelayed() { + return getDepartureTimeDeviation() > ModCommonConfig.SCHEDULE_DEVIATION_THRESHOLD.get(); + } + + public boolean isArrivalDelayed() { + return getArrivalTimeDeviation() > ModCommonConfig.SCHEDULE_DEVIATION_THRESHOLD.get(); + } + + public CompoundTag toNbt() { + CompoundTag nbt = new CompoundTag(); + + nbt.putInt(NBT_STATION_INDEX, stationEntryIndex); + nbt.putString(NBT_NAME, name); + nbt.putLong(NBT_SCHEDULED_DEPARTURE_TIME, scheduledDepartureTime); + nbt.putLong(NBT_SCHEDULED_ARRIVAL_TIME, scheduledArrivalTime); + nbt.putLong(NBT_REAL_TIME_DEPARTURE_TIME, realTimeDepartureTime); + nbt.putLong(NBT_REAL_TIME_ARRIVAL_TIME, realTimeArrivalTime); + nbt.putString(NBT_DESTINATION, destination); + nbt.putString(NBT_TRAIN_NAME, trainName); + nbt.put(NBT_STATION_INFO, stationInfo.toNbt()); + return nbt; + } + + public static TrainStopDisplayData fromNbt(CompoundTag nbt) { + return new TrainStopDisplayData( + nbt.getInt(NBT_STATION_INDEX), + nbt.getString(NBT_NAME), + nbt.getLong(NBT_SCHEDULED_DEPARTURE_TIME), + nbt.getLong(NBT_SCHEDULED_ARRIVAL_TIME), + nbt.getLong(NBT_REAL_TIME_DEPARTURE_TIME), + nbt.getLong(NBT_REAL_TIME_ARRIVAL_TIME), + nbt.getString(NBT_DESTINATION), + nbt.getString(NBT_TRAIN_NAME), + StationInfo.fromNbt(nbt.getCompound(NBT_STATION_INFO)) + ); + } + + @Override + public final boolean equals(Object obj) { + return obj instanceof TrainStopDisplayData o && o.getDestination().equals(getDestination()) && o.getStationEntryIndex() == getStationEntryIndex(); + } + + @Override + public final int hashCode() { + return Objects.hash(getDestination(), getStationEntryIndex()); + } +} diff --git a/common/src/main/java/de/mrjulsen/crn/debug/DebugOverlay.java b/common/src/main/java/de/mrjulsen/crn/debug/DebugOverlay.java new file mode 100644 index 00000000..a46bebf8 --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/debug/DebugOverlay.java @@ -0,0 +1,127 @@ +package de.mrjulsen.crn.debug; + +import org.lwjgl.glfw.GLFW; + +import com.simibubi.create.content.trains.entity.Carriage; + +import java.lang.StringBuilder; +import java.util.Map; + +import de.mrjulsen.crn.data.train.TrainData; +import de.mrjulsen.crn.data.train.TrainListener; +import de.mrjulsen.crn.data.train.TrainTravelSection; +import de.mrjulsen.crn.mixin.TrainStatusAccessor; +import de.mrjulsen.crn.util.ESpeedUnit; +import de.mrjulsen.crn.util.ModUtils; +import de.mrjulsen.mcdragonlib.client.OverlayManager; +import de.mrjulsen.mcdragonlib.client.gui.DLOverlayScreen; +import de.mrjulsen.mcdragonlib.client.util.Graphics; +import de.mrjulsen.mcdragonlib.client.util.GuiUtils; +import de.mrjulsen.mcdragonlib.core.EAlignment; +import de.mrjulsen.mcdragonlib.util.TextUtils; +import net.minecraft.ChatFormatting; +import net.minecraft.network.chat.Component; + +public class DebugOverlay extends DLOverlayScreen { + + private static long debugOverlayId = -1; + + public static void toggle() { + OverlayManager.remove(debugOverlayId); + if (debugOverlayId == -1) { + debugOverlayId = OverlayManager.add(new DebugOverlay()); + } else { + debugOverlayId = -1; + } + } + + int line = 0; + int trainIndex = 0; + + int lastKey = -1; + + @Override + public void render(Graphics graphics, float partialTicks, int screenWidth, int screenHeight) { + graphics.poseStack().pushPose(); + graphics.poseStack().scale(0.75f, 0.75f, 0.75f); + line = 0; + if (!TrainListener.data.isEmpty()) { + trainIndex %= TrainListener.data.size(); + TrainData data = TrainListener.data.values().stream().skip(trainIndex).findFirst().get(); + drawLine(graphics, data.getTrain().name.getString() + " (" + (data.isPreparing() ? "PREPARING" : (data.isInitialized() ? "READY" : "INITIALIZING")) + ") SessionId: " + data.getSessionId()); + drawLine(graphics, "Display: " + data.getCurrentTravelSection().getDisplayText() + ", Title: " + data.getCurrentTitle() + ", IsDynamic: " + data.isDynamic()); + drawLine(graphics, "Track: " + !((TrainStatusAccessor)data.getTrain().status).crn$track() + ", Conductor: " + !((TrainStatusAccessor)data.getTrain().status).crn$conductor() + ", Navigation: " + !((TrainStatusAccessor)data.getTrain().status).crn$navigation() + ", Paused: " + data.getTrain().runtime.paused + ", Auto: " + data.getTrain().runtime.isAutoSchedule + ", Manual: " + data.isManualControlled + ", Cancelled: " + data.isCancelled()); + drawLine(graphics, "Duration: " + data.getTotalDuration() + ", Ticks: " + data.getTransitTicks() + "/" + data.waitingAtStationTicks() + "/" + data.waitingForSignalTicks + ", Dest: " + (data.getTrain().navigation.destination == null ? "(at station)" : (data.getTrain().navigation.destination.name + ", DestID: " + data.getTrain().navigation.destination.id)) + ", Delay: " + data.getHighestDeviation() + " (-" + data.getDeviationDelayOffset() + "), Status: " + data.debug_statusInfoCount()); + data.getPredictions().forEach(a -> { + drawLine(graphics, TextUtils.text(" - ").append(a.formattedText())); + }); + + StringBuilder builder = new StringBuilder(); + builder.append("( " + data.getCurrentScheduleIndex() + " ): "); + data.getPredictionsChronologically().forEach(a -> { + if (a == null) { + return; + } + builder.append(" > " + a.getStationName() + " (" + a.getRealTimeArrivalTicks() + ")"); + }); + drawLine(graphics, builder.toString()); + + boolean stalled= false; + for (int i = 0; i < data.getTrain().carriages.size(); i++) { + Carriage carriage = data.getTrain().carriages.get(i); + if (carriage.stalled) { + stalled = true; + break; + } + } + + drawLine(graphics, TextUtils.text("Speed: " + (int)ModUtils.calcSpeed(data.getTrain().speed, ESpeedUnit.MS) + " / " + (int)ModUtils.calcSpeed(data.getTrain().targetSpeed, ESpeedUnit.MS) + ", Waiting: " + (data.getTrain().navigation.waitingForSignal == null ? "No" : data.getTrain().navigation.waitingForSignal.getSecond()) + " (" + data.getTrain().navigation.ticksWaitingForSignal + "/" + data.waitingForSignalId + "), Stalled: " + stalled + " (" + data.getTrain().speedBeforeStall + ")").withStyle(data.getTrain().navigation.waitingForSignal == null ? ChatFormatting.RESET : ChatFormatting.RED)); + + for (Map.Entry transitTime : data.currentTransitTime.entrySet()) { + String suffix = "?"; + if (data.transitTimeHistory.containsKey(transitTime.getKey())) { + suffix = String.join(" | ", data.transitTimeHistory.get(transitTime.getKey()).stream().map(x -> String.valueOf(x)).toList()); + } + drawLine(graphics, " - [ " + transitTime.getKey() + " ]: " + transitTime.getValue() + " > " + suffix); + } + + drawLine(graphics, "Sections:"); + for (TrainTravelSection section : data.getSections()) { + drawLine(graphics, " - [ " + section.getScheduleIndex() + " ]: " + section.getDisplayText() + " (" + section.getStartStationName() + " -> " + section.getDestinationStationName() + "), Group: " + (section.getTrainGroup() == null ? "none" : section.getTrainGroup().getGroupName()) + ", Line: " + (section.getTrainLine() == null ? "none" : section.getTrainLine().getLineName()) + ", Include: " + section.shouldIncludeNextStationOfNextSection() + ", Navigable: " + section.isUsable() + ", Next: " + section.nextSection().getScheduleIndex()); + } + } + drawLine(graphics, TextUtils.text("Press K to switch train.").withStyle(ChatFormatting.AQUA)); + + graphics.poseStack().popPose(); + } + + private void drawLine(Graphics graphics, String str) { + drawLine(graphics, TextUtils.text(str)); + } + + private void drawLine(Graphics graphics, Component str) { + int x = 2; + int y = 2; + GuiUtils.fill(graphics, x - 1, y - 1 + line * (getFont().lineHeight + 2), getFont().width(str) + 2, getFont().lineHeight + 2, 0x44000000); + GuiUtils.drawString(graphics, getFont(), x, y + 1 + line * (getFont().lineHeight + 2), str, 0xFFFFFFFF, EAlignment.LEFT, true); + line++; + } + + @Override + public boolean keyPressed(int pKeyCode, int pScanCode, int pModifiers) { + if (lastKey != pKeyCode) { + lastKey = pKeyCode; + } else if (lastKey == pKeyCode){ + lastKey = GLFW.GLFW_KEY_UNKNOWN; + if (pKeyCode == GLFW.GLFW_KEY_K) { + trainIndex++; + return true; + } + } + if (pKeyCode == GLFW.GLFW_KEY_P) { + return true; + } + return super.keyPressed(pKeyCode, pScanCode, pModifiers); + } + +} diff --git a/common/src/main/java/de/mrjulsen/crn/debug/TrainDebugData.java b/common/src/main/java/de/mrjulsen/crn/debug/TrainDebugData.java new file mode 100644 index 00000000..4d77bb39 --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/debug/TrainDebugData.java @@ -0,0 +1,61 @@ +package de.mrjulsen.crn.debug; + +import java.util.UUID; + +import de.mrjulsen.crn.data.train.TrainData; +import net.minecraft.nbt.CompoundTag; + +public record TrainDebugData( + UUID sessionId, + UUID trainId, + String trainName, + int totalDuration, + int predictionsCount, + int predictionsInitialized, + TrainDebugState state +) { + + private static final String NBT_SESSION_ID = "SessionId"; + private static final String NBT_ID = "Id"; + private static final String NBT_NAME = "Name"; + private static final String NBT_DURATION = "Duration"; + private static final String NBT_PREDICTIONS = "Predictions"; + private static final String NBT_INITIALIZED_PREDICTIONS = "InitializedPredictions"; + private static final String NBT_STATE = "State"; + + public CompoundTag toNbt() { + CompoundTag nbt = new CompoundTag(); + nbt.putUUID(NBT_SESSION_ID, sessionId); + nbt.putUUID(NBT_ID, trainId); + nbt.putString(NBT_NAME, trainName); + nbt.putInt(NBT_DURATION, totalDuration); + nbt.putInt(NBT_PREDICTIONS, predictionsCount); + nbt.putInt(NBT_INITIALIZED_PREDICTIONS, predictionsInitialized); + nbt.putByte(NBT_STATE, state.getId()); + return nbt; + } + + public static TrainDebugData fromNbt(CompoundTag nbt) { + return new TrainDebugData( + nbt.getUUID(NBT_SESSION_ID), + nbt.getUUID(NBT_ID), + nbt.getString(NBT_NAME), + nbt.getInt(NBT_DURATION), + nbt.getInt(NBT_PREDICTIONS), + nbt.getInt(NBT_INITIALIZED_PREDICTIONS), + TrainDebugState.getStateById(nbt.getByte(NBT_STATE)) + ); + } + + public static TrainDebugData fromTrain(TrainData train) { + return new TrainDebugData( + train.getSessionId(), + train.getTrainId(), + train.getTrainName(), + train.getTotalDuration(), + train.getPredictionsRaw().size(), + train.debug_initializedStationsCount(), + train.isPreparing() ? TrainDebugState.PREPARING : (train.isInitialized() ? TrainDebugState.READY : TrainDebugState.INITIALIZING) + ); + } +} diff --git a/common/src/main/java/de/mrjulsen/crn/debug/TrainDebugState.java b/common/src/main/java/de/mrjulsen/crn/debug/TrainDebugState.java new file mode 100644 index 00000000..9d2e4649 --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/debug/TrainDebugState.java @@ -0,0 +1,44 @@ +package de.mrjulsen.crn.debug; + +import java.util.Arrays; + +import de.mrjulsen.crn.Constants; +import net.minecraft.util.StringRepresentable; + +public enum TrainDebugState implements StringRepresentable { + PREPARING((byte)-1, "Preparing", Constants.COLOR_DELAYED), + INITIALIZING((byte)1, "Initializing", Constants.COLOR_DELAYED), + READY((byte)0, "Ready", Constants.COLOR_ON_TIME); + + byte id; + String name; + int color; + + TrainDebugState(byte id, String name, int color) { + this.id = id; + this.name = name; + this.color = color; + } + + public byte getId() { + return id; + } + + public int getColor() { + return color; + } + + public String getName() { + return name; + } + + public static TrainDebugState getStateById(int id) { + return Arrays.stream(values()).filter(x -> x.getId() == id).findFirst().orElse(INITIALIZING); + } + + @Override + public String getSerializedName() { + return name; + } + +} diff --git a/common/src/main/java/de/mrjulsen/crn/event/CRNClientEventsRegistryEvent.java b/common/src/main/java/de/mrjulsen/crn/event/CRNClientEventsRegistryEvent.java new file mode 100644 index 00000000..99a37499 --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/event/CRNClientEventsRegistryEvent.java @@ -0,0 +1,12 @@ +package de.mrjulsen.crn.event; + +import de.mrjulsen.crn.event.CRNEventsManager.AbstractBuiltInCRNEvent; + +/** + * Called after all CRN client events have been registered. From this point on, all CRN client events can be accessed without any problems. + */ +public final class CRNClientEventsRegistryEvent extends AbstractBuiltInCRNEvent { + public void run() { + listeners.values().forEach(Runnable::run); + } +} diff --git a/common/src/main/java/de/mrjulsen/crn/event/CRNCommonEventsRegistryEvent.java b/common/src/main/java/de/mrjulsen/crn/event/CRNCommonEventsRegistryEvent.java new file mode 100644 index 00000000..e1c6c475 --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/event/CRNCommonEventsRegistryEvent.java @@ -0,0 +1,12 @@ +package de.mrjulsen.crn.event; + +import de.mrjulsen.crn.event.CRNEventsManager.AbstractBuiltInCRNEvent; + +/** + * Called after all CRN common events have been registered. From this point on, all CRN common events can be accessed without any problems. + */ +public final class CRNCommonEventsRegistryEvent extends AbstractBuiltInCRNEvent { + public void run() { + listeners.values().forEach(Runnable::run); + } +} diff --git a/common/src/main/java/de/mrjulsen/crn/event/CRNEventsManager.java b/common/src/main/java/de/mrjulsen/crn/event/CRNEventsManager.java new file mode 100644 index 00000000..40202a80 --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/event/CRNEventsManager.java @@ -0,0 +1,111 @@ +package de.mrjulsen.crn.event; + +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.function.Supplier; + +import de.mrjulsen.crn.CreateRailwaysNavigator; + +import java.util.HashMap; +import java.util.HashSet; + +public final class CRNEventsManager { + + @SuppressWarnings("rawtypes") + protected static final Map, AbstractCRNEvent> registeredEvents = new HashMap<>(); + + static { + registerEventInternal(new CRNClientEventsRegistryEvent()); + registerEventInternal(new CRNCommonEventsRegistryEvent()); + } + + /** + * Returns the instance of the given Event, if registered, otherwiese a {@code NullPointerException} will be thrown. + * @param The type of the event to return. + * @param clazz The class of the event. + * @return The event, if available. + */ + @SuppressWarnings("unchecked") + public static > T getEvent(Class clazz) { + if (registeredEvents.containsKey(clazz)) { + return (T)registeredEvents.get(clazz); + } + throw new NullPointerException("The Event " + clazz.getName() + " is not registered!"); + } + + @SuppressWarnings("unchecked") + public static > Optional getEventOptional(Class clazz) { + if (registeredEvents.containsKey(clazz)) { + return Optional.ofNullable((T)registeredEvents.get(clazz)); + } + return Optional.empty(); + } + + /** + * Registers a new event. + * @param eventInstance The instance of the new event. + */ + public static void registerEvent(Supplier> eventInstance) { + registerEvent(eventInstance.get()); + } + + /** + * Registers a new event. + * @param eventInstance The instance of the new event. + */ + public static void registerEvent(AbstractCRNEvent eventInstance) { + if (eventInstance instanceof AbstractBuiltInCRNEvent) { + throw new IllegalArgumentException("Cannot register CRN System events!"); + } + registerEventInternal(eventInstance); + } + + static void registerEventInternal(AbstractCRNEvent eventInstance) { + registeredEvents.put(eventInstance.getClass(), eventInstance); + } + + /** + * Removes all registered events. Usually done when stopping the server/world. + */ + public static void clearEvents() { + registeredEvents.values().removeIf(x -> !(x instanceof AbstractBuiltInCRNEvent)); + CreateRailwaysNavigator.LOGGER.info("All events have been closed."); + } + + /** + * Checks if the given event is registered to prevent errors. + * @param The type of the event to return. + * @param clazz The class of the event. + * @return {@true} if the event is registered. + */ + public static > boolean isRegistered(Class clazz) { + return registeredEvents.containsKey(clazz); + } + + + + public static abstract class AbstractCRNEvent implements IEvent { + protected final Map listeners = new HashMap<>(); + protected final Set idsToRemove = new HashSet<>(); + + @Override + public void register(String modid, T event) { + listeners.put(modid, event); + } + + @Override + public void unregister(String modid) { + if (listeners.containsKey(modid)) { + idsToRemove.add(modid); + } + } + + public void tickPost() { + listeners.keySet().removeAll(idsToRemove); + idsToRemove.clear(); + } + } + + static abstract class AbstractBuiltInCRNEvent extends AbstractCRNEvent {} +} diff --git a/common/src/main/java/de/mrjulsen/crn/event/IEvent.java b/common/src/main/java/de/mrjulsen/crn/event/IEvent.java new file mode 100644 index 00000000..a87de698 --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/event/IEvent.java @@ -0,0 +1,16 @@ +package de.mrjulsen.crn.event; + +public interface IEvent { + /** + * Registers a new event listener. + * @param modid The mod ID of the mod listening for this event. This does not have to be the mod ID, but it is recommended to be. + * @param event Your callback which will be executed when the event triggers. + */ + void register(String modid, T event); + + /** + * Removes the event listener. + * @param modid The mod ID of event listener to be removed. + */ + void unregister(String modid); +} diff --git a/common/src/main/java/de/mrjulsen/crn/event/ClientEvents.java b/common/src/main/java/de/mrjulsen/crn/event/ModClientEvents.java similarity index 53% rename from common/src/main/java/de/mrjulsen/crn/event/ClientEvents.java rename to common/src/main/java/de/mrjulsen/crn/event/ModClientEvents.java index cffc17f2..8951f20e 100644 --- a/common/src/main/java/de/mrjulsen/crn/event/ClientEvents.java +++ b/common/src/main/java/de/mrjulsen/crn/event/ModClientEvents.java @@ -4,25 +4,27 @@ import de.mrjulsen.crn.client.ClientWrapper; import de.mrjulsen.crn.client.input.ModKeys; import de.mrjulsen.crn.config.ModClientConfig; -import de.mrjulsen.crn.data.ClientTrainStationSnapshot; -import de.mrjulsen.crn.data.GlobalSettingsManager; -import de.mrjulsen.crn.event.listeners.JourneyListenerManager; -import de.mrjulsen.crn.event.listeners.TrainListener; +import de.mrjulsen.crn.data.SavedRoutesManager; +import de.mrjulsen.crn.data.navigation.ClientTrainListener; +import de.mrjulsen.crn.event.events.DefaultTrainDataRefreshEvent; +import de.mrjulsen.crn.event.events.RouteDetailsActionsEvent; import de.mrjulsen.crn.network.InstanceManager; import de.mrjulsen.crn.registry.ModDisplayTags; import de.mrjulsen.crn.registry.ModExtras; import de.mrjulsen.mcdragonlib.client.OverlayManager; +import de.mrjulsen.mcdragonlib.data.Single.MutableSingle; import dev.architectury.event.events.client.ClientGuiEvent; import dev.architectury.event.events.client.ClientLifecycleEvent; import dev.architectury.event.events.client.ClientPlayerEvent; import dev.architectury.event.events.client.ClientTickEvent; -import net.minecraft.client.Minecraft; -public class ClientEvents { +public class ModClientEvents { + + private static int tickTime; private static int langCheckerTicks = 0; + private static MutableSingle inGame = new MutableSingle(false); - @SuppressWarnings("resource") public static void init() { ClientLifecycleEvent.CLIENT_SETUP.register((mc) -> { @@ -31,46 +33,54 @@ public static void init() { }); ClientTickEvent.CLIENT_POST.register((mc) -> { - JourneyListenerManager.tick(); langCheckerTicks++; if ((langCheckerTicks %= 20) == 0) { ClientWrapper.updateLanguage(ModClientConfig.LANGUAGE.get(), false); } + + if (!inGame.getFirst()) return; + + tickTime++; + if ((tickTime %= 100) == 0) { + ClientTrainListener.tick(() -> { + CRNEventsManager.getEvent(DefaultTrainDataRefreshEvent.class).run(); + }); + } }); ClientLifecycleEvent.CLIENT_LEVEL_LOAD.register((level) -> { - ModExtras.register(); + ModExtras.init(); }); ClientPlayerEvent.CLIENT_PLAYER_JOIN.register((player) -> { - JourneyListenerManager.start(); ClientWrapper.updateLanguage(ModClientConfig.LANGUAGE.get(), true); + + // Register Events + CRNEventsManager.registerEvent(DefaultTrainDataRefreshEvent::new); + CRNEventsManager.registerEvent(RouteDetailsActionsEvent::new); + + CRNEventsManager.getEvent(CRNClientEventsRegistryEvent.class).run(); + + SavedRoutesManager.pull(true, null); + + inGame.setFirst(true); }); ClientPlayerEvent.CLIENT_PLAYER_QUIT.register((player) -> { - InstanceManager.removeRouteOverlay(); + inGame.setFirst(false); + OverlayManager.clear(); CreateRailwaysNavigator.LOGGER.info("Removed all overlays."); - - ClientTrainStationSnapshot.getInstance().dispose(); - InstanceManager.clearAll(); - JourneyListenerManager.stop(); - GlobalSettingsManager.close(); + SavedRoutesManager.removeAllRoutes(); + CRNEventsManager.clearEvents(); + InstanceManager.removeRouteOverlay(); + ClientTrainListener.clear(); }); ClientGuiEvent.DEBUG_TEXT_LEFT.register((texts) -> { - boolean b1 = TrainListener.getInstance() != null; - boolean b2 = ClientTrainStationSnapshot.getInstance() != null; - - if (Minecraft.getInstance().options.renderDebug) { - texts.add(String.format("CRN | T: %s/%s, JL: %s, O: %s, I: %s", - b1 ? TrainListener.getInstance().getListeningTrainCount() : (b2 ? ClientTrainStationSnapshot.getInstance().getListeningTrainCount() : 0), - b1 ? TrainListener.getInstance().getTotalTrainCount() : (b2 ? ClientTrainStationSnapshot.getInstance().getTrainCount() : 0), - JourneyListenerManager.getInstance() == null ? 0 : JourneyListenerManager.getInstance().getCacheSize(), - OverlayManager.count(), - InstanceManager.getInstancesCountString() - )); - } + texts.add(String.format("CRN | RL: %s", + ClientTrainListener.debug_registeredListenersCount() + )); }); } } diff --git a/common/src/main/java/de/mrjulsen/crn/event/ModCommonEvents.java b/common/src/main/java/de/mrjulsen/crn/event/ModCommonEvents.java new file mode 100644 index 00000000..992c9244 --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/event/ModCommonEvents.java @@ -0,0 +1,121 @@ +package de.mrjulsen.crn.event; + +import java.util.Optional; + +import de.mrjulsen.crn.CreateRailwaysNavigator; +import de.mrjulsen.crn.block.display.AdvancedDisplayTarget; +import de.mrjulsen.crn.cmd.DebugCommand; +import de.mrjulsen.crn.data.storage.GlobalSettings; +import de.mrjulsen.crn.data.train.TrainListener; +import de.mrjulsen.crn.event.events.CreateTrainPredictionEvent; +import de.mrjulsen.crn.event.events.GlobalTrainDisplayDataRefreshEventPost; +import de.mrjulsen.crn.event.events.GlobalTrainDisplayDataRefreshEventPre; +import de.mrjulsen.crn.event.events.ScheduleResetEvent; +import de.mrjulsen.crn.event.events.SubmitTrainPredictionsEvent; +import de.mrjulsen.crn.event.events.TotalDurationTimeChangedEvent; +import de.mrjulsen.crn.event.events.TrainArrivalAndDepartureEvent; +import de.mrjulsen.crn.event.events.TrainDestinationChangedEvent; +import de.mrjulsen.crn.registry.ModExtras; +import de.mrjulsen.crn.web.SimpleWebServer; +import de.mrjulsen.mcdragonlib.internal.ClientWrapper; +import dev.architectury.event.events.common.CommandRegistrationEvent; +import dev.architectury.event.events.common.LifecycleEvent; +import dev.architectury.event.events.common.TickEvent; +import net.minecraft.server.MinecraftServer; +import net.minecraft.world.level.Level; + +public class ModCommonEvents { + + private static long lastTicks = 0; + private static MinecraftServer currentServer; + + + public static void init() { + + LifecycleEvent.SETUP.register(() -> { + CreateRailwaysNavigator.LOGGER.info("Welcome to the CREATE RAILWAYS NAVIGATOR mod by MRJULSEN."); + }); + + LifecycleEvent.SERVER_LEVEL_LOAD.register((level) -> { + ModExtras.init(); + }); + + LifecycleEvent.SERVER_STARTED.register((server) -> { + currentServer = server; + // Register Events + CRNEventsManager.registerEvent(GlobalTrainDisplayDataRefreshEventPost::new); + CRNEventsManager.registerEvent(GlobalTrainDisplayDataRefreshEventPre::new); + CRNEventsManager.registerEvent(TrainDestinationChangedEvent::new); + CRNEventsManager.registerEvent(TrainArrivalAndDepartureEvent::new); + CRNEventsManager.registerEvent(SubmitTrainPredictionsEvent::new); + CRNEventsManager.registerEvent(CreateTrainPredictionEvent::new); + CRNEventsManager.registerEvent(ScheduleResetEvent::new); + CRNEventsManager.registerEvent(TotalDurationTimeChangedEvent::new); + + CRNEventsManager.getEvent(CRNCommonEventsRegistryEvent.class).run(); + + TrainListener.start(); + AdvancedDisplayTarget.start(); + + try { + SimpleWebServer.start(); + } catch (Exception e) { + e.printStackTrace(); + } + }); + + LifecycleEvent.SERVER_STOPPING.register((server) -> { + GlobalSettings.clearInstance(); + + TrainListener.stop(); + AdvancedDisplayTarget.stop(); + CRNEventsManager.clearEvents(); + + SimpleWebServer.stop(); + }); + + LifecycleEvent.SERVER_STOPPED.register((server) -> { + currentServer = null; + }); + + LifecycleEvent.SERVER_STARTING.register((server) -> { + }); + + TickEvent.SERVER_POST.register((server) -> { + if (ModCommonEvents.hasServer()) { + long currentTicks = ModCommonEvents.getPhysicalLevel().dayTime(); + long diff = currentTicks - lastTicks; + if (Math.abs(diff) > 1) { + TrainListener.data.values().forEach(x -> x.shiftTime(diff)); + CreateRailwaysNavigator.LOGGER.info("All times have been corrected: " + (diff) + " Ticks"); + } + lastTicks = currentTicks; + } + + TrainListener.tick(); + }); + + CommandRegistrationEvent.EVENT.register((dispatcher, buildContext, selection) -> { + DebugCommand.register(dispatcher, selection); + }); + + LifecycleEvent.SERVER_LEVEL_SAVE.register((server) -> { + TrainListener.save(); + if (GlobalSettings.hasInstance()) GlobalSettings.getInstance().save(); + }); + } + + public static boolean hasServer() { + return currentServer != null; + } + + public static Optional getCurrentServer() { + return Optional.ofNullable(currentServer); + } + + public static Level getPhysicalLevel() { + return hasServer() ? getCurrentServer().get().overworld() : ClientWrapper.getClientLevel(); + } +} + + diff --git a/common/src/main/java/de/mrjulsen/crn/event/ModEvents.java b/common/src/main/java/de/mrjulsen/crn/event/ModEvents.java deleted file mode 100644 index 1190b1fd..00000000 --- a/common/src/main/java/de/mrjulsen/crn/event/ModEvents.java +++ /dev/null @@ -1,53 +0,0 @@ -package de.mrjulsen.crn.event; - -import de.mrjulsen.crn.CreateRailwaysNavigator; -import de.mrjulsen.crn.data.GlobalSettingsManager; -import de.mrjulsen.crn.event.listeners.TrainListener; -import de.mrjulsen.crn.network.packets.stc.TimeCorrectionPacket; -import de.mrjulsen.crn.registry.ModExtras; -import dev.architectury.event.events.common.LifecycleEvent; -import dev.architectury.event.events.common.TickEvent; -import net.minecraft.server.level.ServerLevel; -import net.minecraft.server.level.ServerPlayer; - -public class ModEvents { - - private static long lastTicks = 0; - private static ServerLevel serverLevel; - - public static void init() { - - LifecycleEvent.SETUP.register(() -> { - CreateRailwaysNavigator.LOGGER.info("Welcome to the CREATE RAILWAYS NAVIGATOR mod by MRJULSEN."); - }); - - LifecycleEvent.SERVER_LEVEL_LOAD.register((level) -> { - ModExtras.register(); - }); - - LifecycleEvent.SERVER_STARTED.register((server) -> { - TrainListener.start(server.overworld()); - serverLevel = server.overworld(); - }); - - LifecycleEvent.SERVER_STOPPING.register((server) -> { - TrainListener.stop(); - }); - - LifecycleEvent.SERVER_STOPPED.register((server) -> { - GlobalSettingsManager.close(); - }); - - TickEvent.SERVER_POST.register((server) -> { - if (serverLevel != null) { - long currentTicks = serverLevel.dayTime(); - if (Math.abs(currentTicks - lastTicks) > 1) { - serverLevel.players().stream().filter(p -> p instanceof ServerPlayer).forEach(x -> CreateRailwaysNavigator.net().CHANNEL.sendToPlayer(x, new TimeCorrectionPacket((int)(currentTicks - lastTicks)))); - } - lastTicks = currentTicks; - } - }); - } -} - - diff --git a/common/src/main/java/de/mrjulsen/crn/event/events/CreateTrainPredictionEvent.java b/common/src/main/java/de/mrjulsen/crn/event/events/CreateTrainPredictionEvent.java new file mode 100644 index 00000000..b390b9c3 --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/event/events/CreateTrainPredictionEvent.java @@ -0,0 +1,22 @@ +package de.mrjulsen.crn.event.events; + +import java.util.Map; + +import com.simibubi.create.content.trains.display.GlobalTrainDisplayData.TrainDeparturePrediction; +import com.simibubi.create.content.trains.entity.Train; +import com.simibubi.create.content.trains.schedule.ScheduleRuntime; + +import de.mrjulsen.crn.event.CRNEventsManager.AbstractCRNEvent; +import de.mrjulsen.crn.data.schedule.instruction.IStationPredictableInstruction; + +public final class CreateTrainPredictionEvent extends AbstractCRNEvent { + public void run(Train train, ScheduleRuntime schedule, Map, IStationPredictableInstruction> predictables, int index, int stayDuration, int minStayDuration, TrainDeparturePrediction prediction) { + listeners.values().forEach(x -> x.run(train, schedule, predictables, index, stayDuration, minStayDuration, prediction)); + tickPost(); + } + + @FunctionalInterface + public static interface ICreateTrainPredictionEventData { + void run(Train train, ScheduleRuntime schedule, Map, IStationPredictableInstruction> predictables, int index, int stayDuration, int minStayDuration, TrainDeparturePrediction prediction); + } +} diff --git a/common/src/main/java/de/mrjulsen/crn/event/events/DefaultTrainDataRefreshEvent.java b/common/src/main/java/de/mrjulsen/crn/event/events/DefaultTrainDataRefreshEvent.java new file mode 100644 index 00000000..193a2526 --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/event/events/DefaultTrainDataRefreshEvent.java @@ -0,0 +1,11 @@ +package de.mrjulsen.crn.event.events; + +import de.mrjulsen.crn.event.CRNEventsManager.AbstractCRNEvent; + +public class DefaultTrainDataRefreshEvent extends AbstractCRNEvent { + public void run() { + listeners.values().forEach(Runnable::run); + tickPost(); + } +} + diff --git a/common/src/main/java/de/mrjulsen/crn/event/events/GlobalTrainDisplayDataRefreshEventPost.java b/common/src/main/java/de/mrjulsen/crn/event/events/GlobalTrainDisplayDataRefreshEventPost.java new file mode 100644 index 00000000..597f11f1 --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/event/events/GlobalTrainDisplayDataRefreshEventPost.java @@ -0,0 +1,10 @@ +package de.mrjulsen.crn.event.events; + +import de.mrjulsen.crn.event.CRNEventsManager.AbstractCRNEvent; + +public final class GlobalTrainDisplayDataRefreshEventPost extends AbstractCRNEvent { + public void run() { + listeners.values().forEach(Runnable::run); + tickPost(); + } +} diff --git a/common/src/main/java/de/mrjulsen/crn/event/events/GlobalTrainDisplayDataRefreshEventPre.java b/common/src/main/java/de/mrjulsen/crn/event/events/GlobalTrainDisplayDataRefreshEventPre.java new file mode 100644 index 00000000..2db2d367 --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/event/events/GlobalTrainDisplayDataRefreshEventPre.java @@ -0,0 +1,10 @@ +package de.mrjulsen.crn.event.events; + +import de.mrjulsen.crn.event.CRNEventsManager.AbstractCRNEvent; + +public final class GlobalTrainDisplayDataRefreshEventPre extends AbstractCRNEvent { + public void run() { + listeners.values().forEach(Runnable::run); + tickPost(); + } +} diff --git a/common/src/main/java/de/mrjulsen/crn/event/events/RouteDetailsActionsEvent.java b/common/src/main/java/de/mrjulsen/crn/event/events/RouteDetailsActionsEvent.java new file mode 100644 index 00000000..23821279 --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/event/events/RouteDetailsActionsEvent.java @@ -0,0 +1,23 @@ +package de.mrjulsen.crn.event.events; + +import java.util.Collection; +import java.util.List; + +import de.mrjulsen.crn.client.gui.widgets.routedetails.RoutePartWidget.RoutePartDetailsActionBuilder; +import de.mrjulsen.crn.data.navigation.ClientRoute; +import de.mrjulsen.crn.data.navigation.ClientRoutePart; +import de.mrjulsen.crn.event.CRNEventsManager.AbstractCRNEvent; + +public class RouteDetailsActionsEvent extends AbstractCRNEvent { + + public Collection run(ClientRoute route, ClientRoutePart part, boolean expanded) { + List res = listeners.values().stream().flatMap(x -> x.run(route, part, expanded).stream()).toList(); + tickPost(); + return res; + } + + @FunctionalInterface + public static interface IRouteDetailsActionsEventData { + Collection run(ClientRoute route, ClientRoutePart part, boolean expanded); + } +} diff --git a/common/src/main/java/de/mrjulsen/crn/event/events/ScheduleResetEvent.java b/common/src/main/java/de/mrjulsen/crn/event/events/ScheduleResetEvent.java new file mode 100644 index 00000000..69dfaec8 --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/event/events/ScheduleResetEvent.java @@ -0,0 +1,17 @@ +package de.mrjulsen.crn.event.events; + +import com.simibubi.create.content.trains.entity.Train; + +import de.mrjulsen.crn.event.CRNEventsManager.AbstractCRNEvent; + +public final class ScheduleResetEvent extends AbstractCRNEvent { + public void run(Train train, boolean soft) { + listeners.values().forEach(x -> x.run(train, soft)); + tickPost(); + } + + @FunctionalInterface + public static interface IScheduleResetEventData { + void run(Train train, boolean soft); + } +} diff --git a/common/src/main/java/de/mrjulsen/crn/event/events/SubmitTrainPredictionsEvent.java b/common/src/main/java/de/mrjulsen/crn/event/events/SubmitTrainPredictionsEvent.java new file mode 100644 index 00000000..ea9afd33 --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/event/events/SubmitTrainPredictionsEvent.java @@ -0,0 +1,20 @@ +package de.mrjulsen.crn.event.events; + +import java.util.Collection; + +import com.simibubi.create.content.trains.display.GlobalTrainDisplayData.TrainDeparturePrediction; +import com.simibubi.create.content.trains.entity.Train; + +import de.mrjulsen.crn.event.CRNEventsManager.AbstractCRNEvent; + +public final class SubmitTrainPredictionsEvent extends AbstractCRNEvent { + public void run(Train train, Collection predictions, int entryCount, int accumulatedTime, int current) { + listeners.values().forEach(x -> x.run(train, predictions, entryCount, accumulatedTime, current)); + tickPost(); + } + + @FunctionalInterface + public static interface ISubmitTrainPredictionsEventData { + void run(Train train, Collection predictions, int entryCount, int accumulatedTime, int current); + } +} diff --git a/common/src/main/java/de/mrjulsen/crn/event/events/TotalDurationTimeChangedEvent.java b/common/src/main/java/de/mrjulsen/crn/event/events/TotalDurationTimeChangedEvent.java new file mode 100644 index 00000000..bc5ffd36 --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/event/events/TotalDurationTimeChangedEvent.java @@ -0,0 +1,16 @@ +package de.mrjulsen.crn.event.events; + +import com.simibubi.create.content.trains.entity.Train; +import de.mrjulsen.crn.event.CRNEventsManager.AbstractCRNEvent; + +public final class TotalDurationTimeChangedEvent extends AbstractCRNEvent { + public void run(Train train, long oldTotalDuration, long totalDuration) { + listeners.values().forEach(x -> x.run(train, oldTotalDuration, totalDuration)); + tickPost(); + } + + @FunctionalInterface + public static interface ITotalDurationTimeChangedEventData { + void run(Train train, long oldTotalDuration, long totalDuration); + } +} diff --git a/common/src/main/java/de/mrjulsen/crn/event/events/TrainArrivalAndDepartureEvent.java b/common/src/main/java/de/mrjulsen/crn/event/events/TrainArrivalAndDepartureEvent.java new file mode 100644 index 00000000..40bcde92 --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/event/events/TrainArrivalAndDepartureEvent.java @@ -0,0 +1,23 @@ +package de.mrjulsen.crn.event.events; + +import com.simibubi.create.content.trains.entity.Train; +import com.simibubi.create.content.trains.station.GlobalStation; + +import de.mrjulsen.crn.event.CRNEventsManager.AbstractCRNEvent; + +public final class TrainArrivalAndDepartureEvent extends AbstractCRNEvent { + public void run(Train train, GlobalStation current, boolean arrival) { + listeners.values().forEach(x -> x.run(train, current, arrival)); + tickPost(); + } + + @FunctionalInterface + public static interface ITrainApprochEventData { + /** + * @param train The current train. + * @param current The current station. + * @param departure {@code true} if the train is arriving at the current station, {@code false} when leaving. + */ + void run(Train train, GlobalStation current, boolean arrival); + } +} diff --git a/common/src/main/java/de/mrjulsen/crn/event/events/TrainDestinationChangedEvent.java b/common/src/main/java/de/mrjulsen/crn/event/events/TrainDestinationChangedEvent.java new file mode 100644 index 00000000..e6bf0b41 --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/event/events/TrainDestinationChangedEvent.java @@ -0,0 +1,18 @@ +package de.mrjulsen.crn.event.events; + +import com.simibubi.create.content.trains.entity.Train; +import com.simibubi.create.content.trains.station.GlobalStation; + +import de.mrjulsen.crn.event.CRNEventsManager.AbstractCRNEvent; + +public final class TrainDestinationChangedEvent extends AbstractCRNEvent { + public void run(Train train, GlobalStation current, GlobalStation next, int nextIndex) { + listeners.values().forEach(x -> x.run(train, current, next, nextIndex)); + tickPost(); + } + + @FunctionalInterface + public static interface ITrainDepartureEventData { + void run(Train train, GlobalStation current, GlobalStation next, int nextIndex); + } +} diff --git a/common/src/main/java/de/mrjulsen/crn/event/listeners/IJourneyListenerClient.java b/common/src/main/java/de/mrjulsen/crn/event/listeners/IJourneyListenerClient.java deleted file mode 100644 index 973748bc..00000000 --- a/common/src/main/java/de/mrjulsen/crn/event/listeners/IJourneyListenerClient.java +++ /dev/null @@ -1,7 +0,0 @@ -package de.mrjulsen.crn.event.listeners; - -import java.util.UUID; - -public interface IJourneyListenerClient { - UUID getJourneyListenerClientId(); -} diff --git a/common/src/main/java/de/mrjulsen/crn/event/listeners/JourneyListener.java b/common/src/main/java/de/mrjulsen/crn/event/listeners/JourneyListener.java deleted file mode 100644 index 6c3d59c4..00000000 --- a/common/src/main/java/de/mrjulsen/crn/event/listeners/JourneyListener.java +++ /dev/null @@ -1,881 +0,0 @@ -package de.mrjulsen.crn.event.listeners; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.UUID; -import java.util.function.Consumer; -import java.util.stream.Collectors; - -import de.mrjulsen.crn.CreateRailwaysNavigator; -import de.mrjulsen.crn.client.lang.ELanguage; -import de.mrjulsen.crn.config.ModClientConfig; -import de.mrjulsen.crn.data.DeparturePrediction.SimpleDeparturePrediction; -import de.mrjulsen.crn.data.SimpleRoute; -import de.mrjulsen.crn.data.SimpleRoute.StationEntry; -import de.mrjulsen.crn.data.SimpleRoute.StationTag; -import de.mrjulsen.crn.data.TrainStationAlias.StationInfo; -import de.mrjulsen.crn.network.InstanceManager; -import de.mrjulsen.crn.network.packets.cts.RealtimeRequestPacket; -import de.mrjulsen.mcdragonlib.DragonLib; -import de.mrjulsen.mcdragonlib.util.TextUtils; -import de.mrjulsen.mcdragonlib.util.TimeUtils; -import net.minecraft.client.Minecraft; -import net.minecraft.network.chat.Component; - -public class JourneyListener { - - public static final int ID = 1; - private static final int REALTIME_REFRESH_TIME = 100; - - private final SimpleRoute route; - private int stationIndex = 0; - private State currentState = State.BEFORE_JOURNEY; - private int realTimeRefreshTimer = 0; - private boolean isStarted; - - private static final String keyJourneyBegins = "gui.createrailwaysnavigator.route_overview.journey_begins"; - private static final String keyJourneyBeginsWithPlatform = "gui.createrailwaysnavigator.route_overview.journey_begins_with_platform"; - private static final String keyNextStop = "gui.createrailwaysnavigator.route_overview.next_stop"; - private static final String keyTransfer = "gui.createrailwaysnavigator.route_overview.transfer"; - private static final String keyTransferWithPlatform = "gui.createrailwaysnavigator.route_overview.transfer_with_platform"; - private static final String keyAfterJourney = "gui.createrailwaysnavigator.route_overview.after_journey"; - private static final String keyJourneyInterruptedTitle = "gui.createrailwaysnavigator.route_overview.train_canceled_title"; - private static final String keyJourneyInterrupted = "gui.createrailwaysnavigator.route_overview.train_canceled_info"; - private static final String keyConnectionMissedInfo = "gui.createrailwaysnavigator.route_overview.connection_missed_info"; - private static final String keyOptionsText = "gui.createrailwaysnavigator.route_overview.options"; - private static final String keyKeybindOptions = "key.createrailwaysnavigator.route_overlay_options"; - private static final String keyNotificationJourneyBeginsTitle = "gui.createrailwaysnavigator.route_overview.notification.journey_begins.title"; - private static final String keyNotificationJourneyBegins = "gui.createrailwaysnavigator.route_overview.notification.journey_begins"; - private static final String keyNotificationJourneyBeginsWithPlatform = "gui.createrailwaysnavigator.route_overview.notification.journey_begins_with_platform"; - private static final String keyNotificationPlatformChangedTitle = "gui.createrailwaysnavigator.route_overview.notification.platform_changed.title"; - private static final String keyNotificationPlatformChanged = "gui.createrailwaysnavigator.route_overview.notification.platform_changed"; - private static final String keyNotificationTrainDelayedTitle = "gui.createrailwaysnavigator.route_overview.notification.train_delayed.title"; - private static final String keyNotificationTrainDelayed = "gui.createrailwaysnavigator.route_overview.notification.train_delayed"; - private static final String keyNotificationTransferTitle = "gui.createrailwaysnavigator.route_overview.notification.transfer.title"; - private static final String keyNotificationTransfer = "gui.createrailwaysnavigator.route_overview.notification.transfer"; - private static final String keyNotificationTransferWithPlatform = "gui.createrailwaysnavigator.route_overview.notification.transfer_with_platform"; - private static final String keyNotificationConnectionEndangeredTitle = "gui.createrailwaysnavigator.route_overview.notification.connection_endangered.title"; - private static final String keyNotificationConnectionEndangered = "gui.createrailwaysnavigator.route_overview.notification.connection_endangered"; - private static final String keyNotificationConnectionMissedTitle = "gui.createrailwaysnavigator.route_overview.notification.connection_missed.title"; - private static final String keyNotificationConnectionMissed = "gui.createrailwaysnavigator.route_overview.notification.connection_missed"; - private static final String keyNotificationJourneyCompletedTitle = "gui.createrailwaysnavigator.route_overview.notification.journey_completed.title"; - private static final String keyNotificationJourneyCompleted = "gui.createrailwaysnavigator.route_overview.notification.journey_completed"; - - // Events - public static enum TransferState { - NONE, - DEFAULT, - CONNECTION_MISSED, - CONNECTION_CANCELLED - } - - public static record NotificationData(State state, Component title, Component text) {} - public static record JourneyBeginData(State state, Component infoText, String narratorText) {} - public static record JourneyInterruptData(State state, Component title, Component text, String narratorText) {} - public static record ReachNextStopData(State state, Component infoText, String narratorText, TransferState transferState) {} - public static record ContinueData(State state) {} - public static record FinishJourneyData(State state, Component infoText) {} - public static record AnnounceNextStopData(State state, Component infoText, String narratorText, boolean isTransfer) {} - - private Map> onUpdateRealtime = new HashMap<>(); - private Map>> onInfoTextChange = new HashMap<>(); - private Map>> onNotificationSend = new HashMap<>(); - private Map>> onStateChange = new HashMap<>(); - private Map>> onNarratorAnnounce = new HashMap<>(); - private Map>> onJourneyBegin = new HashMap<>(); - private Map>> onJourneyInterrupt = new HashMap<>(); - private Map>> onReachNextStop = new HashMap<>(); - private Map>> onContinue = new HashMap<>(); - private Map>> onFinishJourney = new HashMap<>(); - private Map>> onAnnounceNextStop = new HashMap<>(); - - private Component lastInfoText = TextUtils.empty(); - private NotificationData lastNotification = null; - private String lastNarratorText = ""; - - private boolean beginAnnounced = false; - - public JourneyListener(SimpleRoute route) { - this.route = route; - } - - public static JourneyListener listenTo(SimpleRoute route) { - return new JourneyListener(route); - } - - public JourneyListener start() { - Component text = currentStation().getInfo().platform() == null || currentStation().getInfo().platform().isBlank() ? - ELanguage.translate(keyJourneyBegins, - currentStation().getTrain().trainName(), - currentStation().getTrain().scheduleTitle(), - TimeUtils.parseTime((int)((currentStation().getEstimatedTimeWithThreshold() + DragonLib.DAYTIME_SHIFT) % DragonLib.TICKS_PER_DAY), ModClientConfig.TIME_FORMAT.get()) - ) : - ELanguage.translate(keyJourneyBeginsWithPlatform, - currentStation().getTrain().trainName(), - currentStation().getTrain().scheduleTitle(), - TimeUtils.parseTime((int)((currentStation().getEstimatedTimeWithThreshold() + DragonLib.DAYTIME_SHIFT) % DragonLib.TICKS_PER_DAY), ModClientConfig.TIME_FORMAT.get()), - currentStation().getInfo().platform() - ); - String narratorText = text.getString() + ". " + ELanguage.translate(keyOptionsText, TextUtils.keybind(keyKeybindOptions)).getString(); - - onJourneyBegin.values().forEach(x -> { - if (x.isPresent()) { - x.get().accept(new JourneyBeginData(currentState, text, narratorText)); - } - }); - setInfoText(text); - setNarratorText(narratorText); - - isStarted = true; - requestRealtimeData(); - return this; - } - - public JourneyListener stop() { - isStarted = false; - return this; - } - - public JourneyListener registerOnUpdateRealtime(IJourneyListenerClient client, Runnable m) { - unregisterOnUpdateRealtime(client); - this.onUpdateRealtime.put(client.getJourneyListenerClientId(), Optional.of(m)); - return this; - } - - public JourneyListener registerOnStateChange(IJourneyListenerClient client, Consumer m) { - unregisterOnUpdateRealtime(client); - this.onStateChange.put(client.getJourneyListenerClientId(), Optional.of(m)); - return this; - } - - public JourneyListener registerOnNarratorAnnounce(IJourneyListenerClient client, Consumer m) { - unregisterOnNarratorAnnounce(client); - this.onNarratorAnnounce.put(client.getJourneyListenerClientId(), Optional.of(m)); - return this; - } - - public JourneyListener registerOnInfoTextChange(IJourneyListenerClient client, Consumer m) { - unregisterOnInfoTextChange(client); - this.onInfoTextChange.put(client.getJourneyListenerClientId(), Optional.of(m)); - return this; - } - - public JourneyListener registerOnNotification(IJourneyListenerClient client, Consumer m) { - unregisterOnNotification(client); - this.onNotificationSend.put(client.getJourneyListenerClientId(), Optional.of(m)); - return this; - } - - public JourneyListener registerOnReachNextStop(IJourneyListenerClient client, Consumer m) { - unregisterOnReachNextStop(client); - this.onReachNextStop.put(client.getJourneyListenerClientId(), Optional.of(m)); - return this; - } - - public JourneyListener registerOnContinueWithJourneyAfterStop(IJourneyListenerClient client, Consumer m) { - unregisterOnContinueWithJourneyAfterStop(client); - this.onContinue.put(client.getJourneyListenerClientId(), Optional.of(m)); - return this; - } - - public JourneyListener registerOnFinishJourney(IJourneyListenerClient client, Consumer m) { - unregisterOnFinishJourney(client); - this.onFinishJourney.put(client.getJourneyListenerClientId(), Optional.of(m)); - return this; - } - - public JourneyListener registerOnAnnounceNextStop(IJourneyListenerClient client, Consumer m) { - unregisterOnAnnounceNextStop(client); - this.onAnnounceNextStop.put(client.getJourneyListenerClientId(), Optional.of(m)); - return this; - } - - public JourneyListener registerOnJourneyBegin(IJourneyListenerClient client, Consumer m) { - unregisterOnJourneyBegin(client); - this.onJourneyBegin.put(client.getJourneyListenerClientId(), Optional.of(m)); - return this; - } - - public JourneyListener registerOnJourneyInterrupt(IJourneyListenerClient client, Consumer m) { - unregisterOnJourneyInterrupt(client); - this.onJourneyInterrupt.put(client.getJourneyListenerClientId(), Optional.of(m)); - return this; - } - - - public void unregisterOnUpdateRealtime(IJourneyListenerClient client) { - if (onUpdateRealtime.containsKey(client.getJourneyListenerClientId())) { - onUpdateRealtime.remove(client.getJourneyListenerClientId()); - } - } - - public void unregisterOnStateChange(IJourneyListenerClient client) { - if (onStateChange.containsKey(client.getJourneyListenerClientId())) { - onStateChange.remove(client.getJourneyListenerClientId()); - } - } - - public void unregisterOnNarratorAnnounce(IJourneyListenerClient client) { - if (onNarratorAnnounce.containsKey(client.getJourneyListenerClientId())) { - onNarratorAnnounce.remove(client.getJourneyListenerClientId()); - } - } - - public void unregisterOnNotification(IJourneyListenerClient client) { - if (onNotificationSend.containsKey(client.getJourneyListenerClientId())) { - onNotificationSend.remove(client.getJourneyListenerClientId()); - } - } - - public void unregisterOnInfoTextChange(IJourneyListenerClient client) { - if (onInfoTextChange.containsKey(client.getJourneyListenerClientId())) { - onInfoTextChange.remove(client.getJourneyListenerClientId()); - } - } - - public void unregisterOnReachNextStop(IJourneyListenerClient client) { - if (onReachNextStop.containsKey(client.getJourneyListenerClientId())) { - onReachNextStop.remove(client.getJourneyListenerClientId()); - } - } - - public void unregisterOnContinueWithJourneyAfterStop(IJourneyListenerClient client) { - if (onContinue.containsKey(client.getJourneyListenerClientId())) { - onContinue.remove(client.getJourneyListenerClientId()); - } - } - - public void unregisterOnFinishJourney(IJourneyListenerClient client) { - if (onFinishJourney.containsKey(client.getJourneyListenerClientId())) { - onFinishJourney.remove(client.getJourneyListenerClientId()); - } - } - - public void unregisterOnAnnounceNextStop(IJourneyListenerClient client) { - if (onAnnounceNextStop.containsKey(client.getJourneyListenerClientId())) { - onAnnounceNextStop.remove(client.getJourneyListenerClientId()); - } - } - - public void unregisterOnJourneyBegin(IJourneyListenerClient client) { - if (onJourneyBegin.containsKey(client.getJourneyListenerClientId())) { - onJourneyBegin.remove(client.getJourneyListenerClientId()); - } - } - - public void unregisterOnJourneyInterrupt(IJourneyListenerClient client) { - if (onJourneyInterrupt.containsKey(client.getJourneyListenerClientId())) { - onJourneyInterrupt.remove(client.getJourneyListenerClientId()); - } - } - - public void unregister(IJourneyListenerClient client) { - unregisterOnAnnounceNextStop(client); - unregisterOnContinueWithJourneyAfterStop(client); - unregisterOnFinishJourney(client); - unregisterOnInfoTextChange(client); - unregisterOnJourneyBegin(client); - unregisterOnNarratorAnnounce(client); - unregisterOnReachNextStop(client); - unregisterOnStateChange(client); - unregisterOnUpdateRealtime(client); - unregisterOnNotification(client); - unregisterOnJourneyInterrupt(client); - } - - - - - public void tick() { - if (!isStarted) { - return; - } - - if (currentState != State.AFTER_JOURNEY && currentState != State.JOURNEY_INTERRUPTED) { - realTimeRefreshTimer++; - if (realTimeRefreshTimer > REALTIME_REFRESH_TIME) { - realTimeRefreshTimer = 0; - requestRealtimeData(); - } - } - - if (!beginAnnounced && firstStation().getEstimatedTime() - ModClientConfig.NEXT_STOP_ANNOUNCEMENT.get() < Minecraft.getInstance().level.getDayTime()) { - Component title = ELanguage.translate(keyNotificationJourneyBeginsTitle, - lastStation().getStationName() - ); - Component description = currentStation().getInfo().platform() == null || currentStation().getInfo().platform().isBlank() ? - ELanguage.translate(keyNotificationJourneyBegins, - currentStation().getTrain().trainName(), - currentStation().getTrain().scheduleTitle(), - TimeUtils.parseTime((int)((currentStation().getEstimatedTimeWithThreshold() + DragonLib.DAYTIME_SHIFT) % DragonLib.TICKS_PER_DAY), ModClientConfig.TIME_FORMAT.get()) - ) - : - ELanguage.translate(keyNotificationJourneyBeginsWithPlatform, - currentStation().getTrain().trainName(), - currentStation().getTrain().scheduleTitle(), - TimeUtils.parseTime((int)((currentStation().getEstimatedTimeWithThreshold() + DragonLib.DAYTIME_SHIFT) % DragonLib.TICKS_PER_DAY), ModClientConfig.TIME_FORMAT.get()), - currentStation().getInfo().platform() - ); - - setNotificationText(new NotificationData(currentState, title, description)); - setNarratorText(title.getString() + " " + description.getString()); - beginAnnounced = true; - } - } - - private void requestRealtimeData() { - final Collection ids = Arrays.stream(route.getStationArray()).map(x -> x.getTrain().trainId()).distinct().toList(); - - long id = InstanceManager.registerClientRealtimeResponseAction((predictions, time) -> { - Map> predMap = predictions.stream().collect(Collectors.groupingBy(SimpleDeparturePrediction::trainId)); - - if (predMap.containsKey(currentStation().getTrain().trainId())) { - SimpleDeparturePrediction currentTrainNextStop = predMap.get(currentStation().getTrain().trainId()).get(0); - List currentTrainSchedule = predMap.get(currentStation().getTrain().trainId()); - - if (currentState != State.BEFORE_JOURNEY && currentState != State.JOURNEY_INTERRUPTED) { - if (currentState != State.WHILE_TRAVELING && currentState != State.WHILE_TRANSFER) { - while (!currentTrainNextStop.stationTagName().equals(currentStation().getStationName()) && currentState != State.AFTER_JOURNEY) { - if (currentStation().getTag() != StationTag.END) { - nextStop(); - } - } - } - } - - if (((!currentState.isWaitingForNextTrainToDepart() || currentState == State.BEFORE_JOURNEY || currentState == State.WHILE_TRANSFER) && currentStation().shouldRenderRealtime()) - && isStationValidForShedule(currentTrainSchedule, currentStation().getTrain().trainId(), stationIndex) && time >= currentStation().getEstimatedTime()) { - if (currentStation().getTag() == StationTag.PART_END) { - if (route.getStationArray()[stationIndex + 1].isTrainCanceled()) { - journeyInterrupt(route.getStationArray()[stationIndex + 1]); - } else if (route.getStationArray()[stationIndex + 1].isDeparted()) { - reachTransferStopConnectionMissed(); - } else { - reachTransferStop(); - } - } else if (currentStation().getTag() == StationTag.END) { - finishJourney(); - } else { - reachNextStop(); - } - } - - if (currentState == State.AFTER_JOURNEY) { - return; - } - } else { - journeyInterrupt(currentStation()); - return; - } - - Map> mappedRoute = Arrays.stream(route.getStationArray()).skip(stationIndex).collect(Collectors.groupingBy(x -> x.getTrain().trainId(), LinkedHashMap::new, Collectors.toList())); - - // Update realtime data - for (int i = stationIndex; i < route.getStationCount(true); i++) { - StationEntry e = route.getStationArray()[i]; - if (!predMap.containsKey(e.getTrain().trainId()) || e.isTrainCanceled()) { - e.setTrainCanceled(true, "", e.getTrain().trainName()); - continue; - } - - List preds = predMap.get(e.getTrain().trainId()); - List stations = mappedRoute.get(e.getTrain().trainId()); - updateRealtime(preds, stations, e.getTrain().trainId(), stationIndex, time); - } - - boolean departed = false; - // check if connection train has departed - for (List routePart : mappedRoute.values()) { - if (mappedRoute.size() < 2) { - continue; - } - - if (routePart.get(0).isDeparted()) { - continue; - } - - if (departed) { - routePart.forEach(x -> x.setDeparted(true)); - continue; - } - - long min = routePart.stream().filter(x -> x.getCurrentTime() + ModClientConfig.TRANSFER_TIME.get() > x.getScheduleTime()).mapToLong(x -> x.getCurrentTime()).min().orElse(-1); - long currentTime = routePart.get(0).getCurrentTime(); - - if (min > 0 && currentTime > min && currentTime + ModClientConfig.TRANSFER_TIME.get() > routePart.get(0).getScheduleTime()) { - routePart.forEach(x -> x.setDeparted(true)); - departed = true; - - Component title = ELanguage.translate(keyNotificationConnectionMissedTitle); - Component description = ELanguage.translate(keyNotificationConnectionMissed, - routePart.get(0).getTrain().trainName(), - routePart.get(0).getTrain().scheduleTitle() - ); - setNotificationText(new NotificationData(currentState, title, description)); - setNarratorText(title.getString() + " " + description.getString()); - } - } - - checkStationAccessibility(); - - // PROGRESS ANIMATION - if (currentState != State.BEFORE_JOURNEY && currentState != State.JOURNEY_INTERRUPTED) { - if (!currentState.nextStopAnnounced() && !currentState.isWaitingForNextTrainToDepart() // state check - && time >= route.getStationArray()[stationIndex].getEstimatedTime() - ModClientConfig.NEXT_STOP_ANNOUNCEMENT.get()) // train check - { - announceNextStop(); - } - } - - onUpdateRealtime.values().forEach(x -> { - if (x.isPresent()) { - x.get().run(); - } - }); - }); - CreateRailwaysNavigator.net().CHANNEL.sendToServer(new RealtimeRequestPacket(id, ids)); - } - - private boolean isStationValidForShedule(List schedule, UUID trainId, int startIndex) { - List filteredStationEntryList = new ArrayList<>(); - for (int i = startIndex; i < route.getStationCount(true); i++) { - StationEntry entry = route.getStationArray()[i]; - if (!entry.getTrain().trainId().equals(trainId)) { - break; - } - filteredStationEntryList.add(entry.getStationName()); - } - String[] filteredStationEntries = filteredStationEntryList.toArray(String[]::new); - String[] sched = schedule.stream().map(x -> x.stationTagName()).toArray(String[]::new); - - int k = 0; - for (int i = 0; i < filteredStationEntries.length; i++) { - if (!filteredStationEntries[i].equals(sched[k])) { - return false; - } - - k++; - if (k > sched.length) { - k = 0; - } - } - return true; - } - - private void updateRealtime(List schedule, List route, UUID trainId, int startIndex, long updateTime) { - boolean b = false; - long lastTime = -1; - - List routePart = route.stream().filter(x -> x.getTrain().trainId().equals(trainId)).toList(); - StationEntry first = routePart.get(0); - if (first.getTag() != StationTag.PART_START && first.getTag() != StationTag.START) { - first = null; - } - StationEntry last = routePart.get(routePart.size() - 1); - boolean wasDelayed = last.isDelayed(); - StationInfo oldInfo = first == null ? null : first.getUpdatedInfo(); - - for (int i = 0, k = 0; i < schedule.size() && k < route.size(); i++) { - SimpleDeparturePrediction current = schedule.get(i); - long newTime = current.departureTicks() + updateTime; - if (route.get(0).getStationName().equals(current.stationTagName())) { - k = 0; - b = true; - } - - if (route.get(k).getStationName().equals(current.stationTagName()) && b == true) { - if (newTime > lastTime/* && newTime + EARLY_ARRIVAL_THRESHOLD > route.get(k).station().getScheduleTime()*/) { - route.get(k).updateRealtimeData(current.departureTicks(), updateTime, current.stationInfo(), () -> { - - }); - lastTime = route.get(k).getCurrentTime(); - } - k++; - } else { - b = false; - } - } - - if (oldInfo != null && !first.getUpdatedInfo().equals(oldInfo)) { - setNotificationText(new NotificationData(currentState, ELanguage.translate(keyNotificationPlatformChangedTitle), ELanguage.translate(keyNotificationPlatformChanged, - first.getStationName(), - first.getUpdatedInfo().platform() - ))); - } - - if (!wasDelayed && last.isDelayed()) { - setNotificationText(new NotificationData(currentState, ELanguage.translate(keyNotificationTrainDelayedTitle, - last.getTrain().trainName(), - TimeUtils.parseDuration(last.getDifferenceTime()) - ), ELanguage.translate(keyNotificationTrainDelayed, - TimeUtils.parseTime((int)(last.getEstimatedTimeWithThreshold() % DragonLib.TICKS_PER_DAY) + DragonLib.DAYTIME_SHIFT, ModClientConfig.TIME_FORMAT.get()), - TimeUtils.parseTime((int)(last.getScheduleTime() % DragonLib.TICKS_PER_DAY) + DragonLib.DAYTIME_SHIFT, ModClientConfig.TIME_FORMAT.get()), - last.getStationName() - ))); - } - } - - - - private void checkStationAccessibility() { - boolean willMiss = false; - for (int i = stationIndex; i < route.getStationCount(true); i++) { - StationEntry station = route.getStationArray()[i]; - StationEntry nextStation = i < route.getStationCount(true) - 1 ? route.getStationArray()[i + 1] : null; - - boolean wasWillMiss = station.willMissStop(); - - if (nextStation == null) { - continue; - } - - if (station.isDeparted()) { - willMiss = true; - } - - if (!willMiss) { - long transferTime = -1; - if (nextStation != null && !nextStation.isDeparted()) { - if (nextStation.getCurrentTime() + ModClientConfig.TRANSFER_TIME.get() < nextStation.getScheduleTime()) { - transferTime = nextStation.getScheduleTime() - station.getScheduleTime(); - } else { - transferTime = nextStation.getCurrentTime() - station.getCurrentTime(); - } - } - - if (transferTime < 0) { - willMiss = true; - } - } - - station.setWillMiss(willMiss); - - if (station.getTag() == StationTag.PART_START && !wasWillMiss && station.willMissStop()) { - setNotificationText(new NotificationData(currentState, ELanguage.translate(keyNotificationConnectionEndangeredTitle), ELanguage.translate(keyNotificationConnectionEndangered, - station.getTrain().trainName(), - station.getTrain().scheduleTitle() - ))); - } - } - } - - public Component getLastInfoText() { - return lastInfoText; - } - - public String lastNarratorText() { - return lastNarratorText; - } - - public NotificationData getLastNotification() { - return lastNotification; - } - - private void setState(State state) { - this.currentState = state; - onStateChange.values().forEach(x -> { - if (x.isPresent()) { - x.get().accept(currentState); - } - }); - } - - private void setInfoText(Component text) { - this.lastInfoText = text; - onInfoTextChange.values().forEach(x -> { - if (x.isPresent()) { - x.get().accept(text); - } - }); - } - - private void setNotificationText(NotificationData data) { - this.lastNotification = data; - onNotificationSend.values().forEach(x -> { - if (x.isPresent()) { - x.get().accept(data); - } - }); - } - - private void setNarratorText(String text) { - this.lastNarratorText = text; - onNarratorAnnounce.values().forEach(x -> { - if (x.isPresent()) { - x.get().accept(text); - } - }); - } - - - private void nextStop() { - if (!changeCurrentStation()) { - return; - } - - setState(State.WHILE_TRAVELING); - - onContinue.values().forEach(x -> { - if (x.isPresent()) { - x.get().accept(new ContinueData(currentState)); - } - }); - } - - private boolean changeCurrentStation() { - if (stationIndex + 1 >= route.getStationCount(true)) { - finishJourney(); - return false; - } - stationIndex++; - return true; - } - - private void finishJourney() { - Component text = ELanguage.translate(keyAfterJourney, route.getStationArray()[route.getStationCount(true) - 1].getStationName()); - setState(State.AFTER_JOURNEY); - - onFinishJourney.values().forEach(x -> { - if (x.isPresent()) { - x.get().accept(new FinishJourneyData(currentState, text)); - } - }); - setInfoText(text); - setNotificationText(new NotificationData(currentState, ELanguage.translate(keyNotificationJourneyCompletedTitle), ELanguage.translate(keyNotificationJourneyCompleted))); - - stop(); - } - - private void announceNextStop() { - Component textA = TextUtils.empty(); - Component textB = TextUtils.empty(); - Component text; - text = textA = ELanguage.translate(keyNextStop, - currentStation().getStationName() - ); - if (currentStation().getTag() == StationTag.PART_END && currentStation().getIndex() + 1 < route.getStationCount(true)) { - Component transferText = textB = nextStation().get().getInfo().platform() == null || nextStation().get().getInfo().platform().isBlank() ? - ELanguage.translate(keyTransfer, - nextStation().get().getTrain().trainName(), - nextStation().get().getTrain().scheduleTitle() - ) : - ELanguage.translate(keyTransferWithPlatform, - nextStation().get().getTrain().trainName(), - nextStation().get().getTrain().scheduleTitle(), - nextStation().get().getInfo().platform() - ); - text = TextUtils.concatWithStarChars(text, transferText); - setState(State.BEFORE_TRANSFER); - setNotificationText(new NotificationData(currentState, ELanguage.translate(keyNotificationTransferTitle), - nextStation().get().getInfo().platform() == null || nextStation().get().getInfo().platform().isBlank() ? - ELanguage.translate(keyNotificationTransfer, - nextStation().get().getTrain().trainName(), - nextStation().get().getTrain().scheduleTitle() - ) : - ELanguage.translate(keyNotificationTransferWithPlatform, - nextStation().get().getTrain().trainName(), - nextStation().get().getTrain().scheduleTitle(), - nextStation().get().getInfo().platform() - ) - )); - } else { - setState(State.BEFORE_NEXT_STOP); - } - - String narratorText = textA.getString() + ". " + textB.getString(); - boolean transfer = currentStation().getTag() == StationTag.PART_END && currentStation().getIndex() + 1 < route.getStationCount(true); - - final Component fText = text; - onAnnounceNextStop.values().forEach(x -> { - if (x.isPresent()) { - x.get().accept(new AnnounceNextStopData(currentState, fText, narratorText, transfer)); - } - }); - setInfoText(text); - setNarratorText(narratorText); - } - - private void reachNextStop() { - Component text = TextUtils.text(currentStation().getStationName()); - String narratorText = text.getString(); - - setState(State.WHILE_NEXT_STOP); - onReachNextStop.values().forEach(x -> { - if (x.isPresent()) { - x.get().accept(new ReachNextStopData(currentState, text, narratorText, TransferState.NONE)); - } - }); - - setInfoText(text); - setNarratorText(narratorText); - } - - private void reachTransferStop() { - Component text = nextStation().isPresent() ? ( - nextStation().get().getInfo().platform() == null || nextStation().get().getInfo().platform().isBlank() ? - ELanguage.translate(keyTransfer, - nextStation().get().getTrain().trainName(), - nextStation().get().getTrain().scheduleTitle() - ) : - ELanguage.translate(keyTransferWithPlatform, - nextStation().get().getTrain().trainName(), - nextStation().get().getTrain().scheduleTitle(), - nextStation().get().getInfo().platform() - ) - ) : TextUtils.empty(); - String narratorText = text.getString(); - - setState(State.WHILE_TRANSFER); - changeCurrentStation(); - - onReachNextStop.values().forEach(x -> { - if (x.isPresent()) { - x.get().accept(new ReachNextStopData(currentState, text, narratorText, TransferState.DEFAULT)); - } - }); - setInfoText(text); - setNarratorText(narratorText); - } - - private void reachTransferStopConnectionMissed() { - Component text = TextUtils.concatWithStarChars( - TextUtils.text(currentStation().getStationName()), - ELanguage.translate(keyConnectionMissedInfo) - ); - String narratorText = ""; - - setState(State.JOURNEY_INTERRUPTED); - onReachNextStop.values().forEach(x -> { - if (x.isPresent()) { - x.get().accept(new ReachNextStopData(currentState, text, narratorText, TransferState.CONNECTION_MISSED)); - } - }); - setInfoText(text); - setNarratorText(narratorText); - - stop(); - } - - private void journeyInterrupt(StationEntry station) { - Component text = ELanguage.translate(keyJourneyInterruptedTitle); - Component desc = ELanguage.translate(keyJourneyInterrupted, station.getTrain().trainName()); - String narratorText = ""; - - setState(State.JOURNEY_INTERRUPTED); - onJourneyInterrupt.values().forEach(x -> { - if (x.isPresent()) { - x.get().accept(new JourneyInterruptData(currentState, text, desc, narratorText)); - } - }); - setInfoText(text); - setNarratorText(narratorText); - - stop(); - } - - public long getTransferTime(int index) { - Optional station = getEntryAt(index); - Optional nextStation = getEntryAt(index + 1); - long transferTime = -1; - if (station.isPresent() && nextStation.isPresent() && !nextStation.get().isDeparted()) { - if (nextStation.get().getCurrentTime() + ModClientConfig.TRANSFER_TIME.get() < nextStation.get().getScheduleTime()) { - transferTime = nextStation.get().getScheduleTime() - station.get().getScheduleTime(); - } else { - transferTime = nextStation.get().getCurrentTime() - station.get().getCurrentTime(); - } - } - - return transferTime; - } - - - - - public StationEntry currentStation() { - return route.getStationArray()[stationIndex]; - } - - public Optional nextStation() { - return stationIndex + 1 < route.getStationCount(true) ? Optional.of(route.getStationArray()[stationIndex + 1]) : Optional.empty(); - } - - public Optional previousSation() { - return stationIndex > 0 ? Optional.of(route.getStationArray()[stationIndex - 1]) : Optional.empty(); - } - - public StationEntry firstStation() { - return route.getStationArray()[0]; - } - - public StationEntry lastStation() { - return route.getStationArray()[route.getStationCount(true) - 1]; - } - - public State getCurrentState() { - return currentState; - } - - public boolean isStarted() { - return isStarted; - } - - public long getTimeDifferenceForStation(int index) { - StationEntry station = route.getStationArray()[index]; - return station.getCurrentRefreshTime() + station.getCurrentTicks() - station.getRefreshTime() - station.getTicks(); - } - - public int getIndex() { - return stationIndex; - } - - public SimpleRoute getListeningRoute() { - return route; - } - - public Optional getEntryAt(int index) { - return index >= 0 && index < route.getStationCount(true) ? Optional.of(route.getStationArray()[index]) : Optional.empty(); - } - - - - public static enum State { - BEFORE_JOURNEY, - WHILE_TRAVELING, - BEFORE_NEXT_STOP, - WHILE_NEXT_STOP, - BEFORE_TRANSFER, - WHILE_TRANSFER, - AFTER_JOURNEY, - JOURNEY_INTERRUPTED; - - public boolean nextStopAnnounced() { - return this == BEFORE_NEXT_STOP || this == BEFORE_TRANSFER; - } - - public boolean isWhileTraveling() { - return this == WHILE_TRAVELING; - } - - public boolean isTranferring() { - return this == WHILE_TRANSFER; - } - - public boolean isAtNextStop() { - return this == WHILE_NEXT_STOP; - } - - public boolean isWaitingForNextTrainToDepart() { - return isTranferring() || isAtNextStop(); - } - - public boolean important() { - return this == State.WHILE_TRANSFER || this == State.BEFORE_TRANSFER || this == State.AFTER_JOURNEY || this == State.BEFORE_JOURNEY ||this == JOURNEY_INTERRUPTED; - } - } -} diff --git a/common/src/main/java/de/mrjulsen/crn/event/listeners/JourneyListenerManager.java b/common/src/main/java/de/mrjulsen/crn/event/listeners/JourneyListenerManager.java deleted file mode 100644 index 033f7198..00000000 --- a/common/src/main/java/de/mrjulsen/crn/event/listeners/JourneyListenerManager.java +++ /dev/null @@ -1,125 +0,0 @@ -package de.mrjulsen.crn.event.listeners; - -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; -import java.util.UUID; - -import de.mrjulsen.crn.CreateRailwaysNavigator; -import de.mrjulsen.crn.data.SimpleRoute; -import de.mrjulsen.crn.event.listeners.JourneyListener.State; -import net.minecraft.client.Minecraft; - -public class JourneyListenerManager { - - private static final int CLEANUP_INTERVALL = 100; - private static JourneyListenerManager instance; - - private int cleanupTimer = 0; - - private final Map journeyListenerCache = new HashMap<>(); - private final Map> dataListeners = new HashMap<>(); - - public UUID register(JourneyListener listener) { - UUID uuid = UUID.randomUUID(); - while (journeyListenerCache.containsKey(uuid)) { - uuid = UUID.randomUUID(); - } - - journeyListenerCache.put(uuid, listener); - return uuid; - } - - public UUID create(SimpleRoute route, IJourneyListenerClient initialListener) { - UUID id = register(new JourneyListener(route)); - dataListeners.put(id, new HashSet<>(Set.of(initialListener.getJourneyListenerClientId()))); - return id; - } - - public JourneyListener get(UUID id, IJourneyListenerClient addListener) { - if (addListener != null) { - if (dataListeners.containsKey(id)) { - dataListeners.get(id).add(addListener.getJourneyListenerClientId()); - } else { - dataListeners.put(id, new HashSet<>(Set.of(addListener.getJourneyListenerClientId()))); - } - } - return journeyListenerCache.get(id); - } - - public void removeClientListener(UUID listenerId, IJourneyListenerClient client) { - if (dataListeners.containsKey(listenerId)) { - dataListeners.get(listenerId).removeIf(x -> x.equals(client.getJourneyListenerClientId())); - } - } - - public void removeClientListenerForAll(IJourneyListenerClient client) { - dataListeners.values().forEach(x -> x.removeIf(a -> a.equals(client.getJourneyListenerClientId()))); - } - - - public static boolean hasInstance() { - return instance != null; - } - - @SuppressWarnings("resource") - public static void tick() { - if (!hasInstance() || Minecraft.getInstance().level == null) { - return; - } - - instance.tickInstance(); - } - - private void tickInstance() { - journeyListenerCache.values().forEach(x -> x.tick()); - - cleanupTimer++; - cleanupTimer = cleanupTimer % CLEANUP_INTERVALL; - if (cleanupTimer == 0) { - dataListeners.entrySet().removeIf(x -> x.getValue().isEmpty()); - journeyListenerCache.entrySet().removeIf(e -> !dataListeners.containsKey(e.getKey()) || e.getValue().getCurrentState() == State.AFTER_JOURNEY); - } - } - - public int getCacheSize() { - return journeyListenerCache.size(); - } - - public boolean exists(UUID listenerId) { - return journeyListenerCache.containsKey(listenerId); - } - - public Collection getAllListeners() { - return journeyListenerCache.values(); - } - - - public static JourneyListenerManager getInstance() { - return instance; - } - - public static JourneyListenerManager start() { - stop(); - instance = new JourneyListenerManager(); - CreateRailwaysNavigator.LOGGER.info("Journey Listener started."); - return instance; - } - - public static void stop() { - if (hasInstance()) { - instance.stopInstance(); - } - - instance = null; - } - - private void stopInstance() { - dataListeners.clear(); - journeyListenerCache.clear(); - CreateRailwaysNavigator.LOGGER.info("Journey Listener stopped."); - } - -} diff --git a/common/src/main/java/de/mrjulsen/crn/event/listeners/TrainListener.java b/common/src/main/java/de/mrjulsen/crn/event/listeners/TrainListener.java deleted file mode 100644 index 7549001b..00000000 --- a/common/src/main/java/de/mrjulsen/crn/event/listeners/TrainListener.java +++ /dev/null @@ -1,136 +0,0 @@ -package de.mrjulsen.crn.event.listeners; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.OptionalInt; -import java.util.UUID; - -import com.simibubi.create.content.trains.entity.Train; -import com.simibubi.create.content.trains.schedule.condition.ScheduleWaitCondition; -import com.simibubi.create.content.trains.schedule.condition.TimedWaitCondition; - -import de.mrjulsen.crn.CreateRailwaysNavigator; -import de.mrjulsen.crn.config.ModCommonConfig; -import de.mrjulsen.crn.mixin.ScheduleDataAccessor; -import de.mrjulsen.crn.util.TrainUtils; -import de.mrjulsen.mcdragonlib.util.ScheduledTask; -import de.mrjulsen.mcdragonlib.util.ScheduledTask.ScheduledTaskContext; -import net.minecraft.nbt.CompoundTag; -import net.minecraft.world.level.Level; - -public class TrainListener { - - private static TrainListener instance; - - private boolean isRunning = true; - private int totalTrainCount; - private int listeingTrainCount; - private final Map> TRAIN_DURATIONS = new HashMap<>(); - private final Map lastTicks = new HashMap<>(); - - public int getDepartmentTime(Level level, Train train) { - List> conditions = train.runtime.getSchedule().entries.get(train.runtime.currentEntry).conditions; - if (conditions.isEmpty() || ((ScheduleDataAccessor)train.runtime).crn$conditionProgress().isEmpty() || ((ScheduleDataAccessor)train.runtime).crn$conditionContext().isEmpty()) - return 0; - - List list = conditions.get(0); - int progress = ((ScheduleDataAccessor)train.runtime).crn$conditionProgress().get(0); - if (progress >= list.size()) - return 0; - - CompoundTag tag = ((ScheduleDataAccessor)train.runtime).crn$conditionContext().get(0); - ScheduleWaitCondition condition = list.get(progress); - return ((TimedWaitCondition)condition).totalWaitTicks() - tag.getInt("Time"); - } - - private boolean performTask(TrainListener instance, ScheduledTaskContext context) { - - if (!isRunning) { - return false; - } - - new Thread(() -> { - Collection trains = TrainUtils.getAllTrains(); - listeingTrainCount = 0; - trains.forEach(train -> { - if (!TrainUtils.isTrainValid(train)) { - return; - } - - OptionalInt maxTrainDuration = TrainUtils.getTrainDeparturePredictions(train.id, null).stream().mapToInt(x -> x.getTicks()).max(); - - if (maxTrainDuration.isPresent()) { - if (!lastTicks.containsKey(train.id)) { - lastTicks.put(train.id, 0); - } - - if (lastTicks.get(train.id) < maxTrainDuration.getAsInt()) { - - } - if (!TRAIN_DURATIONS.containsKey(train.id)) { - TRAIN_DURATIONS.put(train.id, new ArrayList<>()); - } - - TRAIN_DURATIONS.get(train.id).add(maxTrainDuration.getAsInt()); - if (TRAIN_DURATIONS.get(train.id).size() > 30) { - TRAIN_DURATIONS.get(train.id).remove(0); - } - lastTicks.replace(train.id, maxTrainDuration.getAsInt()); - } - listeingTrainCount++; - }); - - this.totalTrainCount = trains.size(); - }, "Train Listener Worker").run(); - - return isRunning; - } - - public static TrainListener getInstance() { - return instance; - } - - public static TrainListener start(Level level) { - if (instance == null) - instance = new TrainListener(); - - ScheduledTask.create(instance, level, ModCommonConfig.TRAIN_WATCHER_INTERVALL.get(), Integer.MAX_VALUE, instance::performTask); - - CreateRailwaysNavigator.LOGGER.info("TrainListener started."); - return instance; - } - - public static void stop() { - if (instance == null) - return; - - instance.stopInstance(); - } - - private void stopInstance() { - isRunning = false; - CreateRailwaysNavigator.LOGGER.info("TrainListener stopped."); - } - - - - public int getApproximatedTrainDuration(Train train) { - int a = getApproximatedTrainDuration(train.id); - return a == 0 ? 1 : a; - } - - public int getApproximatedTrainDuration(UUID trainId) { - return TRAIN_DURATIONS.containsKey(trainId) ? TRAIN_DURATIONS.get(trainId).stream().mapToInt(v -> v).sum() / TRAIN_DURATIONS.get(trainId).size() : 0; - } - - public int getTotalTrainCount() { - return this.totalTrainCount; - } - - public int getListeningTrainCount() { - return this.listeingTrainCount; - } -} diff --git a/common/src/main/java/de/mrjulsen/crn/exceptions/RuntimeSideException.java b/common/src/main/java/de/mrjulsen/crn/exceptions/RuntimeSideException.java new file mode 100644 index 00000000..34b98466 --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/exceptions/RuntimeSideException.java @@ -0,0 +1,7 @@ +package de.mrjulsen.crn.exceptions; + +public class RuntimeSideException extends RuntimeException { + public RuntimeSideException(boolean expectClient) { + super("This method can only be called on the " + (expectClient ? "client" : "server") + " side!"); + } +} diff --git a/common/src/main/java/de/mrjulsen/crn/item/NavigatorItem.java b/common/src/main/java/de/mrjulsen/crn/item/NavigatorItem.java index 06aa8030..01c7c2f9 100644 --- a/common/src/main/java/de/mrjulsen/crn/item/NavigatorItem.java +++ b/common/src/main/java/de/mrjulsen/crn/item/NavigatorItem.java @@ -17,7 +17,8 @@ public NavigatorItem(Properties props) { @Override public InteractionResultHolder use(Level pLevel, Player pPlayer, InteractionHand pUsedHand) { if (pLevel.isClientSide) { - ClientWrapper.showNavigatorGui(pLevel); + ClientWrapper.showNavigatorGui(); + return InteractionResultHolder.success(pPlayer.getItemInHand(pUsedHand)); } return super.use(pLevel, pPlayer, pUsedHand); } diff --git a/common/src/main/java/de/mrjulsen/crn/mixin/ControlsHandlerMixin.java b/common/src/main/java/de/mrjulsen/crn/mixin/ControlsHandlerMixin.java new file mode 100644 index 00000000..13ce5823 --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/mixin/ControlsHandlerMixin.java @@ -0,0 +1,43 @@ +package de.mrjulsen.crn.mixin; + +import java.lang.ref.WeakReference; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import com.simibubi.create.content.contraptions.AbstractContraptionEntity; +import com.simibubi.create.content.contraptions.actors.trainControls.ControlsHandler; +import com.simibubi.create.content.trains.entity.CarriageContraption; +import com.simibubi.create.content.trains.entity.CarriageContraptionEntity; + +import de.mrjulsen.crn.data.train.TrainListener; +import net.minecraft.core.BlockPos; + +@Mixin(ControlsHandler.class) +public class ControlsHandlerMixin { + + @Accessor("entityRef") + private static WeakReference crn$entityRef() { + throw new AssertionError(); + } + + @Inject(method = "startControlling", remap = false, at = @At(value = "HEAD")) + private static void onStartControlling(AbstractContraptionEntity entity, BlockPos controllerLocalPos, CallbackInfo ci) { + if (entity.getContraption() instanceof CarriageContraption carriage && carriage.entity instanceof CarriageContraptionEntity trainEntity) { + if (TrainListener.data.containsKey(trainEntity.trainId)) { + TrainListener.data.get(trainEntity.trainId).isManualControlled = true; + } + } + } + @Inject(method = "stopControlling", remap = false, at = @At(value = "HEAD")) + private static void onStopControlling(CallbackInfo ci) { + if (crn$entityRef().get() != null && crn$entityRef().get().getContraption() instanceof CarriageContraption carriage && carriage.entity instanceof CarriageContraptionEntity trainEntity) { + if (TrainListener.data.containsKey(trainEntity.trainId)) { + TrainListener.data.get(trainEntity.trainId).isManualControlled = false; + } + } + } +} diff --git a/common/src/main/java/de/mrjulsen/crn/mixin/GlobalTrainDisplayDataMixin.java b/common/src/main/java/de/mrjulsen/crn/mixin/GlobalTrainDisplayDataMixin.java new file mode 100644 index 00000000..d65a502e --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/mixin/GlobalTrainDisplayDataMixin.java @@ -0,0 +1,34 @@ +package de.mrjulsen.crn.mixin; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import com.simibubi.create.content.trains.display.GlobalTrainDisplayData; + +import de.mrjulsen.crn.event.CRNEventsManager; +import de.mrjulsen.crn.event.events.GlobalTrainDisplayDataRefreshEventPost; +import de.mrjulsen.crn.event.events.GlobalTrainDisplayDataRefreshEventPre; + +@Mixin(GlobalTrainDisplayData.class) +public class GlobalTrainDisplayDataMixin { + + /* + * Called every ~5 seconds, when create refreshes its display data. + */ + + @Inject(method = "refresh", remap = false, at = @At(value = "HEAD")) + private static void onRefreshPre(CallbackInfo ci) { + if (CRNEventsManager.isRegistered(GlobalTrainDisplayDataRefreshEventPre.class)) { + CRNEventsManager.getEvent(GlobalTrainDisplayDataRefreshEventPre.class).run(); + } + } + + @Inject(method = "refresh", remap = false, at = @At(value = "TAIL")) + private static void onRefreshPost(CallbackInfo ci) { + if (CRNEventsManager.isRegistered(GlobalTrainDisplayDataRefreshEventPost.class)) { + CRNEventsManager.getEvent(GlobalTrainDisplayDataRefreshEventPost.class).run(); + } + } +} diff --git a/common/src/main/java/de/mrjulsen/crn/mixin/ModularGuiLineBuilderAccessor.java b/common/src/main/java/de/mrjulsen/crn/mixin/ModularGuiLineBuilderAccessor.java new file mode 100644 index 00000000..f2528a48 --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/mixin/ModularGuiLineBuilderAccessor.java @@ -0,0 +1,21 @@ +package de.mrjulsen.crn.mixin; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +import com.simibubi.create.foundation.gui.ModularGuiLine; +import com.simibubi.create.foundation.gui.ModularGuiLineBuilder; + +import net.minecraft.client.gui.Font; + +@Mixin(ModularGuiLineBuilder.class) +public interface ModularGuiLineBuilderAccessor { + @Accessor("target") + ModularGuiLine crn$getTarget(); + @Accessor("font") + Font crn$getFont(); + @Accessor("x") + int crn$getX(); + @Accessor("y") + int crn$getY(); +} diff --git a/common/src/main/java/de/mrjulsen/crn/mixin/MountedStorageManagerMixin.java b/common/src/main/java/de/mrjulsen/crn/mixin/MountedStorageManagerMixin.java index 126dc6fa..5f741be5 100644 --- a/common/src/main/java/de/mrjulsen/crn/mixin/MountedStorageManagerMixin.java +++ b/common/src/main/java/de/mrjulsen/crn/mixin/MountedStorageManagerMixin.java @@ -12,14 +12,14 @@ import com.simibubi.create.content.contraptions.MountedStorageManager; import com.simibubi.create.content.trains.entity.CarriageContraption; -import de.mrjulsen.crn.block.be.IContraptionBlockEntity; +import de.mrjulsen.crn.block.blockentity.IContraptionBlockEntity; import net.minecraft.world.level.block.entity.BlockEntity; @Mixin(MountedStorageManager.class) public class MountedStorageManagerMixin { @Inject(method = "entityTick", remap = false, at = @At(value = "HEAD")) - public void tick$inject(AbstractContraptionEntity entity, CallbackInfo ci) { + public void onEntityTick(AbstractContraptionEntity entity, CallbackInfo ci) { if (entity.getContraption() instanceof CarriageContraption carriage) { Set beList = new LinkedHashSet<>(); beList.addAll(entity.getContraption().maybeInstancedBlockEntities); diff --git a/common/src/main/java/de/mrjulsen/crn/mixin/ReloadableServerResourcesMixin.java b/common/src/main/java/de/mrjulsen/crn/mixin/ReloadableServerResourcesMixin.java new file mode 100644 index 00000000..ea5e6652 --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/mixin/ReloadableServerResourcesMixin.java @@ -0,0 +1,36 @@ +package de.mrjulsen.crn.mixin; + +import java.util.ArrayList; +import java.util.List; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import de.mrjulsen.crn.util.ModUtils; +import de.mrjulsen.crn.web.WebsitePreparableReloadListener; +import net.minecraft.commands.Commands; +import net.minecraft.core.RegistryAccess; +import net.minecraft.server.ReloadableServerResources; +import net.minecraft.server.packs.resources.PreparableReloadListener; + +@Mixin(ReloadableServerResources.class) +public class ReloadableServerResourcesMixin { + + public WebsitePreparableReloadListener websitemanager; + + @Inject(method = "", at = @At(value = "TAIL")) + public void onInit(RegistryAccess.Frozen frozen, Commands.CommandSelection commandSelection, int i, CallbackInfo ci) { + this.websitemanager = new WebsitePreparableReloadListener(); + ModUtils.setWebsiteResourceManager(websitemanager); + } + + @Inject(method = "listeners", at = @At("RETURN"), cancellable = true) + private void addListener(CallbackInfoReturnable> cir) { + List listeners = new ArrayList<>(cir.getReturnValue()); + listeners.add(websitemanager); + cir.setReturnValue(listeners); + } +} diff --git a/common/src/main/java/de/mrjulsen/crn/mixin/ScheduleDataAccessor.java b/common/src/main/java/de/mrjulsen/crn/mixin/ScheduleRuntimeAccessor.java similarity index 50% rename from common/src/main/java/de/mrjulsen/crn/mixin/ScheduleDataAccessor.java rename to common/src/main/java/de/mrjulsen/crn/mixin/ScheduleRuntimeAccessor.java index 551f963e..dc741255 100644 --- a/common/src/main/java/de/mrjulsen/crn/mixin/ScheduleDataAccessor.java +++ b/common/src/main/java/de/mrjulsen/crn/mixin/ScheduleRuntimeAccessor.java @@ -4,15 +4,30 @@ import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.gen.Accessor; +import org.spongepowered.asm.mixin.gen.Invoker; +import com.simibubi.create.content.trains.entity.Train; import com.simibubi.create.content.trains.schedule.ScheduleRuntime; import net.minecraft.nbt.CompoundTag; @Mixin(ScheduleRuntime.class) -public interface ScheduleDataAccessor { +public interface ScheduleRuntimeAccessor { + @Accessor("train") + Train crn$getTrain(); + + @Accessor("ticksInTransit") + int crn$getTicksInTransit(); + + @Invoker("estimateStayDuration") + int crn$runEstimateStayDuration(int index); + @Accessor("conditionProgress") List crn$conditionProgress(); + @Accessor("conditionContext") List crn$conditionContext(); + + @Accessor("predictionTicks") + List crn$predictionTicks(); } diff --git a/common/src/main/java/de/mrjulsen/crn/mixin/ScheduleRuntimeMixin.java b/common/src/main/java/de/mrjulsen/crn/mixin/ScheduleRuntimeMixin.java new file mode 100644 index 00000000..cb8a0a8a --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/mixin/ScheduleRuntimeMixin.java @@ -0,0 +1,153 @@ +package de.mrjulsen.crn.mixin; + +import java.util.Collection; +import java.util.Map; +import java.util.LinkedHashMap; +import java.util.List; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; +import org.spongepowered.asm.mixin.injection.callback.LocalCapture; + +import com.simibubi.create.content.trains.display.GlobalTrainDisplayData.TrainDeparturePrediction; +import com.simibubi.create.content.trains.entity.Train; +import com.simibubi.create.content.trains.graph.DiscoveredPath; +import com.simibubi.create.content.trains.schedule.Schedule; +import com.simibubi.create.content.trains.schedule.ScheduleEntry; +import com.simibubi.create.content.trains.schedule.ScheduleRuntime; +import com.simibubi.create.content.trains.schedule.condition.ScheduleWaitCondition; +import com.simibubi.create.content.trains.schedule.condition.ScheduledDelay; +import com.simibubi.create.content.trains.schedule.destination.DestinationInstruction; +import com.simibubi.create.content.trains.schedule.destination.ScheduleInstruction; +import com.simibubi.create.content.trains.station.GlobalStation; + +import de.mrjulsen.crn.data.train.TrainListener; +import de.mrjulsen.crn.event.CRNEventsManager; +import de.mrjulsen.crn.event.events.CreateTrainPredictionEvent; +import de.mrjulsen.crn.event.events.ScheduleResetEvent; +import de.mrjulsen.crn.event.events.SubmitTrainPredictionsEvent; +import de.mrjulsen.crn.event.events.TrainDestinationChangedEvent; +import dev.architectury.injectables.annotations.PlatformOnly; +import de.mrjulsen.crn.data.schedule.condition.DynamicDelayCondition; +import de.mrjulsen.crn.data.schedule.instruction.ICustomSuggestionsInstruction; +import de.mrjulsen.crn.data.schedule.instruction.IPredictableInstruction; +import de.mrjulsen.crn.data.schedule.instruction.IStationPredictableInstruction; + +@Mixin(ScheduleRuntime.class) +public class ScheduleRuntimeMixin { + + public final Map, IStationPredictableInstruction> customData = new LinkedHashMap<>(); + + public ScheduleRuntime self() { + return (ScheduleRuntime)(Object)this; + } + + public ScheduleRuntimeAccessor accessor() { + return (ScheduleRuntimeAccessor)(Object)this; + } + + + @Inject(method = "submitPredictions", remap = false, at = @At(value = "RETURN"), locals = LocalCapture.CAPTURE_FAILHARD) + public void onSubmitPredictions(CallbackInfoReturnable> cir, Collection predictions, int entryCount, int accumulatedTime, int current) { + if (CRNEventsManager.isRegistered(SubmitTrainPredictionsEvent.class)) { + CRNEventsManager.getEvent(SubmitTrainPredictionsEvent.class).run(accessor().crn$getTrain(), predictions, entryCount, accumulatedTime, current); + } + } + + @Inject(method = "", remap = false, at = @At(value = "TAIL")) + public void onResetWhileInit(CallbackInfo ci) { + if (CRNEventsManager.isRegistered(ScheduleResetEvent.class)) { + CRNEventsManager.getEvent(ScheduleResetEvent.class).run(accessor().crn$getTrain(), true); + } + } + + @Inject(method = {"setSchedule", "discardSchedule"}, remap = false, at = @At(value = "INVOKE", target = "Lcom/simibubi/create/content/trains/schedule/ScheduleRuntime;reset()V")) + public void onReset(CallbackInfo ci) { + if (CRNEventsManager.isRegistered(ScheduleResetEvent.class)) { + CRNEventsManager.getEvent(ScheduleResetEvent.class).run(accessor().crn$getTrain(), false); + } + } + + @PlatformOnly(value = "FORGE") + @Inject(method = "startCurrentInstruction", remap = false, at = @At(value = "RETURN"), locals = LocalCapture.CAPTURE_FAILHARD) + public void onStartCurrentInstructionRetForge(CallbackInfoReturnable cir, ScheduleEntry entry, ScheduleInstruction instruction) { + if (CRNEventsManager.isRegistered(TrainDestinationChangedEvent.class) && cir.getReturnValue() != null && instruction instanceof DestinationInstruction) { + CRNEventsManager.getEvent(TrainDestinationChangedEvent.class).run(accessor().crn$getTrain(), accessor().crn$getTrain().getCurrentStation(), cir.getReturnValue(), self().currentEntry); + } + } + + @PlatformOnly(value = "FABRIC") + @Inject(method = "startCurrentInstruction", remap = false, at = @At(value = "RETURN"), locals = LocalCapture.CAPTURE_FAILHARD) + public void onStartCurrentInstructionRetFabric(CallbackInfoReturnable cir, ScheduleEntry entry, ScheduleInstruction instruction) { + if (CRNEventsManager.isRegistered(TrainDestinationChangedEvent.class) && cir.getReturnValue() != null && instruction instanceof DestinationInstruction) { + CRNEventsManager.getEvent(TrainDestinationChangedEvent.class).run(accessor().crn$getTrain(), accessor().crn$getTrain().getCurrentStation(), cir.getReturnValue().destination, self().currentEntry); + } + } + + @Inject(method = "startCurrentInstruction", remap = false, at = @At(value = "TAIL"), cancellable = true, locals = LocalCapture.CAPTURE_FAILHARD) + public void onStartCurrentInstructionPost(CallbackInfoReturnable cir, ScheduleEntry entry, ScheduleInstruction instruction) { + if (instruction instanceof ICustomSuggestionsInstruction custom) { + if (TrainListener.data.containsKey(accessor().crn$getTrain().id)) { + custom.run(self(), TrainListener.data.get(accessor().crn$getTrain().id), accessor().crn$getTrain(), self().currentEntry); + } + + if (instruction instanceof IStationPredictableInstruction predictable) { + customData.put(predictable.getClass(), predictable::predictForStation); + } + + self().state = ScheduleRuntime.State.PRE_TRANSIT; + self().currentEntry++; + } + cir.setReturnValue((GlobalStation)null); + } + + @Inject(method = "predictForEntry", remap = false, at = @At(value = "HEAD")) + public void onPredictForEntryPre(int index, String currentTitle, int accumulatedTime, Collection predictions, CallbackInfoReturnable cir) { + ScheduleInstruction instruction = self().getSchedule().entries.get(index).instruction; + if (instruction instanceof IStationPredictableInstruction predictable) { + customData.put(predictable.getClass(), predictable::predictForStation); + } + if (instruction instanceof IPredictableInstruction predictable) { + predictable.predict(TrainListener.data.get(accessor().crn$getTrain().id), accessor().crn$getTrain().runtime, index, accessor().crn$getTrain()); + } + } + + @Inject(method = "createPrediction", remap = false, at = @At(value = "RETURN")) + public void onCreatePrediction(int index, String destination, String currentTitle, int time, CallbackInfoReturnable cir) { + if (CRNEventsManager.isRegistered(CreateTrainPredictionEvent.class) && cir.getReturnValue() != null) { + int stayDuration = accessor().crn$runEstimateStayDuration(index); + int minStayDuration = estimateMinStayDuration(accessor().crn$getTrain(), index); + CRNEventsManager.getEvent(CreateTrainPredictionEvent.class).run(accessor().crn$getTrain(), self(), new LinkedHashMap<>(customData), index, stayDuration, minStayDuration, cir.getReturnValue()); + } + } + + + + private static int estimateMinStayDuration(Train train, int index) { + Schedule schedule = train.runtime.getSchedule(); + if (index >= schedule.entries.size()) { + if (!schedule.cyclic) + return -1; + index = 0; + } + + ScheduleEntry scheduleEntry = schedule.entries.get(index); + Columns: for (List list : scheduleEntry.conditions) { + int total = 0; + for (ScheduleWaitCondition condition : list) { + if (condition instanceof DynamicDelayCondition wait) + total += wait.minWaitTicks(); + else if (condition instanceof ScheduledDelay wait) + total += wait.totalWaitTicks(); + else + continue Columns; + } + return total; + } + + return -1; + } +} diff --git a/common/src/main/java/de/mrjulsen/crn/mixin/ScheduleScreenAccessor.java b/common/src/main/java/de/mrjulsen/crn/mixin/ScheduleScreenAccessor.java new file mode 100644 index 00000000..66607a42 --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/mixin/ScheduleScreenAccessor.java @@ -0,0 +1,37 @@ +package de.mrjulsen.crn.mixin; + +import java.util.List; +import java.util.function.Consumer; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; +import org.spongepowered.asm.mixin.gen.Invoker; + +import com.simibubi.create.content.trains.schedule.DestinationSuggestions; +import com.simibubi.create.content.trains.schedule.IScheduleInput; +import com.simibubi.create.content.trains.schedule.ScheduleScreen; +import com.simibubi.create.foundation.gui.ModularGuiLine; +import com.simibubi.create.foundation.utility.IntAttached; + +@Mixin(ScheduleScreen.class) +public interface ScheduleScreenAccessor { + + @Accessor("editorSubWidgets") + ModularGuiLine crn$getEditorSubWidgets(); + + @Accessor("destinationSuggestions") + DestinationSuggestions crn$getDestinationSuggestions(); + + @Accessor("destinationSuggestions") + void crn$setDestinationSuggestions(DestinationSuggestions suggestions); + + @Accessor("onEditorClose") + Consumer crn$getOnEditorClose(); + + + @Invoker("onDestinationEdited") + void crn$onDestinationEdited(String text); + + @Invoker("getViableStations") + List> crn$getViableStations(IScheduleInput field); +} diff --git a/common/src/main/java/de/mrjulsen/crn/mixin/ScheduleScreenMixin.java b/common/src/main/java/de/mrjulsen/crn/mixin/ScheduleScreenMixin.java new file mode 100644 index 00000000..3acb5b6a --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/mixin/ScheduleScreenMixin.java @@ -0,0 +1,71 @@ +package de.mrjulsen.crn.mixin; + +import java.util.ArrayList; +import java.util.List; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.At.Shift; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import com.simibubi.create.content.trains.schedule.DestinationSuggestions; +import com.simibubi.create.content.trains.schedule.IScheduleInput; +import com.simibubi.create.content.trains.schedule.ScheduleScreen; +import com.simibubi.create.foundation.utility.IntAttached; + +import de.mrjulsen.crn.data.StationTag; +import de.mrjulsen.crn.data.storage.GlobalSettings; +import de.mrjulsen.crn.data.schedule.instruction.ICustomSuggestionsInstruction; +import de.mrjulsen.crn.data.schedule.instruction.IStationTagInstruction; +import de.mrjulsen.crn.data.schedule.instruction.ITrainNameInstruction; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.components.EditBox; +import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen; + +@Mixin(ScheduleScreen.class) +public class ScheduleScreenMixin { + + public ScheduleScreenAccessor accessor() { + return (ScheduleScreenAccessor)(Object)this; + } + + public ScheduleScreen self() { + return (ScheduleScreen)(Object)this; + } + + public int getTopPos() { + return ((AbstractContainerScreen)(Object)this).topPos; + } + + @SuppressWarnings("resource") + @Inject(method = "updateEditorSubwidgets", remap = false, at = @At(value = "INVOKE", shift = Shift.AFTER, target = "Lcom/simibubi/create/foundation/gui/ModularGuiLine;loadValues(Lnet/minecraft/nbt/CompoundTag;Ljava/util/function/Consumer;Ljava/util/function/Consumer;)V"), cancellable = true) + public void onUpdateEditorSubwidgets(IScheduleInput field, CallbackInfo ci) { + if (field instanceof ICustomSuggestionsInstruction) { + accessor().crn$getEditorSubWidgets().forEach(e -> { + if (!(e instanceof EditBox destinationBox)) + return; + accessor().crn$setDestinationSuggestions(new DestinationSuggestions(Minecraft.getInstance(), self(), destinationBox, Minecraft.getInstance().font, onGetViableStations(field), getTopPos() + 33)); + accessor().crn$getDestinationSuggestions().setAllowSuggestions(true); + accessor().crn$getDestinationSuggestions().updateCommandInfo(); + destinationBox.setResponder(accessor()::crn$onDestinationEdited); + }); + } + + } + + + public List> onGetViableStations(IScheduleInput field) { + if (field instanceof IStationTagInstruction) { + List stations = GlobalSettings.getInstance().getAllStationTags(); + List> result = new ArrayList<>(); + for (int i = 0; i < stations.size(); i++) { + result.add(IntAttached.with(i, stations.get(i).getTagName().get())); + } + return result; + } else if (field instanceof ITrainNameInstruction) { + return null; /* TODO */ + //return ClientTrainStationSnapshot.getInstance().getAllTrainStations().stream().map(station -> IntAttached.with(0, station)).toList(); + } + return null; + } +} diff --git a/common/src/main/java/de/mrjulsen/crn/mixin/TrainMixin.java b/common/src/main/java/de/mrjulsen/crn/mixin/TrainMixin.java new file mode 100644 index 00000000..d7852c88 --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/mixin/TrainMixin.java @@ -0,0 +1,35 @@ +package de.mrjulsen.crn.mixin; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.LocalCapture; + +import com.simibubi.create.content.trains.entity.Train; +import com.simibubi.create.content.trains.station.GlobalStation; + +import de.mrjulsen.crn.event.CRNEventsManager; +import de.mrjulsen.crn.event.events.TrainArrivalAndDepartureEvent; + +@Mixin(Train.class) +public class TrainMixin { + + public Train self() { + return (Train)(Object)this; + } + + @Inject(method = "arriveAt", remap = false, at = @At(value = "TAIL")) + public void onArriveAt(GlobalStation station, CallbackInfo ci) { + if (CRNEventsManager.isRegistered(TrainArrivalAndDepartureEvent.class)) { + CRNEventsManager.getEvent(TrainArrivalAndDepartureEvent.class).run(self(), station, true); + } + } + + @Inject(method = "leaveStation", remap = false, at = @At(value = "TAIL"), locals = LocalCapture.CAPTURE_FAILHARD) + public void onLeaveStation(CallbackInfo ci, GlobalStation currentStation) { + if (CRNEventsManager.isRegistered(TrainArrivalAndDepartureEvent.class)) { + CRNEventsManager.getEvent(TrainArrivalAndDepartureEvent.class).run(self(), currentStation, false); + } + } +} diff --git a/common/src/main/java/de/mrjulsen/crn/mixin/TrainStatusAccessor.java b/common/src/main/java/de/mrjulsen/crn/mixin/TrainStatusAccessor.java new file mode 100644 index 00000000..0360d011 --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/mixin/TrainStatusAccessor.java @@ -0,0 +1,16 @@ +package de.mrjulsen.crn.mixin; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +import com.simibubi.create.content.trains.entity.TrainStatus; + +@Mixin(TrainStatus.class) +public interface TrainStatusAccessor { + @Accessor("navigation") + boolean crn$navigation(); + @Accessor("track") + boolean crn$track(); + @Accessor("conductor") + boolean crn$conductor(); +} diff --git a/common/src/main/java/de/mrjulsen/crn/network/InstanceManager.java b/common/src/main/java/de/mrjulsen/crn/network/InstanceManager.java index 31c17995..a3ed9952 100644 --- a/common/src/main/java/de/mrjulsen/crn/network/InstanceManager.java +++ b/common/src/main/java/de/mrjulsen/crn/network/InstanceManager.java @@ -1,142 +1,10 @@ package de.mrjulsen.crn.network; -import java.util.Map; -import java.util.function.BiConsumer; -import java.util.function.Consumer; - -import de.mrjulsen.crn.data.NearestTrackStationResult; -import de.mrjulsen.crn.data.SimpleRoute; -import de.mrjulsen.crn.data.SimpleTrainConnection; -import de.mrjulsen.crn.data.DeparturePrediction.SimpleDeparturePrediction; -import de.mrjulsen.crn.network.packets.cts.TrainDataRequestPacket.TrainData; -import de.mrjulsen.crn.network.packets.stc.NavigationResponsePacket.NavigationResponseData; import de.mrjulsen.mcdragonlib.client.OverlayManager; -import java.util.Collection; -import java.util.HashMap; -import java.util.List; - public class InstanceManager { - - private static final Map CLIENT_RESPONSE_RECEIVED_ACTION = new HashMap<>(); - private static final Map, NavigationResponseData>> CLIENT_NAVIGATION_RESPONSE_ACTION = new HashMap<>(); - private static final Map> CLIENT_NEAREST_STATION_RESPONSE_ACTION = new HashMap<>(); - private static final Map, Long>> CLIENT_REALTIME_RESPONSE_ACTION = new HashMap<>(); - private static final Map, Long>> CLIENT_NEXT_CONNECTIONS_RESPONSE_ACTION = new HashMap<>(); - private static final Map> CLIENT_NEXT_TRAIN_DATA_RESPONSE_ACTION = new HashMap<>(); - private static long currentRouteOverlayId; - public static String getInstancesCountString() { - return String.format("[%s, %s, %s, %s, %s, %s]", - CLIENT_RESPONSE_RECEIVED_ACTION.size(), - CLIENT_NAVIGATION_RESPONSE_ACTION.size(), - CLIENT_NEAREST_STATION_RESPONSE_ACTION.size(), - CLIENT_REALTIME_RESPONSE_ACTION.size(), - CLIENT_NEXT_CONNECTIONS_RESPONSE_ACTION.size(), - CLIENT_NEXT_TRAIN_DATA_RESPONSE_ACTION.size() - ); - } - - public static void clearAll() { - CLIENT_RESPONSE_RECEIVED_ACTION.clear(); - CLIENT_NAVIGATION_RESPONSE_ACTION.clear(); - CLIENT_NEAREST_STATION_RESPONSE_ACTION.clear(); - CLIENT_REALTIME_RESPONSE_ACTION.clear(); - CLIENT_NEXT_CONNECTIONS_RESPONSE_ACTION.clear(); - CLIENT_NEXT_TRAIN_DATA_RESPONSE_ACTION.clear(); - } - - public static long registerClientResponseReceievedAction(Runnable runnable) { - long id = System.nanoTime(); - CLIENT_RESPONSE_RECEIVED_ACTION.put(id, runnable); - return id; - } - - public static void runClientResponseReceievedAction(long id) { - if (CLIENT_RESPONSE_RECEIVED_ACTION.containsKey(id)) { - Runnable run = CLIENT_RESPONSE_RECEIVED_ACTION.remove(id); - if (run != null) - run.run(); - } - } - - public static long registerClientNavigationResponseAction(BiConsumer, NavigationResponseData> consumer) { - long id = System.nanoTime(); - CLIENT_NAVIGATION_RESPONSE_ACTION.put(id, consumer); - return id; - } - - public static void runClientNavigationResponseAction(long id, List routes, NavigationResponseData data) { - if (CLIENT_NAVIGATION_RESPONSE_ACTION.containsKey(id)) { - BiConsumer, NavigationResponseData> action = CLIENT_NAVIGATION_RESPONSE_ACTION.remove(id); - if (action != null) - action.accept(routes, data); - } - } - - - - public static long registerClientNearestStationResponseAction(Consumer consumer) { - long id = System.nanoTime(); - CLIENT_NEAREST_STATION_RESPONSE_ACTION.put(id, consumer); - return id; - } - - public static void runClientNearestStationResponseAction(long id, NearestTrackStationResult result) { - if (CLIENT_NEAREST_STATION_RESPONSE_ACTION.containsKey(id)) { - Consumer action = CLIENT_NEAREST_STATION_RESPONSE_ACTION.remove(id); - if (action != null) - action.accept(result); - } - } - - - public static long registerClientRealtimeResponseAction(BiConsumer, Long> consumer) { - long id = System.nanoTime(); - CLIENT_REALTIME_RESPONSE_ACTION.put(id, consumer); - return id; - } - - public static void runClientRealtimeResponseAction(long id, Collection result, long time) { - if (CLIENT_REALTIME_RESPONSE_ACTION.containsKey(id)) { - BiConsumer, Long> action = CLIENT_REALTIME_RESPONSE_ACTION.remove(id); - if (action != null) - action.accept(result, time); - } - } - - - public static long registerClientNextConnectionsResponseAction(BiConsumer, Long> consumer) { - long id = System.nanoTime(); - CLIENT_NEXT_CONNECTIONS_RESPONSE_ACTION.put(id, consumer); - return id; - } - - public static void runClientNextConnectionsResponseAction(long id, Collection result, long time) { - if (CLIENT_NEXT_CONNECTIONS_RESPONSE_ACTION.containsKey(id)) { - BiConsumer, Long> action = CLIENT_NEXT_CONNECTIONS_RESPONSE_ACTION.remove(id); - if (action != null) - action.accept(result, time); - } - } - - - - public static long registerClientTrainDataResponseAction(BiConsumer consumer) { - long id = System.nanoTime(); - CLIENT_NEXT_TRAIN_DATA_RESPONSE_ACTION.put(id, consumer); - return id; - } - - public static void runClientTrainDataResponseAction(long id, TrainData data, long time) { - if (CLIENT_NEXT_TRAIN_DATA_RESPONSE_ACTION.containsKey(id)) { - BiConsumer action = CLIENT_NEXT_TRAIN_DATA_RESPONSE_ACTION.remove(id); - if (action != null) - action.accept(data, time); - } - } - public static void setRouteOverlay(long id) { removeRouteOverlay(); currentRouteOverlayId = id; diff --git a/common/src/main/java/de/mrjulsen/crn/network/packets/cts/AdvancedDisplayUpdatePacket.java b/common/src/main/java/de/mrjulsen/crn/network/packets/cts/AdvancedDisplayUpdatePacket.java index ea031060..8cbc8a46 100644 --- a/common/src/main/java/de/mrjulsen/crn/network/packets/cts/AdvancedDisplayUpdatePacket.java +++ b/common/src/main/java/de/mrjulsen/crn/network/packets/cts/AdvancedDisplayUpdatePacket.java @@ -3,10 +3,9 @@ import java.util.function.Supplier; import de.mrjulsen.crn.block.AbstractAdvancedSidedDisplayBlock; -import de.mrjulsen.crn.block.be.AdvancedDisplayBlockEntity; -import de.mrjulsen.crn.data.EDisplayInfo; -import de.mrjulsen.crn.data.EDisplayType; -import de.mrjulsen.crn.data.ESide; +import de.mrjulsen.crn.block.blockentity.AdvancedDisplayBlockEntity; +import de.mrjulsen.crn.block.properties.ESide; +import de.mrjulsen.crn.client.AdvancedDisplaysRegistry.DisplayTypeResourceKey; import de.mrjulsen.mcdragonlib.net.IPacketBase; import dev.architectury.networking.NetworkManager.PacketContext; import net.minecraft.core.BlockPos; @@ -17,51 +16,45 @@ public class AdvancedDisplayUpdatePacket implements IPacketBase { private BlockPos pos; - private EDisplayType type; - private EDisplayInfo info; + private DisplayTypeResourceKey key; private boolean doubleSided; public AdvancedDisplayUpdatePacket() {} - public AdvancedDisplayUpdatePacket(Level level, BlockPos pos, EDisplayType type, EDisplayInfo info, boolean doubleSided) { + public AdvancedDisplayUpdatePacket(Level level, BlockPos pos, DisplayTypeResourceKey key, boolean doubleSided) { this.pos = pos; - this.info = info; - this.type = type; + this.key = key; this.doubleSided = doubleSided; apply(level, this); } - protected AdvancedDisplayUpdatePacket(BlockPos pos, EDisplayType type, EDisplayInfo info, boolean doubleSided) { + protected AdvancedDisplayUpdatePacket(BlockPos pos, DisplayTypeResourceKey key, boolean doubleSided) { this.pos = pos; - this.info = info; - this.type = type; + this.key = key; this.doubleSided = doubleSided; } @Override public void encode(AdvancedDisplayUpdatePacket packet, FriendlyByteBuf buffer) { buffer.writeBlockPos(packet.pos); - buffer.writeInt(packet.info.getId()); - buffer.writeInt(packet.type.getId()); + buffer.writeNbt(packet.key.toNbt()); buffer.writeBoolean(packet.doubleSided); } @Override public AdvancedDisplayUpdatePacket decode(FriendlyByteBuf buffer) { BlockPos pos = buffer.readBlockPos(); - EDisplayInfo info = EDisplayInfo.getTypeById(buffer.readInt()); - EDisplayType type = EDisplayType.getTypeById(buffer.readInt()); + DisplayTypeResourceKey key = DisplayTypeResourceKey.fromNbt(buffer.readNbt()); boolean doubleSided = buffer.readBoolean(); - return new AdvancedDisplayUpdatePacket(pos, type, info, doubleSided); + return new AdvancedDisplayUpdatePacket(pos, key, doubleSided); } private void apply(Level level, AdvancedDisplayUpdatePacket packet) { if (level.isLoaded(packet.pos)) { if (level.getBlockEntity(packet.pos) instanceof AdvancedDisplayBlockEntity blockEntity) { blockEntity.applyToAll(be -> { - be.setDisplayType(packet.type); - be.setInfoType(packet.info); + be.setDisplayTypeKey(packet.key); if (level.getBlockState(be.getBlockPos()).getBlock() instanceof AbstractAdvancedSidedDisplayBlock) { BlockState state = level.getBlockState(be.getBlockPos()); state = state.setValue(AbstractAdvancedSidedDisplayBlock.SIDE, packet.doubleSided ? ESide.BOTH : ESide.FRONT); diff --git a/common/src/main/java/de/mrjulsen/crn/network/packets/cts/GlobalSettingsRequestPacket.java b/common/src/main/java/de/mrjulsen/crn/network/packets/cts/GlobalSettingsRequestPacket.java deleted file mode 100644 index d884d59d..00000000 --- a/common/src/main/java/de/mrjulsen/crn/network/packets/cts/GlobalSettingsRequestPacket.java +++ /dev/null @@ -1,40 +0,0 @@ -package de.mrjulsen.crn.network.packets.cts; - -import java.util.function.Supplier; - -import de.mrjulsen.crn.CreateRailwaysNavigator; -import de.mrjulsen.crn.data.GlobalSettingsManager; -import de.mrjulsen.crn.network.packets.stc.GlobalSettingsResponsePacket; -import de.mrjulsen.mcdragonlib.net.IPacketBase; -import dev.architectury.networking.NetworkManager.PacketContext; -import net.minecraft.network.FriendlyByteBuf; -import net.minecraft.server.level.ServerPlayer; - -public class GlobalSettingsRequestPacket implements IPacketBase { - - public long id; - - public GlobalSettingsRequestPacket() { } - - public GlobalSettingsRequestPacket(long id) { - this.id = id; - } - - @Override - public void encode(GlobalSettingsRequestPacket packet, FriendlyByteBuf buffer) { - buffer.writeLong(packet.id); - } - - @Override - public GlobalSettingsRequestPacket decode(FriendlyByteBuf buffer) { - long id = buffer.readLong(); - return new GlobalSettingsRequestPacket(id); - } - - @Override - public void handle(GlobalSettingsRequestPacket packet, Supplier contextSupplier) { - contextSupplier.get().queue(() -> { - CreateRailwaysNavigator.net().CHANNEL.sendToPlayer((ServerPlayer)contextSupplier.get().getPlayer(), new GlobalSettingsResponsePacket(packet.id, GlobalSettingsManager.getInstance().getSettingsData())); - }); - } -} diff --git a/common/src/main/java/de/mrjulsen/crn/network/packets/cts/GlobalSettingsUpdatePacket.java b/common/src/main/java/de/mrjulsen/crn/network/packets/cts/GlobalSettingsUpdatePacket.java deleted file mode 100644 index c6875803..00000000 --- a/common/src/main/java/de/mrjulsen/crn/network/packets/cts/GlobalSettingsUpdatePacket.java +++ /dev/null @@ -1,152 +0,0 @@ -package de.mrjulsen.crn.network.packets.cts; - -import java.util.function.Supplier; - -import de.mrjulsen.crn.CreateRailwaysNavigator; -import de.mrjulsen.crn.data.AliasName; -import de.mrjulsen.crn.data.GlobalSettingsManager; -import de.mrjulsen.crn.data.TrainGroup; -import de.mrjulsen.crn.data.TrainStationAlias; -import de.mrjulsen.crn.network.InstanceManager; -import de.mrjulsen.crn.network.packets.stc.GlobalSettingsResponsePacket; -import de.mrjulsen.mcdragonlib.net.IPacketBase; -import dev.architectury.networking.NetworkManager.PacketContext; -import net.minecraft.nbt.CompoundTag; -import net.minecraft.network.FriendlyByteBuf; -import net.minecraft.server.level.ServerPlayer; - -public class GlobalSettingsUpdatePacket implements IPacketBase { - private static final String NBT_STRING = "Val"; - private static final String NBT_COMPOUND_TAG = "Tag"; - - public long id; - public CompoundTag data; - public EGlobalSettingsAction action; - - public GlobalSettingsUpdatePacket() { } - - public GlobalSettingsUpdatePacket(long id, CompoundTag nbt, EGlobalSettingsAction action) { - this.id = id; - this.data = nbt; - this.action = action; - } - - public static void send(Object data, EGlobalSettingsAction action, Runnable then) { - CompoundTag nbt = new CompoundTag(); - switch (action) { - case ADD_TO_BLACKLIST: - case REMOVE_FROM_BLACKLIST: - case ADD_TRAIN_TO_BLACKLIST: - case REMOVE_TRAIN_FROM_BLACKLIST: - case UNREGISTER_ALIAS_STRING: - case UNREGISTER_TRAIN_GROUP_TRAIN: - nbt.putString(NBT_STRING, (String)data); - break; - case UNREGISTER_ALIAS: - case REGISTER_ALIAS: - nbt = ((TrainStationAlias)data).toNbt(); - break; - case UNREGISTER_TRAIN_GROUP: - case REGISTER_TRAIN_GROUP: - nbt = ((TrainGroup)data).toNbt(); - break; - case UPDATE_ALIAS: - Object[] dataArr = (Object[])data; - nbt.putString(NBT_STRING, (String)dataArr[0]); - nbt.put(NBT_COMPOUND_TAG, ((TrainStationAlias)dataArr[1]).toNbt()); - break; - case UPDATE_TRAIN_GROUP: - Object[] dataArr1 = (Object[])data; - nbt.putString(NBT_STRING, (String)dataArr1[0]); - nbt.put(NBT_COMPOUND_TAG, ((TrainGroup)dataArr1[1]).toNbt()); - break; - default: - return; - } - CreateRailwaysNavigator.net().CHANNEL.sendToServer(new GlobalSettingsUpdatePacket(InstanceManager.registerClientResponseReceievedAction(then), nbt, action)); - } - - @Override - public void encode(GlobalSettingsUpdatePacket packet, FriendlyByteBuf buffer) { - buffer.writeLong(packet.id); - buffer.writeNbt(packet.data); - buffer.writeEnum(packet.action); - } - - @Override - public GlobalSettingsUpdatePacket decode(FriendlyByteBuf buffer) { - long id = buffer.readLong(); - CompoundTag data = buffer.readNbt(); - EGlobalSettingsAction action = buffer.readEnum(EGlobalSettingsAction.class); - GlobalSettingsUpdatePacket instance = new GlobalSettingsUpdatePacket(id, data, action); - return instance; - } - - @Override - public void handle(GlobalSettingsUpdatePacket packet, Supplier contextSupplier) { - contextSupplier.get().queue(() -> { - if (GlobalSettingsManager.getInstance().getSettingsData() == null) { - CreateRailwaysNavigator.LOGGER.error("Failed to handle GlobalSettingsUpdatePacket! The settings instance of the global settings manager is null."); - return; - } - - switch (packet.action) { - case ADD_TO_BLACKLIST: - GlobalSettingsManager.getInstance().getSettingsData().addToBlacklistServer(packet.data.getString(NBT_STRING)); - break; - case REMOVE_FROM_BLACKLIST: - GlobalSettingsManager.getInstance().getSettingsData().removeFromBlacklistServer(packet.data.getString(NBT_STRING)); - break; - case ADD_TRAIN_TO_BLACKLIST: - GlobalSettingsManager.getInstance().getSettingsData().addTrainToBlacklistServer(packet.data.getString(NBT_STRING)); - break; - case REMOVE_TRAIN_FROM_BLACKLIST: - GlobalSettingsManager.getInstance().getSettingsData().removeTrainFromBlacklistServer(packet.data.getString(NBT_STRING)); - break; - case UNREGISTER_ALIAS_STRING: - GlobalSettingsManager.getInstance().getSettingsData().unregisterAliasServer(packet.data.getString(NBT_STRING)); - break; - case UNREGISTER_ALIAS: - GlobalSettingsManager.getInstance().getSettingsData().unregisterAliasServer(TrainStationAlias.fromNbt(packet.data)); - break; - case REGISTER_ALIAS: - GlobalSettingsManager.getInstance().getSettingsData().registerAliasServer(TrainStationAlias.fromNbt(packet.data)); - break; - case UPDATE_ALIAS: - GlobalSettingsManager.getInstance().getSettingsData().updateAliasServer(AliasName.of(packet.data.getString(NBT_STRING)), TrainStationAlias.fromNbt(packet.data.getCompound(NBT_COMPOUND_TAG))); - break; - case UNREGISTER_TRAIN_GROUP_TRAIN: - GlobalSettingsManager.getInstance().getSettingsData().unregisterTrainGroupServer(packet.data.getString(NBT_STRING)); - break; - case UNREGISTER_TRAIN_GROUP: - GlobalSettingsManager.getInstance().getSettingsData().unregisterTrainGroupServer(TrainGroup.fromNbt(packet.data).getGroupName()); - break; - case REGISTER_TRAIN_GROUP: - GlobalSettingsManager.getInstance().getSettingsData().registerTrainGroupServer(TrainGroup.fromNbt(packet.data)); - break; - case UPDATE_TRAIN_GROUP: - GlobalSettingsManager.getInstance().getSettingsData().updateTrainGroupServer(packet.data.getString(NBT_STRING), TrainGroup.fromNbt(packet.data.getCompound(NBT_COMPOUND_TAG))); - break; - default: - return; - } - GlobalSettingsManager.getInstance().setDirty(); - CreateRailwaysNavigator.net().CHANNEL.sendToPlayer((ServerPlayer)contextSupplier.get().getPlayer(), new GlobalSettingsResponsePacket(packet.id, GlobalSettingsManager.getInstance().getSettingsData())); - }); - } - - public static enum EGlobalSettingsAction { - REGISTER_ALIAS, - UNREGISTER_ALIAS_STRING, - UNREGISTER_ALIAS, - UPDATE_ALIAS, - REGISTER_TRAIN_GROUP, - UNREGISTER_TRAIN_GROUP_TRAIN, - UNREGISTER_TRAIN_GROUP, - UPDATE_TRAIN_GROUP, - ADD_TO_BLACKLIST, - REMOVE_FROM_BLACKLIST, - ADD_TRAIN_TO_BLACKLIST, - REMOVE_TRAIN_FROM_BLACKLIST; - } -} diff --git a/common/src/main/java/de/mrjulsen/crn/network/packets/cts/NavigationRequestPacket.java b/common/src/main/java/de/mrjulsen/crn/network/packets/cts/NavigationRequestPacket.java deleted file mode 100644 index 5e3b7d4a..00000000 --- a/common/src/main/java/de/mrjulsen/crn/network/packets/cts/NavigationRequestPacket.java +++ /dev/null @@ -1,92 +0,0 @@ -package de.mrjulsen.crn.network.packets.cts; - -import java.util.List; -import java.util.ArrayList; -import java.util.function.Supplier; - -import de.mrjulsen.crn.CreateRailwaysNavigator; -import de.mrjulsen.crn.core.navigation.Graph; -import de.mrjulsen.crn.data.GlobalSettingsManager; -import de.mrjulsen.crn.data.Route; -import de.mrjulsen.crn.data.SimpleRoute; -import de.mrjulsen.crn.data.TrainStationAlias; -import de.mrjulsen.crn.data.UserSettings; -import de.mrjulsen.crn.network.packets.stc.NavigationResponsePacket; -import de.mrjulsen.crn.network.packets.stc.ServerErrorPacket; -import de.mrjulsen.mcdragonlib.net.IPacketBase; -import dev.architectury.networking.NetworkManager.PacketContext; -import net.minecraft.network.FriendlyByteBuf; -import net.minecraft.server.level.ServerPlayer; - -public class NavigationRequestPacket implements IPacketBase { - - public long id; - public String start; - public String end; - public UserSettings filterSettings; - - public NavigationRequestPacket() { } - - public NavigationRequestPacket(long id, String start, String end) { - this(id, start, end, new UserSettings()); - } - - private NavigationRequestPacket(long id, String start, String end, UserSettings settings) { - this.id = id; - this.start = start; - this.end = end; - this.filterSettings = settings; - } - - @Override - public void encode(NavigationRequestPacket packet, FriendlyByteBuf buffer) { - buffer.writeLong(packet.id); - buffer.writeUtf(packet.start); - buffer.writeUtf(packet.end); - buffer.writeNbt(packet.filterSettings.toNbt()); - } - - @Override - public NavigationRequestPacket decode(FriendlyByteBuf buffer) { - long id = buffer.readLong(); - String start = buffer.readUtf(); - String end = buffer.readUtf(); - UserSettings filterSettings = UserSettings.fromNbt(buffer.readNbt()); - return new NavigationRequestPacket(id, start, end, filterSettings); - } - - @Override - public void handle(NavigationRequestPacket packet, Supplier contextSupplier) { - contextSupplier.get().queue(() -> { - Thread navigationThread = new Thread(() -> { - List routes = new ArrayList<>(); - final long updateTime = contextSupplier.get().getPlayer().level.getDayTime(); - final long startTime = System.currentTimeMillis(); - - try { - TrainStationAlias startAlias = GlobalSettingsManager.getInstance().getSettingsData().getAliasFor(packet.start); - TrainStationAlias endAlias = GlobalSettingsManager.getInstance().getSettingsData().getAliasFor(packet.end); - - if (startAlias == null || endAlias == null) { - return; - } - - Graph graph = new Graph(contextSupplier.get().getPlayer().getLevel(), packet.filterSettings); - routes.addAll(graph.navigate(startAlias, endAlias, true)); - } catch (Exception e) { - CreateRailwaysNavigator.LOGGER.error("Navigation error: ", e); - CreateRailwaysNavigator.net().CHANNEL.sendToPlayer((ServerPlayer)contextSupplier.get().getPlayer(), new ServerErrorPacket(e.getMessage())); - } finally { - final long estimatedTime = System.currentTimeMillis() - startTime; - CreateRailwaysNavigator.LOGGER.info(String.format("Route calculated. Took %sms.", - estimatedTime - )); - CreateRailwaysNavigator.net().CHANNEL.sendToPlayer((ServerPlayer)contextSupplier.get().getPlayer(), new NavigationResponsePacket(packet.id, new ArrayList<>(routes.stream().filter(x -> !x.isEmpty()).map(x -> new SimpleRoute(x)).toList()), estimatedTime, updateTime)); - } - }); - navigationThread.setPriority(Thread.MIN_PRIORITY); - navigationThread.setName("Navigator"); - navigationThread.start(); - }); - } -} diff --git a/common/src/main/java/de/mrjulsen/crn/network/packets/cts/NearestStationRequestPacket.java b/common/src/main/java/de/mrjulsen/crn/network/packets/cts/NearestStationRequestPacket.java deleted file mode 100644 index 56c9b980..00000000 --- a/common/src/main/java/de/mrjulsen/crn/network/packets/cts/NearestStationRequestPacket.java +++ /dev/null @@ -1,65 +0,0 @@ -package de.mrjulsen.crn.network.packets.cts; - -import java.util.function.Supplier; - -import de.mrjulsen.crn.CreateRailwaysNavigator; -import de.mrjulsen.crn.data.NearestTrackStationResult; -import de.mrjulsen.crn.network.packets.stc.NearestStationResponsePacket; -import de.mrjulsen.crn.network.packets.stc.ServerErrorPacket; -import de.mrjulsen.crn.util.TrainUtils; -import de.mrjulsen.mcdragonlib.net.IPacketBase; -import dev.architectury.networking.NetworkManager.PacketContext; -import net.minecraft.core.BlockPos; -import net.minecraft.network.FriendlyByteBuf; -import net.minecraft.server.level.ServerPlayer; -import net.minecraft.world.phys.Vec3; - -public class NearestStationRequestPacket implements IPacketBase { - - public long id; - public BlockPos pos; - - public NearestStationRequestPacket() { } - - public NearestStationRequestPacket(long id, Vec3 pos) { - this(id, new BlockPos(pos)); - } - - public NearestStationRequestPacket(long id, BlockPos pos) { - this.id = id; - this.pos = pos; - } - - @Override - public void encode(NearestStationRequestPacket packet, FriendlyByteBuf buffer) { - buffer.writeLong(packet.id); - buffer.writeBlockPos(packet.pos); - } - - @Override - public NearestStationRequestPacket decode(FriendlyByteBuf buffer) { - long id = buffer.readLong(); - BlockPos pos = buffer.readBlockPos(); - return new NearestStationRequestPacket(id, pos); - } - - @Override - public void handle(NearestStationRequestPacket packet, Supplier contextSupplier) { - contextSupplier.get().queue(() -> { - Thread navigationThread = new Thread(() -> { - NearestTrackStationResult result = NearestTrackStationResult.empty(); - try { - result = TrainUtils.getNearestTrackStation(contextSupplier.get().getPlayer().getLevel(), packet.pos); - } catch (Exception e) { - CreateRailwaysNavigator.LOGGER.error("Error while trying to find nearest track station ", e); - CreateRailwaysNavigator.net().CHANNEL.sendToPlayer((ServerPlayer)contextSupplier.get().getPlayer(), new ServerErrorPacket(e.getMessage())); - } finally { - CreateRailwaysNavigator.net().CHANNEL.sendToPlayer((ServerPlayer)contextSupplier.get().getPlayer(), new NearestStationResponsePacket(packet.id, result)); - } - }); - navigationThread.setPriority(Thread.MIN_PRIORITY); - navigationThread.setName("Station Location Calculator"); - navigationThread.start(); - }); - } -} diff --git a/common/src/main/java/de/mrjulsen/crn/network/packets/cts/NextConnectionsRequestPacket.java b/common/src/main/java/de/mrjulsen/crn/network/packets/cts/NextConnectionsRequestPacket.java deleted file mode 100644 index 9199d9ea..00000000 --- a/common/src/main/java/de/mrjulsen/crn/network/packets/cts/NextConnectionsRequestPacket.java +++ /dev/null @@ -1,57 +0,0 @@ -package de.mrjulsen.crn.network.packets.cts; - -import java.util.UUID; -import java.util.function.Supplier; - -import de.mrjulsen.crn.CreateRailwaysNavigator; -import de.mrjulsen.crn.network.packets.stc.NextConnectionsResponsePacket; -import de.mrjulsen.crn.util.TrainUtils; -import de.mrjulsen.mcdragonlib.net.IPacketBase; -import dev.architectury.networking.NetworkManager.PacketContext; -import net.minecraft.network.FriendlyByteBuf; -import net.minecraft.server.level.ServerPlayer; - -public class NextConnectionsRequestPacket implements IPacketBase { - - public long requestId; - public UUID trainId; - public long ticksToNextStop; - public String currentStationName; - - public NextConnectionsRequestPacket() { } - - public NextConnectionsRequestPacket(long requestId, UUID trainId, String currentStationName, long ticksToNextStop) { - this.requestId = requestId; - this.trainId = trainId; - this.ticksToNextStop = ticksToNextStop; - this.currentStationName = currentStationName; - } - - @Override - public void encode(NextConnectionsRequestPacket packet, FriendlyByteBuf buffer) { - buffer.writeLong(packet.requestId); - buffer.writeUUID(packet.trainId); - buffer.writeLong(packet.ticksToNextStop); - buffer.writeUtf(packet.currentStationName); - } - - @Override - public NextConnectionsRequestPacket decode(FriendlyByteBuf buffer) { - long requestId = buffer.readLong(); - UUID trainId = buffer.readUUID(); - long ticksToNextStop = buffer.readLong(); - String currentStationName = buffer.readUtf(); - - return new NextConnectionsRequestPacket(requestId, trainId, currentStationName, ticksToNextStop); - } - - @Override - public void handle(NextConnectionsRequestPacket packet, Supplier contextSupplier) { - contextSupplier.get().queue(() -> { - new Thread(() -> { - final long updateTime = contextSupplier.get().getPlayer().getLevel().getDayTime(); - CreateRailwaysNavigator.net().CHANNEL.sendToPlayer((ServerPlayer)contextSupplier.get().getPlayer(), new NextConnectionsResponsePacket(packet.requestId, TrainUtils.getConnectionsAt(packet.currentStationName, packet.trainId, (int)packet.ticksToNextStop), updateTime)); - }, "Connections Loader").run(); - }); - } -} diff --git a/common/src/main/java/de/mrjulsen/crn/network/packets/cts/RealtimeRequestPacket.java b/common/src/main/java/de/mrjulsen/crn/network/packets/cts/RealtimeRequestPacket.java deleted file mode 100644 index f624853a..00000000 --- a/common/src/main/java/de/mrjulsen/crn/network/packets/cts/RealtimeRequestPacket.java +++ /dev/null @@ -1,72 +0,0 @@ -package de.mrjulsen.crn.network.packets.cts; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Comparator; -import java.util.UUID; -import java.util.function.Supplier; - -import de.mrjulsen.crn.CreateRailwaysNavigator; -import de.mrjulsen.crn.data.GlobalSettingsManager; -import de.mrjulsen.crn.data.DeparturePrediction.SimpleDeparturePrediction; -import de.mrjulsen.crn.network.packets.stc.RealtimeResponsePacket; -import de.mrjulsen.crn.util.TrainUtils; -import de.mrjulsen.mcdragonlib.net.IPacketBase; -import dev.architectury.networking.NetworkManager.PacketContext; -import net.minecraft.network.FriendlyByteBuf; -import net.minecraft.server.level.ServerPlayer; -import net.minecraft.world.level.Level; - -public class RealtimeRequestPacket implements IPacketBase { - - public long requestId; - public Collection ids; - - public RealtimeRequestPacket() { } - - public RealtimeRequestPacket(long requestId, Collection ids) { - this.requestId = requestId; - this.ids = ids; - } - - @Override - public void encode(RealtimeRequestPacket packet, FriendlyByteBuf buffer) { - buffer.writeLong(packet.requestId); - buffer.writeInt(packet.ids.size()); - for (UUID u : packet.ids) { - buffer.writeUUID(u); - } - } - - @Override - public RealtimeRequestPacket decode(FriendlyByteBuf buffer) { - long requestId = buffer.readLong(); - int count = buffer.readInt(); - Collection uuids = new ArrayList<>(); - for (int i = 0; i < count; i++) { - uuids.add(buffer.readUUID()); - } - return new RealtimeRequestPacket(requestId, uuids); - } - - @Override - public void handle(RealtimeRequestPacket packet, Supplier contextSupplier) { - contextSupplier.get().queue(() -> { - final Level level = contextSupplier.get().getPlayer().getLevel(); - new Thread(() -> { - final long updateTime = level.getDayTime(); - Collection predictions = new ArrayList<>(); - packet.ids.forEach(x -> { - if (!TrainUtils.isTrainIdValid(x)) { - return; - } - - predictions.addAll(TrainUtils.getTrainDeparturePredictions(x, contextSupplier.get().getPlayer().getLevel()).stream().map(a -> a.simplify()).filter(a -> !GlobalSettingsManager.getInstance().getSettingsData().isBlacklisted(a.stationName())).sorted(Comparator.comparingInt(a -> a.departureTicks())).toList()); - }); - CreateRailwaysNavigator.net().CHANNEL.sendToPlayer((ServerPlayer)contextSupplier.get().getPlayer(), (new RealtimeResponsePacket(packet.requestId, predictions, updateTime))); - }, "Realtime Provider").run(); - }); - } - - public static record StationData(Collection stationName, Collection indices, UUID trainId) {} -} diff --git a/common/src/main/java/de/mrjulsen/crn/network/packets/cts/TrackStationsRequestPacket.java b/common/src/main/java/de/mrjulsen/crn/network/packets/cts/TrackStationsRequestPacket.java deleted file mode 100644 index e8fc3e84..00000000 --- a/common/src/main/java/de/mrjulsen/crn/network/packets/cts/TrackStationsRequestPacket.java +++ /dev/null @@ -1,47 +0,0 @@ -package de.mrjulsen.crn.network.packets.cts; - -import java.util.function.Supplier; - -import de.mrjulsen.crn.CreateRailwaysNavigator; -import de.mrjulsen.crn.event.listeners.TrainListener; -import de.mrjulsen.crn.network.packets.stc.TrackStationResponsePacket; -import de.mrjulsen.crn.util.TrainUtils; -import de.mrjulsen.mcdragonlib.net.IPacketBase; -import dev.architectury.networking.NetworkManager.PacketContext; -import net.minecraft.network.FriendlyByteBuf; -import net.minecraft.server.level.ServerPlayer; - -public class TrackStationsRequestPacket implements IPacketBase { - - public long id; - - public TrackStationsRequestPacket() { } - - public TrackStationsRequestPacket(long id) { - this.id = id; - } - - @Override - public void encode(TrackStationsRequestPacket packet, FriendlyByteBuf buffer) { - buffer.writeLong(packet.id); - } - - @Override - public TrackStationsRequestPacket decode(FriendlyByteBuf buffer) { - long id = buffer.readLong(); - return new TrackStationsRequestPacket(id); - } - - @Override - public void handle(TrackStationsRequestPacket packet, Supplier contextSupplier) { - contextSupplier.get().queue(() -> { - CreateRailwaysNavigator.net().CHANNEL.sendToPlayer((ServerPlayer)contextSupplier.get().getPlayer(), new TrackStationResponsePacket( - packet.id, - TrainUtils.getAllStations().stream().map(x -> x.name).toList(), - TrainUtils.getAllTrains().stream().map(x -> x.name.getString()).toList(), - TrainListener.getInstance().getListeningTrainCount(), - TrainListener.getInstance().getTotalTrainCount() - )); - }); - } -} diff --git a/common/src/main/java/de/mrjulsen/crn/network/packets/cts/TrainDataRequestPacket.java b/common/src/main/java/de/mrjulsen/crn/network/packets/cts/TrainDataRequestPacket.java deleted file mode 100644 index 4c04489c..00000000 --- a/common/src/main/java/de/mrjulsen/crn/network/packets/cts/TrainDataRequestPacket.java +++ /dev/null @@ -1,196 +0,0 @@ -package de.mrjulsen.crn.network.packets.cts; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Optional; -import java.util.UUID; -import java.util.function.Supplier; - -import com.simibubi.create.content.trains.entity.Train; - -import de.mrjulsen.crn.Constants; -import de.mrjulsen.crn.CreateRailwaysNavigator; -import de.mrjulsen.crn.client.lang.ELanguage; -import de.mrjulsen.crn.data.DeparturePrediction.TrainExitSide; -import de.mrjulsen.crn.data.DeparturePrediction.SimpleDeparturePrediction; -import de.mrjulsen.crn.data.GlobalSettingsManager; -import de.mrjulsen.crn.data.SimpleTrainSchedule; -import de.mrjulsen.crn.data.TrainStop; -import de.mrjulsen.crn.network.packets.stc.TrainDataResponsePacket; -import de.mrjulsen.crn.util.TrainUtils; -import de.mrjulsen.mcdragonlib.data.Cache; -import de.mrjulsen.mcdragonlib.net.IPacketBase; -import dev.architectury.networking.NetworkManager.PacketContext; -import net.minecraft.nbt.CompoundTag; -import net.minecraft.nbt.ListTag; -import net.minecraft.nbt.Tag; -import net.minecraft.network.FriendlyByteBuf; -import net.minecraft.network.chat.MutableComponent; -import net.minecraft.server.level.ServerPlayer; -import net.minecraft.world.level.Level; - -public class TrainDataRequestPacket implements IPacketBase { - - public long requestId; - public UUID trainId; - public boolean getPredictions; - - public TrainDataRequestPacket() { } - - public TrainDataRequestPacket(long requestId, UUID trainId, boolean getPredictions) { - this.requestId = requestId; - this.trainId = trainId; - this.getPredictions = getPredictions; - } - - @Override - public void encode(TrainDataRequestPacket packet, FriendlyByteBuf buffer) { - buffer.writeLong(packet.requestId); - buffer.writeUUID(packet.trainId); - buffer.writeBoolean(packet.getPredictions); - } - - @Override - public TrainDataRequestPacket decode(FriendlyByteBuf buffer) { - long requestId = buffer.readLong(); - UUID trainId = buffer.readUUID(); - boolean getPredictions = buffer.readBoolean(); - - return new TrainDataRequestPacket(requestId, trainId, getPredictions); - } - - @Override - public void handle(TrainDataRequestPacket packet, Supplier contextSupplier) { - contextSupplier.get().queue(() -> { - final Level level = contextSupplier.get().getPlayer().getLevel(); - final long updateTime = level.getDayTime(); - Train train = TrainUtils.getTrain(packet.trainId); - List departurePredictions = new ArrayList<>(); - if (packet.getPredictions && train != null) { - Collection stops = new ArrayList<>(TrainUtils.getTrainStopsSorted(packet.trainId, level).stream().filter(x -> !GlobalSettingsManager.getInstance().getSettingsData().isBlacklisted(x.getPrediction().getStationName())).toList()); - if (stops != null) { - departurePredictions.addAll(SimpleTrainSchedule.of(stops).makeScheduleUntilNextRepeat().getAllStops().stream().map(x -> x.getPrediction().simplify()).toList()); - } - } - - CreateRailwaysNavigator.net().CHANNEL.sendToPlayer((ServerPlayer)contextSupplier.get().getPlayer(), new TrainDataResponsePacket(packet.requestId, new TrainData( - packet.trainId, - train.name.getString(), - departurePredictions, - train.speed, - train.navigation.ticksWaitingForSignal, - train.currentlyBackwards, - true - ), updateTime)); - }); - } - - public static class TrainData { - private static final String NBT_TRAIN_ID = "Id"; - private static final String NBT_SPEED = "Speed"; - private static final String NBT_WAITING_FOR_SIGNAL = "WaitingForSignal"; - private static final String NBT_NAME = "Name"; - private static final String NBT_PREDICTIONS = "Predictions"; - private static final String NBT_TRAIN_DIRECTION = "Direction"; - private static final String NBT_ON_TRAIN = "OnTrain"; - - private final UUID trainId; - private final String trainName; - private final List predictions; - private final double speed; - private final int ticksWaitingForSignal; - private final boolean oppositeDirection; - - private final boolean onTrain; - - private final Cache> stopovers = new Cache<>(() -> { - List s = new ArrayList<>(); - if (predictions().size() >= 2) { - for (int i = 1; i < predictions().size() - 1; i++) { - s.add(predictions().get(i)); - } - } - return s; - }); - - public TrainData(UUID trainId, String trainName, List predictions, double speed, int ticksWaitingForSignal, boolean oppositeDirection, boolean onTrain) { - this.trainId = trainId; - this.trainName = trainName; - this.predictions = predictions; - this.speed = speed; - this.ticksWaitingForSignal = ticksWaitingForSignal; - this.oppositeDirection = oppositeDirection; - this.onTrain = onTrain; - } - - public UUID trainId() { - return trainId; - } - - public String trainName() { - return trainName; - } - - public List predictions() { - return predictions; - } - - public double speed() { - return speed; - } - - public int ticksWaitingForSignal() { - return ticksWaitingForSignal; - } - - public List stopovers() { - return stopovers.get(); - } - - public boolean isOppositeDirection() { - return oppositeDirection; - } - - public CompoundTag toNbt() { - CompoundTag nbt = new CompoundTag(); - nbt.putUUID(NBT_TRAIN_ID, trainId); - nbt.putDouble(NBT_SPEED, speed); - nbt.putInt(NBT_WAITING_FOR_SIGNAL, ticksWaitingForSignal); - nbt.putString(NBT_NAME, trainName); - nbt.putBoolean(NBT_TRAIN_DIRECTION, oppositeDirection); - nbt.putBoolean(NBT_ON_TRAIN, onTrain); - - ListTag list = new ListTag(); - list.addAll(predictions().stream().map(x -> x.toNbt()).toList()); - nbt.put(NBT_PREDICTIONS, list); - return nbt; - } - - public static TrainData fromNbt(CompoundTag nbt) { - return new TrainData( - nbt.getUUID(NBT_TRAIN_ID), - nbt.getString(NBT_NAME), - nbt.getList(NBT_PREDICTIONS, Tag.TAG_COMPOUND).stream().map(x -> SimpleDeparturePrediction.fromNbt(((CompoundTag)x))).toList(), - nbt.getDouble(NBT_SPEED), - nbt.getInt(NBT_WAITING_FOR_SIGNAL), - nbt.getBoolean(NBT_TRAIN_DIRECTION), - nbt.getBoolean(NBT_ON_TRAIN) - ); - } - - public Optional getNextStop() { - return predictions().size() > 0 ? Optional.of(predictions().get(0)) : Optional.empty(); - } - - public Optional getLastStop() { - return predictions().size() > 0 ? Optional.of(predictions().get(predictions().size() - 1)) : Optional.empty(); - } - - public static TrainData empty(boolean onTrain) { - MutableComponent text = onTrain ? ELanguage.translate("block.createrailwaysnavigator.advanced_display.ber.shunting_trip") : ELanguage.translate("block.createrailwaysnavigator.advanced_display.ber.not_in_service"); - return new TrainData(Constants.ZERO_UUID, "", List.of(new SimpleDeparturePrediction("", "", 0, text.getString(), "", Constants.ZERO_UUID, null, TrainExitSide.UNKNOWN)), 0, 0, false, onTrain); - } - - } -} diff --git a/common/src/main/java/de/mrjulsen/crn/network/packets/stc/GlobalSettingsResponsePacket.java b/common/src/main/java/de/mrjulsen/crn/network/packets/stc/GlobalSettingsResponsePacket.java deleted file mode 100644 index 2f97790d..00000000 --- a/common/src/main/java/de/mrjulsen/crn/network/packets/stc/GlobalSettingsResponsePacket.java +++ /dev/null @@ -1,49 +0,0 @@ -package de.mrjulsen.crn.network.packets.stc; - -import java.util.function.Supplier; - -import de.mrjulsen.crn.data.GlobalSettings; -import de.mrjulsen.crn.data.GlobalSettingsManager; -import de.mrjulsen.crn.network.InstanceManager; -import de.mrjulsen.mcdragonlib.net.IPacketBase; -import dev.architectury.networking.NetworkManager.PacketContext; -import dev.architectury.utils.Env; -import dev.architectury.utils.EnvExecutor; -import net.minecraft.nbt.CompoundTag; -import net.minecraft.network.FriendlyByteBuf; - -public class GlobalSettingsResponsePacket implements IPacketBase { - public long id; - public GlobalSettings settings; - - public GlobalSettingsResponsePacket() { } - - public GlobalSettingsResponsePacket(long id, GlobalSettings settings) { - this.id = id; - this.settings = settings; - } - - @Override - public void encode(GlobalSettingsResponsePacket packet, FriendlyByteBuf buffer) { - buffer.writeLong(packet.id); - buffer.writeNbt(packet.settings.toNbt(new CompoundTag())); - } - - @Override - public GlobalSettingsResponsePacket decode(FriendlyByteBuf buffer) { - long id = buffer.readLong(); - GlobalSettings settings = GlobalSettings.fromNbt(buffer.readNbt()); - return new GlobalSettingsResponsePacket(id, settings); - } - - @Override - public void handle(GlobalSettingsResponsePacket packet, Supplier contextSupplier) { - EnvExecutor.runInEnv(Env.CLIENT, () -> () -> { - contextSupplier.get().queue(() -> { - GlobalSettingsManager.createClientInstance().updateSettingsData(packet.settings); - InstanceManager.runClientResponseReceievedAction(packet.id); - }); - }); - } -} - diff --git a/common/src/main/java/de/mrjulsen/crn/network/packets/stc/NavigationResponsePacket.java b/common/src/main/java/de/mrjulsen/crn/network/packets/stc/NavigationResponsePacket.java deleted file mode 100644 index 043ea74e..00000000 --- a/common/src/main/java/de/mrjulsen/crn/network/packets/stc/NavigationResponsePacket.java +++ /dev/null @@ -1,65 +0,0 @@ -package de.mrjulsen.crn.network.packets.stc; - -import java.util.List; -import java.util.ArrayList; -import java.util.function.Supplier; - -import de.mrjulsen.crn.data.SimpleRoute; -import de.mrjulsen.crn.network.InstanceManager; -import de.mrjulsen.mcdragonlib.net.IPacketBase; -import dev.architectury.networking.NetworkManager.PacketContext; -import dev.architectury.utils.EnvExecutor; -import dev.architectury.utils.Env; -import net.minecraft.network.FriendlyByteBuf; - -public class NavigationResponsePacket implements IPacketBase { - public long id; - public List routes; - public long duration; - public long lastUpdated; - - public NavigationResponsePacket() { } - - public NavigationResponsePacket(long id, List routes, long duration, long lastUpdated) { - this.id = id; - this.routes = routes; - this.duration = duration; - this.lastUpdated = lastUpdated; - } - - @Override - public void encode(NavigationResponsePacket packet, FriendlyByteBuf buffer) { - buffer.writeLong(packet.id); - buffer.writeLong(packet.duration); - buffer.writeLong(packet.lastUpdated); - buffer.writeInt(packet.routes.size()); - for (SimpleRoute route : packet.routes) { - buffer.writeNbt(route.toNbt()); - } - } - - @Override - public NavigationResponsePacket decode(FriendlyByteBuf buffer) { - long id = buffer.readLong(); - long duration = buffer.readLong(); - long lastUpdated = buffer.readLong(); - int routesCount = buffer.readInt(); - List routes = new ArrayList<>(routesCount); - for (int i = 0; i < routesCount; i++) { - routes.add(SimpleRoute.fromNbt(buffer.readNbt())); - } - return new NavigationResponsePacket(id, routes, duration, lastUpdated); - } - - @Override - public void handle(NavigationResponsePacket packet, Supplier contextSupplier) { - EnvExecutor.runInEnv(Env.CLIENT, () -> () -> { - contextSupplier.get().queue(() -> { - InstanceManager.runClientNavigationResponseAction(packet.id, packet.routes, new NavigationResponseData(packet.lastUpdated, packet.duration)); - }); - }); - } - - public static record NavigationResponseData(long lastUpdated, long calculationTime) {} -} - diff --git a/common/src/main/java/de/mrjulsen/crn/network/packets/stc/NearestStationResponsePacket.java b/common/src/main/java/de/mrjulsen/crn/network/packets/stc/NearestStationResponsePacket.java deleted file mode 100644 index cd865cab..00000000 --- a/common/src/main/java/de/mrjulsen/crn/network/packets/stc/NearestStationResponsePacket.java +++ /dev/null @@ -1,46 +0,0 @@ -package de.mrjulsen.crn.network.packets.stc; - -import java.util.function.Supplier; - -import de.mrjulsen.crn.data.NearestTrackStationResult; -import de.mrjulsen.crn.network.InstanceManager; -import de.mrjulsen.mcdragonlib.net.IPacketBase; -import dev.architectury.networking.NetworkManager.PacketContext; -import dev.architectury.utils.EnvExecutor; -import dev.architectury.utils.Env; -import net.minecraft.network.FriendlyByteBuf; - -public class NearestStationResponsePacket implements IPacketBase { - public long id; - public NearestTrackStationResult result; - - public NearestStationResponsePacket() { } - - public NearestStationResponsePacket(long id, NearestTrackStationResult result) { - this.id = id; - this.result = result; - } - - @Override - public void encode(NearestStationResponsePacket packet, FriendlyByteBuf buffer) { - buffer.writeLong(packet.id); - packet.result.serialize(buffer); - } - - @Override - public NearestStationResponsePacket decode(FriendlyByteBuf buffer) { - long id = buffer.readLong(); - NearestTrackStationResult result = NearestTrackStationResult.deserialize(buffer); - return new NearestStationResponsePacket(id, result); - } - - @Override - public void handle(NearestStationResponsePacket packet, Supplier contextSupplier) { - EnvExecutor.runInEnv(Env.CLIENT, () -> () -> { - contextSupplier.get().queue(() -> { - InstanceManager.runClientNearestStationResponseAction(packet.id, packet.result); - }); - }); - } -} - diff --git a/common/src/main/java/de/mrjulsen/crn/network/packets/stc/NextConnectionsResponsePacket.java b/common/src/main/java/de/mrjulsen/crn/network/packets/stc/NextConnectionsResponsePacket.java deleted file mode 100644 index 50a14eb1..00000000 --- a/common/src/main/java/de/mrjulsen/crn/network/packets/stc/NextConnectionsResponsePacket.java +++ /dev/null @@ -1,60 +0,0 @@ -package de.mrjulsen.crn.network.packets.stc; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.function.Supplier; - -import de.mrjulsen.crn.data.SimpleTrainConnection; -import de.mrjulsen.crn.network.InstanceManager; -import de.mrjulsen.mcdragonlib.net.IPacketBase; -import dev.architectury.networking.NetworkManager.PacketContext; -import dev.architectury.utils.EnvExecutor; -import dev.architectury.utils.Env; -import net.minecraft.network.FriendlyByteBuf; - -public class NextConnectionsResponsePacket implements IPacketBase { - public long id; - public Collection connections; - public long time; - - public NextConnectionsResponsePacket() { } - - public NextConnectionsResponsePacket(long id, Collection connections, long time) { - this.id = id; - this.connections = connections; - this.time = time; - } - - @Override - public void encode(NextConnectionsResponsePacket packet, FriendlyByteBuf buffer) { - buffer.writeLong(packet.id); - buffer.writeLong(packet.time); - buffer.writeInt(packet.connections.size()); - for (SimpleTrainConnection s : packet.connections) { - buffer.writeNbt(s.toNbt()); - } - } - - @Override - public NextConnectionsResponsePacket decode(FriendlyByteBuf buffer) { - long id = buffer.readLong(); - long time = buffer.readLong(); - int count = buffer.readInt(); - List connections = new ArrayList<>(count); - for (int i = 0; i < count; i++) { - connections.add(SimpleTrainConnection.fromNbt(buffer.readNbt())); - } - return new NextConnectionsResponsePacket(id, connections, time); - } - - @Override - public void handle(NextConnectionsResponsePacket packet, Supplier contextSupplier) { - EnvExecutor.runInEnv(Env.CLIENT, () -> () -> { - contextSupplier.get().queue(() -> { - InstanceManager.runClientNextConnectionsResponseAction(packet.id, packet.connections, packet.time); - }); - }); - } -} - diff --git a/common/src/main/java/de/mrjulsen/crn/network/packets/stc/RealtimeResponsePacket.java b/common/src/main/java/de/mrjulsen/crn/network/packets/stc/RealtimeResponsePacket.java deleted file mode 100644 index a8eed92d..00000000 --- a/common/src/main/java/de/mrjulsen/crn/network/packets/stc/RealtimeResponsePacket.java +++ /dev/null @@ -1,62 +0,0 @@ -package de.mrjulsen.crn.network.packets.stc; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.function.Supplier; - -import de.mrjulsen.crn.data.DeparturePrediction.SimpleDeparturePrediction; -import de.mrjulsen.crn.network.InstanceManager; -import de.mrjulsen.mcdragonlib.net.IPacketBase; -import dev.architectury.networking.NetworkManager.PacketContext; -import dev.architectury.utils.EnvExecutor; -import dev.architectury.utils.Env; -import net.minecraft.network.FriendlyByteBuf; - -public class RealtimeResponsePacket implements IPacketBase { - public long id; - public Collection departure; - public long time; - - public RealtimeResponsePacket() { } - - public RealtimeResponsePacket(long id, Collection departure, long time) { - this.id = id; - this.departure = departure; - this.time = time; - } - - @Override - public void encode(RealtimeResponsePacket packet, FriendlyByteBuf buffer) { - buffer.writeLong(packet.id); - buffer.writeLong(packet.time); - buffer.writeInt(packet.departure.size()); - for (SimpleDeparturePrediction s : packet.departure) { - buffer.writeNbt(s.toNbt()); - } - } - - @Override - public RealtimeResponsePacket decode(FriendlyByteBuf buffer) { - long id = buffer.readLong(); - long time = buffer.readLong(); - int count = buffer.readInt(); - List departure = new ArrayList<>(count); - for (int i = 0; i < count; i++) { - departure.add(SimpleDeparturePrediction.fromNbt(buffer.readNbt())); - } - return new RealtimeResponsePacket(id, departure, time); - } - - @Override - public void handle(RealtimeResponsePacket packet, Supplier contextSupplier) { - EnvExecutor.runInEnv(Env.CLIENT, () -> () -> { - contextSupplier.get().queue(() -> { - new Thread(() -> { - InstanceManager.runClientRealtimeResponseAction(packet.id, packet.departure, packet.time); - }, "Realtime Processor").run(); - }); - }); - } -} - diff --git a/common/src/main/java/de/mrjulsen/crn/network/packets/stc/ServerErrorPacket.java b/common/src/main/java/de/mrjulsen/crn/network/packets/stc/ServerErrorPacket.java index 20974106..5f78907d 100644 --- a/common/src/main/java/de/mrjulsen/crn/network/packets/stc/ServerErrorPacket.java +++ b/common/src/main/java/de/mrjulsen/crn/network/packets/stc/ServerErrorPacket.java @@ -6,7 +6,7 @@ import de.mrjulsen.mcdragonlib.net.IPacketBase; import dev.architectury.networking.NetworkManager.PacketContext; import dev.architectury.utils.EnvExecutor; -import dev.architectury.utils.Env; +import net.fabricmc.api.EnvType; import net.minecraft.network.FriendlyByteBuf; public class ServerErrorPacket implements IPacketBase { @@ -30,7 +30,7 @@ public ServerErrorPacket decode(FriendlyByteBuf buffer) { @Override public void handle(ServerErrorPacket packet, Supplier contextSupplier) { - EnvExecutor.runInEnv(Env.CLIENT, () -> () -> { + EnvExecutor.runInEnv(EnvType.CLIENT, () -> () -> { contextSupplier.get().queue(() -> { ClientWrapper.handleErrorMessagePacket(packet, contextSupplier); }); diff --git a/common/src/main/java/de/mrjulsen/crn/network/packets/stc/TimeCorrectionPacket.java b/common/src/main/java/de/mrjulsen/crn/network/packets/stc/TimeCorrectionPacket.java deleted file mode 100644 index 20d2454c..00000000 --- a/common/src/main/java/de/mrjulsen/crn/network/packets/stc/TimeCorrectionPacket.java +++ /dev/null @@ -1,40 +0,0 @@ -package de.mrjulsen.crn.network.packets.stc; - -import java.util.function.Supplier; - -import de.mrjulsen.crn.event.listeners.JourneyListenerManager; -import de.mrjulsen.mcdragonlib.net.IPacketBase; -import dev.architectury.networking.NetworkManager.PacketContext; -import dev.architectury.utils.EnvExecutor; -import dev.architectury.utils.Env; -import net.minecraft.network.FriendlyByteBuf; - -public class TimeCorrectionPacket implements IPacketBase { - public int amount; - - public TimeCorrectionPacket() { } - - public TimeCorrectionPacket(int amount) { - this.amount = amount; - } - - @Override - public void encode(TimeCorrectionPacket packet, FriendlyByteBuf buffer) { - buffer.writeInt(packet.amount); - } - - @Override - public TimeCorrectionPacket decode(FriendlyByteBuf buffer) { - return new TimeCorrectionPacket(buffer.readInt()); - } - - @Override - public void handle(TimeCorrectionPacket packet, Supplier contextSupplier) { - EnvExecutor.runInEnv(Env.CLIENT, () -> () -> { - contextSupplier.get().queue(() -> { - JourneyListenerManager.getInstance().getAllListeners().forEach(x -> x.getListeningRoute().shiftTime(packet.amount)); - }); - }); - } -} - diff --git a/common/src/main/java/de/mrjulsen/crn/network/packets/stc/TrackStationResponsePacket.java b/common/src/main/java/de/mrjulsen/crn/network/packets/stc/TrackStationResponsePacket.java deleted file mode 100644 index b99f9c07..00000000 --- a/common/src/main/java/de/mrjulsen/crn/network/packets/stc/TrackStationResponsePacket.java +++ /dev/null @@ -1,83 +0,0 @@ -package de.mrjulsen.crn.network.packets.stc; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.function.Supplier; - -import de.mrjulsen.crn.data.ClientTrainStationSnapshot; -import de.mrjulsen.crn.network.InstanceManager; -import de.mrjulsen.mcdragonlib.net.IPacketBase; -import dev.architectury.networking.NetworkManager.PacketContext; -import dev.architectury.utils.EnvExecutor; -import dev.architectury.utils.Env; -import net.minecraft.network.FriendlyByteBuf; - -public class TrackStationResponsePacket implements IPacketBase { - public long id; - public Collection stationNames; - public Collection trainNames; - public int listingTrainCount; - public int totalTrainCount; - - public TrackStationResponsePacket() { } - - public TrackStationResponsePacket(long id, Collection stationNames, Collection trainNames, int listingTrainCount, int totalTrainCount) { - this.id = id; - this.stationNames = stationNames; - this.trainNames = trainNames; - this.listingTrainCount = listingTrainCount; - this.totalTrainCount = totalTrainCount; - } - - @Override - public void encode(TrackStationResponsePacket packet, FriendlyByteBuf buffer) { - buffer.writeLong(packet.id); - buffer.writeInt(packet.listingTrainCount); - buffer.writeInt(packet.totalTrainCount); - buffer.writeInt(packet.stationNames.size()); - for (String s : packet.stationNames) { - buffer.writeUtf(s); - } - - buffer.writeInt(packet.trainNames.size()); - for (String s : packet.trainNames) { - buffer.writeUtf(s); - } - } - - @Override - public TrackStationResponsePacket decode(FriendlyByteBuf buffer) { - long id = buffer.readLong(); - int listingTrainCount = buffer.readInt(); - int totalTrainCount = buffer.readInt(); - int count = buffer.readInt(); - List stationNames = new ArrayList<>(count); - for (int i = 0; i < count; i++) { - stationNames.add(buffer.readUtf()); - } - - count = buffer.readInt(); - List trainNames = new ArrayList<>(count); - for (int i = 0; i < count; i++) { - trainNames.add(buffer.readUtf()); - } - return new TrackStationResponsePacket(id, stationNames, trainNames, listingTrainCount, totalTrainCount); - } - - @Override - public void handle(TrackStationResponsePacket packet, Supplier contextSupplier) { - EnvExecutor.runInEnv(Env.CLIENT, () -> () -> { - contextSupplier.get().queue(() -> { - ClientTrainStationSnapshot.makeNew( - packet.stationNames == null || packet.stationNames.isEmpty() ? new ArrayList<>() : new ArrayList<>(packet.stationNames), - packet.trainNames == null || packet.trainNames.isEmpty() ? new ArrayList<>() : new ArrayList<>(packet.trainNames), - packet.listingTrainCount, - packet.totalTrainCount - ); - InstanceManager.runClientResponseReceievedAction(packet.id); - }); - }); - } -} - diff --git a/common/src/main/java/de/mrjulsen/crn/network/packets/stc/TrainDataResponsePacket.java b/common/src/main/java/de/mrjulsen/crn/network/packets/stc/TrainDataResponsePacket.java deleted file mode 100644 index 99208745..00000000 --- a/common/src/main/java/de/mrjulsen/crn/network/packets/stc/TrainDataResponsePacket.java +++ /dev/null @@ -1,50 +0,0 @@ -package de.mrjulsen.crn.network.packets.stc; - -import java.util.function.Supplier; - -import de.mrjulsen.crn.network.InstanceManager; -import de.mrjulsen.crn.network.packets.cts.TrainDataRequestPacket.TrainData; -import de.mrjulsen.mcdragonlib.net.IPacketBase; -import dev.architectury.networking.NetworkManager.PacketContext; -import dev.architectury.utils.EnvExecutor; -import dev.architectury.utils.Env; -import net.minecraft.network.FriendlyByteBuf; - -public class TrainDataResponsePacket implements IPacketBase { - public long id; - public TrainData departure; - public long time; - - public TrainDataResponsePacket() { } - - public TrainDataResponsePacket(long id, TrainData departure, long time) { - this.id = id; - this.departure = departure; - this.time = time; - } - - @Override - public void encode(TrainDataResponsePacket packet, FriendlyByteBuf buffer) { - buffer.writeLong(packet.id); - buffer.writeLong(packet.time); - buffer.writeNbt(packet.departure.toNbt()); - } - - @Override - public TrainDataResponsePacket decode(FriendlyByteBuf buffer) { - long id = buffer.readLong(); - long time = buffer.readLong(); - TrainData departure = TrainData.fromNbt(buffer.readNbt()); - - return new TrainDataResponsePacket(id, departure, time); - } - @Override - public void handle(TrainDataResponsePacket packet, Supplier contextSupplier) { - EnvExecutor.runInEnv(Env.CLIENT, () -> () -> { - contextSupplier.get().queue(() -> { - InstanceManager.runClientTrainDataResponseAction(packet.id, packet.departure, packet.time); - }); - }); - } -} - diff --git a/common/src/main/java/de/mrjulsen/crn/registry/ModAccessorTypes.java b/common/src/main/java/de/mrjulsen/crn/registry/ModAccessorTypes.java new file mode 100644 index 00000000..17d2146a --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/registry/ModAccessorTypes.java @@ -0,0 +1,932 @@ +package de.mrjulsen.crn.registry; + +import java.util.UUID; +import java.util.List; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.stream.Collectors; + +import com.simibubi.create.content.trains.entity.Train; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.Queue; +import java.util.Set; +import java.util.ArrayList; +import java.util.Optional; + +import de.mrjulsen.crn.CreateRailwaysNavigator; +import de.mrjulsen.crn.client.ClientWrapper; +import de.mrjulsen.crn.data.NearestTrackStationResult; +import de.mrjulsen.crn.data.StationTag; +import de.mrjulsen.crn.data.TagName; +import de.mrjulsen.crn.data.TrainGroup; +import de.mrjulsen.crn.data.TrainLine; +import de.mrjulsen.crn.data.UserSettings; +import de.mrjulsen.crn.data.StationTag.StationInfo; +import de.mrjulsen.crn.data.storage.GlobalSettings; +import de.mrjulsen.crn.data.storage.GlobalSettingsClient.AddStationTagEntryData; +import de.mrjulsen.crn.data.storage.GlobalSettingsClient.RemoveStationTagEntryData; +import de.mrjulsen.crn.data.storage.GlobalSettingsClient.UpdateStationTagNameData; +import de.mrjulsen.crn.data.storage.GlobalSettingsClient.UpdateTrainGroupColorData; +import de.mrjulsen.crn.data.storage.GlobalSettingsClient.UpdateTrainLineColorData; +import de.mrjulsen.crn.data.train.ClientTrainStop; +import de.mrjulsen.crn.data.train.TrainData; +import de.mrjulsen.crn.data.train.TrainListener; +import de.mrjulsen.crn.data.train.TrainPrediction; +import de.mrjulsen.crn.data.train.TrainStop; +import de.mrjulsen.crn.data.train.TrainTravelSection; +import de.mrjulsen.crn.data.train.TrainUtils; +import de.mrjulsen.crn.data.train.ClientTrainStop.TrainStopRealTimeData; +import de.mrjulsen.crn.data.train.portable.NextConnectionsDisplayData; +import de.mrjulsen.crn.data.train.portable.TrainDisplayData; +import de.mrjulsen.crn.debug.TrainDebugData; +import de.mrjulsen.crn.data.navigation.ClientRoute; +import de.mrjulsen.crn.data.navigation.NavigatableGraph; +import de.mrjulsen.crn.data.navigation.Route; +import de.mrjulsen.crn.data.navigation.RoutePart; +import de.mrjulsen.crn.data.navigation.ClientRoutePart.TrainRealTimeData; +import de.mrjulsen.mcdragonlib.data.Pair; +import de.mrjulsen.mcdragonlib.data.Single.MutableSingle; +import de.mrjulsen.mcdragonlib.util.accessor.BasicDataAccessorPacket.IChunkReceiver; +import de.mrjulsen.mcdragonlib.util.accessor.DataAccessorType; +import de.mrjulsen.crn.network.packets.stc.ServerErrorPacket; +import de.mrjulsen.crn.registry.data.NextConnectionsRequestData; +import net.minecraft.core.BlockPos; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.ListTag; +import net.minecraft.nbt.Tag; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerPlayer; + +public final class ModAccessorTypes { + +//#region STATION TAGS + + public static final DataAccessorType GET_STATION_TAG = DataAccessorType.register(new ResourceLocation(CreateRailwaysNavigator.MOD_ID, "get_station_tag"), DataAccessorType.Builder.create( + (in, nbt) -> { + nbt.putString(DataAccessorType.DEFAULT_NBT_DATA, in); + }, (nbt) -> { + return nbt.getString(DataAccessorType.DEFAULT_NBT_DATA); + }, (player, in, temp, nbt, iteration) -> { + nbt.put(DataAccessorType.DEFAULT_NBT_DATA, GlobalSettings.getInstance().getOrCreateStationTagFor(in).toNbt()); + return false; + }, (hasMore, previousData, iteration, nbt) -> { + return StationTag.fromNbt(nbt.getCompound(DataAccessorType.DEFAULT_NBT_DATA), null); + } + )); + + public static final DataAccessorType GET_STATION_TAG_BY_TAG_NAME = DataAccessorType.register(new ResourceLocation(CreateRailwaysNavigator.MOD_ID, "get_station_tag_by_tag_name"), DataAccessorType.Builder.create( + (in, nbt) -> { + nbt.putString(DataAccessorType.DEFAULT_NBT_DATA, in.get()); + }, (nbt) -> { + return TagName.of(nbt.getString(DataAccessorType.DEFAULT_NBT_DATA)); + }, (player, in, temp, nbt, iteration) -> { + nbt.put(DataAccessorType.DEFAULT_NBT_DATA, GlobalSettings.getInstance().getOrCreateStationTagFor(in).toNbt()); + return false; + }, (hasMore, previousData, iteration, nbt) -> { + return StationTag.fromNbt(nbt.getCompound(DataAccessorType.DEFAULT_NBT_DATA), null); + } + )); + + public static final DataAccessorType CREATE_STATION_TAG = DataAccessorType.register(new ResourceLocation(CreateRailwaysNavigator.MOD_ID, "create_station_tag"), DataAccessorType.Builder.create( + (in, nbt) -> { + nbt.putString(DataAccessorType.DEFAULT_NBT_DATA, in); + }, (nbt) -> { + return nbt.getString(DataAccessorType.DEFAULT_NBT_DATA); + }, (player, in, temp, nbt, iteration) -> { + nbt.put(DataAccessorType.DEFAULT_NBT_DATA, GlobalSettings.getInstance().createOrGetStationTag(in).toNbt()); + return false; + }, (hasMore, data, iteration, nbt) -> { + return StationTag.fromNbt(nbt.getCompound(DataAccessorType.DEFAULT_NBT_DATA), null); + } + )); + + public static final DataAccessorType REGISTER_STATION_TAG = DataAccessorType.register(new ResourceLocation(CreateRailwaysNavigator.MOD_ID, "register_station_tag"), DataAccessorType.Builder.createEmptyResponse( + (in, nbt) -> { + nbt.put(DataAccessorType.DEFAULT_NBT_DATA, in.toNbt()); + }, (nbt) -> { + return StationTag.fromNbt(nbt.getCompound(DataAccessorType.DEFAULT_NBT_DATA), null); + }, (player, in, temp, nbt, iteration) -> { + in.updateLastEdited(player.getGameProfile().getName()); + GlobalSettings.getInstance().registerStationTag(in); + return false; + } + )); + + public static final DataAccessorType DELETE_STATION_TAG = DataAccessorType.register(new ResourceLocation(CreateRailwaysNavigator.MOD_ID, "delete_station_tag"), DataAccessorType.Builder.createEmptyResponse( + (in, nbt) -> { + nbt.putUUID(DataAccessorType.DEFAULT_NBT_DATA, in); + }, (nbt) -> { + return nbt.getUUID(DataAccessorType.DEFAULT_NBT_DATA); + }, (player, in, temp, nbt, iteration) -> { + GlobalSettings.getInstance().removeStationTag(in); + return false; + } + )); + + public static final DataAccessorType UPDATE_STATION_TAG_NAME = DataAccessorType.register(new ResourceLocation(CreateRailwaysNavigator.MOD_ID, "update_station_tag_name"), DataAccessorType.Builder.createEmptyResponse( + (in, nbt) -> { + nbt.putUUID("Id", in.tagId()); + nbt.putString("Name", in.name()); + }, (nbt) -> { + return new UpdateStationTagNameData(nbt.getUUID("Id"), nbt.getString("Name")); + }, (player, in, temp, nbt, iteration) -> { + GlobalSettings.getInstance().getStationTag(in.tagId()).ifPresent(x -> { + x.updateLastEdited(player.getGameProfile().getName()); + x.setName(TagName.of(in.name())); + }); + return false; + } + )); + + public static final DataAccessorType, Optional> ADD_STATION_TAG_ENTRY = DataAccessorType.register(new ResourceLocation(CreateRailwaysNavigator.MOD_ID, "add_station_tag_entry"), DataAccessorType.Builder.create( + (in, nbt) -> { + nbt.putUUID("Id", in.tagId()); + nbt.putString("Name", in.station()); + nbt.put("Info", in.info().toNbt()); + }, (nbt) -> { + return new AddStationTagEntryData(nbt.getUUID("Id"), nbt.getString("Name"), StationInfo.fromNbt(nbt.getCompound("Info"))); + }, (player, in, temp, nbt, iteration) -> { + GlobalSettings.getInstance().getStationTag(in.tagId()).ifPresent(x -> { + x.add(in.station(), in.info()); + x.updateLastEdited(player.getGameProfile().getName()); + nbt.put(DataAccessorType.DEFAULT_NBT_DATA, x.toNbt()); + }); + return false; + }, (hasMore, data, iteration, nbt) -> { + return Optional.ofNullable(nbt.contains(DataAccessorType.DEFAULT_NBT_DATA) ? StationTag.fromNbt(nbt.getCompound(DataAccessorType.DEFAULT_NBT_DATA), null) : null); + } + )); + + public static final DataAccessorType, Optional> UPDATE_STATION_TAG_ENTRY = DataAccessorType.register(new ResourceLocation(CreateRailwaysNavigator.MOD_ID, "update_station_tag_entry"), DataAccessorType.Builder.create( + (in, nbt) -> { + nbt.putUUID("Id", in.tagId()); + nbt.putString("Name", in.station()); + nbt.put("Info", in.info().toNbt()); + }, (nbt) -> { + return new AddStationTagEntryData(nbt.getUUID("Id"), nbt.getString("Name"), StationInfo.fromNbt(nbt.getCompound("Info"))); + }, (player, in, temp, nbt, iteration) -> { + GlobalSettings.getInstance().getStationTag(in.tagId()).ifPresent(x -> { + x.updateInfoForStation(in.station(), in.info()); + x.updateLastEdited(player.getGameProfile().getName()); + nbt.put(DataAccessorType.DEFAULT_NBT_DATA, x.toNbt()); + }); + return false; + }, (hasMore, data, iteration, nbt) -> { + return Optional.ofNullable(nbt.contains(DataAccessorType.DEFAULT_NBT_DATA) ? StationTag.fromNbt(nbt.getCompound(DataAccessorType.DEFAULT_NBT_DATA), null) : null); + } + )); + + public static final DataAccessorType, Optional> REMOVE_STATION_TAG_ENTRY = DataAccessorType.register(new ResourceLocation(CreateRailwaysNavigator.MOD_ID, "remove_station_tag_entry"), DataAccessorType.Builder.create( + (in, nbt) -> { + nbt.putUUID("Id", in.tagId()); + nbt.putString("Name", in.station()); + }, (nbt) -> { + return new RemoveStationTagEntryData(nbt.getUUID("Id"), nbt.getString("Name")); + }, (player, in, temp, nbt, iteration) -> { + GlobalSettings.getInstance().getStationTag(in.tagId()).ifPresent(x -> { + x.remove(in.station()); + x.updateLastEdited(player.getGameProfile().getName()); + nbt.put(DataAccessorType.DEFAULT_NBT_DATA, x.toNbt()); + }); + return false; + }, (hasMore, data, iteration, nbt) -> { + return Optional.ofNullable(nbt.contains(DataAccessorType.DEFAULT_NBT_DATA) ? StationTag.fromNbt(nbt.getCompound(DataAccessorType.DEFAULT_NBT_DATA), null) : null); + } + )); + + public static final DataAccessorType, Collection> GET_ALL_STATION_TAGS = DataAccessorType.register(new ResourceLocation(CreateRailwaysNavigator.MOD_ID, "get_all_station_tags"), DataAccessorType.Builder.createNoInputChunked( + (player, in, temp, nbt, iteration) -> { + if (temp.getFirst() == null) { + temp.setFirst(new ConcurrentLinkedQueue<>(GlobalSettings.getInstance().getAllStationTags().stream().sorted((a, b) -> a.getTagName().get().compareToIgnoreCase(b.getTagName().get())).toList())); + } + @SuppressWarnings("unchecked") + Queue tags = (Queue)((MutableSingle)temp).getFirst(); + if (tags.isEmpty()) { + return false; + } + for (int i = 0; i < 64 && !tags.isEmpty(); i++) { + nbt.put(DataAccessorType.DEFAULT_NBT_DATA + i, tags.poll().toNbt()); + } + return !tags.isEmpty(); + }, (IChunkReceiver>)(hasMore, list, iteration, nbt) -> { + if (list == null) { + list = new ArrayList(); + } + final Collection l = list; + nbt.getAllKeys().forEach(x -> l.add(StationTag.fromNbt(nbt.getCompound(x), null))); + return l; + }, (chunks) -> { + return chunks; + } + )); + + /** Input: true = exclude blacklisted */ + public static final DataAccessorType, Collection> GET_ALL_STATIONS_AS_TAGS = DataAccessorType.register(new ResourceLocation(CreateRailwaysNavigator.MOD_ID, "get_all_stations_as_tags"), DataAccessorType.Builder.createChunked( + (in, nbt) -> { + nbt.putBoolean(DataAccessorType.DEFAULT_NBT_DATA, in); + }, (nbt) -> { + return nbt.getBoolean(DataAccessorType.DEFAULT_NBT_DATA); + }, (player, in, temp, nbt, iteration) -> { + if (temp.getFirst() == null) { + temp.setFirst(new ConcurrentLinkedQueue<>(TrainUtils.getAllStations().stream().filter(x -> !in || !GlobalSettings.getInstance().isStationBlacklisted(x)).map(x -> GlobalSettings.getInstance().getOrCreateStationTagFor(x)).distinct().sorted((a, b) -> a.getTagName().get().compareToIgnoreCase(b.getTagName().get())).toList())); + } + @SuppressWarnings("unchecked") + Queue tags = (Queue)((MutableSingle)temp).getFirst(); + if (tags.isEmpty()) { + return false; + } + for (int i = 0; i < 64 && !tags.isEmpty(); i++) { + nbt.put(DataAccessorType.DEFAULT_NBT_DATA + i, tags.poll().toNbt()); + } + return !tags.isEmpty(); + }, (IChunkReceiver>)(hasMore, list, iteration, nbt) -> { + if (list == null) { + list = new ArrayList(); + } + final Collection l = list; + nbt.getAllKeys().forEach(x -> l.add(StationTag.fromNbt(nbt.getCompound(x), null))); + return l; + }, (chunks) -> { + return chunks; + } + )); + +//#endregion +//#region TRAIN GROUPS + + public static final DataAccessorType, List> GET_ALL_TRAIN_GROUPS = DataAccessorType.register(new ResourceLocation(CreateRailwaysNavigator.MOD_ID, "get_all_train_groups"), DataAccessorType.Builder.createNoInputChunked( + (player, in, temp, nbt, iteration) -> { + if (temp.getFirst() == null) { + temp.setFirst(new ConcurrentLinkedQueue<>(GlobalSettings.getInstance().getAllTrainGroups())); + } + @SuppressWarnings("unchecked") + Queue tags = (Queue)((MutableSingle)temp).getFirst(); + if (tags.isEmpty()) { + return false; + } + for (int i = 0; i < 64 && !tags.isEmpty(); i++) { + nbt.put(DataAccessorType.DEFAULT_NBT_DATA + i, tags.poll().toNbt()); + } + return !tags.isEmpty(); + }, (IChunkReceiver>)(hasMore, list, iteration, nbt) -> { + if (list == null) { + list = new ArrayList(); + } + final List l = list; + nbt.getAllKeys().forEach(x -> l.add(TrainGroup.fromNbt(nbt.getCompound(x)))); + return l; + }, (chunks) -> { + return chunks; + } + )); + + public static final DataAccessorType DELETE_TRAIN_GROUP = DataAccessorType.register(new ResourceLocation(CreateRailwaysNavigator.MOD_ID, "delete_train_group"), DataAccessorType.Builder.createEmptyResponse( + (in, nbt) -> { + nbt.putString(DataAccessorType.DEFAULT_NBT_DATA, in); + }, (nbt) -> { + return nbt.getString(DataAccessorType.DEFAULT_NBT_DATA); + }, (player, in, temp, nbt, iteration) -> { + GlobalSettings.getInstance().removeTrainGroup(in); + return false; + } + )); + + public static final DataAccessorType UPDATE_TRAIN_GROUP_COLOR = DataAccessorType.register(new ResourceLocation(CreateRailwaysNavigator.MOD_ID, "update_train_group_color"), DataAccessorType.Builder.createEmptyResponse( + (in, nbt) -> { + nbt.putString("Id", in.name()); + nbt.putInt("Color", in.color()); + }, (nbt) -> { + return new UpdateTrainGroupColorData(nbt.getString("Id"), nbt.getInt("Color")); + }, (player, in, temp, nbt, iteration) -> { + GlobalSettings.getInstance().getTrainGroup(in.name()).ifPresent(x -> { + x.setColor(in.color()); + }); + return false; + } + )); + + public static final DataAccessorType CREATE_TRAIN_GROUP = DataAccessorType.register(new ResourceLocation(CreateRailwaysNavigator.MOD_ID, "create_train_group"), DataAccessorType.Builder.create( + (in, nbt) -> { + nbt.putString(DataAccessorType.DEFAULT_NBT_DATA, in); + }, (nbt) -> { + return nbt.getString(DataAccessorType.DEFAULT_NBT_DATA); + }, (player, in, temp, nbt, iteration) -> { + TrainGroup group = GlobalSettings.getInstance().createOrGetTrainGroup(in); + nbt.put(DataAccessorType.DEFAULT_NBT_DATA, group.toNbt()); + return false; + }, (hasMore, data, iteration, nbt) -> { + return TrainGroup.fromNbt(nbt.getCompound(DataAccessorType.DEFAULT_NBT_DATA)); + } + )); + +//#endregion +//#region STATION BLACKLIST + + public static final DataAccessorType, Collection> ADD_STATION_TO_BLACKLIST = DataAccessorType.register(new ResourceLocation(CreateRailwaysNavigator.MOD_ID, "add_station_to_blacklist"), DataAccessorType.Builder.createChunked( + (in, nbt) -> { + nbt.putString(DataAccessorType.DEFAULT_NBT_DATA, in); + }, (nbt) -> { + return nbt.getString(DataAccessorType.DEFAULT_NBT_DATA); + }, (player, in, temp, nbt, iteration) -> { + if (temp.getFirst() == null) { + GlobalSettings.getInstance().blacklistStation(in); + temp.setFirst(new ConcurrentLinkedQueue<>(GlobalSettings.getInstance().getAllBlacklistedStations())); + } + @SuppressWarnings("unchecked") + Queue tags = (Queue)((MutableSingle)temp).getFirst(); + for (int i = 0; i < 256 && !tags.isEmpty(); i++) { + nbt.putString(DataAccessorType.DEFAULT_NBT_DATA + i, tags.poll()); + } + return !tags.isEmpty(); + }, (IChunkReceiver>)(hasMore, list, iteration, nbt) -> { + if (list == null) { + list = new ArrayList(); + } + final Collection l = list; + nbt.getAllKeys().forEach(x -> l.add(nbt.getString(x))); + return l; + }, (chunks) -> { + return chunks; + } + )); + + public static final DataAccessorType, Collection> REMOVE_STATION_FROM_BLACKLIST = DataAccessorType.register(new ResourceLocation(CreateRailwaysNavigator.MOD_ID, "remove_station_from_blacklist"), DataAccessorType.Builder.createChunked( + (in, nbt) -> { + nbt.putString(DataAccessorType.DEFAULT_NBT_DATA, in); + }, (nbt) -> { + return nbt.getString(DataAccessorType.DEFAULT_NBT_DATA); + }, (player, in, temp, nbt, iteration) -> { + if (temp.getFirst() == null) { + GlobalSettings.getInstance().removeStationFromBlacklist(in); + temp.setFirst(new ConcurrentLinkedQueue<>(GlobalSettings.getInstance().getAllBlacklistedStations())); + } + @SuppressWarnings("unchecked") + Queue tags = (Queue)((MutableSingle)temp).getFirst(); + for (int i = 0; i < 256 && !tags.isEmpty(); i++) { + nbt.putString(DataAccessorType.DEFAULT_NBT_DATA + i, tags.poll()); + } + return !tags.isEmpty(); + }, (IChunkReceiver>)(hasMore, list, iteration, nbt) -> { + if (list == null) { + list = new ArrayList(); + } + final Collection l = list; + nbt.getAllKeys().forEach(x -> l.add(nbt.getString(x))); + return l; + }, (chunks) -> { + return chunks; + } + )); + + public static final DataAccessorType, List> GET_BLACKLISTED_STATIONS = DataAccessorType.register(new ResourceLocation(CreateRailwaysNavigator.MOD_ID, "get_blacklisted_stations"), DataAccessorType.Builder.createNoInputChunked( + (player, in, temp, nbt, iteration) -> { + if (temp.getFirst() == null) { + temp.setFirst(new ConcurrentLinkedQueue<>(GlobalSettings.getInstance().getAllBlacklistedStations())); + } + @SuppressWarnings("unchecked") + Queue tags = (Queue)((MutableSingle)temp).getFirst(); + for (int i = 0; i < 256 && !tags.isEmpty(); i++) { + nbt.putString(DataAccessorType.DEFAULT_NBT_DATA + i, tags.poll()); + } + return !tags.isEmpty(); + }, (IChunkReceiver>)(hasMore, list, iteration, nbt) -> { + if (list == null) { + list = new ArrayList(); + } + final List l = list; + nbt.getAllKeys().forEach(x -> l.add(nbt.getString(x))); + return l; + }, (chunks) -> { + return chunks; + } + )); + +//#endregion +//#region TRAIN BLACKLIST + + public static final DataAccessorType, Collection> ADD_TRAIN_TO_BLACKLIST = DataAccessorType.register(new ResourceLocation(CreateRailwaysNavigator.MOD_ID, "add_train_to_blacklist"), DataAccessorType.Builder.createChunked( + (in, nbt) -> { + nbt.putString(DataAccessorType.DEFAULT_NBT_DATA, in); + }, (nbt) -> { + return nbt.getString(DataAccessorType.DEFAULT_NBT_DATA); + }, (player, in, temp, nbt, iteration) -> { + if (temp.getFirst() == null) { + GlobalSettings.getInstance().blacklistTrain(in); + temp.setFirst(new ConcurrentLinkedQueue<>(GlobalSettings.getInstance().getAllBlacklistedTrains())); + } + @SuppressWarnings("unchecked") + Queue tags = (Queue)((MutableSingle)temp).getFirst(); + for (int i = 0; i < 256 && !tags.isEmpty(); i++) { + nbt.putString(DataAccessorType.DEFAULT_NBT_DATA + i, tags.poll()); + } + return !tags.isEmpty(); + }, (IChunkReceiver>)(hasMore, list, iteration, nbt) -> { + if (list == null) { + list = new ArrayList(); + } + final Collection l = list; + nbt.getAllKeys().forEach(x -> l.add(nbt.getString(x))); + return l; + }, (chunks) -> { + return chunks; + } + )); + + public static final DataAccessorType, Collection> REMOVE_TRAIN_FROM_BLACKLIST = DataAccessorType.register(new ResourceLocation(CreateRailwaysNavigator.MOD_ID, "remove_train_from_blacklist"), DataAccessorType.Builder.createChunked( + (in, nbt) -> { + nbt.putString(DataAccessorType.DEFAULT_NBT_DATA, in); + }, (nbt) -> { + return nbt.getString(DataAccessorType.DEFAULT_NBT_DATA); + }, (player, in, temp, nbt, iteration) -> { + if (temp.getFirst() == null) { + GlobalSettings.getInstance().removeTrainFromBlacklist(in); + temp.setFirst(new ConcurrentLinkedQueue<>(GlobalSettings.getInstance().getAllBlacklistedTrains())); + } + @SuppressWarnings("unchecked") + Queue tags = (Queue)((MutableSingle)temp).getFirst(); + for (int i = 0; i < 256 && !tags.isEmpty(); i++) { + nbt.putString(DataAccessorType.DEFAULT_NBT_DATA + i, tags.poll()); + } + return !tags.isEmpty(); + }, (IChunkReceiver>)(hasMore, list, iteration, nbt) -> { + if (list == null) { + list = new ArrayList(); + } + final Collection l = list; + nbt.getAllKeys().forEach(x -> l.add(nbt.getString(x))); + return l; + }, (chunks) -> { + return chunks; + } + )); + + public static final DataAccessorType, List> GET_BLACKLISTED_TRAINS = DataAccessorType.register(new ResourceLocation(CreateRailwaysNavigator.MOD_ID, "get_blacklisted_trains"), DataAccessorType.Builder.createNoInputChunked( + (player, in, temp, nbt, iteration) -> { + if (temp.getFirst() == null) { + temp.setFirst(new ConcurrentLinkedQueue<>(GlobalSettings.getInstance().getAllBlacklistedTrains())); + } + @SuppressWarnings("unchecked") + Queue tags = (Queue)((MutableSingle)temp).getFirst(); + for (int i = 0; i < 256 && !tags.isEmpty(); i++) { + nbt.putString(DataAccessorType.DEFAULT_NBT_DATA + i, tags.poll()); + } + return !tags.isEmpty(); + }, (IChunkReceiver>)(hasMore, list, iteration, nbt) -> { + if (list == null) { + list = new ArrayList(); + } + final List l = list; + nbt.getAllKeys().forEach(x -> l.add(nbt.getString(x))); + return l; + }, (chunks) -> { + return chunks; + } + )); + + + + public static final DataAccessorType UPDATE_REALTIME = DataAccessorType.register(new ResourceLocation(CreateRailwaysNavigator.MOD_ID, "update_realtime"), DataAccessorType.Builder.create( + (in, nbt) -> { + nbt.putUUID(DataAccessorType.DEFAULT_NBT_DATA, in); + }, (nbt) -> { + return nbt.getUUID(DataAccessorType.DEFAULT_NBT_DATA); + }, (player, in, temp, nbt, iteration) -> { + if (!TrainListener.data.containsKey(in)) { + return false; + } + + TrainData data = TrainListener.data.get(in); + Map values = data.getPredictions().stream().map(a -> new TrainStopRealTimeData( + a.getStationTag().getClientTag(a.getStationName()), + a.getEntryIndex(), + a.getScheduledArrivalTime(), + a.getScheduledDepartureTime(), + a.getRealTimeArrivalTime(), + a.getRealTimeDepartureTime(), + a.getArrivalTimeDeviation(), + a.getDepartureTimeDeviation(), + a.getRealTimeArrivalTicks(), + a.getCurrentCycle() + )).collect(Collectors.toMap(a -> a.entryIndex(), a -> a)); + nbt.put(DataAccessorType.DEFAULT_NBT_DATA, new TrainRealTimeData(data.getSessionId(), values, data.getStatus(), data.isCancelled()).toNbt()); + return false; + }, (hasMore, previousData, iteration, nbt) -> { + return nbt.contains(DataAccessorType.DEFAULT_NBT_DATA) ? TrainRealTimeData.fromNbt(nbt.getCompound(DataAccessorType.DEFAULT_NBT_DATA)) : null; + } + )); + + + + public static final DataAccessorType GET_USER_SETTINGS = DataAccessorType.register(new ResourceLocation(CreateRailwaysNavigator.MOD_ID, "get_user_settings"), DataAccessorType.Builder.create( + (in, nbt) -> { + nbt.putUUID(DataAccessorType.DEFAULT_NBT_DATA, in); + }, (nbt) -> { + return nbt.getUUID(DataAccessorType.DEFAULT_NBT_DATA); + }, (player, in, temp, nbt, iteration) -> { + nbt.put(DataAccessorType.DEFAULT_NBT_DATA, UserSettings.getSettingsFor(in, false).toNbt()); + nbt.putUUID("Id", in); + return false; + }, (hasMore, list, iteration, nbt) -> { + return UserSettings.fromNbt(nbt.getCompound(DataAccessorType.DEFAULT_NBT_DATA), nbt.getUUID("Id"), false); + } + )); + + public static final DataAccessorType SAVE_USER_SETTINGS = DataAccessorType.register(new ResourceLocation(CreateRailwaysNavigator.MOD_ID, "save_user_settings"), DataAccessorType.Builder.createEmptyResponse( + (in, nbt) -> { + nbt.put(DataAccessorType.DEFAULT_NBT_DATA, in.toNbt()); + nbt.putUUID("Id", in.getOwnerId()); + }, (nbt) -> { + return UserSettings.fromNbt(nbt.getCompound(DataAccessorType.DEFAULT_NBT_DATA), nbt.getUUID("Id"), false); + }, (player, in, temp, nbt, iteration) -> { + in.save(); + return false; + } + )); + + public static final DataAccessorType GET_NEAREST_STATION = DataAccessorType.register(new ResourceLocation(CreateRailwaysNavigator.MOD_ID, "get_nearest_station"), DataAccessorType.Builder.create( + (in, nbt) -> { + nbt.putInt("x", in.getX()); + nbt.putInt("y", in.getY()); + nbt.putInt("z", in.getZ()); + }, (nbt) -> { + return new BlockPos(nbt.getInt("x"), nbt.getInt("y"), nbt.getInt("z")); + }, (player, in, temp, nbt, iteration) -> { + NearestTrackStationResult result = NearestTrackStationResult.empty(); + try { + result = TrainUtils.getNearestTrackStation(player.level, in); + } catch (Exception e) { + CreateRailwaysNavigator.LOGGER.error("Error while trying to find nearest track station.", e); + CreateRailwaysNavigator.net().CHANNEL.sendToPlayer((ServerPlayer)player, new ServerErrorPacket(e.getMessage())); + } + nbt.put(DataAccessorType.DEFAULT_NBT_DATA, result.toNbt()); + return false; + }, (hasMore, list, iteration, nbt) -> { + return NearestTrackStationResult.fromNbt(nbt.getCompound(DataAccessorType.DEFAULT_NBT_DATA)); + } + )); + + public static final DataAccessorType GET_TRAIN_DISPLAY_DATA = DataAccessorType.register(new ResourceLocation(CreateRailwaysNavigator.MOD_ID, "get_train_display_data"), DataAccessorType.Builder.create( + (in, nbt) -> { + nbt.putUUID(DataAccessorType.DEFAULT_NBT_DATA, in); + }, (nbt) -> { + return nbt.getUUID(DataAccessorType.DEFAULT_NBT_DATA); + }, (player, in, temp, nbt, iteration) -> { + Optional trainOpt = TrainUtils.getTrain(in); + if (!trainOpt.isPresent() || !TrainUtils.isTrainUsable(trainOpt.get()) || GlobalSettings.getInstance().isTrainBlacklisted(trainOpt.get())) { + nbt.put(DataAccessorType.DEFAULT_NBT_DATA, TrainDisplayData.empty().toNbt()); + return false; + } + nbt.put(DataAccessorType.DEFAULT_NBT_DATA, TrainDisplayData.of(trainOpt.get()).toNbt()); + return false; + }, (hasMore, list, iteration, nbt) -> { + return TrainDisplayData.fromNbt(nbt.getCompound(DataAccessorType.DEFAULT_NBT_DATA)); + } + )); + + public static final DataAccessorType GET_NEXT_CONNECTIONS_DISPLAY_DATA = DataAccessorType.register(new ResourceLocation(CreateRailwaysNavigator.MOD_ID, "get_next_connections_display_data"), DataAccessorType.Builder.create( + (in, nbt) -> { + nbt.put(DataAccessorType.DEFAULT_NBT_DATA, in.toNbt()); + }, (nbt) -> { + return NextConnectionsRequestData.fromNbt(nbt.getCompound(DataAccessorType.DEFAULT_NBT_DATA)); + }, (player, in, temp, nbt, iteration) -> { + nbt.put(DataAccessorType.DEFAULT_NBT_DATA, NextConnectionsDisplayData.at(in.stationName(), in.selfTrainId()).toNbt()); + return false; + }, (hasMore, list, iteration, nbt) -> { + return NextConnectionsDisplayData.fromNbt(nbt.getCompound(DataAccessorType.DEFAULT_NBT_DATA)); + } + )); + + + public static final DataAccessorType, Collection> GET_ALL_TRAIN_NAMES = DataAccessorType.register(new ResourceLocation(CreateRailwaysNavigator.MOD_ID, "get_all_train_names"), DataAccessorType.Builder.createNoInputChunked( + (player, in, temp, nbt, iteration) -> { + if (temp.getFirst() == null) { + temp.setFirst(new ConcurrentLinkedQueue<>(TrainUtils.getTrains(false).stream().map(x -> x.name.getString()).toList())); + } + @SuppressWarnings("unchecked") + Queue tags = (Queue)((MutableSingle)temp).getFirst(); + for (int i = 0; i < 256 && !tags.isEmpty(); i++) { + nbt.putString(DataAccessorType.DEFAULT_NBT_DATA + i, tags.poll()); + } + return !tags.isEmpty(); + }, (IChunkReceiver>)(hasMore, list, iteration, nbt) -> { + if (list == null) { + list = new ArrayList(); + } + final Collection l = list; + nbt.getAllKeys().forEach(x -> l.add(nbt.getString(x))); + return l; + }, (chunks) -> { + return chunks; + } + )); + + public static final DataAccessorType, Collection> GET_ALL_STATION_NAMES = DataAccessorType.register(new ResourceLocation(CreateRailwaysNavigator.MOD_ID, "get_all_station_names"), DataAccessorType.Builder.createNoInputChunked( + (player, in, temp, nbt, iteration) -> { + if (temp.getFirst() == null) { + temp.setFirst(new ConcurrentLinkedQueue<>(TrainUtils.getAllStations().stream().map(x -> x.name).toList())); + } + @SuppressWarnings("unchecked") + Queue tags = (Queue)((MutableSingle)temp).getFirst(); + for (int i = 0; i < 256 && !tags.isEmpty(); i++) { + nbt.putString(DataAccessorType.DEFAULT_NBT_DATA + i, tags.poll()); + } + return !tags.isEmpty(); + }, (IChunkReceiver>)(hasMore, list, iteration, nbt) -> { + if (list == null) { + list = new ArrayList(); + } + final Collection l = list; + nbt.getAllKeys().forEach(x -> l.add(nbt.getString(x))); + return l; + }, (chunks) -> { + return chunks; + } + )); + + //#region TRAIN LINES + + public static final DataAccessorType, List> GET_ALL_TRAIN_LINES = DataAccessorType.register(new ResourceLocation(CreateRailwaysNavigator.MOD_ID, "get_all_train_lines"), DataAccessorType.Builder.createNoInputChunked( + (player, in, temp, nbt, iteration) -> { + if (temp.getFirst() == null) { + temp.setFirst(new ConcurrentLinkedQueue<>(GlobalSettings.getInstance().getAllTrainLines())); + } + @SuppressWarnings("unchecked") + Queue tags = (Queue)((MutableSingle)temp).getFirst(); + if (tags.isEmpty()) { + return false; + } + + for (int i = 0; i < 256 && !tags.isEmpty(); i++) { + nbt.put(DataAccessorType.DEFAULT_NBT_DATA + i, tags.poll().toNbt()); + } + return !tags.isEmpty(); + }, (IChunkReceiver>)(hasMore, list, iteration, nbt) -> { + if (list == null) { + list = new ArrayList(); + } + final List l = list; + nbt.getAllKeys().forEach(x -> l.add(TrainLine.fromNbt(nbt.getCompound(x)))); + return l; + }, (chunks) -> { + return chunks; + } + )); + + public static final DataAccessorType DELETE_TRAIN_LINE = DataAccessorType.register(new ResourceLocation(CreateRailwaysNavigator.MOD_ID, "delete_train_line"), DataAccessorType.Builder.createEmptyResponse( + (in, nbt) -> { + nbt.putString(DataAccessorType.DEFAULT_NBT_DATA, in); + }, (nbt) -> { + return nbt.getString(DataAccessorType.DEFAULT_NBT_DATA); + }, (player, in, temp, nbt, iteration) -> { + GlobalSettings.getInstance().removeTrainLine(in); + return false; + } + )); + + public static final DataAccessorType UPDATE_TRAIN_LINE_COLOR = DataAccessorType.register(new ResourceLocation(CreateRailwaysNavigator.MOD_ID, "update_train_line_color"), DataAccessorType.Builder.createEmptyResponse( + (in, nbt) -> { + nbt.putString("Id", in.name()); + nbt.putInt("Color", in.color()); + }, (nbt) -> { + return new UpdateTrainLineColorData(nbt.getString("Id"), nbt.getInt("Color")); + }, (player, in, temp, nbt, iteration) -> { + GlobalSettings.getInstance().getTrainLine(in.name()).ifPresent(x -> { + x.setColor(in.color()); + }); + return false; + } + )); + + public static final DataAccessorType CREATE_TRAIN_LINE = DataAccessorType.register(new ResourceLocation(CreateRailwaysNavigator.MOD_ID, "create_train_line"), DataAccessorType.Builder.create( + (in, nbt) -> { + nbt.putString(DataAccessorType.DEFAULT_NBT_DATA, in); + }, (nbt) -> { + return nbt.getString(DataAccessorType.DEFAULT_NBT_DATA); + }, (player, in, temp, nbt, iteration) -> { + TrainLine group = GlobalSettings.getInstance().createOrGetTrainLine(in); + nbt.put(DataAccessorType.DEFAULT_NBT_DATA, group.toNbt()); + return false; + }, (hasMore, data, iteration, nbt) -> { + return TrainLine.fromNbt(nbt.getCompound(DataAccessorType.DEFAULT_NBT_DATA)); + } + )); + + //#endregion + + public static record NavigationData(String start, String end, UUID player) {} + public static final DataAccessorType, List> NAVIGATE = DataAccessorType.register(new ResourceLocation(CreateRailwaysNavigator.MOD_ID, "navigate"), DataAccessorType.Builder.createChunked( + (in, nbt) -> { + nbt.putString("Start", in.start()); + nbt.putString("End", in.end()); + nbt.putUUID("Player", in.player()); + }, (nbt) -> { + return new NavigationData(nbt.getString("Start"), nbt.getString("End"), nbt.getUUID("Player")); + }, (player, in, temp, nbt, iteration) -> { + try { + if (temp.getFirst() == null) { + GlobalSettings settings = GlobalSettings.getInstance(); + List routes = NavigatableGraph.searchRoutes( + settings.getTagByName(TagName.of(in.start())).orElse(settings.getOrCreateStationTagFor(in.start())), + settings.getTagByName(TagName.of(in.end())).orElse(settings.getOrCreateStationTagFor(in.end())), + in.player(), + true + ); + temp.setFirst(new ConcurrentLinkedQueue<>(routes)); + } + @SuppressWarnings("unchecked") + Queue tags = (Queue)((MutableSingle)temp).getFirst(); + if (tags.isEmpty()) { + return false; + } + Route r = tags.poll(); + nbt.put(DataAccessorType.DEFAULT_NBT_DATA, r.toNbt()); + return !tags.isEmpty(); + } catch (Exception e) { + CreateRailwaysNavigator.LOGGER.error("Navigation error.", e); + } + return false; + }, (IChunkReceiver>)(hasMore, list, iteration, nbt) -> { + if (!nbt.contains(DataAccessorType.DEFAULT_NBT_DATA)) { + return List.of(); + } + if (list == null) { + list = new ArrayList(); + } + list.add(ClientRoute.fromNbt(nbt.getCompound(DataAccessorType.DEFAULT_NBT_DATA), true)); + return list; + }, (chunks) -> { + return chunks; + } + )); + + public static record DepartureRoutesData(String stationTagName, UUID player) {} + public static final DataAccessorType>, List>> GET_DEPARTURE_AND_ARRIVAL_ROUTES_AT = DataAccessorType.register(new ResourceLocation(CreateRailwaysNavigator.MOD_ID, "get_departure_and_arrival_routes_at"), DataAccessorType.Builder.createChunked( + (in, nbt) -> { + nbt.putString("Station", in.stationTagName()); + nbt.putUUID("Player", in.player()); + }, (nbt) -> { + return new DepartureRoutesData(nbt.getString("Station"), nbt.getUUID("Player")); + }, (player, in, temp, nbt, iteration) -> { + try { + if (temp.getFirst() == null) { + UserSettings settings = UserSettings.getSettingsFor(in.player(), true); + StationTag station = GlobalSettings.getInstance().getOrCreateStationTagFor(TagName.of(in.stationTagName())); + Set trains = TrainUtils.getDepartingTrainsAt(station).stream().filter(x -> + TrainUtils.isTrainUsable(x) && + !GlobalSettings.getInstance().isTrainBlacklisted(x) && + TrainListener.data.containsKey(x.id) + ).collect(Collectors.toSet()); + + List> routesL = new ArrayList<>(); + for (Train train : trains) { + TrainData data = TrainListener.data.get(train.id); + List matchingPredictions = data.getPredictionsChronologically().stream().filter(x -> x.getStationTag().equals(station)).toList(); + + for (TrainPrediction prediction : matchingPredictions) { + TrainTravelSection section = prediction.getSection(); + if ((!section.isUsable() && !(section.isFirstStop(prediction) && section.previousSection().isUsable() && section.previousSection().shouldIncludeNextStationOfNextSection())) || (section.getTrainGroup() != null && settings.searchExcludedTrainGroups.getValue().contains(section.getTrainGroup().getGroupName()))) { + continue; + } + + TrainTravelSection previousSection = section.previousSection(); + boolean isStart = section.isFirstStop(prediction); + boolean isStartAndFinal = isStart && previousSection.isUsable() && previousSection.shouldIncludeNextStationOfNextSection() && (previousSection.getTrainGroup() == null || !settings.searchExcludedTrainGroups.getValue().contains(previousSection.getTrainGroup().getGroupName())); + + TrainStop stop = new TrainStop(prediction); + stop.simulateTicks(settings.searchDepartureInTicks.getValue()); + TrainPrediction fromPrediction = section.getFirstStop().get(); + TrainStop from = new TrainStop(fromPrediction); + + Route route = new Route(List.of(new RoutePart(data.getSessionId(), train.id, List.of(stop /* current/target */, from /* from */), section.getAllStops(settings.searchDepartureInTicks.getValue(), prediction.getEntryIndex()))), false); + + if ((!isStart || isStartAndFinal) && (section.getTrainGroup() == null || !settings.searchExcludedTrainGroups.getValue().contains(section.getTrainGroup().getGroupName()))) { + + Route selectedRoute = route; + if (isStartAndFinal) { + TrainPrediction frPred = previousSection.getFirstStop().get(); + TrainStop fr = new TrainStop(frPred); + selectedRoute = new Route(List.of(new RoutePart(data.getSessionId(), train.id, List.of(stop /* current/target */, fr /* from */), previousSection.getAllStops(settings.searchDepartureInTicks.getValue(), prediction.getEntryIndex()))), false); + } + routesL.add(Pair.of(true, selectedRoute)); // Arrival + } + if ((section.isUsable()) && (section.getTrainGroup() == null || !settings.searchExcludedTrainGroups.getValue().contains(section.getTrainGroup().getGroupName()))) { + routesL.add(Pair.of(false, route)); // Departure + } + } + } + + Collections.sort(routesL, (a, b) -> { + long val1 = a.getFirst() ? a.getSecond().getStart().getScheduledArrivalTime() : a.getSecond().getStart().getScheduledDepartureTime(); + long val2 = b.getFirst() ? b.getSecond().getStart().getScheduledArrivalTime() : b.getSecond().getStart().getScheduledDepartureTime(); + return Long.compare(val1, val2); + }); + temp.setFirst(new ConcurrentLinkedQueue<>(routesL)); + } + + @SuppressWarnings("unchecked") + Queue> tags = (Queue>)((MutableSingle)temp).getFirst(); + if (tags.isEmpty()) { + return false; + } + Pair r = tags.poll(); + nbt.putBoolean("IsArrival", r.getFirst()); + nbt.put(DataAccessorType.DEFAULT_NBT_DATA, r.getSecond().toNbt()); + return !tags.isEmpty(); + } catch (Exception e) { + CreateRailwaysNavigator.LOGGER.error("Schedule board generation error.", e); + } + return false; + }, (IChunkReceiver>>)(hasMore, list, iteration, nbt) -> { + if (!nbt.contains(DataAccessorType.DEFAULT_NBT_DATA)) { + return List.of(); + } + if (list == null) { + list = new ArrayList>(); + } + list.add(Pair.of(nbt.getBoolean("IsArrival"), ClientRoute.fromNbt(nbt.getCompound(DataAccessorType.DEFAULT_NBT_DATA), false))); + return list; + }, (chunks) -> { + return chunks; + } + )); + + + public static record DeparturesData(UUID stationTagId, UUID trainId) {} + public static final DataAccessorType, List> GET_DEPARTURES_AT = DataAccessorType.register(new ResourceLocation(CreateRailwaysNavigator.MOD_ID, "get_departures_at"), DataAccessorType.Builder.create( + (in, nbt) -> { + nbt.putUUID("Tag", in.stationTagId()); + nbt.putUUID("Train", in.trainId()); + }, (nbt) -> { + return new DeparturesData(nbt.getUUID("Tag"), nbt.getUUID("Train")); + }, (player, in, temp, nbt, iteration) -> { + try { + StationTag tag = GlobalSettings.getInstance().getStationTag(in.stationTagId()).get(); + ListTag list = new ListTag(); + list.addAll(TrainUtils.getDeparturesAt(tag, in.trainId()).stream().map(x -> x.toNbt(true)).toList()); + nbt.put(DataAccessorType.DEFAULT_NBT_DATA, list); + } catch (Exception e) { + CreateRailwaysNavigator.LOGGER.error("Next connections error.", e); + } + return false; + }, (hasMore, data, iteration, nbt) -> { + return nbt.getList(DataAccessorType.DEFAULT_NBT_DATA, Tag.TAG_COMPOUND).stream().map(x -> (ClientTrainStop)ClientTrainStop.fromNbt((CompoundTag)x)).toList(); + } + )); + + public static final DataAccessorType ALL_TRAINS_INITIALIZED = DataAccessorType.register(new ResourceLocation(CreateRailwaysNavigator.MOD_ID, "all_trains_initialized"), DataAccessorType.Builder.createNoInput( + (player, in, temp, nbt, iteration) -> { + nbt.putBoolean(DataAccessorType.DEFAULT_NBT_DATA, TrainListener.allTrainsInitialized()); + return false; + }, (hasMore, data, iteration, nbt) -> { + return nbt.getBoolean(DataAccessorType.DEFAULT_NBT_DATA); + } + )); + + public static final DataAccessorType, List> GET_ALL_TRAINS_DEBUG_DATA = DataAccessorType.register(new ResourceLocation(CreateRailwaysNavigator.MOD_ID, "get_all_trains_debug_data"), DataAccessorType.Builder.createNoInput( + (player, in, temp, nbt, iteration) -> { + ListTag list = new ListTag(); + list.addAll(TrainListener.data.values().stream().map(x -> TrainDebugData.fromTrain(x).toNbt()).toList()); + nbt.put(DataAccessorType.DEFAULT_NBT_DATA, list); + return false; + }, (hasMore, data, iteration, nbt) -> { + return nbt.getList(DataAccessorType.DEFAULT_NBT_DATA, Tag.TAG_COMPOUND).stream().map(x -> TrainDebugData.fromNbt((CompoundTag)x)).toList(); + } + )); + + public static final DataAccessorType SHOW_TRAIN_DEBUG_SCREEN = DataAccessorType.register(new ResourceLocation(CreateRailwaysNavigator.MOD_ID, "show_train_debug_screen"), DataAccessorType.Builder.createNoIO( + (player, in, temp, nbt, iteration) -> { + ClientWrapper.showTrainDebugScreen(); + return false; + } + )); + + public static final DataAccessorType TRAIN_SOFT_RESET = DataAccessorType.register(new ResourceLocation(CreateRailwaysNavigator.MOD_ID, "train_soft_reset"), DataAccessorType.Builder.createEmptyResponse( + (in, nbt) -> { + nbt.putUUID(DataAccessorType.DEFAULT_NBT_DATA, in); + }, (nbt) -> { + return nbt.getUUID(DataAccessorType.DEFAULT_NBT_DATA); + }, (player, in, temp, nbt, iteration) -> { + if (TrainListener.data.containsKey(in)) { + TrainListener.data.get(in).resetPredictions(); + } + return false; + } + )); + + public static final DataAccessorType TRAIN_HARD_RESET = DataAccessorType.register(new ResourceLocation(CreateRailwaysNavigator.MOD_ID, "train_hard_reset"), DataAccessorType.Builder.createEmptyResponse( + (in, nbt) -> { + nbt.putUUID(DataAccessorType.DEFAULT_NBT_DATA, in); + }, (nbt) -> { + return nbt.getUUID(DataAccessorType.DEFAULT_NBT_DATA); + }, (player, in, temp, nbt, iteration) -> { + if (TrainListener.data.containsKey(in)) { + TrainListener.data.get(in).hardResetPredictions(); + } + return false; + } + )); + + public static void init() {} +} diff --git a/common/src/main/java/de/mrjulsen/crn/registry/ModBlockEntities.java b/common/src/main/java/de/mrjulsen/crn/registry/ModBlockEntities.java index 70bdd6e3..30e8cb91 100644 --- a/common/src/main/java/de/mrjulsen/crn/registry/ModBlockEntities.java +++ b/common/src/main/java/de/mrjulsen/crn/registry/ModBlockEntities.java @@ -3,8 +3,8 @@ import com.tterrag.registrate.util.entry.BlockEntityEntry; import de.mrjulsen.crn.CreateRailwaysNavigator; -import de.mrjulsen.crn.block.be.AdvancedDisplayBlockEntity; -import de.mrjulsen.crn.block.be.TrainStationClockBlockEntity; +import de.mrjulsen.crn.block.blockentity.AdvancedDisplayBlockEntity; +import de.mrjulsen.crn.block.blockentity.TrainStationClockBlockEntity; import de.mrjulsen.mcdragonlib.client.ber.StaticBlockEntityRenderer; public class ModBlockEntities { @@ -17,7 +17,8 @@ public class ModBlockEntities { ModBlocks.ADVANCED_DISPLAY_PANEL, ModBlocks.ADVANCED_DISPLAY_HALF_PANEL, ModBlocks.ADVANCED_DISPLAY_SMALL, - ModBlocks.ADVANCED_DISPLAY_SLOPED + ModBlocks.ADVANCED_DISPLAY_SLOPED, + ModBlocks.ADVANCED_DISPLAY_SLAB ) .renderer(() -> StaticBlockEntityRenderer::new) .register(); @@ -30,6 +31,6 @@ public class ModBlockEntities { .renderer(() -> StaticBlockEntityRenderer::new) .register(); - public static void register() { + public static void init() { } } diff --git a/common/src/main/java/de/mrjulsen/crn/registry/ModBlocks.java b/common/src/main/java/de/mrjulsen/crn/registry/ModBlocks.java index 287d7ef1..89b8ab72 100644 --- a/common/src/main/java/de/mrjulsen/crn/registry/ModBlocks.java +++ b/common/src/main/java/de/mrjulsen/crn/registry/ModBlocks.java @@ -14,6 +14,7 @@ import de.mrjulsen.crn.block.AdvancedDisplayBoardBlock; import de.mrjulsen.crn.block.AdvancedDisplayHalfPanelBlock; import de.mrjulsen.crn.block.AdvancedDisplayPanelBlock; +import de.mrjulsen.crn.block.AdvancedDisplaySlabBlock; import de.mrjulsen.crn.block.AdvancedDisplaySlopedBlock; import de.mrjulsen.crn.block.AdvancedDisplaySmallBlock; import de.mrjulsen.crn.block.TrainStationClockBlock; @@ -21,7 +22,7 @@ import de.mrjulsen.crn.block.connected.AdvancedDisplaySmallCTBehaviour; import de.mrjulsen.crn.block.display.AdvancedDisplayTarget; import dev.architectury.utils.EnvExecutor; -import dev.architectury.utils.Env; +import net.fabricmc.api.EnvType; import net.minecraft.client.renderer.RenderType; import net.minecraft.world.level.block.Block; @@ -38,6 +39,16 @@ public class ModBlocks { .transform(TagGen.pickaxeOnly()) .onRegister(AllDisplayBehaviours.assignDataBehaviour(new AdvancedDisplayTarget())) .item() + .tab(() -> ModCreativeModeTab.MAIN) + .build() + .register(); + public static final BlockEntry ADVANCED_DISPLAY_SLAB = CreateRailwaysNavigator.REGISTRATE.block("advanced_display_slab", AdvancedDisplaySlabBlock::new) + .onRegister(connectedTextures(() -> new AdvancedDisplaySmallCTBehaviour(ClientWrapper.CT_HORIZONTAL_ADVANCED_DISPLAY_SMALL, ClientWrapper.CT_ADVANCED_DISPLAY_SMALL))) + .addLayer(() -> RenderType::cutout) + .initialProperties(SharedProperties::softMetal) + .transform(TagGen.pickaxeOnly()) + .onRegister(AllDisplayBehaviours.assignDataBehaviour(new AdvancedDisplayTarget())) + .item() .build() .register(); @@ -105,9 +116,9 @@ public static NonNullConsumer connectedTextures( } protected static void onClient(Supplier toRun) { - EnvExecutor.runInEnv(Env.CLIENT, toRun); + EnvExecutor.runInEnv(EnvType.CLIENT, toRun); } - public static void register() { + public static void init() { } } diff --git a/common/src/main/java/de/mrjulsen/crn/registry/ModDisplayTags.java b/common/src/main/java/de/mrjulsen/crn/registry/ModDisplayTags.java index c77385ae..a534dde7 100644 --- a/common/src/main/java/de/mrjulsen/crn/registry/ModDisplayTags.java +++ b/common/src/main/java/de/mrjulsen/crn/registry/ModDisplayTags.java @@ -12,6 +12,7 @@ public static void register() { .add(ModBlocks.ADVANCED_DISPLAY_HALF_PANEL) .add(ModBlocks.ADVANCED_DISPLAY_SMALL) .add(ModBlocks.ADVANCED_DISPLAY_SLOPED) + .add(ModBlocks.ADVANCED_DISPLAY_SLAB) ; } } diff --git a/common/src/main/java/de/mrjulsen/crn/registry/ModDisplayTypes.java b/common/src/main/java/de/mrjulsen/crn/registry/ModDisplayTypes.java new file mode 100644 index 00000000..95edd1e1 --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/registry/ModDisplayTypes.java @@ -0,0 +1,81 @@ +package de.mrjulsen.crn.registry; + +import de.mrjulsen.crn.CreateRailwaysNavigator; +import de.mrjulsen.crn.block.properties.EDisplayInfo; +import de.mrjulsen.crn.block.properties.EDisplayType; +import de.mrjulsen.crn.client.AdvancedDisplaysRegistry; +import de.mrjulsen.crn.client.AdvancedDisplaysRegistry.DisplayTypeInfo; +import de.mrjulsen.crn.client.AdvancedDisplaysRegistry.DisplayTypeResourceKey; +import de.mrjulsen.crn.client.ber.variants.BERPassengerInfoInformative; +import de.mrjulsen.crn.client.ber.variants.BERPassengerInfoSimple; +import de.mrjulsen.crn.client.ber.variants.BERPlatformDetailed; +import de.mrjulsen.crn.client.ber.variants.BERPlatformInformative; +import de.mrjulsen.crn.client.ber.variants.BERPlatformSimple; +import de.mrjulsen.crn.client.ber.variants.BERTrainDestinationDetailed; +import de.mrjulsen.crn.client.ber.variants.BERTrainDestinationInformative; +import de.mrjulsen.crn.client.ber.variants.BERTrainDestinationSimple; +import net.minecraft.resources.ResourceLocation; + +public final class ModDisplayTypes { + + public static final DisplayTypeResourceKey PASSENGER_INFORMATION_RUNNING_TEXT = AdvancedDisplaysRegistry.registerDisplayType( + EDisplayType.PASSENGER_INFORMATION, new ResourceLocation(CreateRailwaysNavigator.MOD_ID, "running_text"), + BERPassengerInfoSimple::new, new DisplayTypeInfo(true, null)); + + public static final DisplayTypeResourceKey PASSENGER_INFORMATION_OVERVIEW = AdvancedDisplaysRegistry.registerDisplayType( + EDisplayType.PASSENGER_INFORMATION, new ResourceLocation(CreateRailwaysNavigator.MOD_ID, "detailed_with_schedule"), + BERPassengerInfoInformative::new, new DisplayTypeInfo(false, null)); + + public static final DisplayTypeResourceKey TRAIN_DESTINATION_SIMPLE = AdvancedDisplaysRegistry.registerDisplayType( + EDisplayType.TRAIN_DESTINATION, new ResourceLocation(CreateRailwaysNavigator.MOD_ID, "simple"), + BERTrainDestinationSimple::new, new DisplayTypeInfo(true, null)); + + public static final DisplayTypeResourceKey TRAIN_DESTINATION_DETAILED = AdvancedDisplaysRegistry.registerDisplayType( + EDisplayType.TRAIN_DESTINATION, new ResourceLocation(CreateRailwaysNavigator.MOD_ID, "extended"), + BERTrainDestinationDetailed::new, new DisplayTypeInfo(true, null)); + + public static final DisplayTypeResourceKey TRAIN_DESTINATION_OVERVIEW = AdvancedDisplaysRegistry.registerDisplayType( + EDisplayType.TRAIN_DESTINATION, new ResourceLocation(CreateRailwaysNavigator.MOD_ID, "detailed"), + BERTrainDestinationInformative::new, new DisplayTypeInfo(true, null)); + + public static final DisplayTypeResourceKey PLATFORM_RUNNING_TEXT = AdvancedDisplaysRegistry.registerDisplayType( + EDisplayType.PLATFORM, new ResourceLocation(CreateRailwaysNavigator.MOD_ID, "running_text"), + BERPlatformSimple::new, new DisplayTypeInfo(true, be -> 32)); + + public static final DisplayTypeResourceKey PLATFORM_TABLE = AdvancedDisplaysRegistry.registerDisplayType( + EDisplayType.PLATFORM, new ResourceLocation(CreateRailwaysNavigator.MOD_ID, "table"), + BERPlatformDetailed::new, new DisplayTypeInfo(false, be -> be.getYSize() * 3 - 1)); + + public static final DisplayTypeResourceKey PLATFORM_FOCUS = AdvancedDisplaysRegistry.registerDisplayType( + EDisplayType.PLATFORM, new ResourceLocation(CreateRailwaysNavigator.MOD_ID, "focus"), + BERPlatformInformative::new, new DisplayTypeInfo(false, be -> be.getYSize() * 3 - 2)); + + @Deprecated + public static DisplayTypeResourceKey legacy_getKeyForType(EDisplayType type, EDisplayInfo info) { + switch (type) { + case PASSENGER_INFORMATION -> { + switch (info) { + case INFORMATIVE -> { return PASSENGER_INFORMATION_OVERVIEW; } + default -> { return PASSENGER_INFORMATION_RUNNING_TEXT; } + } + } + case TRAIN_DESTINATION -> { + switch (info) { + case DETAILED -> { return TRAIN_DESTINATION_DETAILED; } + case INFORMATIVE -> { return TRAIN_DESTINATION_OVERVIEW; } + default -> { return TRAIN_DESTINATION_SIMPLE; } + } + } + case PLATFORM -> { + switch (info) { + case DETAILED -> { return PLATFORM_TABLE; } + case INFORMATIVE -> { return PLATFORM_FOCUS; } + default -> { return PLATFORM_RUNNING_TEXT; } + } + } + default -> { return TRAIN_DESTINATION_SIMPLE; } + } + } + + public static void init() {} +} diff --git a/common/src/main/java/de/mrjulsen/crn/registry/ModExtras.java b/common/src/main/java/de/mrjulsen/crn/registry/ModExtras.java index 2f4b5653..2e2d4c93 100644 --- a/common/src/main/java/de/mrjulsen/crn/registry/ModExtras.java +++ b/common/src/main/java/de/mrjulsen/crn/registry/ModExtras.java @@ -14,7 +14,7 @@ public class ModExtras { public static boolean registeredTrackStationSource = false; - public static void register() { + public static void init() { Block maybeRegistered = null; try { diff --git a/common/src/main/java/de/mrjulsen/crn/registry/ModItems.java b/common/src/main/java/de/mrjulsen/crn/registry/ModItems.java index def3fb7a..8fc5a2c7 100644 --- a/common/src/main/java/de/mrjulsen/crn/registry/ModItems.java +++ b/common/src/main/java/de/mrjulsen/crn/registry/ModItems.java @@ -15,6 +15,6 @@ public class ModItems { .properties(p -> p.stacksTo(1)) .register(); - public static void register() { + public static void init() { } } diff --git a/common/src/main/java/de/mrjulsen/crn/registry/ModSchedule.java b/common/src/main/java/de/mrjulsen/crn/registry/ModSchedule.java new file mode 100644 index 00000000..83a424dd --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/registry/ModSchedule.java @@ -0,0 +1,34 @@ +package de.mrjulsen.crn.registry; + +import java.util.function.Supplier; + +import com.simibubi.create.content.trains.schedule.Schedule; +import com.simibubi.create.content.trains.schedule.condition.ScheduleWaitCondition; +import com.simibubi.create.content.trains.schedule.destination.ScheduleInstruction; +import com.simibubi.create.foundation.utility.Pair; + +import de.mrjulsen.crn.CreateRailwaysNavigator; +import de.mrjulsen.crn.data.schedule.condition.DynamicDelayCondition; +import de.mrjulsen.crn.data.schedule.instruction.ResetTimingsInstruction; +import de.mrjulsen.crn.data.schedule.instruction.TravelSectionInstruction; +import net.minecraft.resources.ResourceLocation; + +public class ModSchedule { + + static { + registerInstruction("travel_section", TravelSectionInstruction::new); + registerInstruction("reset_timings", ResetTimingsInstruction::new); + + registerCondition("dynamic_delay", DynamicDelayCondition::new); + } + + private static void registerInstruction(String name, Supplier factory) { + Schedule.INSTRUCTION_TYPES.add(Pair.of(new ResourceLocation(CreateRailwaysNavigator.MOD_ID, name), factory)); + } + + private static void registerCondition(String name, Supplier factory) { + Schedule.CONDITION_TYPES.add(Pair.of(new ResourceLocation(CreateRailwaysNavigator.MOD_ID, name), factory)); + } + + public static void init() {} +} \ No newline at end of file diff --git a/common/src/main/java/de/mrjulsen/crn/registry/ModTrainStatusInfos.java b/common/src/main/java/de/mrjulsen/crn/registry/ModTrainStatusInfos.java new file mode 100644 index 00000000..e9f23df3 --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/registry/ModTrainStatusInfos.java @@ -0,0 +1,76 @@ +package de.mrjulsen.crn.registry; + +import java.util.Optional; + +import com.simibubi.create.content.trains.entity.Train; + +import de.mrjulsen.crn.CreateRailwaysNavigator; +import de.mrjulsen.crn.client.lang.ELanguage; +import de.mrjulsen.crn.data.train.TrainListener; +import de.mrjulsen.crn.data.train.TrainStatus; +import de.mrjulsen.crn.data.train.TrainStatus.Registry; +import de.mrjulsen.crn.data.train.TrainStatus.TrainStatusCategory; +import de.mrjulsen.crn.data.train.TrainStatus.TrainStatusType; +import de.mrjulsen.crn.mixin.TrainStatusAccessor; + +public final class ModTrainStatusInfos { + + public static final Registry REGISTRY = Registry.create(CreateRailwaysNavigator.MOD_ID); + + // Custom + public static final TrainStatus RED_SIGNAL = REGISTRY.register("red_signal", new TrainStatus(TrainStatusCategory.TRAIN, TrainStatusType.DELAY, (data) -> ELanguage.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".train_status.red_signal"), (data) -> { + return data.isCurrentSectionDelayed() && + data.getTrain().navigation.waitingForSignal != null && + data.getTrain().navigation.waitingForSignal.getSecond() && + data.occupyingTrains.isEmpty() + ; + })); + + public static final TrainStatus PRIORITY_OTHER_TRAIN = REGISTRY.register("priority_other_train", new TrainStatus(TrainStatusCategory.TRAIN, TrainStatusType.DELAY, (data) -> ELanguage.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".train_status.priority_other_train"), (data) -> { + if (!data.isCurrentSectionDelayed() || data.getTrain().navigation.waitingForSignal == null) { + return false; + } + + Optional occupyingTrain = data.occupyingTrains.stream().findFirst(); + if (!occupyingTrain.isPresent()) { + return false; + } + + return TrainListener.data.containsKey(occupyingTrain.get().id) ? !TrainListener.data.get(occupyingTrain.get().id).isDelayed() : false; + })); + + public static final TrainStatus PERVIOUS_TRAIN_DELAYED = REGISTRY.register("previous_train_delayed", new TrainStatus(TrainStatusCategory.TRAIN, TrainStatusType.DELAY, (data) -> ELanguage.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".train_status.delay_other_train"), (data) -> { + if (!data.isCurrentSectionDelayed() || data.getTrain().navigation.waitingForSignal == null) { + return false; + } + + Optional occupyingTrain = data.occupyingTrains.stream().findFirst(); + if (!occupyingTrain.isPresent()) { + return false; + } + + return TrainListener.data.containsKey(occupyingTrain.get().id) ? TrainListener.data.get(occupyingTrain.get().id).isDelayed() : false; + })); + + public static final TrainStatus TRACK_CLOSED = REGISTRY.register("track_closed", new TrainStatus(TrainStatusCategory.TRAIN, TrainStatusType.DELAY, (data) -> ELanguage.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".train_status.track_closed"), (data) -> { + return data.isCurrentSectionDelayed() && ((TrainStatusAccessor)data.getTrain().status).crn$track(); + })); + + public static final TrainStatus STAFF_SHORTAGE = REGISTRY.register("staff_shortage", new TrainStatus(TrainStatusCategory.TRAIN, TrainStatusType.DELAY, (data) -> ELanguage.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".train_status.staff_shortage"), (data) -> { + return data.isCurrentSectionDelayed() && ((TrainStatusAccessor)data.getTrain().status).crn$conductor(); + })); + + public static final TrainStatus OPERATIONAL_DISRUPTION = REGISTRY.register("operational_disruption", new TrainStatus(TrainStatusCategory.TRAIN, TrainStatusType.DELAY, (data) -> ELanguage.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".train_status.operational_disruption"), (data) -> { + return data.isCurrentSectionDelayed() && ((TrainStatusAccessor)data.getTrain().status).crn$navigation(); + })); + + public static final TrainStatus SPECIAL_JOURNEY = REGISTRY.register("special_journey", new TrainStatus(TrainStatusCategory.TRAIN, TrainStatusType.DELAY, (data) -> ELanguage.translate("gui." + CreateRailwaysNavigator.MOD_ID + ".train_status.special_trip"), (data) -> { + return false; + })); + + public static final TrainStatus OUT_OF_SERVICE = REGISTRY.register("out_of_service", new TrainStatus(TrainStatusCategory.TRAIN, TrainStatusType.DELAY, (data) -> ELanguage.translate("block." + CreateRailwaysNavigator.MOD_ID + ".advanced_display.ber.not_in_service"), (data) -> { + return data.isCurrentSectionDelayed() && data.getTrain().runtime.paused; + })); + + public static void init() {} +} diff --git a/common/src/main/java/de/mrjulsen/crn/registry/data/NextConnectionsRequestData.java b/common/src/main/java/de/mrjulsen/crn/registry/data/NextConnectionsRequestData.java new file mode 100644 index 00000000..0ef29c3f --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/registry/data/NextConnectionsRequestData.java @@ -0,0 +1,24 @@ +package de.mrjulsen.crn.registry.data; + +import java.util.UUID; + +import net.minecraft.nbt.CompoundTag; + +public record NextConnectionsRequestData(String stationName, UUID selfTrainId) { + public static final String NBT_STATION_NAME = "StationName"; + public static final String NBT_TRAIN_ID = "TrainId"; + + public CompoundTag toNbt() { + CompoundTag nbt = new CompoundTag(); + nbt.putString(NBT_STATION_NAME, stationName()); + nbt.putUUID(NBT_TRAIN_ID, selfTrainId()); + return nbt; + } + + public static NextConnectionsRequestData fromNbt(CompoundTag nbt) { + return new NextConnectionsRequestData( + nbt.getString(NBT_STATION_NAME), + nbt.getUUID(NBT_TRAIN_ID) + ); + } +} diff --git a/common/src/main/java/de/mrjulsen/crn/util/BERUtils.java b/common/src/main/java/de/mrjulsen/crn/util/BERUtils.java deleted file mode 100644 index ad0725a1..00000000 --- a/common/src/main/java/de/mrjulsen/crn/util/BERUtils.java +++ /dev/null @@ -1,66 +0,0 @@ -package de.mrjulsen.crn.util; - -import com.mojang.blaze3d.platform.GlStateManager; -import com.mojang.blaze3d.systems.RenderSystem; -import com.mojang.blaze3d.vertex.PoseStack; -import com.mojang.blaze3d.vertex.VertexConsumer; - -import de.mrjulsen.crn.client.ModGuiUtils; -import de.mrjulsen.mcdragonlib.util.ColorUtils; -import net.minecraft.client.renderer.MultiBufferSource; -import net.minecraft.client.renderer.RenderType; -import net.minecraft.client.renderer.texture.OverlayTexture; -import net.minecraft.core.Direction; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.level.block.entity.BlockEntity; -import net.minecraft.world.level.block.state.BlockState; - -public class BERUtils { - - public void initRenderEngine() { - RenderSystem.enableBlend(); - RenderSystem.enableDepthTest(); - RenderSystem.blendFunc(GlStateManager.SourceFactor.SRC_ALPHA, GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA); - } - - public void setTint(int r, int g, int b, int a) { - RenderSystem.setShaderColor(r, g, b, a); - } - - public void renderTexture(ResourceLocation texture, MultiBufferSource bufferSource, BlockEntity blockEntity, PoseStack poseStack, float x, float y, float z, float w, float h, float u0, float v0, float u1, float v1, Direction facing, int tint, int light) { - VertexConsumer vertexconsumer = bufferSource.getBuffer(RenderType.text(texture)); - short[] color = ColorUtils.decodeARGB(tint); - addQuadSide(blockEntity, blockEntity.getBlockState(), facing, vertexconsumer, poseStack, - x, y, z, - x + w, y + h, z, - u0, v0, - u1, v1, - (float)color[1] / 255.0F, (float)color[2] / 255.0F, (float)color[3] / 255.0F, (float)color[0] / 255.0F, - light - ); - } - - public static void addVert(VertexConsumer builder, PoseStack pPoseStack, float x, float y, float z, float u, float v, float r, float g, float b, float a, int lu, int lv) { - builder.vertex(pPoseStack.last().pose(), x, y, z).color(r, g, b, a).uv(u, v).uv2(lu, lv).overlayCoords(OverlayTexture.NO_OVERLAY).normal(pPoseStack.last().normal(), 0, 0, 1).endVertex(); - } - - private static void renderWithoutAO(VertexConsumer builder, PoseStack pPoseStack, float x0, float y0, float z0, float x1, float y1, float z1, float u0, float v0, float u1, float v1, float r, float g, float b, float a, int packedLight) { - addVert(builder, pPoseStack, x0, y0, z0, u0, v0, r, g, b, a, packedLight & 0xFFFF, (packedLight >> 16) & 0xFFFF); - addVert(builder, pPoseStack, x0, y1, z0, u0, v1, r, g, b, a, packedLight & 0xFFFF, (packedLight >> 16) & 0xFFFF); - addVert(builder, pPoseStack, x1, y1, z1, u1, v1, r, g, b, a, packedLight & 0xFFFF, (packedLight >> 16) & 0xFFFF); - addVert(builder, pPoseStack, x1, y0, z1, u1, v0, r, g, b, a, packedLight & 0xFFFF, (packedLight >> 16) & 0xFFFF); - } - - @SuppressWarnings("resources") - public static void addQuadSide(BlockEntity be, BlockState state, Direction direction, VertexConsumer builder, PoseStack pPoseStack, float x0, float y0, float z0, float x1, float y1, float z1, float u0, float v0, float u1, float v1, float r, float g, float b, float a, int packedLight) { - renderWithoutAO(builder, pPoseStack, x0, y0, z0, x1, y1, z1, u0, v0, u1, v1, r, g, b, a, packedLight); - } - - public void fillColor(MultiBufferSource pBufferSource, BlockEntity blockEntity, int color, PoseStack poseStack, float x, float y, float z, float w, float h, Direction facing, int light) { - renderTexture(ModGuiUtils.getBlankTexture(), pBufferSource, blockEntity, poseStack, x, y, z, w, h, 0, 0, 1, 1, facing, color, light); - } - - - - -} diff --git a/common/src/main/java/de/mrjulsen/crn/util/DLListUtils.java b/common/src/main/java/de/mrjulsen/crn/util/DLListUtils.java new file mode 100644 index 00000000..3285a590 --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/util/DLListUtils.java @@ -0,0 +1,48 @@ +package de.mrjulsen.crn.util; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.function.BiConsumer; +import java.util.function.BiPredicate; + +public final class DLListUtils { + public static void iterateLooped(List list, int startIndex, BiConsumer action) { + for (int i = 0; i < list.size(); i++) { + int j = (i + startIndex) % list.size(); + action.accept(j, list.get(j)); + } + } + + public static List getNextN(List list, int startIndex, int count) { + if (count > list.size()) { + throw new IndexOutOfBoundsException("The number of elements to be obtained is greater than the list."); + } + List elements = new ArrayList<>(); + for (int i = 0; i < count; i++) { + int j = (i + startIndex) % list.size(); + elements.add(list.get(j)); + } + return elements; + } + + public static Optional getNext(List list, int startIndex, BiPredicate predicate) { + for (int i = 0; i < list.size(); i++) { + int j = (i + startIndex) % list.size(); + if (predicate.test(j, list.get(j))) { + return Optional.of(list.get(j)); + } + } + return Optional.empty(); + } + + public static Optional getPrevious(List list, int startIndex, BiPredicate predicate) { + for (int i = 0; i < list.size(); i++) { + int j = (i + startIndex) % list.size(); + if (predicate.test(j, list.get(j))) { + return Optional.of(list.get(j)); + } + } + return Optional.empty(); + } +} diff --git a/common/src/main/java/de/mrjulsen/crn/util/IListenable.java b/common/src/main/java/de/mrjulsen/crn/util/IListenable.java new file mode 100644 index 00000000..09ca49b2 --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/util/IListenable.java @@ -0,0 +1,76 @@ +package de.mrjulsen.crn.util; + +import java.util.ArrayList; +import java.util.IdentityHashMap; +import java.util.Map; +import java.util.function.Consumer; + +public interface IListenable { + + Map>> getListeners(); + + default void createEvent(String name) { + if (getListeners().containsKey(name)) { + throw new IllegalArgumentException("An event with this ID has already been created: " + name); + } + getListeners().put(name, new IdentityHashMap<>()); + } + + default void deleteEvent(String name) { + if (getListeners().containsKey(name)) { + getListeners().remove(name); + } + } + + default void clearEvents() { + getListeners().clear(); + } + + default int eventsCount() { + return getListeners().size(); + } + + default int listenersCount(String name) { + if (!hasEvent(name)) { + throw new IllegalArgumentException("This listener event does not exist: " + name); + } + + return getListeners().get(name).size(); + } + + default boolean hasEvent(String name) { + return getListeners().containsKey(name); + } + + default void listen(String name, Object listenerObject, Consumer listener) { + if (!hasEvent(name)) { + throw new IllegalArgumentException("This listener event does not exist: " + name); + } + + getListeners().get(name).put(listenerObject, listener); + } + + default void stopListening(String name, Object listenerObject) { + if (!hasEvent(name)) { + throw new IllegalArgumentException("This listener event does not exist: " + name); + } + + getListeners().get(name).remove(listenerObject); + } + + default void stopListeningAll(Object listenerObject) { + getListeners().values().stream().forEach(x -> { + if (x.containsKey(listenerObject)) { + x.remove(listenerObject); + } + }); + } + + default void notifyListeners(String name, T data) { + if (!hasEvent(name)) { + throw new IllegalArgumentException("This listener event does not exist: " + name); + } + + new ArrayList<>(getListeners().get(name).values()).stream().forEach(x -> x.accept(data)); + } +} diff --git a/common/src/main/java/de/mrjulsen/crn/util/LockedList.java b/common/src/main/java/de/mrjulsen/crn/util/LockedList.java new file mode 100644 index 00000000..65b19771 --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/util/LockedList.java @@ -0,0 +1,73 @@ +package de.mrjulsen.crn.util; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.stream.Stream; + +public class LockedList extends ArrayList { + + private boolean locked; + + private void lock() { + while (locked) {} + locked = true; + } + + private void unlock() { + locked = false; + } + + @Override + public boolean add(T e) { + lock(); + boolean b = super.add(e); + unlock(); + return b; + } + + @Override + public void add(int index, T element) { + lock(); + super.add(index, element); + unlock(); + } + + @Override + public boolean remove(Object o) { + lock(); + boolean b = super.remove(o); + unlock(); + return b; + } + + @Override + public void clear() { + lock(); + super.clear(); + unlock(); + } + + @Override + public T get(int index) { + lock(); + T t = super.get(index); + unlock(); + return t; + } + + @Override + public boolean retainAll(Collection c) { + lock(); + boolean b = super.retainAll(c); + unlock(); + return b; + } + + @Override + public Stream stream() { + lock(); + Stream s = super.stream(); + unlock(); + return s; + } +} diff --git a/common/src/main/java/de/mrjulsen/crn/util/ModUtils.java b/common/src/main/java/de/mrjulsen/crn/util/ModUtils.java index 69bcbbcb..84673a5d 100644 --- a/common/src/main/java/de/mrjulsen/crn/util/ModUtils.java +++ b/common/src/main/java/de/mrjulsen/crn/util/ModUtils.java @@ -1,17 +1,28 @@ package de.mrjulsen.crn.util; -import java.util.Map; -import java.util.Set; - +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; +import java.util.function.Predicate; import com.simibubi.create.foundation.utility.Components; import com.simibubi.create.foundation.utility.Lang; +import de.mrjulsen.crn.config.ModClientConfig; +import de.mrjulsen.crn.exceptions.RuntimeSideException; +import de.mrjulsen.crn.web.WebsitePreparableReloadListener; import de.mrjulsen.mcdragonlib.DragonLib; import de.mrjulsen.mcdragonlib.util.TextUtils; +import de.mrjulsen.mcdragonlib.util.TimeUtils; +import dev.architectury.platform.Platform; +import dev.architectury.utils.Env; import net.minecraft.network.chat.MutableComponent; import net.minecraft.util.Mth; public class ModUtils { + + private static WebsitePreparableReloadListener websitemanager; + public static float clockHandDegrees(long time, int divisor) { return 360.0F / divisor * (time % divisor); } @@ -22,14 +33,38 @@ public static double calcSpeed(double metersPerTick, ESpeedUnit unit) { public static MutableComponent calcSpeedString(double metersPerTick, ESpeedUnit unit) { return TextUtils.text((int) Math.abs(Math.round(calcSpeed(metersPerTick, unit))) + " " + unit.getUnit()); + } + + public static int calculateMedian(Queue history, int smoothingThreshold, Predicate filter) { + if (history.isEmpty()) { + return 0; + } + + List values = new LinkedList<>(); + for (int i : history) { + if (!filter.test(i)) + continue; + + values.add(i); + } + + Collections.sort(values); + int median = 0; + if (values.size() % 2 == 0) { + median = (int)(((double)values.get(values.size() / 2) + (double)values.get(values.size() / 2 + 1)) / 2D); + } + median = values.get(values.size() / 2); + + final int med = median; + return (int)history.stream().mapToInt(x -> x).filter(x -> Math.abs(med - x) <= smoothingThreshold).average().orElse(0); } - public static String timeRemainingString(int ticks) { + public static String timeRemainingString(long ticks) { StringBuilder sb = new StringBuilder(); final String unpredictable = " ~ "; final String whitespace = " "; - if (ticks == -1 || ticks >= 12000 - 15 * 20) { + if (ticks == -1 || ticks >= 120000 - 15 * 20) { sb.append(whitespace); sb.append(unpredictable); @@ -37,8 +72,8 @@ public static String timeRemainingString(int ticks) { sb.append(Lang.translateDirect("display_source.station_summary.now").getString()); } else { - int min = ticks / 1200; - int sec = (ticks / 20) % 60; + long min = ticks / 1200; + long sec = (ticks / 20) % 60; sec = Mth.ceil(sec / 15f) * 15; if (sec == 60) { min++; @@ -51,17 +86,30 @@ public static String timeRemainingString(int ticks) { return sb.toString(); } - public static boolean areEqual(Set> set1, Set> set2) { - if (set1.size() != set2.size()) { - return false; - } + public static long generateId(Predicate exists) { + long id; + do { + id = DragonLib.RANDOM.nextLong(); + } while (exists.test(id)); + return id; + } - for (Map.Entry entry : set1) { - if(!set2.contains(entry)) { - return false; - } - } + public static void setWebsiteResourceManager(WebsitePreparableReloadListener manager) { + websitemanager = manager; + } - return true; + public static WebsitePreparableReloadListener getWebsiteResourceManager() { + return websitemanager; + } + + /** Client-side only! */ + public static String formatTime(long time, boolean asETA) throws RuntimeSideException { + if (Platform.getEnvironment() != Env.CLIENT) { + throw new RuntimeSideException(true); + } + if (asETA) { + return timeRemainingString(time - DragonLib.getCurrentWorldTime()); + } + return TimeUtils.parseTime((time + DragonLib.DAYTIME_SHIFT) % DragonLib.TICKS_PER_DAY, ModClientConfig.TIME_FORMAT.get()); } } diff --git a/common/src/main/java/de/mrjulsen/crn/util/TrainUtils.java b/common/src/main/java/de/mrjulsen/crn/util/TrainUtils.java deleted file mode 100644 index a984b399..00000000 --- a/common/src/main/java/de/mrjulsen/crn/util/TrainUtils.java +++ /dev/null @@ -1,297 +0,0 @@ -package de.mrjulsen.crn.util; - -import java.util.AbstractMap; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Comparator; -import java.util.Map; -import java.util.Optional; -import java.util.Set; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.UUID; -import java.util.stream.Collectors; - -import com.simibubi.create.Create; -import com.simibubi.create.content.decoration.slidingDoor.DoorControlBehaviour; -import com.simibubi.create.content.trains.GlobalRailwayManager; -import com.simibubi.create.content.trains.display.GlobalTrainDisplayData; -import com.simibubi.create.content.trains.display.GlobalTrainDisplayData.TrainDeparturePrediction; -import com.simibubi.create.content.trains.entity.Train; -import com.simibubi.create.content.trains.graph.EdgePointType; -import com.simibubi.create.content.trains.graph.TrackEdge; -import com.simibubi.create.content.trains.signal.TrackEdgePoint; -import com.simibubi.create.content.trains.station.GlobalStation; -import com.simibubi.create.content.trains.station.StationBlockEntity; -import de.mrjulsen.crn.data.DeparturePrediction; -import de.mrjulsen.crn.data.GlobalSettingsManager; -import de.mrjulsen.crn.data.NearestTrackStationResult; -import de.mrjulsen.crn.data.SimpleTrainConnection; -import de.mrjulsen.crn.data.SimpleTrainSchedule; -import de.mrjulsen.crn.data.SimulatedTrainSchedule; -import de.mrjulsen.crn.data.TrainStationAlias; -import de.mrjulsen.crn.data.TrainStop; -import de.mrjulsen.crn.data.DeparturePrediction.TrainExitSide; -import de.mrjulsen.mcdragonlib.util.MathUtils; -import net.minecraft.core.BlockPos; -import net.minecraft.core.Direction; -import net.minecraft.core.Vec3i; -import net.minecraft.world.level.Level; -import net.minecraft.world.phys.Vec3; - -public class TrainUtils { - - public static final GlobalRailwayManager RAILWAY_MANAGER = Create.RAILWAYS; - - /** - * Get data about all trains and when they arrive where. - * @return a Map where the key is the station name and the value is a list of data from all trains that will arrive at this stations. - */ - public static Map> Gott() { - return new HashMap<>(GlobalTrainDisplayData.statusByDestination); - } - - public static Map> getMappedDeparturePredictions() { - Map> statusData = Gott(); - Map> map = new HashMap<>(); - GlobalSettingsManager.getInstance().getSettingsData().getAliasList().forEach(alias -> { - map.put(alias, statusData.entrySet().stream().filter(x -> alias.contains(x.getKey())).flatMap(x -> x.getValue().stream()).map(x -> new DeparturePrediction(x)).toList()); - }); - return map; - } - - public static void getMappedDeparturePredictions(Map> globalPredictions, Map> trainPredictions) { - Map> statusData = Gott(); - statusData.entrySet().forEach((e) -> { - String key = e.getKey(); - Collection value = e.getValue(); - TrainStationAlias alias = GlobalSettingsManager.getInstance().getSettingsData().getAliasFor(key); - if (!globalPredictions.containsKey(alias.getAliasName().get())) { - globalPredictions.put(alias.getAliasName().get(), new ArrayList<>()); - } - globalPredictions.get(alias.getAliasName().get()).addAll(value.stream().map(x -> new DeparturePrediction(x)).toList()); - value.forEach(x -> { - if (!trainPredictions.containsKey(x.train.id)) { - trainPredictions.put(x.train.id, new ArrayList<>()); - } - trainPredictions.get(x.train.id).add(new DeparturePrediction(x)); - }); - }); - } - - public static Collection getTrainDeparturePredictions(UUID trainId, Level level) { - final Map> edges = level != null ? getAllEdgesMapped() : new HashMap<>(); - final Map> stationsByName = level != null ? getAllStations().stream().collect(Collectors.groupingBy(x -> x.name, Collectors.toSet())) : new HashMap<>(); - - Collection preds = Gott().values().stream().flatMap(x -> x.stream()).filter(x -> x.train.id.equals(trainId)).map(x -> { - DeparturePrediction prediction = new DeparturePrediction(x); - if (stationsByName.containsKey(prediction.getStationName())) { - Set exitSides = stationsByName.get(prediction.getStationName()).stream().filter(a -> edges.containsKey(a.id)).map(a -> getTrainStationExit(a, Direction.fromYRot(angleOn(a, edges.get(a.id).stream().findFirst().get())), level)).collect(Collectors.toSet()); - if (exitSides.size() == 1) { - prediction.setExit(exitSides.stream().findFirst().get()); - } - } - return prediction; - }).toList(); - return preds; - } - - public static Collection getTrainStopsSorted(UUID trainId, Level level) { - return getTrainDeparturePredictions(trainId, level).stream().map(x -> new TrainStop(x.getNextStop(), x)).filter(x -> !GlobalSettingsManager.getInstance().getSettingsData().isBlacklisted(x.getPrediction().getStationName())).sorted(Comparator.comparingInt(x -> x.getPrediction().getTicks())).toList(); - } - - public static List getConnectionsAt(String stationName, UUID currentTrainId, int ticksToNextStop) { - TrainStationAlias alias = GlobalSettingsManager.getInstance().getSettingsData().getAliasFor(stationName); - SimpleTrainSchedule ownSchedule = SimpleTrainSchedule.of(getTrainStopsSorted(currentTrainId, null)); - GlobalTrainDisplayData.refresh(); - - List excludedSchedules = new ArrayList<>(); - Map scheduleByPrediction = new HashMap<>(); - Map simulatedScheduleByPrediction = new HashMap<>(); - - return Gott().entrySet().stream().filter(x -> alias.contains(x.getKey())).map(x -> x.getValue()) - .flatMap(x -> x.parallelStream().map(y -> new DeparturePrediction(y))) - .peek(x -> { - SimpleTrainSchedule schedule = SimpleTrainSchedule.of(getTrainStopsSorted(x.getTrain().id, null)); - scheduleByPrediction.put(x, schedule); - simulatedScheduleByPrediction.put(x, schedule.simulate(x.getTrain(), ticksToNextStop, alias)); - }) - .sorted(Comparator.comparingInt(x -> simulatedScheduleByPrediction.get(x).getSimulationData().simulationCorrection())) - .filter(x -> { - SimpleTrainSchedule schedule = scheduleByPrediction.get(x); - SimulatedTrainSchedule directionalSchedule = simulatedScheduleByPrediction.get(x); - - if (excludedSchedules.stream().anyMatch(y -> y.exactEquals(directionalSchedule))) { - return false; - } - - boolean b = !x.getTrain().id.equals(currentTrainId) && - TrainUtils.isTrainValid(x.getTrain()) && - !GlobalSettingsManager.getInstance().getSettingsData().isTrainBlacklisted(x.getTrain()) && - !schedule.equals(ownSchedule); - - if (b) { - excludedSchedules.add(directionalSchedule); - } - - return b; - }).map(x -> { - SimulatedTrainSchedule sched = simulatedScheduleByPrediction.get(x); - Optional firstStop = sched.getFirstStopOf(x.getNextStop()); - return new SimpleTrainConnection( - x.getTrain().name.getString(), - x.getTrain().id, - x.getTrain().icon.getId(), - sched.getSimulationData().simulationTime() + sched.getSimulationData().simulationCorrection(), - firstStop.isPresent() ? firstStop.get().getPrediction().getScheduleTitle() : x.getScheduleTitle(), - firstStop.isPresent() ? firstStop.get().getStationAlias().getInfoForStation(firstStop.get().getPrediction().getStationName()) : x.getInfo() - ); - }).sorted(Comparator.comparingInt(x -> x.ticks())).toList(); - - } - - public static boolean GottKnows(String station) { - return Gott().keySet().stream().anyMatch(x -> { - String regex = x.isBlank() ? x : "\\Q" + x.replace("*", "\\E.*\\Q") + "\\E"; - return station.matches(regex); - }); - } - - /** - * A list of all stations in the world. - * @return a list containing all track stations. - */ - public static Collection getAllStations() { - final Collection stations = new ArrayList<>(); - RAILWAY_MANAGER.trackNetworks.forEach((uuid, graph) -> { - Collection foundStations = graph.getPoints(EdgePointType.STATION); - stations.addAll(foundStations); - }); - return stations; - } - - public static Collection getAllEdges() { - final Set edges = new HashSet<>(); - RAILWAY_MANAGER.trackNetworks.forEach((uuid, graph) -> { - edges.addAll(graph.getNodes().stream().map(x -> graph.locateNode(x)).flatMap(x -> graph.getConnectionsFrom(x).values().stream()).collect(Collectors.toSet())); - }); - return edges; - } - - public static Map> getAllEdgesMapped() { - final Map> edges = new HashMap<>(); - RAILWAY_MANAGER.trackNetworks.forEach((uuid, graph) -> { - edges.putAll(graph.getNodes().stream() - .map(x -> graph.locateNode(x)) - .flatMap(x -> graph.getConnectionsFrom(x).values().stream()) - .distinct() - .flatMap(edge -> edge.getEdgeData().getPoints().stream().map(point -> new AbstractMap.SimpleEntry<>(point.id, edge))) - .collect(Collectors.groupingBy(Map.Entry::getKey, Collectors.mapping(Map.Entry::getValue, Collectors.toSet()))) - ); - }); - return edges; - } - - public static Optional getEdgeForStation(GlobalStation station) { - return getAllEdges().stream().filter(x -> x.getEdgeData().getPoints().stream().anyMatch(y -> y.equals(station))).findFirst(); - } - - public static NearestTrackStationResult getNearestTrackStation(Level level, Vec3i pos) { - Optional station = getAllStations().stream().filter(x -> - GottKnows(x.name) && - x.getBlockEntityDimension().equals(level.dimension()) && - !GlobalSettingsManager.getInstance().getSettingsData().isBlacklisted(x.name) - ).min((a, b) -> Double.compare(a.getBlockEntityPos().distSqr(pos), b.getBlockEntityPos().distSqr(pos))); - - double distance = station.isPresent() ? station.get().getBlockEntityPos().distSqr(pos) : 0; - return new NearestTrackStationResult(station, distance); - } - - public static double getStationAngle(GlobalStation station) { - return angleOn(station, getEdgeForStation(station).get()); - } - - public static Direction getStationDirection(GlobalStation station) { - return Direction.fromYRot(getStationAngle(station)); - } - - public static double angleOn(TrackEdgePoint point, TrackEdge edge) { - double basePos = point.isPrimary(edge.node1) ? edge.getLength() - point.position : point.position; - Vec3 vec = edge.getDirectionAt(basePos); - return point.isPrimary(edge.node1) ? MathUtils.getVectorAngle(vec) : MathUtils.getVectorAngle(vec.reverse()); - } - - public static DoorControlBehaviour getTrainStationDoorControl(GlobalStation station, Level level) { - BlockPos stationPos = station.getBlockEntityPos(); - if (level == null || !level.isLoaded(stationPos)) { - return null; - } - if (level.getBlockEntity(stationPos) instanceof StationBlockEntity be) { - return be.doorControls; - } - return null; - } - - /** - * - * @param station - * @param server - * @return 1 = right, -1 = left, 0 = unknown - */ - public static TrainExitSide getTrainStationExitDirection(GlobalStation station, Level level) { - DoorControlBehaviour dcb = getTrainStationDoorControl(station, level); - if (dcb == null) { - return TrainExitSide.UNKNOWN; - } - Direction stationDirection = getStationDirection(station); - - if (dcb.mode.matches(stationDirection.getClockWise())) { - return TrainExitSide.RIGHT; - } else if (dcb.mode.matches(stationDirection.getCounterClockWise())) { - return TrainExitSide.LEFT; - } - return TrainExitSide.UNKNOWN; - } - - public static TrainExitSide getTrainStationExit(GlobalStation station, Direction stationDirection, Level level) { - DoorControlBehaviour dcb = getTrainStationDoorControl(station, level); - if (dcb == null) { - return TrainExitSide.UNKNOWN; - } - - if (dcb.mode.matches(stationDirection.getClockWise())) { - return TrainExitSide.RIGHT; - } else if (dcb.mode.matches(stationDirection.getCounterClockWise())) { - return TrainExitSide.LEFT; - } - return TrainExitSide.UNKNOWN; - } - - - - /** - * A list of all trains in the world. - * @return a list containing all trains. - */ - public static Collection getAllTrains() { - return RAILWAY_MANAGER.trains.values(); - } - - public static Train getTrain(UUID trainId) { - return RAILWAY_MANAGER.trains.get(trainId); - } - - public static boolean isTrainValid(Train train) { - return !train.derailed && - !train.invalid && - !train.runtime.paused && - train.runtime.getSchedule() != null && - train.graph != null - ; - } - - public static boolean isTrainIdValid(UUID trainId) { - return isTrainValid(getTrain(trainId)); - } -} diff --git a/common/src/main/java/de/mrjulsen/crn/web/SimpleWebServer.java b/common/src/main/java/de/mrjulsen/crn/web/SimpleWebServer.java new file mode 100644 index 00000000..82812505 --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/web/SimpleWebServer.java @@ -0,0 +1,274 @@ +package de.mrjulsen.crn.web; + +import java.util.Map; +import java.util.Optional; +import java.util.TreeMap; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; + +import de.mrjulsen.crn.CreateRailwaysNavigator; +import de.mrjulsen.crn.util.ModUtils; +import de.mrjulsen.crn.data.storage.GlobalSettings; +import de.mrjulsen.crn.event.ModCommonEvents; +import de.mrjulsen.crn.data.navigation.NavigatableGraph; +import de.mrjulsen.crn.data.navigation.Route; +import de.mrjulsen.mcdragonlib.DragonLib; +import de.mrjulsen.mcdragonlib.util.DLUtils; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.packs.PackType; + +import com.sun.net.httpserver.HttpHandler; +import com.sun.net.httpserver.HttpServer; +import com.sun.net.httpserver.HttpsServer; +import com.google.common.net.MediaType; +import com.sun.net.httpserver.HttpExchange; + +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URLDecoder; + +public class SimpleWebServer { + + static HttpServer server; + + public static void start() throws Exception { + /* + server = HttpServer.create(new InetSocketAddress(80), 0); + server.createContext("/hello", new HelloWorldHandler()); + server.createContext("/api/" + CreateRailwaysNavigator.MOD_ID + "/navigate", new V1NavigationHandler()); + server.createContext("/" + CreateRailwaysNavigator.MOD_ID, new WebsiteHandler(CreateRailwaysNavigator.MOD_ID)); + server.createContext("/" + CreateRailwaysNavigator.SHORT_MOD_ID, new WebsiteHandler(CreateRailwaysNavigator.SHORT_MOD_ID)); + server.createContext("/status", new TestHandler()); + server.setExecutor(null); // creates a default executor + server.start(); + */ + } + + public static void stop() { + DLUtils.doIfNotNull(server, x -> { + x.stop(0); + }); + } + + public static Map> parseQueryParameters(HttpExchange ex, Charset charset) { + String queryString = ex.getRequestURI().getRawQuery(); + if (queryString == null || queryString.isEmpty()) { + return Collections.emptyMap(); + } + Map> parsedParams = new TreeMap>(); + for (String param : queryString.split("&")) { + String[] parts = param.split("=", 2); + String key = parts[0]; + String value = parts.length == 2 ? parts[1] : ""; + try { + key = URLDecoder.decode(key, charset.name()); + value = URLDecoder.decode(value, charset.name()); + } catch (UnsupportedEncodingException e) { + throw new AssertionError(e); + } + List values = parsedParams.get(key); + if (values == null) { + values = new LinkedList(); + parsedParams.put(key, values); + } + values.add(value); + } + + for (Map.Entry> me : parsedParams.entrySet()) { + me.setValue(Collections.unmodifiableList(me.getValue())); + } + return Collections.unmodifiableMap(parsedParams); + } + + private static void startResponse(HttpExchange ex, int code, MediaType contentType, boolean hasBody) throws IOException { + if (contentType != null) { + ex.getResponseHeaders().set("Content-Type", contentType.type()); + } + if (!hasBody) { // No body. Required for HEAD requests + ex.sendResponseHeaders(code, -1); + } else { // Chuncked encoding + ex.sendResponseHeaders(code, 0); + } + } + + private static void sendError(HttpExchange ex, int code, String msg) { + CreateRailwaysNavigator.LOGGER.warn(msg); + try { + respond(ex, code, MediaType.PLAIN_TEXT_UTF_8, msg.getBytes()); + } catch (IOException e) { + CreateRailwaysNavigator.LOGGER.error("Unable to send error response.", e); + } + } + + private static void respond(HttpExchange ex, int code, MediaType contentType, byte response[]) throws IOException { + startResponse(ex, code, contentType, response != null); + if (response != null) { + OutputStream responseBody = ex.getResponseBody(); + responseBody.write(response); + responseBody.flush(); + responseBody.close(); + } + ex.close(); + } + + public static void sendRedirect(HttpExchange ex, URI location) throws IOException { + ex.getResponseHeaders().set("Location", location.toString()); + respond(ex, HttpURLConnection.HTTP_SEE_OTHER, null, null); + } + + public static URI getRequestUri(HttpExchange ex) { + String host = ex.getRequestHeaders().getFirst("Host"); + if (host == null) { // Client must be using HTTP/1.0 + CreateRailwaysNavigator.LOGGER.warn("Request did not provide Host header, using 'localhost' as hostname"); + int port = ex.getHttpContext().getServer().getAddress().getPort(); + host = "localhost:" + port; + } + String protocol = (ex.getHttpContext().getServer() instanceof HttpsServer) ? "https" : "http"; + URI base; + try { + base = new URI(protocol, host, "/", null, null); + } catch (URISyntaxException e) { + throw new IllegalStateException(e); + } + URI requestedUri = ex.getRequestURI(); + requestedUri = base.resolve(requestedUri); + return requestedUri; + } + + public static void redirectTo(HttpExchange ex, String redirect) { + URI base = getRequestUri(ex); + URI path; + try { + path = new URI(redirect); + sendRedirect(ex, base.resolve(path)); + } catch (URISyntaxException | IOException e) { + CreateRailwaysNavigator.LOGGER.error("Could not construct URI.", e); + } + } + + + + static class HelloWorldHandler implements HttpHandler { + + @Override + public void handle(HttpExchange t) throws IOException { + String response = "Hello World! " + CreateRailwaysNavigator.MOD_ID; + t.sendResponseHeaders(200, response.length()); + OutputStream os = t.getResponseBody(); + os.write(response.getBytes()); + os.close(); + } + } + + static class WebsiteHandler implements HttpHandler { + + private final String subUrl; + private final int subUrlLength; + + public WebsiteHandler(String subUrl) { + this.subUrl = subUrl; + this.subUrlLength = subUrl.length() + 1; + } + + @Override + public void handle(HttpExchange t) throws IOException { + + String requestedPath = t.getRequestURI().getPath(); + if (requestedPath.startsWith("/" + subUrl) && requestedPath.length() >= subUrlLength) { + requestedPath = requestedPath.substring(subUrlLength); + } else { + sendError(t, HttpURLConnection.HTTP_BAD_REQUEST, "The requested URL is invalid: " + requestedPath); + } + + if (requestedPath.isBlank()) { + redirectTo(t, "/" + subUrl + "/"); + return; + } + + if (requestedPath.equals("/")) { + requestedPath = "/index.html"; // Set default page to index.html + } + + CreateRailwaysNavigator.LOGGER.info("A web service requested a resource: " + requestedPath); + Optional fileData = ModUtils.getWebsiteResourceManager().getFileBytesFor(requestedPath); + + if (!fileData.isPresent()) { + sendError(t, HttpURLConnection.HTTP_NOT_FOUND, "The requested resource does not exist: " + requestedPath); + /* + String response = "404 (Not Found)\n" + requestedPath + " does not exist."; + t.sendResponseHeaders(404, response.length()); + OutputStream os = t.getResponseBody(); + os.write(response.getBytes()); + os.close(); + */ + } else { + respond(t, HttpURLConnection.HTTP_OK, null, fileData.get()); + /* + byte[] fileBytes = fileData.get(); + System.out.println("BYTES: " + fileBytes.length); + t.sendResponseHeaders(200, 0); + OutputStream os = t.getResponseBody(); + os.write(fileBytes); + os.close(); + */ + } + } + } + + static class TestHandler implements HttpHandler { + @Override + public void handle(HttpExchange t) throws IOException { + String response = "PACKS"; + for (String str : ModCommonEvents.getCurrentServer().get().getPackRepository().getAvailableIds()) { + response += "\n - " + str; + } + response += "\nCRN PACK"; + for (ResourceLocation str : ModCommonEvents.getCurrentServer().get().getPackRepository().getPack("mod:" + CreateRailwaysNavigator.MOD_ID).open().getResources(PackType.SERVER_DATA, CreateRailwaysNavigator.MOD_ID, "", (str) -> true)) { + response += "\n - " + str; + } + t.sendResponseHeaders(200, response.length()); + OutputStream os = t.getResponseBody(); + os.write(response.getBytes()); + os.close(); + } + } + + static class V1NavigationHandler implements HttpHandler { + + private static final String KEY_START = "start"; + private static final String KEY_DESTINATION = "destination"; + + @Override + public void handle(HttpExchange t) throws IOException { + Map> params = parseQueryParameters(t, StandardCharsets.UTF_8); + + if (!params.containsKey(KEY_START) || !params.containsKey(KEY_DESTINATION)) { + sendError(t, HttpURLConnection.HTTP_BAD_REQUEST, "Wrong parameters."); + } + + try { + + GlobalSettings settings = GlobalSettings.getInstance(); + List routes = NavigatableGraph.searchRoutes(settings.getOrCreateStationTagFor(params.get(KEY_START).get(0)), settings.getOrCreateStationTagFor(params.get(KEY_DESTINATION).get(0)), null, true); + String response = DragonLib.GSON.toJson(routes); + + t.sendResponseHeaders(200, 0); + OutputStream os = t.getResponseBody(); + os.write(response.getBytes()); + os.close(); + + } catch (Exception e) { + CreateRailwaysNavigator.LOGGER.error("DEAD", e); + } + + + } + } +} diff --git a/common/src/main/java/de/mrjulsen/crn/web/WebsitePreparableReloadListener.java b/common/src/main/java/de/mrjulsen/crn/web/WebsitePreparableReloadListener.java new file mode 100644 index 00000000..cd2b0086 --- /dev/null +++ b/common/src/main/java/de/mrjulsen/crn/web/WebsitePreparableReloadListener.java @@ -0,0 +1,57 @@ +package de.mrjulsen.crn.web; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.function.Supplier; + +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.packs.resources.Resource; +import net.minecraft.server.packs.resources.ResourceManager; +import net.minecraft.server.packs.resources.SimplePreparableReloadListener; +import net.minecraft.util.profiling.ProfilerFiller; + +public class WebsitePreparableReloadListener extends SimplePreparableReloadListener>> { + + public static final String RESOURCE_PATH = "crn_website"; + + private Map> data; + + @Override + protected void apply(Map> object, ResourceManager resourceManager, ProfilerFiller profiler) { + data = object; + } + + @Override + protected Map> prepare(ResourceManager resourceManager, ProfilerFiller profiler) { + + Map> map = new HashMap<>(); + Collection locations = resourceManager.listResources(RESOURCE_PATH, x -> true).keySet(); + + for (ResourceLocation loc : locations) { + final ResourceLocation localLoc = loc; + String path = localLoc.getPath().replace(RESOURCE_PATH, ""); + map.put(path, () -> { + try { + Resource resource = resourceManager.getResourceOrThrow(localLoc); + try (InputStream inputstream = resource.open()) { + return inputstream.readAllBytes(); + } + } catch (IOException e) { + e.printStackTrace(); + return null; + } + }); + } + + return map; + } + + public Optional getFileBytesFor(String path) { + return Optional.ofNullable(data.containsKey(path) ? data.get(path).get() : null); + } + +} diff --git a/common/src/main/resources/assets/createrailwaysnavigator/blockstates/advanced_display_slab.json b/common/src/main/resources/assets/createrailwaysnavigator/blockstates/advanced_display_slab.json new file mode 100644 index 00000000..ca37c364 --- /dev/null +++ b/common/src/main/resources/assets/createrailwaysnavigator/blockstates/advanced_display_slab.json @@ -0,0 +1,101 @@ +{ + "variants": { + "facing=north,side=front,y_alignment=negative": { + "model": "createrailwaysnavigator:block/advanced_display_slab_neg" + }, + "facing=east,side=front,y_alignment=negative": { + "model": "createrailwaysnavigator:block/advanced_display_slab_neg", + "y": 90 + }, + "facing=south,side=front,y_alignment=negative": { + "model": "createrailwaysnavigator:block/advanced_display_slab_neg", + "y": 180 + }, + "facing=west,side=front,y_alignment=negative": { + "model": "createrailwaysnavigator:block/advanced_display_slab_neg", + "y": 270 + }, + + "facing=north,side=both,y_alignment=negative": { + "model": "createrailwaysnavigator:block/advanced_display_slab_double_neg" + }, + "facing=east,side=both,y_alignment=negative": { + "model": "createrailwaysnavigator:block/advanced_display_slab_double_neg", + "y": 90 + }, + "facing=south,side=both,y_alignment=negative": { + "model": "createrailwaysnavigator:block/advanced_display_slab_double_neg", + "y": 180 + }, + "facing=west,side=both,y_alignment=negative": { + "model": "createrailwaysnavigator:block/advanced_display_slab_double_neg", + "y": 270 + }, + + + "facing=north,side=front,y_alignment=center": { + "model": "createrailwaysnavigator:block/advanced_display_slab_cen" + }, + "facing=east,side=front,y_alignment=center": { + "model": "createrailwaysnavigator:block/advanced_display_slab_cen", + "y": 90 + }, + "facing=south,side=front,y_alignment=center": { + "model": "createrailwaysnavigator:block/advanced_display_slab_cen", + "y": 180 + }, + "facing=west,side=front,y_alignment=center": { + "model": "createrailwaysnavigator:block/advanced_display_slab_cen", + "y": 270 + }, + + "facing=north,side=both,y_alignment=center": { + "model": "createrailwaysnavigator:block/advanced_display_slab_double_cen" + }, + "facing=east,side=both,y_alignment=center": { + "model": "createrailwaysnavigator:block/advanced_display_slab_double_cen", + "y": 90 + }, + "facing=south,side=both,y_alignment=center": { + "model": "createrailwaysnavigator:block/advanced_display_slab_double_cen", + "y": 180 + }, + "facing=west,side=both,y_alignment=center": { + "model": "createrailwaysnavigator:block/advanced_display_slab_double_cen", + "y": 270 + }, + + + "facing=north,side=front,y_alignment=positive": { + "model": "createrailwaysnavigator:block/advanced_display_slab_pos" + }, + "facing=east,side=front,y_alignment=positive": { + "model": "createrailwaysnavigator:block/advanced_display_slab_pos", + "y": 90 + }, + "facing=south,side=front,y_alignment=positive": { + "model": "createrailwaysnavigator:block/advanced_display_slab_pos", + "y": 180 + }, + "facing=west,side=front,y_alignment=positive": { + "model": "createrailwaysnavigator:block/advanced_display_slab_pos", + "y": 270 + }, + + "facing=north,side=both,y_alignment=positive": { + "model": "createrailwaysnavigator:block/advanced_display_slab_double_pos" + }, + "facing=east,side=both,y_alignment=positive": { + "model": "createrailwaysnavigator:block/advanced_display_slab_double_pos", + "y": 90 + }, + "facing=south,side=both,y_alignment=positive": { + "model": "createrailwaysnavigator:block/advanced_display_slab_double_pos", + "y": 180 + }, + "facing=west,side=both,y_alignment=positive": { + "model": "createrailwaysnavigator:block/advanced_display_slab_double_pos", + "y": 270 + } + } +} \ No newline at end of file diff --git a/common/src/main/resources/assets/createrailwaysnavigator/lang/bar.json b/common/src/main/resources/assets/createrailwaysnavigator/lang/bar.json index 595ba832..9d9aea12 100644 --- a/common/src/main/resources/assets/createrailwaysnavigator/lang/bar.json +++ b/common/src/main/resources/assets/createrailwaysnavigator/lang/bar.json @@ -35,8 +35,7 @@ "block.createrailwaysnavigator.advanced_display_sloped.tooltip.condition1": "R-Klick mid am SchraubenschlĂŒssel", "block.createrailwaysnavigator.advanced_display_sloped.tooltip.behaviour1": "Öffne a _MenĂŒ_, um de Anzeige zu _konfigurieren_.", - "block.createrailwaysnavigator.advanced_display.ber.not_in_service": "Außa Betrieb", - "block.createrailwaysnavigator.advanced_display.ber.shunting_trip": "Betriebsfahrt", + "block.createrailwaysnavigator.advanced_display.ber.not_in_service": "Betriebsfahrt", "category.createrailwaysnavigator.crn": "Create Railways Navigator", "key.createrailwaysnavigator.route_overlay_options": "Routenanzeig EinstĂ€iunga", @@ -108,16 +107,16 @@ "gui.createrailwaysnavigator.route_overview.after_journey": "Sie hom %s eareicht. Mia bedankn uns fia Ihre Reise und Servus.", "gui.createrailwaysnavigator.route_overview.next_connections": "Naxte OschlĂŒsse", "gui.createrailwaysnavigator.route_overview.schedule_transfer": "Umstieg", - "gui.createrailwaysnavigator.route_overview.train_canceled": "Zuag foid aus", + "gui.createrailwaysnavigator.route_overview.train_cancelled": "Zuag foid aus", "gui.createrailwaysnavigator.route_overview.train_cancellation_info": "Informadion zua %s", - "gui.createrailwaysnavigator.route_overview.stop_canceled": "❌ Foid aus", + "gui.createrailwaysnavigator.route_overview.stop_cancelled": "❌ Foid aus", "gui.createrailwaysnavigator.route_overview.connection_endangered": "Oschluss gfĂ€hrdet", "gui.createrailwaysnavigator.route_overview.connection_missed": "Oschluss vapasst", - "gui.createrailwaysnavigator.route_overview.connection_canceled": "Zuag foid aus", + "gui.createrailwaysnavigator.route_overview.connection_cancelled": "Zuag foid aus", "gui.createrailwaysnavigator.route_overview.journey_interrupted": "Ihre Reise noch %s konn ned fortgsetzt wern.", "gui.createrailwaysnavigator.route_overview.connection_missed_info": "Aufgrund oana ZuagvaspĂ€tung hom Sie Ihrn Oschluss vapasst. Suchn Sie noch oana Oidanative im Navigatoa. Mia entschuidign uns fia de Unannehmlichkeitn.", - "gui.createrailwaysnavigator.route_overview.train_canceled_info": "Informadion zua %s: Dea Zuag foid heid aus! Mia entschuidign uns fia de Unannehmlichkeitn. Suchn Sie noch oana Oidanative im Navigatoa.", - "gui.createrailwaysnavigator.route_overview.train_canceled_title": "Zuag foid heid aus", + "gui.createrailwaysnavigator.route_overview.train_cancelled_info": "Informadion zua %s: Dea Zuag foid heid aus! Mia entschuidign uns fia de Unannehmlichkeitn. Suchn Sie noch oana Oidanative im Navigatoa.", + "gui.createrailwaysnavigator.route_overview.train_cancelled_title": "Zuag foid heid aus", "gui.createrailwaysnavigator.route_overview.journey_interrupted_info": "Ihre Reise noch %s konn ned fortgsetzt wern. Suchn Sie noch oana Oidanative im Navigatoa.", "gui.createrailwaysnavigator.route_overview.options": "Drugge %s fia Optiona.", "gui.createrailwaysnavigator.route_overview.notification.journey_begins.title": "Ihre Reise noch %s ofangt!", @@ -127,8 +126,8 @@ "gui.createrailwaysnavigator.route_overview.notification.platform_changed": "Ihr Zuag in %s fahrd heid vo Gleis %s ob.", "gui.createrailwaysnavigator.route_overview.notification.train_delayed.title": "%s: Okunft %s vaspĂ€tet.", "gui.createrailwaysnavigator.route_overview.notification.train_delayed": "%s stod %s in %s", - "gui.createrailwaysnavigator.route_overview.notification.train_canceled.title": "%s: Zuag foid aus", - "gui.createrailwaysnavigator.route_overview.notification.train_canceled": "%s noch %s foid heid aus.", + "gui.createrailwaysnavigator.route_overview.notification.train_cancelled.title": "%s: Zuag foid aus", + "gui.createrailwaysnavigator.route_overview.notification.train_cancelled": "%s noch %s foid heid aus.", "gui.createrailwaysnavigator.route_overview.notification.transfer.title": "Ihr Umstieg stĂ€d bevoa", "gui.createrailwaysnavigator.route_overview.notification.transfer": "Steign Sie in %s → %s auf Gleis %s um", "gui.createrailwaysnavigator.route_overview.notification.transfer_with_platform": "Steign Sie in %s → %s um", @@ -151,16 +150,16 @@ "gui.createrailwaysnavigator.global_settings.train_blacklist.title": "Zuag Blacklist", "gui.createrailwaysnavigator.global_settings.train_blacklist.description": "Schliaße ZĂŒg aus, z.B. GĂŒterzĂŒg, SonderzĂŒg, etc., damit de ned in den VorschlĂ€gn auftaan.", - "gui.createrailwaysnavigator.alias_settings.title": "Bohhof Dogs EinstĂ€iunga", - "gui.createrailwaysnavigator.alias_settings.summary": "EnthĂ€lt %s Stationa", - "gui.createrailwaysnavigator.alias_settings.editor": "Zualetzt vo %s am %s bearbadet", - "gui.createrailwaysnavigator.alias_settings.add.tooltip": "Nein Eintrog eastĂ€in", - "gui.createrailwaysnavigator.alias_settings.delete_alias.tooltip": "Dog löschn", - "gui.createrailwaysnavigator.alias_settings.delete_station.tooltip": "Station entferna", - "gui.createrailwaysnavigator.alias_settings.add_station.tooltip": "Station hizufĂŒgn", - "gui.createrailwaysnavigator.alias_settings.hint.station_name": "Bohhofsnama", - "gui.createrailwaysnavigator.alias_settings.hint.platform": "Gleisbezeichnung", - "gui.createrailwaysnavigator.alias_settings.enter_name": "Nama eingem", + "gui.createrailwaysnavigator.station_tags.title": "Bohhof Dogs EinstĂ€iunga", + "gui.createrailwaysnavigator.station_tags.summary": "EnthĂ€lt %s Stationa", + "gui.createrailwaysnavigator.station_tags.editor": "Zualetzt vo %s am %s bearbadet", + "gui.createrailwaysnavigator.station_tags.add.tooltip": "Nein Eintrog eastĂ€in", + "gui.createrailwaysnavigator.station_tags.delete_alias.tooltip": "Dog löschn", + "gui.createrailwaysnavigator.station_tags.delete_station.tooltip": "Station entferna", + "gui.createrailwaysnavigator.station_tags.add_station.tooltip": "Station hizufĂŒgn", + "gui.createrailwaysnavigator.station_tags.hint.station_name": "Bohhofsnama", + "gui.createrailwaysnavigator.station_tags.hint.platform": "Gleisbezeichnung", + "gui.createrailwaysnavigator.station_tags.enter_name": "Nama eingem", "gui.createrailwaysnavigator.train_group_settings.title": "Zuagkategorin EinstĂ€iunga", "gui.createrailwaysnavigator.train_group_settings.summary": "EnthĂ€lt %s Ziag", diff --git a/common/src/main/resources/assets/createrailwaysnavigator/lang/de_de.json b/common/src/main/resources/assets/createrailwaysnavigator/lang/de_de.json index 340f81a5..e690d6a3 100644 --- a/common/src/main/resources/assets/createrailwaysnavigator/lang/de_de.json +++ b/common/src/main/resources/assets/createrailwaysnavigator/lang/de_de.json @@ -34,9 +34,12 @@ "block.createrailwaysnavigator.advanced_display_sloped.tooltip.summary": "Benutze den Block auf _ZĂŒgen_ als _Zugzielanzeigen_ oder _Fahrgastinformationsanzeigen_ oder an _Bahnhöfen_ als verbesserte _Bahnsteiganzeige_, die auch mehr Informationen anzeigen als normale Anzeigetafeln.", "block.createrailwaysnavigator.advanced_display_sloped.tooltip.condition1": "R-Klick mit einem SchraubenschlĂŒssel", "block.createrailwaysnavigator.advanced_display_sloped.tooltip.behaviour1": "Öffne ein _MenĂŒ_, um die Anzeige zu _konfigurieren_.", + "block.createrailwaysnavigator.advanced_display_slab": "Verbesserte Anzeige-Stufe", + "block.createrailwaysnavigator.advanced_display_slab.tooltip.summary": "Benutze den Block auf _ZĂŒgen_ als _Zugzielanzeigen_ oder _Fahrgastinformationsanzeigen_ oder an _Bahnhöfen_ als verbesserte _Bahnsteiganzeige_, die auch mehr Informationen anzeigen als normale Anzeigetafeln.", + "block.createrailwaysnavigator.advanced_display_slab.tooltip.condition1": "R-Klick mit einem SchraubenschlĂŒssel", + "block.createrailwaysnavigator.advanced_display_slab.tooltip.behaviour1": "Öffne ein _MenĂŒ_, um die Anzeige zu _konfigurieren_.", - "block.createrailwaysnavigator.advanced_display.ber.not_in_service": "Außer Betrieb", - "block.createrailwaysnavigator.advanced_display.ber.shunting_trip": "Betriebsfahrt", + "block.createrailwaysnavigator.advanced_display.ber.not_in_service": "Betriebsfahrt", "category.createrailwaysnavigator.crn": "Create Railways Navigator", "key.createrailwaysnavigator.route_overlay_options": "Routenanzeige Einstellungen", @@ -73,7 +76,9 @@ "gui.createrailwaysnavigator.common.search": "Suchen", "gui.createrailwaysnavigator.common.auto": "Autom.", "gui.createrailwaysnavigator.common.server_error": "Fehler beim AusfĂŒhren der Aufgabe. Schaue in die Serverkonsole.", - "gui.createrailwaysnavigator.via": "ĂŒber", + "gui.createrailwaysnavigator.common.delete": "Löschen", + "gui.createrailwaysnavigator.common.add": "HinzufĂŒgen", + "gui.createrailwaysnavigator.common.help": "Hilfe erhalten", "gui.createrailwaysnavigator.navigator.title": "Create Railways Navigator", "gui.createrailwaysnavigator.navigator.no_connections": "Keine Verbindungen gefunden.", @@ -89,15 +94,15 @@ "gui.createrailwaysnavigator.navigator.search_settings.tooltip": "Sucheinstellungen", "gui.createrailwaysnavigator.navigator.search.tooltip": "Suchen", "gui.createrailwaysnavigator.navigator.location.tooltip": "NĂ€chsgelegene Station von aktueller Position", + "gui.createrailwaysnavigator.navigator.refresh.tooltip": "Aktualisieren", "gui.createrailwaysnavigator.navigator.switch.tooltip": "Eingaben tauschen", "gui.createrailwaysnavigator.route_details.title": "Streckendetails", "gui.createrailwaysnavigator.route_details.departure": "Abfahrt in", "gui.createrailwaysnavigator.route_details.next_transfer_time": "Umstieg in", "gui.createrailwaysnavigator.route_details.transfer": "Umstieg", - "gui.createrailwaysnavigator.route_details.save_route": "Route speichern", - "gui.createrailwaysnavigator.route_overview.title": "Streckendetails", + "gui.createrailwaysnavigator.route_overview.title": "Reisebegleitung", "gui.createrailwaysnavigator.route_overview.journey_begins": "Ihre Reise beginnt! %s nach %s, Abfahrt %s", "gui.createrailwaysnavigator.route_overview.journey_begins_with_platform": "Ihre Reise beginnt! %s nach %s, Abfahrt %s von Gleis %s", "gui.createrailwaysnavigator.route_overview.train_details": "%s nach %s", @@ -108,16 +113,16 @@ "gui.createrailwaysnavigator.route_overview.after_journey": "Sie haben %s erreicht. Wir bedanken uns fĂŒr Ihre Reise und wĂŒnschen einen schönen Tag.", "gui.createrailwaysnavigator.route_overview.next_connections": "NĂ€chste AnschlĂŒsse", "gui.createrailwaysnavigator.route_overview.schedule_transfer": "Umstieg", - "gui.createrailwaysnavigator.route_overview.train_canceled": "Zug fĂ€llt aus", + "gui.createrailwaysnavigator.route_overview.train_cancelled": "Zug fĂ€llt aus", "gui.createrailwaysnavigator.route_overview.train_cancellation_info": "Information zu %s", - "gui.createrailwaysnavigator.route_overview.stop_canceled": "❌ FĂ€llt aus", + "gui.createrailwaysnavigator.route_overview.stop_cancelled": "❌ FĂ€llt aus", "gui.createrailwaysnavigator.route_overview.connection_endangered": "Anschluss gefĂ€hrdet", "gui.createrailwaysnavigator.route_overview.connection_missed": "Anschluss verpasst", - "gui.createrailwaysnavigator.route_overview.connection_canceled": "Zug fĂ€llt aus", + "gui.createrailwaysnavigator.route_overview.connection_cancelled": "Zug fĂ€llt aus", "gui.createrailwaysnavigator.route_overview.journey_interrupted": "Ihre Reise nach %s kann nicht fortgesetzt werden.", "gui.createrailwaysnavigator.route_overview.connection_missed_info": "Aufgrund einer ZugverspĂ€tung haben Sie Ihren Anschluss verpasst. Suchen Sie nach einer Alternative im Navigator. Wir entschuldigen uns fĂŒr die Unannehmlichkeiten.", - "gui.createrailwaysnavigator.route_overview.train_canceled_info": "Information zu %s: Dieser Zug fĂ€llt heute aus! Wir entschuldigen uns fĂŒr die Unannehmlichkeiten. Suchen Sie nach einer Alternative im Navigator.", - "gui.createrailwaysnavigator.route_overview.train_canceled_title": "Zug fĂ€llt heute aus", + "gui.createrailwaysnavigator.route_overview.train_cancelled_info": "Information zu %s: Dieser Zug fĂ€llt heute aus! Wir entschuldigen uns fĂŒr die Unannehmlichkeiten. Suchen Sie nach einer Alternative im Navigator.", + "gui.createrailwaysnavigator.route_overview.train_cancelled_title": "Zug fĂ€llt heute aus", "gui.createrailwaysnavigator.route_overview.journey_interrupted_info": "Ihre Reise nach %s kann nicht fortgesetzt werden. Suchen Sie nach einer Alternative im Navigator.", "gui.createrailwaysnavigator.route_overview.options": "DrĂŒcke %s fĂŒr Optionen.", "gui.createrailwaysnavigator.route_overview.notification.journey_begins.title": "Ihre Reise nach %s beginnt!", @@ -127,8 +132,8 @@ "gui.createrailwaysnavigator.route_overview.notification.platform_changed": "Ihr Zug in %s fĂ€hrt heute von Gleis %s ab.", "gui.createrailwaysnavigator.route_overview.notification.train_delayed.title": "%s: Ankunft %s verspĂ€tet.", "gui.createrailwaysnavigator.route_overview.notification.train_delayed": "%s statt %s in %s", - "gui.createrailwaysnavigator.route_overview.notification.train_canceled.title": "%s: Zug fĂ€llt aus", - "gui.createrailwaysnavigator.route_overview.notification.train_canceled": "%s nach %s fĂ€llt heute aus.", + "gui.createrailwaysnavigator.route_overview.notification.train_cancelled.title": "%s: Zug fĂ€llt aus", + "gui.createrailwaysnavigator.route_overview.notification.train_cancelled": "%s nach %s fĂ€llt heute aus.", "gui.createrailwaysnavigator.route_overview.notification.transfer.title": "Ihr Umstieg steht bevor", "gui.createrailwaysnavigator.route_overview.notification.transfer": "Steigen Sie in %s → %s auf Gleis %s um", "gui.createrailwaysnavigator.route_overview.notification.transfer_with_platform": "Steigen Sie in %s → %s um", @@ -143,7 +148,7 @@ "gui.createrailwaysnavigator.global_settings.title": "Globale Einstellungen", "gui.createrailwaysnavigator.global_settings.option.tooltip": "Zum Bearbeiten klicken", "gui.createrailwaysnavigator.global_settings.option_alias.title": "Bahnhof Tags", - "gui.createrailwaysnavigator.global_settings.option_alias.description": "Erstelle Tags, um mehrere Stationen (z.B. MyStation 1, MyStation 2, ...) als einen Bahnhof (z.B. MyStation) zu behandeln.", + "gui.createrailwaysnavigator.global_settings.option_alias.description": "Fasse einzelne Bahnhöfe zu einem einzigen Bahnhof mit mehreren Bahnsteigen und eigenem Namen zusammen, damit der Navigator Umsteigemöglichkeiten etc. vorschlagen kann.", "gui.createrailwaysnavigator.global_settings.option_blacklist.title": "Bahnhof Blacklist", "gui.createrailwaysnavigator.global_settings.option_blacklist.description": "Schließe Stationen aus, die nicht in den Navigtionsergebnissen erscheinen sollen. Diese Stationen werden bei der Suche ignoriert.", "gui.createrailwaysnavigator.global_settings.train_group.title": "Zugkategorien", @@ -151,16 +156,16 @@ "gui.createrailwaysnavigator.global_settings.train_blacklist.title": "Zug Blacklist", "gui.createrailwaysnavigator.global_settings.train_blacklist.description": "Schließe ZĂŒge aus, z.B. GĂŒterzĂŒge, SonderzĂŒge, etc., damit diese nicht in den VorschlĂ€gen auftauchen.", - "gui.createrailwaysnavigator.alias_settings.title": "Bahnhof Tags Einstellungen", - "gui.createrailwaysnavigator.alias_settings.summary": "EnthĂ€lt %s Stationen", - "gui.createrailwaysnavigator.alias_settings.editor": "Zuletzt von %s am %s bearbeitet", - "gui.createrailwaysnavigator.alias_settings.add.tooltip": "Neuen Eintrag erstellen", - "gui.createrailwaysnavigator.alias_settings.delete_alias.tooltip": "Tag löschen", - "gui.createrailwaysnavigator.alias_settings.delete_station.tooltip": "Station entfernen", - "gui.createrailwaysnavigator.alias_settings.add_station.tooltip": "Station hinzufĂŒgen", - "gui.createrailwaysnavigator.alias_settings.hint.station_name": "Bahnhofsname", - "gui.createrailwaysnavigator.alias_settings.hint.platform": "Gleisbezeichnung", - "gui.createrailwaysnavigator.alias_settings.enter_name": "Name eingeben", + "gui.createrailwaysnavigator.station_tags.summary": "EnthĂ€lt %s Stationen", + "gui.createrailwaysnavigator.station_tags.editor": "Zuletzt von %s am %s bearbeitet", + "gui.createrailwaysnavigator.station_tags.add.tooltip": "Neuen Eintrag erstellen", + "gui.createrailwaysnavigator.station_tags.delete_alias.tooltip": "Tag löschen", + "gui.createrailwaysnavigator.station_tags.delete_station.tooltip": "Station entfernen", + "gui.createrailwaysnavigator.station_tags.modify_platform.tooltip": "Zum Ändern klicken", + "gui.createrailwaysnavigator.station_tags.add_station.tooltip": "Station hinzufĂŒgen", + "gui.createrailwaysnavigator.station_tags.hint.station_name": "Bahnhofsname", + "gui.createrailwaysnavigator.station_tags.hint.platform": "Gleisbezeichnung", + "gui.createrailwaysnavigator.station_tags.enter_name": "Name eingeben", "gui.createrailwaysnavigator.train_group_settings.title": "Zugkategorien Einstellungen", "gui.createrailwaysnavigator.train_group_settings.summary": "EnthĂ€lt %s ZĂŒge", @@ -200,6 +205,7 @@ "gui.createrailwaysnavigator.destination": "Ziel", "gui.createrailwaysnavigator.line": "Linie", "gui.createrailwaysnavigator.following_trains": "FolgezĂŒge:", + "gui.createrailwaysnavigator.via": "ĂŒber", "gui.createrailwaysnavigator.advanced_display_settings.title": "Verbesserte Anzeige Einstellungen", "gui.createrailwaysnavigator.advanced_display_settings.display_type": "Anzeigetyp", @@ -245,6 +251,101 @@ "gui.createrailwaysnavigator.display_source.advanced_display.train_name_width.description": "Die Breite der Spalte fĂŒr den Zugname in Pixel. (Standard: 16)", "gui.createrailwaysnavigator.display_source.advanced_display.platform_width": "Gleis Spaltenbreite", "gui.createrailwaysnavigator.display_source.advanced_display.platform_width.description": "Die Breite der Spalte fĂŒr das Gleis in Pixel. (-1 = Autom., Standard: -1)", + + "gui.createrailwaysnavigator.section_settings.title": "Abschnittseinstellungen", + "gui.createrailwaysnavigator.section_settings.train_groups": "Zugkategorie zuweisen", + "gui.createrailwaysnavigator.section_settings.train_lines": "Zuglinie zuweisen", + "gui.createrailwaysnavigator.section_settings.include_previous_station": "Start des nĂ€chsten Bereichs einbinden", + "gui.createrailwaysnavigator.section_settings.usable": "Navigierbar", + "gui.createrailwaysnavigator.section_settings.none": "(Keine)", + + "createrailwaysnavigator.schedule.condition.dynamic_delay": "Dynamische Wartezeit", + "createrailwaysnavigator.schedule.condition.dynamic_delay.min_duration": "Mindestwartezeit", + "createrailwaysnavigator.schedule.condition.dynamic_delay.title": "Warte: %s..%s", + "createrailwaysnavigator.schedule.condition.dynamic_delay.at_least": "oder mindestens %s", + + "createrailwaysnavigator.schedule.instruction.travel_section": "Neuer Fahrplanabschnitt", + "createrailwaysnavigator.schedule.instruction.travel_section.description": "Anfang eines neuen Fahrplanabschnitts.", + "createrailwaysnavigator.schedule.instruction.travel_section.configure": "Konfigurieren...", + "createrailwaysnavigator.schedule.instruction.travel_section.train_group": " - Setze Zugkategorie: ", + "createrailwaysnavigator.schedule.instruction.travel_section.train_line": " - Setze Zuglinie: ", + "createrailwaysnavigator.schedule.instruction.travel_section.include_previous_station": " - Start des nĂ€chsten Bereichs einbinden: ", + "createrailwaysnavigator.schedule.instruction.travel_section.usable": " - Navigierbar: ", + + "createrailwaysnavigator.schedule.instruction.reset_timings": "Zeiten zurĂŒcksetzen", + + "display.createrailwaysnavigator.train_destination.simple": "Kompakt", + "display.createrailwaysnavigator.train_destination.extended": "Erweitert", + "display.createrailwaysnavigator.train_destination.detailed": "Detailliert", + "display.createrailwaysnavigator.passenger_information.running_text": "Lauftext", + "display.createrailwaysnavigator.passenger_information.detailed_with_schedule": "Detailliert mit Fahrplan", + "display.createrailwaysnavigator.platform.running_text": "Lauftext", + "display.createrailwaysnavigator.platform.table": "Tabelle", + "display.createrailwaysnavigator.platform.focus": "Fokussiert", + + "gui.createrailwaysnavigator.saved_routes.title": "Gespeicherte Reisen", + "gui.createrailwaysnavigator.schedule_board.title": "Abfahrtenliste", + "gui.createrailwaysnavigator.station_tags.title": "Bahnhof-Tags", + "gui.createrailwaysnavigator.journey_info.title": "Reiseinformation", + "gui.createrailwaysnavigator.journey_info.date": "Tag %s", + "gui.createrailwaysnavigator.journey_info.train": "%s (%s) nach %s", + "gui.createrailwaysnavigator.color_picker.custom": "Neu...", + "gui.createrailwaysnavigator.color_picker.no_color": "Keine Farbe", + "gui.createrailwaysnavigator.schedule_board.view_details": "Details ansehen", + "gui.createrailwaysnavigator.schedule_board.train_from": "von %s", + "gui.createrailwaysnavigator.search_options.departure_in": "Abfahrt in", + "gui.createrailwaysnavigator.search_options.train_groups": "Zugkategorien", + "gui.createrailwaysnavigator.search_options.transfer_time": "Umstiegszeit", + "gui.createrailwaysnavigator.search_options.advanced_options": "Erweiterte Optionen", + "gui.createrailwaysnavigator.search_options.train_groups.all": "Alle", + "gui.createrailwaysnavigator.search_options.train_groups.excluded": "%s ausgeschlossen", + "gui.createrailwaysnavigator.search_options.saved_routes.all": "Alle", + "gui.createrailwaysnavigator.search_options.saved_routes.excluded": "%s gespeichert", + "gui.createrailwaysnavigator.empty_list": "Die Liste ist leer", + "gui.createrailwaysnavigator.new_entry.add": "Neuen Eintrag hinzufĂŒgen", + "gui.createrailwaysnavigator.new_entry.new": "Neu:", + "gui.createrailwaysnavigator.saved_routes.saved": "%s gespeichert", + "gui.createrailwaysnavigator.route_overview.notification.schedule_changed.title": "Fahrzeiten geĂ€ndert!", + "gui.createrailwaysnavigator.route_overview.notification.schedule_changed": "Die Fahrzeiten eines gespeicherten Route haben sich geĂ€ndert. Bitte informieren Sie sich im Navigator ĂŒber diese Änderungen.", + "gui.createrailwaysnavigator.route_overview.transfers": "%s Umstiege", + "gui.createrailwaysnavigator.route_overview.cancelled": "FĂ€llt aus", + "gui.createrailwaysnavigator.saved_routes.saved_route": "Gespeicherte Reise", + "block.createrailwaysnavigator.advanced_display.ber.arrival": "Ankunft", + "block.createrailwaysnavigator.advanced_display.ber.cancelled": "FĂ€llt heute aus!", + "block.createrailwaysnavigator.advanced_display.ber.delayed": "VerspĂ€tung ca. %s Minuten", + "block.createrailwaysnavigator.advanced_display.ber.information_about_cancelled": "Information zu %s: FĂ€llt heute aus!", + "block.createrailwaysnavigator.advanced_display.ber.information_about_delayed": "Information zu %s: VerspĂ€tung ca. %s Minuten", + "block.createrailwaysnavigator.advanced_display.ber.cancelled2": ", fĂ€llt heute aus. Wir bitten um Entschuldigung.", + "block.createrailwaysnavigator.advanced_display.ber.delayed2": ", heute circa %s Minuten spĂ€ter.", + "block.createrailwaysnavigator.advanced_display.ber.reason": "Grund dafĂŒr ist eine ", + "gui.createrailwaysnavigator.route_widget.show_details": "Details ansehen", + "gui.createrailwaysnavigator.route_widget.save": "Speichern", + "gui.createrailwaysnavigator.route_widget.remove": "Nicht mehr speichern", + "gui.createrailwaysnavigator.route_widget.share": "Teilen...", + "gui.createrailwaysnavigator.saved_routes.in_the_past": "In der Vergangenheit", + "gui.createrailwaysnavigator.saved_routes.today": "Heute", + "gui.createrailwaysnavigator.saved_routes.tomorrow": "Morgen", + "gui.createrailwaysnavigator.saved_routes.in_days": "In %s Tagen", + "gui.createrailwaysnavigator.saved_route_widget.show_details": "Details ansehen", + "gui.createrailwaysnavigator.saved_route_widget.share": "Teilen...", + "gui.createrailwaysnavigator.saved_route_widget.notifications": "Benachrichtigunen aktivieren", + "gui.createrailwaysnavigator.train_status.unknown_delay": "Verzögerungen im Betriebsablauf", + "gui.createrailwaysnavigator.train_status.delay_previous_journey": "VerspĂ€tung aus vorheriger Fahrt", + "gui.createrailwaysnavigator.train_status.red_signal": "Haltzeigendes Signal", + "gui.createrailwaysnavigator.train_status.priority_other_train": "Vorfahrt eines anderen Zuges", + "gui.createrailwaysnavigator.train_status.delay_other_train": "VerspĂ€tung eines vorausfahrenden Zuges", + "gui.createrailwaysnavigator.train_status.track_closed": "Streckensperrung", + "gui.createrailwaysnavigator.train_status.staff_shortage": "Personalausfall", + "gui.createrailwaysnavigator.train_status.operational_disruption": "Betriebsstörung", + "gui.createrailwaysnavigator.train_status.special_trip": "Sonderfahrt", + "gui.createrailwaysnavigator.route_details.save_route.tooltip": "Route speichern", + "gui.createrailwaysnavigator.route_details.remove_route.tooltip": "Route nicht mehr speichern", + "gui.createrailwaysnavigator.route_details.show_popup.tooltip": "Popup-Fenster anzeigen", + "gui.createrailwaysnavigator.navigator.my_profile": "Mein Profil", + "gui.createrailwaysnavigator.navigator.train_initialization_warning": "Manche Routen können unvollstĂ€ndig sein oder ĂŒberhaupt nicht vorgeschalgen werden, da Create Railways Navigator noch nicht alle ZĂŒge initialisiert hat.", + "gui.createrailwaysnavigator.global_settings.train_line.title": "Zuglinien", + "gui.createrailwaysnavigator.global_settings.train_line.color": "Farbe auswĂ€hlen", + "gui.createrailwaysnavigator.global_settings.train_line.description": "Erstelle Linien, um mehrere ZĂŒge zu LinienzĂŒgen zusammenzufassen, deren Anzeigename zu ĂŒberschreiben oder um weitere Einstellungen anzupassen. (Kann nur im Fahrplan zugewiesen werden)", "createrailwaysnavigator.moin": "moin" } diff --git a/common/src/main/resources/assets/createrailwaysnavigator/lang/en_us.json b/common/src/main/resources/assets/createrailwaysnavigator/lang/en_us.json index 76210995..c12e9f8c 100644 --- a/common/src/main/resources/assets/createrailwaysnavigator/lang/en_us.json +++ b/common/src/main/resources/assets/createrailwaysnavigator/lang/en_us.json @@ -29,14 +29,17 @@ "block.createrailwaysnavigator.advanced_display_half_panel": "Half Advanced Display Panel", "block.createrailwaysnavigator.advanced_display_half_panel.tooltip.summary": "Use it on _trains_, as a _train destination display_ or _passenger information display_, or at _train stations_ as improved _platform displays_ which also shows more information than regular display boards.", "block.createrailwaysnavigator.advanced_display_half_panel.tooltip.condition1": "When R-Clicked using a Wrench", - "block.createrailwaysnavigator.advanced_display_half_panel.tooltip.behaviour1": "Open a menu to _configure_ the _display_.", + "block.createrailwaysnavigator.advanced_display_half_panel.tooltip.behaviour1": "Open a menu to _configure_ the _display_.", "block.createrailwaysnavigator.advanced_display_sloped": "Sloped Advanced Display", "block.createrailwaysnavigator.advanced_display_sloped.tooltip.summary": "Use it on _trains_, as a _train destination display_ or _passenger information display_, or at _train stations_ as improved _platform displays_ which also shows more information than regular display boards.", "block.createrailwaysnavigator.advanced_display_sloped.tooltip.condition1": "When R-Clicked using a Wrench", "block.createrailwaysnavigator.advanced_display_sloped.tooltip.behaviour1": "Open a menu to _configure_ the _display_.", + "block.createrailwaysnavigator.advanced_display_slab": "Advanced Display Slab", + "block.createrailwaysnavigator.advanced_display_slab.tooltip.summary": "Use it on _trains_, as a _train destination display_ or _passenger information display_, or at _train stations_ as improved _platform displays_ which also shows more information than regular display boards.", + "block.createrailwaysnavigator.advanced_display_slab.tooltip.condition1": "When R-Clicked using a Wrench", + "block.createrailwaysnavigator.advanced_display_slab.tooltip.behaviour1": "Open a menu to _configure_ the _display_.", "block.createrailwaysnavigator.advanced_display.ber.not_in_service": "Out of service!", - "block.createrailwaysnavigator.advanced_display.ber.shunting_trip": "non-passenger ride", "category.createrailwaysnavigator.crn": "Create Railways Navigator", "key.createrailwaysnavigator.route_overlay_options": "Show Route Overlay Options", @@ -73,6 +76,9 @@ "gui.createrailwaysnavigator.common.search": "Search", "gui.createrailwaysnavigator.common.auto": "Auto", "gui.createrailwaysnavigator.common.server_error": "Server error while executing task. Look console for details.", + "gui.createrailwaysnavigator.common.delete": "Delete", + "gui.createrailwaysnavigator.common.add": "Add", + "gui.createrailwaysnavigator.common.help": "Get Help", "gui.createrailwaysnavigator.navigator.title": "Create Railways Navigator", "gui.createrailwaysnavigator.navigator.no_connections": "No connections found.", @@ -88,15 +94,15 @@ "gui.createrailwaysnavigator.navigator.search_settings.tooltip": "Search Settings", "gui.createrailwaysnavigator.navigator.search.tooltip": "Search", "gui.createrailwaysnavigator.navigator.location.tooltip": "Nearest station from current position", + "gui.createrailwaysnavigator.navigator.refresh.tooltip": "Refresh", "gui.createrailwaysnavigator.navigator.switch.tooltip": "Switch fields", "gui.createrailwaysnavigator.route_details.title": "Route Details", "gui.createrailwaysnavigator.route_details.departure": "Departure in", "gui.createrailwaysnavigator.route_details.next_transfer_time": "Transfer in", "gui.createrailwaysnavigator.route_details.transfer": "Transfer", - "gui.createrailwaysnavigator.route_details.save_route": "Save Connection", - "gui.createrailwaysnavigator.route_overview.title": "Route Details", + "gui.createrailwaysnavigator.route_overview.title": "Travel Companion", "gui.createrailwaysnavigator.route_overview.journey_begins": "Your journey begins! %s to %s, departure %s", "gui.createrailwaysnavigator.route_overview.journey_begins_with_platform": "Your journey begins! %s to %s, departure %s on platform %s", "gui.createrailwaysnavigator.route_overview.train_details": "%s to %s", @@ -107,16 +113,16 @@ "gui.createrailwaysnavigator.route_overview.after_journey": "You have reached %s. Thank you for traveling and have a nice day.", "gui.createrailwaysnavigator.route_overview.next_connections": "Next connections", "gui.createrailwaysnavigator.route_overview.schedule_transfer": "Transfer", - "gui.createrailwaysnavigator.route_overview.train_canceled": "Train Canceled", + "gui.createrailwaysnavigator.route_overview.train_cancelled": "Train Cancelled", "gui.createrailwaysnavigator.route_overview.train_cancellation_info": "Information about %s", - "gui.createrailwaysnavigator.route_overview.stop_canceled": "❌ Canceled", + "gui.createrailwaysnavigator.route_overview.stop_cancelled": "❌ Cancelled", "gui.createrailwaysnavigator.route_overview.connection_endangered": "Connection endangered", "gui.createrailwaysnavigator.route_overview.connection_missed": "Connection missed", - "gui.createrailwaysnavigator.route_overview.connection_canceled": "Train canceled", + "gui.createrailwaysnavigator.route_overview.connection_cancelled": "Train cancelled", "gui.createrailwaysnavigator.route_overview.journey_interrupted": "Your journey to %s cannot be continued.", "gui.createrailwaysnavigator.route_overview.connection_missed_info": "Due to a train delay you missed your connecting train. Search for an alternative in the navigator. We apologize for the inconvenience.", - "gui.createrailwaysnavigator.route_overview.train_canceled_info": "Information about %s: This train is canceled today! We apologize for the inconvenience. Search for an alternative in the navigator.", - "gui.createrailwaysnavigator.route_overview.train_canceled_title": "Train has been canceled", + "gui.createrailwaysnavigator.route_overview.train_cancelled_info": "Information about %s: This train is cancelled today! We apologize for the inconvenience. Search for an alternative in the navigator.", + "gui.createrailwaysnavigator.route_overview.train_cancelled_title": "Train has been cancelled", "gui.createrailwaysnavigator.route_overview.journey_interrupted_info": "Your journey to %s cannot be continued. Search for an alternative in the navigator.", "gui.createrailwaysnavigator.route_overview.options": "Press %s for options.", "gui.createrailwaysnavigator.route_overview.notification.journey_begins.title": "Your journey to %s begins!", @@ -126,10 +132,10 @@ "gui.createrailwaysnavigator.route_overview.notification.platform_changed": "Your train in %s leaves from platform %s today.", "gui.createrailwaysnavigator.route_overview.notification.train_delayed.title": "%s: Arrival %s delayed.", "gui.createrailwaysnavigator.route_overview.notification.train_delayed": "%s instead of %s in %s", - "gui.createrailwaysnavigator.route_overview.notification.train_canceled.title": "%s: Train canceled", - "gui.createrailwaysnavigator.route_overview.notification.train_canceled": "%s to %s is canceled today.", + "gui.createrailwaysnavigator.route_overview.notification.train_cancelled.title": "%s: Train cancelled", + "gui.createrailwaysnavigator.route_overview.notification.train_cancelled": "%s to %s is cancelled today.", "gui.createrailwaysnavigator.route_overview.notification.transfer.title": "Transfer is coming", - "gui.createrailwaysnavigator.route_overview.notification.transfer": "Change to %s → %s on platform %s", + "gui.createrailwaysnavigator.route_overview.notification.transfer": "Change to %s → %s", "gui.createrailwaysnavigator.route_overview.notification.transfer_with_platform": "Change to %s → %s on platform %s", "gui.createrailwaysnavigator.route_overview.notification.connection_endangered.title": "Your connection is endangered!", "gui.createrailwaysnavigator.route_overview.notification.connection_endangered": "You probably won't be able to reach %s to %s.", @@ -142,24 +148,24 @@ "gui.createrailwaysnavigator.global_settings.title": "Global Settings", "gui.createrailwaysnavigator.global_settings.option.tooltip": "Click to edit", "gui.createrailwaysnavigator.global_settings.option_alias.title": "Train Station Tags", - "gui.createrailwaysnavigator.global_settings.option_alias.description": "Define station tags to threaten multi-platform stations (e.g. MyStation 1, MyStation 2, ...) as a single station (e.g. MyStation) with custom names.", + "gui.createrailwaysnavigator.global_settings.option_alias.description": "Combine individual train stations into a single multi-platform station with its own name so that the navigator can suggest transfers and more.", "gui.createrailwaysnavigator.global_settings.option_blacklist.title": "Train Station Blacklist", - "gui.createrailwaysnavigator.global_settings.option_blacklist.description": "Exclude Track Stations which should not appear in the navigation results. This stations will be ignored when generating routes.", + "gui.createrailwaysnavigator.global_settings.option_blacklist.description": "Exclude Train Stations which should not appear in the navigation results. These stations will be ignored when generating routes.", "gui.createrailwaysnavigator.global_settings.train_group.title": "Train Groups", - "gui.createrailwaysnavigator.global_settings.train_group.description": "Create train groups to organize all trains (e.g. regional services, long-distance services, ...). Users can decide which groups they want to use.", + "gui.createrailwaysnavigator.global_settings.train_group.description": "Create train groups to organize all trains (e.g. regional services, long-distance services, ...). Users can decide which train groups they want to use.", "gui.createrailwaysnavigator.global_settings.train_blacklist.title": "Train Blacklist", "gui.createrailwaysnavigator.global_settings.train_blacklist.description": "Exclude trains, e.g. freight trains, special trains, etc., so that they are not used in the route suggestions.", - "gui.createrailwaysnavigator.alias_settings.title": "Train Station Tag Settings", - "gui.createrailwaysnavigator.alias_settings.summary": "Contains %s Track Stations", - "gui.createrailwaysnavigator.alias_settings.editor": "Last edited by %s on %s", - "gui.createrailwaysnavigator.alias_settings.add.tooltip": "Create new Entry", - "gui.createrailwaysnavigator.alias_settings.delete_alias.tooltip": "Delete Tag", - "gui.createrailwaysnavigator.alias_settings.delete_station.tooltip": "Remove Station", - "gui.createrailwaysnavigator.alias_settings.add_station.tooltip": "Add Station", - "gui.createrailwaysnavigator.alias_settings.hint.station_name": "Track Station Name", - "gui.createrailwaysnavigator.alias_settings.hint.platform": "Platform", - "gui.createrailwaysnavigator.alias_settings.enter_name": "Enter name here", + "gui.createrailwaysnavigator.station_tags.summary": "Contains %s Train Stations", + "gui.createrailwaysnavigator.station_tags.editor": "Last edited by %s on %s", + "gui.createrailwaysnavigator.station_tags.add.tooltip": "Create new Entry", + "gui.createrailwaysnavigator.station_tags.delete_alias.tooltip": "Delete Tag", + "gui.createrailwaysnavigator.station_tags.delete_station.tooltip": "Remove Station", + "gui.createrailwaysnavigator.station_tags.modify_platform.tooltip": "Click to change", + "gui.createrailwaysnavigator.station_tags.add_station.tooltip": "Add Station", + "gui.createrailwaysnavigator.station_tags.hint.station_name": "Train Station Name", + "gui.createrailwaysnavigator.station_tags.hint.platform": "Platform Label", + "gui.createrailwaysnavigator.station_tags.enter_name": "Enter name here", "gui.createrailwaysnavigator.train_group_settings.title": "Train Group Settings", "gui.createrailwaysnavigator.train_group_settings.summary": "Contains %s Trains", @@ -246,5 +252,100 @@ "gui.createrailwaysnavigator.display_source.advanced_display.platform_width": "Platform Column Width", "gui.createrailwaysnavigator.display_source.advanced_display.platform_width.description": "in block pixels. (Default: Auto)", + "gui.createrailwaysnavigator.section_settings.title": "Section Settings", + "gui.createrailwaysnavigator.section_settings.train_groups": "Assign Train Group", + "gui.createrailwaysnavigator.section_settings.train_lines": "Assign Train Line", + "gui.createrailwaysnavigator.section_settings.include_previous_station": "Include start of next section", + "gui.createrailwaysnavigator.section_settings.usable": "Navigable", + "gui.createrailwaysnavigator.section_settings.none": "(None)", + + "createrailwaysnavigator.schedule.condition.dynamic_delay": "Dynamic Delay", + "createrailwaysnavigator.schedule.condition.dynamic_delay.min_duration": "Minimum Duration", + "createrailwaysnavigator.schedule.condition.dynamic_delay.title": "Wait: %s..%s", + "createrailwaysnavigator.schedule.condition.dynamic_delay.at_least": "or at least %s", + + "createrailwaysnavigator.schedule.instruction.travel_section": "New Schedule Section", + "createrailwaysnavigator.schedule.instruction.travel_section.description": "Beginning of a new schedule section.", + "createrailwaysnavigator.schedule.instruction.travel_section.configure": "Configure...", + "createrailwaysnavigator.schedule.instruction.travel_section.train_group": " - Set Train Group: ", + "createrailwaysnavigator.schedule.instruction.travel_section.train_line": " - Set Train Line: ", + "createrailwaysnavigator.schedule.instruction.travel_section.include_previous_station": " - Include start of next section: ", + "createrailwaysnavigator.schedule.instruction.travel_section.usable": " - Navigable: ", + + "createrailwaysnavigator.schedule.instruction.reset_timings": "Reset Timings", + + "display.createrailwaysnavigator.train_destination.simple": "Compact", + "display.createrailwaysnavigator.train_destination.extended": "Extended", + "display.createrailwaysnavigator.train_destination.detailed": "Detailed", + "display.createrailwaysnavigator.passenger_information.running_text": "Scrolling Text", + "display.createrailwaysnavigator.passenger_information.detailed_with_schedule": "Detailed with Schedule", + "display.createrailwaysnavigator.platform.running_text": "Scrolling Text", + "display.createrailwaysnavigator.platform.table": "Table", + "display.createrailwaysnavigator.platform.focus": "Focus", + + "gui.createrailwaysnavigator.saved_routes.title": "Saved Routes", + "gui.createrailwaysnavigator.schedule_board.title": "Schedule Board", + "gui.createrailwaysnavigator.station_tags.title": "Station Tags", + "gui.createrailwaysnavigator.journey_info.title": "Journey information", + "gui.createrailwaysnavigator.journey_info.date": "Day %s", + "gui.createrailwaysnavigator.journey_info.train": "%s (%s) to %s", + "gui.createrailwaysnavigator.color_picker.custom": "Custom...", + "gui.createrailwaysnavigator.color_picker.no_color": "No Color", + "gui.createrailwaysnavigator.schedule_board.view_details": "View Details", + "gui.createrailwaysnavigator.schedule_board.train_from": "from %s", + "gui.createrailwaysnavigator.search_options.departure_in": "Departure in", + "gui.createrailwaysnavigator.search_options.train_groups": "Train Groups", + "gui.createrailwaysnavigator.search_options.transfer_time": "Transfer Time", + "gui.createrailwaysnavigator.search_options.advanced_options": "Advanced Options", + "gui.createrailwaysnavigator.search_options.train_groups.all": "All", + "gui.createrailwaysnavigator.search_options.train_groups.excluded": "%s excluded", + "gui.createrailwaysnavigator.search_options.saved_routes.all": "All", + "gui.createrailwaysnavigator.search_options.saved_routes.excluded": "%s excluded", + "gui.createrailwaysnavigator.empty_list": "The list is empty", + "gui.createrailwaysnavigator.new_entry.add": "Add new entry", + "gui.createrailwaysnavigator.new_entry.new": "New:", + "gui.createrailwaysnavigator.saved_routes.saved": "%s saved", + "gui.createrailwaysnavigator.route_overview.notification.schedule_changed.title": "Schedule has changed!", + "gui.createrailwaysnavigator.route_overview.notification.schedule_changed": "The schedule of your saved route has changed. Please check the navigator for these changes.", + "gui.createrailwaysnavigator.route_overview.transfers": "%s Transfers", + "gui.createrailwaysnavigator.route_overview.cancelled": "Cancelled", + "gui.createrailwaysnavigator.saved_routes.saved_route": "Saved Route", + "block.createrailwaysnavigator.advanced_display.ber.arrival": "Arrival", + "block.createrailwaysnavigator.advanced_display.ber.cancelled": "Cancelled", + "block.createrailwaysnavigator.advanced_display.ber.delayed": "Delay approx. %s minutes", + "block.createrailwaysnavigator.advanced_display.ber.information_about_cancelled": "Information about %s: This train got cancelled", + "block.createrailwaysnavigator.advanced_display.ber.information_about_delayed": "Information about %s: Delay approx. %s minutes", + "block.createrailwaysnavigator.advanced_display.ber.cancelled2": ", is cancelled today. We apologize for the inconvenience.", + "block.createrailwaysnavigator.advanced_display.ber.delayed2": ", today approx. %s minutes delayed.", + "block.createrailwaysnavigator.advanced_display.ber.reason": "Reason: ", + "gui.createrailwaysnavigator.route_widget.show_details": "Show details", + "gui.createrailwaysnavigator.route_widget.save": "Save", + "gui.createrailwaysnavigator.route_widget.remove": "Remove", + "gui.createrailwaysnavigator.route_widget.share": "Share...", + "gui.createrailwaysnavigator.saved_routes.in_the_past": "In the past", + "gui.createrailwaysnavigator.saved_routes.today": "Today", + "gui.createrailwaysnavigator.saved_routes.tomorrow": "Tomorrow", + "gui.createrailwaysnavigator.saved_routes.in_days": "In %s days", + "gui.createrailwaysnavigator.saved_route_widget.show_details": "Show details", + "gui.createrailwaysnavigator.saved_route_widget.share": "Share...", + "gui.createrailwaysnavigator.saved_route_widget.notifications": "Show Notifications", + "gui.createrailwaysnavigator.train_status.unknown_delay": "Delay in operations", + "gui.createrailwaysnavigator.train_status.delay_previous_journey": "Delay in previous journey", + "gui.createrailwaysnavigator.train_status.red_signal": "Red signal", + "gui.createrailwaysnavigator.train_status.priority_other_train": "Priority of another train", + "gui.createrailwaysnavigator.train_status.delay_other_train": "Delay of another train", + "gui.createrailwaysnavigator.train_status.track_closed": "Track closed", + "gui.createrailwaysnavigator.train_status.staff_shortage": "Staff shortage", + "gui.createrailwaysnavigator.train_status.operational_disruption": "Operational disruption", + "gui.createrailwaysnavigator.train_status.special_trip": "Special trip", + "gui.createrailwaysnavigator.route_details.save_route.tooltip": "Save route", + "gui.createrailwaysnavigator.route_details.remove_route.tooltip": "Remove route", + "gui.createrailwaysnavigator.route_details.show_popup.tooltip": "Show Popup", + "gui.createrailwaysnavigator.navigator.my_profile": "My Profile", + "gui.createrailwaysnavigator.navigator.train_initialization_warning": "Some routes may be incomplete or not suggested at all because Create Railways Navigator has not initialized all trains yet.", + "gui.createrailwaysnavigator.global_settings.train_line.title": "Train Lines", + "gui.createrailwaysnavigator.global_settings.train_line.color": "Select Color", + "gui.createrailwaysnavigator.global_settings.train_line.description": "Create train lines to group different trains together, overwrite their display name and adjust other settings for each line. (Can only be assigned to the trains in the schedule)", + "createrailwaysnavigator.moin": "moin" } diff --git a/common/src/main/resources/assets/createrailwaysnavigator/lang/es_es.json b/common/src/main/resources/assets/createrailwaysnavigator/lang/es_es.json index bbd4eacf..be3d0836 100644 --- a/common/src/main/resources/assets/createrailwaysnavigator/lang/es_es.json +++ b/common/src/main/resources/assets/createrailwaysnavigator/lang/es_es.json @@ -36,7 +36,6 @@ "block.createrailwaysnavigator.advanced_display_sloped.tooltip.behaviour1": "Abre un menĂș para _configurar_ la _pantalla_.", "block.createrailwaysnavigator.advanced_display.ber.not_in_service": "ÂĄFuera de servicio!", - "block.createrailwaysnavigator.advanced_display.ber.shunting_trip": "viaje sin pasajeros", "category.createrailwaysnavigator.crn": "Navegador de ferrocarriles", "key.createrailwaysnavigator.route_overlay_options": "Mostrar opciones de superposiciĂłn de ruta", @@ -107,16 +106,16 @@ "gui.createrailwaysnavigator.route_overview.after_journey": "Has llegado a %s. Gracias por viajar y que tengas un buen dĂ­a.", "gui.createrailwaysnavigator.route_overview.next_connections": "PrĂłximas conexiones", "gui.createrailwaysnavigator.route_overview.schedule_transfer": "Transbordo", - "gui.createrailwaysnavigator.route_overview.train_canceled": "Tren cancelado", + "gui.createrailwaysnavigator.route_overview.train_cancelled": "Tren cancelado", "gui.createrailwaysnavigator.route_overview.train_cancellation_info": "InformaciĂłn sobre %s", - "gui.createrailwaysnavigator.route_overview.stop_canceled": "❌ Cancelado", + "gui.createrailwaysnavigator.route_overview.stop_cancelled": "❌ Cancelado", "gui.createrailwaysnavigator.route_overview.connection_endangered": "ConexiĂłn en peligro", "gui.createrailwaysnavigator.route_overview.connection_missed": "ConexiĂłn perdida", - "gui.createrailwaysnavigator.route_overview.connection_canceled": "Tren cancelado", + "gui.createrailwaysnavigator.route_overview.connection_cancelled": "Tren cancelado", "gui.createrailwaysnavigator.route_overview.journey_interrupted": "Tu viaje a %s no puede continuar.", "gui.createrailwaysnavigator.route_overview.connection_missed_info": "Debido a un retraso del tren, has perdido la conexiĂłn del tren. Busca una alternativa en el navegador. Pedimos disculpas por las molestias.", - "gui.createrailwaysnavigator.route_overview.train_canceled_info": "InformaciĂłn sobre %s: ÂĄEste tren estĂĄ cancelado hoy! Pedimos disculpas por las molestias. Busca una alternativa en el navegador.", - "gui.createrailwaysnavigator.route_overview.train_canceled_title": "El tren ha sido cancelado", + "gui.createrailwaysnavigator.route_overview.train_cancelled_info": "InformaciĂłn sobre %s: ÂĄEste tren estĂĄ cancelado hoy! Pedimos disculpas por las molestias. Busca una alternativa en el navegador.", + "gui.createrailwaysnavigator.route_overview.train_cancelled_title": "El tren ha sido cancelado", "gui.createrailwaysnavigator.route_overview.journey_interrupted_info": "Tu viaje a %s no puede continuar. Busca una alternativa en el navegador.", "gui.createrailwaysnavigator.route_overview.options": "Pulsa %s para opciones.", "gui.createrailwaysnavigator.route_overview.notification.journey_begins.title": "ÂĄTu viaje a %s comienza!", @@ -126,8 +125,8 @@ "gui.createrailwaysnavigator.route_overview.notification.platform_changed": "Tu tren en %s sale hoy desde el andĂ©n %s.", "gui.createrailwaysnavigator.route_overview.notification.train_delayed.title": "%s: Llegada %s retrasada.", "gui.createrailwaysnavigator.route_overview.notification.train_delayed": "%s en lugar de %s en %s", - "gui.createrailwaysnavigator.route_overview.notification.train_canceled.title": "%s: Tren cancelado", - "gui.createrailwaysnavigator.route_overview.notification.train_canceled": "%s a %s estĂĄ cancelado hoy.", + "gui.createrailwaysnavigator.route_overview.notification.train_cancelled.title": "%s: Tren cancelado", + "gui.createrailwaysnavigator.route_overview.notification.train_cancelled": "%s a %s estĂĄ cancelado hoy.", "gui.createrailwaysnavigator.route_overview.notification.transfer.title": "Transbordo prĂłximo", "gui.createrailwaysnavigator.route_overview.notification.transfer": "Cambio a %s → %s en el andĂ©n %s", "gui.createrailwaysnavigator.route_overview.notification.transfer_with_platform": "Cambio a %s → %s en el andĂ©n %s", @@ -150,16 +149,16 @@ "gui.createrailwaysnavigator.global_settings.train_blacklist.title": "Lista negra de trenes", "gui.createrailwaysnavigator.global_settings.train_blacklist.description": "Excluir trenes, por ejemplo, trenes de carga, trenes especiales, etc., para que no se utilicen en las sugerencias de rutas.", - "gui.createrailwaysnavigator.alias_settings.title": "ConfiguraciĂłn de etiquetas de estaciones de tren", - "gui.createrailwaysnavigator.alias_settings.summary": "Contiene %s estaciones de tren", - "gui.createrailwaysnavigator.alias_settings.editor": "Última ediciĂłn por %s el %s", - "gui.createrailwaysnavigator.alias_settings.add.tooltip": "Crear nueva entrada", - "gui.createrailwaysnavigator.alias_settings.delete_alias.tooltip": "Eliminar etiqueta", - "gui.createrailwaysnavigator.alias_settings.delete_station.tooltip": "Eliminar estaciĂłn", - "gui.createrailwaysnavigator.alias_settings.add_station.tooltip": "Agregar estaciĂłn", - "gui.createrailwaysnavigator.alias_settings.hint.station_name": "Nombre de la estaciĂłn de tren", - "gui.createrailwaysnavigator.alias_settings.hint.platform": "AndĂ©n", - "gui.createrailwaysnavigator.alias_settings.enter_name": "Introduce el nombre aquĂ­", + "gui.createrailwaysnavigator.station_tags.title": "ConfiguraciĂłn de etiquetas de estaciones de tren", + "gui.createrailwaysnavigator.station_tags.summary": "Contiene %s estaciones de tren", + "gui.createrailwaysnavigator.station_tags.editor": "Última ediciĂłn por %s el %s", + "gui.createrailwaysnavigator.station_tags.add.tooltip": "Crear nueva entrada", + "gui.createrailwaysnavigator.station_tags.delete_alias.tooltip": "Eliminar etiqueta", + "gui.createrailwaysnavigator.station_tags.delete_station.tooltip": "Eliminar estaciĂłn", + "gui.createrailwaysnavigator.station_tags.add_station.tooltip": "Agregar estaciĂłn", + "gui.createrailwaysnavigator.station_tags.hint.station_name": "Nombre de la estaciĂłn de tren", + "gui.createrailwaysnavigator.station_tags.hint.platform": "AndĂ©n", + "gui.createrailwaysnavigator.station_tags.enter_name": "Introduce el nombre aquĂ­", "gui.createrailwaysnavigator.train_group_settings.title": "ConfiguraciĂłn de grupos de trenes", "gui.createrailwaysnavigator.train_group_settings.summary": "Contiene %s trenes", diff --git a/common/src/main/resources/assets/createrailwaysnavigator/lang/fr_fr.json b/common/src/main/resources/assets/createrailwaysnavigator/lang/fr_fr.json new file mode 100644 index 00000000..33f6046e --- /dev/null +++ b/common/src/main/resources/assets/createrailwaysnavigator/lang/fr_fr.json @@ -0,0 +1,250 @@ +{ + "advancement.createrailwaysnavigator.navigator": "Merci d'avoir voyagĂ©", + "advancement.createrailwaysnavigator.navigator.description": "Crafter un Navigateur pour trouver une connection entre une station et une autre.", + "advancement.createrailwaysnavigator.advanced_display": "Pas vraiment 4k", + "advancement.createrailwaysnavigator.advanced_display.description": "AmĂ©liorer vos tableaux d'affichage pour afficher plus d'information et mĂȘme les placer dans vos trains.", + + "itemGroup.createrailwaysnavigator.tab": "Create Railways Navigator", + + "item.createrailwaysnavigator.navigator": "Create Navigateur Ferroviaire", + "item.createrailwaysnavigator.navigator.tooltip.summary": "Le _navigateur_ montre les _correspondances_ possibles avec des informations supplĂ©mentaires comme les _escales_, des _informations en temps rĂ©el_ et plus encore.", + + "block.createrailwaysnavigator.train_station_clock": "Horloge de Gare", + "block.createrailwaysnavigator.advanced_display_block": "Bloc d'Affichage AvancĂ©", + "block.createrailwaysnavigator.advanced_display_block.tooltip.summary": "Utilisez-le sur des _trains_, comme _affichage de la destination_ ou _affichage pour les passagers_, ou dans des _stations de trains_ comme des _affichage de quai_ amĂ©liorĂ© qui montre plus d'informations que des tableaux d'affichage classique.", + "block.createrailwaysnavigator.advanced_display_block.tooltip.condition1": "Quand click-droit en utilisant une clĂ©", + "block.createrailwaysnavigator.advanced_display_block.tooltip.behaviour1": "Ouvre un menu pour _configurer_ l'_affichage_.", + "block.createrailwaysnavigator.advanced_display": "Tableau d'Affichage AvancĂ©", + "block.createrailwaysnavigator.advanced_display.tooltip.summary": "Utilisez-le sur des _trains_, comme _affichage de la destination_ ou _affichage pour les passagers_, ou dans des _stations de trains_ comme des _affichage de quai_ amĂ©liorĂ© qui montre plus d'informations que des tableaux d'affichage classique.", + "block.createrailwaysnavigator.advanced_display.tooltip.condition1": "Quand click-droit en utilisant une clĂ©", + "block.createrailwaysnavigator.advanced_display.tooltip.behaviour1": "Ouvre un menu pour _configurer_ l'_affichage_.", + "block.createrailwaysnavigator.advanced_display_small": "Petit Tableau d'Affichage AvancĂ©", + "block.createrailwaysnavigator.advanced_display_small.tooltip.summary": "Utilisez-le sur des _trains_, comme _affichage de la destination_ ou _affichage pour les passagers_, ou dans des _stations de trains_ comme des _affichage de quai_ amĂ©liorĂ© qui montre plus d'informations que des tableaux d'affichage classique.", + "block.createrailwaysnavigator.advanced_display_small.tooltip.condition1": "Quand click-droit en utilisant une clĂ©", + "block.createrailwaysnavigator.advanced_display_small.tooltip.behaviour1": "Ouvre un menu pour _configurer_ l'_affichage_.", + "block.createrailwaysnavigator.advanced_display_panel": "Panneau d'Affichage AvancĂ©", + "block.createrailwaysnavigator.advanced_display_panel.tooltip.summary": "Utilisez-le sur des _trains_, comme _affichage de la destination_ ou _affichage pour les passagers_, ou dans des _stations de trains_ comme des _affichage de quai_ amĂ©liorĂ© qui montre plus d'informations que des tableaux d'affichage classique.", + "block.createrailwaysnavigator.advanced_display_panel.tooltip.condition1": "Quand click-droit en utilisant une clĂ©", + "block.createrailwaysnavigator.advanced_display_panel.tooltip.behaviour1": "Ouvre un menu pour _configurer_ l'_affichage_.", + "block.createrailwaysnavigator.advanced_display_half_panel": "Demi-Panneau d'Affichage AvancĂ©", + "block.createrailwaysnavigator.advanced_display_half_panel.tooltip.summary": "Utilisez-le sur des _trains_, comme _affichage de la destination_ ou _affichage pour les passagers_, ou dans des _stations de trains_ comme des _affichage de quai_ amĂ©liorĂ© qui montre plus d'informations que des tableaux d'affichage classique.", + "block.createrailwaysnavigator.advanced_display_half_panel.tooltip.condition1": "Quand click-droit en utilisant une clĂ©", + "block.createrailwaysnavigator.advanced_display_half_panel.tooltip.behaviour1": "Ouvre un menu pour _configurer_ l'_affichage_.", + "block.createrailwaysnavigator.advanced_display_sloped": "Affichage InclinĂ© AvancĂ©", + "block.createrailwaysnavigator.advanced_display_sloped.tooltip.summary": "Utilisez-le sur des _trains_, comme _affichage de la destination_ ou _affichage pour les passagers_, ou dans des _stations de trains_ comme des _affichage de quai_ amĂ©liorĂ© qui montre plus d'informations que des tableaux d'affichage classique.", + "block.createrailwaysnavigator.advanced_display_sloped.tooltip.condition1": "Quand click-droit en utilisant une clĂ©", + "block.createrailwaysnavigator.advanced_display_sloped.tooltip.behaviour1": "Ouvre un menu pour _configurer_ l'_affichage_.", + + "block.createrailwaysnavigator.advanced_display.ber.not_in_service": "Hors service!", + "block.createrailwaysnavigator.advanced_display.ber.shunting_trip": "train sans passagers", + + "category.createrailwaysnavigator.crn": "Create Railways Navigator", + "key.createrailwaysnavigator.route_overlay_options": "Afficher les Options de l'Overlay d'ItinĂ©raire", + + "enum.createrailwaysnavigator.overlay_position": "Position de l'Affichage", + "enum.createrailwaysnavigator.overlay_position.info.top_left": "Coin SupĂ©rieur Gauche", + "enum.createrailwaysnavigator.overlay_position.info.top_right": "Coin SupĂ©rieur Droit", + "enum.createrailwaysnavigator.overlay_position.info.bottom_left": "Coin InfĂ©rieur Gauche", + "enum.createrailwaysnavigator.overlay_position.info.bottom_right": "Coin InfĂ©rieur Droit", + + "gui.createrailwaysnavigator.loading.title": "TĂ©lĂ©chargement des donnĂ©es du serveur...", + + "gui.createrailwaysnavigator.overlay_settings.title": "ParamĂštres de l'Overlay d'ItinĂ©raire", + "gui.createrailwaysnavigator.route_overlay_settings.show_details": "Afficher les dĂ©tails", + "gui.createrailwaysnavigator.route_overlay_settings.unpin": "Enlever l'overlay d'itinĂ©raire", + "gui.createrailwaysnavigator.route_overlay_settings.narrator.on": "Annonces du narrateur activĂ©es.", + "gui.createrailwaysnavigator.route_overlay_settings.narrator.off": "Annonces du narrateur dĂ©sactivĂ©es.", + "gui.createrailwaysnavigator.route_overlay_settings.notifications.on": "Notifications activĂ©es", + "gui.createrailwaysnavigator.route_overlay_settings.notifications.off": "Notifications dĂ©sactivĂ©es", + "gui.createrailwaysnavigator.route_overlay_settings.scale": "Taille de l'interface", + "gui.createrailwaysnavigator.route_overlay_settings.narrator": "Annonces du narrateur", + "gui.createrailwaysnavigator.route_overlay_settings.narrator.description": "Le Narrateur annonce des Ă©vĂšnements importants pendant votre voyage, ex. le prochain arrĂȘt, des changements, etc.", + "gui.createrailwaysnavigator.route_overlay_settings.notifications": "Notifications", + "gui.createrailwaysnavigator.route_overlay_settings.notifications.description": "Recevez des notifications pop-up sur les Ă©vĂšnements importants de votre voyage, ex. le prochain arrĂȘt, des changements, etc.", + + "gui.createrailwaysnavigator.common.expand": "Afficher les dĂ©tails", + "gui.createrailwaysnavigator.common.collapse": "Cacher les dĂ©tails", + "gui.createrailwaysnavigator.common.go_back": "Retour", + "gui.createrailwaysnavigator.common.go_to_top": "Aller en haut", + "gui.createrailwaysnavigator.common.reset_defaults": "RĂ©initialiser aux valeurs par dĂ©faut", + "gui.createrailwaysnavigator.common.count": "Compte", + "gui.createrailwaysnavigator.common.true": "Oui", + "gui.createrailwaysnavigator.common.false": "Non", + "gui.createrailwaysnavigator.common.search": "Rechercher", + "gui.createrailwaysnavigator.common.auto": "Auto", + "gui.createrailwaysnavigator.common.server_error": "Erreur serveur lors de l'Ă©xecution de la tĂąche. Regarder la console pour en savoir plus.", + + "gui.createrailwaysnavigator.navigator.title": "Create Railways Navigator", + "gui.createrailwaysnavigator.navigator.no_connections": "Aucun itinĂ©raire trouvĂ©.", + "gui.createrailwaysnavigator.navigator.not_searched": "Rien n'a encore Ă©tĂ© recherchĂ©.", + "gui.createrailwaysnavigator.navigator.searching": "Recherche d'un itinĂ©raire...", + "gui.createrailwaysnavigator.navigator.error_title": "Impossible de naviguer!", + "gui.createrailwaysnavigator.navigator.start_end_null": "Le dĂ©part ou la destination est vide.", + "gui.createrailwaysnavigator.navigator.start_end_equal": "Le dĂ©part et la destination sont Ă©gaux.", + "gui.createrailwaysnavigator.navigator.route_entry.connection_in_past": "❌ ItinĂ©raire dans le passĂ©", + "gui.createrailwaysnavigator.navigator.route_entry.transfer": "Corresp.", + "gui.createrailwaysnavigator.navigator.route_entry.station_start": "Ă  partir de %s", + "gui.createrailwaysnavigator.navigator.global_settings.tooltip": "ParamĂštres globaux", + "gui.createrailwaysnavigator.navigator.search_settings.tooltip": "ParamĂštres de recherche", + "gui.createrailwaysnavigator.navigator.search.tooltip": "Recherche", + "gui.createrailwaysnavigator.navigator.location.tooltip": "Gare la plus proche de votre position", + "gui.createrailwaysnavigator.navigator.switch.tooltip": "Inverser le point de dĂ©part et la destination", + + "gui.createrailwaysnavigator.route_details.title": "DĂ©tail de l'itinĂ©raire", + "gui.createrailwaysnavigator.route_details.departure": "DĂ©part dans", + "gui.createrailwaysnavigator.route_details.next_transfer_time": "Correspondance dans", + "gui.createrailwaysnavigator.route_details.transfer": "Correspondance", + "gui.createrailwaysnavigator.route_details.save_route": "Enregistrer l'itinĂ©raire'", + + "gui.createrailwaysnavigator.route_overview.title": "DĂ©tail de l'itinĂ©raire", + "gui.createrailwaysnavigator.route_overview.journey_begins": "Votre voyage commence! %s Ă  %s, dĂ©part %s", + "gui.createrailwaysnavigator.route_overview.journey_begins_with_platform": "Votre voyage commence! %s Ă  %s, dĂ©part %s sur la voie %s", + "gui.createrailwaysnavigator.route_overview.train_details": "%s Ă  %s", + "gui.createrailwaysnavigator.route_overview.next_stop": "Prochain arrĂȘt: %s", + "gui.createrailwaysnavigator.route_overview.transfer": "Correspondance entre %s → %s", + "gui.createrailwaysnavigator.route_overview.transfer_with_platform": "Correspondance entre %s → %s sur la voie %s", + "gui.createrailwaysnavigator.route_overview.journey_completed": "Voyage terminĂ©!", + "gui.createrailwaysnavigator.route_overview.after_journey": "Vous ĂȘtes arrivĂ©s Ă  %s. Merci d'avoir voyagĂ© et passez une bonne journĂ©e.", + "gui.createrailwaysnavigator.route_overview.next_connections": "Prochaines correspondances", + "gui.createrailwaysnavigator.route_overview.schedule_transfer": "Correspondance", + "gui.createrailwaysnavigator.route_overview.train_cancelled": "Train AnnulĂ©", + "gui.createrailwaysnavigator.route_overview.train_cancellation_info": "Information Ă  propos de %s", + "gui.createrailwaysnavigator.route_overview.stop_cancelled": "❌ AnnulĂ©", + "gui.createrailwaysnavigator.route_overview.connection_endangered": "Correspondance en danger", + "gui.createrailwaysnavigator.route_overview.connection_missed": "Correspondance manquĂ©", + "gui.createrailwaysnavigator.route_overview.connection_cancelled": "Train annulĂ©", + "gui.createrailwaysnavigator.route_overview.journey_interrupted": "Votre voyage vers %s ne peut se poursuivre.", + "gui.createrailwaysnavigator.route_overview.connection_missed_info": "A cause d'un retard, vous avez manquĂ© votre correspondace. Rechercher une alternative dans le Navigateur. Nous nous excusons pour le dĂ©rangement.", + "gui.createrailwaysnavigator.route_overview.train_cancelled_info": "Information Ă  propos de %s: Ce train a Ă©tĂ© annulĂ© pour aujourd'hui! Nous nous excusons pour le dĂ©rangement. Rechercher une alternative dans le Navigateur.", + "gui.createrailwaysnavigator.route_overview.train_cancelled_title": "Le train a Ă©tĂ© annulĂ©", + "gui.createrailwaysnavigator.route_overview.journey_interrupted_info": "Votre voyage vers %s ne peut pas se poursuivre. Rechercher une alternative dans le Navigateur.", + "gui.createrailwaysnavigator.route_overview.options": "Appuyer sur %s pour voir les options.", + "gui.createrailwaysnavigator.route_overview.notification.journey_begins.title": "Votre voyage vers %s commence!", + "gui.createrailwaysnavigator.route_overview.notification.journey_begins": "%s vers %s, dĂ©part %s", + "gui.createrailwaysnavigator.route_overview.notification.journey_begins_with_platform": "%s vers %s, dĂ©part %s depuis la voie %s", + "gui.createrailwaysnavigator.route_overview.notification.platform_changed.title": "Votre voie a changĂ©!", + "gui.createrailwaysnavigator.route_overview.notification.platform_changed": "Votre train dans %s part de la voie %s aujourd'hui.", + "gui.createrailwaysnavigator.route_overview.notification.train_delayed.title": "%s: DĂ©part %s retardĂ©.", + "gui.createrailwaysnavigator.route_overview.notification.train_delayed": "%s au lieu de %s dans %s", + "gui.createrailwaysnavigator.route_overview.notification.train_cancelled.title": "%s: Train annulĂ©", + "gui.createrailwaysnavigator.route_overview.notification.train_cancelled": "%s vers %s est annulĂ© aujourd'hui.", + "gui.createrailwaysnavigator.route_overview.notification.transfer.title": "Votre correspondance est en approche", + "gui.createrailwaysnavigator.route_overview.notification.transfer": "Correspondance entre %s → %s sur la voie %s", + "gui.createrailwaysnavigator.route_overview.notification.transfer_with_platform": "Correspondance entre %s → %s sur la voie %s", + "gui.createrailwaysnavigator.route_overview.notification.connection_endangered.title": "Votre correspondance est en danger!", + "gui.createrailwaysnavigator.route_overview.notification.connection_endangered": "Vous ne pourrez probablement pas atteindre %s vers %s.", + "gui.createrailwaysnavigator.route_overview.notification.connection_missed.title": "Correspondance manquĂ©e", + "gui.createrailwaysnavigator.route_overview.notification.connection_missed": "Vous avez manquĂ© votre correspondace %s vers %s.", + "gui.createrailwaysnavigator.route_overview.notification.journey_completed.title": "Vous avez atteint votre destination !", + "gui.createrailwaysnavigator.route_overview.notification.journey_completed": "Merci d'avoir voyagĂ© et passez une bonne journĂ©e", + "gui.createrailwaysnavigator.route_overview.date": "Jour %s, %s", + + "gui.createrailwaysnavigator.global_settings.title": "ParamĂštres globaux", + "gui.createrailwaysnavigator.global_settings.option.tooltip": "Cliquez ici pour modifier", + "gui.createrailwaysnavigator.global_settings.option_alias.title": "Balises de Gare", + "gui.createrailwaysnavigator.global_settings.option_alias.description": "DĂ©finissez des balises de gares pour traĂźter une gare Ă  multiples quais (ex. MaGare 1, MaGare 2) comme une seule gare (e.g. MaGare) avec un nom personnalisĂ©.", + "gui.createrailwaysnavigator.global_settings.option_blacklist.title": "Liste Noire de Gare", + "gui.createrailwaysnavigator.global_settings.option_blacklist.description": "Exclure les gares qui ne devrait pas apparaĂźtre dans les rĂ©sultats de navigations. Ces gares seront ignorĂ©s lors de la gĂ©nĂ©ration d'itinĂ©raire.", + "gui.createrailwaysnavigator.global_settings.train_group.title": "Groupes de Train", + "gui.createrailwaysnavigator.global_settings.train_group.description": "CrĂ©ez des groupes de trains pour les organiser (ex. services rĂ©gionaux, services longue distance, ...). Les utilisateurs peuvent dĂ©cider quels groupes ils souhaitent utiliser.", + "gui.createrailwaysnavigator.global_settings.train_blacklist.title": "Liste Noire de Train", + "gui.createrailwaysnavigator.global_settings.train_blacklist.description": "Exclure des trains, ex. trains de marchandises, trains spĂ©ciaux, etc., de maniĂšre Ă  ce qu'ils ne puissent ĂȘtre utilisĂ© dans des itinĂ©raires.", + + "gui.createrailwaysnavigator.station_tags.title": "ParamĂštres des Balises de Gares", + "gui.createrailwaysnavigator.station_tags.summary": "Contient %s Gares", + "gui.createrailwaysnavigator.station_tags.editor": "ModifiĂ© par %s le %s", + "gui.createrailwaysnavigator.station_tags.add.tooltip": "CrĂ©er une nouvelle entrĂ©e", + "gui.createrailwaysnavigator.station_tags.delete_alias.tooltip": "Supprimer la Balise", + "gui.createrailwaysnavigator.station_tags.delete_station.tooltip": "Retirer une Gare", + "gui.createrailwaysnavigator.station_tags.add_station.tooltip": "Ajouter une Gare", + "gui.createrailwaysnavigator.station_tags.hint.station_name": "Nom de la Station", + "gui.createrailwaysnavigator.station_tags.hint.platform": "Quai", + "gui.createrailwaysnavigator.station_tags.enter_name": "Entrer un nom ici", + + "gui.createrailwaysnavigator.train_group_settings.title": "ParamĂštres des Groupes de Trains", + "gui.createrailwaysnavigator.train_group_settings.summary": "Contient %s Trains", + "gui.createrailwaysnavigator.train_group_settings.editor": "ModifiĂ© par %s le %s", + "gui.createrailwaysnavigator.train_group_settings.delete_alias.tooltip": "Supprimer le Groupe", + "gui.createrailwaysnavigator.train_group_settings.delete_station.tooltip": "Retirer le Train", + "gui.createrailwaysnavigator.train_group_settings.add_station.tooltip": "Ajouter un Train", + + "gui.createrailwaysnavigator.blacklist.title": "Liste noire des Gares", + "gui.createrailwaysnavigator.blacklist.add.tooltip": "Ajouter Ă  la liste noire", + "gui.createrailwaysnavigator.blacklist.delete.tooltip": "Retirer de la liste noire", + + "gui.createrailwaysnavigator.train_blacklist.title": "Liste noire des Trains", + "gui.createrailwaysnavigator.train_blacklist.add.tooltip": "Ajouter Ă  la liste noire", + "gui.createrailwaysnavigator.train_blacklist.delete.tooltip": "Retirer de la liste noire", + + "gui.createrailwaysnavigator.search_settings.title": "ParamĂštres de recherche", + "gui.createrailwaysnavigator.search_settings.transfer_time": "Temps de Correspondance Minimum", + "gui.createrailwaysnavigator.search_settings.transfer_time.description": "Le temps minimum qui devrait ĂȘtre disponible pour changer de train. (1h ~ 50 secondes rĂ©elles)", + "gui.createrailwaysnavigator.search_settings.train_groups": "Filtre de CatĂ©gorie de Train", + "gui.createrailwaysnavigator.search_settings.train_groups.description": "DĂ©cidez quels trains et quelles catĂ©gories de trains vous souhaitez utiliser.", + "gui.createrailwaysnavigator.search_settings.train_groups.overview": "%s catĂ©gories sĂ©lĂ©ctionĂ©s", + "gui.createrailwaysnavigator.search_settings.train_groups.overview.all": "Tous", + "gui.createrailwaysnavigator.search_settings.train_groups.overview.none": "Aucun", + "gui.createrailwaysnavigator.search_settings.train_groups.tooltip.reset": "RĂ©initialiser les filtres", + + "gui.createrailwaysnavigator.new_text_entry.add.tooltip": "Ajouter", + + "gui.createrailwaysnavigator.time": "Temps: %s", + "gui.createrailwaysnavigator.time.now": "maintenant", + "gui.createrailwaysnavigator.time_format.dhm": "%s jours %s h. %s m.", + "gui.createrailwaysnavigator.time_format.hm": "%s h. %s min.", + "gui.createrailwaysnavigator.time_format.m": "%s m.", + + "gui.createrailwaysnavigator.platform": "Voie", + "gui.createrailwaysnavigator.departure": "DĂ©part", + "gui.createrailwaysnavigator.destination": "Destination", + "gui.createrailwaysnavigator.line": "Ligne", + "gui.createrailwaysnavigator.following_trains": "Trains suivants:", + "gui.createrailwaysnavigator.via": "via", + + "gui.createrailwaysnavigator.advanced_display_settings.title": "ParamĂštres des Affichages AvancĂ©s", + "gui.createrailwaysnavigator.advanced_display_settings.display_type": "Type d'Affichage", + "gui.createrailwaysnavigator.advanced_display_settings.display_type.description": "DĂ©termine les informations affichĂ©s.", + "gui.createrailwaysnavigator.advanced_display_settings.info_type": "Type d'Information", + "gui.createrailwaysnavigator.advanced_display_settings.info_type.description": "DĂ©termine le degrĂ© de dĂ©tail des informations.", + "gui.createrailwaysnavigator.advanced_display_settings.double_sided": "Double-face", + + "enum.createrailwaysnavigator.display_info_type": "Type d'Affiche d'Information", + "enum.createrailwaysnavigator.display_info_type.description": "DĂ©termine combien d'informations devraient ĂȘtre affichĂ©s sur votre tableau d'affichage.", + "enum.createrailwaysnavigator.display_info_type.simple": "Simple", + "enum.createrailwaysnavigator.display_info_type.info.simple": "L'Ă©cran va seulement afficher les informations les plus importantes sans dĂ©tails supplĂ©mentaires.", + "enum.createrailwaysnavigator.display_info_type.detailed": "DĂ©taillĂ©", + "enum.createrailwaysnavigator.display_info_type.info.detailed": "Les informations les plus importantes avec quelques dĂ©tails seront affichĂ©s, comme la vitesse du trains, les escales, etc.", + "enum.createrailwaysnavigator.display_info_type.informative": "Informatif", + "enum.createrailwaysnavigator.display_info_type.info.informative": "Affiche toutes les informations qui pourraient ĂȘtre intĂ©ressantes et les affiches dans une disposition agrĂ©able. DĂ©conseillĂ© pour les petits petits affichages car le texte peu devenir trĂšs petit.", + + "enum.createrailwaysnavigator.display_type": "Type d'Affichage", + "enum.createrailwaysnavigator.display_type.description": "Le type d'affiche en fonction de son objectif.", + "enum.createrailwaysnavigator.display_type.train_destination": "Destination du Train", + "enum.createrailwaysnavigator.display_type.info.train_destination": "DestinĂ© Ă  ĂȘtre utilisĂ© en dehors des trains car il affiche des informations sur le train lui-mĂȘme, comme le nom, la destinations et (si sĂ©lĂ©ctionĂ©s) les escales.", + "enum.createrailwaysnavigator.display_type.passenger_information": "Informations pour les passagers", + "enum.createrailwaysnavigator.display_type.info.passenger_information": "ReprĂ©sentes les Ă©crans prĂ©sents Ă  l'interieur des trains. Ces Ă©crans afficheront les prochains arrĂȘts, la direction de la sortie et (si sĂ©lĂ©ctionĂ©s) la vitesse du train et une vue d'ensemble de la route", + "enum.createrailwaysnavigator.display_type.platform": "Affichage de quai", + "enum.createrailwaysnavigator.display_type.info.platform": "Ces Ă©crans doivent ĂȘtre utilisĂ©s sur les quais des gares et montre les prochains trains arrivant avec quelques dĂ©tails en plus si sĂ©lĂ©ctionĂ©s. Ne peut pas ĂȘtre utilisĂ©s Ă  l'intĂ©rieur des trains!", + + "enum.createrailwaysnavigator.side": "Face", + "enum.createrailwaysnavigator.side.description": "La face du bloc oĂč les informations devraient ĂȘtre affichĂ©s.", + "enum.createrailwaysnavigator.side.front": "Face avant", + "enum.createrailwaysnavigator.side.info.front": "Les informations seront affichĂ©s uniquement sur la face avant du bloc. C'est le comportement par dĂ©faut.", + "enum.createrailwaysnavigator.side.both": "Toutes les faces", + "enum.createrailwaysnavigator.side.info.both": "Les informations seront affichĂ©s sur toutes les faces.", + + "enum.createrailwaysnavigator.time_display": "Affichage des horaires", + "enum.createrailwaysnavigator.time_display.description": "DĂ©termines comment les horaires devraient ĂȘtre affichĂ©s", + "enum.createrailwaysnavigator.time_display.abs": "ABS", + "enum.createrailwaysnavigator.time_display.info.abs": "ABS (absolu)", + "enum.createrailwaysnavigator.time_display.eta": "ETA", + "enum.createrailwaysnavigator.time_display.info.eta": "ETA (estimation du temps d'arrivĂ©e)", + + "create.display_source.advanced_display": "Affichage AvancĂ©", + "gui.createrailwaysnavigator.display_source.advanced_display.train_name_width": "Largeur de la colonne du nom du train", + "gui.createrailwaysnavigator.display_source.advanced_display.train_name_width.description": "en pixels bloc. (Defaut: 16)", + "gui.createrailwaysnavigator.display_source.advanced_display.platform_width": "Largeur de la colonne du quai", + "gui.createrailwaysnavigator.display_source.advanced_display.platform_width.description": "en pixel bloc. (Defaut: Auto)", + + "createrailwaysnavigator.moin": "moin" +} diff --git a/common/src/main/resources/assets/createrailwaysnavigator/lang/ko_kr.json b/common/src/main/resources/assets/createrailwaysnavigator/lang/ko_kr.json new file mode 100644 index 00000000..87d4f882 --- /dev/null +++ b/common/src/main/resources/assets/createrailwaysnavigator/lang/ko_kr.json @@ -0,0 +1,250 @@ +{ + "advancement.createrailwaysnavigator.navigator": "였늘도 ì—Žì°šë„Œ ìŽìš©í•ŽìŁŒì…”ì„œ êł ë§™ìŠ”ë‹ˆë‹€", + "advancement.createrailwaysnavigator.navigator.description": "êž°ì°šì—­ ì‚ŹìŽë„Œ 잇는 ì—Žì°šë„Œ êČ€ìƒ‰í•˜êž° 위한 탐색Ʞ넌 제작하섞요.", + "advancement.createrailwaysnavigator.advanced_display": "4K 화질은 ì•„ë‹ˆëĄœê”°", + "advancement.createrailwaysnavigator.advanced_display.description": "더 많은 ì •ëłŽë„Œ 표시하Ʞ 위핎 전ꎑ판을 ì—…ê·žë ˆìŽë“œí•˜êł  돎렀 êž°ì°š 안에닀가도 ì„€ìč˜í•ŽëłŽì„žìš”.", + + "itemGroup.createrailwaysnavigator.tab": "Create ìȠ도 탐색Ʞ", + + "item.createrailwaysnavigator.navigator": "Create ìȠ도 탐색Ʞ", + "item.createrailwaysnavigator.navigator.tooltip.summary": "_탐색Ʞ_는 추가적읞 _ì—Žì°š êČœëĄœ_와 핚께 _êČœìœ ì§€_, _싀시간 데읎터_ 등 닀양한 ì •ëłŽë„Œ ëłŽì—Źì€ë‹ˆë‹€.", + + "block.createrailwaysnavigator.train_station_clock": "ìŠčê°•ìž„ ì‹œêł„", + "block.createrailwaysnavigator.advanced_display_block": "êł êž‰ 전ꎑ판 뾔록", + "block.createrailwaysnavigator.advanced_display_block.tooltip.summary": "_도착역 ì •ëłŽ_와 _ìŠč객 안낎용 ì •ëłŽ_ë„Œ í‘œì‹œí•˜ë„ëĄ _찚낎전ꎑ판_ìœŒëĄœ ì‚Źìš©í•˜ê±°ë‚˜, _ì—Žì°šì—­_에서 ì‚Źìš©í•˜ì—Ź 음반 ì „êŽ‘íŒëłŽë‹€ 더 많은 ì •ëłŽë„Œ 표시합니닀.", + "block.createrailwaysnavigator.advanced_display_block.tooltip.condition1": "렌ìč˜ë„Œ ì‚Źìš©í•˜ì—Ź 우큎늭할 êČœìš°", + "block.createrailwaysnavigator.advanced_display_block.tooltip.behaviour1": "_전ꎑ판_을 _섀정_하는 메뉎넌 엜니닀.", + "block.createrailwaysnavigator.advanced_display": "êł êž‰ 전ꎑ판", + "block.createrailwaysnavigator.advanced_display.tooltip.summary": "_도착역 ì •ëłŽ_와 _ìŠč객 안낎용 ì •ëłŽ_ë„Œ í‘œì‹œí•˜ë„ëĄ _찚낎전ꎑ판_ìœŒëĄœ ì‚Źìš©í•˜ê±°ë‚˜, _ì—Žì°šì—­_에서 ì‚Źìš©í•˜ì—Ź 음반 ì „êŽ‘íŒëłŽë‹€ 더 많은 ì •ëłŽë„Œ 표시합니닀.", + "block.createrailwaysnavigator.advanced_display.tooltip.condition1": "렌ìč˜ë„Œ ì‚Źìš©í•˜ì—Ź 우큎늭할 êČœìš°", + "block.createrailwaysnavigator.advanced_display.tooltip.behaviour1": "_전ꎑ판_을 _섀정_하는 메뉎넌 엜니닀.", + "block.createrailwaysnavigator.advanced_display_small": "소형 êł êž‰ 전ꎑ판", + "block.createrailwaysnavigator.advanced_display_small.tooltip.summary": "_도착역 ì •ëłŽ_와 _ìŠč객 안낎용 ì •ëłŽ_ë„Œ í‘œì‹œí•˜ë„ëĄ _찚낎전ꎑ판_ìœŒëĄœ ì‚Źìš©í•˜ê±°ë‚˜, _ì—Žì°šì—­_에서 ì‚Źìš©í•˜ì—Ź 음반 ì „êŽ‘íŒëłŽë‹€ 더 많은 ì •ëłŽë„Œ 표시합니닀.", + "block.createrailwaysnavigator.advanced_display_small.tooltip.condition1": "렌ìč˜ë„Œ ì‚Źìš©í•˜ì—Ź 우큎늭할 êČœìš°", + "block.createrailwaysnavigator.advanced_display_small.tooltip.behaviour1": "_전ꎑ판_을 _섀정_하는 메뉎넌 엜니닀.", + "block.createrailwaysnavigator.advanced_display_panel": "êł êž‰ 전ꎑ판 판넬", + "block.createrailwaysnavigator.advanced_display_panel.tooltip.summary": "_도착역 ì •ëłŽ_와 _ìŠč객 안낎용 ì •ëłŽ_ë„Œ í‘œì‹œí•˜ë„ëĄ _찚낎전ꎑ판_ìœŒëĄœ ì‚Źìš©í•˜ê±°ë‚˜, _ì—Žì°šì—­_에서 ì‚Źìš©í•˜ì—Ź 음반 ì „êŽ‘íŒëłŽë‹€ 더 많은 ì •ëłŽë„Œ 표시합니닀.", + "block.createrailwaysnavigator.advanced_display_panel.tooltip.condition1": "렌ìč˜ë„Œ ì‚Źìš©í•˜ì—Ź 우큎늭할 êČœìš°", + "block.createrailwaysnavigator.advanced_display_panel.tooltip.behaviour1": "_전ꎑ판_을 _섀정_하는 메뉎넌 엜니닀.", + "block.createrailwaysnavigator.advanced_display_half_panel": "êł êž‰ 전ꎑ판 반뾔록", + "block.createrailwaysnavigator.advanced_display_half_panel.tooltip.summary": "_도착역 ì •ëłŽ_와 _ìŠč객 안낎용 ì •ëłŽ_ë„Œ í‘œì‹œí•˜ë„ëĄ _찚낎전ꎑ판_ìœŒëĄœ ì‚Źìš©í•˜ê±°ë‚˜, _ì—Žì°šì—­_에서 ì‚Źìš©í•˜ì—Ź 음반 ì „êŽ‘íŒëłŽë‹€ 더 많은 ì •ëłŽë„Œ 표시합니닀.", + "block.createrailwaysnavigator.advanced_display_half_panel.tooltip.condition1": "렌ìč˜ë„Œ ì‚Źìš©í•˜ì—Ź 우큎늭할 êČœìš°", + "block.createrailwaysnavigator.advanced_display_half_panel.tooltip.behaviour1": "_전ꎑ판_을 _섀정_하는 메뉎넌 엜니닀.", + "block.createrailwaysnavigator.advanced_display_sloped": "역êČœì‚Źí˜• êł êž‰ 전ꎑ판", + "block.createrailwaysnavigator.advanced_display_sloped.tooltip.summary": "_도착역 ì •ëłŽ_와 _ìŠč객 안낎용 ì •ëłŽ_ë„Œ í‘œì‹œí•˜ë„ëĄ _찚낎전ꎑ판_ìœŒëĄœ ì‚Źìš©í•˜ê±°ë‚˜, _ì—Žì°šì—­_에서 ì‚Źìš©í•˜ì—Ź 음반 ì „êŽ‘íŒëłŽë‹€ 더 많은 ì •ëłŽë„Œ 표시합니닀.", + "block.createrailwaysnavigator.advanced_display_sloped.tooltip.condition1": "렌ìč˜ë„Œ ì‚Źìš©í•˜ì—Ź 우큎늭할 êČœìš°", + "block.createrailwaysnavigator.advanced_display_sloped.tooltip.behaviour1": "_전ꎑ판_을 _섀정_하는 메뉎넌 엜니닀.", + + "block.createrailwaysnavigator.advanced_display.ber.not_in_service": "점êȀ 쀑", + "block.createrailwaysnavigator.advanced_display.ber.shunting_trip": "돎ìŠč객엎찚", + + "category.createrailwaysnavigator.crn": "Create ìȠ도 탐색Ʞ", + "key.createrailwaysnavigator.route_overlay_options": "êČœëĄœ 였ëČ„ë ˆìŽ 옔션 ëłŽìŽêž°", + + "enum.createrailwaysnavigator.overlay_position": "안낎판 위ìč˜", + "enum.createrailwaysnavigator.overlay_position.info.top_left": "ìąŒìžĄ 상닚", + "enum.createrailwaysnavigator.overlay_position.info.top_right": "ìš°ìžĄ 상닚", + "enum.createrailwaysnavigator.overlay_position.info.bottom_left": "ìąŒìžĄ 하당", + "enum.createrailwaysnavigator.overlay_position.info.bottom_right": "ìš°ìžĄ 하당", + + "gui.createrailwaysnavigator.loading.title": "서ëČ„ì—ì„œ 데읎터넌 가젞옔니닀...", + + "gui.createrailwaysnavigator.overlay_settings.title": "êČœëĄœ 였ëČ„ë ˆìŽ 섀정", + "gui.createrailwaysnavigator.route_overlay_settings.show_details": "자섞히 ëłŽêž°", + "gui.createrailwaysnavigator.route_overlay_settings.unpin": "êČœëĄœ 였ëČ„ë ˆìŽ 삭제", + "gui.createrailwaysnavigator.route_overlay_settings.narrator.on": "ì•ˆë‚Žë°©ì†Ą 활성화", + "gui.createrailwaysnavigator.route_overlay_settings.narrator.off": "ì•ˆë‚Žë°©ì†Ą ëč„활성화", + "gui.createrailwaysnavigator.route_overlay_settings.notifications.on": "알늌 활성화", + "gui.createrailwaysnavigator.route_overlay_settings.notifications.off": "알늌 ëč„활성화", + "gui.createrailwaysnavigator.route_overlay_settings.scale": "안낎찜 íŹêž°", + "gui.createrailwaysnavigator.route_overlay_settings.narrator": "ì•ˆë‚Žë°©ì†Ą", + "gui.createrailwaysnavigator.route_overlay_settings.narrator.description": "ì—Žì°š 욎행 쀑 쀑요한 ì •ëłŽ (도착역, 변êČœì‚Źí•­ 등)에 대한 음성 안낎넌 받슔니닀.", + "gui.createrailwaysnavigator.route_overlay_settings.notifications": "알늌", + "gui.createrailwaysnavigator.route_overlay_settings.notifications.description": "ì—Žì°š 욎행 쀑 쀑요한 ì •ëłŽ (도착역, 변êČœì‚Źí•­ 등)에 대한 알늌을 받슔니닀.", + + "gui.createrailwaysnavigator.common.expand": "자섞히 ëłŽêž°", + "gui.createrailwaysnavigator.common.collapse": "숚ꞰꞰ", + "gui.createrailwaysnavigator.common.go_back": "ë’€ëĄœ", + "gui.createrailwaysnavigator.common.go_to_top": "맚 ìœ„ëĄœ", + "gui.createrailwaysnavigator.common.reset_defaults": "쎈Ʞ화", + "gui.createrailwaysnavigator.common.count": "개수", + "gui.createrailwaysnavigator.common.true": "예", + "gui.createrailwaysnavigator.common.false": "아니였", + "gui.createrailwaysnavigator.common.search": "êČ€ìƒ‰", + "gui.createrailwaysnavigator.common.auto": "자동", + "gui.createrailwaysnavigator.common.server_error": "작업을 수행하던 쀑 서ëȄ 였넘가 발생했슔니닀. 자섞한 ì •ëłŽëŠ” 윘솔을 확읞하십시였.", + + "gui.createrailwaysnavigator.navigator.title": "Create ìȠ도 탐색Ʞ", + "gui.createrailwaysnavigator.navigator.no_connections": "연êȰ된 ì—Žì°šê°€ 없슔니닀.", + "gui.createrailwaysnavigator.navigator.not_searched": "아직 êČ€ìƒ‰ë˜ì§€ 않았슔니닀.", + "gui.createrailwaysnavigator.navigator.searching": "êČ€ìƒ‰ 쀑...", + "gui.createrailwaysnavigator.navigator.error_title": "불가늄한 êČœëĄœìž…ë‹ˆë‹€!", + "gui.createrailwaysnavigator.navigator.start_end_null": "출발지 í˜č은 도착지가 ìĄŽìžŹí•˜ì§€ 않슔니닀.", + "gui.createrailwaysnavigator.navigator.start_end_equal": "출발지와 도착지가 동음합니닀.", + "gui.createrailwaysnavigator.navigator.route_entry.connection_in_past": "❌ 갱신되지 않은 연êČ°", + "gui.createrailwaysnavigator.navigator.route_entry.transfer": "환ìŠč역", + "gui.createrailwaysnavigator.navigator.route_entry.station_start": "%s발", + "gui.createrailwaysnavigator.navigator.global_settings.tooltip": "ꎑ역 섀정", + "gui.createrailwaysnavigator.navigator.search_settings.tooltip": "êČ€ìƒ‰ 섀정", + "gui.createrailwaysnavigator.navigator.search.tooltip": "êČ€ìƒ‰", + "gui.createrailwaysnavigator.navigator.location.tooltip": "í˜„ìžŹ 위ìč˜ì—ì„œ 가임 가êčŒìšŽ ì—Žì°š", + "gui.createrailwaysnavigator.navigator.switch.tooltip": "영역 전환", + + "gui.createrailwaysnavigator.route_details.title": "êČœëĄœ 상섞", + "gui.createrailwaysnavigator.route_details.departure": "출발 시각", + "gui.createrailwaysnavigator.route_details.next_transfer_time": "환ìŠč 시각", + "gui.createrailwaysnavigator.route_details.transfer": "환ìŠč", + "gui.createrailwaysnavigator.route_details.save_route": "연êČ° 저임", + + "gui.createrailwaysnavigator.route_overview.title": "êČœëĄœ 상섞", + "gui.createrailwaysnavigator.route_overview.journey_begins": "%s ì—Žì°š, %s 행, 출발시각 %s", + "gui.createrailwaysnavigator.route_overview.journey_begins_with_platform": "%s ì—Žì°š, %s 행, 출발시각 %s, 타는 êłł %s", + "gui.createrailwaysnavigator.route_overview.train_details": "%s ì—Žì°š, %s행", + "gui.createrailwaysnavigator.route_overview.next_stop": "읎ëȈ 역은 %s 입니닀", + "gui.createrailwaysnavigator.route_overview.transfer": "%s 엎찚의 %s í–‰ìœŒëĄœ 환ìŠč", + "gui.createrailwaysnavigator.route_overview.transfer_with_platform": "%s ì—Žì°š(%s 행, 타는 êłł %s)로 환ìŠč", + "gui.createrailwaysnavigator.route_overview.journey_completed": "ì—Źì • ì™„ëŁŒ", + "gui.createrailwaysnavigator.route_overview.after_journey": "%s에 도착하셚슔니닀. 였늘도 ì—Žì°šë„Œ ìŽìš©í•ŽìŁŒì…”ì„œ êł ë§™ìŠ”ë‹ˆë‹€. 안녕히 가십시였.", + "gui.createrailwaysnavigator.route_overview.next_connections": "닀음 êČœëĄœ", + "gui.createrailwaysnavigator.route_overview.schedule_transfer": "환ìŠč", + "gui.createrailwaysnavigator.route_overview.train_cancelled": "욎행 췚소됚", + "gui.createrailwaysnavigator.route_overview.train_cancellation_info": "%s ì—Žì°š ì •ëłŽ", + "gui.createrailwaysnavigator.route_overview.stop_cancelled": "❌ 욎행 췚소", + "gui.createrailwaysnavigator.route_overview.connection_endangered": "핮ë‹č êČœëĄœì— ꞎ꞉상황 발생", + "gui.createrailwaysnavigator.route_overview.connection_missed": "êČœëĄœ 읎탈", + "gui.createrailwaysnavigator.route_overview.connection_cancelled": "ì—Žì°šê°€ 욎행 췚소됚", + "gui.createrailwaysnavigator.route_overview.journey_interrupted": "%s로 햄하는 욎행읎 닚축욎행 되었슔니닀. 불펞을 ëŒìłë“œë € 대당히 ìŁ„ì†Ąí•©ë‹ˆë‹€.", + "gui.createrailwaysnavigator.route_overview.connection_missed_info": "ì—Žì°š ì§€ì—°ìœŒëĄœ 읞핎 닀음 엎찚에 탑ìŠč하싀 수 없슔니닀. ìƒˆëĄœìšŽ 대ìČŽíŽžì„±ì„ êČ€ìƒ‰í•ŽìŁŒì‹­ì‹œì˜€. 읎용에 불펞을 ëŒìłë“œë € 대당히 ìŁ„ì†Ąí•©ë‹ˆë‹€.", + "gui.createrailwaysnavigator.route_overview.train_cancelled_info": "ꞈ음 %s 엎찚는 욎행읎 췚소되었슔니닀. ìƒˆëĄœìšŽ 대ìČŽíŽžì„±ì„ êČ€ìƒ‰í•ŽìŁŒì‹­ì‹œì˜€. 읎용에 불펞을 ëŒìłë“œë € 대당히 ìŁ„ì†Ąí•©ë‹ˆë‹€.", + "gui.createrailwaysnavigator.route_overview.train_cancelled_title": "ì—Žì°š 욎행 췚소에 대한 ì‚ŹêłŒë§ì”€", + "gui.createrailwaysnavigator.route_overview.journey_interrupted_info": "%s로 햄하는 ì—Žì°šê°€ 닚축욎행 되었슔니닀. ìƒˆëĄœìšŽ 대ìČŽíŽžì„±ì„ êČ€ìƒ‰í•ŽìŁŒì‹­ì‹œì˜€. 읎용에 불펞을 ëŒìłë“œë € 대당히 ìŁ„ì†Ąí•©ë‹ˆë‹€.", + "gui.createrailwaysnavigator.route_overview.options": "%s 눌러 ì„ íƒì‚Źí•­ 확읞", + "gui.createrailwaysnavigator.route_overview.notification.journey_begins.title": "%s로 햄하는 ì—Źì • 시작", + "gui.createrailwaysnavigator.route_overview.notification.journey_begins": "%s ì—Žì°š, %s 행, 출발시각 %s", + "gui.createrailwaysnavigator.route_overview.notification.journey_begins_with_platform": "%s ì—Žì°š, %s 행, 출발시각 %s, 타는 êłł %s", + "gui.createrailwaysnavigator.route_overview.notification.platform_changed.title": "타는 êłłìŽ 변êČœë˜ì—ˆìŠ”ë‹ˆë‹€!", + "gui.createrailwaysnavigator.route_overview.notification.platform_changed": "%s의 엎찚는 였늘 타는 êłł %sìœŒëĄœ ë“€ì–Žì˜Ź 예정입니닀.", + "gui.createrailwaysnavigator.route_overview.notification.train_delayed.title": "%s의 %s 지연에 ꎀ한 안낎", + "gui.createrailwaysnavigator.route_overview.notification.train_delayed": "%s 도착 예정, êž°ìĄŽ 시각 %s (%s)", + "gui.createrailwaysnavigator.route_overview.notification.train_cancelled.title": "%s ì—Žì°š 췚소에 ꎀ한 안낎", + "gui.createrailwaysnavigator.route_overview.notification.train_cancelled": "%s에서 탑ìŠčí•Žì•Œ 할 %s행 ì—Žì°šê°€ 췚소되었슔니닀.", + "gui.createrailwaysnavigator.route_overview.notification.transfer.title": "êł§ 환ìŠčí•Žì•Œ 합니닀.", + "gui.createrailwaysnavigator.route_overview.notification.transfer": "%s ì—Žì°š(%s행)로 환ìŠč", + "gui.createrailwaysnavigator.route_overview.notification.transfer_with_platform": "%s ì—Žì°š(%s행, 타는 êłł %s)로 환ìŠč", + "gui.createrailwaysnavigator.route_overview.notification.connection_endangered.title": "ꞎ꞉상황발생에 ë”°ë„ž ì—Žì°š 욎행쀑닚에 ꎀ한 안낎", + "gui.createrailwaysnavigator.route_overview.notification.connection_endangered": "위와 같은 ì‚Źìœ ëĄœ %s ì—Žì°š(%s행)는 읎 역에서 욎행을 쀑닚하였슔니닀. 불펞을 ëŒìłë“œë € 대당히 ìŁ„ì†Ąí•©ë‹ˆë‹€.", + "gui.createrailwaysnavigator.route_overview.notification.connection_missed.title": "환ìŠč ì—Žì°šë„Œ ë†“ìł€ìŠ”ë‹ˆë‹€!", + "gui.createrailwaysnavigator.route_overview.notification.connection_missed": "%s ì—Žì°š(%s행)ë„Œ 탈 수 없슔니닀.", + "gui.createrailwaysnavigator.route_overview.notification.journey_completed.title": "욎행 ì™„ëŁŒ.", + "gui.createrailwaysnavigator.route_overview.notification.journey_completed": "였늘도 ìČ ë„ë„Œ ìŽìš©í•ŽìŁŒì…”ì„œ êł ë§™ìŠ”ë‹ˆë‹€. 가시는 ëȘ©ì ì§€êčŒì§€ 안녕히 가십시였.", + "gui.createrailwaysnavigator.route_overview.date": "%s음, %s", + + "gui.createrailwaysnavigator.global_settings.title": "ꎑ역 섀정", + "gui.createrailwaysnavigator.global_settings.option.tooltip": "íŽëŠ­í•˜ì—Ź 수정하Ʞ", + "gui.createrailwaysnavigator.global_settings.option_alias.title": "êž°ì°šì—­ 태귞", + "gui.createrailwaysnavigator.global_settings.option_alias.description": "êž°ì°šì—­ 태귞넌 만듀얎 ì—ŹëŸŹê°œì˜ ìŠč강임읎 있는 êČœìš° (예: OO역 타는 êłł 1ëȈ, 2ëȈ, 3ëȈ...) 하나의 êž°ì°šì—­ìœŒëĄœ 표시할 수 있슔니닀.", + "gui.createrailwaysnavigator.global_settings.option_blacklist.title": "êž°ì°šì—­ ëž”ëž™ëŠŹìŠ€íŠž", + "gui.createrailwaysnavigator.global_settings.option_blacklist.description": "êž°ì°šì—­ ëž”ëž™ëŠŹìŠ€íŠžì— ë“±ëĄëœ Ʞ찚역은 êČœëĄœ 추ìČœì— ì‚Źìš©ë˜ì§€ 않슔니닀.", + "gui.createrailwaysnavigator.global_settings.train_group.title": "êž°ì°š ê·žëŁč", + "gui.createrailwaysnavigator.global_settings.train_group.description": "êž°ì°š ê·žëŁč을 만듀얎 닀양한 ëȘ©ì ì˜ ì—Žì°š(지역엎찚, ꎑ역엎찚 등)ë„Œ êŽ€ëŠŹí•©ë‹ˆë‹€. 원하는 ê·žëŁč을 ì‚Źìš©í•  수도 있슔니닀.", + "gui.createrailwaysnavigator.global_settings.train_blacklist.title": "êž°ì°š ëž”ëž™ëŠŹìŠ€íŠž", + "gui.createrailwaysnavigator.global_settings.train_blacklist.description": "êž°ì°š ëž”ëž™ëŠŹìŠ€íŠžì— ë“±ëĄëœ êž°ì°š(예: í™”ëŹŒì—Žì°š, íŠč수엎찚)는 êČœëĄœ 추ìČœì— ì‚Źìš©ë˜ì§€ 않슔니닀.", + + "gui.createrailwaysnavigator.station_tags.title": "êž°ì°šì—­ 태귞 섀정", + "gui.createrailwaysnavigator.station_tags.summary": "%s개의 타는 êłł 포핹", + "gui.createrailwaysnavigator.station_tags.editor": "%s님읎 %s에 ë§ˆì§€ë§‰ìœŒëĄœ 수정핚", + "gui.createrailwaysnavigator.station_tags.add.tooltip": "새 태귞 추가", + "gui.createrailwaysnavigator.station_tags.delete_alias.tooltip": "태귞 삭제", + "gui.createrailwaysnavigator.station_tags.delete_station.tooltip": "타는 êłł 삭제", + "gui.createrailwaysnavigator.station_tags.add_station.tooltip": "타는 êłł 추가", + "gui.createrailwaysnavigator.station_tags.hint.station_name": "ì •ê±°ìž„ 읎늄", + "gui.createrailwaysnavigator.station_tags.hint.platform": "타는 êłł", + "gui.createrailwaysnavigator.station_tags.enter_name": "읎늄 ìž…ë „", + + "gui.createrailwaysnavigator.train_group_settings.title": "êž°ì°š ê·žëŁč 섀정", + "gui.createrailwaysnavigator.train_group_settings.summary": "%s개의 êž°ì°š 포핹", + "gui.createrailwaysnavigator.train_group_settings.editor": "%s읎/가 %s에 ë§ˆì§€ë§‰ìœŒëĄœ 펞집", + "gui.createrailwaysnavigator.train_group_settings.delete_alias.tooltip": "ê·žëŁč 삭제", + "gui.createrailwaysnavigator.train_group_settings.delete_station.tooltip": "êž°ì°š 삭제", + "gui.createrailwaysnavigator.train_group_settings.add_station.tooltip": "êž°ì°š 추가", + + "gui.createrailwaysnavigator.blacklist.title": "êž°ì°šì—­ ëž”ëž™ëŠŹìŠ€íŠž", + "gui.createrailwaysnavigator.blacklist.add.tooltip": "ëž”ëž™ëŠŹìŠ€íŠžì— 추가", + "gui.createrailwaysnavigator.blacklist.delete.tooltip": "ëž”ëž™ëŠŹìŠ€íŠžì—ì„œ 삭제", + + "gui.createrailwaysnavigator.train_blacklist.title": "êž°ì°š ëž”ëž™ëŠŹìŠ€íŠž", + "gui.createrailwaysnavigator.train_blacklist.add.tooltip": "ëž”ëž™ëŠŹìŠ€íŠžì— 추가", + "gui.createrailwaysnavigator.train_blacklist.delete.tooltip": "ëž”ëž™ëŠŹìŠ€íŠžì—ì„œ 삭제", + + "gui.createrailwaysnavigator.search_settings.title": "êČ€ìƒ‰ 섀정", + "gui.createrailwaysnavigator.search_settings.transfer_time": "씜소 환ìŠč 시간", + "gui.createrailwaysnavigator.search_settings.transfer_time.description": "환ìŠč하는 데 걞늎 씜소 시간을 섀정합니닀. (싀제 시간 Ʞ쀀 1시간 ~ 50쎈)", + "gui.createrailwaysnavigator.search_settings.train_groups": "êž°ì°š ìą…ë„˜ 선택", + "gui.createrailwaysnavigator.search_settings.train_groups.description": "êČ€ìƒ‰í•  êž°ì°š ìą…ë„˜ë„Œ 선택합니닀.", + "gui.createrailwaysnavigator.search_settings.train_groups.overview": "%s개 선택됚", + "gui.createrailwaysnavigator.search_settings.train_groups.overview.all": "ëȘšë‘ì„ íƒ", + "gui.createrailwaysnavigator.search_settings.train_groups.overview.none": "선택췚소", + "gui.createrailwaysnavigator.search_settings.train_groups.tooltip.reset": "필터 쎈Ʞ화", + + "gui.createrailwaysnavigator.new_text_entry.add.tooltip": "추가하Ʞ", + + "gui.createrailwaysnavigator.time": "í˜„ìžŹì‹œê° %s", + "gui.createrailwaysnavigator.time.now": "지ꞈ", + "gui.createrailwaysnavigator.time_format.dhm": "%s음 %s시간 %s분", + "gui.createrailwaysnavigator.time_format.hm": "%s시간 %s분", + "gui.createrailwaysnavigator.time_format.m": "%s분", + + "gui.createrailwaysnavigator.platform": "타는 êłł", + "gui.createrailwaysnavigator.departure": "출발시간", + "gui.createrailwaysnavigator.destination": "도착역", + "gui.createrailwaysnavigator.line": "ì—Žì°šìą…ë„˜", + "gui.createrailwaysnavigator.following_trains": "닀음 ì—Žì°š", + "gui.createrailwaysnavigator.via": "êČœìœ : ", + + "gui.createrailwaysnavigator.advanced_display_settings.title": "êł êž‰ 전ꎑ판 섀정", + "gui.createrailwaysnavigator.advanced_display_settings.display_type": "전ꎑ판 ìą…ë„˜", + "gui.createrailwaysnavigator.advanced_display_settings.display_type.description": "얎디에 ì‚Źìš©í•  전ꎑ판읞지 êČ°ì •í•©ë‹ˆë‹€.", + "gui.createrailwaysnavigator.advanced_display_settings.info_type": "표시 ë‚Žìš©", + "gui.createrailwaysnavigator.advanced_display_settings.info_type.description": "낎용의 상섞 정도넌 êČ°ì •í•©ë‹ˆë‹€.", + "gui.createrailwaysnavigator.advanced_display_settings.double_sided": "ì–‘ë©Ž 표Ʞ", + + "enum.createrailwaysnavigator.display_info_type": "전ꎑ판 표시 ë‚Žìš©", + "enum.createrailwaysnavigator.display_info_type.description": "전ꎑ판에 얎느정도 ë‚Žìš©êčŒì§€ 표시할지 êČ°ì •í•©ë‹ˆë‹€.", + "enum.createrailwaysnavigator.display_info_type.simple": "간닚하êȌ", + "enum.createrailwaysnavigator.display_info_type.info.simple": "추가적읞 ë‚Žìš© 없읎 가임 필요한 ì •ëłŽë§Œ 표시합니닀.", + "enum.createrailwaysnavigator.display_info_type.detailed": "상섞하êȌ", + "enum.createrailwaysnavigator.display_info_type.info.detailed": "êž°ì°š 속도, êČœìœ ì§€ì™€ 같은 쀑요한 ì •ëłŽë“€ë„ 표시합니닀.", + "enum.createrailwaysnavigator.display_info_type.informative": "풍부하êȌ", + "enum.createrailwaysnavigator.display_info_type.info.informative": "í„ëŻžëĄœìšž 수 있는 ëȘšë“  ì •ëłŽë„Œ 멋지êȌ 표시합니닀. 작은 전ꎑ판에선 Ꞁ씚가 작아질 수 있슔니닀.", + + "enum.createrailwaysnavigator.display_type": "전ꎑ판 ìą…ë„˜", + "enum.createrailwaysnavigator.display_type.description": "각각의 ëȘ©ì ì— 맞는 전ꎑ판을 선택합니닀.", + "enum.createrailwaysnavigator.display_type.train_destination": "ì—Žì°š 행선지", + "enum.createrailwaysnavigator.display_type.info.train_destination": "êž°ì°š 왞부에 ì„€ìč˜ë˜ëŠ” ì „êŽ‘íŒìœŒëĄœ, ì—Žì°š 읎늄, 도착지, êČœìœ ì§€(선택한 êČœìš°) 등을 표시합니닀.", + "enum.createrailwaysnavigator.display_type.passenger_information": "ìŠč객 안낎용", + "enum.createrailwaysnavigator.display_type.info.passenger_information": "êž°ì°š 낎부에 ì„€ìč˜ë˜ëŠ” ì „êŽ‘íŒìœŒëĄœ, 닀음 역, 낮멮 돞, êž°ì°š 속도(선택한 êČœìš°), êž°ì°š êČœëĄœ ì •ëłŽ 등을 표시합니닀.", + "enum.createrailwaysnavigator.display_type.platform": "ìŠčê°•ìž„ 전ꎑ판", + "enum.createrailwaysnavigator.display_type.info.platform": "ìŠč강임에 ì„€ìč˜ë˜ëŠ” ì „êŽ‘íŒìœŒëĄœ, 닀음에 도착할 엎찚와 ê·ž 엎찚의 ì •ëłŽ 등을 표시합니닀. 엎찚에는 ì‚Źìš©í•  수 없슔니닀!", + + "enum.createrailwaysnavigator.side": "전ꎑ판 위ìč˜", + "enum.createrailwaysnavigator.side.description": "전ꎑ판읎 얎느 ìȘœì— 표시될지 êČ°ì •í•©ë‹ˆë‹€.", + "enum.createrailwaysnavigator.side.front": "앞ìȘœ", + "enum.createrailwaysnavigator.side.info.front": "전ꎑ판의 앞멎에 표시됩니닀. (êž°ëłž)", + "enum.createrailwaysnavigator.side.both": "양ìȘœ", + "enum.createrailwaysnavigator.side.info.both": "전ꎑ판의 양멎에 표시됩니닀.", + + "enum.createrailwaysnavigator.time_display": "시간 표Ʞëȕ", + "enum.createrailwaysnavigator.time_display.description": "시간을 표시할 ë°©ëČ•ì„ êČ°ì •í•©ë‹ˆë‹€.", + "enum.createrailwaysnavigator.time_display.abs": "절대", + "enum.createrailwaysnavigator.time_display.info.abs": "정확한 시간을 띄웁니닀. (êž°ëłž)", + "enum.createrailwaysnavigator.time_display.eta": "상대", + "enum.createrailwaysnavigator.time_display.info.eta": "낚은 시간을 띄웁니닀.", + + "create.display_source.advanced_display": "êł êž‰ 전ꎑ판", + "gui.createrailwaysnavigator.display_source.advanced_display.train_name_width": "êž°ì°š 읎늄 폭", + "gui.createrailwaysnavigator.display_source.advanced_display.train_name_width.description": "픜셀 닚위. (êž°ëłž: 16)", + "gui.createrailwaysnavigator.display_source.advanced_display.platform_width": "타는 êłł 읎늄 폭", + "gui.createrailwaysnavigator.display_source.advanced_display.platform_width.description": "픜셀 닚위. (êž°ëłž: 자동)", + + "createrailwaysnavigator.moin": "moin" +} diff --git a/common/src/main/resources/assets/createrailwaysnavigator/lang/nl_nl.json b/common/src/main/resources/assets/createrailwaysnavigator/lang/nl_nl.json index d981ee89..58006b95 100644 --- a/common/src/main/resources/assets/createrailwaysnavigator/lang/nl_nl.json +++ b/common/src/main/resources/assets/createrailwaysnavigator/lang/nl_nl.json @@ -36,7 +36,6 @@ "block.createrailwaysnavigator.advanced_display_sloped.tooltip.behaviour1": "Opent een menu om het _display_ te _configureren_.", "block.createrailwaysnavigator.advanced_display.ber.not_in_service": "Buiten dienst!", - "block.createrailwaysnavigator.advanced_display.ber.shunting_trip": "Rangeertocht", "category.createrailwaysnavigator.crn": "Create Railways Navigator", "key.createrailwaysnavigator.route_overlay_options": "Toon Route Overlay Opties", @@ -107,16 +106,16 @@ "gui.createrailwaysnavigator.route_overview.after_journey": "U heeft %s bereikt. Bedankt voor het reizen en een fijne dag verder.", "gui.createrailwaysnavigator.route_overview.next_connections": "Volgende verbindingen", "gui.createrailwaysnavigator.route_overview.schedule_transfer": "Overstap", - "gui.createrailwaysnavigator.route_overview.train_canceled": "Trein geannuleerd", + "gui.createrailwaysnavigator.route_overview.train_cancelled": "Trein geannuleerd", "gui.createrailwaysnavigator.route_overview.train_cancellation_info": "Informatie over %s", - "gui.createrailwaysnavigator.route_overview.stop_canceled": "❌ Geannuleerd", + "gui.createrailwaysnavigator.route_overview.stop_cancelled": "❌ Geannuleerd", "gui.createrailwaysnavigator.route_overview.connection_endangered": "Verbinding in gevaar", "gui.createrailwaysnavigator.route_overview.connection_missed": "Verbinding gemist", - "gui.createrailwaysnavigator.route_overview.connection_canceled": "Trein geannuleerd", + "gui.createrailwaysnavigator.route_overview.connection_cancelled": "Trein geannuleerd", "gui.createrailwaysnavigator.route_overview.journey_interrupted": "Uw reis naar %s kan niet worden voortgezet.", "gui.createrailwaysnavigator.route_overview.connection_missed_info": "Door een treinvertraging heeft u uw aansluitende trein gemist. Zoek naar een alternatief in de navigator. Onze excuses voor het ongemak.", - "gui.createrailwaysnavigator.route_overview.train_canceled_info": "Informatie over %s: Deze trein is vandaag geannuleerd! Onze excuses voor het ongemak. Zoek naar een alternatief in de navigator.", - "gui.createrailwaysnavigator.route_overview.train_canceled_title": "Trein is geannuleerd", + "gui.createrailwaysnavigator.route_overview.train_cancelled_info": "Informatie over %s: Deze trein is vandaag geannuleerd! Onze excuses voor het ongemak. Zoek naar een alternatief in de navigator.", + "gui.createrailwaysnavigator.route_overview.train_cancelled_title": "Trein is geannuleerd", "gui.createrailwaysnavigator.route_overview.journey_interrupted_info": "Uw reis naar %s kan niet worden voortgezet. Zoek naar een alternatief in de navigator.", "gui.createrailwaysnavigator.route_overview.options": "Druk op %s voor opties.", "gui.createrailwaysnavigator.route_overview.notification.journey_begins.title": "Uw reis naar %s begint!", @@ -126,8 +125,8 @@ "gui.createrailwaysnavigator.route_overview.notification.platform_changed": "Uw trein in %s vertrekt vandaag vanaf platform %s.", "gui.createrailwaysnavigator.route_overview.notification.train_delayed.title": "%s: Aankomst %s vertraagd.", "gui.createrailwaysnavigator.route_overview.notification.train_delayed": "%s in plaats van %s in %s", - "gui.createrailwaysnavigator.route_overview.notification.train_canceled.title": "%s: Trein geannuleerd", - "gui.createrailwaysnavigator.route_overview.notification.train_canceled": "%s naar %s is vandaag geannuleerd.", + "gui.createrailwaysnavigator.route_overview.notification.train_cancelled.title": "%s: Trein geannuleerd", + "gui.createrailwaysnavigator.route_overview.notification.train_cancelled": "%s naar %s is vandaag geannuleerd.", "gui.createrailwaysnavigator.route_overview.notification.transfer.title": "Overstap komt eraan", "gui.createrailwaysnavigator.route_overview.notification.transfer": "Wissel naar %s → %s op platform %s", "gui.createrailwaysnavigator.route_overview.notification.transfer_with_platform": "Wissel naar %s → %s op platform %s", @@ -150,16 +149,16 @@ "gui.createrailwaysnavigator.global_settings.train_blacklist.title": "Trein Zwarte Lijst", "gui.createrailwaysnavigator.global_settings.train_blacklist.description": "Sluit treinen uit, bijv. vrachttreinen, speciale treinen, etc., zodat ze niet worden gebruikt in de routesuggesties.", - "gui.createrailwaysnavigator.alias_settings.title": "Treinstation Tag Instellingen", - "gui.createrailwaysnavigator.alias_settings.summary": "Bevat %s Spoor Stations", - "gui.createrailwaysnavigator.alias_settings.editor": "Laatst bewerkt door %s op %s", - "gui.createrailwaysnavigator.alias_settings.add.tooltip": "Nieuwe invoer maken", - "gui.createrailwaysnavigator.alias_settings.delete_alias.tooltip": "Tag verwijderen", - "gui.createrailwaysnavigator.alias_settings.delete_station.tooltip": "Station verwijderen", - "gui.createrailwaysnavigator.alias_settings.add_station.tooltip": "Station toevoegen", - "gui.createrailwaysnavigator.alias_settings.hint.station_name": "Spoor Station Naam", - "gui.createrailwaysnavigator.alias_settings.hint.platform": "Platform", - "gui.createrailwaysnavigator.alias_settings.enter_name": "Voer hier de naam in", + "gui.createrailwaysnavigator.station_tags.title": "Treinstation Tag Instellingen", + "gui.createrailwaysnavigator.station_tags.summary": "Bevat %s Spoor Stations", + "gui.createrailwaysnavigator.station_tags.editor": "Laatst bewerkt door %s op %s", + "gui.createrailwaysnavigator.station_tags.add.tooltip": "Nieuwe invoer maken", + "gui.createrailwaysnavigator.station_tags.delete_alias.tooltip": "Tag verwijderen", + "gui.createrailwaysnavigator.station_tags.delete_station.tooltip": "Station verwijderen", + "gui.createrailwaysnavigator.station_tags.add_station.tooltip": "Station toevoegen", + "gui.createrailwaysnavigator.station_tags.hint.station_name": "Spoor Station Naam", + "gui.createrailwaysnavigator.station_tags.hint.platform": "Platform", + "gui.createrailwaysnavigator.station_tags.enter_name": "Voer hier de naam in", "gui.createrailwaysnavigator.train_group_settings.title": "Trein Groep Instellingen", "gui.createrailwaysnavigator.train_group_settings.summary": "Bevat %s Treinen", diff --git a/common/src/main/resources/assets/createrailwaysnavigator/lang/pl_pl.json b/common/src/main/resources/assets/createrailwaysnavigator/lang/pl_pl.json index f2e7e617..1999b03d 100644 --- a/common/src/main/resources/assets/createrailwaysnavigator/lang/pl_pl.json +++ b/common/src/main/resources/assets/createrailwaysnavigator/lang/pl_pl.json @@ -36,7 +36,6 @@ "block.createrailwaysnavigator.advanced_display_sloped.tooltip.behaviour1": "OtwĂłrz menu _konfiguracji_ _wyƛwietlacza_.", "block.createrailwaysnavigator.advanced_display.ber.not_in_service": "Nieczynny!", - "block.createrailwaysnavigator.advanced_display.ber.shunting_trip": "Wycieczka manewrowa", "category.createrailwaysnavigator.crn": "Create Railways Navigator", "key.createrailwaysnavigator.route_overlay_options": "PokaĆŒ opcje nakƂadki trasy", @@ -107,16 +106,16 @@ "gui.createrailwaysnavigator.route_overview.after_journey": "DotarƂeƛ do %s. Dziękujemy za podrĂłĆŒ i ĆŒyczymy miƂego dnia.", "gui.createrailwaysnavigator.route_overview.next_connections": "Następne poƂączenia", "gui.createrailwaysnavigator.route_overview.schedule_transfer": "Przesiadka", - "gui.createrailwaysnavigator.route_overview.train_canceled": "Pociąg odwoƂany", + "gui.createrailwaysnavigator.route_overview.train_cancelled": "Pociąg odwoƂany", "gui.createrailwaysnavigator.route_overview.train_cancellation_info": "Informacja o %s", - "gui.createrailwaysnavigator.route_overview.stop_canceled": "❌ OdwoƂano", + "gui.createrailwaysnavigator.route_overview.stop_cancelled": "❌ OdwoƂano", "gui.createrailwaysnavigator.route_overview.connection_endangered": "PoƂączenie zagroĆŒone", "gui.createrailwaysnavigator.route_overview.connection_missed": "PoƂączenie przegapione", - "gui.createrailwaysnavigator.route_overview.connection_canceled": "Pociąg odwoƂany", + "gui.createrailwaysnavigator.route_overview.connection_cancelled": "Pociąg odwoƂany", "gui.createrailwaysnavigator.route_overview.journey_interrupted": "Twoja podrĂłĆŒ do %s jest niemoĆŒliwa do kontynuowania.", "gui.createrailwaysnavigator.route_overview.connection_missed_info": "W związku z opĂłĆŒnieniem pociągu, przegapiƂeƛ poƂączenie. Wyszukaj inne poƂączenie w Nawigatorze. Za utrudnienia przepraszamy.", - "gui.createrailwaysnavigator.route_overview.train_canceled_info": "Informacja o %s: Ten pociąg zostaƂ odwoƂany! Za utrudnienia przepraszamy. Wyszukaj inne poƂączenie w Nawigatorze.", - "gui.createrailwaysnavigator.route_overview.train_canceled_title": "Pociąg zostaƂ odwoƂany", + "gui.createrailwaysnavigator.route_overview.train_cancelled_info": "Informacja o %s: Ten pociąg zostaƂ odwoƂany! Za utrudnienia przepraszamy. Wyszukaj inne poƂączenie w Nawigatorze.", + "gui.createrailwaysnavigator.route_overview.train_cancelled_title": "Pociąg zostaƂ odwoƂany", "gui.createrailwaysnavigator.route_overview.journey_interrupted_info": "Twoja podrĂłĆŒ do %s jest niemoĆŒliwa do kontynuowania. Wyszukaj inne poƂączenie w Nawigatorze.", "gui.createrailwaysnavigator.route_overview.options": "Wciƛnij %s aby zobaczyć opcje.", "gui.createrailwaysnavigator.route_overview.notification.journey_begins.title": "Twoja podrĂłĆŒ do %s rozpoczyna się!", @@ -126,8 +125,8 @@ "gui.createrailwaysnavigator.route_overview.notification.platform_changed": "TwĂłj pociąg w %s wyrusza dzisiaj z peronu %s.", "gui.createrailwaysnavigator.route_overview.notification.train_delayed.title": "%s: Przyjazd %s opĂłĆŒniony.", "gui.createrailwaysnavigator.route_overview.notification.train_delayed": "%s zamiast %s w %s", - "gui.createrailwaysnavigator.route_overview.notification.train_canceled.title": "%s: Pociąg odwoƂany", - "gui.createrailwaysnavigator.route_overview.notification.train_canceled": "%s do %s zostaƂ odwoƂany.", + "gui.createrailwaysnavigator.route_overview.notification.train_cancelled.title": "%s: Pociąg odwoƂany", + "gui.createrailwaysnavigator.route_overview.notification.train_cancelled": "%s do %s zostaƂ odwoƂany.", "gui.createrailwaysnavigator.route_overview.notification.transfer.title": "Nadchodzi przesiadka", "gui.createrailwaysnavigator.route_overview.notification.transfer": "Przesiadka %s → %s na peronie %s", "gui.createrailwaysnavigator.route_overview.notification.transfer_with_platform": "Przesiadka %s → %s na peronie %s", @@ -150,16 +149,16 @@ "gui.createrailwaysnavigator.global_settings.train_blacklist.title": "Czarna lista pociągĂłw", "gui.createrailwaysnavigator.global_settings.train_blacklist.description": "Wyklucz pociągi, np. towarowe, specjalne, itp., aby nie pojawiaƂy się one w wynikach wyszukiwania.", - "gui.createrailwaysnavigator.alias_settings.title": "Ustawienia tagĂłw stacji", - "gui.createrailwaysnavigator.alias_settings.summary": "Zawiera %s stacji", - "gui.createrailwaysnavigator.alias_settings.editor": "Ostatnio edytowane przez %s o %s", - "gui.createrailwaysnavigator.alias_settings.add.tooltip": "UtwĂłrz nowy wpis", - "gui.createrailwaysnavigator.alias_settings.delete_alias.tooltip": "UsuƄ tag", - "gui.createrailwaysnavigator.alias_settings.delete_station.tooltip": "UsuƄ stację", - "gui.createrailwaysnavigator.alias_settings.add_station.tooltip": "Dodaj stację", - "gui.createrailwaysnavigator.alias_settings.hint.station_name": "Nazwa stacji", - "gui.createrailwaysnavigator.alias_settings.hint.platform": "Peron", - "gui.createrailwaysnavigator.alias_settings.enter_name": "WprowadĆș nazwę", + "gui.createrailwaysnavigator.station_tags.title": "Ustawienia tagĂłw stacji", + "gui.createrailwaysnavigator.station_tags.summary": "Zawiera %s stacji", + "gui.createrailwaysnavigator.station_tags.editor": "Ostatnio edytowane przez %s o %s", + "gui.createrailwaysnavigator.station_tags.add.tooltip": "UtwĂłrz nowy wpis", + "gui.createrailwaysnavigator.station_tags.delete_alias.tooltip": "UsuƄ tag", + "gui.createrailwaysnavigator.station_tags.delete_station.tooltip": "UsuƄ stację", + "gui.createrailwaysnavigator.station_tags.add_station.tooltip": "Dodaj stację", + "gui.createrailwaysnavigator.station_tags.hint.station_name": "Nazwa stacji", + "gui.createrailwaysnavigator.station_tags.hint.platform": "Peron", + "gui.createrailwaysnavigator.station_tags.enter_name": "WprowadĆș nazwę", "gui.createrailwaysnavigator.train_group_settings.title": "Ustawienia grupy pociągĂłw", "gui.createrailwaysnavigator.train_group_settings.summary": "Zawiera %s pociągĂłw", diff --git a/common/src/main/resources/assets/createrailwaysnavigator/lang/pt_pt.json b/common/src/main/resources/assets/createrailwaysnavigator/lang/pt_pt.json new file mode 100644 index 00000000..928591c4 --- /dev/null +++ b/common/src/main/resources/assets/createrailwaysnavigator/lang/pt_pt.json @@ -0,0 +1,250 @@ +{ + "advancement.createrailwaysnavigator.navigator": "Obrigado por viajar", + "advancement.createrailwaysnavigator.navigator.description": "Crie um navegador para procurar ligaçÔes ferroviĂĄrias de uma estação ferroviĂĄria para outra.", + "advancement.createrailwaysnavigator.advanced_display": "NĂŁo Ă© bem 4k", + "advancement.createrailwaysnavigator.advanced_display.description": "Atualize as suas placas de exibição para exibir mais informaçÔes e atĂ© mesmo colocĂĄ-las nos seus comboios.", + + "itemGroup.createrailwaysnavigator.tab": "Criar Navegador FerroviĂĄrio", + + "item.createrailwaysnavigator.navigator": "Criar Navegador FerroviĂĄrio", + "item.createrailwaysnavigator.navigator.tooltip.summary": "O _navegador_ mostra possĂ­veis _ligaçÔes de comboios_ com informaçÔes adicionais, como _escalas_, data_ em tempo _real e muito mais.", + + "block.createrailwaysnavigator.train_station_clock": "RelĂłgio da Estação de Comboios", + "block.createrailwaysnavigator.advanced_display_block": "Bloco de exibição avançada", + "block.createrailwaysnavigator.advanced_display_block.tooltip.summary": "Use-o em _comboios_, como um ecrĂŁ_ de destino _comboio ou _passageiro informaçÔes ecrĂŁ_, ou em _estaçÔes de comboios_ como _ecrĂŁs de plataformas_ melhorado que tambĂ©m mostra mais informaçÔes do que placas de exibição regulares.", + "block.createrailwaysnavigator.advanced_display_block.tooltip.condition1": "Quando lado direito do rato Ă© clicado usando uma chave inglesa", + "block.createrailwaysnavigator.advanced_display_block.tooltip.behaviour1": "Abra um menu para _configurar_ o _ecrĂŁ_.", + "block.createrailwaysnavigator.advanced_display": "Placa de exibição avançada", + "block.createrailwaysnavigator.advanced_display.tooltip.summary": "Use-o em _comboios_, como um ecrĂŁ_ de destino _comboio ou _passageiro informaçÔes ecrĂŁ_, ou em _estaçÔes de comboios_ como _ecrĂŁs de plataformas_ melhorado que tambĂ©m mostra mais informaçÔes do que placas de exibição regulares.", + "block.createrailwaysnavigator.advanced_display.tooltip.condition1": "Quando lado direito do rato Ă© clicado usando uma chave inglesa", + "block.createrailwaysnavigator.advanced_display.tooltip.behaviour1": "Abra um menu para _configurar_ o _ecrĂŁ_.", + "block.createrailwaysnavigator.advanced_display_small": "EcrĂŁ de exibição pequena avançada", + "block.createrailwaysnavigator.advanced_display_small.tooltip.summary": "Use-o em _comboios_, como um ecrĂŁ_ de destino _comboio ou _passageiro informaçÔes ecrĂŁ_, ou em _estaçÔes de comboios_ como _ecrĂŁs de plataformas_ melhorado que tambĂ©m mostra mais informaçÔes do que placas de exibição regulares.", + "block.createrailwaysnavigator.advanced_display_small.tooltip.condition1": "Quando lado direito do rato Ă© clicado usando uma chave inglesa", + "block.createrailwaysnavigator.advanced_display_small.tooltip.behaviour1": "Abra um menu para _configurar_ o _ecrĂŁ_.", + "block.createrailwaysnavigator.advanced_display_panel": "Painel de exibição avançada", + "block.createrailwaysnavigator.advanced_display_panel.tooltip.summary": "Use-o em _comboios_, como um ecrĂŁ_ de destino _comboio ou _passageiro informaçÔes ecrĂŁ_, ou em _estaçÔes de comboios_ como _ecrĂŁs de plataformas_ melhorado que tambĂ©m mostra mais informaçÔes do que placas de exibição regulares.", + "block.createrailwaysnavigator.advanced_display_panel.tooltip.condition1": "Quando lado direito do rato Ă© clicado usando uma chave inglesa", + "block.createrailwaysnavigator.advanced_display_panel.tooltip.behaviour1": "Abra um menu para _configurar_ o _ecrĂŁ_.", + "block.createrailwaysnavigator.advanced_display_half_panel": "Painel de exibição meio avançado", + "block.createrailwaysnavigator.advanced_display_half_panel.tooltip.summary": "Use-o em _comboios_, como um ecrĂŁ_ de destino _comboio ou _passageiro informaçÔes ecrĂŁ_, ou em _estaçÔes de comboios_ como _ecrĂŁs de plataformas_ melhorado que tambĂ©m mostra mais informaçÔes do que placas de exibição regulares.", + "block.createrailwaysnavigator.advanced_display_half_panel.tooltip.condition1": "Quando lado direito do rato Ă© clicado usando uma chave inglesa", + "block.createrailwaysnavigator.advanced_display_half_panel.tooltip.behaviour1": "Abra um menu para _configurar_ o _ecrĂŁ_.", + "block.createrailwaysnavigator.advanced_display_sloped": "EcrĂŁ avançado inclinado", + "block.createrailwaysnavigator.advanced_display_sloped.tooltip.summary": "Use-o em _comboios_, como um ecrĂŁ_ de destino _comboio ou _passageiro informaçÔes ecrĂŁ_, ou em _estaçÔes de comboios_ como _ecrĂŁs de plataformas_ melhorado que tambĂ©m mostra mais informaçÔes do que placas de exibição regulares.", + "block.createrailwaysnavigator.advanced_display_sloped.tooltip.condition1": "Quando lado direito do rato Ă© clicado usando uma chave inglesa", + "block.createrailwaysnavigator.advanced_display_sloped.tooltip.behaviour1": "Abra um menu para _configurar_ o _ecrĂŁ_.", + + "block.createrailwaysnavigator.advanced_display.ber.not_in_service": "Fora de Serviço!", + "block.createrailwaysnavigator.advanced_display.ber.shunting_trip": "Viagem sem passageiros", + + "category.createrailwaysnavigator.crn": "Criar Navegador FerroviĂĄrio", + "key.createrailwaysnavigator.route_overlay_options": "Mostrar opçÔes de sobreposição de rota", + + "enum.createrailwaysnavigator.overlay_position": "Posição do ecrĂŁ", + "enum.createrailwaysnavigator.overlay_position.info.top_left": "Canto superior esquerdo", + "enum.createrailwaysnavigator.overlay_position.info.top_right": "Canto superior direito", + "enum.createrailwaysnavigator.overlay_position.info.bottom_left": "Canto inferior esquerdo", + "enum.createrailwaysnavigator.overlay_position.info.bottom_right": "Canto inferior direito", + + "gui.createrailwaysnavigator.loading.title": "Baixando dados do servidor...", + + "gui.createrailwaysnavigator.overlay_settings.title": "ConfiguraçÔes de sobreposição de rota", + "gui.createrailwaysnavigator.route_overlay_settings.show_details": "Mostrar detalhes", + "gui.createrailwaysnavigator.route_overlay_settings.unpin": "Remover sobreposicao de rota", + "gui.createrailwaysnavigator.route_overlay_settings.narrator.on": "Anuncios do Narrador ativados.", + "gui.createrailwaysnavigator.route_overlay_settings.narrator.off": "Anuncios do Narrador desativados.", + "gui.createrailwaysnavigator.route_overlay_settings.notifications.on": "NotificaçÔes ativadas", + "gui.createrailwaysnavigator.route_overlay_settings.notifications.off": "NotificaçÔes desativadas", + "gui.createrailwaysnavigator.route_overlay_settings.scale": "Escala GUI", + "gui.createrailwaysnavigator.route_overlay_settings.narrator": "Anuncios do Narrador", + "gui.createrailwaysnavigator.route_overlay_settings.narrator.description": "O Narrador anuncia eventos importantes em sua jornada, por exemplo, a proxima parada, mudancas, etc.", + "gui.createrailwaysnavigator.route_overlay_settings.notifications": "NotificaçÔes", + "gui.createrailwaysnavigator.route_overlay_settings.notifications.description": "Receba notificacoes do sistema sobre eventos importantes em sua jornada, por exemplo, a proxima parada, alteracoes, etc.", + + "gui.createrailwaysnavigator.common.expand": "Mostrar detalhes", + "gui.createrailwaysnavigator.common.collapse": "Ocultar detalhes", + "gui.createrailwaysnavigator.common.go_back": "Voltar", + "gui.createrailwaysnavigator.common.go_to_top": "Rolar para cima", + "gui.createrailwaysnavigator.common.reset_defaults": "Redefinir para o padrĂŁo", + "gui.createrailwaysnavigator.common.count": "Contar", + "gui.createrailwaysnavigator.common.true": "Sim", + "gui.createrailwaysnavigator.common.false": "NĂŁo", + "gui.createrailwaysnavigator.common.search": "Procurar", + "gui.createrailwaysnavigator.common.auto": "AutomĂĄtico", + "gui.createrailwaysnavigator.common.server_error": "Erro do servidor durante a execucao da tarefa. Procure detalhes no console.", + + "gui.createrailwaysnavigator.navigator.title": "Create Railways Navigator", + "gui.createrailwaysnavigator.navigator.no_connections": "Nenhuma conexao encontrada.", + "gui.createrailwaysnavigator.navigator.not_searched": "Nada pesquisado ainda.", + "gui.createrailwaysnavigator.navigator.searching": "Procurar conexĂ”es ...", + "gui.createrailwaysnavigator.navigator.error_title": "Incapaz de navegar!", + "gui.createrailwaysnavigator.navigator.start_end_null": "O inĂ­cio ou o destino estao vazios.", + "gui.createrailwaysnavigator.navigator.start_end_equal": "InĂ­cio e destino sao iguais.", + "gui.createrailwaysnavigator.navigator.route_entry.connection_in_past": "❌ ConexĂŁo no passado", + "gui.createrailwaysnavigator.navigator.route_entry.transfer": "Trans.", + "gui.createrailwaysnavigator.navigator.route_entry.station_start": "de %s", + "gui.createrailwaysnavigator.navigator.global_settings.tooltip": "ConfiguraçÔes globais", + "gui.createrailwaysnavigator.navigator.search_settings.tooltip": "ConfiguraçÔes de pesquisa", + "gui.createrailwaysnavigator.navigator.search.tooltip": "Procurar", + "gui.createrailwaysnavigator.navigator.location.tooltip": "Estacao mais proxima da posicao atual", + "gui.createrailwaysnavigator.navigator.switch.tooltip": "Alternar campos", + + "gui.createrailwaysnavigator.route_details.title": "Detalhes da Rota", + "gui.createrailwaysnavigator.route_details.departure": "Partida em", + "gui.createrailwaysnavigator.route_details.next_transfer_time": "TransferĂȘncia em", + "gui.createrailwaysnavigator.route_details.transfer": "TransferĂȘncia", + "gui.createrailwaysnavigator.route_details.save_route": "Salvar conexĂŁo", + + "gui.createrailwaysnavigator.route_overview.title": "Detalhes da Rota", + "gui.createrailwaysnavigator.route_overview.journey_begins": "A sua jornada começa aqui! %s a %s, partida %s", + "gui.createrailwaysnavigator.route_overview.journey_begins_with_platform": "A sua jornada começa aqui! %s a %s, partida %s na plataforma %s", + "gui.createrailwaysnavigator.route_overview.train_details": "%s a %s", + "gui.createrailwaysnavigator.route_overview.next_stop": "PrĂłxima estação: %s", + "gui.createrailwaysnavigator.route_overview.transfer": "Mude para %s → %s", + "gui.createrailwaysnavigator.route_overview.transfer_with_platform": "Mude para %s → %s na plataforma %s", + "gui.createrailwaysnavigator.route_overview.journey_completed": "Jornada concluĂ­da", + "gui.createrailwaysnavigator.route_overview.after_journey": "Atingiu! %s. Obrigado por viajar e tenha um bom dia.", + "gui.createrailwaysnavigator.route_overview.next_connections": "PrĂłximas conexĂ”es", + "gui.createrailwaysnavigator.route_overview.schedule_transfer": "TransferĂȘncia", + "gui.createrailwaysnavigator.route_overview.train_cancelled": "Comboio cancelado", + "gui.createrailwaysnavigator.route_overview.train_cancellation_info": "Informação sobre %s", + "gui.createrailwaysnavigator.route_overview.stop_cancelled": "❌ Cancelado", + "gui.createrailwaysnavigator.route_overview.connection_endangered": "ConexĂŁo ameaçada", + "gui.createrailwaysnavigator.route_overview.connection_missed": "ConexĂŁo perdida", + "gui.createrailwaysnavigator.route_overview.connection_cancelled": "Comboio cancelado", + "gui.createrailwaysnavigator.route_overview.journey_interrupted": "A sua jornada para %s nĂŁo pode ser continuada.", + "gui.createrailwaysnavigator.route_overview.connection_missed_info": "Devido a um atraso no comboio, perdeu o comboio de ligacao. Procure uma alternativa no navegador. Pedimos desculpas pelo inconveniente.", + "gui.createrailwaysnavigator.route_overview.train_cancelled_info": "InformaçÔes sobre %s: Este comboio foi cancelado hoje! Pedimos desculpas pelo inconveniente. Procure uma alternativa no navegador.", + "gui.createrailwaysnavigator.route_overview.train_cancelled_title": "O comboio foi cancelado", + "gui.createrailwaysnavigator.route_overview.journey_interrupted_info": "A sua jornada para %s nĂŁo pode ser continuada. Procure uma alternativa no navegador.", + "gui.createrailwaysnavigator.route_overview.options": "Pressione %s para obter opçÔes.", + "gui.createrailwaysnavigator.route_overview.notification.journey_begins.title": "Sua jornada para %s começa!", + "gui.createrailwaysnavigator.route_overview.notification.journey_begins": "%s a %s, partida %s", + "gui.createrailwaysnavigator.route_overview.notification.journey_begins_with_platform": "%s a %s, partida %s da plataforma %s", + "gui.createrailwaysnavigator.route_overview.notification.platform_changed.title": "Sua plataforma mudou!", + "gui.createrailwaysnavigator.route_overview.notification.platform_changed": "O seu comboio em %s sai da plataforma %s hoje.", + "gui.createrailwaysnavigator.route_overview.notification.train_delayed.title": "%s: Chegada %s atrasada.", + "gui.createrailwaysnavigator.route_overview.notification.train_delayed": "%s em vez de %s em %s", + "gui.createrailwaysnavigator.route_overview.notification.train_cancelled.title": "%s: Comboio cancelado", + "gui.createrailwaysnavigator.route_overview.notification.train_cancelled": "%s a %s foi cancelado hoje.", + "gui.createrailwaysnavigator.route_overview.notification.transfer.title": "TransferĂȘncia esta chegando", + "gui.createrailwaysnavigator.route_overview.notification.transfer": "Mudar para %s → %s na plataforma %s", + "gui.createrailwaysnavigator.route_overview.notification.transfer_with_platform": "Mudar para %s → %s na plataforma %s", + "gui.createrailwaysnavigator.route_overview.notification.connection_endangered.title": "A sua conexĂŁo esta ameaçada!", + "gui.createrailwaysnavigator.route_overview.notification.connection_endangered": "VocĂȘ provavelmente nĂŁo conseguirĂĄ alcancar %s a %s.", + "gui.createrailwaysnavigator.route_overview.notification.connection_missed.title": "Conexao perdida", + "gui.createrailwaysnavigator.route_overview.notification.connection_missed": "VocĂȘ perdeu o comboio de conexĂŁo %s para %s.", + "gui.createrailwaysnavigator.route_overview.notification.journey_completed.title": "Chegou ao seu destino!", + "gui.createrailwaysnavigator.route_overview.notification.journey_completed": "Obrigado por viajar e tenha um bom dia", + "gui.createrailwaysnavigator.route_overview.date": "Dia %s, %s", + + "gui.createrailwaysnavigator.global_settings.title": "ConfiguraçÔes globais", + "gui.createrailwaysnavigator.global_settings.option.tooltip": "Clique para editar", + "gui.createrailwaysnavigator.global_settings.option_alias.title": "Tags da estaçao de comboio", + "gui.createrailwaysnavigator.global_settings.option_alias.description": "Defina tags de estacao para ameacar estacoes multiplataforma (por exemplo, MyStation 1, MyStation 2, ... ) como uma unica estacao (por exemplo, MyStation) com nomes personalizados.", + "gui.createrailwaysnavigator.global_settings.option_blacklist.title": "Lista negra da estacao de comboio", + "gui.createrailwaysnavigator.global_settings.option_blacklist.description": "Excluir estaçÔes de trilha que nao devem aparecer nos resultados da navegacao. Essas estacoes serao ignoradas ao gerar rotas.", + "gui.createrailwaysnavigator.global_settings.train_group.title": "Grupos de comboios", + "gui.createrailwaysnavigator.global_settings.train_group.description": "Criar grupos de comboios para organizar todos os comboios (por exemplo, servicos regionais, servicos de longa distancia, ... ). Os usuarios podem decidir quais grupos desejam usar.", + "gui.createrailwaysnavigator.global_settings.train_blacklist.title": "Lista negra de comboios", + "gui.createrailwaysnavigator.global_settings.train_blacklist.description": "Excluir comboios, por exemplo, comboios de mercadorias, comboios especiais, etc., para que nao sejam utilizados nas sugestoes de rota.", + + "gui.createrailwaysnavigator.station_tags.title": "ConfiguraçÔes de tags da estação de comboios", + "gui.createrailwaysnavigator.station_tags.summary": "ContĂ©m %s Track Stations", + "gui.createrailwaysnavigator.station_tags.editor": "Última edição por %s em %s", + "gui.createrailwaysnavigator.station_tags.add.tooltip": "Criar nova entrada", + "gui.createrailwaysnavigator.station_tags.delete_alias.tooltip": "Excluir Tag", + "gui.createrailwaysnavigator.station_tags.delete_station.tooltip": "Remover estaçãon", + "gui.createrailwaysnavigator.station_tags.add_station.tooltip": "Adicionar estaçÔes", + "gui.createrailwaysnavigator.station_tags.hint.station_name": "Nome de estação de trilha", + "gui.createrailwaysnavigator.station_tags.hint.platform": "Plataforma", + "gui.createrailwaysnavigator.station_tags.enter_name": "Digite o nome aqui", + + "gui.createrailwaysnavigator.train_group_settings.title": "ConfiguraçÔes do grupo de Comboio", + "gui.createrailwaysnavigator.train_group_settings.summary": "ContĂ©m %s Comboios", + "gui.createrailwaysnavigator.train_group_settings.editor": "Ultima edição por %s em %s", + "gui.createrailwaysnavigator.train_group_settings.delete_alias.tooltip": "Delete Group", + "gui.createrailwaysnavigator.train_group_settings.delete_station.tooltip": "Remover comboio", + "gui.createrailwaysnavigator.train_group_settings.add_station.tooltip": "Adicionar Comboio", + + "gui.createrailwaysnavigator.blacklist.title": "Lista negra da estacao de comboios", + "gui.createrailwaysnavigator.blacklist.add.tooltip": "Adicionar a lista negra", + "gui.createrailwaysnavigator.blacklist.delete.tooltip": "Remover da lista negra", + + "gui.createrailwaysnavigator.train_blacklist.title": "Lista negra de comboios", + "gui.createrailwaysnavigator.train_blacklist.add.tooltip": "Adicionar a lista negra", + "gui.createrailwaysnavigator.train_blacklist.delete.tooltip": "Remover da lista negra", + + "gui.createrailwaysnavigator.search_settings.title": "ConfiguraçÔes de pesquisa", + "gui.createrailwaysnavigator.search_settings.transfer_time": "Tempo minimo de transferĂȘncia", + "gui.createrailwaysnavigator.search_settings.transfer_time.description": "O tempo minimo que deve estar disponivel para mudar de comboio. (1h ~ 50 segundos da vida real)", + "gui.createrailwaysnavigator.search_settings.train_groups": "Filtro de categoria de comboio", + "gui.createrailwaysnavigator.search_settings.train_groups.description": "Decida quais comboios de quais categorias de trem voce deseja usar.", + "gui.createrailwaysnavigator.search_settings.train_groups.overview": "%s categorias selecionadas", + "gui.createrailwaysnavigator.search_settings.train_groups.overview.all": "Todo", + "gui.createrailwaysnavigator.search_settings.train_groups.overview.none": "NĂŁo", + "gui.createrailwaysnavigator.search_settings.train_groups.tooltip.reset": "Redefinir filtro", + + "gui.createrailwaysnavigator.new_text_entry.add.tooltip": "Adicionar", + + "gui.createrailwaysnavigator.time": "Tempo: %s", + "gui.createrailwaysnavigator.time.now": "agora", + "gui.createrailwaysnavigator.time_format.dhm": "%s dias %s hrs. %s min.", + "gui.createrailwaysnavigator.time_format.hm": "%s hrs. %s min.", + "gui.createrailwaysnavigator.time_format.m": "%s min.", + + "gui.createrailwaysnavigator.platform": "Plataforma", + "gui.createrailwaysnavigator.departure": "Partida", + "gui.createrailwaysnavigator.destination": "Destino", + "gui.createrailwaysnavigator.line": "Linha", + "gui.createrailwaysnavigator.following_trains": "Seguintes Comboios:", + "gui.createrailwaysnavigator.via": "Via", + + "gui.createrailwaysnavigator.advanced_display_settings.title": "ConfiguraçÔes avançadas de exibição", + "gui.createrailwaysnavigator.advanced_display_settings.display_type": "Tipo de exibição", + "gui.createrailwaysnavigator.advanced_display_settings.display_type.description": "Determina as informaçÔes exibidas.", + "gui.createrailwaysnavigator.advanced_display_settings.info_type": "Tipo de informação", + "gui.createrailwaysnavigator.advanced_display_settings.info_type.description": "Determina o quĂŁo detalhadas sao as informaçÔes.", + "gui.createrailwaysnavigator.advanced_display_settings.double_sided": "Dupla face", + + "enum.createrailwaysnavigator.display_info_type": "Tipo de InformaçÔes de Exibição", + "enum.createrailwaysnavigator.display_info_type.description": "Determina a quantidade de informaçÔes que devem ser exibidas no quadro de exibição.", + "enum.createrailwaysnavigator.display_info_type.simple": "Simples", + "enum.createrailwaysnavigator.display_info_type.info.simple": "A tela mostrara apenas as informacoes mais importantes, sem detalhes adicionais.", + "enum.createrailwaysnavigator.display_info_type.detailed": "Detalhado", + "enum.createrailwaysnavigator.display_info_type.info.detailed": "As informaçÔes mais importantes com alguns detalhes serĂŁo mostradas, como velocidade do comboio, escalas, etc.", + "enum.createrailwaysnavigator.display_info_type.informative": "Informativo", + "enum.createrailwaysnavigator.display_info_type.info.informative": "Mostra todas as informacoes que poderiam ser interessantes e as exibe em um layout sofisticado. Nao recomendado para telas pequenas, pois o texto pode ficar muito pequeno.", + + "enum.createrailwaysnavigator.display_type": "Tipo de exibição", + "enum.createrailwaysnavigator.display_type.description": "O tipo de exibicao que depende de sua finalidade.", + "enum.createrailwaysnavigator.display_type.train_destination": "Destino do Comboio", + "enum.createrailwaysnavigator.display_type.info.train_destination": "Inteded para ser usado fora dos trens, pois mostra informaçÔes sobre o prĂłprio trem, como o nome, destino e (se selecionado) escalas e outras informaçÔes.", + "enum.createrailwaysnavigator.display_type.passenger_information": "InformaçÔes ao Passageiro", + "enum.createrailwaysnavigator.display_type.info.passenger_information": "Representa as telas encontradas dentro dos comboios. Essas exibiçÔes mostrarao a proxima estação, a direção de saĂ­da e (se selecionada) a velocidade do comboio e uma visĂŁo geral da rota.", + "enum.createrailwaysnavigator.display_type.platform": "Exibição da plataforma", + "enum.createrailwaysnavigator.display_type.info.platform": "Esses displays devem ser usados nas plataformas das estaçÔes de trem e mostram os prĂłximos comboios com detalhes adicionais, se selecionados. NĂŁo pode ser usado em Comboios!", + + "enum.createrailwaysnavigator.side": "Lado", + "enum.createrailwaysnavigator.side.description": "O lado do bloco onde as informaçÔes devem ser renderizadas.", + "enum.createrailwaysnavigator.side.front": "Parte Frontal", + "enum.createrailwaysnavigator.side.info.front": "As informaçÔes serĂŁo renderizadas apenas na parte frontal. Esse Ă© o comportamento padrĂŁo", + "enum.createrailwaysnavigator.side.both": "Ambos os lados", + "enum.createrailwaysnavigator.side.info.both": "As informaçÔes serao prestadas em ambos os lados.", + + "enum.createrailwaysnavigator.time_display": "Exibição de tempo", + "enum.createrailwaysnavigator.time_display.description": "Determina como a hora deve ser exibida.", + "enum.createrailwaysnavigator.time_display.abs": "ABS", + "enum.createrailwaysnavigator.time_display.info.abs": "ABS (absoluto)", + "enum.createrailwaysnavigator.time_display.eta": "ETA", + "enum.createrailwaysnavigator.time_display.info.eta": "ETA (hora prevista de chegada)", + + "create.display_source.advanced_display": "Monitores Avancados", + "gui.createrailwaysnavigator.display_source.advanced_display.train_name_width": "Largura da coluna do nome do comboio", + "gui.createrailwaysnavigator.display_source.advanced_display.train_name_width.description": "em pixeis de bloco. (PadrĂŁo: 16)", + "gui.createrailwaysnavigator.display_source.advanced_display.platform_width": "Largura da coluna da plataforma", + "gui.createrailwaysnavigator.display_source.advanced_display.platform_width.description": "em pĂ­xeis de bloco (PadrĂŁo: Auto)", + + "createrailwaysnavigator.moin": "moin" +} diff --git a/common/src/main/resources/assets/createrailwaysnavigator/lang/ru_ru.json b/common/src/main/resources/assets/createrailwaysnavigator/lang/ru_ru.json index aa3c4a38..0a2d7a74 100644 --- a/common/src/main/resources/assets/createrailwaysnavigator/lang/ru_ru.json +++ b/common/src/main/resources/assets/createrailwaysnavigator/lang/ru_ru.json @@ -36,7 +36,6 @@ "block.createrailwaysnavigator.advanced_display_sloped.tooltip.behaviour1": "ОтĐșрыĐČаДт ĐŒĐ”ĐœŃŽ ĐŽĐ»Ń _ĐœĐ°ŃŃ‚Ń€ĐŸĐčĐșĐž ĐŽĐžŃĐżĐ»Đ”Ń._", "block.createrailwaysnavigator.advanced_display.ber.not_in_service": "Đ’Ń‹ŃˆĐ”Đ» Оз ŃŃ‚Ń€ĐŸŃ!", - "block.createrailwaysnavigator.advanced_display.ber.shunting_trip": "ĐŸĐŸĐ”Đ·ĐŽĐșĐ° бДз ĐżĐ°ŃŃĐ°Đ¶ĐžŃ€ĐŸĐČ", "category.createrailwaysnavigator.crn": "Create: НаĐČĐžĐłĐ°Ń‚ĐŸŃ€ Đ¶Đ”Đ»Đ”Đ·ĐœŃ‹Ń… ĐŽĐŸŃ€ĐŸĐł", "key.createrailwaysnavigator.route_overlay_options": "ĐŸĐŸĐșĐ°Đ·Đ°Ń‚ŃŒ ĐżĐ°Ń€Đ°ĐŒĐ”Ń‚Ń€Ń‹ ĐœĐ°Đ»ĐŸĐ¶Đ”ĐœĐžŃ ĐŒĐ°Ń€ŃˆŃ€ŃƒŃ‚Đ°", @@ -107,16 +106,16 @@ "gui.createrailwaysnavigator.route_overview.after_journey": "Вы ĐŽĐŸŃŃ‚ĐžĐłĐ»Đž ŃŃ‚Đ°ĐœŃ†ĐžĐž %s. ĐĄĐżĐ°ŃĐžĐ±ĐŸ Đ·Đ° ĐżĐŸĐ”Đ·ĐŽĐșу.", "gui.createrailwaysnavigator.route_overview.next_connections": "ĐŸĐ”Ń€Đ”ŃĐ°ĐŽĐșĐž ĐœĐ°", "gui.createrailwaysnavigator.route_overview.schedule_transfer": "ĐŸĐ”Ń€Đ”ŃĐ°ĐŽĐșĐ°", - "gui.createrailwaysnavigator.route_overview.train_canceled": "ĐŸĐŸĐ”Đ·ĐŽ ĐŸŃ‚ĐŒĐ”ĐœŃ‘Đœ", + "gui.createrailwaysnavigator.route_overview.train_cancelled": "ĐŸĐŸĐ”Đ·ĐŽ ĐŸŃ‚ĐŒĐ”ĐœŃ‘Đœ", "gui.createrailwaysnavigator.route_overview.train_cancellation_info": "Đ˜ĐœŃ„ĐŸŃ€ĐŒĐ°Ń†ĐžŃ ĐŸ %s", - "gui.createrailwaysnavigator.route_overview.stop_canceled": "❌ ĐŸŃ‚ĐŒĐ”ĐœĐ”ĐœĐŸ", + "gui.createrailwaysnavigator.route_overview.stop_cancelled": "❌ ĐŸŃ‚ĐŒĐ”ĐœĐ”ĐœĐŸ", "gui.createrailwaysnavigator.route_overview.connection_endangered": "ĐĄĐŸĐ”ĐŽĐžĐœĐ”ĐœĐžĐ” ĐżĐŸĐŽ ŃƒĐłŃ€ĐŸĐ·ĐŸĐč", "gui.createrailwaysnavigator.route_overview.connection_missed": "ĐĄĐŸĐ”ĐŽĐžĐœĐ”ĐœĐžĐ” ĐżĐŸŃ‚Đ”Ń€ŃĐœĐŸ", - "gui.createrailwaysnavigator.route_overview.connection_canceled": "ĐŸĐŸĐ”Đ·ĐŽ ĐŸŃ‚ĐŒĐ”ĐœŃ‘Đœ", + "gui.createrailwaysnavigator.route_overview.connection_cancelled": "ĐŸĐŸĐ”Đ·ĐŽ ĐŸŃ‚ĐŒĐ”ĐœŃ‘Đœ", "gui.createrailwaysnavigator.route_overview.journey_interrupted": "Ваша ĐżĐŸĐ”Đ·ĐŽĐșĐ° Đș %s ĐœĐ” ĐŒĐŸĐ¶Đ”Ń‚ Đ±Ń‹Ń‚ŃŒ ĐżŃ€ĐŸĐŽĐŸĐ»Đ¶Đ”ĐœĐ°.", "gui.createrailwaysnavigator.route_overview.connection_missed_info": "Из-Đ·Đ° заЎДржĐșĐž ĐżĐŸĐ”Đ·ĐŽĐ° ĐČы ĐŸĐżĐŸĐ·ĐŽĐ°Đ»Đž ĐœĐ° стыĐșĐŸĐČĐŸŃ‡ĐœŃ‹Đč ĐżĐŸĐ”Đ·ĐŽ. НаĐčЎОтД Đ°Đ»ŃŒŃ‚Đ”Ń€ĐœĐ°Ń‚ĐžĐČĐœŃ‹Đč ĐČĐ°Ń€ĐžĐ°ĐœŃ‚ ĐČ ĐœĐ°ĐČĐžĐłĐ°Ń‚ĐŸŃ€Đ”. ĐŸŃ€ĐžĐœĐŸŃĐžĐŒ ОзĐČĐžĐœĐ”ĐœĐžŃ Đ·Đ° ĐŽĐŸŃŃ‚Đ°ĐČĐ»Đ”ĐœĐœŃ‹Đ” ĐœĐ”ŃƒĐŽĐŸĐ±ŃŃ‚ĐČĐ°.", - "gui.createrailwaysnavigator.route_overview.train_canceled_info": "Đ˜ĐœŃ„ĐŸŃ€ĐŒĐ°Ń†ĐžŃ ĐŸ %s: Đ­Ń‚ĐŸŃ‚ ĐżĐŸĐ”Đ·ĐŽ ŃĐ”ĐłĐŸĐŽĐœŃ ĐŸŃ‚ĐŒĐ”ĐœĐ”Đœ! ĐŸŃ€ĐžĐœĐŸŃĐžĐŒ ОзĐČĐžĐœĐ”ĐœĐžŃ Đ·Đ° ĐŽĐŸŃŃ‚Đ°ĐČĐ»Đ”ĐœĐœŃ‹Đ” ĐœĐ”ŃƒĐŽĐŸĐ±ŃŃ‚ĐČĐ°. НаĐčЎОтД Đ°Đ»ŃŒŃ‚Đ”Ń€ĐœĐ°Ń‚ĐžĐČĐœŃ‹Đč ĐČĐ°Ń€ĐžĐ°ĐœŃ‚ ĐČ ĐœĐ°ĐČĐžĐłĐ°Ń‚ĐŸŃ€Đ”.", - "gui.createrailwaysnavigator.route_overview.train_canceled_title": "ĐŸĐŸĐ”Đ·ĐŽ был ĐŸŃ‚ĐŒĐ”ĐœĐ”Đœ", + "gui.createrailwaysnavigator.route_overview.train_cancelled_info": "Đ˜ĐœŃ„ĐŸŃ€ĐŒĐ°Ń†ĐžŃ ĐŸ %s: Đ­Ń‚ĐŸŃ‚ ĐżĐŸĐ”Đ·ĐŽ ŃĐ”ĐłĐŸĐŽĐœŃ ĐŸŃ‚ĐŒĐ”ĐœĐ”Đœ! ĐŸŃ€ĐžĐœĐŸŃĐžĐŒ ОзĐČĐžĐœĐ”ĐœĐžŃ Đ·Đ° ĐŽĐŸŃŃ‚Đ°ĐČĐ»Đ”ĐœĐœŃ‹Đ” ĐœĐ”ŃƒĐŽĐŸĐ±ŃŃ‚ĐČĐ°. НаĐčЎОтД Đ°Đ»ŃŒŃ‚Đ”Ń€ĐœĐ°Ń‚ĐžĐČĐœŃ‹Đč ĐČĐ°Ń€ĐžĐ°ĐœŃ‚ ĐČ ĐœĐ°ĐČĐžĐłĐ°Ń‚ĐŸŃ€Đ”.", + "gui.createrailwaysnavigator.route_overview.train_cancelled_title": "ĐŸĐŸĐ”Đ·ĐŽ был ĐŸŃ‚ĐŒĐ”ĐœĐ”Đœ", "gui.createrailwaysnavigator.route_overview.journey_interrupted_info": "Вашу ĐżĐŸĐ”Đ·ĐŽĐșу Đș %s ĐœĐ”ĐČĐŸĐ·ĐŒĐŸĐ¶ĐœĐŸ ĐżŃ€ĐŸĐŽĐŸĐ»Đ¶ĐžŃ‚ŃŒ. Đ’Ń‹ĐżĐŸĐ»ĐœĐžŃ‚Đ” ĐżĐŸĐžŃĐș Đ°Đ»ŃŒŃ‚Đ”Ń€ĐœĐ°Ń‚ĐžĐČĐœĐŸĐłĐŸ ĐŒĐ°Ń€ŃˆŃ€ŃƒŃ‚Đ° ĐČ ĐœĐ°ĐČĐžĐłĐ°Ń‚ĐŸŃ€Đ”.", "gui.createrailwaysnavigator.route_overview.options": "ĐĐ°Đ¶ĐŒĐžŃ‚Đ” %s ĐŽĐ»Ń ĐŸĐżŃ†ĐžĐč", "gui.createrailwaysnavigator.route_overview.notification.journey_begins.title": "Ваша ĐżĐŸĐ”Đ·ĐŽĐșĐ° Đș %s ĐœĐ°Ń‡ĐžĐœĐ°Đ”Ń‚ŃŃ!", @@ -126,8 +125,8 @@ "gui.createrailwaysnavigator.route_overview.notification.platform_changed": "Ваш ĐżĐŸĐ”Đ·ĐŽ ĐČ %s ĐŸŃ‚Ń…ĐŸĐŽĐžŃ‚ ĐŸŃ‚ ĐżĐ»Đ°Ń‚Ń„ĐŸŃ€ĐŒŃ‹ %s ŃĐ”ĐłĐŸĐŽĐœŃ.", "gui.createrailwaysnavigator.route_overview.notification.train_delayed.title": "%s: прОбытОД %s заЎДржĐșĐ°.", "gui.createrailwaysnavigator.route_overview.notification.train_delayed": "%s ĐČĐŒĐ”ŃŃ‚ĐŸ %s ĐœĐ° %s", - "gui.createrailwaysnavigator.route_overview.notification.train_canceled.title": "%s: ĐŸĐŸĐ”Đ·ĐŽ ĐŸŃ‚ĐŒĐ”ĐœŃ‘Đœ", - "gui.createrailwaysnavigator.route_overview.notification.train_canceled": "%s ĐČ ŃŃ‚ĐŸŃ€ĐŸĐœŃƒ %s ĐŸŃ‚ĐŒĐ”ĐœŃ‘Đœ ŃĐ”ĐłĐŸĐŽĐœŃ.", + "gui.createrailwaysnavigator.route_overview.notification.train_cancelled.title": "%s: ĐŸĐŸĐ”Đ·ĐŽ ĐŸŃ‚ĐŒĐ”ĐœŃ‘Đœ", + "gui.createrailwaysnavigator.route_overview.notification.train_cancelled": "%s ĐČ ŃŃ‚ĐŸŃ€ĐŸĐœŃƒ %s ĐŸŃ‚ĐŒĐ”ĐœŃ‘Đœ ŃĐ”ĐłĐŸĐŽĐœŃ.", "gui.createrailwaysnavigator.route_overview.notification.transfer.title": "ĐŸĐ”Ń€Đ”ŃĐ°ĐŽĐșĐ° сĐșĐŸŃ€ĐŸ", "gui.createrailwaysnavigator.route_overview.notification.transfer": "Đ˜Đ·ĐŒĐ”ĐœĐžŃ‚ŃŒ %s → %s ĐœĐ° ĐżĐ»Đ°Ń‚Ń„ĐŸŃ€ĐŒĐ” %s", "gui.createrailwaysnavigator.route_overview.notification.transfer_with_platform": "Đ˜Đ·ĐŒĐ”ĐœĐžŃ‚ŃŒ %s → %s ĐœĐ° ĐżĐ»Đ°Ń‚Ń„ĐŸŃ€ĐŒĐ” %s", @@ -150,16 +149,16 @@ "gui.createrailwaysnavigator.global_settings.train_blacklist.title": "Đ§Ń‘Ń€ĐœŃ‹Đč ŃĐżĐžŃĐŸĐș ĐżĐŸĐ”Đ·ĐŽĐŸĐČ", "gui.createrailwaysnavigator.global_settings.train_blacklist.description": "ИсĐșлючОтД ĐżĐŸĐ”Đ·ĐŽĐ°, ĐœĐ°ĐżŃ€ĐžĐŒĐ”Ń€, ĐłŃ€ŃƒĐ·ĐŸĐČŃ‹Đ”, ŃĐżĐ”Ń†ĐžĐ°Đ»ŃŒĐœŃ‹Đ” Đž т.ĐŽ., Ń‡Ń‚ĐŸĐ±Ń‹ ĐŸĐœĐž ĐœĐ” ĐžŃĐżĐŸĐ»ŃŒĐ·ĐŸĐČĐ°Đ»ĐžŃŃŒ ĐČ ĐżŃ€Đ”ĐŽĐ»ĐŸĐ¶Đ”ĐœĐžŃŃ… ĐżĐŸ ĐŒĐ°Ń€ŃˆŃ€ŃƒŃ‚Ńƒ.", - "gui.createrailwaysnavigator.alias_settings.title": "ĐĐ°ŃŃ‚Ń€ĐŸĐčĐșĐž Ń‚Đ”ĐłĐŸĐČ ŃŃ‚Đ°ĐœŃ†ĐžĐč", - "gui.createrailwaysnavigator.alias_settings.summary": "ĐšĐŸĐ»ĐžŃ‡Đ”ŃŃ‚ĐČĐŸ ŃŃ‚Đ°ĐœŃ†ĐžĐč: %s", - "gui.createrailwaysnavigator.alias_settings.editor": "Đ˜Đ·ĐŒĐ”ĐœŃ‘Đœ %s ĐČ %s", - "gui.createrailwaysnavigator.alias_settings.add.tooltip": "ĐĄĐŸĐ·ĐŽĐ°Ń‚ŃŒ ĐœĐŸĐČую Đ·Đ°ĐżĐžŃŃŒ", - "gui.createrailwaysnavigator.alias_settings.delete_alias.tooltip": "ĐŁĐŽĐ°Đ»ĐžŃ‚ŃŒ тДг", - "gui.createrailwaysnavigator.alias_settings.delete_station.tooltip": "ĐŁĐŽĐ°Đ»ĐžŃ‚ŃŒ ŃŃ‚Đ°ĐœŃ†ĐžŃŽ", - "gui.createrailwaysnavigator.alias_settings.add_station.tooltip": "Đ”ĐŸĐ±Đ°ĐČоть ŃŃ‚Đ°ĐœŃ†ĐžŃŽ", - "gui.createrailwaysnavigator.alias_settings.hint.station_name": "Đ˜ĐŒŃ ŃŃ‚Đ°ĐœŃ†ĐžĐž", - "gui.createrailwaysnavigator.alias_settings.hint.platform": "ĐŸĐ»Đ°Ń‚Ń„ĐŸŃ€ĐŒĐ°", - "gui.createrailwaysnavigator.alias_settings.enter_name": "ВĐČДстО ĐžĐŒŃ Đ·ĐŽĐ”ŃŃŒ", + "gui.createrailwaysnavigator.station_tags.title": "ĐĐ°ŃŃ‚Ń€ĐŸĐčĐșĐž Ń‚Đ”ĐłĐŸĐČ ŃŃ‚Đ°ĐœŃ†ĐžĐč", + "gui.createrailwaysnavigator.station_tags.summary": "ĐšĐŸĐ»ĐžŃ‡Đ”ŃŃ‚ĐČĐŸ ŃŃ‚Đ°ĐœŃ†ĐžĐč: %s", + "gui.createrailwaysnavigator.station_tags.editor": "Đ˜Đ·ĐŒĐ”ĐœŃ‘Đœ %s ĐČ %s", + "gui.createrailwaysnavigator.station_tags.add.tooltip": "ĐĄĐŸĐ·ĐŽĐ°Ń‚ŃŒ ĐœĐŸĐČую Đ·Đ°ĐżĐžŃŃŒ", + "gui.createrailwaysnavigator.station_tags.delete_alias.tooltip": "ĐŁĐŽĐ°Đ»ĐžŃ‚ŃŒ тДг", + "gui.createrailwaysnavigator.station_tags.delete_station.tooltip": "ĐŁĐŽĐ°Đ»ĐžŃ‚ŃŒ ŃŃ‚Đ°ĐœŃ†ĐžŃŽ", + "gui.createrailwaysnavigator.station_tags.add_station.tooltip": "Đ”ĐŸĐ±Đ°ĐČоть ŃŃ‚Đ°ĐœŃ†ĐžŃŽ", + "gui.createrailwaysnavigator.station_tags.hint.station_name": "Đ˜ĐŒŃ ŃŃ‚Đ°ĐœŃ†ĐžĐž", + "gui.createrailwaysnavigator.station_tags.hint.platform": "ĐŸĐ»Đ°Ń‚Ń„ĐŸŃ€ĐŒĐ°", + "gui.createrailwaysnavigator.station_tags.enter_name": "ВĐČДстО ĐžĐŒŃ Đ·ĐŽĐ”ŃŃŒ", "gui.createrailwaysnavigator.train_group_settings.title": "НаĐčŃŃ‚Ń€ĐŸĐčĐșĐž групп ĐżĐŸĐ”Đ·ĐŽĐŸĐČ", "gui.createrailwaysnavigator.train_group_settings.summary": "ВĐșлючаДт %s TĐżĐŸĐ”Đ·ĐŽĐŸĐČ", diff --git a/common/src/main/resources/assets/createrailwaysnavigator/lang/sv_se.json b/common/src/main/resources/assets/createrailwaysnavigator/lang/sv_se.json new file mode 100644 index 00000000..57b9c4cf --- /dev/null +++ b/common/src/main/resources/assets/createrailwaysnavigator/lang/sv_se.json @@ -0,0 +1,250 @@ +{ + "advancement.createrailwaysnavigator.navigator": "Tack för att du reser med oss!", + "advancement.createrailwaysnavigator.navigator.description": "Tillverka en Navigator för att söka efter tĂ„gförbildelser frĂ„n en station till en annan.", + "advancement.createrailwaysnavigator.advanced_display": "Inte riktigt 4k", + "advancement.createrailwaysnavigator.advanced_display.description": "Upgradera dina displaytavlor till att visa mer information som Ă€ven funkar i tĂ„g.", + + "itemGroup.createrailwaysnavigator.tab": "Create Railways Navigator", + + "item.createrailwaysnavigator.navigator": "Create Railways Navigator", + "item.createrailwaysnavigator.navigator.tooltip.summary": "The _navigator_ shows possible _train connections_ with additional information such as _stopovers_, _real-time data_ and more.", + + "block.createrailwaysnavigator.train_station_clock": "TĂ„stationsklocka", + "block.createrailwaysnavigator.advanced_display_block": "Avancerat Displayblock", + "block.createrailwaysnavigator.advanced_display_block.tooltip.summary": "AnvĂ€nd den pĂ„ _tĂ„g_, som en _tĂ„gdestinationsdisplay_ eller _passagerarinformationsdisplay_, eller pĂ„ _tĂ„gstationer_ som förbĂ€ttrade _plattformsdisplayer_ som ocksĂ„ visar mer information Ă€n vanliga displaytavlor.", + "block.createrailwaysnavigator.advanced_display_block.tooltip.condition1": "NĂ€r R-Klickats med en skiftnyckel", + "block.createrailwaysnavigator.advanced_display_block.tooltip.behaviour1": "Öppna en meny för att _konfigurera_ _displayen_.", + "block.createrailwaysnavigator.advanced_display": "Avancerad Displaytavla", + "block.createrailwaysnavigator.advanced_display.tooltip.summary": "AnvĂ€nd den pĂ„ _tĂ„g_, som en _tĂ„gdestinationsdisplay_ eller _passagerarinformationsdisplay_, eller pĂ„ _tĂ„gstationer_ som förbĂ€ttrade _plattformsdisplayer_ som ocksĂ„ visar mer information Ă€n vanliga displaytavlor.", + "block.createrailwaysnavigator.advanced_display.tooltip.condition1": "NĂ€r R-Klickats med en skiftnyckel", + "block.createrailwaysnavigator.advanced_display.tooltip.behaviour1": "Öppna en meny för att _konfigurera_ _displayen_.", + "block.createrailwaysnavigator.advanced_display_small": "Liten Avancerad SkĂ€rm", + "block.createrailwaysnavigator.advanced_display_small.tooltip.summary": "AnvĂ€nd den pĂ„ _tĂ„g_, som en _tĂ„gdestinationsdisplay_ eller _passagerarinformationsdisplay_, eller pĂ„ _tĂ„gstationer_ som förbĂ€ttrade _plattformsdisplayer_ som ocksĂ„ visar mer information Ă€n vanliga displaytavlor.", + "block.createrailwaysnavigator.advanced_display_small.tooltip.condition1": "NĂ€r R-Klickats med en skiftnyckel", + "block.createrailwaysnavigator.advanced_display_small.tooltip.behaviour1": "Öppna en meny för att _konfigurera_ _displayen_.", + "block.createrailwaysnavigator.advanced_display_panel": "Avancerad SkĂ€rmpanel", + "block.createrailwaysnavigator.advanced_display_panel.tooltip.summary": "AnvĂ€nd den pĂ„ _tĂ„g_, som en _tĂ„gdestinationsdisplay_ eller _passagerarinformationsdisplay_, eller pĂ„ _tĂ„gstationer_ som förbĂ€ttrade _plattformsdisplayer_ som ocksĂ„ visar mer information Ă€n vanliga displaytavlor.", + "block.createrailwaysnavigator.advanced_display_panel.tooltip.condition1": "NĂ€r R-Klickats med en skiftnyckel", + "block.createrailwaysnavigator.advanced_display_panel.tooltip.behaviour1": "Öppna en meny för att _konfigurera_ _displayen_.", + "block.createrailwaysnavigator.advanced_display_half_panel": "Halv Avancerad SkĂ€rmpanel", + "block.createrailwaysnavigator.advanced_display_half_panel.tooltip.summary": "AnvĂ€nd den pĂ„ _tĂ„g_, som en _tĂ„gdestinationsdisplay_ eller _passagerarinformationsdisplay_, eller pĂ„ _tĂ„gstationer_ som förbĂ€ttrade _plattformsdisplayer_ som ocksĂ„ visar mer information Ă€n vanliga displaytavlor.", + "block.createrailwaysnavigator.advanced_display_half_panel.tooltip.condition1": "NĂ€r R-Klickats med en skiftnyckel", + "block.createrailwaysnavigator.advanced_display_half_panel.tooltip.behaviour1": "Öppna en meny för att _konfigurera_ _displayen_.", + "block.createrailwaysnavigator.advanced_display_sloped": "Lutande Avancerad SkĂ€rm", + "block.createrailwaysnavigator.advanced_display_sloped.tooltip.summary": "AnvĂ€nd den pĂ„ _tĂ„g_, som en _tĂ„gdestinationsdisplay_ eller _passagerarinformationsdisplay_, eller pĂ„ _tĂ„gstationer_ som förbĂ€ttrade _plattformsdisplayer_ som ocksĂ„ visar mer information Ă€n vanliga displaytavlor.", + "block.createrailwaysnavigator.advanced_display_sloped.tooltip.condition1": "NĂ€r R-Klickats med en skiftnyckel", + "block.createrailwaysnavigator.advanced_display_sloped.tooltip.behaviour1": "Öppna en meny för att _konfigurera_ _displayen_.", + + "block.createrailwaysnavigator.advanced_display.ber.not_in_service": "Ej i trafik", + "block.createrailwaysnavigator.advanced_display.ber.shunting_trip": "Abonnerad", + + "category.createrailwaysnavigator.crn": "Create Railways Navigator", + "key.createrailwaysnavigator.route_overlay_options": "Visa Alternativ För Ruttöverlagring", + + "enum.createrailwaysnavigator.overlay_position": "Visa Position", + "enum.createrailwaysnavigator.overlay_position.info.top_left": "Övre VĂ€nstra Hörnet", + "enum.createrailwaysnavigator.overlay_position.info.top_right": "Övre Högra Hörnet", + "enum.createrailwaysnavigator.overlay_position.info.bottom_left": "Nedre VĂ€nstra Hörnet", + "enum.createrailwaysnavigator.overlay_position.info.bottom_right": "Nedre Högra Hörnet", + + "gui.createrailwaysnavigator.loading.title": "Downloading data from server...", + + "gui.createrailwaysnavigator.overlay_settings.title": "InstĂ€llningar för ruttöverlagring", + "gui.createrailwaysnavigator.route_overlay_settings.show_details": "Visa detaljer", + "gui.createrailwaysnavigator.route_overlay_settings.unpin": "Ta bort ruttöverlagring", + "gui.createrailwaysnavigator.route_overlay_settings.narrator.on": "BerĂ€ttarmeddelanden har aktiverats.", + "gui.createrailwaysnavigator.route_overlay_settings.narrator.off": "BerĂ€ttarmeddelanden har inaktiverats.", + "gui.createrailwaysnavigator.route_overlay_settings.notifications.on": "Aviseringar har aktiverats", + "gui.createrailwaysnavigator.route_overlay_settings.notifications.off": "Aviseringar har inaktiverats", + "gui.createrailwaysnavigator.route_overlay_settings.scale": "GUI Storlek", + "gui.createrailwaysnavigator.route_overlay_settings.narrator": "BerĂ€ttarmeddelanden", + "gui.createrailwaysnavigator.route_overlay_settings.narrator.description": "BerĂ€ttaren tillkĂ€nnager viktiga hĂ€ndelser pĂ„ din resa, t.ex. nĂ€sta stopp, förĂ€ndringar osv.", + "gui.createrailwaysnavigator.route_overlay_settings.notifications": "Aviseringar", + "gui.createrailwaysnavigator.route_overlay_settings.notifications.description": "FĂ„ toast-aviseringar om viktiga hĂ€ndelser pĂ„ din resa, t.ex. nĂ€sta stopp, förĂ€ndringar osv.", + + "gui.createrailwaysnavigator.common.expand": "Visa Detaljer", + "gui.createrailwaysnavigator.common.collapse": "Dölj Detaljer", + "gui.createrailwaysnavigator.common.go_back": "GĂ„ Tillbaka", + "gui.createrailwaysnavigator.common.go_to_top": "Skrolla Till Toppen", + "gui.createrailwaysnavigator.common.reset_defaults": "ÅterstĂ€ll till standard", + "gui.createrailwaysnavigator.common.count": "RĂ€kna", + "gui.createrailwaysnavigator.common.true": "Ja", + "gui.createrailwaysnavigator.common.false": "Nej", + "gui.createrailwaysnavigator.common.search": "Sök", + "gui.createrailwaysnavigator.common.auto": "Auto", + "gui.createrailwaysnavigator.common.server_error": "Serverfel nĂ€r uppgiften körs. Se konsolen för detaljer.", + + "gui.createrailwaysnavigator.navigator.title": "Create Railways Navigator", + "gui.createrailwaysnavigator.navigator.no_connections": "Inga anslutningar hittades.", + "gui.createrailwaysnavigator.navigator.not_searched": "Inget sökt Ă€nnu.", + "gui.createrailwaysnavigator.navigator.searching": "Sök efter anslutningar...", + "gui.createrailwaysnavigator.navigator.error_title": "Ingen rutt hittades!", + "gui.createrailwaysnavigator.navigator.start_end_null": "Start eller destination Ă€r tom.", + "gui.createrailwaysnavigator.navigator.start_end_equal": "Du har angett samma start och destination.", + "gui.createrailwaysnavigator.navigator.route_entry.connection_in_past": "❌ Anslutning i det förflutna", + "gui.createrailwaysnavigator.navigator.route_entry.transfer": "Trans.", + "gui.createrailwaysnavigator.navigator.route_entry.station_start": "frĂ„n %s", + "gui.createrailwaysnavigator.navigator.global_settings.tooltip": "Globala InstĂ€llningar", + "gui.createrailwaysnavigator.navigator.search_settings.tooltip": "SökinstĂ€llningar", + "gui.createrailwaysnavigator.navigator.search.tooltip": "Sök", + "gui.createrailwaysnavigator.navigator.location.tooltip": "NĂ€rmaste station frĂ„n nuvarande position", + "gui.createrailwaysnavigator.navigator.switch.tooltip": "VĂ€xla fĂ€lt", + + "gui.createrailwaysnavigator.route_details.title": "Ruttdetaljer", + "gui.createrailwaysnavigator.route_details.departure": "AvgĂ„r om", + "gui.createrailwaysnavigator.route_details.next_transfer_time": "Vidarekoppling om", + "gui.createrailwaysnavigator.route_details.transfer": "Byte", + "gui.createrailwaysnavigator.route_details.save_route": "Spara Anslutning", + + "gui.createrailwaysnavigator.route_overview.title": "Ruttdetaljer", + "gui.createrailwaysnavigator.route_overview.journey_begins": "Din resa börjar! %s mot %s, avgĂ„r %s", + "gui.createrailwaysnavigator.route_overview.journey_begins_with_platform": "Din resa börjar! %s mot %s, avgĂ„r %s frĂ„n plattform %s", + "gui.createrailwaysnavigator.route_overview.train_details": "%s mot %s", + "gui.createrailwaysnavigator.route_overview.next_stop": "NĂ€sta: %s", + "gui.createrailwaysnavigator.route_overview.transfer": "Byt till %s → %s", + "gui.createrailwaysnavigator.route_overview.transfer_with_platform": "Byt till %s → %s on platform %s", + "gui.createrailwaysnavigator.route_overview.journey_completed": "Resa avklarad", + "gui.createrailwaysnavigator.route_overview.after_journey": "Du har nĂ„tt %s. Tack för att du reser med oss och ha en trevlig dag.", + "gui.createrailwaysnavigator.route_overview.next_connections": "Anslutningar", + "gui.createrailwaysnavigator.route_overview.schedule_transfer": "Byte", + "gui.createrailwaysnavigator.route_overview.train_cancelled": "InstĂ€llt", + "gui.createrailwaysnavigator.route_overview.train_cancellation_info": "Information angĂ„ende %s", + "gui.createrailwaysnavigator.route_overview.stop_cancelled": "❌ InstĂ€lld", + "gui.createrailwaysnavigator.route_overview.connection_endangered": "Risk att missa bytet", + "gui.createrailwaysnavigator.route_overview.connection_missed": "Anslutning missad", + "gui.createrailwaysnavigator.route_overview.connection_cancelled": "InstĂ€llt", + "gui.createrailwaysnavigator.route_overview.journey_interrupted": "Din resa till %s kan inte fortsĂ€tta.", + "gui.createrailwaysnavigator.route_overview.connection_missed_info": "PĂ„ grund av en försening missade du din anslutning. Sök efter en alternativ rutt i navigatorn. Vi ber om ursĂ€kt för besvĂ€ret.", + "gui.createrailwaysnavigator.route_overview.train_cancelled_info": "Information om %s: Den hĂ€r avgĂ„ngen Ă€r instĂ€lld idag! Vi ber om ursĂ€kt för besvĂ€ret. Sök efter en alternativ rutt i navigatorn.", + "gui.createrailwaysnavigator.route_overview.train_cancelled_title": "TĂ„get Ă€r instĂ€llt", + "gui.createrailwaysnavigator.route_overview.journey_interrupted_info": "Din resa till %s kan inte fortsĂ€tta. Sök efter en alternativ rutt i navigatorn.", + "gui.createrailwaysnavigator.route_overview.options": "Tryck pĂ„ %s för alternativ.", + "gui.createrailwaysnavigator.route_overview.notification.journey_begins.title": "Din resa till %s börjar!", + "gui.createrailwaysnavigator.route_overview.notification.journey_begins": "%s till %s, avgĂ„r %s", + "gui.createrailwaysnavigator.route_overview.notification.journey_begins_with_platform": "%s till %s, avgĂ„r %s frĂ„n plattform %s", + "gui.createrailwaysnavigator.route_overview.notification.platform_changed.title": "Din anslutning har Ă€ndrat plattform!", + "gui.createrailwaysnavigator.route_overview.notification.platform_changed": "Ditt tĂ„g i %s gĂ„r frĂ„n perrong %s idag.", + "gui.createrailwaysnavigator.route_overview.notification.train_delayed.title": "%s: Ankomst %s försenad.", + "gui.createrailwaysnavigator.route_overview.notification.train_delayed": "%s istĂ€llet för %s i %s", + "gui.createrailwaysnavigator.route_overview.notification.train_cancelled.title": "%s: AvgĂ„ng instĂ€lld", + "gui.createrailwaysnavigator.route_overview.notification.train_cancelled": "%s mot %s Ă€r tyvĂ€rr instĂ€lld idag.", + "gui.createrailwaysnavigator.route_overview.notification.transfer.title": "Din anslutning Ă€r pĂ„ vĂ€g", + "gui.createrailwaysnavigator.route_overview.notification.transfer": "Byt till %s → %s pĂ„ plattform %s", + "gui.createrailwaysnavigator.route_overview.notification.transfer_with_platform": "Byt till %s → %s pĂ„ plattform %s", + "gui.createrailwaysnavigator.route_overview.notification.connection_endangered.title": "Risk att missa bytet", + "gui.createrailwaysnavigator.route_overview.notification.connection_endangered": "Du har en hög risk att missa %s mot %s.", + "gui.createrailwaysnavigator.route_overview.notification.connection_missed.title": "Du har tyvĂ€rr missat ditt byte", + "gui.createrailwaysnavigator.route_overview.notification.connection_missed": "Du har missat ditt byte %s mot %s.", + "gui.createrailwaysnavigator.route_overview.notification.journey_completed.title": "Du Ă€r framme vid din destination!", + "gui.createrailwaysnavigator.route_overview.notification.journey_completed": "Tack för att du reser med oss och ha en fortsatt trevlig dag!", + "gui.createrailwaysnavigator.route_overview.date": "Dag %s, %s", + + "gui.createrailwaysnavigator.global_settings.title": "Globala InstĂ€llningar", + "gui.createrailwaysnavigator.global_settings.option.tooltip": "Klicka för att Ă€ndra", + "gui.createrailwaysnavigator.global_settings.option_alias.title": "Stationstaggar", + "gui.createrailwaysnavigator.global_settings.option_alias.description": "Definiera stationstaggar för att definera multiplattformsstationer (t.ex. MinStation 1, MinStation 2, ...) som en enda station (t.ex. MinStation) med anpassade namn.", + "gui.createrailwaysnavigator.global_settings.option_blacklist.title": "Stationssvartlista", + "gui.createrailwaysnavigator.global_settings.option_blacklist.description": "Uteslut stationer som inte ska visas i navigeringsresultaten. Dessa stationer kommer att ignoreras nĂ€r rutter genereras.", + "gui.createrailwaysnavigator.global_settings.train_group.title": "TĂ„ggrupper", + "gui.createrailwaysnavigator.global_settings.train_group.description": "Skapa tĂ„ggrupper för att organisera alla tĂ„g (t.ex. regionaltrafik, fjĂ€rrtrafik, ...). AnvĂ€ndare kan bestĂ€mma vilka grupper de vill anvĂ€nda.", + "gui.createrailwaysnavigator.global_settings.train_blacklist.title": "TĂ„gsvartlista", + "gui.createrailwaysnavigator.global_settings.train_blacklist.description": "Uteslut tĂ„g, t.ex. godstĂ„g, specialtĂ„g etc. sĂ„ att de inte anvĂ€nds i ruttförslagen.", + + "gui.createrailwaysnavigator.station_tags.title": "InstĂ€llningar För Stationstaggar", + "gui.createrailwaysnavigator.station_tags.summary": "InnehĂ„ller %s stationer", + "gui.createrailwaysnavigator.station_tags.editor": "Senast redigerad av %s den %s", + "gui.createrailwaysnavigator.station_tags.add.tooltip": "Skapa ny Post", + "gui.createrailwaysnavigator.station_tags.delete_alias.tooltip": "Radera Tag", + "gui.createrailwaysnavigator.station_tags.delete_station.tooltip": "Radera Station", + "gui.createrailwaysnavigator.station_tags.add_station.tooltip": "LĂ€gg till Station", + "gui.createrailwaysnavigator.station_tags.hint.station_name": "Stationsnamn", + "gui.createrailwaysnavigator.station_tags.hint.platform": "Plattform", + "gui.createrailwaysnavigator.station_tags.enter_name": "Ange namn hĂ€r", + + "gui.createrailwaysnavigator.train_group_settings.title": "TĂ„ggruppinstĂ€llningar", + "gui.createrailwaysnavigator.train_group_settings.summary": "InnehĂ„ller %s tĂ„g", + "gui.createrailwaysnavigator.train_group_settings.editor": "Senast redigerad av %s den %s", + "gui.createrailwaysnavigator.train_group_settings.delete_alias.tooltip": "Radera Grupp", + "gui.createrailwaysnavigator.train_group_settings.delete_station.tooltip": "Radera TĂ„g", + "gui.createrailwaysnavigator.train_group_settings.add_station.tooltip": "LĂ€gg till TĂ„g", + + "gui.createrailwaysnavigator.blacklist.title": "Stationssvartlista", + "gui.createrailwaysnavigator.blacklist.add.tooltip": "LĂ€gg till pĂ„ svartlistan", + "gui.createrailwaysnavigator.blacklist.delete.tooltip": "Ta bort frĂ„n svartlistan", + + "gui.createrailwaysnavigator.train_blacklist.title": "TĂ„gsvartlista", + "gui.createrailwaysnavigator.train_blacklist.add.tooltip": "LĂ€gg till pĂ„ svartlistan", + "gui.createrailwaysnavigator.train_blacklist.delete.tooltip": "Ta bort frĂ„n svartlistan", + + "gui.createrailwaysnavigator.search_settings.title": "SökinstĂ€llningar", + "gui.createrailwaysnavigator.search_settings.transfer_time": "Minsta överföringstid", + "gui.createrailwaysnavigator.search_settings.transfer_time.description": "Den minsta tid som bör finnas tillgĂ€nglig för att byta tĂ„g. (1h ~ 50 Sekunder i Verkliga Livet)", + "gui.createrailwaysnavigator.search_settings.train_groups": "TĂ„gkategorifilter", + "gui.createrailwaysnavigator.search_settings.train_groups.description": "BestĂ€m vilka tĂ„g frĂ„n vilka tĂ„gkategorier du vill anvĂ€nda.", + "gui.createrailwaysnavigator.search_settings.train_groups.overview": "%s kategorier har valts", + "gui.createrailwaysnavigator.search_settings.train_groups.overview.all": "Alla", + "gui.createrailwaysnavigator.search_settings.train_groups.overview.none": "Inga", + "gui.createrailwaysnavigator.search_settings.train_groups.tooltip.reset": "ÅterstĂ€ll filter", + + "gui.createrailwaysnavigator.new_text_entry.add.tooltip": "LĂ€gg till", + + "gui.createrailwaysnavigator.time": "Tid: %s", + "gui.createrailwaysnavigator.time.now": "Nu", + "gui.createrailwaysnavigator.time_format.dhm": "%s dgr %s tim. %s min.", + "gui.createrailwaysnavigator.time_format.hm": "%s tim. %s min.", + "gui.createrailwaysnavigator.time_format.m": "%s min.", + + "gui.createrailwaysnavigator.platform": "Plattform", + "gui.createrailwaysnavigator.departure": "Avgpng", + "gui.createrailwaysnavigator.destination": "Destination", + "gui.createrailwaysnavigator.line": "Linje", + "gui.createrailwaysnavigator.following_trains": "Följande anslutningar:", + "gui.createrailwaysnavigator.via": "via", + + "gui.createrailwaysnavigator.advanced_display_settings.title": "Avancerade SkĂ€rminstĂ€llningar", + "gui.createrailwaysnavigator.advanced_display_settings.display_type": "SkĂ€rmtyp", + "gui.createrailwaysnavigator.advanced_display_settings.display_type.description": "Avgör vilken information som visas.", + "gui.createrailwaysnavigator.advanced_display_settings.info_type": "Informationstyp", + "gui.createrailwaysnavigator.advanced_display_settings.info_type.description": "Avgör hur detaljerad informationen Ă€r.", + "gui.createrailwaysnavigator.advanced_display_settings.double_sided": "Dubbelsidig", + + "enum.createrailwaysnavigator.display_info_type": "Visa Infotyp", + "enum.createrailwaysnavigator.display_info_type.description": "BestĂ€mmer hur mycket information som ska visas pĂ„ skĂ€rmen.", + "enum.createrailwaysnavigator.display_info_type.simple": "Enkel", + "enum.createrailwaysnavigator.display_info_type.info.simple": "SkĂ€rmen visar bara den viktigaste informationen utan ytterligare detaljer.", + "enum.createrailwaysnavigator.display_info_type.detailed": "Detaljerad", + "enum.createrailwaysnavigator.display_info_type.info.detailed": "Den viktigaste informationen med vissa detaljer kommer att visas, sĂ„som tĂ„ghastighet, mellanstationer, osv.", + "enum.createrailwaysnavigator.display_info_type.informative": "Informativ", + "enum.createrailwaysnavigator.display_info_type.info.informative": "Visar all information som kan vara intressant och visar den i en snygg layout. Rekommenderas inte för smĂ„ skĂ€rmar eftersom texten kan bli vĂ€ldigt liten.", + + "enum.createrailwaysnavigator.display_type": "BildskĂ€rmstyp", + "enum.createrailwaysnavigator.display_type.description": "Typen av din skĂ€rm som beror pĂ„ deras syfte.", + "enum.createrailwaysnavigator.display_type.train_destination": "TĂ„gdestination", + "enum.createrailwaysnavigator.display_type.info.train_destination": "Avsedd att anvĂ€ndas utanför tĂ„g dĂ„ den visar information om sjĂ€lva tĂ„get sĂ„som namn, destination och (om valt) mellanstationer och annan information.", + "enum.createrailwaysnavigator.display_type.passenger_information": "Passagerarinformation", + "enum.createrailwaysnavigator.display_type.info.passenger_information": "Representerar skĂ€rmarna som finns inuti tĂ„gen. Dessa displayer visar nĂ€sta stopp, utfartsriktningen och (om valt) tĂ„ghastighet och en ruttöversikt.", + "enum.createrailwaysnavigator.display_type.platform": "Plattformsdisplay", + "enum.createrailwaysnavigator.display_type.info.platform": "Dessa displayer bör anvĂ€ndas pĂ„ plattformar och perronger och visar nĂ€sta ankommande tĂ„g med ytterligare detaljer om de vĂ€ljs. Kan inte anvĂ€ndas i tĂ„g!", + + "enum.createrailwaysnavigator.side": "Sida", + "enum.createrailwaysnavigator.side.description": "Den sida av blocket dĂ€r informationen ska Ă„terges.", + "enum.createrailwaysnavigator.side.front": "Framsida", + "enum.createrailwaysnavigator.side.info.front": "Informationen Ă„terges endast pĂ„ framsidan. Detta Ă€r standardbeteendet.", + "enum.createrailwaysnavigator.side.both": "BĂ„da Sidor", + "enum.createrailwaysnavigator.side.info.both": "Informationen kommer att Ă„terges pĂ„ bĂ„da sidor.", + + "enum.createrailwaysnavigator.time_display": "Tidsvisning", + "enum.createrailwaysnavigator.time_display.description": "BestĂ€mmer hur tiden ska visas.", + "enum.createrailwaysnavigator.time_display.abs": "ABS", + "enum.createrailwaysnavigator.time_display.info.abs": "ABS (visar klockslaget dĂ„ fordonet ankommer)", + "enum.createrailwaysnavigator.time_display.eta": "ETA", + "enum.createrailwaysnavigator.time_display.info.eta": "ETA (visar fordonets berĂ€knade ankomsttid)", + + "create.display_source.advanced_display": "Avancerade SkĂ€rmar", + "gui.createrailwaysnavigator.display_source.advanced_display.train_name_width": "TĂ„gnamnets kolumnbredd", + "gui.createrailwaysnavigator.display_source.advanced_display.train_name_width.description": "i blockpixlar. (Standard: 16)", + "gui.createrailwaysnavigator.display_source.advanced_display.platform_width": "Plattformens kolumnbredd", + "gui.createrailwaysnavigator.display_source.advanced_display.platform_width.description": "i blockpixlar. (Standard: Auto)", + + "createrailwaysnavigator.moin": "moin" +} diff --git a/common/src/main/resources/assets/createrailwaysnavigator/lang/sxu.json b/common/src/main/resources/assets/createrailwaysnavigator/lang/sxu.json index 767b1612..e1d8537b 100644 --- a/common/src/main/resources/assets/createrailwaysnavigator/lang/sxu.json +++ b/common/src/main/resources/assets/createrailwaysnavigator/lang/sxu.json @@ -35,8 +35,7 @@ "block.createrailwaysnavigator.advanced_display_sloped.tooltip.condition1": "R-Gligg midd nem SchraubnschlĂŒssl", "block.createrailwaysnavigator.advanced_display_sloped.tooltip.behaviour1": "Öffne Ă€ _MenĂŒ_, um dĂ€ Anzeichn ze _gonfiguriern_.", - "block.createrailwaysnavigator.advanced_display.ber.not_in_service": "Ausser Beddrieb", - "block.createrailwaysnavigator.advanced_display.ber.shunting_trip": "Beddriebschfoahrd", + "block.createrailwaysnavigator.advanced_display.ber.not_in_service": "Beddriebschfoahrd", "category.createrailwaysnavigator.crn": "Create Railways Navigator", "key.createrailwaysnavigator.route_overlay_options": "Schdreggnanzeiche Eenschdellungn", @@ -108,16 +107,16 @@ "gui.createrailwaysnavigator.route_overview.after_journey": "Sie hamm %s orreichd. Wir bedanggen uns fĂŒr Ihre Reise und wĂŒnschen nen schönen Dahch.", "gui.createrailwaysnavigator.route_overview.next_connections": "NĂ€chsde AnschlĂŒsse", "gui.createrailwaysnavigator.route_overview.schedule_transfer": "Umschdiech", - "gui.createrailwaysnavigator.route_overview.train_canceled": "Zuhch fĂ€lld aus", + "gui.createrailwaysnavigator.route_overview.train_cancelled": "Zuhch fĂ€lld aus", "gui.createrailwaysnavigator.route_overview.train_cancellation_info": "Informadschion ze %s", - "gui.createrailwaysnavigator.route_overview.stop_canceled": "❌ FĂ€lld aus", + "gui.createrailwaysnavigator.route_overview.stop_cancelled": "❌ FĂ€lld aus", "gui.createrailwaysnavigator.route_overview.connection_endangered": "Anschluss gefĂ€hrd'", "gui.createrailwaysnavigator.route_overview.connection_missed": "Anschluss forbassd", - "gui.createrailwaysnavigator.route_overview.connection_canceled": "Zuhch fĂ€lld aus", + "gui.createrailwaysnavigator.route_overview.connection_cancelled": "Zuhch fĂ€lld aus", "gui.createrailwaysnavigator.route_overview.journey_interrupted": "Ihre Reise nooch %s gann ne fordgesedsd wer'n.", "gui.createrailwaysnavigator.route_overview.connection_missed_info": "Ufgrund Ă€nner ZuchforschbĂ€dung hamm Se Ihren Anschluss forbassd. Suchen Se nooch Ă€nner Aldornaddife im Wegweesr. Wir endschuldschn uns fĂŒr dĂ€ Unannehmlichgeidn.", - "gui.createrailwaysnavigator.route_overview.train_canceled_info": "Informadschion ze %s: Diesorr Zuhch fĂ€lld heude aus! Wir endschuldschen uns fĂŒr dĂ€ Unannehmlichgeidn. Suchen Se nooch Ă€nner Aldornaddife im Wegweesr.", - "gui.createrailwaysnavigator.route_overview.train_canceled_title": "Zuhch fĂ€lld heude aus", + "gui.createrailwaysnavigator.route_overview.train_cancelled_info": "Informadschion ze %s: Diesorr Zuhch fĂ€lld heude aus! Wir endschuldschen uns fĂŒr dĂ€ Unannehmlichgeidn. Suchen Se nooch Ă€nner Aldornaddife im Wegweesr.", + "gui.createrailwaysnavigator.route_overview.train_cancelled_title": "Zuhch fĂ€lld heude aus", "gui.createrailwaysnavigator.route_overview.journey_interrupted_info": "Ihre Reise nooch %s gann ne fordgesedzd wer'n. Suchen Se nooch Ă€nner Aldornaddife im Wegweesr..", "gui.createrailwaysnavigator.route_overview.options": "DrĂŒgge %s fĂŒr Obdschonen.", "gui.createrailwaysnavigator.route_overview.notification.journey_begins.title": "Ihre Reise nooch %s beginnd!", @@ -127,8 +126,8 @@ "gui.createrailwaysnavigator.route_overview.notification.platform_changed": "Ihr Zuhch in %s fĂ€hrd heude fonn Gleis %s ab.", "gui.createrailwaysnavigator.route_overview.notification.train_delayed.title": "%s: Anngunfd %s ferschbĂ€ded.", "gui.createrailwaysnavigator.route_overview.notification.train_delayed": "%s schdadd %s in %s", - "gui.createrailwaysnavigator.route_overview.notification.train_canceled.title": "%s: Zuhch fĂ€lld aus", - "gui.createrailwaysnavigator.route_overview.notification.train_canceled": "%s nooch %s fĂ€lld heude aus.", + "gui.createrailwaysnavigator.route_overview.notification.train_cancelled.title": "%s: Zuhch fĂ€lld aus", + "gui.createrailwaysnavigator.route_overview.notification.train_cancelled": "%s nooch %s fĂ€lld heude aus.", "gui.createrailwaysnavigator.route_overview.notification.transfer.title": "Ihr Umschdiech schdehd befor", "gui.createrailwaysnavigator.route_overview.notification.transfer": "Schdeichen se in %s → %s uf Gleis %s um", "gui.createrailwaysnavigator.route_overview.notification.transfer_with_platform": "Schdeichen e in %s → %s um", @@ -151,16 +150,16 @@ "gui.createrailwaysnavigator.global_settings.train_blacklist.title": "Zuhch BlĂ€gglisd", "gui.createrailwaysnavigator.global_settings.train_blacklist.description": "Schließe ZĂŒhche aus, z.B. GĂŒdorzĂŒhche, SonderzĂŒhche, edc., damidd diese ne in'dn ForschlĂ€chn Ufdauchn.", - "gui.createrailwaysnavigator.alias_settings.title": "Boahnhof DĂ€gs Eenschdellungn", - "gui.createrailwaysnavigator.alias_settings.summary": "EndhĂ€ld %s Schdadsionen", - "gui.createrailwaysnavigator.alias_settings.editor": "Z'ledsd fonn %s am %s beoarbeided", - "gui.createrailwaysnavigator.alias_settings.add.tooltip": "Neuen Eindrahch erschdelln", - "gui.createrailwaysnavigator.alias_settings.delete_alias.tooltip": "Dahch löschn", - "gui.createrailwaysnavigator.alias_settings.delete_station.tooltip": "Schdadschion endfernn", - "gui.createrailwaysnavigator.alias_settings.add_station.tooltip": "Schdadschion hinzufĂŒchn", - "gui.createrailwaysnavigator.alias_settings.hint.station_name": "Boahnhofsname", - "gui.createrailwaysnavigator.alias_settings.hint.platform": "Gleisbezeichnung", - "gui.createrailwaysnavigator.alias_settings.enter_name": "Name eingeb'n", + "gui.createrailwaysnavigator.station_tags.title": "Boahnhof DĂ€gs Eenschdellungn", + "gui.createrailwaysnavigator.station_tags.summary": "EndhĂ€ld %s Schdadsionen", + "gui.createrailwaysnavigator.station_tags.editor": "Z'ledsd fonn %s am %s beoarbeided", + "gui.createrailwaysnavigator.station_tags.add.tooltip": "Neuen Eindrahch erschdelln", + "gui.createrailwaysnavigator.station_tags.delete_alias.tooltip": "Dahch löschn", + "gui.createrailwaysnavigator.station_tags.delete_station.tooltip": "Schdadschion endfernn", + "gui.createrailwaysnavigator.station_tags.add_station.tooltip": "Schdadschion hinzufĂŒchn", + "gui.createrailwaysnavigator.station_tags.hint.station_name": "Boahnhofsname", + "gui.createrailwaysnavigator.station_tags.hint.platform": "Gleisbezeichnung", + "gui.createrailwaysnavigator.station_tags.enter_name": "Name eingeb'n", "gui.createrailwaysnavigator.train_group_settings.title": "Zuhchgaddegorien Eenschdellungn", "gui.createrailwaysnavigator.train_group_settings.summary": "EndhĂ€ld %s ZĂŒhche", diff --git a/common/src/main/resources/assets/createrailwaysnavigator/lang/zh_cn.json b/common/src/main/resources/assets/createrailwaysnavigator/lang/zh_cn.json index 06a2ab67..ec0cfa8b 100644 --- a/common/src/main/resources/assets/createrailwaysnavigator/lang/zh_cn.json +++ b/common/src/main/resources/assets/createrailwaysnavigator/lang/zh_cn.json @@ -36,7 +36,6 @@ "block.createrailwaysnavigator.advanced_display_sloped.tooltip.behaviour1": "æ‰“ćŒ€èœć•ä»„_é…çœź_星ç€ș晹。", "block.createrailwaysnavigator.advanced_display.ber.not_in_service": "æœȘ搯甹", - "block.createrailwaysnavigator.advanced_display.ber.shunting_trip": "è°ƒèœŠèĄŒçš‹", "category.createrailwaysnavigator.crn": "æœșæą°ćŠšćŠ›ïŒšé“è·ŻćŻŒèˆȘ", "key.createrailwaysnavigator.route_overlay_options": "星ç€șè·ŻçșżèŠ†ç›–选éĄč", @@ -107,16 +106,16 @@ "gui.createrailwaysnavigator.route_overview.after_journey": "æ‚šć·Č抔蟟%să€‚æ„Ÿè°ąæ‚šçš„äč˜ćïŒŒç„æ‚šæ—…é€”æ„‰ćż«ă€‚", "gui.createrailwaysnavigator.route_overview.next_connections": "äž‹äž€èœŠæŹĄ", "gui.createrailwaysnavigator.route_overview.schedule_transfer": "æąäč˜", - "gui.createrailwaysnavigator.route_overview.train_canceled": "èœŠæŹĄć–æ¶ˆ", + "gui.createrailwaysnavigator.route_overview.train_cancelled": "èœŠæŹĄć–æ¶ˆ", "gui.createrailwaysnavigator.route_overview.train_cancellation_info": "慳äșŽ%sçš„äżĄæŻ", - "gui.createrailwaysnavigator.route_overview.stop_canceled": "❌ ć·Čć–æ¶ˆ", + "gui.createrailwaysnavigator.route_overview.stop_cancelled": "❌ ć·Čć–æ¶ˆ", "gui.createrailwaysnavigator.route_overview.connection_endangered": "æąäč˜ć—阻", "gui.createrailwaysnavigator.route_overview.connection_missed": "é”™èż‡æąäč˜", - "gui.createrailwaysnavigator.route_overview.connection_canceled": "èœŠæŹĄć–æ¶ˆ", + "gui.createrailwaysnavigator.route_overview.connection_cancelled": "èœŠæŹĄć–æ¶ˆ", "gui.createrailwaysnavigator.route_overview.journey_interrupted": "æ‚šć‰ćŸ€%sçš„æ—…çš‹æ— æł•ç»§ç»­ă€‚", "gui.createrailwaysnavigator.route_overview.connection_missed_info": "由äșŽćˆ—èœŠć»¶èŻŻïŒŒæ‚šé”™èż‡äș†æąäč˜ćˆ—èœŠă€‚èŻ·ćœšćŻŒèˆȘä»Șäž­æœçŽąæ›żä»Łè·Żçșżă€‚ćŻčäșŽç»™æ‚šé€ æˆçš„äžäŸżïŒŒæˆ‘ä»Źæ·±èĄšæ­‰æ„ă€‚", - "gui.createrailwaysnavigator.route_overview.train_canceled_info": "慳äșŽ%sçš„äżĄæŻïŒšèŻ„èœŠæŹĄć·Čć–æ¶ˆïŒćŻčäșŽç»™æ‚šé€ æˆçš„äžäŸżïŒŒæˆ‘ä»Źæ·±èĄšæ­‰æ„ă€‚èŻ·ćœšćŻŒèˆȘä»Șäž­æœçŽąæ›żä»Łè·Żçșżă€‚", - "gui.createrailwaysnavigator.route_overview.train_canceled_title": "èœŠæŹĄć–æ¶ˆ", + "gui.createrailwaysnavigator.route_overview.train_cancelled_info": "慳äșŽ%sçš„äżĄæŻïŒšèŻ„èœŠæŹĄć·Čć–æ¶ˆïŒćŻčäșŽç»™æ‚šé€ æˆçš„äžäŸżïŒŒæˆ‘ä»Źæ·±èĄšæ­‰æ„ă€‚èŻ·ćœšćŻŒèˆȘä»Șäž­æœçŽąæ›żä»Łè·Żçșżă€‚", + "gui.createrailwaysnavigator.route_overview.train_cancelled_title": "èœŠæŹĄć–æ¶ˆ", "gui.createrailwaysnavigator.route_overview.journey_interrupted_info": "æ‚šć‰ćŸ€%sçš„æ—…çš‹æ— æł•ç»§ç»­ă€‚èŻ·ćœšćŻŒèˆȘä»Șäž­æœçŽąæ›żä»Łè·Żçșżă€‚", "gui.createrailwaysnavigator.route_overview.options": "按%sé”źæŸ„çœ‹é€‰éĄč。", "gui.createrailwaysnavigator.route_overview.notification.journey_begins.title": "æ‚šć‰ćŸ€%sçš„æ—…çš‹ćŒ€ć§‹äș†ïŒ", @@ -126,8 +125,8 @@ "gui.createrailwaysnavigator.route_overview.notification.platform_changed": "æ‚šćœš%sçš„ćˆ—èœŠć°†ä»Ž%sć·ç«™ć°ć‡ș揑。", "gui.createrailwaysnavigator.route_overview.notification.train_delayed.title": "%sïŒšć»¶èŻŻ%s", "gui.createrailwaysnavigator.route_overview.notification.train_delayed": "掟漚%2$sïŒŒçŽ°ć°†äșŽ%1$s抔蟟%3$s", - "gui.createrailwaysnavigator.route_overview.notification.train_canceled.title": "%sïŒšćˆ—èœŠć–æ¶ˆ", - "gui.createrailwaysnavigator.route_overview.notification.train_canceled": "ć‰ćŸ€%2$s的%1$sć·Čć–æ¶ˆă€‚", + "gui.createrailwaysnavigator.route_overview.notification.train_cancelled.title": "%sïŒšćˆ—èœŠć–æ¶ˆ", + "gui.createrailwaysnavigator.route_overview.notification.train_cancelled": "ć‰ćŸ€%2$s的%1$sć·Čć–æ¶ˆă€‚", "gui.createrailwaysnavigator.route_overview.notification.transfer.title": "ćłć°†æąäč˜", "gui.createrailwaysnavigator.route_overview.notification.transfer": "æąäč˜%s→%sïŒŒćœš%sç«™ć°äč˜èœŠ", "gui.createrailwaysnavigator.route_overview.notification.transfer_with_platform": "æąäč˜%s→%sïŒŒćœš%sç«™ć°äč˜èœŠ", @@ -150,16 +149,16 @@ "gui.createrailwaysnavigator.global_settings.train_blacklist.title": "ćˆ—èœŠé»‘ćć•", "gui.createrailwaysnavigator.global_settings.train_blacklist.description": "æŽ’é™€ćˆ—èœŠïŒŒäŸ‹ćŠ‚èŽ§èżćˆ—èœŠă€äž“ćˆ—ç­‰ïŒŒä»„äŸżćœšè·Żçșżć»șèźźäž­äžæ˜Ÿç€șćźƒä»Źă€‚", - "gui.createrailwaysnavigator.alias_settings.title": "èœŠç«™æ ‡ç­ŸèźŸćźš", - "gui.createrailwaysnavigator.alias_settings.summary": "ćŒ…ć«%säžȘ蜊站", - "gui.createrailwaysnavigator.alias_settings.editor": "æœ€ćŽçŒ–èŸ‘è€…ïŒš%säșŽ%s", - "gui.createrailwaysnavigator.alias_settings.add.tooltip": "戛ć»șæ–°æĄç›ź", - "gui.createrailwaysnavigator.alias_settings.delete_alias.tooltip": "ćˆ é™€æ ‡ç­Ÿ", - "gui.createrailwaysnavigator.alias_settings.delete_station.tooltip": "移陀蜊站", - "gui.createrailwaysnavigator.alias_settings.add_station.tooltip": "æ·»ćŠ èœŠç«™", - "gui.createrailwaysnavigator.alias_settings.hint.station_name": "èœŠç«™ćç§°", - "gui.createrailwaysnavigator.alias_settings.hint.platform": "ç«™ć°", - "gui.createrailwaysnavigator.alias_settings.enter_name": "ćœšæ­€èŸ“ć…„ćç§°", + "gui.createrailwaysnavigator.station_tags.title": "èœŠç«™æ ‡ç­ŸèźŸćźš", + "gui.createrailwaysnavigator.station_tags.summary": "ćŒ…ć«%säžȘ蜊站", + "gui.createrailwaysnavigator.station_tags.editor": "æœ€ćŽçŒ–èŸ‘è€…ïŒš%säșŽ%s", + "gui.createrailwaysnavigator.station_tags.add.tooltip": "戛ć»șæ–°æĄç›ź", + "gui.createrailwaysnavigator.station_tags.delete_alias.tooltip": "ćˆ é™€æ ‡ç­Ÿ", + "gui.createrailwaysnavigator.station_tags.delete_station.tooltip": "移陀蜊站", + "gui.createrailwaysnavigator.station_tags.add_station.tooltip": "æ·»ćŠ èœŠç«™", + "gui.createrailwaysnavigator.station_tags.hint.station_name": "èœŠç«™ćç§°", + "gui.createrailwaysnavigator.station_tags.hint.platform": "ç«™ć°", + "gui.createrailwaysnavigator.station_tags.enter_name": "ćœšæ­€èŸ“ć…„ćç§°", "gui.createrailwaysnavigator.train_group_settings.title": "ćˆ—èœŠćˆ†ç±»èźŸçœź", "gui.createrailwaysnavigator.train_group_settings.summary": "ćŒ…ć«%sèŸ†ćˆ—èœŠ", "gui.createrailwaysnavigator.train_group_settings.editor": "æœ€ćŽçŒ–èŸ‘è€…ïŒš%s%s", diff --git a/common/src/main/resources/assets/createrailwaysnavigator/models/block/advanced_display_slab_cen.json b/common/src/main/resources/assets/createrailwaysnavigator/models/block/advanced_display_slab_cen.json new file mode 100644 index 00000000..9c33aad6 --- /dev/null +++ b/common/src/main/resources/assets/createrailwaysnavigator/models/block/advanced_display_slab_cen.json @@ -0,0 +1,62 @@ +{ + "credit": "Made with Blockbench", + "texture_size": [64, 64], + "textures": { + "2": "createrailwaysnavigator:block/advanced_display_back", + "3": "createrailwaysnavigator:block/advanced_display_small", + "particle": "createrailwaysnavigator:block/advanced_display" + }, + "elements": [ + { + "from": [0, 4, 0], + "to": [16, 12, 16], + "rotation": {"angle": 0, "axis": "y", "origin": [0, 0, -8]}, + "faces": { + "north": {"uv": [0, 0, 16, 8], "texture": "#3", "cullface": "north"}, + "east": {"uv": [0, 8, 8, 12], "texture": "#2", "cullface": "east"}, + "south": {"uv": [0, 8, 8, 12], "texture": "#2", "cullface": "south"}, + "west": {"uv": [8, 8, 0, 12], "texture": "#2", "cullface": "west"}, + "up": {"uv": [0, 0, 8, 8], "texture": "#2"}, + "down": {"uv": [0, 0, 8, 8], "texture": "#2"} + } + } + ], + "display": { + "thirdperson_righthand": { + "rotation": [75, 45, 0], + "translation": [0, 2.5, 0], + "scale": [0.375, 0.375, 0.375] + }, + "thirdperson_lefthand": { + "rotation": [75, 45, 0], + "translation": [0, 2.5, 0], + "scale": [0.375, 0.375, 0.375] + }, + "firstperson_righthand": { + "rotation": [0, 45, 0], + "translation": [-1.5, 0, 0], + "scale": [0.4, 0.4, 0.4] + }, + "firstperson_lefthand": { + "rotation": [0, 45, 0], + "translation": [-1.5, 0, 0], + "scale": [0.4, 0.4, 0.4] + }, + "ground": { + "translation": [0, 3, -1], + "scale": [0.25, 0.25, 0.25] + }, + "gui": { + "rotation": [30, 225, 0], + "translation": [0, -0.25, 0], + "scale": [0.625, 0.625, 0.625] + }, + "head": { + "translation": [0, 0, -8] + }, + "fixed": { + "translation": [0, 0, -0.75], + "scale": [0.5, 0.5, 0.5] + } + } +} \ No newline at end of file diff --git a/common/src/main/resources/assets/createrailwaysnavigator/models/block/advanced_display_slab_double_cen.json b/common/src/main/resources/assets/createrailwaysnavigator/models/block/advanced_display_slab_double_cen.json new file mode 100644 index 00000000..67a2fbe4 --- /dev/null +++ b/common/src/main/resources/assets/createrailwaysnavigator/models/block/advanced_display_slab_double_cen.json @@ -0,0 +1,62 @@ +{ + "credit": "Made with Blockbench", + "texture_size": [64, 64], + "textures": { + "2": "createrailwaysnavigator:block/advanced_display_back", + "3": "createrailwaysnavigator:block/advanced_display_small", + "particle": "createrailwaysnavigator:block/advanced_display" + }, + "elements": [ + { + "from": [0, 4, 0], + "to": [16, 12, 16], + "rotation": {"angle": 0, "axis": "y", "origin": [0, 0, -8]}, + "faces": { + "north": {"uv": [0, 0, 16, 8], "texture": "#3", "cullface": "north"}, + "east": {"uv": [0, 8, 8, 12], "texture": "#2", "cullface": "east"}, + "south": {"uv": [0, 0, 16, 8], "texture": "#3", "cullface": "south"}, + "west": {"uv": [8, 8, 0, 12], "texture": "#2", "cullface": "west"}, + "up": {"uv": [0, 0, 8, 8], "texture": "#2"}, + "down": {"uv": [0, 0, 8, 8], "texture": "#2"} + } + } + ], + "display": { + "thirdperson_righthand": { + "rotation": [75, 45, 0], + "translation": [0, 2.5, 0], + "scale": [0.375, 0.375, 0.375] + }, + "thirdperson_lefthand": { + "rotation": [75, 45, 0], + "translation": [0, 2.5, 0], + "scale": [0.375, 0.375, 0.375] + }, + "firstperson_righthand": { + "rotation": [0, 45, 0], + "translation": [-1.5, 0, 0], + "scale": [0.4, 0.4, 0.4] + }, + "firstperson_lefthand": { + "rotation": [0, 45, 0], + "translation": [-1.5, 0, 0], + "scale": [0.4, 0.4, 0.4] + }, + "ground": { + "translation": [0, 3, -1], + "scale": [0.25, 0.25, 0.25] + }, + "gui": { + "rotation": [30, 225, 0], + "translation": [1.75, 1, 0], + "scale": [0.625, 0.625, 0.625] + }, + "head": { + "translation": [0, 0, -8] + }, + "fixed": { + "translation": [0, 0, -0.75], + "scale": [0.5, 0.5, 0.5] + } + } +} \ No newline at end of file diff --git a/common/src/main/resources/assets/createrailwaysnavigator/models/block/advanced_display_slab_double_neg.json b/common/src/main/resources/assets/createrailwaysnavigator/models/block/advanced_display_slab_double_neg.json new file mode 100644 index 00000000..e566e226 --- /dev/null +++ b/common/src/main/resources/assets/createrailwaysnavigator/models/block/advanced_display_slab_double_neg.json @@ -0,0 +1,62 @@ +{ + "credit": "Made with Blockbench", + "texture_size": [64, 64], + "textures": { + "2": "createrailwaysnavigator:block/advanced_display_back", + "3": "createrailwaysnavigator:block/advanced_display_small", + "particle": "createrailwaysnavigator:block/advanced_display" + }, + "elements": [ + { + "from": [0, 0, 0], + "to": [16, 8, 16], + "rotation": {"angle": 0, "axis": "y", "origin": [0, -4, -8]}, + "faces": { + "north": {"uv": [0, 0, 16, 8], "texture": "#3", "cullface": "north"}, + "east": {"uv": [0, 8, 8, 12], "texture": "#2", "cullface": "east"}, + "south": {"uv": [0, 0, 16, 8], "texture": "#3", "cullface": "south"}, + "west": {"uv": [8, 8, 0, 12], "texture": "#2", "cullface": "west"}, + "up": {"uv": [0, 0, 8, 8], "texture": "#2"}, + "down": {"uv": [0, 0, 8, 8], "texture": "#2", "cullface": "down"} + } + } + ], + "display": { + "thirdperson_righthand": { + "rotation": [75, 45, 0], + "translation": [0, 2.5, 0], + "scale": [0.375, 0.375, 0.375] + }, + "thirdperson_lefthand": { + "rotation": [75, 45, 0], + "translation": [0, 2.5, 0], + "scale": [0.375, 0.375, 0.375] + }, + "firstperson_righthand": { + "rotation": [0, 45, 0], + "translation": [-1.5, 0, 0], + "scale": [0.4, 0.4, 0.4] + }, + "firstperson_lefthand": { + "rotation": [0, 45, 0], + "translation": [-1.5, 0, 0], + "scale": [0.4, 0.4, 0.4] + }, + "ground": { + "translation": [0, 3, -1], + "scale": [0.25, 0.25, 0.25] + }, + "gui": { + "rotation": [30, 225, 0], + "translation": [1.75, 1, 0], + "scale": [0.625, 0.625, 0.625] + }, + "head": { + "translation": [0, 0, -8] + }, + "fixed": { + "translation": [0, 0, -0.75], + "scale": [0.5, 0.5, 0.5] + } + } +} \ No newline at end of file diff --git a/common/src/main/resources/assets/createrailwaysnavigator/models/block/advanced_display_slab_double_pos.json b/common/src/main/resources/assets/createrailwaysnavigator/models/block/advanced_display_slab_double_pos.json new file mode 100644 index 00000000..146c2ec8 --- /dev/null +++ b/common/src/main/resources/assets/createrailwaysnavigator/models/block/advanced_display_slab_double_pos.json @@ -0,0 +1,62 @@ +{ + "credit": "Made with Blockbench", + "texture_size": [64, 64], + "textures": { + "2": "createrailwaysnavigator:block/advanced_display_back", + "3": "createrailwaysnavigator:block/advanced_display_small", + "particle": "createrailwaysnavigator:block/advanced_display" + }, + "elements": [ + { + "from": [0, 8, 0], + "to": [16, 16, 16], + "rotation": {"angle": 0, "axis": "y", "origin": [0, 4, -8]}, + "faces": { + "north": {"uv": [0, 0, 16, 8], "texture": "#3", "cullface": "north"}, + "east": {"uv": [0, 8, 8, 12], "texture": "#2", "cullface": "east"}, + "south": {"uv": [0, 0, 16, 8], "texture": "#3", "cullface": "south"}, + "west": {"uv": [8, 8, 0, 12], "texture": "#2", "cullface": "west"}, + "up": {"uv": [0, 0, 8, 8], "texture": "#2", "cullface": "up"}, + "down": {"uv": [0, 0, 8, 8], "texture": "#2"} + } + } + ], + "display": { + "thirdperson_righthand": { + "rotation": [75, 45, 0], + "translation": [0, 2.5, 0], + "scale": [0.375, 0.375, 0.375] + }, + "thirdperson_lefthand": { + "rotation": [75, 45, 0], + "translation": [0, 2.5, 0], + "scale": [0.375, 0.375, 0.375] + }, + "firstperson_righthand": { + "rotation": [0, 45, 0], + "translation": [-1.5, 0, 0], + "scale": [0.4, 0.4, 0.4] + }, + "firstperson_lefthand": { + "rotation": [0, 45, 0], + "translation": [-1.5, 0, 0], + "scale": [0.4, 0.4, 0.4] + }, + "ground": { + "translation": [0, 3, -1], + "scale": [0.25, 0.25, 0.25] + }, + "gui": { + "rotation": [30, 225, 0], + "translation": [1.75, 1, 0], + "scale": [0.625, 0.625, 0.625] + }, + "head": { + "translation": [0, 0, -8] + }, + "fixed": { + "translation": [0, 0, -0.75], + "scale": [0.5, 0.5, 0.5] + } + } +} \ No newline at end of file diff --git a/common/src/main/resources/assets/createrailwaysnavigator/models/block/advanced_display_slab_neg.json b/common/src/main/resources/assets/createrailwaysnavigator/models/block/advanced_display_slab_neg.json new file mode 100644 index 00000000..37d2a568 --- /dev/null +++ b/common/src/main/resources/assets/createrailwaysnavigator/models/block/advanced_display_slab_neg.json @@ -0,0 +1,62 @@ +{ + "credit": "Made with Blockbench", + "texture_size": [64, 64], + "textures": { + "2": "createrailwaysnavigator:block/advanced_display_back", + "3": "createrailwaysnavigator:block/advanced_display_small", + "particle": "createrailwaysnavigator:block/advanced_display" + }, + "elements": [ + { + "from": [0, 0, 0], + "to": [16, 8, 16], + "rotation": {"angle": 0, "axis": "y", "origin": [0, -4, -8]}, + "faces": { + "north": {"uv": [0, 0, 16, 8], "texture": "#3", "cullface": "north"}, + "east": {"uv": [0, 8, 8, 12], "texture": "#2", "cullface": "east"}, + "south": {"uv": [0, 8, 8, 12], "texture": "#2", "cullface": "south"}, + "west": {"uv": [8, 8, 0, 12], "texture": "#2", "cullface": "west"}, + "up": {"uv": [0, 0, 8, 8], "texture": "#2"}, + "down": {"uv": [0, 0, 8, 8], "texture": "#2", "cullface": "down"} + } + } + ], + "display": { + "thirdperson_righthand": { + "rotation": [75, 45, 0], + "translation": [0, 2.5, 0], + "scale": [0.375, 0.375, 0.375] + }, + "thirdperson_lefthand": { + "rotation": [75, 45, 0], + "translation": [0, 2.5, 0], + "scale": [0.375, 0.375, 0.375] + }, + "firstperson_righthand": { + "rotation": [0, 45, 0], + "translation": [-1.5, 0, 0], + "scale": [0.4, 0.4, 0.4] + }, + "firstperson_lefthand": { + "rotation": [0, 45, 0], + "translation": [-1.5, 0, 0], + "scale": [0.4, 0.4, 0.4] + }, + "ground": { + "translation": [0, 3, -1], + "scale": [0.25, 0.25, 0.25] + }, + "gui": { + "rotation": [30, 225, 0], + "translation": [1.75, 1, 0], + "scale": [0.625, 0.625, 0.625] + }, + "head": { + "translation": [0, 0, -8] + }, + "fixed": { + "translation": [0, 0, -0.75], + "scale": [0.5, 0.5, 0.5] + } + } +} \ No newline at end of file diff --git a/common/src/main/resources/assets/createrailwaysnavigator/models/block/advanced_display_slab_pos.json b/common/src/main/resources/assets/createrailwaysnavigator/models/block/advanced_display_slab_pos.json new file mode 100644 index 00000000..6bf910ae --- /dev/null +++ b/common/src/main/resources/assets/createrailwaysnavigator/models/block/advanced_display_slab_pos.json @@ -0,0 +1,62 @@ +{ + "credit": "Made with Blockbench", + "texture_size": [64, 64], + "textures": { + "2": "createrailwaysnavigator:block/advanced_display_back", + "3": "createrailwaysnavigator:block/advanced_display_small", + "particle": "createrailwaysnavigator:block/advanced_display" + }, + "elements": [ + { + "from": [0, 8, 0], + "to": [16, 16, 16], + "rotation": {"angle": 0, "axis": "y", "origin": [0, 4, -8]}, + "faces": { + "north": {"uv": [0, 0, 16, 8], "texture": "#3", "cullface": "north"}, + "east": {"uv": [0, 8, 8, 12], "texture": "#2", "cullface": "east"}, + "south": {"uv": [0, 8, 8, 12], "texture": "#2", "cullface": "south"}, + "west": {"uv": [8, 8, 0, 12], "texture": "#2", "cullface": "west"}, + "up": {"uv": [0, 0, 8, 8], "texture": "#2", "cullface": "up"}, + "down": {"uv": [0, 0, 8, 8], "texture": "#2"} + } + } + ], + "display": { + "thirdperson_righthand": { + "rotation": [75, 45, 0], + "translation": [0, 2.5, 0], + "scale": [0.375, 0.375, 0.375] + }, + "thirdperson_lefthand": { + "rotation": [75, 45, 0], + "translation": [0, 2.5, 0], + "scale": [0.375, 0.375, 0.375] + }, + "firstperson_righthand": { + "rotation": [0, 45, 0], + "translation": [-1.5, 0, 0], + "scale": [0.4, 0.4, 0.4] + }, + "firstperson_lefthand": { + "rotation": [0, 45, 0], + "translation": [-1.5, 0, 0], + "scale": [0.4, 0.4, 0.4] + }, + "ground": { + "translation": [0, 3, -1], + "scale": [0.25, 0.25, 0.25] + }, + "gui": { + "rotation": [30, 225, 0], + "translation": [0, -2.25, 0], + "scale": [0.625, 0.625, 0.625] + }, + "head": { + "translation": [0, 0, -8] + }, + "fixed": { + "translation": [0, 0, -0.75], + "scale": [0.5, 0.5, 0.5] + } + } +} \ No newline at end of file diff --git a/common/src/main/resources/assets/createrailwaysnavigator/models/item/advanced_display_slab.json b/common/src/main/resources/assets/createrailwaysnavigator/models/item/advanced_display_slab.json new file mode 100644 index 00000000..21a0303f --- /dev/null +++ b/common/src/main/resources/assets/createrailwaysnavigator/models/item/advanced_display_slab.json @@ -0,0 +1,3 @@ +{ + "parent": "createrailwaysnavigator:block/advanced_display_slab_cen" +} \ No newline at end of file diff --git a/common/src/main/resources/assets/createrailwaysnavigator/textures/gui/container_blue.png b/common/src/main/resources/assets/createrailwaysnavigator/textures/gui/container_blue.png new file mode 100644 index 00000000..28ce1258 Binary files /dev/null and b/common/src/main/resources/assets/createrailwaysnavigator/textures/gui/container_blue.png differ diff --git a/common/src/main/resources/assets/createrailwaysnavigator/textures/gui/container_gold.png b/common/src/main/resources/assets/createrailwaysnavigator/textures/gui/container_gold.png new file mode 100644 index 00000000..89bb2be0 Binary files /dev/null and b/common/src/main/resources/assets/createrailwaysnavigator/textures/gui/container_gold.png differ diff --git a/common/src/main/resources/assets/createrailwaysnavigator/textures/gui/container_gray.png b/common/src/main/resources/assets/createrailwaysnavigator/textures/gui/container_gray.png new file mode 100644 index 00000000..78d41c62 Binary files /dev/null and b/common/src/main/resources/assets/createrailwaysnavigator/textures/gui/container_gray.png differ diff --git a/common/src/main/resources/assets/createrailwaysnavigator/textures/gui/container_purple.png b/common/src/main/resources/assets/createrailwaysnavigator/textures/gui/container_purple.png new file mode 100644 index 00000000..a5d2f574 Binary files /dev/null and b/common/src/main/resources/assets/createrailwaysnavigator/textures/gui/container_purple.png differ diff --git a/common/src/main/resources/assets/createrailwaysnavigator/textures/gui/gui.png b/common/src/main/resources/assets/createrailwaysnavigator/textures/gui/gui.png new file mode 100644 index 00000000..e77d23ee Binary files /dev/null and b/common/src/main/resources/assets/createrailwaysnavigator/textures/gui/gui.png differ diff --git a/common/src/main/resources/assets/createrailwaysnavigator/textures/gui/icons.png b/common/src/main/resources/assets/createrailwaysnavigator/textures/gui/icons.png index 3adddf9a..7f9006bf 100644 Binary files a/common/src/main/resources/assets/createrailwaysnavigator/textures/gui/icons.png and b/common/src/main/resources/assets/createrailwaysnavigator/textures/gui/icons.png differ diff --git a/common/src/main/resources/assets/createrailwaysnavigator/textures/gui/section_settings.png b/common/src/main/resources/assets/createrailwaysnavigator/textures/gui/section_settings.png new file mode 100644 index 00000000..afa84130 Binary files /dev/null and b/common/src/main/resources/assets/createrailwaysnavigator/textures/gui/section_settings.png differ diff --git a/common/src/main/resources/assets/createrailwaysnavigator/textures/gui/shadow.png b/common/src/main/resources/assets/createrailwaysnavigator/textures/gui/shadow.png new file mode 100644 index 00000000..d408b047 Binary files /dev/null and b/common/src/main/resources/assets/createrailwaysnavigator/textures/gui/shadow.png differ diff --git a/common/src/main/resources/assets/createrailwaysnavigator/textures/gui/widgets.png b/common/src/main/resources/assets/createrailwaysnavigator/textures/gui/widgets.png index b02863e6..8d6cee53 100644 Binary files a/common/src/main/resources/assets/createrailwaysnavigator/textures/gui/widgets.png and b/common/src/main/resources/assets/createrailwaysnavigator/textures/gui/widgets.png differ diff --git a/common/src/main/resources/assets/createrailwaysnavigator/textures/mod_icon.png b/common/src/main/resources/assets/createrailwaysnavigator/textures/mod_icon.png index 632b7c73..62ba0651 100644 Binary files a/common/src/main/resources/assets/createrailwaysnavigator/textures/mod_icon.png and b/common/src/main/resources/assets/createrailwaysnavigator/textures/mod_icon.png differ diff --git a/common/src/main/resources/createrailwaysnavigator.accesswidener b/common/src/main/resources/createrailwaysnavigator.accesswidener index d22c6f26..443dfac2 100644 --- a/common/src/main/resources/createrailwaysnavigator.accesswidener +++ b/common/src/main/resources/createrailwaysnavigator.accesswidener @@ -1,2 +1,4 @@ accessWidener v2 named -accessible class net/minecraft/client/gui/Font$StringRenderOutput \ No newline at end of file +accessible class net/minecraft/client/gui/Font$StringRenderOutput +accessible field net/minecraft/client/gui/chat/NarratorChatListener narrator Lcom/mojang/text2speech/Narrator; +accessible field net/minecraft/client/gui/screens/inventory/AbstractContainerScreen topPos I \ No newline at end of file diff --git a/common/src/main/resources/createrailwaysnavigator.mixins.json b/common/src/main/resources/createrailwaysnavigator.mixins.json index e70bfca5..e4c0bd9b 100644 --- a/common/src/main/resources/createrailwaysnavigator.mixins.json +++ b/common/src/main/resources/createrailwaysnavigator.mixins.json @@ -4,10 +4,19 @@ "compatibilityLevel": "JAVA_17", "minVersion": "0.8", "client": [ + "ScheduleScreenAccessor", + "ModularGuiLineBuilderAccessor", + "ScheduleScreenMixin" ], "mixins": [ - "ScheduleDataAccessor", - "MountedStorageManagerMixin" + "ScheduleRuntimeAccessor", + "TrainStatusAccessor", + "ControlsHandlerMixin", + "MountedStorageManagerMixin", + "ScheduleRuntimeMixin", + "GlobalTrainDisplayDataMixin", + "TrainMixin", + "ReloadableServerResourcesMixin" ], "injectors": { "defaultRequire": 1 diff --git a/common/src/main/resources/data/createrailwaysnavigator/crn_website/index.html b/common/src/main/resources/data/createrailwaysnavigator/crn_website/index.html new file mode 100644 index 00000000..80d22a9f --- /dev/null +++ b/common/src/main/resources/data/createrailwaysnavigator/crn_website/index.html @@ -0,0 +1,13 @@ + + + + + + Create Railways Navigator + + + +

HELLO CRN WORLD!

+ + + \ No newline at end of file diff --git a/common/src/main/resources/data/createrailwaysnavigator/crn_website/script.js b/common/src/main/resources/data/createrailwaysnavigator/crn_website/script.js new file mode 100644 index 00000000..dd6aceff --- /dev/null +++ b/common/src/main/resources/data/createrailwaysnavigator/crn_website/script.js @@ -0,0 +1,4 @@ +function test() { + let text = document.getElementById("text").innerText; + alert(text); +} \ No newline at end of file diff --git a/common/src/main/resources/data/createrailwaysnavigator/crn_website/style.css b/common/src/main/resources/data/createrailwaysnavigator/crn_website/style.css new file mode 100644 index 00000000..bbac77c3 --- /dev/null +++ b/common/src/main/resources/data/createrailwaysnavigator/crn_website/style.css @@ -0,0 +1,10 @@ + +.button { + border: none; + color: white; + padding: 15px 32px; + text-align: center; + text-decoration: none; + display: inline-block; + font-size: 16px; + } \ No newline at end of file diff --git a/common/src/main/resources/data/createrailwaysnavigator/loot_tables/blocks/advanced_display_slab.json b/common/src/main/resources/data/createrailwaysnavigator/loot_tables/blocks/advanced_display_slab.json new file mode 100644 index 00000000..22c4d72a --- /dev/null +++ b/common/src/main/resources/data/createrailwaysnavigator/loot_tables/blocks/advanced_display_slab.json @@ -0,0 +1,20 @@ +{ + "type": "minecraft:block", + "pools": [ + { + "rolls": 1.0, + "bonus_rolls": 0.0, + "entries": [ + { + "type": "minecraft:item", + "name": "createrailwaysnavigator:advanced_display_slab" + } + ], + "conditions": [ + { + "condition": "minecraft:survives_explosion" + } + ] + } + ] + } \ No newline at end of file diff --git a/common/src/main/resources/data/createrailwaysnavigator/tags/blocks/advanced_displays.json b/common/src/main/resources/data/createrailwaysnavigator/tags/blocks/advanced_displays.json index 56791a79..2d5e4f03 100644 --- a/common/src/main/resources/data/createrailwaysnavigator/tags/blocks/advanced_displays.json +++ b/common/src/main/resources/data/createrailwaysnavigator/tags/blocks/advanced_displays.json @@ -6,6 +6,7 @@ "createrailwaysnavigator:advanced_display_panel", "createrailwaysnavigator:advanced_display_half_panel", "createrailwaysnavigator:advanced_display_small", - "createrailwaysnavigator:advanced_display_sloped" + "createrailwaysnavigator:advanced_display_sloped", + "createrailwaysnavigator:advanced_display_slab" ] } \ No newline at end of file diff --git a/common/src/main/resources/data/createrailwaysnavigator/tags/items/advanced_displays.json b/common/src/main/resources/data/createrailwaysnavigator/tags/items/advanced_displays.json index 07778035..21d1678f 100644 --- a/common/src/main/resources/data/createrailwaysnavigator/tags/items/advanced_displays.json +++ b/common/src/main/resources/data/createrailwaysnavigator/tags/items/advanced_displays.json @@ -6,6 +6,7 @@ "createrailwaysnavigator:advanced_display_panel", "createrailwaysnavigator:advanced_display_half_panel", "createrailwaysnavigator:advanced_display_sloped", - "createrailwaysnavigator:advanced_display_small" + "createrailwaysnavigator:advanced_display_small", + "createrailwaysnavigator:advanced_display_slab" ] } \ No newline at end of file diff --git a/common/src/main/resources/data/minecraft/tags/blocks/mineable/pickaxe.json b/common/src/main/resources/data/minecraft/tags/blocks/mineable/pickaxe.json index 059f2a61..060c572d 100644 --- a/common/src/main/resources/data/minecraft/tags/blocks/mineable/pickaxe.json +++ b/common/src/main/resources/data/minecraft/tags/blocks/mineable/pickaxe.json @@ -7,6 +7,7 @@ "createrailwaysnavigator:advanced_display_half_panel", "createrailwaysnavigator:advanced_display_small", "createrailwaysnavigator:advanced_display_sloped", + "createrailwaysnavigator:advanced_display_slab", "createrailwaysnavigator:train_station_clock" ] } \ No newline at end of file diff --git a/common/src/main/resources/icon.png b/common/src/main/resources/icon.png index 32e3e897..11fbdf9d 100644 Binary files a/common/src/main/resources/icon.png and b/common/src/main/resources/icon.png differ diff --git a/fabric/src/main/java/de/mrjulsen/crn/fabric/CreateRailwaysNavigatorFabric.java b/fabric/src/main/java/de/mrjulsen/crn/fabric/CreateRailwaysNavigatorFabric.java index 473d1c9d..a96fe7be 100644 --- a/fabric/src/main/java/de/mrjulsen/crn/fabric/CreateRailwaysNavigatorFabric.java +++ b/fabric/src/main/java/de/mrjulsen/crn/fabric/CreateRailwaysNavigatorFabric.java @@ -17,9 +17,9 @@ public void onInitialize() { CreateRailwaysNavigator.REGISTRATE.register(); if (Platform.getEnvironment() == Env.CLIENT) { - ClientWorldEvents.LOAD.register((mc, level) -> ModExtras.register()); + ClientWorldEvents.LOAD.register((mc, level) -> ModExtras.init()); } - ServerWorldEvents.LOAD.register((server, level) -> ModExtras.register()); - ModExtras.register(); + ServerWorldEvents.LOAD.register((server, level) -> ModExtras.init()); + ModExtras.init(); } } diff --git a/fabric/src/main/resources/data/createrailwaysnavigator/recipes/advanced_display_slab.json b/fabric/src/main/resources/data/createrailwaysnavigator/recipes/advanced_display_slab.json new file mode 100644 index 00000000..d140d663 --- /dev/null +++ b/fabric/src/main/resources/data/createrailwaysnavigator/recipes/advanced_display_slab.json @@ -0,0 +1,15 @@ +{ + "type": "minecraft:crafting_shaped", + "pattern": [ + "BBB" + ], + "key": { + "B": { + "item": "createrailwaysnavigator:advanced_display_block" + } + }, + "result": { + "item": "createrailwaysnavigator:advanced_display_slab", + "count": 6 + } + } \ No newline at end of file diff --git a/fabric/src/main/resources/fabric.mod.json b/fabric/src/main/resources/fabric.mod.json index b6c4f67a..e42cd924 100644 --- a/fabric/src/main/resources/fabric.mod.json +++ b/fabric/src/main/resources/fabric.mod.json @@ -27,7 +27,7 @@ "fabric": "*", "minecraft": ">=1.19.2", "architectury": ">=6.5.85", - "dragonlib": ">=1.19.2-2.1.13", + "dragonlib": ">=1.19.2-2.2.16", "create": "*" }, "accessWidener": "createrailwaysnavigator.accesswidener" diff --git a/forge/src/main/resources/META-INF/mods.toml b/forge/src/main/resources/META-INF/mods.toml index 633a5c4d..abfbcae1 100644 --- a/forge/src/main/resources/META-INF/mods.toml +++ b/forge/src/main/resources/META-INF/mods.toml @@ -40,7 +40,7 @@ side = "BOTH" [[dependencies.createrailwaysnavigator]] modId = "dragonlib" mandatory = true -versionRange = "[1.19.2-2.1.13,)" +versionRange = "[1.19.2-2.2.16,)" ordering = "AFTER" side = "BOTH" diff --git a/forge/src/main/resources/data/createrailwaysnavigator/recipes/advanced_display_slab.json b/forge/src/main/resources/data/createrailwaysnavigator/recipes/advanced_display_slab.json new file mode 100644 index 00000000..d140d663 --- /dev/null +++ b/forge/src/main/resources/data/createrailwaysnavigator/recipes/advanced_display_slab.json @@ -0,0 +1,15 @@ +{ + "type": "minecraft:crafting_shaped", + "pattern": [ + "BBB" + ], + "key": { + "B": { + "item": "createrailwaysnavigator:advanced_display_block" + } + }, + "result": { + "item": "createrailwaysnavigator:advanced_display_slab", + "count": 6 + } + } \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 83ffdc0c..38fadcf8 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,7 +3,7 @@ org.gradle.jvmargs=-Xmx6G org.gradle.parallel=true # Mod properties -mod_version = 0.5.4 +mod_version = 0.6.0 release_channel = beta maven_group = de.mrjulsen.crn archives_name = createrailwaysnavigator @@ -19,7 +19,7 @@ fabric_loader_version = 0.15.11 fabric_api_version = 0.77.0+1.19.2 forge_version = 1.19.2-43.3.13 -dragonlib_version = 2.1.13 +dragonlib_version = 2.2.16 modmenu_version=4.1.2 forge_config_api_port_version=4.2.11 create_fabric_version = 0.5.1-f-build.1416+mc1.19.2 diff --git a/icon.png b/icon.png index dbf0e242..11fbdf9d 100644 Binary files a/icon.png and b/icon.png differ diff --git a/icon_256px.png b/icon_256px.png index 32e3e897..838d1695 100644 Binary files a/icon_256px.png and b/icon_256px.png differ diff --git a/reset.bat b/reset.bat new file mode 100644 index 00000000..a97c1c82 --- /dev/null +++ b/reset.bat @@ -0,0 +1,11 @@ +@echo off +setlocal + +rem Remove all directories with .gradle... +for /d /r %%G in (*.gradle) do ( + echo Remove directory: %%G + rd /s /q "%%G" +) + +echo Done! +endlocal