OpenTTD Source 20251213-master-g1091fa6071
viewport.cpp
Go to the documentation of this file.
1/*
2 * This file is part of OpenTTD.
3 * 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.
4 * 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.
5 * 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 <https://www.gnu.org/licenses/old-licenses/gpl-2.0>.
6 */
7
63#include "stdafx.h"
64#include "core/backup_type.hpp"
65#include "landscape.h"
66#include "viewport_func.h"
67#include "station_base.h"
68#include "waypoint_base.h"
69#include "town.h"
70#include "signs_base.h"
71#include "signs_func.h"
72#include "vehicle_base.h"
73#include "vehicle_gui.h"
74#include "blitter/factory.hpp"
75#include "strings_func.h"
76#include "zoom_func.h"
77#include "vehicle_func.h"
78#include "company_func.h"
79#include "waypoint_func.h"
80#include "window_func.h"
81#include "tilehighlight_func.h"
82#include "window_gui.h"
84#include "viewport_kdtree.h"
85#include "town_kdtree.h"
87#include "bridge_map.h"
88#include "company_base.h"
89#include "command_func.h"
91#include "framerate_type.h"
92#include "viewport_cmd.h"
93
94#include <forward_list>
95#include <stack>
96
98
99#include "table/strings.h"
100#include "table/string_colours.h"
101
102#include "safeguards.h"
103
104Point _tile_fract_coords;
105
106
107ViewportSignKdtree _viewport_sign_kdtree{};
108static int _viewport_sign_maxwidth = 0;
109
110
111static const int MAX_TILE_EXTENT_LEFT = ZOOM_BASE * TILE_PIXELS;
112static const int MAX_TILE_EXTENT_RIGHT = ZOOM_BASE * TILE_PIXELS;
113static const int MAX_TILE_EXTENT_TOP = ZOOM_BASE * MAX_BUILDING_PIXELS;
114static const int MAX_TILE_EXTENT_BOTTOM = ZOOM_BASE * (TILE_PIXELS + 2 * TILE_HEIGHT);
115
117 std::string string;
118 uint16_t width;
119 Colours colour;
121 int32_t x;
122 int32_t y;
123};
124
126 SpriteID image;
127 PaletteID pal;
128 const SubSprite *sub;
129 int32_t x;
130 int32_t y;
131};
132
134 SpriteID image;
135 PaletteID pal;
136 const SubSprite *sub;
137 int32_t x;
138 int32_t y;
139 bool relative;
140 int next;
141};
142
144enum FoundationPart : uint8_t {
148 FOUNDATION_PART_END
149};
150
160
161typedef std::vector<TileSpriteToDraw> TileSpriteToDrawVector;
162typedef std::vector<StringSpriteToDraw> StringSpriteToDrawVector;
163typedef std::vector<ParentSpriteToDraw> ParentSpriteToDrawVector;
164typedef std::vector<ChildScreenSpriteToDraw> ChildScreenSpriteToDrawVector;
165
166constexpr int LAST_CHILD_NONE = -1;
167constexpr int LAST_CHILD_PARENT = -2;
168
171 DrawPixelInfo dpi;
172
173 StringSpriteToDrawVector string_sprites_to_draw;
174 TileSpriteToDrawVector tile_sprites_to_draw;
175 ParentSpriteToDrawVector parent_sprites_to_draw;
176 ParentSpriteToSortVector parent_sprites_to_sort;
177 ChildScreenSpriteToDrawVector child_screen_sprites_to_draw;
178
179 int last_child;
180
182
183 int foundation[FOUNDATION_PART_END];
185 int last_foundation_child[FOUNDATION_PART_END];
186 Point foundation_offset[FOUNDATION_PART_END];
187};
188
189static bool MarkViewportDirty(const Viewport &vp, int left, int top, int right, int bottom);
190
191static ViewportDrawer _vd;
192
194static TileInfo _cur_ti;
195bool _draw_bounding_boxes = false;
196bool _draw_dirty_blocks = false;
197uint _dirty_block_colour = 0;
198static VpSpriteSorter _vp_sprite_sorter = nullptr;
199
200static Point MapXYZToViewport(const Viewport &vp, int x, int y, int z)
201{
202 Point p = RemapCoords(x, y, z);
203 p.x -= vp.virtual_width / 2;
204 p.y -= vp.virtual_height / 2;
205 return p;
206}
207
218void InitializeWindowViewport(Window *w, int x, int y,
219 int width, int height, std::variant<TileIndex, VehicleID> focus, ZoomLevel zoom)
220{
221 assert(w->viewport == nullptr);
222
223 auto vp = std::make_unique<ViewportData>();
224
225 vp->left = x + w->left;
226 vp->top = y + w->top;
227 vp->width = width;
228 vp->height = height;
229
231
232 vp->virtual_width = ScaleByZoom(width, zoom);
233 vp->virtual_height = ScaleByZoom(height, zoom);
234
235 Point pt;
236
237 if (std::holds_alternative<VehicleID>(focus)) {
238 const Vehicle *veh;
239
240 vp->follow_vehicle = std::get<VehicleID>(focus);
241 veh = Vehicle::Get(vp->follow_vehicle);
242 pt = MapXYZToViewport(*vp, veh->x_pos, veh->y_pos, veh->z_pos);
243 } else {
244 TileIndex tile = std::get<TileIndex>(focus);
245 if (tile == INVALID_TILE) {
246 /* No tile? Use center of main viewport. */
247 const Window *mw = GetMainWindow();
248
249 /* center on same place as main window (zoom is maximum, no adjustment needed) */
250 pt.x = mw->viewport->scrollpos_x + mw->viewport->virtual_width / 2;
251 pt.x -= vp->virtual_width / 2;
252 pt.y = mw->viewport->scrollpos_y + mw->viewport->virtual_height / 2;
253 pt.y -= vp->virtual_height / 2;
254 } else {
255 x = TileX(tile) * TILE_SIZE;
256 y = TileY(tile) * TILE_SIZE;
257 pt = MapXYZToViewport(*vp, x, y, GetSlopePixelZ(x, y));
258 }
259 vp->follow_vehicle = VehicleID::Invalid();
260 }
261
262 vp->scrollpos_x = pt.x;
263 vp->scrollpos_y = pt.y;
264 vp->dest_scrollpos_x = pt.x;
265 vp->dest_scrollpos_y = pt.y;
266
267 vp->overlay = nullptr;
268
269 vp->virtual_left = 0;
270 vp->virtual_top = 0;
271
272 w->viewport = std::move(vp);
273}
274
275static Point _vp_move_offs;
276
277static void DoSetViewportPosition(Window::IteratorToFront it, int left, int top, int width, int height)
278{
279 for (; !it.IsEnd(); ++it) {
280 const Window *w = *it;
281 if (left + width > w->left &&
282 w->left + w->width > left &&
283 top + height > w->top &&
284 w->top + w->height > top) {
285
286 if (left < w->left) {
287 DoSetViewportPosition(it, left, top, w->left - left, height);
288 DoSetViewportPosition(it, left + (w->left - left), top, width - (w->left - left), height);
289 return;
290 }
291
292 if (left + width > w->left + w->width) {
293 DoSetViewportPosition(it, left, top, (w->left + w->width - left), height);
294 DoSetViewportPosition(it, left + (w->left + w->width - left), top, width - (w->left + w->width - left), height);
295 return;
296 }
297
298 if (top < w->top) {
299 DoSetViewportPosition(it, left, top, width, (w->top - top));
300 DoSetViewportPosition(it, left, top + (w->top - top), width, height - (w->top - top));
301 return;
302 }
303
304 if (top + height > w->top + w->height) {
305 DoSetViewportPosition(it, left, top, width, (w->top + w->height - top));
306 DoSetViewportPosition(it, left, top + (w->top + w->height - top), width, height - (w->top + w->height - top));
307 return;
308 }
309
310 return;
311 }
312 }
313
314 {
315 int xo = _vp_move_offs.x;
316 int yo = _vp_move_offs.y;
317
318 if (abs(xo) >= width || abs(yo) >= height) {
319 /* fully_outside */
320 RedrawScreenRect(left, top, left + width, top + height);
321 return;
322 }
323
324 GfxScroll(left, top, width, height, xo, yo);
325
326 if (xo > 0) {
327 RedrawScreenRect(left, top, xo + left, top + height);
328 left += xo;
329 width -= xo;
330 } else if (xo < 0) {
331 RedrawScreenRect(left + width + xo, top, left + width, top + height);
332 width += xo;
333 }
334
335 if (yo > 0) {
336 RedrawScreenRect(left, top, width + left, top + yo);
337 } else if (yo < 0) {
338 RedrawScreenRect(left, top + height + yo, width + left, top + height);
339 }
340 }
341}
342
343static void SetViewportPosition(Window *w, int x, int y)
344{
345 Viewport &vp = *w->viewport;
346 int old_left = vp.virtual_left;
347 int old_top = vp.virtual_top;
348 int i;
349 int left, top, width, height;
350
351 vp.virtual_left = x;
352 vp.virtual_top = y;
353
354 /* Viewport is bound to its left top corner, so it must be rounded down (UnScaleByZoomLower)
355 * else glitch described in FS#1412 will happen (offset by 1 pixel with zoom level > NORMAL)
356 */
357 old_left = UnScaleByZoomLower(old_left, vp.zoom);
358 old_top = UnScaleByZoomLower(old_top, vp.zoom);
359 x = UnScaleByZoomLower(x, vp.zoom);
360 y = UnScaleByZoomLower(y, vp.zoom);
361
362 old_left -= x;
363 old_top -= y;
364
365 if (old_top == 0 && old_left == 0) return;
366
367 _vp_move_offs.x = old_left;
368 _vp_move_offs.y = old_top;
369
370 left = vp.left;
371 top = vp.top;
372 width = vp.width;
373 height = vp.height;
374
375 if (left < 0) {
376 width += left;
377 left = 0;
378 }
379
380 i = left + width - _screen.width;
381 if (i >= 0) width -= i;
382
383 if (width > 0) {
384 if (top < 0) {
385 height += top;
386 top = 0;
387 }
388
389 i = top + height - _screen.height;
390 if (i >= 0) height -= i;
391
392 if (height > 0) {
394 ++it;
395 DoSetViewportPosition(it, left, top, width, height);
396 }
397 }
398}
399
408Viewport *IsPtInWindowViewport(const Window *w, int x, int y)
409{
410 if (w->viewport == nullptr) return nullptr;
411
412 const Viewport &vp = *w->viewport;
413 if (IsInsideMM(x, vp.left, vp.left + vp.width) && IsInsideMM(y, vp.top, vp.top + vp.height)) return w->viewport.get();
414
415 return nullptr;
416}
417
430Point TranslateXYToTileCoord(const Viewport &vp, int x, int y, bool clamp_to_map)
431{
432 if (!IsInsideBS(x, vp.left, vp.width) || !IsInsideBS(y, vp.top, vp.height)) {
433 Point pt = { -1, -1 };
434 return pt;
435 }
436
437 return InverseRemapCoords2(
438 ScaleByZoom(x - vp.left, vp.zoom) + vp.virtual_left,
439 ScaleByZoom(y - vp.top, vp.zoom) + vp.virtual_top, clamp_to_map);
440}
441
442/* When used for zooming, check area below current coordinates (x,y)
443 * and return the tile of the zoomed out/in position (zoom_x, zoom_y)
444 * when you just want the tile, make x = zoom_x and y = zoom_y */
445static Point GetTileFromScreenXY(int x, int y, int zoom_x, int zoom_y)
446{
447 if (Window *w = FindWindowFromPt(x, y); w != nullptr) {
448 if (Viewport *vp = IsPtInWindowViewport(w, x, y); vp != nullptr) {
449 return TranslateXYToTileCoord(*vp, zoom_x, zoom_y);
450 }
451 }
452
453 return {-1, -1};
454}
455
456Point GetTileBelowCursor()
457{
458 return GetTileFromScreenXY(_cursor.pos.x, _cursor.pos.y, _cursor.pos.x, _cursor.pos.y);
459}
460
461
462Point GetTileZoomCenterWindow(bool in, Window * w)
463{
464 int x, y;
465 const Viewport &vp = *w->viewport;
466
467 if (in) {
468 x = ((_cursor.pos.x - vp.left) >> 1) + (vp.width >> 2);
469 y = ((_cursor.pos.y - vp.top) >> 1) + (vp.height >> 2);
470 } else {
471 x = vp.width - (_cursor.pos.x - vp.left);
472 y = vp.height - (_cursor.pos.y - vp.top);
473 }
474 /* Get the tile below the cursor and center on the zoomed-out center */
475 return GetTileFromScreenXY(_cursor.pos.x, _cursor.pos.y, x + vp.left, y + vp.top);
476}
477
486void HandleZoomMessage(Window *w, const Viewport &vp, WidgetID widget_zoom_in, WidgetID widget_zoom_out)
487{
488 w->SetWidgetDisabledState(widget_zoom_in, vp.zoom <= _settings_client.gui.zoom_min);
489 w->SetWidgetDirty(widget_zoom_in);
490
491 w->SetWidgetDisabledState(widget_zoom_out, vp.zoom >= _settings_client.gui.zoom_max);
492 w->SetWidgetDirty(widget_zoom_out);
493}
494
507static void AddTileSpriteToDraw(SpriteID image, PaletteID pal, int32_t x, int32_t y, int z, const SubSprite *sub = nullptr, int extra_offs_x = 0, int extra_offs_y = 0)
508{
509 assert((image & SPRITE_MASK) < MAX_SPRITES);
510
511 TileSpriteToDraw &ts = _vd.tile_sprites_to_draw.emplace_back();
512 ts.image = image;
513 ts.pal = pal;
514 ts.sub = sub;
515 Point pt = RemapCoords(x, y, z);
516 ts.x = pt.x + extra_offs_x;
517 ts.y = pt.y + extra_offs_y;
518}
519
532static void AddChildSpriteToFoundation(SpriteID image, PaletteID pal, const SubSprite *sub, FoundationPart foundation_part, int extra_offs_x, int extra_offs_y)
533{
534 assert(IsInsideMM(foundation_part, 0, FOUNDATION_PART_END));
535 assert(_vd.foundation[foundation_part] != -1);
536 Point offs = _vd.foundation_offset[foundation_part];
537
538 /* Change the active ChildSprite list to the one of the foundation */
539 AutoRestoreBackup backup(_vd.last_child, _vd.last_foundation_child[foundation_part]);
540 AddChildSpriteScreen(image, pal, offs.x + extra_offs_x, offs.y + extra_offs_y, false, sub, false, false);
541}
542
556void DrawGroundSpriteAt(SpriteID image, PaletteID pal, int32_t x, int32_t y, int z, const SubSprite *sub, int extra_offs_x, int extra_offs_y)
557{
558 /* Switch to first foundation part, if no foundation was drawn */
560
561 if (_vd.foundation[_vd.foundation_part] != -1) {
562 Point pt = RemapCoords(x, y, z);
563 AddChildSpriteToFoundation(image, pal, sub, _vd.foundation_part, pt.x + extra_offs_x * ZOOM_BASE, pt.y + extra_offs_y * ZOOM_BASE);
564 } else {
565 AddTileSpriteToDraw(image, pal, _cur_ti.x + x, _cur_ti.y + y, _cur_ti.z + z, sub, extra_offs_x * ZOOM_BASE, extra_offs_y * ZOOM_BASE);
566 }
567}
568
579void DrawGroundSprite(SpriteID image, PaletteID pal, const SubSprite *sub, int extra_offs_x, int extra_offs_y)
580{
581 DrawGroundSpriteAt(image, pal, 0, 0, 0, sub, extra_offs_x, extra_offs_y);
582}
583
591void OffsetGroundSprite(int x, int y)
592{
593 /* Switch to next foundation part */
594 switch (_vd.foundation_part) {
597 break;
600 break;
601 default: NOT_REACHED();
602 }
603
604 /* _vd.last_child is LAST_CHILD_NONE if foundation sprite was clipped by the viewport bounds */
605 if (_vd.last_child != LAST_CHILD_NONE) _vd.foundation[_vd.foundation_part] = static_cast<uint>(_vd.parent_sprites_to_draw.size()) - 1;
606
607 _vd.foundation_offset[_vd.foundation_part].x = x * ZOOM_BASE;
608 _vd.foundation_offset[_vd.foundation_part].y = y * ZOOM_BASE;
609 _vd.last_foundation_child[_vd.foundation_part] = _vd.last_child;
610}
611
623static void AddCombinedSprite(SpriteID image, PaletteID pal, int x, int y, int z, const SubSprite *sub)
624{
625 Point pt = RemapCoords(x, y, z);
626 const Sprite *spr = GetSprite(image & SPRITE_MASK, SpriteType::Normal);
627
628 if (pt.x + spr->x_offs >= _vd.dpi.left + _vd.dpi.width ||
629 pt.x + spr->x_offs + spr->width <= _vd.dpi.left ||
630 pt.y + spr->y_offs >= _vd.dpi.top + _vd.dpi.height ||
631 pt.y + spr->y_offs + spr->height <= _vd.dpi.top)
632 return;
633
634 const ParentSpriteToDraw &pstd = _vd.parent_sprites_to_draw.back();
635 AddChildSpriteScreen(image, pal, pt.x - pstd.left, pt.y - pstd.top, false, sub, false);
636}
637
663void AddSortableSpriteToDraw(SpriteID image, PaletteID pal, int x, int y, int z, const SpriteBounds &bounds, bool transparent, const SubSprite *sub)
664{
665 int32_t left, right, top, bottom;
666
667 assert((image & SPRITE_MASK) < MAX_SPRITES);
668
669 /* Move to bounding box. */
670 x += bounds.origin.x;
671 y += bounds.origin.y;
672 z += bounds.origin.z;
673
674 /* make the sprites transparent with the right palette */
675 if (transparent) {
678 }
679
681 AddCombinedSprite(image, pal, x + bounds.offset.x, y + bounds.offset.y, z + bounds.offset.z, sub);
682 return;
683 }
684
685 _vd.last_child = LAST_CHILD_NONE;
686
687 Point pt = RemapCoords(x + bounds.offset.x, y + bounds.offset.y, z + bounds.offset.z);
688 int tmp_left, tmp_top, tmp_x = pt.x, tmp_y = pt.y;
689
690 /* Compute screen extents of sprite */
691 if (image == SPR_EMPTY_BOUNDING_BOX) {
692 left = tmp_left = RemapCoords(x + bounds.extent.x, y, z).x;
693 right = RemapCoords(x, y + bounds.extent.y, z).x + 1;
694 top = tmp_top = RemapCoords(x, y, z + bounds.extent.z).y;
695 bottom = RemapCoords(x + bounds.extent.x, y + bounds.extent.y, z).y + 1;
696 } else {
697 const Sprite *spr = GetSprite(image & SPRITE_MASK, SpriteType::Normal);
698 left = tmp_left = (pt.x += spr->x_offs);
699 right = (pt.x + spr->width );
700 top = tmp_top = (pt.y += spr->y_offs);
701 bottom = (pt.y + spr->height);
702 }
703
704 if (_draw_bounding_boxes && (image != SPR_EMPTY_BOUNDING_BOX)) {
705 /* Compute maximal extents of sprite and its bounding box */
706 left = std::min(left , RemapCoords(x + bounds.extent.x, y, z).x);
707 right = std::max(right , RemapCoords(x, y + bounds.extent.y, z).x + 1);
708 top = std::min(top , RemapCoords(x, y, z + bounds.extent.z).y);
709 bottom = std::max(bottom, RemapCoords(x + bounds.extent.x, y + bounds.extent.y, z).y + 1);
710 }
711
712 /* Do not add the sprite to the viewport, if it is outside */
713 if (left >= _vd.dpi.left + _vd.dpi.width ||
714 right <= _vd.dpi.left ||
715 top >= _vd.dpi.top + _vd.dpi.height ||
716 bottom <= _vd.dpi.top) {
717 return;
718 }
719
720 ParentSpriteToDraw &ps = _vd.parent_sprites_to_draw.emplace_back();
721 ps.x = tmp_x;
722 ps.y = tmp_y;
723
724 ps.left = tmp_left;
725 ps.top = tmp_top;
726
727 ps.image = image;
728 ps.pal = pal;
729 ps.sub = sub;
730 ps.xmin = x;
731 ps.xmax = x + bounds.extent.x - 1;
732
733 ps.ymin = y;
734 ps.ymax = y + bounds.extent.y - 1;
735
736 ps.zmin = z;
737 ps.zmax = z + bounds.extent.z - 1;
738
740
741 _vd.last_child = LAST_CHILD_PARENT;
742
744}
745
769
779
789static bool IsInRangeInclusive(int begin, int end, int check)
790{
791 if (begin > end) std::swap(begin, end);
792 return begin <= check && check <= end;
793}
794
801bool IsInsideRotatedRectangle(int x, int y)
802{
803 int dist_a = (_thd.size.x + _thd.size.y); // Rotated coordinate system for selected rectangle.
804 int dist_b = (_thd.size.x - _thd.size.y); // We don't have to divide by 2. It's all relative!
805 int a = ((x - _thd.pos.x) + (y - _thd.pos.y)); // Rotated coordinate system for the point under scrutiny.
806 int b = ((x - _thd.pos.x) - (y - _thd.pos.y));
807
808 /* Check if a and b are between 0 and dist_a or dist_b respectively. */
809 return IsInRangeInclusive(dist_a, 0, a) && IsInRangeInclusive(dist_b, 0, b);
810}
811
824void AddChildSpriteScreen(SpriteID image, PaletteID pal, int x, int y, bool transparent, const SubSprite *sub, bool scale, bool relative)
825{
826 assert((image & SPRITE_MASK) < MAX_SPRITES);
827
828 /* If the ParentSprite was clipped by the viewport bounds, do not draw the ChildSprites either */
829 if (_vd.last_child == LAST_CHILD_NONE) return;
830
831 /* make the sprites transparent with the right palette */
832 if (transparent) {
835 }
836
837 int32_t child_id = static_cast<int32_t>(_vd.child_screen_sprites_to_draw.size());
838 if (_vd.last_child != LAST_CHILD_PARENT) {
839 _vd.child_screen_sprites_to_draw[_vd.last_child].next = child_id;
840 } else {
841 _vd.parent_sprites_to_draw.back().first_child = child_id;
842 }
843
844 ChildScreenSpriteToDraw &cs = _vd.child_screen_sprites_to_draw.emplace_back();
845 cs.image = image;
846 cs.pal = pal;
847 cs.sub = sub;
848 cs.x = scale ? x * ZOOM_BASE : x;
849 cs.y = scale ? y * ZOOM_BASE : y;
850 cs.relative = relative;
852
853 /* Append the sprite to the active ChildSprite list.
854 * If the active ParentSprite is a foundation, update last_foundation_child as well.
855 * Note: ChildSprites of foundations are NOT sequential in the vector, as selection sprites are added at last. */
856 if (_vd.last_foundation_child[0] == _vd.last_child) _vd.last_foundation_child[0] = child_id;
857 if (_vd.last_foundation_child[1] == _vd.last_child) _vd.last_foundation_child[1] = child_id;
858 _vd.last_child = child_id;
859}
860
870static std::string &AddStringToDraw(int x, int y, Colours colour, ViewportStringFlags flags, uint16_t width)
871{
872 assert(width != 0);
873 StringSpriteToDraw &ss = _vd.string_sprites_to_draw.emplace_back();
874 ss.colour = colour;
875 ss.flags = flags;
876 ss.x = x;
877 ss.y = y;
878 ss.width = width;
879
880 return ss.string;
881}
882
883
897static void DrawSelectionSprite(SpriteID image, PaletteID pal, const TileInfo *ti, int z_offset, FoundationPart foundation_part, int extra_offs_x = 0, int extra_offs_y = 0)
898{
899 if (_vd.foundation[foundation_part] == -1) {
900 /* draw on real ground */
901 AddTileSpriteToDraw(image, pal, ti->x, ti->y, ti->z + z_offset, nullptr, extra_offs_x, extra_offs_y);
902 } else {
903 /* draw on top of foundation */
904 AddChildSpriteToFoundation(image, pal, nullptr, foundation_part, extra_offs_x, extra_offs_y - z_offset * ZOOM_BASE);
905 }
906}
907
914static void DrawTileSelectionRect(const TileInfo *ti, PaletteID pal)
915{
916 if (!IsValidTile(ti->tile)) return;
917
918 SpriteID sel;
919 if (IsHalftileSlope(ti->tileh)) {
920 Corner halftile_corner = GetHalftileSlopeCorner(ti->tileh);
921 SpriteID sel2 = SPR_HALFTILE_SELECTION_FLAT + halftile_corner;
923
924 Corner opposite_corner = OppositeCorner(halftile_corner);
925 if (IsSteepSlope(ti->tileh)) {
926 sel = SPR_HALFTILE_SELECTION_DOWN;
927 } else {
928 sel = ((ti->tileh & SlopeWithOneCornerRaised(opposite_corner)) != 0 ? SPR_HALFTILE_SELECTION_UP : SPR_HALFTILE_SELECTION_FLAT);
929 }
930 sel += opposite_corner;
931 } else {
932 sel = SPR_SELECT_TILE + SlopeToSpriteOffset(ti->tileh);
933 }
935}
936
937static bool IsPartOfAutoLine(int px, int py)
938{
939 px -= _thd.selstart.x;
940 py -= _thd.selstart.y;
941
942 if ((_thd.drawstyle & HT_DRAG_MASK) != HT_LINE) return false;
943
944 switch (_thd.drawstyle & HT_DIR_MASK) {
945 case HT_DIR_X: return py == 0; // x direction
946 case HT_DIR_Y: return px == 0; // y direction
947 case HT_DIR_HU: return px == -py || px == -py - 16; // horizontal upper
948 case HT_DIR_HL: return px == -py || px == -py + 16; // horizontal lower
949 case HT_DIR_VL: return px == py || px == py + 16; // vertical left
950 case HT_DIR_VR: return px == py || px == py - 16; // vertical right
951 default:
952 NOT_REACHED();
953 }
954}
955
956/* [direction][side] */
957static const HighLightStyle _autorail_type[6][2] = {
958 { HT_DIR_X, HT_DIR_X },
959 { HT_DIR_Y, HT_DIR_Y },
960 { HT_DIR_HU, HT_DIR_HL },
961 { HT_DIR_HL, HT_DIR_HU },
962 { HT_DIR_VL, HT_DIR_VR },
964};
965
966#include "table/autorail.h"
967
974static void DrawAutorailSelection(const TileInfo *ti, uint autorail_type)
975{
976 SpriteID image;
977 PaletteID pal;
978 int offset;
979
980 FoundationPart foundation_part = FOUNDATION_PART_NORMAL;
981 Slope autorail_tileh = RemoveHalftileSlope(ti->tileh);
982 if (IsHalftileSlope(ti->tileh)) {
983 static const uint _lower_rail[4] = { 5U, 2U, 4U, 3U };
984 Corner halftile_corner = GetHalftileSlopeCorner(ti->tileh);
985 if (autorail_type != _lower_rail[halftile_corner]) {
986 foundation_part = FOUNDATION_PART_HALFTILE;
987 /* Here we draw the highlights of the "three-corners-raised"-slope. That looks ok to me. */
988 autorail_tileh = SlopeWithThreeCornersRaised(OppositeCorner(halftile_corner));
989 }
990 }
991
992 offset = _AutorailTilehSprite[autorail_tileh][autorail_type];
993 if (offset >= 0) {
994 image = SPR_AUTORAIL_BASE + offset;
995 pal = PAL_NONE;
996 } else {
997 image = SPR_AUTORAIL_BASE - offset;
999 }
1000
1001 DrawSelectionSprite(image, _thd.make_square_red ? PALETTE_SEL_TILE_RED : pal, ti, 7, foundation_part);
1002}
1003
1004enum TileHighlightType : uint8_t {
1005 THT_NONE,
1006 THT_WHITE,
1007 THT_BLUE,
1008 THT_RED,
1009};
1010
1016
1022static TileHighlightType GetTileHighlightType(TileIndex t)
1023{
1024 if (_viewport_highlight_station != nullptr) {
1025 if (IsTileType(t, MP_STATION) && GetStationIndex(t) == _viewport_highlight_station->index) return THT_WHITE;
1026 if (_viewport_highlight_station->TileIsInCatchment(t)) return THT_BLUE;
1027 }
1028
1029 if (_viewport_highlight_station_rect != nullptr) {
1032 if (r->PtInExtendedRect(TileX(t), TileY(t))) return THT_BLUE;
1033 }
1034
1035 if (_viewport_highlight_waypoint != nullptr) {
1036 if (IsTileType(t, MP_STATION) && GetStationIndex(t) == _viewport_highlight_waypoint->index) return THT_BLUE;
1037 }
1038
1039 if (_viewport_highlight_waypoint_rect != nullptr) {
1042 if (r->PtInExtendedRect(TileX(t), TileY(t))) return THT_BLUE;
1043 }
1044
1045 if (_viewport_highlight_town != nullptr) {
1046 if (IsTileType(t, MP_HOUSE)) {
1048 TileHighlightType type = THT_RED;
1050 if (st->owner != _current_company) continue;
1051 if (st->TileIsInCatchment(t)) return THT_BLUE;
1052 }
1053 return type;
1054 }
1055 } else if (IsTileType(t, MP_STATION)) {
1057 if (st->owner != _current_company) continue;
1058 if (GetStationIndex(t) == st->index) return THT_WHITE;
1059 }
1060 }
1061 }
1062
1063 return THT_NONE;
1064}
1065
1071static void DrawTileHighlightType(const TileInfo *ti, TileHighlightType tht)
1072{
1073 switch (tht) {
1074 default:
1075 case THT_NONE: break;
1076 case THT_WHITE: DrawTileSelectionRect(ti, PAL_NONE); break;
1077 case THT_BLUE: DrawTileSelectionRect(ti, PALETTE_SEL_TILE_BLUE); break;
1078 case THT_RED: DrawTileSelectionRect(ti, PALETTE_SEL_TILE_RED); break;
1079 }
1080}
1081
1087{
1088 /* Going through cases in order of computational time. */
1089
1090 if (_town_local_authority_kdtree.Count() == 0) return;
1091
1092 /* Tile belongs to town regardless of distance from town. */
1093 if (GetTileType(ti->tile) == MP_HOUSE) {
1094 if (!Town::GetByTile(ti->tile)->show_zone) return;
1095
1097 return;
1098 }
1099
1100 /* If the closest town in the highlighted list is far, we can stop searching. */
1101 TownID tid = _town_local_authority_kdtree.FindNearest(TileX(ti->tile), TileY(ti->tile));
1102 Town *closest_highlighted_town = Town::Get(tid);
1103
1104 if (DistanceManhattan(ti->tile, closest_highlighted_town->xy) >= _settings_game.economy.dist_local_authority) return;
1105
1106 /* Tile is inside of the local autrhority distance of a highlighted town,
1107 but it is possible that a non-highlighted town is even closer. */
1109
1110 if (closest_town->show_zone) {
1112 }
1113
1114}
1115
1120static void DrawTileSelection(const TileInfo *ti)
1121{
1122 /* Highlight tiles inside local authority of selected towns. */
1124
1125 /* Draw a red error square? */
1126 bool is_redsq = _thd.redsq == ti->tile;
1128
1129 TileHighlightType tht = GetTileHighlightType(ti->tile);
1130 DrawTileHighlightType(ti, tht);
1131
1132 /* No tile selection active? */
1133 if ((_thd.drawstyle & HT_DRAG_MASK) == HT_NONE) return;
1134
1135 if (_thd.diagonal) { // We're drawing a 45 degrees rotated (diagonal) rectangle
1136 if (IsInsideRotatedRectangle((int)ti->x, (int)ti->y)) goto draw_inner;
1137 return;
1138 }
1139
1140 /* Inside the inner area? */
1141 if (IsInsideBS(ti->x, _thd.pos.x, _thd.size.x) &&
1142 IsInsideBS(ti->y, _thd.pos.y, _thd.size.y)) {
1143draw_inner:
1144 if (_thd.drawstyle & HT_RECT) {
1145 if (!is_redsq) DrawTileSelectionRect(ti, _thd.make_square_red ? PALETTE_SEL_TILE_RED : PAL_NONE);
1146 } else if (_thd.drawstyle & HT_POINT) {
1147 /* Figure out the Z coordinate for the single dot. */
1148 int z = 0;
1149 FoundationPart foundation_part = FOUNDATION_PART_NORMAL;
1150 if (ti->tileh & SLOPE_N) {
1151 z += TILE_HEIGHT;
1153 }
1154 if (IsHalftileSlope(ti->tileh)) {
1155 Corner halftile_corner = GetHalftileSlopeCorner(ti->tileh);
1156 if ((halftile_corner == CORNER_W) || (halftile_corner == CORNER_E)) z += TILE_HEIGHT;
1157 if (halftile_corner != CORNER_S) {
1158 foundation_part = FOUNDATION_PART_HALFTILE;
1159 if (IsSteepSlope(ti->tileh)) z -= TILE_HEIGHT;
1160 }
1161 }
1162 DrawSelectionSprite(SPR_DOT, PAL_NONE, ti, z, foundation_part);
1163 } else if (_thd.drawstyle & HT_RAIL) {
1164 /* autorail highlight piece under cursor */
1165 HighLightStyle type = _thd.drawstyle & HT_DIR_MASK;
1166 assert(type < HT_DIR_END);
1167 DrawAutorailSelection(ti, _autorail_type[type][0]);
1168 } else if (IsPartOfAutoLine(ti->x, ti->y)) {
1169 /* autorail highlighting long line */
1171 uint side;
1172
1173 if (dir == HT_DIR_X || dir == HT_DIR_Y) {
1174 side = 0;
1175 } else {
1176 TileIndex start = TileVirtXY(_thd.selstart.x, _thd.selstart.y);
1177 side = Delta(Delta(TileX(start), TileX(ti->tile)), Delta(TileY(start), TileY(ti->tile)));
1178 }
1179
1180 DrawAutorailSelection(ti, _autorail_type[dir][side]);
1181 }
1182 return;
1183 }
1184
1185 /* Check if it's inside the outer area? */
1186 if (!is_redsq && (tht == THT_NONE || tht == THT_RED) && _thd.outersize.x > 0 &&
1187 IsInsideBS(ti->x, _thd.pos.x + _thd.offs.x, _thd.size.x + _thd.outersize.x) &&
1188 IsInsideBS(ti->y, _thd.pos.y + _thd.offs.y, _thd.size.y + _thd.outersize.y)) {
1189 /* Draw a blue rect. */
1191 return;
1192 }
1193}
1194
1201static int GetViewportY(Point tile)
1202{
1203 /* Each increment in X or Y direction moves down by half a tile, i.e. TILE_PIXELS / 2. */
1204 return (tile.y * (int)(TILE_PIXELS / 2) + tile.x * (int)(TILE_PIXELS / 2) - TilePixelHeightOutsideMap(tile.x, tile.y)) << ZOOM_BASE_SHIFT;
1205}
1206
1211{
1212 assert(_vd.dpi.top <= _vd.dpi.top + _vd.dpi.height);
1213 assert(_vd.dpi.left <= _vd.dpi.left + _vd.dpi.width);
1214
1215 Point upper_left = InverseRemapCoords(_vd.dpi.left, _vd.dpi.top);
1216 Point upper_right = InverseRemapCoords(_vd.dpi.left + _vd.dpi.width, _vd.dpi.top);
1217
1218 /* Transformations between tile coordinates and viewport rows/columns: See vp_column_row
1219 * column = y - x
1220 * row = x + y
1221 * x = (row - column) / 2
1222 * y = (row + column) / 2
1223 * Note: (row, columns) pairs are only valid, if they are both even or both odd.
1224 */
1225
1226 /* Columns overlap with neighbouring columns by a half tile.
1227 * - Left column is column of upper_left (rounded down) and one column to the left.
1228 * - Right column is column of upper_right (rounded up) and one column to the right.
1229 * Note: Integer-division does not round down for negative numbers, so ensure rounding with another increment/decrement.
1230 */
1231 int left_column = (upper_left.y - upper_left.x) / (int)TILE_SIZE - 2;
1232 int right_column = (upper_right.y - upper_right.x) / (int)TILE_SIZE + 2;
1233
1234 int potential_bridge_height = ZOOM_BASE * TILE_HEIGHT * _settings_game.construction.max_bridge_height;
1235
1236 /* Rows overlap with neighbouring rows by a half tile.
1237 * The first row that could possibly be visible is the row above upper_left (if it is at height 0).
1238 * Due to integer-division not rounding down for negative numbers, we need another decrement.
1239 */
1240 int row = (upper_left.x + upper_left.y) / (int)TILE_SIZE - 2;
1241 bool last_row = false;
1242 for (; !last_row; row++) {
1243 last_row = true;
1244 for (int column = left_column; column <= right_column; column++) {
1245 /* Valid row/column? */
1246 if ((row + column) % 2 != 0) continue;
1247
1248 Point tilecoord;
1249 tilecoord.x = (row - column) / 2;
1250 tilecoord.y = (row + column) / 2;
1251 assert(column == tilecoord.y - tilecoord.x);
1252 assert(row == tilecoord.y + tilecoord.x);
1253
1254 TileType tile_type;
1255 _cur_ti.x = tilecoord.x * TILE_SIZE;
1256 _cur_ti.y = tilecoord.y * TILE_SIZE;
1257
1258 if (IsInsideBS(tilecoord.x, 0, Map::SizeX()) && IsInsideBS(tilecoord.y, 0, Map::SizeY())) {
1259 /* This includes the south border at Map::MaxX / Map::MaxY. When terraforming we still draw tile selections there. */
1260 _cur_ti.tile = TileXY(tilecoord.x, tilecoord.y);
1261 tile_type = GetTileType(_cur_ti.tile);
1262 } else {
1263 _cur_ti.tile = INVALID_TILE;
1264 tile_type = MP_VOID;
1265 }
1266
1267 if (tile_type != MP_VOID) {
1268 /* We are inside the map => paint landscape. */
1269 std::tie(_cur_ti.tileh, _cur_ti.z) = GetTilePixelSlope(_cur_ti.tile);
1270 } else {
1271 /* We are outside the map => paint black. */
1272 std::tie(_cur_ti.tileh, _cur_ti.z) = GetTilePixelSlopeOutsideMap(tilecoord.x, tilecoord.y);
1273 }
1274
1275 int viewport_y = GetViewportY(tilecoord);
1276
1277 if (viewport_y + MAX_TILE_EXTENT_BOTTOM < _vd.dpi.top) {
1278 /* The tile in this column is not visible yet.
1279 * Tiles in other columns may be visible, but we need more rows in any case. */
1280 last_row = false;
1281 continue;
1282 }
1283
1284 int min_visible_height = viewport_y - (_vd.dpi.top + _vd.dpi.height);
1285 bool tile_visible = min_visible_height <= 0;
1286
1287 if (tile_type != MP_VOID) {
1288 /* Is tile with buildings visible? */
1289 if (min_visible_height < MAX_TILE_EXTENT_TOP) tile_visible = true;
1290
1291 if (IsBridgeAbove(_cur_ti.tile)) {
1292 /* Is the bridge visible? */
1293 TileIndex bridge_tile = GetNorthernBridgeEnd(_cur_ti.tile);
1294 int bridge_height = ZOOM_BASE * (GetBridgePixelHeight(bridge_tile) - TilePixelHeight(_cur_ti.tile));
1295 if (min_visible_height < bridge_height + MAX_TILE_EXTENT_TOP) tile_visible = true;
1296 }
1297
1298 /* Would a higher bridge on a more southern tile be visible?
1299 * If yes, we need to loop over more rows to possibly find one. */
1300 if (min_visible_height < potential_bridge_height + MAX_TILE_EXTENT_TOP) last_row = false;
1301 } else {
1302 /* Outside of map. If we are on the north border of the map, there may still be a bridge visible,
1303 * so we need to loop over more rows to possibly find one. */
1304 if ((tilecoord.x <= 0 || tilecoord.y <= 0) && min_visible_height < potential_bridge_height + MAX_TILE_EXTENT_TOP) last_row = false;
1305 }
1306
1307 if (tile_visible) {
1308 last_row = false;
1310 _vd.foundation[0] = -1;
1311 _vd.foundation[1] = -1;
1314
1315 _tile_type_procs[tile_type]->draw_tile_proc(&_cur_ti);
1316 if (_cur_ti.tile != INVALID_TILE) DrawTileSelection(&_cur_ti);
1317 }
1318 }
1319 }
1320}
1321
1330std::string *ViewportAddString(const DrawPixelInfo *dpi, const ViewportSign *sign, ViewportStringFlags flags, Colours colour)
1331{
1332 int left = dpi->left;
1333 int top = dpi->top;
1334 int right = left + dpi->width;
1335 int bottom = top + dpi->height;
1336
1337 bool small = flags.Test(ViewportStringFlag::Small);
1338 int sign_height = ScaleByZoom(WidgetDimensions::scaled.fullbevel.top + GetCharacterHeight(small ? FS_SMALL : FS_NORMAL) + WidgetDimensions::scaled.fullbevel.bottom, dpi->zoom);
1339 int sign_half_width = ScaleByZoom((small ? sign->width_small : sign->width_normal) / 2, dpi->zoom);
1340
1341 if (bottom < sign->top ||
1342 top > sign->top + sign_height ||
1343 right < sign->center - sign_half_width ||
1344 left > sign->center + sign_half_width) {
1345 return nullptr;
1346 }
1347
1348 return &AddStringToDraw(sign->center - sign_half_width, sign->top, colour, flags, small ? sign->width_small : sign->width_normal);
1349}
1350
1351static Rect ExpandRectWithViewportSignMargins(Rect r, ZoomLevel zoom)
1352{
1353 const int fh = std::max(GetCharacterHeight(FS_NORMAL), GetCharacterHeight(FS_SMALL));
1354 const int max_tw = _viewport_sign_maxwidth / 2 + 1;
1355 const int expand_y = ScaleByZoom(WidgetDimensions::scaled.fullbevel.top + fh + WidgetDimensions::scaled.fullbevel.bottom, zoom);
1356 const int expand_x = ScaleByZoom(WidgetDimensions::scaled.fullbevel.left + max_tw + WidgetDimensions::scaled.fullbevel.right, zoom);
1357
1358 r.left -= expand_x;
1359 r.right += expand_x;
1360 r.top -= expand_y;
1361 r.bottom += expand_y;
1362
1363 return r;
1364}
1365
1372static void ViewportAddTownStrings(DrawPixelInfo *dpi, const std::vector<const Town *> &towns, bool small)
1373{
1374 ViewportStringFlags flags{};
1376
1377 StringID stringid_town = !small && _settings_client.gui.population_in_label ? STR_VIEWPORT_TOWN_POP : STR_TOWN_NAME;
1378 StringID stringid_town_city = stringid_town;
1379 if (!small) {
1380 stringid_town_city = _settings_client.gui.population_in_label ? STR_VIEWPORT_TOWN_CITY_POP : STR_VIEWPORT_TOWN_CITY;
1381 }
1382
1383 for (const Town *t : towns) {
1384 std::string *str = ViewportAddString(dpi, &t->cache.sign, flags, INVALID_COLOUR);
1385 if (str == nullptr) continue;
1386
1387 if (t->larger_town) {
1388 *str = GetString(stringid_town_city, t->index, t->cache.population);
1389 } else {
1390 *str = GetString(stringid_town, t->index, t->cache.population);
1391 }
1392 }
1393}
1394
1401static void ViewportAddSignStrings(DrawPixelInfo *dpi, const std::vector<const Sign *> &signs, bool small)
1402{
1403 ViewportStringFlags flags{};
1404 if (small) flags.Set(ViewportStringFlag::Small);
1405
1406 /* Signs placed by a game script don't have a frame. */
1407 ViewportStringFlags deity_flags{ flags };
1409
1411
1412 for (const Sign *si : signs) {
1413 /* Workaround to make sure white is actually white. The string drawing logic changes all
1414 * colours that are not INVALID_COLOUR slightly, turning white into a light gray. */
1415 const Colours deity_colour = si->text_colour == COLOUR_WHITE ? INVALID_COLOUR : si->text_colour;
1416
1417 std::string *str = ViewportAddString(dpi, &si->sign, (si->owner == OWNER_DEITY) ? deity_flags : flags,
1418 (si->owner == OWNER_NONE) ? COLOUR_GREY : (si->owner == OWNER_DEITY ? deity_colour : _company_colours[si->owner]));
1419 if (str == nullptr) continue;
1420
1421 *str = GetString(STR_SIGN_NAME, si->index);
1422 }
1423}
1424
1431static void ViewportAddStationStrings(DrawPixelInfo *dpi, const std::vector<const BaseStation *> &stations, bool small)
1432{
1433 /* Transparent station signs have colour text instead of a colour panel. */
1435 if (small) flags.Set(ViewportStringFlag::Small);
1436
1437 for (const BaseStation *st : stations) {
1438 std::string *str = ViewportAddString(dpi, &st->sign, flags, (st->owner == OWNER_NONE || !st->IsInUse()) ? COLOUR_GREY : _company_colours[st->owner]);
1439 if (str == nullptr) continue;
1440
1441 if (Station::IsExpected(st)) { /* Station */
1442 *str = GetString(small ? STR_STATION_NAME : STR_VIEWPORT_STATION, st->index, st->facilities);
1443 } else { /* Waypoint */
1444 *str = GetString(STR_WAYPOINT_NAME, st->index);
1445 }
1446 }
1447}
1448
1449static void ViewportAddKdtreeSigns(DrawPixelInfo *dpi)
1450{
1451 Rect search_rect{ dpi->left, dpi->top, dpi->left + dpi->width, dpi->top + dpi->height };
1452 search_rect = ExpandRectWithViewportSignMargins(search_rect, dpi->zoom);
1453
1454 bool show_stations = HasBit(_display_opt, DO_SHOW_STATION_NAMES) && _game_mode != GM_MENU;
1455 bool show_waypoints = HasBit(_display_opt, DO_SHOW_WAYPOINT_NAMES) && _game_mode != GM_MENU;
1456 bool show_towns = HasBit(_display_opt, DO_SHOW_TOWN_NAMES) && _game_mode != GM_MENU;
1458 bool show_competitors = HasBit(_display_opt, DO_SHOW_COMPETITOR_SIGNS);
1459
1460 /* Collect all the items first and draw afterwards, to ensure layering */
1461 std::vector<const BaseStation *> stations;
1462 std::vector<const Town *> towns;
1463 std::vector<const Sign *> signs;
1464
1465 _viewport_sign_kdtree.FindContained(search_rect.left, search_rect.top, search_rect.right, search_rect.bottom, [&](const ViewportSignKdtreeItem & item) {
1466 switch (item.type) {
1467 case ViewportSignKdtreeItem::VKI_STATION: {
1468 if (!show_stations) break;
1469 const BaseStation *st = BaseStation::Get(std::get<StationID>(item.id));
1470
1471 /* If no facilities are present the station is a ghost station. */
1472 StationFacilities facilities = st->facilities;
1473 if (facilities.None()) facilities = STATION_FACILITY_GHOST;
1474
1475 if (!facilities.Any(_facility_display_opt)) break;
1476
1477 /* Don't draw if station is owned by another company and competitor station names are hidden. Stations owned by none are never ignored. */
1478 if (!show_competitors && _local_company != st->owner && st->owner != OWNER_NONE) break;
1479
1480 stations.push_back(st);
1481 break;
1482 }
1483
1484 case ViewportSignKdtreeItem::VKI_WAYPOINT: {
1485 if (!show_waypoints) break;
1486 const BaseStation *st = BaseStation::Get(std::get<StationID>(item.id));
1487
1488 /* Don't draw if station is owned by another company and competitor station names are hidden. Stations owned by none are never ignored. */
1489 if (!show_competitors && _local_company != st->owner && st->owner != OWNER_NONE) break;
1490
1491 stations.push_back(st);
1492 break;
1493 }
1494
1495 case ViewportSignKdtreeItem::VKI_TOWN:
1496 if (!show_towns) break;
1497 towns.push_back(Town::Get(std::get<TownID>(item.id)));
1498 break;
1499
1500 case ViewportSignKdtreeItem::VKI_SIGN: {
1501 if (!show_signs) break;
1502 const Sign *si = Sign::Get(std::get<SignID>(item.id));
1503
1504 /* Don't draw if sign is owned by another company and competitor signs should be hidden.
1505 * Note: It is intentional that also signs owned by OWNER_NONE are hidden. Bankrupt
1506 * companies can leave OWNER_NONE signs after them. */
1507 if (!show_competitors && _local_company != si->owner && si->owner != OWNER_DEITY) break;
1508
1509 signs.push_back(si);
1510 break;
1511 }
1512
1513 default:
1514 NOT_REACHED();
1515 }
1516 });
1517
1518 /* Small versions of signs are used zoom level 4X and higher. */
1519 bool small = dpi->zoom >= ZoomLevel::Out4x;
1520
1521 /* Layering order (bottom to top): Town names, signs, stations */
1522 ViewportAddTownStrings(dpi, towns, small);
1523
1524 /* Do not draw signs nor station names if they are set invisible */
1525 if (IsInvisibilitySet(TO_SIGNS)) return;
1526
1527 ViewportAddSignStrings(dpi, signs, small);
1528 ViewportAddStationStrings(dpi, stations, small);
1529}
1530
1531
1539void ViewportSign::UpdatePosition(int center, int top, std::string_view str, std::string_view str_small)
1540{
1541 if (this->width_normal != 0) this->MarkDirty();
1542
1543 this->top = top;
1544
1546 this->center = center;
1547
1548 /* zoomed out version */
1549 if (str_small.empty()) str_small = str;
1551
1552 this->MarkDirty();
1553}
1554
1562{
1563 Rect zoomlevels[to_underlying(ZoomLevel::End)];
1564
1565 /* We don't know which size will be drawn, so mark the largest area dirty. */
1566 const uint half_width = std::max(this->width_normal, this->width_small) / 2 + 1;
1568
1569 for (ZoomLevel zoom = ZoomLevel::Begin; zoom != ZoomLevel::End; zoom++) {
1570 zoomlevels[to_underlying(zoom)].left = this->center - ScaleByZoom(half_width, zoom);
1571 zoomlevels[to_underlying(zoom)].top = this->top - ScaleByZoom(1, zoom);
1572 zoomlevels[to_underlying(zoom)].right = this->center + ScaleByZoom(half_width, zoom);
1573 zoomlevels[to_underlying(zoom)].bottom = this->top + ScaleByZoom(height, zoom);
1574 }
1575
1576 for (const Window *w : Window::Iterate()) {
1577 if (w->viewport == nullptr) continue;
1578
1579 Viewport &vp = *w->viewport;
1580 if (vp.zoom <= maxzoom) {
1581 assert(vp.width != 0);
1582 Rect &zl = zoomlevels[to_underlying(vp.zoom)];
1583 MarkViewportDirty(vp, zl.left, zl.top, zl.right, zl.bottom);
1584 }
1585 }
1586}
1587
1588static void ViewportDrawTileSprites(const TileSpriteToDrawVector *tstdv)
1589{
1590 for (const TileSpriteToDraw &ts : *tstdv) {
1591 DrawSpriteViewport(ts.image, ts.pal, ts.x, ts.y, ts.sub);
1592 }
1593}
1594
1597{
1598 return true;
1599}
1600
1602static void ViewportSortParentSprites(ParentSpriteToSortVector *psdv)
1603{
1604 if (psdv->size() < 2) return;
1605
1606 /* We rely on sprites being, for the most part, already ordered.
1607 * So we don't need to move many of them and can keep track of their
1608 * order efficiently by using stack. We always move sprites to the front
1609 * of the current position, i.e. to the top of the stack.
1610 * Also use special constants to indicate sorting state without
1611 * adding extra fields to ParentSpriteToDraw structure.
1612 */
1613 const uint32_t ORDER_COMPARED = UINT32_MAX; // Sprite was compared but we still need to compare the ones preceding it
1614 const uint32_t ORDER_RETURNED = UINT32_MAX - 1; // Mark sorted sprite in case there are other occurrences of it in the stack
1615 std::stack<ParentSpriteToDraw *> sprite_order;
1616 uint32_t next_order = 0;
1617
1618 std::forward_list<std::pair<int64_t, ParentSpriteToDraw *>> sprite_list; // We store sprites in a list sorted by xmin+ymin
1619
1620 /* Initialize sprite list and order. */
1621 for (auto p = psdv->rbegin(); p != psdv->rend(); p++) {
1622 sprite_list.emplace_front((*p)->xmin + (*p)->ymin, *p);
1623 sprite_order.push(*p);
1624 (*p)->order = next_order++;
1625 }
1626
1627 sprite_list.sort();
1628
1629 std::vector<ParentSpriteToDraw *> preceding; // Temporarily stores sprites that precede current and their position in the list
1630 auto preceding_prev = sprite_list.begin(); // Store iterator in case we need to delete a single preceding sprite
1631 auto out = psdv->begin(); // Iterator to output sorted sprites
1632
1633 while (!sprite_order.empty()) {
1634
1635 auto s = sprite_order.top();
1636 sprite_order.pop();
1637
1638 /* Sprite is already sorted, ignore it. */
1639 if (s->order == ORDER_RETURNED) continue;
1640
1641 /* Sprite was already compared, just need to output it. */
1642 if (s->order == ORDER_COMPARED) {
1643 *(out++) = s;
1644 s->order = ORDER_RETURNED;
1645 continue;
1646 }
1647
1648 preceding.clear();
1649
1650 /* We only need sprites with xmin <= s->xmax && ymin <= s->ymax && zmin <= s->zmax
1651 * So by iterating sprites with xmin + ymin <= s->xmax + s->ymax
1652 * we get all we need and some more that we filter out later.
1653 * We don't include zmin into the sum as there are usually more neighbours on x and y than z
1654 * so including it will actually increase the amount of false positives.
1655 * Also min coordinates can be > max so using max(xmin, xmax) + max(ymin, ymax)
1656 * to ensure that we iterate the current sprite as we need to remove it from the list.
1657 */
1658 auto ssum = std::max(s->xmax, s->xmin) + std::max(s->ymax, s->ymin);
1659 auto prev = sprite_list.before_begin();
1660 auto x = sprite_list.begin();
1661 while (x != sprite_list.end() && x->first <= ssum) {
1662 auto p = x->second;
1663 if (p == s) {
1664 /* We found the current sprite, remove it and move on. */
1665 x = sprite_list.erase_after(prev);
1666 continue;
1667 }
1668
1669 auto p_prev = prev;
1670 prev = x++;
1671
1672 if (s->xmax < p->xmin || s->ymax < p->ymin || s->zmax < p->zmin) continue;
1673 if (s->xmin <= p->xmax && // overlap in X?
1674 s->ymin <= p->ymax && // overlap in Y?
1675 s->zmin <= p->zmax) { // overlap in Z?
1676 if (s->xmin + s->xmax + s->ymin + s->ymax + s->zmin + s->zmax <=
1677 p->xmin + p->xmax + p->ymin + p->ymax + p->zmin + p->zmax) {
1678 continue;
1679 }
1680 }
1681 preceding.push_back(p);
1682 preceding_prev = p_prev;
1683 }
1684
1685 if (preceding.empty()) {
1686 /* No preceding sprites, add current one to the output */
1687 *(out++) = s;
1688 s->order = ORDER_RETURNED;
1689 continue;
1690 }
1691
1692 /* Optimization for the case when we only have 1 sprite to move. */
1693 if (preceding.size() == 1) {
1694 auto p = preceding[0];
1695 /* We can only output the preceding sprite if there can't be any other sprites preceding it. */
1696 if (p->xmax <= s->xmax && p->ymax <= s->ymax && p->zmax <= s->zmax) {
1697 p->order = ORDER_RETURNED;
1698 s->order = ORDER_RETURNED;
1699 sprite_list.erase_after(preceding_prev);
1700 *(out++) = p;
1701 *(out++) = s;
1702 continue;
1703 }
1704 }
1705
1706 /* Sort all preceding sprites by order and assign new orders in reverse (as original sorter did). */
1707 std::sort(preceding.begin(), preceding.end(), [](const ParentSpriteToDraw *a, const ParentSpriteToDraw *b) {
1708 return a->order > b->order;
1709 });
1710
1711 s->order = ORDER_COMPARED;
1712 sprite_order.push(s); // Still need to output so push it back for now
1713
1714 for (auto p: preceding) {
1715 p->order = next_order++;
1716 sprite_order.push(p);
1717 }
1718 }
1719}
1720
1721
1722static void ViewportDrawParentSprites(const ParentSpriteToSortVector *psd, const ChildScreenSpriteToDrawVector *csstdv)
1723{
1724 for (const ParentSpriteToDraw *ps : *psd) {
1725 if (ps->image != SPR_EMPTY_BOUNDING_BOX) DrawSpriteViewport(ps->image, ps->pal, ps->x, ps->y, ps->sub);
1726
1727 int child_idx = ps->first_child;
1728 while (child_idx >= 0) {
1729 const ChildScreenSpriteToDraw *cs = &(*csstdv)[child_idx];
1730 child_idx = cs->next;
1731 if (cs->relative) {
1732 DrawSpriteViewport(cs->image, cs->pal, ps->left + cs->x, ps->top + cs->y, cs->sub);
1733 } else {
1734 DrawSpriteViewport(cs->image, cs->pal, ps->x + cs->x, ps->y + cs->y, cs->sub);
1735 }
1736 }
1737 }
1738}
1739
1744static void ViewportDrawBoundingBoxes(const ParentSpriteToSortVector *psd)
1745{
1746 for (const ParentSpriteToDraw *ps : *psd) {
1747 Point pt1 = RemapCoords(ps->xmax + 1, ps->ymax + 1, ps->zmax + 1); // top front corner
1748 Point pt2 = RemapCoords(ps->xmin , ps->ymax + 1, ps->zmax + 1); // top left corner
1749 Point pt3 = RemapCoords(ps->xmax + 1, ps->ymin , ps->zmax + 1); // top right corner
1750 Point pt4 = RemapCoords(ps->xmax + 1, ps->ymax + 1, ps->zmin ); // bottom front corner
1751
1752 DrawBox( pt1.x, pt1.y,
1753 pt2.x - pt1.x, pt2.y - pt1.y,
1754 pt3.x - pt1.x, pt3.y - pt1.y,
1755 pt4.x - pt1.x, pt4.y - pt1.y);
1756 }
1757}
1758
1763{
1765 const DrawPixelInfo *dpi = _cur_dpi;
1766 void *dst;
1767 int right = UnScaleByZoom(dpi->width, dpi->zoom);
1768 int bottom = UnScaleByZoom(dpi->height, dpi->zoom);
1769
1770 PixelColour colour = _string_colourmap[_dirty_block_colour & 0xF];
1771
1772 dst = dpi->dst_ptr;
1773
1774 uint8_t bo = UnScaleByZoom(dpi->left + dpi->top, dpi->zoom) & 1;
1775 do {
1776 for (int i = (bo ^= 1); i < right; i += 2) blitter->SetPixel(dst, i, 0, colour);
1777 dst = blitter->MoveTo(dst, 0, 1);
1778 } while (--bottom > 0);
1779}
1780
1781static void ViewportDrawStrings(ZoomLevel zoom, const StringSpriteToDrawVector *sstdv)
1782{
1783 for (const StringSpriteToDraw &ss : *sstdv) {
1784 bool small = ss.flags.Test(ViewportStringFlag::Small);
1785 int w = ss.width;
1786 int x = UnScaleByZoom(ss.x, zoom);
1787 int y = UnScaleByZoom(ss.y, zoom);
1788 int h = WidgetDimensions::scaled.fullbevel.top + GetCharacterHeight(small ? FS_SMALL : FS_NORMAL) + WidgetDimensions::scaled.fullbevel.bottom;
1789
1790 TextColour colour = TC_WHITE;
1791 if (ss.flags.Test(ViewportStringFlag::ColourRect)) {
1792 if (ss.colour != INVALID_COLOUR) DrawFrameRect(x, y, x + w - 1, y + h - 1, ss.colour, {});
1793 colour = TC_BLACK;
1794 } else if (ss.flags.Test(ViewportStringFlag::TransparentRect)) {
1795 DrawFrameRect(x, y, x + w - 1, y + h - 1, ss.colour, FrameFlag::Transparent);
1796 }
1797
1798 if (ss.flags.Test(ViewportStringFlag::TextColour)) {
1799 if (ss.colour != INVALID_COLOUR) colour = GetColourGradient(ss.colour, SHADE_LIGHTER).ToTextColour();
1800 }
1801
1802 int left = x + WidgetDimensions::scaled.fullbevel.left;
1803 int right = x + w - 1 - WidgetDimensions::scaled.fullbevel.right;
1804 int top = y + WidgetDimensions::scaled.fullbevel.top;
1805
1806 int shadow_offset = 0;
1807 if (small && ss.flags.Test(ViewportStringFlag::Shadow)) {
1808 /* Shadow needs to be shifted 1 pixel. */
1809 shadow_offset = WidgetDimensions::scaled.fullbevel.top;
1810 DrawString(left + shadow_offset, right + shadow_offset, top, ss.string, TC_BLACK, SA_HOR_CENTER, false, FS_SMALL);
1811 }
1812
1813 DrawString(left, right, top - shadow_offset, ss.string, colour, SA_HOR_CENTER, false, small ? FS_SMALL : FS_NORMAL);
1814 }
1815}
1816
1817void ViewportDoDraw(const Viewport &vp, int left, int top, int right, int bottom)
1818{
1819 _vd.dpi.zoom = vp.zoom;
1820 int mask = ScaleByZoom(-1, vp.zoom);
1821
1823
1824 _vd.dpi.width = (right - left) & mask;
1825 _vd.dpi.height = (bottom - top) & mask;
1826 _vd.dpi.left = left & mask;
1827 _vd.dpi.top = top & mask;
1828 _vd.dpi.pitch = _cur_dpi->pitch;
1829 _vd.last_child = LAST_CHILD_NONE;
1830
1831 int x = UnScaleByZoom(_vd.dpi.left - (vp.virtual_left & mask), vp.zoom) + vp.left;
1832 int y = UnScaleByZoom(_vd.dpi.top - (vp.virtual_top & mask), vp.zoom) + vp.top;
1833
1834 _vd.dpi.dst_ptr = BlitterFactory::GetCurrentBlitter()->MoveTo(_cur_dpi->dst_ptr, x - _cur_dpi->left, y - _cur_dpi->top);
1835 AutoRestoreBackup dpi_backup(_cur_dpi, &_vd.dpi);
1836
1838 ViewportAddVehicles(&_vd.dpi);
1839
1840 ViewportAddKdtreeSigns(&_vd.dpi);
1841
1842 DrawTextEffects(&_vd.dpi);
1843
1844 if (!_vd.tile_sprites_to_draw.empty()) ViewportDrawTileSprites(&_vd.tile_sprites_to_draw);
1845
1846 for (auto &psd : _vd.parent_sprites_to_draw) {
1847 _vd.parent_sprites_to_sort.push_back(&psd);
1848 }
1849
1850 _vp_sprite_sorter(&_vd.parent_sprites_to_sort);
1851 ViewportDrawParentSprites(&_vd.parent_sprites_to_sort, &_vd.child_screen_sprites_to_draw);
1852
1853 if (_draw_bounding_boxes) ViewportDrawBoundingBoxes(&_vd.parent_sprites_to_sort);
1854 if (_draw_dirty_blocks) ViewportDrawDirtyBlocks();
1855
1856 DrawPixelInfo dp = _vd.dpi;
1857 ZoomLevel zoom = _vd.dpi.zoom;
1858 dp.zoom = ZoomLevel::Min;
1859 dp.width = UnScaleByZoom(dp.width, zoom);
1860 dp.height = UnScaleByZoom(dp.height, zoom);
1861 AutoRestoreBackup cur_dpi(_cur_dpi, &dp);
1862
1863 if (vp.overlay != nullptr && vp.overlay->GetCargoMask() != 0 && vp.overlay->GetCompanyMask().Any()) {
1864 /* translate to window coordinates */
1865 dp.left = x;
1866 dp.top = y;
1867 vp.overlay->Draw(&dp);
1868 }
1869
1870 if (!_vd.string_sprites_to_draw.empty()) {
1871 /* translate to world coordinates */
1872 dp.left = UnScaleByZoom(_vd.dpi.left, zoom);
1873 dp.top = UnScaleByZoom(_vd.dpi.top, zoom);
1874 ViewportDrawStrings(zoom, &_vd.string_sprites_to_draw);
1875 }
1876
1877 _vd.string_sprites_to_draw.clear();
1878 _vd.tile_sprites_to_draw.clear();
1879 _vd.parent_sprites_to_draw.clear();
1880 _vd.parent_sprites_to_sort.clear();
1881 _vd.child_screen_sprites_to_draw.clear();
1882}
1883
1884static inline void ViewportDraw(const Viewport &vp, int left, int top, int right, int bottom)
1885{
1886 if (right <= vp.left || bottom <= vp.top) return;
1887
1888 if (left >= vp.left + vp.width) return;
1889
1890 if (left < vp.left) left = vp.left;
1891 if (right > vp.left + vp.width) right = vp.left + vp.width;
1892
1893 if (top >= vp.top + vp.height) return;
1894
1895 if (top < vp.top) top = vp.top;
1896 if (bottom > vp.top + vp.height) bottom = vp.top + vp.height;
1897
1898 ViewportDoDraw(vp,
1899 ScaleByZoom(left - vp.left, vp.zoom) + vp.virtual_left,
1900 ScaleByZoom(top - vp.top, vp.zoom) + vp.virtual_top,
1901 ScaleByZoom(right - vp.left, vp.zoom) + vp.virtual_left,
1902 ScaleByZoom(bottom - vp.top, vp.zoom) + vp.virtual_top
1903 );
1904}
1905
1910{
1912
1913 DrawPixelInfo *dpi = _cur_dpi;
1914
1915 dpi->left += this->left;
1916 dpi->top += this->top;
1917
1918 ViewportDraw(*this->viewport, dpi->left, dpi->top, dpi->left + dpi->width, dpi->top + dpi->height);
1919
1920 dpi->left -= this->left;
1921 dpi->top -= this->top;
1922}
1923
1934static inline void ClampViewportToMap(const Viewport &vp, int *scroll_x, int *scroll_y)
1935{
1936 /* Centre of the viewport is hot spot. */
1937 Point pt = {
1938 *scroll_x + vp.virtual_width / 2,
1939 *scroll_y + vp.virtual_height / 2
1940 };
1941
1942 /* Find nearest tile that is within borders of the map. */
1943 bool clamped;
1944 pt = InverseRemapCoords2(pt.x, pt.y, true, &clamped);
1945
1946 if (clamped) {
1947 /* Convert back to viewport coordinates and remove centering. */
1948 pt = RemapCoords2(pt.x, pt.y);
1949 *scroll_x = pt.x - vp.virtual_width / 2;
1950 *scroll_y = pt.y - vp.virtual_height / 2;
1951 }
1952}
1953
1966static void ClampSmoothScroll(uint32_t delta_ms, int64_t delta_hi, int64_t delta_lo, int &delta_hi_clamped, int &delta_lo_clamped)
1967{
1969 constexpr int PIXELS_PER_TILE = TILE_PIXELS * 2 * ZOOM_BASE;
1970
1971 assert(delta_hi != 0);
1972
1973 /* Move at most 75% of the distance every 30ms, for a smooth experience */
1974 int64_t delta_left = delta_hi * std::pow(0.75, delta_ms / 30.0);
1975 /* Move never more than 16 tiles per 30ms. */
1976 int max_scroll = Map::ScaleBySize1D(16 * PIXELS_PER_TILE * delta_ms / 30);
1977
1978 /* We never go over the max_scroll speed. */
1979 delta_hi_clamped = Clamp(delta_hi - delta_left, -max_scroll, max_scroll);
1980 /* The lower delta is in ratio of the higher delta, so we keep going straight at the destination. */
1981 delta_lo_clamped = delta_lo * delta_hi_clamped / delta_hi;
1982
1983 /* Ensure we always move (delta_hi can't be zero). */
1984 if (delta_hi_clamped == 0) {
1985 delta_hi_clamped = delta_hi > 0 ? 1 : -1;
1986 }
1987}
1988
1994void UpdateViewportPosition(Window *w, uint32_t delta_ms)
1995{
1996 ViewportData &vp = *w->viewport;
1997
1998 if (vp.follow_vehicle != VehicleID::Invalid()) {
1999 const Vehicle *veh = Vehicle::Get(vp.follow_vehicle);
2000 Point pt = MapXYZToViewport(vp, veh->x_pos, veh->y_pos, veh->z_pos);
2001
2002 vp.scrollpos_x = pt.x;
2003 vp.scrollpos_y = pt.y;
2004 SetViewportPosition(w, pt.x, pt.y);
2005 } else {
2006 /* Ensure the destination location is within the map */
2008
2009 int delta_x = vp.dest_scrollpos_x - vp.scrollpos_x;
2010 int delta_y = vp.dest_scrollpos_y - vp.scrollpos_y;
2011
2012 int current_x = vp.scrollpos_x;
2013 int current_y = vp.scrollpos_y;
2014
2015 bool update_overlay = false;
2016 if (delta_x != 0 || delta_y != 0) {
2018 int delta_x_clamped;
2019 int delta_y_clamped;
2020
2021 if (abs(delta_x) > abs(delta_y)) {
2022 ClampSmoothScroll(delta_ms, delta_x, delta_y, delta_x_clamped, delta_y_clamped);
2023 } else {
2024 ClampSmoothScroll(delta_ms, delta_y, delta_x, delta_y_clamped, delta_x_clamped);
2025 }
2026
2027 vp.scrollpos_x += delta_x_clamped;
2028 vp.scrollpos_y += delta_y_clamped;
2029 } else {
2032 }
2033 update_overlay = (vp.scrollpos_x == vp.dest_scrollpos_x &&
2034 vp.scrollpos_y == vp.dest_scrollpos_y);
2035 }
2036
2038
2039 /* When moving small amounts around the border we can get stuck, and
2040 * not actually move. In those cases, teleport to the destination. */
2041 if ((delta_x != 0 || delta_y != 0) && current_x == vp.scrollpos_x && current_y == vp.scrollpos_y) {
2044 }
2045
2046 SetViewportPosition(w, vp.scrollpos_x, vp.scrollpos_y);
2047 if (update_overlay) RebuildViewportOverlay(w);
2048 }
2049}
2050
2061static bool MarkViewportDirty(const Viewport &vp, int left, int top, int right, int bottom)
2062{
2063 /* Rounding wrt. zoom-out level */
2064 right += (1 << to_underlying(vp.zoom)) - 1;
2065 bottom += (1 << to_underlying(vp.zoom)) - 1;
2066
2067 right -= vp.virtual_left;
2068 if (right <= 0) return false;
2069
2070 bottom -= vp.virtual_top;
2071 if (bottom <= 0) return false;
2072
2073 left = std::max(0, left - vp.virtual_left);
2074
2075 if (left >= vp.virtual_width) return false;
2076
2077 top = std::max(0, top - vp.virtual_top);
2078
2079 if (top >= vp.virtual_height) return false;
2080
2082 UnScaleByZoomLower(left, vp.zoom) + vp.left,
2083 UnScaleByZoomLower(top, vp.zoom) + vp.top,
2084 UnScaleByZoom(right, vp.zoom) + vp.left + 1,
2085 UnScaleByZoom(bottom, vp.zoom) + vp.top + 1
2086 );
2087
2088 return true;
2089}
2090
2100bool MarkAllViewportsDirty(int left, int top, int right, int bottom)
2101{
2102 bool dirty = false;
2103
2104 for (const Window *w : Window::Iterate()) {
2105 if (w->viewport != nullptr) {
2106 assert(w->viewport->width != 0);
2107 if (MarkViewportDirty(*w->viewport, left, top, right, bottom)) dirty = true;
2108 }
2109 }
2110
2111 return dirty;
2112}
2113
2114void ConstrainAllViewportsZoom()
2115{
2116 for (Window *w : Window::Iterate()) {
2117 if (w->viewport == nullptr) continue;
2118
2120 if (zoom != w->viewport->zoom) {
2121 while (w->viewport->zoom < zoom) DoZoomInOutWindow(ZOOM_OUT, w);
2122 while (w->viewport->zoom > zoom) DoZoomInOutWindow(ZOOM_IN, w);
2123 }
2124 }
2125}
2126
2134void MarkTileDirtyByTile(TileIndex tile, int bridge_level_offset, int tile_height_override)
2135{
2136 Point pt = RemapCoords(TileX(tile) * TILE_SIZE, TileY(tile) * TILE_SIZE, tile_height_override * TILE_HEIGHT);
2139 pt.y - MAX_TILE_EXTENT_TOP - ZOOM_BASE * TILE_HEIGHT * bridge_level_offset,
2142}
2143
2152{
2153 int x_size = _thd.size.x;
2154 int y_size = _thd.size.y;
2155
2156 if (!_thd.diagonal) { // Selecting in a straight rectangle (or a single square)
2157 int x_start = _thd.pos.x;
2158 int y_start = _thd.pos.y;
2159
2160 if (_thd.outersize.x != 0) {
2161 x_size += _thd.outersize.x;
2162 x_start += _thd.offs.x;
2163 y_size += _thd.outersize.y;
2164 y_start += _thd.offs.y;
2165 }
2166
2167 x_size -= TILE_SIZE;
2168 y_size -= TILE_SIZE;
2169
2170 assert(x_size >= 0);
2171 assert(y_size >= 0);
2172
2173 int x_end = Clamp(x_start + x_size, 0, Map::SizeX() * TILE_SIZE - TILE_SIZE);
2174 int y_end = Clamp(y_start + y_size, 0, Map::SizeY() * TILE_SIZE - TILE_SIZE);
2175
2176 x_start = Clamp(x_start, 0, Map::SizeX() * TILE_SIZE - TILE_SIZE);
2177 y_start = Clamp(y_start, 0, Map::SizeY() * TILE_SIZE - TILE_SIZE);
2178
2179 /* make sure everything is multiple of TILE_SIZE */
2180 assert((x_end | y_end | x_start | y_start) % TILE_SIZE == 0);
2181
2182 /* How it works:
2183 * Suppose we have to mark dirty rectangle of 3x4 tiles:
2184 * x
2185 * xxx
2186 * xxxxx
2187 * xxxxx
2188 * xxx
2189 * x
2190 * This algorithm marks dirty columns of tiles, so it is done in 3+4-1 steps:
2191 * 1) x 2) x
2192 * xxx Oxx
2193 * Oxxxx xOxxx
2194 * xxxxx Oxxxx
2195 * xxx xxx
2196 * x x
2197 * And so forth...
2198 */
2199
2200 int top_x = x_end; // coordinates of top dirty tile
2201 int top_y = y_start;
2202 int bot_x = top_x; // coordinates of bottom dirty tile
2203 int bot_y = top_y;
2204
2205 do {
2206 /* topmost dirty point */
2207 TileIndex top_tile = TileVirtXY(top_x, top_y);
2208 Point top = RemapCoords(top_x, top_y, GetTileMaxPixelZ(top_tile));
2209
2210 /* bottommost point */
2211 TileIndex bottom_tile = TileVirtXY(bot_x, bot_y);
2212 Point bot = RemapCoords(bot_x + TILE_SIZE, bot_y + TILE_SIZE, GetTilePixelZ(bottom_tile)); // bottommost point
2213
2214 /* the 'x' coordinate of 'top' and 'bot' is the same (and always in the same distance from tile middle),
2215 * tile height/slope affects only the 'y' on-screen coordinate! */
2216
2217 int l = top.x - TILE_PIXELS * ZOOM_BASE; // 'x' coordinate of left side of the dirty rectangle
2218 int t = top.y; // 'y' coordinate of top side of the dirty rectangle
2219 int r = top.x + TILE_PIXELS * ZOOM_BASE; // 'x' coordinate of right side of the dirty rectangle
2220 int b = bot.y; // 'y' coordinate of bottom side of the dirty rectangle
2221
2222 static const int OVERLAY_WIDTH = 4 * ZOOM_BASE; // part of selection sprites is drawn outside the selected area (in particular: terraforming)
2223
2224 /* For halftile foundations on SLOPE_STEEP_S the sprite extents some more towards the top */
2225 MarkAllViewportsDirty(l - OVERLAY_WIDTH, t - OVERLAY_WIDTH - TILE_HEIGHT * ZOOM_BASE, r + OVERLAY_WIDTH, b + OVERLAY_WIDTH);
2226
2227 /* haven't we reached the topmost tile yet? */
2228 if (top_x != x_start) {
2229 top_x -= TILE_SIZE;
2230 } else {
2231 top_y += TILE_SIZE;
2232 }
2233
2234 /* the way the bottom tile changes is different when we reach the bottommost tile */
2235 if (bot_y != y_end) {
2236 bot_y += TILE_SIZE;
2237 } else {
2238 bot_x -= TILE_SIZE;
2239 }
2240 } while (bot_x >= top_x);
2241 } else { // Selecting in a 45 degrees rotated (diagonal) rectangle.
2242 /* a_size, b_size describe a rectangle with rotated coordinates */
2243 int a_size = x_size + y_size, b_size = x_size - y_size;
2244
2245 int interval_a = a_size < 0 ? -(int)TILE_SIZE : (int)TILE_SIZE;
2246 int interval_b = b_size < 0 ? -(int)TILE_SIZE : (int)TILE_SIZE;
2247
2248 for (int a = -interval_a; a != a_size + interval_a; a += interval_a) {
2249 for (int b = -interval_b; b != b_size + interval_b; b += interval_b) {
2250 uint x = (_thd.pos.x + (a + b) / 2) / TILE_SIZE;
2251 uint y = (_thd.pos.y + (a - b) / 2) / TILE_SIZE;
2252
2253 if (x < Map::MaxX() && y < Map::MaxY()) {
2255 }
2256 }
2257 }
2258 }
2259}
2260
2261
2262void SetSelectionRed(bool b)
2263{
2264 _thd.make_square_red = b;
2266}
2267
2276static bool CheckClickOnViewportSign(const Viewport &vp, int x, int y, const ViewportSign *sign)
2277{
2278 bool small = (vp.zoom >= ZoomLevel::Out4x);
2279 int sign_half_width = ScaleByZoom((small ? sign->width_small : sign->width_normal) / 2, vp.zoom);
2280 int sign_height = ScaleByZoom(WidgetDimensions::scaled.fullbevel.top + GetCharacterHeight(small ? FS_SMALL : FS_NORMAL) + WidgetDimensions::scaled.fullbevel.bottom, vp.zoom);
2281
2282 return y >= sign->top && y < sign->top + sign_height &&
2283 x >= sign->center - sign_half_width && x < sign->center + sign_half_width;
2284}
2285
2286
2294static bool CheckClickOnViewportSign(const Viewport &vp, int x, int y)
2295{
2296 if (_game_mode == GM_MENU) return false;
2297
2298 x = ScaleByZoom(x - vp.left, vp.zoom) + vp.virtual_left;
2299 y = ScaleByZoom(y - vp.top, vp.zoom) + vp.virtual_top;
2300
2301 Rect search_rect{ x - 1, y - 1, x + 1, y + 1 };
2302 search_rect = ExpandRectWithViewportSignMargins(search_rect, vp.zoom);
2303
2306 bool show_towns = HasBit(_display_opt, DO_SHOW_TOWN_NAMES);
2308 bool show_competitors = HasBit(_display_opt, DO_SHOW_COMPETITOR_SIGNS);
2309
2310 /* Topmost of each type that was hit */
2311 BaseStation *st = nullptr, *last_st = nullptr;
2312 Town *t = nullptr, *last_t = nullptr;
2313 Sign *si = nullptr, *last_si = nullptr;
2314
2315 /* See ViewportAddKdtreeSigns() for details on the search logic */
2316 _viewport_sign_kdtree.FindContained(search_rect.left, search_rect.top, search_rect.right, search_rect.bottom, [&](const ViewportSignKdtreeItem & item) {
2317 switch (item.type) {
2318 case ViewportSignKdtreeItem::VKI_STATION: {
2319 if (!show_stations) break;
2320 st = BaseStation::Get(std::get<StationID>(item.id));
2321 if (!show_competitors && _local_company != st->owner && st->owner != OWNER_NONE) break;
2322
2323 StationFacilities facilities = st->facilities;
2324 if (facilities.None()) facilities = STATION_FACILITY_GHOST;
2325 if (!facilities.Any(_facility_display_opt)) break;
2326
2327 if (CheckClickOnViewportSign(vp, x, y, &st->sign)) last_st = st;
2328 break;
2329 }
2330
2331 case ViewportSignKdtreeItem::VKI_WAYPOINT:
2332 if (!show_waypoints) break;
2333 st = BaseStation::Get(std::get<StationID>(item.id));
2334 if (!show_competitors && _local_company != st->owner && st->owner != OWNER_NONE) break;
2335 if (CheckClickOnViewportSign(vp, x, y, &st->sign)) last_st = st;
2336 break;
2337
2338 case ViewportSignKdtreeItem::VKI_TOWN:
2339 if (!show_towns) break;
2340 t = Town::Get(std::get<TownID>(item.id));
2341 if (CheckClickOnViewportSign(vp, x, y, &t->cache.sign)) last_t = t;
2342 break;
2343
2344 case ViewportSignKdtreeItem::VKI_SIGN:
2345 if (!show_signs) break;
2346 si = Sign::Get(std::get<SignID>(item.id));
2347 if (!show_competitors && _local_company != si->owner && si->owner != OWNER_DEITY) break;
2348 if (CheckClickOnViewportSign(vp, x, y, &si->sign)) last_si = si;
2349 break;
2350
2351 default:
2352 NOT_REACHED();
2353 }
2354 });
2355
2356 /* Select which hit to handle based on priority */
2357 if (last_st != nullptr) {
2358 if (Station::IsExpected(last_st)) {
2359 ShowStationViewWindow(last_st->index);
2360 } else {
2362 }
2363 return true;
2364 } else if (last_t != nullptr) {
2365 ShowTownViewWindow(last_t->index);
2366 return true;
2367 } else if (last_si != nullptr) {
2368 HandleClickOnSign(last_si);
2369 return true;
2370 } else {
2371 return false;
2372 }
2373}
2374
2375
2376ViewportSignKdtreeItem ViewportSignKdtreeItem::MakeStation(StationID id)
2377{
2379 item.type = VKI_STATION;
2380 item.id = id;
2381
2382 const Station *st = Station::Get(id);
2383 assert(st->sign.kdtree_valid);
2384 item.center = st->sign.center;
2385 item.top = st->sign.top;
2386
2387 /* Assume the sign can be a candidate for drawing, so measure its width */
2388 _viewport_sign_maxwidth = std::max<int>({_viewport_sign_maxwidth, st->sign.width_normal, st->sign.width_small});
2389
2390 return item;
2391}
2392
2393ViewportSignKdtreeItem ViewportSignKdtreeItem::MakeWaypoint(StationID id)
2394{
2396 item.type = VKI_WAYPOINT;
2397 item.id = id;
2398
2399 const Waypoint *st = Waypoint::Get(id);
2400 assert(st->sign.kdtree_valid);
2401 item.center = st->sign.center;
2402 item.top = st->sign.top;
2403
2404 /* Assume the sign can be a candidate for drawing, so measure its width */
2405 _viewport_sign_maxwidth = std::max<int>({_viewport_sign_maxwidth, st->sign.width_normal, st->sign.width_small});
2406
2407 return item;
2408}
2409
2410ViewportSignKdtreeItem ViewportSignKdtreeItem::MakeTown(TownID id)
2411{
2413 item.type = VKI_TOWN;
2414 item.id = id;
2415
2416 const Town *town = Town::Get(id);
2417 assert(town->cache.sign.kdtree_valid);
2418 item.center = town->cache.sign.center;
2419 item.top = town->cache.sign.top;
2420
2421 /* Assume the sign can be a candidate for drawing, so measure its width */
2422 _viewport_sign_maxwidth = std::max<int>({_viewport_sign_maxwidth, town->cache.sign.width_normal, town->cache.sign.width_small});
2423
2424 return item;
2425}
2426
2427ViewportSignKdtreeItem ViewportSignKdtreeItem::MakeSign(SignID id)
2428{
2430 item.type = VKI_SIGN;
2431 item.id = id;
2432
2433 const Sign *sign = Sign::Get(id);
2434 assert(sign->sign.kdtree_valid);
2435 item.center = sign->sign.center;
2436 item.top = sign->sign.top;
2437
2438 /* Assume the sign can be a candidate for drawing, so measure its width */
2439 _viewport_sign_maxwidth = std::max<int>({_viewport_sign_maxwidth, sign->sign.width_normal, sign->sign.width_small});
2440
2441 return item;
2442}
2443
2444void RebuildViewportKdtree()
2445{
2446 /* Reset biggest size sign seen */
2447 _viewport_sign_maxwidth = 0;
2448
2449 std::vector<ViewportSignKdtreeItem> items;
2451
2452 for (const Station *st : Station::Iterate()) {
2453 if (st->sign.kdtree_valid) items.push_back(ViewportSignKdtreeItem::MakeStation(st->index));
2454 }
2455
2456 for (const Waypoint *wp : Waypoint::Iterate()) {
2457 if (wp->sign.kdtree_valid) items.push_back(ViewportSignKdtreeItem::MakeWaypoint(wp->index));
2458 }
2459
2460 for (const Town *town : Town::Iterate()) {
2461 if (town->cache.sign.kdtree_valid) items.push_back(ViewportSignKdtreeItem::MakeTown(town->index));
2462 }
2463
2464 for (const Sign *sign : Sign::Iterate()) {
2465 if (sign->sign.kdtree_valid) items.push_back(ViewportSignKdtreeItem::MakeSign(sign->index));
2466 }
2467
2468 _viewport_sign_kdtree.Build(items.begin(), items.end());
2469}
2470
2471
2472static bool CheckClickOnLandscape(const Viewport &vp, int x, int y)
2473{
2474 Point pt = TranslateXYToTileCoord(vp, x, y);
2475
2476 if (pt.x != -1) return ClickTile(TileVirtXY(pt.x, pt.y));
2477 return true;
2478}
2479
2480static void PlaceObject()
2481{
2482 Point pt;
2483 Window *w;
2484
2485 pt = GetTileBelowCursor();
2486 if (pt.x == -1) return;
2487
2488 if ((_thd.place_mode & HT_DRAG_MASK) == HT_POINT) {
2489 pt.x += TILE_SIZE / 2;
2490 pt.y += TILE_SIZE / 2;
2491 }
2492
2493 _tile_fract_coords.x = pt.x & TILE_UNIT_MASK;
2494 _tile_fract_coords.y = pt.y & TILE_UNIT_MASK;
2495
2496 w = _thd.GetCallbackWnd();
2497 if (w != nullptr) w->OnPlaceObject(pt, TileVirtXY(pt.x, pt.y));
2498}
2499
2500
2501bool HandleViewportClicked(const Viewport &vp, int x, int y)
2502{
2503 const Vehicle *v = CheckClickOnVehicle(vp, x, y);
2504
2505 if (_thd.place_mode & HT_VEHICLE) {
2506 if (v != nullptr && VehicleClicked(v)) return true;
2507 }
2508
2509 /* Vehicle placement mode already handled above. */
2510 if ((_thd.place_mode & HT_DRAG_MASK) != HT_NONE) {
2511 PlaceObject();
2512 return true;
2513 }
2514
2515 if (CheckClickOnViewportSign(vp, x, y)) return true;
2516 bool result = CheckClickOnLandscape(vp, x, y);
2517
2518 if (v != nullptr) {
2519 Debug(misc, 2, "Vehicle {} (index {}) at {}", v->unitnumber, v->index, fmt::ptr(v));
2521 v = v->First();
2522 if (_ctrl_pressed && v->owner == _local_company) {
2523 StartStopVehicle(v, true);
2524 } else {
2526 }
2527 }
2528 return true;
2529 }
2530 return result;
2531}
2532
2533void RebuildViewportOverlay(Window *w)
2534{
2535 if (w->viewport->overlay != nullptr &&
2536 w->viewport->overlay->GetCompanyMask().Any() &&
2537 w->viewport->overlay->GetCargoMask() != 0) {
2538 w->viewport->overlay->SetDirty();
2539 w->SetDirty();
2540 }
2541}
2542
2552bool ScrollWindowTo(int x, int y, int z, Window *w, bool instant)
2553{
2554 /* The slope cannot be acquired outside of the map, so make sure we are always within the map. */
2555 if (z == -1) {
2556 if ( x >= 0 && x <= (int)Map::SizeX() * (int)TILE_SIZE - 1
2557 && y >= 0 && y <= (int)Map::SizeY() * (int)TILE_SIZE - 1) {
2558 z = GetSlopePixelZ(x, y);
2559 } else {
2560 z = TileHeightOutsideMap(x / (int)TILE_SIZE, y / (int)TILE_SIZE);
2561 }
2562 }
2563
2564 Point pt = MapXYZToViewport(*w->viewport, x, y, z);
2565 w->viewport->CancelFollow(*w);
2566
2567 if (w->viewport->dest_scrollpos_x == pt.x && w->viewport->dest_scrollpos_y == pt.y) return false;
2568
2569 if (instant) {
2570 w->viewport->scrollpos_x = pt.x;
2571 w->viewport->scrollpos_y = pt.y;
2572 RebuildViewportOverlay(w);
2573 }
2574
2575 w->viewport->dest_scrollpos_x = pt.x;
2576 w->viewport->dest_scrollpos_y = pt.y;
2577 return true;
2578}
2579
2587bool ScrollWindowToTile(TileIndex tile, Window *w, bool instant)
2588{
2589 return ScrollWindowTo(TileX(tile) * TILE_SIZE, TileY(tile) * TILE_SIZE, -1, w, instant);
2590}
2591
2598bool ScrollMainWindowToTile(TileIndex tile, bool instant)
2599{
2600 return ScrollMainWindowTo(TileX(tile) * TILE_SIZE + TILE_SIZE / 2, TileY(tile) * TILE_SIZE + TILE_SIZE / 2, -1, instant);
2601}
2602
2608{
2609 TileIndex old;
2610
2611 old = _thd.redsq;
2612 _thd.redsq = tile;
2613
2614 if (tile != old) {
2615 if (tile != INVALID_TILE) MarkTileDirtyByTile(tile);
2616 if (old != INVALID_TILE) MarkTileDirtyByTile(old);
2617 }
2618}
2619
2625void SetTileSelectSize(int w, int h)
2626{
2627 _thd.new_size.x = w * TILE_SIZE;
2628 _thd.new_size.y = h * TILE_SIZE;
2629 _thd.new_outersize.x = 0;
2630 _thd.new_outersize.y = 0;
2631}
2632
2633void SetTileSelectBigSize(int ox, int oy, int sx, int sy)
2634{
2635 _thd.offs.x = ox * TILE_SIZE;
2636 _thd.offs.y = oy * TILE_SIZE;
2637 _thd.new_outersize.x = sx * TILE_SIZE;
2638 _thd.new_outersize.y = sy * TILE_SIZE;
2639}
2640
2642static HighLightStyle GetAutorailHT(int x, int y)
2643{
2644 return HT_RAIL | _autorail_piece[x & TILE_UNIT_MASK][y & TILE_UNIT_MASK];
2645}
2646
2651{
2652 this->pos.x = 0;
2653 this->pos.y = 0;
2654 this->new_pos.x = 0;
2655 this->new_pos.y = 0;
2656}
2657
2666
2675
2676
2677
2686{
2687 int x1;
2688 int y1;
2689
2690 if (_thd.freeze) return;
2691
2692 HighLightStyle new_drawstyle = HT_NONE;
2693 bool new_diagonal = false;
2694
2695 if ((_thd.place_mode & HT_DRAG_MASK) == HT_SPECIAL) {
2696 x1 = _thd.selend.x;
2697 y1 = _thd.selend.y;
2698 if (x1 != -1) {
2699 int x2 = _thd.selstart.x & ~TILE_UNIT_MASK;
2700 int y2 = _thd.selstart.y & ~TILE_UNIT_MASK;
2701 x1 &= ~TILE_UNIT_MASK;
2702 y1 &= ~TILE_UNIT_MASK;
2703
2704 if (_thd.IsDraggingDiagonal()) {
2705 new_diagonal = true;
2706 } else {
2707 if (x1 >= x2) std::swap(x1, x2);
2708 if (y1 >= y2) std::swap(y1, y2);
2709 }
2710 _thd.new_pos.x = x1;
2711 _thd.new_pos.y = y1;
2712 _thd.new_size.x = x2 - x1;
2713 _thd.new_size.y = y2 - y1;
2714 if (!new_diagonal) {
2715 _thd.new_size.x += TILE_SIZE;
2716 _thd.new_size.y += TILE_SIZE;
2717 }
2718 new_drawstyle = _thd.next_drawstyle;
2719 }
2720 } else if ((_thd.place_mode & HT_DRAG_MASK) != HT_NONE) {
2721 Point pt = GetTileBelowCursor();
2722 x1 = pt.x;
2723 y1 = pt.y;
2724 if (x1 != -1) {
2725 switch (_thd.place_mode & HT_DRAG_MASK) {
2726 case HT_RECT:
2727 new_drawstyle = HT_RECT;
2728 break;
2729 case HT_POINT:
2730 new_drawstyle = HT_POINT;
2731 x1 += TILE_SIZE / 2;
2732 y1 += TILE_SIZE / 2;
2733 break;
2734 case HT_RAIL:
2735 /* Draw one highlighted tile in any direction */
2736 new_drawstyle = GetAutorailHT(pt.x, pt.y);
2737 break;
2738 case HT_LINE:
2739 switch (_thd.place_mode & HT_DIR_MASK) {
2740 case HT_DIR_X: new_drawstyle = HT_LINE | HT_DIR_X; break;
2741 case HT_DIR_Y: new_drawstyle = HT_LINE | HT_DIR_Y; break;
2742
2743 case HT_DIR_HU:
2744 case HT_DIR_HL:
2745 new_drawstyle = (pt.x & TILE_UNIT_MASK) + (pt.y & TILE_UNIT_MASK) <= TILE_SIZE ? HT_LINE | HT_DIR_HU : HT_LINE | HT_DIR_HL;
2746 break;
2747
2748 case HT_DIR_VL:
2749 case HT_DIR_VR:
2750 new_drawstyle = (pt.x & TILE_UNIT_MASK) > (pt.y & TILE_UNIT_MASK) ? HT_LINE | HT_DIR_VL : HT_LINE | HT_DIR_VR;
2751 break;
2752
2753 default: NOT_REACHED();
2754 }
2755 _thd.selstart.x = x1 & ~TILE_UNIT_MASK;
2756 _thd.selstart.y = y1 & ~TILE_UNIT_MASK;
2757 break;
2758 default:
2759 NOT_REACHED();
2760 }
2761 _thd.new_pos.x = x1 & ~TILE_UNIT_MASK;
2762 _thd.new_pos.y = y1 & ~TILE_UNIT_MASK;
2763 }
2764 }
2765
2766 /* redraw selection */
2767 if (_thd.drawstyle != new_drawstyle ||
2768 _thd.pos.x != _thd.new_pos.x || _thd.pos.y != _thd.new_pos.y ||
2769 _thd.size.x != _thd.new_size.x || _thd.size.y != _thd.new_size.y ||
2770 _thd.outersize.x != _thd.new_outersize.x ||
2771 _thd.outersize.y != _thd.new_outersize.y ||
2772 _thd.diagonal != new_diagonal) {
2773 /* Clear the old tile selection? */
2775
2776 _thd.drawstyle = new_drawstyle;
2777 _thd.pos = _thd.new_pos;
2778 _thd.size = _thd.new_size;
2779 _thd.outersize = _thd.new_outersize;
2780 _thd.diagonal = new_diagonal;
2781 _thd.dirty = 0xff;
2782
2783 /* Draw the new tile selection? */
2784 if ((new_drawstyle & HT_DRAG_MASK) != HT_NONE) SetSelectionTilesDirty();
2785 }
2786}
2787
2792static inline void ShowMeasurementTooltips(EncodedString &&text)
2793{
2795 GuiShowTooltips(_thd.GetCallbackWnd(), std::move(text), TCC_EXIT_VIEWPORT);
2796}
2797
2798static void HideMeasurementTooltips()
2799{
2801}
2802
2805{
2806 _thd.select_method = method;
2807 _thd.select_proc = process;
2808 _thd.selend.x = TileX(tile) * TILE_SIZE;
2809 _thd.selstart.x = TileX(tile) * TILE_SIZE;
2810 _thd.selend.y = TileY(tile) * TILE_SIZE;
2811 _thd.selstart.y = TileY(tile) * TILE_SIZE;
2812
2813 /* Needed so several things (road, autoroad, bridges, ...) are placed correctly.
2814 * In effect, placement starts from the centre of a tile
2815 */
2816 if (method == VPM_X_OR_Y || method == VPM_FIX_X || method == VPM_FIX_Y) {
2817 _thd.selend.x += TILE_SIZE / 2;
2818 _thd.selend.y += TILE_SIZE / 2;
2819 _thd.selstart.x += TILE_SIZE / 2;
2820 _thd.selstart.y += TILE_SIZE / 2;
2821 }
2822
2824 if ((_thd.place_mode & HT_DRAG_MASK) == HT_RECT) {
2825 _thd.place_mode = HT_SPECIAL | others;
2826 _thd.next_drawstyle = HT_RECT | others;
2827 } else if (_thd.place_mode & (HT_RAIL | HT_LINE)) {
2828 _thd.place_mode = HT_SPECIAL | others;
2829 _thd.next_drawstyle = _thd.drawstyle | others;
2830 } else {
2831 _thd.place_mode = HT_SPECIAL | others;
2832 _thd.next_drawstyle = HT_POINT | others;
2833 }
2835}
2836
2839{
2841 _thd.select_proc = process;
2842 _thd.selstart.x = 0;
2843 _thd.selstart.y = 0;
2844 _thd.next_drawstyle = HT_RECT;
2845
2847}
2848
2849void VpSetPlaceSizingLimit(int limit)
2850{
2851 _thd.sizelimit = limit;
2852}
2853
2860{
2861 uint64_t distance = DistanceManhattan(from, to) + 1;
2862
2863 _thd.selend.x = TileX(to) * TILE_SIZE;
2864 _thd.selend.y = TileY(to) * TILE_SIZE;
2865 _thd.selstart.x = TileX(from) * TILE_SIZE;
2866 _thd.selstart.y = TileY(from) * TILE_SIZE;
2867 _thd.next_drawstyle = HT_RECT;
2868
2869 /* show measurement only if there is any length to speak of */
2870 if (distance > 1) {
2871 ShowMeasurementTooltips(GetEncodedString(STR_MEASURE_LENGTH, distance));
2872 } else {
2873 HideMeasurementTooltips();
2874 }
2875}
2876
2877static void VpStartPreSizing()
2878{
2879 _thd.selend.x = -1;
2881}
2882
2888{
2889 int fxpy = _tile_fract_coords.x + _tile_fract_coords.y;
2890 int sxpy = (_thd.selend.x & TILE_UNIT_MASK) + (_thd.selend.y & TILE_UNIT_MASK);
2891 int fxmy = _tile_fract_coords.x - _tile_fract_coords.y;
2892 int sxmy = (_thd.selend.x & TILE_UNIT_MASK) - (_thd.selend.y & TILE_UNIT_MASK);
2893
2894 switch (mode) {
2895 default: NOT_REACHED();
2896 case 0: // end piece is lower right
2897 if (fxpy >= 20 && sxpy <= 12) return HT_DIR_HL;
2898 if (fxmy < -3 && sxmy > 3) return HT_DIR_VR;
2899 return HT_DIR_Y;
2900
2901 case 1:
2902 if (fxmy > 3 && sxmy < -3) return HT_DIR_VL;
2903 if (fxpy <= 12 && sxpy >= 20) return HT_DIR_HU;
2904 return HT_DIR_Y;
2905
2906 case 2:
2907 if (fxmy > 3 && sxmy < -3) return HT_DIR_VL;
2908 if (fxpy >= 20 && sxpy <= 12) return HT_DIR_HL;
2909 return HT_DIR_X;
2910
2911 case 3:
2912 if (fxmy < -3 && sxmy > 3) return HT_DIR_VR;
2913 if (fxpy <= 12 && sxpy >= 20) return HT_DIR_HU;
2914 return HT_DIR_X;
2915 }
2916}
2917
2931static bool SwapDirection(HighLightStyle style, TileIndex start_tile, TileIndex end_tile)
2932{
2933 uint start_x = TileX(start_tile);
2934 uint start_y = TileY(start_tile);
2935 uint end_x = TileX(end_tile);
2936 uint end_y = TileY(end_tile);
2937
2938 switch (style & HT_DRAG_MASK) {
2939 case HT_RAIL:
2940 case HT_LINE: return (end_x > start_x || (end_x == start_x && end_y > start_y));
2941
2942 case HT_RECT:
2943 case HT_POINT: return (end_x != start_x && end_y < start_y);
2944 default: NOT_REACHED();
2945 }
2946
2947 return false;
2948}
2949
2965static int CalcHeightdiff(HighLightStyle style, uint distance, TileIndex start_tile, TileIndex end_tile)
2966{
2967 bool swap = SwapDirection(style, start_tile, end_tile);
2968 uint h0, h1; // Start height and end height.
2969
2970 if (start_tile == end_tile) return 0;
2971 if (swap) std::swap(start_tile, end_tile);
2972
2973 switch (style & HT_DRAG_MASK) {
2974 case HT_RECT:
2975 /* In the case of an area we can determine whether we were dragging south or
2976 * east by checking the X-coordinates of the tiles */
2977 if (TileX(end_tile) > TileX(start_tile)) {
2978 /* Dragging south does not need to change the start tile. */
2979 end_tile = TileAddByDir(end_tile, DIR_S);
2980 } else {
2981 /* Dragging east. */
2982 start_tile = TileAddByDir(start_tile, DIR_SW);
2983 end_tile = TileAddByDir(end_tile, DIR_SE);
2984 }
2985 [[fallthrough]];
2986
2987 case HT_POINT:
2988 h0 = TileHeight(start_tile);
2989 h1 = TileHeight(end_tile);
2990 break;
2991 default: { // All other types, this is mostly only line/autorail
2992 static const HighLightStyle flip_style_direction[] = {
2994 };
2995 static const std::pair<TileIndexDiffC, TileIndexDiffC> start_heightdiff_line_by_dir[] = {
2996 { {1, 0}, {1, 1} }, // HT_DIR_X
2997 { {0, 1}, {1, 1} }, // HT_DIR_Y
2998 { {1, 0}, {0, 0} }, // HT_DIR_HU
2999 { {1, 0}, {1, 1} }, // HT_DIR_HL
3000 { {1, 0}, {1, 1} }, // HT_DIR_VL
3001 { {0, 1}, {1, 1} }, // HT_DIR_VR
3002 };
3003 static const std::pair<TileIndexDiffC, TileIndexDiffC> end_heightdiff_line_by_dir[] = {
3004 { {0, 1}, {0, 0} }, // HT_DIR_X
3005 { {1, 0}, {0, 0} }, // HT_DIR_Y
3006 { {0, 1}, {0, 0} }, // HT_DIR_HU
3007 { {1, 1}, {0, 1} }, // HT_DIR_HL
3008 { {1, 0}, {0, 0} }, // HT_DIR_VL
3009 { {0, 0}, {0, 1} }, // HT_DIR_VR
3010 };
3011 static_assert(std::size(start_heightdiff_line_by_dir) == HT_DIR_END);
3012 static_assert(std::size(end_heightdiff_line_by_dir) == HT_DIR_END);
3013
3014 distance %= 2; // we're only interested if the distance is even or uneven
3015 style &= HT_DIR_MASK;
3016 assert(style < HT_DIR_END);
3017
3018 /* To handle autorail, we do some magic to be able to use a lookup table.
3019 * Firstly if we drag the other way around, we switch start&end, and if needed
3020 * also flip the drag-position. Eg if it was on the left, and the distance is even
3021 * that means the end, which is now the start is on the right */
3022 if (swap && distance == 0) style = flip_style_direction[style];
3023
3024 /* Lambda to help calculating the height at one side of the line. */
3025 auto get_height = [](auto &tile, auto &heightdiffs) {
3026 return std::max(
3027 TileHeight(TileAdd(tile, ToTileIndexDiff(heightdiffs.first))),
3028 TileHeight(TileAdd(tile, ToTileIndexDiff(heightdiffs.second))));
3029 };
3030
3031 /* Use lookup table for start-tile based on HighLightStyle direction */
3032 h0 = get_height(start_tile, start_heightdiff_line_by_dir[style]);
3033
3034 /* Use lookup table for end-tile based on HighLightStyle direction
3035 * flip around side (lower/upper, left/right) based on distance */
3036 if (distance == 0) style = flip_style_direction[style];
3037 h1 = get_height(end_tile, end_heightdiff_line_by_dir[style]);
3038 break;
3039 }
3040 }
3041
3042 if (swap) std::swap(h0, h1);
3043 return (int)(h1 - h0) * TILE_HEIGHT_STEP;
3044}
3045
3052static void CheckUnderflow(int &test, int &other, int mult)
3053{
3054 if (test >= 0) return;
3055
3056 other += mult * test;
3057 test = 0;
3058}
3059
3067static void CheckOverflow(int &test, int &other, int max, int mult)
3068{
3069 if (test <= max) return;
3070
3071 other += mult * (test - max);
3072 test = max;
3073}
3074
3076static void CalcRaildirsDrawstyle(int x, int y, int method)
3077{
3079
3080 int dx = _thd.selstart.x - (_thd.selend.x & ~TILE_UNIT_MASK);
3081 int dy = _thd.selstart.y - (_thd.selend.y & ~TILE_UNIT_MASK);
3082 uint w = abs(dx) + TILE_SIZE;
3083 uint h = abs(dy) + TILE_SIZE;
3084
3085 if (method & ~(VPM_RAILDIRS | VPM_SIGNALDIRS)) {
3086 /* We 'force' a selection direction; first four rail buttons. */
3087 method &= ~(VPM_RAILDIRS | VPM_SIGNALDIRS);
3088 int raw_dx = _thd.selstart.x - _thd.selend.x;
3089 int raw_dy = _thd.selstart.y - _thd.selend.y;
3090 switch (method) {
3091 case VPM_FIX_X:
3092 b = HT_LINE | HT_DIR_Y;
3093 x = _thd.selstart.x;
3094 break;
3095
3096 case VPM_FIX_Y:
3097 b = HT_LINE | HT_DIR_X;
3098 y = _thd.selstart.y;
3099 break;
3100
3101 case VPM_FIX_HORIZONTAL:
3102 if (dx == -dy) {
3103 /* We are on a straight horizontal line. Determine the 'rail'
3104 * to build based the sub tile location. */
3106 } else {
3107 /* We are not on a straight line. Determine the rail to build
3108 * based on whether we are above or below it. */
3109 b = dx + dy >= (int)TILE_SIZE ? HT_LINE | HT_DIR_HU : HT_LINE | HT_DIR_HL;
3110
3111 /* Calculate where a horizontal line through the start point and
3112 * a vertical line from the selected end point intersect and
3113 * use that point as the end point. */
3114 int offset = (raw_dx - raw_dy) / 2;
3115 x = _thd.selstart.x - (offset & ~TILE_UNIT_MASK);
3116 y = _thd.selstart.y + (offset & ~TILE_UNIT_MASK);
3117
3118 /* 'Build' the last half rail tile if needed */
3119 if ((offset & TILE_UNIT_MASK) > (TILE_SIZE / 2)) {
3120 if (dx + dy >= (int)TILE_SIZE) {
3121 x -= (int)TILE_SIZE;
3122 } else {
3123 y += (int)TILE_SIZE;
3124 }
3125 }
3126
3127 /* Make sure we do not overflow the map! */
3128 CheckUnderflow(x, y, 1);
3129 CheckUnderflow(y, x, 1);
3130 CheckOverflow(x, y, (Map::MaxX() - 1) * TILE_SIZE, 1);
3131 CheckOverflow(y, x, (Map::MaxY() - 1) * TILE_SIZE, 1);
3132 assert(x >= 0 && y >= 0 && x <= (int)(Map::MaxX() * TILE_SIZE) && y <= (int)(Map::MaxY() * TILE_SIZE));
3133 }
3134 break;
3135
3136 case VPM_FIX_VERTICAL:
3137 if (dx == dy) {
3138 /* We are on a straight vertical line. Determine the 'rail'
3139 * to build based the sub tile location. */
3141 } else {
3142 /* We are not on a straight line. Determine the rail to build
3143 * based on whether we are left or right from it. */
3144 b = dx < dy ? HT_LINE | HT_DIR_VL : HT_LINE | HT_DIR_VR;
3145
3146 /* Calculate where a vertical line through the start point and
3147 * a horizontal line from the selected end point intersect and
3148 * use that point as the end point. */
3149 int offset = (raw_dx + raw_dy + (int)TILE_SIZE) / 2;
3150 x = _thd.selstart.x - (offset & ~TILE_UNIT_MASK);
3151 y = _thd.selstart.y - (offset & ~TILE_UNIT_MASK);
3152
3153 /* 'Build' the last half rail tile if needed */
3154 if ((offset & TILE_UNIT_MASK) > (TILE_SIZE / 2)) {
3155 if (dx < dy) {
3156 y -= (int)TILE_SIZE;
3157 } else {
3158 x -= (int)TILE_SIZE;
3159 }
3160 }
3161
3162 /* Make sure we do not overflow the map! */
3163 CheckUnderflow(x, y, -1);
3164 CheckUnderflow(y, x, -1);
3165 CheckOverflow(x, y, (Map::MaxX() - 1) * TILE_SIZE, -1);
3166 CheckOverflow(y, x, (Map::MaxY() - 1) * TILE_SIZE, -1);
3167 assert(x >= 0 && y >= 0 && x <= (int)(Map::MaxX() * TILE_SIZE) && y <= (int)(Map::MaxY() * TILE_SIZE));
3168 }
3169 break;
3170
3171 default:
3172 NOT_REACHED();
3173 }
3174 } else if (TileVirtXY(_thd.selstart.x, _thd.selstart.y) == TileVirtXY(x, y)) { // check if we're only within one tile
3175 if (method & VPM_RAILDIRS) {
3176 b = GetAutorailHT(x, y);
3177 } else { // rect for autosignals on one tile
3178 b = HT_RECT;
3179 }
3180 } else if (h == TILE_SIZE) { // Is this in X direction?
3181 if (dx == (int)TILE_SIZE) { // 2x1 special handling
3182 b = (Check2x1AutoRail(3)) | HT_LINE;
3183 } else if (dx == -(int)TILE_SIZE) {
3184 b = (Check2x1AutoRail(2)) | HT_LINE;
3185 } else {
3186 b = HT_LINE | HT_DIR_X;
3187 }
3188 y = _thd.selstart.y;
3189 } else if (w == TILE_SIZE) { // Or Y direction?
3190 if (dy == (int)TILE_SIZE) { // 2x1 special handling
3191 b = (Check2x1AutoRail(1)) | HT_LINE;
3192 } else if (dy == -(int)TILE_SIZE) { // 2x1 other direction
3193 b = (Check2x1AutoRail(0)) | HT_LINE;
3194 } else {
3195 b = HT_LINE | HT_DIR_Y;
3196 }
3197 x = _thd.selstart.x;
3198 } else if (w > h * 2) { // still count as x dir?
3199 b = HT_LINE | HT_DIR_X;
3200 y = _thd.selstart.y;
3201 } else if (h > w * 2) { // still count as y dir?
3202 b = HT_LINE | HT_DIR_Y;
3203 x = _thd.selstart.x;
3204 } else { // complicated direction
3205 int d = w - h;
3206 _thd.selend.x = _thd.selend.x & ~TILE_UNIT_MASK;
3207 _thd.selend.y = _thd.selend.y & ~TILE_UNIT_MASK;
3208
3209 /* four cases. */
3210 if (x > _thd.selstart.x) {
3211 if (y > _thd.selstart.y) {
3212 /* south */
3213 if (d == 0) {
3215 } else if (d >= 0) {
3216 x = _thd.selstart.x + h;
3217 b = HT_LINE | HT_DIR_VL;
3218 } else {
3219 y = _thd.selstart.y + w;
3220 b = HT_LINE | HT_DIR_VR;
3221 }
3222 } else {
3223 /* west */
3224 if (d == 0) {
3226 } else if (d >= 0) {
3227 x = _thd.selstart.x + h;
3228 b = HT_LINE | HT_DIR_HL;
3229 } else {
3230 y = _thd.selstart.y - w;
3231 b = HT_LINE | HT_DIR_HU;
3232 }
3233 }
3234 } else {
3235 if (y > _thd.selstart.y) {
3236 /* east */
3237 if (d == 0) {
3239 } else if (d >= 0) {
3240 x = _thd.selstart.x - h;
3241 b = HT_LINE | HT_DIR_HU;
3242 } else {
3243 y = _thd.selstart.y + w;
3244 b = HT_LINE | HT_DIR_HL;
3245 }
3246 } else {
3247 /* north */
3248 if (d == 0) {
3250 } else if (d >= 0) {
3251 x = _thd.selstart.x - h;
3252 b = HT_LINE | HT_DIR_VR;
3253 } else {
3254 y = _thd.selstart.y - w;
3255 b = HT_LINE | HT_DIR_VL;
3256 }
3257 }
3258 }
3259 }
3260
3262 TileIndex t0 = TileVirtXY(_thd.selstart.x, _thd.selstart.y);
3263 TileIndex t1 = TileVirtXY(x, y);
3264 uint distance = DistanceManhattan(t0, t1) + 1;
3265
3266 if (distance == 1) {
3267 HideMeasurementTooltips();
3268 } else {
3269 int heightdiff = CalcHeightdiff(b, distance, t0, t1);
3270 /* If we are showing a tooltip for horizontal or vertical drags,
3271 * 2 tiles have a length of 1. To bias towards the ceiling we add
3272 * one before division. It feels more natural to count 3 lengths as 2 */
3273 if ((b & HT_DIR_MASK) != HT_DIR_X && (b & HT_DIR_MASK) != HT_DIR_Y) {
3274 distance = CeilDiv(distance, 2);
3275 }
3276
3277 if (heightdiff == 0) {
3278 ShowMeasurementTooltips(GetEncodedString(STR_MEASURE_LENGTH, distance));
3279 } else {
3280 ShowMeasurementTooltips(GetEncodedString(STR_MEASURE_LENGTH_HEIGHTDIFF, distance, heightdiff));
3281 }
3282 }
3283 }
3284
3285 _thd.selend.x = x;
3286 _thd.selend.y = y;
3287 _thd.next_drawstyle = b;
3288}
3289
3298{
3299 int sx, sy;
3300 HighLightStyle style;
3301
3302 if (x == -1) {
3303 _thd.selend.x = -1;
3304 return;
3305 }
3306
3307 /* Special handling of drag in any (8-way) direction */
3308 if (method & (VPM_RAILDIRS | VPM_SIGNALDIRS)) {
3309 _thd.selend.x = x;
3310 _thd.selend.y = y;
3311 CalcRaildirsDrawstyle(x, y, method);
3312 return;
3313 }
3314
3315 /* Needed so level-land is placed correctly */
3316 if ((_thd.next_drawstyle & HT_DRAG_MASK) == HT_POINT) {
3317 x += TILE_SIZE / 2;
3318 y += TILE_SIZE / 2;
3319 }
3320
3321 sx = _thd.selstart.x;
3322 sy = _thd.selstart.y;
3323
3324 int limit = 0;
3325
3326 switch (method) {
3327 case VPM_X_OR_Y: // drag in X or Y direction
3328 if (abs(sy - y) < abs(sx - x)) {
3329 y = sy;
3330 style = HT_DIR_X;
3331 } else {
3332 x = sx;
3333 style = HT_DIR_Y;
3334 }
3335 goto calc_heightdiff_single_direction;
3336
3337 case VPM_X_LIMITED: // Drag in X direction (limited size).
3338 limit = (_thd.sizelimit - 1) * TILE_SIZE;
3339 [[fallthrough]];
3340
3341 case VPM_FIX_X: // drag in Y direction
3342 x = sx;
3343 style = HT_DIR_Y;
3344 goto calc_heightdiff_single_direction;
3345
3346 case VPM_Y_LIMITED: // Drag in Y direction (limited size).
3347 limit = (_thd.sizelimit - 1) * TILE_SIZE;
3348 [[fallthrough]];
3349
3350 case VPM_FIX_Y: // drag in X direction
3351 y = sy;
3352 style = HT_DIR_X;
3353
3354calc_heightdiff_single_direction:;
3355 if (limit > 0) {
3356 x = sx + Clamp(x - sx, -limit, limit);
3357 y = sy + Clamp(y - sy, -limit, limit);
3358 }
3360 TileIndex t0 = TileVirtXY(sx, sy);
3361 TileIndex t1 = TileVirtXY(x, y);
3362 uint distance = DistanceManhattan(t0, t1) + 1;
3363
3364 if (distance == 1) {
3365 HideMeasurementTooltips();
3366 } else {
3367 /* With current code passing a HT_LINE style to calculate the height
3368 * difference is enough. However if/when a point-tool is created
3369 * with this method, function should be called with new_style (below)
3370 * instead of HT_LINE | style case HT_POINT is handled specially
3371 * new_style := (_thd.next_drawstyle & HT_RECT) ? HT_LINE | style : _thd.next_drawstyle; */
3372 int heightdiff = CalcHeightdiff(HT_LINE | style, 0, t0, t1);
3373
3374 if (heightdiff == 0) {
3375 ShowMeasurementTooltips(GetEncodedString(STR_MEASURE_LENGTH, distance));
3376 } else {
3377 ShowMeasurementTooltips(GetEncodedString(STR_MEASURE_LENGTH_HEIGHTDIFF, distance, heightdiff));
3378 }
3379 }
3380 }
3381 break;
3382
3383 case VPM_X_AND_Y_LIMITED: // Drag an X by Y constrained rect area.
3384 limit = (_thd.sizelimit - 1) * TILE_SIZE;
3385 x = sx + Clamp(x - sx, -limit, limit);
3386 y = sy + Clamp(y - sy, -limit, limit);
3387 [[fallthrough]];
3388
3389 case VPM_X_AND_Y: // drag an X by Y area
3391 TileIndex t0 = TileVirtXY(sx, sy);
3392 TileIndex t1 = TileVirtXY(x, y);
3393 uint dx = Delta(TileX(t0), TileX(t1)) + 1;
3394 uint dy = Delta(TileY(t0), TileY(t1)) + 1;
3395
3396 /* If dragging an area (eg dynamite tool) and it is actually a single
3397 * row/column, change the type to 'line' to get proper calculation for height */
3398 style = (HighLightStyle)_thd.next_drawstyle;
3399 if (_thd.IsDraggingDiagonal()) {
3400 /* Determine the "area" of the diagonal dragged selection.
3401 * We assume the area is the number of tiles along the X
3402 * edge and the number of tiles along the Y edge. However,
3403 * multiplying these two numbers does not give the exact
3404 * number of tiles; basically we are counting the black
3405 * squares on a chess board and ignore the white ones to
3406 * make the tile counts at the edges match up. There is no
3407 * other way to make a proper count though.
3408 *
3409 * First convert to the rotated coordinate system. */
3410 int dist_x = TileX(t0) - TileX(t1);
3411 int dist_y = TileY(t0) - TileY(t1);
3412 int a_max = dist_x + dist_y;
3413 int b_max = dist_y - dist_x;
3414
3415 /* Now determine the size along the edge, but due to the
3416 * chess board principle this counts double. */
3417 a_max = abs(a_max + (a_max > 0 ? 2 : -2)) / 2;
3418 b_max = abs(b_max + (b_max > 0 ? 2 : -2)) / 2;
3419
3420 /* We get a 1x1 on normal 2x1 rectangles, due to it being
3421 * a seen as two sides. As the result for actual building
3422 * will be the same as non-diagonal dragging revert to that
3423 * behaviour to give it a more normally looking size. */
3424 if (a_max != 1 || b_max != 1) {
3425 dx = a_max;
3426 dy = b_max;
3427 }
3428 } else if (style & HT_RECT) {
3429 if (dx == 1) {
3430 style = HT_LINE | HT_DIR_Y;
3431 } else if (dy == 1) {
3432 style = HT_LINE | HT_DIR_X;
3433 }
3434 }
3435
3436 if (dx != 1 || dy != 1) {
3437 int heightdiff = CalcHeightdiff(style, 0, t0, t1);
3438
3439 dx -= (style & HT_POINT ? 1 : 0);
3440 dy -= (style & HT_POINT ? 1 : 0);
3441
3442 if (heightdiff == 0) {
3443 ShowMeasurementTooltips(GetEncodedString(STR_MEASURE_AREA, dx, dy));
3444 } else {
3445 ShowMeasurementTooltips(GetEncodedString(STR_MEASURE_AREA_HEIGHTDIFF, dx, dy, heightdiff));
3446 }
3447 }
3448 }
3449 break;
3450
3451 default: NOT_REACHED();
3452 }
3453
3454 _thd.selend.x = x;
3455 _thd.selend.y = y;
3456}
3457
3463{
3465
3466 /* stop drag mode if the window has been closed */
3467 Window *w = _thd.GetCallbackWnd();
3468 if (w == nullptr) {
3470 return ES_HANDLED;
3471 }
3472
3473 /* while dragging execute the drag procedure of the corresponding window (mostly VpSelectTilesWithMethod() ) */
3474 if (_left_button_down) {
3476 /* Only register a drag event when the mouse moved. */
3477 if (_thd.new_pos.x == _thd.selstart.x && _thd.new_pos.y == _thd.selstart.y) return ES_HANDLED;
3478 _thd.selstart.x = _thd.new_pos.x;
3479 _thd.selstart.y = _thd.new_pos.y;
3480 }
3481
3482 w->OnPlaceDrag(_thd.select_method, _thd.select_proc, GetTileBelowCursor());
3483 return ES_HANDLED;
3484 }
3485
3486 /* Mouse button released. */
3489
3490 /* Keep the selected tool, but reset it to the original mode. */
3492 if ((_thd.next_drawstyle & HT_DRAG_MASK) == HT_RECT) {
3493 _thd.place_mode = HT_RECT | others;
3494 } else if (_thd.select_method & VPM_SIGNALDIRS) {
3495 _thd.place_mode = HT_RECT | others;
3496 } else if (_thd.select_method & VPM_RAILDIRS) {
3497 _thd.place_mode = (_thd.select_method & ~VPM_RAILDIRS) ? _thd.next_drawstyle : (HT_RAIL | others);
3498 } else {
3499 _thd.place_mode = HT_POINT | others;
3500 }
3501 SetTileSelectSize(1, 1);
3502
3503 HideMeasurementTooltips();
3504 w->OnPlaceMouseUp(_thd.select_method, _thd.select_proc, _thd.selend, TileVirtXY(_thd.selstart.x, _thd.selstart.y), TileVirtXY(_thd.selend.x, _thd.selend.y));
3505
3506 return ES_HANDLED;
3507}
3508
3517{
3518 SetObjectToPlace(icon, pal, mode, w->window_class, w->window_number);
3519}
3520
3521#include "table/animcursors.h"
3522
3531void SetObjectToPlace(CursorID icon, PaletteID pal, HighLightStyle mode, WindowClass window_class, WindowNumber window_num)
3532{
3533 if (_thd.window_class != WC_INVALID) {
3534 /* Undo clicking on button and drag & drop */
3535 Window *w = _thd.GetCallbackWnd();
3536 /* Call the abort function, but set the window class to something
3537 * that will never be used to avoid infinite loops. Setting it to
3538 * the 'next' window class must not be done because recursion into
3539 * this function might in some cases reset the newly set object to
3540 * place or not properly reset the original selection. */
3541 _thd.window_class = WC_INVALID;
3542 if (w != nullptr) {
3543 w->OnPlaceObjectAbort();
3544 HideMeasurementTooltips();
3545 }
3546 }
3547
3548 /* Mark the old selection dirty, in case the selection shape or colour changes */
3550
3551 SetTileSelectSize(1, 1);
3552
3553 _thd.make_square_red = false;
3554
3555 if (mode == HT_DRAG) { // HT_DRAG is for dragdropping trains in the depot window
3556 mode = HT_NONE;
3558 } else {
3560 }
3561
3562 _thd.place_mode = mode;
3563 _thd.window_class = window_class;
3564 _thd.window_number = window_num;
3565
3566 if ((mode & HT_DRAG_MASK) == HT_SPECIAL) { // special tools, like tunnels or docks start with presizing mode
3567 VpStartPreSizing();
3568 }
3569
3570 if ((icon & ANIMCURSOR_FLAG) != 0) {
3572 } else {
3573 SetMouseCursor(icon, pal);
3574 }
3575
3576}
3577
3583
3584Point GetViewportStationMiddle(const Viewport &vp, const Station *st)
3585{
3586 int x = TileX(st->xy) * TILE_SIZE;
3587 int y = TileY(st->xy) * TILE_SIZE;
3588 int z = GetSlopePixelZ(Clamp(x, 0, Map::SizeX() * TILE_SIZE - 1), Clamp(y, 0, Map::SizeY() * TILE_SIZE - 1));
3589
3590 Point p = RemapCoords(x, y, z);
3591 p.x = UnScaleByZoom(p.x - vp.virtual_left, vp.zoom) + vp.left;
3592 p.y = UnScaleByZoom(p.y - vp.virtual_top, vp.zoom) + vp.top;
3593 return p;
3594}
3595
3601
3604#ifdef WITH_SSE
3605 { &ViewportSortParentSpritesSSE41Checker, &ViewportSortParentSpritesSSE41 },
3606#endif
3608};
3609
3612{
3613 for (const auto &sprite_sorter : _vp_sprite_sorters) {
3614 if (sprite_sorter.fct_checker()) {
3615 _vp_sprite_sorter = sprite_sorter.fct_sorter;
3616 break;
3617 }
3618 }
3619 assert(_vp_sprite_sorter != nullptr);
3620}
3621
3631{
3632 if (_current_company != OWNER_DEITY) return CMD_ERROR;
3633 switch (target) {
3634 case VST_EVERYONE:
3635 break;
3636 case VST_COMPANY:
3637 if (_local_company != (CompanyID)ref) return CommandCost();
3638 break;
3639 case VST_CLIENT:
3640 if (_network_own_client_id != (ClientID)ref) return CommandCost();
3641 break;
3642 default:
3643 return CMD_ERROR;
3644 }
3645
3646 if (flags.Test(DoCommandFlag::Execute)) {
3649 }
3650 return CommandCost();
3651}
3652
3653void MarkCatchmentTilesDirty()
3654{
3655 if (_viewport_highlight_town != nullptr) {
3657 return;
3658 }
3659
3660 if (_viewport_highlight_station != nullptr) {
3664 } else {
3666 for (TileIndex tile = it; tile != INVALID_TILE; tile = ++it) {
3667 MarkTileDirtyByTile(tile);
3668 }
3669 }
3670 }
3671
3672 if (_viewport_highlight_station_rect != nullptr) {
3675 }
3677 }
3678
3679 if (_viewport_highlight_waypoint != nullptr) {
3682 }
3684 }
3685
3686 if (_viewport_highlight_waypoint_rect != nullptr) {
3689 }
3691 }
3692}
3693
3694static void SetWindowDirtyForViewportCatchment()
3695{
3701}
3702
3703static void ClearViewportCatchment()
3704{
3705 MarkCatchmentTilesDirty();
3710 _viewport_highlight_town = nullptr;
3711}
3712
3719void SetViewportCatchmentStation(const Station *st, bool sel)
3720{
3721 SetWindowDirtyForViewportCatchment();
3722 /* Mark tiles dirty for redrawing and update selected station if a different station is already highlighted. */
3723 if (sel && _viewport_highlight_station != st) {
3724 ClearViewportCatchment();
3726 MarkCatchmentTilesDirty();
3727 /* Mark tiles dirty for redrawing and clear station selection if deselecting highlight. */
3728 } else if (!sel && _viewport_highlight_station == st) {
3729 MarkCatchmentTilesDirty();
3731 }
3732 /* Redraw the currently selected station window */
3734}
3735
3742void SetViewportStationRect(const Station *st, bool sel)
3743{
3744 SetWindowDirtyForViewportCatchment();
3745 /* Mark tiles dirty for redrawing and update selected station if a different station is already highlighted. */
3746 if (sel && _viewport_highlight_station_rect != st) {
3747 ClearViewportCatchment();
3749 MarkCatchmentTilesDirty();
3750 /* Mark tiles dirty for redrawing and clear station selection if deselecting highlight. */
3751 } else if (!sel && _viewport_highlight_station_rect == st) {
3752 MarkCatchmentTilesDirty();
3754 }
3755 /* Redraw the currently selected station window */
3757}
3758
3766{
3767 SetWindowDirtyForViewportCatchment();
3768 /* Mark tiles dirty for redrawing and update selected waypoint if a different waypoint is already highlighted. */
3769 if (sel && _viewport_highlight_waypoint != wp) {
3770 ClearViewportCatchment();
3772 MarkCatchmentTilesDirty();
3773 /* Mark tiles dirty for redrawing and clear waypoint selection if deselecting highlight. */
3774 } else if (!sel && _viewport_highlight_waypoint == wp) {
3775 MarkCatchmentTilesDirty();
3777 }
3778 /* Redraw the currently selected waypoint window */
3780}
3781
3788void SetViewportWaypointRect(const Waypoint *wp, bool sel)
3789{
3790 SetWindowDirtyForViewportCatchment();
3791 /* Mark tiles dirty for redrawing and update selected waypoint if a different waypoint is already highlighted. */
3792 if (sel && _viewport_highlight_waypoint_rect != wp) {
3793 ClearViewportCatchment();
3795 MarkCatchmentTilesDirty();
3796 /* Mark tiles dirty for redrawing and clear waypoint selection if deselecting highlight. */
3797 } else if (!sel && _viewport_highlight_waypoint_rect == wp) {
3798 MarkCatchmentTilesDirty();
3800 }
3801 /* Redraw the currently selected waypoint window */
3803}
3804
3811void SetViewportCatchmentTown(const Town *t, bool sel)
3812{
3813 SetWindowDirtyForViewportCatchment();
3814 /* Mark tiles dirty for redrawing and update selected town if a different town is already highlighted. */
3815 if (sel && _viewport_highlight_town != t) {
3816 ClearViewportCatchment();
3819 /* Mark tiles dirty for redrawing and clear town selection if deselecting highlight. */
3820 } else if (!sel && _viewport_highlight_town == t) {
3821 _viewport_highlight_town = nullptr;
3823 }
3824 /* Redraw the currently selected town window */
3826}
3827
3832void ViewportData::CancelFollow(const Window &viewport_window)
3833{
3834 if (this->follow_vehicle == VehicleID::Invalid()) return;
3835
3836 if (viewport_window.window_class == WC_MAIN_WINDOW) {
3837 /* We're cancelling follow in the main viewport, so we need to check for a vehicle view window
3838 * to raise the location follow widget. */
3839 Window *vehicle_window = FindWindowById(WC_VEHICLE_VIEW, this->follow_vehicle);
3840 if (vehicle_window != nullptr) vehicle_window->RaiseWidgetWhenLowered(WID_VV_LOCATION);
3841 }
3842
3843 this->follow_vehicle = VehicleID::Invalid();
3844}
This file defines all the the animated cursors.
static constexpr std::span< const AnimCursor > _animcursors[]
This is an array of pointers to all the animated cursor definitions we have above.
Definition animcursors.h:67
Highlight/sprite information for autorail.
Class for backupping variables and making sure they are restored later.
constexpr T SetBit(T &x, const uint8_t y)
Set a bit in a variable.
constexpr bool HasBit(const T x, const uint8_t y)
Checks if a bit in a value is set.
TileIndex GetNorthernBridgeEnd(TileIndex t)
Finds the northern end of a bridge starting at a middle tile.
Map accessor functions for bridges.
int GetBridgePixelHeight(TileIndex tile)
Get the height ('z') of a bridge in pixels.
Definition bridge_map.h:84
bool IsBridgeAbove(Tile t)
checks if a bridge is set above the ground of this tile
Definition bridge_map.h:45
constexpr bool Test(Tvalue_type value) const
Test if the value-th bit is set.
constexpr Timpl & Set()
Set all bits.
Iterator to iterate over all tiles belonging to a bitmaptilearea.
static Blitter * GetCurrentBlitter()
Get the current active blitter (always set by calling SelectBlitter).
Definition factory.hpp:136
How all blitters should look like.
Definition base.hpp:29
virtual void * MoveTo(void *video, int x, int y)=0
Move the destination pointer the requested amount x and y, keeping in mind any pitch and bpp of the r...
virtual void SetPixel(void *video, int x, int y, PixelColour colour)=0
Draw a pixel with a given colour on the video-buffer.
Common return value for all commands.
Container for an encoded string, created by GetEncodedString.
K-dimensional tree, specialised for 2-dimensional space.
Definition kdtree.hpp:33
void Build(It begin, It end)
Clear and rebuild the tree from a new sequence of elements,.
Definition kdtree.hpp:360
size_t Count() const
Get number of elements stored in tree.
Definition kdtree.hpp:428
void FindContained(CoordT x1, CoordT y1, CoordT x2, CoordT y2, const Outputter &outputter) const
Find all items contained within the given rectangle.
Definition kdtree.hpp:457
T FindNearest(CoordT x, CoordT y) const
Find the element closest to given coordinate, in Manhattan distance.
Definition kdtree.hpp:439
RAII class for measuring multi-step elements of performance.
static WidgetDimensions scaled
Widget dimensions scaled for current zoom level.
Definition window_gui.h:30
RectPadding fullbevel
Always-scaled bevel thickness.
Definition window_gui.h:39
Functions related to commands.
static const CommandCost CMD_ERROR
Define a default return value for a failed command.
@ Execute
execute the given command
Definition of stuff that is very close to a company, like the company struct itself.
TypedIndexContainer< std::array< Colours, MAX_COMPANIES >, CompanyID > _company_colours
NOSAVE: can be determined from company structs.
CompanyID _local_company
Company controlled by the human player at this client. Can also be COMPANY_SPECTATOR.
CompanyID _current_company
Company currently doing an action.
Functions related to companies.
static constexpr Owner OWNER_DEITY
The object is owned by a superuser / goal script.
static constexpr Owner OWNER_NONE
The tile has no ownership.
#define Debug(category, level, format_string,...)
Output a line of debugging information.
Definition debug.h:37
@ DIR_SW
Southwest.
@ DIR_SE
Southeast.
@ DIR_S
South.
constexpr std::underlying_type_t< enum_type > to_underlying(enum_type e)
Implementation of std::to_underlying (from C++23)
Definition enum_type.hpp:17
Factory to 'query' all available blitters.
int GetCharacterHeight(FontSize size)
Get height of a character for a given font size.
Types for recording game performance data.
@ PFE_DRAWWORLD
Time spent drawing world viewports in GUI.
void SetMouseCursor(CursorID sprite, PaletteID pal)
Assign a single non-animated sprite to the cursor.
Definition gfx.cpp:1700
bool _left_button_down
Is left mouse button pressed?
Definition gfx.cpp:41
Dimension GetStringBoundingBox(std::string_view str, FontSize start_fontsize)
Return the string dimension in pixels.
Definition gfx.cpp:891
int DrawString(int left, int right, int top, std::string_view str, TextColour colour, StringAlignment align, bool underline, FontSize fontsize)
Draw string, possibly truncated to make it fit in its allocated space.
Definition gfx.cpp:662
bool _ctrl_pressed
Is Ctrl pressed?
Definition gfx.cpp:38
void SetAnimatedMouseCursor(std::span< const AnimCursor > table)
Assign an animation to the cursor.
Definition gfx.cpp:1713
void DrawSpriteViewport(SpriteID img, PaletteID pal, int x, int y, const SubSprite *sub)
Draw a sprite in a viewport.
Definition gfx.cpp:1000
void DrawBox(int x, int y, int dx1, int dy1, int dx2, int dy2, int dx3, int dy3)
Draws the projection of a parallelepiped.
Definition gfx.cpp:422
uint32_t SpriteID
The number of a sprite, without mapping bits and colourtables.
Definition gfx_type.h:17
@ Normal
The most basic (normal) sprite.
@ FS_SMALL
Index of the small font in the font tables.
Definition gfx_type.h:250
@ FS_NORMAL
Index of the normal font in the font tables.
Definition gfx_type.h:249
uint32_t CursorID
The number of the cursor (sprite)
Definition gfx_type.h:19
@ SA_HOR_CENTER
Horizontally center the text.
Definition gfx_type.h:389
uint32_t PaletteID
The number of the palette.
Definition gfx_type.h:18
TextColour
Colour of the strings, see _string_colourmap in table/string_colours.h or docs/ottd-colourtext-palett...
Definition gfx_type.h:307
bool MarkAllViewportsDirty(int left, int top, int right, int bottom)
Mark all viewports that display an area as dirty (in need of repaint).
void SetDirty() const
Mark entire window as dirty (in need of re-paint)
Definition window.cpp:966
void AddDirtyBlock(int left, int top, int right, int bottom)
Extend the internal _invalid_rect rectangle to contain the rectangle defined by the given parameters.
Definition gfx.cpp:1510
void MarkDirty(ZoomLevel maxzoom=ZoomLevel::Max) const
Mark the sign dirty in all viewports.
static bool MarkViewportDirty(const Viewport &vp, int left, int top, int right, int bottom)
Marks a viewport as dirty for repaint if it displays (a part of) the area the needs to be repainted.
void MarkWholeScreenDirty()
This function mark the whole screen as dirty.
Definition gfx.cpp:1543
void RedrawScreenRect(int left, int top, int right, int bottom)
Repaints a specific rectangle of the screen.
Definition gfx.cpp:1420
void MarkTileDirtyByTile(TileIndex tile, int bridge_level_offset, int tile_height_override)
Mark a tile given by its index dirty for repaint.
static void SetSelectionTilesDirty()
Marks the selected tiles as dirty.
const TileTypeProcs *const _tile_type_procs[16]
Tile callback functions for each type of tile.
Definition landscape.cpp:68
Point InverseRemapCoords2(int x, int y, bool clamp_to_map, bool *clamped)
Map 2D viewport or smallmap coordinate to 3D world or tile coordinate.
int GetSlopePixelZ(int x, int y, bool ground_vehicle)
Return world Z coordinate of a given point of a tile.
Functions related to OTTD's landscape.
Point RemapCoords(int x, int y, int z)
Map 3D world or tile coordinate to equivalent 2D coordinate as used in the viewports and smallmap.
Definition landscape.h:81
Point RemapCoords2(int x, int y)
Map 3D world or tile coordinate to equivalent 2D coordinate as used in the viewports and smallmap.
Definition landscape.h:97
Point InverseRemapCoords(int x, int y)
Map 2D viewport or smallmap coordinate to 3D world or tile coordinate.
Definition landscape.h:111
Declaration of linkgraph overlay GUI.
bool DoZoomInOutWindow(ZoomStateChange how, Window *w)
Zooms a viewport in a window in or out.
Definition main_gui.cpp:93
uint DistanceManhattan(TileIndex t0, TileIndex t1)
Gets the Manhattan distance between the two given tiles.
Definition map.cpp:158
static TileIndex TileVirtXY(uint x, uint y)
Get a tile from the virtual XY-coordinate.
Definition map_func.h:416
TileIndex TileAddByDir(TileIndex tile, Direction dir)
Adds a Direction to a tile.
Definition map_func.h:611
TileIndexDiff ToTileIndexDiff(TileIndexDiffC tidc)
Return the offset between two tiles from a TileIndexDiffC struct.
Definition map_func.h:452
static TileIndex TileXY(uint x, uint y)
Returns the TileIndex of a coordinate.
Definition map_func.h:385
static uint TileY(TileIndex tile)
Get the Y component of a tile.
Definition map_func.h:437
static uint TileX(TileIndex tile)
Get the X component of a tile.
Definition map_func.h:427
constexpr TileIndex TileAdd(TileIndex tile, TileIndexDiff offset)
Adds a given offset to a tile.
Definition map_func.h:469
constexpr bool IsInsideBS(const T x, const size_t base, const size_t size)
Checks if a value is between a window started at some base point.
constexpr bool IsInsideMM(const size_t x, const size_t min, const size_t max) noexcept
Checks if a value is in an interval.
constexpr T abs(const T a)
Returns the absolute value of (scalar) variable.
Definition math_func.hpp:23
constexpr T Align(const T x, uint n)
Return the smallest multiple of n equal or greater than x.
Definition math_func.hpp:37
constexpr uint CeilDiv(uint a, uint b)
Computes ceil(a / b) for non-negative a and b.
constexpr T Delta(const T a, const T b)
Returns the (absolute) difference between two (scalar) variables.
constexpr T Clamp(const T a, const T min, const T max)
Clamp a value between an interval.
Definition math_func.hpp:79
void GuiShowTooltips(Window *parent, EncodedString &&text, TooltipCloseCondition close_tooltip)
Shows a tooltip.
Definition misc_gui.cpp:693
ClientID _network_own_client_id
Our client identifier.
Definition network.cpp:71
Network functions used by other parts of OpenTTD.
ClientID
'Unique' identifier to be given to clients
@ DO_SHOW_TOWN_NAMES
Display town names.
Definition openttd.h:46
@ DO_SHOW_COMPETITOR_SIGNS
Display signs, station names and waypoint names of opponent companies. Buoys and oilrig-stations are ...
Definition openttd.h:52
@ DO_SHOW_SIGNS
Display signs.
Definition openttd.h:48
@ DO_SHOW_WAYPOINT_NAMES
Display waypoint names.
Definition openttd.h:51
@ DO_SHOW_STATION_NAMES
Display station names.
Definition openttd.h:47
PixelColour GetColourGradient(Colours colour, ColourShade shade)
Get colour gradient palette index.
Definition palette.cpp:388
A number of safeguards to prevent using unsafe methods.
GameSettings _settings_game
Game settings of a running game or the scenario editor.
Definition settings.cpp:61
ClientSettings _settings_client
The current settings for this game.
Definition settings.cpp:60
Base class for signs.
Functions related to signs.
void HandleClickOnSign(const Sign *si)
Handle clicking on a sign.
Slope SlopeWithThreeCornersRaised(Corner corner)
Returns the slope with all except one corner raised.
Definition slope_func.h:206
Corner OppositeCorner(Corner corner)
Returns the opposite corner.
Definition slope_func.h:184
static constexpr Corner GetHalftileSlopeCorner(Slope s)
Returns the leveled halftile of a halftile slope.
Definition slope_func.h:148
static constexpr Slope RemoveHalftileSlope(Slope s)
Removes a halftile slope from a slope.
Definition slope_func.h:60
uint SlopeToSpriteOffset(Slope s)
Returns the Sprite offset for a given Slope.
Definition slope_func.h:415
static constexpr bool IsSteepSlope(Slope s)
Checks if a slope is steep.
Definition slope_func.h:36
static constexpr bool IsHalftileSlope(Slope s)
Checks for non-continuous slope on halftile foundations.
Definition slope_func.h:47
Slope SlopeWithOneCornerRaised(Corner corner)
Returns the slope with a specific corner raised.
Definition slope_func.h:99
Corner
Enumeration of tile corners.
Definition slope_type.h:22
Slope
Enumeration for the slope-type.
Definition slope_type.h:48
@ SLOPE_N
the north corner of the tile is raised
Definition slope_type.h:53
@ SLOPE_STEEP_N
a steep slope falling to south (from north)
Definition slope_type.h:69
bool ScrollMainWindowTo(int x, int y, int z, bool instant)
Scrolls the main window to given coordinates.
static constexpr uint32_t MAX_SPRITES
Masks needed for sprite operations.
Definition sprites.h:1569
static const PaletteID PALETTE_TILE_RED_PULSATING
pulsating red tile drawn if you try to build a wrong tunnel or raise/lower land where it is not possi...
Definition sprites.h:1583
static const CursorID ANIMCURSOR_FLAG
Flag for saying a cursor sprite is an animated cursor.
Definition sprites.h:1521
static const PaletteID PALETTE_SEL_TILE_RED
makes a square red. is used when removing rails or other stuff
Definition sprites.h:1584
static constexpr uint32_t SPRITE_MASK
The mask to for the main sprite.
Definition sprites.h:1570
static constexpr uint8_t PALETTE_MODIFIER_TRANSPARENT
when a sprite is to be displayed transparently, this bit needs to be set.
Definition sprites.h:1561
static const PaletteID PALETTE_CRASH
Recolour sprite greying of crashed vehicles.
Definition sprites.h:1619
static const CursorID SPR_CURSOR_MOUSE
Cursor sprite numbers.
Definition sprites.h:1404
static const PaletteID PALETTE_SEL_TILE_BLUE
This draws a blueish square (catchment areas for example)
Definition sprites.h:1585
static const PaletteID PALETTE_TO_TRANSPARENT
This sets the sprite to transparent.
Definition sprites.h:1616
Base classes/functions for stations.
void ShowStationViewWindow(StationID station)
Opens StationViewWindow for given station.
StationID GetStationIndex(Tile t)
Get StationID from a tile.
Definition station_map.h:28
Definition of base types and functions in a cross-platform compatible way.
The colour translation of GRF's strings.
static constexpr PixelColour _string_colourmap[17]
Colour mapping for TextColour.
EncodedString GetEncodedString(StringID str)
Encode a string with no parameters into an encoded string.
Definition strings.cpp:90
std::string GetString(StringID string)
Resolve the given StringID into a std::string with formatting but no parameters.
Definition strings.cpp:424
Functions related to OTTD's strings.
uint32_t StringID
Numeric value that represents a string, independent of the selected language.
Class to backup a specific variable and restore it upon destruction of this object to prevent stack v...
Base class for all station-ish types.
TileIndex xy
Base tile of the station.
StationRect rect
NOSAVE: Station spread out rectangle maintained by StationRect::xxx() functions.
bool IsInUse() const
Check whether the base station currently is in use; in use means that it is not scheduled for deletio...
TrackedViewportSign sign
NOSAVE: Dimensions of sign.
int next
next child to draw (-1 at the end)
Definition viewport.cpp:140
const SubSprite * sub
only draw a rectangular part of the sprite
Definition viewport.cpp:136
GUISettings gui
settings related to the GUI
uint8_t max_bridge_height
maximum height of bridges
T y
Y coordinate.
T x
X coordinate.
T x
X coordinate.
T y
Y coordinate.
T z
Z coordinate.
Point pos
logical mouse position
Definition gfx_type.h:125
Data about how and where to blit pixels.
Definition gfx_type.h:157
uint8_t dist_local_authority
distance for town local authority, default 20
bool population_in_label
show the population of a town in its label?
bool measure_tooltip
show a permanent tooltip when dragging tools
ZoomLevel zoom_min
minimum zoom out level
bool smooth_scroll
smooth scroll viewports
ZoomLevel zoom_max
maximum zoom out level
EconomySettings economy
settings to change the economy
ConstructionSettings construction
construction of things in-game
static uint SizeX()
Get the size of the map along the X.
Definition map_func.h:272
static uint SizeY()
Get the size of the map along the Y.
Definition map_func.h:281
static uint ScaleBySize1D(uint n)
Scales the given value by the maps circumference, where the given value is for a 256 by 256 map.
Definition map_func.h:353
static uint MaxY()
Gets the maximum Y coordinate within the map, including MP_VOID.
Definition map_func.h:308
static uint MaxX()
Gets the maximum X coordinate within the map, including MP_VOID.
Definition map_func.h:299
TileIndex tile
The base tile of the area.
Parent sprite that should be drawn.
const SubSprite * sub
only draw a rectangular part of the sprite
int32_t zmin
minimal world Z coordinate of bounding box
SpriteID image
sprite to draw
int32_t xmax
maximal world X coordinate of bounding box
int32_t ymax
maximal world Y coordinate of bounding box
int32_t x
screen X coordinate of sprite
int32_t y
screen Y coordinate of sprite
int32_t first_child
the first child to draw.
int32_t zmax
maximal world Z coordinate of bounding box
int32_t xmin
minimal world X coordinate of bounding box
int32_t ymin
minimal world Y coordinate of bounding box
PaletteID pal
palette to use
int32_t left
minimal screen X coordinate of sprite (= x + sprite->x_offs), reference point for child sprites
int32_t top
minimal screen Y coordinate of sprite (= y + sprite->y_offs), reference point for child sprites
Colour for pixel/line drawing.
Definition gfx_type.h:405
static Titem * Get(auto index)
Returns Titem with given index.
static size_t GetNumItems()
Returns number of valid items in the pool.
Tindex index
Index of this pool item.
Specification of a rectangle with absolute coordinates of all edges.
static bool IsExpected(const BaseStation *st)
Helper for checking whether the given station is of this type.
static Station * Get(auto index)
Gets station with given index.
static Waypoint * From(BaseStation *st)
Converts a BaseStation to SpecializedStation with type checking.
Coord3D< int8_t > offset
Relative position of sprite from bounding box.
Definition sprite.h:21
Coord3D< int8_t > origin
Position of northern corner within tile.
Definition sprite.h:19
Coord3D< uint8_t > extent
Size of bounding box.
Definition sprite.h:20
Data structure describing a sprite.
uint16_t width
Width of the sprite.
uint16_t height
Height of the sprite.
int16_t y_offs
Number of pixels to shift the sprite downwards.
int16_t x_offs
Number of pixels to shift the sprite to the right.
StationRect - used to track station spread out rectangle - cheaper than scanning whole map.
bool PtInExtendedRect(int x, int y, int distance=0) const
Determines whether a given point (x, y) is within a certain distance of the station rectangle.
Definition station.cpp:564
Station data structure.
BitmapTileArea catchment_tiles
NOSAVE: Set of individual tiles covered by catchment area.
Used to only draw a part of the sprite.
Definition gfx_type.h:278
Metadata about the current highlighting.
TileIndex redsq
The tile that has to get a red selection.
Point new_pos
New value for pos; used to determine whether to redraw the selection.
Window * GetCallbackWnd()
Get the window that started the current highlighting.
bool make_square_red
Whether to give a tile a red selection.
bool IsDraggingDiagonal()
Is the user dragging a 'diagonal rectangle'?
ViewportDragDropSelectionProcess select_proc
The procedure that has to be called when the selection is done.
Point selstart
The location where the dragging started.
Point new_outersize
New value for outersize; used to determine whether to redraw the selection.
HighLightStyle drawstyle
Lower bits 0-3 are reserved for detailed highlight information.
HighLightStyle place_mode
Method which is used to place the selection.
WindowClass window_class
The WindowClass of the window that is responsible for the selection mode.
uint8_t dirty
Whether the build station window needs to redraw due to the changed selection.
Point offs
Offset, in tile "units", for the blue coverage area from the selected area's northern tile.
Point size
Size, in tile "units", of the white/red selection area.
uint8_t sizelimit
Whether the selection is limited in length, and what the maximum length is.
Point pos
Location, in tile "units", of the northern tile of the selected area.
Point outersize
Size, in tile "units", of the blue coverage area excluding the side of the selected area.
bool diagonal
Whether the dragged area is a 45 degrees rotated rectangle.
void Reset()
Reset tile highlighting.
Point selend
The location where the drag currently ends.
Point new_size
New value for size; used to determine whether to redraw the selection.
bool freeze
Freeze highlight in place.
HighLightStyle next_drawstyle
Queued, but not yet drawn style.
ViewportPlaceMethod select_method
The method which governs how tiles are selected.
WindowNumber window_number
The WindowNumber of the window that is responsible for the selection mode.
Tile information, used while rendering the tile.
Definition tile_cmd.h:30
Slope tileh
Slope of the tile.
Definition tile_cmd.h:31
TileIndex tile
Tile index.
Definition tile_cmd.h:32
int32_t y
screen Y coordinate of sprite
Definition viewport.cpp:130
int32_t x
screen X coordinate of sprite
Definition viewport.cpp:129
const SubSprite * sub
only draw a rectangular part of the sprite
Definition viewport.cpp:128
DrawTileProc * draw_tile_proc
Called to render the tile and its contents to the screen.
Definition tile_cmd.h:153
TrackedViewportSign sign
Location of name sign, UpdateVirtCoord updates this.
Definition town.h:54
Town data structure.
Definition town.h:63
TileIndex xy
town center tile
Definition town.h:64
TownCache cache
Container for all cacheable data.
Definition town.h:66
bool show_zone
NOSAVE: mark town to show the local authority zone in the viewports.
Definition town.h:153
StationList stations_near
NOSAVE: List of nearby stations.
Definition town.h:140
bool kdtree_valid
Are the sign data valid for use with the _viewport_sign_kdtree?
Vehicle data structure.
int32_t z_pos
z coordinate.
Vehicle * First() const
Get the first vehicle of this vehicle chain.
int32_t y_pos
y coordinate.
int32_t x_pos
x coordinate.
Owner owner
Which company owns the vehicle?
UnitID unitnumber
unit number, for display purposes only
Data structure for a window viewport.
Definition window_gui.h:251
void CancelFollow(const Window &viewport_window)
Cancel viewport vehicle following, and raise follow location widget if needed.
int32_t dest_scrollpos_y
Current destination y coordinate to display (virtual screen coordinate of topleft corner of the viewp...
Definition window_gui.h:256
int32_t scrollpos_y
Currently shown y coordinate (virtual screen coordinate of topleft corner of the viewport).
Definition window_gui.h:254
int32_t dest_scrollpos_x
Current destination x coordinate to display (virtual screen coordinate of topleft corner of the viewp...
Definition window_gui.h:255
VehicleID follow_vehicle
VehicleID to follow if following a vehicle, VehicleID::Invalid() otherwise.
Definition window_gui.h:252
int32_t scrollpos_x
Currently shown x coordinate (virtual screen coordinate of topleft corner of the viewport).
Definition window_gui.h:253
Data structure storing rendering information.
Definition viewport.cpp:170
int foundation[FOUNDATION_PART_END]
Foundation sprites (index into parent_sprites_to_draw).
Definition viewport.cpp:183
int last_foundation_child[FOUNDATION_PART_END]
Tail of ChildSprite list of the foundations. (index into child_screen_sprites_to_draw)
Definition viewport.cpp:185
ParentSpriteToSortVector parent_sprites_to_sort
Parent sprite pointer array used for sorting.
Definition viewport.cpp:176
Point foundation_offset[FOUNDATION_PART_END]
Pixel offset for ground sprites on the foundations.
Definition viewport.cpp:186
SpriteCombineMode combine_sprites
Current mode of "sprite combining".
Definition viewport.cpp:181
FoundationPart foundation_part
Currently active foundation for ground sprite drawing.
Definition viewport.cpp:184
Helper class for getting the best sprite sorter.
VpSorterChecker fct_checker
The check function.
VpSpriteSorter fct_sorter
The sorting function.
Location information about a sign as seen on the viewport.
int32_t center
The center position of the sign.
uint16_t width_small
The width when zoomed out (small font)
uint16_t width_normal
The width when not zoomed out (normal font)
void UpdatePosition(int center, int top, std::string_view str, std::string_view str_small={})
Update the position of the viewport sign.
int32_t top
The top of the sign.
Data structure for viewport, display of a part of the world.
int top
Screen coordinate top edge of the viewport.
int width
Screen width of the viewport.
ZoomLevel zoom
The zoom level of the viewport.
int virtual_top
Virtual top coordinate.
int virtual_left
Virtual left coordinate.
int virtual_width
width << zoom
int left
Screen coordinate left edge of the viewport.
int height
Screen height of the viewport.
int virtual_height
height << zoom
Representation of a waypoint.
Number to differentiate different windows of the same class.
Iterator to iterate all valid Windows.
Definition window_gui.h:875
Data structure for an opened window.
Definition window_gui.h:274
void SetWidgetDirty(WidgetID widget_index) const
Invalidate a widget, i.e.
Definition window.cpp:556
std::unique_ptr< ViewportData > viewport
Pointer to viewport data, if present.
Definition window_gui.h:319
void RaiseWidgetWhenLowered(WidgetID widget_index)
Marks a widget as raised and dirty (redraw), when it is marked as lowered.
Definition window_gui.h:479
virtual void OnPlaceObjectAbort()
The user cancelled a tile highlight mode that has been set.
Definition window_gui.h:822
WindowClass window_class
Window class.
Definition window_gui.h:302
void DrawViewport() const
Draw the viewport of this window.
virtual void OnPlaceObject(Point pt, TileIndex tile)
The user clicked some place on the map when a tile highlight mode has been set.
Definition window_gui.h:801
int left
x position of left edge of the window
Definition window_gui.h:310
int top
y position of top edge of the window
Definition window_gui.h:311
virtual void OnPlaceMouseUp(ViewportPlaceMethod select_method, ViewportDragDropSelectionProcess select_proc, Point pt, TileIndex start_tile, TileIndex end_tile)
The user has dragged over the map when the tile highlight mode has been set.
Definition window_gui.h:843
virtual void OnPlaceDrag(ViewportPlaceMethod select_method, ViewportDragDropSelectionProcess select_proc, Point pt)
The user is dragging over the map when the tile highlight mode has been set.
Definition window_gui.h:832
void SetWidgetDisabledState(WidgetID widget_index, bool disab_stat)
Sets the enabled/disabled status of a widget.
Definition window_gui.h:382
AllWindows< false > Iterate
Iterate all windows in whatever order is easiest.
Definition window_gui.h:933
int height
Height of the window (number of pixels down in y direction)
Definition window_gui.h:313
int width
width of the window (number of pixels to the right in x direction)
Definition window_gui.h:312
WindowNumber window_number
Window number within the window class.
Definition window_gui.h:303
std::tuple< Slope, int > GetTilePixelSlopeOutsideMap(int x, int y)
Return the slope of a given tile, also for tiles outside the map (virtual "black" tiles).
Definition tile_map.cpp:78
static bool IsTileType(Tile tile, TileType type)
Checks if a tile is a given tiletype.
Definition tile_map.h:150
static uint TileHeight(Tile tile)
Returns the height of a tile.
Definition tile_map.h:29
int GetTilePixelZ(TileIndex tile)
Get bottom height of the tile.
Definition tile_map.h:302
int GetTileMaxPixelZ(TileIndex tile)
Get top height of the tile.
Definition tile_map.h:312
std::tuple< Slope, int > GetTilePixelSlope(TileIndex tile)
Return the slope of a given tile.
Definition tile_map.h:289
bool IsValidTile(Tile tile)
Checks if a tile is valid.
Definition tile_map.h:161
uint TilePixelHeight(Tile tile)
Returns the height of a tile in pixels.
Definition tile_map.h:72
uint TilePixelHeightOutsideMap(int x, int y)
Returns the height of a tile in pixels, also for tiles outside the map (virtual "black" tiles).
Definition tile_map.h:84
static TileType GetTileType(Tile tile)
Get the tiletype of a given tile.
Definition tile_map.h:96
uint TileHeightOutsideMap(int x, int y)
Returns the height of a tile, also for tiles outside the map (virtual "black" tiles).
Definition tile_map.h:42
static constexpr uint TILE_UNIT_MASK
For masking in/out the inner-tile world coordinate units.
Definition tile_type.h:16
static constexpr uint MAX_BUILDING_PIXELS
Maximum height of a building in pixels in ZOOM_BASE. (Also applies to "bridge buildings" on the bridg...
Definition tile_type.h:20
constexpr TileIndex INVALID_TILE
The very nice invalid tile marker.
Definition tile_type.h:95
static constexpr uint TILE_SIZE
Tile size in world coordinates.
Definition tile_type.h:15
static constexpr uint TILE_PIXELS
Pixel distance between tile columns/rows in ZOOM_BASE.
Definition tile_type.h:17
static constexpr uint TILE_HEIGHT
Height of a height level in world coordinate AND in pixels in ZOOM_BASE.
Definition tile_type.h:18
TileType
The different types of tiles.
Definition tile_type.h:47
@ MP_STATION
A tile of a station.
Definition tile_type.h:53
@ MP_HOUSE
A house by a town.
Definition tile_type.h:51
@ MP_VOID
Invisible tiles at the SW and SE border.
Definition tile_type.h:55
Functions related to tile highlights.
HighLightStyle
Highlighting draw styles.
@ HT_LINE
used for autorail highlighting (longer stretches), lower bits: direction
@ HT_DIR_HL
horizontal lower
@ HT_DIR_HU
horizontal upper
@ HT_DRAG
dragging items in the depot windows
@ HT_NONE
default
@ HT_DIR_X
X direction.
@ HT_DIAGONAL
Also allow 'diagonal rectangles'. Only usable in combination with HT_RECT or HT_POINT.
@ HT_POINT
point (lower land, raise land, level land, ...)
@ HT_RECT
rectangle (stations, depots, ...)
@ HT_DIR_MASK
masks the drag-direction
@ HT_RAIL
autorail (one piece), lower bits: direction
@ HT_DIR_END
end marker
@ HT_DRAG_MASK
Mask for the tile drag-type modes.
@ HT_DIR_VL
vertical left
@ HT_VEHICLE
vehicle is accepted as target as well (bitmask)
@ HT_DIR_VR
vertical right
@ HT_SPECIAL
special mode used for highlighting while dragging (and for tunnels/docks)
@ HT_DIR_Y
Y direction.
Base of the town class.
Town * ClosestTownFromTile(TileIndex tile, uint threshold)
Return the town closest (in distance or ownership) to a given tile, within a given threshold.
Declarations for accessing the k-d tree of towns.
TownID GetTownIndex(Tile t)
Get the index of which town this house/street is attached to.
Definition town_map.h:23
bool IsTransparencySet(TransparencyOption to)
Check if the transparency option bit is set and if we aren't in the game menu (there's never transpar...
uint8_t _display_opt
What do we want to draw/do?
bool IsInvisibilitySet(TransparencyOption to)
Check if the invisibility option bit is set and if we aren't in the game menu (there's never transpar...
@ TO_SIGNS
signs
void ViewportAddVehicles(DrawPixelInfo *dpi)
Add the vehicle sprites that should be drawn at a part of the screen.
Definition vehicle.cpp:1121
Vehicle * CheckClickOnVehicle(const Viewport &vp, int x, int y)
Find the vehicle close to the clicked coordinates.
Definition vehicle.cpp:1215
Base class for all vehicles.
Functions related to vehicles.
bool IsCompanyBuildableVehicleType(VehicleType type)
Is the given vehicle type buildable by a company?
void ShowVehicleViewWindow(const Vehicle *v)
Shows the vehicle view window of the given vehicle.
bool VehicleClicked(const Vehicle *v)
Dispatch a "vehicle selected" event if any window waits for it.
void StartStopVehicle(const Vehicle *v, bool texteffect)
Executes CMD_START_STOP_VEHICLE for given vehicle.
Functions related to the vehicle's GUIs.
Types related to the vehicle widgets.
@ WID_VV_LOCATION
Center the main view on this vehicle.
static void HighlightTownLocalAuthorityTiles(const TileInfo *ti)
Highlights tiles inside local authority of selected towns.
std::string * ViewportAddString(const DrawPixelInfo *dpi, const ViewportSign *sign, ViewportStringFlags flags, Colours colour)
Add a string to draw in the current viewport.
static void CheckOverflow(int &test, int &other, int max, int mult)
Check for overflowing the map.
bool ScrollWindowToTile(TileIndex tile, Window *w, bool instant)
Scrolls the viewport in a window to a given location.
FoundationPart
Enumeration of multi-part foundations.
Definition viewport.cpp:144
@ FOUNDATION_PART_NONE
Neither foundation nor groundsprite drawn yet.
Definition viewport.cpp:145
@ FOUNDATION_PART_HALFTILE
Second part (halftile foundation)
Definition viewport.cpp:147
@ FOUNDATION_PART_NORMAL
First part (normal foundation or no foundation)
Definition viewport.cpp:146
void SetTileSelectSize(int w, int h)
Highlight w by h tiles at the cursor.
CommandCost CmdScrollViewport(DoCommandFlags flags, TileIndex tile, ViewportScrollTarget target, uint32_t ref)
Scroll players main viewport.
static void ViewportAddStationStrings(DrawPixelInfo *dpi, const std::vector< const BaseStation * > &stations, bool small)
Add station strings to a viewport.
void OffsetGroundSprite(int x, int y)
Called when a foundation has been drawn for the current tile.
Definition viewport.cpp:591
constexpr int LAST_CHILD_NONE
There is no last_child to fill.
Definition viewport.cpp:166
void ResetObjectToPlace()
Reset the cursor and mouse mode handling back to default (normal cursor, only clicking in windows).
bool ScrollMainWindowToTile(TileIndex tile, bool instant)
Scrolls the viewport of the main window to a given location.
static void ViewportAddSignStrings(DrawPixelInfo *dpi, const std::vector< const Sign * > &signs, bool small)
Add sign strings to a viewport.
void SetObjectToPlaceWnd(CursorID icon, PaletteID pal, HighLightStyle mode, Window *w)
Change the cursor and mouse click/drag handling to a mode for performing special operations like tile...
constexpr int LAST_CHILD_PARENT
Fill last_child of the most recent parent sprite.
Definition viewport.cpp:167
static const int MAX_TILE_EXTENT_TOP
Maximum top extent of tile relative to north corner (not considering bridges).
Definition viewport.cpp:113
void SetViewportStationRect(const Station *st, bool sel)
Select or deselect station for rectangle area highlight.
static void ShowMeasurementTooltips(EncodedString &&text)
Displays the measurement tooltips when selecting multiple tiles.
static const int MAX_TILE_EXTENT_LEFT
Maximum left extent of tile relative to north corner.
Definition viewport.cpp:111
bool IsInsideRotatedRectangle(int x, int y)
Checks whether a point is inside the selected a diagonal rectangle given by _thd.size and _thd....
Definition viewport.cpp:801
static int CalcHeightdiff(HighLightStyle style, uint distance, TileIndex start_tile, TileIndex end_tile)
Calculates height difference between one tile and another.
static void DrawTileSelectionRect(const TileInfo *ti, PaletteID pal)
Draws a selection rectangle on a tile.
Definition viewport.cpp:914
void StartSpriteCombine()
Starts a block of sprites, which are "combined" into a single bounding box.
Definition viewport.cpp:764
void InitializeSpriteSorter()
Choose the "best" sprite sorter and set _vp_sprite_sorter.
static const ViewportSSCSS _vp_sprite_sorters[]
List of sorters ordered from best to worst.
static void AddCombinedSprite(SpriteID image, PaletteID pal, int x, int y, int z, const SubSprite *sub)
Adds a child sprite to a parent sprite.
Definition viewport.cpp:623
static int GetViewportY(Point tile)
Returns the y coordinate in the viewport coordinate system where the given tile is painted.
static void AddChildSpriteToFoundation(SpriteID image, PaletteID pal, const SubSprite *sub, FoundationPart foundation_part, int extra_offs_x, int extra_offs_y)
Adds a child sprite to the active foundation.
Definition viewport.cpp:532
static HighLightStyle GetAutorailHT(int x, int y)
returns the best autorail highlight type from map coordinates
static void ClampSmoothScroll(uint32_t delta_ms, int64_t delta_hi, int64_t delta_lo, int &delta_hi_clamped, int &delta_lo_clamped)
Clamp the smooth scroll to a maxmimum speed and distance based on time elapsed.
static void DrawTileSelection(const TileInfo *ti)
Checks if the specified tile is selected and if so draws selection using correct selectionstyle.
static TileHighlightType GetTileHighlightType(TileIndex t)
Get tile highlight type of coverage area for a given tile.
static bool IsInRangeInclusive(int begin, int end, int check)
Check if the parameter "check" is inside the interval between begin and end, including both begin and...
Definition viewport.cpp:789
const Waypoint * _viewport_highlight_waypoint_rect
Currently selected waypoint for rectangle highlight.
Point TranslateXYToTileCoord(const Viewport &vp, int x, int y, bool clamp_to_map)
Translate screen coordinate in a viewport to underlying tile coordinate.
Definition viewport.cpp:430
static void DrawAutorailSelection(const TileInfo *ti, uint autorail_type)
Draws autorail highlights.
Definition viewport.cpp:974
SpriteCombineMode
Mode of "sprite combining".
Definition viewport.cpp:155
@ SPRITE_COMBINE_PENDING
Sprite combining will start with the next unclipped sprite.
Definition viewport.cpp:157
@ SPRITE_COMBINE_ACTIVE
Sprite combining is active. AddSortableSpriteToDraw outputs child sprites.
Definition viewport.cpp:158
@ SPRITE_COMBINE_NONE
Every AddSortableSpriteToDraw start its own bounding box.
Definition viewport.cpp:156
static void DrawTileHighlightType(const TileInfo *ti, TileHighlightType tht)
Draw tile highlight for coverage area highlight.
static void ViewportDrawBoundingBoxes(const ParentSpriteToSortVector *psd)
Draws the bounding boxes of all ParentSprites.
static void ViewportAddTownStrings(DrawPixelInfo *dpi, const std::vector< const Town * > &towns, bool small)
Add town strings to a viewport.
void SetViewportCatchmentWaypoint(const Waypoint *wp, bool sel)
Select or deselect waypoint for coverage area highlight.
void SetRedErrorSquare(TileIndex tile)
Set a tile to display a red error square.
static void ViewportSortParentSprites(ParentSpriteToSortVector *psdv)
Sort parent sprites pointer array replicating the way original sorter did it.
void VpSetPresizeRange(TileIndex from, TileIndex to)
Highlights all tiles between a set of two tiles.
void VpSelectTilesWithMethod(int x, int y, ViewportPlaceMethod method)
Selects tiles while dragging.
EventState VpHandlePlaceSizingDrag()
Handle the mouse while dragging for placement/resizing.
Viewport * IsPtInWindowViewport(const Window *w, int x, int y)
Is a xy position inside the viewport of the window?
Definition viewport.cpp:408
void AddSortableSpriteToDraw(SpriteID image, PaletteID pal, int x, int y, int z, const SpriteBounds &bounds, bool transparent, const SubSprite *sub)
Draw a (transparent) sprite at given coordinates with a given bounding box.
Definition viewport.cpp:663
void SetObjectToPlace(CursorID icon, PaletteID pal, HighLightStyle mode, WindowClass window_class, WindowNumber window_num)
Change the cursor and mouse click/drag handling to a mode for performing special operations like tile...
void EndSpriteCombine()
Terminates a block of sprites started by StartSpriteCombine.
Definition viewport.cpp:774
static const int MAX_TILE_EXTENT_RIGHT
Maximum right extent of tile relative to north corner.
Definition viewport.cpp:112
void AddChildSpriteScreen(SpriteID image, PaletteID pal, int x, int y, bool transparent, const SubSprite *sub, bool scale, bool relative)
Add a child sprite to a parent sprite.
Definition viewport.cpp:824
static void CheckUnderflow(int &test, int &other, int mult)
Check for underflowing the map.
static bool ViewportSortParentSpritesChecker()
This fallback sprite checker always exists.
void DrawGroundSpriteAt(SpriteID image, PaletteID pal, int32_t x, int32_t y, int z, const SubSprite *sub, int extra_offs_x, int extra_offs_y)
Draws a ground sprite at a specific world-coordinate relative to the current tile.
Definition viewport.cpp:556
void VpStartDragging(ViewportDragDropSelectionProcess process)
Drag over the map while holding the left mouse down.
void UpdateTileSelection()
Updates tile highlighting for all cases.
static void DrawSelectionSprite(SpriteID image, PaletteID pal, const TileInfo *ti, int z_offset, FoundationPart foundation_part, int extra_offs_x=0, int extra_offs_y=0)
Draws sprites between ground sprite and everything above.
Definition viewport.cpp:897
static const int MAX_TILE_EXTENT_BOTTOM
Maximum bottom extent of tile relative to north corner (worst case: SLOPE_STEEP_N).
Definition viewport.cpp:114
static bool CheckClickOnViewportSign(const Viewport &vp, int x, int y, const ViewportSign *sign)
Test whether a sign is below the mouse.
void SetViewportCatchmentStation(const Station *st, bool sel)
Select or deselect station for coverage area highlight.
void SetViewportCatchmentTown(const Town *t, bool sel)
Select or deselect town for coverage area highlight.
static bool SwapDirection(HighLightStyle style, TileIndex start_tile, TileIndex end_tile)
Check if the direction of start and end tile should be swapped based on the dragging-style.
void InitializeWindowViewport(Window *w, int x, int y, int width, int height, std::variant< TileIndex, VehicleID > focus, ZoomLevel zoom)
Initialize viewport of the window for use.
Definition viewport.cpp:218
void DrawGroundSprite(SpriteID image, PaletteID pal, const SubSprite *sub, int extra_offs_x, int extra_offs_y)
Draws a ground sprite for the current tile.
Definition viewport.cpp:579
static void CalcRaildirsDrawstyle(int x, int y, int method)
while dragging
static HighLightStyle Check2x1AutoRail(int mode)
returns information about the 2x1 piece to be build.
void SetViewportWaypointRect(const Waypoint *wp, bool sel)
Select or deselect waypoint for rectangle area highlight.
static void ViewportDrawDirtyBlocks()
Draw/colour the blocks that have been redrawn.
void HandleZoomMessage(Window *w, const Viewport &vp, WidgetID widget_zoom_in, WidgetID widget_zoom_out)
Update the status of the zoom-buttons according to the zoom-level of the viewport.
Definition viewport.cpp:486
void VpStartPlaceSizing(TileIndex tile, ViewportPlaceMethod method, ViewportDragDropSelectionProcess process)
highlighting tiles while only going over them with the mouse
const Station * _viewport_highlight_station_rect
Currently selected station for rectangle highlight.
static void ClampViewportToMap(const Viewport &vp, int *scroll_x, int *scroll_y)
Ensure that a given viewport has a valid scroll position.
static void AddTileSpriteToDraw(SpriteID image, PaletteID pal, int32_t x, int32_t y, int z, const SubSprite *sub=nullptr, int extra_offs_x=0, int extra_offs_y=0)
Schedules a tile sprite for drawing.
Definition viewport.cpp:507
static void ViewportAddLandscape()
Add the landscape to the viewport, i.e.
static std::string & AddStringToDraw(int x, int y, Colours colour, ViewportStringFlags flags, uint16_t width)
Add a string to draw to a viewport.
Definition viewport.cpp:870
const Station * _viewport_highlight_station
Currently selected station for coverage area highlight.
const Town * _viewport_highlight_town
Currently selected town for coverage area highlight.
void UpdateViewportPosition(Window *w, uint32_t delta_ms)
Update the viewport position being displayed.
const Waypoint * _viewport_highlight_waypoint
Currently selected waypoint for coverage area highlight.
bool ScrollWindowTo(int x, int y, int z, Window *w, bool instant)
Scrolls the viewport in a window to a given location.
Command definitions related to viewports.
Functions related to (drawing on) viewports.
static const int TILE_HEIGHT_STEP
One Z unit tile height difference is displayed as 50m.
Types related to sprite sorting.
bool(* VpSorterChecker)()
Type for method for checking whether a viewport sprite sorter exists.
void(* VpSpriteSorter)(ParentSpriteToSortVector *psd)
Type for the actual viewport sprite sorter.
ViewportScrollTarget
Target of the viewport scrolling GS method.
@ VST_EVERYONE
All players.
@ VST_COMPANY
All players in specific company.
@ VST_CLIENT
Single player.
ViewportPlaceMethod
Viewport place method (type of highlighted area and placed objects)
@ VPM_FIX_Y
drag only in Y axis
@ VPM_Y_LIMITED
Drag only in Y axis with limited size.
@ VPM_X_AND_Y_LIMITED
area of land of limited size
@ VPM_FIX_VERTICAL
drag only in vertical direction
@ VPM_X_LIMITED
Drag only in X axis with limited size.
@ VPM_X_AND_Y
area of land in X and Y directions
@ VPM_FIX_HORIZONTAL
drag only in horizontal direction
@ VPM_FIX_X
drag only in X axis
@ VPM_SIGNALDIRS
similar to VMP_RAILDIRS, but with different cursor
@ VPM_X_OR_Y
drag in X or Y direction
@ VPM_RAILDIRS
all rail directions
@ ZOOM_IN
Zoom in (get more detailed view).
@ ZOOM_OUT
Zoom out (get helicopter view).
ViewportDragDropSelectionProcess
Drag and drop selection process, or, what to do with an area of land when you've selected it.
@ ColourRect
Draw a colour rect around the sign.
@ Small
Draw using the small font.
@ Shadow
Draw an extra text shadow. Should only be used with ViewportStringFlag::Small, as normal font already...
@ TransparentRect
Draw a transparent rect around the sign.
@ TextColour
Draw text in colour.
Base of waypoints.
Functions related to waypoints.
void ShowWaypointWindow(const Waypoint *wp)
Show the window for the given waypoint.
void DrawFrameRect(int left, int top, int right, int bottom, Colours colour, FrameFlags flags)
Draw frame rectangle.
Definition widget.cpp:289
void CloseWindowById(WindowClass cls, WindowNumber number, bool force, int data)
Close a window by its class and window number (if it is open).
Definition window.cpp:1193
Window * GetMainWindow()
Get the main window, i.e.
Definition window.cpp:1180
Window * FindWindowFromPt(int x, int y)
Do a search for a window at specific coordinates.
Definition window.cpp:1842
SpecialMouseMode _special_mouse_mode
Mode of the mouse.
Definition window.cpp:96
Window * FindWindowById(WindowClass cls, WindowNumber number)
Find a window by its class and window number.
Definition window.cpp:1151
void SetWindowDirty(WindowClass cls, WindowNumber number)
Mark window as dirty (in need of repainting)
Definition window.cpp:3176
Window functions not directly related to making/drawing windows.
Functions, definitions and such used only by the GUI.
@ Transparent
Makes the background transparent if set.
@ WSM_DRAGDROP
Drag&drop an object.
@ WSM_DRAGGING
Dragging mode (trees).
@ WSM_PRESIZE
Presizing mode (docks, tunnels).
@ WSM_NONE
No special mouse mode.
@ WSM_SIZING
Sizing mode.
int WidgetID
Widget ID.
Definition window_type.h:20
EventState
State of handling an event.
@ ES_HANDLED
The passed event is handled.
@ ES_NOT_HANDLED
The passed event is not handled.
WindowClass
Window classes.
Definition window_type.h:49
@ WC_INVALID
Invalid window.
@ WC_WAYPOINT_VIEW
Waypoint view; Window numbers:
@ WC_STATION_VIEW
Station view; Window numbers:
@ WC_MAIN_WINDOW
Main window; Window numbers:
Definition window_type.h:56
@ WC_TOWN_VIEW
Town view; Window numbers:
@ WC_TOOLTIPS
Tooltip window; Window numbers:
@ WC_VEHICLE_VIEW
Vehicle view; Window numbers:
Functions related to zooming.
int ScaleByZoom(int value, ZoomLevel zoom)
Scale by zoom level, usually shift left (when zoom > ZoomLevel::Min) When shifting right,...
Definition zoom_func.h:22
int UnScaleByZoomLower(int value, ZoomLevel zoom)
Scale by zoom level, usually shift right (when zoom > ZoomLevel::Min)
Definition zoom_func.h:67
int UnScaleByZoom(int value, ZoomLevel zoom)
Scale by zoom level, usually shift right (when zoom > ZoomLevel::Min) When shifting right,...
Definition zoom_func.h:34
ZoomLevel
All zoom levels we know.
Definition zoom_type.h:20
@ Begin
Begin for iteration.
@ Min
Minimum zoom level.
@ End
End for iteration.
@ Out4x
Zoomed 4 times out.