diff --git a/src/bridge_gui.cpp b/src/bridge_gui.cpp index 797ead1f51..2705f23592 100644 --- a/src/bridge_gui.cpp +++ b/src/bridge_gui.cpp @@ -24,6 +24,7 @@ #include "cmd_helper.h" #include "tunnelbridge_map.h" #include "road_gui.h" +#include "tilehighlight_func.h" #include "widgets/bridge_widget.h" @@ -72,6 +73,8 @@ void CcBuildBridge(const CommandCost &result, TileIndex end_tile, uint32 p1, uin DiagDirection start_direction = ReverseDiagDir(GetTunnelBridgeDirection(p1)); ConnectRoadToStructure(p1, start_direction); } + + StoreRailPlacementEndpoints(p1, end_tile, (TileX(p1) == TileX(end_tile)) ? TRACK_Y : TRACK_X, false); } /** Window class for handling the bridge-build GUI. */ diff --git a/src/lang/english.txt b/src/lang/english.txt index 156012108f..d098eec1b9 100644 --- a/src/lang/english.txt +++ b/src/lang/english.txt @@ -2316,6 +2316,7 @@ STR_RAIL_TOOLBAR_MAGLEV_CONSTRUCTION_CAPTION :Maglev Construc STR_RAIL_TOOLBAR_TOOLTIP_BUILD_RAILROAD_TRACK :{BLACK}Build railway track. Ctrl toggles build/remove for railway construction. Shift toggles building/showing cost estimate STR_RAIL_TOOLBAR_TOOLTIP_BUILD_AUTORAIL :{BLACK}Build railway track using the Autorail mode. Ctrl toggles build/remove for railway construction. Shift toggles building/showing cost estimate +STR_RAIL_TOOLBAR_TOOLTIP_BUILD_POLYRAIL :{BLACK}Build railway track using the Polyline mode. Ctrl toggles build/remove for railway construction. Shift toggles building/showing cost estimate STR_RAIL_TOOLBAR_TOOLTIP_BUILD_TRAIN_DEPOT_FOR_BUILDING :{BLACK}Build train depot (for buying and servicing trains). Shift toggles building/showing cost estimate STR_RAIL_TOOLBAR_TOOLTIP_CONVERT_RAIL_TO_WAYPOINT :{BLACK}Convert rail to waypoint. Ctrl enables joining waypoints. Shift toggles building/showing cost estimate STR_RAIL_TOOLBAR_TOOLTIP_BUILD_RAILROAD_STATION :{BLACK}Build railway station. Ctrl enables joining stations. Shift toggles building/showing cost estimate diff --git a/src/misc.cpp b/src/misc.cpp index d9d506993f..8d25a2434e 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -110,6 +110,7 @@ void InitializeGame(uint size_x, uint size_y, bool reset_date, bool reset_settin InitializeEconomy(); ResetObjectToPlace(); + ResetRailPlacementSnapping(); GamelogReset(); GamelogStartAction(GLAT_START); diff --git a/src/rail_cmd.cpp b/src/rail_cmd.cpp index d3093aa7b9..662b3bf626 100644 --- a/src/rail_cmd.cpp +++ b/src/rail_cmd.cpp @@ -44,6 +44,7 @@ typedef SmallVector TrainList; RailtypeInfo _railtypes[RAILTYPE_END]; +TileIndex _rail_track_endtile; ///< The end of a rail track; as hidden return from the rail build/remove command for GUI purposes. assert_compile(sizeof(_original_railtypes) <= sizeof(_railtypes)); @@ -416,6 +417,8 @@ CommandCost CmdBuildSingleRail(TileIndex tile, DoCommandFlag flags, uint32 p1, u Track track = Extract(p2); CommandCost cost(EXPENSES_CONSTRUCTION); + _rail_track_endtile = INVALID_TILE; + if (!ValParamRailtype(railtype) || !ValParamTrackOrientation(track)) return CMD_ERROR; Slope tileh = GetTileSlope(tile); @@ -432,7 +435,10 @@ CommandCost CmdBuildSingleRail(TileIndex tile, DoCommandFlag flags, uint32 p1, u ret = CheckTrackCombination(tile, trackbit, flags); if (ret.Succeeded()) ret = EnsureNoTrainOnTrack(tile, track); - if (ret.Failed()) return ret; + if (ret.Failed()) { + if (ret.GetErrorMessage() == STR_ERROR_ALREADY_BUILT) _rail_track_endtile = tile; + return ret; + } ret = CheckRailSlope(tileh, trackbit, GetTrackBits(tile), tile); if (ret.Failed()) return ret; @@ -521,6 +527,7 @@ CommandCost CmdBuildSingleRail(TileIndex tile, DoCommandFlag flags, uint32 p1, u } if (IsLevelCrossing(tile) && GetCrossingRailBits(tile) == trackbit) { + _rail_track_endtile = tile; return_cmd_error(STR_ERROR_ALREADY_BUILT); } /* FALL THROUGH */ @@ -560,6 +567,7 @@ CommandCost CmdBuildSingleRail(TileIndex tile, DoCommandFlag flags, uint32 p1, u } cost.AddCost(RailBuildCost(railtype)); + _rail_track_endtile = tile; return cost; } @@ -578,6 +586,8 @@ CommandCost CmdRemoveSingleRail(TileIndex tile, DoCommandFlag flags, uint32 p1, CommandCost cost(EXPENSES_CONSTRUCTION); bool crossing = false; + _rail_track_endtile = INVALID_TILE; + if (!ValParamTrackOrientation(track)) return CMD_ERROR; TrackBits trackbit = TrackToTrackBits(track); @@ -704,6 +714,7 @@ CommandCost CmdRemoveSingleRail(TileIndex tile, DoCommandFlag flags, uint32 p1, if (v != NULL) TryPathReserve(v, true); } + _rail_track_endtile = tile; return cost; } @@ -837,6 +848,8 @@ static CommandCost CmdRailTrackHelper(TileIndex tile, DoCommandFlag flags, uint3 bool remove = HasBit(p2, 7); RailType railtype = Extract(p2); + _rail_track_endtile = INVALID_TILE; + if ((!remove && !ValParamRailtype(railtype)) || !ValParamTrackOrientation(track)) return CMD_ERROR; if (p1 >= MapSize()) return CMD_ERROR; TileIndex end_tile = p1; @@ -848,10 +861,12 @@ static CommandCost CmdRailTrackHelper(TileIndex tile, DoCommandFlag flags, uint3 bool had_success = false; CommandCost last_error = CMD_ERROR; for (;;) { + TileIndex last_endtile = _rail_track_endtile; CommandCost ret = DoCommand(tile, remove ? 0 : railtype, TrackdirToTrack(trackdir), flags, remove ? CMD_REMOVE_SINGLE_RAIL : CMD_BUILD_SINGLE_RAIL); if (ret.Failed()) { last_error = ret; + if (_rail_track_endtile == INVALID_TILE) _rail_track_endtile = last_endtile; if (last_error.GetErrorMessage() != STR_ERROR_ALREADY_BUILT && !remove) { if (HasBit(p2, 8)) return last_error; break; diff --git a/src/rail_gui.cpp b/src/rail_gui.cpp index ff330b0352..4db7f986f8 100644 --- a/src/rail_gui.cpp +++ b/src/rail_gui.cpp @@ -52,9 +52,14 @@ static bool _convert_signal_button; ///< convert signal button in the s static SignalVariant _cur_signal_variant; ///< set the signal variant (for signal GUI) static SignalType _cur_signal_type; ///< set the signal type (for signal GUI) +extern TileIndex _rail_track_endtile; // rail_cmd.cpp + /* Map the setting: default_signal_type to the corresponding signal type */ static const SignalType _default_signal_type[] = {SIGTYPE_NORMAL, SIGTYPE_PBS, SIGTYPE_PBS_ONEWAY}; +static const int HOTKEY_POLYRAIL = 0x1000; +static const int HOTKEY_NEW_POLYRAIL = 0x1001; + struct RailStationGUISettings { Axis orientation; ///< Currently selected rail station orientation @@ -91,13 +96,20 @@ void CcPlaySound1E(const CommandCost &result, TileIndex tile, uint32 p1, uint32 if (result.Succeeded() && _settings_client.sound.confirm) SndPlayTileFx(SND_20_SPLAT_RAIL, tile); } -static void GenericPlaceRail(TileIndex tile, int cmd) +static CommandContainer GenericPlaceRailCmd(TileIndex tile, Track track) { - DoCommandP(tile, _cur_railtype, cmd, - _remove_button_clicked ? - CMD_REMOVE_SINGLE_RAIL | CMD_MSG(STR_ERROR_CAN_T_REMOVE_RAILROAD_TRACK) : - CMD_BUILD_SINGLE_RAIL | CMD_MSG(STR_ERROR_CAN_T_BUILD_RAILROAD_TRACK), - CcPlaySound1E); + CommandContainer ret = { + tile, // tile + _cur_railtype, // p1 + track, // p2 + _remove_button_clicked ? + CMD_REMOVE_SINGLE_RAIL | CMD_MSG(STR_ERROR_CAN_T_REMOVE_RAILROAD_TRACK) : + CMD_BUILD_SINGLE_RAIL | CMD_MSG(STR_ERROR_CAN_T_BUILD_RAILROAD_TRACK), // cmd + CcPlaySound1E, // callback + "" // text + }; + + return ret; } /** @@ -276,6 +288,7 @@ void CcBuildRailTunnel(const CommandCost &result, TileIndex tile, uint32 p1, uin if (result.Succeeded()) { if (_settings_client.sound.confirm) SndPlayTileFx(SND_20_SPLAT_RAIL, tile); if (!_settings_client.gui.persistent_buildingtools) ResetObjectToPlace(); + StoreRailPlacementEndpoints(tile, _build_tunnel_endtile, TileX(tile) == TileX(_build_tunnel_endtile) ? TRACK_Y : TRACK_X, false); } else { SetRedErrorSquare(_build_tunnel_endtile); } @@ -305,7 +318,7 @@ static bool RailToolbar_CtrlChanged(Window *w) /* allow ctrl to switch remove mode only for these widgets */ for (uint i = WID_RAT_BUILD_NS; i <= WID_RAT_BUILD_STATION; i++) { - if ((i <= WID_RAT_AUTORAIL || i >= WID_RAT_BUILD_WAYPOINT) && w->IsWidgetLowered(i)) { + if ((i <= WID_RAT_POLYRAIL || i >= WID_RAT_BUILD_WAYPOINT) && w->IsWidgetLowered(i)) { ToggleRailButton_Remove(w); return true; } @@ -349,25 +362,45 @@ static void BuildRailClick_Remove(Window *w) } } -static void DoRailroadTrack(int mode) +static CommandContainer DoRailroadTrackCmd(TileIndex start_tile, TileIndex end_tile, Track track) { - DoCommandP(TileVirtXY(_thd.selstart.x, _thd.selstart.y), TileVirtXY(_thd.selend.x, _thd.selend.y), _cur_railtype | (mode << 4), - _remove_button_clicked ? - CMD_REMOVE_RAILROAD_TRACK | CMD_MSG(STR_ERROR_CAN_T_REMOVE_RAILROAD_TRACK) : - CMD_BUILD_RAILROAD_TRACK | CMD_MSG(STR_ERROR_CAN_T_BUILD_RAILROAD_TRACK), - CcPlaySound1E); + CommandContainer ret = { + start_tile, // tile + end_tile, // p1 + _cur_railtype | (track << 4), // p2 + _remove_button_clicked ? + CMD_REMOVE_RAILROAD_TRACK | CMD_MSG(STR_ERROR_CAN_T_REMOVE_RAILROAD_TRACK) : + CMD_BUILD_RAILROAD_TRACK | CMD_MSG(STR_ERROR_CAN_T_BUILD_RAILROAD_TRACK), // cmd + CcPlaySound1E, // callback + "" // text + }; + + return ret; } static void HandleAutodirPlacement() { - int trackstat = _thd.drawstyle & HT_DIR_MASK; // 0..5 + Track track = (Track)(_thd.drawstyle & HT_DIR_MASK); // 0..5 + TileIndex start_tile = TileVirtXY(_thd.selstart.x, _thd.selstart.y); + TileIndex end_tile = TileVirtXY(_thd.selend.x, _thd.selend.y); - if (_thd.drawstyle & HT_RAIL) { // one tile case - GenericPlaceRail(TileVirtXY(_thd.selend.x, _thd.selend.y), trackstat); - return; + CommandContainer cmd = (_thd.drawstyle & HT_RAIL) ? + GenericPlaceRailCmd(end_tile, track) : // one tile case + DoRailroadTrackCmd(start_tile, end_tile, track); // multitile selection + + /* When overbuilding existing tracks in polyline mode we just want to move the + * snap point without altering the user with the "already built" error. Don't + * execute the command right away, firstly check if tracks are being overbuilt. */ + if (!(_thd.place_mode & HT_POLY) || _shift_pressed || + DoCommand(&cmd, DC_AUTO | DC_NO_WATER).GetErrorMessage() != STR_ERROR_ALREADY_BUILT) { + /* place tracks */ + if (!DoCommandP(&cmd)) return; } - DoRailroadTrack(trackstat); + /* save new snap points for the polyline tool */ + if (!_shift_pressed && _rail_track_endtile != INVALID_TILE) { + StoreRailPlacementEndpoints(start_tile, _rail_track_endtile, track, true); + } } /** @@ -449,6 +482,7 @@ struct BuildRailToolbarWindow : Window { this->GetWidget(WID_RAT_BUILD_EW)->widget_data = rti->gui_sprites.build_ew_rail; this->GetWidget(WID_RAT_BUILD_Y)->widget_data = rti->gui_sprites.build_y_rail; this->GetWidget(WID_RAT_AUTORAIL)->widget_data = rti->gui_sprites.auto_rail; + this->GetWidget(WID_RAT_POLYRAIL)->widget_data = rti->gui_sprites.auto_rail; this->GetWidget(WID_RAT_BUILD_DEPOT)->widget_data = rti->gui_sprites.build_depot; this->GetWidget(WID_RAT_CONVERT_RAIL)->widget_data = rti->gui_sprites.convert_rail; this->GetWidget(WID_RAT_BUILD_TUNNEL)->widget_data = rti->gui_sprites.build_tunnel; @@ -477,6 +511,7 @@ struct BuildRailToolbarWindow : Window { case WID_RAT_BUILD_EW: case WID_RAT_BUILD_Y: case WID_RAT_AUTORAIL: + case WID_RAT_POLYRAIL: case WID_RAT_BUILD_WAYPOINT: case WID_RAT_BUILD_STATION: case WID_RAT_BUILD_SIGNALS: @@ -508,6 +543,15 @@ struct BuildRailToolbarWindow : Window { } } + virtual void DrawWidget(const Rect &r, int widget) const + { + if (widget == WID_RAT_POLYRAIL) { + Dimension d = GetSpriteSize(SPR_BLOT); + uint offset = this->IsWidgetLowered(WID_RAT_POLYRAIL) ? 1 : 0; + DrawSprite(SPR_BLOT, PALETTE_TO_GREY, (r.left + r.right - d.width) / 2 + offset, (r.top + r.bottom - d.height) / 2 + offset); + } + } + virtual void OnClick(Point pt, int widget, int click_count) { if (widget < WID_RAT_BUILD_NS) return; @@ -539,6 +583,40 @@ struct BuildRailToolbarWindow : Window { this->last_user_action = widget; break; + case WID_RAT_POLYRAIL: { + bool was_snap = CurrentlySnappingRailPlacement(); + bool was_open = this->IsWidgetLowered(WID_RAT_POLYRAIL); + bool do_snap; + bool do_open; + /* "polyrail" hotkey - activate polyline tool in snapping mode, close the tool if snapping mode is already active + * "new_polyrail" hotkey - activate polyline tool in non-snapping (new line) mode, close the tool if non-snapping mode is already active + * button ctrl-clicking - switch between snapping and non-snapping modes, open the tool in non-snapping mode if it is closed + * button clicking - open the tool in non-snapping mode, close the tool if it is opened */ + if (this->last_user_action == HOTKEY_POLYRAIL) { + do_snap = true; + do_open = !was_open || !was_snap; + } else if (this->last_user_action == HOTKEY_NEW_POLYRAIL) { + do_snap = false; + do_open = !was_open || was_snap; + } else if (_ctrl_pressed) { + do_snap = !was_open || !was_snap; + do_open = true; + } else { + do_snap = false; + do_open = !was_open; + } + /* close the tool explicitly so it can be re-opened in different snapping mode */ + if (was_open) ResetObjectToPlace(); + /* open the tool in desired mode */ + if (do_open && HandlePlacePushButton(this, WID_RAT_POLYRAIL, GetRailTypeInfo(railtype)->cursor.autorail, do_snap ? (HT_RAIL | HT_POLY) : (HT_RAIL | HT_NEW_POLY))) { + /* if we are re-opening the tool but we couldn't switch the snapping + * then close the tool instead of appearing to be doing nothing */ + if (was_open && do_snap != CurrentlySnappingRailPlacement()) ResetObjectToPlace(); + } + this->last_user_action = WID_RAT_POLYRAIL; + break; + } + case WID_RAT_DEMOLISH: HandlePlacePushButton(this, WID_RAT_DEMOLISH, ANIMCURSOR_DEMOLISH, HT_RECT | HT_DIAGONAL); this->last_user_action = widget; @@ -603,7 +681,15 @@ struct BuildRailToolbarWindow : Window { virtual EventState OnHotkey(int hotkey) { MarkTileDirtyByTile(TileVirtXY(_thd.pos.x, _thd.pos.y)); // redraw tile selection - return Window::OnHotkey(hotkey); + + if (hotkey == HOTKEY_POLYRAIL || hotkey == HOTKEY_NEW_POLYRAIL) { + /* Indicate to the OnClick that the action comes from a hotkey rather + * then from a click and that the CTRL state should be ignored. */ + this->last_user_action = hotkey; + hotkey = WID_RAT_POLYRAIL; + } + + return this->Window::OnHotkey(hotkey); } virtual void OnPlaceObject(Point pt, TileIndex tile) @@ -626,6 +712,7 @@ struct BuildRailToolbarWindow : Window { break; case WID_RAT_AUTORAIL: + case WID_RAT_POLYRAIL: VpStartPlaceSizing(tile, VPM_RAILDIRS, DDSP_PLACE_RAIL); break; @@ -773,6 +860,8 @@ static EventState RailToolbarGlobalHotkeys(int hotkey) } const uint16 _railtoolbar_autorail_keys[] = {'5', 'A' | WKC_GLOBAL_HOTKEY, 0}; +const uint16 _railtoolbar_polyrail_keys[] = {'5' | WKC_CTRL, 'A' | WKC_CTRL | WKC_GLOBAL_HOTKEY, 0}; +const uint16 _railtoolbar_new_poly_keys[] = {'5' | WKC_CTRL | WKC_SHIFT, 'A' | WKC_CTRL | WKC_SHIFT | WKC_GLOBAL_HOTKEY, 0}; static Hotkey railtoolbar_hotkeys[] = { Hotkey('1', "build_ns", WID_RAT_BUILD_NS), @@ -780,6 +869,8 @@ static Hotkey railtoolbar_hotkeys[] = { Hotkey('3', "build_ew", WID_RAT_BUILD_EW), Hotkey('4', "build_y", WID_RAT_BUILD_Y), Hotkey(_railtoolbar_autorail_keys, "autorail", WID_RAT_AUTORAIL), + Hotkey(_railtoolbar_polyrail_keys, "polyrail", HOTKEY_POLYRAIL), + Hotkey(_railtoolbar_new_poly_keys, "new_polyrail", HOTKEY_NEW_POLYRAIL), Hotkey('6', "demolish", WID_RAT_DEMOLISH), Hotkey('7', "depot", WID_RAT_BUILD_DEPOT), Hotkey('8', "waypoint", WID_RAT_BUILD_WAYPOINT), @@ -810,6 +901,8 @@ static const NWidgetPart _nested_build_rail_widgets[] = { SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_RAIL_NW, STR_RAIL_TOOLBAR_TOOLTIP_BUILD_RAILROAD_TRACK), NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_RAT_AUTORAIL), SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_AUTORAIL, STR_RAIL_TOOLBAR_TOOLTIP_BUILD_AUTORAIL), + NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_RAT_POLYRAIL), + SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_AUTORAIL, STR_RAIL_TOOLBAR_TOOLTIP_BUILD_POLYRAIL), NWidget(WWT_PANEL, COLOUR_DARK_GREEN), SetMinimalSize(4, 22), SetDataTip(0x0, STR_NULL), EndContainer(), diff --git a/src/script/api/game/game_window.hpp.sq b/src/script/api/game/game_window.hpp.sq index 80b43c08b3..13a46389cb 100644 --- a/src/script/api/game/game_window.hpp.sq +++ b/src/script/api/game/game_window.hpp.sq @@ -904,6 +904,7 @@ void SQGSWindow_Register(Squirrel *engine) SQGSWindow.DefSQConst(engine, ScriptWindow::WID_RAT_BUILD_EW, "WID_RAT_BUILD_EW"); SQGSWindow.DefSQConst(engine, ScriptWindow::WID_RAT_BUILD_Y, "WID_RAT_BUILD_Y"); SQGSWindow.DefSQConst(engine, ScriptWindow::WID_RAT_AUTORAIL, "WID_RAT_AUTORAIL"); + SQGSWindow.DefSQConst(engine, ScriptWindow::WID_RAT_POLYRAIL, "WID_RAT_POLYRAIL"); SQGSWindow.DefSQConst(engine, ScriptWindow::WID_RAT_DEMOLISH, "WID_RAT_DEMOLISH"); SQGSWindow.DefSQConst(engine, ScriptWindow::WID_RAT_BUILD_DEPOT, "WID_RAT_BUILD_DEPOT"); SQGSWindow.DefSQConst(engine, ScriptWindow::WID_RAT_BUILD_WAYPOINT, "WID_RAT_BUILD_WAYPOINT"); diff --git a/src/script/api/script_window.hpp b/src/script/api/script_window.hpp index f9d6591083..6f4b8593b1 100644 --- a/src/script/api/script_window.hpp +++ b/src/script/api/script_window.hpp @@ -1982,6 +1982,7 @@ public: WID_RAT_BUILD_EW = ::WID_RAT_BUILD_EW, ///< Build rail along the game view X axis. WID_RAT_BUILD_Y = ::WID_RAT_BUILD_Y, ///< Build rail along the game grid Y axis. WID_RAT_AUTORAIL = ::WID_RAT_AUTORAIL, ///< Autorail tool. + WID_RAT_POLYRAIL = ::WID_RAT_POLYRAIL, ///< Polyline rail tool. WID_RAT_DEMOLISH = ::WID_RAT_DEMOLISH, ///< Destroy something with dynamite! WID_RAT_BUILD_DEPOT = ::WID_RAT_BUILD_DEPOT, ///< Build a depot. WID_RAT_BUILD_WAYPOINT = ::WID_RAT_BUILD_WAYPOINT, ///< Build a waypoint. diff --git a/src/tilehighlight_func.h b/src/tilehighlight_func.h index 3edef509a2..837928d95f 100644 --- a/src/tilehighlight_func.h +++ b/src/tilehighlight_func.h @@ -14,6 +14,7 @@ #include "gfx_type.h" #include "tilehighlight_type.h" +#include "track_type.h" void PlaceProc_DemolishArea(TileIndex tile); bool GUIPlaceProcDragXY(ViewportDragDropSelectionProcess proc, TileIndex start_tile, TileIndex end_tile); @@ -30,6 +31,10 @@ void VpSetPlaceSizingLimit(int limit); void UpdateTileSelection(); +void StoreRailPlacementEndpoints(TileIndex start_tile, TileIndex end_tile, Track start_track, bool bidirectional = true); +void ResetRailPlacementSnapping(); +bool CurrentlySnappingRailPlacement(); + extern TileHighlightData _thd; #endif /* TILEHIGHLIGHT_FUNC_H */ diff --git a/src/tilehighlight_type.h b/src/tilehighlight_type.h index 3d64248dff..ffe2d4beed 100644 --- a/src/tilehighlight_type.h +++ b/src/tilehighlight_type.h @@ -28,6 +28,8 @@ enum HighLightStyle { HT_RAIL = 0x080, ///< autorail (one piece), lower bits: direction HT_VEHICLE = 0x100, ///< vehicle is accepted as target as well (bitmask) HT_DIAGONAL = 0x200, ///< Also allow 'diagonal rectangles'. Only usable in combination with #HT_RECT or #HT_POINT. + HT_POLY = 0x400, ///< polyline mode; connect highlighted track with previous one + HT_NEW_POLY = 0xC00, ///< start completly new polyline; implies #HT_POLY HT_DRAG_MASK = 0x0F8, ///< Mask for the tile drag-type modes. /* lower bits (used with HT_LINE and HT_RAIL): @@ -54,11 +56,15 @@ struct TileHighlightData { Point new_pos; ///< New value for \a pos; used to determine whether to redraw the selection. Point new_size; ///< New value for \a size; used to determine whether to redraw the selection. + Point new_offs; ///< New value for \a offs; used to determine whether to redraw the selection. Point new_outersize; ///< New value for \a outersize; used to determine whether to redraw the selection. byte dirty; ///< Whether the build station window needs to redraw due to the changed selection. Point selstart; ///< The location where the dragging started. Point selend; ///< The location where the drag currently ends. + Point selstart2; ///< The location where the second segment of a polyline track starts. + Point selend2; ///< The location where the second segment of a polyline track ends. + HighLightStyle dir2; ///< Direction of the second segment of a polyline track, HT_DIR_END if second segment is not selected. HT_LINE drawstyle. byte sizelimit; ///< Whether the selection is limited in length, and what the maximum length is. HighLightStyle drawstyle; ///< Lower bits 0-3 are reserved for detailed highlight information. diff --git a/src/viewport.cpp b/src/viewport.cpp index 06c0dbe8ef..a246064dbe 100644 --- a/src/viewport.cpp +++ b/src/viewport.cpp @@ -69,6 +69,8 @@ */ #include "stdafx.h" +#include "core/math_func.hpp" +#include "core/smallvec_type.hpp" #include "landscape.h" #include "viewport_func.h" #include "station_base.h" @@ -149,6 +151,34 @@ typedef SmallVector StringSpriteToDrawVector; typedef SmallVector ParentSpriteToDrawVector; typedef SmallVector ChildScreenSpriteToDrawVector; +enum RailSnapMode { + RSM_NO_SNAP, + RSM_SNAP_TO_TILE, + RSM_SNAP_TO_RAIL, +}; + +/** + * Snapping point for a track. + * + * Point where a track (rail/road/other) can be snapped to while selecting tracks with polyline + * tool (HT_POLY). Besides of x/y coordinates expressed in tile "units" it contains a set of + * allowed line directions. + */ +struct LineSnapPoint : Point { + uint8 dirs; ///< Allowed line directions, set of #Direction bits. +}; + +typedef SmallVector LineSnapPoints; ///< Set of snapping points + +/** Coordinates of a polyline track made of 2 connected line segments. */ +struct Polyline { + Point start; ///< The point where the first segment starts (as given in LineSnapPoint). + Direction first_dir; ///< Direction of the first line segment. + uint first_len; ///< Length of the first segment - number of track pieces. + Direction second_dir; ///< Direction of the second line segment. + uint second_len; ///< Length of the second segment - number of track pieces. +}; + /** Data structure storing rendering information */ struct ViewportDrawer { DrawPixelInfo dpi; @@ -180,6 +210,16 @@ bool _draw_dirty_blocks = false; uint _dirty_block_colour = 0; static VpSpriteSorter _vp_sprite_sorter = NULL; +static RailSnapMode _rail_snap_mode = RSM_NO_SNAP; ///< Type of rail track snapping (polyline tool). +static LineSnapPoints _tile_snap_points; ///< Tile to which a rail track will be snapped to (polyline tool). +static LineSnapPoints _rail_snap_points; ///< Set of points where a rail track will be snapped to (polyline tool). +static LineSnapPoint _current_snap_lock; ///< Start point and direction at which selected track is locked on currently (while dragging in polyline mode). + +static RailSnapMode GetRailSnapMode(); +static void SetRailSnapMode(RailSnapMode mode); +static TileIndex GetRailSnapTile(); +static void SetRailSnapTile(TileIndex tile); + static Point MapXYZToViewport(const ViewPort *vp, int x, int y, int z) { Point p = RemapCoords(x, y, z); @@ -806,13 +846,17 @@ static bool IsInRangeInclusive(int begin, int end, int check) } /** - * Checks whether a point is inside the selected a diagonal rectangle given by _thd.size and _thd.pos + * Checks whether a point is inside the selected rectangle given by _thd.size, _thd.pos and _thd.diagonal * @param x The x coordinate of the point to be checked. * @param y The y coordinate of the point to be checked. * @return True if the point is inside the rectangle, else false. */ -bool IsInsideRotatedRectangle(int x, int y) +static bool IsInsideSelectedRectangle(int x, int y) { + if (!_thd.diagonal) { + return IsInsideBS(x, _thd.pos.x, _thd.size.x) && IsInsideBS(y, _thd.pos.y, _thd.size.y); + } + int dist_a = (_thd.size.x + _thd.size.y); // Rotated coordinate system for selected rectangle. int dist_b = (_thd.size.x - _thd.size.y); // We don't have to divide by 2. It's all relative! int a = ((x - _thd.pos.x) + (y - _thd.pos.y)); // Rotated coordinate system for the point under scrutiny. @@ -929,34 +973,26 @@ static void DrawTileSelectionRect(const TileInfo *ti, PaletteID pal) DrawSelectionSprite(sel, pal, ti, 7, FOUNDATION_PART_NORMAL); } -static bool IsPartOfAutoLine(int px, int py) +static HighLightStyle GetPartOfAutoLine(int px, int py, const Point &selstart, const Point &selend, HighLightStyle dir) { - px -= _thd.selstart.x; - py -= _thd.selstart.y; + if (!IsInRangeInclusive(selstart.x & ~TILE_UNIT_MASK, selend.x & ~TILE_UNIT_MASK, px)) return HT_DIR_END; + if (!IsInRangeInclusive(selstart.y & ~TILE_UNIT_MASK, selend.y & ~TILE_UNIT_MASK, py)) return HT_DIR_END; - if ((_thd.drawstyle & HT_DRAG_MASK) != HT_LINE) return false; + px -= selstart.x & ~TILE_UNIT_MASK; + py -= selstart.y & ~TILE_UNIT_MASK; - switch (_thd.drawstyle & HT_DIR_MASK) { - case HT_DIR_X: return py == 0; // x direction - case HT_DIR_Y: return px == 0; // y direction - case HT_DIR_HU: return px == -py || px == -py - 16; // horizontal upper - case HT_DIR_HL: return px == -py || px == -py + 16; // horizontal lower - case HT_DIR_VL: return px == py || px == py + 16; // vertical left - case HT_DIR_VR: return px == py || px == py - 16; // vertical right - default: - NOT_REACHED(); + switch (dir) { + case HT_DIR_X: return (py == 0) ? HT_DIR_X : HT_DIR_END; + case HT_DIR_Y: return (px == 0) ? HT_DIR_Y : HT_DIR_END; + case HT_DIR_HU: return (px == -py) ? HT_DIR_HU : (px == -py - (int)TILE_SIZE) ? HT_DIR_HL : HT_DIR_END; + case HT_DIR_HL: return (px == -py) ? HT_DIR_HL : (px == -py + (int)TILE_SIZE) ? HT_DIR_HU : HT_DIR_END; + case HT_DIR_VL: return (px == py) ? HT_DIR_VL : (px == py + (int)TILE_SIZE) ? HT_DIR_VR : HT_DIR_END; + case HT_DIR_VR: return (px == py) ? HT_DIR_VR : (px == py - (int)TILE_SIZE) ? HT_DIR_VL : HT_DIR_END; + default: NOT_REACHED(); break; } -} -/* [direction][side] */ -static const HighLightStyle _autorail_type[6][2] = { - { HT_DIR_X, HT_DIR_X }, - { HT_DIR_Y, HT_DIR_Y }, - { HT_DIR_HU, HT_DIR_HL }, - { HT_DIR_HL, HT_DIR_HU }, - { HT_DIR_VL, HT_DIR_VR }, - { HT_DIR_VR, HT_DIR_VL } -}; + return HT_DIR_END; +} #include "table/autorail.h" @@ -964,18 +1000,18 @@ static const HighLightStyle _autorail_type[6][2] = { * Draws autorail highlights. * * @param *ti TileInfo Tile that is being drawn - * @param autorail_type Offset into _AutorailTilehSprite[][] + * @param autorail_type \c HT_DIR_XXX, offset into _AutorailTilehSprite[][] + * @param pal Palette to use, -1 to autodetect */ -static void DrawAutorailSelection(const TileInfo *ti, uint autorail_type) +static void DrawAutorailSelection(const TileInfo *ti, HighLightStyle autorail_type, PaletteID pal = -1) { SpriteID image; - PaletteID pal; int offset; FoundationPart foundation_part = FOUNDATION_PART_NORMAL; Slope autorail_tileh = RemoveHalftileSlope(ti->tileh); if (IsHalftileSlope(ti->tileh)) { - static const uint _lower_rail[4] = { 5U, 2U, 4U, 3U }; + static const HighLightStyle _lower_rail[CORNER_END] = { HT_DIR_VR, HT_DIR_HU, HT_DIR_VL, HT_DIR_HL }; // CORNER_W, CORNER_S, CORNER_E, CORNER_N Corner halftile_corner = GetHalftileSlopeCorner(ti->tileh); if (autorail_type != _lower_rail[halftile_corner]) { foundation_part = FOUNDATION_PART_HALFTILE; @@ -984,16 +1020,17 @@ static void DrawAutorailSelection(const TileInfo *ti, uint autorail_type) } } + assert(autorail_type < HT_DIR_END); offset = _AutorailTilehSprite[autorail_tileh][autorail_type]; if (offset >= 0) { image = SPR_AUTORAIL_BASE + offset; - pal = PAL_NONE; + if (pal == (PaletteID)-1) pal = _thd.make_square_red ? PALETTE_SEL_TILE_RED : PAL_NONE; } else { image = SPR_AUTORAIL_BASE - offset; - pal = PALETTE_SEL_TILE_RED; + if (pal == (PaletteID)-1) pal = PALETTE_SEL_TILE_RED; } - DrawSelectionSprite(image, _thd.make_square_red ? PALETTE_SEL_TILE_RED : pal, ti, 7, foundation_part); + DrawSelectionSprite(image, pal, ti, 7, foundation_part); } /** @@ -1006,66 +1043,61 @@ static void DrawTileSelection(const TileInfo *ti) bool is_redsq = _thd.redsq == ti->tile; if (is_redsq) DrawTileSelectionRect(ti, PALETTE_TILE_RED_PULSATING); - /* No tile selection active? */ - if ((_thd.drawstyle & HT_DRAG_MASK) == HT_NONE) return; + switch (_thd.drawstyle & HT_DRAG_MASK) { + default: break; // No tile selection active? - if (_thd.diagonal) { // We're drawing a 45 degrees rotated (diagonal) rectangle - if (IsInsideRotatedRectangle((int)ti->x, (int)ti->y)) goto draw_inner; - return; - } - - /* Inside the inner area? */ - if (IsInsideBS(ti->x, _thd.pos.x, _thd.size.x) && - IsInsideBS(ti->y, _thd.pos.y, _thd.size.y)) { -draw_inner: - if (_thd.drawstyle & HT_RECT) { - if (!is_redsq) DrawTileSelectionRect(ti, _thd.make_square_red ? PALETTE_SEL_TILE_RED : PAL_NONE); - } else if (_thd.drawstyle & HT_POINT) { - /* Figure out the Z coordinate for the single dot. */ - int z = 0; - FoundationPart foundation_part = FOUNDATION_PART_NORMAL; - if (ti->tileh & SLOPE_N) { - z += TILE_HEIGHT; - if (RemoveHalftileSlope(ti->tileh) == SLOPE_STEEP_N) z += TILE_HEIGHT; - } - if (IsHalftileSlope(ti->tileh)) { - Corner halftile_corner = GetHalftileSlopeCorner(ti->tileh); - if ((halftile_corner == CORNER_W) || (halftile_corner == CORNER_E)) z += TILE_HEIGHT; - if (halftile_corner != CORNER_S) { - foundation_part = FOUNDATION_PART_HALFTILE; - if (IsSteepSlope(ti->tileh)) z -= TILE_HEIGHT; + case HT_RECT: + if (!is_redsq) { + if (IsInsideSelectedRectangle(ti->x, ti->y)) { + DrawTileSelectionRect(ti, _thd.make_square_red ? PALETTE_SEL_TILE_RED : PAL_NONE); + } else if (_thd.outersize.x > 0 && + /* Check if it's inside the outer area? */ + IsInsideBS(ti->x, _thd.pos.x + _thd.offs.x, _thd.size.x + _thd.outersize.x) && + IsInsideBS(ti->y, _thd.pos.y + _thd.offs.y, _thd.size.y + _thd.outersize.y)) { + /* Draw a blue rect. */ + DrawTileSelectionRect(ti, PALETTE_SEL_TILE_BLUE); } } - DrawSelectionSprite(_cur_dpi->zoom <= ZOOM_LVL_DETAIL ? SPR_DOT : SPR_DOT_SMALL, PAL_NONE, ti, z, foundation_part); - } else if (_thd.drawstyle & HT_RAIL) { - /* autorail highlight piece under cursor */ - HighLightStyle type = _thd.drawstyle & HT_DIR_MASK; - assert(type < HT_DIR_END); - DrawAutorailSelection(ti, _autorail_type[type][0]); - } else if (IsPartOfAutoLine(ti->x, ti->y)) { - /* autorail highlighting long line */ - HighLightStyle dir = _thd.drawstyle & HT_DIR_MASK; - uint side; + break; - if (dir == HT_DIR_X || dir == HT_DIR_Y) { - side = 0; - } else { - TileIndex start = TileVirtXY(_thd.selstart.x, _thd.selstart.y); - side = Delta(Delta(TileX(start), TileX(ti->tile)), Delta(TileY(start), TileY(ti->tile))); + case HT_POINT: + if (IsInsideSelectedRectangle(ti->x, ti->y)) { + /* Figure out the Z coordinate for the single dot. */ + int z = 0; + FoundationPart foundation_part = FOUNDATION_PART_NORMAL; + if (ti->tileh & SLOPE_N) { + z += TILE_HEIGHT; + if (RemoveHalftileSlope(ti->tileh) == SLOPE_STEEP_N) z += TILE_HEIGHT; + } + if (IsHalftileSlope(ti->tileh)) { + Corner halftile_corner = GetHalftileSlopeCorner(ti->tileh); + if ((halftile_corner == CORNER_W) || (halftile_corner == CORNER_E)) z += TILE_HEIGHT; + if (halftile_corner != CORNER_S) { + foundation_part = FOUNDATION_PART_HALFTILE; + if (IsSteepSlope(ti->tileh)) z -= TILE_HEIGHT; + } + } + DrawSelectionSprite(_cur_dpi->zoom <= ZOOM_LVL_DETAIL ? SPR_DOT : SPR_DOT_SMALL, PAL_NONE, ti, z, foundation_part); } + break; - DrawAutorailSelection(ti, _autorail_type[dir][side]); + case HT_RAIL: + if (ti->tile == TileVirtXY(_thd.pos.x, _thd.pos.y)) { + assert((_thd.drawstyle & HT_DIR_MASK) < HT_DIR_END); + DrawAutorailSelection(ti, _thd.drawstyle & HT_DIR_MASK); + } + break; + + case HT_LINE: { + HighLightStyle type = GetPartOfAutoLine(ti->x, ti->y, _thd.selstart, _thd.selend, _thd.drawstyle & HT_DIR_MASK); + if (type < HT_DIR_END) { + DrawAutorailSelection(ti, type); + } else if (_thd.dir2 < HT_DIR_END) { + type = GetPartOfAutoLine(ti->x, ti->y, _thd.selstart2, _thd.selend2, _thd.dir2); + if (type < HT_DIR_END) DrawAutorailSelection(ti, type, PALETTE_SEL_TILE_BLUE); + } + break; } - return; - } - - /* Check if it's inside the outer area? */ - if (!is_redsq && _thd.outersize.x > 0 && - IsInsideBS(ti->x, _thd.pos.x + _thd.offs.x, _thd.size.x + _thd.outersize.x) && - IsInsideBS(ti->y, _thd.pos.y + _thd.offs.y, _thd.size.y + _thd.outersize.y)) { - /* Draw a blue rect. */ - DrawTileSelectionRect(ti, PALETTE_SEL_TILE_BLUE); - return; } } @@ -2265,7 +2297,7 @@ static void SetSelectionTilesDirty() int x_start = _thd.pos.x; int y_start = _thd.pos.y; - if (_thd.outersize.x != 0) { + if (_thd.outersize.x != 0 || _thd.outersize.y != 0) { x_size += _thd.outersize.x; x_start += _thd.offs.x; y_size += _thd.outersize.y; @@ -2488,7 +2520,7 @@ static void PlaceObject() } -bool HandleViewportClicked(const ViewPort *vp, int x, int y) +bool HandleViewportClicked(const ViewPort *vp, int x, int y, bool double_click) { const Vehicle *v = CheckClickOnVehicle(vp, x, y); @@ -2498,6 +2530,18 @@ bool HandleViewportClicked(const ViewPort *vp, int x, int y) /* Vehicle placement mode already handled above. */ if ((_thd.place_mode & HT_DRAG_MASK) != HT_NONE) { + if (_thd.place_mode & HT_POLY) { + /* In polyline mode double-clicking on a single white line, finishes current polyline. + * If however the user double-clicks on a line that has a white and a blue section, + * both lines (white and blue) will be constructed consecutively. */ + static bool stop_snap_on_double_click = false; + if (double_click && stop_snap_on_double_click) { + SetRailSnapMode(RSM_NO_SNAP); + return true; + } + stop_snap_on_double_click = !(_thd.drawstyle & HT_LINE) || (_thd.dir2 == HT_DIR_END); + } + PlaceObject(); return true; } @@ -2624,8 +2668,8 @@ void SetTileSelectSize(int w, int h) void SetTileSelectBigSize(int ox, int oy, int sx, int sy) { - _thd.offs.x = ox * TILE_SIZE; - _thd.offs.y = oy * TILE_SIZE; + _thd.new_offs.x = ox * TILE_SIZE; + _thd.new_offs.y = oy * TILE_SIZE; _thd.new_outersize.x = sx * TILE_SIZE; _thd.new_outersize.y = sy * TILE_SIZE; } @@ -2665,7 +2709,36 @@ Window *TileHighlightData::GetCallbackWnd() return FindWindowById(this->window_class, this->window_number); } +static HighLightStyle CalcPolyrailDrawstyle(Point pt, bool dragging); +static inline void CalcNewPolylineOutersize() +{ + /* use the 'outersize' to mark the second (blue) part of a polyline selection */ + if (_thd.dir2 < HT_DIR_END) { + /* get bounds of the second part */ + int outer_x1 = _thd.selstart2.x & ~TILE_UNIT_MASK; + int outer_y1 = _thd.selstart2.y & ~TILE_UNIT_MASK; + int outer_x2 = _thd.selend2.x & ~TILE_UNIT_MASK; + int outer_y2 = _thd.selend2.y & ~TILE_UNIT_MASK; + if (outer_x1 > outer_x2) Swap(outer_x1, outer_x2); + if (outer_y1 > outer_y2) Swap(outer_y1, outer_y2); + /* include the first part */ + outer_x1 = min(outer_x1, _thd.new_pos.x); + outer_y1 = min(outer_y1, _thd.new_pos.y); + outer_x2 = max(outer_x2, _thd.new_pos.x + _thd.new_size.x - TILE_SIZE); + outer_y2 = max(outer_y2, _thd.new_pos.y + _thd.new_size.y - TILE_SIZE); + /* write new values */ + _thd.new_offs.x = outer_x1 - _thd.new_pos.x; + _thd.new_offs.y = outer_y1 - _thd.new_pos.y; + _thd.new_outersize.x = outer_x2 - outer_x1 + TILE_SIZE - _thd.new_size.x; + _thd.new_outersize.y = outer_y2 - outer_y1 + TILE_SIZE - _thd.new_size.y; + } else { + _thd.new_offs.x = 0; + _thd.new_offs.y = 0; + _thd.new_outersize.x = 0; + _thd.new_outersize.y = 0; + } +} /** * Updates tile highlighting for all cases. @@ -2722,10 +2795,42 @@ void UpdateTileSelection() y1 += TILE_SIZE / 2; break; case HT_RAIL: - /* Draw one highlighted tile in any direction */ - new_drawstyle = GetAutorailHT(pt.x, pt.y); - break; case HT_LINE: + /* HT_POLY */ + if (_thd.place_mode & HT_POLY) { + RailSnapMode snap_mode = GetRailSnapMode(); + if (snap_mode == RSM_NO_SNAP || + (snap_mode == RSM_SNAP_TO_TILE && GetRailSnapTile() == TileVirtXY(pt.x, pt.y))) { + new_drawstyle = GetAutorailHT(pt.x, pt.y); + _thd.new_offs.x = 0; + _thd.new_offs.y = 0; + _thd.new_outersize.x = 0; + _thd.new_outersize.y = 0; + _thd.dir2 = HT_DIR_END; + } else { + new_drawstyle = CalcPolyrailDrawstyle(pt, false); + if (new_drawstyle != HT_NONE) { + x1 = _thd.selstart.x & ~TILE_UNIT_MASK; + y1 = _thd.selstart.y & ~TILE_UNIT_MASK; + int x2 = _thd.selend.x & ~TILE_UNIT_MASK; + int y2 = _thd.selend.y & ~TILE_UNIT_MASK; + if (x1 > x2) Swap(x1, x2); + if (y1 > y2) Swap(y1, y2); + _thd.new_pos.x = x1; + _thd.new_pos.y = y1; + _thd.new_size.x = x2 - x1 + TILE_SIZE; + _thd.new_size.y = y2 - y1 + TILE_SIZE; + } + } + break; + } + /* HT_RAIL */ + if (_thd.place_mode & HT_RAIL) { + /* Draw one highlighted tile in any direction */ + new_drawstyle = GetAutorailHT(pt.x, pt.y); + break; + } + /* HT_LINE */ switch (_thd.place_mode & HT_DIR_MASK) { case HT_DIR_X: new_drawstyle = HT_LINE | HT_DIR_X; break; case HT_DIR_Y: new_drawstyle = HT_LINE | HT_DIR_Y; break; @@ -2744,6 +2849,8 @@ void UpdateTileSelection() } _thd.selstart.x = x1 & ~TILE_UNIT_MASK; _thd.selstart.y = y1 & ~TILE_UNIT_MASK; + _thd.selend.x = x1; + _thd.selend.y = y1; break; default: NOT_REACHED(); @@ -2754,10 +2861,13 @@ void UpdateTileSelection() } } + if (new_drawstyle & HT_LINE) CalcNewPolylineOutersize(); + /* redraw selection */ if (_thd.drawstyle != new_drawstyle || _thd.pos.x != _thd.new_pos.x || _thd.pos.y != _thd.new_pos.y || _thd.size.x != _thd.new_size.x || _thd.size.y != _thd.new_size.y || + _thd.offs.x != _thd.new_offs.x || _thd.offs.y != _thd.new_offs.y || _thd.outersize.x != _thd.new_outersize.x || _thd.outersize.y != _thd.new_outersize.y || _thd.diagonal != new_diagonal) { @@ -2767,6 +2877,7 @@ void UpdateTileSelection() _thd.drawstyle = new_drawstyle; _thd.pos = _thd.new_pos; _thd.size = _thd.new_size; + _thd.offs = _thd.new_offs; _thd.outersize = _thd.new_outersize; _thd.diagonal = new_diagonal; _thd.dirty = 0xff; @@ -2816,6 +2927,11 @@ void VpStartPlaceSizing(TileIndex tile, ViewportPlaceMethod method, ViewportDrag } else if (_thd.place_mode & (HT_RAIL | HT_LINE)) { _thd.place_mode = HT_SPECIAL | others; _thd.next_drawstyle = _thd.drawstyle | others; + _current_snap_lock.x = -1; + if ((_thd.place_mode & HT_POLY) != 0 && GetRailSnapMode() == RSM_NO_SNAP) { + SetRailSnapMode(RSM_SNAP_TO_TILE); + SetRailSnapTile(tile); + } } else { _thd.place_mode = HT_SPECIAL | others; _thd.next_drawstyle = HT_POINT | others; @@ -3007,7 +3123,31 @@ static int CalcHeightdiff(HighLightStyle style, uint distance, TileIndex start_t return (int)(h1 - h0) * TILE_HEIGHT_STEP; } -static const StringID measure_strings_length[] = {STR_NULL, STR_MEASURE_LENGTH, STR_MEASURE_LENGTH_HEIGHTDIFF}; +static void ShowLengthMeasurement(HighLightStyle style, TileIndex start_tile, TileIndex end_tile, TooltipCloseCondition close_cond = TCC_LEFT_CLICK, bool show_single_tile_length = false) +{ + static const StringID measure_strings_length[] = {STR_NULL, STR_MEASURE_LENGTH, STR_MEASURE_LENGTH_HEIGHTDIFF}; + + if (_settings_client.gui.measure_tooltip) { + uint distance = DistanceManhattan(start_tile, end_tile) + 1; + byte index = 0; + uint64 params[2]; + + if (show_single_tile_length || distance != 1) { + int heightdiff = CalcHeightdiff(style, distance, start_tile, end_tile); + /* If we are showing a tooltip for horizontal or vertical drags, + * 2 tiles have a length of 1. To bias towards the ceiling we add + * one before division. It feels more natural to count 3 lengths as 2 */ + if ((style & HT_DIR_MASK) != HT_DIR_X && (style & HT_DIR_MASK) != HT_DIR_Y) { + distance = CeilDiv(distance, 2); + } + + params[index++] = distance; + if (heightdiff != 0) params[index++] = heightdiff; + } + + ShowMeasurementTooltips(measure_strings_length[index], index, params, close_cond); + } +} /** * Check for underflowing the map. @@ -3038,6 +3178,170 @@ static void CheckOverflow(int &test, int &other, int max, int mult) test = max; } +static const uint X_DIRS = (1 << DIR_NE) | (1 << DIR_SW); +static const uint Y_DIRS = (1 << DIR_SE) | (1 << DIR_NW); +static const uint HORZ_DIRS = (1 << DIR_W) | (1 << DIR_E); +static const uint VERT_DIRS = (1 << DIR_N) | (1 << DIR_S); + +Trackdir PointDirToTrackdir(const Point &pt, Direction dir) +{ + Trackdir ret; + + if (IsDiagonalDirection(dir)) { + ret = DiagDirToDiagTrackdir(DirToDiagDir(dir)); + } else { + int x = pt.x & TILE_UNIT_MASK; + int y = pt.y & TILE_UNIT_MASK; + int ns = x + y; + int we = y - x; + if (HasBit(HORZ_DIRS, dir)) { + ret = TrackDirectionToTrackdir(ns < (int)TILE_SIZE ? TRACK_UPPER : TRACK_LOWER, dir); + } else { + ret = TrackDirectionToTrackdir(we < 0 ? TRACK_LEFT : TRACK_RIGHT, dir); + } + } + + return ret; +} + +static bool FindPolyline(const Point &pt, const LineSnapPoint &start, Polyline *ret) +{ + /* relative coordinats of the mouse point (offset against the snap point) */ + int x = pt.x - start.x; + int y = pt.y - start.y; + int we = y - x; + int ns = x + y; + + /* in-tile alignment of the snap point (there are two variants: [0, 8] or [8, 0]) */ + uint align_x = start.x & TILE_UNIT_MASK; + uint align_y = start.y & TILE_UNIT_MASK; + assert((align_x == TILE_SIZE / 2 && align_y == 0 && !(start.dirs & X_DIRS)) || (align_x == 0 && align_y == TILE_SIZE / 2 && !(start.dirs & Y_DIRS))); + + /* absolute distance between points (in tiles) */ + uint d_x = abs(RoundDivSU(x < 0 ? x - align_y : x + align_y, TILE_SIZE)); + uint d_y = abs(RoundDivSU(y < 0 ? y - align_x : y + align_x, TILE_SIZE)); + uint d_ns = abs(RoundDivSU(ns, TILE_SIZE)); + uint d_we = abs(RoundDivSU(we, TILE_SIZE)); + + /* Find on which quadrant is the mouse point (reltively to the snap point). + * Numeration (clockwise like in Direction): + * ortho diag + * \ 2 / 2 | 3 + * \ / --+---> [we] + * 1 X 3 1 | 0 + * / \ v + * [x] 0 [y] [ns] */ + uint ortho_quadrant = 2 * (x < 0) + ((x < 0) != (y < 0)); // implicit cast: false/true --> 0/1 + uint diag_quadrant = 2 * (ns < 0) + ((ns < 0) != (we < 0)); + + /* direction from the snap point to the mouse point */ + Direction ortho_line_dir = ChangeDir(DIR_S, (DirDiff)(2 * ortho_quadrant)); // DIR_S is the middle of the ortho quadrant no. 0 + Direction diag_line_dir = ChangeDir(DIR_SE, (DirDiff)(2 * diag_quadrant)); // DIR_SE is the middle of the diag quadrant no. 0 + if (!HasBit(start.dirs, ortho_line_dir) && !HasBit(start.dirs, diag_line_dir)) return false; + + /* length of booth segments of auto line (choosing orthogonal direction first) */ + uint ortho_len = 0, ortho_len2 = 0; + if (HasBit(start.dirs, ortho_line_dir)) { + bool is_len_even = (align_x != 0) ? d_x >= d_y : d_x <= d_y; + ortho_len = 2 * min(d_x, d_y) - (int)is_len_even; + assert((int)ortho_len >= 0); + if (d_ns == 0 || d_we == 0) { // just single segment? + ortho_len++; + } else { + ortho_len2 = abs((int)d_x - (int)d_y) + (int)is_len_even; + } + } + + /* length of booth segments of auto line (choosing diagonal direction first) */ + uint diag_len = 0, diag_len2 = 0; + if (HasBit(start.dirs, diag_line_dir)) { + if (d_x == 0 || d_y == 0) { // just single segment? + diag_len = d_x + d_y; + } else { + diag_len = min(d_ns, d_we); + diag_len2 = d_x + d_y - diag_len; + } + } + + /* choose the best variant */ + if (ortho_len != 0 && diag_len != 0) { + /* in the first place, choose this line whose first segment ends up closer + * to the mouse point (thus the second segment is shorter) */ + int cmp = ortho_len2 - diag_len2; + /* if equeal, choose the shorter line */ + if (cmp == 0) cmp = ortho_len - diag_len; + /* finally look at small "units" and choose the line which is closer to the mouse point */ + if (cmp == 0) cmp = min(abs(we), abs(ns)) - min(abs(x), abs(y)); + /* based on comparison, disable one of variants */ + if (cmp > 0) { + ortho_len = 0; + } else { + diag_len = 0; + } + } + + /* store results */ + if (ortho_len != 0) { + ret->first_dir = ortho_line_dir; + ret->first_len = ortho_len; + ret->second_dir = (ortho_len2 != 0) ? diag_line_dir : INVALID_DIR; + ret->second_len = ortho_len2; + } else if (diag_len != 0) { + ret->first_dir = diag_line_dir; + ret->first_len = diag_len; + ret->second_dir = (diag_len2 != 0) ? ortho_line_dir : INVALID_DIR; + ret->second_len = diag_len2; + } else { + return false; + } + + ret->start = start; + return true; +} + +/** + * Calculate squared euclidean distance between two points. + * @param a the first point + * @param b the second point + * @return |b - a| ^ 2 + */ +static inline uint SqrDist(const Point &a, const Point &b) +{ + return (b.x - a.x) * (b.x - a.x) + (b.y - a.y) * (b.y - a.y); +} + +static LineSnapPoint *FindBestPolyline(const Point &pt, LineSnapPoint *snap_points, uint num_points, Polyline *ret) +{ + /* Find the best polyline (a pair of two lines - the white one and the blue + * one) led from any of saved snap points to the mouse cursor. */ + + LineSnapPoint *best_snap_point = NULL; // the best polyline we found so far is led from this snap point + + for (int i = 0; i < (int)num_points; i++) { + /* try to fit a polyline */ + Polyline polyline; + if (!FindPolyline(pt, snap_points[i], &polyline)) continue; // skip non-matching snap points + /* check whether we've found a better polyline */ + if (best_snap_point != NULL) { + /* firstly choose shorter polyline (the one with smaller amount of + * track pieces composing booth the white and the blue line) */ + uint cur_len = polyline.first_len + polyline.second_len; + uint best_len = ret->first_len + ret->second_len; + if (cur_len > best_len) continue; + /* secondly choose that polyline which has longer first (white) line */ + if (cur_len == best_len && polyline.first_len < ret->first_len) continue; + /* finally check euclidean distance to snap points and choose the + * one which is closer */ + if (cur_len == best_len && polyline.first_len == ret->first_len && SqrDist(pt, snap_points[i]) >= SqrDist(pt, *best_snap_point)) continue; + } + /* save the found polyline */ + *ret = polyline; + best_snap_point = &snap_points[i]; + } + + return best_snap_point; +} + /** while dragging */ static void CalcRaildirsDrawstyle(int x, int y, int method) { @@ -3224,32 +3528,83 @@ static void CalcRaildirsDrawstyle(int x, int y, int method) } } - if (_settings_client.gui.measure_tooltip) { - TileIndex t0 = TileVirtXY(_thd.selstart.x, _thd.selstart.y); - TileIndex t1 = TileVirtXY(x, y); - uint distance = DistanceManhattan(t0, t1) + 1; - byte index = 0; - uint64 params[2]; - - if (distance != 1) { - int heightdiff = CalcHeightdiff(b, distance, t0, t1); - /* If we are showing a tooltip for horizontal or vertical drags, - * 2 tiles have a length of 1. To bias towards the ceiling we add - * one before division. It feels more natural to count 3 lengths as 2 */ - if ((b & HT_DIR_MASK) != HT_DIR_X && (b & HT_DIR_MASK) != HT_DIR_Y) { - distance = CeilDiv(distance, 2); - } - - params[index++] = distance; - if (heightdiff != 0) params[index++] = heightdiff; - } - - ShowMeasurementTooltips(measure_strings_length[index], index, params); - } - _thd.selend.x = x; _thd.selend.y = y; + _thd.dir2 = HT_DIR_END; _thd.next_drawstyle = b; + + ShowLengthMeasurement(b, TileVirtXY(_thd.selstart.x, _thd.selstart.y), TileVirtXY(_thd.selend.x, _thd.selend.y)); +} + +static HighLightStyle CalcPolyrailDrawstyle(Point pt, bool dragging) +{ + RailSnapMode snap_mode = GetRailSnapMode(); + + /* are we only within one tile? */ + if (snap_mode == RSM_SNAP_TO_TILE && GetRailSnapTile() == TileVirtXY(pt.x, pt.y)) { + _thd.selend.x = pt.x; + _thd.selend.y = pt.y; + return GetAutorailHT(pt.x, pt.y); + } + + /* find the best track */ + Polyline line; + + bool lock_snapping = dragging && snap_mode == RSM_SNAP_TO_RAIL; + if (!lock_snapping) _current_snap_lock.x = -1; + + const LineSnapPoint *snap_point; + if (_current_snap_lock.x != -1) { + snap_point = FindBestPolyline(pt, &_current_snap_lock, 1, &line); + } else if (snap_mode == RSM_SNAP_TO_TILE) { + snap_point = FindBestPolyline(pt, _tile_snap_points.Begin(), _tile_snap_points.Length(), &line); + } else { + assert(snap_mode == RSM_SNAP_TO_RAIL); + snap_point = FindBestPolyline(pt, _rail_snap_points.Begin(), _rail_snap_points.Length(), &line); + } + + if (snap_point == NULL) return HT_NONE; // no match + + if (lock_snapping && _current_snap_lock.x == -1) { + /* lock down the snap point */ + _current_snap_lock = *snap_point; + _current_snap_lock.dirs &= (1 << line.first_dir) | (1 << ReverseDir(line.first_dir)); + } + + TileIndexDiffC first_dir = TileIndexDiffCByDir(line.first_dir); + _thd.selstart.x = line.start.x; + _thd.selstart.y = line.start.y; + _thd.selend.x = _thd.selstart.x + line.first_len * first_dir.x * (IsDiagonalDirection(line.first_dir) ? TILE_SIZE : TILE_SIZE / 2); + _thd.selend.y = _thd.selstart.y + line.first_len * first_dir.y * (IsDiagonalDirection(line.first_dir) ? TILE_SIZE : TILE_SIZE / 2); + _thd.selstart2.x = _thd.selend.x; + _thd.selstart2.y = _thd.selend.y; + _thd.selstart.x += first_dir.x; + _thd.selstart.y += first_dir.y; + _thd.selend.x -= first_dir.x; + _thd.selend.y -= first_dir.y; + Trackdir seldir = PointDirToTrackdir(_thd.selstart, line.first_dir); + _thd.selstart.x &= ~TILE_UNIT_MASK; + _thd.selstart.y &= ~TILE_UNIT_MASK; + + if (line.second_len != 0) { + TileIndexDiffC second_dir = TileIndexDiffCByDir(line.second_dir); + _thd.selend2.x = _thd.selstart2.x + line.second_len * second_dir.x * (IsDiagonalDirection(line.second_dir) ? TILE_SIZE : TILE_SIZE / 2); + _thd.selend2.y = _thd.selstart2.y + line.second_len * second_dir.y * (IsDiagonalDirection(line.second_dir) ? TILE_SIZE : TILE_SIZE / 2); + _thd.selstart2.x += second_dir.x; + _thd.selstart2.y += second_dir.y; + _thd.selend2.x -= second_dir.x; + _thd.selend2.y -= second_dir.y; + Trackdir seldir2 = PointDirToTrackdir(_thd.selstart2, line.second_dir); + _thd.selstart2.x &= ~TILE_UNIT_MASK; + _thd.selstart2.y &= ~TILE_UNIT_MASK; + _thd.dir2 = (HighLightStyle)TrackdirToTrack(seldir2); + } else { + _thd.dir2 = HT_DIR_END; + } + + HighLightStyle ret = HT_LINE | (HighLightStyle)TrackdirToTrack(seldir); + ShowLengthMeasurement(ret, TileVirtXY(_thd.selstart.x, _thd.selstart.y), TileVirtXY(_thd.selend.x, _thd.selend.y), TCC_HOVER, true); + return ret; } /** @@ -3269,6 +3624,12 @@ void VpSelectTilesWithMethod(int x, int y, ViewportPlaceMethod method) return; } + if ((_thd.place_mode & HT_POLY) && GetRailSnapMode() != RSM_NO_SNAP) { + Point pt = { x, y }; + _thd.next_drawstyle = CalcPolyrailDrawstyle(pt, true); + return; + } + /* Special handling of drag in any (8-way) direction */ if (method & (VPM_RAILDIRS | VPM_SIGNALDIRS)) { _thd.selend.x = x; @@ -3321,27 +3682,12 @@ calc_heightdiff_single_direction:; x = sx + Clamp(x - sx, -limit, limit); y = sy + Clamp(y - sy, -limit, limit); } - if (_settings_client.gui.measure_tooltip) { - TileIndex t0 = TileVirtXY(sx, sy); - TileIndex t1 = TileVirtXY(x, y); - uint distance = DistanceManhattan(t0, t1) + 1; - byte index = 0; - uint64 params[2]; - - if (distance != 1) { - /* With current code passing a HT_LINE style to calculate the height - * difference is enough. However if/when a point-tool is created - * with this method, function should be called with new_style (below) - * instead of HT_LINE | style case HT_POINT is handled specially - * new_style := (_thd.next_drawstyle & HT_RECT) ? HT_LINE | style : _thd.next_drawstyle; */ - int heightdiff = CalcHeightdiff(HT_LINE | style, 0, t0, t1); - - params[index++] = distance; - if (heightdiff != 0) params[index++] = heightdiff; - } - - ShowMeasurementTooltips(measure_strings_length[index], index, params); - } + /* With current code passing a HT_LINE style to calculate the height + * difference is enough. However if/when a point-tool is created + * with this method, function should be called with new_style (below) + * instead of HT_LINE | style case HT_POINT is handled specially + * new_style := (_thd.next_drawstyle & HT_RECT) ? HT_LINE | style : _thd.next_drawstyle; */ + ShowLengthMeasurement(HT_LINE | style, TileVirtXY(sx, sy), TileVirtXY(x, y)); break; case VPM_X_AND_Y_LIMITED: // Drag an X by Y constrained rect area. @@ -3420,6 +3766,7 @@ calc_heightdiff_single_direction:; _thd.selend.x = x; _thd.selend.y = y; + _thd.dir2 = HT_DIR_END; } /** @@ -3437,11 +3784,10 @@ EventState VpHandlePlaceSizingDrag() return ES_HANDLED; } - /* while dragging execute the drag procedure of the corresponding window (mostly VpSelectTilesWithMethod() ) */ - if (_left_button_down) { - w->OnPlaceDrag(_thd.select_method, _thd.select_proc, GetTileBelowCursor()); - return ES_HANDLED; - } + /* While dragging execute the drag procedure of the corresponding window (mostly VpSelectTilesWithMethod() ). + * Do it even if the button is no longer pressed to make sure that OnPlaceDrag was called at least once. */ + w->OnPlaceDrag(_thd.select_method, _thd.select_proc, GetTileBelowCursor()); + if (_left_button_down) return ES_HANDLED; /* mouse button released.. * keep the selected tool, but reset it to the original mode. */ @@ -3452,14 +3798,18 @@ EventState VpHandlePlaceSizingDrag() } else if (_thd.select_method & VPM_SIGNALDIRS) { _thd.place_mode = HT_RECT | others; } else if (_thd.select_method & VPM_RAILDIRS) { - _thd.place_mode = (_thd.select_method & ~VPM_RAILDIRS) ? _thd.next_drawstyle : (HT_RAIL | others); + _thd.place_mode = (_thd.select_method & ~VPM_RAILDIRS ? _thd.next_drawstyle : HT_RAIL) | others; } else { _thd.place_mode = HT_POINT | others; } SetTileSelectSize(1, 1); - w->OnPlaceMouseUp(_thd.select_method, _thd.select_proc, _thd.selend, TileVirtXY(_thd.selstart.x, _thd.selstart.y), TileVirtXY(_thd.selend.x, _thd.selend.y)); + if (_thd.place_mode & HT_POLY) { + if (GetRailSnapMode() == RSM_SNAP_TO_TILE) SetRailSnapMode(RSM_NO_SNAP); + if (_thd.drawstyle == HT_NONE) return ES_HANDLED; + } + w->OnPlaceMouseUp(_thd.select_method, _thd.select_proc, _thd.selend, TileVirtXY(_thd.selstart.x, _thd.selstart.y), TileVirtXY(_thd.selend.x, _thd.selend.y)); return ES_HANDLED; } @@ -3506,6 +3856,10 @@ void SetObjectToPlace(CursorID icon, PaletteID pal, HighLightStyle mode, WindowC VpStartPreSizing(); } + if (mode & HT_POLY) { + SetRailSnapMode((mode & HT_NEW_POLY) == HT_NEW_POLY ? RSM_NO_SNAP : RSM_SNAP_TO_RAIL); + } + if ((icon & ANIMCURSOR_FLAG) != 0) { SetAnimatedMouseCursor(_animcursors[icon & ~ANIMCURSOR_FLAG]); } else { @@ -3556,3 +3910,116 @@ void InitializeSpriteSorter() } assert(_vp_sprite_sorter != NULL); } + +static LineSnapPoint LineSnapPointAtRailTrackEndpoint(TileIndex tile, DiagDirection exit_dir, bool bidirectional) +{ + LineSnapPoint ret; + ret.x = (TILE_SIZE / 2) * (uint)(2 * TileX(tile) + TileIndexDiffCByDiagDir(exit_dir).x + 1); + ret.y = (TILE_SIZE / 2) * (uint)(2 * TileY(tile) + TileIndexDiffCByDiagDir(exit_dir).y + 1); + + ret.dirs = 0; + SetBit(ret.dirs, DiagDirToDir(exit_dir)); + SetBit(ret.dirs, ChangeDir(DiagDirToDir(exit_dir), DIRDIFF_45LEFT)); + SetBit(ret.dirs, ChangeDir(DiagDirToDir(exit_dir), DIRDIFF_45RIGHT)); + if (bidirectional) ret.dirs |= ROR(ret.dirs, DIRDIFF_REVERSE); + + return ret; +} + +/** + * Store the position of lastly built rail track; for highlighting purposes. + * + * In "polyline" highlighting mode, the stored end point will be used as a snapping point for new + * tracks allowing to place multi-segment polylines. + * + * @param start_tile tile where the track starts + * @param end_tile tile where the track ends + * @param start_track track piece on the start_tile + * @param bidirectional_exit whether to allow to highlight next track in any direction; otherwise new track will have to fallow the stored one (usefull when placing tunnels and bridges) + */ +void StoreRailPlacementEndpoints(TileIndex start_tile, TileIndex end_tile, Track start_track, bool bidirectional_exit) +{ + if (start_tile != INVALID_TILE && end_tile != INVALID_TILE) { + /* calculate trackdirs at booth ends of the track */ + Trackdir exit_trackdir_at_start = TrackToTrackdir(start_track); + Trackdir exit_trackdir_at_end = ReverseTrackdir(TrackToTrackdir(start_track)); + if (start_tile != end_tile) { // multi-tile case + /* determine proper direction (pointing outside of the track) */ + uint distance = DistanceManhattan(start_tile, end_tile); + if (distance > DistanceManhattan(TileAddByDiagDir(start_tile, TrackdirToExitdir(exit_trackdir_at_start)), end_tile)) { + Swap(exit_trackdir_at_start, exit_trackdir_at_end); + } + /* determine proper track on the end tile - switch between upper/lower or left/right based on the length */ + if (distance % 2 != 0) exit_trackdir_at_end = NextTrackdir(exit_trackdir_at_end); + } + + LineSnapPoint snap_start = LineSnapPointAtRailTrackEndpoint(start_tile, TrackdirToExitdir(exit_trackdir_at_start), bidirectional_exit); + LineSnapPoint snap_end = LineSnapPointAtRailTrackEndpoint(end_tile, TrackdirToExitdir(exit_trackdir_at_end), bidirectional_exit); + /* Find if we already had these coordinates before. */ + LineSnapPoint *snap; + bool had_start = false; + bool had_end = false; + for (snap = _rail_snap_points.Begin(); snap != _rail_snap_points.End(); snap++) { + had_start |= (snap->x == snap_start.x && snap->y == snap_start.y); + had_end |= (snap->x == snap_end.x && snap->y == snap_end.y); + } + /* Create new snap point set. */ + if (had_start && had_end) { + /* just stop snaping, don't forget snap points */ + SetRailSnapMode(RSM_NO_SNAP); + } else { + /* include only new points */ + _rail_snap_points.Clear(); + if (!had_start) *_rail_snap_points.Append() = snap_start; + if (!had_end) *_rail_snap_points.Append() = snap_end; + SetRailSnapMode(RSM_SNAP_TO_RAIL); + } + } +} + +bool CurrentlySnappingRailPlacement() +{ + return (_thd.place_mode & HT_POLY) && GetRailSnapMode() == RSM_SNAP_TO_RAIL; +} + +static RailSnapMode GetRailSnapMode() +{ + if (_rail_snap_mode == RSM_SNAP_TO_TILE && _tile_snap_points.Length() == 0) return RSM_NO_SNAP; + if (_rail_snap_mode == RSM_SNAP_TO_RAIL && _rail_snap_points.Length() == 0) return RSM_NO_SNAP; + return _rail_snap_mode; +} + +static void SetRailSnapMode(RailSnapMode mode) +{ + _rail_snap_mode = mode; + + if ((_thd.place_mode & HT_POLY) && (GetRailSnapMode() == RSM_NO_SNAP)) { + SetTileSelectSize(1, 1); + } +} + +static TileIndex GetRailSnapTile() +{ + if (_tile_snap_points.Length() == 0) return INVALID_TILE; + return TileVirtXY(_tile_snap_points[DIAGDIR_NE].x, _tile_snap_points[DIAGDIR_NE].y); +} + +static void SetRailSnapTile(TileIndex tile) +{ + _tile_snap_points.Clear(); + if (tile == INVALID_TILE) return; + + for (DiagDirection dir = DIAGDIR_BEGIN; dir < DIAGDIR_END; dir++) { + LineSnapPoint *point = _tile_snap_points.Append(); + *point = LineSnapPointAtRailTrackEndpoint(tile, dir, false); + point->dirs = ROR(point->dirs, DIRDIFF_REVERSE); + } +} + +void ResetRailPlacementSnapping() +{ + _rail_snap_mode = RSM_NO_SNAP; + _tile_snap_points.Clear(); + _rail_snap_points.Clear(); + _current_snap_lock.x = -1; +} diff --git a/src/viewport_func.h b/src/viewport_func.h index 3e05162d8a..21fb5f680d 100644 --- a/src/viewport_func.h +++ b/src/viewport_func.h @@ -58,7 +58,7 @@ void ViewportAddString(const DrawPixelInfo *dpi, ZoomLevel small_from, const Vie void StartSpriteCombine(); void EndSpriteCombine(); -bool HandleViewportClicked(const ViewPort *vp, int x, int y); +bool HandleViewportClicked(const ViewPort *vp, int x, int y, bool double_click); void SetRedErrorSquare(TileIndex tile); void SetTileSelectSize(int w, int h); void SetTileSelectBigSize(int ox, int oy, int sx, int sy); diff --git a/src/widgets/rail_widget.h b/src/widgets/rail_widget.h index f977f44803..50a0cd4f36 100644 --- a/src/widgets/rail_widget.h +++ b/src/widgets/rail_widget.h @@ -21,6 +21,7 @@ enum RailToolbarWidgets { WID_RAT_BUILD_EW, ///< Build rail along the game view X axis. WID_RAT_BUILD_Y, ///< Build rail along the game grid Y axis. WID_RAT_AUTORAIL, ///< Autorail tool. + WID_RAT_POLYRAIL, ///< Polyline rail tool. WID_RAT_DEMOLISH, ///< Destroy something with dynamite! WID_RAT_BUILD_DEPOT, ///< Build a depot. WID_RAT_BUILD_WAYPOINT, ///< Build a waypoint. diff --git a/src/window.cpp b/src/window.cpp index 398ddf6703..f0c31093e0 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -2796,7 +2796,7 @@ static void MouseLoop(MouseClick click, int mousewheel) case MC_DOUBLE_LEFT: case MC_LEFT: DEBUG(misc, 2, "Cursor: 0x%X (%d)", _cursor.sprite, _cursor.sprite); - if (!HandleViewportClicked(vp, x, y) && + if (!HandleViewportClicked(vp, x, y, click == MC_DOUBLE_LEFT) && !(w->flags & WF_DISABLE_VP_SCROLL) && _settings_client.gui.left_mouse_btn_scrolling) { _scrolling_viewport = true;