Import polyline rail tool patch v10b

http://www.tt-forums.net/viewtopic.php?f=33&t=57080
pull/8/head
patch-import 8 years ago committed by Jonathan G Rennison
parent 6b0ea01f4e
commit 01cf154f51

@ -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. */

@ -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

@ -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);

@ -44,6 +44,7 @@
typedef SmallVector<Train *, 16> 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<Track, 0, 3>(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<RailType, 0, 4>(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;

@ -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
if (_thd.drawstyle & HT_RAIL) { // one tile case
GenericPlaceRail(TileVirtXY(_thd.selend.x, _thd.selend.y), trackstat);
return;
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);
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<NWidgetCore>(WID_RAT_BUILD_EW)->widget_data = rti->gui_sprites.build_ew_rail;
this->GetWidget<NWidgetCore>(WID_RAT_BUILD_Y)->widget_data = rti->gui_sprites.build_y_rail;
this->GetWidget<NWidgetCore>(WID_RAT_AUTORAIL)->widget_data = rti->gui_sprites.auto_rail;
this->GetWidget<NWidgetCore>(WID_RAT_POLYRAIL)->widget_data = rti->gui_sprites.auto_rail;
this->GetWidget<NWidgetCore>(WID_RAT_BUILD_DEPOT)->widget_data = rti->gui_sprites.build_depot;
this->GetWidget<NWidgetCore>(WID_RAT_CONVERT_RAIL)->widget_data = rti->gui_sprites.convert_rail;
this->GetWidget<NWidgetCore>(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(),

@ -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");

@ -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.

@ -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 */

@ -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.

@ -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<StringSpriteToDraw, 4> StringSpriteToDrawVector;
typedef SmallVector<ParentSpriteToDraw, 64> ParentSpriteToDrawVector;
typedef SmallVector<ChildScreenSpriteToDraw, 16> 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<LineSnapPoint, 4> 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;
if (_thd.diagonal) { // We're drawing a 45 degrees rotated (diagonal) rectangle
if (IsInsideRotatedRectangle((int)ti->x, (int)ti->y)) goto draw_inner;
return;
}
switch (_thd.drawstyle & HT_DRAG_MASK) {
default: break; // No tile selection active?
/* 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;
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);
}
}
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;
break;
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);
}
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;
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)));
break;
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;
DrawAutorailSelection(ti, _autorail_type[dir][side]);
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<int>(outer_x1, _thd.new_pos.x);
outer_y1 = min<int>(outer_y1, _thd.new_pos.y);
outer_x2 = max<int>(outer_x2, _thd.new_pos.x + _thd.new_size.x - TILE_SIZE);
outer_y2 = max<int>(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];
_thd.selend.x = x;
_thd.selend.y = y;
_thd.dir2 = HT_DIR_END;
_thd.next_drawstyle = b;
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);
}
ShowLengthMeasurement(b, TileVirtXY(_thd.selstart.x, _thd.selstart.y), TileVirtXY(_thd.selend.x, _thd.selend.y));
}
params[index++] = distance;
if (heightdiff != 0) params[index++] = heightdiff;
}
static HighLightStyle CalcPolyrailDrawstyle(Point pt, bool dragging)
{
RailSnapMode snap_mode = GetRailSnapMode();
ShowMeasurementTooltips(measure_strings_length[index], index, params);
/* 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);
}
_thd.selend.x = x;
_thd.selend.y = y;
_thd.next_drawstyle = b;
/* 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<uint8>(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<uint8>(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;
}

@ -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);

@ -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.

@ -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;

Loading…
Cancel
Save