From 68eccd1d51ec45b63d97df5d32a107782bef21b4 Mon Sep 17 00:00:00 2001 From: simibubi <31564874+simibubi@users.noreply.github.com> Date: Sat, 25 Feb 2023 14:15:31 +0100 Subject: [PATCH] Don't open sesame - Added door controls to elevator contact and train station UI --- src/generated/resources/.cache/cache | 2 +- .../resources/assets/create/lang/en_us.json | 14 +++ .../components/actors/DoorControl.java | 76 ++++++++++++ .../actors/DoorControlBehaviour.java | 45 +++++++ .../elevator/ElevatorContactBlock.java | 3 +- .../elevator/ElevatorContactBlockEntity.java | 6 +- .../elevator/ElevatorContactEditPacket.java | 13 +- .../elevator/ElevatorContactScreen.java | 16 ++- .../deco/SlidingDoorMovementBehaviour.java | 112 ++++++++++++++++-- .../edgePoint/station/AssemblyScreen.java | 22 ++-- .../edgePoint/station/StationBlockEntity.java | 3 + .../edgePoint/station/StationEditPacket.java | 14 ++- .../edgePoint/station/StationScreen.java | 41 +++++-- .../gui/widget/AbstractSimiWidget.java | 1 + .../foundation/gui/widget/ScrollInput.java | 15 ++- .../gui/widget/SelectionScrollInput.java | 9 +- .../assets/create/lang/default/interface.json | 15 +++ .../create/textures/gui/display_link.png | Bin 2037 -> 2141 bytes .../assets/create/textures/gui/schedule_2.png | Bin 1500 -> 2150 bytes 19 files changed, 366 insertions(+), 41 deletions(-) create mode 100644 src/main/java/com/simibubi/create/content/contraptions/components/actors/DoorControl.java create mode 100644 src/main/java/com/simibubi/create/content/contraptions/components/actors/DoorControlBehaviour.java diff --git a/src/generated/resources/.cache/cache b/src/generated/resources/.cache/cache index 7050f3c44..506607afc 100644 --- a/src/generated/resources/.cache/cache +++ b/src/generated/resources/.cache/cache @@ -566,7 +566,7 @@ bf2b0310500213ff853c748c236eb5d01f61658e assets/create/blockstates/yellow_toolbo 7f39521b211441f5c3e06d60c5978cebe16cacfb assets/create/blockstates/zinc_block.json b7181bcd8182b2f17088e5aa881f374c9c65470c assets/create/blockstates/zinc_ore.json 3054a5519fbf91481b0c9c8160a20679fa9530da assets/create/lang/en_ud.json -8db7da2dab7745aa409e536d7a36cbe9fcce21a4 assets/create/lang/en_us.json +093dfa9846e481071cd27239b4d66760848b160e assets/create/lang/en_us.json 487a511a01b2a4531fb672f917922312db78f958 assets/create/models/block/acacia_window.json b48060cba1a382f373a05bf0039054053eccf076 assets/create/models/block/acacia_window_pane_noside.json 3066db1bf03cffa1a9c7fbacf47ae586632f4eb3 assets/create/models/block/acacia_window_pane_noside_alt.json diff --git a/src/generated/resources/assets/create/lang/en_us.json b/src/generated/resources/assets/create/lang/en_us.json index ec960fbc1..ae444941a 100644 --- a/src/generated/resources/assets/create/lang/en_us.json +++ b/src/generated/resources/assets/create/lang/en_us.json @@ -1654,6 +1654,20 @@ "create.contraption.controls.actor_toggle.on": "On", "create.contraption.controls.actor_toggle.off": "Off", "create.contraption.controls.floor_unreachable": "Unreachable", + "create.contraption.door_control": "Onboard Door Control", + "create.contraption.door_control.all": "Open All Doors", + "create.contraption.door_control.all.short": "Open All", + "create.contraption.door_control.north": "North Side Only", + "create.contraption.door_control.north.short": "North", + "create.contraption.door_control.east": "East Side Only", + "create.contraption.door_control.east.short": "East", + "create.contraption.door_control.south": "South Side Only", + "create.contraption.door_control.south.short": "South", + "create.contraption.door_control.west": "West Side Only", + "create.contraption.door_control.west.short": "West", + "create.contraption.door_control.none": "Keep Doors Closed", + "create.contraption.door_control.none.short": "None", + "create.contraption.door_control.player_facing": "You are facing: %1$s", "create.display_link.set": "Targeted position selected", "create.display_link.success": "Successfully bound to targeted position", diff --git a/src/main/java/com/simibubi/create/content/contraptions/components/actors/DoorControl.java b/src/main/java/com/simibubi/create/content/contraptions/components/actors/DoorControl.java new file mode 100644 index 000000000..f6cf7af1d --- /dev/null +++ b/src/main/java/com/simibubi/create/content/contraptions/components/actors/DoorControl.java @@ -0,0 +1,76 @@ +package com.simibubi.create.content.contraptions.components.actors; + +import java.util.Arrays; +import java.util.function.Consumer; + +import com.simibubi.create.foundation.gui.widget.Label; +import com.simibubi.create.foundation.gui.widget.ScrollInput; +import com.simibubi.create.foundation.gui.widget.SelectionScrollInput; +import com.simibubi.create.foundation.utility.Components; +import com.simibubi.create.foundation.utility.Lang; +import com.simibubi.create.foundation.utility.Pair; + +import net.minecraft.client.Minecraft; +import net.minecraft.core.Direction; +import net.minecraft.world.entity.Entity; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; + +public enum DoorControl { + + ALL, NORTH, EAST, SOUTH, WEST, NONE; + + private static String[] valuesAsString() { + DoorControl[] values = values(); + return Arrays.stream(values) + .map(dc -> Lang.asId(dc.name())) + .toList() + .toArray(new String[values.length]); + } + + public boolean matches(Direction doorDirection) { + return switch (this) { + case ALL -> true; + case NORTH -> doorDirection == Direction.NORTH; + case EAST -> doorDirection == Direction.EAST; + case SOUTH -> doorDirection == Direction.SOUTH; + case WEST -> doorDirection == Direction.WEST; + default -> false; + }; + } + + @OnlyIn(Dist.CLIENT) + public static Pair createWidget(int x, int y, Consumer callback, + DoorControl initial) { + + DoorControl playerFacing = NONE; + Entity cameraEntity = Minecraft.getInstance().cameraEntity; + if (cameraEntity != null) { + Direction direction = cameraEntity.getDirection(); + if (direction == Direction.EAST) + playerFacing = EAST; + if (direction == Direction.WEST) + playerFacing = WEST; + if (direction == Direction.NORTH) + playerFacing = NORTH; + if (direction == Direction.SOUTH) + playerFacing = SOUTH; + } + + Label label = new Label(x + 4, y + 6, Components.empty()).withShadow(); + ScrollInput input = new SelectionScrollInput(x, y, 53, 16) + .forOptions(Lang.translatedOptions("contraption.door_control", valuesAsString())) + .titled(Lang.translateDirect("contraption.door_control")) + .calling(s -> { + DoorControl mode = values()[s]; + label.text = Lang.translateDirect("contraption.door_control." + Lang.asId(mode.name()) + ".short"); + callback.accept(mode); + }) + .addHint(Lang.translateDirect("contraption.door_control.player_facing", + Lang.translateDirect("contraption.door_control." + Lang.asId(playerFacing.name()) + ".short"))) + .setState(initial.ordinal()); + input.onChanged(); + return Pair.of(input, label); + } + +} diff --git a/src/main/java/com/simibubi/create/content/contraptions/components/actors/DoorControlBehaviour.java b/src/main/java/com/simibubi/create/content/contraptions/components/actors/DoorControlBehaviour.java new file mode 100644 index 000000000..df25f85d2 --- /dev/null +++ b/src/main/java/com/simibubi/create/content/contraptions/components/actors/DoorControlBehaviour.java @@ -0,0 +1,45 @@ +package com.simibubi.create.content.contraptions.components.actors; + +import com.simibubi.create.foundation.blockEntity.BlockEntityBehaviour; +import com.simibubi.create.foundation.blockEntity.SmartBlockEntity; +import com.simibubi.create.foundation.blockEntity.behaviour.BehaviourType; +import com.simibubi.create.foundation.utility.NBTHelper; + +import net.minecraft.nbt.CompoundTag; + +public class DoorControlBehaviour extends BlockEntityBehaviour { + + public static final BehaviourType TYPE = new BehaviourType<>(); + + public DoorControl mode; + + public DoorControlBehaviour(SmartBlockEntity be) { + super(be); + mode = DoorControl.ALL; + } + + public void set(DoorControl mode) { + if (this.mode == mode) + return; + this.mode = mode; + blockEntity.notifyUpdate(); + } + + @Override + public void write(CompoundTag nbt, boolean clientPacket) { + NBTHelper.writeEnum(nbt, "DoorControl", mode); + super.write(nbt, clientPacket); + } + + @Override + public void read(CompoundTag nbt, boolean clientPacket) { + mode = NBTHelper.readEnum(nbt, "DoorControl", DoorControl.class); + super.read(nbt, clientPacket); + } + + @Override + public BehaviourType getType() { + return TYPE; + } + +} diff --git a/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/elevator/ElevatorContactBlock.java b/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/elevator/ElevatorContactBlock.java index 931f09a15..319ef50e0 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/elevator/ElevatorContactBlock.java +++ b/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/elevator/ElevatorContactBlock.java @@ -212,7 +212,8 @@ public class ElevatorContactBlock extends WrenchableDirectionalBlock @OnlyIn(value = Dist.CLIENT) protected void displayScreen(ElevatorContactBlockEntity be, Player player) { if (player instanceof LocalPlayer) - ScreenOpener.open(new ElevatorContactScreen(be.getBlockPos(), be.shortName, be.longName)); + ScreenOpener + .open(new ElevatorContactScreen(be.getBlockPos(), be.shortName, be.longName, be.doorControls.mode)); } public static int getLight(BlockState state) { diff --git a/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/elevator/ElevatorContactBlockEntity.java b/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/elevator/ElevatorContactBlockEntity.java index 93d992fc8..f46f67bf4 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/elevator/ElevatorContactBlockEntity.java +++ b/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/elevator/ElevatorContactBlockEntity.java @@ -2,6 +2,7 @@ package com.simibubi.create.content.contraptions.components.structureMovement.el import java.util.List; +import com.simibubi.create.content.contraptions.components.actors.DoorControlBehaviour; import com.simibubi.create.content.contraptions.components.structureMovement.elevator.ElevatorColumn.ColumnCoords; import com.simibubi.create.content.logistics.block.display.DisplayLinkBlock; import com.simibubi.create.foundation.blockEntity.BlockEntityBehaviour; @@ -16,6 +17,7 @@ import net.minecraft.world.level.block.state.BlockState; public class ElevatorContactBlockEntity extends SmartBlockEntity { + public DoorControlBehaviour doorControls; public ColumnCoords columnCoords; public boolean activateBlock; @@ -35,7 +37,9 @@ public class ElevatorContactBlockEntity extends SmartBlockEntity { } @Override - public void addBehaviours(List behaviours) {} + public void addBehaviours(List behaviours) { + behaviours.add(doorControls = new DoorControlBehaviour(this)); + } @Override protected void write(CompoundTag tag, boolean clientPacket) { diff --git a/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/elevator/ElevatorContactEditPacket.java b/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/elevator/ElevatorContactEditPacket.java index 8407c5ba3..6011ef81e 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/elevator/ElevatorContactEditPacket.java +++ b/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/elevator/ElevatorContactEditPacket.java @@ -1,40 +1,47 @@ package com.simibubi.create.content.contraptions.components.structureMovement.elevator; +import com.simibubi.create.content.contraptions.components.actors.DoorControl; import com.simibubi.create.foundation.networking.BlockEntityConfigurationPacket; import net.minecraft.core.BlockPos; import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.util.Mth; public class ElevatorContactEditPacket extends BlockEntityConfigurationPacket { private String shortName; private String longName; + private DoorControl doorControl; - public ElevatorContactEditPacket(BlockPos pos, String shortName, String longName) { + public ElevatorContactEditPacket(BlockPos pos, String shortName, String longName, DoorControl doorControl) { super(pos); this.shortName = shortName; this.longName = longName; + this.doorControl = doorControl; } - + public ElevatorContactEditPacket(FriendlyByteBuf buffer) { super(buffer); } - + @Override protected void writeSettings(FriendlyByteBuf buffer) { buffer.writeUtf(shortName, 4); buffer.writeUtf(longName, 30); + buffer.writeVarInt(doorControl.ordinal()); } @Override protected void readSettings(FriendlyByteBuf buffer) { shortName = buffer.readUtf(4); longName = buffer.readUtf(30); + doorControl = DoorControl.values()[Mth.clamp(buffer.readVarInt(), 0, DoorControl.values().length)]; } @Override protected void applySettings(ElevatorContactBlockEntity be) { be.updateName(shortName, longName); + be.doorControls.set(doorControl); } } diff --git a/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/elevator/ElevatorContactScreen.java b/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/elevator/ElevatorContactScreen.java index 31e712eb1..10cdb1a9c 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/elevator/ElevatorContactScreen.java +++ b/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/elevator/ElevatorContactScreen.java @@ -5,15 +5,19 @@ import org.lwjgl.glfw.GLFW; import com.google.common.collect.ImmutableList; import com.mojang.blaze3d.vertex.PoseStack; import com.simibubi.create.AllBlocks; +import com.simibubi.create.content.contraptions.components.actors.DoorControl; import com.simibubi.create.foundation.gui.AbstractSimiScreen; import com.simibubi.create.foundation.gui.AllGuiTextures; import com.simibubi.create.foundation.gui.AllIcons; import com.simibubi.create.foundation.gui.element.GuiGameElement; 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.gui.widget.TooltipArea; import com.simibubi.create.foundation.networking.AllPackets; import com.simibubi.create.foundation.utility.Components; import com.simibubi.create.foundation.utility.Lang; +import com.simibubi.create.foundation.utility.Pair; import net.minecraft.ChatFormatting; import net.minecraft.client.gui.components.EditBox; @@ -31,12 +35,14 @@ public class ElevatorContactScreen extends AbstractSimiScreen { private String shortName; private String longName; + private DoorControl doorControl; private BlockPos pos; - public ElevatorContactScreen(BlockPos pos, String prevShortName, String prevLongName) { + public ElevatorContactScreen(BlockPos pos, String prevShortName, String prevLongName, DoorControl prevDoorControl) { super(Lang.translateDirect("elevator_contact.title")); this.pos = pos; + this.doorControl = prevDoorControl; background = AllGuiTextures.ELEVATOR_CONTACT; this.shortName = prevShortName; this.longName = prevLongName; @@ -88,6 +94,10 @@ public class ElevatorContactScreen extends AbstractSimiScreen { .component(), rmbToEdit))); + Pair doorControlWidgets = + DoorControl.createWidget(x + 58, y + 57, mode -> doorControl = mode, doorControl); + addRenderableWidget(doorControlWidgets.getFirst()); + addRenderableWidget(doorControlWidgets.getSecond()); } private int centerInput(int x) { @@ -122,6 +132,7 @@ public class ElevatorContactScreen extends AbstractSimiScreen { .scale(5) .render(ms); + itemRenderer.renderGuiItem(AllBlocks.TRAIN_DOOR.asStack(), x + 37, y + 58); } @Override @@ -164,7 +175,8 @@ public class ElevatorContactScreen extends AbstractSimiScreen { } private void confirm() { - AllPackets.getChannel().sendToServer(new ElevatorContactEditPacket(pos, shortName, longName)); + AllPackets.getChannel() + .sendToServer(new ElevatorContactEditPacket(pos, shortName, longName, doorControl)); onClose(); } diff --git a/src/main/java/com/simibubi/create/content/curiosities/deco/SlidingDoorMovementBehaviour.java b/src/main/java/com/simibubi/create/content/curiosities/deco/SlidingDoorMovementBehaviour.java index 8ea99ade6..b8e08bc8e 100644 --- a/src/main/java/com/simibubi/create/content/curiosities/deco/SlidingDoorMovementBehaviour.java +++ b/src/main/java/com/simibubi/create/content/curiosities/deco/SlidingDoorMovementBehaviour.java @@ -1,23 +1,37 @@ package com.simibubi.create.content.curiosities.deco; +import java.lang.ref.WeakReference; import java.util.Map; +import com.simibubi.create.content.contraptions.components.actors.DoorControl; +import com.simibubi.create.content.contraptions.components.actors.DoorControlBehaviour; import com.simibubi.create.content.contraptions.components.structureMovement.Contraption; import com.simibubi.create.content.contraptions.components.structureMovement.MovementBehaviour; import com.simibubi.create.content.contraptions.components.structureMovement.MovementContext; +import com.simibubi.create.content.contraptions.components.structureMovement.elevator.ElevatorColumn; +import com.simibubi.create.content.contraptions.components.structureMovement.elevator.ElevatorColumn.ColumnCoords; import com.simibubi.create.content.contraptions.components.structureMovement.elevator.ElevatorContraption; +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.CarriageSyncData; +import com.simibubi.create.content.logistics.trains.management.edgePoint.station.GlobalStation; +import com.simibubi.create.foundation.blockEntity.BlockEntityBehaviour; import com.simibubi.create.foundation.utility.animation.LerpedFloat.Chaser; import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.core.Direction.AxisDirection; +import net.minecraft.resources.ResourceKey; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerLevel; import net.minecraft.sounds.SoundEvents; import net.minecraft.sounds.SoundSource; +import net.minecraft.world.level.Level; import net.minecraft.world.level.block.DoorBlock; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.properties.DoubleBlockHalf; import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate.StructureBlockInfo; +import net.minecraft.world.phys.Vec3; public class SlidingDoorMovementBehaviour implements MovementBehaviour { @@ -25,12 +39,12 @@ public class SlidingDoorMovementBehaviour implements MovementBehaviour { public boolean renderAsNormalBlockEntity() { return true; } - + @Override public boolean mustTickWhileDisabled() { return true; } - + @Override public void tick(MovementContext context) { StructureBlockInfo structureBlockInfo = context.contraption.getBlocks() @@ -43,7 +57,7 @@ public class SlidingDoorMovementBehaviour implements MovementBehaviour { tickOpen(context, open); Map tes = context.contraption.presentBlockEntities; - if (!(tes.get(context.localPos) instanceof SlidingDoorBlockEntity doorTE)) + if (!(tes.get(context.localPos)instanceof SlidingDoorBlockEntity doorTE)) return; boolean wasSettled = doorTE.animation.settled(); doorTE.animation.chase(open ? 1 : 0, .15f, Chaser.LINEAR); @@ -105,14 +119,90 @@ public class SlidingDoorMovementBehaviour implements MovementBehaviour { protected boolean shouldOpen(MovementContext context) { if (context.disabled) return false; - if (context.contraption instanceof ElevatorContraption ec && ec.arrived) - return true; - if (context.contraption.entity instanceof CarriageContraptionEntity cce) { - CarriageSyncData carriageData = cce.getCarriageData(); - if (Math.abs(carriageData.distanceToDestination) > 1) - return false; + Contraption contraption = context.contraption; + boolean canOpen = context.motion.length() < 1 / 128f && !contraption.entity.isStalled() + || contraption instanceof ElevatorContraption ec && ec.arrived; + + if (!canOpen) { + context.temporaryData = null; + return false; } - return context.motion.length() < 1 / 128f && !context.contraption.entity.isStalled(); + + if (context.temporaryData instanceof WeakReference wr && wr.get()instanceof DoorControlBehaviour dcb) + if (dcb.blockEntity != null && !dcb.blockEntity.isRemoved()) + return shouldOpenAt(dcb, context); + + context.temporaryData = null; + DoorControlBehaviour doorControls = null; + + if (contraption instanceof ElevatorContraption ec) + doorControls = getElevatorDoorControl(ec, context); + if (context.contraption.entity instanceof CarriageContraptionEntity cce) + doorControls = getTrainStationDoorControl(cce, context); + + if (doorControls == null) + return false; + + context.temporaryData = new WeakReference<>(doorControls); + return shouldOpenAt(doorControls, context); + } + + protected boolean shouldOpenAt(DoorControlBehaviour controller, MovementContext context) { + if (controller.mode == DoorControl.ALL) + return true; + if (controller.mode == DoorControl.NONE) + return false; + return controller.mode.matches(getDoorFacing(context)); + } + + protected DoorControlBehaviour getElevatorDoorControl(ElevatorContraption ec, MovementContext context) { + Integer currentTargetY = ec.getCurrentTargetY(context.world); + if (currentTargetY == null) + return null; + ColumnCoords columnCoords = ec.getGlobalColumn(); + if (columnCoords == null) + return null; + ElevatorColumn elevatorColumn = ElevatorColumn.get(context.world, columnCoords); + if (elevatorColumn == null) + return null; + return BlockEntityBehaviour.get(context.world, elevatorColumn.contactAt(currentTargetY), + DoorControlBehaviour.TYPE); + } + + protected DoorControlBehaviour getTrainStationDoorControl(CarriageContraptionEntity cce, MovementContext context) { + Carriage carriage = cce.getCarriage(); + if (carriage == null || carriage.train == null) + return null; + GlobalStation currentStation = carriage.train.getCurrentStation(); + if (currentStation == null) + return null; + + BlockPos stationPos = currentStation.getBlockEntityPos(); + ResourceKey stationDim = currentStation.getBlockEntityDimension(); + MinecraftServer server = context.world.getServer(); + if (server == null) + return null; + ServerLevel stationLevel = server.getLevel(stationDim); + if (stationLevel == null || !stationLevel.isLoaded(stationPos)) + return null; + return BlockEntityBehaviour.get(stationLevel, stationPos, DoorControlBehaviour.TYPE); + } + + protected Direction getDoorFacing(MovementContext context) { + Direction stateFacing = context.state.getValue(DoorBlock.FACING); + Direction originalFacing = Direction.get(AxisDirection.POSITIVE, stateFacing.getAxis()); + Vec3 centerOfContraption = context.contraption.bounds.getCenter(); + Vec3 diff = Vec3.atCenterOf(context.localPos) + .add(Vec3.atLowerCornerOf(stateFacing.getNormal()) + .scale(-.45f)) + .subtract(centerOfContraption); + if (originalFacing.getAxis() + .choose(diff.x, diff.y, diff.z) < 0) + originalFacing = originalFacing.getOpposite(); + + Vec3 directionVec = Vec3.atLowerCornerOf(originalFacing.getNormal()); + directionVec = context.rotation.apply(directionVec); + return Direction.getNearest(directionVec.x, directionVec.y, directionVec.z); } } diff --git a/src/main/java/com/simibubi/create/content/logistics/trains/management/edgePoint/station/AssemblyScreen.java b/src/main/java/com/simibubi/create/content/logistics/trains/management/edgePoint/station/AssemblyScreen.java index 81e19290d..82ce98601 100644 --- a/src/main/java/com/simibubi/create/content/logistics/trains/management/edgePoint/station/AssemblyScreen.java +++ b/src/main/java/com/simibubi/create/content/logistics/trains/management/edgePoint/station/AssemblyScreen.java @@ -65,14 +65,16 @@ public class AssemblyScreen extends AbstractStationScreen { toggleAssemblyButton.active = false; toggleAssemblyButton.setToolTip(Lang.translateDirect("station.assemble_train")); toggleAssemblyButton.withCallback(() -> { - AllPackets.getChannel().sendToServer(StationEditPacket.tryAssemble(blockEntity.getBlockPos())); + AllPackets.getChannel() + .sendToServer(StationEditPacket.tryAssemble(blockEntity.getBlockPos())); }); quitAssembly = new IconButton(x + 73, by, AllIcons.I_DISABLE); quitAssembly.active = true; quitAssembly.setToolTip(Lang.translateDirect("station.cancel")); quitAssembly.withCallback(() -> { - AllPackets.getChannel().sendToServer(StationEditPacket.configure(blockEntity.getBlockPos(), false, station.name)); + AllPackets.getChannel() + .sendToServer(StationEditPacket.configure(blockEntity.getBlockPos(), false, station.name, null)); minecraft.setScreen(new StationScreen(blockEntity, station)); }); @@ -90,7 +92,8 @@ public class AssemblyScreen extends AbstractStationScreen { toggleAssemblyButton.active = blockEntity.bogeyCount > 0 || train != null; if (train != null) { - AllPackets.getChannel().sendToServer(StationEditPacket.configure(blockEntity.getBlockPos(), false, station.name)); + AllPackets.getChannel() + .sendToServer(StationEditPacket.configure(blockEntity.getBlockPos(), false, station.name, null)); minecraft.setScreen(new StationScreen(blockEntity, station)); for (Carriage carriage : train.carriages) carriage.updateConductors(); @@ -105,10 +108,12 @@ public class AssemblyScreen extends AbstractStationScreen { toggleAssemblyButton.setToolTip(Lang.translateDirect("station.assemble_train")); toggleAssemblyButton.setIcon(AllGuiTextures.I_ASSEMBLE_TRAIN); toggleAssemblyButton.withCallback(() -> { - AllPackets.getChannel().sendToServer(StationEditPacket.tryAssemble(blockEntity.getBlockPos())); + AllPackets.getChannel() + .sendToServer(StationEditPacket.tryAssemble(blockEntity.getBlockPos())); }); } else { - AllPackets.getChannel().sendToServer(StationEditPacket.configure(blockEntity.getBlockPos(), false, station.name)); + AllPackets.getChannel() + .sendToServer(StationEditPacket.configure(blockEntity.getBlockPos(), false, station.name, null)); minecraft.setScreen(new StationScreen(blockEntity, station)); } } @@ -128,8 +133,8 @@ public class AssemblyScreen extends AbstractStationScreen { font.draw(ms, text, x + 97 - font.width(text) / 2, y + 47, 0x775B5B); int offset = 0; if (blockEntity.failedCarriageIndex != -1) { - font.draw(ms, Lang.translateDirect("station.carriage_number", blockEntity.failedCarriageIndex), x + 30, y + 67, - 0x7A7A7A); + font.draw(ms, Lang.translateDirect("station.carriage_number", blockEntity.failedCarriageIndex), x + 30, + y + 67, 0x7A7A7A); offset += 10; } font.drawWordWrap(lastAssemblyException.component, x + 30, y + 67 + offset, 134, 0x775B5B); @@ -158,7 +163,8 @@ public class AssemblyScreen extends AbstractStationScreen { if (train != null) { ResourceLocation iconId = iconTypes.get(iconTypeScroll.getState()); train.icon = TrainIconType.byId(iconId); - AllPackets.getChannel().sendToServer(new TrainEditPacket(train.id, "", iconId)); + AllPackets.getChannel() + .sendToServer(new TrainEditPacket(train.id, "", iconId)); } } diff --git a/src/main/java/com/simibubi/create/content/logistics/trains/management/edgePoint/station/StationBlockEntity.java b/src/main/java/com/simibubi/create/content/logistics/trains/management/edgePoint/station/StationBlockEntity.java index 54457b894..f2d76cbc6 100644 --- a/src/main/java/com/simibubi/create/content/logistics/trains/management/edgePoint/station/StationBlockEntity.java +++ b/src/main/java/com/simibubi/create/content/logistics/trains/management/edgePoint/station/StationBlockEntity.java @@ -16,6 +16,7 @@ import com.simibubi.create.AllBlocks; import com.simibubi.create.AllItems; import com.simibubi.create.AllSoundEvents; import com.simibubi.create.Create; +import com.simibubi.create.content.contraptions.components.actors.DoorControlBehaviour; import com.simibubi.create.content.contraptions.components.structureMovement.AssemblyException; import com.simibubi.create.content.contraptions.components.structureMovement.ITransformableBlockEntity; import com.simibubi.create.content.contraptions.components.structureMovement.StructureTransform; @@ -80,6 +81,7 @@ import net.minecraftforge.network.PacketDistributor; public class StationBlockEntity extends SmartBlockEntity implements ITransformableBlockEntity { public TrackTargetingBehaviour edgePoint; + public DoorControlBehaviour doorControls; public LerpedFloat flag; protected int failedCarriageIndex; @@ -111,6 +113,7 @@ public class StationBlockEntity extends SmartBlockEntity implements ITransformab @Override public void addBehaviours(List behaviours) { behaviours.add(edgePoint = new TrackTargetingBehaviour<>(this, EdgePointType.STATION)); + behaviours.add(doorControls = new DoorControlBehaviour(this)); behaviours.add(depotBehaviour = new DepotBehaviour(this).onlyAccepts(AllItems.SCHEDULE::isIn) .withCallback(s -> applyAutoSchedule())); depotBehaviour.addSubBehaviours(behaviours); diff --git a/src/main/java/com/simibubi/create/content/logistics/trains/management/edgePoint/station/StationEditPacket.java b/src/main/java/com/simibubi/create/content/logistics/trains/management/edgePoint/station/StationEditPacket.java index e90c10f0f..5f545f510 100644 --- a/src/main/java/com/simibubi/create/content/logistics/trains/management/edgePoint/station/StationEditPacket.java +++ b/src/main/java/com/simibubi/create/content/logistics/trains/management/edgePoint/station/StationEditPacket.java @@ -1,6 +1,7 @@ package com.simibubi.create.content.logistics.trains.management.edgePoint.station; import com.simibubi.create.Create; +import com.simibubi.create.content.contraptions.components.actors.DoorControl; import com.simibubi.create.content.logistics.trains.GraphLocation; import com.simibubi.create.content.logistics.trains.entity.Train; import com.simibubi.create.foundation.networking.BlockEntityConfigurationPacket; @@ -9,6 +10,7 @@ import com.simibubi.create.foundation.utility.VecHelper; import net.minecraft.core.BlockPos; import net.minecraft.network.FriendlyByteBuf; import net.minecraft.server.level.ServerPlayer; +import net.minecraft.util.Mth; import net.minecraft.world.entity.item.ItemEntity; import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.Level; @@ -20,6 +22,7 @@ public class StationEditPacket extends BlockEntityConfigurationPacket AllPackets.getChannel().sendToServer(StationEditPacket.dropSchedule(blockEntity.getBlockPos()))); + dropScheduleButton.withCallback(() -> AllPackets.getChannel() + .sendToServer(StationEditPacket.dropSchedule(blockEntity.getBlockPos()))); addRenderableWidget(dropScheduleButton); onTextChanged = s -> trainNameBox.x = nameBoxX(s, trainNameBox); @@ -103,6 +110,11 @@ public class StationScreen extends AbstractStationScreen { trainNameBox.active = false; tickTrainDisplay(); + + Pair doorControlWidgets = + DoorControl.createWidget(x + 35, y + 102, mode -> doorControl = mode, doorControl); + addRenderableWidget(doorControlWidgets.getFirst()); + addRenderableWidget(doorControlWidgets.getSecond()); } @Override @@ -192,12 +204,13 @@ public class StationScreen extends AbstractStationScreen { } boolean trainAtStation = trainPresent(); - disassembleTrainButton.active = trainAtStation && blockEntity.trainCanDisassemble && blockEntity.edgePoint.isOrthogonal(); + disassembleTrainButton.active = + trainAtStation && blockEntity.trainCanDisassemble && blockEntity.edgePoint.isOrthogonal(); dropScheduleButton.active = blockEntity.trainHasSchedule; if (blockEntity.trainHasSchedule) - dropScheduleButton.setToolTip( - Lang.translateDirect(blockEntity.trainHasAutoSchedule ? "station.remove_auto_schedule" : "station.remove_schedule")); + dropScheduleButton.setToolTip(Lang.translateDirect( + blockEntity.trainHasAutoSchedule ? "station.remove_auto_schedule" : "station.remove_schedule")); else dropScheduleButton.getToolTip() .clear(); @@ -237,6 +250,8 @@ public class StationScreen extends AbstractStationScreen { if (!nameBox.isFocused()) AllGuiTextures.STATION_EDIT_NAME.render(ms, nameBoxX(text, nameBox) + font.width(text) + 5, y + 1); + itemRenderer.renderGuiItem(AllBlocks.TRAIN_DOOR.asStack(), x + 14, y + 103); + Train train = displayedTrain.get(); if (train == null) { MutableComponent header = Lang.translateDirect("station.idle"); @@ -333,32 +348,38 @@ public class StationScreen extends AbstractStationScreen { 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())); + AllPackets.getChannel() + .sendToServer(new TrainEditPacket(train.id, trainNameBox.getValue(), train.icon.getId())); } private void syncStationName() { if (!nameBox.getValue() .equals(station.name)) - AllPackets.getChannel().sendToServer(StationEditPacket.configure(blockEntity.getBlockPos(), false, nameBox.getValue())); + AllPackets.getChannel() + .sendToServer( + StationEditPacket.configure(blockEntity.getBlockPos(), false, nameBox.getValue(), doorControl)); } @Override public void removed() { super.removed(); AllPackets.getChannel() - .sendToServer(StationEditPacket.configure(blockEntity.getBlockPos(), switchingToAssemblyMode, nameBox.getValue())); + .sendToServer(StationEditPacket.configure(blockEntity.getBlockPos(), switchingToAssemblyMode, + nameBox.getValue(), doorControl)); Train train = displayedTrain.get(); if (train == null) return; if (!switchingToAssemblyMode) - AllPackets.getChannel().sendToServer(new TrainEditPacket(train.id, trainNameBox.getValue(), train.icon.getId())); + AllPackets.getChannel() + .sendToServer(new TrainEditPacket(train.id, trainNameBox.getValue(), train.icon.getId())); else blockEntity.imminentTrain = null; } @Override protected PartialModel getFlag(float partialTicks) { - return blockEntity.flag.getValue(partialTicks) > 0.75f ? AllPartialModels.STATION_ON : AllPartialModels.STATION_OFF; + return blockEntity.flag.getValue(partialTicks) > 0.75f ? AllPartialModels.STATION_ON + : AllPartialModels.STATION_OFF; } } diff --git a/src/main/java/com/simibubi/create/foundation/gui/widget/AbstractSimiWidget.java b/src/main/java/com/simibubi/create/foundation/gui/widget/AbstractSimiWidget.java index 6d0d747c3..01a459aca 100644 --- a/src/main/java/com/simibubi/create/foundation/gui/widget/AbstractSimiWidget.java +++ b/src/main/java/com/simibubi/create/foundation/gui/widget/AbstractSimiWidget.java @@ -17,6 +17,7 @@ import net.minecraft.network.chat.Component; public abstract class AbstractSimiWidget extends AbstractWidget implements TickableGuiEventListener { public static final int HEADER_RGB = 0x5391E1; + public static final int HINT_RGB = 0x96B7E0; protected float z; protected boolean wasHovered = false; diff --git a/src/main/java/com/simibubi/create/foundation/gui/widget/ScrollInput.java b/src/main/java/com/simibubi/create/foundation/gui/widget/ScrollInput.java index 6635db7a9..98a78ed5e 100644 --- a/src/main/java/com/simibubi/create/foundation/gui/widget/ScrollInput.java +++ b/src/main/java/com/simibubi/create/foundation/gui/widget/ScrollInput.java @@ -22,6 +22,7 @@ public class ScrollInput extends AbstractSimiWidget { protected Component title = Lang.translateDirect("gui.scrollInput.defaultTitle"); protected final Component scrollToModify = Lang.translateDirect("gui.scrollInput.scrollToModify"); protected final Component shiftScrollsFaster = Lang.translateDirect("gui.scrollInput.shiftScrollsFaster"); + protected Component hint = null; protected Label displayLabel; protected boolean inverted; protected Function formatter; @@ -76,6 +77,12 @@ public class ScrollInput extends AbstractSimiWidget { return this; } + public ScrollInput addHint(MutableComponent hint) { + this.hint = hint; + updateTooltip(); + return this; + } + public ScrollInput withStepFunction(Function step) { this.step = step; return this; @@ -128,7 +135,10 @@ public class ScrollInput extends AbstractSimiWidget { clampState(); if (priorState != state) { - Minecraft.getInstance().getSoundManager().play(SimpleSoundInstance.forUI(AllSoundEvents.SCROLL_VALUE.getMainEvent(), 1.5f + 0.1f * (state-min)/(max-min))); + Minecraft.getInstance() + .getSoundManager() + .play(SimpleSoundInstance.forUI(AllSoundEvents.SCROLL_VALUE.getMainEvent(), + 1.5f + 0.1f * (state - min) / (max - min))); onChanged(); } @@ -160,6 +170,9 @@ public class ScrollInput extends AbstractSimiWidget { return; toolTip.add(title.plainCopy() .withStyle(s -> s.withColor(HEADER_RGB))); + if (hint != null) + toolTip.add(hint.plainCopy() + .withStyle(s -> s.withColor(HINT_RGB))); toolTip.add(scrollToModify.plainCopy() .withStyle(ChatFormatting.ITALIC, ChatFormatting.DARK_GRAY)); toolTip.add(shiftScrollsFaster.plainCopy() diff --git a/src/main/java/com/simibubi/create/foundation/gui/widget/SelectionScrollInput.java b/src/main/java/com/simibubi/create/foundation/gui/widget/SelectionScrollInput.java index 097f21b32..158175120 100644 --- a/src/main/java/com/simibubi/create/foundation/gui/widget/SelectionScrollInput.java +++ b/src/main/java/com/simibubi/create/foundation/gui/widget/SelectionScrollInput.java @@ -43,7 +43,8 @@ public class SelectionScrollInput extends ScrollInput { if (this.min + 1 == min) min--; if (min > this.min) - toolTip.add(Components.literal("> ...").withStyle(ChatFormatting.GRAY)); + toolTip.add(Components.literal("> ...") + .withStyle(ChatFormatting.GRAY)); if (this.max - 1 == max) max++; for (int i = min; i < max; i++) { @@ -59,8 +60,12 @@ public class SelectionScrollInput extends ScrollInput { .withStyle(ChatFormatting.GRAY)); } if (max < this.max) - toolTip.add(Components.literal("> ...").withStyle(ChatFormatting.GRAY)); + toolTip.add(Components.literal("> ...") + .withStyle(ChatFormatting.GRAY)); + if (hint != null) + toolTip.add(hint.plainCopy() + .withStyle(s -> s.withColor(HINT_RGB))); toolTip.add(scrollToSelect.plainCopy() .withStyle(ChatFormatting.DARK_GRAY, ChatFormatting.ITALIC)); } diff --git a/src/main/resources/assets/create/lang/default/interface.json b/src/main/resources/assets/create/lang/default/interface.json index 9e38399db..cffbf4879 100644 --- a/src/main/resources/assets/create/lang/default/interface.json +++ b/src/main/resources/assets/create/lang/default/interface.json @@ -814,6 +814,21 @@ "create.contraption.controls.actor_toggle.on": "On", "create.contraption.controls.actor_toggle.off": "Off", "create.contraption.controls.floor_unreachable": "Unreachable", + + "create.contraption.door_control": "Onboard Door Control", + "create.contraption.door_control.all": "Open All Doors", + "create.contraption.door_control.all.short": "Open All", + "create.contraption.door_control.north": "North Side Only", + "create.contraption.door_control.north.short": "North", + "create.contraption.door_control.east": "East Side Only", + "create.contraption.door_control.east.short": "East", + "create.contraption.door_control.south": "South Side Only", + "create.contraption.door_control.south.short": "South", + "create.contraption.door_control.west": "West Side Only", + "create.contraption.door_control.west.short": "West", + "create.contraption.door_control.none": "Keep Doors Closed", + "create.contraption.door_control.none.short": "None", + "create.contraption.door_control.player_facing": "You are facing: %1$s", "create.display_link.set": "Targeted position selected", "create.display_link.success": "Successfully bound to targeted position", diff --git a/src/main/resources/assets/create/textures/gui/display_link.png b/src/main/resources/assets/create/textures/gui/display_link.png index 216ab336a7c39d8e9ca734108f295cc825143dc7..14a18c49e585226ab3c514ed5435fa4705c6b641 100644 GIT binary patch delta 1862 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 NT*m`@i~kjv@IRBc0mT3S diff --git a/src/main/resources/assets/create/textures/gui/schedule_2.png b/src/main/resources/assets/create/textures/gui/schedule_2.png index aca4d1fe8cfa26059905169139897ef74839ddd6..ef60c2f16be7506ae34b0fbdf1a5f6d4a891fb40 100644 GIT binary patch literal 2150 zcmb_eX;4#H7QXNK0t5v@!s39CfIu`r2oQFS2?Hn+5J3b~Ld1q>D_bi|p_oS)6bvG2 zx5f>yLE2`KWe}9GgaN@;$3a0B6~W*FZF&$B5fMqh%ydmn)%=}T-@E6WTlakD+HRgUx0qCntM(c|}G>dU$voI&?@Z z7MGWo<8U|_hPAY`APD-OFG0?#d431!j1Bk|8527&8 zKqL(y2;ygN9Oz`~ZA;v+hQy+hylf0TR~xRQl6>rl9;*%AtqEQ>L^mq}lR|J`ZRkQV za3&kLSreF61cs%)lcl~3h2Tsvpj+rWuF_+W4IIq%C=^OULV{c_9~l`P85tQK9u|p2 zbUOXPg9q3hN1IkPVeMwVBP;&R<2AKg0WS1EV9(%oNt=g)Xiz&24jBEIoOL(t zne=v4XR7jEYue$}UPrUKyGH@@n`7v%5R6ZJeQ>^tb?w&1u_PUu!%QL@|FJ+J3nIy|0tcDA&9%9VHJ zlHCl1j~5rkZyuG$8h5TeI;?$3)De)q>)<|Z#n1*wc)jsJH8t|}>-pwH#l7EpO6GSQ zxKuu={I2?ws-wMUNO9Fb&{#3<96$c)RUWav#PsKL*>5kI?qD~l1s8wNCKju0Yb(Qr z+Cj2O`RrL0O-OWrwkUtno{mT*I6(h6A^0*K(Lu&Mu`XljgxtUOhUC5P7|N9ZhJVBH6& zE@Fy=d^EW3L>-4%>^;?e!J+#zlW3a{+RnOg6o<*}WoXa1hdG-qFEbFRPH|F?*7nf) zCqZgY53N|UYGmJk%;&r+Wnj!G)<}=x{Wv(K*02jZ`CVS~|JEZ|#bw81O|Crs?$2rG z&}~&x#6}}v)cyHxSb}zxhB~M9A9r1s>!+fIMoHtc`}&Mf*5#`}S3(2iU)EE1&JVwc zg4Eo&qVYUX6YDC|VTty$M6Y$>X1lwpsoB|rx|~YDER*%Tu`o5U=6`(kYH)xphqrIp zvR?0jHHdxKCqgpKH-(|C%WIH%GYMT*tES_oN}-$RlL1%qvT&T|-1t>6&`GGzN>6{2 z#J>oJm!u^Am-BGj3NEQ$^z}C0N#*p$=m29RWYXIl@LhleowvsVsPlHOR#@x-Q3*Y@>7&;SZEHFg1Ta=Fq0EEoXm9~PJ zrAx3@=FP2pHd-&35LQAnS&j8`-w$sJk%;YjW(Vv zU^$hB#=6-Rf-xZsbJc0Es?^MNdk#l0U`6Y!u1^I{vJ_6>|5J;?&fuFjNIEMYeM!G7 z+MW0Gj-*T940@A9$w`KCHvw;FiF0>6DQjFmT5W%9@DHYYkOT4>9&$ z$o6&P5pM%Xt2$|rmnME{o-v1izG$K}Ew`!O@Pro=$I#h3CTh^x$GS3TSmMxEVD$bd o>x%az?8~y~bx+S)65ZXaBIkJIySj?qH2)7E&_BfQhA&_AJLTA_F8}}l literal 1500 zcmaKsdo+}39LJyMHN!BCcaY7xj$CF$2FbND~i18^ZkvAx&;{8B((2q)sbeEgP>L;w_9mKU@k`s*)0N%V7d z1ohpT<9IX1@eFXmb!lm7baYfAkxC?zfq{X}&dw>(ez90A6bh@VswyihTZ)|v3ky%2 zIKk)h)6&wCl9J-$;<#LHP*6}{U?7Xd+GPQ4ZEbg&gPmqz2M!ZhTU&2ug6*bY8_rs= z%>;y)05|5U5MvOGvjzm=7y&agGiPH}Q&Uq1hRPLB z)TmosTAP!5zBoajR-t?E(g%c!0Rjy`5(^;ILU+~VNIF)*cDlA?sc$S+bnA9cwQhFT zO$bP+o^M$5oymwOxzcDvbh|k7I#3Y&3&!oZx?n1(Y0DnZfaCGuA2}=nVKxL)Sao-0 zYbqdezR-@WAivWRbjCl4g83?urL^Ts%N}1?V2UpAuo^Fj5-iE=FjYPiLlA=(2m||y zI!7v_g)9KcyMMh=%@?It0cj4z51J{tEl`*Fyhzg`$@@HEZ=-0p3OJhxIdRW8k zbt}K}{Ga{VSHUkeo#-IaDCSi5otuZtx~_Mi!9yBvzh%BwDJLIuRX5+XN2MTY^@F6m zFM(m$%~R22NhX`syrI!(==BF5w5MlCXSm{}Uv1y3zjM55`%+#cX{8^^eG(tJuIYlO zPGZ`DD4)@3Wa~BMg06^`rBH@tz8CVncuLI4GRkXfTO$+c$@kLa_aE@E3?FM)M2BZ8{Mp`P3(g<7{Bq8?JHT04xU zOQD{45RH*SN{JNZN}+N8Fq$ESGP#e@6e*;4-$&_ENIo}&9*{z`tP8c4Lb-wgbe9w= zO-{9Yq_nov=hz>m%||qjW~q@d{x_?4{ML7{=Y1$jq)m!!nk+IXg1(NwcK=8cei|K^ zQ@eNu;WBxP>cr0Bl8qG6(H2p#f9S*1*1Q^K4M0eVvX@zLJlrhbaPKXx6@i!2k82yuIP_T6Y`PG=SN{3T*u)Mt})u&97|&Z+>O7BpOt$6CZiv_*HBkp@e!Xoq-KM52QEQ^Dk3 zll+IanO+P8xefV7J$(m!Opq)Jdo{Ade(%zc%$z-N%m4`Xc zEU`veZ3;mxND1a-1F_*lRXZO!B)|43xL*#U&y)=u*~37bc@Um{hE#dJ z8s;-{Ms!ib%p2nk3Nn(S@-ZhT!^FpIFp<^aa5q}%(Ur~SRQx9bZZ6)=^^Rfue*jH) BC1L;o