4 * This file is part of OpenTTD.
5 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
6 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
7 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
11 * @file viewport.cpp Handling of all viewports.
14 * The in-game coordinate system looks like this *
30 #include "landscape.h"
31 #include "viewport_func.h"
32 #include "station_base.h"
33 #include "waypoint_base.h"
35 #include "signs_base.h"
36 #include "signs_func.h"
37 #include "vehicle_base.h"
38 #include "vehicle_gui.h"
39 #include "blitter/factory.hpp"
40 #include "strings_func.h"
41 #include "zoom_func.h"
42 #include "vehicle_func.h"
43 #include "company_func.h"
44 #include "waypoint_func.h"
45 #include "window_func.h"
46 #include "tilehighlight_func.h"
47 #include "window_gui.h"
49 #include "table/strings.h"
51 Point _tile_fract_coords
;
53 struct StringSpriteToDraw
{
62 struct TileSpriteToDraw
{
65 const SubSprite
*sub
; ///< only draw a rectangular part of the sprite
66 int32 x
; ///< screen X coordinate of sprite
67 int32 y
; ///< screen Y coordinate of sprite
70 struct ChildScreenSpriteToDraw
{
73 const SubSprite
*sub
; ///< only draw a rectangular part of the sprite
76 int next
; ///< next child to draw (-1 at the end)
79 /** Parent sprite that should be drawn */
80 struct ParentSpriteToDraw
{
81 SpriteID image
; ///< sprite to draw
82 PaletteID pal
; ///< palette to use
83 const SubSprite
*sub
; ///< only draw a rectangular part of the sprite
85 int32 x
; ///< screen X coordinate of sprite
86 int32 y
; ///< screen Y coordinate of sprite
88 int32 left
; ///< minimal screen X coordinate of sprite (= x + sprite->x_offs), reference point for child sprites
89 int32 top
; ///< minimal screen Y coordinate of sprite (= y + sprite->y_offs), reference point for child sprites
91 int32 xmin
; ///< minimal world X coordinate of bounding box
92 int32 xmax
; ///< maximal world X coordinate of bounding box
93 int32 ymin
; ///< minimal world Y coordinate of bounding box
94 int32 ymax
; ///< maximal world Y coordinate of bounding box
95 int zmin
; ///< minimal world Z coordinate of bounding box
96 int zmax
; ///< maximal world Z coordinate of bounding box
98 int first_child
; ///< the first child to draw.
99 bool comparison_done
; ///< Used during sprite sorting: true if sprite has been compared with all other sprites
102 /** Enumeration of multi-part foundations */
103 enum FoundationPart
{
104 FOUNDATION_PART_NONE
= 0xFF, ///< Neither foundation nor groundsprite drawn yet.
105 FOUNDATION_PART_NORMAL
= 0, ///< First part (normal foundation or no foundation)
106 FOUNDATION_PART_HALFTILE
= 1, ///< Second part (halftile foundation)
111 * Mode of "sprite combining"
112 * @see StartSpriteCombine
114 enum SpriteCombineMode
{
115 SPRITE_COMBINE_NONE
, ///< Every #AddSortableSpriteToDraw start its own bounding box
116 SPRITE_COMBINE_PENDING
, ///< %Sprite combining will start with the next unclipped sprite.
117 SPRITE_COMBINE_ACTIVE
, ///< %Sprite combining is active. #AddSortableSpriteToDraw outputs child sprites.
120 typedef SmallVector
<TileSpriteToDraw
, 64> TileSpriteToDrawVector
;
121 typedef SmallVector
<StringSpriteToDraw
, 4> StringSpriteToDrawVector
;
122 typedef SmallVector
<ParentSpriteToDraw
, 64> ParentSpriteToDrawVector
;
123 typedef SmallVector
<ParentSpriteToDraw
*, 64> ParentSpriteToSortVector
;
124 typedef SmallVector
<ChildScreenSpriteToDraw
, 16> ChildScreenSpriteToDrawVector
;
126 /** Data structure storing rendering information */
127 struct ViewportDrawer
{
130 StringSpriteToDrawVector string_sprites_to_draw
;
131 TileSpriteToDrawVector tile_sprites_to_draw
;
132 ParentSpriteToDrawVector parent_sprites_to_draw
;
133 ParentSpriteToSortVector parent_sprites_to_sort
; ///< Parent sprite pointer array used for sorting
134 ChildScreenSpriteToDrawVector child_screen_sprites_to_draw
;
138 SpriteCombineMode combine_sprites
; ///< Current mode of "sprite combining". @see StartSpriteCombine
140 int foundation
[FOUNDATION_PART_END
]; ///< Foundation sprites (index into parent_sprites_to_draw).
141 FoundationPart foundation_part
; ///< Currently active foundation for ground sprite drawing.
142 int *last_foundation_child
[FOUNDATION_PART_END
]; ///< Tail of ChildSprite list of the foundations. (index into child_screen_sprites_to_draw)
143 Point foundation_offset
[FOUNDATION_PART_END
]; ///< Pixel offset for ground sprites on the foundations.
146 static ViewportDrawer _vd
;
148 TileHighlightData _thd
;
149 static TileInfo
*_cur_ti
;
150 bool _draw_bounding_boxes
= false;
152 static Point
MapXYZToViewport(const ViewPort
*vp
, int x
, int y
, int z
)
154 Point p
= RemapCoords(x
, y
, z
);
155 p
.x
-= vp
->virtual_width
/ 2;
156 p
.y
-= vp
->virtual_height
/ 2;
160 void DeleteWindowViewport(Window
*w
)
167 * Initialize viewport of the window for use.
168 * @param w Window to use/display the viewport in
169 * @param x Offset of left edge of viewport with respect to left edge window \a w
170 * @param y Offset of top edge of viewport with respect to top edge window \a w
171 * @param width Width of the viewport
172 * @param height Height of the viewport
173 * @param follow_flags Flags controlling the viewport.
174 * - If bit 31 is set, the lower 20 bits are the vehicle that the viewport should follow.
175 * - If bit 31 is clear, it is a #TileIndex.
176 * @param zoom Zoomlevel to display
178 void InitializeWindowViewport(Window
*w
, int x
, int y
,
179 int width
, int height
, uint32 follow_flags
, ZoomLevel zoom
)
181 assert(w
->viewport
== NULL
);
183 ViewportData
*vp
= CallocT
<ViewportData
>(1);
185 vp
->left
= x
+ w
->left
;
186 vp
->top
= y
+ w
->top
;
192 vp
->virtual_width
= ScaleByZoom(width
, zoom
);
193 vp
->virtual_height
= ScaleByZoom(height
, zoom
);
197 if (follow_flags
& 0x80000000) {
200 vp
->follow_vehicle
= (VehicleID
)(follow_flags
& 0xFFFFF);
201 veh
= Vehicle::Get(vp
->follow_vehicle
);
202 pt
= MapXYZToViewport(vp
, veh
->x_pos
, veh
->y_pos
, veh
->z_pos
);
204 uint x
= TileX(follow_flags
) * TILE_SIZE
;
205 uint y
= TileY(follow_flags
) * TILE_SIZE
;
207 vp
->follow_vehicle
= INVALID_VEHICLE
;
208 pt
= MapXYZToViewport(vp
, x
, y
, GetSlopeZ(x
, y
));
211 vp
->scrollpos_x
= pt
.x
;
212 vp
->scrollpos_y
= pt
.y
;
213 vp
->dest_scrollpos_x
= pt
.x
;
214 vp
->dest_scrollpos_y
= pt
.y
;
217 vp
->virtual_left
= 0;//pt.x;
218 vp
->virtual_top
= 0;//pt.y;
221 static Point _vp_move_offs
;
223 static void DoSetViewportPosition(const Window
*w
, int left
, int top
, int width
, int height
)
225 FOR_ALL_WINDOWS_FROM_BACK_FROM(w
, w
) {
226 if (left
+ width
> w
->left
&&
227 w
->left
+ w
->width
> left
&&
228 top
+ height
> w
->top
&&
229 w
->top
+ w
->height
> top
) {
231 if (left
< w
->left
) {
232 DoSetViewportPosition(w
, left
, top
, w
->left
- left
, height
);
233 DoSetViewportPosition(w
, left
+ (w
->left
- left
), top
, width
- (w
->left
- left
), height
);
237 if (left
+ width
> w
->left
+ w
->width
) {
238 DoSetViewportPosition(w
, left
, top
, (w
->left
+ w
->width
- left
), height
);
239 DoSetViewportPosition(w
, left
+ (w
->left
+ w
->width
- left
), top
, width
- (w
->left
+ w
->width
- left
), height
);
244 DoSetViewportPosition(w
, left
, top
, width
, (w
->top
- top
));
245 DoSetViewportPosition(w
, left
, top
+ (w
->top
- top
), width
, height
- (w
->top
- top
));
249 if (top
+ height
> w
->top
+ w
->height
) {
250 DoSetViewportPosition(w
, left
, top
, width
, (w
->top
+ w
->height
- top
));
251 DoSetViewportPosition(w
, left
, top
+ (w
->top
+ w
->height
- top
), width
, height
- (w
->top
+ w
->height
- top
));
260 int xo
= _vp_move_offs
.x
;
261 int yo
= _vp_move_offs
.y
;
263 if (abs(xo
) >= width
|| abs(yo
) >= height
) {
265 RedrawScreenRect(left
, top
, left
+ width
, top
+ height
);
269 GfxScroll(left
, top
, width
, height
, xo
, yo
);
272 RedrawScreenRect(left
, top
, xo
+ left
, top
+ height
);
276 RedrawScreenRect(left
+ width
+ xo
, top
, left
+ width
, top
+ height
);
281 RedrawScreenRect(left
, top
, width
+ left
, top
+ yo
);
283 RedrawScreenRect(left
, top
+ height
+ yo
, width
+ left
, top
+ height
);
288 static void SetViewportPosition(Window
*w
, int x
, int y
)
290 ViewPort
*vp
= w
->viewport
;
291 int old_left
= vp
->virtual_left
;
292 int old_top
= vp
->virtual_top
;
294 int left
, top
, width
, height
;
296 vp
->virtual_left
= x
;
299 /* Viewport is bound to its left top corner, so it must be rounded down (UnScaleByZoomLower)
300 * else glitch described in FS#1412 will happen (offset by 1 pixel with zoom level > NORMAL)
302 old_left
= UnScaleByZoomLower(old_left
, vp
->zoom
);
303 old_top
= UnScaleByZoomLower(old_top
, vp
->zoom
);
304 x
= UnScaleByZoomLower(x
, vp
->zoom
);
305 y
= UnScaleByZoomLower(y
, vp
->zoom
);
310 if (old_top
== 0 && old_left
== 0) return;
312 _vp_move_offs
.x
= old_left
;
313 _vp_move_offs
.y
= old_top
;
325 i
= left
+ width
- _screen
.width
;
326 if (i
>= 0) width
-= i
;
334 i
= top
+ height
- _screen
.height
;
335 if (i
>= 0) height
-= i
;
337 if (height
> 0) DoSetViewportPosition(w
->z_front
, left
, top
, width
, height
);
342 * Is a xy position inside the viewport of the window?
343 * @param w Window to examine its viewport
344 * @param x X coordinate of the xy position
345 * @param y Y coordinate of the xy position
346 * @return Pointer to the viewport if the xy position is in the viewport of the window,
347 * otherwise \c NULL is returned.
349 ViewPort
*IsPtInWindowViewport(const Window
*w
, int x
, int y
)
351 ViewPort
*vp
= w
->viewport
;
354 IsInsideMM(x
, vp
->left
, vp
->left
+ vp
->width
) &&
355 IsInsideMM(y
, vp
->top
, vp
->top
+ vp
->height
))
362 * Translate screen coordinate in a viewport to a tile coordinate
363 * @param vp Viewport that contains the (\a x, \a y) screen coordinate
364 * @param x Screen x coordinate
365 * @param y Screen y coordinate
366 * @return Tile coordinate
368 static Point
TranslateXYToTileCoord(const ViewPort
*vp
, int x
, int y
)
374 if ( (uint
)(x
-= vp
->left
) >= (uint
)vp
->width
||
375 (uint
)(y
-= vp
->top
) >= (uint
)vp
->height
) {
380 x
= (ScaleByZoom(x
, vp
->zoom
) + vp
->virtual_left
) >> 2;
381 y
= (ScaleByZoom(y
, vp
->zoom
) + vp
->virtual_top
) >> 1;
386 /* we need to move variables in to the valid range, as the
387 * GetTileZoomCenterWindow() function can call here with invalid x and/or y,
388 * when the user tries to zoom out along the sides of the map */
389 a
= Clamp(a
, -4 * (int)TILE_SIZE
, (int)(MapMaxX() * TILE_SIZE
) - 1);
390 b
= Clamp(b
, -4 * (int)TILE_SIZE
, (int)(MapMaxY() * TILE_SIZE
) - 1);
392 /* (a, b) is the X/Y-world coordinate that belongs to (x,y) if the landscape would be completely flat on height 0.
393 * Now find the Z-world coordinate by fix point iteration.
394 * This is a bit tricky because the tile height is non-continuous at foundations.
395 * The clicked point should be approached from the back, otherwise there are regions that are not clickable.
396 * (FOUNDATION_HALFTILE_LOWER on SLOPE_STEEP_S hides north halftile completely)
397 * So give it a z-malus of 4 in the first iterations.
401 int min_coord
= _settings_game
.construction
.freeform_edges
? TILE_SIZE
: 0;
403 for (int i
= 0; i
< 5; i
++) z
= GetSlopeZ(Clamp(a
+ (int)max(z
, 4u) - 4, min_coord
, MapMaxX() * TILE_SIZE
- 1), Clamp(b
+ (int)max(z
, 4u) - 4, min_coord
, MapMaxY() * TILE_SIZE
- 1)) / 2;
404 for (uint malus
= 3; malus
> 0; malus
--) z
= GetSlopeZ(Clamp(a
+ (int)max(z
, malus
) - (int)malus
, min_coord
, MapMaxX() * TILE_SIZE
- 1), Clamp(b
+ (int)max(z
, malus
) - (int)malus
, min_coord
, MapMaxY() * TILE_SIZE
- 1)) / 2;
405 for (int i
= 0; i
< 5; i
++) z
= GetSlopeZ(Clamp(a
+ (int)z
, min_coord
, MapMaxX() * TILE_SIZE
- 1), Clamp(b
+ (int)z
, min_coord
, MapMaxY() * TILE_SIZE
- 1)) / 2;
407 pt
.x
= Clamp(a
+ (int)z
, min_coord
, MapMaxX() * TILE_SIZE
- 1);
408 pt
.y
= Clamp(b
+ (int)z
, min_coord
, MapMaxY() * TILE_SIZE
- 1);
413 /* When used for zooming, check area below current coordinates (x,y)
414 * and return the tile of the zoomed out/in position (zoom_x, zoom_y)
415 * when you just want the tile, make x = zoom_x and y = zoom_y */
416 static Point
GetTileFromScreenXY(int x
, int y
, int zoom_x
, int zoom_y
)
422 if ( (w
= FindWindowFromPt(x
, y
)) != NULL
&&
423 (vp
= IsPtInWindowViewport(w
, x
, y
)) != NULL
)
424 return TranslateXYToTileCoord(vp
, zoom_x
, zoom_y
);
430 Point
GetTileBelowCursor()
432 return GetTileFromScreenXY(_cursor
.pos
.x
, _cursor
.pos
.y
, _cursor
.pos
.x
, _cursor
.pos
.y
);
436 Point
GetTileZoomCenterWindow(bool in
, Window
* w
)
439 ViewPort
*vp
= w
->viewport
;
442 x
= ((_cursor
.pos
.x
- vp
->left
) >> 1) + (vp
->width
>> 2);
443 y
= ((_cursor
.pos
.y
- vp
->top
) >> 1) + (vp
->height
>> 2);
445 x
= vp
->width
- (_cursor
.pos
.x
- vp
->left
);
446 y
= vp
->height
- (_cursor
.pos
.y
- vp
->top
);
448 /* Get the tile below the cursor and center on the zoomed-out center */
449 return GetTileFromScreenXY(_cursor
.pos
.x
, _cursor
.pos
.y
, x
+ vp
->left
, y
+ vp
->top
);
453 * Update the status of the zoom-buttons according to the zoom-level
454 * of the viewport. This will update their status and invalidate accordingly
455 * @param w Window pointer to the window that has the zoom buttons
456 * @param vp pointer to the viewport whose zoom-level the buttons represent
457 * @param widget_zoom_in widget index for window with zoom-in button
458 * @param widget_zoom_out widget index for window with zoom-out button
460 void HandleZoomMessage(Window
*w
, const ViewPort
*vp
, byte widget_zoom_in
, byte widget_zoom_out
)
462 w
->SetWidgetDisabledState(widget_zoom_in
, vp
->zoom
== ZOOM_LVL_MIN
);
463 w
->SetWidgetDirty(widget_zoom_in
);
465 w
->SetWidgetDisabledState(widget_zoom_out
, vp
->zoom
== ZOOM_LVL_MAX
);
466 w
->SetWidgetDirty(widget_zoom_out
);
470 * Schedules a tile sprite for drawing.
472 * @param image the image to draw.
473 * @param pal the provided palette.
474 * @param x position x (world coordinates) of the sprite.
475 * @param y position y (world coordinates) of the sprite.
476 * @param z position z (world coordinates) of the sprite.
477 * @param sub Only draw a part of the sprite.
478 * @param extra_offs_x Pixel X offset for the sprite position.
479 * @param extra_offs_y Pixel Y offset for the sprite position.
481 static void AddTileSpriteToDraw(SpriteID image
, PaletteID pal
, int32 x
, int32 y
, int z
, const SubSprite
*sub
= NULL
, int extra_offs_x
= 0, int extra_offs_y
= 0)
483 assert((image
& SPRITE_MASK
) < MAX_SPRITES
);
485 TileSpriteToDraw
*ts
= _vd
.tile_sprites_to_draw
.Append();
489 Point pt
= RemapCoords(x
, y
, z
);
490 ts
->x
= pt
.x
+ extra_offs_x
;
491 ts
->y
= pt
.y
+ extra_offs_y
;
495 * Adds a child sprite to the active foundation.
497 * The pixel offset of the sprite relative to the ParentSprite is the sum of the offset passed to OffsetGroundSprite() and extra_offs_?.
499 * @param image the image to draw.
500 * @param pal the provided palette.
501 * @param sub Only draw a part of the sprite.
502 * @param foundation_part Foundation part.
503 * @param extra_offs_x Pixel X offset for the sprite position.
504 * @param extra_offs_y Pixel Y offset for the sprite position.
506 static void AddChildSpriteToFoundation(SpriteID image
, PaletteID pal
, const SubSprite
*sub
, FoundationPart foundation_part
, int extra_offs_x
, int extra_offs_y
)
508 assert(IsInsideMM(foundation_part
, 0, FOUNDATION_PART_END
));
509 assert(_vd
.foundation
[foundation_part
] != -1);
510 Point offs
= _vd
.foundation_offset
[foundation_part
];
512 /* Change the active ChildSprite list to the one of the foundation */
513 int *old_child
= _vd
.last_child
;
514 _vd
.last_child
= _vd
.last_foundation_child
[foundation_part
];
516 AddChildSpriteScreen(image
, pal
, offs
.x
+ extra_offs_x
, offs
.y
+ extra_offs_y
, false, sub
);
518 /* Switch back to last ChildSprite list */
519 _vd
.last_child
= old_child
;
523 * Draws a ground sprite at a specific world-coordinate relative to the current tile.
524 * If the current tile is drawn on top of a foundation the sprite is added as child sprite to the "foundation"-ParentSprite.
526 * @param image the image to draw.
527 * @param pal the provided palette.
528 * @param x position x (world coordinates) of the sprite relative to current tile.
529 * @param y position y (world coordinates) of the sprite relative to current tile.
530 * @param z position z (world coordinates) of the sprite relative to current tile.
531 * @param sub Only draw a part of the sprite.
532 * @param extra_offs_x Pixel X offset for the sprite position.
533 * @param extra_offs_y Pixel Y offset for the sprite position.
535 void DrawGroundSpriteAt(SpriteID image
, PaletteID pal
, int32 x
, int32 y
, int z
, const SubSprite
*sub
, int extra_offs_x
, int extra_offs_y
)
537 /* Switch to first foundation part, if no foundation was drawn */
538 if (_vd
.foundation_part
== FOUNDATION_PART_NONE
) _vd
.foundation_part
= FOUNDATION_PART_NORMAL
;
540 if (_vd
.foundation
[_vd
.foundation_part
] != -1) {
541 Point pt
= RemapCoords(x
, y
, z
);
542 AddChildSpriteToFoundation(image
, pal
, sub
, _vd
.foundation_part
, pt
.x
+ extra_offs_x
, pt
.y
+ extra_offs_y
);
544 AddTileSpriteToDraw(image
, pal
, _cur_ti
->x
+ x
, _cur_ti
->y
+ y
, _cur_ti
->z
+ z
, sub
, extra_offs_x
, extra_offs_y
);
549 * Draws a ground sprite for the current tile.
550 * If the current tile is drawn on top of a foundation the sprite is added as child sprite to the "foundation"-ParentSprite.
552 * @param image the image to draw.
553 * @param pal the provided palette.
554 * @param sub Only draw a part of the sprite.
555 * @param extra_offs_x Pixel X offset for the sprite position.
556 * @param extra_offs_y Pixel Y offset for the sprite position.
558 void DrawGroundSprite(SpriteID image
, PaletteID pal
, const SubSprite
*sub
, int extra_offs_x
, int extra_offs_y
)
560 DrawGroundSpriteAt(image
, pal
, 0, 0, 0, sub
, extra_offs_x
, extra_offs_y
);
564 * Called when a foundation has been drawn for the current tile.
565 * Successive ground sprites for the current tile will be drawn as child sprites of the "foundation"-ParentSprite, not as TileSprites.
567 * @param x sprite x-offset (screen coordinates) of ground sprites relative to the "foundation"-ParentSprite.
568 * @param y sprite y-offset (screen coordinates) of ground sprites relative to the "foundation"-ParentSprite.
570 void OffsetGroundSprite(int x
, int y
)
572 /* Switch to next foundation part */
573 switch (_vd
.foundation_part
) {
574 case FOUNDATION_PART_NONE
:
575 _vd
.foundation_part
= FOUNDATION_PART_NORMAL
;
577 case FOUNDATION_PART_NORMAL
:
578 _vd
.foundation_part
= FOUNDATION_PART_HALFTILE
;
580 default: NOT_REACHED();
583 /* _vd.last_child == NULL if foundation sprite was clipped by the viewport bounds */
584 if (_vd
.last_child
!= NULL
) _vd
.foundation
[_vd
.foundation_part
] = _vd
.parent_sprites_to_draw
.Length() - 1;
586 _vd
.foundation_offset
[_vd
.foundation_part
].x
= x
;
587 _vd
.foundation_offset
[_vd
.foundation_part
].y
= y
;
588 _vd
.last_foundation_child
[_vd
.foundation_part
] = _vd
.last_child
;
592 * Adds a child sprite to a parent sprite.
593 * In contrast to "AddChildSpriteScreen()" the sprite position is in world coordinates
595 * @param image the image to draw.
596 * @param pal the provided palette.
597 * @param x position x of the sprite.
598 * @param y position y of the sprite.
599 * @param z position z of the sprite.
600 * @param sub Only draw a part of the sprite.
602 static void AddCombinedSprite(SpriteID image
, PaletteID pal
, int x
, int y
, byte z
, const SubSprite
*sub
)
604 Point pt
= RemapCoords(x
, y
, z
);
605 const Sprite
*spr
= GetSprite(image
& SPRITE_MASK
, ST_NORMAL
);
607 if (pt
.x
+ spr
->x_offs
>= _vd
.dpi
.left
+ _vd
.dpi
.width
||
608 pt
.x
+ spr
->x_offs
+ spr
->width
<= _vd
.dpi
.left
||
609 pt
.y
+ spr
->y_offs
>= _vd
.dpi
.top
+ _vd
.dpi
.height
||
610 pt
.y
+ spr
->y_offs
+ spr
->height
<= _vd
.dpi
.top
)
613 const ParentSpriteToDraw
*pstd
= _vd
.parent_sprites_to_draw
.End() - 1;
614 AddChildSpriteScreen(image
, pal
, pt
.x
- pstd
->left
, pt
.y
- pstd
->top
, false, sub
);
618 * Draw a (transparent) sprite at given coordinates with a given bounding box.
619 * The bounding box extends from (x + bb_offset_x, y + bb_offset_y, z + bb_offset_z) to (x + w - 1, y + h - 1, z + dz - 1), both corners included.
620 * Bounding boxes with bb_offset_x == w or bb_offset_y == h or bb_offset_z == dz are allowed and produce thin slices.
622 * @note Bounding boxes are normally specified with bb_offset_x = bb_offset_y = bb_offset_z = 0. The extent of the bounding box in negative direction is
623 * defined by the sprite offset in the grf file.
624 * However if modifying the sprite offsets is not suitable (e.g. when using existing graphics), the bounding box can be tuned by bb_offset.
626 * @pre w >= bb_offset_x, h >= bb_offset_y, dz >= bb_offset_z. Else w, h or dz are ignored.
628 * @param image the image to combine and draw,
629 * @param pal the provided palette,
630 * @param x position X (world) of the sprite,
631 * @param y position Y (world) of the sprite,
632 * @param w bounding box extent towards positive X (world),
633 * @param h bounding box extent towards positive Y (world),
634 * @param dz bounding box extent towards positive Z (world),
635 * @param z position Z (world) of the sprite,
636 * @param transparent if true, switch the palette between the provided palette and the transparent palette,
637 * @param bb_offset_x bounding box extent towards negative X (world),
638 * @param bb_offset_y bounding box extent towards negative Y (world),
639 * @param bb_offset_z bounding box extent towards negative Z (world)
640 * @param sub Only draw a part of the sprite.
642 void AddSortableSpriteToDraw(SpriteID image
, PaletteID pal
, int x
, int y
, int w
, int h
, int dz
, int z
, bool transparent
, int bb_offset_x
, int bb_offset_y
, int bb_offset_z
, const SubSprite
*sub
)
644 int32 left
, right
, top
, bottom
;
646 assert((image
& SPRITE_MASK
) < MAX_SPRITES
);
648 /* make the sprites transparent with the right palette */
650 SetBit(image
, PALETTE_MODIFIER_TRANSPARENT
);
651 pal
= PALETTE_TO_TRANSPARENT
;
654 if (_vd
.combine_sprites
== SPRITE_COMBINE_ACTIVE
) {
655 AddCombinedSprite(image
, pal
, x
, y
, z
, sub
);
659 _vd
.last_child
= NULL
;
661 Point pt
= RemapCoords(x
, y
, z
);
662 int tmp_left
, tmp_top
, tmp_x
= pt
.x
, tmp_y
= pt
.y
;
664 /* Compute screen extents of sprite */
665 if (image
== SPR_EMPTY_BOUNDING_BOX
) {
666 left
= tmp_left
= RemapCoords(x
+ w
, y
+ bb_offset_y
, z
+ bb_offset_z
).x
;
667 right
= RemapCoords(x
+ bb_offset_x
, y
+ h
, z
+ bb_offset_z
).x
+ 1;
668 top
= tmp_top
= RemapCoords(x
+ bb_offset_x
, y
+ bb_offset_y
, z
+ dz
).y
;
669 bottom
= RemapCoords(x
+ w
, y
+ h
, z
+ bb_offset_z
).y
+ 1;
671 const Sprite
*spr
= GetSprite(image
& SPRITE_MASK
, ST_NORMAL
);
672 left
= tmp_left
= (pt
.x
+= spr
->x_offs
);
673 right
= (pt
.x
+ spr
->width
);
674 top
= tmp_top
= (pt
.y
+= spr
->y_offs
);
675 bottom
= (pt
.y
+ spr
->height
);
678 if (_draw_bounding_boxes
&& (image
!= SPR_EMPTY_BOUNDING_BOX
)) {
679 /* Compute maximal extents of sprite and its bounding box */
680 left
= min(left
, RemapCoords(x
+ w
, y
+ bb_offset_y
, z
+ bb_offset_z
).x
);
681 right
= max(right
, RemapCoords(x
+ bb_offset_x
, y
+ h
, z
+ bb_offset_z
).x
+ 1);
682 top
= min(top
, RemapCoords(x
+ bb_offset_x
, y
+ bb_offset_y
, z
+ dz
).y
);
683 bottom
= max(bottom
, RemapCoords(x
+ w
, y
+ h
, z
+ bb_offset_z
).y
+ 1);
686 /* Do not add the sprite to the viewport, if it is outside */
687 if (left
>= _vd
.dpi
.left
+ _vd
.dpi
.width
||
688 right
<= _vd
.dpi
.left
||
689 top
>= _vd
.dpi
.top
+ _vd
.dpi
.height
||
690 bottom
<= _vd
.dpi
.top
) {
694 ParentSpriteToDraw
*ps
= _vd
.parent_sprites_to_draw
.Append();
704 ps
->xmin
= x
+ bb_offset_x
;
705 ps
->xmax
= x
+ max(bb_offset_x
, w
) - 1;
707 ps
->ymin
= y
+ bb_offset_y
;
708 ps
->ymax
= y
+ max(bb_offset_y
, h
) - 1;
710 ps
->zmin
= z
+ bb_offset_z
;
711 ps
->zmax
= z
+ max(bb_offset_z
, dz
) - 1;
713 ps
->comparison_done
= false;
714 ps
->first_child
= -1;
716 _vd
.last_child
= &ps
->first_child
;
718 if (_vd
.combine_sprites
== SPRITE_COMBINE_PENDING
) _vd
.combine_sprites
= SPRITE_COMBINE_ACTIVE
;
722 * Starts a block of sprites, which are "combined" into a single bounding box.
724 * Subsequent calls to #AddSortableSpriteToDraw will be drawn into the same bounding box.
725 * That is: The first sprite that is not clipped by the viewport defines the bounding box, and
726 * the following sprites will be child sprites to that one.
729 * - The drawing order is definite. No other sprites will be sorted between those of the block.
730 * - You have to provide a valid bounding box for all sprites,
731 * as you won't know which one is the first non-clipped one.
732 * Preferable you use the same bounding box for all.
733 * - You cannot use #AddChildSpriteScreen inside the block, as its result will be indefinite.
735 * The block is terminated by #EndSpriteCombine.
737 * You cannot nest "combined" blocks.
739 void StartSpriteCombine()
741 assert(_vd
.combine_sprites
== SPRITE_COMBINE_NONE
);
742 _vd
.combine_sprites
= SPRITE_COMBINE_PENDING
;
746 * Terminates a block of sprites started by #StartSpriteCombine.
747 * Take a look there for details.
749 void EndSpriteCombine()
751 assert(_vd
.combine_sprites
!= SPRITE_COMBINE_NONE
);
752 _vd
.combine_sprites
= SPRITE_COMBINE_NONE
;
756 * Check if the parameter "check" is inside the interval between
757 * begin and end, including both begin and end.
758 * @note Whether \c begin or \c end is the biggest does not matter.
759 * This method will account for that.
760 * @param begin The begin of the interval.
761 * @param end The end of the interval.
762 * @param check The value to check.
764 static bool IsInRangeInclusive(int begin
, int end
, int check
)
766 if (begin
> end
) Swap(begin
, end
);
767 return begin
<= check
&& check
<= end
;
771 * Checks whether a point is inside the selected a diagonal rectangle given by _thd.size and _thd.pos
772 * @param x The x coordinate of the point to be checked.
773 * @param y The y coordinate of the point to be checked.
774 * @return True if the point is inside the rectangle, else false.
776 bool IsInsideRotatedRectangle(int x
, int y
)
778 int dist_a
= (_thd
.size
.x
+ _thd
.size
.y
); // Rotated coordinate system for selected rectangle.
779 int dist_b
= (_thd
.size
.x
- _thd
.size
.y
); // We don't have to divide by 2. It's all relative!
780 int a
= ((x
- _thd
.pos
.x
) + (y
- _thd
.pos
.y
)); // Rotated coordinate system for the point under scrutiny.
781 int b
= ((x
- _thd
.pos
.x
) - (y
- _thd
.pos
.y
));
783 /* Check if a and b are between 0 and dist_a or dist_b respectively. */
784 return IsInRangeInclusive(dist_a
, 0, a
) && IsInRangeInclusive(dist_b
, 0, b
);
788 * Add a child sprite to a parent sprite.
790 * @param image the image to draw.
791 * @param pal the provided palette.
792 * @param x sprite x-offset (screen coordinates) relative to parent sprite.
793 * @param y sprite y-offset (screen coordinates) relative to parent sprite.
794 * @param transparent if true, switch the palette between the provided palette and the transparent palette,
795 * @param sub Only draw a part of the sprite.
797 void AddChildSpriteScreen(SpriteID image
, PaletteID pal
, int x
, int y
, bool transparent
, const SubSprite
*sub
)
799 assert((image
& SPRITE_MASK
) < MAX_SPRITES
);
801 /* If the ParentSprite was clipped by the viewport bounds, do not draw the ChildSprites either */
802 if (_vd
.last_child
== NULL
) return;
804 /* make the sprites transparent with the right palette */
806 SetBit(image
, PALETTE_MODIFIER_TRANSPARENT
);
807 pal
= PALETTE_TO_TRANSPARENT
;
810 *_vd
.last_child
= _vd
.child_screen_sprites_to_draw
.Length();
812 ChildScreenSpriteToDraw
*cs
= _vd
.child_screen_sprites_to_draw
.Append();
820 /* Append the sprite to the active ChildSprite list.
821 * If the active ParentSprite is a foundation, update last_foundation_child as well.
822 * Note: ChildSprites of foundations are NOT sequential in the vector, as selection sprites are added at last. */
823 if (_vd
.last_foundation_child
[0] == _vd
.last_child
) _vd
.last_foundation_child
[0] = &cs
->next
;
824 if (_vd
.last_foundation_child
[1] == _vd
.last_child
) _vd
.last_foundation_child
[1] = &cs
->next
;
825 _vd
.last_child
= &cs
->next
;
828 static void AddStringToDraw(int x
, int y
, StringID string
, uint64 params_1
, uint64 params_2
, Colours colour
, uint16 width
)
831 StringSpriteToDraw
*ss
= _vd
.string_sprites_to_draw
.Append();
835 ss
->params
[0] = params_1
;
836 ss
->params
[1] = params_2
;
843 * Draws sprites between ground sprite and everything above.
845 * The sprite is either drawn as TileSprite or as ChildSprite of the active foundation.
847 * @param image the image to draw.
848 * @param pal the provided palette.
849 * @param ti TileInfo Tile that is being drawn
850 * @param z_offset Z offset relative to the groundsprite. Only used for the sprite position, not for sprite sorting.
851 * @param foundation_part Foundation part the sprite belongs to.
853 static void DrawSelectionSprite(SpriteID image
, PaletteID pal
, const TileInfo
*ti
, int z_offset
, FoundationPart foundation_part
)
855 /* FIXME: This is not totally valid for some autorail highlights that extend over the edges of the tile. */
856 if (_vd
.foundation
[foundation_part
] == -1) {
857 /* draw on real ground */
858 AddTileSpriteToDraw(image
, pal
, ti
->x
, ti
->y
, ti
->z
+ z_offset
);
860 /* draw on top of foundation */
861 AddChildSpriteToFoundation(image
, pal
, NULL
, foundation_part
, 0, -z_offset
);
866 * Draws a selection rectangle on a tile.
868 * @param ti TileInfo Tile that is being drawn
869 * @param pal Palette to apply.
871 static void DrawTileSelectionRect(const TileInfo
*ti
, PaletteID pal
)
873 if (!IsValidTile(ti
->tile
)) return;
876 if (IsHalftileSlope(ti
->tileh
)) {
877 Corner halftile_corner
= GetHalftileSlopeCorner(ti
->tileh
);
878 SpriteID sel2
= SPR_HALFTILE_SELECTION_FLAT
+ halftile_corner
;
879 DrawSelectionSprite(sel2
, pal
, ti
, 7 + TILE_HEIGHT
, FOUNDATION_PART_HALFTILE
);
881 Corner opposite_corner
= OppositeCorner(halftile_corner
);
882 if (IsSteepSlope(ti
->tileh
)) {
883 sel
= SPR_HALFTILE_SELECTION_DOWN
;
885 sel
= ((ti
->tileh
& SlopeWithOneCornerRaised(opposite_corner
)) != 0 ? SPR_HALFTILE_SELECTION_UP
: SPR_HALFTILE_SELECTION_FLAT
);
887 sel
+= opposite_corner
;
889 sel
= SPR_SELECT_TILE
+ SlopeToSpriteOffset(ti
->tileh
);
891 DrawSelectionSprite(sel
, pal
, ti
, 7, FOUNDATION_PART_NORMAL
);
894 static bool IsPartOfAutoLine(int px
, int py
)
896 px
-= _thd
.selstart
.x
;
897 py
-= _thd
.selstart
.y
;
899 if ((_thd
.drawstyle
& HT_DRAG_MASK
) != HT_LINE
) return false;
901 switch (_thd
.drawstyle
& HT_DIR_MASK
) {
902 case HT_DIR_X
: return py
== 0; // x direction
903 case HT_DIR_Y
: return px
== 0; // y direction
904 case HT_DIR_HU
: return px
== -py
|| px
== -py
- 16; // horizontal upper
905 case HT_DIR_HL
: return px
== -py
|| px
== -py
+ 16; // horizontal lower
906 case HT_DIR_VL
: return px
== py
|| px
== py
+ 16; // vertical left
907 case HT_DIR_VR
: return px
== py
|| px
== py
- 16; // vertical right
913 /* [direction][side] */
914 static const HighLightStyle _autorail_type
[6][2] = {
915 { HT_DIR_X
, HT_DIR_X
},
916 { HT_DIR_Y
, HT_DIR_Y
},
917 { HT_DIR_HU
, HT_DIR_HL
},
918 { HT_DIR_HL
, HT_DIR_HU
},
919 { HT_DIR_VL
, HT_DIR_VR
},
920 { HT_DIR_VR
, HT_DIR_VL
}
923 #include "table/autorail.h"
926 * Draws autorail highlights.
928 * @param *ti TileInfo Tile that is being drawn
929 * @param autorail_type Offset into _AutorailTilehSprite[][]
931 static void DrawAutorailSelection(const TileInfo
*ti
, uint autorail_type
)
937 FoundationPart foundation_part
= FOUNDATION_PART_NORMAL
;
938 Slope autorail_tileh
= RemoveHalftileSlope(ti
->tileh
);
939 if (IsHalftileSlope(ti
->tileh
)) {
940 static const uint _lower_rail
[4] = { 5U, 2U, 4U, 3U };
941 Corner halftile_corner
= GetHalftileSlopeCorner(ti
->tileh
);
942 if (autorail_type
!= _lower_rail
[halftile_corner
]) {
943 foundation_part
= FOUNDATION_PART_HALFTILE
;
944 /* Here we draw the highlights of the "three-corners-raised"-slope. That looks ok to me. */
945 autorail_tileh
= SlopeWithThreeCornersRaised(OppositeCorner(halftile_corner
));
949 offset
= _AutorailTilehSprite
[autorail_tileh
][autorail_type
];
951 image
= SPR_AUTORAIL_BASE
+ offset
;
954 image
= SPR_AUTORAIL_BASE
- offset
;
955 pal
= PALETTE_SEL_TILE_RED
;
958 DrawSelectionSprite(image
, _thd
.make_square_red
? PALETTE_SEL_TILE_RED
: pal
, ti
, 7, foundation_part
);
962 * Checks if the specified tile is selected and if so draws selection using correct selectionstyle.
963 * @param *ti TileInfo Tile that is being drawn
965 static void DrawTileSelection(const TileInfo
*ti
)
967 /* Draw a red error square? */
968 bool is_redsq
= _thd
.redsq
== ti
->tile
;
969 if (is_redsq
) DrawTileSelectionRect(ti
, PALETTE_TILE_RED_PULSATING
);
971 /* No tile selection active? */
972 if ((_thd
.drawstyle
& HT_DRAG_MASK
) == HT_NONE
) return;
974 if (_thd
.diagonal
) { // We're drawing a 45 degrees rotated (diagonal) rectangle
975 if (IsInsideRotatedRectangle((int)ti
->x
, (int)ti
->y
)) goto draw_inner
;
979 /* Inside the inner area? */
980 if (IsInsideBS(ti
->x
, _thd
.pos
.x
, _thd
.size
.x
) &&
981 IsInsideBS(ti
->y
, _thd
.pos
.y
, _thd
.size
.y
)) {
983 if (_thd
.drawstyle
& HT_RECT
) {
984 if (!is_redsq
) DrawTileSelectionRect(ti
, _thd
.make_square_red
? PALETTE_SEL_TILE_RED
: PAL_NONE
);
985 } else if (_thd
.drawstyle
& HT_POINT
) {
986 /* Figure out the Z coordinate for the single dot. */
988 FoundationPart foundation_part
= FOUNDATION_PART_NORMAL
;
989 if (ti
->tileh
& SLOPE_N
) {
991 if (RemoveHalftileSlope(ti
->tileh
) == SLOPE_STEEP_N
) z
+= TILE_HEIGHT
;
993 if (IsHalftileSlope(ti
->tileh
)) {
994 Corner halftile_corner
= GetHalftileSlopeCorner(ti
->tileh
);
995 if ((halftile_corner
== CORNER_W
) || (halftile_corner
== CORNER_E
)) z
+= TILE_HEIGHT
;
996 if (halftile_corner
!= CORNER_S
) {
997 foundation_part
= FOUNDATION_PART_HALFTILE
;
998 if (IsSteepSlope(ti
->tileh
)) z
-= TILE_HEIGHT
;
1001 DrawSelectionSprite(_cur_dpi
->zoom
<= ZOOM_LVL_DETAIL
? SPR_DOT
: SPR_DOT_SMALL
, PAL_NONE
, ti
, z
, foundation_part
);
1002 } else if (_thd
.drawstyle
& HT_RAIL
) {
1003 /* autorail highlight piece under cursor */
1004 HighLightStyle type
= _thd
.drawstyle
& HT_DIR_MASK
;
1005 assert(type
< HT_DIR_END
);
1006 DrawAutorailSelection(ti
, _autorail_type
[type
][0]);
1007 } else if (IsPartOfAutoLine(ti
->x
, ti
->y
)) {
1008 /* autorail highlighting long line */
1009 HighLightStyle dir
= _thd
.drawstyle
& HT_DIR_MASK
;
1012 if (dir
== HT_DIR_X
|| dir
== HT_DIR_Y
) {
1015 TileIndex start
= TileVirtXY(_thd
.selstart
.x
, _thd
.selstart
.y
);
1016 side
= Delta(Delta(TileX(start
), TileX(ti
->tile
)), Delta(TileY(start
), TileY(ti
->tile
)));
1019 DrawAutorailSelection(ti
, _autorail_type
[dir
][side
]);
1024 /* Check if it's inside the outer area? */
1025 if (!is_redsq
&& _thd
.outersize
.x
> 0 &&
1026 IsInsideBS(ti
->x
, _thd
.pos
.x
+ _thd
.offs
.x
, _thd
.size
.x
+ _thd
.outersize
.x
) &&
1027 IsInsideBS(ti
->y
, _thd
.pos
.y
+ _thd
.offs
.y
, _thd
.size
.y
+ _thd
.outersize
.y
)) {
1028 /* Draw a blue rect. */
1029 DrawTileSelectionRect(ti
, PALETTE_SEL_TILE_BLUE
);
1034 static void ViewportAddLandscape()
1036 int x
, y
, width
, height
;
1042 /* Transform into tile coordinates and round to closest full tile */
1043 x
= ((_vd
.dpi
.top
>> 1) - (_vd
.dpi
.left
>> 2)) & ~TILE_UNIT_MASK
;
1044 y
= ((_vd
.dpi
.top
>> 1) + (_vd
.dpi
.left
>> 2) - TILE_SIZE
) & ~TILE_UNIT_MASK
;
1046 /* determine size of area */
1048 Point pt
= RemapCoords(x
, y
, 241);
1049 width
= (_vd
.dpi
.left
+ _vd
.dpi
.width
- pt
.x
+ 95) >> 6;
1050 height
= (_vd
.dpi
.top
+ _vd
.dpi
.height
- pt
.y
) >> 5 << 1;
1059 int width_cur
= width
;
1064 TileType tt
= MP_VOID
;
1071 ti
.tileh
= SLOPE_FLAT
;
1072 ti
.tile
= INVALID_TILE
;
1074 if (x_cur
< MapMaxX() * TILE_SIZE
&&
1075 y_cur
< MapMaxY() * TILE_SIZE
) {
1076 TileIndex tile
= TileVirtXY(x_cur
, y_cur
);
1078 if (!_settings_game
.construction
.freeform_edges
|| (TileX(tile
) != 0 && TileY(tile
) != 0)) {
1079 if (x_cur
== ((int)MapMaxX() - 1) * TILE_SIZE
|| y_cur
== ((int)MapMaxY() - 1) * TILE_SIZE
) {
1080 uint maxh
= max
<uint
>(TileHeight(tile
), 1);
1081 for (uint h
= 0; h
< maxh
; h
++) {
1082 AddTileSpriteToDraw(SPR_SHADOW_CELL
, PAL_NONE
, ti
.x
, ti
.y
, h
* TILE_HEIGHT
);
1087 ti
.tileh
= GetTileSlope(tile
, &ti
.z
);
1088 tt
= GetTileType(tile
);
1092 _vd
.foundation_part
= FOUNDATION_PART_NONE
;
1093 _vd
.foundation
[0] = -1;
1094 _vd
.foundation
[1] = -1;
1095 _vd
.last_foundation_child
[0] = NULL
;
1096 _vd
.last_foundation_child
[1] = NULL
;
1098 _tile_type_procs
[tt
]->draw_tile_proc(&ti
);
1100 if ((x_cur
== (int)MapMaxX() * TILE_SIZE
&& IsInsideMM(y_cur
, 0, MapMaxY() * TILE_SIZE
+ 1)) ||
1101 (y_cur
== (int)MapMaxY() * TILE_SIZE
&& IsInsideMM(x_cur
, 0, MapMaxX() * TILE_SIZE
+ 1))) {
1102 TileIndex tile
= TileVirtXY(x_cur
, y_cur
);
1104 ti
.tileh
= GetTileSlope(tile
, &ti
.z
);
1105 tt
= GetTileType(tile
);
1107 if (ti
.tile
!= INVALID_TILE
) DrawTileSelection(&ti
);
1111 } while (--width_cur
);
1113 if ((direction
^= 1) != 0) {
1122 * Add a string to draw in the viewport
1123 * @param dpi current viewport area
1124 * @param small_from Zoomlevel from when the small font should be used
1125 * @param sign sign position and dimension
1126 * @param string_normal String for normal and 2x zoom level
1127 * @param string_small String for 4x and 8x zoom level
1128 * @param string_small_shadow Shadow string for 4x and 8x zoom level; or #STR_NULL if no shadow
1129 * @param colour colour of the sign background; or 0 if transparent
1131 void ViewportAddString(const DrawPixelInfo
*dpi
, ZoomLevel small_from
, const ViewportSign
*sign
, StringID string_normal
, StringID string_small
, StringID string_small_shadow
, uint64 params_1
, uint64 params_2
, Colours colour
)
1133 bool small
= dpi
->zoom
>= small_from
;
1135 int left
= dpi
->left
;
1137 int right
= left
+ dpi
->width
;
1138 int bottom
= top
+ dpi
->height
;
1140 int sign_height
= ScaleByZoom(VPSM_TOP
+ FONT_HEIGHT_NORMAL
+ VPSM_BOTTOM
, dpi
->zoom
);
1141 int sign_half_width
= ScaleByZoom((small
? sign
->width_small
: sign
->width_normal
) / 2, dpi
->zoom
);
1143 if (bottom
< sign
->top
||
1144 top
> sign
->top
+ sign_height
||
1145 right
< sign
->center
- sign_half_width
||
1146 left
> sign
->center
+ sign_half_width
) {
1151 AddStringToDraw(sign
->center
- sign_half_width
, sign
->top
, string_normal
, params_1
, params_2
, colour
, sign
->width_normal
);
1153 int shadow_offset
= 0;
1154 if (string_small_shadow
!= STR_NULL
) {
1156 AddStringToDraw(sign
->center
- sign_half_width
+ shadow_offset
, sign
->top
, string_small_shadow
, params_1
, params_2
, INVALID_COLOUR
, sign
->width_small
);
1158 AddStringToDraw(sign
->center
- sign_half_width
, sign
->top
- shadow_offset
, string_small
, params_1
, params_2
,
1159 colour
, sign
->width_small
| 0x8000);
1163 static void ViewportAddTownNames(DrawPixelInfo
*dpi
)
1165 if (!HasBit(_display_opt
, DO_SHOW_TOWN_NAMES
) || _game_mode
== GM_MENU
) return;
1169 ViewportAddString(dpi
, ZOOM_LVL_OUT_4X
, &t
->sign
,
1170 _settings_client
.gui
.population_in_label
? STR_VIEWPORT_TOWN_POP
: STR_VIEWPORT_TOWN
,
1171 STR_VIEWPORT_TOWN_TINY_WHITE
, STR_VIEWPORT_TOWN_TINY_BLACK
,
1172 t
->index
, t
->population
);
1177 static void ViewportAddStationNames(DrawPixelInfo
*dpi
)
1179 if (!(HasBit(_display_opt
, DO_SHOW_STATION_NAMES
) || HasBit(_display_opt
, DO_SHOW_WAYPOINT_NAMES
)) || _game_mode
== GM_MENU
) return;
1181 const BaseStation
*st
;
1182 FOR_ALL_BASE_STATIONS(st
) {
1183 /* Check whether the base station is a station or a waypoint */
1184 bool is_station
= Station::IsExpected(st
);
1186 /* Don't draw if the display options are disabled */
1187 if (!HasBit(_display_opt
, is_station
? DO_SHOW_STATION_NAMES
: DO_SHOW_WAYPOINT_NAMES
)) continue;
1189 /* Don't draw if station is owned by another company and competitor station names are hidden. Stations owned by none are never ignored. */
1190 if (!HasBit(_display_opt
, DO_SHOW_COMPETITOR_SIGNS
) && _local_company
!= st
->owner
&& st
->owner
!= OWNER_NONE
) continue;
1192 ViewportAddString(dpi
, ZOOM_LVL_OUT_4X
, &st
->sign
,
1193 is_station
? STR_VIEWPORT_STATION
: STR_VIEWPORT_WAYPOINT
,
1194 (is_station
? STR_VIEWPORT_STATION
: STR_VIEWPORT_WAYPOINT
) + 1, STR_NULL
,
1195 st
->index
, st
->facilities
, (st
->owner
== OWNER_NONE
|| !st
->IsInUse()) ? COLOUR_GREY
: _company_colours
[st
->owner
]);
1200 static void ViewportAddSigns(DrawPixelInfo
*dpi
)
1202 /* Signs are turned off or are invisible */
1203 if (!HasBit(_display_opt
, DO_SHOW_SIGNS
) || IsInvisibilitySet(TO_SIGNS
)) return;
1207 /* Don't draw if sign is owned by another company and competitor signs should be hidden.
1208 * Note: It is intentional that also signs owned by OWNER_NONE are hidden. Bankrupt
1209 * companies can leave OWNER_NONE signs after them. */
1210 if (!HasBit(_display_opt
, DO_SHOW_COMPETITOR_SIGNS
) && _local_company
!= si
->owner
) continue;
1212 ViewportAddString(dpi
, ZOOM_LVL_OUT_4X
, &si
->sign
,
1214 IsTransparencySet(TO_SIGNS
) ? STR_VIEWPORT_SIGN_SMALL_WHITE
: STR_VIEWPORT_SIGN_SMALL_BLACK
, STR_NULL
,
1215 si
->index
, 0, (si
->owner
== OWNER_NONE
) ? COLOUR_GREY
: _company_colours
[si
->owner
]);
1220 * Update the position of the viewport sign.
1221 * @param center the (preferred) center of the viewport sign
1222 * @param top the new top of the sign
1223 * @param str the string to show in the sign
1225 void ViewportSign::UpdatePosition(int center
, int top
, StringID str
)
1227 if (this->width_normal
!= 0) this->MarkDirty();
1231 char buffer
[DRAW_STRING_BUFFER
];
1233 GetString(buffer
, str
, lastof(buffer
));
1234 this->width_normal
= VPSM_LEFT
+ Align(GetStringBoundingBox(buffer
).width
, 2) + VPSM_RIGHT
;
1235 this->center
= center
;
1237 /* zoomed out version */
1238 this->width_small
= VPSM_LEFT
+ Align(GetStringBoundingBox(buffer
, FS_SMALL
).width
, 2) + VPSM_RIGHT
;
1244 * Mark the sign dirty in all viewports.
1248 void ViewportSign::MarkDirty() const
1250 /* We use ZOOM_LVL_MAX here, as every viewport can have another zoom,
1251 * and there is no way for us to know which is the biggest. So make the
1252 * biggest area dirty, and we are safe for sure.
1253 * We also add 1 to make sure the whole thing is redrawn. */
1254 MarkAllViewportsDirty(
1255 this->center
- ScaleByZoom(this->width_normal
/ 2 + 1, ZOOM_LVL_MAX
),
1256 this->top
- ScaleByZoom(1, ZOOM_LVL_MAX
),
1257 this->center
+ ScaleByZoom(this->width_normal
/ 2 + 1, ZOOM_LVL_MAX
),
1258 this->top
+ ScaleByZoom(VPSM_TOP
+ FONT_HEIGHT_NORMAL
+ VPSM_BOTTOM
+ 1, ZOOM_LVL_MAX
));
1261 static void ViewportDrawTileSprites(const TileSpriteToDrawVector
*tstdv
)
1263 const TileSpriteToDraw
*tsend
= tstdv
->End();
1264 for (const TileSpriteToDraw
*ts
= tstdv
->Begin(); ts
!= tsend
; ++ts
) {
1265 DrawSprite(ts
->image
, ts
->pal
, ts
->x
, ts
->y
, ts
->sub
);
1269 /** Sort parent sprites pointer array */
1270 static void ViewportSortParentSprites(ParentSpriteToSortVector
*psdv
)
1272 ParentSpriteToDraw
**psdvend
= psdv
->End();
1273 ParentSpriteToDraw
**psd
= psdv
->Begin();
1274 while (psd
!= psdvend
) {
1275 ParentSpriteToDraw
*ps
= *psd
;
1277 if (ps
->comparison_done
) {
1282 ps
->comparison_done
= true;
1284 for (ParentSpriteToDraw
**psd2
= psd
+ 1; psd2
!= psdvend
; psd2
++) {
1285 ParentSpriteToDraw
*ps2
= *psd2
;
1287 if (ps2
->comparison_done
) continue;
1289 /* Decide which comparator to use, based on whether the bounding
1292 if (ps
->xmax
>= ps2
->xmin
&& ps
->xmin
<= ps2
->xmax
&& // overlap in X?
1293 ps
->ymax
>= ps2
->ymin
&& ps
->ymin
<= ps2
->ymax
&& // overlap in Y?
1294 ps
->zmax
>= ps2
->zmin
&& ps
->zmin
<= ps2
->zmax
) { // overlap in Z?
1295 /* Use X+Y+Z as the sorting order, so sprites closer to the bottom of
1296 * the screen and with higher Z elevation, are drawn in front.
1297 * Here X,Y,Z are the coordinates of the "center of mass" of the sprite,
1298 * i.e. X=(left+right)/2, etc.
1299 * However, since we only care about order, don't actually divide / 2
1301 if (ps
->xmin
+ ps
->xmax
+ ps
->ymin
+ ps
->ymax
+ ps
->zmin
+ ps
->zmax
<=
1302 ps2
->xmin
+ ps2
->xmax
+ ps2
->ymin
+ ps2
->ymax
+ ps2
->zmin
+ ps2
->zmax
) {
1306 /* We only change the order, if it is definite.
1307 * I.e. every single order of X, Y, Z says ps2 is behind ps or they overlap.
1308 * That is: If one partial order says ps behind ps2, do not change the order.
1310 if (ps
->xmax
< ps2
->xmin
||
1311 ps
->ymax
< ps2
->ymin
||
1312 ps
->zmax
< ps2
->zmin
) {
1317 /* Move ps2 in front of ps */
1318 ParentSpriteToDraw
*temp
= ps2
;
1319 for (ParentSpriteToDraw
**psd3
= psd2
; psd3
> psd
; psd3
--) {
1320 *psd3
= *(psd3
- 1);
1327 static void ViewportDrawParentSprites(const ParentSpriteToSortVector
*psd
, const ChildScreenSpriteToDrawVector
*csstdv
)
1329 const ParentSpriteToDraw
* const *psd_end
= psd
->End();
1330 for (const ParentSpriteToDraw
* const *it
= psd
->Begin(); it
!= psd_end
; it
++) {
1331 const ParentSpriteToDraw
*ps
= *it
;
1332 if (ps
->image
!= SPR_EMPTY_BOUNDING_BOX
) DrawSprite(ps
->image
, ps
->pal
, ps
->x
, ps
->y
, ps
->sub
);
1334 int child_idx
= ps
->first_child
;
1335 while (child_idx
>= 0) {
1336 const ChildScreenSpriteToDraw
*cs
= csstdv
->Get(child_idx
);
1337 child_idx
= cs
->next
;
1338 DrawSprite(cs
->image
, cs
->pal
, ps
->left
+ cs
->x
, ps
->top
+ cs
->y
, cs
->sub
);
1344 * Draws the bounding boxes of all ParentSprites
1345 * @param psd Array of ParentSprites
1347 static void ViewportDrawBoundingBoxes(const ParentSpriteToSortVector
*psd
)
1349 const ParentSpriteToDraw
* const *psd_end
= psd
->End();
1350 for (const ParentSpriteToDraw
* const *it
= psd
->Begin(); it
!= psd_end
; it
++) {
1351 const ParentSpriteToDraw
*ps
= *it
;
1352 Point pt1
= RemapCoords(ps
->xmax
+ 1, ps
->ymax
+ 1, ps
->zmax
+ 1); // top front corner
1353 Point pt2
= RemapCoords(ps
->xmin
, ps
->ymax
+ 1, ps
->zmax
+ 1); // top left corner
1354 Point pt3
= RemapCoords(ps
->xmax
+ 1, ps
->ymin
, ps
->zmax
+ 1); // top right corner
1355 Point pt4
= RemapCoords(ps
->xmax
+ 1, ps
->ymax
+ 1, ps
->zmin
); // bottom front corner
1357 DrawBox( pt1
.x
, pt1
.y
,
1358 pt2
.x
- pt1
.x
, pt2
.y
- pt1
.y
,
1359 pt3
.x
- pt1
.x
, pt3
.y
- pt1
.y
,
1360 pt4
.x
- pt1
.x
, pt4
.y
- pt1
.y
);
1364 static void ViewportDrawStrings(DrawPixelInfo
*dpi
, const StringSpriteToDrawVector
*sstdv
)
1373 dp
.zoom
= ZOOM_LVL_NORMAL
;
1375 dp
.left
= UnScaleByZoom(dp
.left
, zoom
);
1376 dp
.top
= UnScaleByZoom(dp
.top
, zoom
);
1377 dp
.width
= UnScaleByZoom(dp
.width
, zoom
);
1378 dp
.height
= UnScaleByZoom(dp
.height
, zoom
);
1380 const StringSpriteToDraw
*ssend
= sstdv
->End();
1381 for (const StringSpriteToDraw
*ss
= sstdv
->Begin(); ss
!= ssend
; ++ss
) {
1382 TextColour colour
= TC_BLACK
;
1383 bool small
= HasBit(ss
->width
, 15);
1384 int w
= GB(ss
->width
, 0, 15);
1385 int x
= UnScaleByZoom(ss
->x
, zoom
);
1386 int y
= UnScaleByZoom(ss
->y
, zoom
);
1387 int h
= VPSM_TOP
+ (small
? FONT_HEIGHT_SMALL
: FONT_HEIGHT_NORMAL
) + VPSM_BOTTOM
;
1389 SetDParam(0, ss
->params
[0]);
1390 SetDParam(1, ss
->params
[1]);
1392 if (ss
->colour
!= INVALID_COLOUR
) {
1393 /* Do not draw signs nor station names if they are set invisible */
1394 if (IsInvisibilitySet(TO_SIGNS
) && ss
->string
!= STR_WHITE_SIGN
) continue;
1396 /* if we didn't draw a rectangle, or if transparant building is on,
1397 * draw the text in the colour the rectangle would have */
1398 if (IsTransparencySet(TO_SIGNS
) && ss
->string
!= STR_WHITE_SIGN
) {
1399 /* Real colours need the TC_IS_PALETTE_COLOUR flag
1400 * otherwise colours from _string_colourmap are assumed. */
1401 colour
= (TextColour
)_colour_gradient
[ss
->colour
][6] | TC_IS_PALETTE_COLOUR
;
1404 /* Draw the rectangle if 'tranparent station signs' is off,
1405 * or if we are drawing a general text sign (STR_WHITE_SIGN) */
1406 if (!IsTransparencySet(TO_SIGNS
) || ss
->string
== STR_WHITE_SIGN
) {
1408 x
, y
, x
+ w
, y
+ h
, ss
->colour
,
1409 IsTransparencySet(TO_SIGNS
) ? FR_TRANSPARENT
: FR_NONE
1414 DrawString(x
+ VPSM_LEFT
, x
+ w
- 1 - VPSM_RIGHT
, y
+ VPSM_TOP
, ss
->string
, colour
, SA_HOR_CENTER
);
1418 void ViewportDoDraw(const ViewPort
*vp
, int left
, int top
, int right
, int bottom
)
1420 DrawPixelInfo
*old_dpi
= _cur_dpi
;
1421 _cur_dpi
= &_vd
.dpi
;
1423 _vd
.dpi
.zoom
= vp
->zoom
;
1424 int mask
= ScaleByZoom(-1, vp
->zoom
);
1426 _vd
.combine_sprites
= SPRITE_COMBINE_NONE
;
1428 _vd
.dpi
.width
= (right
- left
) & mask
;
1429 _vd
.dpi
.height
= (bottom
- top
) & mask
;
1430 _vd
.dpi
.left
= left
& mask
;
1431 _vd
.dpi
.top
= top
& mask
;
1432 _vd
.dpi
.pitch
= old_dpi
->pitch
;
1433 _vd
.last_child
= NULL
;
1435 int x
= UnScaleByZoom(_vd
.dpi
.left
- (vp
->virtual_left
& mask
), vp
->zoom
) + vp
->left
;
1436 int y
= UnScaleByZoom(_vd
.dpi
.top
- (vp
->virtual_top
& mask
), vp
->zoom
) + vp
->top
;
1438 _vd
.dpi
.dst_ptr
= BlitterFactoryBase::GetCurrentBlitter()->MoveTo(old_dpi
->dst_ptr
, x
- old_dpi
->left
, y
- old_dpi
->top
);
1440 ViewportAddLandscape();
1441 ViewportAddVehicles(&_vd
.dpi
);
1443 ViewportAddTownNames(&_vd
.dpi
);
1444 ViewportAddStationNames(&_vd
.dpi
);
1445 ViewportAddSigns(&_vd
.dpi
);
1447 DrawTextEffects(&_vd
.dpi
);
1449 if (_vd
.tile_sprites_to_draw
.Length() != 0) ViewportDrawTileSprites(&_vd
.tile_sprites_to_draw
);
1451 ParentSpriteToDraw
*psd_end
= _vd
.parent_sprites_to_draw
.End();
1452 for (ParentSpriteToDraw
*it
= _vd
.parent_sprites_to_draw
.Begin(); it
!= psd_end
; it
++) {
1453 *_vd
.parent_sprites_to_sort
.Append() = it
;
1456 ViewportSortParentSprites(&_vd
.parent_sprites_to_sort
);
1457 ViewportDrawParentSprites(&_vd
.parent_sprites_to_sort
, &_vd
.child_screen_sprites_to_draw
);
1459 if (_draw_bounding_boxes
) ViewportDrawBoundingBoxes(&_vd
.parent_sprites_to_sort
);
1461 if (_vd
.string_sprites_to_draw
.Length() != 0) ViewportDrawStrings(&_vd
.dpi
, &_vd
.string_sprites_to_draw
);
1465 _vd
.string_sprites_to_draw
.Clear();
1466 _vd
.tile_sprites_to_draw
.Clear();
1467 _vd
.parent_sprites_to_draw
.Clear();
1468 _vd
.parent_sprites_to_sort
.Clear();
1469 _vd
.child_screen_sprites_to_draw
.Clear();
1473 * Make sure we don't draw a too big area at a time.
1474 * If we do, the sprite memory will overflow.
1476 static void ViewportDrawChk(const ViewPort
*vp
, int left
, int top
, int right
, int bottom
)
1478 if (ScaleByZoom(bottom
- top
, vp
->zoom
) * ScaleByZoom(right
- left
, vp
->zoom
) > 180000) {
1479 if ((bottom
- top
) > (right
- left
)) {
1480 int t
= (top
+ bottom
) >> 1;
1481 ViewportDrawChk(vp
, left
, top
, right
, t
);
1482 ViewportDrawChk(vp
, left
, t
, right
, bottom
);
1484 int t
= (left
+ right
) >> 1;
1485 ViewportDrawChk(vp
, left
, top
, t
, bottom
);
1486 ViewportDrawChk(vp
, t
, top
, right
, bottom
);
1490 ScaleByZoom(left
- vp
->left
, vp
->zoom
) + vp
->virtual_left
,
1491 ScaleByZoom(top
- vp
->top
, vp
->zoom
) + vp
->virtual_top
,
1492 ScaleByZoom(right
- vp
->left
, vp
->zoom
) + vp
->virtual_left
,
1493 ScaleByZoom(bottom
- vp
->top
, vp
->zoom
) + vp
->virtual_top
1498 static inline void ViewportDraw(const ViewPort
*vp
, int left
, int top
, int right
, int bottom
)
1500 if (right
<= vp
->left
|| bottom
<= vp
->top
) return;
1502 if (left
>= vp
->left
+ vp
->width
) return;
1504 if (left
< vp
->left
) left
= vp
->left
;
1505 if (right
> vp
->left
+ vp
->width
) right
= vp
->left
+ vp
->width
;
1507 if (top
>= vp
->top
+ vp
->height
) return;
1509 if (top
< vp
->top
) top
= vp
->top
;
1510 if (bottom
> vp
->top
+ vp
->height
) bottom
= vp
->top
+ vp
->height
;
1512 ViewportDrawChk(vp
, left
, top
, right
, bottom
);
1516 * Draw the viewport of this window.
1518 void Window::DrawViewport() const
1520 DrawPixelInfo
*dpi
= _cur_dpi
;
1522 dpi
->left
+= this->left
;
1523 dpi
->top
+= this->top
;
1525 ViewportDraw(this->viewport
, dpi
->left
, dpi
->top
, dpi
->left
+ dpi
->width
, dpi
->top
+ dpi
->height
);
1527 dpi
->left
-= this->left
;
1528 dpi
->top
-= this->top
;
1531 static inline void ClampViewportToMap(const ViewPort
*vp
, int &x
, int &y
)
1533 /* Centre of the viewport is hot spot */
1534 x
+= vp
->virtual_width
/ 2;
1535 y
+= vp
->virtual_height
/ 2;
1537 /* Convert viewport coordinates to map coordinates
1538 * Calculation is scaled by 4 to avoid rounding errors */
1539 int vx
= -x
+ y
* 2;
1542 /* clamp to size of map */
1543 vx
= Clamp(vx
, 0, MapMaxX() * TILE_SIZE
* 4);
1544 vy
= Clamp(vy
, 0, MapMaxY() * TILE_SIZE
* 4);
1546 /* Convert map coordinates to viewport coordinates */
1550 /* Remove centering */
1551 x
-= vp
->virtual_width
/ 2;
1552 y
-= vp
->virtual_height
/ 2;
1556 * Update the viewport position being displayed.
1557 * @param w %Window owning the viewport.
1559 void UpdateViewportPosition(Window
*w
)
1561 const ViewPort
*vp
= w
->viewport
;
1563 if (w
->viewport
->follow_vehicle
!= INVALID_VEHICLE
) {
1564 const Vehicle
*veh
= Vehicle::Get(w
->viewport
->follow_vehicle
);
1565 Point pt
= MapXYZToViewport(vp
, veh
->x_pos
, veh
->y_pos
, veh
->z_pos
);
1567 w
->viewport
->scrollpos_x
= pt
.x
;
1568 w
->viewport
->scrollpos_y
= pt
.y
;
1569 SetViewportPosition(w
, pt
.x
, pt
.y
);
1571 /* Ensure the destination location is within the map */
1572 ClampViewportToMap(vp
, w
->viewport
->dest_scrollpos_x
, w
->viewport
->dest_scrollpos_y
);
1574 int delta_x
= w
->viewport
->dest_scrollpos_x
- w
->viewport
->scrollpos_x
;
1575 int delta_y
= w
->viewport
->dest_scrollpos_y
- w
->viewport
->scrollpos_y
;
1577 if (delta_x
!= 0 || delta_y
!= 0) {
1578 if (_settings_client
.gui
.smooth_scroll
) {
1579 int max_scroll
= ScaleByMapSize1D(512);
1580 /* Not at our desired position yet... */
1581 w
->viewport
->scrollpos_x
+= Clamp(delta_x
/ 4, -max_scroll
, max_scroll
);
1582 w
->viewport
->scrollpos_y
+= Clamp(delta_y
/ 4, -max_scroll
, max_scroll
);
1584 w
->viewport
->scrollpos_x
= w
->viewport
->dest_scrollpos_x
;
1585 w
->viewport
->scrollpos_y
= w
->viewport
->dest_scrollpos_y
;
1589 ClampViewportToMap(vp
, w
->viewport
->scrollpos_x
, w
->viewport
->scrollpos_y
);
1591 SetViewportPosition(w
, w
->viewport
->scrollpos_x
, w
->viewport
->scrollpos_y
);
1596 * Marks a viewport as dirty for repaint if it displays (a part of) the area the needs to be repainted.
1597 * @param vp The viewport to mark as dirty
1598 * @param left Left edge of area to repaint
1599 * @param top Top edge of area to repaint
1600 * @param right Right edge of area to repaint
1601 * @param bottom Bottom edge of area to repaint
1604 static void MarkViewportDirty(const ViewPort
*vp
, int left
, int top
, int right
, int bottom
)
1606 right
-= vp
->virtual_left
;
1607 if (right
<= 0) return;
1609 bottom
-= vp
->virtual_top
;
1610 if (bottom
<= 0) return;
1612 left
= max(0, left
- vp
->virtual_left
);
1614 if (left
>= vp
->virtual_width
) return;
1616 top
= max(0, top
- vp
->virtual_top
);
1618 if (top
>= vp
->virtual_height
) return;
1621 UnScaleByZoomLower(left
, vp
->zoom
) + vp
->left
,
1622 UnScaleByZoomLower(top
, vp
->zoom
) + vp
->top
,
1623 UnScaleByZoom(right
, vp
->zoom
) + vp
->left
+ 1,
1624 UnScaleByZoom(bottom
, vp
->zoom
) + vp
->top
+ 1
1629 * Mark all viewports that display an area as dirty (in need of repaint).
1630 * @param left Left edge of area to repaint
1631 * @param top Top edge of area to repaint
1632 * @param right Right edge of area to repaint
1633 * @param bottom Bottom edge of area to repaint
1636 void MarkAllViewportsDirty(int left
, int top
, int right
, int bottom
)
1639 FOR_ALL_WINDOWS_FROM_BACK(w
) {
1640 ViewPort
*vp
= w
->viewport
;
1642 assert(vp
->width
!= 0);
1643 MarkViewportDirty(vp
, left
, top
, right
, bottom
);
1649 * Mark a tile given by its index dirty for repaint.
1650 * @param tile The tile to mark dirty.
1653 void MarkTileDirtyByTile(TileIndex tile
)
1655 Point pt
= RemapCoords(TileX(tile
) * TILE_SIZE
, TileY(tile
) * TILE_SIZE
, GetTileZ(tile
));
1656 MarkAllViewportsDirty(
1665 * Marks the selected tiles as dirty.
1667 * This function marks the selected tiles as dirty for repaint
1671 static void SetSelectionTilesDirty()
1673 int x_size
= _thd
.size
.x
;
1674 int y_size
= _thd
.size
.y
;
1676 if (!_thd
.diagonal
) { // Selecting in a straigth rectangle (or a single square)
1677 int x_start
= _thd
.pos
.x
;
1678 int y_start
= _thd
.pos
.y
;
1680 if (_thd
.outersize
.x
!= 0) {
1681 x_size
+= _thd
.outersize
.x
;
1682 x_start
+= _thd
.offs
.x
;
1683 y_size
+= _thd
.outersize
.y
;
1684 y_start
+= _thd
.offs
.y
;
1687 x_size
-= TILE_SIZE
;
1688 y_size
-= TILE_SIZE
;
1690 assert(x_size
>= 0);
1691 assert(y_size
>= 0);
1693 int x_end
= Clamp(x_start
+ x_size
, 0, MapSizeX() * TILE_SIZE
- TILE_SIZE
);
1694 int y_end
= Clamp(y_start
+ y_size
, 0, MapSizeY() * TILE_SIZE
- TILE_SIZE
);
1696 x_start
= Clamp(x_start
, 0, MapSizeX() * TILE_SIZE
- TILE_SIZE
);
1697 y_start
= Clamp(y_start
, 0, MapSizeY() * TILE_SIZE
- TILE_SIZE
);
1699 /* make sure everything is multiple of TILE_SIZE */
1700 assert((x_end
| y_end
| x_start
| y_start
) % TILE_SIZE
== 0);
1703 * Suppose we have to mark dirty rectangle of 3x4 tiles:
1710 * This algorithm marks dirty columns of tiles, so it is done in 3+4-1 steps:
1720 int top_x
= x_end
; // coordinates of top dirty tile
1721 int top_y
= y_start
;
1722 int bot_x
= top_x
; // coordinates of bottom dirty tile
1726 /* topmost dirty point */
1727 TileIndex top_tile
= TileVirtXY(top_x
, top_y
);
1728 Point top
= RemapCoords(top_x
, top_y
, GetTileMaxZ(top_tile
));
1730 /* bottommost point */
1731 TileIndex bottom_tile
= TileVirtXY(bot_x
, bot_y
);
1732 Point bot
= RemapCoords(bot_x
+ TILE_SIZE
, bot_y
+ TILE_SIZE
, GetTileZ(bottom_tile
)); // bottommost point
1734 /* the 'x' coordinate of 'top' and 'bot' is the same (and always in the same distance from tile middle),
1735 * tile height/slope affects only the 'y' on-screen coordinate! */
1737 int l
= top
.x
- (TILE_PIXELS
- 2); // 'x' coordinate of left side of dirty rectangle
1738 int t
= top
.y
; // 'y' coordinate of top side -//-
1739 int r
= top
.x
+ (TILE_PIXELS
- 2); // right side of dirty rectangle
1740 int b
= bot
.y
; // bottom -//-
1742 static const int OVERLAY_WIDTH
= 4; // part of selection sprites is drawn outside the selected area
1744 /* For halftile foundations on SLOPE_STEEP_S the sprite extents some more towards the top */
1745 MarkAllViewportsDirty(l
- OVERLAY_WIDTH
, t
- OVERLAY_WIDTH
- TILE_HEIGHT
, r
+ OVERLAY_WIDTH
, b
+ OVERLAY_WIDTH
);
1747 /* haven't we reached the topmost tile yet? */
1748 if (top_x
!= x_start
) {
1754 /* the way the bottom tile changes is different when we reach the bottommost tile */
1755 if (bot_y
!= y_end
) {
1760 } while (bot_x
>= top_x
);
1761 } else { // Selecting in a 45 degrees rotated (diagonal) rectangle.
1762 /* a_size, b_size describe a rectangle with rotated coordinates */
1763 int a_size
= x_size
+ y_size
, b_size
= x_size
- y_size
;
1765 int interval_a
= a_size
< 0 ? -(int)TILE_SIZE
: (int)TILE_SIZE
;
1766 int interval_b
= b_size
< 0 ? -(int)TILE_SIZE
: (int)TILE_SIZE
;
1768 for (int a
= -interval_a
; a
!= a_size
+ interval_a
; a
+= interval_a
) {
1769 for (int b
= -interval_b
; b
!= b_size
+ interval_b
; b
+= interval_b
) {
1770 uint x
= (_thd
.pos
.x
+ (a
+ b
) / 2) / TILE_SIZE
;
1771 uint y
= (_thd
.pos
.y
+ (a
- b
) / 2) / TILE_SIZE
;
1773 if (x
< MapMaxX() && y
< MapMaxY()) {
1774 MarkTileDirtyByTile(TileXY(x
, y
));
1782 void SetSelectionRed(bool b
)
1784 _thd
.make_square_red
= b
;
1785 SetSelectionTilesDirty();
1789 * Test whether a sign is below the mouse
1790 * @param vp the clicked viewport
1791 * @param x X position of click
1792 * @param y Y position of click
1793 * @param sign the sign to check
1794 * @return true if the sign was hit
1796 static bool CheckClickOnViewportSign(const ViewPort
*vp
, int x
, int y
, const ViewportSign
*sign
)
1798 bool small
= (vp
->zoom
>= ZOOM_LVL_OUT_4X
);
1799 int sign_half_width
= ScaleByZoom((small
? sign
->width_small
: sign
->width_normal
) / 2, vp
->zoom
);
1800 int sign_height
= ScaleByZoom(VPSM_TOP
+ (small
? FONT_HEIGHT_SMALL
: FONT_HEIGHT_NORMAL
) + VPSM_BOTTOM
, vp
->zoom
);
1802 x
= ScaleByZoom(x
- vp
->left
, vp
->zoom
) + vp
->virtual_left
;
1803 y
= ScaleByZoom(y
- vp
->top
, vp
->zoom
) + vp
->virtual_top
;
1805 return y
>= sign
->top
&& y
< sign
->top
+ sign_height
&&
1806 x
>= sign
->center
- sign_half_width
&& x
< sign
->center
+ sign_half_width
;
1809 static bool CheckClickOnTown(const ViewPort
*vp
, int x
, int y
)
1811 if (!HasBit(_display_opt
, DO_SHOW_TOWN_NAMES
)) return false;
1815 if (CheckClickOnViewportSign(vp
, x
, y
, &t
->sign
)) {
1816 ShowTownViewWindow(t
->index
);
1824 static bool CheckClickOnStation(const ViewPort
*vp
, int x
, int y
)
1826 if (!(HasBit(_display_opt
, DO_SHOW_STATION_NAMES
) || HasBit(_display_opt
, DO_SHOW_WAYPOINT_NAMES
)) || IsInvisibilitySet(TO_SIGNS
)) return false;
1828 const BaseStation
*st
;
1829 FOR_ALL_BASE_STATIONS(st
) {
1830 /* Check whether the base station is a station or a waypoint */
1831 bool is_station
= Station::IsExpected(st
);
1833 /* Don't check if the display options are disabled */
1834 if (!HasBit(_display_opt
, is_station
? DO_SHOW_STATION_NAMES
: DO_SHOW_WAYPOINT_NAMES
)) continue;
1836 /* Don't check if competitor signs are not shown and the sign isn't owned by the local company */
1837 if (!HasBit(_display_opt
, DO_SHOW_COMPETITOR_SIGNS
) && _local_company
!= st
->owner
&& st
->owner
!= OWNER_NONE
) continue;
1839 if (CheckClickOnViewportSign(vp
, x
, y
, &st
->sign
)) {
1841 ShowStationViewWindow(st
->index
);
1843 ShowWaypointWindow(Waypoint::From(st
));
1853 static bool CheckClickOnSign(const ViewPort
*vp
, int x
, int y
)
1855 /* Signs are turned off, or they are transparent and invisibility is ON, or company is a spectator */
1856 if (!HasBit(_display_opt
, DO_SHOW_SIGNS
) || IsInvisibilitySet(TO_SIGNS
) || _local_company
== COMPANY_SPECTATOR
) return false;
1860 /* If competitor signs are hidden, don't check signs that aren't owned by local company */
1861 if (!HasBit(_display_opt
, DO_SHOW_COMPETITOR_SIGNS
) && _local_company
!= si
->owner
) continue;
1863 if (CheckClickOnViewportSign(vp
, x
, y
, &si
->sign
)) {
1864 HandleClickOnSign(si
);
1873 static bool CheckClickOnLandscape(const ViewPort
*vp
, int x
, int y
)
1875 Point pt
= TranslateXYToTileCoord(vp
, x
, y
);
1877 if (pt
.x
!= -1) return ClickTile(TileVirtXY(pt
.x
, pt
.y
));
1881 static void PlaceObject()
1886 pt
= GetTileBelowCursor();
1887 if (pt
.x
== -1) return;
1889 if ((_thd
.place_mode
& HT_DRAG_MASK
) == HT_POINT
) {
1894 _tile_fract_coords
.x
= pt
.x
& TILE_UNIT_MASK
;
1895 _tile_fract_coords
.y
= pt
.y
& TILE_UNIT_MASK
;
1897 w
= _thd
.GetCallbackWnd();
1898 if (w
!= NULL
) w
->OnPlaceObject(pt
, TileVirtXY(pt
.x
, pt
.y
));
1902 bool HandleViewportClicked(const ViewPort
*vp
, int x
, int y
)
1904 const Vehicle
*v
= CheckClickOnVehicle(vp
, x
, y
);
1906 if (_thd
.place_mode
& HT_VEHICLE
) {
1907 if (v
!= NULL
&& VehicleClicked(v
)) return true;
1910 /* Vehicle placement mode already handled above. */
1911 if ((_thd
.place_mode
& HT_DRAG_MASK
) != HT_NONE
) {
1916 if (CheckClickOnTown(vp
, x
, y
)) return true;
1917 if (CheckClickOnStation(vp
, x
, y
)) return true;
1918 if (CheckClickOnSign(vp
, x
, y
)) return true;
1919 bool result
= CheckClickOnLandscape(vp
, x
, y
);
1922 DEBUG(misc
, 2, "Vehicle %d (index %d) at %p", v
->unitnumber
, v
->index
, v
);
1923 if (IsCompanyBuildableVehicleType(v
)) {
1925 if (_ctrl_pressed
&& v
->owner
== _local_company
) {
1926 StartStopVehicle(v
, true);
1928 ShowVehicleViewWindow(v
);
1938 * Scrolls the viewport in a window to a given location.
1939 * @param x Desired x location of the map to scroll to (world coordinate).
1940 * @param y Desired y location of the map to scroll to (world coordinate).
1941 * @param z Desired z location of the map to scroll to (world coordinate). Use \c -1 to scroll to the height of the map at the \a x, \a y location.
1942 * @param w %Window containing the viewport.
1943 * @param instant Jump to the location instead of slowly moving to it.
1944 * @return Destination of the viewport was changed (to activate other actions when the viewport is already at the desired position).
1946 bool ScrollWindowTo(int x
, int y
, int z
, Window
*w
, bool instant
)
1948 /* The slope cannot be acquired outside of the map, so make sure we are always within the map. */
1949 if (z
== -1) z
= GetSlopeZ(Clamp(x
, 0, MapSizeX() * TILE_SIZE
- 1), Clamp(y
, 0, MapSizeY() * TILE_SIZE
- 1));
1951 Point pt
= MapXYZToViewport(w
->viewport
, x
, y
, z
);
1952 w
->viewport
->follow_vehicle
= INVALID_VEHICLE
;
1954 if (w
->viewport
->dest_scrollpos_x
== pt
.x
&& w
->viewport
->dest_scrollpos_y
== pt
.y
) return false;
1957 w
->viewport
->scrollpos_x
= pt
.x
;
1958 w
->viewport
->scrollpos_y
= pt
.y
;
1961 w
->viewport
->dest_scrollpos_x
= pt
.x
;
1962 w
->viewport
->dest_scrollpos_y
= pt
.y
;
1967 * Scrolls the viewport in a window to a given location.
1968 * @param tile Desired tile to center on.
1969 * @param w %Window containing the viewport.
1970 * @param instant Jump to the location instead of slowly moving to it.
1971 * @return Destination of the viewport was changed (to activate other actions when the viewport is already at the desired position).
1973 bool ScrollWindowToTile(TileIndex tile
, Window
*w
, bool instant
)
1975 return ScrollWindowTo(TileX(tile
) * TILE_SIZE
, TileY(tile
) * TILE_SIZE
, -1, w
, instant
);
1979 * Scrolls the viewport of the main window to a given location.
1980 * @param tile Desired tile to center on.
1981 * @param instant Jump to the location instead of slowly moving to it.
1982 * @return Destination of the viewport was changed (to activate other actions when the viewport is already at the desired position).
1984 bool ScrollMainWindowToTile(TileIndex tile
, bool instant
)
1986 return ScrollMainWindowTo(TileX(tile
) * TILE_SIZE
+ TILE_SIZE
/ 2, TileY(tile
) * TILE_SIZE
+ TILE_SIZE
/ 2, -1, instant
);
1990 * Set a tile to display a red error square.
1991 * @param tile Tile that should show the red error square.
1993 void SetRedErrorSquare(TileIndex tile
)
2001 if (tile
!= INVALID_TILE
) MarkTileDirtyByTile(tile
);
2002 if (old
!= INVALID_TILE
) MarkTileDirtyByTile(old
);
2007 * Highlight \a w by \a h tiles at the cursor.
2008 * @param w Width of the highlighted tiles rectangle.
2009 * @param h Height of the highlighted tiles rectangle.
2011 void SetTileSelectSize(int w
, int h
)
2013 _thd
.new_size
.x
= w
* TILE_SIZE
;
2014 _thd
.new_size
.y
= h
* TILE_SIZE
;
2015 _thd
.new_outersize
.x
= 0;
2016 _thd
.new_outersize
.y
= 0;
2019 void SetTileSelectBigSize(int ox
, int oy
, int sx
, int sy
)
2021 _thd
.offs
.x
= ox
* TILE_SIZE
;
2022 _thd
.offs
.y
= oy
* TILE_SIZE
;
2023 _thd
.new_outersize
.x
= sx
* TILE_SIZE
;
2024 _thd
.new_outersize
.y
= sy
* TILE_SIZE
;
2027 /** returns the best autorail highlight type from map coordinates */
2028 static HighLightStyle
GetAutorailHT(int x
, int y
)
2030 return HT_RAIL
| _autorail_piece
[x
& TILE_UNIT_MASK
][y
& TILE_UNIT_MASK
];
2034 * Reset tile highlighting.
2036 void TileHighlightData::Reset()
2040 this->new_pos
.x
= 0;
2041 this->new_pos
.y
= 0;
2045 * Is the user dragging a 'diagonal rectangle'?
2046 * @return User is dragging a rotated rectangle.
2048 bool TileHighlightData::IsDraggingDiagonal()
2050 return (this->place_mode
& HT_DIAGONAL
) != 0 && _ctrl_pressed
&& _left_button_down
;
2054 * Get the window that started the current highlighting.
2055 * @return The window that requested the current tile highlighting, or \c NULL if not available.
2057 Window
*TileHighlightData::GetCallbackWnd()
2059 return FindWindowById(this->window_class
, this->window_number
);
2065 * Updates tile highlighting for all cases.
2066 * Uses _thd.selstart and _thd.selend and _thd.place_mode (set elsewhere) to determine _thd.pos and _thd.size
2067 * Also drawstyle is determined. Uses _thd.new.* as a buffer and calls SetSelectionTilesDirty() twice,
2068 * Once for the old and once for the new selection.
2069 * _thd is TileHighlightData, found in viewport.h
2071 void UpdateTileSelection()
2076 HighLightStyle new_drawstyle
= HT_NONE
;
2077 bool new_diagonal
= false;
2079 if ((_thd
.place_mode
& HT_DRAG_MASK
) == HT_SPECIAL
) {
2083 int x2
= _thd
.selstart
.x
& ~TILE_UNIT_MASK
;
2084 int y2
= _thd
.selstart
.y
& ~TILE_UNIT_MASK
;
2085 x1
&= ~TILE_UNIT_MASK
;
2086 y1
&= ~TILE_UNIT_MASK
;
2088 if (_thd
.IsDraggingDiagonal()) {
2089 new_diagonal
= true;
2091 if (x1
>= x2
) Swap(x1
, x2
);
2092 if (y1
>= y2
) Swap(y1
, y2
);
2094 _thd
.new_pos
.x
= x1
;
2095 _thd
.new_pos
.y
= y1
;
2096 _thd
.new_size
.x
= x2
- x1
;
2097 _thd
.new_size
.y
= y2
- y1
;
2098 if (!new_diagonal
) {
2099 _thd
.new_size
.x
+= TILE_SIZE
;
2100 _thd
.new_size
.y
+= TILE_SIZE
;
2102 new_drawstyle
= _thd
.next_drawstyle
;
2104 } else if ((_thd
.place_mode
& HT_DRAG_MASK
) != HT_NONE
) {
2105 Point pt
= GetTileBelowCursor();
2109 switch (_thd
.place_mode
& HT_DRAG_MASK
) {
2111 new_drawstyle
= HT_RECT
;
2114 new_drawstyle
= HT_POINT
;
2115 x1
+= TILE_SIZE
/ 2;
2116 y1
+= TILE_SIZE
/ 2;
2119 /* Draw one highlighted tile in any direction */
2120 new_drawstyle
= GetAutorailHT(pt
.x
, pt
.y
);
2123 switch (_thd
.place_mode
& HT_DIR_MASK
) {
2124 case HT_DIR_X
: new_drawstyle
= HT_LINE
| HT_DIR_X
; break;
2125 case HT_DIR_Y
: new_drawstyle
= HT_LINE
| HT_DIR_Y
; break;
2129 new_drawstyle
= (pt
.x
& TILE_UNIT_MASK
) + (pt
.y
& TILE_UNIT_MASK
) <= TILE_SIZE
? HT_LINE
| HT_DIR_HU
: HT_LINE
| HT_DIR_HL
;
2134 new_drawstyle
= (pt
.x
& TILE_UNIT_MASK
) > (pt
.y
& TILE_UNIT_MASK
) ? HT_LINE
| HT_DIR_VL
: HT_LINE
| HT_DIR_VR
;
2137 default: NOT_REACHED();
2139 _thd
.selstart
.x
= x1
& ~TILE_UNIT_MASK
;
2140 _thd
.selstart
.y
= y1
& ~TILE_UNIT_MASK
;
2146 _thd
.new_pos
.x
= x1
& ~TILE_UNIT_MASK
;
2147 _thd
.new_pos
.y
= y1
& ~TILE_UNIT_MASK
;
2151 /* redraw selection */
2152 if (_thd
.drawstyle
!= new_drawstyle
||
2153 _thd
.pos
.x
!= _thd
.new_pos
.x
|| _thd
.pos
.y
!= _thd
.new_pos
.y
||
2154 _thd
.size
.x
!= _thd
.new_size
.x
|| _thd
.size
.y
!= _thd
.new_size
.y
||
2155 _thd
.outersize
.x
!= _thd
.new_outersize
.x
||
2156 _thd
.outersize
.y
!= _thd
.new_outersize
.y
||
2157 _thd
.diagonal
!= new_diagonal
) {
2158 /* Clear the old tile selection? */
2159 if ((_thd
.drawstyle
& HT_DRAG_MASK
) != HT_NONE
) SetSelectionTilesDirty();
2161 _thd
.drawstyle
= new_drawstyle
;
2162 _thd
.pos
= _thd
.new_pos
;
2163 _thd
.size
= _thd
.new_size
;
2164 _thd
.outersize
= _thd
.new_outersize
;
2165 _thd
.diagonal
= new_diagonal
;
2168 /* Draw the new tile selection? */
2169 if ((new_drawstyle
& HT_DRAG_MASK
) != HT_NONE
) SetSelectionTilesDirty();
2174 * Displays the measurement tooltips when selecting multiple tiles
2175 * @param str String to be displayed
2176 * @param paramcount number of params to deal with
2177 * @param params (optional) up to 5 pieces of additional information that may be added to a tooltip
2178 * @param close_cond Condition for closing this tooltip.
2180 static inline void ShowMeasurementTooltips(StringID str
, uint paramcount
, const uint64 params
[], TooltipCloseCondition close_cond
= TCC_LEFT_CLICK
)
2182 if (!_settings_client
.gui
.measure_tooltip
) return;
2183 GuiShowTooltips(_thd
.GetCallbackWnd(), str
, paramcount
, params
, close_cond
);
2186 /** highlighting tiles while only going over them with the mouse */
2187 void VpStartPlaceSizing(TileIndex tile
, ViewportPlaceMethod method
, ViewportDragDropSelectionProcess process
)
2189 _thd
.select_method
= method
;
2190 _thd
.select_proc
= process
;
2191 _thd
.selend
.x
= TileX(tile
) * TILE_SIZE
;
2192 _thd
.selstart
.x
= TileX(tile
) * TILE_SIZE
;
2193 _thd
.selend
.y
= TileY(tile
) * TILE_SIZE
;
2194 _thd
.selstart
.y
= TileY(tile
) * TILE_SIZE
;
2196 /* Needed so several things (road, autoroad, bridges, ...) are placed correctly.
2197 * In effect, placement starts from the centre of a tile
2199 if (method
== VPM_X_OR_Y
|| method
== VPM_FIX_X
|| method
== VPM_FIX_Y
) {
2200 _thd
.selend
.x
+= TILE_SIZE
/ 2;
2201 _thd
.selend
.y
+= TILE_SIZE
/ 2;
2202 _thd
.selstart
.x
+= TILE_SIZE
/ 2;
2203 _thd
.selstart
.y
+= TILE_SIZE
/ 2;
2206 HighLightStyle others
= _thd
.place_mode
& ~(HT_DRAG_MASK
| HT_DIR_MASK
);
2207 if ((_thd
.place_mode
& HT_DRAG_MASK
) == HT_RECT
) {
2208 _thd
.place_mode
= HT_SPECIAL
| others
;
2209 _thd
.next_drawstyle
= HT_RECT
| others
;
2210 } else if (_thd
.place_mode
& (HT_RAIL
| HT_LINE
)) {
2211 _thd
.place_mode
= HT_SPECIAL
| others
;
2212 _thd
.next_drawstyle
= _thd
.drawstyle
| others
;
2214 _thd
.place_mode
= HT_SPECIAL
| others
;
2215 _thd
.next_drawstyle
= HT_POINT
| others
;
2217 _special_mouse_mode
= WSM_SIZING
;
2220 void VpSetPlaceSizingLimit(int limit
)
2222 _thd
.sizelimit
= limit
;
2226 * Highlights all tiles between a set of two tiles. Used in dock and tunnel placement
2227 * @param from TileIndex of the first tile to highlight
2228 * @param to TileIndex of the last tile to highlight
2230 void VpSetPresizeRange(TileIndex from
, TileIndex to
)
2232 uint64 distance
= DistanceManhattan(from
, to
) + 1;
2234 _thd
.selend
.x
= TileX(to
) * TILE_SIZE
;
2235 _thd
.selend
.y
= TileY(to
) * TILE_SIZE
;
2236 _thd
.selstart
.x
= TileX(from
) * TILE_SIZE
;
2237 _thd
.selstart
.y
= TileY(from
) * TILE_SIZE
;
2238 _thd
.next_drawstyle
= HT_RECT
;
2240 /* show measurement only if there is any length to speak of */
2241 if (distance
> 1) ShowMeasurementTooltips(STR_MEASURE_LENGTH
, 1, &distance
, TCC_HOVER
);
2244 static void VpStartPreSizing()
2247 _special_mouse_mode
= WSM_PRESIZE
;
2251 * returns information about the 2x1 piece to be build.
2252 * The lower bits (0-3) are the track type.
2254 static HighLightStyle
Check2x1AutoRail(int mode
)
2256 int fxpy
= _tile_fract_coords
.x
+ _tile_fract_coords
.y
;
2257 int sxpy
= (_thd
.selend
.x
& TILE_UNIT_MASK
) + (_thd
.selend
.y
& TILE_UNIT_MASK
);
2258 int fxmy
= _tile_fract_coords
.x
- _tile_fract_coords
.y
;
2259 int sxmy
= (_thd
.selend
.x
& TILE_UNIT_MASK
) - (_thd
.selend
.y
& TILE_UNIT_MASK
);
2262 default: NOT_REACHED();
2263 case 0: // end piece is lower right
2264 if (fxpy
>= 20 && sxpy
<= 12) return HT_DIR_HL
;
2265 if (fxmy
< -3 && sxmy
> 3) return HT_DIR_VR
;
2269 if (fxmy
> 3 && sxmy
< -3) return HT_DIR_VL
;
2270 if (fxpy
<= 12 && sxpy
>= 20) return HT_DIR_HU
;
2274 if (fxmy
> 3 && sxmy
< -3) return HT_DIR_VL
;
2275 if (fxpy
>= 20 && sxpy
<= 12) return HT_DIR_HL
;
2279 if (fxmy
< -3 && sxmy
> 3) return HT_DIR_VR
;
2280 if (fxpy
<= 12 && sxpy
>= 20) return HT_DIR_HU
;
2286 * Check if the direction of start and end tile should be swapped based on
2287 * the dragging-style. Default directions are:
2288 * in the case of a line (HT_RAIL, HT_LINE): DIR_NE, DIR_NW, DIR_N, DIR_E
2289 * in the case of a rect (HT_RECT, HT_POINT): DIR_S, DIR_E
2290 * For example dragging a rectangle area from south to north should be swapped to
2291 * north-south (DIR_S) to obtain the same results with less code. This is what
2292 * the return value signifies.
2293 * @param style HighLightStyle dragging style
2294 * @param start_tile start tile of drag
2295 * @param end_tile end tile of drag
2296 * @return boolean value which when true means start/end should be swapped
2298 static bool SwapDirection(HighLightStyle style
, TileIndex start_tile
, TileIndex end_tile
)
2300 uint start_x
= TileX(start_tile
);
2301 uint start_y
= TileY(start_tile
);
2302 uint end_x
= TileX(end_tile
);
2303 uint end_y
= TileY(end_tile
);
2305 switch (style
& HT_DRAG_MASK
) {
2307 case HT_LINE
: return (end_x
> start_x
|| (end_x
== start_x
&& end_y
> start_y
));
2310 case HT_POINT
: return (end_x
!= start_x
&& end_y
< start_y
);
2311 default: NOT_REACHED();
2318 * Calculates height difference between one tile and another.
2319 * Multiplies the result to suit the standard given by #TILE_HEIGHT_STEP.
2321 * To correctly get the height difference we need the direction we are dragging
2322 * in, as well as with what kind of tool we are dragging. For example a horizontal
2323 * autorail tool that starts in bottom and ends at the top of a tile will need the
2324 * maximum of SW, S and SE, N corners respectively. This is handled by the lookup table below
2325 * See #_tileoffs_by_dir in map.cpp for the direction enums if you can't figure out the values yourself.
2326 * @param style Highlighting style of the drag. This includes direction and style (autorail, rect, etc.)
2327 * @param distance Number of tiles dragged, important for horizontal/vertical drags, ignored for others.
2328 * @param start_tile Start tile of the drag operation.
2329 * @param end_tile End tile of the drag operation.
2330 * @return Height difference between two tiles. The tile measurement tool utilizes this value in its tooltip.
2332 static int CalcHeightdiff(HighLightStyle style
, uint distance
, TileIndex start_tile
, TileIndex end_tile
)
2334 bool swap
= SwapDirection(style
, start_tile
, end_tile
);
2335 uint h0
, h1
; // Start height and end height.
2337 if (start_tile
== end_tile
) return 0;
2338 if (swap
) Swap(start_tile
, end_tile
);
2340 switch (style
& HT_DRAG_MASK
) {
2342 static const TileIndexDiffC heightdiff_area_by_dir
[] = {
2343 /* Start */ {1, 0}, /* Dragging east */ {0, 0}, // Dragging south
2344 /* End */ {0, 1}, /* Dragging east */ {1, 1} // Dragging south
2347 /* In the case of an area we can determine whether we were dragging south or
2348 * east by checking the X-coordinates of the tiles */
2349 byte style_t
= (byte
)(TileX(end_tile
) > TileX(start_tile
));
2350 start_tile
= TILE_ADD(start_tile
, ToTileIndexDiff(heightdiff_area_by_dir
[style_t
]));
2351 end_tile
= TILE_ADD(end_tile
, ToTileIndexDiff(heightdiff_area_by_dir
[2 + style_t
]));
2356 h0
= TileHeight(start_tile
);
2357 h1
= TileHeight(end_tile
);
2359 default: { // All other types, this is mostly only line/autorail
2360 static const HighLightStyle flip_style_direction
[] = {
2361 HT_DIR_X
, HT_DIR_Y
, HT_DIR_HL
, HT_DIR_HU
, HT_DIR_VR
, HT_DIR_VL
2363 static const TileIndexDiffC heightdiff_line_by_dir
[] = {
2364 /* Start */ {1, 0}, {1, 1}, /* HT_DIR_X */ {0, 1}, {1, 1}, // HT_DIR_Y
2365 /* Start */ {1, 0}, {0, 0}, /* HT_DIR_HU */ {1, 0}, {1, 1}, // HT_DIR_HL
2366 /* Start */ {1, 0}, {1, 1}, /* HT_DIR_VL */ {0, 1}, {1, 1}, // HT_DIR_VR
2368 /* Start */ {0, 1}, {0, 0}, /* HT_DIR_X */ {1, 0}, {0, 0}, // HT_DIR_Y
2369 /* End */ {0, 1}, {0, 0}, /* HT_DIR_HU */ {1, 1}, {0, 1}, // HT_DIR_HL
2370 /* End */ {1, 0}, {0, 0}, /* HT_DIR_VL */ {0, 0}, {0, 1}, // HT_DIR_VR
2373 distance
%= 2; // we're only interested if the distance is even or uneven
2374 style
&= HT_DIR_MASK
;
2376 /* To handle autorail, we do some magic to be able to use a lookup table.
2377 * Firstly if we drag the other way around, we switch start&end, and if needed
2378 * also flip the drag-position. Eg if it was on the left, and the distance is even
2379 * that means the end, which is now the start is on the right */
2380 if (swap
&& distance
== 0) style
= flip_style_direction
[style
];
2382 /* Use lookup table for start-tile based on HighLightStyle direction */
2383 byte style_t
= style
* 2;
2384 assert(style_t
< lengthof(heightdiff_line_by_dir
) - 13);
2385 h0
= TileHeight(TILE_ADD(start_tile
, ToTileIndexDiff(heightdiff_line_by_dir
[style_t
])));
2386 uint ht
= TileHeight(TILE_ADD(start_tile
, ToTileIndexDiff(heightdiff_line_by_dir
[style_t
+ 1])));
2389 /* Use lookup table for end-tile based on HighLightStyle direction
2390 * flip around side (lower/upper, left/right) based on distance */
2391 if (distance
== 0) style_t
= flip_style_direction
[style
] * 2;
2392 assert(style_t
< lengthof(heightdiff_line_by_dir
) - 13);
2393 h1
= TileHeight(TILE_ADD(end_tile
, ToTileIndexDiff(heightdiff_line_by_dir
[12 + style_t
])));
2394 ht
= TileHeight(TILE_ADD(end_tile
, ToTileIndexDiff(heightdiff_line_by_dir
[12 + style_t
+ 1])));
2400 if (swap
) Swap(h0
, h1
);
2401 return (int)(h1
- h0
) * TILE_HEIGHT_STEP
;
2404 static const StringID measure_strings_length
[] = {STR_NULL
, STR_MEASURE_LENGTH
, STR_MEASURE_LENGTH_HEIGHTDIFF
};
2407 * Check for underflowing the map.
2408 * @param test the variable to test for underflowing
2409 * @param other the other variable to update to keep the line
2410 * @param mult the constant to multiply the difference by for \c other
2412 static void CheckUnderflow(int &test
, int &other
, int mult
)
2414 if (test
>= 0) return;
2416 other
+= mult
* test
;
2421 * Check for overflowing the map.
2422 * @param test the variable to test for overflowing
2423 * @param other the other variable to update to keep the line
2424 * @param max the maximum value for the \c test variable
2425 * @param mult the constant to multiply the difference by for \c other
2427 static void CheckOverflow(int &test
, int &other
, int max
, int mult
)
2429 if (test
<= max
) return;
2431 other
+= mult
* (test
- max
);
2435 /** while dragging */
2436 static void CalcRaildirsDrawstyle(int x
, int y
, int method
)
2440 int dx
= _thd
.selstart
.x
- (_thd
.selend
.x
& ~TILE_UNIT_MASK
);
2441 int dy
= _thd
.selstart
.y
- (_thd
.selend
.y
& ~TILE_UNIT_MASK
);
2442 uint w
= abs(dx
) + TILE_SIZE
;
2443 uint h
= abs(dy
) + TILE_SIZE
;
2445 if (method
& ~(VPM_RAILDIRS
| VPM_SIGNALDIRS
)) {
2446 /* We 'force' a selection direction; first four rail buttons. */
2447 method
&= ~(VPM_RAILDIRS
| VPM_SIGNALDIRS
);
2448 int raw_dx
= _thd
.selstart
.x
- _thd
.selend
.x
;
2449 int raw_dy
= _thd
.selstart
.y
- _thd
.selend
.y
;
2452 b
= HT_LINE
| HT_DIR_Y
;
2453 x
= _thd
.selstart
.x
;
2457 b
= HT_LINE
| HT_DIR_X
;
2458 y
= _thd
.selstart
.y
;
2461 case VPM_FIX_HORIZONTAL
:
2463 /* We are on a straight horizontal line. Determine the 'rail'
2464 * to build based the sub tile location. */
2465 b
= (x
& TILE_UNIT_MASK
) + (y
& TILE_UNIT_MASK
) >= TILE_SIZE
? HT_LINE
| HT_DIR_HL
: HT_LINE
| HT_DIR_HU
;
2467 /* We are not on a straight line. Determine the rail to build
2468 * based on whether we are above or below it. */
2469 b
= dx
+ dy
>= (int)TILE_SIZE
? HT_LINE
| HT_DIR_HU
: HT_LINE
| HT_DIR_HL
;
2471 /* Calculate where a horizontal line through the start point and
2472 * a vertical line from the selected end point intersect and
2473 * use that point as the end point. */
2474 int offset
= (raw_dx
- raw_dy
) / 2;
2475 x
= _thd
.selstart
.x
- (offset
& ~TILE_UNIT_MASK
);
2476 y
= _thd
.selstart
.y
+ (offset
& ~TILE_UNIT_MASK
);
2478 /* 'Build' the last half rail tile if needed */
2479 if ((offset
& TILE_UNIT_MASK
) > (TILE_SIZE
/ 2)) {
2480 if (dx
+ dy
>= (int)TILE_SIZE
) {
2481 x
+= (dx
+ dy
< 0) ? (int)TILE_SIZE
: -(int)TILE_SIZE
;
2483 y
+= (dx
+ dy
< 0) ? (int)TILE_SIZE
: -(int)TILE_SIZE
;
2487 /* Make sure we do not overflow the map! */
2488 CheckUnderflow(x
, y
, 1);
2489 CheckUnderflow(y
, x
, 1);
2490 CheckOverflow(x
, y
, (MapMaxX() - 1) * TILE_SIZE
, 1);
2491 CheckOverflow(y
, x
, (MapMaxY() - 1) * TILE_SIZE
, 1);
2492 assert(x
>= 0 && y
>= 0 && x
<= (int)(MapMaxX() * TILE_SIZE
) && y
<= (int)(MapMaxY() * TILE_SIZE
));
2496 case VPM_FIX_VERTICAL
:
2498 /* We are on a straight vertical line. Determine the 'rail'
2499 * to build based the sub tile location. */
2500 b
= (x
& TILE_UNIT_MASK
) > (y
& TILE_UNIT_MASK
) ? HT_LINE
| HT_DIR_VL
: HT_LINE
| HT_DIR_VR
;
2502 /* We are not on a straight line. Determine the rail to build
2503 * based on whether we are left or right from it. */
2504 b
= dx
< dy
? HT_LINE
| HT_DIR_VL
: HT_LINE
| HT_DIR_VR
;
2506 /* Calculate where a vertical line through the start point and
2507 * a horizontal line from the selected end point intersect and
2508 * use that point as the end point. */
2509 int offset
= (raw_dx
+ raw_dy
+ (int)TILE_SIZE
) / 2;
2510 x
= _thd
.selstart
.x
- (offset
& ~TILE_UNIT_MASK
);
2511 y
= _thd
.selstart
.y
- (offset
& ~TILE_UNIT_MASK
);
2513 /* 'Build' the last half rail tile if needed */
2514 if ((offset
& TILE_UNIT_MASK
) > (TILE_SIZE
/ 2)) {
2516 y
+= (dx
> dy
) ? (int)TILE_SIZE
: -(int)TILE_SIZE
;
2518 x
+= (dx
< dy
) ? (int)TILE_SIZE
: -(int)TILE_SIZE
;
2522 /* Make sure we do not overflow the map! */
2523 CheckUnderflow(x
, y
, -1);
2524 CheckUnderflow(y
, x
, -1);
2525 CheckOverflow(x
, y
, (MapMaxX() - 1) * TILE_SIZE
, -1);
2526 CheckOverflow(y
, x
, (MapMaxY() - 1) * TILE_SIZE
, -1);
2527 assert(x
>= 0 && y
>= 0 && x
<= (int)(MapMaxX() * TILE_SIZE
) && y
<= (int)(MapMaxY() * TILE_SIZE
));
2534 } else if (TileVirtXY(_thd
.selstart
.x
, _thd
.selstart
.y
) == TileVirtXY(x
, y
)) { // check if we're only within one tile
2535 if (method
& VPM_RAILDIRS
) {
2536 b
= GetAutorailHT(x
, y
);
2537 } else { // rect for autosignals on one tile
2540 } else if (h
== TILE_SIZE
) { // Is this in X direction?
2541 if (dx
== (int)TILE_SIZE
) { // 2x1 special handling
2542 b
= (Check2x1AutoRail(3)) | HT_LINE
;
2543 } else if (dx
== -(int)TILE_SIZE
) {
2544 b
= (Check2x1AutoRail(2)) | HT_LINE
;
2546 b
= HT_LINE
| HT_DIR_X
;
2548 y
= _thd
.selstart
.y
;
2549 } else if (w
== TILE_SIZE
) { // Or Y direction?
2550 if (dy
== (int)TILE_SIZE
) { // 2x1 special handling
2551 b
= (Check2x1AutoRail(1)) | HT_LINE
;
2552 } else if (dy
== -(int)TILE_SIZE
) { // 2x1 other direction
2553 b
= (Check2x1AutoRail(0)) | HT_LINE
;
2555 b
= HT_LINE
| HT_DIR_Y
;
2557 x
= _thd
.selstart
.x
;
2558 } else if (w
> h
* 2) { // still count as x dir?
2559 b
= HT_LINE
| HT_DIR_X
;
2560 y
= _thd
.selstart
.y
;
2561 } else if (h
> w
* 2) { // still count as y dir?
2562 b
= HT_LINE
| HT_DIR_Y
;
2563 x
= _thd
.selstart
.x
;
2564 } else { // complicated direction
2566 _thd
.selend
.x
= _thd
.selend
.x
& ~TILE_UNIT_MASK
;
2567 _thd
.selend
.y
= _thd
.selend
.y
& ~TILE_UNIT_MASK
;
2570 if (x
> _thd
.selstart
.x
) {
2571 if (y
> _thd
.selstart
.y
) {
2574 b
= (x
& TILE_UNIT_MASK
) > (y
& TILE_UNIT_MASK
) ? HT_LINE
| HT_DIR_VL
: HT_LINE
| HT_DIR_VR
;
2575 } else if (d
>= 0) {
2576 x
= _thd
.selstart
.x
+ h
;
2577 b
= HT_LINE
| HT_DIR_VL
;
2579 y
= _thd
.selstart
.y
+ w
;
2580 b
= HT_LINE
| HT_DIR_VR
;
2585 b
= (x
& TILE_UNIT_MASK
) + (y
& TILE_UNIT_MASK
) >= TILE_SIZE
? HT_LINE
| HT_DIR_HL
: HT_LINE
| HT_DIR_HU
;
2586 } else if (d
>= 0) {
2587 x
= _thd
.selstart
.x
+ h
;
2588 b
= HT_LINE
| HT_DIR_HL
;
2590 y
= _thd
.selstart
.y
- w
;
2591 b
= HT_LINE
| HT_DIR_HU
;
2595 if (y
> _thd
.selstart
.y
) {
2598 b
= (x
& TILE_UNIT_MASK
) + (y
& TILE_UNIT_MASK
) >= TILE_SIZE
? HT_LINE
| HT_DIR_HL
: HT_LINE
| HT_DIR_HU
;
2599 } else if (d
>= 0) {
2600 x
= _thd
.selstart
.x
- h
;
2601 b
= HT_LINE
| HT_DIR_HU
;
2603 y
= _thd
.selstart
.y
+ w
;
2604 b
= HT_LINE
| HT_DIR_HL
;
2609 b
= (x
& TILE_UNIT_MASK
) > (y
& TILE_UNIT_MASK
) ? HT_LINE
| HT_DIR_VL
: HT_LINE
| HT_DIR_VR
;
2610 } else if (d
>= 0) {
2611 x
= _thd
.selstart
.x
- h
;
2612 b
= HT_LINE
| HT_DIR_VR
;
2614 y
= _thd
.selstart
.y
- w
;
2615 b
= HT_LINE
| HT_DIR_VL
;
2621 if (_settings_client
.gui
.measure_tooltip
) {
2622 TileIndex t0
= TileVirtXY(_thd
.selstart
.x
, _thd
.selstart
.y
);
2623 TileIndex t1
= TileVirtXY(x
, y
);
2624 uint distance
= DistanceManhattan(t0
, t1
) + 1;
2628 if (distance
!= 1) {
2629 int heightdiff
= CalcHeightdiff(b
, distance
, t0
, t1
);
2630 /* If we are showing a tooltip for horizontal or vertical drags,
2631 * 2 tiles have a length of 1. To bias towards the ceiling we add
2632 * one before division. It feels more natural to count 3 lengths as 2 */
2633 if ((b
& HT_DIR_MASK
) != HT_DIR_X
&& (b
& HT_DIR_MASK
) != HT_DIR_Y
) {
2634 distance
= CeilDiv(distance
, 2);
2637 params
[index
++] = distance
;
2638 if (heightdiff
!= 0) params
[index
++] = heightdiff
;
2641 ShowMeasurementTooltips(measure_strings_length
[index
], index
, params
);
2646 _thd
.next_drawstyle
= b
;
2650 * Selects tiles while dragging
2651 * @param x X coordinate of end of selection
2652 * @param y Y coordinate of end of selection
2653 * @param method modifies the way tiles are selected. Possible
2654 * methods are VPM_* in viewport.h
2656 void VpSelectTilesWithMethod(int x
, int y
, ViewportPlaceMethod method
)
2659 HighLightStyle style
;
2666 /* Special handling of drag in any (8-way) direction */
2667 if (method
& (VPM_RAILDIRS
| VPM_SIGNALDIRS
)) {
2670 CalcRaildirsDrawstyle(x
, y
, method
);
2674 /* Needed so level-land is placed correctly */
2675 if ((_thd
.next_drawstyle
& HT_DRAG_MASK
) == HT_POINT
) {
2680 sx
= _thd
.selstart
.x
;
2681 sy
= _thd
.selstart
.y
;
2686 case VPM_X_OR_Y
: // drag in X or Y direction
2687 if (abs(sy
- y
) < abs(sx
- x
)) {
2694 goto calc_heightdiff_single_direction
;
2696 case VPM_X_LIMITED
: // Drag in X direction (limited size).
2697 limit
= (_thd
.sizelimit
- 1) * TILE_SIZE
;
2700 case VPM_FIX_X
: // drag in Y direction
2703 goto calc_heightdiff_single_direction
;
2705 case VPM_Y_LIMITED
: // Drag in Y direction (limited size).
2706 limit
= (_thd
.sizelimit
- 1) * TILE_SIZE
;
2709 case VPM_FIX_Y
: // drag in X direction
2713 calc_heightdiff_single_direction
:;
2715 x
= sx
+ Clamp(x
- sx
, -limit
, limit
);
2716 y
= sy
+ Clamp(y
- sy
, -limit
, limit
);
2718 if (_settings_client
.gui
.measure_tooltip
) {
2719 TileIndex t0
= TileVirtXY(sx
, sy
);
2720 TileIndex t1
= TileVirtXY(x
, y
);
2721 uint distance
= DistanceManhattan(t0
, t1
) + 1;
2725 if (distance
!= 1) {
2726 /* With current code passing a HT_LINE style to calculate the height
2727 * difference is enough. However if/when a point-tool is created
2728 * with this method, function should be called with new_style (below)
2729 * instead of HT_LINE | style case HT_POINT is handled specially
2730 * new_style := (_thd.next_drawstyle & HT_RECT) ? HT_LINE | style : _thd.next_drawstyle; */
2731 int heightdiff
= CalcHeightdiff(HT_LINE
| style
, 0, t0
, t1
);
2733 params
[index
++] = distance
;
2734 if (heightdiff
!= 0) params
[index
++] = heightdiff
;
2737 ShowMeasurementTooltips(measure_strings_length
[index
], index
, params
);
2741 case VPM_X_AND_Y_LIMITED
: // Drag an X by Y constrained rect area.
2742 limit
= (_thd
.sizelimit
- 1) * TILE_SIZE
;
2743 x
= sx
+ Clamp(x
- sx
, -limit
, limit
);
2744 y
= sy
+ Clamp(y
- sy
, -limit
, limit
);
2747 case VPM_X_AND_Y
: // drag an X by Y area
2748 if (_settings_client
.gui
.measure_tooltip
) {
2749 static const StringID measure_strings_area
[] = {
2750 STR_NULL
, STR_NULL
, STR_MEASURE_AREA
, STR_MEASURE_AREA_HEIGHTDIFF
2753 TileIndex t0
= TileVirtXY(sx
, sy
);
2754 TileIndex t1
= TileVirtXY(x
, y
);
2755 uint dx
= Delta(TileX(t0
), TileX(t1
)) + 1;
2756 uint dy
= Delta(TileY(t0
), TileY(t1
)) + 1;
2760 /* If dragging an area (eg dynamite tool) and it is actually a single
2761 * row/column, change the type to 'line' to get proper calculation for height */
2762 style
= (HighLightStyle
)_thd
.next_drawstyle
;
2763 if (_thd
.IsDraggingDiagonal()) {
2764 /* Determine the "area" of the diagonal dragged selection.
2765 * We assume the area is the number of tiles along the X
2766 * edge and the number of tiles along the Y edge. However,
2767 * multiplying these two numbers does not give the exact
2768 * number of tiles; basically we are counting the black
2769 * squares on a chess board and ignore the white ones to
2770 * make the tile counts at the edges match up. There is no
2771 * other way to make a proper count though.
2773 * First convert to the rotated coordinate system. */
2774 int dist_x
= TileX(t0
) - TileX(t1
);
2775 int dist_y
= TileY(t0
) - TileY(t1
);
2776 int a_max
= dist_x
+ dist_y
;
2777 int b_max
= dist_y
- dist_x
;
2779 /* Now determine the size along the edge, but due to the
2780 * chess board principle this counts double. */
2781 a_max
= abs(a_max
+ (a_max
> 0 ? 2 : -2)) / 2;
2782 b_max
= abs(b_max
+ (b_max
> 0 ? 2 : -2)) / 2;
2784 /* We get a 1x1 on normal 2x1 rectangles, due to it being
2785 * a seen as two sides. As the result for actual building
2786 * will be the same as non-diagonal dragging revert to that
2787 * behaviour to give it a more normally looking size. */
2788 if (a_max
!= 1 || b_max
!= 1) {
2792 } else if (style
& HT_RECT
) {
2794 style
= HT_LINE
| HT_DIR_Y
;
2795 } else if (dy
== 1) {
2796 style
= HT_LINE
| HT_DIR_X
;
2800 if (dx
!= 1 || dy
!= 1) {
2801 int heightdiff
= CalcHeightdiff(style
, 0, t0
, t1
);
2803 params
[index
++] = dx
- (style
& HT_POINT
? 1 : 0);
2804 params
[index
++] = dy
- (style
& HT_POINT
? 1 : 0);
2805 if (heightdiff
!= 0) params
[index
++] = heightdiff
;
2808 ShowMeasurementTooltips(measure_strings_area
[index
], index
, params
);
2812 default: NOT_REACHED();
2820 * Handle the mouse while dragging for placement/resizing.
2821 * @return State of handling the event.
2823 EventState
VpHandlePlaceSizingDrag()
2825 if (_special_mouse_mode
!= WSM_SIZING
) return ES_NOT_HANDLED
;
2827 /* stop drag mode if the window has been closed */
2828 Window
*w
= _thd
.GetCallbackWnd();
2830 ResetObjectToPlace();
2834 /* while dragging execute the drag procedure of the corresponding window (mostly VpSelectTilesWithMethod() ) */
2835 if (_left_button_down
) {
2836 w
->OnPlaceDrag(_thd
.select_method
, _thd
.select_proc
, GetTileBelowCursor());
2840 /* mouse button released..
2841 * keep the selected tool, but reset it to the original mode. */
2842 _special_mouse_mode
= WSM_NONE
;
2843 HighLightStyle others
= _thd
.place_mode
& ~(HT_DRAG_MASK
| HT_DIR_MASK
);
2844 if ((_thd
.next_drawstyle
& HT_DRAG_MASK
) == HT_RECT
) {
2845 _thd
.place_mode
= HT_RECT
| others
;
2846 } else if (_thd
.select_method
& VPM_SIGNALDIRS
) {
2847 _thd
.place_mode
= HT_RECT
| others
;
2848 } else if (_thd
.select_method
& VPM_RAILDIRS
) {
2849 _thd
.place_mode
= (_thd
.select_method
& ~VPM_RAILDIRS
) ? _thd
.next_drawstyle
: (HT_RAIL
| others
);
2851 _thd
.place_mode
= HT_POINT
| others
;
2853 SetTileSelectSize(1, 1);
2855 w
->OnPlaceMouseUp(_thd
.select_method
, _thd
.select_proc
, _thd
.selend
, TileVirtXY(_thd
.selstart
.x
, _thd
.selstart
.y
), TileVirtXY(_thd
.selend
.x
, _thd
.selend
.y
));
2860 void SetObjectToPlaceWnd(CursorID icon
, PaletteID pal
, HighLightStyle mode
, Window
*w
)
2862 SetObjectToPlace(icon
, pal
, mode
, w
->window_class
, w
->window_number
);
2865 #include "table/animcursors.h"
2867 void SetObjectToPlace(CursorID icon
, PaletteID pal
, HighLightStyle mode
, WindowClass window_class
, WindowNumber window_num
)
2869 if (_thd
.window_class
!= WC_INVALID
) {
2870 /* Undo clicking on button and drag & drop */
2871 Window
*w
= _thd
.GetCallbackWnd();
2872 /* Call the abort function, but set the window class to something
2873 * that will never be used to avoid infinite loops. Setting it to
2874 * the 'next' window class must not be done because recursion into
2875 * this function might in some cases reset the newly set object to
2876 * place or not properly reset the original selection. */
2877 _thd
.window_class
= WC_INVALID
;
2878 if (w
!= NULL
) w
->OnPlaceObjectAbort();
2881 /* Mark the old selection dirty, in case the selection shape or colour changes */
2882 if ((_thd
.drawstyle
& HT_DRAG_MASK
) != HT_NONE
) SetSelectionTilesDirty();
2884 SetTileSelectSize(1, 1);
2886 _thd
.make_square_red
= false;
2888 if (mode
== HT_DRAG
) { // HT_DRAG is for dragdropping trains in the depot window
2890 _special_mouse_mode
= WSM_DRAGDROP
;
2892 _special_mouse_mode
= WSM_NONE
;
2895 _thd
.place_mode
= mode
;
2896 _thd
.window_class
= window_class
;
2897 _thd
.window_number
= window_num
;
2899 if ((mode
& HT_DRAG_MASK
) == HT_SPECIAL
) { // special tools, like tunnels or docks start with presizing mode
2903 if ((icon
& ANIMCURSOR_FLAG
) != 0) {
2904 SetAnimatedMouseCursor(_animcursors
[icon
& ~ANIMCURSOR_FLAG
]);
2906 SetMouseCursor(icon
, pal
);
2911 void ResetObjectToPlace()
2913 SetObjectToPlace(SPR_CURSOR_MOUSE
, PAL_NONE
, HT_NONE
, WC_MAIN_WINDOW
, 0);