diff --git a/src/main/java/cam72cam/immersiverailroading/gui/TrackExchangerGui.java b/src/main/java/cam72cam/immersiverailroading/gui/TrackExchangerGui.java index 364b8b02d..721d96bad 100644 --- a/src/main/java/cam72cam/immersiverailroading/gui/TrackExchangerGui.java +++ b/src/main/java/cam72cam/immersiverailroading/gui/TrackExchangerGui.java @@ -95,11 +95,14 @@ public void draw(IScreenBuilder builder) { // This could be more efficient... RailSettings settings = new RailSettings(gauge, track, - TrackItems.STRAIGHT, + TrackItems.STRAIGHT,TrackItems.STRAIGHT, 10, 0, 1, TrackPositionType.FIXED, TrackSmoothing.BOTH, + 0,0, + 0,0, + true,-1, TrackDirection.NONE, railBed, ItemStack.EMPTY, diff --git a/src/main/java/cam72cam/immersiverailroading/gui/TrackGui.java b/src/main/java/cam72cam/immersiverailroading/gui/TrackGui.java index 44e216545..01adff4b1 100644 --- a/src/main/java/cam72cam/immersiverailroading/gui/TrackGui.java +++ b/src/main/java/cam72cam/immersiverailroading/gui/TrackGui.java @@ -2,7 +2,6 @@ import cam72cam.immersiverailroading.Config; import cam72cam.immersiverailroading.gui.components.ListSelector; -import cam72cam.immersiverailroading.gui.components.NumberInputer; import cam72cam.immersiverailroading.items.nbt.RailSettings; import cam72cam.immersiverailroading.library.*; import cam72cam.immersiverailroading.net.ItemRailUpdatePacket; @@ -10,13 +9,8 @@ import cam72cam.immersiverailroading.registry.TrackDefinition; import cam72cam.immersiverailroading.render.rail.RailRender; import cam72cam.immersiverailroading.tile.TileRailPreview; -import cam72cam.immersiverailroading.track.BuilderTransferTable; -import cam72cam.immersiverailroading.track.BuilderTurnTable; -import cam72cam.immersiverailroading.track.TrackBase; -import cam72cam.immersiverailroading.util.IRFuzzy; -import cam72cam.immersiverailroading.util.MathUtil; -import cam72cam.immersiverailroading.util.PlacementInfo; -import cam72cam.immersiverailroading.util.RailInfo; +import cam72cam.immersiverailroading.track.*; +import cam72cam.immersiverailroading.util.*; import cam72cam.mod.MinecraftClient; import cam72cam.mod.entity.Player; import cam72cam.mod.gui.helpers.GUIHelpers; @@ -29,6 +23,7 @@ import util.Matrix4; import java.util.*; +import java.util.function.Consumer; import java.util.stream.Collectors; import static cam72cam.immersiverailroading.gui.ClickListHelper.next; @@ -63,10 +58,40 @@ public class TrackGui implements IScreen { private ListSelector gaugeSelector; private ListSelector typeSelector; - private ListSelector trackSelector; + private ListSelector trackSelector; private ListSelector railBedSelector; private ListSelector railBedFillSelector; + //multiWaySwitch + private MultiSwitchInfo.Mutable multiSwitchInfo; + private RailSettings.Mutable selectedWaySettings; + private int selectedWay = 0;//0=default,1=MID1,2=MID2,3=MID3,4=MID4,5=TURN + private ListSelector subTypeSelector; + private Button showSubSelectorButton; + private Button wayCircleButton; + private Button insertWayButton; + private Button addWayButton; + private Button delWayButton; + //spiralCurve + // curves based on cubic-curve are limited in some case, we may need new types for more complex curve and function, + // like completely decoupling vertical and horizontal curve, more spiralCurve and real arc turn(this will looks better with big radius)? + private TextField farRadiusInput; + private Button farRadiusText; + private Button toggleStraightAtP1; + + //micro judge + private Slider nearHeightOffsetSlider; + private Slider farHeightOffsetSlider; + + //vertical smooth config +// private Slider nearPitchSlider; +// private Slider farPitchSlider; + private TextField nearPitchInput; + private Button nearPitchText;//only used to show text + private TextField farPitchInput; + private Button farPitchText;//only used to show text + + //zoom private double zoom = 1; public TrackGui() { @@ -81,6 +106,9 @@ public TrackGui(TileRailPreview te) { private TrackGui(ItemStack stack) { stack = stack.copy(); settings = RailSettings.from(stack).mutable(); + multiSwitchInfo = MultiSwitchInfo.from(stack).mutable(); + selectedWay = MultiSwitchInfo.getSelectedFrom(stack); + oreDict = new ArrayList<>(); oreDict.add(ItemStack.EMPTY); oreDict.addAll(IRFuzzy.IR_RAIL_BED.enumerate()); @@ -100,8 +128,10 @@ public void init(IScreenBuilder screen) { int height = 20; int xtop = -GUIHelpers.getScreenWidth() / 2; int ytop = -GUIHelpers.getScreenHeight() / 4; + updateSelectedWay(selectedWay); + this.lengthInput = new TextField(screen, xtop, ytop, width-1, height); - this.lengthInput.setText("" + settings.length); + this.lengthInput.setText("" + (selectedWay==0 ? settings.length : selectedWaySettings.length)); this.lengthInput.setValidator(s -> { if (s == null || s.length() == 0) { return true; @@ -119,7 +149,14 @@ public void init(IScreenBuilder screen) { : BuilderTransferTable.maxLength(settings.gauge); } if (val > 0 && val <= max) { - settings.length = val; + if(settings.type == TrackItems.CUBICPARABOLA && !CubicCurve.isCubicParabolaDeltaValid(settings.length,settings.degrees,val))return false; + if(selectedWay == 0){ + settings.length = val; + }else{ + selectedWaySettings.length = val; + syncMultiSwitchInfo(); + } + return true; } return false; @@ -137,7 +174,95 @@ public void init(IScreenBuilder screen) { // settings.length = val.intValue(); // } // }); + + //pitch (right panel) + //we should read this from config file? tan(6) = 0.1051... +// float angleLimit = 6; +// nearPitchSlider = new Slider(screen, xtop + width + 50, ytop, "near pitch:", -angleLimit, angleLimit, settings.pitchTag.getFloat("start"), true) { +// @Override +// public void onSlider() { +// settings.pitchTag.setFloat("start",(float) this.getValue()); +// updateListSetting(mutable -> mutable.pitchTag.setFloat("start",(float) this.getValue())); +// nearPitchSlider.setText("near pitch:"+String.format("%.2f", settings.pitchTag.getFloat("start"))); +// } +// }; +// nearPitchSlider.onSlider(); + + nearPitchText = new Button(screen ,GUIHelpers.getScreenWidth() / 2 - width, ytop, 80, height, GuiText.TRACK_NEAR_PITCH.toString()); + nearPitchInput = new TextField(screen, GUIHelpers.getScreenWidth() / 2 - width + 80, ytop, width - 81, height); + nearPitchInput.setText(String.format("%.2f", settings.pitchTag.getFloat("start"))); + nearPitchInput.setValidator(s -> { + if (s == null || s.length() == 0) { + return true; + } + float val; + try { + val = Float.parseFloat(s);//real angle = arctan(val/1000) + } catch (NumberFormatException e) { + if(s.equals(".") || s.equals("-"))return true; + return false; + } + Float max = 1000f; + if (Math.abs(val) < max) { + settings.pitchTag.setFloat("start", val); + updateListSetting(mutable -> mutable.pitchTag.setFloat("start", val)); + return true; + } + + return false; + }); + nearPitchInput.setFocused(true); + nearPitchInput.setVisible(settings.type.hasSmoothing()); + nearPitchText.setVisible(settings.type.hasSmoothing()); + ytop += height; +// farPitchSlider = new Slider(screen, xtop + width + 50, ytop, "far pitch:", -angleLimit, angleLimit, settings.pitchTag.getFloat("end"), true) { +// @Override +// public void onSlider() { +// if(selectedWay == 0){ +// settings.pitchTag.setFloat("end",(float) this.getValue()); +// farPitchSlider.setText("far pitch:"+String.format("%.2f", settings.pitchTag.getFloat("end"))); +// }else { +// selectedWaySettings.pitchTag.setFloat("end",(float) this.getValue()); +// farPitchSlider.setText("far pitch:"+String.format("%.2f", selectedWaySettings.pitchTag.getFloat("end"))); +// syncMultiSwitchInfo(); +// } +// +// } +// }; +// farPitchSlider.onSlider(); + + farPitchText = new Button(screen ,GUIHelpers.getScreenWidth() / 2 - width, ytop, 80, height, GuiText.TRACK_FAR_PITCH.toString()); + farPitchInput = new TextField(screen, GUIHelpers.getScreenWidth() / 2 - width + 80, ytop, width - 81, height); + farPitchInput.setText(String.format("%.2f", selectedWay == 0 ? settings.pitchTag.getFloat("end"):selectedWaySettings.pitchTag.getFloat("end"))); + farPitchInput.setValidator(s -> { + if (s == null || s.length() == 0) { + return true; + } + float val; + try { + val = Float.parseFloat(s);//real angle = arctan(val/1000) + } catch (NumberFormatException e) { + if(s.equals(".") || s.equals("-"))return true; + return false; + } + Float max = 1000f; + if (Math.abs(val) < max) { + if(selectedWay == 0) { + settings.pitchTag.setFloat("end", val); + return true; + }else { + selectedWaySettings.pitchTag.setFloat("end", val); + syncMultiSwitchInfo(); + return true; + } + } + + return false; + }); + farPitchInput.setFocused(true); + farPitchInput.setVisible(selectedWay == 0 ? settings.type.hasSmoothing() : selectedWaySettings.type.hasSmoothing()); + farPitchText.setVisible(selectedWay == 0 ? settings.type.hasSmoothing() : selectedWaySettings.type.hasSmoothing()); gaugeSelector = new ListSelector(screen, width, 100, height, settings.gauge, Gauge.values().stream().collect(Collectors.toMap(Gauge::toString, g -> g, (u, v) -> u, LinkedHashMap::new)) @@ -145,6 +270,8 @@ public void init(IScreenBuilder screen) { @Override public void onClick(Gauge gauge) { settings.gauge = gauge; + updateListSetting(mutable -> mutable.gauge = gauge); + gaugeButton.setText(GuiText.SELECTOR_GAUGE.toString(settings.gauge)); if (settings.type.isTable()) { int max = settings.type == TrackItems.TURNTABLE @@ -164,6 +291,91 @@ public void onClick(Player.Hand hand) { }; ytop += height; + + //multiSwitch buttons (right panel) + wayCircleButton = new Button(screen, GUIHelpers.getScreenWidth() / 2 - width, ytop, 85, height, GuiText.TRACK_SELECTED_WAY.toString(selectedWay)) { + @Override + public void onClick(Player.Hand hand) { + if(hand == Player.Hand.SECONDARY){ + updateSelectedWay((selectedWay + 1) % (multiSwitchInfo.wayList.size() + 1)); + }else { + updateSelectedWay((selectedWay - 1 + multiSwitchInfo.wayList.size() + 1) % (multiSwitchInfo.wayList.size() + 1)); + } + + if(selectedWay == 0){ + subTypeSelector.onClick(multiSwitchInfo.realShapeType); + }else { + subTypeSelector.onClick(selectedWaySettings.type); + } + } + }; + showSubSelectorButton = new Button(screen, GUIHelpers.getScreenWidth() / 2 - width + 85, ytop, 115, height, GuiText.SELECTOR_SUB_TYPE.toString(selectedWay==0?multiSwitchInfo.realShapeType:selectedWaySettings.type)) { + @Override + public void onClick(Player.Hand hand) { + showSelector(subTypeSelector); + } + }; + + //go nextline + insertWayButton = new Button(screen, GUIHelpers.getScreenWidth() / 2 - width + 50, ytop + height, 50, height, GuiText.TRACK_INSERT_WAY.toString()) { + @Override + public void onClick(Player.Hand hand) { + if(selectedWay - 1 >= 0 && multiSwitchInfo.wayList.size()< 6 - 1){ + SingleWayInfo singleWayInfo = new SingleWayInfo(SingleWayInfo.defaultSettings, SingleWayInfo.defaultPos, null, selectedWay - 1); + multiSwitchInfo.wayList.add(selectedWay-1,singleWayInfo); + for(int i = selectedWay; i < multiSwitchInfo.wayList.size(); i++) { + multiSwitchInfo.wayList.get(i).mutable().wayOrder++; + } + updateSelectedWay(selectedWay); + } + } + }; + addWayButton = new Button(screen, GUIHelpers.getScreenWidth() / 2 - width + 50 + 50, ytop + height, 50, height, GuiText.TRACK_ADD_WAY.toString()) { + @Override + public void onClick(Player.Hand hand) { + if(multiSwitchInfo.wayList.size() < 6 - 1){ + SingleWayInfo singleWayInfo = new SingleWayInfo(SingleWayInfo.defaultSettings.with( + mutable -> { + mutable.pitchTag.setFloat("start",settings.pitchTag.getFloat("start")); + mutable.posOffsetTag.setFloat("placementOffset",settings.posOffsetTag.getFloat("placementOffset")); + mutable.gauge = settings.gauge; + mutable.track = settings.track; + mutable.railBed = settings.railBed; + mutable.railBedFill = settings.railBedFill; + } + ),SingleWayInfo.defaultPos,null,multiSwitchInfo.wayList.size()); + multiSwitchInfo.wayList.add(singleWayInfo); + } + } + }; + delWayButton = new Button(screen, GUIHelpers.getScreenWidth() / 2 - width + 50 + 50*2, ytop + height, 50, height, GuiText.TRACK_DELETE_WAY.toString()) { + @Override + public void onClick(Player.Hand hand) { + //in common case waylist and multiSwitchInfo should not be null + if(selectedWay == 1 && multiSwitchInfo.wayList.size() > 1) { + multiSwitchInfo.wayList.remove(0); + for(int i = 0; i < multiSwitchInfo.wayList.size(); i++) { + multiSwitchInfo.wayList.get(i).mutable().wayOrder--; + } + updateSelectedWay(1); + wayCircleButton.onClick(Player.Hand.SECONDARY); + }else if(selectedWay != 0) { + multiSwitchInfo.wayList.remove(selectedWay - 1); + for(int i = selectedWay - 1; i < multiSwitchInfo.wayList.size(); i++) { + multiSwitchInfo.wayList.get(i).mutable().wayOrder--; + } + updateSelectedWay(selectedWay - 1); + wayCircleButton.onClick(Player.Hand.SECONDARY); + } + } + }; + insertWayButton.setVisible(settings.type.isMulti()); + addWayButton.setVisible(settings.type.isMulti()); + delWayButton.setVisible(settings.type.isMulti()); + wayCircleButton.setVisible(settings.type.isMulti()); + showSubSelectorButton.setVisible(settings.type.isMulti()); + + //back to left panel typeSelector = new ListSelector(screen, width, 100, height, settings.type, Arrays.stream(TrackItems.values()) .filter(i -> i != TrackItems.CROSSING) @@ -173,11 +385,70 @@ public void onClick(Player.Hand hand) { @Override public void onClick(TrackItems option) { settings.type = option; - typeButton.setText(GuiText.SELECTOR_TYPE.toString(settings.type)); + settings.pickType = option; + + updateSelectedWay(0); + + if(settings.type == TrackItems.CUBICPARABOLA && settings.degrees > CubicCurve.cubicParabolaMaxAngle) { + settings.degrees = (float) CubicCurve.cubicParabolaMaxAngle; + degreesSlider.setValue(settings.degrees * Config.ConfigBalance.AnglePlacementSegmentation / 90); + }else { + degreesSlider.setValue(settings.degrees * Config.ConfigBalance.AnglePlacementSegmentation / 90); + } + degreesSlider.setText(GuiText.SELECTOR_QUARTERS.toString(degreesSlider.getValueInt() * (90.0 / Config.ConfigBalance.AnglePlacementSegmentation))); degreesSlider.setVisible(settings.type.hasQuarters()); + + curvositySlider.setText(GuiText.SELECTOR_CURVOSITY.toString(String.format("%.2f", settings.curvosity))); curvositySlider.setVisible(settings.type.hasCurvosity()); + curvositySlider.setValue(settings.curvosity); + + smoothingButton.setText(GuiText.SELECTOR_SMOOTHING.toString(settings.smoothing)); smoothingButton.setVisible(settings.type.hasSmoothing()); + + directionButton.setText(GuiText.SELECTOR_DIRECTION.toString(settings.direction)); directionButton.setVisible(settings.type.hasDirection()); + +// nearPitchSlider.setValue(settings.pitchTag.getFloat("start")); +// nearPitchSlider.setText("near pitch:"+String.format("%.2f", nearPitchSlider.getValue())); +// nearPitchSlider.setVisible(settings.type.hasSmoothing()); + +// farPitchSlider.setValue(settings.pitchTag.getFloat("end")); +// farPitchSlider.setText("far pitch:"+String.format("%.2f", farPitchSlider.getValue())); +// farPitchSlider.setVisible(settings.type.hasSmoothing()); + + nearPitchInput.setText(String.format("%.2f", settings.pitchTag.getFloat("start"))); + nearPitchInput.setVisible(settings.type.hasSmoothing()); + nearPitchText.setVisible(settings.type.hasSmoothing()); + + farPitchInput.setText(String.format("%.2f", settings.pitchTag.getFloat("end"))); + farPitchInput.setVisible(settings.type.hasSmoothing()); + farPitchText.setVisible(settings.type.hasSmoothing()); + + nearHeightOffsetSlider.setValue(settings.posOffsetTag.getFloat("placementOffset")); + nearHeightOffsetSlider.setText(GuiText.TRACK_NEAR_HEIGHT_OFFSET.toString(String.format("%.2f", settings.posOffsetTag.getFloat("placementOffset")))); + nearHeightOffsetSlider.setVisible(settings.type.hasSmoothing()); + + farHeightOffsetSlider.setValue(settings.posOffsetTag.getFloat("customOffset")); + farHeightOffsetSlider.setText(GuiText.TRACK_FAR_HEIGHT_OFFSET.toString(String.format("%.2f", settings.posOffsetTag.getFloat("customOffset")))); + farHeightOffsetSlider.setVisible(settings.type.hasSmoothing()); + + farRadiusInput.setText(""+settings.transitionCurvesTag.getInteger("farRadius")); + farRadiusInput.setVisible(settings.type.hasFarRadius()); + farRadiusText.setVisible(settings.type.hasFarRadius()); + + toggleStraightAtP1.setText(GuiText.TRACK_IS_FORWARD.toString(settings.transitionCurvesTag.getBoolean("isForward"))); + toggleStraightAtP1.setVisible(settings.type.hasFarRadius()); + + lengthInput.setText(""+settings.length); + + typeButton.setText(GuiText.SELECTOR_TYPE.toString(settings.type)); + + insertWayButton.setVisible(settings.type.isMulti()); + addWayButton.setVisible(settings.type.isMulti()); + delWayButton.setVisible(settings.type.isMulti()); + wayCircleButton.setVisible(settings.type.isMulti()); + showSubSelectorButton.setVisible(settings.type.isMulti()); + if (settings.type.isTable()) { int max = settings.type == TrackItems.TURNTABLE ? BuilderTurnTable.maxLength(settings.gauge) @@ -197,15 +468,176 @@ public void onClick(Player.Hand hand) { }; ytop += height; + nearHeightOffsetSlider = new Slider(screen, GUIHelpers.getScreenWidth() / 2 - width + 50, ytop + height, "", -0.5, 0.5, settings.posOffsetTag.getFloat("placementOffset"), true) { + @Override + public void onSlider() { + settings.posOffsetTag.setFloat("placementOffset",(float) this.getValue()); + nearHeightOffsetSlider.setText(GuiText.TRACK_NEAR_HEIGHT_OFFSET.toString(String.format("%.2f", settings.posOffsetTag.getFloat("placementOffset")))); + updateListSetting(mutable -> mutable.posOffsetTag.setFloat("placementOffset",(float) this.getValue())); + } + }; + nearHeightOffsetSlider.onSlider(); + farHeightOffsetSlider = new Slider(screen, GUIHelpers.getScreenWidth() / 2 - width + 50, ytop + height * 2, "", 0.0, 1.0, selectedWay==0 ? settings.posOffsetTag.getFloat("customOffset") : selectedWaySettings.posOffsetTag.getFloat("customOffset"), true) { + @Override + public void onSlider() { + if(Math.abs(farHeightOffsetSlider.getValue() - 1) < 1e-6) { + farHeightOffsetSlider.setValue(0.99); + } + + if(selectedWay == 0) { + settings.posOffsetTag.setFloat("customOffset",(float) this.getValue()); + farHeightOffsetSlider.setText(GuiText.TRACK_FAR_HEIGHT_OFFSET.toString(String.format("%.2f", settings.posOffsetTag.getFloat("customOffset")))); + }else { + selectedWaySettings.posOffsetTag.setFloat("customOffset",(float) this.getValue()); + farHeightOffsetSlider.setText(GuiText.TRACK_FAR_HEIGHT_OFFSET.toString(String.format("%.2f", selectedWaySettings.posOffsetTag.getFloat("customOffset")))); + syncMultiSwitchInfo(); + } + } + }; + farHeightOffsetSlider.onSlider(); + + subTypeSelector = new ListSelector(screen, width, 100, height, selectedWaySettings.type, + Arrays.stream(TrackItems.values()) + .filter(TrackItems::isSwitchWay) + .sorted(Comparator.comparingInt(TrackItems::getOrder)) + .collect(Collectors.toMap(TrackItems::toString, g -> g, (u, v) -> u, LinkedHashMap::new)) + ) {//TODO: add check for duplicated curves + @Override + public void onClick(TrackItems option) { + if(selectedWay != 0){ + selectedWaySettings.type = option; + selectedWaySettings.pickType = option; + syncMultiSwitchInfo(); + + if(selectedWaySettings.type == TrackItems.CUBICPARABOLA && selectedWaySettings.degrees > CubicCurve.cubicParabolaMaxAngle) { + selectedWaySettings.degrees = (float) CubicCurve.cubicParabolaMaxAngle; + degreesSlider.setValue(selectedWaySettings.degrees * Config.ConfigBalance.AnglePlacementSegmentation / 90); + }else { + degreesSlider.setValue(selectedWaySettings.degrees * Config.ConfigBalance.AnglePlacementSegmentation / 90); + } + degreesSlider.setText(GuiText.SELECTOR_QUARTERS.toString(degreesSlider.getValueInt() * (90.0 / Config.ConfigBalance.AnglePlacementSegmentation))); + degreesSlider.setVisible(selectedWaySettings.type.hasQuarters()); + + curvositySlider.setText(GuiText.SELECTOR_CURVOSITY.toString(String.format("%.2f", selectedWaySettings.curvosity))); + curvositySlider.setValue(selectedWaySettings.curvosity); + curvositySlider.setVisible(selectedWaySettings.type.hasCurvosity()); + + smoothingButton.setText(GuiText.SELECTOR_SMOOTHING.toString(selectedWaySettings.smoothing)); + smoothingButton.setVisible(selectedWaySettings.type.hasSmoothing()); + + directionButton.setText(GuiText.SELECTOR_DIRECTION.toString(selectedWaySettings.direction)); + directionButton.setVisible(selectedWaySettings.type.hasDirection()); + +// nearPitchSlider.setValue(selectedWaySettings.pitchTag.getFloat("start")); +// nearPitchSlider.setText("near pitch:"+String.format("%.2f", nearPitchSlider.getValue())); +// nearPitchSlider.setVisible(selectedWaySettings.type.hasSmoothing()); + +// farPitchSlider.setValue(selectedWaySettings.pitchTag.getFloat("end")); +// farPitchSlider.setText("far pitch:"+String.format("%.2f", farPitchSlider.getValue())); +// farPitchSlider.setVisible(selectedWaySettings.type.hasSmoothing()); + + nearPitchInput.setText(String.format("%.2f", selectedWaySettings.pitchTag.getFloat("start"))); + nearPitchInput.setVisible(selectedWaySettings.type.hasSmoothing()); + nearPitchText.setVisible(selectedWaySettings.type.hasSmoothing()); + + farPitchInput.setText(String.format("%.2f", selectedWaySettings.pitchTag.getFloat("end"))); + farPitchInput.setVisible(selectedWaySettings.type.hasSmoothing()); + farPitchText.setVisible(selectedWaySettings.type.hasSmoothing()); + + nearHeightOffsetSlider.setValue(selectedWaySettings.posOffsetTag.getFloat("placementOffset")); + nearHeightOffsetSlider.setText(GuiText.TRACK_NEAR_HEIGHT_OFFSET.toString(String.format("%.2f", selectedWaySettings.posOffsetTag.getFloat("placementOffset")))); + + farHeightOffsetSlider.setValue(selectedWaySettings.posOffsetTag.getFloat("customOffset")); + farHeightOffsetSlider.setText(GuiText.TRACK_FAR_HEIGHT_OFFSET.toString(String.format("%.2f", selectedWaySettings.posOffsetTag.getFloat("customOffset")))); + + farRadiusInput.setText(""+selectedWaySettings.transitionCurvesTag.getInteger("farRadius")); + farRadiusInput.setVisible(selectedWaySettings.type.hasFarRadius()); + farRadiusText.setVisible(selectedWaySettings.type.hasFarRadius()); + + toggleStraightAtP1.setText(GuiText.TRACK_IS_FORWARD.toString(selectedWaySettings.transitionCurvesTag.getBoolean("isForward"))); + toggleStraightAtP1.setVisible(selectedWaySettings.type.hasFarRadius()); + + lengthInput.setText("" + selectedWaySettings.length); + //typeButton text should not be changd + typeButton.setText(GuiText.SELECTOR_TYPE.toString(settings.type)); + showSubSelectorButton.setText(GuiText.SELECTOR_SUB_TYPE.toString(selectedWaySettings.type)); + }else { + multiSwitchInfo.realShapeType = option; + syncMultiSwitchInfo(); + + if(multiSwitchInfo.realShapeType == TrackItems.CUBICPARABOLA && settings.degrees > CubicCurve.cubicParabolaMaxAngle) { + settings.degrees = (float) CubicCurve.cubicParabolaMaxAngle; + degreesSlider.setValue(settings.degrees * Config.ConfigBalance.AnglePlacementSegmentation / 90); + }else { + degreesSlider.setValue(settings.degrees * Config.ConfigBalance.AnglePlacementSegmentation / 90); + } + degreesSlider.setText(GuiText.SELECTOR_QUARTERS.toString(degreesSlider.getValueInt() * (90.0/Config.ConfigBalance.AnglePlacementSegmentation))); + degreesSlider.setVisible(multiSwitchInfo.realShapeType.hasQuarters()); + + curvositySlider.setText(GuiText.SELECTOR_CURVOSITY.toString(String.format("%.2f", settings.curvosity))); + curvositySlider.setValue(settings.curvosity); + curvositySlider.setVisible(multiSwitchInfo.realShapeType.hasCurvosity()); + + smoothingButton.setText(GuiText.SELECTOR_SMOOTHING.toString(settings.smoothing)); + smoothingButton.setVisible(multiSwitchInfo.realShapeType.hasSmoothing()); + + directionButton.setText(GuiText.SELECTOR_DIRECTION.toString(settings.direction)); + directionButton.setVisible(multiSwitchInfo.realShapeType.hasDirection()); + +// nearPitchSlider.setValue(settings.pitchTag.getFloat("start")); +// nearPitchSlider.setText("near pitch:"+String.format("%.2f", nearPitchSlider.getValue())); +// nearPitchSlider.setVisible(settings.type.hasSmoothing()); + +// farPitchSlider.setValue(settings.pitchTag.getFloat("end")); +// farPitchSlider.setText("far pitch:"+String.format("%.2f", farPitchSlider.getValue())); +// farPitchSlider.setVisible(settings.type.hasSmoothing()); + + nearPitchInput.setText(String.format("%.2f", settings.pitchTag.getFloat("start"))); + nearPitchInput.setVisible(settings.type.hasSmoothing()); + nearPitchText.setVisible(settings.type.hasSmoothing()); + + farPitchInput.setText(String.format("%.2f", settings.pitchTag.getFloat("end"))); + farPitchInput.setVisible(settings.type.hasSmoothing()); + farPitchText.setVisible(settings.type.hasSmoothing()); + + nearHeightOffsetSlider.setValue(settings.posOffsetTag.getFloat("placementOffset")); + nearHeightOffsetSlider.setText(GuiText.TRACK_NEAR_HEIGHT_OFFSET.toString(String.format("%.2f", settings.posOffsetTag.getFloat("placementOffset")))); + + farHeightOffsetSlider.setValue(settings.posOffsetTag.getFloat("customOffset")); + farHeightOffsetSlider.setText(GuiText.TRACK_FAR_HEIGHT_OFFSET.toString(String.format("%.2f", settings.posOffsetTag.getFloat("customOffset")))); + + farRadiusInput.setText(""+settings.transitionCurvesTag.getInteger("farRadius")); + farRadiusInput.setVisible(multiSwitchInfo.realShapeType.hasFarRadius()); + farRadiusText.setVisible(multiSwitchInfo.realShapeType.hasFarRadius()); + + toggleStraightAtP1.setText(GuiText.TRACK_IS_FORWARD.toString(settings.transitionCurvesTag.getBoolean("isForward"))); + toggleStraightAtP1.setVisible(settings.type.hasFarRadius()); + + lengthInput.setText(""+settings.length); + //typeButton text should not be changd + typeButton.setText(GuiText.SELECTOR_TYPE.toString(settings.type)); + showSubSelectorButton.setText(GuiText.SELECTOR_SUB_TYPE.toString(multiSwitchInfo.realShapeType)); + } + transfertableEntryCountSlider.setVisible(false); + transfertableEntrySpacingSlider.setVisible(false); + } + }; + //Transfer table doesn't have these property so we can have them overlapped - smoothingButton = new Button(screen, xtop, ytop, width, height, GuiText.SELECTOR_SMOOTHING.toString(settings.smoothing)) { + smoothingButton = new Button(screen, xtop, ytop, width, height, GuiText.SELECTOR_SMOOTHING.toString(selectedWay==0?settings.smoothing:selectedWaySettings.smoothing)) { @Override public void onClick(Player.Hand hand) { - settings.smoothing = next(settings.smoothing, hand); - smoothingButton.setText(GuiText.SELECTOR_SMOOTHING.toString(settings.smoothing)); + if(selectedWay == 0) { + settings.smoothing = next(settings.smoothing, hand); + smoothingButton.setText(GuiText.SELECTOR_SMOOTHING.toString(settings.smoothing)); + } else{ + selectedWaySettings.smoothing = next(selectedWaySettings.smoothing, hand); + smoothingButton.setText(GuiText.SELECTOR_SMOOTHING.toString(selectedWaySettings.smoothing)); + syncMultiSwitchInfo(); + } } }; - smoothingButton.setVisible(settings.type.hasSmoothing()); + smoothingButton.setVisible(selectedWay == 0 ? settings.type.hasSmoothing() : selectedWaySettings.type.hasSmoothing()); transfertableEntryCountSlider = new Slider(screen, 25+xtop, ytop, "", 1, 71, settings.transfertableEntryCount, false) { @Override @@ -218,14 +650,20 @@ public void onSlider() { transfertableEntryCountSlider.onSlider(); ytop += height; - directionButton = new Button(screen, xtop, ytop, width, height, GuiText.SELECTOR_DIRECTION.toString(settings.direction)) { + directionButton = new Button(screen, xtop, ytop, width, height, GuiText.SELECTOR_DIRECTION.toString(selectedWay == 0 ? settings.direction:selectedWaySettings.direction)) { @Override public void onClick(Player.Hand hand) { - settings.direction = next(settings.direction, hand); - directionButton.setText(GuiText.SELECTOR_DIRECTION.toString(settings.direction)); + if(selectedWay == 0) { + settings.direction = next(settings.direction, hand); + directionButton.setText(GuiText.SELECTOR_DIRECTION.toString(settings.direction)); + }else { + selectedWaySettings.direction = next(selectedWaySettings.direction, hand); + directionButton.setText(GuiText.SELECTOR_DIRECTION.toString(selectedWaySettings.direction)); + syncMultiSwitchInfo(); + } } }; - directionButton.setVisible(settings.type.hasDirection()); + directionButton.setVisible(selectedWay == 0 ? settings.type.hasDirection() : selectedWaySettings.type.hasDirection()); transfertableEntrySpacingSlider = new Slider(screen, 25+xtop, ytop, "", 1, 15, settings.transfertableEntrySpacing, false) { @Override @@ -239,35 +677,114 @@ public void onSlider() { ytop += height; - this.degreesSlider = new Slider(screen, 25+xtop, ytop, "", 1, Config.ConfigBalance.AnglePlacementSegmentation, settings.degrees / 90 * Config.ConfigBalance.AnglePlacementSegmentation, false) { + this.degreesSlider = new Slider(screen, 25+xtop, ytop, "", 1, Config.ConfigBalance.AnglePlacementSegmentation, selectedWay==0?(settings.degrees * Config.ConfigBalance.AnglePlacementSegmentation / 90):(selectedWaySettings.degrees / 90 * Config.ConfigBalance.AnglePlacementSegmentation), false) { @Override public void onSlider() { - settings.degrees = degreesSlider.getValueInt() * (90F/Config.ConfigBalance.AnglePlacementSegmentation); + float degreeValue = degreesSlider.getValueInt() * (90F/Config.ConfigBalance.AnglePlacementSegmentation); + if(selectedWay==0) { + if(settings.type == TrackItems.CUBICPARABOLA){ + while (degreeValue >= CubicCurve.cubicParabolaMaxAngle || !CubicCurve.isCubicParabolaDeltaValid(settings.length,settings.degrees,settings.transitionCurvesTag.getInteger("farRadius"))){ + degreeValue -= 90F / Config.ConfigBalance.AnglePlacementSegmentation; + if(Math.abs(degreeValue) < 1e-6) break; + } + } + settings.degrees = degreeValue; + }else { + if(selectedWaySettings.type == TrackItems.CUBICPARABOLA){ + while (degreeValue >= CubicCurve.cubicParabolaMaxAngle || !CubicCurve.isCubicParabolaDeltaValid(selectedWaySettings.length,selectedWaySettings.degrees,selectedWaySettings.transitionCurvesTag.getInteger("farRadius"))){ + degreeValue -= 90F / Config.ConfigBalance.AnglePlacementSegmentation; + if(Math.abs(degreeValue) < 1e-6) break; + } + } + selectedWaySettings.degrees = degreeValue; + syncMultiSwitchInfo(); + } degreesSlider.setText(GuiText.SELECTOR_QUARTERS.toString(this.getValueInt() * (90.0/Config.ConfigBalance.AnglePlacementSegmentation))); } }; degreesSlider.onSlider(); ytop += height; + farRadiusText = new Button(screen, xtop, ytop, 80, height, GuiText.TRACK_FAR_RADIUS.toString()); + farRadiusInput = new TextField(screen, xtop + 80, ytop, width-81, height); + farRadiusInput.setText("" + (selectedWay==0 ? settings.transitionCurvesTag.getInteger("farRadius") : selectedWaySettings.transitionCurvesTag.getInteger("farRadius"))); + farRadiusInput.setValidator(s -> { + if (s == null || s.length() == 0) { + return true; + } + int val; + try { + val = Integer.parseInt(s); + } catch (NumberFormatException e) { + if(s.equals("-"))return true; + return false; + } + int max = 0x3f3f3f3f; + if(selectedWay == 0) { + if (val >= -1 && val != 0 && val <= max && CubicCurve.isCubicParabolaDeltaValid(settings.length,settings.degrees,val)) { + settings.transitionCurvesTag.setInteger("farRadius", val); + return true; + } + }else { + if (val >= -1 && val != 0 && val <= max && CubicCurve.isCubicParabolaDeltaValid(selectedWaySettings.length,selectedWaySettings.degrees,val)) { + selectedWaySettings.transitionCurvesTag.setInteger("farRadius", val); + syncMultiSwitchInfo(); + return true; + } + } - this.curvositySlider = new Slider(screen, 25+xtop, ytop, "", 0.25, 1.5, settings.curvosity, true) { + return false; + }); + farRadiusInput.setFocused(true); + farRadiusInput.setVisible(selectedWay==0 ? (settings.type == TrackItems.CUBICPARABOLA) : (selectedWaySettings.type == TrackItems.CUBICPARABOLA)); + farRadiusText.setVisible(selectedWay==0 ? (settings.type == TrackItems.CUBICPARABOLA) : (selectedWaySettings.type == TrackItems.CUBICPARABOLA)); + + this.curvositySlider = new Slider(screen, 25+xtop, ytop, "", 0.25, 1.5, selectedWay == 0 ? settings.curvosity:selectedWaySettings.curvosity, true) { @Override public void onSlider() { - settings.curvosity = (float) this.getValue(); - curvositySlider.setText(GuiText.SELECTOR_CURVOSITY.toString(String.format("%.2f", settings.curvosity))); + if(selectedWay == 0) { + settings.curvosity = (float) this.getValue(); + curvositySlider.setText(GuiText.SELECTOR_CURVOSITY.toString(String.format("%.2f", settings.curvosity))); + } else{ + selectedWaySettings.curvosity = (float) this.getValue(); + syncMultiSwitchInfo(); + curvositySlider.setText(GuiText.SELECTOR_CURVOSITY.toString(String.format("%.2f", selectedWaySettings.curvosity))); + } } }; curvositySlider.onSlider(); ytop += height; - directionButton.setVisible(settings.type.hasDirection()); - degreesSlider.setVisible(settings.type.hasQuarters()); - curvositySlider.setVisible(settings.type.hasCurvosity()); - smoothingButton.setVisible(settings.type.hasSmoothing()); - transfertableEntryCountSlider.setVisible(settings.type == TrackItems.TRANSFERTABLE); - transfertableEntrySpacingSlider.setVisible(settings.type == TrackItems.TRANSFERTABLE); - + String toggleStraightAtP1Text; + if(selectedWay==0){ + toggleStraightAtP1Text = GuiText.TRACK_IS_FORWARD.toString(settings.transitionCurvesTag.getBoolean("isForward")); + }else { + toggleStraightAtP1Text = GuiText.TRACK_IS_FORWARD.toString(selectedWaySettings.transitionCurvesTag.getBoolean("isForward")); + } + this.toggleStraightAtP1 = new Button(screen, xtop, ytop, width, height, toggleStraightAtP1Text) { + @Override + public void onClick(Player.Hand hand) { + if(selectedWay == 0){ + boolean wasForward = settings.transitionCurvesTag.getBoolean("isForward"); + settings.transitionCurvesTag.setBoolean("isForward", !wasForward); + toggleStraightAtP1.setText(GuiText.TRACK_IS_FORWARD.toString(settings.transitionCurvesTag.getBoolean("isForward"))); + }else { + boolean wasForward = selectedWaySettings.transitionCurvesTag.getBoolean("isForward"); + selectedWaySettings.transitionCurvesTag.setBoolean("isForward", !wasForward); + toggleStraightAtP1.setText(GuiText.TRACK_IS_FORWARD.toString(selectedWaySettings.transitionCurvesTag.getBoolean("isForward"))); + syncMultiSwitchInfo(); + } + } + }; + toggleStraightAtP1.setVisible(selectedWay == 0 ? (settings.type == TrackItems.CUBICPARABOLA) : (selectedWaySettings.type == TrackItems.CUBICPARABOLA)); + ytop += height; + directionButton.setVisible(selectedWay == 0 ? settings.type.hasDirection() : selectedWaySettings.type.hasDirection()); + degreesSlider.setVisible(selectedWay == 0 ? settings.type.hasQuarters() : selectedWaySettings.type.hasQuarters()); + curvositySlider.setVisible(selectedWay == 0 ? settings.type.hasCurvosity() : selectedWaySettings.type.hasCurvosity()); + smoothingButton.setVisible(selectedWay == 0 ? settings.type.hasSmoothing() : selectedWaySettings.type.hasSmoothing()); + transfertableEntryCountSlider.setVisible(settings.type == TrackItems.TRANSFERTABLE && selectedWay == 0); + transfertableEntrySpacingSlider.setVisible(settings.type == TrackItems.TRANSFERTABLE && selectedWay == 0); // Bottom Pane //width = 200; @@ -282,6 +799,7 @@ public void onSlider() { @Override public void onClick(TrackDefinition track) { settings.track = track.trackID; + updateListSetting(mutable -> mutable.track = track.trackID); trackButton.setText(GuiText.SELECTOR_TRACK.toString(fitString(DefinitionManager.getTrack(settings.track).name, 24))); } }; @@ -299,6 +817,7 @@ public void onClick(Player.Hand hand) { @Override public void onClick(ItemStack option) { settings.railBed = option; + updateListSetting(mutable -> mutable.railBed = option); bedTypeButton.setText(GuiText.SELECTOR_RAIL_BED.toString(getStackName(settings.railBed))); } }; @@ -316,6 +835,7 @@ public void onClick(Player.Hand hand) { @Override public void onClick(ItemStack option) { settings.railBedFill = option; + updateListSetting(mutable -> mutable.railBedFill = option); bedFillButton.setText(GuiText.SELECTOR_RAIL_BED_FILL.toString(getStackName(settings.railBedFill))); } }; @@ -331,6 +851,7 @@ public void onClick(Player.Hand hand) { @Override public void onClick(Player.Hand hand) { settings.posType = next(settings.posType, hand); + updateListSetting(mutable -> mutable.posType = settings.posType); posTypeButton.setText(GuiText.SELECTOR_POSITION.toString(settings.posType)); } }; @@ -340,6 +861,7 @@ public void onClick(Player.Hand hand) { @Override public void onClick(Player.Hand hand) { settings.isPreview = isPreviewCB.isChecked(); + updateListSetting(mutable -> mutable.isPreview = settings.isPreview); } }; // ytop += height; @@ -348,6 +870,7 @@ public void onClick(Player.Hand hand) { @Override public void onClick(Player.Hand hand) { settings.isGradeCrossing = isGradeCrossingCB.isChecked(); + updateListSetting(mutable -> mutable.isGradeCrossing = settings.isGradeCrossing); } }; ytop += height; @@ -366,6 +889,7 @@ private void showSelector(ListSelector selector) { gaugeSelector.setVisible(false); typeSelector.setVisible(false); + subTypeSelector.setVisible(false); trackSelector.setVisible(false); railBedSelector.setVisible(false); railBedFillSelector.setVisible(false); @@ -373,6 +897,28 @@ private void showSelector(ListSelector selector) { selector.setVisible(!isVisible); } + private void updateSelectedWay(int i) {//haven't checked boundary here + selectedWay = i; + selectedWaySettings = selectedWay == 0 ? settings : multiSwitchInfo.wayList.get(selectedWay - 1).settings.mutable(); + if(wayCircleButton != null)wayCircleButton.setText(GuiText.TRACK_SELECTED_WAY.toString(selectedWay)); + } + + private void syncMultiSwitchInfo() { + if(selectedWay > 0){ + SingleWayInfo update = multiSwitchInfo.wayList.get(selectedWay - 1).with(mutable -> mutable.settings = selectedWaySettings.immutable()); + multiSwitchInfo.wayList.set(selectedWay - 1, update); + } + } + + private void updateListSetting(Consumer mod) { + for (int i = 0; i < multiSwitchInfo.wayList.size(); i++) { + SingleWayInfo updated = multiSwitchInfo.wayList.get(i); + RailSettings t = updated.settings.with(mod); + updated = updated.with(m -> m.settings = t); + multiSwitchInfo.wayList.set(i, updated); + } + } + @Override public void onEnterKey(IScreenBuilder builder) { builder.close(); @@ -382,9 +928,9 @@ public void onEnterKey(IScreenBuilder builder) { public void onClose() { if (!this.lengthInput.getText().isEmpty()) { if (this.te != null) { - new ItemRailUpdatePacket(te.getPos(), settings.immutable()).sendToServer(); + new ItemRailUpdatePacket(te.getPos(), settings.immutable(), multiSwitchInfo.immutable(), selectedWay).sendToServer(); } else { - new ItemRailUpdatePacket(settings.immutable()).sendToServer(); + new ItemRailUpdatePacket(settings.immutable(), multiSwitchInfo.immutable(), selectedWay).sendToServer(); } } } @@ -406,7 +952,7 @@ public void draw(IScreenBuilder builder, RenderState state) { rendered.type = TrackItems.STRAIGHT; }), new PlacementInfo(new Vec3d(0.5, 0, 0.5), TrackDirection.NONE, 0, null), - null, SwitchState.NONE, SwitchState.NONE, 0, true); + null, null, SwitchState.NONE, SwitchState.NONE, 0, true); double scale = GUIHelpers.getScreenWidth() / 12.0 * zoom; @@ -445,7 +991,7 @@ public void draw(IScreenBuilder builder, RenderState state) { rendered.type = TrackItems.STRAIGHT; }), new PlacementInfo(new Vec3d(0.5, 0, 0.5), TrackDirection.NONE, 0, null), - null, SwitchState.NONE, SwitchState.NONE, 0, true); + null, null, SwitchState.NONE, SwitchState.NONE, 0, true); double scale = GUIHelpers.getScreenWidth() / 15.0 * zoom; @@ -488,6 +1034,7 @@ public void draw(IScreenBuilder builder, RenderState state) { : settings.type == TrackItems.TRANSFERTABLE ? (frame / 50.0) % (settings.transfertableEntrySpacing * (settings.transfertableEntryCount - 1)) : 0; + PlacementInfo placementInfo = new PlacementInfo(new Vec3d(0.5, 0, 0.5), settings.direction, 0, null); RailInfo info = new RailInfo( settings.immutable().with(b -> { int length = b.length; @@ -499,10 +1046,14 @@ public void draw(IScreenBuilder builder, RenderState state) { } b.length = length; }), - new PlacementInfo(new Vec3d(0.5, 0, 0.5), settings.direction, 0, null), - null, SwitchState.NONE, SwitchState.NONE, tablePos, true); + placementInfo, + null, multiSwitchInfo.immutable(), SwitchState.NONE, SwitchState.NONE, tablePos, true);//why int length = info.settings.length; + + multiSwitchInfo.writePlacement(placementInfo);//this is safe as placementInfo will be recalculated when placing or rendering + info = info.with(mutable -> mutable.multiSwitchInfo = multiSwitchInfo.immutable()); + double scale = (GUIHelpers.getScreenWidth() / (length * 2.25)) * zoom; if (settings.type.isTable()) { scale /= 2; diff --git a/src/main/java/cam72cam/immersiverailroading/items/ItemTrackBlueprint.java b/src/main/java/cam72cam/immersiverailroading/items/ItemTrackBlueprint.java index b01611ba2..f27c61ae8 100644 --- a/src/main/java/cam72cam/immersiverailroading/items/ItemTrackBlueprint.java +++ b/src/main/java/cam72cam/immersiverailroading/items/ItemTrackBlueprint.java @@ -9,10 +9,7 @@ import cam72cam.immersiverailroading.registry.TrackDefinition; import cam72cam.immersiverailroading.tile.TileRailBase; import cam72cam.immersiverailroading.tile.TileRailPreview; -import cam72cam.immersiverailroading.util.BlockUtil; -import cam72cam.immersiverailroading.util.IRFuzzy; -import cam72cam.immersiverailroading.util.PlacementInfo; -import cam72cam.immersiverailroading.util.RailInfo; +import cam72cam.immersiverailroading.util.*; import cam72cam.mod.entity.Player; import cam72cam.mod.item.*; import cam72cam.mod.math.Vec3d; @@ -56,6 +53,7 @@ public void onClickAir(Player player, World world, Player.Hand hand) { public ClickResult onClickBlock(Player player, World world, Vec3i pos, Player.Hand hand, Facing facing, Vec3d hit) { ItemStack stack = player.getHeldItem(hand); RailSettings stackInfo = RailSettings.from(stack); + MultiSwitchInfo.Mutable multiSwitchInfo = MultiSwitchInfo.from(stack).mutable(); if (world.isServer && hand == Player.Hand.SECONDARY) { ItemStack blockinfo = world.getItemStack(pos); @@ -65,6 +63,7 @@ public ClickResult onClickBlock(Player player, World world, Vec3i pos, Player.Ha stackInfo = stackInfo.with(b -> b.railBed = blockinfo); } stackInfo.write(stack); + multiSwitchInfo.immutable().write(stack); return ClickResult.ACCEPTED; } @@ -89,8 +88,11 @@ public ClickResult onClickBlock(Player player, World world, Vec3i pos, Player.Ha return ClickResult.ACCEPTED; } + PlacementInfo placementInfo = new PlacementInfo(stack, player.getYawHead(), hit.subtract(0, hit.y, 0)); - RailInfo info = new RailInfo(stack, placementInfo, null); + multiSwitchInfo.writePlacement(placementInfo); + RailInfo info = new RailInfo(stack, placementInfo, null, multiSwitchInfo.immutable()); + info.build(player, pos); return ClickResult.ACCEPTED; } diff --git a/src/main/java/cam72cam/immersiverailroading/items/nbt/RailSettings.java b/src/main/java/cam72cam/immersiverailroading/items/nbt/RailSettings.java index b4aeecfdc..8f0202fa3 100644 --- a/src/main/java/cam72cam/immersiverailroading/items/nbt/RailSettings.java +++ b/src/main/java/cam72cam/immersiverailroading/items/nbt/RailSettings.java @@ -11,9 +11,16 @@ public class RailSettings { public final Gauge gauge; public final TrackItems type; + public final TrackItems pickType;//pickType stores origin type in subCurves public final int length; public final float degrees; public final float curvosity; + public final float pitchStart; + public final float pitchEnd; + public final float placementOffset; + public final float customOffset; + public final boolean isForward; + public final int farRadius; public final TrackPositionType posType; public final TrackSmoothing smoothing; public final TrackDirection direction; @@ -25,10 +32,11 @@ public class RailSettings { public final int transfertableEntryCount; public final int transfertableEntrySpacing; - public RailSettings(Gauge gauge, String track, TrackItems type, int length, float degrees, float curvosity, TrackPositionType posType, TrackSmoothing smoothing, TrackDirection direction, ItemStack railBed, ItemStack railBedFill, boolean isPreview, boolean isGradeCrossing, int count, int spacing) { + public RailSettings(Gauge gauge, String track, TrackItems type, TrackItems pickType, int length, float degrees, float curvosity, TrackPositionType posType, TrackSmoothing smoothing, float pitchStart, float pitchEnd, float placementOffset, float customOffset, boolean isForward, int farRadius, TrackDirection direction, ItemStack railBed, ItemStack railBedFill, boolean isPreview, boolean isGradeCrossing, int count, int spacing) { this.gauge = gauge; this.track = track; this.type = type; + this.pickType = pickType; this.length = length; this.degrees = degrees; this.posType = posType; @@ -41,21 +49,44 @@ public RailSettings(Gauge gauge, String track, TrackItems type, int length, floa this.curvosity = curvosity; this.transfertableEntryCount = count; this.transfertableEntrySpacing = spacing; + this.pitchStart = pitchStart; + this.pitchEnd = pitchEnd; + this.placementOffset = placementOffset; + this.customOffset = customOffset; + this.isForward = isForward; + this.farRadius = farRadius; } - public void write(ItemStack stack) { + TagCompound root = stack.getTagCompound(); + if (root == null) { + root = new TagCompound(); + } + TagCompound data = new TagCompound(); try { TagSerializer.serialize(data, mutable()); } catch (SerializationException e) { ImmersiveRailroading.catching(e); } - stack.setTagCompound(data); + + root.set("settings", data); + stack.setTagCompound(root); } public static RailSettings from(ItemStack stack) { + TagCompound root = stack.getTagCompound(); + if (root == null || !root.hasKey("settings")) { + //legacy data + try { + return new Mutable(stack.getTagCompound()).immutable(); + } catch (SerializationException e) { + throw new RuntimeException(e); + } + } + try { - return new Mutable(stack.getTagCompound()).immutable(); + TagCompound data = root.get("settings"); + return new Mutable(data).immutable(); } catch (SerializationException e) { throw new RuntimeException(e); } @@ -113,6 +144,8 @@ public static class Mutable { public Gauge gauge; @TagField("type") public TrackItems type; + @TagField("pickType") + public TrackItems pickType; @TagField("length") public int length; @TagField(value = "degrees", mapper = DegreesMapper.class) @@ -135,7 +168,12 @@ public static class Mutable { public boolean isGradeCrossing; @TagField("track") public String track; - + @TagField(value = "pitch") + public TagCompound pitchTag; + @TagField(value = "posOffset") + public TagCompound posOffsetTag; + @TagField(value = "transitionCurves") + public TagCompound transitionCurvesTag; @TagField("transfertableEntryCount") public int transfertableEntryCount; @TagField("transfertableEntrySpacing") @@ -144,7 +182,21 @@ public static class Mutable { private Mutable(RailSettings settings) { this.gauge = settings.gauge; this.track = settings.track; + + pitchTag = new TagCompound(); + pitchTag.setFloat("start", settings.pitchStart); + pitchTag.setFloat("end", settings.pitchEnd); + + posOffsetTag = new TagCompound(); + posOffsetTag.setFloat("placementOffset", settings.placementOffset); + posOffsetTag.setFloat("customOffset", settings.customOffset); + + transitionCurvesTag = new TagCompound(); + transitionCurvesTag.setBoolean("isForward", settings.isForward); + transitionCurvesTag.setInteger("farRadius", settings.farRadius); + this.type = settings.type; + this.pickType = settings.pickType; this.length = settings.length; this.degrees = settings.degrees; this.curvosity = settings.curvosity; @@ -163,7 +215,21 @@ private Mutable(TagCompound data) throws SerializationException { // Defaults gauge = Gauge.from(Gauge.STANDARD); type = TrackItems.STRAIGHT; + pickType = type; track = "default"; + + pitchTag = new TagCompound(); + pitchTag.setFloat("start", 0.0f); + pitchTag.setFloat("end", 0.0f); + + posOffsetTag = new TagCompound(); + posOffsetTag.setFloat("placementOffset", 0.0f); + posOffsetTag.setFloat("customOffset", 0.0f); + + transitionCurvesTag = new TagCompound(); + transitionCurvesTag.setBoolean("isForward", true); + transitionCurvesTag.setInteger("farRadius", -1); + length = 10; degrees = 90; posType = TrackPositionType.FIXED; @@ -185,11 +251,18 @@ public RailSettings immutable() { gauge, track, type, + pickType, length, degrees, curvosity, posType, smoothing, + pitchTag.getFloat("start"), + pitchTag.getFloat("end"), + posOffsetTag.getFloat("placementOffset"), + posOffsetTag.getFloat("customOffset"), + transitionCurvesTag.getBoolean("isForward"), + transitionCurvesTag.getInteger("farRadius"), direction, railBed, railBedFill, diff --git a/src/main/java/cam72cam/immersiverailroading/library/GuiText.java b/src/main/java/cam72cam/immersiverailroading/library/GuiText.java index 7ca93d7b5..5cc55b018 100644 --- a/src/main/java/cam72cam/immersiverailroading/library/GuiText.java +++ b/src/main/java/cam72cam/immersiverailroading/library/GuiText.java @@ -22,6 +22,7 @@ public enum GuiText { SELECTOR_TRACK("selector.track"), SELECTOR_TRANSFER_TABLE_ENTRY_COUNT("selector.transfer_table_entry_count"), SELECTOR_TRANSFER_TABLE_ENTRY_SPACING("selector.transfer_table_entry_spacing"), + SELECTOR_SUB_TYPE("selector.sub_type"), SELECTOR_AUGMENT_DETECT("selector.augment.detect"), SELECTOR_AUGMENT_REDSTONE("selector.augment.redstone"), SELECTOR_AUGMENT_PUSHPULL("selector.augment.pushpull"), @@ -45,8 +46,18 @@ public enum GuiText { TRACK_POSITION("track.position"), TRACK_SMOOTHING("track.smoothing"), TRACK_DIRECTION("track.direction"), + TRACK_FAR_RADIUS("track.far_radius"), + TRACK_IS_FORWARD("track.is_forward"), + TRACK_NEAR_HEIGHT_OFFSET("track.near_height_offset"), + TRACK_FAR_HEIGHT_OFFSET("track.far_height_offset"), + TRACK_NEAR_PITCH("track.near_pitch"), + TRACK_FAR_PITCH("track.far_pitch"), TRACK_PLACE_BLUEPRINT_TRUE("track.place_blueprint_true"), TRACK_PLACE_BLUEPRINT_FALSE("track.place_blueprint_false"), + TRACK_SELECTED_WAY("track.selected_way"), + TRACK_ADD_WAY("track.add_way"), + TRACK_DELETE_WAY("track.delete_way"), + TRACK_INSERT_WAY("track.insert_way"), LOCO_WORKS("loco.works"), // LOCO_HORSE_POWER("loco.horse_power"), diff --git a/src/main/java/cam72cam/immersiverailroading/library/SwitchState.java b/src/main/java/cam72cam/immersiverailroading/library/SwitchState.java index 55083a8c8..e7095a801 100644 --- a/src/main/java/cam72cam/immersiverailroading/library/SwitchState.java +++ b/src/main/java/cam72cam/immersiverailroading/library/SwitchState.java @@ -7,7 +7,8 @@ public enum SwitchState { NONE, STRAIGHT, - TURN; + TURN, + MID1,MID2,MID3,MID4;//multi-way(max=6) turnout:from left to right:straight->mid->turn? @Override public String toString() { diff --git a/src/main/java/cam72cam/immersiverailroading/library/TrackItems.java b/src/main/java/cam72cam/immersiverailroading/library/TrackItems.java index 1abdc4720..898924380 100644 --- a/src/main/java/cam72cam/immersiverailroading/library/TrackItems.java +++ b/src/main/java/cam72cam/immersiverailroading/library/TrackItems.java @@ -12,7 +12,9 @@ public enum TrackItems { SWITCH(4), TURNTABLE(5), CUSTOM(7), - TRANSFERTABLE(6); + TRANSFERTABLE(6), + CUBICPARABOLA(8), + MULTISWITCH(9); private final int order; @@ -29,6 +31,8 @@ public boolean hasQuarters() { switch (this) { case TURN: case SWITCH: + case CUBICPARABOLA: + case MULTISWITCH: return true; default: return false; @@ -39,6 +43,7 @@ public boolean hasCurvosity() { switch (this) { case SWITCH: case CUSTOM: + case MULTISWITCH: return true; default: return false; @@ -49,7 +54,9 @@ public boolean hasSmoothing() { switch (this) { case SLOPE: case TURN: + case CUBICPARABOLA: case SWITCH: + case MULTISWITCH: case CUSTOM: return true; default: @@ -60,7 +67,9 @@ public boolean hasSmoothing() { public boolean hasDirection() { switch (this) { case TURN: + case CUBICPARABOLA: case SWITCH: + case MULTISWITCH: return true; default: return false; @@ -77,6 +86,34 @@ public boolean isTable() { } } + public boolean isMulti() { + switch (this) { + case MULTISWITCH: + return true; + default: + return false; + } + } + + public boolean hasFarRadius() { + switch (this) { + case CUBICPARABOLA: + return true; + default: + return false; + } + } + public boolean isSwitchWay() { + switch (this) { + case CUBICPARABOLA: + case CUSTOM: + case TURN: + return true; + default: + return false; + } + } + public int getOrder() { return this.order; } diff --git a/src/main/java/cam72cam/immersiverailroading/library/TrackPositionType.java b/src/main/java/cam72cam/immersiverailroading/library/TrackPositionType.java index 8f1f5353b..163d6f1ed 100644 --- a/src/main/java/cam72cam/immersiverailroading/library/TrackPositionType.java +++ b/src/main/java/cam72cam/immersiverailroading/library/TrackPositionType.java @@ -11,6 +11,18 @@ public enum TrackPositionType { SMOOTH, SMOOTH_LOCKED, ; + //TODO: + // new value: + // snapping_direction(which allows going around the nearest track path), + // snapping_pos(which locks xyz of nearest point), + // snapping_all(lock pos and direction to nearest point) + // and for pixel we can have an config option to make (or store it in item and config in gui would be better): + // pixels2,pixels4,pixels8,pixels32,pixels64, ...like LittleTiles + // TrackGui:add a button besides posType,to determine pixel level/snapping type mentioned above + // others: + // make a gui to display pos and yaw when holding itemTrackBlueprint + // for superelevation, first we need a method fits segmentation, and we need a option to determine whether to offset height with superelevation + // do we need to implement free interpolation? id so what method should we use? bezier? @Override public String toString() { diff --git a/src/main/java/cam72cam/immersiverailroading/library/TrackSmoothing.java b/src/main/java/cam72cam/immersiverailroading/library/TrackSmoothing.java index 8c4feb091..c02bf89fa 100644 --- a/src/main/java/cam72cam/immersiverailroading/library/TrackSmoothing.java +++ b/src/main/java/cam72cam/immersiverailroading/library/TrackSmoothing.java @@ -8,7 +8,8 @@ public enum TrackSmoothing { BOTH, NEAR, FAR, - NEITHER; + NEITHER, + PITCH_LOCKED_CUBIC; @Override public String toString() { diff --git a/src/main/java/cam72cam/immersiverailroading/net/ItemRailUpdatePacket.java b/src/main/java/cam72cam/immersiverailroading/net/ItemRailUpdatePacket.java index 97546d441..13fd5b746 100644 --- a/src/main/java/cam72cam/immersiverailroading/net/ItemRailUpdatePacket.java +++ b/src/main/java/cam72cam/immersiverailroading/net/ItemRailUpdatePacket.java @@ -2,6 +2,7 @@ import cam72cam.immersiverailroading.items.nbt.RailSettings; import cam72cam.immersiverailroading.tile.TileRailPreview; +import cam72cam.immersiverailroading.util.MultiSwitchInfo; import cam72cam.mod.entity.Player; import cam72cam.mod.item.ItemStack; import cam72cam.mod.math.Vec3i; @@ -12,17 +13,25 @@ public class ItemRailUpdatePacket extends Packet { @TagField private RailSettings settings; @TagField + private MultiSwitchInfo multiSwitchInfo;//extra data for settings + @TagField private Vec3i pos; + @TagField + private int selectedOrder; public ItemRailUpdatePacket() { } - public ItemRailUpdatePacket(RailSettings settings) { + public ItemRailUpdatePacket(RailSettings settings, MultiSwitchInfo multiSwitchInfo, int selectedOrder) { this.settings = settings; + this.multiSwitchInfo = multiSwitchInfo; + this.selectedOrder = selectedOrder; } - public ItemRailUpdatePacket(Vec3i tilePreviewPos, RailSettings settings) { + public ItemRailUpdatePacket(Vec3i tilePreviewPos, RailSettings settings, MultiSwitchInfo multiSwitchInfo, int selectedOrder) { this.pos = tilePreviewPos; this.settings = settings; + this.multiSwitchInfo = multiSwitchInfo; + this.selectedOrder = selectedOrder; } @Override @@ -31,13 +40,21 @@ public void handle() { TileRailPreview tile = this.getWorld().getBlockEntity(pos, TileRailPreview.class); if (tile != null) { ItemStack item = tile.getItem(); + settings.write(item); + multiSwitchInfo.write(item); + MultiSwitchInfo.writeSelected(item,selectedOrder); + tile.setItem(item, getPlayer()); } } else { Player player = this.getPlayer(); ItemStack stack = player.getHeldItem(Player.Hand.PRIMARY); + settings.write(stack); + multiSwitchInfo.write(stack); + MultiSwitchInfo.writeSelected(stack,selectedOrder); + player.setHeldItem(Player.Hand.PRIMARY, stack); } } diff --git a/src/main/java/cam72cam/immersiverailroading/render/block/RailBaseModel.java b/src/main/java/cam72cam/immersiverailroading/render/block/RailBaseModel.java index e38b0d36b..625c796ee 100644 --- a/src/main/java/cam72cam/immersiverailroading/render/block/RailBaseModel.java +++ b/src/main/java/cam72cam/immersiverailroading/render/block/RailBaseModel.java @@ -37,6 +37,10 @@ public static StandardModel getModel(TileRailBase te) { //TODO render switch and don't render turn info = info.withSettings(b -> b.type = TrackItems.STRAIGHT); } + if (info.settings.type == TrackItems.MULTISWITCH) { + RailInfo finalInfo = info; + info = info.withSettings(b -> b.type = finalInfo.multiSwitchInfo.realShapeType); + } if (info.settings.type.isTable()) { ItemStack held = MinecraftClient.getPlayer().getHeldItem(Player.Hand.PRIMARY); if (held.is(IRItems.ITEM_TRACK_BLUEPRINT) || held.is(IRItems.ITEM_GOLDEN_SPIKE)) { diff --git a/src/main/java/cam72cam/immersiverailroading/render/item/TrackBlueprintItemModel.java b/src/main/java/cam72cam/immersiverailroading/render/item/TrackBlueprintItemModel.java index f65d35d72..9577c0721 100644 --- a/src/main/java/cam72cam/immersiverailroading/render/item/TrackBlueprintItemModel.java +++ b/src/main/java/cam72cam/immersiverailroading/render/item/TrackBlueprintItemModel.java @@ -1,13 +1,12 @@ package cam72cam.immersiverailroading.render.item; +import cam72cam.immersiverailroading.items.nbt.RailSettings; import cam72cam.immersiverailroading.library.TrackItems; import cam72cam.immersiverailroading.render.ExpireableMap; import cam72cam.immersiverailroading.render.rail.RailRender; import cam72cam.immersiverailroading.tile.TileRailBase; -import cam72cam.immersiverailroading.util.BlockUtil; +import cam72cam.immersiverailroading.util.*; import cam72cam.mod.render.*; -import cam72cam.immersiverailroading.util.PlacementInfo; -import cam72cam.immersiverailroading.util.RailInfo; import cam72cam.mod.entity.Player; import cam72cam.mod.item.ItemStack; import cam72cam.mod.math.Vec3d; @@ -21,10 +20,18 @@ public class TrackBlueprintItemModel implements ItemRender.IItemModel { public StandardModel getModel(World world, ItemStack stack) { return new StandardModel().addCustom((state, pt) -> TrackBlueprintItemModel.render(stack, world, state)); } + + //render the model of inventory public static void render(ItemStack stack, World world, RenderState state) { - RailInfo info = new RailInfo(stack, new PlacementInfo(stack, 1, new Vec3d(0.5, 0.5, 0.5)), null); + PlacementInfo placementInfo = new PlacementInfo(stack, 1, new Vec3d(0.5, 0.5, 0.5)); + placementInfo = placementInfo.withFloorYoffset(RailSettings.from(stack).placementOffset); + RailInfo info = new RailInfo(stack, placementInfo, MultiSwitchInfo.from(stack).defaultCustom, MultiSwitchInfo.from(stack)); info = info.withSettings(b -> b.length = 10); + MultiSwitchInfo.Mutable multiSwitchInfo = info.multiSwitchInfo.mutable(); + multiSwitchInfo.writePlacement(placementInfo); + info = info.with(mutable -> mutable.multiSwitchInfo = multiSwitchInfo.immutable()); + state.cull_face(false); state.lighting(false); @@ -54,6 +61,8 @@ public static void render(ItemStack stack, World world, RenderState state) { } private static ExpireableMap infoCache = new ExpireableMap<>(); + + //render the preview when mouse target at a block public static void renderMouseover(Player player, ItemStack stack, Vec3i pos, Vec3d vec, RenderState state, float partialTicks) { Vec3d hit = vec.subtract(pos); World world = player.getWorld(); @@ -66,7 +75,14 @@ public static void renderMouseover(Player player, ItemStack stack, Vec3i pos, Ve } } - RailInfo info = new RailInfo(stack, new PlacementInfo(stack, player.getRotationYawHead(), hit.subtract(0, hit.y, 0)), null); + PlacementInfo placementInfo = new PlacementInfo(stack, player.getRotationYawHead(), hit.subtract(0, hit.y, 0)); + placementInfo = placementInfo.withFloorYoffset(RailSettings.from(stack).placementOffset); + RailInfo info = new RailInfo(stack, placementInfo, MultiSwitchInfo.from(stack).defaultCustom, MultiSwitchInfo.from(stack)); + + MultiSwitchInfo.Mutable multiSwitchInfo = info.multiSwitchInfo.mutable(); + multiSwitchInfo.writePlacement(placementInfo); + info = info.with(mutable -> mutable.multiSwitchInfo = multiSwitchInfo.immutable()); + String key = info.uniqueID + info.placementInfo.placementPosition; RailInfo cached = infoCache.get(key); if (cached != null) { diff --git a/src/main/java/cam72cam/immersiverailroading/render/item/TrackExchangerModel.java b/src/main/java/cam72cam/immersiverailroading/render/item/TrackExchangerModel.java index 258e531e9..f0e5eccbe 100644 --- a/src/main/java/cam72cam/immersiverailroading/render/item/TrackExchangerModel.java +++ b/src/main/java/cam72cam/immersiverailroading/render/item/TrackExchangerModel.java @@ -6,6 +6,7 @@ import cam72cam.immersiverailroading.render.rail.RailRender; import cam72cam.immersiverailroading.tile.TileRail; import cam72cam.immersiverailroading.tile.TileRailBase; +import cam72cam.immersiverailroading.util.MultiSwitchInfo; import cam72cam.immersiverailroading.util.PlacementInfo; import cam72cam.immersiverailroading.util.RailInfo; import cam72cam.mod.MinecraftClient; @@ -19,6 +20,8 @@ import cam72cam.mod.resource.Identifier; import cam72cam.mod.world.World; +import java.util.ArrayList; + public class TrackExchangerModel implements ItemRender.IItemModel { private static OBJModel MODEL; @@ -37,9 +40,10 @@ public StandardModel getModel(World world, ItemStack stack) { public static void render(ItemStack stack, World world, RenderState state) { ItemTrackExchanger.Data data = new ItemTrackExchanger.Data(stack); RailInfo info = new RailInfo( - new RailSettings(data.gauge, data.track, TrackItems.STRAIGHT, 18, 0, 1, TrackPositionType.FIXED, TrackSmoothing.BOTH, TrackDirection.NONE, data.railBed, ItemStack.EMPTY, false, false, 1, 1), + new RailSettings(data.gauge, data.track, TrackItems.STRAIGHT, TrackItems.STRAIGHT, 18, 0, 1, TrackPositionType.FIXED, TrackSmoothing.BOTH, 0, 0, 0,0,true, -1, TrackDirection.NONE, data.railBed, ItemStack.EMPTY, false, false, 1, 1), new PlacementInfo(Vec3d.ZERO, TrackDirection.NONE, 0, Vec3d.ZERO), null, + null, SwitchState.NONE, SwitchState.NONE, 0); diff --git a/src/main/java/cam72cam/immersiverailroading/tile/TileRail.java b/src/main/java/cam72cam/immersiverailroading/tile/TileRail.java index 7d9438586..d972c7dc8 100644 --- a/src/main/java/cam72cam/immersiverailroading/tile/TileRail.java +++ b/src/main/java/cam72cam/immersiverailroading/tile/TileRail.java @@ -214,10 +214,23 @@ public double percentFloating() { } if (tracks == null) { - tracks = (info.settings.type == TrackItems.SWITCH ? info.withSettings(b -> b.type = TrackItems.STRAIGHT) : info).getBuilder(getWorld(), new Vec3i(info.placementInfo.placementPosition).add(getPos())).getTracksForFloating(); + if(info.settings.type == TrackItems.SWITCH) { + tracks = info.withSettings(b -> b.type = TrackItems.STRAIGHT).getBuilder(getWorld(), new Vec3i(info.placementInfo.placementPosition).add(getPos())).getTracksForFloating(); + }else if(info.settings.type == TrackItems.MULTISWITCH){ + tracks = info.withSettings(b -> b.type = info.multiSwitchInfo.realShapeType).getBuilder(getWorld(), new Vec3i(info.placementInfo.placementPosition).add(getPos())).getTracksForFloating(); + }else { + tracks = info.getBuilder(getWorld(), new Vec3i(info.placementInfo.placementPosition).add(getPos())).getTracksForFloating(); + } // This is just terrible Vec3i offset = getPos().subtract(tracks.get(0).getPos()); - tracks = (info.settings.type == TrackItems.SWITCH ? info.withSettings(b -> b.type = TrackItems.STRAIGHT) : info).getBuilder(getWorld(), new Vec3i(info.placementInfo.placementPosition).add(getPos().add(offset))).getTracksForFloating(); + + if(info.settings.type == TrackItems.SWITCH) { + tracks = info.withSettings(b -> b.type = TrackItems.STRAIGHT).getBuilder(getWorld(), new Vec3i(info.placementInfo.placementPosition).add(getPos().add(offset))).getTracksForFloating(); + }else if(info.settings.type == TrackItems.MULTISWITCH){ + tracks = info.withSettings(b -> b.type = info.multiSwitchInfo.realShapeType).getBuilder(getWorld(), new Vec3i(info.placementInfo.placementPosition).add(getPos().add(offset))).getTracksForFloating(); + }else { + tracks = info.getBuilder(getWorld(), new Vec3i(info.placementInfo.placementPosition).add(getPos().add(offset))).getTracksForFloating(); + } } diff --git a/src/main/java/cam72cam/immersiverailroading/tile/TileRailBase.java b/src/main/java/cam72cam/immersiverailroading/tile/TileRailBase.java index b4a93e48f..2060d7574 100644 --- a/src/main/java/cam72cam/immersiverailroading/tile/TileRailBase.java +++ b/src/main/java/cam72cam/immersiverailroading/tile/TileRailBase.java @@ -25,6 +25,7 @@ import cam72cam.mod.math.Vec3d; import cam72cam.mod.math.Vec3i; import cam72cam.mod.serialization.TagField; +import cam72cam.mod.serialization.TagMapper; import cam72cam.mod.sound.Audio; import cam72cam.mod.sound.SoundCategory; import cam72cam.mod.sound.StandardSound; @@ -42,6 +43,8 @@ public class TileRailBase extends BlockEntityTrackTickable implements IRedstoneProvider { @TagField("parent") private Vec3i parent; + @TagField(value = "childWay",mapper = Vec3iListMapper.class)//only used in parent of multiSwitch + private List childWay = new ArrayList<>(); @TagField("height") private float bedHeight = 0; @TagField("railHeight") @@ -235,6 +238,20 @@ public Vec3i getParent() { public void setParent(Vec3i pos) { this.parent = pos.subtract(this.getPos()); } + + public Vec3i getChild(int order) { + if(order < childWay.size()){ + return childWay.get(order); + }else{ + ImmersiveRailroading.warn("invalid TileRailBase:"+this); + return null; + } + } + public void setChildList(List childWayParentPos) { + for(Vec3i pos:childWayParentPos){ + childWay.add(pos.subtract(this.getPos())); + } + } public boolean isFlexible() { return this.flexible || !(this instanceof TileRail); @@ -323,6 +340,17 @@ public TileRail getParentTile() { } return te; } + public TileRail getChildWayTile(int order) { + //when this is called, getChild() shouldn't return null + if (this.getChild(order) == null) { + return null; + } + TileRail te = getWorld().getBlockEntity(this.getChild(order).add(this.getPos()), TileRail.class); + if (te == null || te.info == null) { + return null; + } + return te; + } public void setReplaced(TagCompound replaced) { this.replaced = replaced; } @@ -425,8 +453,31 @@ public Vec3d getNextPositionShort(Vec3d currentPosition, Vec3d motion) { SwitchState state = SwitchUtil.getSwitchState(tile, currentPosition); - if (state == SwitchState.STRAIGHT) { - tile = tile.getParentTile(); + int midState = -1; + switch (state){ + case STRAIGHT: + tile = tile.getParentTile(); + break; + case MID1: + midState = 0; + break; + case MID2: + midState = 1; + break; + case MID3: + midState = 2; + break; + case MID4: + midState = 3; + break; + case TURN: + case NONE: + midState = 4; + break; + } + if(midState!= -1 && midState != 4 && tile.getParentTile().info.settings.type == TrackItems.MULTISWITCH + && tile.getParentTile().info.multiSwitchInfo.getWayAmount() > midState){ + tile = tile.getParentTile().getChildWayTile(midState); } Vec3d potential = MovementTrack.nextPositionDirect(getWorld(), currentPosition, tile, motion); @@ -452,7 +503,7 @@ public Vec3d getNextPositionShort(Vec3d currentPosition, Vec3d motion) { } tiles = tileMap.values(); } - +// System.out.println("case2"); Vec3d nextPos = currentPosition; Vec3d predictedPos = currentPosition.add(motion); @@ -460,16 +511,40 @@ public Vec3d getNextPositionShort(Vec3d currentPosition, Vec3d motion) { for (TileRail tile : tiles) { SwitchState state = SwitchUtil.getSwitchState(tile, currentPosition); +// System.out.println("state:"+state); - if (state == SwitchState.STRAIGHT) { - tile = tile.getParentTile(); + int midState = -1; + switch (state){ + case STRAIGHT: + tile = tile.getParentTile(); + break; + case MID1: + midState = 0; + break; + case MID2: + midState = 1; + break; + case MID3: + midState = 2; + break; + case MID4: + midState = 3; + break; + case TURN: + case NONE: + midState = 4; + break; + } + if(midState!= -1 && midState != 4 && tile.getParentTile().info.settings.type == TrackItems.MULTISWITCH + && tile.getParentTile().info.multiSwitchInfo.getWayAmount() > midState){ + tile = tile.getParentTile().getChildWayTile(midState); } Vec3d potential = MovementTrack.nextPositionDirect(getWorld(), currentPosition, tile, motion); if (potential != null) { // If the track veers onto the curved leg of a switch, try that (with angle limitation) // If two overlapped switches are both set, we could have a weird situation, but it's a incredibly unlikely edge case - if (state == SwitchState.TURN) { + if (state == SwitchState.TURN || state == SwitchState.MID1 || state == SwitchState.MID2 || state == SwitchState.MID3 || state == SwitchState.MID4) {//is MID1~4 needed here? // This code is *fundamentally* broken and most of the time no-longer matters due to the complex parent position logic above float other = VecUtil.toWrongYaw(potential.subtract(currentPosition)); float rotationYaw = VecUtil.toWrongYaw(motion); @@ -895,7 +970,7 @@ public TileRail findSwitchParent(TileRailBase cur) { if (cur instanceof TileRail) { TileRail curTR = (TileRail) cur; - if (curTR.info.settings.type.equals(TrackItems.SWITCH)) { + if (curTR.info.settings.type.equals(TrackItems.SWITCH) || curTR.info.settings.type.equals(TrackItems.MULTISWITCH)) { return curTR; } } @@ -1016,7 +1091,14 @@ public ItemStack onPick() { if (parent == null) { return stack; } - parent.info.settings.write(stack); + parent.info.settings.with(mutable -> mutable.type = mutable.pickType).write(stack); + if(parent.info.multiSwitchInfo != null){ + if(parent.info.settings.type == TrackItems.MULTISWITCH){ + parent.info.multiSwitchInfo.write(stack); + }else { + parent.info.multiSwitchInfo.with(mutable -> mutable.isMultiSwitchWay = false).write(stack); + } + } return stack; } @@ -1124,4 +1206,15 @@ public int getTicksExisted() { public void stockOverhead(EntityMoveableRollingStock stock) { this.overhead = stock; } + + static class Vec3iListMapper implements TagMapper> { + public TagAccessor> apply(Class> t, String f, TagField tag) { + return new TagAccessor<>( + (nbt, list) -> nbt.setList(f, list, + v -> new TagCompound().setVec3i("v", v)), + nbt -> nbt.getList(f, + tc -> tc.getVec3i("v")) + ); + } + } } diff --git a/src/main/java/cam72cam/immersiverailroading/tile/TileRailPreview.java b/src/main/java/cam72cam/immersiverailroading/tile/TileRailPreview.java index 11795b324..e1011fad6 100644 --- a/src/main/java/cam72cam/immersiverailroading/tile/TileRailPreview.java +++ b/src/main/java/cam72cam/immersiverailroading/tile/TileRailPreview.java @@ -1,15 +1,14 @@ package cam72cam.immersiverailroading.tile; import cam72cam.immersiverailroading.IRItems; +import cam72cam.immersiverailroading.ImmersiveRailroading; import cam72cam.immersiverailroading.items.nbt.RailSettings; import cam72cam.immersiverailroading.library.GuiTypes; import cam72cam.immersiverailroading.library.TrackDirection; import cam72cam.immersiverailroading.library.TrackItems; import cam72cam.immersiverailroading.net.PreviewRenderPacket; import cam72cam.immersiverailroading.track.IIterableTrack; -import cam72cam.immersiverailroading.util.BlockUtil; -import cam72cam.immersiverailroading.util.PlacementInfo; -import cam72cam.immersiverailroading.util.RailInfo; +import cam72cam.immersiverailroading.util.*; import cam72cam.mod.block.BlockEntityTickable; import cam72cam.mod.entity.Player; import cam72cam.mod.entity.boundingbox.IBoundingBox; @@ -30,6 +29,8 @@ public class TileRailPreview extends BlockEntityTickable { @TagField private PlacementInfo customInfo; @TagField + private boolean isCustomDirty = false; + @TagField private boolean isAboveRails = false; public ItemStack getItem() { @@ -39,12 +40,22 @@ public ItemStack getItem() { public void setup(ItemStack stack, PlacementInfo info) { this.item = stack.copy(); this.placementInfo = info; + + MultiSwitchInfo.Mutable multiSwitchInfo = MultiSwitchInfo.from(item).mutable(); + multiSwitchInfo.writePlacement(placementInfo); + multiSwitchInfo.immutable().write(item); + this.isAboveRails = BlockUtil.isIRRail(getWorld(), getPos().down()) && getWorld().getBlockEntity(getPos().down(), TileRailBase.class).getRailHeight() < 0.5; this.markDirty(); } public void setItem(ItemStack stack, Player player) { this.item = stack.copy(); + + MultiSwitchInfo.Mutable multiSwitchInfo = MultiSwitchInfo.from(item).mutable(); + multiSwitchInfo.writePlacement(placementInfo); + multiSwitchInfo.immutable().write(item); + RailSettings settings = RailSettings.from(item); if (settings.direction != TrackDirection.NONE) { @@ -71,7 +82,27 @@ public void load(TagCompound nbt) { public void setCustomInfo(PlacementInfo info) { this.customInfo = info; if (customInfo != null) { - RailSettings settings = RailSettings.from(item); + RailSettings settings; + + boolean replaceType = false; + Integer selectedOrder = MultiSwitchInfo.getSelectedFrom(item); + if(selectedOrder == 0){ + settings = RailSettings.from(item); + if(settings.type == TrackItems.MULTISWITCH) { + MultiSwitchInfo multiSwitchInfo = MultiSwitchInfo.from(item); + settings = settings.with(mutable -> mutable.type = multiSwitchInfo.realShapeType); + replaceType = true; + } + }else { + MultiSwitchInfo multiSwitchInfo = MultiSwitchInfo.from(item); + if(multiSwitchInfo != null && multiSwitchInfo.getWayList() != null&&selectedOrder <= multiSwitchInfo.getWayAmount()){ + settings = multiSwitchInfo.getWayList().get(selectedOrder - 1).settings; + }else { + ImmersiveRailroading.warn("invalid multiSwitchInfo:"+multiSwitchInfo+",or selectedOrder:"+selectedOrder); + return; + } + } + if(settings.type ==TrackItems.TURN || settings.type == TrackItems.STRAIGHT || settings.type == TrackItems.SLOPE){ @@ -107,7 +138,26 @@ public void setCustomInfo(PlacementInfo info) { settings = settings.with(b -> b.length = length); } - settings.write(item); + if(selectedOrder==0){ + if(replaceType) { + settings = settings.with(mutable -> mutable.type = TrackItems.MULTISWITCH); + MultiSwitchInfo multiSwitchInfo = MultiSwitchInfo.from(item); + multiSwitchInfo = multiSwitchInfo.with(mutable -> mutable.defaultCustom = customInfo);//use defaultCustom when it is MultiSwitchInfo! + multiSwitchInfo.write(item); + isCustomDirty = true; + } + settings.write(item); + }else { + MultiSwitchInfo.Mutable multiSwitchInfo = MultiSwitchInfo.from(item).mutable(); + if(multiSwitchInfo != null && multiSwitchInfo.wayList != null && selectedOrder <= multiSwitchInfo.wayList.size()){ + SingleWayInfo singleWayInfo = multiSwitchInfo.wayList.get(selectedOrder-1); + RailSettings finalSettings = settings; + singleWayInfo = singleWayInfo.with(mutable -> mutable.settings = finalSettings); + multiSwitchInfo.wayList.set(selectedOrder - 1,singleWayInfo); + multiSwitchInfo.immutable().write(item); + isCustomDirty = true; + } + } } this.markDirty(); } @@ -150,17 +200,83 @@ public IBoundingBox getRenderBoundingBox() { return IBoundingBox.INFINITE; } - public RailInfo getRailRenderInfo() { + public RailInfo getRailRenderInfo() {//not only for render, but also for build if (getWorld() != null && item != null && (info == null || info.settings == null)) { - info = new RailInfo(item, placementInfo, customInfo); + PlacementInfo custom; + if(RailSettings.from(item).type == TrackItems.MULTISWITCH) { + PlacementInfo defaultCustom = MultiSwitchInfo.from(item).defaultCustom; + custom = defaultCustom == null ? null : defaultCustom.withFloorYoffset(RailSettings.from(item).customOffset); + }else { + custom = customInfo;//non-MultiSwitch types + } + + info = new RailInfo(item, placementInfo, custom, MultiSwitchInfo.from(item)); + + //write wayList placement & custom offset into info + writePosOffset(); + } + return info;//build will go here + } + + //TODO:move custom of normal types in multiSwitchInfo/settings? + private void writePosOffset() {//write wayList placement & custom offset into info + MultiSwitchInfo.Mutable multiSwitchInfo = MultiSwitchInfo.from(item).mutable(); + if(multiSwitchInfo != null && multiSwitchInfo.wayList != null) { + for(int i = 0; i < multiSwitchInfo.wayList.size(); i++) { + SingleWayInfo singleWayInfo = multiSwitchInfo.wayList.get(i); + SingleWayInfo finalSingleWayInfo = singleWayInfo; + + singleWayInfo = singleWayInfo.with(mutable -> { + mutable.placementInfo = mutable.placementInfo.withFloorYoffset(RailSettings.from(item).placementOffset); + if(mutable.customInfo != null)mutable.customInfo = mutable.customInfo.withFloorYoffset(finalSingleWayInfo.settings.customOffset); + }); + multiSwitchInfo.wayList.set(i, singleWayInfo); + } + info = info.with(mutable -> mutable.multiSwitchInfo = multiSwitchInfo.immutable()); } - return info; } @Override public void markDirty() { super.markDirty(); - info = new RailInfo(item, placementInfo, customInfo); + + //selectedOrder==0:non-multiSwitch or straightBuilder(multiSwitch) of multiSwitch,selectedOrder>0:turnBuilder(multiSwitch) + int selectedOrder = MultiSwitchInfo.getSelectedFrom(item); + + //update offset placement offset from item <= packet <= Gui + placementInfo = placementInfo.withFloorYoffset(RailSettings.from(item).placementOffset); + + info = new RailInfo(item, placementInfo, customInfo, MultiSwitchInfo.from(item)); + + //update custom if it is multiSwitch + if(selectedOrder > 0 && isCustomDirty) { + MultiSwitchInfo.Mutable multiSwitchInfo = MultiSwitchInfo.from(item).mutable(); + multiSwitchInfo.writeCustom(customInfo,selectedOrder); + info = info.with(mutable -> mutable.multiSwitchInfo = multiSwitchInfo.immutable()); + multiSwitchInfo.immutable().write(item);//both item and info are updated + } + if(info.settings.type == TrackItems.MULTISWITCH) { + MultiSwitchInfo multiSwitchInfo = MultiSwitchInfo.from(item); + + PlacementInfo custom = multiSwitchInfo.defaultCustom; + if(custom != null)custom = custom.withFloorYoffset(RailSettings.from(item).customOffset); + PlacementInfo finalCustom = custom; + + info = info.with(mutable -> { + mutable.customInfo = finalCustom;//only need to overwrite info.customInfo, no update in item + }); + } + isCustomDirty = false; + + //update custom offset from item <= packet <= Gui if selectedOrder>0 + if(selectedOrder == 0) {//if selectedOrder == 0 + customInfo = customInfo == null ? null : customInfo.withFloorYoffset(RailSettings.from(item).customOffset); + info = info.with(mutable -> mutable.customInfo = customInfo); + } + + //write wayList placement & custom offset into info + writePosOffset(); + if (isMulti() && getWorld().isServer) { new PreviewRenderPacket(this).sendToAll(); } diff --git a/src/main/java/cam72cam/immersiverailroading/track/BuilderCubicCurve.java b/src/main/java/cam72cam/immersiverailroading/track/BuilderCubicCurve.java index aa7d03675..278fc52fb 100644 --- a/src/main/java/cam72cam/immersiverailroading/track/BuilderCubicCurve.java +++ b/src/main/java/cam72cam/immersiverailroading/track/BuilderCubicCurve.java @@ -45,7 +45,7 @@ public BuilderCubicCurve(RailInfo info, World world, Vec3i pos, boolean endOfTra //delta = delta.subtract(new Vec3i(delta)); // Relative position within the block PlacementInfo startPos = new PlacementInfo(subCurve.p1.add(delta), info.placementInfo.direction, subCurve.angleStart(), subCurve.ctrl1.add(delta)); PlacementInfo endPos = new PlacementInfo(subCurve.p2.add(delta), info.placementInfo.direction, subCurve.angleStop(), subCurve.ctrl2.add(delta)); - RailInfo subInfo = new RailInfo(info.settings.with(b -> b.type = TrackItems.CUSTOM), startPos, endPos, SwitchState.NONE, SwitchState.NONE, 0); + RailInfo subInfo = new RailInfo(info.settings.with(b -> b.type = TrackItems.CUSTOM), startPos, endPos, info.multiSwitchInfo, SwitchState.NONE, SwitchState.NONE, 0); BuilderCubicCurve subBuilder = new BuilderCubicCurve(subInfo, world, sPos); if (!subBuilders.isEmpty()) { @@ -84,7 +84,7 @@ public CubicCurve getCurve() { Vec3d ctrl1 = VecUtil.fromYaw(ctrlGuess, angle); Vec3d ctrl2 = nextPos.add(VecUtil.fromYaw(ctrlGuess, angle2)); - CubicCurve adjusted = new CubicCurve(Vec3d.ZERO, ctrl1, ctrl2, nextPos).linearize(info.settings.smoothing); + CubicCurve adjusted = new CubicCurve(Vec3d.ZERO, ctrl1, ctrl2, nextPos).linearize(info.settings.smoothing, info.settings.pitchStart, info.settings.pitchEnd); ctrl1 = adjusted.ctrl1; ctrl2 = adjusted.ctrl2; diff --git a/src/main/java/cam72cam/immersiverailroading/track/BuilderCubicParabola.java b/src/main/java/cam72cam/immersiverailroading/track/BuilderCubicParabola.java new file mode 100644 index 000000000..7ada9e3c7 --- /dev/null +++ b/src/main/java/cam72cam/immersiverailroading/track/BuilderCubicParabola.java @@ -0,0 +1,34 @@ +package cam72cam.immersiverailroading.track; + +import cam72cam.immersiverailroading.library.TrackDirection; +import cam72cam.immersiverailroading.util.RailInfo; +import cam72cam.mod.math.Vec3i; +import cam72cam.mod.world.World; +import util.Matrix4; + +public class BuilderCubicParabola extends BuilderCubicCurve{ + public BuilderCubicParabola(RailInfo info, World world, Vec3i pos) { + super(info, world, pos); + } + + @Override + public CubicCurve getCurve() { + int radius = info.settings.length -1;//why does Radius of turn is length-1? + + Matrix4 mat = new Matrix4(); + mat.rotate(Math.toRadians(info.placementInfo.yaw-90), 0, 1, 0); + if (info.placementInfo.direction == TrackDirection.LEFT) { + mat.scale(1, 1, -1); + } + CubicCurve curve; + if(info.settings.farRadius < 0){ + curve = CubicCurve.cubicParabolaAngle(radius, info.settings.degrees, info.settings.isForward).apply(mat); + }else{ + curve = CubicCurve.cubicParabolaR1R2Angle(radius, info.settings.degrees, info.settings.farRadius).apply(mat); + } + + double height = info.customInfo.placementPosition.y - info.placementInfo.placementPosition.y; + curve = new CubicCurve(curve.p1, curve.ctrl1, curve.ctrl2.add(0, height, 0), curve.p2.add(0, height, 0)).linearize(info.settings.smoothing,info.settings.pitchStart,info.settings.pitchEnd); + return curve; + } +} diff --git a/src/main/java/cam72cam/immersiverailroading/track/BuilderIterator.java b/src/main/java/cam72cam/immersiverailroading/track/BuilderIterator.java index 0ec0fadff..fec89040f 100644 --- a/src/main/java/cam72cam/immersiverailroading/track/BuilderIterator.java +++ b/src/main/java/cam72cam/immersiverailroading/track/BuilderIterator.java @@ -6,6 +6,7 @@ import java.util.List; import cam72cam.immersiverailroading.Config; +import cam72cam.immersiverailroading.ImmersiveRailroading; import cam72cam.immersiverailroading.library.SwitchState; import cam72cam.immersiverailroading.library.TrackDirection; import cam72cam.immersiverailroading.library.TrackModelPart; @@ -149,6 +150,44 @@ public BuilderIterator(RailInfo info, World world, Vec3i pos, boolean endOfTrack tracks.add(tg); } } + + public void replaceTrackRail(Vec3i newPos, Vec3i oldPos) { + int newTrackRailIndex = -1; + int oldTrackRailIndex = -1; + boolean foundNewTrackRail = false; + boolean foundOldTrackRail = false; + TrackRail trackAtNew = null; + TrackGag trackAtOld = null; + + for(int i = 0; i < tracks.size(); i++) { + TrackBase track = tracks.get(i); + + if(track instanceof TrackGag && track.getPos().equals(newPos)) { + trackAtNew = new TrackRail(this, track.rel); + + trackAtNew.setRailHeight(track.getRailHeight()); + trackAtNew.setBedHeight(track.getBedHeight()); + foundNewTrackRail = true; + newTrackRailIndex = i; + } + if(track instanceof TrackRail && track.getPos().equals(oldPos)){ + trackAtOld = new TrackGag(this, track.rel); + + trackAtOld.setRailHeight(track.getRailHeight()); + trackAtOld.setBedHeight(track.getBedHeight()); + foundOldTrackRail = true; + oldTrackRailIndex = i; + } + + if(foundOldTrackRail && foundNewTrackRail) {//do we need to set flex here? + tracks.set(newTrackRailIndex,trackAtNew); + tracks.set(oldTrackRailIndex,trackAtOld); + this.setParentPos(newPos.subtract(this.pos)); +// System.out.println("replaced "+oldPos+" with "+newPos); + break; + } + } + } @Override public List getTracksForRender() { @@ -177,8 +216,37 @@ public List getRenderData() { renderScale *= 1.005f;//Avoid some gaps boolean switchStraight = info.switchState == SwitchState.STRAIGHT; + if(info.multiSwitchInfo != null && info.multiSwitchInfo.isMultiSwitchWay){//assume that switch ways are ordered by curve shape and pos + try { + switch (info.multiSwitchInfo.orderAsChild) { + case 0: + switchStraight = !(info.switchState == SwitchState.STRAIGHT); + break; + case 1: + switchStraight = !(info.switchState == SwitchState.MID1); + break; + case 2: + switchStraight = !(info.switchState == SwitchState.MID2); + break; + case 3: + switchStraight = !(info.switchState == SwitchState.MID3); + break; + case 4: + switchStraight = !(info.switchState == SwitchState.MID4); + break; + case 5: + switchStraight = !(info.switchState == SwitchState.TURN); + break; + } + }catch (Exception e) { + ImmersiveRailroading.warn("invalid multiSwitchInfo from info:"+info); + } + } int switchSize = 0; TrackDirection direction = info.placementInfo.direction; + +// if(info.multiSwitchInfo != null && info.multiSwitchInfo.isMultiSwitchWay)direction = info.settings.direction; + if (switchStraight ) { for (int i = 0; i < points.size(); i++) { VecYPR cur = points.get(i); @@ -222,7 +290,7 @@ public List getRenderData() { VecYPR next = points.get(i+1); angle = delta(prev.getYaw(), next.getYaw()); } - if (angle != 0) { + if (angle != 0) {//TODO: make both side of track movable? and for monorail we need a defined number to determine how much to move (and move the whole rail?) VecYPR vec = new VecYPR(cur, renderScale, TrackModelPart.RAIL_BASE); if (direction == TrackDirection.RIGHT) { vec.addChild(new VecYPR(switchPos, (1 - angle / 180) * renderScale, TrackModelPart.RAIL_LEFT)); diff --git a/src/main/java/cam72cam/immersiverailroading/track/BuilderMultiSwitch.java b/src/main/java/cam72cam/immersiverailroading/track/BuilderMultiSwitch.java new file mode 100644 index 000000000..1edd1b331 --- /dev/null +++ b/src/main/java/cam72cam/immersiverailroading/track/BuilderMultiSwitch.java @@ -0,0 +1,234 @@ +package cam72cam.immersiverailroading.track; + +import cam72cam.immersiverailroading.ImmersiveRailroading; +import cam72cam.immersiverailroading.library.SwitchState; +import cam72cam.immersiverailroading.library.TrackItems; +import cam72cam.immersiverailroading.util.MultiSwitchInfo; +import cam72cam.immersiverailroading.util.RailInfo; +import cam72cam.immersiverailroading.util.SingleWayInfo; +import cam72cam.mod.item.ItemStack; +import cam72cam.mod.math.Vec3i; +import cam72cam.mod.world.World; +import org.apache.commons.lang3.tuple.Pair; + +import javax.annotation.Nullable; +import java.util.*; +import java.util.stream.Collectors; + +public class BuilderMultiSwitch extends BuilderBase implements IIterableTrack{ + //"Straight" is just a name from BuilderSwitch, can be 5 kinds of curves, it is used to mark the parent builders + private BuilderCubicCurve realStraightBuilder; + private BuilderIterator straightBuilder; + private BuilderCubicCurve straightBuilderReal; + private List turnBuilders = new ArrayList<>(); + + public BuilderMultiSwitch(RailInfo info, World world, Vec3i pos) { + super(info, world, pos); + + List childParentPosList = new ArrayList<>(); + Map, Integer> freq = new HashMap<>(); + + @Nullable + MultiSwitchInfo multiSwitchInfo = info.multiSwitchInfo; + + int wayAmount = multiSwitchInfo != null ? multiSwitchInfo.getWayAmount() : 0; + TrackItems realShapeOfStraight = multiSwitchInfo != null ? multiSwitchInfo.realShapeType : null;//if multi-info is not null then variable in it won't be null + + straightBuilder = constructBuilder(info, realShapeOfStraight);//child level has the same info, as long as parent builder is build properly then child will be the same + realStraightBuilder = constructBuilder(info, realShapeOfStraight); + straightBuilderReal = constructBuilder(info.withSettings(mutable -> mutable.type = realShapeOfStraight), realShapeOfStraight); + + for (Pair v : straightBuilder.positions) { + freq.merge(v, 1, Integer::sum); + } + + for(int i = 0 ; i < wayAmount; i++) { + //Only STRAIGHT,SLOPE,TURN,CUBICPARABOLA,CUSTOM are valid + RailInfo turnInfo = fromSingleWayInfo(multiSwitchInfo.getWayList().get(i)); + turnInfo = turnInfo.with(mutable -> { + mutable.multiSwitchInfo = mutable.multiSwitchInfo.with(mutable1 ->mutable1.isMultiSwitchWay = true); + }); + + BuilderIterator turnBuilder = (BuilderIterator) turnInfo.getBuilder(world,pos); + turnBuilder.overrideFlexible = true; + turnBuilders.add(turnBuilder); + + for (Pair v : turnBuilder.positions) { + freq.merge(v, 1, Integer::sum); + } + } + + Set> uniquePositions = freq.entrySet().stream() + .filter(e -> e.getValue() == 1) + .map(Map.Entry::getKey) + .collect(Collectors.toSet()); + + straightBuilder.positions.retainAll(uniquePositions); + + for(int i = 0 ; i < wayAmount; i++) { + BuilderIterator turnBuilder = turnBuilders.get(i); + turnBuilder.positions.retainAll(uniquePositions); + + Pair defaultRelParentPos = Pair.of(turnBuilder.getParentPos().x - turnBuilder.pos.x, turnBuilder.getParentPos().z - turnBuilder.pos.z); + if(!turnBuilder.positions.contains(defaultRelParentPos)) {//if parent is overlapped with straightBuilder + for(int j = turnBuilder.tracks.size() - 1; j >= 0; j --) { + TrackBase turn = turnBuilder.tracks.get(j); + + Pair turnPos = Pair.of(turn.rel.x, turn.rel.z); + if (turn instanceof TrackGag && turnBuilder.positions.contains(turnPos)) { + turnBuilder.replaceTrackRail(turn.getPos(), turnBuilder.getParentPos()); + break; + } + } + } + + childParentPosList.add(turnBuilder.getParentPos()); + + for(TrackBase turn : turnBuilder.tracks) {//override parent to straightBuilder + if (turn instanceof TrackRail) { +// System.out.println("trackRail of turn:"+turn.getPos()); + turn.overrideParent(straightBuilder.getParentPos()); + } + } + } + + for (TrackBase straight : straightBuilder.tracks) { + if (straight instanceof TrackGag) { + straight.setFlexible(); + } +// if(straight instanceof TrackRail) { +// System.out.println("straightRail of turn:"+straight.getPos()); +// } + //also move trackRail in straightBuilder to another place? + + Vec3i pos1 = straight.getPos(); + Vec3i pos2 = straightBuilder.getParentPos(); + if(pos1.equals(pos2)){ + straight.setChildList(childParentPosList); + } + } + } + + private RailInfo fromSingleWayInfo(SingleWayInfo singleWayInfo) { + return new RailInfo(singleWayInfo.settings, singleWayInfo.placementInfo, singleWayInfo.customInfo, + new MultiSwitchInfo(null, singleWayInfo.settings.type, singleWayInfo.wayOrder, true, null), + SwitchState.NONE, SwitchState.NONE, 0); + } + + private BuilderCubicCurve constructBuilder(RailInfo railInfo, TrackItems type) { + switch (type) { + case STRAIGHT: + return new BuilderStraight(railInfo, world, pos); + case SLOPE: + return new BuilderSlope(railInfo, world, pos); + case TURN: + return new BuilderTurn(railInfo, world, pos); + case CUBICPARABOLA: + return new BuilderCubicParabola(railInfo,world,pos); + case CUSTOM: + return new BuilderCubicCurve(railInfo, world, pos); + } + ImmersiveRailroading.warn("invalid way type:"+type); + return null; + } + + @Override + public List getSubBuilders() { + List res = new ArrayList<>(); + + for (BuilderIterator turn : turnBuilders) { + List subTurns = turn.getSubBuilders(); + if (subTurns == null) { + res.add(turn); + } else { + res.addAll(subTurns); + } + } + + List subStraights = straightBuilderReal.getSubBuilders(); + if (subStraights == null) { + res.add(straightBuilderReal); + } else { + res.addAll(subStraights); + } + + return res.isEmpty() ? null : res; + } + + @Override + public int costTies() { + int costTiles = straightBuilder.costTies(); + for(BuilderIterator turn : turnBuilders)costTiles += turn.costTies(); + return costTiles; + } + + @Override + public int costRails() { + int costRails = straightBuilder.costRails(); + for(BuilderIterator turn : turnBuilders)costRails += turn.costRails(); + return costRails; + } + + @Override + public int costBed() { + int costBed = straightBuilder.costBed(); + for(BuilderIterator turn : turnBuilders)costBed += turn.costBed(); + return costBed; + } + + @Override + public int costFill() { + int costFill = straightBuilder.costFill(); + for(BuilderIterator turn : turnBuilders)costFill += turn.costFill(); + return costFill; + } + + @Override + public void setDrops(List drops) { + if(straightBuilder !=null) straightBuilder.setDrops(drops); + } + + + @Override + public boolean canBuild() { + boolean canBuild = straightBuilder.canBuild(); + for(BuilderIterator turn : turnBuilders){ + if(!turn.canBuild()){ + canBuild = false; + break; + } + } + return canBuild; + } + + @Override + public void build() { + straightBuilder.build(); + for(BuilderIterator turn : turnBuilders)turn.build(); + } + + @Override + public void clearArea() { + straightBuilder.clearArea(); + for(BuilderIterator turn : turnBuilders)turn.clearArea(); + } + + @Override + public List getTracksForRender() { + List data = straightBuilder.getTracksForRender(); + for(BuilderIterator turn : turnBuilders)data.addAll(turn.getTracksForRender()); + return data; + } + + @Override + public List getRenderData() { + List data = straightBuilder.getRenderData(); + for(BuilderIterator turn : turnBuilders)data.addAll(turn.getRenderData()); + return data; + } + + @Override + public List getPath(double stepSize) { + return realStraightBuilder.getPath(stepSize); + } +} diff --git a/src/main/java/cam72cam/immersiverailroading/track/BuilderSlope.java b/src/main/java/cam72cam/immersiverailroading/track/BuilderSlope.java index 844e53299..b110957de 100644 --- a/src/main/java/cam72cam/immersiverailroading/track/BuilderSlope.java +++ b/src/main/java/cam72cam/immersiverailroading/track/BuilderSlope.java @@ -17,6 +17,6 @@ public CubicCurve getCurve() { delta = -info.placementInfo.placementPosition.subtract(info.customInfo.placementPosition).y; } curve = new CubicCurve(curve.p1, curve.ctrl1, curve.ctrl2.add(0, delta, 0), curve.p2.add(0, delta, 0)); - return curve.linearize(info.settings.smoothing); + return curve.linearize(info.settings.smoothing,info.settings.pitchStart,info.settings.pitchEnd); } } diff --git a/src/main/java/cam72cam/immersiverailroading/track/BuilderTurn.java b/src/main/java/cam72cam/immersiverailroading/track/BuilderTurn.java index 90aa9b206..1d60a5618 100644 --- a/src/main/java/cam72cam/immersiverailroading/track/BuilderTurn.java +++ b/src/main/java/cam72cam/immersiverailroading/track/BuilderTurn.java @@ -23,9 +23,7 @@ public CubicCurve getCurve() { CubicCurve curve = CubicCurve.circle(radius, info.settings.degrees).apply(mat); double height = info.customInfo.placementPosition.y - info.placementInfo.placementPosition.y; - if (height != 0) { - curve = new CubicCurve(curve.p1, curve.ctrl1, curve.ctrl2.add(0, height, 0), curve.p2.add(0, height, 0)).linearize(info.settings.smoothing); - } + curve = new CubicCurve(curve.p1, curve.ctrl1, curve.ctrl2.add(0, height, 0), curve.p2.add(0, height, 0)).linearize(info.settings.smoothing, info.settings.pitchStart, info.settings.pitchEnd); return curve; } } diff --git a/src/main/java/cam72cam/immersiverailroading/track/CubicCurve.java b/src/main/java/cam72cam/immersiverailroading/track/CubicCurve.java index 6b73c21e0..6997b872f 100644 --- a/src/main/java/cam72cam/immersiverailroading/track/CubicCurve.java +++ b/src/main/java/cam72cam/immersiverailroading/track/CubicCurve.java @@ -42,6 +42,131 @@ public static CubicCurve circle(int radius, float degrees) { return new CubicCurve(p1, ctrl1, quart.apply(ctrl2), quart.apply(p2)).apply(new Matrix4().translate(0, 0, -radius)); } + //https://help.autodesk.com/view/CIV3D/2025/ENU/?guid=GUID-DD7C0EA1-8465-45BA-9A39-FC05106FD822 +// public static double cubicParabolaMaxAngle = Math.toDegrees(Math.atan(1.0/Math.sqrt(5))); + public static double cubicParabolaMaxAngle = 24.09484255211;//ease3Parabola will meet min R at this angle + + public static CubicCurve cubicParabola(double Radius, double Len, boolean straightAtP1) {//Len just means x-axis value of P2 here,real l ≈ (x₁ – x₀) + (9a²/10)(x₁⁵ – x₀⁵) + double Len2 = Len * Len; + Vec3d p1, ctrl1, ctrl2, p2; + if (straightAtP1) { //straight to round + p1 = new Vec3d(0, 0, 0); + ctrl1 = new Vec3d(Len / 3.0, 0, 0); + ctrl2 = new Vec3d(2 * Len / 3.0, 0, 0); + p2 = new Vec3d(Len, 0, -Len2 / (6 * Radius));//these seems the best? + + return new CubicCurve(p1, ctrl1, ctrl2, p2); + } else { //round to straight + p1 = new Vec3d(0, 0, 0); + ctrl1 = new Vec3d(Len / 3.0, 0, Len2 / (6 * Radius)); + ctrl2 = new Vec3d(2 * Len / 3.0, 0, Len2 / (6 * Radius)); + p2 = new Vec3d(Len, 0, Len2 / (6 * Radius)); + + Matrix4 quart = new Matrix4(); + quart.rotate(Math.atan(0.5*Len/Radius), 0, 1, 0); + + return new CubicCurve(p1, quart.apply(ctrl1), quart.apply(ctrl2), quart.apply(p2)); + } + } + + public static CubicCurve cubicParabolaAngle(double Radius, double angleDeg, boolean straightAtP1) { + double Len = 2 * Radius * Math.tan(Math.toRadians(angleDeg)); + return cubicParabola(Radius, Len, straightAtP1); + } + + public static CubicCurve cubicParabolaR1R2Angle(double Radius, double angleDeg, double nextRadius) { + //warning:angleDeg must be bigger than this.cubicParabolaMaxAngle, or Len will turn to NaN and stack overflow error will be thrown! + boolean shouldLocateAtP2 = nextRadius < Radius; + if(nextRadius < Radius){ + double t = Radius; + Radius = nextRadius; + nextRadius = t; + } + double k = Radius/nextRadius; + + double tanAngleDeg = Math.tan(Math.toRadians(angleDeg)); + double k2 = k * k; + double delta = (1 - k2) * (1 - k2) - 4 * k2 * tanAngleDeg * tanAngleDeg; + double tanAtRadius = ( (1 - k2) - Math.sqrt(delta) ) / (2 * k2 * tanAngleDeg);//delta is dangerous + double len = 2*Radius*tanAtRadius; + + return shouldLocateAtP2?cubicParabolaR1R2Len(nextRadius,Radius,len):cubicParabolaR1R2Len(Radius,nextRadius,len); + } + public static boolean isCubicParabolaDeltaValid(double Radius, double angleDeg, double nextRadius){ + if(nextRadius < Radius){ + double t = Radius; + Radius = nextRadius; + nextRadius = t; + } + double k = Radius/nextRadius; + double tanAngleDeg = Math.tan(Math.toRadians(angleDeg)); + double k2 = k * k; + double delta = (1 - k2) * (1 - k2) - 4 * k2 * tanAngleDeg * tanAngleDeg; + return delta >= 0; + } + +// (matlab script) +// R = 300; +// R2 = 1000; +// k = R/R2; +// L = 40; +// piece = L*(1-k)/3; +// a = 1/(6*R*L); +// +// x = linspace(0,L,500); +// y_th = a*x.^3; +// +// P0 = [k*L k^3*L^2/(6*R)]; +// P1 = [k*L+piece k^3*L^2/(6*R)+piece*k^2*L/(2*R)]; +// P2 = [k*L+piece*2 L^2/(6*R)-piece*L/(2*R)]; +// P3 = [L L^2/(6*R)]; + + public static CubicCurve cubicParabolaR1R2Len(double Radius, double nextRadius, double Len) {//Len just means x-axis value of P2 here,real l ≈ (x₁ – x₀) + (9a²/10)(x₁⁵ – x₀⁵) + boolean shouldLocateAtP2 = nextRadius < Radius; + if(nextRadius < Radius){ + double t = Radius; + Radius = nextRadius; + nextRadius = t; + } + double k = Radius/nextRadius; + double piece = Len * (1-k)/3; + double k2 = k * k; + double k3 = k * k * k; + double Len2 = Len * Len; + double PL = piece * Len; + + Vec3d p1, ctrl1, ctrl2, p2; + Matrix4 quart = new Matrix4(); + + if(!shouldLocateAtP2){ + p1 = new Vec3d(k * Len, 0, k3 * Len2 / (6 * Radius)); + ctrl1 = new Vec3d(k * Len + piece, 0, k3 * Len2 / (6 * Radius) + k2 * PL / (2 * Radius)); + ctrl2 = new Vec3d(k * Len + piece * 2, 0, Len2 / (6 * Radius) - PL / (2 * Radius)); + p2 = new Vec3d(Len, 0, Len2 / (6 * Radius)); + //translate + double dx = p1.x; + double dz = p1.z; + p1 = p1.add(-dx, 0, -dz); + p2 = p2.add(-dx, 0, -dz); + ctrl1 = ctrl1.add(-dx, 0, -dz); + ctrl2 = ctrl2.add(-dx, 0, -dz); + //mirror + p2 = p2.add(0, 0, -p2.z*2); + ctrl1 = ctrl1.add(0, 0, -ctrl1.z*2); + ctrl2 = ctrl2.add(0, 0, -ctrl2.z*2); + + quart.rotate(- Math.atan(0.5 * Len * k / nextRadius), 0, 1, 0); + }else{ + p1 = new Vec3d(0,0,0); + ctrl1 = new Vec3d(piece, 0, PL / (2 * Radius)); + ctrl2 = new Vec3d(piece * 2, 0, (1 - k3) * Len2 / (6 * Radius)- k2 * PL / (2 * Radius)); + p2 = new Vec3d(Len * (1 - k), 0, (1 - k3) * Len2 / (6 * Radius)); + + quart.rotate(Math.atan(0.5 * Len / Radius), 0, 1, 0); + } + return new CubicCurve(quart.apply(p1), quart.apply(ctrl1), quart.apply(ctrl2), quart.apply(p2)); + } + public CubicCurve apply(Matrix4 mat) { return new CubicCurve( mat.apply(p1), @@ -215,7 +340,7 @@ public List subsplit(int maxSize) { } - public CubicCurve linearize(TrackSmoothing smoothing) { + public CubicCurve linearize(TrackSmoothing smoothing, float pitchStart, float pitchEnd) { double start = p1.distanceTo(ctrl1); double middle = ctrl1.distanceTo(ctrl2); double end = ctrl2.distanceTo(p2); @@ -245,8 +370,38 @@ public CubicCurve linearize(TrackSmoothing smoothing) { ctrl2, p2 ); + case PITCH_LOCKED_CUBIC: + return pitchLockedLinearize(p1, ctrl1, ctrl2, p2, pitchStart, pitchEnd); case BOTH: default: return this; } } + + private static CubicCurve pitchLockedLinearize( + Vec3d p1, Vec3d ctrl1, Vec3d ctrl2, Vec3d p2, + float pitchStart, float pitchEnd + ) { + pitchStart = (float) Math.atan(pitchStart / 1000f); + pitchEnd = (float) Math.atan(pitchEnd / 1000f); + + Vec3d tanStart = ctrl1.subtract(p1); + Vec3d tanEnd = p2.subtract(ctrl2); + double hLenStart = Math.hypot(tanStart.x, tanStart.z); + double hLenEnd = Math.hypot(tanEnd.x, tanEnd.z); + Vec3d hDirStart = hLenStart > 0 ? + new Vec3d(tanStart.x / hLenStart, 0, tanStart.z / hLenStart) : + new Vec3d(1, 0, 0); + Vec3d hDirEnd = hLenEnd > 0 ? + new Vec3d(tanEnd.x / hLenEnd, 0, tanEnd.z / hLenEnd) : + new Vec3d(1, 0, 0); + + double vyStart = Math.tan(pitchStart) * hLenStart; + double vyEnd = Math.tan(pitchEnd) * hLenEnd; + + Vec3d newCtrl1 = p1.add(hDirStart.scale(hLenStart)) + .add(0, vyStart, 0); + Vec3d newCtrl2 = p2.subtract(hDirEnd.scale(hLenEnd)) + .subtract(0, vyEnd, 0); + return new CubicCurve(p1, newCtrl1, newCtrl2, p2); + } } diff --git a/src/main/java/cam72cam/immersiverailroading/track/TrackBase.java b/src/main/java/cam72cam/immersiverailroading/track/TrackBase.java index d6b8526ce..647ddb6c5 100644 --- a/src/main/java/cam72cam/immersiverailroading/track/TrackBase.java +++ b/src/main/java/cam72cam/immersiverailroading/track/TrackBase.java @@ -11,6 +11,8 @@ import cam72cam.mod.serialization.TagCompound; import cam72cam.mod.util.SingleCache; +import java.util.List; + public abstract class TrackBase { public BuilderBase builder; @@ -25,6 +27,7 @@ public abstract class TrackBase { private boolean flexible = false; private Vec3i parent; + private List childList; public boolean solidNotRequired; @@ -70,6 +73,10 @@ public TileRailBase placeTrack(boolean actuallyPlace) { } else { tr.setParent(builder.getParentPos()); } + + if (childList != null) { + tr.setChildList(childList); + } tr.setRailHeight(getRailHeight()); tr.setBedHeight(getBedHeight()); tr.setScaleModel(isScaleModel()); @@ -111,6 +118,10 @@ public TileRailBase placeTrack(boolean actuallyPlace) { } else { tr.setParent(builder.getParentPos()); } + + if (childList != null) { + tr.setChildList(childList); + } tr.setRailHeight(getRailHeight()); tr.setBedHeight(getBedHeight()); tr.setScaleModel(isScaleModel()); @@ -159,4 +170,7 @@ public boolean isFlexible() { public void overrideParent(Vec3i blockPos) { this.parent = blockPos; } + public void setChildList(List posList) { + this.childList = posList; + } } diff --git a/src/main/java/cam72cam/immersiverailroading/util/MultiSwitchInfo.java b/src/main/java/cam72cam/immersiverailroading/util/MultiSwitchInfo.java new file mode 100644 index 000000000..5af844269 --- /dev/null +++ b/src/main/java/cam72cam/immersiverailroading/util/MultiSwitchInfo.java @@ -0,0 +1,257 @@ +package cam72cam.immersiverailroading.util; + +import cam72cam.immersiverailroading.ImmersiveRailroading; +import cam72cam.immersiverailroading.library.TrackItems; +import cam72cam.mod.item.ItemStack; +import cam72cam.mod.serialization.*; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.function.Consumer; + +@TagMapped(MultiSwitchInfo.TagMapper.class) +public class MultiSwitchInfo { + private final List wayList;//TODO: although it works now but List is not really cloned so there might be some potential safety problems + public List getWayList() { + return Collections.unmodifiableList(wayList); + } + public int getWayAmount() { + return wayList.size(); + } + public final TrackItems realShapeType; + public final PlacementInfo defaultCustom;//cache custom when confining ways in wayList, only be written and read in TileRailPreview + public final boolean isMultiSwitchWay;//this should only be written to true in BuilderMultiSwitch + public final int orderAsChild; + + //selectedWayOrder is stored in itemTrackBlueprint and can communicate with TrackGui + + public MultiSwitchInfo(List wayList, TrackItems realShapeType, int orderAsChild, boolean isMultiSwitchWay, PlacementInfo defaultCustom) { + this.wayList = wayList; + this.realShapeType = realShapeType; + this.orderAsChild = orderAsChild; + this.isMultiSwitchWay = isMultiSwitchWay; + this.defaultCustom = defaultCustom; + } + + public static class Mutable { + @TagField(value = "wayList",mapper = WayListMapper.class) + public List wayList; + @TagField("realShapeType") + public TrackItems realShapeType; + @TagField("orderAsChild") + public int orderAsChild; + @TagField("isMultiSwitchWay") + public boolean isMultiSwitchWay; + @TagField("defaultCustom") + public PlacementInfo defaultCustom; + + public Mutable(MultiSwitchInfo info) { + this.wayList = info.wayList; + this.realShapeType = info.realShapeType; + this.orderAsChild = info.orderAsChild; + this.isMultiSwitchWay = info.isMultiSwitchWay; + this.defaultCustom = info.defaultCustom; + } + + public Mutable(TagCompound data) throws SerializationException { + // Defaults + realShapeType = TrackItems.STRAIGHT; + wayList = new ArrayList<>(); + wayList.add(new SingleWayInfo(SingleWayInfo.defaultSettings, SingleWayInfo.defaultPos, null, 0)); + orderAsChild = 0;//0=straight(parent)as default;1=MID1,2=MID2,3=MID3,4=MID4,5=TURN + isMultiSwitchWay = false; + defaultCustom = null; + + TagSerializer.deserialize(data, this); + } + + public MultiSwitchInfo immutable() { + return new MultiSwitchInfo( + wayList, + realShapeType, + orderAsChild, + isMultiSwitchWay, + defaultCustom + ); + } + + public void writePlacement(PlacementInfo placementInfo) { + if(wayList != null){ + for(int i = 0; i < wayList.size(); i++) { + SingleWayInfo singleWayInfo = wayList.get(i); + SingleWayInfo finalSingleWayInfo = singleWayInfo; + + singleWayInfo = singleWayInfo.with(m -> m.placementInfo = placementInfo.withDirection(finalSingleWayInfo.settings.direction)); + wayList.set(i, singleWayInfo); + } + } + } + + public void writeCustom(PlacementInfo customInfo, int wayOrder) {//0=default,1=wayList[0],2=wayList[1],... + if(wayOrder < 0)return; + wayOrder --; + if(wayList != null && wayOrder < wayList.size()){ + SingleWayInfo singleWayInfo = wayList.get(wayOrder); + + if(customInfo==null) { + singleWayInfo = singleWayInfo.with(m -> + m.customInfo = null); + }else { + singleWayInfo = singleWayInfo.with(m -> + m.customInfo = customInfo); + } + + wayList.set(wayOrder, singleWayInfo); + } + } + } + + public MultiSwitchInfo.Mutable mutable() { + return new MultiSwitchInfo.Mutable(this); + } + public static MultiSwitchInfo from(ItemStack stack) { + TagCompound root = stack.getTagCompound(); + if (root == null || !root.hasKey("multiSwitchInfo")) { + //default fallback + List wayList = new ArrayList<>(); + wayList.add(new SingleWayInfo(SingleWayInfo.defaultSettings,SingleWayInfo.defaultPos,null,0)); + return new MultiSwitchInfo(wayList, TrackItems.TURN, 0, false, null); + } + + try { + TagCompound multiSwitchData = root.get("multiSwitchInfo"); + return new MultiSwitchInfo.Mutable(multiSwitchData).immutable(); + } catch (SerializationException e) { + throw new RuntimeException(e); + } + } + public void write(ItemStack stack) { + TagCompound root = stack.getTagCompound(); + if (root == null) { + root = new TagCompound(); + } + + TagCompound multiSwitchData = new TagCompound(); + try { + TagSerializer.serialize(multiSwitchData, mutable()); + } catch (SerializationException e) { + ImmersiveRailroading.catching(e); + } + + root.set("multiSwitchInfo", multiSwitchData); + + stack.setTagCompound(root); + } + + public static Integer getSelectedFrom(ItemStack stack) {//0=default,1=wayList[0],2=wayList[1],... + TagCompound root = stack.getTagCompound(); + if (root == null || !root.hasKey("selectedWayOrder")) { + //default fallback + return 0; + } + return root.getInteger("selectedWayOrder"); + } + public static void writeSelected(ItemStack stack,int selectedWayOrder) { + TagCompound root = stack.getTagCompound(); + if (root == null) { + root = new TagCompound(); + } + root.setInteger("selectedWayOrder", selectedWayOrder); + stack.setTagCompound(root); + } + + public MultiSwitchInfo with(Consumer mod) { + MultiSwitchInfo.Mutable mutable = mutable(); + mod.accept(mutable); + return mutable.immutable(); + } + + private static class WayListMapper implements cam72cam.mod.serialization.TagMapper> { + public TagAccessor> apply(Class> t, String fieldname, TagField tag) { + return new TagAccessor<>( + (nbt, list) -> { + if(list == null){ + nbt.remove(fieldname); + return; + } + TagCompound wayListTag = new TagCompound(); + for (int i = 0; i < list.size(); i++) { + TagCompound singleWayInfo = new TagCompound(); + try { + TagSerializer.serialize(singleWayInfo, new SingleWayInfo.Mutable(list.get(i))); + } catch (SerializationException e) { + throw new RuntimeException(e); + } + wayListTag.set(i + "", singleWayInfo); + } + wayListTag.setInteger("amount", list.size()); + nbt.set(fieldname,wayListTag); + }, + nbt -> { + if(!nbt.hasKey(fieldname)){ + return null; + } + TagCompound wayListTag = nbt.get(fieldname); + int amount = wayListTag.getInteger("amount"); + List list = new ArrayList<>(); + for (int i = 0; i < amount; i++) { + try { + list.add(new SingleWayInfo.Mutable(wayListTag.get(i + "")).immutable()); + } catch (SerializationException e) { + throw new RuntimeException(e); + } + } + return list; + } + ); + } + } + + static class TagMapper implements cam72cam.mod.serialization.TagMapper { + @Override + public TagAccessor apply(Class type, String fieldName, TagField tag) { + return new TagAccessor<>( + (d, o) -> { + if (o == null) { + d.remove(fieldName); + return; + } + TagCompound target = new TagCompound(); + try { + TagSerializer.serialize(target, o.mutable()); + } catch (SerializationException e) { + throw new RuntimeException(e); + } + d.set(fieldName, target); + }, + d -> { + if(!d.hasKey(fieldName)){ + return null; + } + try { + return new MultiSwitchInfo.Mutable(d.get(fieldName)).immutable(); + } catch (SerializationException e) { + throw new RuntimeException(e); + } + } + ); + } + } + + @Override + public String toString() { + String id = "multiSwitchInfo:{"; + id += this.realShapeType; + id += this.orderAsChild; + id += this.isMultiSwitchWay; + id += this.defaultCustom; + if(this.wayList != null){//turnBuilder in MultiSwitchBuilder won't have wayList + for (SingleWayInfo singleWayInfo : this.wayList) { + id += singleWayInfo; + } + } + id += "}"; + return id; + } +} diff --git a/src/main/java/cam72cam/immersiverailroading/util/PlacementInfo.java b/src/main/java/cam72cam/immersiverailroading/util/PlacementInfo.java index 5283417c7..347b89203 100644 --- a/src/main/java/cam72cam/immersiverailroading/util/PlacementInfo.java +++ b/src/main/java/cam72cam/immersiverailroading/util/PlacementInfo.java @@ -156,6 +156,11 @@ public PlacementInfo withDirection(TrackDirection direction) { return new PlacementInfo(placementPosition, direction, yaw, control); } + public PlacementInfo withFloorYoffset(float offset) { + Vec3d updated = new Vec3d(placementPosition.x, Math.floor(placementPosition.y) + offset, placementPosition.z); + return new PlacementInfo(updated, direction, yaw, control); + } + static class TagMapper implements cam72cam.mod.serialization.TagMapper { @Override public TagAccessor apply(Class type, String fieldName, TagField tag) { diff --git a/src/main/java/cam72cam/immersiverailroading/util/RailInfo.java b/src/main/java/cam72cam/immersiverailroading/util/RailInfo.java index cd9cbfaf8..182712327 100644 --- a/src/main/java/cam72cam/immersiverailroading/util/RailInfo.java +++ b/src/main/java/cam72cam/immersiverailroading/util/RailInfo.java @@ -27,6 +27,7 @@ public class RailInfo { public final RailSettings settings; public final PlacementInfo placementInfo; public final PlacementInfo customInfo; + public final MultiSwitchInfo multiSwitchInfo; // Used for tile rendering only public final SwitchState switchState; @@ -37,11 +38,11 @@ public class RailInfo { public final boolean itemHeld; - public RailInfo(RailSettings settings, PlacementInfo placementInfo, PlacementInfo customInfo, SwitchState switchState, SwitchState switchForced, double tablePos) { - this(settings, placementInfo, customInfo, switchState, switchForced, tablePos, false); + public RailInfo(RailSettings settings, PlacementInfo placementInfo, PlacementInfo customInfo, MultiSwitchInfo multiSwitchInfo, SwitchState switchState, SwitchState switchForced, double tablePos) { + this(settings, placementInfo, customInfo, multiSwitchInfo, switchState, switchForced, tablePos, false); } - public RailInfo(RailSettings settings, PlacementInfo placementInfo, PlacementInfo customInfo, SwitchState switchState, SwitchState switchForced, double tablePos, boolean itemHeld) { + public RailInfo(RailSettings settings, PlacementInfo placementInfo, PlacementInfo customInfo, MultiSwitchInfo multiSwitchInfo, SwitchState switchState, SwitchState switchForced, double tablePos, boolean itemHeld) { if (customInfo == null) { customInfo = placementInfo; //#1566: Use customInfo to adjust slope height @@ -53,6 +54,7 @@ public RailInfo(RailSettings settings, PlacementInfo placementInfo, PlacementInf this.settings = settings; this.placementInfo = placementInfo; this.customInfo = customInfo; + this.multiSwitchInfo = multiSwitchInfo; this.switchState = switchState; this.switchForced = switchForced; this.tablePos = tablePos; @@ -69,6 +71,12 @@ private String generateID() { this.settings.gauge, this.settings.track, this.settings.smoothing, + this.settings.pitchStart, + this.settings.placementOffset, + this.settings.customOffset, + this.settings.pitchEnd, + this.settings.isForward, + this.settings.farRadius, this.settings.isGradeCrossing, this.switchState, this.switchForced, @@ -76,7 +84,7 @@ private String generateID() { this.placementInfo.yaw, this.placementInfo.direction, this.customInfo.yaw, - this.customInfo.direction + this.customInfo.direction, }; String id = Arrays.toString(props); if (!placementInfo.placementPosition.equals(customInfo.placementPosition) || this.settings.posType != TrackPositionType.FIXED) { @@ -98,17 +106,21 @@ private String generateID() { if (settings.type.isTable()) { id += this.itemHeld; } + if(this.multiSwitchInfo != null){//move to toSting? format? + id += this.multiSwitchInfo; + } return id; } - public RailInfo(ItemStack settings, PlacementInfo placementInfo, PlacementInfo customInfo) { - this(RailSettings.from(settings), placementInfo, customInfo, SwitchState.NONE, SwitchState.NONE, 0); + public RailInfo(ItemStack settings, PlacementInfo placementInfo, PlacementInfo customInfo, MultiSwitchInfo multiSwitchInfo) { + this(RailSettings.from(settings), placementInfo, customInfo, multiSwitchInfo, SwitchState.NONE, SwitchState.NONE, 0); } public RailInfo withSettings(Consumer mod) { return with(b -> b.settings = b.settings.with(mod)); } + public RailInfo offset(Vec3i offset) { return with(b -> { b.placementInfo = placementInfo.offset(offset); @@ -123,6 +135,8 @@ public static class Mutable { public PlacementInfo placementInfo; @TagField("custom") public PlacementInfo customInfo; + @TagField("multiSwitchInfo") + public MultiSwitchInfo multiSwitchInfo; @TagField("switchState") public SwitchState switchState; @TagField("switchForced") @@ -137,6 +151,7 @@ private Mutable(RailInfo info) { this.settings = info.settings; this.placementInfo = info.placementInfo; this.customInfo = info.customInfo; + this.multiSwitchInfo = info.multiSwitchInfo; this.switchState = info.switchState; this.switchForced = info.switchForced; this.tablePos = info.tablePos; @@ -156,6 +171,7 @@ public RailInfo immutable() { settings, placementInfo, customInfo, + multiSwitchInfo, switchState, switchForced, tablePos, @@ -190,6 +206,8 @@ private BuilderBase constructBuilder(World world, Vec3i pos) { return new BuilderSlope(this, world, pos); case TURN: return new BuilderTurn(this, world, pos); + case CUBICPARABOLA: + return new BuilderCubicParabola(this,world,pos); case SWITCH: return new BuilderSwitch(this, world, pos); case TURNTABLE: @@ -198,6 +216,8 @@ private BuilderBase constructBuilder(World world, Vec3i pos) { return new BuilderTransferTable(this, world, pos); case CUSTOM: return new BuilderCubicCurve(this, world, pos); + case MULTISWITCH: + return new BuilderMultiSwitch(this, world, pos); } return null; } @@ -414,8 +434,8 @@ private static RailInfo legacy(TagCompound nbt) { SwitchState switchForced = SwitchState.values()[nbt.getInteger("switchForced")]; double tablePos = nbt.getDouble("tablePos"); - RailSettings settings = new RailSettings(gauge, "default", type, length, quarters / 4F * 90, 1, TrackPositionType.FIXED, type == TrackItems.SLOPE ? TrackSmoothing.NEITHER : TrackSmoothing.BOTH , TrackDirection.NONE, railBed, cam72cam.mod.item.ItemStack.EMPTY, false, false, 1, 1); - return new RailInfo(settings, placementInfo, null, switchState, switchForced, tablePos); + RailSettings settings = new RailSettings(gauge, "default", type, type, length, quarters / 4F * 90, 1, TrackPositionType.FIXED, type == TrackItems.SLOPE ? TrackSmoothing.NEITHER : TrackSmoothing.BOTH , 0, 0, 0, 0, true, -1, TrackDirection.NONE, railBed, cam72cam.mod.item.ItemStack.EMPTY, false, false, 1, 1); + return new RailInfo(settings, placementInfo, null, null, switchState, switchForced, tablePos); } } } diff --git a/src/main/java/cam72cam/immersiverailroading/util/SingleWayInfo.java b/src/main/java/cam72cam/immersiverailroading/util/SingleWayInfo.java new file mode 100644 index 000000000..e8521616c --- /dev/null +++ b/src/main/java/cam72cam/immersiverailroading/util/SingleWayInfo.java @@ -0,0 +1,155 @@ +package cam72cam.immersiverailroading.util; + +import cam72cam.immersiverailroading.items.nbt.RailSettings; +import cam72cam.immersiverailroading.library.*; +import cam72cam.mod.item.ItemStack; +import cam72cam.mod.math.Vec3d; +import cam72cam.mod.math.Vec3i; +import cam72cam.mod.serialization.*; + +import java.util.Arrays; +import java.util.function.Consumer; + +@TagMapped(SingleWayInfo.TagMapper.class) +public class SingleWayInfo { + public final RailSettings settings; + public final PlacementInfo placementInfo; + public final PlacementInfo customInfo; + public final int wayOrder; + + public SingleWayInfo(RailSettings settings,PlacementInfo placementInfo, PlacementInfo customInfo,int wayOrder) { + if (customInfo == null) { + customInfo = placementInfo; + if (settings.type == TrackItems.SLOPE) { + customInfo = customInfo.offset(new Vec3i(0,1,0)); + } + } + + this.settings = settings; + this.placementInfo = placementInfo; + this.customInfo = customInfo; + this.wayOrder = wayOrder; + } + + public static final RailSettings defaultSettings = new RailSettings( + Gauge.standard(), + "default", + TrackItems.TURN,TrackItems.TURN, + 15, + 90, + 1f, + TrackPositionType.FIXED, TrackSmoothing.BOTH, + 0f,0f, + 0f,0f, + true,-1, + TrackDirection.RIGHT, + ItemStack.EMPTY, ItemStack.EMPTY, + true, + false, + 1, + 1 + ); + public static final PlacementInfo defaultPos = new PlacementInfo( + new Vec3d(0.5, 0, 0.5), TrackDirection.LEFT, 0, null + ); + + public static class Mutable { + @TagField("settings") + public RailSettings settings; + @TagField("placement") + public PlacementInfo placementInfo; + @TagField("custom") + public PlacementInfo customInfo; + @TagField("wayOrder") + public int wayOrder; + + public Mutable(SingleWayInfo info) { + this.settings = info.settings; + this.placementInfo = info.placementInfo; + this.customInfo = info.customInfo; + this.wayOrder = info.wayOrder; + } + + public Mutable(TagCompound data) throws SerializationException { + // Defaults + TagSerializer.deserialize(data, this); + } + + public SingleWayInfo immutable() { + return new SingleWayInfo( + settings, + placementInfo, + customInfo, + wayOrder + ); + } + } + + public SingleWayInfo.Mutable mutable() { + return new SingleWayInfo.Mutable(this); + } + static class TagMapper implements cam72cam.mod.serialization.TagMapper { + @Override + public TagAccessor apply(Class type, String fieldName, TagField tag) { + return new TagAccessor<>( + (d, o) -> { + TagCompound target = new TagCompound(); + try { + TagSerializer.serialize(target, o.mutable()); + } catch (SerializationException e) { + throw new RuntimeException(e); + } + d.set(fieldName, target); + }, + d -> { + try { + return new SingleWayInfo.Mutable(d.get(fieldName)).immutable(); + } catch (SerializationException e) { + throw new RuntimeException(e); + } + } + ); + } + } + + public SingleWayInfo with(Consumer mod) { + SingleWayInfo.Mutable mutable = mutable(); + mod.accept(mutable); + return mutable.immutable(); + } + + @Override + public String toString() { + Object[] props = new Object [] { + this.wayOrder, + this.settings.type, + this.settings.length, + this.settings.degrees, + this.settings.curvosity, + this.settings.railBed, + this.settings.gauge, + this.settings.track, + this.settings.smoothing, + this.settings.pitchStart, + this.settings.pitchEnd, + this.settings.isForward, + this.settings.farRadius, + this.settings.isGradeCrossing, + this.placementInfo.yaw, + this.placementInfo.direction, + this.customInfo.yaw, + this.customInfo.direction, + }; + String id = Arrays.toString(props); + if (!placementInfo.placementPosition.equals(customInfo.placementPosition) || this.settings.posType != TrackPositionType.FIXED) { + id += placementInfo.placementPosition.subtract(customInfo.placementPosition); + } + if (placementInfo.control != null) { + id += placementInfo.control; + } + if (customInfo.control != null) { + id += customInfo.control; + } + return id; + } +} diff --git a/src/main/java/cam72cam/immersiverailroading/util/SwitchUtil.java b/src/main/java/cam72cam/immersiverailroading/util/SwitchUtil.java index 463b77ca5..05f48584e 100644 --- a/src/main/java/cam72cam/immersiverailroading/util/SwitchUtil.java +++ b/src/main/java/cam72cam/immersiverailroading/util/SwitchUtil.java @@ -19,19 +19,23 @@ public static SwitchState getSwitchState(TileRail rail, Vec3d position) { return SwitchState.NONE; } - if (rail.info.settings.type != TrackItems.TURN && rail.info.settings.type != TrackItems.CUSTOM) { - return SwitchState.NONE; + if(rail.info.settings.type.isTable()) return SwitchState.NONE; + if(rail.info.settings.type == TrackItems.STRAIGHT || rail.info.settings.type == TrackItems.SLOPE) { + if(rail.info.multiSwitchInfo == null)return SwitchState.NONE; + if(!rail.info.multiSwitchInfo.isMultiSwitchWay) { + return SwitchState.NONE; + } } TileRail parent = rail.getParentTile(); if (parent == null) { return SwitchState.NONE; } - if (parent.info.settings.type != TrackItems.SWITCH) { + if (parent.info.settings.type != TrackItems.SWITCH && parent.info.settings.type != TrackItems.MULTISWITCH) { return SwitchState.NONE; } - if (position != null) { + if (position != null && parent.info.settings.type != TrackItems.MULTISWITCH) { IIterableTrack switchBuilder = (IIterableTrack) parent.info.getBuilder(rail.getWorld()); IIterableTrack turnBuilder = (IIterableTrack) rail.info.getBuilder(rail.getWorld()); double isOnStraight = switchBuilder.offsetFromTrack(parent.info, parent.getPos(), position); @@ -46,17 +50,145 @@ public static SwitchState getSwitchState(TileRail rail, Vec3d position) { } } + if(position != null && parent.info.settings.type == TrackItems.MULTISWITCH){ + //find target + SwitchState targetState; + if (parent.isSwitchForced()) { + targetState = parent.info.switchForced; + }else{ + targetState = fromRedStoneLevel(RailPoweredLevel(rail)); + } + + TileRail targetRail; + int targetMidState = -1; + switch (targetState){ + case NONE: + case STRAIGHT: + break; + case MID1: + targetMidState = 0; + break; + case MID2: + targetMidState = 1; + break; + case MID3: + targetMidState = 2; + break; + case MID4: + targetMidState = 3; + break; + case TURN: + targetMidState = 4; + break; + } + if(targetMidState != -1 && targetMidState < parent.info.multiSwitchInfo.getWayAmount()){ + targetRail = parent.getChildWayTile(targetMidState); + }else if(targetMidState == -1 ){ + targetRail = parent;//NONE and STRAIGHT + }else{ + targetRail = parent.getChildWayTile(parent.info.multiSwitchInfo.getWayAmount() - 1);//convert to the biggest one + } + IIterableTrack targetBuilder = (IIterableTrack) targetRail.info.getBuilder(rail.getWorld()); + + //offset + double targetOffset = targetBuilder.offsetFromTrack(targetRail.info, targetRail.getPos(), position); + double currentOffset = 0x3f3f3f; + + //find current + SwitchState currentState = SwitchState.NONE; + int currentStateInt = 0;//0,1,2,3,4,5,6=NONE,STRAIGHT,MID1,MID2,MID3,MID4,TURN + for(int i = 0; i < parent.info.multiSwitchInfo.getWayAmount(); i++){ + TileRail currentRail = parent.getChildWayTile(i); + IIterableTrack currentBuilder = (IIterableTrack) currentRail.info.getBuilder(rail.getWorld()); + double delta = currentBuilder.offsetFromTrack(currentRail.info, currentRail.getPos(), position); + if(delta < currentOffset){ + currentStateInt = i + 2;//STRAIGHT=1,MID1=2(i=0),MID2=3(i=1),... + currentOffset = delta; + } + } + IIterableTrack straightBuilder = (IIterableTrack) parent.info.getBuilder(rail.getWorld()); + double straightOffset = straightBuilder.offsetFromTrack(parent.info, parent.getPos(), position); + if(straightOffset < currentOffset){ + currentStateInt = 1; + currentOffset = straightOffset; + } + currentState = fromInt(currentStateInt); + + //compare + if(targetOffset > rail.info.settings.gauge.scale() / 16){ + if(targetState == SwitchState.TURN) { + return currentState; + }else { + if(currentState == SwitchState.TURN){ + return SwitchState.NONE; + }else { + return currentState; + } + } + } + } + if (parent.isSwitchForced()) { return parent.info.switchForced; } - if (isRailPowered(rail)) { - return SwitchState.TURN; + if(parent.info.settings.type == TrackItems.MULTISWITCH){ + return fromRedStoneLevel(RailPoweredLevel(rail)); + }else{ + if (isRailPowered(rail)) { + return SwitchState.TURN; + } } return SwitchState.STRAIGHT; } + private static SwitchState fromRedStoneLevel(int redStoneLevel) { + switch (redStoneLevel){ + case 0: + return SwitchState.STRAIGHT; + case 1: + case 2: + case 3: + return SwitchState.MID1; + case 4: + case 5: + case 6: + return SwitchState.MID2; + case 7: + case 8: + case 9: + return SwitchState.MID3; + case 10: + case 11: + case 12: + return SwitchState.MID4; + case 13: + case 14: + case 15: + return SwitchState.TURN; + } + return SwitchState.STRAIGHT; + } + + private static SwitchState fromInt(int i) { + switch(i) { + case 1: + return SwitchState.STRAIGHT; + case 2: + return SwitchState.MID1; + case 3: + return SwitchState.MID2; + case 4: + return SwitchState.MID3; + case 5: + return SwitchState.MID4; + case 6: + return SwitchState.TURN; + } + return SwitchState.NONE; + } + public static boolean isRailPowered(TileRail rail) { Vec3d redstoneOrigin = rail.info.placementInfo.placementPosition.add(rail.getPos()); double horiz = rail.info.settings.gauge.scale() * 1.1; @@ -77,4 +209,27 @@ public static boolean isRailPowered(TileRail rail) { } return false; } + + public static int RailPoweredLevel(TileRail rail) { + Vec3d redstoneOrigin = rail.info.placementInfo.placementPosition.add(rail.getPos()); + double horiz = rail.info.settings.gauge.scale() * 1.1; + if (Config.ConfigDebug.oldNarrowWidth && rail.info.settings.gauge.value() < 1) { + horiz = horiz/2; + } + + int maxPower = 0; + + int scale = (int)Math.round(horiz); + for (int x = -scale; x <= scale; x++) { + for (int z = -scale; z <= scale; z++) { + Vec3i gagPos = new Vec3i(redstoneOrigin.add(new Vec3d(x, 0, z))); + TileRailBase gagRail = rail.getWorld().getBlockEntity(gagPos, TileRailBase.class); + if (gagRail != null && (rail.getPos().equals(gagRail.getParent()) || gagRail.getReplaced() != null)) { + maxPower = Math.max(maxPower,rail.getWorld().getRedstone(gagPos)); + } + } + } + if(maxPower>0)System.out.println("maxPower:"+maxPower); + return maxPower; + } } diff --git a/src/main/resources/assets/immersiverailroading/lang/de_de.lang b/src/main/resources/assets/immersiverailroading/lang/de_de.lang index 4b526a2a1..9a39c89bf 100644 --- a/src/main/resources/assets/immersiverailroading/lang/de_de.lang +++ b/src/main/resources/assets/immersiverailroading/lang/de_de.lang @@ -70,6 +70,7 @@ gui.immersiverailroading:label.reverser=Richtungswender gui.immersiverailroading:slider.zoom=Zoom: gui.immersiverailroading:selector.page=Seite %s von %s gui.immersiverailroading:selector.type=Typ: %s +gui.immersiverailroading:selector.sub_type=Typ: %s gui.immersiverailroading:selector.gauge=Spurweite: %s gui.immersiverailroading:selector.track=Schienentyp: %s gui.immersiverailroading:selector.quarters=%s Grad Kurve @@ -79,6 +80,16 @@ gui.immersiverailroading:selector.rail_bed_fill=Schienenbettfüllung: %s gui.immersiverailroading:selector.position=Position: %s gui.immersiverailroading:selector.smoothing=Vertikale Glättung: %s gui.immersiverailroading:selector.direction=Richtung: %s +gui.immersiverailroading:track.far_radius=Weiter Radius: +gui.immersiverailroading:track.is_forward=Umgekehrte Krümmung: %s +gui.immersiverailroading:track.near_height_offset=Nahe Höhenversatz: %s +gui.immersiverailroading:track.far_height_offset=Entfernte Höhenversatz: %s +gui.immersiverailroading:track.near_pitch=Nahe Steigung(‰): +gui.immersiverailroading:track.far_pitch=Entfernte Steigung(‰): +gui.immersiverailroading:track.selected_way=Ausgewählter Weg: %s +gui.immersiverailroading:track.add_way=Hinzufügen +gui.immersiverailroading:track.delete_way=Löschen +gui.immersiverailroading:track.insert_way=Einfügen gui.immersiverailroading:selector.transfer_table_entry_count=Anzahl der Gleise: %s gui.immersiverailroading:selector.transfer_table_entry_spacing=Abstand der Gleise: %s gui.immersiverailroading:selector.place_blueprint=Entwurf platzieren @@ -150,6 +161,8 @@ track.immersiverailroading:class.turntable=Drehscheibe track.immersiverailroading:class.transfertable=Schiebebühne track.immersiverailroading:class.switch=Weiche track.immersiverailroading:class.custom=Individuell +track.immersiverailroading:class.cubicparabola=Dreifach gekrümmte Glättungskurve +track.immersiverailroading:class.multiswitch=Mehrspur-Schalter track.immersiverailroading:position.fixed=Fixiert track.immersiverailroading:position.pixels=Pixel @@ -161,6 +174,7 @@ track.immersiverailroading:smoothing.both=Beides track.immersiverailroading:smoothing.near=Nah track.immersiverailroading:smoothing.far=Fern track.immersiverailroading:smoothing.neither=Nichts +track.immersiverailroading:smoothing.pitch_locked_cubic=Gesperrter Winkel track.immersiverailroading:direction.none=Flexibel track.immersiverailroading:direction.left=Sperre rechts diff --git a/src/main/resources/assets/immersiverailroading/lang/en_us.lang b/src/main/resources/assets/immersiverailroading/lang/en_us.lang index 7bfe701aa..0b9245db6 100644 --- a/src/main/resources/assets/immersiverailroading/lang/en_us.lang +++ b/src/main/resources/assets/immersiverailroading/lang/en_us.lang @@ -71,6 +71,7 @@ gui.immersiverailroading:label.reverser=Reverser gui.immersiverailroading:slider.zoom=Zoom: gui.immersiverailroading:selector.page=Page %s of %s gui.immersiverailroading:selector.type=Type: %s +gui.immersiverailroading:selector.sub_type=Type: %s gui.immersiverailroading:selector.gauge=Gauge: %s gui.immersiverailroading:selector.track=Track Style: %s gui.immersiverailroading:selector.quarters=%s Degree Turn @@ -80,6 +81,16 @@ gui.immersiverailroading:selector.rail_bed_fill=Rail Bed Fill: %s gui.immersiverailroading:selector.position=Position: %s gui.immersiverailroading:selector.smoothing=Vertical Smoothing: %s gui.immersiverailroading:selector.direction=Direction: %s +gui.immersiverailroading:track.far_radius=Far Radius: +gui.immersiverailroading:track.is_forward=Reverse Curvosity: %s +gui.immersiverailroading:track.near_height_offset=Near Height Offset: %s +gui.immersiverailroading:track.far_height_offset=Far Height Offset: %s +gui.immersiverailroading:track.near_pitch=Near Pitch(‰): +gui.immersiverailroading:track.far_pitch=Far Pitch(‰): +gui.immersiverailroading:track.selected_way=Selected Way: %s +gui.immersiverailroading:track.add_way=Add +gui.immersiverailroading:track.delete_way=Delte +gui.immersiverailroading:track.insert_way=Insert gui.immersiverailroading:selector.transfer_table_entry_count=Transfer Table Entries: %s gui.immersiverailroading:selector.transfer_table_entry_spacing=Distance Between Entries: %s gui.immersiverailroading:selector.place_blueprint=Place Blueprint @@ -163,6 +174,8 @@ track.immersiverailroading:class.turntable=Turn Table track.immersiverailroading:class.transfertable=Transfer Table track.immersiverailroading:class.switch=Switch track.immersiverailroading:class.custom=Custom Curve +track.immersiverailroading:class.cubicparabola=Cubic Parabola +track.immersiverailroading:class.multiswitch=Multi-Switch track.immersiverailroading:position.fixed=Fixed track.immersiverailroading:position.pixels=Pixels @@ -174,6 +187,7 @@ track.immersiverailroading:smoothing.both=Both track.immersiverailroading:smoothing.near=Near track.immersiverailroading:smoothing.far=Far track.immersiverailroading:smoothing.neither=Neither +track.immersiverailroading:smoothing.pitch_locked_cubic=Pitch Locked track.immersiverailroading:direction.none=Flexible track.immersiverailroading:direction.left=Lock Right diff --git a/src/main/resources/assets/immersiverailroading/lang/fr_fr.lang b/src/main/resources/assets/immersiverailroading/lang/fr_fr.lang index 0e7ce9bb7..ae0efe522 100644 --- a/src/main/resources/assets/immersiverailroading/lang/fr_fr.lang +++ b/src/main/resources/assets/immersiverailroading/lang/fr_fr.lang @@ -70,6 +70,7 @@ gui.immersiverailroading:label.reverser=Inverseur gui.immersiverailroading:slider.zoom=[Need translate] gui.immersiverailroading:selector.page=[Need translate] gui.immersiverailroading:selector.type=Type: %s +gui.immersiverailroading:selector.sub_type=Type: %s gui.immersiverailroading:selector.gauge=Ecartement: %s gui.immersiverailroading:selector.track=Style de voie: %s gui.immersiverailroading:selector.quarters=Courbe de %s degrés @@ -79,6 +80,16 @@ gui.immersiverailroading:selector.rail_bed_fill=Remplissage sous ballast: %s gui.immersiverailroading:selector.position=Position: %s gui.immersiverailroading:selector.smoothing=Lissage de la rampe: %s gui.immersiverailroading:selector.direction=Direction: %s +gui.immersiverailroading:track.far_radius=Rayon lointain: +gui.immersiverailroading:track.is_forward=Courbure inversée: %s +gui.immersiverailroading:track.near_height_offset=Décalage hauteur proche: %s +gui.immersiverailroading:track.far_height_offset=Décalage hauteur lointain: %s +gui.immersiverailroading:track.near_pitch=Pente proche(‰): +gui.immersiverailroading:track.far_pitch=Pente lointaine(‰): +gui.immersiverailroading:track.selected_way=Voie sélectionnée: %s +gui.immersiverailroading:track.add_way=Ajouter +gui.immersiverailroading:track.delete_way=Supprimer +gui.immersiverailroading:track.insert_way=Insérer gui.immersiverailroading:selector.transfer_table_entry_count=[Need translate] gui.immersiverailroading:selector.transfer_table_entry_spacing=[Need translate] gui.immersiverailroading:selector.place_blueprint=Montrer un aperçu @@ -150,6 +161,8 @@ track.immersiverailroading:class.turntable=Pont tournant track.immersiverailroading:class.transfertable=Pont transbordeur track.immersiverailroading:class.switch=Aiguillage track.immersiverailroading:class.custom=Courbe personnalisée +track.immersiverailroading:class.cubicparabola=Parabole tertiaire +track.immersiverailroading:class.multiswitch=Commutateur multipiste track.immersiverailroading:position.fixed=Fixée track.immersiverailroading:position.pixels=Pixels @@ -161,6 +174,7 @@ track.immersiverailroading:smoothing.both=Les deux track.immersiverailroading:smoothing.near=Proche track.immersiverailroading:smoothing.far=Lointain track.immersiverailroading:smoothing.neither=Pas de lissage +track.immersiverailroading:smoothing.pitch_locked_cubic=Angle verrouillé track.immersiverailroading:direction.none=Flexible track.immersiverailroading:direction.left=Verrouillée vers la droite diff --git a/src/main/resources/assets/immersiverailroading/lang/ja_jp.lang b/src/main/resources/assets/immersiverailroading/lang/ja_jp.lang index eafa019cf..35636696c 100644 --- a/src/main/resources/assets/immersiverailroading/lang/ja_jp.lang +++ b/src/main/resources/assets/immersiverailroading/lang/ja_jp.lang @@ -70,6 +70,7 @@ gui.immersiverailroading:label.reverser=逆転機 gui.immersiverailroading:slider.zoom=ズーム: gui.immersiverailroading:selector.page=%s / %s ページ gui.immersiverailroading:selector.type=種類:%s +gui.immersiverailroading:selector.sub_type=種類:%s gui.immersiverailroading:selector.gauge=軌間:%s gui.immersiverailroading:selector.track=レールのスタイル:%s gui.immersiverailroading:selector.quarters=%s 度曲線 @@ -79,6 +80,16 @@ gui.immersiverailroading:selector.rail_bed_fill=線路下のブロック充填 gui.immersiverailroading:selector.position=位置:%s gui.immersiverailroading:selector.smoothing=垂直スムージング:%s gui.immersiverailroading:selector.direction=方向:%s +gui.immersiverailroading:track.far_radius=遠端半径: +gui.immersiverailroading:track.is_forward=逆曲率:%s +gui.immersiverailroading:track.near_height_offset=近端高さ補正:%s +gui.immersiverailroading:track.far_height_offset=遠端高さ補正:%s +gui.immersiverailroading:track.near_pitch=近端勾配(‰): +gui.immersiverailroading:track.far_pitch=遠端勾配(‰): +gui.immersiverailroading:track.selected_way=選択レール:%s +gui.immersiverailroading:track.add_way=追加 +gui.immersiverailroading:track.delete_way=削除 +gui.immersiverailroading:track.insert_way=挿入 gui.immersiverailroading:selector.transfer_table_entry_count=トラバーサー線路数:%s gui.immersiverailroading:selector.transfer_table_entry_spacing=隣接する線路との間隔:%s gui.immersiverailroading:selector.place_blueprint=設計図設置 @@ -150,6 +161,8 @@ track.immersiverailroading:class.turntable=転車台 track.immersiverailroading:class.transfertable=トラバーサー track.immersiverailroading:class.switch=分岐器 track.immersiverailroading:class.custom=カスタム +track.immersiverailroading:class.cubicparabola=三次放物緩和曲線 +track.immersiverailroading:class.multiswitch=多路分岐器 track.immersiverailroading:position.fixed=ブロックに単位 track.immersiverailroading:position.pixels=ピクセル単位 @@ -161,6 +174,7 @@ track.immersiverailroading:smoothing.both=両者 track.immersiverailroading:smoothing.near=近い track.immersiverailroading:smoothing.far=遠い track.immersiverailroading:smoothing.neither=どちらでもない +track.immersiverailroading:smoothing.pitch_locked_cubic=ロック角度 track.immersiverailroading:direction.none=両方向 track.immersiverailroading:direction.left=左側に固定 diff --git a/src/main/resources/assets/immersiverailroading/lang/ru_ru.lang b/src/main/resources/assets/immersiverailroading/lang/ru_ru.lang index a3e9b6df9..0dbc3b004 100644 --- a/src/main/resources/assets/immersiverailroading/lang/ru_ru.lang +++ b/src/main/resources/assets/immersiverailroading/lang/ru_ru.lang @@ -70,6 +70,7 @@ gui.immersiverailroading:label.reverser=Реверсор gui.immersiverailroading:slider.zoom=Масштабирования: gui.immersiverailroading:selector.page=Страница %s из %s gui.immersiverailroading:selector.type=Тип: %s +gui.immersiverailroading:selector.sub_type=Тип: %s gui.immersiverailroading:selector.gauge=Колея: %s gui.immersiverailroading:selector.track=Модель пути: %s gui.immersiverailroading:selector.quarters=Радиус поворота %s° @@ -79,6 +80,16 @@ gui.immersiverailroading:selector.rail_bed_fill=Насыпь: %s gui.immersiverailroading:selector.position=Позиция установки пути: %s gui.immersiverailroading:selector.smoothing=Искривление %s gui.immersiverailroading:selector.direction=Фиксация направления: %s +gui.immersiverailroading:track.far_radius=Дальний радиус: +gui.immersiverailroading:track.is_forward=Обратная кривизна: %s +gui.immersiverailroading:track.near_height_offset=Смещение высоты вблизи: %s +gui.immersiverailroading:track.far_height_offset=Смещение высоты вдали: %s +gui.immersiverailroading:track.near_pitch=Уклон вблизи(‰): +gui.immersiverailroading:track.far_pitch=Уклон вдали(‰): +gui.immersiverailroading:track.selected_way=Выбранный путь: %s +gui.immersiverailroading:track.add_way=Добавить +gui.immersiverailroading:track.delete_way=Удалить +gui.immersiverailroading:track.insert_way=Вставить gui.immersiverailroading:selector.transfer_table_entry_count=Количество записей в таблице переноса gui.immersiverailroading:selector.transfer_table_entry_spacing=Расстояние между входами в таблицу переноса gui.immersiverailroading:selector.place_blueprint=Включить предварительный режим @@ -150,6 +161,8 @@ track.immersiverailroading:class.turntable=Поворотный круг track.immersiverailroading:class.transfertable=Трансбордер track.immersiverailroading:class.switch=Стрелка track.immersiverailroading:class.custom=Настраиваемый +track.immersiverailroading:class.cubicparabola=Третичная парабола +track.immersiverailroading:class.multiswitch=Многоканальный переключатель track.immersiverailroading:position.fixed=Фиксированная блочная track.immersiverailroading:position.pixels=Пиксельная @@ -161,6 +174,7 @@ track.immersiverailroading:smoothing.both=на обоих окончаниях track.immersiverailroading:smoothing.near=в начале пути track.immersiverailroading:smoothing.far=в конце пути track.immersiverailroading:smoothing.neither=Нет +track.immersiverailroading:smoothing.pitch_locked_cubic=Заблокированный угол track.immersiverailroading:direction.none=Нет track.immersiverailroading:direction.left=Только влево diff --git a/src/main/resources/assets/immersiverailroading/lang/zh_cn.lang b/src/main/resources/assets/immersiverailroading/lang/zh_cn.lang index 2b0112606..cf49cc6a9 100644 --- a/src/main/resources/assets/immersiverailroading/lang/zh_cn.lang +++ b/src/main/resources/assets/immersiverailroading/lang/zh_cn.lang @@ -70,6 +70,7 @@ gui.immersiverailroading:label.reverser=换向器 gui.immersiverailroading:slider.zoom=缩放比例: gui.immersiverailroading:selector.page=第 %s 页,共 %s 页 gui.immersiverailroading:selector.type=类型:%s +gui.immersiverailroading:selector.sub_type=类型:%s gui.immersiverailroading:selector.gauge=轨距:%s gui.immersiverailroading:selector.track=轨道样式:%s gui.immersiverailroading:selector.quarters=%s 度弯道 @@ -110,6 +111,16 @@ gui.immersiverailroading:track.curvosity=%s 曲率 gui.immersiverailroading:track.position=位置锁定方式:%s gui.immersiverailroading:track.smoothing=垂直平滑:%s gui.immersiverailroading:track.direction=方向:%s +gui.immersiverailroading:track.far_radius=远端半径: +gui.immersiverailroading:track.is_forward=反转曲率:%s +gui.immersiverailroading:track.near_height_offset=近处高度偏移:%s +gui.immersiverailroading:track.far_height_offset=远处高度偏移:%s +gui.immersiverailroading:track.near_pitch=近处坡度(‰): +gui.immersiverailroading:track.far_pitch=远处坡度(‰): +gui.immersiverailroading:track.selected_way=当前分路:%s +gui.immersiverailroading:track.add_way=添加 +gui.immersiverailroading:track.delete_way=删除 +gui.immersiverailroading:track.insert_way=插入 gui.immersiverailroading:track.rail_bed=道床:%s gui.immersiverailroading:track.rail_bed_fill=道床填方:%s gui.immersiverailroading:track.place_blueprint_true=放置轨道蓝图 @@ -150,6 +161,8 @@ track.immersiverailroading:class.turntable=转车台 track.immersiverailroading:class.transfertable=移车台 track.immersiverailroading:class.switch=道岔 track.immersiverailroading:class.custom=自定义曲线 +track.immersiverailroading:class.cubicparabola=三次抛物缓和曲线 +track.immersiverailroading:class.multiswitch=多路道岔 track.immersiverailroading:position.fixed=对齐方块 track.immersiverailroading:position.pixels=对齐像素 @@ -161,6 +174,7 @@ track.immersiverailroading:smoothing.both=双向平滑 track.immersiverailroading:smoothing.near=仅近端平滑 track.immersiverailroading:smoothing.far=仅远端平滑 track.immersiverailroading:smoothing.neither=无平滑 +track.immersiverailroading:smoothing.pitch_locked_cubic=锁定角度 track.immersiverailroading:direction.none=不锁定 track.immersiverailroading:direction.left=锁定至右向