OpenTTD Source 20260421-master-gc2fbc6fdeb
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
26
62
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;
120 ViewportStringFlags flags;
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
230 vp->zoom = Clamp(zoom, _settings_client.gui.zoom_min, _settings_client.gui.zoom_max);
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)->GetMovingFront();
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 */
559 if (_vd.foundation_part == FOUNDATION_PART_NONE) _vd.foundation_part = FOUNDATION_PART_NORMAL;
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) {
596 _vd.foundation_part = FOUNDATION_PART_NORMAL;
597 break;
599 _vd.foundation_part = FOUNDATION_PART_HALFTILE;
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
658void AddSortableSpriteToDraw(SpriteID image, PaletteID pal, int x, int y, int z, const SpriteBounds &bounds, bool transparent, const SubSprite *sub)
659{
660 int32_t left, right, top, bottom;
661
662 assert((image & SPRITE_MASK) < MAX_SPRITES);
663
664 /* Move to bounding box. */
665 x += bounds.origin.x;
666 y += bounds.origin.y;
667 z += bounds.origin.z;
668
669 /* make the sprites transparent with the right palette */
670 if (transparent) {
673 }
674
675 if (_vd.combine_sprites == SPRITE_COMBINE_ACTIVE) {
676 AddCombinedSprite(image, pal, x + bounds.offset.x, y + bounds.offset.y, z + bounds.offset.z, sub);
677 return;
678 }
679
680 _vd.last_child = LAST_CHILD_NONE;
681
682 Point pt = RemapCoords(x + bounds.offset.x, y + bounds.offset.y, z + bounds.offset.z);
683 int tmp_left, tmp_top, tmp_x = pt.x, tmp_y = pt.y;
684
685 /* Compute screen extents of sprite */
686 if (image == SPR_EMPTY_BOUNDING_BOX) {
687 left = tmp_left = RemapCoords(x + bounds.extent.x, y, z).x;
688 right = RemapCoords(x, y + bounds.extent.y, z).x + 1;
689 top = tmp_top = RemapCoords(x, y, z + bounds.extent.z).y;
690 bottom = RemapCoords(x + bounds.extent.x, y + bounds.extent.y, z).y + 1;
691 } else {
692 const Sprite *spr = GetSprite(image & SPRITE_MASK, SpriteType::Normal);
693 left = tmp_left = (pt.x += spr->x_offs);
694 right = (pt.x + spr->width );
695 top = tmp_top = (pt.y += spr->y_offs);
696 bottom = (pt.y + spr->height);
697 }
698
699 if (_draw_bounding_boxes && (image != SPR_EMPTY_BOUNDING_BOX)) {
700 /* Compute maximal extents of sprite and its bounding box */
701 left = std::min(left , RemapCoords(x + bounds.extent.x, y, z).x);
702 right = std::max(right , RemapCoords(x, y + bounds.extent.y, z).x + 1);
703 top = std::min(top , RemapCoords(x, y, z + bounds.extent.z).y);
704 bottom = std::max(bottom, RemapCoords(x + bounds.extent.x, y + bounds.extent.y, z).y + 1);
705 }
706
707 /* Do not add the sprite to the viewport, if it is outside */
708 if (left >= _vd.dpi.left + _vd.dpi.width ||
709 right <= _vd.dpi.left ||
710 top >= _vd.dpi.top + _vd.dpi.height ||
711 bottom <= _vd.dpi.top) {
712 return;
713 }
714
715 ParentSpriteToDraw &ps = _vd.parent_sprites_to_draw.emplace_back();
716 ps.x = tmp_x;
717 ps.y = tmp_y;
718
719 ps.left = tmp_left;
720 ps.top = tmp_top;
721
722 ps.image = image;
723 ps.pal = pal;
724 ps.sub = sub;
725 ps.xmin = x;
726 ps.xmax = x + bounds.extent.x - 1;
727
728 ps.ymin = y;
729 ps.ymax = y + bounds.extent.y - 1;
730
731 ps.zmin = z;
732 ps.zmax = z + bounds.extent.z - 1;
733
735
736 _vd.last_child = LAST_CHILD_PARENT;
737
738 if (_vd.combine_sprites == SPRITE_COMBINE_PENDING) _vd.combine_sprites = SPRITE_COMBINE_ACTIVE;
739}
740
760{
761 assert(_vd.combine_sprites == SPRITE_COMBINE_NONE);
762 _vd.combine_sprites = SPRITE_COMBINE_PENDING;
763}
764
770{
771 assert(_vd.combine_sprites != SPRITE_COMBINE_NONE);
772 _vd.combine_sprites = SPRITE_COMBINE_NONE;
773}
774
785static bool IsInRangeInclusive(int begin, int end, int check)
786{
787 if (begin > end) std::swap(begin, end);
788 return begin <= check && check <= end;
789}
790
797bool IsInsideRotatedRectangle(int x, int y)
798{
799 int dist_a = (_thd.size.x + _thd.size.y); // Rotated coordinate system for selected rectangle.
800 int dist_b = (_thd.size.x - _thd.size.y); // We don't have to divide by 2. It's all relative!
801 int a = ((x - _thd.pos.x) + (y - _thd.pos.y)); // Rotated coordinate system for the point under scrutiny.
802 int b = ((x - _thd.pos.x) - (y - _thd.pos.y));
803
804 /* Check if a and b are between 0 and dist_a or dist_b respectively. */
805 return IsInRangeInclusive(dist_a, 0, a) && IsInRangeInclusive(dist_b, 0, b);
806}
807
820void AddChildSpriteScreen(SpriteID image, PaletteID pal, int x, int y, bool transparent, const SubSprite *sub, bool scale, bool relative)
821{
822 assert((image & SPRITE_MASK) < MAX_SPRITES);
823
824 /* If the ParentSprite was clipped by the viewport bounds, do not draw the ChildSprites either */
825 if (_vd.last_child == LAST_CHILD_NONE) return;
826
827 /* make the sprites transparent with the right palette */
828 if (transparent) {
831 }
832
833 int32_t child_id = static_cast<int32_t>(_vd.child_screen_sprites_to_draw.size());
834 if (_vd.last_child != LAST_CHILD_PARENT) {
835 _vd.child_screen_sprites_to_draw[_vd.last_child].next = child_id;
836 } else {
837 _vd.parent_sprites_to_draw.back().first_child = child_id;
838 }
839
840 ChildScreenSpriteToDraw &cs = _vd.child_screen_sprites_to_draw.emplace_back();
841 cs.image = image;
842 cs.pal = pal;
843 cs.sub = sub;
844 cs.x = scale ? x * ZOOM_BASE : x;
845 cs.y = scale ? y * ZOOM_BASE : y;
846 cs.relative = relative;
848
849 /* Append the sprite to the active ChildSprite list.
850 * If the active ParentSprite is a foundation, update last_foundation_child as well.
851 * Note: ChildSprites of foundations are NOT sequential in the vector, as selection sprites are added at last. */
852 if (_vd.last_foundation_child[0] == _vd.last_child) _vd.last_foundation_child[0] = child_id;
853 if (_vd.last_foundation_child[1] == _vd.last_child) _vd.last_foundation_child[1] = child_id;
854 _vd.last_child = child_id;
855}
856
866static std::string &AddStringToDraw(int x, int y, Colours colour, ViewportStringFlags flags, uint16_t width)
867{
868 assert(width != 0);
869 StringSpriteToDraw &ss = _vd.string_sprites_to_draw.emplace_back();
870 ss.colour = colour;
871 ss.flags = flags;
872 ss.x = x;
873 ss.y = y;
874 ss.width = width;
875
876 return ss.string;
877}
878
879
893static 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)
894{
895 if (_vd.foundation[foundation_part] == -1) {
896 /* draw on real ground */
897 AddTileSpriteToDraw(image, pal, ti->x, ti->y, ti->z + z_offset, nullptr, extra_offs_x, extra_offs_y);
898 } else {
899 /* draw on top of foundation */
900 AddChildSpriteToFoundation(image, pal, nullptr, foundation_part, extra_offs_x, extra_offs_y - z_offset * ZOOM_BASE);
901 }
902}
903
910static void DrawTileSelectionRect(const TileInfo *ti, PaletteID pal)
911{
912 if (!IsValidTile(ti->tile)) return;
913
914 SpriteID sel;
915 if (IsHalftileSlope(ti->tileh)) {
916 Corner halftile_corner = GetHalftileSlopeCorner(ti->tileh);
917 SpriteID sel2 = SPR_HALFTILE_SELECTION_FLAT + halftile_corner;
919
920 Corner opposite_corner = OppositeCorner(halftile_corner);
921 if (IsSteepSlope(ti->tileh)) {
922 sel = SPR_HALFTILE_SELECTION_DOWN;
923 } else {
924 sel = ((ti->tileh & SlopeWithOneCornerRaised(opposite_corner)) != 0 ? SPR_HALFTILE_SELECTION_UP : SPR_HALFTILE_SELECTION_FLAT);
925 }
926 sel += opposite_corner;
927 } else {
928 sel = SPR_SELECT_TILE + SlopeToSpriteOffset(ti->tileh);
929 }
931}
932
933static bool IsPartOfAutoLine(int px, int py)
934{
935 px -= _thd.selstart.x;
936 py -= _thd.selstart.y;
937
938 if ((_thd.drawstyle & HT_DRAG_MASK) != HT_LINE) return false;
939
940 switch (_thd.drawstyle & HT_DIR_MASK) {
941 case HT_DIR_X: return py == 0; // x direction
942 case HT_DIR_Y: return px == 0; // y direction
943 case HT_DIR_HU: return px == -py || px == -py - 16; // horizontal upper
944 case HT_DIR_HL: return px == -py || px == -py + 16; // horizontal lower
945 case HT_DIR_VL: return px == py || px == py + 16; // vertical left
946 case HT_DIR_VR: return px == py || px == py - 16; // vertical right
947 default:
948 NOT_REACHED();
949 }
950}
951
952/* [direction][side] */
953static const HighLightStyle _autorail_type[6][2] = {
954 { HT_DIR_X, HT_DIR_X },
955 { HT_DIR_Y, HT_DIR_Y },
956 { HT_DIR_HU, HT_DIR_HL },
957 { HT_DIR_HL, HT_DIR_HU },
958 { HT_DIR_VL, HT_DIR_VR },
960};
961
962#include "table/autorail.h"
963
970static void DrawAutorailSelection(const TileInfo *ti, uint autorail_type)
971{
972 SpriteID image;
973 PaletteID pal;
974 int offset;
975
976 FoundationPart foundation_part = FOUNDATION_PART_NORMAL;
977 Slope autorail_tileh = RemoveHalftileSlope(ti->tileh);
978 if (IsHalftileSlope(ti->tileh)) {
979 static const uint _lower_rail[4] = { 5U, 2U, 4U, 3U };
980 Corner halftile_corner = GetHalftileSlopeCorner(ti->tileh);
981 if (autorail_type != _lower_rail[halftile_corner]) {
982 foundation_part = FOUNDATION_PART_HALFTILE;
983 /* Here we draw the highlights of the "three-corners-raised"-slope. That looks ok to me. */
984 autorail_tileh = SlopeWithThreeCornersRaised(OppositeCorner(halftile_corner));
985 }
986 }
987
988 offset = _AutorailTilehSprite[autorail_tileh][autorail_type];
989 if (offset >= 0) {
990 image = SPR_AUTORAIL_BASE + offset;
991 pal = PAL_NONE;
992 } else {
993 image = SPR_AUTORAIL_BASE - offset;
995 }
996
997 DrawSelectionSprite(image, _thd.make_square_red ? PALETTE_SEL_TILE_RED : pal, ti, 7, foundation_part);
998}
999
1000enum TileHighlightType : uint8_t {
1001 THT_NONE,
1002 THT_WHITE,
1003 THT_BLUE,
1004 THT_RED,
1005};
1006
1012
1018static TileHighlightType GetTileHighlightType(TileIndex t)
1019{
1020 if (_viewport_highlight_station != nullptr) {
1021 if (IsTileType(t, TileType::Station) && GetStationIndex(t) == _viewport_highlight_station->index) return THT_WHITE;
1022 if (_viewport_highlight_station->TileIsInCatchment(t)) return THT_BLUE;
1023 }
1024
1025 if (_viewport_highlight_station_rect != nullptr) {
1026 if (IsTileType(t, TileType::Station) && GetStationIndex(t) == _viewport_highlight_station_rect->index) return THT_WHITE;
1028 if (r->PtInExtendedRect(TileX(t), TileY(t))) return THT_BLUE;
1029 }
1030
1031 if (_viewport_highlight_waypoint != nullptr) {
1032 if (IsTileType(t, TileType::Station) && GetStationIndex(t) == _viewport_highlight_waypoint->index) return THT_BLUE;
1033 }
1034
1035 if (_viewport_highlight_waypoint_rect != nullptr) {
1036 if (IsTileType(t, TileType::Station) && GetStationIndex(t) == _viewport_highlight_waypoint_rect->index) return THT_WHITE;
1038 if (r->PtInExtendedRect(TileX(t), TileY(t))) return THT_BLUE;
1039 }
1040
1041 if (_viewport_highlight_town != nullptr) {
1042 if (IsTileType(t, TileType::House)) {
1043 if (GetTownIndex(t) == _viewport_highlight_town->index) {
1044 TileHighlightType type = THT_RED;
1045 for (const Station *st : _viewport_highlight_town->stations_near) {
1046 if (st->owner != _current_company) continue;
1047 if (st->TileIsInCatchment(t)) return THT_BLUE;
1048 }
1049 return type;
1050 }
1051 } else if (IsTileType(t, TileType::Station)) {
1052 for (const Station *st : _viewport_highlight_town->stations_near) {
1053 if (st->owner != _current_company) continue;
1054 if (GetStationIndex(t) == st->index) return THT_WHITE;
1055 }
1056 }
1057 }
1058
1059 return THT_NONE;
1060}
1061
1067static void DrawTileHighlightType(const TileInfo *ti, TileHighlightType tht)
1068{
1069 switch (tht) {
1070 default:
1071 case THT_NONE: break;
1072 case THT_WHITE: DrawTileSelectionRect(ti, PAL_NONE); break;
1073 case THT_BLUE: DrawTileSelectionRect(ti, PALETTE_SEL_TILE_BLUE); break;
1074 case THT_RED: DrawTileSelectionRect(ti, PALETTE_SEL_TILE_RED); break;
1075 }
1076}
1077
1083{
1084 /* Going through cases in order of computational time. */
1085
1086 if (_town_local_authority_kdtree.Count() == 0) return;
1087
1088 /* Tile belongs to town regardless of distance from town. */
1089 if (GetTileType(ti->tile) == TileType::House) {
1090 if (!Town::GetByTile(ti->tile)->show_zone) return;
1091
1093 return;
1094 }
1095
1096 /* If the closest town in the highlighted list is far, we can stop searching. */
1097 TownID tid = _town_local_authority_kdtree.FindNearest(TileX(ti->tile), TileY(ti->tile));
1098 Town *closest_highlighted_town = Town::Get(tid);
1099
1100 if (DistanceManhattan(ti->tile, closest_highlighted_town->xy) >= _settings_game.economy.dist_local_authority) return;
1101
1102 /* Tile is inside of the local autrhority distance of a highlighted town,
1103 but it is possible that a non-highlighted town is even closer. */
1104 Town *closest_town = ClosestTownFromTile(ti->tile, _settings_game.economy.dist_local_authority);
1105
1106 if (closest_town->show_zone) {
1108 }
1109
1110}
1111
1116static void DrawTileSelection(const TileInfo *ti)
1117{
1118 /* Highlight tiles inside local authority of selected towns. */
1120
1121 /* Draw a red error square? */
1122 bool is_redsq = _thd.redsq == ti->tile;
1124
1125 TileHighlightType tht = GetTileHighlightType(ti->tile);
1126 DrawTileHighlightType(ti, tht);
1127
1128 /* No tile selection active? */
1129 if ((_thd.drawstyle & HT_DRAG_MASK) == HT_NONE) return;
1130
1131 if (_thd.diagonal) { // We're drawing a 45 degrees rotated (diagonal) rectangle
1132 if (IsInsideRotatedRectangle((int)ti->x, (int)ti->y)) goto draw_inner;
1133 return;
1134 }
1135
1136 /* Inside the inner area? */
1137 if (IsInsideBS(ti->x, _thd.pos.x, _thd.size.x) &&
1138 IsInsideBS(ti->y, _thd.pos.y, _thd.size.y)) {
1139draw_inner:
1140 if (_thd.drawstyle & HT_RECT) {
1141 if (!is_redsq) DrawTileSelectionRect(ti, _thd.make_square_red ? PALETTE_SEL_TILE_RED : PAL_NONE);
1142 } else if (_thd.drawstyle & HT_POINT) {
1143 /* Figure out the Z coordinate for the single dot. */
1144 int z = 0;
1145 FoundationPart foundation_part = FOUNDATION_PART_NORMAL;
1146 if (ti->tileh & SLOPE_N) {
1147 z += TILE_HEIGHT;
1149 }
1150 if (IsHalftileSlope(ti->tileh)) {
1151 Corner halftile_corner = GetHalftileSlopeCorner(ti->tileh);
1152 if ((halftile_corner == CORNER_W) || (halftile_corner == CORNER_E)) z += TILE_HEIGHT;
1153 if (halftile_corner != CORNER_S) {
1154 foundation_part = FOUNDATION_PART_HALFTILE;
1155 if (IsSteepSlope(ti->tileh)) z -= TILE_HEIGHT;
1156 }
1157 }
1158 DrawSelectionSprite(SPR_DOT, PAL_NONE, ti, z, foundation_part);
1159 } else if (_thd.drawstyle & HT_RAIL) {
1160 /* autorail highlight piece under cursor */
1161 HighLightStyle type = _thd.drawstyle & HT_DIR_MASK;
1162 assert(type < HT_DIR_END);
1163 DrawAutorailSelection(ti, _autorail_type[type][0]);
1164 } else if (IsPartOfAutoLine(ti->x, ti->y)) {
1165 /* autorail highlighting long line */
1166 HighLightStyle dir = _thd.drawstyle & HT_DIR_MASK;
1167 uint side;
1168
1169 if (dir == HT_DIR_X || dir == HT_DIR_Y) {
1170 side = 0;
1171 } else {
1172 TileIndex start = TileVirtXY(_thd.selstart.x, _thd.selstart.y);
1173 side = Delta(Delta(TileX(start), TileX(ti->tile)), Delta(TileY(start), TileY(ti->tile)));
1174 }
1175
1176 DrawAutorailSelection(ti, _autorail_type[dir][side]);
1177 }
1178 return;
1179 }
1180
1181 /* Check if it's inside the outer area? */
1182 if (!is_redsq && (tht == THT_NONE || tht == THT_RED) && _thd.outersize.x > 0 &&
1183 IsInsideBS(ti->x, _thd.pos.x + _thd.offs.x, _thd.size.x + _thd.outersize.x) &&
1184 IsInsideBS(ti->y, _thd.pos.y + _thd.offs.y, _thd.size.y + _thd.outersize.y)) {
1185 /* Draw a blue rect. */
1187 return;
1188 }
1189}
1190
1197static int GetViewportY(Point tile)
1198{
1199 /* Each increment in X or Y direction moves down by half a tile, i.e. TILE_PIXELS / 2. */
1200 return (tile.y * (int)(TILE_PIXELS / 2) + tile.x * (int)(TILE_PIXELS / 2) - TilePixelHeightOutsideMap(tile.x, tile.y)) << ZOOM_BASE_SHIFT;
1201}
1202
1207{
1208 assert(_vd.dpi.top <= _vd.dpi.top + _vd.dpi.height);
1209 assert(_vd.dpi.left <= _vd.dpi.left + _vd.dpi.width);
1210
1211 Point upper_left = InverseRemapCoords(_vd.dpi.left, _vd.dpi.top);
1212 Point upper_right = InverseRemapCoords(_vd.dpi.left + _vd.dpi.width, _vd.dpi.top);
1213
1214 /* Transformations between tile coordinates and viewport rows/columns: See vp_column_row
1215 * column = y - x
1216 * row = x + y
1217 * x = (row - column) / 2
1218 * y = (row + column) / 2
1219 * Note: (row, columns) pairs are only valid, if they are both even or both odd.
1220 */
1221
1222 /* Columns overlap with neighbouring columns by a half tile.
1223 * - Left column is column of upper_left (rounded down) and one column to the left.
1224 * - Right column is column of upper_right (rounded up) and one column to the right.
1225 * Note: Integer-division does not round down for negative numbers, so ensure rounding with another increment/decrement.
1226 */
1227 int left_column = (upper_left.y - upper_left.x) / (int)TILE_SIZE - 2;
1228 int right_column = (upper_right.y - upper_right.x) / (int)TILE_SIZE + 2;
1229
1230 int potential_bridge_height = ZOOM_BASE * TILE_HEIGHT * _settings_game.construction.max_bridge_height;
1231
1232 /* Rows overlap with neighbouring rows by a half tile.
1233 * The first row that could possibly be visible is the row above upper_left (if it is at height 0).
1234 * Due to integer-division not rounding down for negative numbers, we need another decrement.
1235 */
1236 int row = (upper_left.x + upper_left.y) / (int)TILE_SIZE - 2;
1237 bool last_row = false;
1238 for (; !last_row; row++) {
1239 last_row = true;
1240 for (int column = left_column; column <= right_column; column++) {
1241 /* Valid row/column? */
1242 if ((row + column) % 2 != 0) continue;
1243
1244 Point tilecoord;
1245 tilecoord.x = (row - column) / 2;
1246 tilecoord.y = (row + column) / 2;
1247 assert(column == tilecoord.y - tilecoord.x);
1248 assert(row == tilecoord.y + tilecoord.x);
1249
1250 TileType tile_type;
1251 _cur_ti.x = tilecoord.x * TILE_SIZE;
1252 _cur_ti.y = tilecoord.y * TILE_SIZE;
1253
1254 if (IsInsideBS(tilecoord.x, 0, Map::SizeX()) && IsInsideBS(tilecoord.y, 0, Map::SizeY())) {
1255 /* This includes the south border at Map::MaxX / Map::MaxY. When terraforming we still draw tile selections there. */
1256 _cur_ti.tile = TileXY(tilecoord.x, tilecoord.y);
1257 tile_type = GetTileType(_cur_ti.tile);
1258 } else {
1259 _cur_ti.tile = INVALID_TILE;
1260 tile_type = TileType::Void;
1261 }
1262
1263 if (tile_type != TileType::Void) {
1264 /* We are inside the map => paint landscape. */
1265 std::tie(_cur_ti.tileh, _cur_ti.z) = GetTilePixelSlope(_cur_ti.tile);
1266 } else {
1267 /* We are outside the map => paint black. */
1268 std::tie(_cur_ti.tileh, _cur_ti.z) = GetTilePixelSlopeOutsideMap(tilecoord.x, tilecoord.y);
1269 }
1270
1271 int viewport_y = GetViewportY(tilecoord);
1272
1273 if (viewport_y + MAX_TILE_EXTENT_BOTTOM < _vd.dpi.top) {
1274 /* The tile in this column is not visible yet.
1275 * Tiles in other columns may be visible, but we need more rows in any case. */
1276 last_row = false;
1277 continue;
1278 }
1279
1280 int min_visible_height = viewport_y - (_vd.dpi.top + _vd.dpi.height);
1281 bool tile_visible = min_visible_height <= 0;
1282
1283 if (tile_type != TileType::Void) {
1284 /* Is tile with buildings visible? */
1285 if (min_visible_height < MAX_TILE_EXTENT_TOP) tile_visible = true;
1286
1287 if (IsBridgeAbove(_cur_ti.tile)) {
1288 /* Is the bridge visible? */
1289 TileIndex bridge_tile = GetNorthernBridgeEnd(_cur_ti.tile);
1290 int bridge_height = ZOOM_BASE * (GetBridgePixelHeight(bridge_tile) - TilePixelHeight(_cur_ti.tile));
1291 if (min_visible_height < bridge_height + MAX_TILE_EXTENT_TOP) tile_visible = true;
1292 }
1293
1294 /* Would a higher bridge on a more southern tile be visible?
1295 * If yes, we need to loop over more rows to possibly find one. */
1296 if (min_visible_height < potential_bridge_height + MAX_TILE_EXTENT_TOP) last_row = false;
1297 } else {
1298 /* Outside of map. If we are on the north border of the map, there may still be a bridge visible,
1299 * so we need to loop over more rows to possibly find one. */
1300 if ((tilecoord.x <= 0 || tilecoord.y <= 0) && min_visible_height < potential_bridge_height + MAX_TILE_EXTENT_TOP) last_row = false;
1301 }
1302
1303 if (tile_visible) {
1304 last_row = false;
1305 _vd.foundation_part = FOUNDATION_PART_NONE;
1306 _vd.foundation[0] = -1;
1307 _vd.foundation[1] = -1;
1308 _vd.last_foundation_child[0] = LAST_CHILD_NONE;
1309 _vd.last_foundation_child[1] = LAST_CHILD_NONE;
1310
1311 _tile_type_procs[tile_type]->draw_tile_proc(&_cur_ti);
1312 if (_cur_ti.tile != INVALID_TILE) DrawTileSelection(&_cur_ti);
1313 }
1314 }
1315 }
1316}
1317
1326std::string *ViewportAddString(const DrawPixelInfo *dpi, const ViewportSign *sign, ViewportStringFlags flags, Colours colour)
1327{
1328 int left = dpi->left;
1329 int top = dpi->top;
1330 int right = left + dpi->width;
1331 int bottom = top + dpi->height;
1332
1333 bool small = flags.Test(ViewportStringFlag::Small);
1334 int sign_height = ScaleByZoom(WidgetDimensions::scaled.fullbevel.top + GetCharacterHeight(small ? FontSize::Small : FontSize::Normal) + WidgetDimensions::scaled.fullbevel.bottom, dpi->zoom);
1335 int sign_half_width = ScaleByZoom((small ? sign->width_small : sign->width_normal) / 2, dpi->zoom);
1336
1337 if (bottom < sign->top ||
1338 top > sign->top + sign_height ||
1339 right < sign->center - sign_half_width ||
1340 left > sign->center + sign_half_width) {
1341 return nullptr;
1342 }
1343
1344 return &AddStringToDraw(sign->center - sign_half_width, sign->top, colour, flags, small ? sign->width_small : sign->width_normal);
1345}
1346
1347static Rect ExpandRectWithViewportSignMargins(Rect r, ZoomLevel zoom)
1348{
1350 const int max_tw = _viewport_sign_maxwidth / 2 + 1;
1351 const int expand_y = ScaleByZoom(WidgetDimensions::scaled.fullbevel.top + fh + WidgetDimensions::scaled.fullbevel.bottom, zoom);
1352 const int expand_x = ScaleByZoom(WidgetDimensions::scaled.fullbevel.left + max_tw + WidgetDimensions::scaled.fullbevel.right, zoom);
1353
1354 r.left -= expand_x;
1355 r.right += expand_x;
1356 r.top -= expand_y;
1357 r.bottom += expand_y;
1358
1359 return r;
1360}
1361
1368static void ViewportAddTownStrings(DrawPixelInfo *dpi, const std::vector<const Town *> &towns, bool small)
1369{
1370 ViewportStringFlags flags{};
1372
1373 StringID stringid_town = !small && _settings_client.gui.population_in_label ? STR_VIEWPORT_TOWN_POP : STR_TOWN_NAME;
1374 StringID stringid_town_city = stringid_town;
1375 if (!small) {
1376 stringid_town_city = _settings_client.gui.population_in_label ? STR_VIEWPORT_TOWN_CITY_POP : STR_VIEWPORT_TOWN_CITY;
1377 }
1378
1379 for (const Town *t : towns) {
1380 std::string *str = ViewportAddString(dpi, &t->cache.sign, flags, Colours::Invalid);
1381 if (str == nullptr) continue;
1382
1383 if (t->larger_town) {
1384 *str = GetString(stringid_town_city, t->index, t->cache.population);
1385 } else {
1386 *str = GetString(stringid_town, t->index, t->cache.population);
1387 }
1388 }
1389}
1390
1397static void ViewportAddSignStrings(DrawPixelInfo *dpi, const std::vector<const Sign *> &signs, bool small)
1398{
1399 ViewportStringFlags flags{};
1400 if (small) flags.Set(ViewportStringFlag::Small);
1401
1402 /* Signs placed by a game script don't have a frame. */
1403 ViewportStringFlags deity_flags{ flags };
1405
1407
1408 for (const Sign *si : signs) {
1409 /* Workaround to make sure white is actually white. The string drawing logic changes all
1410 * colours that are not Colours::Invalid slightly, turning white into a light gray. */
1411 const Colours deity_colour = si->text_colour == Colours::White ? Colours::Invalid : si->text_colour;
1412
1413 std::string *str = ViewportAddString(dpi, &si->sign, (si->owner == OWNER_DEITY) ? deity_flags : flags,
1414 (si->owner == OWNER_NONE) ? Colours::Grey : (si->owner == OWNER_DEITY ? deity_colour : _company_colours[si->owner]));
1415 if (str == nullptr) continue;
1416
1417 *str = GetString(STR_SIGN_NAME, si->index);
1418 }
1419}
1420
1427static void ViewportAddStationStrings(DrawPixelInfo *dpi, const std::vector<const BaseStation *> &stations, bool small)
1428{
1429 /* Transparent station signs have colour text instead of a colour panel. */
1431 if (small) flags.Set(ViewportStringFlag::Small);
1432
1433 for (const BaseStation *st : stations) {
1434 std::string *str = ViewportAddString(dpi, &st->sign, flags, (st->owner == OWNER_NONE || !st->IsInUse()) ? Colours::Grey : _company_colours[st->owner]);
1435 if (str == nullptr) continue;
1436
1437 if (Station::IsExpected(st)) { /* Station */
1438 *str = GetString(small ? STR_STATION_NAME : STR_VIEWPORT_STATION, st->index, st->facilities);
1439 } else { /* Waypoint */
1440 *str = GetString(STR_WAYPOINT_NAME, st->index);
1441 }
1442 }
1443}
1444
1445static void ViewportAddKdtreeSigns(DrawPixelInfo *dpi)
1446{
1447 Rect search_rect{ dpi->left, dpi->top, dpi->left + dpi->width, dpi->top + dpi->height };
1448 search_rect = ExpandRectWithViewportSignMargins(search_rect, dpi->zoom);
1449
1450 bool show_stations = HasBit(_display_opt, DO_SHOW_STATION_NAMES) && _game_mode != GM_MENU;
1451 bool show_waypoints = HasBit(_display_opt, DO_SHOW_WAYPOINT_NAMES) && _game_mode != GM_MENU;
1452 bool show_towns = HasBit(_display_opt, DO_SHOW_TOWN_NAMES) && _game_mode != GM_MENU;
1454 bool show_competitors = HasBit(_display_opt, DO_SHOW_COMPETITOR_SIGNS);
1455
1456 /* Collect all the items first and draw afterwards, to ensure layering */
1457 std::vector<const BaseStation *> stations;
1458 std::vector<const Town *> towns;
1459 std::vector<const Sign *> signs;
1460
1461 _viewport_sign_kdtree.FindContained(search_rect.left, search_rect.top, search_rect.right, search_rect.bottom, [&](const ViewportSignKdtreeItem & item) {
1462 switch (item.type) {
1463 case ViewportSignKdtreeItem::VKI_STATION: {
1464 if (!show_stations) break;
1465 const BaseStation *st = BaseStation::Get(std::get<StationID>(item.id));
1466
1467 /* If no facilities are present the station is a ghost station. */
1468 StationFacilities facilities = st->facilities;
1469 if (facilities.None()) facilities = STATION_FACILITY_GHOST;
1470
1471 if (!facilities.Any(_facility_display_opt)) break;
1472
1473 /* Don't draw if station is owned by another company and competitor station names are hidden. Stations owned by none are never ignored. */
1474 if (!show_competitors && _local_company != st->owner && st->owner != OWNER_NONE) break;
1475
1476 stations.push_back(st);
1477 break;
1478 }
1479
1480 case ViewportSignKdtreeItem::VKI_WAYPOINT: {
1481 if (!show_waypoints) break;
1482 const BaseStation *st = BaseStation::Get(std::get<StationID>(item.id));
1483
1484 /* Don't draw if station is owned by another company and competitor station names are hidden. Stations owned by none are never ignored. */
1485 if (!show_competitors && _local_company != st->owner && st->owner != OWNER_NONE) break;
1486
1487 stations.push_back(st);
1488 break;
1489 }
1490
1491 case ViewportSignKdtreeItem::VKI_TOWN:
1492 if (!show_towns) break;
1493 towns.push_back(Town::Get(std::get<TownID>(item.id)));
1494 break;
1495
1496 case ViewportSignKdtreeItem::VKI_SIGN: {
1497 if (!show_signs) break;
1498 const Sign *si = Sign::Get(std::get<SignID>(item.id));
1499
1500 /* Don't draw if sign is owned by another company and competitor signs should be hidden.
1501 * Note: It is intentional that also signs owned by OWNER_NONE are hidden. Bankrupt
1502 * companies can leave OWNER_NONE signs after them. */
1503 if (!show_competitors && _local_company != si->owner && si->owner != OWNER_DEITY) break;
1504
1505 signs.push_back(si);
1506 break;
1507 }
1508
1509 default:
1510 NOT_REACHED();
1511 }
1512 });
1513
1514 /* Small versions of signs are used zoom level 4X and higher. */
1515 bool small = dpi->zoom >= ZoomLevel::Out4x;
1516
1517 /* Layering order (bottom to top): Town names, signs, stations */
1518 ViewportAddTownStrings(dpi, towns, small);
1519
1520 /* Do not draw signs nor station names if they are set invisible */
1521 if (IsInvisibilitySet(TO_SIGNS)) return;
1522
1523 ViewportAddSignStrings(dpi, signs, small);
1524 ViewportAddStationStrings(dpi, stations, small);
1525}
1526
1527
1535void ViewportSign::UpdatePosition(int center, int top, std::string_view str, std::string_view str_small)
1536{
1537 if (this->width_normal != 0) this->MarkDirty();
1538
1539 this->top = top;
1540
1541 this->width_normal = WidgetDimensions::scaled.fullbevel.left + Align(GetStringBoundingBox(str).width, 2) + WidgetDimensions::scaled.fullbevel.right;
1542 this->center = center;
1543
1544 /* zoomed out version */
1545 if (str_small.empty()) str_small = str;
1546 this->width_small = WidgetDimensions::scaled.fullbevel.left + Align(GetStringBoundingBox(str_small, FontSize::Small).width, 2) + WidgetDimensions::scaled.fullbevel.right;
1547
1548 this->MarkDirty();
1549}
1550
1558{
1559 Rect zoomlevels[to_underlying(ZoomLevel::End)];
1560
1561 /* We don't know which size will be drawn, so mark the largest area dirty. */
1562 const uint half_width = std::max(this->width_normal, this->width_small) / 2 + 1;
1563 const uint height = WidgetDimensions::scaled.fullbevel.top + std::max(GetCharacterHeight(FontSize::Normal), GetCharacterHeight(FontSize::Small)) + WidgetDimensions::scaled.fullbevel.bottom + 1;
1564
1565 for (ZoomLevel zoom = ZoomLevel::Begin; zoom != ZoomLevel::End; zoom++) {
1566 zoomlevels[to_underlying(zoom)].left = this->center - ScaleByZoom(half_width, zoom);
1567 zoomlevels[to_underlying(zoom)].top = this->top - ScaleByZoom(1, zoom);
1568 zoomlevels[to_underlying(zoom)].right = this->center + ScaleByZoom(half_width, zoom);
1569 zoomlevels[to_underlying(zoom)].bottom = this->top + ScaleByZoom(height, zoom);
1570 }
1571
1572 for (const Window *w : Window::Iterate()) {
1573 if (w->viewport == nullptr) continue;
1574
1575 Viewport &vp = *w->viewport;
1576 if (vp.zoom <= maxzoom) {
1577 assert(vp.width != 0);
1578 Rect &zl = zoomlevels[to_underlying(vp.zoom)];
1579 MarkViewportDirty(vp, zl.left, zl.top, zl.right, zl.bottom);
1580 }
1581 }
1582}
1583
1584static void ViewportDrawTileSprites(const TileSpriteToDrawVector *tstdv)
1585{
1586 for (const TileSpriteToDraw &ts : *tstdv) {
1587 DrawSpriteViewport(ts.image, ts.pal, ts.x, ts.y, ts.sub);
1588 }
1589}
1590
1595static void ViewportSortParentSprites(ParentSpriteToSortVector *psdv)
1596{
1597 if (psdv->size() < 2) return;
1598
1599 /* We rely on sprites being, for the most part, already ordered.
1600 * So we don't need to move many of them and can keep track of their
1601 * order efficiently by using stack. We always move sprites to the front
1602 * of the current position, i.e. to the top of the stack.
1603 * Also use special constants to indicate sorting state without
1604 * adding extra fields to ParentSpriteToDraw structure.
1605 */
1606 const uint32_t ORDER_COMPARED = UINT32_MAX; // Sprite was compared but we still need to compare the ones preceding it
1607 const uint32_t ORDER_RETURNED = UINT32_MAX - 1; // Mark sorted sprite in case there are other occurrences of it in the stack
1608 std::stack<ParentSpriteToDraw *> sprite_order;
1609 uint32_t next_order = 0;
1610
1611 std::forward_list<std::pair<int64_t, ParentSpriteToDraw *>> sprite_list; // We store sprites in a list sorted by xmin+ymin
1612
1613 /* Initialize sprite list and order. */
1614 for (auto p = psdv->rbegin(); p != psdv->rend(); p++) {
1615 sprite_list.emplace_front((*p)->xmin + (*p)->ymin, *p);
1616 sprite_order.push(*p);
1617 (*p)->order = next_order++;
1618 }
1619
1620 sprite_list.sort();
1621
1622 std::vector<ParentSpriteToDraw *> preceding; // Temporarily stores sprites that precede current and their position in the list
1623 auto preceding_prev = sprite_list.begin(); // Store iterator in case we need to delete a single preceding sprite
1624 auto out = psdv->begin(); // Iterator to output sorted sprites
1625
1626 while (!sprite_order.empty()) {
1627
1628 auto s = sprite_order.top();
1629 sprite_order.pop();
1630
1631 /* Sprite is already sorted, ignore it. */
1632 if (s->order == ORDER_RETURNED) continue;
1633
1634 /* Sprite was already compared, just need to output it. */
1635 if (s->order == ORDER_COMPARED) {
1636 *(out++) = s;
1637 s->order = ORDER_RETURNED;
1638 continue;
1639 }
1640
1641 preceding.clear();
1642
1643 /* We only need sprites with xmin <= s->xmax && ymin <= s->ymax && zmin <= s->zmax
1644 * So by iterating sprites with xmin + ymin <= s->xmax + s->ymax
1645 * we get all we need and some more that we filter out later.
1646 * We don't include zmin into the sum as there are usually more neighbours on x and y than z
1647 * so including it will actually increase the amount of false positives.
1648 * Also min coordinates can be > max so using max(xmin, xmax) + max(ymin, ymax)
1649 * to ensure that we iterate the current sprite as we need to remove it from the list.
1650 */
1651 auto ssum = std::max(s->xmax, s->xmin) + std::max(s->ymax, s->ymin);
1652 auto prev = sprite_list.before_begin();
1653 auto x = sprite_list.begin();
1654 while (x != sprite_list.end() && x->first <= ssum) {
1655 auto p = x->second;
1656 if (p == s) {
1657 /* We found the current sprite, remove it and move on. */
1658 x = sprite_list.erase_after(prev);
1659 continue;
1660 }
1661
1662 auto p_prev = prev;
1663 prev = x++;
1664
1665 if (s->xmax < p->xmin || s->ymax < p->ymin || s->zmax < p->zmin) continue;
1666 if (s->xmin <= p->xmax && // overlap in X?
1667 s->ymin <= p->ymax && // overlap in Y?
1668 s->zmin <= p->zmax) { // overlap in Z?
1669 if (s->xmin + s->xmax + s->ymin + s->ymax + s->zmin + s->zmax <=
1670 p->xmin + p->xmax + p->ymin + p->ymax + p->zmin + p->zmax) {
1671 continue;
1672 }
1673 }
1674 preceding.push_back(p);
1675 preceding_prev = p_prev;
1676 }
1677
1678 if (preceding.empty()) {
1679 /* No preceding sprites, add current one to the output */
1680 *(out++) = s;
1681 s->order = ORDER_RETURNED;
1682 continue;
1683 }
1684
1685 /* Optimization for the case when we only have 1 sprite to move. */
1686 if (preceding.size() == 1) {
1687 auto p = preceding[0];
1688 /* We can only output the preceding sprite if there can't be any other sprites preceding it. */
1689 if (p->xmax <= s->xmax && p->ymax <= s->ymax && p->zmax <= s->zmax) {
1690 p->order = ORDER_RETURNED;
1691 s->order = ORDER_RETURNED;
1692 sprite_list.erase_after(preceding_prev);
1693 *(out++) = p;
1694 *(out++) = s;
1695 continue;
1696 }
1697 }
1698
1699 /* Sort all preceding sprites by order and assign new orders in reverse (as original sorter did). */
1700 std::sort(preceding.begin(), preceding.end(), [](const ParentSpriteToDraw *a, const ParentSpriteToDraw *b) {
1701 return a->order > b->order;
1702 });
1703
1704 s->order = ORDER_COMPARED;
1705 sprite_order.push(s); // Still need to output so push it back for now
1706
1707 for (auto p: preceding) {
1708 p->order = next_order++;
1709 sprite_order.push(p);
1710 }
1711 }
1712}
1713
1714
1715static void ViewportDrawParentSprites(const ParentSpriteToSortVector *psd, const ChildScreenSpriteToDrawVector *csstdv)
1716{
1717 for (const ParentSpriteToDraw *ps : *psd) {
1718 if (ps->image != SPR_EMPTY_BOUNDING_BOX) DrawSpriteViewport(ps->image, ps->pal, ps->x, ps->y, ps->sub);
1719
1720 int child_idx = ps->first_child;
1721 while (child_idx >= 0) {
1722 const ChildScreenSpriteToDraw *cs = &(*csstdv)[child_idx];
1723 child_idx = cs->next;
1724 if (cs->relative) {
1725 DrawSpriteViewport(cs->image, cs->pal, ps->left + cs->x, ps->top + cs->y, cs->sub);
1726 } else {
1727 DrawSpriteViewport(cs->image, cs->pal, ps->x + cs->x, ps->y + cs->y, cs->sub);
1728 }
1729 }
1730 }
1731}
1732
1737static void ViewportDrawBoundingBoxes(const ParentSpriteToSortVector *psd)
1738{
1739 for (const ParentSpriteToDraw *ps : *psd) {
1740 Point pt1 = RemapCoords(ps->xmax + 1, ps->ymax + 1, ps->zmax + 1); // top front corner
1741 Point pt2 = RemapCoords(ps->xmin , ps->ymax + 1, ps->zmax + 1); // top left corner
1742 Point pt3 = RemapCoords(ps->xmax + 1, ps->ymin , ps->zmax + 1); // top right corner
1743 Point pt4 = RemapCoords(ps->xmax + 1, ps->ymax + 1, ps->zmin ); // bottom front corner
1744
1745 DrawBox( pt1.x, pt1.y,
1746 pt2.x - pt1.x, pt2.y - pt1.y,
1747 pt3.x - pt1.x, pt3.y - pt1.y,
1748 pt4.x - pt1.x, pt4.y - pt1.y);
1749 }
1750}
1751
1756{
1758 const DrawPixelInfo *dpi = _cur_dpi;
1759 void *dst;
1760 int right = UnScaleByZoom(dpi->width, dpi->zoom);
1761 int bottom = UnScaleByZoom(dpi->height, dpi->zoom);
1762
1763 PixelColour colour = _string_colourmap[_dirty_block_colour & 0xF];
1764
1765 dst = dpi->dst_ptr;
1766
1767 uint8_t bo = UnScaleByZoom(dpi->left + dpi->top, dpi->zoom) & 1;
1768 do {
1769 for (int i = (bo ^= 1); i < right; i += 2) blitter->SetPixel(dst, i, 0, colour);
1770 dst = blitter->MoveTo(dst, 0, 1);
1771 } while (--bottom > 0);
1772}
1773
1774static void ViewportDrawStrings(ZoomLevel zoom, const StringSpriteToDrawVector *sstdv)
1775{
1776 for (const StringSpriteToDraw &ss : *sstdv) {
1777 bool small = ss.flags.Test(ViewportStringFlag::Small);
1778 int w = ss.width;
1779 int x = UnScaleByZoom(ss.x, zoom);
1780 int y = UnScaleByZoom(ss.y, zoom);
1781 int h = WidgetDimensions::scaled.fullbevel.top + GetCharacterHeight(small ? FontSize::Small : FontSize::Normal) + WidgetDimensions::scaled.fullbevel.bottom;
1782
1783 TextColour colour = TC_WHITE;
1784 if (ss.flags.Test(ViewportStringFlag::ColourRect)) {
1785 if (ss.colour != Colours::Invalid) DrawFrameRect(x, y, x + w - 1, y + h - 1, ss.colour, {});
1786 colour = TC_BLACK;
1787 } else if (ss.flags.Test(ViewportStringFlag::TransparentRect)) {
1788 DrawFrameRect(x, y, x + w - 1, y + h - 1, ss.colour, FrameFlag::Transparent);
1789 }
1790
1791 if (ss.flags.Test(ViewportStringFlag::TextColour)) {
1792 if (ss.colour != Colours::Invalid) colour = GetColourGradient(ss.colour, SHADE_LIGHTER).ToTextColour();
1793 }
1794
1795 int left = x + WidgetDimensions::scaled.fullbevel.left;
1796 int right = x + w - 1 - WidgetDimensions::scaled.fullbevel.right;
1797 int top = y + WidgetDimensions::scaled.fullbevel.top;
1798
1799 int shadow_offset = 0;
1800 if (small && ss.flags.Test(ViewportStringFlag::Shadow)) {
1801 /* Shadow needs to be shifted 1 pixel. */
1802 shadow_offset = WidgetDimensions::scaled.fullbevel.top;
1803 DrawString(left + shadow_offset, right + shadow_offset, top, ss.string, TC_BLACK, SA_HOR_CENTER, false, FontSize::Small);
1804 }
1805
1806 DrawString(left, right, top - shadow_offset, ss.string, colour, SA_HOR_CENTER, false, small ? FontSize::Small : FontSize::Normal);
1807 }
1808}
1809
1810void ViewportDoDraw(const Viewport &vp, int left, int top, int right, int bottom)
1811{
1812 _vd.dpi.zoom = vp.zoom;
1813 int mask = ScaleByZoom(-1, vp.zoom);
1814
1815 _vd.combine_sprites = SPRITE_COMBINE_NONE;
1816
1817 _vd.dpi.width = (right - left) & mask;
1818 _vd.dpi.height = (bottom - top) & mask;
1819 _vd.dpi.left = left & mask;
1820 _vd.dpi.top = top & mask;
1821 _vd.dpi.pitch = _cur_dpi->pitch;
1822 _vd.last_child = LAST_CHILD_NONE;
1823
1824 int x = UnScaleByZoom(_vd.dpi.left - (vp.virtual_left & mask), vp.zoom) + vp.left;
1825 int y = UnScaleByZoom(_vd.dpi.top - (vp.virtual_top & mask), vp.zoom) + vp.top;
1826
1827 _vd.dpi.dst_ptr = BlitterFactory::GetCurrentBlitter()->MoveTo(_cur_dpi->dst_ptr, x - _cur_dpi->left, y - _cur_dpi->top);
1828 AutoRestoreBackup dpi_backup(_cur_dpi, &_vd.dpi);
1829
1831 ViewportAddVehicles(&_vd.dpi);
1832
1833 ViewportAddKdtreeSigns(&_vd.dpi);
1834
1835 DrawTextEffects(&_vd.dpi);
1836
1837 if (!_vd.tile_sprites_to_draw.empty()) ViewportDrawTileSprites(&_vd.tile_sprites_to_draw);
1838
1839 for (auto &psd : _vd.parent_sprites_to_draw) {
1840 _vd.parent_sprites_to_sort.push_back(&psd);
1841 }
1842
1843 _vp_sprite_sorter(&_vd.parent_sprites_to_sort);
1844 ViewportDrawParentSprites(&_vd.parent_sprites_to_sort, &_vd.child_screen_sprites_to_draw);
1845
1846 if (_draw_bounding_boxes) ViewportDrawBoundingBoxes(&_vd.parent_sprites_to_sort);
1847 if (_draw_dirty_blocks) ViewportDrawDirtyBlocks();
1848
1849 DrawPixelInfo dp = _vd.dpi;
1850 ZoomLevel zoom = _vd.dpi.zoom;
1851 dp.zoom = ZoomLevel::Min;
1852 dp.width = UnScaleByZoom(dp.width, zoom);
1853 dp.height = UnScaleByZoom(dp.height, zoom);
1854 AutoRestoreBackup cur_dpi(_cur_dpi, &dp);
1855
1856 if (vp.overlay != nullptr && vp.overlay->GetCargoMask() != 0 && vp.overlay->GetCompanyMask().Any()) {
1857 /* translate to window coordinates */
1858 dp.left = x;
1859 dp.top = y;
1860 vp.overlay->Draw(&dp);
1861 }
1862
1863 if (!_vd.string_sprites_to_draw.empty()) {
1864 /* translate to world coordinates */
1865 dp.left = UnScaleByZoom(_vd.dpi.left, zoom);
1866 dp.top = UnScaleByZoom(_vd.dpi.top, zoom);
1867 ViewportDrawStrings(zoom, &_vd.string_sprites_to_draw);
1868 }
1869
1870 _vd.string_sprites_to_draw.clear();
1871 _vd.tile_sprites_to_draw.clear();
1872 _vd.parent_sprites_to_draw.clear();
1873 _vd.parent_sprites_to_sort.clear();
1874 _vd.child_screen_sprites_to_draw.clear();
1875}
1876
1877static inline void ViewportDraw(const Viewport &vp, int left, int top, int right, int bottom)
1878{
1879 if (right <= vp.left || bottom <= vp.top) return;
1880
1881 if (left >= vp.left + vp.width) return;
1882
1883 if (left < vp.left) left = vp.left;
1884 if (right > vp.left + vp.width) right = vp.left + vp.width;
1885
1886 if (top >= vp.top + vp.height) return;
1887
1888 if (top < vp.top) top = vp.top;
1889 if (bottom > vp.top + vp.height) bottom = vp.top + vp.height;
1890
1891 ViewportDoDraw(vp,
1892 ScaleByZoom(left - vp.left, vp.zoom) + vp.virtual_left,
1893 ScaleByZoom(top - vp.top, vp.zoom) + vp.virtual_top,
1894 ScaleByZoom(right - vp.left, vp.zoom) + vp.virtual_left,
1895 ScaleByZoom(bottom - vp.top, vp.zoom) + vp.virtual_top
1896 );
1897}
1898
1903{
1905
1906 DrawPixelInfo *dpi = _cur_dpi;
1907
1908 dpi->left += this->left;
1909 dpi->top += this->top;
1910
1911 ViewportDraw(*this->viewport, dpi->left, dpi->top, dpi->left + dpi->width, dpi->top + dpi->height);
1912
1913 dpi->left -= this->left;
1914 dpi->top -= this->top;
1915}
1916
1927static inline void ClampViewportToMap(const Viewport &vp, int *scroll_x, int *scroll_y)
1928{
1929 /* Centre of the viewport is hot spot. */
1930 Point pt = {
1931 *scroll_x + vp.virtual_width / 2,
1932 *scroll_y + vp.virtual_height / 2
1933 };
1934
1935 /* Find nearest tile that is within borders of the map. */
1936 bool clamped;
1937 pt = InverseRemapCoords2(pt.x, pt.y, true, &clamped);
1938
1939 if (clamped) {
1940 /* Convert back to viewport coordinates and remove centering. */
1941 pt = RemapCoords2(pt.x, pt.y);
1942 *scroll_x = pt.x - vp.virtual_width / 2;
1943 *scroll_y = pt.y - vp.virtual_height / 2;
1944 }
1945}
1946
1959static void ClampSmoothScroll(uint32_t delta_ms, int64_t delta_hi, int64_t delta_lo, int &delta_hi_clamped, int &delta_lo_clamped)
1960{
1962 constexpr int PIXELS_PER_TILE = TILE_PIXELS * 2 * ZOOM_BASE;
1963
1964 assert(delta_hi != 0);
1965
1966 /* Move at most 75% of the distance every 30ms, for a smooth experience */
1967 int64_t delta_left = delta_hi * std::pow(0.75, delta_ms / 30.0);
1968 /* Move never more than 16 tiles per 30ms. */
1969 int max_scroll = Map::ScaleBySize1D(16 * PIXELS_PER_TILE * delta_ms / 30);
1970
1971 /* We never go over the max_scroll speed. */
1972 delta_hi_clamped = Clamp(delta_hi - delta_left, -max_scroll, max_scroll);
1973 /* The lower delta is in ratio of the higher delta, so we keep going straight at the destination. */
1974 delta_lo_clamped = delta_lo * delta_hi_clamped / delta_hi;
1975
1976 /* Ensure we always move (delta_hi can't be zero). */
1977 if (delta_hi_clamped == 0) {
1978 delta_hi_clamped = delta_hi > 0 ? 1 : -1;
1979 }
1980}
1981
1987void UpdateViewportPosition(Window *w, uint32_t delta_ms)
1988{
1989 ViewportData &vp = *w->viewport;
1990
1991 if (vp.follow_vehicle != VehicleID::Invalid()) {
1992 const Vehicle *veh = Vehicle::Get(vp.follow_vehicle)->GetMovingFront();
1993 Point pt = MapXYZToViewport(vp, veh->x_pos, veh->y_pos, veh->z_pos);
1994
1995 vp.scrollpos_x = pt.x;
1996 vp.scrollpos_y = pt.y;
1997 SetViewportPosition(w, pt.x, pt.y);
1998 } else {
1999 /* Ensure the destination location is within the map */
2001
2002 int delta_x = vp.dest_scrollpos_x - vp.scrollpos_x;
2003 int delta_y = vp.dest_scrollpos_y - vp.scrollpos_y;
2004
2005 int current_x = vp.scrollpos_x;
2006 int current_y = vp.scrollpos_y;
2007
2008 bool update_overlay = false;
2009 if (delta_x != 0 || delta_y != 0) {
2010 if (_settings_client.gui.smooth_scroll) {
2011 int delta_x_clamped;
2012 int delta_y_clamped;
2013
2014 if (abs(delta_x) > abs(delta_y)) {
2015 ClampSmoothScroll(delta_ms, delta_x, delta_y, delta_x_clamped, delta_y_clamped);
2016 } else {
2017 ClampSmoothScroll(delta_ms, delta_y, delta_x, delta_y_clamped, delta_x_clamped);
2018 }
2019
2020 vp.scrollpos_x += delta_x_clamped;
2021 vp.scrollpos_y += delta_y_clamped;
2022 } else {
2025 }
2026 update_overlay = (vp.scrollpos_x == vp.dest_scrollpos_x &&
2027 vp.scrollpos_y == vp.dest_scrollpos_y);
2028 }
2029
2031
2032 /* When moving small amounts around the border we can get stuck, and
2033 * not actually move. In those cases, teleport to the destination. */
2034 if ((delta_x != 0 || delta_y != 0) && current_x == vp.scrollpos_x && current_y == vp.scrollpos_y) {
2037 }
2038
2039 SetViewportPosition(w, vp.scrollpos_x, vp.scrollpos_y);
2040 if (update_overlay) RebuildViewportOverlay(w);
2041 }
2042}
2043
2054static bool MarkViewportDirty(const Viewport &vp, int left, int top, int right, int bottom)
2055{
2056 /* Rounding wrt. zoom-out level */
2057 right += (1 << to_underlying(vp.zoom)) - 1;
2058 bottom += (1 << to_underlying(vp.zoom)) - 1;
2059
2060 right -= vp.virtual_left;
2061 if (right <= 0) return false;
2062
2063 bottom -= vp.virtual_top;
2064 if (bottom <= 0) return false;
2065
2066 left = std::max(0, left - vp.virtual_left);
2067
2068 if (left >= vp.virtual_width) return false;
2069
2070 top = std::max(0, top - vp.virtual_top);
2071
2072 if (top >= vp.virtual_height) return false;
2073
2075 UnScaleByZoomLower(left, vp.zoom) + vp.left,
2076 UnScaleByZoomLower(top, vp.zoom) + vp.top,
2077 UnScaleByZoom(right, vp.zoom) + vp.left + 1,
2078 UnScaleByZoom(bottom, vp.zoom) + vp.top + 1
2079 );
2080
2081 return true;
2082}
2083
2093bool MarkAllViewportsDirty(int left, int top, int right, int bottom)
2094{
2095 bool dirty = false;
2096
2097 for (const Window *w : Window::Iterate()) {
2098 if (w->viewport != nullptr) {
2099 assert(w->viewport->width != 0);
2100 if (MarkViewportDirty(*w->viewport, left, top, right, bottom)) dirty = true;
2101 }
2102 }
2103
2104 return dirty;
2105}
2106
2107void ConstrainAllViewportsZoom()
2108{
2109 for (Window *w : Window::Iterate()) {
2110 if (w->viewport == nullptr) continue;
2111
2112 ZoomLevel zoom = Clamp(w->viewport->zoom, _settings_client.gui.zoom_min, _settings_client.gui.zoom_max);
2113 if (zoom != w->viewport->zoom) {
2114 while (w->viewport->zoom < zoom) DoZoomInOutWindow(ZOOM_OUT, w);
2115 while (w->viewport->zoom > zoom) DoZoomInOutWindow(ZOOM_IN, w);
2116 }
2117 }
2118}
2119
2127void MarkTileDirtyByTile(TileIndex tile, int bridge_level_offset, int tile_height_override)
2128{
2129 Point pt = RemapCoords(TileX(tile) * TILE_SIZE, TileY(tile) * TILE_SIZE, tile_height_override * TILE_HEIGHT);
2132 pt.y - MAX_TILE_EXTENT_TOP - ZOOM_BASE * TILE_HEIGHT * bridge_level_offset,
2135}
2136
2145{
2146 int x_size = _thd.size.x;
2147 int y_size = _thd.size.y;
2148
2149 if (!_thd.diagonal) { // Selecting in a straight rectangle (or a single square)
2150 int x_start = _thd.pos.x;
2151 int y_start = _thd.pos.y;
2152
2153 if (_thd.outersize.x != 0) {
2154 x_size += _thd.outersize.x;
2155 x_start += _thd.offs.x;
2156 y_size += _thd.outersize.y;
2157 y_start += _thd.offs.y;
2158 }
2159
2160 x_size -= TILE_SIZE;
2161 y_size -= TILE_SIZE;
2162
2163 assert(x_size >= 0);
2164 assert(y_size >= 0);
2165
2166 int x_end = Clamp(x_start + x_size, 0, Map::SizeX() * TILE_SIZE - TILE_SIZE);
2167 int y_end = Clamp(y_start + y_size, 0, Map::SizeY() * TILE_SIZE - TILE_SIZE);
2168
2169 x_start = Clamp(x_start, 0, Map::SizeX() * TILE_SIZE - TILE_SIZE);
2170 y_start = Clamp(y_start, 0, Map::SizeY() * TILE_SIZE - TILE_SIZE);
2171
2172 /* make sure everything is multiple of TILE_SIZE */
2173 assert((x_end | y_end | x_start | y_start) % TILE_SIZE == 0);
2174
2175 /* How it works:
2176 * Suppose we have to mark dirty rectangle of 3x4 tiles:
2177 * x
2178 * xxx
2179 * xxxxx
2180 * xxxxx
2181 * xxx
2182 * x
2183 * This algorithm marks dirty columns of tiles, so it is done in 3+4-1 steps:
2184 * 1) x 2) x
2185 * xxx Oxx
2186 * Oxxxx xOxxx
2187 * xxxxx Oxxxx
2188 * xxx xxx
2189 * x x
2190 * And so forth...
2191 */
2192
2193 int top_x = x_end; // coordinates of top dirty tile
2194 int top_y = y_start;
2195 int bot_x = top_x; // coordinates of bottom dirty tile
2196 int bot_y = top_y;
2197
2198 do {
2199 /* topmost dirty point */
2200 TileIndex top_tile = TileVirtXY(top_x, top_y);
2201 Point top = RemapCoords(top_x, top_y, GetTileMaxPixelZ(top_tile));
2202
2203 /* bottommost point */
2204 TileIndex bottom_tile = TileVirtXY(bot_x, bot_y);
2205 Point bot = RemapCoords(bot_x + TILE_SIZE, bot_y + TILE_SIZE, GetTilePixelZ(bottom_tile)); // bottommost point
2206
2207 /* the 'x' coordinate of 'top' and 'bot' is the same (and always in the same distance from tile middle),
2208 * tile height/slope affects only the 'y' on-screen coordinate! */
2209
2210 int l = top.x - TILE_PIXELS * ZOOM_BASE; // 'x' coordinate of left side of the dirty rectangle
2211 int t = top.y; // 'y' coordinate of top side of the dirty rectangle
2212 int r = top.x + TILE_PIXELS * ZOOM_BASE; // 'x' coordinate of right side of the dirty rectangle
2213 int b = bot.y; // 'y' coordinate of bottom side of the dirty rectangle
2214
2215 static const int OVERLAY_WIDTH = 4 * ZOOM_BASE; // part of selection sprites is drawn outside the selected area (in particular: terraforming)
2216
2217 /* For halftile foundations on SLOPE_STEEP_S the sprite extents some more towards the top */
2218 MarkAllViewportsDirty(l - OVERLAY_WIDTH, t - OVERLAY_WIDTH - TILE_HEIGHT * ZOOM_BASE, r + OVERLAY_WIDTH, b + OVERLAY_WIDTH);
2219
2220 /* haven't we reached the topmost tile yet? */
2221 if (top_x != x_start) {
2222 top_x -= TILE_SIZE;
2223 } else {
2224 top_y += TILE_SIZE;
2225 }
2226
2227 /* the way the bottom tile changes is different when we reach the bottommost tile */
2228 if (bot_y != y_end) {
2229 bot_y += TILE_SIZE;
2230 } else {
2231 bot_x -= TILE_SIZE;
2232 }
2233 } while (bot_x >= top_x);
2234 } else { // Selecting in a 45 degrees rotated (diagonal) rectangle.
2235 /* a_size, b_size describe a rectangle with rotated coordinates */
2236 int a_size = x_size + y_size, b_size = x_size - y_size;
2237
2238 int interval_a = a_size < 0 ? -(int)TILE_SIZE : (int)TILE_SIZE;
2239 int interval_b = b_size < 0 ? -(int)TILE_SIZE : (int)TILE_SIZE;
2240
2241 for (int a = -interval_a; a != a_size + interval_a; a += interval_a) {
2242 for (int b = -interval_b; b != b_size + interval_b; b += interval_b) {
2243 uint x = (_thd.pos.x + (a + b) / 2) / TILE_SIZE;
2244 uint y = (_thd.pos.y + (a - b) / 2) / TILE_SIZE;
2245
2246 if (x < Map::MaxX() && y < Map::MaxY()) {
2248 }
2249 }
2250 }
2251 }
2252}
2253
2254
2255void SetSelectionRed(bool b)
2256{
2257 _thd.make_square_red = b;
2259}
2260
2269static bool CheckClickOnViewportSign(const Viewport &vp, int x, int y, const ViewportSign *sign)
2270{
2271 bool small = (vp.zoom >= ZoomLevel::Out4x);
2272 int sign_half_width = ScaleByZoom((small ? sign->width_small : sign->width_normal) / 2, vp.zoom);
2273 int sign_height = ScaleByZoom(WidgetDimensions::scaled.fullbevel.top + GetCharacterHeight(small ? FontSize::Small : FontSize::Normal) + WidgetDimensions::scaled.fullbevel.bottom, vp.zoom);
2274
2275 return y >= sign->top && y < sign->top + sign_height &&
2276 x >= sign->center - sign_half_width && x < sign->center + sign_half_width;
2277}
2278
2279
2287static bool CheckClickOnViewportSign(const Viewport &vp, int x, int y)
2288{
2289 if (_game_mode == GM_MENU) return false;
2290
2291 x = ScaleByZoom(x - vp.left, vp.zoom) + vp.virtual_left;
2292 y = ScaleByZoom(y - vp.top, vp.zoom) + vp.virtual_top;
2293
2294 Rect search_rect{ x - 1, y - 1, x + 1, y + 1 };
2295 search_rect = ExpandRectWithViewportSignMargins(search_rect, vp.zoom);
2296
2299 bool show_towns = HasBit(_display_opt, DO_SHOW_TOWN_NAMES);
2301 bool show_competitors = HasBit(_display_opt, DO_SHOW_COMPETITOR_SIGNS);
2302
2303 /* Topmost of each type that was hit */
2304 BaseStation *st = nullptr, *last_st = nullptr;
2305 Town *t = nullptr, *last_t = nullptr;
2306 Sign *si = nullptr, *last_si = nullptr;
2307
2308 /* See ViewportAddKdtreeSigns() for details on the search logic */
2309 _viewport_sign_kdtree.FindContained(search_rect.left, search_rect.top, search_rect.right, search_rect.bottom, [&](const ViewportSignKdtreeItem & item) {
2310 switch (item.type) {
2311 case ViewportSignKdtreeItem::VKI_STATION: {
2312 if (!show_stations) break;
2313 st = BaseStation::Get(std::get<StationID>(item.id));
2314 if (!show_competitors && _local_company != st->owner && st->owner != OWNER_NONE) break;
2315
2316 StationFacilities facilities = st->facilities;
2317 if (facilities.None()) facilities = STATION_FACILITY_GHOST;
2318 if (!facilities.Any(_facility_display_opt)) break;
2319
2320 if (CheckClickOnViewportSign(vp, x, y, &st->sign)) last_st = st;
2321 break;
2322 }
2323
2324 case ViewportSignKdtreeItem::VKI_WAYPOINT:
2325 if (!show_waypoints) break;
2326 st = BaseStation::Get(std::get<StationID>(item.id));
2327 if (!show_competitors && _local_company != st->owner && st->owner != OWNER_NONE) break;
2328 if (CheckClickOnViewportSign(vp, x, y, &st->sign)) last_st = st;
2329 break;
2330
2331 case ViewportSignKdtreeItem::VKI_TOWN:
2332 if (!show_towns) break;
2333 t = Town::Get(std::get<TownID>(item.id));
2334 if (CheckClickOnViewportSign(vp, x, y, &t->cache.sign)) last_t = t;
2335 break;
2336
2337 case ViewportSignKdtreeItem::VKI_SIGN:
2338 if (!show_signs) break;
2339 si = Sign::Get(std::get<SignID>(item.id));
2340 if (!show_competitors && _local_company != si->owner && si->owner != OWNER_DEITY) break;
2341 if (CheckClickOnViewportSign(vp, x, y, &si->sign)) last_si = si;
2342 break;
2343
2344 default:
2345 NOT_REACHED();
2346 }
2347 });
2348
2349 /* Select which hit to handle based on priority */
2350 if (last_st != nullptr) {
2351 if (Station::IsExpected(last_st)) {
2352 ShowStationViewWindow(last_st->index);
2353 } else {
2355 }
2356 return true;
2357 } else if (last_t != nullptr) {
2358 ShowTownViewWindow(last_t->index);
2359 return true;
2360 } else if (last_si != nullptr) {
2361 HandleClickOnSign(last_si);
2362 return true;
2363 } else {
2364 return false;
2365 }
2366}
2367
2368
2369ViewportSignKdtreeItem ViewportSignKdtreeItem::MakeStation(StationID id)
2370{
2371 ViewportSignKdtreeItem item;
2372 item.type = VKI_STATION;
2373 item.id = id;
2374
2375 const Station *st = Station::Get(id);
2376 assert(st->sign.kdtree_valid);
2377 item.center = st->sign.center;
2378 item.top = st->sign.top;
2379
2380 /* Assume the sign can be a candidate for drawing, so measure its width */
2381 _viewport_sign_maxwidth = std::max<int>({_viewport_sign_maxwidth, st->sign.width_normal, st->sign.width_small});
2382
2383 return item;
2384}
2385
2386ViewportSignKdtreeItem ViewportSignKdtreeItem::MakeWaypoint(StationID id)
2387{
2388 ViewportSignKdtreeItem item;
2389 item.type = VKI_WAYPOINT;
2390 item.id = id;
2391
2392 const Waypoint *st = Waypoint::Get(id);
2393 assert(st->sign.kdtree_valid);
2394 item.center = st->sign.center;
2395 item.top = st->sign.top;
2396
2397 /* Assume the sign can be a candidate for drawing, so measure its width */
2398 _viewport_sign_maxwidth = std::max<int>({_viewport_sign_maxwidth, st->sign.width_normal, st->sign.width_small});
2399
2400 return item;
2401}
2402
2403ViewportSignKdtreeItem ViewportSignKdtreeItem::MakeTown(TownID id)
2404{
2405 ViewportSignKdtreeItem item;
2406 item.type = VKI_TOWN;
2407 item.id = id;
2408
2409 const Town *town = Town::Get(id);
2410 assert(town->cache.sign.kdtree_valid);
2411 item.center = town->cache.sign.center;
2412 item.top = town->cache.sign.top;
2413
2414 /* Assume the sign can be a candidate for drawing, so measure its width */
2415 _viewport_sign_maxwidth = std::max<int>({_viewport_sign_maxwidth, town->cache.sign.width_normal, town->cache.sign.width_small});
2416
2417 return item;
2418}
2419
2420ViewportSignKdtreeItem ViewportSignKdtreeItem::MakeSign(SignID id)
2421{
2422 ViewportSignKdtreeItem item;
2423 item.type = VKI_SIGN;
2424 item.id = id;
2425
2426 const Sign *sign = Sign::Get(id);
2427 assert(sign->sign.kdtree_valid);
2428 item.center = sign->sign.center;
2429 item.top = sign->sign.top;
2430
2431 /* Assume the sign can be a candidate for drawing, so measure its width */
2432 _viewport_sign_maxwidth = std::max<int>({_viewport_sign_maxwidth, sign->sign.width_normal, sign->sign.width_small});
2433
2434 return item;
2435}
2436
2437void RebuildViewportKdtree()
2438{
2439 /* Reset biggest size sign seen */
2440 _viewport_sign_maxwidth = 0;
2441
2442 std::vector<ViewportSignKdtreeItem> items;
2444
2445 for (const Station *st : Station::Iterate()) {
2446 if (st->sign.kdtree_valid) items.push_back(ViewportSignKdtreeItem::MakeStation(st->index));
2447 }
2448
2449 for (const Waypoint *wp : Waypoint::Iterate()) {
2450 if (wp->sign.kdtree_valid) items.push_back(ViewportSignKdtreeItem::MakeWaypoint(wp->index));
2451 }
2452
2453 for (const Town *town : Town::Iterate()) {
2454 if (town->cache.sign.kdtree_valid) items.push_back(ViewportSignKdtreeItem::MakeTown(town->index));
2455 }
2456
2457 for (const Sign *sign : Sign::Iterate()) {
2458 if (sign->sign.kdtree_valid) items.push_back(ViewportSignKdtreeItem::MakeSign(sign->index));
2459 }
2460
2461 _viewport_sign_kdtree.Build(items.begin(), items.end());
2462}
2463
2464
2465static bool CheckClickOnLandscape(const Viewport &vp, int x, int y)
2466{
2467 Point pt = TranslateXYToTileCoord(vp, x, y);
2468
2469 if (pt.x != -1) return ClickTile(TileVirtXY(pt.x, pt.y));
2470 return true;
2471}
2472
2473static void PlaceObject()
2474{
2475 Point pt;
2476 Window *w;
2477
2478 pt = GetTileBelowCursor();
2479 if (pt.x == -1) return;
2480
2481 if ((_thd.place_mode & HT_DRAG_MASK) == HT_POINT) {
2482 pt.x += TILE_SIZE / 2;
2483 pt.y += TILE_SIZE / 2;
2484 }
2485
2486 _tile_fract_coords.x = pt.x & TILE_UNIT_MASK;
2487 _tile_fract_coords.y = pt.y & TILE_UNIT_MASK;
2488
2489 w = _thd.GetCallbackWnd();
2490 if (w != nullptr) w->OnPlaceObject(pt, TileVirtXY(pt.x, pt.y));
2491}
2492
2493
2494bool HandleViewportClicked(const Viewport &vp, int x, int y)
2495{
2496 const Vehicle *v = CheckClickOnVehicle(vp, x, y);
2497
2498 if (_thd.place_mode & HT_VEHICLE) {
2499 if (v != nullptr && VehicleClicked(v)) return true;
2500 }
2501
2502 /* Vehicle placement mode already handled above. */
2503 if ((_thd.place_mode & HT_DRAG_MASK) != HT_NONE) {
2504 PlaceObject();
2505 return true;
2506 }
2507
2508 if (CheckClickOnViewportSign(vp, x, y)) return true;
2509 bool result = CheckClickOnLandscape(vp, x, y);
2510
2511 if (v != nullptr) {
2512 Debug(misc, 2, "Vehicle {} (index {}) at {}", v->unitnumber, v->index, fmt::ptr(v));
2514 v = v->First();
2515 if (_ctrl_pressed && v->owner == _local_company) {
2516 StartStopVehicle(v, true);
2517 } else {
2519 }
2520 }
2521 return true;
2522 }
2523 return result;
2524}
2525
2526void RebuildViewportOverlay(Window *w)
2527{
2528 if (w->viewport->overlay != nullptr &&
2529 w->viewport->overlay->GetCompanyMask().Any() &&
2530 w->viewport->overlay->GetCargoMask() != 0) {
2531 w->viewport->overlay->SetDirty();
2532 w->SetDirty();
2533 }
2534}
2535
2545bool ScrollWindowTo(int x, int y, int z, Window *w, bool instant)
2546{
2547 /* The slope cannot be acquired outside of the map, so make sure we are always within the map. */
2548 if (z == -1) {
2549 if ( x >= 0 && x <= (int)Map::SizeX() * (int)TILE_SIZE - 1
2550 && y >= 0 && y <= (int)Map::SizeY() * (int)TILE_SIZE - 1) {
2551 z = GetSlopePixelZ(x, y);
2552 } else {
2553 z = TileHeightOutsideMap(x / (int)TILE_SIZE, y / (int)TILE_SIZE);
2554 }
2555 }
2556
2557 Point pt = MapXYZToViewport(*w->viewport, x, y, z);
2558 w->viewport->CancelFollow(*w);
2559
2560 if (w->viewport->dest_scrollpos_x == pt.x && w->viewport->dest_scrollpos_y == pt.y) return false;
2561
2562 if (instant) {
2563 w->viewport->scrollpos_x = pt.x;
2564 w->viewport->scrollpos_y = pt.y;
2565 RebuildViewportOverlay(w);
2566 }
2567
2568 w->viewport->dest_scrollpos_x = pt.x;
2569 w->viewport->dest_scrollpos_y = pt.y;
2570 return true;
2571}
2572
2580bool ScrollWindowToTile(TileIndex tile, Window *w, bool instant)
2581{
2582 return ScrollWindowTo(TileX(tile) * TILE_SIZE, TileY(tile) * TILE_SIZE, -1, w, instant);
2583}
2584
2591bool ScrollMainWindowToTile(TileIndex tile, bool instant)
2592{
2593 return ScrollMainWindowTo(TileX(tile) * TILE_SIZE + TILE_SIZE / 2, TileY(tile) * TILE_SIZE + TILE_SIZE / 2, -1, instant);
2594}
2595
2601{
2602 TileIndex old;
2603
2604 old = _thd.redsq;
2605 _thd.redsq = tile;
2606
2607 if (tile != old) {
2608 if (tile != INVALID_TILE) MarkTileDirtyByTile(tile);
2609 if (old != INVALID_TILE) MarkTileDirtyByTile(old);
2610 }
2611}
2612
2618void SetTileSelectSize(int w, int h)
2619{
2620 _thd.new_size.x = w * TILE_SIZE;
2621 _thd.new_size.y = h * TILE_SIZE;
2622 _thd.new_outersize.x = 0;
2623 _thd.new_outersize.y = 0;
2624}
2625
2626void SetTileSelectBigSize(int ox, int oy, int sx, int sy)
2627{
2628 _thd.offs.x = ox * TILE_SIZE;
2629 _thd.offs.y = oy * TILE_SIZE;
2630 _thd.new_outersize.x = sx * TILE_SIZE;
2631 _thd.new_outersize.y = sy * TILE_SIZE;
2632}
2633
2640static HighLightStyle GetAutorailHT(int x, int y)
2641{
2643}
2644
2649{
2650 this->pos.x = 0;
2651 this->pos.y = 0;
2652 this->new_pos.x = 0;
2653 this->new_pos.y = 0;
2654}
2655
2664
2673
2674
2675
2684{
2685 int x1;
2686 int y1;
2687
2688 if (_thd.freeze) return;
2689
2690 HighLightStyle new_drawstyle = HT_NONE;
2691 bool new_diagonal = false;
2692
2693 if ((_thd.place_mode & HT_DRAG_MASK) == HT_SPECIAL) {
2694 x1 = _thd.selend.x;
2695 y1 = _thd.selend.y;
2696 if (x1 != -1) {
2697 int x2 = _thd.selstart.x & ~TILE_UNIT_MASK;
2698 int y2 = _thd.selstart.y & ~TILE_UNIT_MASK;
2699 x1 &= ~TILE_UNIT_MASK;
2700 y1 &= ~TILE_UNIT_MASK;
2701
2702 if (_thd.IsDraggingDiagonal()) {
2703 new_diagonal = true;
2704 } else {
2705 if (x1 >= x2) std::swap(x1, x2);
2706 if (y1 >= y2) std::swap(y1, y2);
2707 }
2708 _thd.new_pos.x = x1;
2709 _thd.new_pos.y = y1;
2710 _thd.new_size.x = x2 - x1;
2711 _thd.new_size.y = y2 - y1;
2712 if (!new_diagonal) {
2713 _thd.new_size.x += TILE_SIZE;
2714 _thd.new_size.y += TILE_SIZE;
2715 }
2716 new_drawstyle = _thd.next_drawstyle;
2717 }
2718 } else if ((_thd.place_mode & HT_DRAG_MASK) != HT_NONE) {
2719 Point pt = GetTileBelowCursor();
2720 x1 = pt.x;
2721 y1 = pt.y;
2722 if (x1 != -1) {
2723 switch (_thd.place_mode & HT_DRAG_MASK) {
2724 case HT_RECT:
2725 new_drawstyle = HT_RECT;
2726 break;
2727 case HT_POINT:
2728 new_drawstyle = HT_POINT;
2729 x1 += TILE_SIZE / 2;
2730 y1 += TILE_SIZE / 2;
2731 break;
2732 case HT_RAIL:
2733 /* Draw one highlighted tile in any direction */
2734 new_drawstyle = GetAutorailHT(pt.x, pt.y);
2735 break;
2736 case HT_LINE:
2737 switch (_thd.place_mode & HT_DIR_MASK) {
2738 case HT_DIR_X: new_drawstyle = HT_LINE | HT_DIR_X; break;
2739 case HT_DIR_Y: new_drawstyle = HT_LINE | HT_DIR_Y; break;
2740
2741 case HT_DIR_HU:
2742 case HT_DIR_HL:
2743 new_drawstyle = (pt.x & TILE_UNIT_MASK) + (pt.y & TILE_UNIT_MASK) <= TILE_SIZE ? HT_LINE | HT_DIR_HU : HT_LINE | HT_DIR_HL;
2744 break;
2745
2746 case HT_DIR_VL:
2747 case HT_DIR_VR:
2748 new_drawstyle = (pt.x & TILE_UNIT_MASK) > (pt.y & TILE_UNIT_MASK) ? HT_LINE | HT_DIR_VL : HT_LINE | HT_DIR_VR;
2749 break;
2750
2751 default: NOT_REACHED();
2752 }
2753 _thd.selstart.x = x1 & ~TILE_UNIT_MASK;
2754 _thd.selstart.y = y1 & ~TILE_UNIT_MASK;
2755 break;
2756 default:
2757 NOT_REACHED();
2758 }
2759 _thd.new_pos.x = x1 & ~TILE_UNIT_MASK;
2760 _thd.new_pos.y = y1 & ~TILE_UNIT_MASK;
2761 }
2762 }
2763
2764 /* redraw selection */
2765 if (_thd.drawstyle != new_drawstyle ||
2766 _thd.pos.x != _thd.new_pos.x || _thd.pos.y != _thd.new_pos.y ||
2767 _thd.size.x != _thd.new_size.x || _thd.size.y != _thd.new_size.y ||
2768 _thd.outersize.x != _thd.new_outersize.x ||
2769 _thd.outersize.y != _thd.new_outersize.y ||
2770 _thd.diagonal != new_diagonal) {
2771 /* Clear the old tile selection? */
2772 if ((_thd.drawstyle & HT_DRAG_MASK) != HT_NONE) SetSelectionTilesDirty();
2773
2774 _thd.drawstyle = new_drawstyle;
2775 _thd.pos = _thd.new_pos;
2776 _thd.size = _thd.new_size;
2777 _thd.outersize = _thd.new_outersize;
2778 _thd.diagonal = new_diagonal;
2779 _thd.dirty = 0xff;
2780
2781 /* Draw the new tile selection? */
2782 if ((new_drawstyle & HT_DRAG_MASK) != HT_NONE) SetSelectionTilesDirty();
2783 }
2784}
2785
2790static inline void ShowMeasurementTooltips(EncodedString &&text)
2791{
2792 if (!_settings_client.gui.measure_tooltip) return;
2793 GuiShowTooltips(_thd.GetCallbackWnd(), std::move(text), TCC_EXIT_VIEWPORT);
2794}
2795
2796static void HideMeasurementTooltips()
2797{
2799}
2800
2808{
2809 _thd.select_method = method;
2810 _thd.select_proc = process;
2811 _thd.selend.x = TileX(tile) * TILE_SIZE;
2812 _thd.selstart.x = TileX(tile) * TILE_SIZE;
2813 _thd.selend.y = TileY(tile) * TILE_SIZE;
2814 _thd.selstart.y = TileY(tile) * TILE_SIZE;
2815
2816 /* Needed so several things (road, autoroad, bridges, ...) are placed correctly.
2817 * In effect, placement starts from the centre of a tile
2818 */
2819 if (method == VPM_X_OR_Y || method == VPM_FIX_X || method == VPM_FIX_Y) {
2820 _thd.selend.x += TILE_SIZE / 2;
2821 _thd.selend.y += TILE_SIZE / 2;
2822 _thd.selstart.x += TILE_SIZE / 2;
2823 _thd.selstart.y += TILE_SIZE / 2;
2824 }
2825
2826 HighLightStyle others = _thd.place_mode & ~(HT_DRAG_MASK | HT_DIR_MASK);
2827 if ((_thd.place_mode & HT_DRAG_MASK) == HT_RECT) {
2828 _thd.place_mode = HT_SPECIAL | others;
2829 _thd.next_drawstyle = HT_RECT | others;
2830 } else if (_thd.place_mode & (HT_RAIL | HT_LINE)) {
2831 _thd.place_mode = HT_SPECIAL | others;
2832 _thd.next_drawstyle = _thd.drawstyle | others;
2833 } else {
2834 _thd.place_mode = HT_SPECIAL | others;
2835 _thd.next_drawstyle = HT_POINT | others;
2836 }
2838}
2839
2845{
2846 _thd.select_method = VPM_X_AND_Y;
2847 _thd.select_proc = process;
2848 _thd.selstart.x = 0;
2849 _thd.selstart.y = 0;
2850 _thd.next_drawstyle = HT_RECT;
2851
2853}
2854
2855void VpSetPlaceSizingLimit(int limit)
2856{
2857 _thd.sizelimit = limit;
2858}
2859
2866{
2867 uint64_t distance = DistanceManhattan(from, to) + 1;
2868
2869 _thd.selend.x = TileX(to) * TILE_SIZE;
2870 _thd.selend.y = TileY(to) * TILE_SIZE;
2871 _thd.selstart.x = TileX(from) * TILE_SIZE;
2872 _thd.selstart.y = TileY(from) * TILE_SIZE;
2873 _thd.next_drawstyle = HT_RECT;
2874
2875 /* show measurement only if there is any length to speak of */
2876 if (distance > 1) {
2877 ShowMeasurementTooltips(GetEncodedString(STR_MEASURE_LENGTH, distance));
2878 } else {
2879 HideMeasurementTooltips();
2880 }
2881}
2882
2883static void VpStartPreSizing()
2884{
2885 _thd.selend.x = -1;
2887}
2888
2897{
2898 int fxpy = _tile_fract_coords.x + _tile_fract_coords.y;
2899 int sxpy = (_thd.selend.x & TILE_UNIT_MASK) + (_thd.selend.y & TILE_UNIT_MASK);
2900 int fxmy = _tile_fract_coords.x - _tile_fract_coords.y;
2901 int sxmy = (_thd.selend.x & TILE_UNIT_MASK) - (_thd.selend.y & TILE_UNIT_MASK);
2902
2903 switch (direction) {
2904 default: NOT_REACHED();
2905 case DIAGDIR_SE: // end piece is lower right
2906 if (fxpy >= 20 && sxpy <= 12) return HT_DIR_HL;
2907 if (fxmy < -3 && sxmy > 3) return HT_DIR_VR;
2908 return HT_DIR_Y;
2909
2910 case DIAGDIR_NW:
2911 if (fxmy > 3 && sxmy < -3) return HT_DIR_VL;
2912 if (fxpy <= 12 && sxpy >= 20) return HT_DIR_HU;
2913 return HT_DIR_Y;
2914
2915 case DIAGDIR_SW:
2916 if (fxmy > 3 && sxmy < -3) return HT_DIR_VL;
2917 if (fxpy >= 20 && sxpy <= 12) return HT_DIR_HL;
2918 return HT_DIR_X;
2919
2920 case DIAGDIR_NE:
2921 if (fxmy < -3 && sxmy > 3) return HT_DIR_VR;
2922 if (fxpy <= 12 && sxpy >= 20) return HT_DIR_HU;
2923 return HT_DIR_X;
2924 }
2925}
2926
2940static bool SwapDirection(HighLightStyle style, TileIndex start_tile, TileIndex end_tile)
2941{
2942 uint start_x = TileX(start_tile);
2943 uint start_y = TileY(start_tile);
2944 uint end_x = TileX(end_tile);
2945 uint end_y = TileY(end_tile);
2946
2947 switch (style & HT_DRAG_MASK) {
2948 case HT_RAIL:
2949 case HT_LINE: return (end_x > start_x || (end_x == start_x && end_y > start_y));
2950
2951 case HT_RECT:
2952 case HT_POINT: return (end_x != start_x && end_y < start_y);
2953 default: NOT_REACHED();
2954 }
2955
2956 return false;
2957}
2958
2974static int CalcHeightdiff(HighLightStyle style, uint distance, TileIndex start_tile, TileIndex end_tile)
2975{
2976 bool swap = SwapDirection(style, start_tile, end_tile);
2977 uint h0, h1; // Start height and end height.
2978
2979 if (start_tile == end_tile) return 0;
2980 if (swap) std::swap(start_tile, end_tile);
2981
2982 switch (style & HT_DRAG_MASK) {
2983 case HT_RECT:
2984 /* In the case of an area we can determine whether we were dragging south or
2985 * east by checking the X-coordinates of the tiles */
2986 if (TileX(end_tile) > TileX(start_tile)) {
2987 /* Dragging south does not need to change the start tile. */
2988 end_tile = TileAddByDir(end_tile, DIR_S);
2989 } else {
2990 /* Dragging east. */
2991 start_tile = TileAddByDir(start_tile, DIR_SW);
2992 end_tile = TileAddByDir(end_tile, DIR_SE);
2993 }
2994 [[fallthrough]];
2995
2996 case HT_POINT:
2997 h0 = TileHeight(start_tile);
2998 h1 = TileHeight(end_tile);
2999 break;
3000 default: { // All other types, this is mostly only line/autorail
3001 static const HighLightStyle flip_style_direction[] = {
3003 };
3004 static const std::pair<TileIndexDiffC, TileIndexDiffC> start_heightdiff_line_by_dir[] = {
3005 { {1, 0}, {1, 1} }, // HT_DIR_X
3006 { {0, 1}, {1, 1} }, // HT_DIR_Y
3007 { {1, 0}, {0, 0} }, // HT_DIR_HU
3008 { {1, 0}, {1, 1} }, // HT_DIR_HL
3009 { {1, 0}, {1, 1} }, // HT_DIR_VL
3010 { {0, 1}, {1, 1} }, // HT_DIR_VR
3011 };
3012 static const std::pair<TileIndexDiffC, TileIndexDiffC> end_heightdiff_line_by_dir[] = {
3013 { {0, 1}, {0, 0} }, // HT_DIR_X
3014 { {1, 0}, {0, 0} }, // HT_DIR_Y
3015 { {0, 1}, {0, 0} }, // HT_DIR_HU
3016 { {1, 1}, {0, 1} }, // HT_DIR_HL
3017 { {1, 0}, {0, 0} }, // HT_DIR_VL
3018 { {0, 0}, {0, 1} }, // HT_DIR_VR
3019 };
3020 static_assert(std::size(start_heightdiff_line_by_dir) == HT_DIR_END);
3021 static_assert(std::size(end_heightdiff_line_by_dir) == HT_DIR_END);
3022
3023 distance %= 2; // we're only interested if the distance is even or uneven
3024 style &= HT_DIR_MASK;
3025 assert(style < HT_DIR_END);
3026
3027 /* To handle autorail, we do some magic to be able to use a lookup table.
3028 * Firstly if we drag the other way around, we switch start&end, and if needed
3029 * also flip the drag-position. Eg if it was on the left, and the distance is even
3030 * that means the end, which is now the start is on the right */
3031 if (swap && distance == 0) style = flip_style_direction[style];
3032
3033 /* Lambda to help calculating the height at one side of the line. */
3034 auto get_height = [](auto &tile, auto &heightdiffs) {
3035 return std::max(
3036 TileHeight(TileAdd(tile, ToTileIndexDiff(heightdiffs.first))),
3037 TileHeight(TileAdd(tile, ToTileIndexDiff(heightdiffs.second))));
3038 };
3039
3040 /* Use lookup table for start-tile based on HighLightStyle direction */
3041 h0 = get_height(start_tile, start_heightdiff_line_by_dir[style]);
3042
3043 /* Use lookup table for end-tile based on HighLightStyle direction
3044 * flip around side (lower/upper, left/right) based on distance */
3045 if (distance == 0) style = flip_style_direction[style];
3046 h1 = get_height(end_tile, end_heightdiff_line_by_dir[style]);
3047 break;
3048 }
3049 }
3050
3051 if (swap) std::swap(h0, h1);
3052 return (int)(h1 - h0) * TILE_HEIGHT_STEP;
3053}
3054
3061static void CheckUnderflow(int &test, int &other, int mult)
3062{
3063 if (test >= 0) return;
3064
3065 other += mult * test;
3066 test = 0;
3067}
3068
3076static void CheckOverflow(int &test, int &other, int max, int mult)
3077{
3078 if (test <= max) return;
3079
3080 other += mult * (test - max);
3081 test = max;
3082}
3083
3090static void CalcRaildirsDrawstyle(int x, int y, int method)
3091{
3093
3094 int dx = _thd.selstart.x - (_thd.selend.x & ~TILE_UNIT_MASK);
3095 int dy = _thd.selstart.y - (_thd.selend.y & ~TILE_UNIT_MASK);
3096 uint w = abs(dx) + TILE_SIZE;
3097 uint h = abs(dy) + TILE_SIZE;
3098
3099 if (method & ~(VPM_RAILDIRS | VPM_SIGNALDIRS)) {
3100 /* We 'force' a selection direction; first four rail buttons. */
3101 method &= ~(VPM_RAILDIRS | VPM_SIGNALDIRS);
3102 int raw_dx = _thd.selstart.x - _thd.selend.x;
3103 int raw_dy = _thd.selstart.y - _thd.selend.y;
3104 switch (method) {
3105 case VPM_FIX_X:
3106 b = HT_LINE | HT_DIR_Y;
3107 x = _thd.selstart.x;
3108 break;
3109
3110 case VPM_FIX_Y:
3111 b = HT_LINE | HT_DIR_X;
3112 y = _thd.selstart.y;
3113 break;
3114
3115 case VPM_FIX_HORIZONTAL:
3116 if (dx == -dy) {
3117 /* We are on a straight horizontal line. Determine the 'rail'
3118 * to build based the sub tile location. */
3120 } else {
3121 /* We are not on a straight line. Determine the rail to build
3122 * based on whether we are above or below it. */
3123 b = dx + dy >= (int)TILE_SIZE ? HT_LINE | HT_DIR_HU : HT_LINE | HT_DIR_HL;
3124
3125 /* Calculate where a horizontal line through the start point and
3126 * a vertical line from the selected end point intersect and
3127 * use that point as the end point. */
3128 int offset = (raw_dx - raw_dy) / 2;
3129 x = _thd.selstart.x - (offset & ~TILE_UNIT_MASK);
3130 y = _thd.selstart.y + (offset & ~TILE_UNIT_MASK);
3131
3132 /* 'Build' the last half rail tile if needed */
3133 if ((offset & TILE_UNIT_MASK) > (TILE_SIZE / 2)) {
3134 if (dx + dy >= (int)TILE_SIZE) {
3135 x -= (int)TILE_SIZE;
3136 } else {
3137 y += (int)TILE_SIZE;
3138 }
3139 }
3140
3141 /* Make sure we do not overflow the map! */
3142 CheckUnderflow(x, y, 1);
3143 CheckUnderflow(y, x, 1);
3144 CheckOverflow(x, y, (Map::MaxX() - 1) * TILE_SIZE, 1);
3145 CheckOverflow(y, x, (Map::MaxY() - 1) * TILE_SIZE, 1);
3146 assert(x >= 0 && y >= 0 && x <= (int)(Map::MaxX() * TILE_SIZE) && y <= (int)(Map::MaxY() * TILE_SIZE));
3147 }
3148 break;
3149
3150 case VPM_FIX_VERTICAL:
3151 if (dx == dy) {
3152 /* We are on a straight vertical line. Determine the 'rail'
3153 * to build based the sub tile location. */
3155 } else {
3156 /* We are not on a straight line. Determine the rail to build
3157 * based on whether we are left or right from it. */
3158 b = dx < dy ? HT_LINE | HT_DIR_VL : HT_LINE | HT_DIR_VR;
3159
3160 /* Calculate where a vertical line through the start point and
3161 * a horizontal line from the selected end point intersect and
3162 * use that point as the end point. */
3163 int offset = (raw_dx + raw_dy + (int)TILE_SIZE) / 2;
3164 x = _thd.selstart.x - (offset & ~TILE_UNIT_MASK);
3165 y = _thd.selstart.y - (offset & ~TILE_UNIT_MASK);
3166
3167 /* 'Build' the last half rail tile if needed */
3168 if ((offset & TILE_UNIT_MASK) > (TILE_SIZE / 2)) {
3169 if (dx < dy) {
3170 y -= (int)TILE_SIZE;
3171 } else {
3172 x -= (int)TILE_SIZE;
3173 }
3174 }
3175
3176 /* Make sure we do not overflow the map! */
3177 CheckUnderflow(x, y, -1);
3178 CheckUnderflow(y, x, -1);
3179 CheckOverflow(x, y, (Map::MaxX() - 1) * TILE_SIZE, -1);
3180 CheckOverflow(y, x, (Map::MaxY() - 1) * TILE_SIZE, -1);
3181 assert(x >= 0 && y >= 0 && x <= (int)(Map::MaxX() * TILE_SIZE) && y <= (int)(Map::MaxY() * TILE_SIZE));
3182 }
3183 break;
3184
3185 default:
3186 NOT_REACHED();
3187 }
3188 } else if (TileVirtXY(_thd.selstart.x, _thd.selstart.y) == TileVirtXY(x, y)) { // check if we're only within one tile
3189 if (method & VPM_RAILDIRS) {
3190 b = GetAutorailHT(x, y);
3191 } else { // rect for autosignals on one tile
3192 b = HT_RECT;
3193 }
3194 } else if (h == TILE_SIZE) { // Is this in X direction?
3195 if (dx == (int)TILE_SIZE) { // 2x1 special handling
3197 } else if (dx == -(int)TILE_SIZE) {
3199 } else {
3200 b = HT_LINE | HT_DIR_X;
3201 }
3202 y = _thd.selstart.y;
3203 } else if (w == TILE_SIZE) { // Or Y direction?
3204 if (dy == (int)TILE_SIZE) { // 2x1 special handling
3206 } else if (dy == -(int)TILE_SIZE) { // 2x1 other direction
3208 } else {
3209 b = HT_LINE | HT_DIR_Y;
3210 }
3211 x = _thd.selstart.x;
3212 } else if (w > h * 2) { // still count as x dir?
3213 b = HT_LINE | HT_DIR_X;
3214 y = _thd.selstart.y;
3215 } else if (h > w * 2) { // still count as y dir?
3216 b = HT_LINE | HT_DIR_Y;
3217 x = _thd.selstart.x;
3218 } else { // complicated direction
3219 int d = w - h;
3220 _thd.selend.x = _thd.selend.x & ~TILE_UNIT_MASK;
3221 _thd.selend.y = _thd.selend.y & ~TILE_UNIT_MASK;
3222
3223 /* four cases. */
3224 if (x > _thd.selstart.x) {
3225 if (y > _thd.selstart.y) {
3226 /* south */
3227 if (d == 0) {
3229 } else if (d >= 0) {
3230 x = _thd.selstart.x + h;
3231 b = HT_LINE | HT_DIR_VL;
3232 } else {
3233 y = _thd.selstart.y + w;
3234 b = HT_LINE | HT_DIR_VR;
3235 }
3236 } else {
3237 /* west */
3238 if (d == 0) {
3240 } else if (d >= 0) {
3241 x = _thd.selstart.x + h;
3242 b = HT_LINE | HT_DIR_HL;
3243 } else {
3244 y = _thd.selstart.y - w;
3245 b = HT_LINE | HT_DIR_HU;
3246 }
3247 }
3248 } else {
3249 if (y > _thd.selstart.y) {
3250 /* east */
3251 if (d == 0) {
3253 } else if (d >= 0) {
3254 x = _thd.selstart.x - h;
3255 b = HT_LINE | HT_DIR_HU;
3256 } else {
3257 y = _thd.selstart.y + w;
3258 b = HT_LINE | HT_DIR_HL;
3259 }
3260 } else {
3261 /* north */
3262 if (d == 0) {
3264 } else if (d >= 0) {
3265 x = _thd.selstart.x - h;
3266 b = HT_LINE | HT_DIR_VR;
3267 } else {
3268 y = _thd.selstart.y - w;
3269 b = HT_LINE | HT_DIR_VL;
3270 }
3271 }
3272 }
3273 }
3274
3275 if (_settings_client.gui.measure_tooltip) {
3276 TileIndex t0 = TileVirtXY(_thd.selstart.x, _thd.selstart.y);
3277 TileIndex t1 = TileVirtXY(x, y);
3278 uint distance = DistanceManhattan(t0, t1) + 1;
3279
3280 if (distance == 1) {
3281 HideMeasurementTooltips();
3282 } else {
3283 int heightdiff = CalcHeightdiff(b, distance, t0, t1);
3284 /* If we are showing a tooltip for horizontal or vertical drags,
3285 * 2 tiles have a length of 1. To bias towards the ceiling we add
3286 * one before division. It feels more natural to count 3 lengths as 2 */
3287 if ((b & HT_DIR_MASK) != HT_DIR_X && (b & HT_DIR_MASK) != HT_DIR_Y) {
3288 distance = CeilDiv(distance, 2);
3289 }
3290
3291 if (heightdiff == 0) {
3292 ShowMeasurementTooltips(GetEncodedString(STR_MEASURE_LENGTH, distance));
3293 } else {
3294 ShowMeasurementTooltips(GetEncodedString(STR_MEASURE_LENGTH_HEIGHTDIFF, distance, heightdiff));
3295 }
3296 }
3297 }
3298
3299 _thd.selend.x = x;
3300 _thd.selend.y = y;
3301 _thd.next_drawstyle = b;
3302}
3303
3312{
3313 int sx, sy;
3314 HighLightStyle style;
3315
3316 if (x == -1) {
3317 _thd.selend.x = -1;
3318 return;
3319 }
3320
3321 /* Special handling of drag in any (8-way) direction */
3322 if (method & (VPM_RAILDIRS | VPM_SIGNALDIRS)) {
3323 _thd.selend.x = x;
3324 _thd.selend.y = y;
3325 CalcRaildirsDrawstyle(x, y, method);
3326 return;
3327 }
3328
3329 /* Needed so level-land is placed correctly */
3330 if ((_thd.next_drawstyle & HT_DRAG_MASK) == HT_POINT) {
3331 x += TILE_SIZE / 2;
3332 y += TILE_SIZE / 2;
3333 }
3334
3335 sx = _thd.selstart.x;
3336 sy = _thd.selstart.y;
3337
3338 int limit = 0;
3339
3340 switch (method) {
3341 case VPM_X_OR_Y: // drag in X or Y direction
3342 if (abs(sy - y) < abs(sx - x)) {
3343 y = sy;
3344 style = HT_DIR_X;
3345 } else {
3346 x = sx;
3347 style = HT_DIR_Y;
3348 }
3349 goto calc_heightdiff_single_direction;
3350
3351 case VPM_X_LIMITED: // Drag in X direction (limited size).
3352 limit = (_thd.sizelimit - 1) * TILE_SIZE;
3353 [[fallthrough]];
3354
3355 case VPM_FIX_X: // drag in Y direction
3356 x = sx;
3357 style = HT_DIR_Y;
3358 goto calc_heightdiff_single_direction;
3359
3360 case VPM_Y_LIMITED: // Drag in Y direction (limited size).
3361 limit = (_thd.sizelimit - 1) * TILE_SIZE;
3362 [[fallthrough]];
3363
3364 case VPM_FIX_Y: // drag in X direction
3365 y = sy;
3366 style = HT_DIR_X;
3367
3368calc_heightdiff_single_direction:;
3369 if (limit > 0) {
3370 x = sx + Clamp(x - sx, -limit, limit);
3371 y = sy + Clamp(y - sy, -limit, limit);
3372 }
3373 if (_settings_client.gui.measure_tooltip) {
3374 TileIndex t0 = TileVirtXY(sx, sy);
3375 TileIndex t1 = TileVirtXY(x, y);
3376 uint distance = DistanceManhattan(t0, t1) + 1;
3377
3378 if (distance == 1) {
3379 HideMeasurementTooltips();
3380 } else {
3381 /* With current code passing a HT_LINE style to calculate the height
3382 * difference is enough. However if/when a point-tool is created
3383 * with this method, function should be called with new_style (below)
3384 * instead of HT_LINE | style case HT_POINT is handled specially
3385 * new_style := (_thd.next_drawstyle & HT_RECT) ? HT_LINE | style : _thd.next_drawstyle; */
3386 int heightdiff = CalcHeightdiff(HT_LINE | style, 0, t0, t1);
3387
3388 if (heightdiff == 0) {
3389 ShowMeasurementTooltips(GetEncodedString(STR_MEASURE_LENGTH, distance));
3390 } else {
3391 ShowMeasurementTooltips(GetEncodedString(STR_MEASURE_LENGTH_HEIGHTDIFF, distance, heightdiff));
3392 }
3393 }
3394 }
3395 break;
3396
3397 case VPM_X_AND_Y_LIMITED: // Drag an X by Y constrained rect area.
3398 limit = (_thd.sizelimit - 1) * TILE_SIZE;
3399 x = sx + Clamp(x - sx, -limit, limit);
3400 y = sy + Clamp(y - sy, -limit, limit);
3401 [[fallthrough]];
3402
3403 case VPM_X_AND_Y: // drag an X by Y area
3404 if (_settings_client.gui.measure_tooltip) {
3405 TileIndex t0 = TileVirtXY(sx, sy);
3406 TileIndex t1 = TileVirtXY(x, y);
3407 uint dx = Delta(TileX(t0), TileX(t1)) + 1;
3408 uint dy = Delta(TileY(t0), TileY(t1)) + 1;
3409
3410 /* If dragging an area (eg dynamite tool) and it is actually a single
3411 * row/column, change the type to 'line' to get proper calculation for height */
3412 style = (HighLightStyle)_thd.next_drawstyle;
3413 if (_thd.IsDraggingDiagonal()) {
3414 /* Determine the "area" of the diagonal dragged selection.
3415 * We assume the area is the number of tiles along the X
3416 * edge and the number of tiles along the Y edge. However,
3417 * multiplying these two numbers does not give the exact
3418 * number of tiles; basically we are counting the black
3419 * squares on a chess board and ignore the white ones to
3420 * make the tile counts at the edges match up. There is no
3421 * other way to make a proper count though.
3422 *
3423 * First convert to the rotated coordinate system. */
3424 int dist_x = TileX(t0) - TileX(t1);
3425 int dist_y = TileY(t0) - TileY(t1);
3426 int a_max = dist_x + dist_y;
3427 int b_max = dist_y - dist_x;
3428
3429 /* Now determine the size along the edge, but due to the
3430 * chess board principle this counts double. */
3431 a_max = abs(a_max + (a_max > 0 ? 2 : -2)) / 2;
3432 b_max = abs(b_max + (b_max > 0 ? 2 : -2)) / 2;
3433
3434 /* We get a 1x1 on normal 2x1 rectangles, due to it being
3435 * a seen as two sides. As the result for actual building
3436 * will be the same as non-diagonal dragging revert to that
3437 * behaviour to give it a more normally looking size. */
3438 if (a_max != 1 || b_max != 1) {
3439 dx = a_max;
3440 dy = b_max;
3441 }
3442 } else if (style & HT_RECT) {
3443 if (dx == 1) {
3444 style = HT_LINE | HT_DIR_Y;
3445 } else if (dy == 1) {
3446 style = HT_LINE | HT_DIR_X;
3447 }
3448 }
3449
3450 if (dx != 1 || dy != 1) {
3451 int heightdiff = CalcHeightdiff(style, 0, t0, t1);
3452
3453 dx -= (style & HT_POINT ? 1 : 0);
3454 dy -= (style & HT_POINT ? 1 : 0);
3455
3456 if (heightdiff == 0) {
3457 ShowMeasurementTooltips(GetEncodedString(STR_MEASURE_AREA, dx, dy));
3458 } else {
3459 ShowMeasurementTooltips(GetEncodedString(STR_MEASURE_AREA_HEIGHTDIFF, dx, dy, heightdiff));
3460 }
3461 }
3462 }
3463 break;
3464
3465 default: NOT_REACHED();
3466 }
3467
3468 _thd.selend.x = x;
3469 _thd.selend.y = y;
3470}
3471
3477{
3479
3480 /* stop drag mode if the window has been closed */
3481 Window *w = _thd.GetCallbackWnd();
3482 if (w == nullptr) {
3484 return ES_HANDLED;
3485 }
3486
3487 /* while dragging execute the drag procedure of the corresponding window (mostly VpSelectTilesWithMethod() ) */
3488 if (_left_button_down) {
3490 /* Only register a drag event when the mouse moved. */
3491 if (_thd.new_pos.x == _thd.selstart.x && _thd.new_pos.y == _thd.selstart.y) return ES_HANDLED;
3492 _thd.selstart.x = _thd.new_pos.x;
3493 _thd.selstart.y = _thd.new_pos.y;
3494 }
3495
3496 w->OnPlaceDrag(_thd.select_method, _thd.select_proc, GetTileBelowCursor());
3497 return ES_HANDLED;
3498 }
3499
3500 /* Mouse button released. */
3503
3504 /* Keep the selected tool, but reset it to the original mode. */
3505 HighLightStyle others = _thd.place_mode & ~(HT_DRAG_MASK | HT_DIR_MASK);
3506 if ((_thd.next_drawstyle & HT_DRAG_MASK) == HT_RECT) {
3507 _thd.place_mode = HT_RECT | others;
3508 } else if (_thd.select_method & VPM_SIGNALDIRS) {
3509 _thd.place_mode = HT_RECT | others;
3510 } else if (_thd.select_method & VPM_RAILDIRS) {
3511 _thd.place_mode = (_thd.select_method & ~VPM_RAILDIRS) ? _thd.next_drawstyle : (HT_RAIL | others);
3512 } else {
3513 _thd.place_mode = HT_POINT | others;
3514 }
3515 SetTileSelectSize(1, 1);
3516
3517 HideMeasurementTooltips();
3518 w->OnPlaceMouseUp(_thd.select_method, _thd.select_proc, _thd.selend, TileVirtXY(_thd.selstart.x, _thd.selstart.y), TileVirtXY(_thd.selend.x, _thd.selend.y));
3519
3520 return ES_HANDLED;
3521}
3522
3531{
3532 SetObjectToPlace(icon, pal, mode, w->window_class, w->window_number);
3533}
3534
3535#include "table/animcursors.h"
3536
3545void SetObjectToPlace(CursorID icon, PaletteID pal, HighLightStyle mode, WindowClass window_class, WindowNumber window_num)
3546{
3547 if (_thd.window_class != WC_INVALID) {
3548 /* Undo clicking on button and drag & drop */
3549 Window *w = _thd.GetCallbackWnd();
3550 /* Call the abort function, but set the window class to something
3551 * that will never be used to avoid infinite loops. Setting it to
3552 * the 'next' window class must not be done because recursion into
3553 * this function might in some cases reset the newly set object to
3554 * place or not properly reset the original selection. */
3555 _thd.window_class = WC_INVALID;
3556 if (w != nullptr) {
3557 w->OnPlaceObjectAbort();
3558 HideMeasurementTooltips();
3559 }
3560 }
3561
3562 /* Mark the old selection dirty, in case the selection shape or colour changes */
3563 if ((_thd.drawstyle & HT_DRAG_MASK) != HT_NONE) SetSelectionTilesDirty();
3564
3565 SetTileSelectSize(1, 1);
3566
3567 _thd.make_square_red = false;
3568
3569 if (mode == HT_DRAG) { // HT_DRAG is for dragdropping trains in the depot window
3570 mode = HT_NONE;
3572 } else {
3574 }
3575
3576 _thd.place_mode = mode;
3577 _thd.window_class = window_class;
3578 _thd.window_number = window_num;
3579
3580 if ((mode & HT_DRAG_MASK) == HT_SPECIAL) { // special tools, like tunnels or docks start with presizing mode
3581 VpStartPreSizing();
3582 }
3583
3584 SetCursor(icon, pal);
3585}
3586
3592
3593Point GetViewportStationMiddle(const Viewport &vp, const Station *st)
3594{
3595 int x = TileX(st->xy) * TILE_SIZE;
3596 int y = TileY(st->xy) * TILE_SIZE;
3597 int z = GetSlopePixelZ(Clamp(x, 0, Map::SizeX() * TILE_SIZE - 1), Clamp(y, 0, Map::SizeY() * TILE_SIZE - 1));
3598
3599 Point p = RemapCoords(x, y, z);
3600 p.x = UnScaleByZoom(p.x - vp.virtual_left, vp.zoom) + vp.left;
3601 p.y = UnScaleByZoom(p.y - vp.virtual_top, vp.zoom) + vp.top;
3602 return p;
3603}
3604
3610
3613#ifdef WITH_SSE
3614 { &ViewportSortParentSpritesSSE41Checker, &ViewportSortParentSpritesSSE41 },
3615#endif
3616 { []() { return true; /* Always available */ }, &ViewportSortParentSprites }
3617};
3618
3621{
3622 for (const auto &sprite_sorter : _vp_sprite_sorters) {
3623 if (sprite_sorter.fct_checker()) {
3624 _vp_sprite_sorter = sprite_sorter.fct_sorter;
3625 break;
3626 }
3627 }
3628 assert(_vp_sprite_sorter != nullptr);
3629}
3630
3639CommandCost CmdScrollViewport(DoCommandFlags flags, TileIndex tile, ViewportScrollTarget target, uint32_t ref)
3640{
3641 if (_current_company != OWNER_DEITY) return CMD_ERROR;
3642 switch (target) {
3643 case VST_EVERYONE:
3644 break;
3645 case VST_COMPANY:
3646 if (_local_company != (CompanyID)ref) return CommandCost();
3647 break;
3648 case VST_CLIENT:
3649 if (_network_own_client_id != (ClientID)ref) return CommandCost();
3650 break;
3651 default:
3652 return CMD_ERROR;
3653 }
3654
3655 if (flags.Test(DoCommandFlag::Execute)) {
3658 }
3659 return CommandCost();
3660}
3661
3662void MarkCatchmentTilesDirty()
3663{
3664 if (_viewport_highlight_town != nullptr) {
3666 return;
3667 }
3668
3669 if (_viewport_highlight_station != nullptr) {
3670 if (_viewport_highlight_station->catchment_tiles.tile == INVALID_TILE) {
3673 } else {
3675 for (TileIndex tile = it; tile != INVALID_TILE; tile = ++it) {
3676 MarkTileDirtyByTile(tile);
3677 }
3678 }
3679 }
3680
3681 if (_viewport_highlight_station_rect != nullptr) {
3682 if (!_viewport_highlight_station_rect->IsInUse()) {
3684 }
3686 }
3687
3688 if (_viewport_highlight_waypoint != nullptr) {
3689 if (!_viewport_highlight_waypoint->IsInUse()) {
3691 }
3693 }
3694
3695 if (_viewport_highlight_waypoint_rect != nullptr) {
3696 if (!_viewport_highlight_waypoint_rect->IsInUse()) {
3698 }
3700 }
3701}
3702
3703static void SetWindowDirtyForViewportCatchment()
3704{
3710}
3711
3712static void ClearViewportCatchment()
3713{
3714 MarkCatchmentTilesDirty();
3719 _viewport_highlight_town = nullptr;
3720}
3721
3728void SetViewportCatchmentStation(const Station *st, bool sel)
3729{
3730 SetWindowDirtyForViewportCatchment();
3731 /* Mark tiles dirty for redrawing and update selected station if a different station is already highlighted. */
3732 if (sel && _viewport_highlight_station != st) {
3733 ClearViewportCatchment();
3735 MarkCatchmentTilesDirty();
3736 /* Mark tiles dirty for redrawing and clear station selection if deselecting highlight. */
3737 } else if (!sel && _viewport_highlight_station == st) {
3738 MarkCatchmentTilesDirty();
3740 }
3741 /* Redraw the currently selected station window */
3743}
3744
3751void SetViewportStationRect(const Station *st, bool sel)
3752{
3753 SetWindowDirtyForViewportCatchment();
3754 /* Mark tiles dirty for redrawing and update selected station if a different station is already highlighted. */
3755 if (sel && _viewport_highlight_station_rect != st) {
3756 ClearViewportCatchment();
3758 MarkCatchmentTilesDirty();
3759 /* Mark tiles dirty for redrawing and clear station selection if deselecting highlight. */
3760 } else if (!sel && _viewport_highlight_station_rect == st) {
3761 MarkCatchmentTilesDirty();
3763 }
3764 /* Redraw the currently selected station window */
3766}
3767
3775{
3776 SetWindowDirtyForViewportCatchment();
3777 /* Mark tiles dirty for redrawing and update selected waypoint if a different waypoint is already highlighted. */
3778 if (sel && _viewport_highlight_waypoint != wp) {
3779 ClearViewportCatchment();
3781 MarkCatchmentTilesDirty();
3782 /* Mark tiles dirty for redrawing and clear waypoint selection if deselecting highlight. */
3783 } else if (!sel && _viewport_highlight_waypoint == wp) {
3784 MarkCatchmentTilesDirty();
3786 }
3787 /* Redraw the currently selected waypoint window */
3789}
3790
3797void SetViewportWaypointRect(const Waypoint *wp, bool sel)
3798{
3799 SetWindowDirtyForViewportCatchment();
3800 /* Mark tiles dirty for redrawing and update selected waypoint if a different waypoint is already highlighted. */
3801 if (sel && _viewport_highlight_waypoint_rect != wp) {
3802 ClearViewportCatchment();
3804 MarkCatchmentTilesDirty();
3805 /* Mark tiles dirty for redrawing and clear waypoint selection if deselecting highlight. */
3806 } else if (!sel && _viewport_highlight_waypoint_rect == wp) {
3807 MarkCatchmentTilesDirty();
3809 }
3810 /* Redraw the currently selected waypoint window */
3812}
3813
3820void SetViewportCatchmentTown(const Town *t, bool sel)
3821{
3822 SetWindowDirtyForViewportCatchment();
3823 /* Mark tiles dirty for redrawing and update selected town if a different town is already highlighted. */
3824 if (sel && _viewport_highlight_town != t) {
3825 ClearViewportCatchment();
3828 /* Mark tiles dirty for redrawing and clear town selection if deselecting highlight. */
3829 } else if (!sel && _viewport_highlight_town == t) {
3830 _viewport_highlight_town = nullptr;
3832 }
3833 /* Redraw the currently selected town window */
3835}
3836
3841void ViewportData::CancelFollow(const Window &viewport_window)
3842{
3843 if (this->follow_vehicle == VehicleID::Invalid()) return;
3844
3845 if (viewport_window.window_class == WC_MAIN_WINDOW) {
3846 /* We're cancelling follow in the main viewport, so we need to check for a vehicle view window
3847 * to raise the location follow widget. */
3848 Window *vehicle_window = FindWindowById(WC_VEHICLE_VIEW, this->follow_vehicle);
3849 if (vehicle_window != nullptr) vehicle_window->RaiseWidgetWhenLowered(WID_VV_LOCATION);
3850 }
3851
3852 this->follow_vehicle = VehicleID::Invalid();
3853}
This file defines all the the animated cursors.
Highlight/sprite information for autorail.
static const int _AutorailTilehSprite[][6]
Table maps each of the six rail directions and tileh combinations to a sprite.
Definition autorail.h:25
static const HighLightStyle _autorail_piece[][16]
Maps each pixel of a tile (16x16) to a selection type (0,0) is the top corner, (16,...
Definition autorail.h:63
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:139
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.
RAII class for measuring multi-step elements of performance.
static WidgetDimensions scaled
Widget dimensions scaled for current zoom level.
Definition window_gui.h:30
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.
DiagDirection
Enumeration for diagonal directions.
@ DIAGDIR_NE
Northeast, upper right on your monitor.
@ DIAGDIR_NW
Northwest.
@ DIAGDIR_SE
Southeast.
@ DIAGDIR_SW
Southwest.
constexpr std::underlying_type_t< enum_type > to_underlying(enum_type e)
Implementation of std::to_underlying (from C++23).
Definition enum_type.hpp:21
Factory to 'query' all available blitters.
int GetCharacterHeight(FontSize size)
Get height of a character for a given font size.
Definition fontcache.cpp:88
Types for recording game performance data.
@ PFE_DRAWWORLD
Time spent drawing world viewports in GUI.
bool _left_button_down
Is left mouse button pressed?
Definition gfx.cpp:42
Dimension GetStringBoundingBox(std::string_view str, FontSize start_fontsize)
Return the string dimension in pixels.
Definition gfx.cpp:900
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:669
bool _ctrl_pressed
Is Ctrl pressed?
Definition gfx.cpp:39
void SetCursor(CursorID icon, PaletteID pal)
Assign an animation or a non-animated sprite to the cursor.
Definition gfx.cpp:1738
void DrawSpriteViewport(SpriteID img, PaletteID pal, int x, int y, const SubSprite *sub)
Draw a sprite in a viewport.
Definition gfx.cpp:1010
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:426
uint32_t SpriteID
The number of a sprite, without mapping bits and colourtables.
Definition gfx_type.h:17
@ Normal
The most basic (normal) sprite.
Definition gfx_type.h:358
@ Small
Index of the small font in the font tables.
Definition gfx_type.h:250
@ 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
Colours
One of 16 base colours used for companies and windows/widgets.
Definition gfx_type.h:283
@ White
White.
Definition gfx_type.h:300
@ Invalid
Invalid marker.
Definition gfx_type.h:302
@ Grey
Grey.
Definition gfx_type.h:299
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:980
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:1521
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:1554
void RedrawScreenRect(int left, int top, int right, int bottom)
Repaints a specific rectangle of the screen.
Definition gfx.cpp:1431
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 EnumClassIndexContainer< std::array< const TileTypeProcs *, to_underlying(TileType::MaxSize)>, TileType > _tile_type_procs
Tile callback functions for each type of tile.
Definition landscape.cpp:69
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.
#define Rect
Macro that prevents name conflicts between included headers.
#define Point
Macro that prevents name conflicts between included headers.
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:169
static TileIndex TileVirtXY(uint x, uint y)
Get a tile from the virtual XY-coordinate.
Definition map_func.h:407
TileIndex TileAddByDir(TileIndex tile, Direction dir)
Adds a Direction to a tile.
Definition map_func.h:603
TileIndexDiff ToTileIndexDiff(TileIndexDiffC tidc)
Return the offset between two tiles from a TileIndexDiffC struct.
Definition map_func.h:444
static TileIndex TileXY(uint x, uint y)
Returns the TileIndex of a coordinate.
Definition map_func.h:376
static uint TileY(TileIndex tile)
Get the Y component of a tile.
Definition map_func.h:429
static uint TileX(TileIndex tile)
Get the X component of a tile.
Definition map_func.h:419
constexpr TileIndex TileAdd(TileIndex tile, TileIndexDiff offset)
Adds a given offset to a tile.
Definition map_func.h:461
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:687
ClientID _network_own_client_id
Our client identifier.
Definition network.cpp:72
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:393
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.
PoolID< uint16_t, struct SignIDTag, 64000, 0xFFFF > SignID
The type of the IDs of signs.
Definition signs_type.h:16
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:21
Slope
Enumeration for the slope-type.
Definition slope_type.h:47
@ SLOPE_N
the north corner of the tile is raised
Definition slope_type.h:52
@ SLOPE_STEEP_N
a steep slope falling to south (from north)
Definition slope_type.h:68
bool ScrollMainWindowTo(int x, int y, int z, bool instant)
Scrolls the main window to given coordinates.
@ Town
Source/destination is a town.
Definition source_type.h:22
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 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.
Owner owner
The owner of this station.
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
T y
Y coordinate.
T x
X coordinate.
T x
X coordinate.
T y
Y coordinate.
T z
Z coordinate.
Data about how and where to blit pixels.
Definition gfx_type.h:157
static uint SizeX()
Get the size of the map along the X.
Definition map_func.h:262
static uint SizeY()
Get the size of the map along the Y.
Definition map_func.h:271
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:344
static uint MaxY()
Gets the maximum Y coordinate within the map, including TileType::Void.
Definition map_func.h:298
static uint MaxX()
Gets the maximum X coordinate within the map, including TileType::Void.
Definition map_func.h:289
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 Pool::IterateWrapper< Town > Iterate(size_t from=0)
static Vehicle * Get(auto index)
Specification of a rectangle with absolute coordinates of all edges.
static bool IsExpected(const BaseStation *st)
static Pool::IterateWrapper< Station > Iterate(size_t from=0)
static Station * Get(auto index)
static Waypoint * From(BaseStation *st)
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:567
Station data structure.
Used to only draw a part of the sprite.
Definition gfx_type.h:278
Metadata about the current highlighting.
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 IsDraggingDiagonal()
Is the user dragging a 'diagonal rectangle'?
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.
Point pos
Location, in tile "units", of the northern tile of the selected area.
void Reset()
Reset tile highlighting.
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:33
Slope tileh
Slope of the tile.
Definition tile_cmd.h:34
TileIndex tile
Tile index.
Definition tile_cmd.h:35
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
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:201
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.
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:570
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:829
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:807
WindowIterator< false > IteratorToFront
Iterate in Z order towards front.
Definition window_gui.h:918
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:850
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:839
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:940
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:77
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
StrongType::Typedef< uint32_t, struct TileIndexTag, StrongType::Compare, StrongType::Integer, StrongType::Compatible< int32_t >, StrongType::Compatible< int64_t > > TileIndex
The index/ID of a Tile.
Definition tile_type.h:92
constexpr TileIndex INVALID_TILE
The very nice invalid tile marker.
Definition tile_type.h:100
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:48
@ Station
A tile of a station or airport.
Definition tile_type.h:54
@ Void
Invisible tiles at the SW and SE border.
Definition tile_type.h:56
@ House
A house by a town.
Definition tile_type.h:52
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.
@ Sign
Toggle sign creation tool.
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:1151
Vehicle * CheckClickOnVehicle(const Viewport &vp, int x, int y)
Find the vehicle close to the clicked coordinates.
Definition vehicle.cpp:1245
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 Commands::StartStopVehicle 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:797
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:910
void StartSpriteCombine()
Starts a block of sprites, which are "combined" into a single bounding box.
Definition viewport.cpp:759
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)
Determine 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:785
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:970
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.
static HighLightStyle Check2x1AutoRail(DiagDirection direction)
What would be the highlight style when trying to build a 2 tile long piece of rail.
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:658
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:769
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:820
static void CheckUnderflow(int &test, int &other, int mult)
Check for underflowing the map.
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:893
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)
Determine and set the draw style, as well as the end point of the drag.
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)
Prepare state for highlighting tiles while dragging 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:866
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.
Declarations for accessing the k-d tree of viewports.
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:309
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:1209
Window * GetMainWindow()
Get the main window, i.e.
Definition window.cpp:1195
Window * FindWindowFromPt(int x, int y)
Do a search for a window at specific coordinates.
Definition window.cpp:1858
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:1166
void SetWindowDirty(WindowClass cls, WindowNumber number)
Mark window as dirty (in need of repainting).
Definition window.cpp:3200
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.
Definition window_gui.h:25
@ 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:21
EventState
State of handling an event.
@ ES_HANDLED
The passed event is handled.
@ ES_NOT_HANDLED
The passed event is not handled.
@ 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:57
@ WC_TOWN_VIEW
Town view; Window numbers:
@ WC_TOOLTIPS
Tooltip window; Window numbers:
@ WC_VEHICLE_VIEW
Vehicle view; Window numbers:
@ Waypoint
waypoint encountered (could be a target next time)
Definition yapf_type.hpp:25
@ Station
station encountered (could be a target next time)
Definition yapf_type.hpp:26
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.
Definition zoom_type.h:22
@ Min
Minimum zoom level.
Definition zoom_type.h:23
@ End
End for iteration.
Definition zoom_type.h:31
@ Out4x
Zoomed 4 times out.
Definition zoom_type.h:28