From 091812b42d9bb69784f8902231fd7663d289064a Mon Sep 17 00:00:00 2001 From: simibubi <31564874+simibubi@users.noreply.github.com> Date: Thu, 9 Jun 2022 15:00:04 +0200 Subject: [PATCH] Speed limit - Added a means to control the throttle/max speed of a controlled train - Train Controls now show an xp bar overlay --- .../com/simibubi/create/CreateClient.java | 2 + .../interaction/controls/ControlsHandler.java | 4 +- .../interaction/controls/TrainHUD.java | 153 ++++++++++++++++++ .../controls/TrainHUDUpdatePacket.java | 82 ++++++++++ .../entity/CarriageContraptionEntity.java | 17 +- .../logistics/trains/entity/Navigation.java | 11 +- .../logistics/trains/entity/Train.java | 16 +- .../logistics/trains/track/TrackRemoval.java | 139 ---------------- .../simibubi/create/events/ClientEvents.java | 4 +- .../simibubi/create/events/InputEvents.java | 3 +- .../create/foundation/gui/AllGuiTextures.java | 7 + .../foundation/networking/AllPackets.java | 22 ++- .../utility/placement/PlacementHelpers.java | 2 +- .../assets/create/textures/gui/widgets.png | Bin 5693 -> 9720 bytes 14 files changed, 297 insertions(+), 165 deletions(-) create mode 100644 src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/interaction/controls/TrainHUD.java create mode 100644 src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/interaction/controls/TrainHUDUpdatePacket.java delete mode 100644 src/main/java/com/simibubi/create/content/logistics/trains/track/TrackRemoval.java diff --git a/src/main/java/com/simibubi/create/CreateClient.java b/src/main/java/com/simibubi/create/CreateClient.java index 4d04b9c97..b5c6f2a9d 100644 --- a/src/main/java/com/simibubi/create/CreateClient.java +++ b/src/main/java/com/simibubi/create/CreateClient.java @@ -2,6 +2,7 @@ package com.simibubi.create; import com.simibubi.create.content.contraptions.base.KineticTileEntityRenderer; import com.simibubi.create.content.contraptions.components.structureMovement.glue.SuperGlueSelectionHandler; +import com.simibubi.create.content.contraptions.components.structureMovement.interaction.controls.TrainHUD; import com.simibubi.create.content.contraptions.components.structureMovement.render.ContraptionRenderDispatcher; import com.simibubi.create.content.contraptions.components.structureMovement.render.SBBContraptionManager; import com.simibubi.create.content.contraptions.goggles.GoggleOverlayRenderer; @@ -102,6 +103,7 @@ public class CreateClient { private static void registerOverlays() { // Register overlays in reverse order OverlayRegistry.registerOverlayAbove(ForgeIngameGui.AIR_LEVEL_ELEMENT, "Create's Remaining Air", CopperBacktankArmorLayer.REMAINING_AIR_OVERLAY); + OverlayRegistry.registerOverlayAbove(ForgeIngameGui.EXPERIENCE_BAR_ELEMENT, "Create's Train Driver HUD", TrainHUD.OVERLAY); OverlayRegistry.registerOverlayAbove(ForgeIngameGui.HOTBAR_ELEMENT, "Create's Toolboxes", ToolboxHandlerClient.OVERLAY); OverlayRegistry.registerOverlayAbove(ForgeIngameGui.HOTBAR_ELEMENT, "Create's Goggle Information", GoggleOverlayRenderer.OVERLAY); OverlayRegistry.registerOverlayAbove(ForgeIngameGui.HOTBAR_ELEMENT, "Create's Blueprints", BlueprintOverlayRenderer.OVERLAY); diff --git a/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/interaction/controls/ControlsHandler.java b/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/interaction/controls/ControlsHandler.java index 405485ff7..1da4473a5 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/interaction/controls/ControlsHandler.java +++ b/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/interaction/controls/ControlsHandler.java @@ -106,11 +106,11 @@ public class ControlsHandler { // Keepalive Pressed Keys if (packetCooldown == 0) { - if (!pressedKeys.isEmpty()) { +// if (!pressedKeys.isEmpty()) { AllPackets.channel .sendToServer(new ControlsInputPacket(pressedKeys, true, entity.getId(), controlsPos, false)); packetCooldown = PACKET_RATE; - } +// } } currentlyPressed = pressedKeys; diff --git a/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/interaction/controls/TrainHUD.java b/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/interaction/controls/TrainHUD.java new file mode 100644 index 000000000..4803ffed0 --- /dev/null +++ b/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/interaction/controls/TrainHUD.java @@ -0,0 +1,153 @@ +package com.simibubi.create.content.contraptions.components.structureMovement.interaction.controls; + +import com.mojang.blaze3d.vertex.PoseStack; +import com.simibubi.create.content.logistics.trains.entity.Carriage; +import com.simibubi.create.content.logistics.trains.entity.CarriageContraptionEntity; +import com.simibubi.create.content.logistics.trains.entity.Train; +import com.simibubi.create.foundation.config.AllConfigs; +import com.simibubi.create.foundation.gui.AllGuiTextures; +import com.simibubi.create.foundation.networking.AllPackets; +import com.simibubi.create.foundation.utility.AngleHelper; +import com.simibubi.create.foundation.utility.animation.LerpedFloat; +import com.simibubi.create.foundation.utility.animation.LerpedFloat.Chaser; +import com.simibubi.create.foundation.utility.placement.PlacementHelpers; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiComponent; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.util.Mth; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate.StructureBlockInfo; +import net.minecraftforge.client.gui.ForgeIngameGui; +import net.minecraftforge.client.gui.IIngameOverlay; + +public class TrainHUD { + + public static final IIngameOverlay OVERLAY = TrainHUD::renderOverlay; + + static LerpedFloat displayedSpeed = LerpedFloat.linear(); + static LerpedFloat displayedThrottle = LerpedFloat.linear(); + + static Double editedThrottle = null; + static int hudPacketCooldown = 5; + + public static void tick() { + Carriage carriage = getCarriage(); + if (carriage == null) + return; + + Train train = carriage.train; + double value = + Math.abs(train.speed) / (train.maxSpeed() * AllConfigs.SERVER.trains.manualTrainSpeedModifier.getF()); + value = Mth.clamp(value + 0.05f, 0, 1); + displayedSpeed.chase((int) (value * 18) / 18f, .5f, Chaser.EXP); + displayedSpeed.tickChaser(); + displayedThrottle.chase(editedThrottle != null ? editedThrottle : train.throttle, .75f, Chaser.EXP); + displayedThrottle.tickChaser(); + + if (editedThrottle == null) + return; + if (Mth.equal(editedThrottle, train.throttle)) { + editedThrottle = null; + hudPacketCooldown = 5; + return; + } + + if (hudPacketCooldown-- > 0) + return; + AllPackets.channel.sendToServer(new TrainHUDUpdatePacket.Serverbound(train, editedThrottle)); + hudPacketCooldown = 5; + } + + private static Carriage getCarriage() { + if (!(ControlsHandler.entityRef.get() instanceof CarriageContraptionEntity cce)) + return null; + return cce.getCarriage(); + } + + public static void renderOverlay(ForgeIngameGui gui, PoseStack poseStack, float partialTicks, int width, + int height) { + if (!(ControlsHandler.entityRef.get() instanceof CarriageContraptionEntity cce)) + return; + Carriage carriage = cce.getCarriage(); + if (carriage == null) + return; + Entity cameraEntity = Minecraft.getInstance() + .getCameraEntity(); + if (cameraEntity == null) + return; + BlockPos localPos = ControlsHandler.controlsPos; + if (localPos == null) + return; + + poseStack.pushPose(); + poseStack.translate(width / 2 - 91, height - 29, 0); + + // Speed, Throttle + + AllGuiTextures.TRAIN_HUD_FRAME.render(poseStack, -2, 1); + AllGuiTextures.TRAIN_HUD_SPEED_BG.render(poseStack, 0, 0); + + int w = (int) (AllGuiTextures.TRAIN_HUD_SPEED.width * displayedSpeed.getValue(partialTicks)); + int h = AllGuiTextures.TRAIN_HUD_SPEED.height; + + AllGuiTextures.TRAIN_HUD_SPEED.bind(); + GuiComponent.blit(poseStack, 0, 0, 0, AllGuiTextures.TRAIN_HUD_SPEED.startX, + AllGuiTextures.TRAIN_HUD_SPEED.startY, w, h, 256, 256); + + AllGuiTextures.TRAIN_HUD_DIRECTION.render(poseStack, 77, -20); + + w = (int) (AllGuiTextures.TRAIN_HUD_THROTTLE.width * (1 - displayedThrottle.getValue(partialTicks))); + AllGuiTextures.TRAIN_HUD_THROTTLE.bind(); + int invW = AllGuiTextures.TRAIN_HUD_THROTTLE.width - w; + GuiComponent.blit(poseStack, invW, 0, 0, AllGuiTextures.TRAIN_HUD_THROTTLE.startX + invW, + AllGuiTextures.TRAIN_HUD_THROTTLE.startY, w, h, 256, 256); + AllGuiTextures.TRAIN_HUD_THROTTLE_POINTER.render(poseStack, + Math.max(1, AllGuiTextures.TRAIN_HUD_THROTTLE.width - w) - 3, -2); + + // Direction + + StructureBlockInfo info = cce.getContraption() + .getBlocks() + .get(localPos); + Direction initialOrientation = cce.getInitialOrientation() + .getCounterClockWise(); + boolean inverted = false; + if (info != null && info.state.hasProperty(ControlsBlock.FACING)) + inverted = !info.state.getValue(ControlsBlock.FACING) + .equals(initialOrientation); + + boolean reversing = ControlsHandler.currentlyPressed.contains(1); + inverted ^= reversing; + int angleOffset = (ControlsHandler.currentlyPressed.contains(2) ? -45 : 0) + + (ControlsHandler.currentlyPressed.contains(3) ? 45 : 0); + if (reversing) + angleOffset *= -1; + + float snapSize = 22.5f; + float diff = AngleHelper.getShortestAngleDiff(cameraEntity.getYRot(), cce.yaw) + (inverted ? -90 : 90); + if (Math.abs(diff) < 60) + diff = 0; + + float angle = diff + angleOffset; + float snappedAngle = (snapSize * Math.round(angle / snapSize)) % 360f; + + poseStack.translate(91, -9, 0); + poseStack.scale(0.925f, 0.925f, 1); + PlacementHelpers.textured(poseStack, 0, 0, 1, snappedAngle); + + poseStack.popPose(); + } + + public static boolean onScroll(double delta) { + Carriage carriage = getCarriage(); + if (carriage == null) + return false; + + double prevThrottle = editedThrottle == null ? carriage.train.throttle : editedThrottle; + editedThrottle = Mth.clamp(prevThrottle + (delta > 0 ? 1 : -1) / 18f, 1 / 18f, 1); + return true; + } + +} diff --git a/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/interaction/controls/TrainHUDUpdatePacket.java b/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/interaction/controls/TrainHUDUpdatePacket.java new file mode 100644 index 000000000..d70180213 --- /dev/null +++ b/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/interaction/controls/TrainHUDUpdatePacket.java @@ -0,0 +1,82 @@ +package com.simibubi.create.content.contraptions.components.structureMovement.interaction.controls; + +import java.util.UUID; +import java.util.function.Supplier; + +import com.simibubi.create.Create; +import com.simibubi.create.content.logistics.trains.entity.Train; +import com.simibubi.create.foundation.networking.SimplePacketBase; + +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.server.level.ServerPlayer; +import net.minecraftforge.network.NetworkEvent.Context; + +public class TrainHUDUpdatePacket extends SimplePacketBase { + + UUID trainId; + + Double throttle; + double speed; + int fuelTicks; + + public TrainHUDUpdatePacket() {} + + public TrainHUDUpdatePacket(Train train) { + trainId = train.id; + throttle = train.throttle; + speed = train.speedBeforeStall == null ? train.speed : train.speedBeforeStall; + fuelTicks = train.fuelTicks; + } + + public TrainHUDUpdatePacket(FriendlyByteBuf buffer) { + trainId = buffer.readUUID(); + if (buffer.readBoolean()) + throttle = buffer.readDouble(); + speed = buffer.readDouble(); + fuelTicks = buffer.readInt(); + } + + @Override + public void write(FriendlyByteBuf buffer) { + buffer.writeUUID(trainId); + buffer.writeBoolean(throttle != null); + if (throttle != null) + buffer.writeDouble(throttle); + buffer.writeDouble(speed); + buffer.writeInt(fuelTicks); + } + + @Override + public void handle(Supplier context) { + Context c = context.get(); + c.enqueueWork(() -> { + ServerPlayer sender = c.getSender(); + boolean clientSide = sender == null; + Train train = Create.RAILWAYS.sided(clientSide ? null : sender.level).trains.get(trainId); + if (train == null) + return; + + if (throttle != null) + train.throttle = throttle; + if (clientSide) { + train.speed = speed; + train.fuelTicks = fuelTicks; + } + + }); + c.setPacketHandled(true); + } + + public static class Serverbound extends TrainHUDUpdatePacket { + + public Serverbound(FriendlyByteBuf buffer) { + super(buffer); + } + + public Serverbound(Train train, Double sendThrottle) { + trainId = train.id; + throttle = sendThrottle; + } + } + +} diff --git a/src/main/java/com/simibubi/create/content/logistics/trains/entity/CarriageContraptionEntity.java b/src/main/java/com/simibubi/create/content/logistics/trains/entity/CarriageContraptionEntity.java index 026d36477..a62be655f 100644 --- a/src/main/java/com/simibubi/create/content/logistics/trains/entity/CarriageContraptionEntity.java +++ b/src/main/java/com/simibubi/create/content/logistics/trains/entity/CarriageContraptionEntity.java @@ -11,7 +11,6 @@ import java.util.UUID; import javax.annotation.Nullable; import com.google.common.base.Strings; -import com.jozufozu.flywheel.repack.joml.Math; import com.simibubi.create.AllEntityDataSerializers; import com.simibubi.create.AllEntityTypes; import com.simibubi.create.Create; @@ -21,6 +20,7 @@ import com.simibubi.create.content.contraptions.components.structureMovement.Mov import com.simibubi.create.content.contraptions.components.structureMovement.MovementContext; import com.simibubi.create.content.contraptions.components.structureMovement.OrientedContraptionEntity; import com.simibubi.create.content.contraptions.components.structureMovement.interaction.controls.ControlsBlock; +import com.simibubi.create.content.contraptions.components.structureMovement.interaction.controls.TrainHUDUpdatePacket; import com.simibubi.create.content.contraptions.particle.CubeParticleData; import com.simibubi.create.content.logistics.trains.TrackGraph; import com.simibubi.create.content.logistics.trains.entity.Carriage.DimensionalCarriageEntity; @@ -45,6 +45,7 @@ import net.minecraft.network.chat.TextComponent; import net.minecraft.network.syncher.EntityDataAccessor; import net.minecraft.network.syncher.EntityDataSerializers; import net.minecraft.network.syncher.SynchedEntityData; +import net.minecraft.server.level.ServerPlayer; import net.minecraft.util.Mth; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.EntityType; @@ -279,7 +280,7 @@ public class CarriageContraptionEntity extends OrientedContraptionEntity { if (sounds == null) sounds = new CarriageSounds(this); sounds.tick(dce); - + if (particles == null) particles = new CarriageParticles(this); particles.tick(dce); @@ -503,6 +504,7 @@ public class CarriageContraptionEntity extends OrientedContraptionEntity { } double navDistanceTotal = 0; + int hudPacketCooldown = 0; @Override public boolean control(BlockPos controlsLocalPos, Collection heldControls, Player player) { @@ -527,6 +529,11 @@ public class CarriageContraptionEntity extends OrientedContraptionEntity { inverted = !info.state.getValue(ControlsBlock.FACING) .equals(initialOrientation); + if (hudPacketCooldown-- <= 0 && player instanceof ServerPlayer sp) { + AllPackets.channel.send(PacketDistributor.PLAYER.with(() -> sp), new TrainHUDUpdatePacket(carriage.train)); + hudPacketCooldown = 5; + } + int targetSpeed = 0; if (heldControls.contains(0)) targetSpeed++; @@ -584,6 +591,7 @@ public class CarriageContraptionEntity extends OrientedContraptionEntity { int targetColor = arrived ? 0x00_91EA44 : 0x00_ffffff; player.displayClientMessage(greenComponent.withStyle(st -> st.withColor(mixedColor)) .append(whiteComponent.withStyle(st -> st.withColor(targetColor))), true); + carriage.train.manualTick = true; return true; } } @@ -609,14 +617,15 @@ public class CarriageContraptionEntity extends OrientedContraptionEntity { targetSteer < 0 ? SteerDirection.RIGHT : targetSteer > 0 ? SteerDirection.LEFT : SteerDirection.NONE; double topSpeed = carriage.train.maxSpeed() * AllConfigs.SERVER.trains.manualTrainSpeedModifier.getF(); + double cappedTopSpeed = topSpeed * carriage.train.throttle; if (carriage.getLeadingPoint().edge != null && carriage.getLeadingPoint().edge.isTurn() || carriage.getTrailingPoint().edge != null && carriage.getTrailingPoint().edge.isTurn()) topSpeed = carriage.train.maxTurnSpeed(); - carriage.train.targetSpeed = topSpeed * targetSpeed; if (slow) - carriage.train.targetSpeed /= 6; + topSpeed /= 4; + carriage.train.targetSpeed = Math.min(topSpeed, cappedTopSpeed) * targetSpeed; boolean counteringAcceleration = Math.abs(Math.signum(targetSpeed) - Math.signum(carriage.train.speed)) > 1.5f; carriage.train.manualTick = true; diff --git a/src/main/java/com/simibubi/create/content/logistics/trains/entity/Navigation.java b/src/main/java/com/simibubi/create/content/logistics/trains/entity/Navigation.java index e772b435f..c7557a931 100644 --- a/src/main/java/com/simibubi/create/content/logistics/trains/entity/Navigation.java +++ b/src/main/java/com/simibubi/create/content/logistics/trains/entity/Navigation.java @@ -17,7 +17,6 @@ import javax.annotation.Nullable; import org.apache.commons.lang3.mutable.MutableDouble; import org.apache.commons.lang3.mutable.MutableObject; -import com.jozufozu.flywheel.repack.joml.Math; import com.simibubi.create.Create; import com.simibubi.create.content.logistics.trains.DimensionPalette; import com.simibubi.create.content.logistics.trains.TrackEdge; @@ -158,8 +157,8 @@ public class Navigation { boolean primary = entering.equals(signal.groups.getFirst()); boolean crossSignal = signal.types.get(primary) == SignalType.CROSS_SIGNAL; - boolean occupied = - signal.isForcedRed(nodes.getSecond()) || signalEdgeGroup.isOccupiedUnless(train); + boolean occupied = !train.manualTick + && (signal.isForcedRed(nodes.getSecond()) || signalEdgeGroup.isOccupiedUnless(train)); if (!crossSignalTracked) { if (crossSignal) { // Now entering cross signal path @@ -244,7 +243,6 @@ public class Navigation { train.burnFuel(); double topSpeed = train.maxSpeed(); - double turnTopSpeed = train.maxTurnSpeed(); if (targetDistance < 10) { double target = topSpeed * ((targetDistance) / 10); @@ -253,6 +251,9 @@ public class Navigation { return; } } + + topSpeed *= train.throttle; + double turnTopSpeed = Math.min(topSpeed, train.maxTurnSpeed()); double targetSpeed = targetDistance > brakingDistance ? topSpeed * speedMod : 0; @@ -279,6 +280,8 @@ public class Navigation { } private boolean currentSignalResolved() { + if (train.manualTick) + return true; if (distanceToDestination < .5f) return true; SignalBoundary signal = train.graph.getPoint(EdgePointType.SIGNAL, waitingForSignal.getFirst()); diff --git a/src/main/java/com/simibubi/create/content/logistics/trains/entity/Train.java b/src/main/java/com/simibubi/create/content/logistics/trains/entity/Train.java index 41f860d99..11c237443 100644 --- a/src/main/java/com/simibubi/create/content/logistics/trains/entity/Train.java +++ b/src/main/java/com/simibubi/create/content/logistics/trains/entity/Train.java @@ -77,6 +77,9 @@ public class Train { public double speed = 0; public double targetSpeed = 0; public Double speedBeforeStall = null; + + public double throttle = 1; + public boolean honk = false; public UUID id; public UUID owner; @@ -886,15 +889,16 @@ public class Train { } public void approachTargetSpeed(float accelerationMod) { - if (Mth.equal(targetSpeed, speed)) + double actualTarget = targetSpeed; + if (Mth.equal(actualTarget, speed)) return; if (manualTick) leaveStation(); double acceleration = acceleration(); - if (speed < targetSpeed) - speed = Math.min(speed + acceleration * accelerationMod, targetSpeed); - else if (speed > targetSpeed) - speed = Math.max(speed - acceleration * accelerationMod, targetSpeed); + if (speed < actualTarget) + speed = Math.min(speed + acceleration * accelerationMod, actualTarget); + else if (speed > actualTarget) + speed = Math.max(speed - acceleration * accelerationMod, actualTarget); } public void collectInitiallyOccupiedSignalBlocks() { @@ -1060,6 +1064,7 @@ public class Train { tag.putIntArray("CarriageSpacing", carriageSpacing); tag.putBoolean("DoubleEnded", doubleEnded); tag.putDouble("Speed", speed); + tag.putDouble("Throttle", throttle); if (speedBeforeStall != null) tag.putDouble("SpeedBeforeStall", speedBeforeStall); tag.putInt("Fuel", fuelTicks); @@ -1113,6 +1118,7 @@ public class Train { Train train = new Train(id, owner, graph, carriages, carriageSpacing, doubleEnded); train.speed = tag.getDouble("Speed"); + train.throttle = tag.getDouble("Throttle"); if (tag.contains("SpeedBeforeStall")) train.speedBeforeStall = tag.getDouble("SpeedBeforeStall"); train.targetSpeed = tag.getDouble("TargetSpeed"); diff --git a/src/main/java/com/simibubi/create/content/logistics/trains/track/TrackRemoval.java b/src/main/java/com/simibubi/create/content/logistics/trains/track/TrackRemoval.java deleted file mode 100644 index d629b6598..000000000 --- a/src/main/java/com/simibubi/create/content/logistics/trains/track/TrackRemoval.java +++ /dev/null @@ -1,139 +0,0 @@ -package com.simibubi.create.content.logistics.trains.track; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -import com.simibubi.create.AllItems; -import com.simibubi.create.content.logistics.trains.ITrackBlock; -import com.simibubi.create.content.logistics.trains.TrackNodeLocation.DiscoveredLocation; -import com.simibubi.create.content.logistics.trains.entity.TrainRelocator; -import com.simibubi.create.foundation.networking.AllPackets; - -import net.minecraft.ChatFormatting; -import net.minecraft.client.Minecraft; -import net.minecraft.client.player.LocalPlayer; -import net.minecraft.core.BlockPos; -import net.minecraft.network.chat.TextComponent; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.level.Level; -import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.phys.BlockHitResult; -import net.minecraft.world.phys.HitResult; -import net.minecraft.world.phys.HitResult.Type; -import net.minecraftforge.api.distmarker.Dist; -import net.minecraftforge.api.distmarker.OnlyIn; - -public class TrackRemoval { - - static BlockPos startPos; - static BlockPos hoveringPos; - static Set toRemove; - - // TODO localisation - - public static void sneakWrenched(BlockPos pos) { - LocalPlayer player = Minecraft.getInstance().player; - if (startPos != null) { - startPos = null; - player.displayClientMessage(new TextComponent("Track removal aborted").withStyle(ChatFormatting.RED), true); - return; - } - startPos = pos; - } - - public static void wrenched(BlockPos pos) { - if (startPos == null || hoveringPos == null || toRemove == null) - return; - if (TrainRelocator.isRelocating()) - return; - AllPackets.channel.sendToServer(new TrackRemovalPacket(toRemove)); - startPos = null; - } - - @OnlyIn(Dist.CLIENT) - public static void clientTick() { - if (startPos == null) - return; - - LocalPlayer player = Minecraft.getInstance().player; - ItemStack stack = player.getMainHandItem(); - HitResult hitResult = Minecraft.getInstance().hitResult; - - if (hitResult == null) - return; - if (hitResult.getType() != Type.BLOCK) - return; - - if (!AllItems.WRENCH.isIn(stack)) { - hoveringPos = null; - startPos = null; - player.displayClientMessage(new TextComponent("Track removal aborted").withStyle(ChatFormatting.RED), true); - return; - } - - BlockHitResult result = (BlockHitResult) hitResult; - BlockPos blockPos = result.getBlockPos(); - Level level = player.level; - BlockState blockState = level.getBlockState(blockPos); - if (!(blockState.getBlock()instanceof ITrackBlock track)) { - player.displayClientMessage(new TextComponent("Select a second track piece, Unequip Wrench to abort"), - true); - return; - } - - if (blockPos.equals(hoveringPos)) { - if (hoveringPos.equals(startPos)) { - player.displayClientMessage( - new TextComponent("Starting point selected. Right-Click a second Track piece"), true); - } else if (toRemove == null) { - player.displayClientMessage(new TextComponent("Starting point not reachable, Sneak-Click to abort") - .withStyle(ChatFormatting.RED), true); - } else - player.displayClientMessage(new TextComponent("Right-Click to confirm").withStyle(ChatFormatting.GREEN), - true); - - // - - return; - } - - hoveringPos = blockPos; - toRemove = new HashSet<>(); - - List frontier = new ArrayList<>(); - Set visited = new HashSet<>(); - - if (search(level, hoveringPos, frontier, visited, 0)) { - toRemove.add(hoveringPos); - toRemove.add(startPos); - return; - } - - toRemove = null; - } - - private static boolean search(Level level, BlockPos pos, List frontier, Set visited, - int depth) { - if (pos.equals(startPos)) - return true; - if (depth > 32) - return false; - if (!visited.add(pos)) - return false; - BlockState blockState = level.getBlockState(pos); - if (!(blockState.getBlock()instanceof ITrackBlock track)) - return false; - for (DiscoveredLocation discoveredLocation : track.getConnected(level, pos, blockState, false, null)) { - for (BlockPos blockPos : discoveredLocation.allAdjacent()) { - if (!search(level, blockPos, frontier, visited, depth + 1)) - continue; - toRemove.add(pos); - return true; - } - } - return false; - } - -} diff --git a/src/main/java/com/simibubi/create/events/ClientEvents.java b/src/main/java/com/simibubi/create/events/ClientEvents.java index 073b27a43..b01cb5cea 100644 --- a/src/main/java/com/simibubi/create/events/ClientEvents.java +++ b/src/main/java/com/simibubi/create/events/ClientEvents.java @@ -16,6 +16,7 @@ import com.simibubi.create.content.contraptions.components.steam.SteamEngineBloc import com.simibubi.create.content.contraptions.components.structureMovement.ContraptionHandler; import com.simibubi.create.content.contraptions.components.structureMovement.chassis.ChassisRangeDisplay; import com.simibubi.create.content.contraptions.components.structureMovement.interaction.controls.ControlsHandler; +import com.simibubi.create.content.contraptions.components.structureMovement.interaction.controls.TrainHUD; import com.simibubi.create.content.contraptions.components.structureMovement.render.ContraptionRenderDispatcher; import com.simibubi.create.content.contraptions.components.structureMovement.train.CouplingHandlerClient; import com.simibubi.create.content.contraptions.components.structureMovement.train.CouplingPhysics; @@ -44,7 +45,6 @@ import com.simibubi.create.content.logistics.trains.management.schedule.TrainHat import com.simibubi.create.content.logistics.trains.track.CurvedTrackInteraction; import com.simibubi.create.content.logistics.trains.track.TrackBlockOutline; import com.simibubi.create.content.logistics.trains.track.TrackPlacement; -import com.simibubi.create.content.logistics.trains.track.TrackRemoval; import com.simibubi.create.foundation.config.AllConfigs; import com.simibubi.create.foundation.config.ui.BaseConfigScreen; import com.simibubi.create.foundation.fluid.FluidHelper; @@ -164,11 +164,11 @@ public class ClientEvents { ToolboxHandlerClient.clientTick(); TrackTargetingClient.clientTick(); TrackPlacement.clientTick(); - TrackRemoval.clientTick(); TrainRelocator.clientTick(); DisplayLinkBlockItem.clientTick(); CurvedTrackInteraction.clientTick(); CameraDistanceModifier.tick(); + TrainHUD.tick(); } @SubscribeEvent diff --git a/src/main/java/com/simibubi/create/events/InputEvents.java b/src/main/java/com/simibubi/create/events/InputEvents.java index 25eb34b58..b40b720cd 100644 --- a/src/main/java/com/simibubi/create/events/InputEvents.java +++ b/src/main/java/com/simibubi/create/events/InputEvents.java @@ -1,6 +1,7 @@ package com.simibubi.create.events; import com.simibubi.create.CreateClient; +import com.simibubi.create.content.contraptions.components.structureMovement.interaction.controls.TrainHUD; import com.simibubi.create.content.curiosities.toolbox.ToolboxHandlerClient; import com.simibubi.create.content.logistics.item.LinkedControllerClientHandler; import com.simibubi.create.content.logistics.trains.entity.TrainRelocator; @@ -42,7 +43,7 @@ public class InputEvents { // CollisionDebugger.onScroll(delta); boolean cancelled = CreateClient.SCHEMATIC_HANDLER.mouseScrolled(delta) || CreateClient.SCHEMATIC_AND_QUILL_HANDLER.mouseScrolled(delta) || FilteringHandler.onScroll(delta) - || ScrollValueHandler.onScroll(delta); + || ScrollValueHandler.onScroll(delta) || TrainHUD.onScroll(delta); event.setCanceled(cancelled); } diff --git a/src/main/java/com/simibubi/create/foundation/gui/AllGuiTextures.java b/src/main/java/com/simibubi/create/foundation/gui/AllGuiTextures.java index a5e172909..0b4f1b1dc 100644 --- a/src/main/java/com/simibubi/create/foundation/gui/AllGuiTextures.java +++ b/src/main/java/com/simibubi/create/foundation/gui/AllGuiTextures.java @@ -161,6 +161,13 @@ public enum AllGuiTextures implements ScreenElement { SPEECH_TOOLTIP_BACKGROUND("widgets", 0, 24, 8, 8), SPEECH_TOOLTIP_COLOR("widgets", 8, 24, 8, 8), + + TRAIN_HUD_SPEED_BG("widgets", 0, 190, 182, 5), + TRAIN_HUD_SPEED("widgets", 0, 185, 182, 5), + TRAIN_HUD_THROTTLE("widgets", 0, 195, 182, 5), + TRAIN_HUD_THROTTLE_POINTER("widgets", 0, 209, 6, 9), + TRAIN_HUD_FRAME("widgets", 0, 200, 186, 7), + TRAIN_HUD_DIRECTION("widgets", 77, 165, 28, 20), // PlacementIndicator PLACEMENT_INDICATOR_SHEET("placement_indicator", 0, 0, 16, 256); diff --git a/src/main/java/com/simibubi/create/foundation/networking/AllPackets.java b/src/main/java/com/simibubi/create/foundation/networking/AllPackets.java index b85e84673..f216ae966 100644 --- a/src/main/java/com/simibubi/create/foundation/networking/AllPackets.java +++ b/src/main/java/com/simibubi/create/foundation/networking/AllPackets.java @@ -18,6 +18,7 @@ import com.simibubi.create.content.contraptions.components.structureMovement.glu import com.simibubi.create.content.contraptions.components.structureMovement.glue.SuperGlueSelectionPacket; import com.simibubi.create.content.contraptions.components.structureMovement.interaction.controls.ControlsInputPacket; import com.simibubi.create.content.contraptions.components.structureMovement.interaction.controls.ControlsStopControllingPacket; +import com.simibubi.create.content.contraptions.components.structureMovement.interaction.controls.TrainHUDUpdatePacket; import com.simibubi.create.content.contraptions.components.structureMovement.sync.ClientMotionPacket; import com.simibubi.create.content.contraptions.components.structureMovement.sync.ContraptionFluidPacket; import com.simibubi.create.content.contraptions.components.structureMovement.sync.ContraptionInteractionPacket; @@ -94,7 +95,7 @@ public enum AllPackets { CONFIGURE_SCHEMATICANNON(ConfigureSchematicannonPacket.class, ConfigureSchematicannonPacket::new, PLAY_TO_SERVER), CONFIGURE_STOCKSWITCH(ConfigureStockswitchPacket.class, ConfigureStockswitchPacket::new, PLAY_TO_SERVER), CONFIGURE_SEQUENCER(ConfigureSequencedGearshiftPacket.class, ConfigureSequencedGearshiftPacket::new, - PLAY_TO_SERVER), + PLAY_TO_SERVER), PLACE_SCHEMATIC(SchematicPlacePacket.class, SchematicPlacePacket::new, PLAY_TO_SERVER), UPLOAD_SCHEMATIC(SchematicUploadPacket.class, SchematicUploadPacket::new, PLAY_TO_SERVER), CLEAR_CONTAINER(ClearContainerPacket.class, ClearContainerPacket::new, PLAY_TO_SERVER), @@ -114,10 +115,12 @@ public enum AllPackets { EJECTOR_ELYTRA(EjectorElytraPacket.class, EjectorElytraPacket::new, PLAY_TO_SERVER), LINKED_CONTROLLER_INPUT(LinkedControllerInputPacket.class, LinkedControllerInputPacket::new, PLAY_TO_SERVER), LINKED_CONTROLLER_BIND(LinkedControllerBindPacket.class, LinkedControllerBindPacket::new, PLAY_TO_SERVER), - LINKED_CONTROLLER_USE_LECTERN(LinkedControllerStopLecternPacket.class, LinkedControllerStopLecternPacket::new, PLAY_TO_SERVER), + LINKED_CONTROLLER_USE_LECTERN(LinkedControllerStopLecternPacket.class, LinkedControllerStopLecternPacket::new, + PLAY_TO_SERVER), C_CONFIGURE_CONFIG(CConfigureConfigPacket.class, CConfigureConfigPacket::new, PLAY_TO_SERVER), SUBMIT_GHOST_ITEM(GhostItemSubmitPacket.class, GhostItemSubmitPacket::new, PLAY_TO_SERVER), - BLUEPRINT_COMPLETE_RECIPE(BlueprintAssignCompleteRecipePacket.class, BlueprintAssignCompleteRecipePacket::new, PLAY_TO_SERVER), + BLUEPRINT_COMPLETE_RECIPE(BlueprintAssignCompleteRecipePacket.class, BlueprintAssignCompleteRecipePacket::new, + PLAY_TO_SERVER), CONFIGURE_SYMMETRY_WAND(ConfigureSymmetryWandPacket.class, ConfigureSymmetryWandPacket::new, PLAY_TO_SERVER), CONFIGURE_WORLDSHAPER(ConfigureWorldshaperPacket.class, ConfigureWorldshaperPacket::new, PLAY_TO_SERVER), TOOLBOX_EQUIP(ToolboxEquipPacket.class, ToolboxEquipPacket::new, PLAY_TO_SERVER), @@ -135,6 +138,7 @@ public enum AllPackets { GLUE_IN_AREA(SuperGlueSelectionPacket.class, SuperGlueSelectionPacket::new, PLAY_TO_SERVER), GLUE_REMOVED(SuperGlueRemovalPacket.class, SuperGlueRemovalPacket::new, PLAY_TO_SERVER), TRAIN_COLLISION(TrainCollisionPacket.class, TrainCollisionPacket::new, PLAY_TO_SERVER), + C_TRAIN_HUD(TrainHUDUpdatePacket.Serverbound.class, TrainHUDUpdatePacket.Serverbound::new, PLAY_TO_SERVER), // Server to Client SYMMETRY_EFFECT(SymmetryEffectPacket.class, SymmetryEffectPacket::new, PLAY_TO_CLIENT), @@ -156,14 +160,17 @@ public enum AllPackets { FUNNEL_FLAP(FunnelFlapPacket.class, FunnelFlapPacket::new, PLAY_TO_CLIENT), POTATO_CANNON(PotatoCannonPacket.class, PotatoCannonPacket::new, PLAY_TO_CLIENT), SOUL_PULSE(SoulPulseEffectPacket.class, SoulPulseEffectPacket::new, PLAY_TO_CLIENT), - PERSISTENT_DATA(ISyncPersistentData.PersistentDataPacket.class, ISyncPersistentData.PersistentDataPacket::new, PLAY_TO_CLIENT), - SYNC_POTATO_PROJECTILE_TYPES(PotatoProjectileTypeManager.SyncPacket.class, PotatoProjectileTypeManager.SyncPacket::new, PLAY_TO_CLIENT), + PERSISTENT_DATA(ISyncPersistentData.PersistentDataPacket.class, ISyncPersistentData.PersistentDataPacket::new, + PLAY_TO_CLIENT), + SYNC_POTATO_PROJECTILE_TYPES(PotatoProjectileTypeManager.SyncPacket.class, + PotatoProjectileTypeManager.SyncPacket::new, PLAY_TO_CLIENT), SYNC_RAIL_GRAPH(TrackGraphSyncPacket.class, TrackGraphSyncPacket::new, PLAY_TO_CLIENT), SYNC_EDGE_GROUP(SignalEdgeGroupPacket.class, SignalEdgeGroupPacket::new, PLAY_TO_CLIENT), SYNC_TRAIN(TrainPacket.class, TrainPacket::new, PLAY_TO_CLIENT), REMOVE_TE(RemoveTileEntityPacket.class, RemoveTileEntityPacket::new, PLAY_TO_CLIENT), S_CONFIGURE_TRAIN(TrainEditReturnPacket.class, TrainEditReturnPacket::new, PLAY_TO_CLIENT), CONTROLS_ABORT(ControlsStopControllingPacket.class, ControlsStopControllingPacket::new, PLAY_TO_CLIENT), + S_TRAIN_HUD(TrainHUDUpdatePacket.class, TrainHUDUpdatePacket::new, PLAY_TO_CLIENT), ; @@ -190,8 +197,9 @@ public enum AllPackets { } public static void sendToNear(Level world, BlockPos pos, int range, Object message) { - channel.send(PacketDistributor.NEAR - .with(TargetPoint.p(pos.getX(), pos.getY(), pos.getZ(), range, world.dimension())), message); + channel.send( + PacketDistributor.NEAR.with(TargetPoint.p(pos.getX(), pos.getY(), pos.getZ(), range, world.dimension())), + message); } private static class LoadedPacket { diff --git a/src/main/java/com/simibubi/create/foundation/utility/placement/PlacementHelpers.java b/src/main/java/com/simibubi/create/foundation/utility/placement/PlacementHelpers.java index afe1b427f..537ba31a4 100644 --- a/src/main/java/com/simibubi/create/foundation/utility/placement/PlacementHelpers.java +++ b/src/main/java/com/simibubi/create/foundation/utility/placement/PlacementHelpers.java @@ -245,7 +245,7 @@ public class PlacementHelpers { ms.popPose(); } - private static void textured(PoseStack ms, float centerX, float centerY, float alpha, float snappedAngle) { + public static void textured(PoseStack ms, float centerX, float centerY, float alpha, float snappedAngle) { RenderSystem.enableTexture(); AllGuiTextures.PLACEMENT_INDICATOR_SHEET.bind(); RenderSystem.enableDepthTest(); diff --git a/src/main/resources/assets/create/textures/gui/widgets.png b/src/main/resources/assets/create/textures/gui/widgets.png index f51f71fc6feeb6344d43508310bd9a9ec7d779a9..978685b26ffdb81b7fc0796190784d31d4ba9430 100644 GIT binary patch literal 9720 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 YdX|9oIpy!u-&%m8teQ+Y)a>*B1qw!R$N&HU literal 5693 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 DPk^h~