- Added fullscreen map integration for FTB Chunks and Journeymap
This commit is contained in:
simibubi 2024-09-03 12:58:02 +02:00
parent 59a3ab244b
commit 97205a7d51
39 changed files with 2222 additions and 74 deletions

View file

@ -141,6 +141,22 @@ repositories {
name = "squiddev"
url = "https://squiddev.cc/maven/"
}
maven {
name = "ftb"
url = "https://maven.saps.dev/releases"
}
maven {
name = "architectury"
url = "https://maven.architectury.dev/"
}
maven {
url = "https://jm.gserv.me/repository/maven-public/"
content {
includeGroup "info.journeymap"
includeGroup "mysticdrew"
}
}
maven {
url = 'https://www.cursemaven.com'
@ -200,6 +216,14 @@ dependencies {
// implementation fg.deobf("curse.maven:ic2-classic-242942:5555152")
// implementation fg.deobf("curse.maven:druidcraft-340991:3101903")
// implementation fg.deobf("com.railwayteam.railways:railways-1.19.2-1.6.4:all") { transitive = false }
implementation fg.deobf("dev.architectury:architectury-forge:9.1.12")
implementation fg.deobf("dev.ftb.mods:ftb-chunks-forge:2001.3.1")
implementation fg.deobf("dev.ftb.mods:ftb-teams-forge:2001.3.0")
implementation fg.deobf("dev.ftb.mods:ftb-library-forge:2001.2.4")
implementation fg.deobf("curse.maven:journeymap-32274:5457831")
// implementation fg.deobf("ignored:journeymap-1.20.1-5.10.1-forge")
// runtimeOnly fg.deobf("curse.maven:framedblocks-441647:5399211")
// runtimeOnly fg.deobf("curse.maven:galosphere-631098:4574834")

View file

@ -8,7 +8,7 @@ mod_version = 0.5.1.g
artifact_minecraft_version = 1.20.1
minecraft_version = 1.20.1
forge_version = 47.1.43
forge_version = 47.1.47
# build dependency versions
forgegradle_version = 6.0.6

View file

@ -1,4 +1,4 @@
// 1.20.1 2024-08-08T08:37:57.7244288 Registrate Provider for create [Recipes, Advancements, Loot Tables, Tags (blocks), Tags (items), Tags (fluids), Tags (entity_types), Blockstates, Item models, Lang (en_us/en_ud)]
// 1.20.1 2024-09-03T11:32:11.6637155 Registrate Provider for create [Recipes, Advancements, Loot Tables, Tags (blocks), Tags (items), Tags (fluids), Tags (entity_types), Blockstates, Item models, Lang (en_us/en_ud)]
60bbdf92d2ac9824ea6144955c74043a6005f79d assets/create/blockstates/acacia_window.json
6a67703c2697d81b7dc83e9d72a66f9c9ff08383 assets/create/blockstates/acacia_window_pane.json
c3ae87b62e81d8e9476eccd793bb1548d74c66a1 assets/create/blockstates/adjustable_chain_gearshift.json
@ -585,8 +585,8 @@ b0d8f08968763a5f74e5cd5644377a76a9f39753 assets/create/blockstates/yellow_toolbo
fe8c497aacc641c2f01cec90bba9f19e59cc2ed2 assets/create/blockstates/yellow_valve_handle.json
e819e93fdcbe9fd9c050a052d2718ff3b3539365 assets/create/blockstates/zinc_block.json
64121dcb216381c83b4fe28aa361ea07c24c9ad0 assets/create/blockstates/zinc_ore.json
b9295b12fb7ba72de2f245064305b9dd4d6f9ce1 assets/create/lang/en_ud.json
16147eb4e472f1e670bcfc65a13ebcda0c8f58b8 assets/create/lang/en_us.json
1195fdc4fb51659c921e2bbe744a35107f787aa2 assets/create/lang/en_ud.json
632d1aac7255fc0f4804f4df138ce9926134d2f9 assets/create/lang/en_us.json
a97e1060e00ae701a02e39cd4ef8054cf345fac4 assets/create/models/block/acacia_window.json
103e032c0b1a0a6a27c67da8c91179a564bd281c assets/create/models/block/acacia_window_pane_noside.json
fb00b627abda76ad4fea867ca57dbfadd24fffa3 assets/create/models/block/acacia_window_pane_noside_alt.json

View file

@ -2479,6 +2479,7 @@
"create.station.remove_auto_schedule": ןnpǝɥɔS-oʇnⱯ pɹɐɔsıᗡ",
"create.station.remove_schedule": ןnpǝɥɔS ǝʌǝıɹʇǝᴚ",
"create.station.retry": "ʎɹʇǝɹ puɐ sıɥʇ ǝʌןosǝᴚ",
"create.station.train_map_color": "sdɐW uo ɹoןoƆ",
"create.station.train_not_aligned": "'ǝןqɯǝssɐsıp ʇouuɐƆ",
"create.station.train_not_aligned_1": "pǝubıןɐ sǝbɐıɹɹɐɔ ןןɐ ʇou",
"create.subtitle.blaze_munch": "sǝɥɔunɯ ɹǝuɹnᗺ ǝzɐןᗺ",
@ -2649,6 +2650,21 @@
"create.train_assembly.sideways_controls": "sʎɐʍǝpıs ǝɔɐɟ ʇouuɐɔ sןoɹʇuoƆ uıɐɹ⟘",
"create.train_assembly.single_bogey_carriage": "uʍo sʇı uo ǝbɐıɹɹɐɔ ɐ ʇɹoddns ʇouuɐɔ ǝdʎʇ ʎǝboᗺ sıɥ⟘",
"create.train_assembly.too_many_bogeys": "%1$s :pǝɥɔɐʇʇɐ sʎǝboᗺ ʎuɐɯ oo⟘",
"create.train_map.cannot_traverse_section": "ǝsɹǝʌɐɹʇ ʎןןnɟ ʇouuɐƆ ",
"create.train_map.conductor_missing": "buıssıW ɹoʇɔnpuoƆ >¡< ",
"create.train_map.derailed": "pǝןıɐɹǝᗡ >¡< ",
"create.train_map.for_other_train": "%1$s ɹoɟ ",
"create.train_map.fuel_boosted": "✔ pǝʇsooq ןǝnℲ ",
"create.train_map.navigation_failed": "pǝןıɐℲ uoıʇɐbıʌɐN >¡< ",
"create.train_map.player_controlled": "ɹǝʎɐןԀ ʎq pǝןןoɹʇuoƆ >- ",
"create.train_map.redstone_powered": "pǝɹǝʍoԀ ǝuoʇspǝᴚ ",
"create.train_map.schedule_interrupted": "pǝʇdnɹɹǝʇuI ǝןnpǝɥɔS >¡< ",
"create.train_map.section_reserved": "pǝʌɹǝsǝɹ uoıʇɔǝS ",
"create.train_map.toggle": "ʎɐןɹǝʌo ʞɹoʍʇǝu uıɐɹ⟘",
"create.train_map.train_at_station": "%1$s |> ",
"create.train_map.train_moving_to_station": ")ɯ%2$s( %1$s >> ",
"create.train_map.train_owned_by": "%1$s ʎq",
"create.train_map.waiting_at_signal": "ןɐubıS ʇɐ buıʇıɐM ",
"create.tunnel.selection_mode.forced_round_robin": "uıqoᴚ punoᴚ pǝɔɹoℲ",
"create.tunnel.selection_mode.forced_split": ıןdS pǝɔɹoℲ",
"create.tunnel.selection_mode.prefer_nearest": "ʇsǝɹɐǝN ɹǝɟǝɹԀ",

View file

@ -2479,6 +2479,7 @@
"create.station.remove_auto_schedule": "Discard Auto-Schedule",
"create.station.remove_schedule": "Retrieve Schedule",
"create.station.retry": "Resolve this and retry",
"create.station.train_map_color": "Color on Maps",
"create.station.train_not_aligned": "Cannot disassemble,",
"create.station.train_not_aligned_1": "not all carriages aligned",
"create.subtitle.blaze_munch": "Blaze Burner munches",
@ -2649,6 +2650,21 @@
"create.train_assembly.sideways_controls": "Train Controls cannot face sideways",
"create.train_assembly.single_bogey_carriage": "This Bogey type cannot support a carriage on its own",
"create.train_assembly.too_many_bogeys": "Too many Bogeys attached: %1$s",
"create.train_map.cannot_traverse_section": " Cannot fully traverse",
"create.train_map.conductor_missing": " <!> Conductor Missing",
"create.train_map.derailed": " <!> Derailed",
"create.train_map.for_other_train": " for %1$s",
"create.train_map.fuel_boosted": " Fuel boosted ✔",
"create.train_map.navigation_failed": " <!> Navigation Failed",
"create.train_map.player_controlled": " -> Controlled by Player",
"create.train_map.redstone_powered": " Redstone Powered",
"create.train_map.schedule_interrupted": " <!> Schedule Interrupted",
"create.train_map.section_reserved": " Section reserved",
"create.train_map.toggle": "Train network overlay",
"create.train_map.train_at_station": " >| %1$s",
"create.train_map.train_moving_to_station": " >> %1$s (%2$sm)",
"create.train_map.train_owned_by": "by %1$s",
"create.train_map.waiting_at_signal": " Waiting at Signal",
"create.tunnel.selection_mode.forced_round_robin": "Forced Round Robin",
"create.tunnel.selection_mode.forced_split": "Forced Split",
"create.tunnel.selection_mode.prefer_nearest": "Prefer Nearest",

View file

@ -8,6 +8,8 @@ import java.util.function.Function;
import java.util.function.Supplier;
import com.simibubi.create.compat.computercraft.AttachedComputerPacket;
import com.simibubi.create.compat.trainmap.TrainMapSyncPacket;
import com.simibubi.create.compat.trainmap.TrainMapSyncRequestPacket;
import com.simibubi.create.content.contraptions.ContraptionBlockChangedPacket;
import com.simibubi.create.content.contraptions.ContraptionColliderLockPacket;
import com.simibubi.create.content.contraptions.ContraptionColliderLockPacket.ContraptionColliderLockPacketRequest;
@ -164,6 +166,7 @@ public enum AllPackets {
CLIPBOARD_EDIT(ClipboardEditPacket.class, ClipboardEditPacket::new, PLAY_TO_SERVER),
CONTRAPTION_COLLIDER_LOCK_REQUEST(ContraptionColliderLockPacketRequest.class,
ContraptionColliderLockPacketRequest::new, PLAY_TO_SERVER),
TRAIN_MAP_REQUEST(TrainMapSyncRequestPacket.class, TrainMapSyncRequestPacket::new, PLAY_TO_SERVER),
// Server to Client
SYMMETRY_EFFECT(SymmetryEffectPacket.class, SymmetryEffectPacket::new, PLAY_TO_CLIENT),
@ -208,7 +211,8 @@ public enum AllPackets {
CONTRAPTION_ACTOR_TOGGLE(ContraptionDisableActorPacket.class, ContraptionDisableActorPacket::new, PLAY_TO_CLIENT),
CONTRAPTION_COLLIDER_LOCK(ContraptionColliderLockPacket.class, ContraptionColliderLockPacket::new, PLAY_TO_CLIENT),
ATTACHED_COMPUTER(AttachedComputerPacket.class, AttachedComputerPacket::new, PLAY_TO_CLIENT),
SERVER_DEBUG_INFO(ServerDebugInfoPacket.class, ServerDebugInfoPacket::new, PLAY_TO_CLIENT)
SERVER_DEBUG_INFO(ServerDebugInfoPacket.class, ServerDebugInfoPacket::new, PLAY_TO_CLIENT),
TRAIN_MAP_SYNC(TrainMapSyncPacket.class, TrainMapSyncPacket::new, PLAY_TO_CLIENT)
;
public static final ResourceLocation CHANNEL_NAME = Create.asResource("main");

View file

@ -31,7 +31,9 @@ public enum Mods {
TCONSTRUCT,
FRAMEDBLOCKS,
XLPACKETS,
MODERNUI;
MODERNUI,
FTBCHUNKS,
JOURNEYMAP;
private final String id;

View file

@ -129,7 +129,7 @@ public class StationPeripheral extends SyncedPeripheral<StationBlockEntity> {
public final void setTrainName(String name) throws LuaException {
Train train = getTrainOrThrow();
train.name = Components.literal(name);
AllPackets.getChannel().send(PacketDistributor.ALL.noArg(), new TrainEditPacket.TrainEditReturnPacket(train.id, name, train.icon.getId()));
AllPackets.getChannel().send(PacketDistributor.ALL.noArg(), new TrainEditPacket.TrainEditReturnPacket(train.id, name, train.icon.getId(), train.mapColorIndex));
}
@LuaFunction

View file

@ -0,0 +1,158 @@
package com.simibubi.create.compat.trainmap;
import java.util.List;
import com.mojang.blaze3d.vertex.PoseStack;
import com.simibubi.create.foundation.gui.RemovedGuiUtils;
import com.simibubi.create.foundation.utility.Lang;
import com.simibubi.create.infrastructure.config.AllConfigs;
import dev.ftb.mods.ftbchunks.client.gui.LargeMapScreen;
import dev.ftb.mods.ftbchunks.client.gui.RegionMapPanel;
import dev.ftb.mods.ftblibrary.ui.ScreenWrapper;
import dev.ftb.mods.ftblibrary.ui.Widget;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.client.renderer.Rect2i;
import net.minecraft.network.chat.FormattedText;
import net.minecraft.util.Mth;
import net.minecraftforge.client.event.InputEvent;
import net.minecraftforge.client.event.RenderTooltipEvent;
import net.minecraftforge.client.event.ScreenEvent;
import net.minecraftforge.fml.util.ObfuscationReflectionHelper;
public class FTBChunksTrainMap {
private static int cancelTooltips = 0;
private static boolean renderingTooltip = false;
private static boolean requesting;
public static void tick() {
if (cancelTooltips > 0)
cancelTooltips--;
if (!AllConfigs.client().showTrainMapOverlay.get()
|| getAsLargeMapScreen(Minecraft.getInstance().screen) == null) {
if (requesting)
TrainMapSyncClient.stopRequesting();
requesting = false;
return;
}
TrainMapManager.tick();
requesting = true;
TrainMapSyncClient.requestData();
}
public static void cancelTooltips(RenderTooltipEvent.Pre event) {
if (getAsLargeMapScreen(Minecraft.getInstance().screen) == null)
return;
if (renderingTooltip || cancelTooltips == 0)
return;
event.setCanceled(true);
}
public static void mouseClick(InputEvent.MouseButton.Pre event) {
LargeMapScreen screen = getAsLargeMapScreen(Minecraft.getInstance().screen);
if (screen == null)
return;
if (TrainMapManager.handleToggleWidgetClick(screen.getMouseX(), screen.getMouseY(), 20, 2))
event.setCanceled(true);
}
public static void renderGui(ScreenEvent.Render.Post event) {
LargeMapScreen largeMapScreen = getAsLargeMapScreen(event.getScreen());
if (largeMapScreen == null)
return;
Object panel = ObfuscationReflectionHelper.getPrivateValue(LargeMapScreen.class, largeMapScreen, "regionPanel");
if (!(panel instanceof RegionMapPanel regionMapPanel))
return;
GuiGraphics graphics = event.getGuiGraphics();
if (!AllConfigs.client().showTrainMapOverlay.get()) {
renderToggleWidgetAndTooltip(event, largeMapScreen, graphics);
return;
}
int blocksPerRegion = 16 * 32;
int minX = Mth.floor(regionMapPanel.getScrollX());
int minY = Mth.floor(regionMapPanel.getScrollY());
float regionTileSize = largeMapScreen.getRegionTileSize() / (float) blocksPerRegion;
int regionMinX =
ObfuscationReflectionHelper.getPrivateValue(RegionMapPanel.class, regionMapPanel, "regionMinX");
int regionMinZ =
ObfuscationReflectionHelper.getPrivateValue(RegionMapPanel.class, regionMapPanel, "regionMinZ");
float mouseX = event.getMouseX();
float mouseY = event.getMouseY();
boolean linearFiltering = largeMapScreen.getRegionTileSize() * Minecraft.getInstance()
.getWindow()
.getGuiScale() < 512D;
PoseStack pose = graphics.pose();
pose.pushPose();
pose.translate(-minX, -minY, 0);
pose.scale(regionTileSize, regionTileSize, 1);
pose.translate(-regionMinX * blocksPerRegion, -regionMinZ * blocksPerRegion, 0);
mouseX += minX;
mouseY += minY;
mouseX /= regionTileSize;
mouseY /= regionTileSize;
mouseX += regionMinX * blocksPerRegion;
mouseY += regionMinZ * blocksPerRegion;
Rect2i bounds = new Rect2i(Mth.floor(minX / regionTileSize + regionMinX * blocksPerRegion),
Mth.floor(minY / regionTileSize + regionMinZ * blocksPerRegion),
Mth.floor(largeMapScreen.width / regionTileSize), Mth.floor(largeMapScreen.height / regionTileSize));
List<FormattedText> tooltip = TrainMapManager.renderAndPick(graphics, Mth.floor(mouseX), Mth.floor(mouseY),
event.getPartialTick(), linearFiltering, bounds);
pose.popPose();
if (!renderToggleWidgetAndTooltip(event, largeMapScreen, graphics) && tooltip != null) {
renderingTooltip = true;
RemovedGuiUtils.drawHoveringText(graphics, tooltip, event.getMouseX(), event.getMouseY(),
largeMapScreen.width, largeMapScreen.height, 256, Minecraft.getInstance().font);
renderingTooltip = false;
cancelTooltips = 5;
}
pose.pushPose();
pose.translate(0, 0, 300);
for (Widget widget : largeMapScreen.getWidgets()) {
if (!widget.isEnabled())
continue;
if (widget == panel)
continue;
widget.draw(graphics, largeMapScreen.getTheme(), widget.getPosX(), widget.getPosY(), widget.getWidth(),
widget.getHeight());
}
pose.popPose();
}
private static boolean renderToggleWidgetAndTooltip(ScreenEvent.Render.Post event, LargeMapScreen largeMapScreen,
GuiGraphics graphics) {
TrainMapManager.renderToggleWidget(graphics, 20, 2);
if (!TrainMapManager.isToggleWidgetHovered(event.getMouseX(), event.getMouseY(), 20, 2))
return false;
renderingTooltip = true;
RemovedGuiUtils.drawHoveringText(graphics, List.of(Lang.translate("train_map.toggle")
.component()), event.getMouseX(), event.getMouseY() + 20, largeMapScreen.width, largeMapScreen.height, 256,
Minecraft.getInstance().font);
renderingTooltip = false;
cancelTooltips = 5;
return true;
}
private static LargeMapScreen getAsLargeMapScreen(Screen screen) {
if (!(screen instanceof ScreenWrapper screenWrapper))
return null;
Object wrapped = ObfuscationReflectionHelper.getPrivateValue(ScreenWrapper.class, screenWrapper, "wrappedGui");
if (!(wrapped instanceof LargeMapScreen largeMapScreen))
return null;
return largeMapScreen;
}
}

View file

@ -0,0 +1,108 @@
package com.simibubi.create.compat.trainmap;
import java.util.List;
import com.mojang.blaze3d.platform.Window;
import com.mojang.blaze3d.vertex.PoseStack;
import com.simibubi.create.foundation.gui.RemovedGuiUtils;
import com.simibubi.create.foundation.utility.Lang;
import com.simibubi.create.infrastructure.config.AllConfigs;
import journeymap.client.api.display.Context.UI;
import journeymap.client.api.util.UIState;
import journeymap.client.ui.fullscreen.Fullscreen;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.renderer.Rect2i;
import net.minecraft.network.chat.FormattedText;
import net.minecraft.util.Mth;
import net.minecraftforge.client.event.InputEvent;
public class JourneyTrainMap {
private static boolean requesting;
public static void tick() {
if (!AllConfigs.client().showTrainMapOverlay.get() || !(Minecraft.getInstance().screen instanceof Fullscreen)) {
if (requesting)
TrainMapSyncClient.stopRequesting();
requesting = false;
return;
}
TrainMapManager.tick();
requesting = true;
TrainMapSyncClient.requestData();
}
public static void mouseClick(InputEvent.MouseButton.Pre event) {
Minecraft mc = Minecraft.getInstance();
if (!(mc.screen instanceof Fullscreen screen))
return;
Window window = mc.getWindow();
double mX = mc.mouseHandler.xpos() * window.getGuiScaledWidth() / window.getScreenWidth();
double mY = mc.mouseHandler.ypos() * window.getGuiScaledHeight() / window.getScreenHeight();
if (TrainMapManager.handleToggleWidgetClick(Mth.floor(mX), Mth.floor(mY), 3, 30))
event.setCanceled(true);
}
// Called by JourneyFullscreenMapMixin
public static void onRender(GuiGraphics graphics, Fullscreen screen, double x, double z, int mX, int mY, float pt) {
UIState state = screen.getUiState();
if (state == null)
return;
if (state.ui != UI.Fullscreen)
return;
if (!state.active)
return;
if (!AllConfigs.client().showTrainMapOverlay.get()) {
renderToggleWidgetAndTooltip(graphics, screen, mX, mY);
return;
}
Minecraft mc = Minecraft.getInstance();
Window window = mc.getWindow();
double guiScale = (double) window.getScreenWidth() / window.getGuiScaledWidth();
double scale = state.blockSize / guiScale;
PoseStack pose = graphics.pose();
pose.pushPose();
pose.translate(screen.width / 2.0f, screen.height / 2.0f, 0);
pose.scale((float) scale, (float) scale, 1);
pose.translate(-x, -z, 0);
float mouseX = mX - screen.width / 2.0f;
float mouseY = mY - screen.height / 2.0f;
mouseX /= scale;
mouseY /= scale;
mouseX += x;
mouseY += z;
Rect2i bounds =
new Rect2i(Mth.floor(-screen.width / 2.0f / scale + x), Mth.floor(-screen.height / 2.0f / scale + z),
Mth.floor(screen.width / scale), Mth.floor(screen.height / scale));
List<FormattedText> tooltip =
TrainMapManager.renderAndPick(graphics, Mth.floor(mouseX), Mth.floor(mouseY), pt, false, bounds);
pose.popPose();
if (!renderToggleWidgetAndTooltip(graphics, screen, mX, mY) && tooltip != null)
RemovedGuiUtils.drawHoveringText(graphics, tooltip, mX, mY, screen.width, screen.height, 256, mc.font);
}
private static boolean renderToggleWidgetAndTooltip(GuiGraphics graphics, Fullscreen screen, int mouseX,
int mouseY) {
TrainMapManager.renderToggleWidget(graphics, 3, 30);
if (!TrainMapManager.isToggleWidgetHovered(mouseX, mouseY, 3, 30))
return false;
RemovedGuiUtils.drawHoveringText(graphics, List.of(Lang.translate("train_map.toggle")
.component()), mouseX, mouseY + 20, screen.width, screen.height, 256, Minecraft.getInstance().font);
return true;
}
}

View file

@ -0,0 +1,56 @@
package com.simibubi.create.compat.trainmap;
import com.mojang.blaze3d.platform.InputConstants;
import com.simibubi.create.compat.Mods;
import net.minecraft.client.Minecraft;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.client.event.InputEvent;
import net.minecraftforge.client.event.RenderTooltipEvent;
import net.minecraftforge.client.event.ScreenEvent;
import net.minecraftforge.event.TickEvent.ClientTickEvent;
import net.minecraftforge.event.TickEvent.Phase;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod.EventBusSubscriber;
@EventBusSubscriber(value = Dist.CLIENT)
public class TrainMapEvents {
@SubscribeEvent
public static void tick(ClientTickEvent event) {
if (event.phase == Phase.START)
return;
Minecraft mc = Minecraft.getInstance();
if (mc.level == null)
return;
if (Mods.FTBCHUNKS.isLoaded())
FTBChunksTrainMap.tick();
if (Mods.JOURNEYMAP.isLoaded())
JourneyTrainMap.tick();
}
@SubscribeEvent
public static void mouseClick(InputEvent.MouseButton.Pre event) {
if (event.getAction() != InputConstants.PRESS)
return;
if (Mods.FTBCHUNKS.isLoaded())
FTBChunksTrainMap.mouseClick(event);
if (Mods.JOURNEYMAP.isLoaded())
JourneyTrainMap.mouseClick(event);
}
@SubscribeEvent
public static void cancelTooltips(RenderTooltipEvent.Pre event) {
if (Mods.FTBCHUNKS.isLoaded())
FTBChunksTrainMap.cancelTooltips(event);
}
@SubscribeEvent
public static void renderGui(ScreenEvent.Render.Post event) {
if (Mods.FTBCHUNKS.isLoaded())
FTBChunksTrainMap.renderGui(event);
}
}

View file

@ -0,0 +1,730 @@
package com.simibubi.create.compat.trainmap;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.UUID;
import com.mojang.blaze3d.systems.RenderSystem;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.math.Axis;
import com.simibubi.create.CreateClient;
import com.simibubi.create.compat.trainmap.TrainMapSync.SignalState;
import com.simibubi.create.compat.trainmap.TrainMapSync.TrainMapSyncEntry;
import com.simibubi.create.compat.trainmap.TrainMapSync.TrainState;
import com.simibubi.create.content.trains.entity.Carriage;
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.graph.TrackGraph;
import com.simibubi.create.content.trains.graph.TrackNode;
import com.simibubi.create.content.trains.graph.TrackNodeLocation;
import com.simibubi.create.content.trains.station.GlobalStation;
import com.simibubi.create.content.trains.track.BezierConnection;
import com.simibubi.create.foundation.gui.AllGuiTextures;
import com.simibubi.create.foundation.utility.AnimationTickHolder;
import com.simibubi.create.foundation.utility.Components;
import com.simibubi.create.foundation.utility.Couple;
import com.simibubi.create.foundation.utility.Iterate;
import com.simibubi.create.foundation.utility.Lang;
import com.simibubi.create.foundation.utility.Pair;
import com.simibubi.create.infrastructure.config.AllConfigs;
import com.simibubi.create.infrastructure.config.CClient;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.renderer.Rect2i;
import net.minecraft.core.BlockPos;
import net.minecraft.network.chat.FormattedText;
import net.minecraft.resources.ResourceKey;
import net.minecraft.util.FastColor;
import net.minecraft.util.Mth;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.Vec3;
public class TrainMapManager {
public static void tick() {
TrainMapRenderer map = TrainMapRenderer.INSTANCE;
if (map.trackingVersion != CreateClient.RAILWAYS.version
|| map.trackingDim != Minecraft.getInstance().level.dimension()
|| map.trackingTheme != AllConfigs.client().trainMapColorTheme.get()) {
redrawAll();
}
}
public static List<FormattedText> renderAndPick(GuiGraphics graphics, int mouseX, int mouseY, float pt,
boolean linearFiltering, Rect2i bounds) {
Object hoveredElement = null;
int offScreenMargin = 32;
bounds.setX(bounds.getX() - offScreenMargin);
bounds.setY(bounds.getY() - offScreenMargin);
bounds.setWidth(bounds.getWidth() + 2 * offScreenMargin);
bounds.setHeight(bounds.getHeight() + 2 * offScreenMargin);
TrainMapRenderer.INSTANCE.render(graphics, mouseX, mouseY, pt, linearFiltering, bounds);
hoveredElement = drawTrains(graphics, mouseX, mouseY, pt, hoveredElement, bounds);
hoveredElement = drawPoints(graphics, mouseX, mouseY, pt, hoveredElement, bounds);
graphics.bufferSource()
.endBatch();
if (hoveredElement instanceof GlobalStation station)
return List.of(Components.literal(station.name));
if (hoveredElement instanceof Train train)
return listTrainDetails(train);
return null;
}
public static void renderToggleWidget(GuiGraphics graphics, int x, int y) {
boolean enabled = AllConfigs.client().showTrainMapOverlay.get();
if (CreateClient.RAILWAYS.trackNetworks.isEmpty())
return;
RenderSystem.enableBlend();
PoseStack pose = graphics.pose();
pose.pushPose();
pose.translate(0, 0, 300);
AllGuiTextures.TRAINMAP_TOGGLE_PANEL.render(graphics, x, y);
(enabled ? AllGuiTextures.TRAINMAP_TOGGLE_ON : AllGuiTextures.TRAINMAP_TOGGLE_OFF).render(graphics, x + 18,
y + 3);
pose.popPose();
}
public static boolean handleToggleWidgetClick(int mouseX, int mouseY, int x, int y) {
if (!isToggleWidgetHovered(mouseX, mouseY, x, y))
return false;
CClient config = AllConfigs.client();
config.showTrainMapOverlay.set(!config.showTrainMapOverlay.get());
return true;
}
public static boolean isToggleWidgetHovered(int mouseX, int mouseY, int x, int y) {
if (CreateClient.RAILWAYS.trackNetworks.isEmpty())
return false;
if (mouseX < x || mouseX >= x + AllGuiTextures.TRAINMAP_TOGGLE_PANEL.width)
return false;
if (mouseY < y || mouseY >= y + AllGuiTextures.TRAINMAP_TOGGLE_PANEL.height)
return false;
return true;
}
private static List<FormattedText> listTrainDetails(Train train) {
List<FormattedText> output = new ArrayList<>();
int blue = 0xD3DEDC;
int darkBlue = 0x92A9BD;
int bright = 0xFFEFEF;
int orange = 0xFFAD60;
TrainMapSyncEntry trainEntry = TrainMapSyncClient.currentData.get(train.id);
if (trainEntry == null)
return Collections.emptyList();
TrainState state = trainEntry.state;
SignalState signalState = trainEntry.signalState;
Lang.text(train.name.getString())
.color(bright)
.addTo(output);
if (!trainEntry.ownerName.isBlank())
Lang.translate("train_map.train_owned_by", trainEntry.ownerName)
.color(blue)
.addTo(output);
switch (state) {
case CONDUCTOR_MISSING:
Lang.translate("train_map.conductor_missing")
.color(orange)
.addTo(output);
return output;
case DERAILED:
Lang.translate("train_map.derailed")
.color(orange)
.addTo(output);
return output;
case NAVIGATION_FAILED:
Lang.translate("train_map.navigation_failed")
.color(orange)
.addTo(output);
return output;
case SCHEDULE_INTERRUPTED:
Lang.translate("train_map.schedule_interrupted")
.color(orange)
.addTo(output);
return output;
case RUNNING_MANUALLY:
Lang.translate("train_map.player_controlled")
.color(blue)
.addTo(output);
break;
case RUNNING:
default:
break;
}
String currentStation = trainEntry.targetStationName;
int targetStationDistance = trainEntry.targetStationDistance;
if (!currentStation.isBlank()) {
if (targetStationDistance == 0)
Lang.translate("train_map.train_at_station", currentStation)
.color(darkBlue)
.addTo(output);
else
Lang.translate("train_map.train_moving_to_station", currentStation, targetStationDistance)
.color(darkBlue)
.addTo(output);
}
if (signalState != SignalState.NOT_WAITING) {
boolean chainSignal = signalState == SignalState.CHAIN_SIGNAL;
Lang.translate("train_map.waiting_at_signal")
.color(orange)
.addTo(output);
if (signalState == SignalState.WAITING_FOR_REDSTONE)
Lang.translate("train_map.redstone_powered")
.color(blue)
.addTo(output);
else {
UUID waitingFor = trainEntry.waitingForTrain;
boolean trainFound = false;
if (waitingFor != null) {
Train trainWaitingFor = CreateClient.RAILWAYS.trains.get(waitingFor);
if (trainWaitingFor != null) {
Lang.translate("train_map.for_other_train", trainWaitingFor.name.getString())
.color(blue)
.addTo(output);
trainFound = true;
}
}
if (!trainFound) {
if (chainSignal)
Lang.translate("train_map.cannot_traverse_section")
.color(blue)
.addTo(output);
else
Lang.translate("train_map.section_reserved")
.color(blue)
.addTo(output);
}
}
}
if (trainEntry.fueled)
Lang.translate("train_map.fuel_boosted")
.color(darkBlue)
.addTo(output);
return output;
}
private static Object drawPoints(GuiGraphics graphics, int mouseX, int mouseY, float pt, Object hoveredElement,
Rect2i bounds) {
PoseStack pose = graphics.pose();
RenderSystem.enableDepthTest();
for (TrackGraph graph : CreateClient.RAILWAYS.trackNetworks.values()) {
for (GlobalStation station : graph.getPoints(EdgePointType.STATION)) {
Couple<TrackNodeLocation> edgeLocation = station.edgeLocation;
TrackNode node = graph.locateNode(edgeLocation.getFirst());
TrackNode other = graph.locateNode(edgeLocation.getSecond());
if (node == null || other == null)
continue;
if (node.getLocation().dimension != TrainMapRenderer.INSTANCE.trackingDim)
continue;
TrackEdge edge = graph.getConnection(Couple.create(node, other));
if (edge == null)
continue;
double tLength = station.getLocationOn(edge);
double t = tLength / edge.getLength();
Vec3 position = edge.getPosition(graph, t);
int x = Mth.floor(position.x());
int y = Mth.floor(position.z());
if (!bounds.contains(x, y))
continue;
Vec3 diff = edge.getDirectionAt(tLength)
.normalize();
int rotation = Mth.positiveModulo(Mth.floor(0.5
+ (Math.atan2(diff.z, diff.x) * Mth.RAD_TO_DEG + 90 + (station.isPrimary(node) ? 180 : 0)) / 45),
8);
AllGuiTextures sprite = AllGuiTextures.TRAINMAP_STATION_ORTHO;
AllGuiTextures highlightSprite = AllGuiTextures.TRAINMAP_STATION_ORTHO_HIGHLIGHT;
if (rotation % 2 != 0) {
sprite = AllGuiTextures.TRAINMAP_STATION_DIAGO;
highlightSprite = AllGuiTextures.TRAINMAP_STATION_DIAGO_HIGHLIGHT;
}
boolean highlight = hoveredElement == null && Math.max(Math.abs(mouseX - x), Math.abs(mouseY - y)) < 3;
pose.pushPose();
pose.translate(x - 2, y - 2, 5);
pose.translate(sprite.width / 2.0, sprite.height / 2.0, 0);
pose.mulPose(Axis.ZP.rotationDegrees(90 * (rotation / 2)));
pose.translate(-sprite.width / 2.0, -sprite.height / 2.0, 0);
sprite.render(graphics, 0, 0);
sprite.render(graphics, 0, 0);
if (highlight) {
pose.translate(0, 0, 5);
highlightSprite.render(graphics, -1, -1);
hoveredElement = station;
}
pose.popPose();
}
}
return hoveredElement;
}
private static Object drawTrains(GuiGraphics graphics, int mouseX, int mouseY, float pt, Object hoveredElement,
Rect2i bounds) {
PoseStack pose = graphics.pose();
RenderSystem.enableDepthTest();
RenderSystem.enableBlend();
int spriteYOffset = -3;
double time = AnimationTickHolder.getTicks();
time += AnimationTickHolder.getPartialTicks();
time -= TrainMapSyncClient.lastPacket;
time /= TrainMapSync.lightPacketInterval;
time = Mth.clamp(time, 0, 1);
int[] sliceXShiftByRotationIndex = new int[] { 0, 1, 2, 2, 3, -2, -2, -1 };
int[] sliceYShiftByRotationIndex = new int[] { 3, 2, 2, 1, 0, 1, 2, 2 };
for (Train train : CreateClient.RAILWAYS.trains.values()) {
TrainMapSyncEntry trainEntry = TrainMapSyncClient.currentData.get(train.id);
if (trainEntry == null)
continue;
Vec3 frontPos = Vec3.ZERO;
List<Carriage> carriages = train.carriages;
boolean otherDim = true;
double avgY = 0;
for (int i = 0; i < carriages.size(); i++) {
for (boolean firstBogey : Iterate.trueAndFalse)
avgY += trainEntry.getPosition(i, firstBogey, time)
.y();
}
avgY /= carriages.size() * 2;
for (int i = 0; i < carriages.size(); i++) {
Carriage carriage = carriages.get(i);
Vec3 pos1 = trainEntry.getPosition(i, true, time);
Vec3 pos2 = trainEntry.getPosition(i, false, time);
ResourceKey<Level> dim = trainEntry.dimensions.get(i);
if (dim == null || dim != TrainMapRenderer.INSTANCE.trackingDim)
continue;
if (!bounds.contains(Mth.floor(pos1.x()), Mth.floor(pos1.z()))
&& !bounds.contains(Mth.floor(pos2.x()), Mth.floor(pos2.z())))
continue;
otherDim = false;
if (!trainEntry.backwards && i == 0)
frontPos = pos1;
if (trainEntry.backwards && i == train.carriages.size() - 1)
frontPos = pos2;
Vec3 diff = pos2.subtract(pos1);
int size = carriage.bogeySpacing + 1;
Vec3 center = pos1.add(pos2)
.scale(0.5);
double pX = center.x;
double pY = center.z;
int rotation =
Mth.positiveModulo(Mth.floor(0.5 + (Math.atan2(diff.x, diff.z) * Mth.RAD_TO_DEG) / 22.5), 8);
if (trainEntry.state == TrainState.DERAILED)
rotation =
Mth.positiveModulo((AnimationTickHolder.getTicks() / 8 + i * 3) * (i % 2 == 0 ? 1 : -1), 8);
AllGuiTextures sprite = AllGuiTextures.TRAINMAP_SPRITES;
int slices = 2;
if (rotation == 0 || rotation == 4) {
// Orthogonal, slices add 3 pixels
slices += Mth.floor((size - 2) / (3.0) + 0.5);
}
else if (rotation == 2 || rotation == 6) {
// Diagonal, slices add 2*sqrt(2) pixels
slices += Mth.floor((size - (5 - 2 * Mth.SQRT_OF_TWO)) / (2 * Mth.SQRT_OF_TWO) + 0.5);
}
else {
// Slanty, slices add sqrt(5) pixels
slices += Mth.floor((size - (5 - Mth.sqrt(5))) / (Mth.sqrt(5)) + 0.5);
}
slices = Math.max(2, slices);
sprite.bind();
pose.pushPose();
float pivotX = 7.5f + (slices - 3) * sliceXShiftByRotationIndex[rotation] / 2.0f;
float pivotY = 6.5f + (slices - 3) * sliceYShiftByRotationIndex[rotation] / 2.0f;
// Ysort at home
pose.translate(pX - pivotX, pY - pivotY, 10 + (avgY / 512.0) + (1024.0 + center.z() % 8192.0) / 1024.0);
int trainColorIndex = train.mapColorIndex;
int colorRow = trainColorIndex / 4;
int colorCol = trainColorIndex % 4;
for (int slice = 0; slice < slices; slice++) {
int row = slice == 0 ? 1 : slice == slices - 1 ? 2 : 3;
int sliceShifts = slice == 0 ? 0 : slice == slices - 1 ? slice - 2 : slice - 1;
int col = rotation;
int positionX = sliceShifts * sliceXShiftByRotationIndex[rotation];
int positionY = sliceShifts * sliceYShiftByRotationIndex[rotation] + spriteYOffset;
int sheetX = col * 16 + colorCol * 128;
int sheetY = row * 16 + colorRow * 64;
graphics.blit(sprite.location, positionX, positionY, sheetX, sheetY, 16, 16, sprite.width,
sprite.height);
}
pose.popPose();
int margin = 1;
int sizeX = 8 + (slices - 3) * sliceXShiftByRotationIndex[rotation];
int sizeY = 12 + (slices - 3) * sliceYShiftByRotationIndex[rotation];
double pXm = pX - sizeX / 2;
double pYm = pY - sizeY / 2 + spriteYOffset;
if (hoveredElement == null && mouseX < pXm + margin + sizeX && mouseX > pXm - margin
&& mouseY < pYm + margin + sizeY && mouseY > pYm - margin)
hoveredElement = train;
}
if (otherDim)
continue;
if (trainEntry.signalState != SignalState.NOT_WAITING) {
pose.pushPose();
pose.translate(frontPos.x - 0.5, frontPos.z - 0.5, 20 + (1024.0 + frontPos.z() % 8192.0) / 1024.0);
AllGuiTextures.TRAINMAP_SIGNAL.render(graphics, 0, -3);
pose.popPose();
}
}
return hoveredElement;
}
// Background first so we can mindlessly paint over it
static final int PHASE_BACKGROUND = 0;
// Straights before curves so that curves anti-alias properly at the transition
static final int PHASE_STRAIGHTS = 1;
static final int PHASE_CURVES = 2;
public static void redrawAll() {
TrainMapRenderer map = TrainMapRenderer.INSTANCE;
map.trackingVersion = CreateClient.RAILWAYS.version;
map.trackingDim = Minecraft.getInstance().level.dimension();
map.trackingTheme = AllConfigs.client().trainMapColorTheme.get();
map.startDrawing();
int mainColor = 0xFF_7C57D4;
int darkerColor = 0xFF_70437D;
int darkerColorShadow = 0xFF_4A2754;
switch (map.trackingTheme) {
case GREY:
mainColor = 0xFF_A8B5B5;
darkerColor = 0xFF_776E6C;
darkerColorShadow = 0xFF_56504E;
break;
case WHITE:
mainColor = 0xFF_E8F9F9;
darkerColor = 0xFF_889595;
darkerColorShadow = 0xFF_56504E;
break;
default:
break;
}
List<Couple<Integer>> collisions = new ObjectArrayList<>();
for (int phase = 0; phase <= 2; phase++)
renderPhase(map, collisions, mainColor, darkerColor, phase);
highlightYDifferences(map, collisions, mainColor, darkerColor, darkerColor, darkerColorShadow);
map.finishDrawing();
}
private static void renderPhase(TrainMapRenderer map, List<Couple<Integer>> collisions, int mainColor,
int darkerColor, int phase) {
int outlineColor = 0xFF_000000;
int portalFrameColor = 0xFF_4C2D5B;
int portalColor = 0xFF_FF7FD6;
for (TrackGraph graph : CreateClient.RAILWAYS.trackNetworks.values()) {
for (TrackNodeLocation nodeLocation : graph.getNodes()) {
if (nodeLocation.dimension != map.trackingDim)
continue;
TrackNode node = graph.locateNode(nodeLocation);
Map<TrackNode, TrackEdge> connectionsFrom = graph.getConnectionsFrom(node);
int hashCode = node.hashCode();
for (Entry<TrackNode, TrackEdge> entry : connectionsFrom.entrySet()) {
TrackNode other = entry.getKey();
TrackNodeLocation otherLocation = other.getLocation();
TrackEdge edge = entry.getValue();
BezierConnection turn = edge.getTurn();
// Portal track
if (edge.isInterDimensional()) {
Vec3 vec = node.getLocation()
.getLocation();
int x = Mth.floor(vec.x);
int z = Mth.floor(vec.z);
if (phase == PHASE_CURVES)
continue;
if (phase == PHASE_BACKGROUND) {
map.setPixels(x - 3, z - 2, x + 3, z + 2, outlineColor);
map.setPixels(x - 2, z - 3, x + 2, z + 3, outlineColor);
continue;
}
int a = mapYtoAlpha(Mth.floor(vec.y()));
for (int xi = x - 2; xi <= x + 2; xi++) {
for (int zi = z - 2; zi <= z + 2; zi++) {
int alphaAt = map.alphaAt(xi, zi);
if (alphaAt > 0 && alphaAt != a)
collisions.add(Couple.create(xi, zi));
int c = (xi - x) * (xi - x) + (zi - z) * (zi - z) > 2 ? portalFrameColor : portalColor;
if (alphaAt <= a) {
map.setPixel(xi, zi, markY(c, vec.y()));
}
}
}
continue;
}
if (other.hashCode() > hashCode)
continue;
if (turn == null) {
if (phase == PHASE_CURVES)
continue;
float x1 = nodeLocation.getX();
float z1 = nodeLocation.getZ();
float x2 = otherLocation.getX();
float z2 = otherLocation.getZ();
double y1 = nodeLocation.getLocation()
.y();
double y2 = otherLocation.getLocation()
.y();
float xDiffSign = Math.signum(x2 - x1);
float zDiffSign = Math.signum(z2 - z1);
boolean diagonal = xDiffSign != 0 && zDiffSign != 0;
if (xDiffSign != 0) {
x2 -= xDiffSign * .25;
x1 += xDiffSign * .25;
}
if (zDiffSign != 0) {
z2 -= zDiffSign * .25;
z1 += zDiffSign * .25;
}
x1 /= 2;
x2 /= 2;
z1 /= 2;
z2 /= 2;
int y = Mth.floor(y1);
int a = mapYtoAlpha(y);
// Diagonal
if (diagonal) {
int z = Mth.floor(z1);
int x = Mth.floor(x1);
for (int s = 0; s <= Math.abs(x1 - x2); s++) {
if (phase == PHASE_BACKGROUND) {
map.setPixels(x - 1, z, x + 1, z + 1, outlineColor);
map.setPixels(x, z - 1, x, z + 2, outlineColor);
x += xDiffSign;
z += zDiffSign;
continue;
}
int alphaAt = map.alphaAt(x, z);
if (alphaAt > 0 && alphaAt != a)
collisions.add(Couple.create(x, z));
if (alphaAt <= a) {
map.setPixel(x, z, markY(mainColor, y));
}
if (map.alphaAt(x, z + 1) < a) {
map.setPixel(x, z + 1, markY(darkerColor, y));
}
x += xDiffSign;
z += zDiffSign;
}
continue;
}
// Straight
if (phase == PHASE_BACKGROUND) {
int x1i = Mth.floor(Math.min(x1, x2));
int z1i = Mth.floor(Math.min(z1, z2));
int x2i = Mth.floor(Math.max(x1, x2));
int z2i = Mth.floor(Math.max(z1, z2));
map.setPixels(x1i - 1, z1i, x2i + 1, z2i, outlineColor);
map.setPixels(x1i, z1i - 1, x2i, z2i + 1, outlineColor);
continue;
}
int z = Mth.floor(z1);
int x = Mth.floor(x1);
float diff = Math.max(Math.abs(x1 - x2), Math.abs(z1 - z2));
double yStep = (y2 - y1) / diff;
for (int s = 0; s <= diff; s++) {
int alphaAt = map.alphaAt(x, z);
if (alphaAt > 0 && alphaAt != a)
collisions.add(Couple.create(x, z));
if (alphaAt <= a) {
map.setPixel(x, z, markY(mainColor, y));
}
x += xDiffSign;
y += yStep;
z += zDiffSign;
}
continue;
}
if (phase == PHASE_STRAIGHTS)
continue;
BlockPos origin = turn.tePositions.getFirst();
Map<Pair<Integer, Integer>, Double> rasterise = turn.rasterise();
for (boolean antialias : Iterate.falseAndTrue) {
for (Entry<Pair<Integer, Integer>, Double> offset : rasterise.entrySet()) {
Pair<Integer, Integer> xz = offset.getKey();
int x = origin.getX() + xz.getFirst();
int y = Mth.floor(origin.getY() + offset.getValue() + 0.5);
int z = origin.getZ() + xz.getSecond();
if (phase == PHASE_BACKGROUND) {
map.setPixels(x - 1, z, x + 1, z, outlineColor);
map.setPixels(x, z - 1, x, z + 1, outlineColor);
continue;
}
int a = mapYtoAlpha(y);
if (!antialias) {
int alphaAt = map.alphaAt(x, z);
if (alphaAt > 0 && alphaAt != a)
collisions.add(Couple.create(x, z));
if (alphaAt > a)
continue;
map.setPixel(x, z, markY(mainColor, y));
continue;
}
boolean mainColorBelowLeft =
map.is(x + 1, z + 1, mainColor) && Math.abs(map.alphaAt(x + 1, z + 1) - a) <= 1;
boolean mainColorBelowRight =
map.is(x - 1, z + 1, mainColor) && Math.abs(map.alphaAt(x - 1, z + 1) - a) <= 1;
if (mainColorBelowLeft || mainColorBelowRight) {
int alphaAt = map.alphaAt(x, z + 1);
if (alphaAt > 0 && alphaAt != a)
collisions.add(Couple.create(x, z));
if (alphaAt >= a)
continue;
map.setPixel(x, z + 1, markY(darkerColor, y));
// Adjust background
if (map.isEmpty(x + 1, z + 1))
map.setPixel(x + 1, z + 1, outlineColor);
if (map.isEmpty(x - 1, z + 1))
map.setPixel(x - 1, z + 1, outlineColor);
if (map.isEmpty(x, z + 2))
map.setPixel(x, z + 2, outlineColor);
}
}
if (phase == PHASE_BACKGROUND)
break;
}
}
}
}
}
private static void highlightYDifferences(TrainMapRenderer map, List<Couple<Integer>> collisions, int mainColor,
int darkerColor, int mainColorShadow, int darkerColorShadow) {
for (Couple<Integer> couple : collisions) {
int x = couple.getFirst();
int z = couple.getSecond();
int a = map.alphaAt(x, z);
if (a == 0)
continue;
for (int xi = x - 2; xi <= x + 2; xi++) {
for (int zi = z - 2; zi <= z + 2; zi++) {
if (map.alphaAt(xi, zi) >= a)
continue;
if (map.is(xi, zi, mainColor))
map.setPixel(xi, zi, FastColor.ABGR32.color(a, mainColorShadow));
else if (map.is(xi, zi, darkerColor))
map.setPixel(xi, zi, FastColor.ABGR32.color(a, darkerColorShadow));
}
}
}
}
private static int mapYtoAlpha(double y) {
int minY = Minecraft.getInstance().level.getMinBuildHeight();
return Mth.clamp(32 + Mth.floor((y - minY) / 4.0), 0, 255);
}
private static int markY(int color, double y) {
return FastColor.ABGR32.color(mapYtoAlpha(y), color);
}
}

View file

@ -0,0 +1,259 @@
package com.simibubi.create.compat.trainmap;
import java.util.HashSet;
import java.util.Set;
import org.joml.Matrix4f;
import com.mojang.blaze3d.platform.NativeImage;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.VertexConsumer;
import com.simibubi.create.foundation.render.RenderTypes;
import com.simibubi.create.foundation.utility.Couple;
import com.simibubi.create.infrastructure.config.CClient;
import it.unimi.dsi.fastutil.objects.Object2ObjectMap;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.renderer.LightTexture;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.MultiBufferSource.BufferSource;
import net.minecraft.client.renderer.Rect2i;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.texture.DynamicTexture;
import net.minecraft.client.renderer.texture.TextureManager;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.FastColor;
import net.minecraft.util.Mth;
import net.minecraft.world.level.Level;
public class TrainMapRenderer implements AutoCloseable {
public static final TrainMapRenderer INSTANCE = new TrainMapRenderer();
public static final int WIDTH = 128, HEIGHT = 128;
private Object2ObjectMap<Couple<Integer>, TrainMapInstance> maps = new Object2ObjectOpenHashMap<>();
public int trackingVersion;
public ResourceKey<Level> trackingDim;
public CClient.TrainMapTheme trackingTheme;
//
private TrainMapInstance previouslyAccessed;
public void startDrawing() {
previouslyAccessed = null;
maps.values()
.forEach(tmi -> {
tmi.getImage()
.fillRect(0, 0, WIDTH, HEIGHT, 0);
tmi.untouched = true;
});
}
public Object2ObjectMap<Couple<Integer>, TrainMapInstance> getMaps() {
return maps;
}
public void setPixel(int xCoord, int zCoord, int color) {
TrainMapInstance instance = getOrCreateAt(xCoord, zCoord);
xCoord = Mth.positiveModulo(xCoord, WIDTH);
zCoord = Mth.positiveModulo(zCoord, HEIGHT);
instance.getImage()
.setPixelRGBA(xCoord, zCoord, color);
}
public int getPixel(int xCoord, int zCoord) {
Couple<Integer> sectionKey = toSectionKey(xCoord, zCoord);
if (!maps.containsKey(sectionKey))
return 0;
TrainMapInstance instance = getOrCreateAt(xCoord, zCoord);
xCoord = Mth.positiveModulo(xCoord, WIDTH);
zCoord = Mth.positiveModulo(zCoord, HEIGHT);
return instance.getImage()
.getPixelRGBA(xCoord, zCoord);
}
public void setPixels(int xCoordFrom, int zCoordFrom, int xCoordTo, int zCoordTo, int color) {
for (int x = Math.min(xCoordFrom, xCoordTo); x <= Math.max(xCoordFrom, xCoordTo); x++)
for (int z = Math.min(zCoordFrom, zCoordTo); z <= Math.max(zCoordFrom, zCoordTo); z++)
setPixel(x, z, color);
}
public void blendPixel(int xCoord, int zCoord, int color, int alpha) {
TrainMapInstance instance = getOrCreateAt(xCoord, zCoord);
xCoord = Mth.positiveModulo(xCoord, WIDTH);
zCoord = Mth.positiveModulo(zCoord, HEIGHT);
instance.getImage()
.blendPixel(xCoord, zCoord, FastColor.ABGR32.color(alpha, color));
}
public void blendPixels(int xCoordFrom, int zCoordFrom, int xCoordTo, int zCoordTo, int color, int alpha) {
for (int x = Math.min(xCoordFrom, xCoordTo); x <= Math.max(xCoordFrom, xCoordTo); x++)
for (int z = Math.min(zCoordFrom, zCoordTo); z <= Math.max(zCoordFrom, zCoordTo); z++)
blendPixel(x, z, color, alpha);
}
public void finishDrawing() {
previouslyAccessed = null;
Set<Couple<Integer>> stale = new HashSet<>();
maps.forEach((key, tmi) -> {
if (!tmi.untouched)
return;
tmi.close();
stale.add(key);
});
stale.forEach(key -> {
TrainMapInstance tmi = maps.remove(key);
if (tmi != null)
tmi.close();
});
}
public boolean is(int x, int z, int color) {
return (getPixel(x, z) & 0xFFFFFF) == (color & 0xFFFFFF);
}
public boolean isEmpty(int x, int z) {
return getPixel(x, z) == 0;
}
public int alphaAt(int x, int z) {
int pixel = getPixel(x, z);
return ((pixel & 0xFFFFFF) != 0) ? ((pixel >>> 24) & 0xFF) : 0;
}
//
public void render(GuiGraphics graphics, int mouseX, int mouseY, float pt, boolean linearFiltering, Rect2i bounds) {
BufferSource bufferSource = graphics.bufferSource();
PoseStack pose = graphics.pose();
maps.forEach((key, tmi) -> {
if (tmi.canBeSkipped(bounds))
return;
int x = key.getFirst();
int y = key.getSecond();
pose.pushPose();
pose.translate(x * WIDTH, y * HEIGHT, 0);
tmi.draw(pose, bufferSource, linearFiltering);
pose.popPose();
});
}
public TrainMapInstance getOrCreateAt(int xCoord, int zCoord) {
Couple<Integer> sectionKey = toSectionKey(xCoord, zCoord);
if (previouslyAccessed != null && previouslyAccessed.sectionKey.equals(sectionKey))
return previouslyAccessed;
return maps.compute(sectionKey, (key, instance) -> instance == null ? new TrainMapInstance(key) : instance);
}
public Couple<Integer> toSectionKey(int xCoord, int zCoord) {
return Couple.create(Mth.floor(xCoord / (float) WIDTH), Mth.floor(zCoord / (float) HEIGHT));
}
public void resetData() {
for (TrainMapInstance instance : maps.values())
instance.close();
maps.clear();
}
public void close() {
this.resetData();
}
public class TrainMapInstance implements AutoCloseable {
private DynamicTexture texture;
private RenderType renderType;
private boolean requiresUpload;
private boolean linearFiltering;
private Rect2i bounds;
private boolean untouched;
private Couple<Integer> sectionKey;
public ResourceLocation location;
public TrainMapInstance(Couple<Integer> sectionKey) {
TextureManager textureManager = Minecraft.getInstance()
.getTextureManager();
this.sectionKey = sectionKey;
untouched = false;
requiresUpload = true;
texture = new DynamicTexture(128, 128, true);
linearFiltering = false;
location = textureManager
.register("create_trainmap/" + sectionKey.getFirst() + "_" + sectionKey.getSecond(), texture);
renderType = RenderTypes.TRAIN_MAP.apply(location, linearFiltering);
bounds = new Rect2i(sectionKey.getFirst() * WIDTH, sectionKey.getSecond() * HEIGHT, WIDTH, HEIGHT);
}
public boolean canBeSkipped(Rect2i bounds) {
return bounds.getX() + bounds.getWidth() < this.bounds.getX()
|| this.bounds.getX() + this.bounds.getWidth() < bounds.getX()
|| bounds.getY() + bounds.getHeight() < this.bounds.getY()
|| this.bounds.getY() + this.bounds.getHeight() < bounds.getY();
}
public NativeImage getImage() {
untouched = false;
requiresUpload = true;
return texture.getPixels();
}
public void draw(PoseStack pPoseStack, MultiBufferSource pBufferSource, boolean linearFiltering) {
if (texture.getPixels() == null)
return;
if (requiresUpload) {
texture.upload();
requiresUpload = false;
}
if (pPoseStack == null)
return;
if (linearFiltering != this.linearFiltering) {
this.linearFiltering = linearFiltering;
renderType = RenderTypes.TRAIN_MAP.apply(location, linearFiltering);
}
int pPackedLight = LightTexture.FULL_BRIGHT;
Matrix4f matrix4f = pPoseStack.last()
.pose();
VertexConsumer vertexconsumer = pBufferSource.getBuffer(renderType);
vertexconsumer.vertex(matrix4f, 0.0F, HEIGHT, 0)
.color(255, 255, 255, 255)
.uv(0.0F, 1.0F)
.uv2(pPackedLight)
.endVertex();
vertexconsumer.vertex(matrix4f, WIDTH, HEIGHT, 0)
.color(255, 255, 255, 255)
.uv(1.0F, 1.0F)
.uv2(pPackedLight)
.endVertex();
vertexconsumer.vertex(matrix4f, WIDTH, 0.0F, 0)
.color(255, 255, 255, 255)
.uv(1.0F, 0.0F)
.uv2(pPackedLight)
.endVertex();
vertexconsumer.vertex(matrix4f, 0.0F, 0.0F, 0)
.color(255, 255, 255, 255)
.uv(0.0F, 0.0F)
.uv2(pPackedLight)
.endVertex();
}
public void close() {
texture.close();
}
}
}

View file

@ -0,0 +1,344 @@
package com.simibubi.create.compat.trainmap;
import java.lang.ref.WeakReference;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.simibubi.create.AllPackets;
import com.simibubi.create.Create;
import com.simibubi.create.content.trains.entity.Carriage;
import com.simibubi.create.content.trains.entity.Carriage.DimensionalCarriageEntity;
import com.simibubi.create.content.trains.entity.Train;
import com.simibubi.create.content.trains.entity.TravellingPoint;
import com.simibubi.create.content.trains.graph.DimensionPalette;
import com.simibubi.create.content.trains.graph.EdgePointType;
import com.simibubi.create.content.trains.schedule.ScheduleRuntime;
import com.simibubi.create.content.trains.signal.SignalBlock.SignalType;
import com.simibubi.create.content.trains.signal.SignalBoundary;
import com.simibubi.create.content.trains.signal.SignalEdgeGroup;
import com.simibubi.create.content.trains.station.GlobalStation;
import com.simibubi.create.foundation.utility.Pair;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceKey;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.util.Mth;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.Vec3;
import net.minecraftforge.event.TickEvent.ServerTickEvent;
import net.minecraftforge.network.PacketDistributor;
public class TrainMapSync {
public static final int lightPacketInterval = 5;
public static final int fullPacketInterval = 10;
public static int ticks;
public enum TrainState {
RUNNING, RUNNING_MANUALLY, DERAILED, SCHEDULE_INTERRUPTED, CONDUCTOR_MISSING, NAVIGATION_FAILED
}
public enum SignalState {
NOT_WAITING, WAITING_FOR_REDSTONE, BLOCK_SIGNAL, CHAIN_SIGNAL
}
public static class TrainMapSyncEntry {
// Clientside
public float[] prevPositions;
// Updated every 5 ticks
public float[] positions;
public List<ResourceKey<Level>> dimensions;
public TrainState state = TrainState.RUNNING;
public SignalState signalState = SignalState.NOT_WAITING;
public boolean fueled = false;
public boolean backwards = false;
public int targetStationDistance = 0;
// Updated every 10 ticks
public String ownerName = "";
public String targetStationName = "";
public UUID waitingForTrain = null;
public void gatherDimensions(DimensionPalette dimensionPalette) {
for (ResourceKey<Level> resourceKey : dimensions)
if (resourceKey != null)
dimensionPalette.encode(resourceKey);
}
public void send(FriendlyByteBuf buffer, DimensionPalette dimensionPalette, boolean light) {
buffer.writeVarInt(positions.length);
for (float f : positions)
buffer.writeFloat(f);
buffer.writeVarInt(dimensions.size());
for (ResourceKey<Level> resourceKey : dimensions)
buffer.writeVarInt(resourceKey == null ? -1 : dimensionPalette.encode(resourceKey));
buffer.writeVarInt(state.ordinal());
buffer.writeVarInt(signalState.ordinal());
buffer.writeBoolean(fueled);
buffer.writeBoolean(backwards);
buffer.writeVarInt(targetStationDistance);
if (light)
return;
buffer.writeUtf(ownerName);
buffer.writeUtf(targetStationName);
buffer.writeBoolean(waitingForTrain != null);
if (waitingForTrain != null)
buffer.writeUUID(waitingForTrain);
}
public void receive(FriendlyByteBuf buffer, DimensionPalette dimensionPalette, boolean light) {
positions = new float[buffer.readVarInt()];
for (int i = 0; i < positions.length; i++)
positions[i] = buffer.readFloat();
dimensions = new ArrayList<>();
int dimensionsSize = buffer.readVarInt();
for (int i = 0; i < dimensionsSize; i++) {
int index = buffer.readVarInt();
dimensions.add(index == -1 ? null : dimensionPalette.decode(index));
}
state = TrainState.values()[buffer.readVarInt()];
signalState = SignalState.values()[buffer.readVarInt()];
fueled = buffer.readBoolean();
backwards = buffer.readBoolean();
targetStationDistance = buffer.readVarInt();
if (light)
return;
ownerName = buffer.readUtf();
targetStationName = buffer.readUtf();
waitingForTrain = null;
if (buffer.readBoolean())
waitingForTrain = buffer.readUUID();
}
public void updateFrom(TrainMapSyncEntry other, boolean light) {
prevPositions = positions;
positions = other.positions;
state = other.state;
signalState = other.signalState;
fueled = other.fueled;
backwards = other.backwards;
targetStationDistance = other.targetStationDistance;
if (light)
return;
ownerName = other.ownerName;
targetStationName = other.targetStationName;
waitingForTrain = other.waitingForTrain;
}
public Vec3 getPosition(int carriageIndex, boolean firstBogey, double time) {
int startIndex = carriageIndex * 6 + (firstBogey ? 0 : 3);
if (positions == null || positions.length <= startIndex + 2)
return Vec3.ZERO;
Vec3 position = new Vec3(positions[startIndex], positions[startIndex + 1], positions[startIndex + 2]);
if (prevPositions == null || prevPositions.length <= startIndex + 2)
return position;
Vec3 prevPosition =
new Vec3(prevPositions[startIndex], prevPositions[startIndex + 1], prevPositions[startIndex + 2]);
return prevPosition.lerp(position, time);
}
}
public static Cache<UUID, WeakReference<ServerPlayer>> requestingPlayers = CacheBuilder.newBuilder()
.expireAfterWrite(Duration.ofSeconds(1))
.build();
public static void requestReceived(ServerPlayer sender) {
boolean sendImmediately = requestingPlayers.getIfPresent(sender.getUUID()) == null;
requestingPlayers.put(sender.getUUID(), new WeakReference<>(sender));
if (sendImmediately)
send(sender.server, false);
}
public static void serverTick(ServerTickEvent event) {
ticks++;
if (ticks % fullPacketInterval == 0)
send(event.getServer(), false);
else if (ticks % lightPacketInterval == 0)
send(event.getServer(), true);
}
public static void send(MinecraftServer minecraftServer, boolean light) {
if (requestingPlayers.size() == 0)
return;
TrainMapSyncPacket packet = new TrainMapSyncPacket(light);
for (Train train : Create.RAILWAYS.trains.values())
packet.add(train.id, createEntry(minecraftServer, train));
for (WeakReference<ServerPlayer> weakReference : requestingPlayers.asMap()
.values()) {
ServerPlayer player = weakReference.get();
if (player == null)
continue;
AllPackets.getChannel()
.send(PacketDistributor.PLAYER.with(() -> player), packet);
}
}
private static TrainMapSyncEntry createEntry(MinecraftServer minecraftServer, Train train) {
TrainMapSyncEntry entry = new TrainMapSyncEntry();
boolean stopped = Math.abs(train.speed) < 0.05;
entry.positions = new float[train.carriages.size() * 6];
entry.dimensions = new ArrayList<>();
List<Carriage> carriages = train.carriages;
for (int i = 0; i < carriages.size(); i++) {
Carriage carriage = carriages.get(i);
Vec3 leadingPos;
Vec3 trailingPos;
if (train.graph == null) {
// Train is derailed
Pair<ResourceKey<Level>, DimensionalCarriageEntity> dimCarriage =
carriage.anyAvailableDimensionalCarriage();
if (dimCarriage == null || carriage.presentInMultipleDimensions()) {
entry.dimensions.add(null);
continue;
}
leadingPos = dimCarriage.getSecond().rotationAnchors.getFirst();
trailingPos = dimCarriage.getSecond().rotationAnchors.getSecond();
if (leadingPos == null || trailingPos == null) {
entry.dimensions.add(null);
continue;
}
entry.dimensions.add(dimCarriage.getFirst());
} else {
// Train is on Track
TravellingPoint leading = carriage.getLeadingPoint();
TravellingPoint trailing = carriage.getTrailingPoint();
if (leading == null || trailing == null || leading.edge == null || trailing.edge == null) {
entry.dimensions.add(null);
continue;
}
ResourceKey<Level> leadingDim =
(leading.node1 == null || leading.edge == null || leading.edge.isInterDimensional()) ? null
: leading.node1.getLocation()
.getDimension();
ResourceKey<Level> trailingDim =
(trailing.node1 == null || trailing.edge == null || trailing.edge.isInterDimensional()) ? null
: trailing.node1.getLocation()
.getDimension();
ResourceKey<Level> carriageDim = (leadingDim == null || leadingDim != trailingDim) ? null : leadingDim;
entry.dimensions.add(carriageDim);
leadingPos = leading.getPosition(train.graph);
trailingPos = trailing.getPosition(train.graph);
}
entry.positions[i * 6] = (float) leadingPos.x();
entry.positions[i * 6 + 1] = (float) leadingPos.y();
entry.positions[i * 6 + 2] = (float) leadingPos.z();
entry.positions[i * 6 + 3] = (float) trailingPos.x();
entry.positions[i * 6 + 4] = (float) trailingPos.y();
entry.positions[i * 6 + 5] = (float) trailingPos.z();
}
entry.backwards = train.currentlyBackwards;
if (train.owner != null) {
ServerPlayer owner = minecraftServer.getPlayerList()
.getPlayer(train.owner);
if (owner != null)
entry.ownerName = owner.getName()
.getString();
}
if (train.derailed) {
entry.state = TrainState.DERAILED;
return entry;
}
ScheduleRuntime runtime = train.runtime;
if (runtime.getSchedule() != null && stopped) {
if (runtime.paused) {
entry.state = TrainState.SCHEDULE_INTERRUPTED;
return entry;
}
if (train.status.conductor) {
entry.state = TrainState.CONDUCTOR_MISSING;
return entry;
}
if (train.status.navigation) {
entry.state = TrainState.NAVIGATION_FAILED;
return entry;
}
}
if ((runtime.getSchedule() == null || runtime.paused) && train.speed != 0)
entry.state = TrainState.RUNNING_MANUALLY;
GlobalStation currentStation = train.getCurrentStation();
if (currentStation != null) {
entry.targetStationName = currentStation.name;
entry.targetStationDistance = 0;
} else if (train.navigation.destination != null && !runtime.paused) {
entry.targetStationName = train.navigation.destination.name;
entry.targetStationDistance = Math.max(0, Mth.floor(train.navigation.distanceToDestination));
}
if (stopped && train.navigation.waitingForSignal != null) {
UUID signalId = train.navigation.waitingForSignal.getFirst();
boolean side = train.navigation.waitingForSignal.getSecond();
SignalBoundary signal = train.graph.getPoint(EdgePointType.SIGNAL, signalId);
if (signal != null) {
boolean chainSignal = signal.types.get(side) == SignalType.CROSS_SIGNAL;
entry.signalState = chainSignal ? SignalState.CHAIN_SIGNAL : SignalState.BLOCK_SIGNAL;
if (signal.isForcedRed(side))
entry.signalState = SignalState.WAITING_FOR_REDSTONE;
else {
SignalEdgeGroup group = Create.RAILWAYS.signalEdgeGroups.get(signal.groups.get(side));
if (group != null) {
for (Train other : group.trains) {
if (other == train)
continue;
entry.waitingForTrain = other.id;
break;
}
}
}
}
}
if (train.fuelTicks > 0 && !stopped)
entry.fueled = true;
return entry;
}
}

View file

@ -0,0 +1,56 @@
package com.simibubi.create.compat.trainmap;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import com.simibubi.create.AllPackets;
import com.simibubi.create.compat.trainmap.TrainMapSync.TrainMapSyncEntry;
import com.simibubi.create.foundation.utility.AnimationTickHolder;
import com.simibubi.create.foundation.utility.Pair;
public class TrainMapSyncClient {
public static Map<UUID, TrainMapSyncEntry> currentData = new HashMap<>();
public static double lastPacket;
private static int ticks;
public static void requestData() {
ticks++;
if (ticks % 5 == 0)
AllPackets.getChannel()
.sendToServer(new TrainMapSyncRequestPacket());
}
public static void stopRequesting() {
ticks = 0;
currentData.clear();
}
public static void receive(TrainMapSyncPacket packet) {
if (ticks == 0)
return;
lastPacket = AnimationTickHolder.getTicks();
lastPacket += AnimationTickHolder.getPartialTicks();
Set<UUID> staleEntries = new HashSet<>();
staleEntries.addAll(currentData.keySet());
for (Pair<UUID, TrainMapSyncEntry> pair : packet.entries) {
UUID id = pair.getFirst();
TrainMapSyncEntry entry = pair.getSecond();
staleEntries.remove(id);
currentData.computeIfAbsent(id, $ -> entry)
.updateFrom(entry, packet.light);
}
for (UUID uuid : staleEntries)
currentData.remove(uuid);
}
}

View file

@ -0,0 +1,65 @@
package com.simibubi.create.compat.trainmap;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import com.simibubi.create.compat.trainmap.TrainMapSync.TrainMapSyncEntry;
import com.simibubi.create.content.trains.graph.DimensionPalette;
import com.simibubi.create.foundation.networking.SimplePacketBase;
import com.simibubi.create.foundation.utility.Pair;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraftforge.network.NetworkEvent.Context;
public class TrainMapSyncPacket extends SimplePacketBase {
public List<Pair<UUID, TrainMapSyncEntry>> entries = new ArrayList<>();
public boolean light;
public TrainMapSyncPacket(boolean light) {
this.light = light;
}
public void add(UUID trainId, TrainMapSyncEntry data) {
entries.add(Pair.of(trainId, data));
}
public TrainMapSyncPacket(FriendlyByteBuf buffer) {
DimensionPalette dimensionPalette = DimensionPalette.receive(buffer);
light = buffer.readBoolean();
int size = buffer.readVarInt();
for (int i = 0; i < size; i++) {
UUID id = buffer.readUUID();
TrainMapSyncEntry entry = new TrainMapSyncEntry();
entry.receive(buffer, dimensionPalette, light);
entries.add(Pair.of(id, entry));
}
}
@Override
public void write(FriendlyByteBuf buffer) {
DimensionPalette dimensionPalette = new DimensionPalette();
for (Pair<UUID, TrainMapSyncEntry> pair : entries)
pair.getSecond()
.gatherDimensions(dimensionPalette);
dimensionPalette.send(buffer);
buffer.writeBoolean(light);
buffer.writeVarInt(entries.size());
for (Pair<UUID, TrainMapSyncEntry> pair : entries) {
buffer.writeUUID(pair.getFirst());
pair.getSecond()
.send(buffer, dimensionPalette, light);
}
}
@Override
public boolean handle(Context context) {
context.enqueueWork(() -> TrainMapSyncClient.receive(this));
return true;
}
}

View file

@ -0,0 +1,23 @@
package com.simibubi.create.compat.trainmap;
import com.simibubi.create.foundation.networking.SimplePacketBase;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraftforge.network.NetworkEvent.Context;
public class TrainMapSyncRequestPacket extends SimplePacketBase {
public TrainMapSyncRequestPacket() {}
public TrainMapSyncRequestPacket(FriendlyByteBuf buffer) {}
@Override
public void write(FriendlyByteBuf buffer) {}
@Override
public boolean handle(Context context) {
context.enqueueWork(() -> TrainMapSync.requestReceived(context.getSender()));
return true;
}
}

View file

@ -48,6 +48,8 @@ public class GlobalRailwayManager {
private List<Train> waitingTrains;
private RailwaySavedData savedData;
public int version;
public GlobalRailwayManager() {
cleanUp();

View file

@ -29,6 +29,7 @@ import com.simibubi.create.foundation.advancement.AllAdvancements;
import com.simibubi.create.foundation.utility.Couple;
import com.simibubi.create.foundation.utility.Iterate;
import com.simibubi.create.foundation.utility.NBTHelper;
import com.simibubi.create.foundation.utility.Pair;
import com.simibubi.create.foundation.utility.VecHelper;
import net.minecraft.core.BlockPos;
@ -422,6 +423,13 @@ public class Carriage {
return null;
}
public Pair<ResourceKey<Level>, DimensionalCarriageEntity> anyAvailableDimensionalCarriage() {
for (Entry<ResourceKey<Level>, DimensionalCarriageEntity> entry : entities.entrySet())
if (entry.getValue().entity.get() != null)
return Pair.of(entry.getKey(), entry.getValue());
return null;
}
public void forEachPresentEntity(Consumer<CarriageContraptionEntity> callback) {
for (DimensionalCarriageEntity dimensionalCarriageEntity : entities.values()) {
CarriageContraptionEntity entity = dimensionalCarriageEntity.entity.get();

View file

@ -95,6 +95,7 @@ public class Train {
public Navigation navigation;
public ScheduleRuntime runtime;
public TrainIconType icon;
public int mapColorIndex;
public Component name;
public TrainStatus status;
@ -932,6 +933,8 @@ public class Train {
@Nullable
public LivingEntity getOwner(Level level) {
if (level.getServer() == null)
return null;
try {
UUID uuid = owner;
return uuid == null ? null
@ -1130,6 +1133,7 @@ public class Train {
tag.putInt("Fuel", fuelTicks);
tag.putDouble("TargetSpeed", targetSpeed);
tag.putString("IconType", icon.id.toString());
tag.putInt("MapColorIndex", mapColorIndex);
tag.putString("Name", Component.Serializer.toJson(name));
if (currentStation != null)
tag.putUUID("Station", currentStation);
@ -1182,6 +1186,7 @@ public class Train {
train.speedBeforeStall = tag.getDouble("SpeedBeforeStall");
train.targetSpeed = tag.getDouble("TargetSpeed");
train.icon = TrainIconType.byId(new ResourceLocation(tag.getString("IconType")));
train.mapColorIndex = tag.getInt("MapColorIndex");
train.name = Component.Serializer.fromJson(tag.getString("Name"));
train.currentStation = tag.contains("Station") ? tag.getUUID("Station") : null;
train.currentlyBackwards = tag.getBoolean("Backwards");

View file

@ -68,6 +68,7 @@ public class TrainPacket extends SimplePacketBase {
train.name = Component.Serializer.fromJson(buffer.readUtf());
train.icon = TrainIconType.byId(buffer.readResourceLocation());
train.mapColorIndex = buffer.readVarInt();
}
@Override
@ -105,6 +106,7 @@ public class TrainPacket extends SimplePacketBase {
buffer.writeBoolean(train.doubleEnded);
buffer.writeUtf(Component.Serializer.toJson(train.name));
buffer.writeResourceLocation(train.icon.id);
buffer.writeVarInt(train.mapColorIndex);
}
@Override

View file

@ -16,9 +16,9 @@ public class TrainStatus {
Train train;
boolean navigation;
boolean track;
boolean conductor;
public boolean navigation;
public boolean track;
public boolean conductor;
List<Component> queued = new ArrayList<>();

View file

@ -171,6 +171,8 @@ public class TrackGraphSyncPacket extends TrackGraphPacket {
@Override
protected void handle(GlobalRailwayManager manager, TrackGraph graph) {
manager.version++;
if (packetDeletesGraph) {
manager.removeGraph(graph);
return;

View file

@ -49,7 +49,7 @@ public class AssemblyScreen extends AbstractStationScreen {
iconTypes = TrainIconType.REGISTRY.keySet()
.stream()
.toList();
iconTypeScroll = new ScrollInput(x + 4, y + 17, 184, 14).titled(Lang.translateDirect("station.icon_type"));
iconTypeScroll = new ScrollInput(x + 4, y + 17, 162, 14).titled(Lang.translateDirect("station.icon_type"));
iconTypeScroll.withRange(0, iconTypes.size());
iconTypeScroll.withStepFunction(ctx -> -iconTypeScroll.standardStep()
.apply(ctx));
@ -164,7 +164,7 @@ public class AssemblyScreen extends AbstractStationScreen {
ResourceLocation iconId = iconTypes.get(iconTypeScroll.getState());
train.icon = TrainIconType.byId(iconId);
AllPackets.getChannel()
.sendToServer(new TrainEditPacket(train.id, "", iconId));
.sendToServer(new TrainEditPacket(train.id, "", iconId, train.mapColorIndex));
}
}

View file

@ -467,6 +467,18 @@ public class StationBlockEntity extends SmartBlockEntity implements ITransformab
itemEntity.setDeltaMovement(Vec3.ZERO);
getLevel().addFreshEntity(itemEntity);
}
public void updateMapColor(int color) {
GlobalStation station = getStation();
if (station == null)
return;
Train train = station.getPresentTrain();
if (train == null)
return;
train.mapColorIndex = color;
}
private boolean updateStationState(Consumer<GlobalStation> updateState) {
GlobalStation station = getStation();

View file

@ -11,6 +11,7 @@ import com.mojang.blaze3d.vertex.PoseStack;
import com.simibubi.create.AllBlocks;
import com.simibubi.create.AllPackets;
import com.simibubi.create.AllPartialModels;
import com.simibubi.create.compat.Mods;
import com.simibubi.create.content.decoration.slidingDoor.DoorControl;
import com.simibubi.create.content.trains.entity.Carriage;
import com.simibubi.create.content.trains.entity.Train;
@ -21,6 +22,7 @@ import com.simibubi.create.foundation.gui.UIRenderHelper;
import com.simibubi.create.foundation.gui.widget.IconButton;
import com.simibubi.create.foundation.gui.widget.Label;
import com.simibubi.create.foundation.gui.widget.ScrollInput;
import com.simibubi.create.foundation.utility.AnimationTickHolder;
import com.simibubi.create.foundation.utility.Components;
import com.simibubi.create.foundation.utility.Lang;
import com.simibubi.create.foundation.utility.Pair;
@ -44,6 +46,9 @@ public class StationScreen extends AbstractStationScreen {
private int leavingAnimation;
private LerpedFloat trainPosition;
private DoorControl doorControl;
private ScrollInput colorTypeScroll;
private int messedWithColors;
private boolean switchingToAssemblyMode;
@ -99,6 +104,20 @@ public class StationScreen extends AbstractStationScreen {
dropScheduleButton.withCallback(() -> AllPackets.getChannel()
.sendToServer(StationEditPacket.dropSchedule(blockEntity.getBlockPos())));
addRenderableWidget(dropScheduleButton);
colorTypeScroll = new ScrollInput(x + 166, y + 17, 22, 14).titled(Lang.translateDirect("station.train_map_color"));
colorTypeScroll.withRange(0, 16);
colorTypeScroll.withStepFunction(ctx -> -colorTypeScroll.standardStep()
.apply(ctx));
colorTypeScroll.calling(s -> {
Train train = displayedTrain.get();
if (train != null) {
train.mapColorIndex = s;
messedWithColors = 10;
}
});
colorTypeScroll.active = colorTypeScroll.visible = false;
addRenderableWidget(colorTypeScroll);
onTextChanged = s -> trainNameBox.setX(nameBoxX(s, trainNameBox));
trainNameBox = new EditBox(font, x + 23, y + 47, background.width - 75, 10, Components.immutableEmpty());
@ -131,6 +150,12 @@ public class StationScreen extends AbstractStationScreen {
.length());
trainNameBox.setHighlightPos(trainNameBox.getCursorPosition());
}
if (messedWithColors > 0) {
messedWithColors--;
if (messedWithColors == 0)
syncTrainNameAndColor();
}
super.tick();
@ -151,6 +176,8 @@ public class StationScreen extends AbstractStationScreen {
leavingAnimation = 0;
newTrainButton.active = blockEntity.edgePoint.isOrthogonal();
newTrainButton.visible = true;
colorTypeScroll.visible = false;
colorTypeScroll.active = false;
Train imminentTrain = getImminent();
if (imminentTrain != null) {
@ -161,7 +188,9 @@ public class StationScreen extends AbstractStationScreen {
disassembleTrainButton.visible = true;
dropScheduleButton.active = blockEntity.trainHasSchedule;
dropScheduleButton.visible = true;
colorTypeScroll.setState(imminentTrain.mapColorIndex);
colorTypeScroll.visible = true;
colorTypeScroll.active = true;
trainNameBox.active = true;
trainNameBox.setValue(imminentTrain.name.getString());
trainNameBox.setX(nameBoxX(trainNameBox.getValue(), trainNameBox));
@ -185,6 +214,8 @@ public class StationScreen extends AbstractStationScreen {
targetPos -= trainIconWidth - 130;
if (leavingAnimation > 0) {
colorTypeScroll.visible = false;
colorTypeScroll.active = false;
disassembleTrainButton.active = false;
float f = 1 - (leavingAnimation / 80f);
trainPosition.setValue(targetPos + f * f * f * (background.width - targetPos + 5));
@ -301,6 +332,27 @@ public class StationScreen extends AbstractStationScreen {
if (font.width(text) > trainNameBox.getWidth())
graphics.drawString(font, "...", guiLeft + 26, guiTop + 47, 0xa6a6a6);
}
if (!Mods.FTBCHUNKS.isLoaded())
return;
AllGuiTextures sprite = AllGuiTextures.TRAINMAP_SPRITES;
sprite.bind();
int trainColorIndex = colorTypeScroll.getState();
int colorRow = trainColorIndex / 4;
int colorCol = trainColorIndex % 4;
int rotation = (AnimationTickHolder.getTicks() / 5) % 8;
for (int slice = 0; slice < 3; slice++) {
int row = slice == 0 ? 1 : slice == 2 ? 2 : 3;
int col = rotation;
int positionX = colorTypeScroll.getX() + 4;
int positionY = colorTypeScroll.getY() - 1;
int sheetX = col * 16 + colorCol * 128;
int sheetY = row * 16 + colorRow * 64;
graphics.blit(sprite.location, positionX, positionY, sheetX, sheetY, 16, 16, sprite.width, sprite.height);
}
}
@Override
@ -335,19 +387,19 @@ public class StationScreen extends AbstractStationScreen {
if (hitEnter && trainNameBox.isFocused()) {
trainNameBox.setFocused(false);
syncTrainName();
syncTrainNameAndColor();
return true;
}
return super.keyPressed(pKeyCode, pScanCode, pModifiers);
}
private void syncTrainName() {
private void syncTrainNameAndColor() {
Train train = displayedTrain.get();
if (train != null && !trainNameBox.getValue()
.equals(train.name.getString()))
AllPackets.getChannel()
.sendToServer(new TrainEditPacket(train.id, trainNameBox.getValue(), train.icon.getId()));
.sendToServer(new TrainEditPacket(train.id, trainNameBox.getValue(), train.icon.getId(), train.mapColorIndex));
}
private void syncStationName() {
@ -371,7 +423,8 @@ public class StationScreen extends AbstractStationScreen {
return;
if (!switchingToAssemblyMode)
AllPackets.getChannel()
.sendToServer(new TrainEditPacket(train.id, trainNameBox.getValue(), train.icon.getId()));
.sendToServer(
new TrainEditPacket(train.id, trainNameBox.getValue(), train.icon.getId(), train.mapColorIndex));
else
blockEntity.imminentTrain = null;
}

View file

@ -21,17 +21,20 @@ public class TrainEditPacket extends SimplePacketBase {
private String name;
private UUID id;
private ResourceLocation iconType;
private int mapColor;
public TrainEditPacket(UUID id, String name, ResourceLocation iconType) {
public TrainEditPacket(UUID id, String name, ResourceLocation iconType, int mapColor) {
this.name = name;
this.id = id;
this.iconType = iconType;
this.mapColor = mapColor;
}
public TrainEditPacket(FriendlyByteBuf buffer) {
id = buffer.readUUID();
name = buffer.readUtf(256);
iconType = buffer.readResourceLocation();
mapColor = buffer.readVarInt();
}
@Override
@ -39,6 +42,7 @@ public class TrainEditPacket extends SimplePacketBase {
buffer.writeUUID(id);
buffer.writeUtf(name);
buffer.writeResourceLocation(iconType);
buffer.writeVarInt(mapColor);
}
@Override
@ -52,8 +56,9 @@ public class TrainEditPacket extends SimplePacketBase {
if (!name.isBlank())
train.name = Components.literal(name);
train.icon = TrainIconType.byId(iconType);
train.mapColorIndex = mapColor;
if (sender != null)
AllPackets.getChannel().send(PacketDistributor.ALL.noArg(), new TrainEditReturnPacket(id, name, iconType));
AllPackets.getChannel().send(PacketDistributor.ALL.noArg(), new TrainEditReturnPacket(id, name, iconType, mapColor));
});
return true;
}
@ -64,8 +69,8 @@ public class TrainEditPacket extends SimplePacketBase {
super(buffer);
}
public TrainEditReturnPacket(UUID id, String name, ResourceLocation iconType) {
super(id, name, iconType);
public TrainEditReturnPacket(UUID id, String name, ResourceLocation iconType, int mapColor) {
super(id, name, iconType, mapColor);
}
}

View file

@ -1,6 +1,8 @@
package com.simibubi.create.content.trains.track;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import com.jozufozu.flywheel.util.transform.TransformStack;
import com.mojang.blaze3d.vertex.PoseStack;
@ -9,6 +11,7 @@ import com.simibubi.create.AllBlocks;
import com.simibubi.create.foundation.utility.Couple;
import com.simibubi.create.foundation.utility.Iterate;
import com.simibubi.create.foundation.utility.NBTHelper;
import com.simibubi.create.foundation.utility.Pair;
import com.simibubi.create.foundation.utility.VecHelper;
import net.minecraft.core.BlockPos;
@ -108,7 +111,8 @@ public class BezierConnection implements Iterable<BezierConnection.Segment> {
.map(v -> v.add(Vec3.atLowerCornerOf(localTo))),
Couple.deserializeEach(compound.getList("Axes", Tag.TAG_COMPOUND), VecHelper::readNBTCompound),
Couple.deserializeEach(compound.getList("Normals", Tag.TAG_COMPOUND), VecHelper::readNBTCompound),
compound.getBoolean("Primary"), compound.getBoolean("Girder"), TrackMaterial.deserialize(compound.getString("Material")));
compound.getBoolean("Primary"), compound.getBoolean("Girder"),
TrackMaterial.deserialize(compound.getString("Material")));
if (compound.contains("Smoothing"))
smoothing =
@ -398,7 +402,8 @@ public class BezierConnection implements Iterable<BezierConnection.Segment> {
}
public void spawnDestroyParticles(Level level) {
BlockParticleOption data = new BlockParticleOption(ParticleTypes.BLOCK, getMaterial().getBlock().defaultBlockState());
BlockParticleOption data = new BlockParticleOption(ParticleTypes.BLOCK, getMaterial().getBlock()
.defaultBlockState());
BlockParticleOption girderData =
new BlockParticleOption(ParticleTypes.BLOCK, AllBlocks.METAL_GIRDER.getDefaultState());
if (!(level instanceof ServerLevel slevel))
@ -673,4 +678,104 @@ public class BezierConnection implements Iterable<BezierConnection.Segment> {
return bakedGirders;
}
public Map<Pair<Integer, Integer>, Double> rasterise() {
Map<Pair<Integer, Integer>, Double> yLevels = new HashMap<>();
BlockPos tePosition = tePositions.getFirst();
Vec3 end1 = starts.getFirst()
.subtract(Vec3.atLowerCornerOf(tePosition))
.add(0, 3 / 16f, 0);
Vec3 end2 = starts.getSecond()
.subtract(Vec3.atLowerCornerOf(tePosition))
.add(0, 3 / 16f, 0);
Vec3 axis1 = axes.getFirst();
Vec3 axis2 = axes.getSecond();
double handleLength = getHandleLength();
Vec3 finish1 = axis1.scale(handleLength)
.add(end1);
Vec3 finish2 = axis2.scale(handleLength)
.add(end2);
Vec3 faceNormal1 = normals.getFirst();
Vec3 faceNormal2 = normals.getSecond();
int segCount = getSegmentCount();
float[] lut = getStepLUT();
Vec3[] samples = new Vec3[segCount];
for (int i = 0; i < segCount; i++) {
float t = Mth.clamp((i + 0.5f) * lut[i] / segCount, 0, 1);
Vec3 result = VecHelper.bezier(end1, end2, finish1, finish2, t);
Vec3 derivative = VecHelper.bezierDerivative(end1, end2, finish1, finish2, t)
.normalize();
Vec3 faceNormal =
faceNormal1.equals(faceNormal2) ? faceNormal1 : VecHelper.slerp(t, faceNormal1, faceNormal2);
Vec3 normal = faceNormal.cross(derivative)
.normalize();
Vec3 below = result.add(faceNormal.scale(-.25f));
Vec3 rail1 = below.add(normal.scale(.05f));
Vec3 rail2 = below.subtract(normal.scale(.05f));
Vec3 railMiddle = rail1.add(rail2)
.scale(.5);
samples[i] = railMiddle;
}
Vec3 center = end1.add(end2)
.scale(0.5);
Pair<Integer, Integer> prev = null;
Pair<Integer, Integer> prev2 = null;
Pair<Integer, Integer> prev3 = null;
for (int i = 0; i < segCount; i++) {
Vec3 railMiddle = samples[i];
BlockPos pos = BlockPos.containing(railMiddle);
Pair<Integer, Integer> key = Pair.of(pos.getX(), pos.getZ());
boolean alreadyPresent = yLevels.containsKey(key);
if (alreadyPresent && yLevels.get(key) <= railMiddle.y)
continue;
yLevels.put(key, railMiddle.y);
if (alreadyPresent)
continue;
if (prev3 != null) { // Remove obsolete pixels
boolean doubledViaPrev = isLineDoubled(prev2, prev, key);
boolean doubledViaPrev2 = isLineDoubled(prev3, prev2, prev);
boolean prevCloser = diff(prev, center) > diff(prev2, center);
if (doubledViaPrev2 && (!doubledViaPrev || !prevCloser)) {
yLevels.remove(prev2);
prev2 = prev;
prev = key;
continue;
} else if (doubledViaPrev && doubledViaPrev2 && prevCloser) {
yLevels.remove(prev);
prev = key;
continue;
}
}
prev3 = prev2;
prev2 = prev;
prev = key;
}
return yLevels;
}
private double diff(Pair<Integer, Integer> pFrom, Vec3 to) {
return to.distanceToSqr(pFrom.getFirst() + 0.5, to.y, pFrom.getSecond() + 0.5);
}
private boolean isLineDoubled(Pair<Integer, Integer> pFrom, Pair<Integer, Integer> pVia,
Pair<Integer, Integer> pTo) {
int diff1x = pVia.getFirst() - pFrom.getFirst();
int diff1z = pVia.getSecond() - pFrom.getSecond();
int diff2x = pTo.getFirst() - pVia.getFirst();
int diff2z = pTo.getSecond() - pVia.getSecond();
return Math.abs(diff1x) + Math.abs(diff1z) == 1 && Math.abs(diff2x) + Math.abs(diff2z) == 1 && diff1x != diff2x
&& diff1z != diff2z;
}
}

View file

@ -21,7 +21,6 @@ import com.simibubi.create.foundation.blockEntity.RemoveBlockEntityPacket;
import com.simibubi.create.foundation.blockEntity.SmartBlockEntity;
import com.simibubi.create.foundation.blockEntity.behaviour.BlockEntityBehaviour;
import com.simibubi.create.foundation.utility.Pair;
import com.simibubi.create.foundation.utility.VecHelper;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction.Axis;
@ -363,54 +362,7 @@ public class TrackBlockEntity extends SmartBlockEntity implements ITransformable
}
public void manageFakeTracksAlong(BezierConnection bc, boolean remove) {
Map<Pair<Integer, Integer>, Double> yLevels = new HashMap<>();
BlockPos tePosition = bc.tePositions.getFirst();
Vec3 end1 = bc.starts.getFirst()
.subtract(Vec3.atLowerCornerOf(tePosition))
.add(0, 3 / 16f, 0);
Vec3 end2 = bc.starts.getSecond()
.subtract(Vec3.atLowerCornerOf(tePosition))
.add(0, 3 / 16f, 0);
Vec3 axis1 = bc.axes.getFirst();
Vec3 axis2 = bc.axes.getSecond();
double handleLength = bc.getHandleLength();
Vec3 finish1 = axis1.scale(handleLength)
.add(end1);
Vec3 finish2 = axis2.scale(handleLength)
.add(end2);
Vec3 faceNormal1 = bc.normals.getFirst();
Vec3 faceNormal2 = bc.normals.getSecond();
int segCount = bc.getSegmentCount();
float[] lut = bc.getStepLUT();
for (int i = 0; i < segCount; i++) {
float t = i == segCount ? 1 : i * lut[i] / segCount;
t += 0.5f / segCount;
Vec3 result = VecHelper.bezier(end1, end2, finish1, finish2, t);
Vec3 derivative = VecHelper.bezierDerivative(end1, end2, finish1, finish2, t)
.normalize();
Vec3 faceNormal =
faceNormal1.equals(faceNormal2) ? faceNormal1 : VecHelper.slerp(t, faceNormal1, faceNormal2);
Vec3 normal = faceNormal.cross(derivative)
.normalize();
Vec3 below = result.add(faceNormal.scale(-.25f));
Vec3 rail1 = below.add(normal.scale(.05f));
Vec3 rail2 = below.subtract(normal.scale(.05f));
Vec3 railMiddle = rail1.add(rail2)
.scale(.5);
for (Vec3 vec : new Vec3[] { railMiddle }) {
BlockPos pos = BlockPos.containing(vec);
Pair<Integer, Integer> key = Pair.of(pos.getX(), pos.getZ());
if (!yLevels.containsKey(key) || yLevels.get(key) > vec.y)
yLevels.put(key, vec.y);
}
}
Map<Pair<Integer, Integer>, Double> yLevels = bc.rasterise();
for (Entry<Pair<Integer, Integer>, Double> entry : yLevels.entrySet()) {
double yValue = entry.getValue();
@ -419,7 +371,7 @@ public class TrackBlockEntity extends SmartBlockEntity implements ITransformable
.getFirst(), floor,
entry.getKey()
.getSecond());
targetPos = targetPos.offset(tePosition)
targetPos = targetPos.offset(bc.tePositions.getFirst())
.above(1);
BlockState stateAtPos = level.getBlockState(targetPos);

View file

@ -1,6 +1,7 @@
package com.simibubi.create.foundation.events;
import com.simibubi.create.Create;
import com.simibubi.create.compat.trainmap.TrainMapSync;
import com.simibubi.create.content.contraptions.ContraptionHandler;
import com.simibubi.create.content.contraptions.actors.trainControls.ControlsServerHandler;
import com.simibubi.create.content.contraptions.minecart.CouplingPhysics;
@ -67,6 +68,7 @@ public class CommonEvents {
Create.LAGGER.tick();
ServerSpeedProvider.serverTick();
Create.RAILWAYS.sync.serverTick();
TrainMapSync.serverTick(event);
}
@SubscribeEvent

View file

@ -189,6 +189,18 @@ public enum AllGuiTextures implements ScreenElement {
// PlacementIndicator
PLACEMENT_INDICATOR_SHEET("placement_indicator", 0, 0, 16, 256),
// Train Map
TRAINMAP_SPRITES("trainmap_sprite_sheet", 0, 0, 512, 256),
TRAINMAP_SIGNAL("widgets", 81, 156, 5, 10),
TRAINMAP_STATION_ORTHO("widgets", 49, 156, 5, 5),
TRAINMAP_STATION_DIAGO("widgets", 56, 156, 5, 5),
TRAINMAP_STATION_ORTHO_HIGHLIGHT("widgets", 63, 156, 7, 7),
TRAINMAP_STATION_DIAGO_HIGHLIGHT("widgets", 72, 156, 7, 7),
TRAINMAP_TOGGLE_PANEL("widgets", 166, 74, 33, 14),
TRAINMAP_TOGGLE_ON("widgets", 166, 89, 12, 7),
TRAINMAP_TOGGLE_OFF("widgets", 166, 97, 12, 7),
// ComputerCraft
COMPUTER("computer", 200, 102);

View file

@ -0,0 +1,43 @@
package com.simibubi.create.foundation.mixin;
import java.util.List;
import java.util.Set;
import org.objectweb.asm.tree.ClassNode;
import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin;
import org.spongepowered.asm.mixin.extensibility.IMixinInfo;
import com.simibubi.create.compat.Mods;
public class CreateMixinPlugin implements IMixinConfigPlugin {
@Override
public void onLoad(String mixinPackage) {}
@Override
public String getRefMapperConfig() {
return null;
}
@Override
public boolean shouldApplyMixin(String targetClassName, String mixinClassName) {
if (targetClassName.equals("journeymap/client/ui/fullscreen/Fullscreen") && !Mods.JOURNEYMAP.isLoaded())
return false;
return true;
}
@Override
public void acceptTargets(Set<String> myTargets, Set<String> otherTargets) {}
@Override
public List<String> getMixins() {
return null;
}
@Override
public void preApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) {}
@Override
public void postApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) {}
}

View file

@ -0,0 +1,40 @@
package com.simibubi.create.foundation.mixin.compat;
import java.awt.geom.Point2D;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.At.Shift;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import com.simibubi.create.compat.trainmap.JourneyTrainMap;
import journeymap.client.render.map.GridRenderer;
import journeymap.client.ui.fullscreen.Fullscreen;
import net.minecraft.client.gui.GuiGraphics;
@Mixin(Fullscreen.class)
public abstract class JourneyFullscreenMapMixin {
@Shadow
private static GridRenderer gridRenderer;
@Shadow
private Boolean isScrolling;
@Shadow
public abstract Point2D.Double getMouseDrag();
@Inject(method = "Ljourneymap/client/ui/fullscreen/Fullscreen;render(Lnet/minecraft/client/gui/GuiGraphics;IIF)V", at = @At(target = "Ljourneymap/client/ui/fullscreen/Fullscreen;drawMap(Lnet/minecraft/client/gui/GuiGraphics;II)V", value = "INVOKE", shift = Shift.AFTER))
public void create$journeyMapFullscreenRender(GuiGraphics graphics, int mouseX, int mouseY, float pt,
CallbackInfo ci) {
boolean dragging = isScrolling;
Point2D.Double mouseDrag = getMouseDrag();
double x = gridRenderer.getCenterBlockX() - (dragging ? mouseDrag.x : 0);
double z = gridRenderer.getCenterBlockZ() - (dragging ? mouseDrag.y : 0);
JourneyTrainMap.onRender(graphics, (Fullscreen) (Object) this, x, z, mouseX, mouseY, pt);
}
}

View file

@ -1,12 +1,14 @@
package com.simibubi.create.foundation.render;
import java.io.IOException;
import java.util.function.BiFunction;
import com.mojang.blaze3d.vertex.DefaultVertexFormat;
import com.mojang.blaze3d.vertex.VertexFormat;
import com.simibubi.create.AllSpecialTextures;
import com.simibubi.create.Create;
import net.minecraft.Util;
import net.minecraft.client.renderer.RenderStateShard;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.ShaderInstance;
@ -125,6 +127,19 @@ public class RenderTypes extends RenderStateShard {
return ITEM_PARTIAL_TRANSLUCENT;
}
public static BiFunction<ResourceLocation, Boolean, RenderType> TRAIN_MAP = Util.memoize(RenderTypes::getTrainMap);
private static RenderType getTrainMap(ResourceLocation locationIn, boolean linearFiltering) {
RenderType.CompositeState rendertype$state = RenderType.CompositeState.builder()
.setShaderState(RENDERTYPE_TEXT_SHADER)
.setTextureState(new RenderStateShard.TextureStateShard(locationIn, linearFiltering, false))
.setTransparencyState(NO_TRANSPARENCY)
.setLightmapState(LIGHTMAP)
.createCompositeState(false);
return RenderType.create("create_train_map", DefaultVertexFormat.POSITION_COLOR_TEX_LIGHTMAP, VertexFormat.Mode.QUADS, 256,
false, true, rendertype$state);
}
private static final RenderType FLUID = RenderType.create(createLayerName("fluid"),
DefaultVertexFormat.NEW_ENTITY, VertexFormat.Mode.QUADS, 256, false, true, RenderType.CompositeState.builder()
.setShaderState(RENDERTYPE_ENTITY_TRANSLUCENT_CULL_SHADER)

View file

@ -88,6 +88,10 @@ public class CClient extends ConfigBase {
public final ConfigFloat mountedZoomMultiplier = f(3, 0, "mountedZoomMultiplier", Comments.mountedZoomMultiplier);
public final ConfigBool showTrackGraphOnF3 = b(false, "showTrackGraphOnF3", Comments.showTrackGraphOnF3);
public final ConfigBool showExtendedTrackGraphOnF3 = b(false, "showExtendedTrackGraphOnF3", Comments.showExtendedTrackGraphOnF3);
public final ConfigBool showTrainMapOverlay = b(true, "showTrainMapOverlay", Comments.showTrainMapOverlay);
public final ConfigBool trainMapOverlay = b(true, "showTrainMapOverlay", Comments.showTrainMapOverlay);
public final ConfigEnum<TrainMapTheme> trainMapColorTheme =
e(TrainMapTheme.RED, "trainMapColorTheme", Comments.trainMapColorTheme);
@Override
public String getName() {
@ -97,6 +101,10 @@ public class CClient extends ConfigBase {
public enum PlacementIndicatorSetting {
TEXTURE, TRIANGLE, NONE
}
public enum TrainMapTheme {
RED, GREY, WHITE
}
private static class Comments {
static String client = "Client-only settings - If you're looking for general settings, look inside your worlds serverconfig folder!";
@ -157,6 +165,8 @@ public class CClient extends ConfigBase {
static String ambientVolumeCap = "Maximum volume modifier of Ambient noise";
static String trains = "Railway related settings";
static String showTrainMapOverlay = "Display Track Networks and Trains on supported map mods";
static String trainMapColorTheme = "Track Network Color on maps";
static String mountedZoomMultiplier = "How far away the Camera should zoom when seated on a train";
static String showTrackGraphOnF3 = "Display nodes and edges of a Railway Network while f3 debug mode is active";
static String showExtendedTrackGraphOnF3 = "Additionally display materials of a Rail Network while f3 debug mode is active";

View file

@ -790,6 +790,7 @@
"create.station.cancel": "Cancel Assembly",
"create.station.failed": "Assembly Failed",
"create.station.icon_type": "Icon Type",
"create.station.train_map_color": "Color on Maps",
"create.station.create_train": "Create new Train",
"create.station.assemble_train": "Assemble Train",
"create.station.disassemble_train": "Disassemble Train",
@ -810,6 +811,22 @@
"create.station.how_to_1": "Remove bogeys by breaking the block on top.",
"create.station.how_to_2": "Build carriages attached to one or two bogeys each.",
"create.train_map.toggle": "Train network overlay",
"create.train_map.train_owned_by": "by %1$s",
"create.train_map.conductor_missing": " <!> Conductor Missing",
"create.train_map.derailed": " <!> Derailed",
"create.train_map.navigation_failed": " <!> Navigation Failed",
"create.train_map.schedule_interrupted": " <!> Schedule Interrupted",
"create.train_map.player_controlled": " -> Controlled by Player",
"create.train_map.train_at_station": " >| %1$s",
"create.train_map.train_moving_to_station": " >> %1$s (%2$sm)",
"create.train_map.waiting_at_signal": " Waiting at Signal",
"create.train_map.redstone_powered": " Redstone Powered",
"create.train_map.for_other_train": " for %1$s",
"create.train_map.cannot_traverse_section": " Cannot fully traverse",
"create.train_map.section_reserved": " Section reserved",
"create.train_map.fuel_boosted": " Fuel boosted \u2714",
"create.train_assembly.too_many_bogeys": "Too many Bogeys attached: %1$s",
"create.train_assembly.frontmost_bogey_at_station": "Frontmost Bogey must be at Station Marker",
"create.train_assembly.no_bogeys": "No Bogeys Found",

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.7 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View file

@ -5,6 +5,7 @@
"package": "com.simibubi.create.foundation.mixin",
"compatibilityLevel": "JAVA_17",
"refmap": "create.refmap.json",
"plugin": "com.simibubi.create.foundation.mixin.CreateMixinPlugin",
"mixins": [
"BlockItemMixin",
"ClientboundMapItemDataPacketMixin",
@ -43,7 +44,8 @@
"client.LevelRendererMixin",
"client.MapRendererMapInstanceMixin",
"client.PlayerRendererMixin",
"client.WindowResizeMixin"
"client.WindowResizeMixin",
"compat.JourneyFullscreenMapMixin"
],
"injectors": {
"defaultRequire": 1