OpenTTD Source 20260621-master-g720d10536d
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;
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 class FoundationPart : uint8_t {
145 None = 0xFF,
146 Normal = 0,
149};
150
155enum class SpriteCombineMode : uint8_t {
159};
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
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(foundation_part < FoundationPart::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 == FoundationPart::None) _vd.foundation_part = FoundationPart::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
590{
591 switch (foundation_part) {
594 default: NOT_REACHED();
595 }
596}
597
605void OffsetGroundSprite(int x, int y)
606{
607 /* Switch to next foundation part */
608 _vd.foundation_part = GetNextFoundationPart(_vd.foundation_part);
609
610 /* _vd.last_child is LAST_CHILD_NONE if foundation sprite was clipped by the viewport bounds */
611 if (_vd.last_child != LAST_CHILD_NONE) _vd.foundation[_vd.foundation_part] = static_cast<uint>(_vd.parent_sprites_to_draw.size()) - 1;
612
613 _vd.foundation_offset[_vd.foundation_part].x = x * ZOOM_BASE;
614 _vd.foundation_offset[_vd.foundation_part].y = y * ZOOM_BASE;
615 _vd.last_foundation_child[_vd.foundation_part] = _vd.last_child;
616}
617
629static void AddCombinedSprite(SpriteID image, PaletteID pal, int x, int y, int z, const SubSprite *sub)
630{
631 Point pt = RemapCoords(x, y, z);
632 const Sprite *spr = GetSprite(image & SPRITE_MASK, SpriteType::Normal);
633
634 if (pt.x + spr->x_offs >= _vd.dpi.left + _vd.dpi.width ||
635 pt.x + spr->x_offs + spr->width <= _vd.dpi.left ||
636 pt.y + spr->y_offs >= _vd.dpi.top + _vd.dpi.height ||
637 pt.y + spr->y_offs + spr->height <= _vd.dpi.top)
638 return;
639
640 const ParentSpriteToDraw &pstd = _vd.parent_sprites_to_draw.back();
641 AddChildSpriteScreen(image, pal, pt.x - pstd.left, pt.y - pstd.top, false, sub, false);
642}
643
664void AddSortableSpriteToDraw(SpriteID image, PaletteID pal, int x, int y, int z, const SpriteBounds &bounds, bool transparent, const SubSprite *sub)
665{
666 int32_t left, right, top, bottom;
667
668 assert((image & SPRITE_MASK) < MAX_SPRITES);
669
670 /* Move to bounding box. */
671 x += bounds.origin.x;
672 y += bounds.origin.y;
673 z += bounds.origin.z;
674
675 /* make the sprites transparent with the right palette */
676 if (transparent) {
679 }
680
681 if (_vd.combine_sprites == SpriteCombineMode::Active) {
682 AddCombinedSprite(image, pal, x + bounds.offset.x, y + bounds.offset.y, z + bounds.offset.z, sub);
683 return;
684 }
685
686 _vd.last_child = LAST_CHILD_NONE;
687
688 Point pt = RemapCoords(x + bounds.offset.x, y + bounds.offset.y, z + bounds.offset.z);
689 int tmp_left, tmp_top, tmp_x = pt.x, tmp_y = pt.y;
690
691 /* Compute screen extents of sprite */
692 if (image == SPR_EMPTY_BOUNDING_BOX) {
693 left = tmp_left = RemapCoords(x + bounds.extent.x, y, z).x;
694 right = RemapCoords(x, y + bounds.extent.y, z).x + 1;
695 top = tmp_top = RemapCoords(x, y, z + bounds.extent.z).y;
696 bottom = RemapCoords(x + bounds.extent.x, y + bounds.extent.y, z).y + 1;
697 } else {
698 const Sprite *spr = GetSprite(image & SPRITE_MASK, SpriteType::Normal);
699 left = tmp_left = (pt.x += spr->x_offs);
700 right = (pt.x + spr->width );
701 top = tmp_top = (pt.y += spr->y_offs);
702 bottom = (pt.y + spr->height);
703 }
704
705 if (_draw_bounding_boxes && (image != SPR_EMPTY_BOUNDING_BOX)) {
706 /* Compute maximal extents of sprite and its bounding box */
707 left = std::min(left , RemapCoords(x + bounds.extent.x, y, z).x);
708 right = std::max(right , RemapCoords(x, y + bounds.extent.y, z).x + 1);
709 top = std::min(top , RemapCoords(x, y, z + bounds.extent.z).y);
710 bottom = std::max(bottom, RemapCoords(x + bounds.extent.x, y + bounds.extent.y, z).y + 1);
711 }
712
713 /* Do not add the sprite to the viewport, if it is outside */
714 if (left >= _vd.dpi.left + _vd.dpi.width ||
715 right <= _vd.dpi.left ||
716 top >= _vd.dpi.top + _vd.dpi.height ||
717 bottom <= _vd.dpi.top) {
718 return;
719 }
720
721 ParentSpriteToDraw &ps = _vd.parent_sprites_to_draw.emplace_back();
722 ps.x = tmp_x;
723 ps.y = tmp_y;
724
725 ps.left = tmp_left;
726 ps.top = tmp_top;
727
728 ps.image = image;
729 ps.pal = pal;
730 ps.sub = sub;
731 ps.xmin = x;
732 ps.xmax = x + bounds.extent.x - 1;
733
734 ps.ymin = y;
735 ps.ymax = y + bounds.extent.y - 1;
736
737 ps.zmin = z;
738 ps.zmax = z + bounds.extent.z - 1;
739
741
742 _vd.last_child = LAST_CHILD_PARENT;
743
744 if (_vd.combine_sprites == SpriteCombineMode::Pending) _vd.combine_sprites = SpriteCombineMode::Active;
745}
746
766{
767 assert(_vd.combine_sprites == SpriteCombineMode::None);
768 _vd.combine_sprites = SpriteCombineMode::Pending;
769}
770
776{
777 assert(_vd.combine_sprites != SpriteCombineMode::None);
778 _vd.combine_sprites = SpriteCombineMode::None;
779}
780
791static bool IsInRangeInclusive(int begin, int end, int check)
792{
793 if (begin > end) std::swap(begin, end);
794 return begin <= check && check <= end;
795}
796
803bool IsInsideRotatedRectangle(int x, int y)
804{
805 int dist_a = (_thd.size.x + _thd.size.y); // Rotated coordinate system for selected rectangle.
806 int dist_b = (_thd.size.x - _thd.size.y); // We don't have to divide by 2. It's all relative!
807 int a = ((x - _thd.pos.x) + (y - _thd.pos.y)); // Rotated coordinate system for the point under scrutiny.
808 int b = ((x - _thd.pos.x) - (y - _thd.pos.y));
809
810 /* Check if a and b are between 0 and dist_a or dist_b respectively. */
811 return IsInRangeInclusive(dist_a, 0, a) && IsInRangeInclusive(dist_b, 0, b);
812}
813
826void AddChildSpriteScreen(SpriteID image, PaletteID pal, int x, int y, bool transparent, const SubSprite *sub, bool scale, bool relative)
827{
828 assert((image & SPRITE_MASK) < MAX_SPRITES);
829
830 /* If the ParentSprite was clipped by the viewport bounds, do not draw the ChildSprites either */
831 if (_vd.last_child == LAST_CHILD_NONE) return;
832
833 /* make the sprites transparent with the right palette */
834 if (transparent) {
837 }
838
839 int32_t child_id = static_cast<int32_t>(_vd.child_screen_sprites_to_draw.size());
840 if (_vd.last_child != LAST_CHILD_PARENT) {
841 _vd.child_screen_sprites_to_draw[_vd.last_child].next = child_id;
842 } else {
843 _vd.parent_sprites_to_draw.back().first_child = child_id;
844 }
845
846 ChildScreenSpriteToDraw &cs = _vd.child_screen_sprites_to_draw.emplace_back();
847 cs.image = image;
848 cs.pal = pal;
849 cs.sub = sub;
850 cs.x = scale ? x * ZOOM_BASE : x;
851 cs.y = scale ? y * ZOOM_BASE : y;
852 cs.relative = relative;
854
855 /* Append the sprite to the active ChildSprite list.
856 * If the active ParentSprite is a foundation, update last_foundation_child as well.
857 * Note: ChildSprites of foundations are NOT sequential in the vector, as selection sprites are added at last. */
858 if (_vd.last_foundation_child[FoundationPart::Normal] == _vd.last_child) _vd.last_foundation_child[FoundationPart::Normal] = child_id;
859 if (_vd.last_foundation_child[FoundationPart::Halftile] == _vd.last_child) _vd.last_foundation_child[FoundationPart::Halftile] = child_id;
860 _vd.last_child = child_id;
861}
862
872static std::string &AddStringToDraw(int x, int y, Colours colour, ViewportStringFlags flags, uint16_t width)
873{
874 assert(width != 0);
875 StringSpriteToDraw &ss = _vd.string_sprites_to_draw.emplace_back();
876 ss.colour = colour;
877 ss.flags = flags;
878 ss.x = x;
879 ss.y = y;
880 ss.width = width;
881
882 return ss.string;
883}
884
885
899static 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)
900{
901 if (_vd.foundation[foundation_part] == -1) {
902 /* draw on real ground */
903 AddTileSpriteToDraw(image, pal, ti->x, ti->y, ti->z + z_offset, nullptr, extra_offs_x, extra_offs_y);
904 } else {
905 /* draw on top of foundation */
906 AddChildSpriteToFoundation(image, pal, nullptr, foundation_part, extra_offs_x, extra_offs_y - z_offset * ZOOM_BASE);
907 }
908}
909
916static void DrawTileSelectionRect(const TileInfo *ti, PaletteID pal)
917{
918 if (!IsValidTile(ti->tile)) return;
919
920 SpriteID sel;
921 if (IsHalftileSlope(ti->tileh)) {
922 Corner halftile_corner = GetHalftileSlopeCorner(ti->tileh);
923 SpriteID sel2 = SPR_HALFTILE_SELECTION_FLAT + halftile_corner;
925
926 Corner opposite_corner = OppositeCorner(halftile_corner);
927 if (IsSteepSlope(ti->tileh)) {
928 sel = SPR_HALFTILE_SELECTION_DOWN;
929 } else {
930 sel = ((ti->tileh & SlopeWithOneCornerRaised(opposite_corner)) != 0 ? SPR_HALFTILE_SELECTION_UP : SPR_HALFTILE_SELECTION_FLAT);
931 }
932 sel += opposite_corner;
933 } else {
934 sel = SPR_SELECT_TILE + SlopeToSpriteOffset(ti->tileh);
935 }
937}
938
939static bool IsPartOfAutoLine(int px, int py)
940{
941 px -= _thd.selstart.x;
942 py -= _thd.selstart.y;
943
944 if ((_thd.drawstyle & HT_DRAG_MASK) != HT_LINE) return false;
945
946 switch (_thd.drawstyle & HT_DIR_MASK) {
947 case HT_DIR_X: return py == 0; // x direction
948 case HT_DIR_Y: return px == 0; // y direction
949 case HT_DIR_HU: return px == -py || px == -py - 16; // horizontal upper
950 case HT_DIR_HL: return px == -py || px == -py + 16; // horizontal lower
951 case HT_DIR_VL: return px == py || px == py + 16; // vertical left
952 case HT_DIR_VR: return px == py || px == py - 16; // vertical right
953 default:
954 NOT_REACHED();
955 }
956}
957
958/* [direction][side] */
959static const HighLightStyle _autorail_type[6][2] = {
960 { HT_DIR_X, HT_DIR_X },
961 { HT_DIR_Y, HT_DIR_Y },
962 { HT_DIR_HU, HT_DIR_HL },
963 { HT_DIR_HL, HT_DIR_HU },
964 { HT_DIR_VL, HT_DIR_VR },
966};
967
968#include "table/autorail.h"
969
976static void DrawAutorailSelection(const TileInfo *ti, HighLightStyle highlight_style)
977{
978 SpriteID image;
979 PaletteID pal;
980
981 FoundationPart foundation_part = FoundationPart::Normal;
982 Slope slope = RemoveHalftileSlope(ti->tileh);
983 if (IsHalftileSlope(ti->tileh)) {
985 Corner halftile_corner = GetHalftileSlopeCorner(ti->tileh);
986 if ((highlight_style & HT_DIR_MASK) != lower_rail[halftile_corner]) {
987 foundation_part = FoundationPart::Halftile;
988 /* Here we draw the highlights of the "three-corners-raised"-slope. That looks ok to me. */
989 slope = SlopeWithThreeCornersRaised(OppositeCorner(halftile_corner));
990 }
991 }
992
993 int offset = _autorail_slope_sprite_offsets[slope][highlight_style & HT_DIR_MASK];
994 if (offset >= 0) {
995 image = SPR_AUTORAIL_BASE + offset;
996 pal = PAL_NONE;
997 } else {
998 image = SPR_AUTORAIL_BASE - offset;
1000 }
1001
1002 DrawSelectionSprite(image, _thd.make_square_red ? PALETTE_SEL_TILE_RED : pal, ti, 7, foundation_part);
1003}
1004
1006enum class TileHighlightType : uint8_t {
1011};
1012
1018
1025{
1026 if (_viewport_highlight_station != nullptr) {
1028 if (_viewport_highlight_station->TileIsInCatchment(t)) return TileHighlightType::Blue;
1029 }
1030
1031 if (_viewport_highlight_station_rect != nullptr) {
1034 if (r->PtInExtendedRect(TileX(t), TileY(t))) return TileHighlightType::Blue;
1035 }
1036
1037 if (_viewport_highlight_waypoint != nullptr) {
1039 }
1040
1041 if (_viewport_highlight_waypoint_rect != nullptr) {
1044 if (r->PtInExtendedRect(TileX(t), TileY(t))) return TileHighlightType::Blue;
1045 }
1046
1047 if (_viewport_highlight_town != nullptr) {
1048 if (IsTileType(t, TileType::House)) {
1049 if (GetTownIndex(t) == _viewport_highlight_town->index) {
1050 for (const Station *st : _viewport_highlight_town->stations_near) {
1051 if (st->owner != _current_company) continue;
1052 if (st->TileIsInCatchment(t)) return TileHighlightType::Blue;
1053 }
1055 }
1056 } else if (IsTileType(t, TileType::Station)) {
1057 for (const Station *st : _viewport_highlight_town->stations_near) {
1058 if (st->owner != _current_company) continue;
1059 if (GetStationIndex(t) == st->index) return TileHighlightType::White;
1060 }
1061 }
1062 }
1063
1065}
1066
1073{
1074 switch (tht) {
1075 default:
1076 case TileHighlightType::None: break;
1077 case TileHighlightType::White: DrawTileSelectionRect(ti, PAL_NONE); break;
1080 }
1081}
1082
1088{
1089 /* Going through cases in order of computational time. */
1090
1091 if (_town_local_authority_kdtree.Count() == 0) return;
1092
1093 /* Tile belongs to town regardless of distance from town. */
1094 if (GetTileType(ti->tile) == TileType::House) {
1095 if (!Town::GetByTile(ti->tile)->show_zone) return;
1096
1098 return;
1099 }
1100
1101 /* If the closest town in the highlighted list is far, we can stop searching. */
1102 TownID tid = _town_local_authority_kdtree.FindNearest(TileX(ti->tile), TileY(ti->tile));
1103 Town *closest_highlighted_town = Town::Get(tid);
1104
1105 if (DistanceManhattan(ti->tile, closest_highlighted_town->xy) >= _settings_game.economy.dist_local_authority) return;
1106
1107 /* Tile is inside of the local autrhority distance of a highlighted town,
1108 but it is possible that a non-highlighted town is even closer. */
1109 Town *closest_town = ClosestTownFromTile(ti->tile, _settings_game.economy.dist_local_authority);
1110
1111 if (closest_town->show_zone) {
1113 }
1114
1115}
1116
1121static void DrawTileSelection(const TileInfo *ti)
1122{
1123 /* Highlight tiles inside local authority of selected towns. */
1125
1126 /* Draw a red error square? */
1127 bool is_redsq = _thd.redsq == ti->tile;
1129
1131 DrawTileHighlightType(ti, tht);
1132
1133 /* No tile selection active? */
1134 if ((_thd.drawstyle & HT_DRAG_MASK) == HT_NONE) return;
1135
1136 if (_thd.diagonal) { // We're drawing a 45 degrees rotated (diagonal) rectangle
1137 if (IsInsideRotatedRectangle((int)ti->x, (int)ti->y)) goto draw_inner;
1138 return;
1139 }
1140
1141 /* Inside the inner area? */
1142 if (IsInsideBS(ti->x, _thd.pos.x, _thd.size.x) &&
1143 IsInsideBS(ti->y, _thd.pos.y, _thd.size.y)) {
1144draw_inner:
1145 if (_thd.drawstyle & HT_RECT) {
1146 if (!is_redsq) DrawTileSelectionRect(ti, _thd.make_square_red ? PALETTE_SEL_TILE_RED : PAL_NONE);
1147 } else if (_thd.drawstyle & HT_POINT) {
1148 /* Figure out the Z coordinate for the single dot. */
1149 int z = 0;
1150 FoundationPart foundation_part = FoundationPart::Normal;
1151 if (ti->tileh & SLOPE_N) {
1152 z += TILE_HEIGHT;
1154 }
1155 if (IsHalftileSlope(ti->tileh)) {
1156 Corner halftile_corner = GetHalftileSlopeCorner(ti->tileh);
1157 if ((halftile_corner == CORNER_W) || (halftile_corner == CORNER_E)) z += TILE_HEIGHT;
1158 if (halftile_corner != CORNER_S) {
1159 foundation_part = FoundationPart::Halftile;
1160 if (IsSteepSlope(ti->tileh)) z -= TILE_HEIGHT;
1161 }
1162 }
1163 DrawSelectionSprite(SPR_DOT, PAL_NONE, ti, z, foundation_part);
1164 } else if (_thd.drawstyle & HT_RAIL) {
1165 /* autorail highlight piece under cursor */
1166 HighLightStyle type = _thd.drawstyle & HT_DIR_MASK;
1167 assert(type < HT_DIR_END);
1168 DrawAutorailSelection(ti, _autorail_type[type][0]);
1169 } else if (IsPartOfAutoLine(ti->x, ti->y)) {
1170 /* autorail highlighting long line */
1171 HighLightStyle dir = _thd.drawstyle & HT_DIR_MASK;
1172 uint side;
1173
1174 if (dir == HT_DIR_X || dir == HT_DIR_Y) {
1175 side = 0;
1176 } else {
1177 TileIndex start = TileVirtXY(_thd.selstart.x, _thd.selstart.y);
1178 side = Delta(Delta(TileX(start), TileX(ti->tile)), Delta(TileY(start), TileY(ti->tile)));
1179 }
1180
1181 DrawAutorailSelection(ti, _autorail_type[dir][side]);
1182 }
1183 return;
1184 }
1185
1186 /* Check if it's inside the outer area? */
1187 if (!is_redsq && (tht == TileHighlightType::None || tht == TileHighlightType::Red) && _thd.outersize.x > 0 &&
1188 IsInsideBS(ti->x, _thd.pos.x + _thd.offs.x, _thd.size.x + _thd.outersize.x) &&
1189 IsInsideBS(ti->y, _thd.pos.y + _thd.offs.y, _thd.size.y + _thd.outersize.y)) {
1190 /* Draw a blue rect. */
1192 return;
1193 }
1194}
1195
1202static int GetViewportY(Point tile)
1203{
1204 /* Each increment in X or Y direction moves down by half a tile, i.e. TILE_PIXELS / 2. */
1205 return (tile.y * (int)(TILE_PIXELS / 2) + tile.x * (int)(TILE_PIXELS / 2) - TilePixelHeightOutsideMap(tile.x, tile.y)) << ZOOM_BASE_SHIFT;
1206}
1207
1212{
1213 assert(_vd.dpi.top <= _vd.dpi.top + _vd.dpi.height);
1214 assert(_vd.dpi.left <= _vd.dpi.left + _vd.dpi.width);
1215
1216 Point upper_left = InverseRemapCoords(_vd.dpi.left, _vd.dpi.top);
1217 Point upper_right = InverseRemapCoords(_vd.dpi.left + _vd.dpi.width, _vd.dpi.top);
1218
1219 /* Transformations between tile coordinates and viewport rows/columns: See vp_column_row
1220 * column = y - x
1221 * row = x + y
1222 * x = (row - column) / 2
1223 * y = (row + column) / 2
1224 * Note: (row, columns) pairs are only valid, if they are both even or both odd.
1225 */
1226
1227 /* Columns overlap with neighbouring columns by a half tile.
1228 * - Left column is column of upper_left (rounded down) and one column to the left.
1229 * - Right column is column of upper_right (rounded up) and one column to the right.
1230 * Note: Integer-division does not round down for negative numbers, so ensure rounding with another increment/decrement.
1231 */
1232 int left_column = (upper_left.y - upper_left.x) / (int)TILE_SIZE - 2;
1233 int right_column = (upper_right.y - upper_right.x) / (int)TILE_SIZE + 2;
1234
1235 int potential_bridge_height = ZOOM_BASE * TILE_HEIGHT * _settings_game.construction.max_bridge_height;
1236
1237 /* Rows overlap with neighbouring rows by a half tile.
1238 * The first row that could possibly be visible is the row above upper_left (if it is at height 0).
1239 * Due to integer-division not rounding down for negative numbers, we need another decrement.
1240 */
1241 int row = (upper_left.x + upper_left.y) / (int)TILE_SIZE - 2;
1242 bool last_row = false;
1243 for (; !last_row; row++) {
1244 last_row = true;
1245 for (int column = left_column; column <= right_column; column++) {
1246 /* Valid row/column? */
1247 if ((row + column) % 2 != 0) continue;
1248
1249 Point tilecoord;
1250 tilecoord.x = (row - column) / 2;
1251 tilecoord.y = (row + column) / 2;
1252 assert(column == tilecoord.y - tilecoord.x);
1253 assert(row == tilecoord.y + tilecoord.x);
1254
1255 TileType tile_type;
1256 _cur_ti.x = tilecoord.x * TILE_SIZE;
1257 _cur_ti.y = tilecoord.y * TILE_SIZE;
1258
1259 if (IsInsideBS(tilecoord.x, 0, Map::SizeX()) && IsInsideBS(tilecoord.y, 0, Map::SizeY())) {
1260 /* This includes the south border at Map::MaxX / Map::MaxY. When terraforming we still draw tile selections there. */
1261 _cur_ti.tile = TileXY(tilecoord.x, tilecoord.y);
1262 tile_type = GetTileType(_cur_ti.tile);
1263 } else {
1264 _cur_ti.tile = INVALID_TILE;
1265 tile_type = TileType::Void;
1266 }
1267
1268 if (tile_type != TileType::Void) {
1269 /* We are inside the map => paint landscape. */
1270 std::tie(_cur_ti.tileh, _cur_ti.z) = GetTilePixelSlope(_cur_ti.tile);
1271 } else {
1272 /* We are outside the map => paint black. */
1273 std::tie(_cur_ti.tileh, _cur_ti.z) = GetTilePixelSlopeOutsideMap(tilecoord.x, tilecoord.y);
1274 }
1275
1276 int viewport_y = GetViewportY(tilecoord);
1277
1278 if (viewport_y + MAX_TILE_EXTENT_BOTTOM < _vd.dpi.top) {
1279 /* The tile in this column is not visible yet.
1280 * Tiles in other columns may be visible, but we need more rows in any case. */
1281 last_row = false;
1282 continue;
1283 }
1284
1285 int min_visible_height = viewport_y - (_vd.dpi.top + _vd.dpi.height);
1286 bool tile_visible = min_visible_height <= 0;
1287
1288 if (tile_type != TileType::Void) {
1289 /* Is tile with buildings visible? */
1290 if (min_visible_height < MAX_TILE_EXTENT_TOP) tile_visible = true;
1291
1292 if (IsBridgeAbove(_cur_ti.tile)) {
1293 /* Is the bridge visible? */
1294 TileIndex bridge_tile = GetNorthernBridgeEnd(_cur_ti.tile);
1295 int bridge_height = ZOOM_BASE * (GetBridgePixelHeight(bridge_tile) - TilePixelHeight(_cur_ti.tile));
1296 if (min_visible_height < bridge_height + MAX_TILE_EXTENT_TOP) tile_visible = true;
1297 }
1298
1299 /* Would a higher bridge on a more southern tile be visible?
1300 * If yes, we need to loop over more rows to possibly find one. */
1301 if (min_visible_height < potential_bridge_height + MAX_TILE_EXTENT_TOP) last_row = false;
1302 } else {
1303 /* Outside of map. If we are on the north border of the map, there may still be a bridge visible,
1304 * so we need to loop over more rows to possibly find one. */
1305 if ((tilecoord.x <= 0 || tilecoord.y <= 0) && min_visible_height < potential_bridge_height + MAX_TILE_EXTENT_TOP) last_row = false;
1306 }
1307
1308 if (tile_visible) {
1309 last_row = false;
1310 _vd.foundation_part = FoundationPart::None;
1311 _vd.foundation[FoundationPart::Normal] = -1;
1312 _vd.foundation[FoundationPart::Halftile] = -1;
1313 _vd.last_foundation_child[FoundationPart::Normal] = LAST_CHILD_NONE;
1314 _vd.last_foundation_child[FoundationPart::Halftile] = LAST_CHILD_NONE;
1315
1316 _tile_type_procs[tile_type]->draw_tile_proc(&_cur_ti);
1317 if (_cur_ti.tile != INVALID_TILE) DrawTileSelection(&_cur_ti);
1318 }
1319 }
1320 }
1321}
1322
1331std::string *ViewportAddString(const DrawPixelInfo *dpi, const ViewportSign *sign, ViewportStringFlags flags, Colours colour)
1332{
1333 int left = dpi->left;
1334 int top = dpi->top;
1335 int right = left + dpi->width;
1336 int bottom = top + dpi->height;
1337
1338 bool small = flags.Test(ViewportStringFlag::Small);
1339 int sign_height = ScaleByZoom(WidgetDimensions::scaled.fullbevel.top + GetCharacterHeight(small ? FontSize::Small : FontSize::Normal) + WidgetDimensions::scaled.fullbevel.bottom, dpi->zoom);
1340 int sign_half_width = ScaleByZoom((small ? sign->width_small : sign->width_normal) / 2, dpi->zoom);
1341
1342 if (bottom < sign->top ||
1343 top > sign->top + sign_height ||
1344 right < sign->center - sign_half_width ||
1345 left > sign->center + sign_half_width) {
1346 return nullptr;
1347 }
1348
1349 return &AddStringToDraw(sign->center - sign_half_width, sign->top, colour, flags, small ? sign->width_small : sign->width_normal);
1350}
1351
1352static Rect ExpandRectWithViewportSignMargins(Rect r, ZoomLevel zoom)
1353{
1355 const int max_tw = _viewport_sign_maxwidth / 2 + 1;
1356 const int expand_y = ScaleByZoom(WidgetDimensions::scaled.fullbevel.top + fh + WidgetDimensions::scaled.fullbevel.bottom, zoom);
1357 const int expand_x = ScaleByZoom(WidgetDimensions::scaled.fullbevel.left + max_tw + WidgetDimensions::scaled.fullbevel.right, zoom);
1358
1359 r.left -= expand_x;
1360 r.right += expand_x;
1361 r.top -= expand_y;
1362 r.bottom += expand_y;
1363
1364 return r;
1365}
1366
1373static void ViewportAddTownStrings(DrawPixelInfo *dpi, const std::vector<const Town *> &towns, bool small)
1374{
1375 ViewportStringFlags flags{};
1377
1378 StringID stringid_town = !small && _settings_client.gui.population_in_label ? STR_VIEWPORT_TOWN_POP : STR_TOWN_NAME;
1379 StringID stringid_town_city = stringid_town;
1380 if (!small) {
1381 stringid_town_city = _settings_client.gui.population_in_label ? STR_VIEWPORT_TOWN_CITY_POP : STR_VIEWPORT_TOWN_CITY;
1382 }
1383
1384 for (const Town *t : towns) {
1385 std::string *str = ViewportAddString(dpi, &t->cache.sign, flags, Colours::Invalid);
1386 if (str == nullptr) continue;
1387
1388 if (t->larger_town) {
1389 *str = GetString(stringid_town_city, t->index, t->cache.population);
1390 } else {
1391 *str = GetString(stringid_town, t->index, t->cache.population);
1392 }
1393 }
1394}
1395
1402static void ViewportAddSignStrings(DrawPixelInfo *dpi, const std::vector<const Sign *> &signs, bool small)
1403{
1404 ViewportStringFlags flags{};
1405 if (small) flags.Set(ViewportStringFlag::Small);
1406
1407 /* Signs placed by a game script don't have a frame. */
1408 ViewportStringFlags deity_flags{ flags };
1410
1412
1413 for (const Sign *si : signs) {
1414 /* Workaround to make sure white is actually white. The string drawing logic changes all
1415 * colours that are not Colours::Invalid slightly, turning white into a light gray. */
1416 const Colours deity_colour = si->text_colour == Colours::White ? Colours::Invalid : si->text_colour;
1417
1418 std::string *str = ViewportAddString(dpi, &si->sign, (si->owner == OWNER_DEITY) ? deity_flags : flags,
1419 (si->owner == OWNER_NONE) ? Colours::Grey : (si->owner == OWNER_DEITY ? deity_colour : _company_colours[si->owner]));
1420 if (str == nullptr) continue;
1421
1422 *str = GetString(STR_SIGN_NAME, si->index);
1423 }
1424}
1425
1432static void ViewportAddStationStrings(DrawPixelInfo *dpi, const std::vector<const BaseStation *> &stations, bool small)
1433{
1434 /* Transparent station signs have colour text instead of a colour panel. */
1436 if (small) flags.Set(ViewportStringFlag::Small);
1437
1438 for (const BaseStation *st : stations) {
1439 std::string *str = ViewportAddString(dpi, &st->sign, flags, (st->owner == OWNER_NONE || !st->IsInUse()) ? Colours::Grey : _company_colours[st->owner]);
1440 if (str == nullptr) continue;
1441
1442 if (Station::IsExpected(st)) { /* Station */
1443 *str = GetString(small ? STR_STATION_NAME : STR_VIEWPORT_STATION, st->index, st->facilities);
1444 } else { /* Waypoint */
1445 *str = GetString(STR_WAYPOINT_NAME, st->index);
1446 }
1447 }
1448}
1449
1450static void ViewportAddKdtreeSigns(DrawPixelInfo *dpi)
1451{
1452 Rect search_rect{ dpi->left, dpi->top, dpi->left + dpi->width, dpi->top + dpi->height };
1453 search_rect = ExpandRectWithViewportSignMargins(search_rect, dpi->zoom);
1454
1455 bool show_stations = _display_opt.Test(DisplayOption::ShowStationNames) && _game_mode != GameMode::Menu;
1456 bool show_waypoints = _display_opt.Test(DisplayOption::ShowWaypointNames) && _game_mode != GameMode::Menu;
1457 bool show_towns = _display_opt.Test(DisplayOption::ShowTownNames) && _game_mode != GameMode::Menu;
1459 bool show_competitors = _display_opt.Test(DisplayOption::ShowCompetitorSigns);
1460
1461 /* Collect all the items first and draw afterwards, to ensure layering */
1462 std::vector<const BaseStation *> stations;
1463 std::vector<const Town *> towns;
1464 std::vector<const Sign *> signs;
1465
1466 _viewport_sign_kdtree.FindContained(search_rect.left, search_rect.top, search_rect.right, search_rect.bottom, [&](const ViewportSignKdtreeItem & item) {
1467 switch (item.type) {
1468 case ViewportSignKdtreeItem::VKI_STATION: {
1469 if (!show_stations) break;
1470 const BaseStation *st = BaseStation::Get(std::get<StationID>(item.id));
1471
1472 /* If no facilities are present the station is a ghost station. */
1473 StationFacilities facilities = st->facilities;
1474 if (facilities.None()) facilities = STATION_FACILITY_GHOST;
1475
1476 if (!facilities.Any(_facility_display_opt)) break;
1477
1478 /* Don't draw if station is owned by another company and competitor station names are hidden. Stations owned by none are never ignored. */
1479 if (!show_competitors && _local_company != st->owner && st->owner != OWNER_NONE) break;
1480
1481 stations.push_back(st);
1482 break;
1483 }
1484
1485 case ViewportSignKdtreeItem::VKI_WAYPOINT: {
1486 if (!show_waypoints) break;
1487 const BaseStation *st = BaseStation::Get(std::get<StationID>(item.id));
1488
1489 /* Don't draw if station is owned by another company and competitor station names are hidden. Stations owned by none are never ignored. */
1490 if (!show_competitors && _local_company != st->owner && st->owner != OWNER_NONE) break;
1491
1492 stations.push_back(st);
1493 break;
1494 }
1495
1496 case ViewportSignKdtreeItem::VKI_TOWN:
1497 if (!show_towns) break;
1498 towns.push_back(Town::Get(std::get<TownID>(item.id)));
1499 break;
1500
1501 case ViewportSignKdtreeItem::VKI_SIGN: {
1502 if (!show_signs) break;
1503 const Sign *si = Sign::Get(std::get<SignID>(item.id));
1504
1505 /* Don't draw if sign is owned by another company and competitor signs should be hidden.
1506 * Note: It is intentional that also signs owned by OWNER_NONE are hidden. Bankrupt
1507 * companies can leave OWNER_NONE signs after them. */
1508 if (!show_competitors && _local_company != si->owner && si->owner != OWNER_DEITY) break;
1509
1510 signs.push_back(si);
1511 break;
1512 }
1513
1514 default:
1515 NOT_REACHED();
1516 }
1517 });
1518
1519 /* Small versions of signs are used zoom level 4X and higher. */
1520 bool small = dpi->zoom >= ZoomLevel::Out4x;
1521
1522 /* Layering order (bottom to top): Town names, signs, stations */
1523 ViewportAddTownStrings(dpi, towns, small);
1524
1525 /* Do not draw signs nor station names if they are set invisible */
1527
1528 ViewportAddSignStrings(dpi, signs, small);
1529 ViewportAddStationStrings(dpi, stations, small);
1530}
1531
1532
1540void ViewportSign::UpdatePosition(int center, int top, std::string_view str, std::string_view str_small)
1541{
1542 if (this->width_normal != 0) this->MarkDirty();
1543
1544 this->top = top;
1545
1546 this->width_normal = WidgetDimensions::scaled.fullbevel.left + Align(GetStringBoundingBox(str).width, 2) + WidgetDimensions::scaled.fullbevel.right;
1547 this->center = center;
1548
1549 /* zoomed out version */
1550 if (str_small.empty()) str_small = str;
1551 this->width_small = WidgetDimensions::scaled.fullbevel.left + Align(GetStringBoundingBox(str_small, FontSize::Small).width, 2) + WidgetDimensions::scaled.fullbevel.right;
1552
1553 this->MarkDirty();
1554}
1555
1563{
1565
1566 /* We don't know which size will be drawn, so mark the largest area dirty. */
1567 const uint half_width = std::max(this->width_normal, this->width_small) / 2 + 1;
1568 const uint height = WidgetDimensions::scaled.fullbevel.top + std::max(GetCharacterHeight(FontSize::Normal), GetCharacterHeight(FontSize::Small)) + WidgetDimensions::scaled.fullbevel.bottom + 1;
1569
1570 for (ZoomLevel zoom : EnumRange(ZoomLevel::End)) {
1571 zoomlevels[zoom].left = this->center - ScaleByZoom(half_width, zoom);
1572 zoomlevels[zoom].top = this->top - ScaleByZoom(1, zoom);
1573 zoomlevels[zoom].right = this->center + ScaleByZoom(half_width, zoom);
1574 zoomlevels[zoom].bottom = this->top + ScaleByZoom(height, zoom);
1575 }
1576
1577 for (const Window *w : Window::Iterate()) {
1578 if (w->viewport == nullptr) continue;
1579
1580 Viewport &vp = *w->viewport;
1581 if (vp.zoom <= maxzoom) {
1582 assert(vp.width != 0);
1583 Rect &zl = zoomlevels[vp.zoom];
1584 MarkViewportDirty(vp, zl.left, zl.top, zl.right, zl.bottom);
1585 }
1586 }
1587}
1588
1589static void ViewportDrawTileSprites(const TileSpriteToDrawVector *tstdv)
1590{
1591 for (const TileSpriteToDraw &ts : *tstdv) {
1592 DrawSpriteViewport(ts.image, ts.pal, ts.x, ts.y, ts.sub);
1593 }
1594}
1595
1600static void ViewportSortParentSprites(ParentSpriteToSortVector *psdv)
1601{
1602 if (psdv->size() < 2) return;
1603
1604 /* We rely on sprites being, for the most part, already ordered.
1605 * So we don't need to move many of them and can keep track of their
1606 * order efficiently by using stack. We always move sprites to the front
1607 * of the current position, i.e. to the top of the stack.
1608 * Also use special constants to indicate sorting state without
1609 * adding extra fields to ParentSpriteToDraw structure.
1610 */
1611 const uint32_t ORDER_COMPARED = UINT32_MAX; // Sprite was compared but we still need to compare the ones preceding it
1612 const uint32_t ORDER_RETURNED = UINT32_MAX - 1; // Mark sorted sprite in case there are other occurrences of it in the stack
1613 std::stack<ParentSpriteToDraw *> sprite_order;
1614 uint32_t next_order = 0;
1615
1616 std::forward_list<std::pair<int64_t, ParentSpriteToDraw *>> sprite_list; // We store sprites in a list sorted by xmin+ymin
1617
1618 /* Initialize sprite list and order. */
1619 for (auto p = psdv->rbegin(); p != psdv->rend(); p++) {
1620 sprite_list.emplace_front((*p)->xmin + (*p)->ymin, *p);
1621 sprite_order.push(*p);
1622 (*p)->order = next_order++;
1623 }
1624
1625 sprite_list.sort();
1626
1627 std::vector<ParentSpriteToDraw *> preceding; // Temporarily stores sprites that precede current and their position in the list
1628 auto preceding_prev = sprite_list.begin(); // Store iterator in case we need to delete a single preceding sprite
1629 auto out = psdv->begin(); // Iterator to output sorted sprites
1630
1631 while (!sprite_order.empty()) {
1632
1633 auto s = sprite_order.top();
1634 sprite_order.pop();
1635
1636 /* Sprite is already sorted, ignore it. */
1637 if (s->order == ORDER_RETURNED) continue;
1638
1639 /* Sprite was already compared, just need to output it. */
1640 if (s->order == ORDER_COMPARED) {
1641 *(out++) = s;
1642 s->order = ORDER_RETURNED;
1643 continue;
1644 }
1645
1646 preceding.clear();
1647
1648 /* We only need sprites with xmin <= s->xmax && ymin <= s->ymax && zmin <= s->zmax
1649 * So by iterating sprites with xmin + ymin <= s->xmax + s->ymax
1650 * we get all we need and some more that we filter out later.
1651 * We don't include zmin into the sum as there are usually more neighbours on x and y than z
1652 * so including it will actually increase the amount of false positives.
1653 * Also min coordinates can be > max so using max(xmin, xmax) + max(ymin, ymax)
1654 * to ensure that we iterate the current sprite as we need to remove it from the list.
1655 */
1656 auto ssum = std::max(s->xmax, s->xmin) + std::max(s->ymax, s->ymin);
1657 auto prev = sprite_list.before_begin();
1658 auto x = sprite_list.begin();
1659 while (x != sprite_list.end() && x->first <= ssum) {
1660 auto p = x->second;
1661 if (p == s) {
1662 /* We found the current sprite, remove it and move on. */
1663 x = sprite_list.erase_after(prev);
1664 continue;
1665 }
1666
1667 auto p_prev = prev;
1668 prev = x++;
1669
1670 if (s->xmax < p->xmin || s->ymax < p->ymin || s->zmax < p->zmin) continue;
1671 if (s->xmin <= p->xmax && // overlap in X?
1672 s->ymin <= p->ymax && // overlap in Y?
1673 s->zmin <= p->zmax) { // overlap in Z?
1674 if (s->xmin + s->xmax + s->ymin + s->ymax + s->zmin + s->zmax <=
1675 p->xmin + p->xmax + p->ymin + p->ymax + p->zmin + p->zmax) {
1676 continue;
1677 }
1678 }
1679 preceding.push_back(p);
1680 preceding_prev = p_prev;
1681 }
1682
1683 if (preceding.empty()) {
1684 /* No preceding sprites, add current one to the output */
1685 *(out++) = s;
1686 s->order = ORDER_RETURNED;
1687 continue;
1688 }
1689
1690 /* Optimization for the case when we only have 1 sprite to move. */
1691 if (preceding.size() == 1) {
1692 auto p = preceding[0];
1693 /* We can only output the preceding sprite if there can't be any other sprites preceding it. */
1694 if (p->xmax <= s->xmax && p->ymax <= s->ymax && p->zmax <= s->zmax) {
1695 p->order = ORDER_RETURNED;
1696 s->order = ORDER_RETURNED;
1697 sprite_list.erase_after(preceding_prev);
1698 *(out++) = p;
1699 *(out++) = s;
1700 continue;
1701 }
1702 }
1703
1704 /* Sort all preceding sprites by order and assign new orders in reverse (as original sorter did). */
1705 std::sort(preceding.begin(), preceding.end(), [](const ParentSpriteToDraw *a, const ParentSpriteToDraw *b) {
1706 return a->order > b->order;
1707 });
1708
1709 s->order = ORDER_COMPARED;
1710 sprite_order.push(s); // Still need to output so push it back for now
1711
1712 for (auto p: preceding) {
1713 p->order = next_order++;
1714 sprite_order.push(p);
1715 }
1716 }
1717}
1718
1719
1720static void ViewportDrawParentSprites(const ParentSpriteToSortVector *psd, const ChildScreenSpriteToDrawVector *csstdv)
1721{
1722 for (const ParentSpriteToDraw *ps : *psd) {
1723 if (ps->image != SPR_EMPTY_BOUNDING_BOX) DrawSpriteViewport(ps->image, ps->pal, ps->x, ps->y, ps->sub);
1724
1725 int child_idx = ps->first_child;
1726 while (child_idx >= 0) {
1727 const ChildScreenSpriteToDraw *cs = &(*csstdv)[child_idx];
1728 child_idx = cs->next;
1729 if (cs->relative) {
1730 DrawSpriteViewport(cs->image, cs->pal, ps->left + cs->x, ps->top + cs->y, cs->sub);
1731 } else {
1732 DrawSpriteViewport(cs->image, cs->pal, ps->x + cs->x, ps->y + cs->y, cs->sub);
1733 }
1734 }
1735 }
1736}
1737
1742static void ViewportDrawBoundingBoxes(const ParentSpriteToSortVector *psd)
1743{
1744 for (const ParentSpriteToDraw *ps : *psd) {
1745 Point pt1 = RemapCoords(ps->xmax + 1, ps->ymax + 1, ps->zmax + 1); // top front corner
1746 Point pt2 = RemapCoords(ps->xmin , ps->ymax + 1, ps->zmax + 1); // top left corner
1747 Point pt3 = RemapCoords(ps->xmax + 1, ps->ymin , ps->zmax + 1); // top right corner
1748 Point pt4 = RemapCoords(ps->xmax + 1, ps->ymax + 1, ps->zmin ); // bottom front corner
1749
1750 DrawBox( pt1.x, pt1.y,
1751 pt2.x - pt1.x, pt2.y - pt1.y,
1752 pt3.x - pt1.x, pt3.y - pt1.y,
1753 pt4.x - pt1.x, pt4.y - pt1.y);
1754 }
1755}
1756
1761{
1763 const DrawPixelInfo *dpi = _cur_dpi;
1764 void *dst;
1765 int right = UnScaleByZoom(dpi->width, dpi->zoom);
1766 int bottom = UnScaleByZoom(dpi->height, dpi->zoom);
1767
1768 PixelColour colour = _string_colourmap[_dirty_block_colour & 0xF];
1769
1770 dst = dpi->dst_ptr;
1771
1772 uint8_t bo = UnScaleByZoom(dpi->left + dpi->top, dpi->zoom) & 1;
1773 do {
1774 for (int i = (bo ^= 1); i < right; i += 2) blitter->SetPixel(dst, i, 0, colour);
1775 dst = blitter->MoveTo(dst, 0, 1);
1776 } while (--bottom > 0);
1777}
1778
1779static void ViewportDrawStrings(ZoomLevel zoom, const StringSpriteToDrawVector *sstdv)
1780{
1781 for (const StringSpriteToDraw &ss : *sstdv) {
1782 bool small = ss.flags.Test(ViewportStringFlag::Small);
1783 int w = ss.width;
1784 int x = UnScaleByZoom(ss.x, zoom);
1785 int y = UnScaleByZoom(ss.y, zoom);
1786 int h = WidgetDimensions::scaled.fullbevel.top + GetCharacterHeight(small ? FontSize::Small : FontSize::Normal) + WidgetDimensions::scaled.fullbevel.bottom;
1787
1789 if (ss.flags.Test(ViewportStringFlag::ColourRect)) {
1790 if (ss.colour != Colours::Invalid) DrawFrameRect(x, y, x + w - 1, y + h - 1, ss.colour, {});
1791 colour = TextColour::Black;
1792 } else if (ss.flags.Test(ViewportStringFlag::TransparentRect)) {
1793 DrawFrameRect(x, y, x + w - 1, y + h - 1, ss.colour, FrameFlag::Transparent);
1794 }
1795
1796 if (ss.flags.Test(ViewportStringFlag::TextColour)) {
1797 if (ss.colour != Colours::Invalid) colour = ExtendedTextColour{GetColourGradient(ss.colour, Shade::Lighter)};
1798 }
1799
1800 int left = x + WidgetDimensions::scaled.fullbevel.left;
1801 int right = x + w - 1 - WidgetDimensions::scaled.fullbevel.right;
1802 int top = y + WidgetDimensions::scaled.fullbevel.top;
1803
1804 int shadow_offset = 0;
1805 if (small && ss.flags.Test(ViewportStringFlag::Shadow)) {
1806 /* Shadow needs to be shifted 1 pixel. */
1807 shadow_offset = WidgetDimensions::scaled.fullbevel.top;
1808 DrawString(left + shadow_offset, right + shadow_offset, top, ss.string, TextColour::Black, SA_HOR_CENTER, false, FontSize::Small);
1809 }
1810
1811 DrawString(left, right, top - shadow_offset, ss.string, colour, SA_HOR_CENTER, false, small ? FontSize::Small : FontSize::Normal);
1812 }
1813}
1814
1815void ViewportDoDraw(const Viewport &vp, int left, int top, int right, int bottom)
1816{
1817 _vd.dpi.zoom = vp.zoom;
1818 int mask = ScaleByZoom(-1, vp.zoom);
1819
1820 _vd.combine_sprites = SpriteCombineMode::None;
1821
1822 _vd.dpi.width = (right - left) & mask;
1823 _vd.dpi.height = (bottom - top) & mask;
1824 _vd.dpi.left = left & mask;
1825 _vd.dpi.top = top & mask;
1826 _vd.dpi.pitch = _cur_dpi->pitch;
1827 _vd.last_child = LAST_CHILD_NONE;
1828
1829 int x = UnScaleByZoom(_vd.dpi.left - (vp.virtual_left & mask), vp.zoom) + vp.left;
1830 int y = UnScaleByZoom(_vd.dpi.top - (vp.virtual_top & mask), vp.zoom) + vp.top;
1831
1832 _vd.dpi.dst_ptr = BlitterFactory::GetCurrentBlitter()->MoveTo(_cur_dpi->dst_ptr, x - _cur_dpi->left, y - _cur_dpi->top);
1833 AutoRestoreBackup dpi_backup(_cur_dpi, &_vd.dpi);
1834
1836 ViewportAddVehicles(&_vd.dpi);
1837
1838 ViewportAddKdtreeSigns(&_vd.dpi);
1839
1840 DrawTextEffects(&_vd.dpi);
1841
1842 if (!_vd.tile_sprites_to_draw.empty()) ViewportDrawTileSprites(&_vd.tile_sprites_to_draw);
1843
1844 for (auto &psd : _vd.parent_sprites_to_draw) {
1845 _vd.parent_sprites_to_sort.push_back(&psd);
1846 }
1847
1848 _vp_sprite_sorter(&_vd.parent_sprites_to_sort);
1849 ViewportDrawParentSprites(&_vd.parent_sprites_to_sort, &_vd.child_screen_sprites_to_draw);
1850
1851 if (_draw_bounding_boxes) ViewportDrawBoundingBoxes(&_vd.parent_sprites_to_sort);
1852 if (_draw_dirty_blocks) ViewportDrawDirtyBlocks();
1853
1854 DrawPixelInfo dp = _vd.dpi;
1855 ZoomLevel zoom = _vd.dpi.zoom;
1856 dp.zoom = ZoomLevel::Min;
1857 dp.width = UnScaleByZoom(dp.width, zoom);
1858 dp.height = UnScaleByZoom(dp.height, zoom);
1859 AutoRestoreBackup cur_dpi(_cur_dpi, &dp);
1860
1861 if (vp.overlay != nullptr && vp.overlay->GetCargoMask().Any() && vp.overlay->GetCompanyMask().Any()) {
1862 /* translate to window coordinates */
1863 dp.left = x;
1864 dp.top = y;
1865 vp.overlay->Draw(&dp);
1866 }
1867
1868 if (!_vd.string_sprites_to_draw.empty()) {
1869 /* translate to world coordinates */
1870 dp.left = UnScaleByZoom(_vd.dpi.left, zoom);
1871 dp.top = UnScaleByZoom(_vd.dpi.top, zoom);
1872 ViewportDrawStrings(zoom, &_vd.string_sprites_to_draw);
1873 }
1874
1875 _vd.string_sprites_to_draw.clear();
1876 _vd.tile_sprites_to_draw.clear();
1877 _vd.parent_sprites_to_draw.clear();
1878 _vd.parent_sprites_to_sort.clear();
1879 _vd.child_screen_sprites_to_draw.clear();
1880}
1881
1882static inline void ViewportDraw(const Viewport &vp, int left, int top, int right, int bottom)
1883{
1884 if (right <= vp.left || bottom <= vp.top) return;
1885
1886 if (left >= vp.left + vp.width) return;
1887
1888 if (left < vp.left) left = vp.left;
1889 if (right > vp.left + vp.width) right = vp.left + vp.width;
1890
1891 if (top >= vp.top + vp.height) return;
1892
1893 if (top < vp.top) top = vp.top;
1894 if (bottom > vp.top + vp.height) bottom = vp.top + vp.height;
1895
1896 ViewportDoDraw(vp,
1897 ScaleByZoom(left - vp.left, vp.zoom) + vp.virtual_left,
1898 ScaleByZoom(top - vp.top, vp.zoom) + vp.virtual_top,
1899 ScaleByZoom(right - vp.left, vp.zoom) + vp.virtual_left,
1900 ScaleByZoom(bottom - vp.top, vp.zoom) + vp.virtual_top
1901 );
1902}
1903
1908{
1910
1911 DrawPixelInfo *dpi = _cur_dpi;
1912
1913 dpi->left += this->left;
1914 dpi->top += this->top;
1915
1916 ViewportDraw(*this->viewport, dpi->left, dpi->top, dpi->left + dpi->width, dpi->top + dpi->height);
1917
1918 dpi->left -= this->left;
1919 dpi->top -= this->top;
1920}
1921
1932static inline void ClampViewportToMap(const Viewport &vp, int *scroll_x, int *scroll_y)
1933{
1934 /* Centre of the viewport is hot spot. */
1935 Point pt = {
1936 *scroll_x + vp.virtual_width / 2,
1937 *scroll_y + vp.virtual_height / 2
1938 };
1939
1940 /* Find nearest tile that is within borders of the map. */
1941 bool clamped;
1942 pt = InverseRemapCoords2(pt.x, pt.y, true, &clamped);
1943
1944 if (clamped) {
1945 /* Convert back to viewport coordinates and remove centering. */
1946 pt = RemapCoords2(pt.x, pt.y);
1947 *scroll_x = pt.x - vp.virtual_width / 2;
1948 *scroll_y = pt.y - vp.virtual_height / 2;
1949 }
1950}
1951
1964static void ClampSmoothScroll(uint32_t delta_ms, int64_t delta_hi, int64_t delta_lo, int &delta_hi_clamped, int &delta_lo_clamped)
1965{
1967 constexpr int PIXELS_PER_TILE = TILE_PIXELS * 2 * ZOOM_BASE;
1968
1969 assert(delta_hi != 0);
1970
1971 /* Move at most 75% of the distance every 30ms, for a smooth experience */
1972 int64_t delta_left = delta_hi * std::pow(0.75, delta_ms / 30.0);
1973 /* Move never more than 16 tiles per 30ms. */
1974 int max_scroll = Map::ScaleBySize1D(16 * PIXELS_PER_TILE * delta_ms / 30);
1975
1976 /* We never go over the max_scroll speed. */
1977 delta_hi_clamped = Clamp(delta_hi - delta_left, -max_scroll, max_scroll);
1978 /* The lower delta is in ratio of the higher delta, so we keep going straight at the destination. */
1979 delta_lo_clamped = delta_lo * delta_hi_clamped / delta_hi;
1980
1981 /* Ensure we always move (delta_hi can't be zero). */
1982 if (delta_hi_clamped == 0) {
1983 delta_hi_clamped = delta_hi > 0 ? 1 : -1;
1984 }
1985}
1986
1992void UpdateViewportPosition(Window *w, uint32_t delta_ms)
1993{
1994 ViewportData &vp = *w->viewport;
1995
1996 if (vp.follow_vehicle != VehicleID::Invalid()) {
1997 const Vehicle *veh = Vehicle::Get(vp.follow_vehicle)->GetMovingFront();
1998 Point pt = MapXYZToViewport(vp, veh->x_pos, veh->y_pos, veh->z_pos);
1999
2000 vp.scrollpos_x = pt.x;
2001 vp.scrollpos_y = pt.y;
2002 SetViewportPosition(w, pt.x, pt.y);
2003 } else {
2004 /* Ensure the destination location is within the map */
2006
2007 int delta_x = vp.dest_scrollpos_x - vp.scrollpos_x;
2008 int delta_y = vp.dest_scrollpos_y - vp.scrollpos_y;
2009
2010 int current_x = vp.scrollpos_x;
2011 int current_y = vp.scrollpos_y;
2012
2013 bool update_overlay = false;
2014 if (delta_x != 0 || delta_y != 0) {
2015 if (_settings_client.gui.smooth_scroll) {
2016 int delta_x_clamped;
2017 int delta_y_clamped;
2018
2019 if (abs(delta_x) > abs(delta_y)) {
2020 ClampSmoothScroll(delta_ms, delta_x, delta_y, delta_x_clamped, delta_y_clamped);
2021 } else {
2022 ClampSmoothScroll(delta_ms, delta_y, delta_x, delta_y_clamped, delta_x_clamped);
2023 }
2024
2025 vp.scrollpos_x += delta_x_clamped;
2026 vp.scrollpos_y += delta_y_clamped;
2027 } else {
2030 }
2031 update_overlay = (vp.scrollpos_x == vp.dest_scrollpos_x &&
2032 vp.scrollpos_y == vp.dest_scrollpos_y);
2033 }
2034
2036
2037 /* When moving small amounts around the border we can get stuck, and
2038 * not actually move. In those cases, teleport to the destination. */
2039 if ((delta_x != 0 || delta_y != 0) && current_x == vp.scrollpos_x && current_y == vp.scrollpos_y) {
2042 }
2043
2044 SetViewportPosition(w, vp.scrollpos_x, vp.scrollpos_y);
2045 if (update_overlay) RebuildViewportOverlay(w);
2046 }
2047}
2048
2059static bool MarkViewportDirty(const Viewport &vp, int left, int top, int right, int bottom)
2060{
2061 /* Rounding wrt. zoom-out level */
2062 right += (1 << to_underlying(vp.zoom)) - 1;
2063 bottom += (1 << to_underlying(vp.zoom)) - 1;
2064
2065 right -= vp.virtual_left;
2066 if (right <= 0) return false;
2067
2068 bottom -= vp.virtual_top;
2069 if (bottom <= 0) return false;
2070
2071 left = std::max(0, left - vp.virtual_left);
2072
2073 if (left >= vp.virtual_width) return false;
2074
2075 top = std::max(0, top - vp.virtual_top);
2076
2077 if (top >= vp.virtual_height) return false;
2078
2080 UnScaleByZoomLower(left, vp.zoom) + vp.left,
2081 UnScaleByZoomLower(top, vp.zoom) + vp.top,
2082 UnScaleByZoom(right, vp.zoom) + vp.left + 1,
2083 UnScaleByZoom(bottom, vp.zoom) + vp.top + 1
2084 );
2085
2086 return true;
2087}
2088
2098bool MarkAllViewportsDirty(int left, int top, int right, int bottom)
2099{
2100 bool dirty = false;
2101
2102 for (const Window *w : Window::Iterate()) {
2103 if (w->viewport != nullptr) {
2104 assert(w->viewport->width != 0);
2105 if (MarkViewportDirty(*w->viewport, left, top, right, bottom)) dirty = true;
2106 }
2107 }
2108
2109 return dirty;
2110}
2111
2112void ConstrainAllViewportsZoom()
2113{
2114 for (Window *w : Window::Iterate()) {
2115 if (w->viewport == nullptr) continue;
2116
2117 ZoomLevel zoom = Clamp(w->viewport->zoom, _settings_client.gui.zoom_min, _settings_client.gui.zoom_max);
2118 if (zoom != w->viewport->zoom) {
2119 while (w->viewport->zoom < zoom) DoZoomInOutWindow(ZOOM_OUT, w);
2120 while (w->viewport->zoom > zoom) DoZoomInOutWindow(ZOOM_IN, w);
2121 }
2122 }
2123}
2124
2132void MarkTileDirtyByTile(TileIndex tile, int bridge_level_offset, int tile_height_override)
2133{
2134 Point pt = RemapCoords(TileX(tile) * TILE_SIZE, TileY(tile) * TILE_SIZE, tile_height_override * TILE_HEIGHT);
2137 pt.y - MAX_TILE_EXTENT_TOP - ZOOM_BASE * TILE_HEIGHT * bridge_level_offset,
2140}
2141
2150{
2151 int x_size = _thd.size.x;
2152 int y_size = _thd.size.y;
2153
2154 if (!_thd.diagonal) { // Selecting in a straight rectangle (or a single square)
2155 int x_start = _thd.pos.x;
2156 int y_start = _thd.pos.y;
2157
2158 if (_thd.outersize.x != 0) {
2159 x_size += _thd.outersize.x;
2160 x_start += _thd.offs.x;
2161 y_size += _thd.outersize.y;
2162 y_start += _thd.offs.y;
2163 }
2164
2165 x_size -= TILE_SIZE;
2166 y_size -= TILE_SIZE;
2167
2168 assert(x_size >= 0);
2169 assert(y_size >= 0);
2170
2171 int x_end = Clamp(x_start + x_size, 0, Map::SizeX() * TILE_SIZE - TILE_SIZE);
2172 int y_end = Clamp(y_start + y_size, 0, Map::SizeY() * TILE_SIZE - TILE_SIZE);
2173
2174 x_start = Clamp(x_start, 0, Map::SizeX() * TILE_SIZE - TILE_SIZE);
2175 y_start = Clamp(y_start, 0, Map::SizeY() * TILE_SIZE - TILE_SIZE);
2176
2177 /* make sure everything is multiple of TILE_SIZE */
2178 assert((x_end | y_end | x_start | y_start) % TILE_SIZE == 0);
2179
2180 /* How it works:
2181 * Suppose we have to mark dirty rectangle of 3x4 tiles:
2182 * x
2183 * xxx
2184 * xxxxx
2185 * xxxxx
2186 * xxx
2187 * x
2188 * This algorithm marks dirty columns of tiles, so it is done in 3+4-1 steps:
2189 * 1) x 2) x
2190 * xxx Oxx
2191 * Oxxxx xOxxx
2192 * xxxxx Oxxxx
2193 * xxx xxx
2194 * x x
2195 * And so forth...
2196 */
2197
2198 int top_x = x_end; // coordinates of top dirty tile
2199 int top_y = y_start;
2200 int bot_x = top_x; // coordinates of bottom dirty tile
2201 int bot_y = top_y;
2202
2203 do {
2204 /* topmost dirty point */
2205 TileIndex top_tile = TileVirtXY(top_x, top_y);
2206 Point top = RemapCoords(top_x, top_y, GetTileMaxPixelZ(top_tile));
2207
2208 /* bottommost point */
2209 TileIndex bottom_tile = TileVirtXY(bot_x, bot_y);
2210 Point bot = RemapCoords(bot_x + TILE_SIZE, bot_y + TILE_SIZE, GetTilePixelZ(bottom_tile)); // bottommost point
2211
2212 /* the 'x' coordinate of 'top' and 'bot' is the same (and always in the same distance from tile middle),
2213 * tile height/slope affects only the 'y' on-screen coordinate! */
2214
2215 int l = top.x - TILE_PIXELS * ZOOM_BASE; // 'x' coordinate of left side of the dirty rectangle
2216 int t = top.y; // 'y' coordinate of top side of the dirty rectangle
2217 int r = top.x + TILE_PIXELS * ZOOM_BASE; // 'x' coordinate of right side of the dirty rectangle
2218 int b = bot.y; // 'y' coordinate of bottom side of the dirty rectangle
2219
2220 static const int OVERLAY_WIDTH = 4 * ZOOM_BASE; // part of selection sprites is drawn outside the selected area (in particular: terraforming)
2221
2222 /* For halftile foundations on SLOPE_STEEP_S the sprite extents some more towards the top */
2223 MarkAllViewportsDirty(l - OVERLAY_WIDTH, t - OVERLAY_WIDTH - TILE_HEIGHT * ZOOM_BASE, r + OVERLAY_WIDTH, b + OVERLAY_WIDTH);
2224
2225 /* haven't we reached the topmost tile yet? */
2226 if (top_x != x_start) {
2227 top_x -= TILE_SIZE;
2228 } else {
2229 top_y += TILE_SIZE;
2230 }
2231
2232 /* the way the bottom tile changes is different when we reach the bottommost tile */
2233 if (bot_y != y_end) {
2234 bot_y += TILE_SIZE;
2235 } else {
2236 bot_x -= TILE_SIZE;
2237 }
2238 } while (bot_x >= top_x);
2239 } else { // Selecting in a 45 degrees rotated (diagonal) rectangle.
2240 /* a_size, b_size describe a rectangle with rotated coordinates */
2241 int a_size = x_size + y_size, b_size = x_size - y_size;
2242
2243 int interval_a = a_size < 0 ? -(int)TILE_SIZE : (int)TILE_SIZE;
2244 int interval_b = b_size < 0 ? -(int)TILE_SIZE : (int)TILE_SIZE;
2245
2246 for (int a = -interval_a; a != a_size + interval_a; a += interval_a) {
2247 for (int b = -interval_b; b != b_size + interval_b; b += interval_b) {
2248 uint x = (_thd.pos.x + (a + b) / 2) / TILE_SIZE;
2249 uint y = (_thd.pos.y + (a - b) / 2) / TILE_SIZE;
2250
2251 if (x < Map::MaxX() && y < Map::MaxY()) {
2253 }
2254 }
2255 }
2256 }
2257}
2258
2259
2260void SetSelectionRed(bool b)
2261{
2262 _thd.make_square_red = b;
2264}
2265
2274static bool CheckClickOnViewportSign(const Viewport &vp, int x, int y, const ViewportSign *sign)
2275{
2276 bool small = (vp.zoom >= ZoomLevel::Out4x);
2277 int sign_half_width = ScaleByZoom((small ? sign->width_small : sign->width_normal) / 2, vp.zoom);
2278 int sign_height = ScaleByZoom(WidgetDimensions::scaled.fullbevel.top + GetCharacterHeight(small ? FontSize::Small : FontSize::Normal) + WidgetDimensions::scaled.fullbevel.bottom, vp.zoom);
2279
2280 return y >= sign->top && y < sign->top + sign_height &&
2281 x >= sign->center - sign_half_width && x < sign->center + sign_half_width;
2282}
2283
2284
2292static bool CheckClickOnViewportSign(const Viewport &vp, int x, int y)
2293{
2294 if (_game_mode == GameMode::Menu) return false;
2295
2296 x = ScaleByZoom(x - vp.left, vp.zoom) + vp.virtual_left;
2297 y = ScaleByZoom(y - vp.top, vp.zoom) + vp.virtual_top;
2298
2299 Rect search_rect{ x - 1, y - 1, x + 1, y + 1 };
2300 search_rect = ExpandRectWithViewportSignMargins(search_rect, vp.zoom);
2301
2304 bool show_towns = _display_opt.Test(DisplayOption::ShowTownNames);
2306 bool show_competitors = _display_opt.Test(DisplayOption::ShowCompetitorSigns);
2307
2308 /* Topmost of each type that was hit */
2309 BaseStation *st = nullptr, *last_st = nullptr;
2310 Town *t = nullptr, *last_t = nullptr;
2311 Sign *si = nullptr, *last_si = nullptr;
2312
2313 /* See ViewportAddKdtreeSigns() for details on the search logic */
2314 _viewport_sign_kdtree.FindContained(search_rect.left, search_rect.top, search_rect.right, search_rect.bottom, [&](const ViewportSignKdtreeItem & item) {
2315 switch (item.type) {
2316 case ViewportSignKdtreeItem::VKI_STATION: {
2317 if (!show_stations) break;
2318 st = BaseStation::Get(std::get<StationID>(item.id));
2319 if (!show_competitors && _local_company != st->owner && st->owner != OWNER_NONE) break;
2320
2321 StationFacilities facilities = st->facilities;
2322 if (facilities.None()) facilities = STATION_FACILITY_GHOST;
2323 if (!facilities.Any(_facility_display_opt)) break;
2324
2325 if (CheckClickOnViewportSign(vp, x, y, &st->sign)) last_st = st;
2326 break;
2327 }
2328
2329 case ViewportSignKdtreeItem::VKI_WAYPOINT:
2330 if (!show_waypoints) break;
2331 st = BaseStation::Get(std::get<StationID>(item.id));
2332 if (!show_competitors && _local_company != st->owner && st->owner != OWNER_NONE) break;
2333 if (CheckClickOnViewportSign(vp, x, y, &st->sign)) last_st = st;
2334 break;
2335
2336 case ViewportSignKdtreeItem::VKI_TOWN:
2337 if (!show_towns) break;
2338 t = Town::Get(std::get<TownID>(item.id));
2339 if (CheckClickOnViewportSign(vp, x, y, &t->cache.sign)) last_t = t;
2340 break;
2341
2342 case ViewportSignKdtreeItem::VKI_SIGN:
2343 if (!show_signs) break;
2344 si = Sign::Get(std::get<SignID>(item.id));
2345 if (!show_competitors && _local_company != si->owner && si->owner != OWNER_DEITY) break;
2346 if (CheckClickOnViewportSign(vp, x, y, &si->sign)) last_si = si;
2347 break;
2348
2349 default:
2350 NOT_REACHED();
2351 }
2352 });
2353
2354 /* Select which hit to handle based on priority */
2355 if (last_st != nullptr) {
2356 if (Station::IsExpected(last_st)) {
2357 ShowStationViewWindow(last_st->index);
2358 } else {
2360 }
2361 return true;
2362 } else if (last_t != nullptr) {
2363 ShowTownViewWindow(last_t->index);
2364 return true;
2365 } else if (last_si != nullptr) {
2366 HandleClickOnSign(last_si);
2367 return true;
2368 } else {
2369 return false;
2370 }
2371}
2372
2373
2374ViewportSignKdtreeItem ViewportSignKdtreeItem::MakeStation(StationID id)
2375{
2376 ViewportSignKdtreeItem item;
2377 item.type = VKI_STATION;
2378 item.id = id;
2379
2380 const Station *st = Station::Get(id);
2381 assert(st->sign.kdtree_valid);
2382 item.center = st->sign.center;
2383 item.top = st->sign.top;
2384
2385 /* Assume the sign can be a candidate for drawing, so measure its width */
2386 _viewport_sign_maxwidth = std::max<int>({_viewport_sign_maxwidth, st->sign.width_normal, st->sign.width_small});
2387
2388 return item;
2389}
2390
2391ViewportSignKdtreeItem ViewportSignKdtreeItem::MakeWaypoint(StationID id)
2392{
2393 ViewportSignKdtreeItem item;
2394 item.type = VKI_WAYPOINT;
2395 item.id = id;
2396
2397 const Waypoint *st = Waypoint::Get(id);
2398 assert(st->sign.kdtree_valid);
2399 item.center = st->sign.center;
2400 item.top = st->sign.top;
2401
2402 /* Assume the sign can be a candidate for drawing, so measure its width */
2403 _viewport_sign_maxwidth = std::max<int>({_viewport_sign_maxwidth, st->sign.width_normal, st->sign.width_small});
2404
2405 return item;
2406}
2407
2408ViewportSignKdtreeItem ViewportSignKdtreeItem::MakeTown(TownID id)
2409{
2410 ViewportSignKdtreeItem item;
2411 item.type = VKI_TOWN;
2412 item.id = id;
2413
2414 const Town *town = Town::Get(id);
2415 assert(town->cache.sign.kdtree_valid);
2416 item.center = town->cache.sign.center;
2417 item.top = town->cache.sign.top;
2418
2419 /* Assume the sign can be a candidate for drawing, so measure its width */
2420 _viewport_sign_maxwidth = std::max<int>({_viewport_sign_maxwidth, town->cache.sign.width_normal, town->cache.sign.width_small});
2421
2422 return item;
2423}
2424
2425ViewportSignKdtreeItem ViewportSignKdtreeItem::MakeSign(SignID id)
2426{
2427 ViewportSignKdtreeItem item;
2428 item.type = VKI_SIGN;
2429 item.id = id;
2430
2431 const Sign *sign = Sign::Get(id);
2432 assert(sign->sign.kdtree_valid);
2433 item.center = sign->sign.center;
2434 item.top = sign->sign.top;
2435
2436 /* Assume the sign can be a candidate for drawing, so measure its width */
2437 _viewport_sign_maxwidth = std::max<int>({_viewport_sign_maxwidth, sign->sign.width_normal, sign->sign.width_small});
2438
2439 return item;
2440}
2441
2442void RebuildViewportKdtree()
2443{
2444 /* Reset biggest size sign seen */
2445 _viewport_sign_maxwidth = 0;
2446
2447 std::vector<ViewportSignKdtreeItem> items;
2449
2450 for (const Station *st : Station::Iterate()) {
2451 if (st->sign.kdtree_valid) items.push_back(ViewportSignKdtreeItem::MakeStation(st->index));
2452 }
2453
2454 for (const Waypoint *wp : Waypoint::Iterate()) {
2455 if (wp->sign.kdtree_valid) items.push_back(ViewportSignKdtreeItem::MakeWaypoint(wp->index));
2456 }
2457
2458 for (const Town *town : Town::Iterate()) {
2459 if (town->cache.sign.kdtree_valid) items.push_back(ViewportSignKdtreeItem::MakeTown(town->index));
2460 }
2461
2462 for (const Sign *sign : Sign::Iterate()) {
2463 if (sign->sign.kdtree_valid) items.push_back(ViewportSignKdtreeItem::MakeSign(sign->index));
2464 }
2465
2466 _viewport_sign_kdtree.Build(items.begin(), items.end());
2467}
2468
2469
2470static bool CheckClickOnLandscape(const Viewport &vp, int x, int y)
2471{
2472 Point pt = TranslateXYToTileCoord(vp, x, y);
2473
2474 if (pt.x != -1) return ClickTile(TileVirtXY(pt.x, pt.y));
2475 return true;
2476}
2477
2478static void PlaceObject()
2479{
2480 Point pt;
2481 Window *w;
2482
2483 pt = GetTileBelowCursor();
2484 if (pt.x == -1) return;
2485
2486 if ((_thd.place_mode & HT_DRAG_MASK) == HT_POINT) {
2487 pt.x += TILE_SIZE / 2;
2488 pt.y += TILE_SIZE / 2;
2489 }
2490
2491 _tile_fract_coords.x = pt.x & TILE_UNIT_MASK;
2492 _tile_fract_coords.y = pt.y & TILE_UNIT_MASK;
2493
2494 w = _thd.GetCallbackWnd();
2495 if (w != nullptr) w->OnPlaceObject(pt, TileVirtXY(pt.x, pt.y));
2496}
2497
2498
2499bool HandleViewportClicked(const Viewport &vp, int x, int y)
2500{
2501 const Vehicle *v = CheckClickOnVehicle(vp, x, y);
2502
2503 if (_thd.place_mode & HT_VEHICLE) {
2504 if (v != nullptr && VehicleClicked(v)) return true;
2505 }
2506
2507 /* Vehicle placement mode already handled above. */
2508 if ((_thd.place_mode & HT_DRAG_MASK) != HT_NONE) {
2509 PlaceObject();
2510 return true;
2511 }
2512
2513 if (CheckClickOnViewportSign(vp, x, y)) return true;
2514 bool result = CheckClickOnLandscape(vp, x, y);
2515
2516 if (v != nullptr) {
2517 Debug(misc, 2, "Vehicle {} (index {}) at {}", v->unitnumber, v->index, fmt::ptr(v));
2519 v = v->First();
2520 if (_ctrl_pressed && v->owner == _local_company) {
2521 StartStopVehicle(v, true);
2522 } else {
2524 }
2525 }
2526 return true;
2527 }
2528 return result;
2529}
2530
2531void RebuildViewportOverlay(Window *w)
2532{
2533 if (w->viewport->overlay != nullptr &&
2534 w->viewport->overlay->GetCompanyMask().Any() &&
2535 w->viewport->overlay->GetCargoMask().Any()) {
2536 w->viewport->overlay->SetDirty();
2537 w->SetDirty();
2538 }
2539}
2540
2550bool ScrollWindowTo(int x, int y, int z, Window *w, bool instant)
2551{
2552 /* The slope cannot be acquired outside of the map, so make sure we are always within the map. */
2553 if (z == -1) {
2554 if ( x >= 0 && x <= (int)Map::SizeX() * (int)TILE_SIZE - 1
2555 && y >= 0 && y <= (int)Map::SizeY() * (int)TILE_SIZE - 1) {
2556 z = GetSlopePixelZ(x, y);
2557 } else {
2558 z = TileHeightOutsideMap(x / (int)TILE_SIZE, y / (int)TILE_SIZE);
2559 }
2560 }
2561
2562 Point pt = MapXYZToViewport(*w->viewport, x, y, z);
2563 w->viewport->CancelFollow(*w);
2564
2565 if (w->viewport->dest_scrollpos_x == pt.x && w->viewport->dest_scrollpos_y == pt.y) return false;
2566
2567 if (instant) {
2568 w->viewport->scrollpos_x = pt.x;
2569 w->viewport->scrollpos_y = pt.y;
2570 RebuildViewportOverlay(w);
2571 }
2572
2573 w->viewport->dest_scrollpos_x = pt.x;
2574 w->viewport->dest_scrollpos_y = pt.y;
2575 return true;
2576}
2577
2585bool ScrollWindowToTile(TileIndex tile, Window *w, bool instant)
2586{
2587 return ScrollWindowTo(TileX(tile) * TILE_SIZE, TileY(tile) * TILE_SIZE, -1, w, instant);
2588}
2589
2596bool ScrollMainWindowToTile(TileIndex tile, bool instant)
2597{
2598 return ScrollMainWindowTo(TileX(tile) * TILE_SIZE + TILE_SIZE / 2, TileY(tile) * TILE_SIZE + TILE_SIZE / 2, -1, instant);
2599}
2600
2606{
2607 TileIndex old;
2608
2609 old = _thd.redsq;
2610 _thd.redsq = tile;
2611
2612 if (tile != old) {
2613 if (tile != INVALID_TILE) MarkTileDirtyByTile(tile);
2614 if (old != INVALID_TILE) MarkTileDirtyByTile(old);
2615 }
2616}
2617
2623void SetTileSelectSize(int w, int h)
2624{
2625 _thd.new_size.x = w * TILE_SIZE;
2626 _thd.new_size.y = h * TILE_SIZE;
2627 _thd.new_outersize.x = 0;
2628 _thd.new_outersize.y = 0;
2629}
2630
2631void SetTileSelectBigSize(int ox, int oy, int sx, int sy)
2632{
2633 _thd.offs.x = ox * TILE_SIZE;
2634 _thd.offs.y = oy * TILE_SIZE;
2635 _thd.new_outersize.x = sx * TILE_SIZE;
2636 _thd.new_outersize.y = sy * TILE_SIZE;
2637}
2638
2645static HighLightStyle GetAutorailHT(int x, int y)
2646{
2648}
2649
2654{
2655 this->pos.x = 0;
2656 this->pos.y = 0;
2657 this->new_pos.x = 0;
2658 this->new_pos.y = 0;
2659}
2660
2669
2678
2679
2680
2689{
2690 int x1;
2691 int y1;
2692
2693 if (_thd.freeze) return;
2694
2695 HighLightStyle new_drawstyle = HT_NONE;
2696 bool new_diagonal = false;
2697
2698 if ((_thd.place_mode & HT_DRAG_MASK) == HT_SPECIAL) {
2699 x1 = _thd.selend.x;
2700 y1 = _thd.selend.y;
2701 if (x1 != -1) {
2702 int x2 = _thd.selstart.x & ~TILE_UNIT_MASK;
2703 int y2 = _thd.selstart.y & ~TILE_UNIT_MASK;
2704 x1 &= ~TILE_UNIT_MASK;
2705 y1 &= ~TILE_UNIT_MASK;
2706
2707 if (_thd.IsDraggingDiagonal()) {
2708 new_diagonal = true;
2709 } else {
2710 if (x1 >= x2) std::swap(x1, x2);
2711 if (y1 >= y2) std::swap(y1, y2);
2712 }
2713 _thd.new_pos.x = x1;
2714 _thd.new_pos.y = y1;
2715 _thd.new_size.x = x2 - x1;
2716 _thd.new_size.y = y2 - y1;
2717 if (!new_diagonal) {
2718 _thd.new_size.x += TILE_SIZE;
2719 _thd.new_size.y += TILE_SIZE;
2720 }
2721 new_drawstyle = _thd.next_drawstyle;
2722 }
2723 } else if ((_thd.place_mode & HT_DRAG_MASK) != HT_NONE) {
2724 Point pt = GetTileBelowCursor();
2725 x1 = pt.x;
2726 y1 = pt.y;
2727 if (x1 != -1) {
2728 switch (_thd.place_mode & HT_DRAG_MASK) {
2729 case HT_RECT:
2730 new_drawstyle = HT_RECT;
2731 break;
2732 case HT_POINT:
2733 new_drawstyle = HT_POINT;
2734 x1 += TILE_SIZE / 2;
2735 y1 += TILE_SIZE / 2;
2736 break;
2737 case HT_RAIL:
2738 /* Draw one highlighted tile in any direction */
2739 new_drawstyle = GetAutorailHT(pt.x, pt.y);
2740 break;
2741 case HT_LINE:
2742 switch (_thd.place_mode & HT_DIR_MASK) {
2743 case HT_DIR_X: new_drawstyle = HT_LINE | HT_DIR_X; break;
2744 case HT_DIR_Y: new_drawstyle = HT_LINE | HT_DIR_Y; break;
2745
2746 case HT_DIR_HU:
2747 case HT_DIR_HL:
2748 new_drawstyle = (pt.x & TILE_UNIT_MASK) + (pt.y & TILE_UNIT_MASK) <= TILE_SIZE ? HT_LINE | HT_DIR_HU : HT_LINE | HT_DIR_HL;
2749 break;
2750
2751 case HT_DIR_VL:
2752 case HT_DIR_VR:
2753 new_drawstyle = (pt.x & TILE_UNIT_MASK) > (pt.y & TILE_UNIT_MASK) ? HT_LINE | HT_DIR_VL : HT_LINE | HT_DIR_VR;
2754 break;
2755
2756 default: NOT_REACHED();
2757 }
2758 _thd.selstart.x = x1 & ~TILE_UNIT_MASK;
2759 _thd.selstart.y = y1 & ~TILE_UNIT_MASK;
2760 break;
2761 default:
2762 NOT_REACHED();
2763 }
2764 _thd.new_pos.x = x1 & ~TILE_UNIT_MASK;
2765 _thd.new_pos.y = y1 & ~TILE_UNIT_MASK;
2766 }
2767 }
2768
2769 /* redraw selection */
2770 if (_thd.drawstyle != new_drawstyle ||
2771 _thd.pos.x != _thd.new_pos.x || _thd.pos.y != _thd.new_pos.y ||
2772 _thd.size.x != _thd.new_size.x || _thd.size.y != _thd.new_size.y ||
2773 _thd.outersize.x != _thd.new_outersize.x ||
2774 _thd.outersize.y != _thd.new_outersize.y ||
2775 _thd.diagonal != new_diagonal) {
2776 /* Clear the old tile selection? */
2777 if ((_thd.drawstyle & HT_DRAG_MASK) != HT_NONE) SetSelectionTilesDirty();
2778
2779 _thd.drawstyle = new_drawstyle;
2780 _thd.pos = _thd.new_pos;
2781 _thd.size = _thd.new_size;
2782 _thd.outersize = _thd.new_outersize;
2783 _thd.diagonal = new_diagonal;
2784 _thd.dirty = 0xff;
2785
2786 /* Draw the new tile selection? */
2787 if ((new_drawstyle & HT_DRAG_MASK) != HT_NONE) SetSelectionTilesDirty();
2788 }
2789}
2790
2795static inline void ShowMeasurementTooltips(EncodedString &&text)
2796{
2797 if (!_settings_client.gui.measure_tooltip) return;
2798 GuiShowTooltips(_thd.GetCallbackWnd(), std::move(text), TooltipCloseCondition::ExitViewport);
2799}
2800
2801static void HideMeasurementTooltips()
2802{
2803 CloseWindowById(WindowClass::ToolTips, 0);
2804}
2805
2813{
2814 _thd.select_method = method;
2815 _thd.select_proc = process;
2816 _thd.selend.x = TileX(tile) * TILE_SIZE;
2817 _thd.selstart.x = TileX(tile) * TILE_SIZE;
2818 _thd.selend.y = TileY(tile) * TILE_SIZE;
2819 _thd.selstart.y = TileY(tile) * TILE_SIZE;
2820
2821 /* Needed so several things (road, autoroad, bridges, ...) are placed correctly.
2822 * In effect, placement starts from the centre of a tile
2823 */
2824 if (method == VPM_X_OR_Y || method == VPM_FIX_X || method == VPM_FIX_Y) {
2825 _thd.selend.x += TILE_SIZE / 2;
2826 _thd.selend.y += TILE_SIZE / 2;
2827 _thd.selstart.x += TILE_SIZE / 2;
2828 _thd.selstart.y += TILE_SIZE / 2;
2829 }
2830
2831 HighLightStyle others = _thd.place_mode & ~(HT_DRAG_MASK | HT_DIR_MASK);
2832 if ((_thd.place_mode & HT_DRAG_MASK) == HT_RECT) {
2833 _thd.place_mode = HT_SPECIAL | others;
2834 _thd.next_drawstyle = HT_RECT | others;
2835 } else if (_thd.place_mode & (HT_RAIL | HT_LINE)) {
2836 _thd.place_mode = HT_SPECIAL | others;
2837 _thd.next_drawstyle = _thd.drawstyle | others;
2838 } else {
2839 _thd.place_mode = HT_SPECIAL | others;
2840 _thd.next_drawstyle = HT_POINT | others;
2841 }
2843}
2844
2850{
2851 _thd.select_method = VPM_X_AND_Y;
2852 _thd.select_proc = process;
2853 _thd.selstart.x = 0;
2854 _thd.selstart.y = 0;
2855 _thd.next_drawstyle = HT_RECT;
2856
2858}
2859
2860void VpSetPlaceSizingLimit(int limit)
2861{
2862 _thd.sizelimit = limit;
2863}
2864
2871{
2872 uint64_t distance = DistanceManhattan(from, to) + 1;
2873
2874 _thd.selend.x = TileX(to) * TILE_SIZE;
2875 _thd.selend.y = TileY(to) * TILE_SIZE;
2876 _thd.selstart.x = TileX(from) * TILE_SIZE;
2877 _thd.selstart.y = TileY(from) * TILE_SIZE;
2878 _thd.next_drawstyle = HT_RECT;
2879
2880 /* show measurement only if there is any length to speak of */
2881 if (distance > 1) {
2882 ShowMeasurementTooltips(GetEncodedString(STR_MEASURE_LENGTH, distance));
2883 } else {
2884 HideMeasurementTooltips();
2885 }
2886}
2887
2888static void VpStartPreSizing()
2889{
2890 _thd.selend.x = -1;
2892}
2893
2902{
2903 int fxpy = _tile_fract_coords.x + _tile_fract_coords.y;
2904 int sxpy = (_thd.selend.x & TILE_UNIT_MASK) + (_thd.selend.y & TILE_UNIT_MASK);
2905 int fxmy = _tile_fract_coords.x - _tile_fract_coords.y;
2906 int sxmy = (_thd.selend.x & TILE_UNIT_MASK) - (_thd.selend.y & TILE_UNIT_MASK);
2907
2908 switch (direction) {
2909 default: NOT_REACHED();
2910 case DiagDirection::SE: // end piece is lower right
2911 if (fxpy >= 20 && sxpy <= 12) return HT_DIR_HL;
2912 if (fxmy < -3 && sxmy > 3) return HT_DIR_VR;
2913 return HT_DIR_Y;
2914
2915 case DiagDirection::NW:
2916 if (fxmy > 3 && sxmy < -3) return HT_DIR_VL;
2917 if (fxpy <= 12 && sxpy >= 20) return HT_DIR_HU;
2918 return HT_DIR_Y;
2919
2920 case DiagDirection::SW:
2921 if (fxmy > 3 && sxmy < -3) return HT_DIR_VL;
2922 if (fxpy >= 20 && sxpy <= 12) return HT_DIR_HL;
2923 return HT_DIR_X;
2924
2925 case DiagDirection::NE:
2926 if (fxmy < -3 && sxmy > 3) return HT_DIR_VR;
2927 if (fxpy <= 12 && sxpy >= 20) return HT_DIR_HU;
2928 return HT_DIR_X;
2929 }
2930}
2931
2945static bool SwapDirection(HighLightStyle style, TileIndex start_tile, TileIndex end_tile)
2946{
2947 uint start_x = TileX(start_tile);
2948 uint start_y = TileY(start_tile);
2949 uint end_x = TileX(end_tile);
2950 uint end_y = TileY(end_tile);
2951
2952 switch (style & HT_DRAG_MASK) {
2953 case HT_RAIL:
2954 case HT_LINE: return (end_x > start_x || (end_x == start_x && end_y > start_y));
2955
2956 case HT_RECT:
2957 case HT_POINT: return (end_x != start_x && end_y < start_y);
2958 default: NOT_REACHED();
2959 }
2960
2961 return false;
2962}
2963
2979static int CalcHeightdiff(HighLightStyle style, uint distance, TileIndex start_tile, TileIndex end_tile)
2980{
2981 bool swap = SwapDirection(style, start_tile, end_tile);
2982 uint h0, h1; // Start height and end height.
2983
2984 if (start_tile == end_tile) return 0;
2985 if (swap) std::swap(start_tile, end_tile);
2986
2987 switch (style & HT_DRAG_MASK) {
2988 case HT_RECT:
2989 /* In the case of an area we can determine whether we were dragging south or
2990 * east by checking the X-coordinates of the tiles */
2991 if (TileX(end_tile) > TileX(start_tile)) {
2992 /* Dragging south does not need to change the start tile. */
2993 end_tile = TileAddByDir(end_tile, Direction::S);
2994 } else {
2995 /* Dragging east. */
2996 start_tile = TileAddByDir(start_tile, Direction::SW);
2997 end_tile = TileAddByDir(end_tile, Direction::SE);
2998 }
2999 [[fallthrough]];
3000
3001 case HT_POINT:
3002 h0 = TileHeight(start_tile);
3003 h1 = TileHeight(end_tile);
3004 break;
3005 default: { // All other types, this is mostly only line/autorail
3006 static const HighLightStyle flip_style_direction[] = {
3008 };
3009 static const std::pair<TileIndexDiffC, TileIndexDiffC> start_heightdiff_line_by_dir[] = {
3010 { {1, 0}, {1, 1} }, // HT_DIR_X
3011 { {0, 1}, {1, 1} }, // HT_DIR_Y
3012 { {1, 0}, {0, 0} }, // HT_DIR_HU
3013 { {1, 0}, {1, 1} }, // HT_DIR_HL
3014 { {1, 0}, {1, 1} }, // HT_DIR_VL
3015 { {0, 1}, {1, 1} }, // HT_DIR_VR
3016 };
3017 static const std::pair<TileIndexDiffC, TileIndexDiffC> end_heightdiff_line_by_dir[] = {
3018 { {0, 1}, {0, 0} }, // HT_DIR_X
3019 { {1, 0}, {0, 0} }, // HT_DIR_Y
3020 { {0, 1}, {0, 0} }, // HT_DIR_HU
3021 { {1, 1}, {0, 1} }, // HT_DIR_HL
3022 { {1, 0}, {0, 0} }, // HT_DIR_VL
3023 { {0, 0}, {0, 1} }, // HT_DIR_VR
3024 };
3025 static_assert(std::size(start_heightdiff_line_by_dir) == HT_DIR_END);
3026 static_assert(std::size(end_heightdiff_line_by_dir) == HT_DIR_END);
3027
3028 distance %= 2; // we're only interested if the distance is even or uneven
3029 style &= HT_DIR_MASK;
3030 assert(style < HT_DIR_END);
3031
3032 /* To handle autorail, we do some magic to be able to use a lookup table.
3033 * Firstly if we drag the other way around, we switch start&end, and if needed
3034 * also flip the drag-position. Eg if it was on the left, and the distance is even
3035 * that means the end, which is now the start is on the right */
3036 if (swap && distance == 0) style = flip_style_direction[style];
3037
3038 /* Lambda to help calculating the height at one side of the line. */
3039 auto get_height = [](auto &tile, auto &heightdiffs) {
3040 return std::max(
3041 TileHeight(TileAdd(tile, ToTileIndexDiff(heightdiffs.first))),
3042 TileHeight(TileAdd(tile, ToTileIndexDiff(heightdiffs.second))));
3043 };
3044
3045 /* Use lookup table for start-tile based on HighLightStyle direction */
3046 h0 = get_height(start_tile, start_heightdiff_line_by_dir[style]);
3047
3048 /* Use lookup table for end-tile based on HighLightStyle direction
3049 * flip around side (lower/upper, left/right) based on distance */
3050 if (distance == 0) style = flip_style_direction[style];
3051 h1 = get_height(end_tile, end_heightdiff_line_by_dir[style]);
3052 break;
3053 }
3054 }
3055
3056 if (swap) std::swap(h0, h1);
3057 return (int)(h1 - h0) * TILE_HEIGHT_STEP;
3058}
3059
3066static void CheckUnderflow(int &test, int &other, int mult)
3067{
3068 if (test >= 0) return;
3069
3070 other += mult * test;
3071 test = 0;
3072}
3073
3081static void CheckOverflow(int &test, int &other, int max, int mult)
3082{
3083 if (test <= max) return;
3084
3085 other += mult * (test - max);
3086 test = max;
3087}
3088
3095static void CalcRaildirsDrawstyle(int x, int y, int method)
3096{
3098
3099 int dx = _thd.selstart.x - (_thd.selend.x & ~TILE_UNIT_MASK);
3100 int dy = _thd.selstart.y - (_thd.selend.y & ~TILE_UNIT_MASK);
3101 uint w = abs(dx) + TILE_SIZE;
3102 uint h = abs(dy) + TILE_SIZE;
3103
3104 if (method & ~(VPM_RAILDIRS | VPM_SIGNALDIRS)) {
3105 /* We 'force' a selection direction; first four rail buttons. */
3106 method &= ~(VPM_RAILDIRS | VPM_SIGNALDIRS);
3107 int raw_dx = _thd.selstart.x - _thd.selend.x;
3108 int raw_dy = _thd.selstart.y - _thd.selend.y;
3109 switch (method) {
3110 case VPM_FIX_X:
3111 b = HT_LINE | HT_DIR_Y;
3112 x = _thd.selstart.x;
3113 break;
3114
3115 case VPM_FIX_Y:
3116 b = HT_LINE | HT_DIR_X;
3117 y = _thd.selstart.y;
3118 break;
3119
3120 case VPM_FIX_HORIZONTAL:
3121 if (dx == -dy) {
3122 /* We are on a straight horizontal line. Determine the 'rail'
3123 * to build based the sub tile location. */
3125 } else {
3126 /* We are not on a straight line. Determine the rail to build
3127 * based on whether we are above or below it. */
3128 b = dx + dy >= (int)TILE_SIZE ? HT_LINE | HT_DIR_HU : HT_LINE | HT_DIR_HL;
3129
3130 /* Calculate where a horizontal line through the start point and
3131 * a vertical line from the selected end point intersect and
3132 * use that point as the end point. */
3133 int offset = (raw_dx - raw_dy) / 2;
3134 x = _thd.selstart.x - (offset & ~TILE_UNIT_MASK);
3135 y = _thd.selstart.y + (offset & ~TILE_UNIT_MASK);
3136
3137 /* 'Build' the last half rail tile if needed */
3138 if ((offset & TILE_UNIT_MASK) > (TILE_SIZE / 2)) {
3139 if (dx + dy >= (int)TILE_SIZE) {
3140 x -= (int)TILE_SIZE;
3141 } else {
3142 y += (int)TILE_SIZE;
3143 }
3144 }
3145
3146 /* Make sure we do not overflow the map! */
3147 CheckUnderflow(x, y, 1);
3148 CheckUnderflow(y, x, 1);
3149 CheckOverflow(x, y, (Map::MaxX() - 1) * TILE_SIZE, 1);
3150 CheckOverflow(y, x, (Map::MaxY() - 1) * TILE_SIZE, 1);
3151 assert(x >= 0 && y >= 0 && x <= (int)(Map::MaxX() * TILE_SIZE) && y <= (int)(Map::MaxY() * TILE_SIZE));
3152 }
3153 break;
3154
3155 case VPM_FIX_VERTICAL:
3156 if (dx == dy) {
3157 /* We are on a straight vertical line. Determine the 'rail'
3158 * to build based the sub tile location. */
3160 } else {
3161 /* We are not on a straight line. Determine the rail to build
3162 * based on whether we are left or right from it. */
3163 b = dx < dy ? HT_LINE | HT_DIR_VL : HT_LINE | HT_DIR_VR;
3164
3165 /* Calculate where a vertical line through the start point and
3166 * a horizontal line from the selected end point intersect and
3167 * use that point as the end point. */
3168 int offset = (raw_dx + raw_dy + (int)TILE_SIZE) / 2;
3169 x = _thd.selstart.x - (offset & ~TILE_UNIT_MASK);
3170 y = _thd.selstart.y - (offset & ~TILE_UNIT_MASK);
3171
3172 /* 'Build' the last half rail tile if needed */
3173 if ((offset & TILE_UNIT_MASK) > (TILE_SIZE / 2)) {
3174 if (dx < dy) {
3175 y -= (int)TILE_SIZE;
3176 } else {
3177 x -= (int)TILE_SIZE;
3178 }
3179 }
3180
3181 /* Make sure we do not overflow the map! */
3182 CheckUnderflow(x, y, -1);
3183 CheckUnderflow(y, x, -1);
3184 CheckOverflow(x, y, (Map::MaxX() - 1) * TILE_SIZE, -1);
3185 CheckOverflow(y, x, (Map::MaxY() - 1) * TILE_SIZE, -1);
3186 assert(x >= 0 && y >= 0 && x <= (int)(Map::MaxX() * TILE_SIZE) && y <= (int)(Map::MaxY() * TILE_SIZE));
3187 }
3188 break;
3189
3190 default:
3191 NOT_REACHED();
3192 }
3193 } else if (TileVirtXY(_thd.selstart.x, _thd.selstart.y) == TileVirtXY(x, y)) { // check if we're only within one tile
3194 if (method & VPM_RAILDIRS) {
3195 b = GetAutorailHT(x, y);
3196 } else { // rect for autosignals on one tile
3197 b = HT_RECT;
3198 }
3199 } else if (h == TILE_SIZE) { // Is this in X direction?
3200 if (dx == (int)TILE_SIZE) { // 2x1 special handling
3202 } else if (dx == -(int)TILE_SIZE) {
3204 } else {
3205 b = HT_LINE | HT_DIR_X;
3206 }
3207 y = _thd.selstart.y;
3208 } else if (w == TILE_SIZE) { // Or Y direction?
3209 if (dy == (int)TILE_SIZE) { // 2x1 special handling
3211 } else if (dy == -(int)TILE_SIZE) { // 2x1 other direction
3213 } else {
3214 b = HT_LINE | HT_DIR_Y;
3215 }
3216 x = _thd.selstart.x;
3217 } else if (w > h * 2) { // still count as x dir?
3218 b = HT_LINE | HT_DIR_X;
3219 y = _thd.selstart.y;
3220 } else if (h > w * 2) { // still count as y dir?
3221 b = HT_LINE | HT_DIR_Y;
3222 x = _thd.selstart.x;
3223 } else { // complicated direction
3224 int d = w - h;
3225 _thd.selend.x = _thd.selend.x & ~TILE_UNIT_MASK;
3226 _thd.selend.y = _thd.selend.y & ~TILE_UNIT_MASK;
3227
3228 /* four cases. */
3229 if (x > _thd.selstart.x) {
3230 if (y > _thd.selstart.y) {
3231 /* south */
3232 if (d == 0) {
3234 } else if (d >= 0) {
3235 x = _thd.selstart.x + h;
3236 b = HT_LINE | HT_DIR_VL;
3237 } else {
3238 y = _thd.selstart.y + w;
3239 b = HT_LINE | HT_DIR_VR;
3240 }
3241 } else {
3242 /* west */
3243 if (d == 0) {
3245 } else if (d >= 0) {
3246 x = _thd.selstart.x + h;
3247 b = HT_LINE | HT_DIR_HL;
3248 } else {
3249 y = _thd.selstart.y - w;
3250 b = HT_LINE | HT_DIR_HU;
3251 }
3252 }
3253 } else {
3254 if (y > _thd.selstart.y) {
3255 /* east */
3256 if (d == 0) {
3258 } else if (d >= 0) {
3259 x = _thd.selstart.x - h;
3260 b = HT_LINE | HT_DIR_HU;
3261 } else {
3262 y = _thd.selstart.y + w;
3263 b = HT_LINE | HT_DIR_HL;
3264 }
3265 } else {
3266 /* north */
3267 if (d == 0) {
3269 } else if (d >= 0) {
3270 x = _thd.selstart.x - h;
3271 b = HT_LINE | HT_DIR_VR;
3272 } else {
3273 y = _thd.selstart.y - w;
3274 b = HT_LINE | HT_DIR_VL;
3275 }
3276 }
3277 }
3278 }
3279
3280 if (_settings_client.gui.measure_tooltip) {
3281 TileIndex t0 = TileVirtXY(_thd.selstart.x, _thd.selstart.y);
3282 TileIndex t1 = TileVirtXY(x, y);
3283 uint distance = DistanceManhattan(t0, t1) + 1;
3284
3285 if (distance == 1) {
3286 HideMeasurementTooltips();
3287 } else {
3288 int heightdiff = CalcHeightdiff(b, distance, t0, t1);
3289 /* If we are showing a tooltip for horizontal or vertical drags,
3290 * 2 tiles have a length of 1. To bias towards the ceiling we add
3291 * one before division. It feels more natural to count 3 lengths as 2 */
3292 if ((b & HT_DIR_MASK) != HT_DIR_X && (b & HT_DIR_MASK) != HT_DIR_Y) {
3293 distance = CeilDiv(distance, 2);
3294 }
3295
3296 if (heightdiff == 0) {
3297 ShowMeasurementTooltips(GetEncodedString(STR_MEASURE_LENGTH, distance));
3298 } else {
3299 ShowMeasurementTooltips(GetEncodedString(STR_MEASURE_LENGTH_HEIGHTDIFF, distance, heightdiff));
3300 }
3301 }
3302 }
3303
3304 _thd.selend.x = x;
3305 _thd.selend.y = y;
3306 _thd.next_drawstyle = b;
3307}
3308
3317{
3318 int sx, sy;
3319 HighLightStyle style;
3320
3321 if (x == -1) {
3322 _thd.selend.x = -1;
3323 return;
3324 }
3325
3326 /* Special handling of drag in any (8-way) direction */
3327 if (method & (VPM_RAILDIRS | VPM_SIGNALDIRS)) {
3328 _thd.selend.x = x;
3329 _thd.selend.y = y;
3330 CalcRaildirsDrawstyle(x, y, method);
3331 return;
3332 }
3333
3334 /* Needed so level-land is placed correctly */
3335 if ((_thd.next_drawstyle & HT_DRAG_MASK) == HT_POINT) {
3336 x += TILE_SIZE / 2;
3337 y += TILE_SIZE / 2;
3338 }
3339
3340 sx = _thd.selstart.x;
3341 sy = _thd.selstart.y;
3342
3343 int limit = 0;
3344
3345 switch (method) {
3346 case VPM_X_OR_Y: // drag in X or Y direction
3347 if (abs(sy - y) < abs(sx - x)) {
3348 y = sy;
3349 style = HT_DIR_X;
3350 } else {
3351 x = sx;
3352 style = HT_DIR_Y;
3353 }
3354 goto calc_heightdiff_single_direction;
3355
3356 case VPM_X_LIMITED: // Drag in X direction (limited size).
3357 limit = (_thd.sizelimit - 1) * TILE_SIZE;
3358 [[fallthrough]];
3359
3360 case VPM_FIX_X: // drag in Y direction
3361 x = sx;
3362 style = HT_DIR_Y;
3363 goto calc_heightdiff_single_direction;
3364
3365 case VPM_Y_LIMITED: // Drag in Y direction (limited size).
3366 limit = (_thd.sizelimit - 1) * TILE_SIZE;
3367 [[fallthrough]];
3368
3369 case VPM_FIX_Y: // drag in X direction
3370 y = sy;
3371 style = HT_DIR_X;
3372
3373calc_heightdiff_single_direction:;
3374 if (limit > 0) {
3375 x = sx + Clamp(x - sx, -limit, limit);
3376 y = sy + Clamp(y - sy, -limit, limit);
3377 }
3378 if (_settings_client.gui.measure_tooltip) {
3379 TileIndex t0 = TileVirtXY(sx, sy);
3380 TileIndex t1 = TileVirtXY(x, y);
3381 uint distance = DistanceManhattan(t0, t1) + 1;
3382
3383 if (distance == 1) {
3384 HideMeasurementTooltips();
3385 } else {
3386 /* With current code passing a HT_LINE style to calculate the height
3387 * difference is enough. However if/when a point-tool is created
3388 * with this method, function should be called with new_style (below)
3389 * instead of HT_LINE | style case HT_POINT is handled specially
3390 * new_style := (_thd.next_drawstyle & HT_RECT) ? HT_LINE | style : _thd.next_drawstyle; */
3391 int heightdiff = CalcHeightdiff(HT_LINE | style, 0, t0, t1);
3392
3393 if (heightdiff == 0) {
3394 ShowMeasurementTooltips(GetEncodedString(STR_MEASURE_LENGTH, distance));
3395 } else {
3396 ShowMeasurementTooltips(GetEncodedString(STR_MEASURE_LENGTH_HEIGHTDIFF, distance, heightdiff));
3397 }
3398 }
3399 }
3400 break;
3401
3402 case VPM_X_AND_Y_LIMITED: // Drag an X by Y constrained rect area.
3403 limit = (_thd.sizelimit - 1) * TILE_SIZE;
3404 x = sx + Clamp(x - sx, -limit, limit);
3405 y = sy + Clamp(y - sy, -limit, limit);
3406 [[fallthrough]];
3407
3408 case VPM_X_AND_Y: // drag an X by Y area
3409 if (_settings_client.gui.measure_tooltip) {
3410 TileIndex t0 = TileVirtXY(sx, sy);
3411 TileIndex t1 = TileVirtXY(x, y);
3412 uint dx = Delta(TileX(t0), TileX(t1)) + 1;
3413 uint dy = Delta(TileY(t0), TileY(t1)) + 1;
3414
3415 /* If dragging an area (eg dynamite tool) and it is actually a single
3416 * row/column, change the type to 'line' to get proper calculation for height */
3417 style = (HighLightStyle)_thd.next_drawstyle;
3418 if (_thd.IsDraggingDiagonal()) {
3419 /* Determine the "area" of the diagonal dragged selection.
3420 * We assume the area is the number of tiles along the X
3421 * edge and the number of tiles along the Y edge. However,
3422 * multiplying these two numbers does not give the exact
3423 * number of tiles; basically we are counting the black
3424 * squares on a chess board and ignore the white ones to
3425 * make the tile counts at the edges match up. There is no
3426 * other way to make a proper count though.
3427 *
3428 * First convert to the rotated coordinate system. */
3429 int dist_x = TileX(t0) - TileX(t1);
3430 int dist_y = TileY(t0) - TileY(t1);
3431 int a_max = dist_x + dist_y;
3432 int b_max = dist_y - dist_x;
3433
3434 /* Now determine the size along the edge, but due to the
3435 * chess board principle this counts double. */
3436 a_max = abs(a_max + (a_max > 0 ? 2 : -2)) / 2;
3437 b_max = abs(b_max + (b_max > 0 ? 2 : -2)) / 2;
3438
3439 /* We get a 1x1 on normal 2x1 rectangles, due to it being
3440 * a seen as two sides. As the result for actual building
3441 * will be the same as non-diagonal dragging revert to that
3442 * behaviour to give it a more normally looking size. */
3443 if (a_max != 1 || b_max != 1) {
3444 dx = a_max;
3445 dy = b_max;
3446 }
3447 } else if (style & HT_RECT) {
3448 if (dx == 1) {
3449 style = HT_LINE | HT_DIR_Y;
3450 } else if (dy == 1) {
3451 style = HT_LINE | HT_DIR_X;
3452 }
3453 }
3454
3455 if (dx != 1 || dy != 1) {
3456 int heightdiff = CalcHeightdiff(style, 0, t0, t1);
3457
3458 dx -= (style & HT_POINT ? 1 : 0);
3459 dy -= (style & HT_POINT ? 1 : 0);
3460
3461 if (heightdiff == 0) {
3462 ShowMeasurementTooltips(GetEncodedString(STR_MEASURE_AREA, dx, dy));
3463 } else {
3464 ShowMeasurementTooltips(GetEncodedString(STR_MEASURE_AREA_HEIGHTDIFF, dx, dy, heightdiff));
3465 }
3466 }
3467 }
3468 break;
3469
3470 default: NOT_REACHED();
3471 }
3472
3473 _thd.selend.x = x;
3474 _thd.selend.y = y;
3475}
3476
3482{
3484
3485 /* stop drag mode if the window has been closed */
3486 Window *w = _thd.GetCallbackWnd();
3487 if (w == nullptr) {
3489 return EventState::Handled;
3490 }
3491
3492 /* while dragging execute the drag procedure of the corresponding window (mostly VpSelectTilesWithMethod() ) */
3493 if (_left_button_down) {
3495 /* Only register a drag event when the mouse moved. */
3496 if (_thd.new_pos.x == _thd.selstart.x && _thd.new_pos.y == _thd.selstart.y) return EventState::Handled;
3497 _thd.selstart.x = _thd.new_pos.x;
3498 _thd.selstart.y = _thd.new_pos.y;
3499 }
3500
3501 w->OnPlaceDrag(_thd.select_method, _thd.select_proc, GetTileBelowCursor());
3502 return EventState::Handled;
3503 }
3504
3505 /* Mouse button released. */
3508
3509 /* Keep the selected tool, but reset it to the original mode. */
3510 HighLightStyle others = _thd.place_mode & ~(HT_DRAG_MASK | HT_DIR_MASK);
3511 if ((_thd.next_drawstyle & HT_DRAG_MASK) == HT_RECT) {
3512 _thd.place_mode = HT_RECT | others;
3513 } else if (_thd.select_method & VPM_SIGNALDIRS) {
3514 _thd.place_mode = HT_RECT | others;
3515 } else if (_thd.select_method & VPM_RAILDIRS) {
3516 _thd.place_mode = (_thd.select_method & ~VPM_RAILDIRS) ? _thd.next_drawstyle : (HT_RAIL | others);
3517 } else {
3518 _thd.place_mode = HT_POINT | others;
3519 }
3520 SetTileSelectSize(1, 1);
3521
3522 HideMeasurementTooltips();
3523 w->OnPlaceMouseUp(_thd.select_method, _thd.select_proc, _thd.selend, TileVirtXY(_thd.selstart.x, _thd.selstart.y), TileVirtXY(_thd.selend.x, _thd.selend.y));
3524
3525 return EventState::Handled;
3526}
3527
3536{
3537 SetObjectToPlace(icon, pal, mode, w->window_class, w->window_number);
3538}
3539
3540#include "table/animcursors.h"
3541
3550void SetObjectToPlace(CursorID icon, PaletteID pal, HighLightStyle mode, WindowClass window_class, WindowNumber window_num)
3551{
3552 if (_thd.window_class != WindowClass::Invalid) {
3553 /* Undo clicking on button and drag & drop */
3554 Window *w = _thd.GetCallbackWnd();
3555 /* Call the abort function, but set the window class to something
3556 * that will never be used to avoid infinite loops. Setting it to
3557 * the 'next' window class must not be done because recursion into
3558 * this function might in some cases reset the newly set object to
3559 * place or not properly reset the original selection. */
3560 _thd.window_class = WindowClass::Invalid;
3561 if (w != nullptr) {
3562 w->OnPlaceObjectAbort();
3563 HideMeasurementTooltips();
3564 }
3565 }
3566
3567 /* Mark the old selection dirty, in case the selection shape or colour changes */
3568 if ((_thd.drawstyle & HT_DRAG_MASK) != HT_NONE) SetSelectionTilesDirty();
3569
3570 SetTileSelectSize(1, 1);
3571
3572 _thd.make_square_red = false;
3573
3574 if (mode == HT_DRAG) { // HT_DRAG is for dragdropping trains in the depot window
3575 mode = HT_NONE;
3577 } else {
3579 }
3580
3581 _thd.place_mode = mode;
3582 _thd.window_class = window_class;
3583 _thd.window_number = window_num;
3584
3585 if ((mode & HT_DRAG_MASK) == HT_SPECIAL) { // special tools, like tunnels or docks start with presizing mode
3586 VpStartPreSizing();
3587 }
3588
3589 SetCursor(icon, pal);
3590}
3591
3594{
3595 SetObjectToPlace(SPR_CURSOR_MOUSE, PAL_NONE, HT_NONE, WindowClass::MainWindow, 0);
3596}
3597
3598Point GetViewportStationMiddle(const Viewport &vp, const Station *st)
3599{
3600 int x = TileX(st->xy) * TILE_SIZE;
3601 int y = TileY(st->xy) * TILE_SIZE;
3602 int z = GetSlopePixelZ(Clamp(x, 0, Map::SizeX() * TILE_SIZE - 1), Clamp(y, 0, Map::SizeY() * TILE_SIZE - 1));
3603
3604 Point p = RemapCoords(x, y, z);
3605 p.x = UnScaleByZoom(p.x - vp.virtual_left, vp.zoom) + vp.left;
3606 p.y = UnScaleByZoom(p.y - vp.virtual_top, vp.zoom) + vp.top;
3607 return p;
3608}
3609
3615
3618#ifdef WITH_SSE
3619 { &ViewportSortParentSpritesSSE41Checker, &ViewportSortParentSpritesSSE41 },
3620#endif
3621 { []() { return true; /* Always available */ }, &ViewportSortParentSprites }
3622};
3623
3626{
3627 for (const auto &sprite_sorter : _vp_sprite_sorters) {
3628 if (sprite_sorter.fct_checker()) {
3629 _vp_sprite_sorter = sprite_sorter.fct_sorter;
3630 break;
3631 }
3632 }
3633 assert(_vp_sprite_sorter != nullptr);
3634}
3635
3645{
3646 if (_current_company != OWNER_DEITY) return CMD_ERROR;
3647 switch (target) {
3648 case VST_EVERYONE:
3649 break;
3650 case VST_COMPANY:
3651 if (_local_company != (CompanyID)ref) return CommandCost();
3652 break;
3653 case VST_CLIENT:
3654 if (_network_own_client_id != (ClientID)ref) return CommandCost();
3655 break;
3656 default:
3657 return CMD_ERROR;
3658 }
3659
3660 if (flags.Test(DoCommandFlag::Execute)) {
3663 }
3664 return CommandCost();
3665}
3666
3667void MarkCatchmentTilesDirty()
3668{
3669 if (_viewport_highlight_town != nullptr) {
3671 return;
3672 }
3673
3674 if (_viewport_highlight_station != nullptr) {
3675 if (_viewport_highlight_station->catchment_tiles.tile == INVALID_TILE) {
3678 } else {
3680 for (TileIndex tile = it; tile != INVALID_TILE; tile = ++it) {
3681 MarkTileDirtyByTile(tile);
3682 }
3683 }
3684 }
3685
3686 if (_viewport_highlight_station_rect != nullptr) {
3687 if (!_viewport_highlight_station_rect->IsInUse()) {
3689 }
3691 }
3692
3693 if (_viewport_highlight_waypoint != nullptr) {
3694 if (!_viewport_highlight_waypoint->IsInUse()) {
3696 }
3698 }
3699
3700 if (_viewport_highlight_waypoint_rect != nullptr) {
3701 if (!_viewport_highlight_waypoint_rect->IsInUse()) {
3703 }
3705 }
3706}
3707
3708static void SetWindowDirtyForViewportCatchment()
3709{
3710 if (_viewport_highlight_station != nullptr) SetWindowDirty(WindowClass::StationView, _viewport_highlight_station->index);
3711 if (_viewport_highlight_station_rect != nullptr) SetWindowDirty(WindowClass::StationView, _viewport_highlight_station_rect->index);
3712 if (_viewport_highlight_waypoint != nullptr) SetWindowDirty(WindowClass::WaypointView, _viewport_highlight_waypoint->index);
3713 if (_viewport_highlight_waypoint_rect != nullptr) SetWindowDirty(WindowClass::WaypointView, _viewport_highlight_waypoint_rect->index);
3714 if (_viewport_highlight_town != nullptr) SetWindowDirty(WindowClass::TownView, _viewport_highlight_town->index);
3715}
3716
3717static void ClearViewportCatchment()
3718{
3719 MarkCatchmentTilesDirty();
3724 _viewport_highlight_town = nullptr;
3725}
3726
3733void SetViewportCatchmentStation(const Station *st, bool sel)
3734{
3735 SetWindowDirtyForViewportCatchment();
3736 /* Mark tiles dirty for redrawing and update selected station if a different station is already highlighted. */
3737 if (sel && _viewport_highlight_station != st) {
3738 ClearViewportCatchment();
3740 MarkCatchmentTilesDirty();
3741 /* Mark tiles dirty for redrawing and clear station selection if deselecting highlight. */
3742 } else if (!sel && _viewport_highlight_station == st) {
3743 MarkCatchmentTilesDirty();
3745 }
3746 /* Redraw the currently selected station window */
3747 if (_viewport_highlight_station != nullptr) SetWindowDirty(WindowClass::StationView, _viewport_highlight_station->index);
3748}
3749
3756void SetViewportStationRect(const Station *st, bool sel)
3757{
3758 SetWindowDirtyForViewportCatchment();
3759 /* Mark tiles dirty for redrawing and update selected station if a different station is already highlighted. */
3760 if (sel && _viewport_highlight_station_rect != st) {
3761 ClearViewportCatchment();
3763 MarkCatchmentTilesDirty();
3764 /* Mark tiles dirty for redrawing and clear station selection if deselecting highlight. */
3765 } else if (!sel && _viewport_highlight_station_rect == st) {
3766 MarkCatchmentTilesDirty();
3768 }
3769 /* Redraw the currently selected station window */
3770 if (_viewport_highlight_station_rect != nullptr) SetWindowDirty(WindowClass::StationView, _viewport_highlight_station_rect->index);
3771}
3772
3780{
3781 SetWindowDirtyForViewportCatchment();
3782 /* Mark tiles dirty for redrawing and update selected waypoint if a different waypoint is already highlighted. */
3783 if (sel && _viewport_highlight_waypoint != wp) {
3784 ClearViewportCatchment();
3786 MarkCatchmentTilesDirty();
3787 /* Mark tiles dirty for redrawing and clear waypoint selection if deselecting highlight. */
3788 } else if (!sel && _viewport_highlight_waypoint == wp) {
3789 MarkCatchmentTilesDirty();
3791 }
3792 /* Redraw the currently selected waypoint window */
3793 if (_viewport_highlight_waypoint != nullptr) SetWindowDirty(WindowClass::WaypointView, _viewport_highlight_waypoint->index);
3794}
3795
3802void SetViewportWaypointRect(const Waypoint *wp, bool sel)
3803{
3804 SetWindowDirtyForViewportCatchment();
3805 /* Mark tiles dirty for redrawing and update selected waypoint if a different waypoint is already highlighted. */
3806 if (sel && _viewport_highlight_waypoint_rect != wp) {
3807 ClearViewportCatchment();
3809 MarkCatchmentTilesDirty();
3810 /* Mark tiles dirty for redrawing and clear waypoint selection if deselecting highlight. */
3811 } else if (!sel && _viewport_highlight_waypoint_rect == wp) {
3812 MarkCatchmentTilesDirty();
3814 }
3815 /* Redraw the currently selected waypoint window */
3816 if (_viewport_highlight_waypoint_rect != nullptr) SetWindowDirty(WindowClass::WaypointView, _viewport_highlight_waypoint_rect->index);
3817}
3818
3825void SetViewportCatchmentTown(const Town *t, bool sel)
3826{
3827 SetWindowDirtyForViewportCatchment();
3828 /* Mark tiles dirty for redrawing and update selected town if a different town is already highlighted. */
3829 if (sel && _viewport_highlight_town != t) {
3830 ClearViewportCatchment();
3833 /* Mark tiles dirty for redrawing and clear town selection if deselecting highlight. */
3834 } else if (!sel && _viewport_highlight_town == t) {
3835 _viewport_highlight_town = nullptr;
3837 }
3838 /* Redraw the currently selected town window */
3839 if (_viewport_highlight_town != nullptr) SetWindowDirty(WindowClass::TownView, _viewport_highlight_town->index);
3840}
3841
3846void ViewportData::CancelFollow(const Window &viewport_window)
3847{
3848 if (this->follow_vehicle == VehicleID::Invalid()) return;
3849
3850 if (viewport_window.window_class == WindowClass::MainWindow) {
3851 /* We're cancelling follow in the main viewport, so we need to check for a vehicle view window
3852 * to raise the location follow widget. */
3853 Window *vehicle_window = FindWindowById(WindowClass::VehicleView, this->follow_vehicle);
3854 if (vehicle_window != nullptr) vehicle_window->RaiseWidgetWhenLowered(WID_VV_LOCATION);
3855 }
3856
3857 this->dest_scrollpos_x = this->scrollpos_x;
3858 this->dest_scrollpos_y = this->scrollpos_y;
3859 this->follow_vehicle = VehicleID::Invalid();
3860}
This file defines all the the animated cursors.
Highlight/sprite information for autorail.
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
static const int _autorail_slope_sprite_offsets[][6]
Table maps each of the six rail directions and tileh combinations to a sprite offset.
Definition autorail.h:25
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.
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.
Iterate a range of enum values.
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
EnumBitSet< DoCommandFlag, uint16_t > DoCommandFlags
Bitset of DoCommandFlag elements.
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
@ SW
Southwest.
@ SE
Southeast.
DiagDirection
Enumeration for diagonal directions.
@ SW
Southwest.
@ NW
Northwest.
@ NE
Northeast, upper right on your monitor.
@ SE
Southeast.
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
EnumClassIndexContainer< std::array< T, to_underlying(N)>, Index > EnumIndexArray
A typedef for EnumClassIndexContainer using std::array as the backing container type.
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:899
bool _ctrl_pressed
Is Ctrl pressed?
Definition gfx.cpp:39
int DrawString(int left, int right, int top, std::string_view str, ExtendedTextColour colour, StringAlignment align, bool underline, FontSize fontsize)
Draw string, possibly truncated to make it fit in its allocated space.
Definition gfx.cpp:668
void SetCursor(CursorID icon, PaletteID pal)
Assign an animation or a non-animated sprite to the cursor.
Definition gfx.cpp:1737
void DrawSpriteViewport(SpriteID img, PaletteID pal, int x, int y, const SubSprite *sub)
Draw a sprite in a viewport.
Definition gfx.cpp:1009
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:406
@ 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:437
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:284
@ White
White.
Definition gfx_type.h:301
@ Invalid
Invalid marker.
Definition gfx_type.h:303
@ Grey
Grey.
Definition gfx_type.h:300
@ White
White colour.
Definition gfx_type.h:331
@ Black
Black colour.
Definition gfx_type.h:335
@ Town
Destination is a town.
Definition goal_type.h:55
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:972
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:1520
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:1553
void RedrawScreenRect(int left, int top, int right, int bottom)
Repaints a specific rectangle of the screen.
Definition gfx.cpp:1430
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.
Point InverseRemapCoords2(int x, int y, bool clamp_to_map, bool *clamped)
Map 2D viewport or smallmap coordinate to 3D world or tile coordinate.
const EnumIndexArray< const TileTypeProcs *, TileType, TileType::MaxSize > _tile_type_procs
Tile callback functions for each type of tile.
Definition landscape.cpp:69
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:690
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
@ ShowSigns
Display signs.
Definition openttd.h:48
@ ShowWaypointNames
Display waypoint names.
Definition openttd.h:51
@ ShowTownNames
Display town names.
Definition openttd.h:46
@ ShowStationNames
Display station names.
Definition openttd.h:47
@ ShowCompetitorSigns
Display signs, station names and waypoint names of opponent companies. Buoys and oilrig-stations are ...
Definition openttd.h:52
@ Menu
In the main menu.
Definition openttd.h:19
PixelColour GetColourGradient(Colours colour, Shade shade)
Get colour gradient palette index.
Definition palette.cpp:393
@ Lighter
Lighter colour shade.
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:413
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:53
@ SLOPE_N
the north corner of the tile is raised
Definition slope_type.h:58
@ SLOPE_STEEP_N
a steep slope falling to south (from north)
Definition slope_type.h:74
EnumIndexArray< T, Corner, CORNER_END > CornerIndexArray
Array with Corner as index.
Definition slope_type.h:35
bool ScrollMainWindowTo(int x, int y, int z, bool instant)
Scrolls the main window to given coordinates.
static constexpr uint32_t MAX_SPRITES
Masks needed for sprite operations.
Definition sprites.h:1569
static const PaletteID PALETTE_TILE_RED_PULSATING
pulsating red tile drawn if you try to build a wrong tunnel or raise/lower land where it is not possi...
Definition sprites.h:1583
static const 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.
@ Sign
Use the Sign cursor.
Definition story_base.h:56
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
Container for the text colour and some text colour related flags for drawing.
Definition gfx_type.h:350
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:308
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:279
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:34
Slope tileh
Slope of the tile.
Definition tile_cmd.h:35
TileIndex tile
Tile index.
Definition tile_cmd.h:36
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:55
Town data structure.
Definition town.h:64
TileIndex xy
town center tile
Definition town.h:65
TownCache cache
Container for all cacheable data.
Definition town.h:67
bool show_zone
NOSAVE: mark town to show the local authority zone in the viewports.
Definition town.h:202
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:250
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:255
int32_t scrollpos_y
Currently shown y coordinate (virtual screen coordinate of topleft corner of the viewport).
Definition window_gui.h:253
int32_t dest_scrollpos_x
Current destination x coordinate to display (virtual screen coordinate of topleft corner of the viewp...
Definition window_gui.h:254
VehicleID follow_vehicle
VehicleID to follow if following a vehicle, VehicleID::Invalid() otherwise.
Definition window_gui.h:251
int32_t scrollpos_x
Currently shown x coordinate (virtual screen coordinate of topleft corner of the viewport).
Definition window_gui.h:252
Data structure storing rendering information.
Definition viewport.cpp:170
ParentSpriteToSortVector parent_sprites_to_sort
Parent sprite pointer array used for sorting.
Definition viewport.cpp:176
EnumIndexArray< Point, FoundationPart, FoundationPart::End > foundation_offset
Pixel offset for ground sprites on the foundations.
Definition viewport.cpp:186
EnumIndexArray< int, FoundationPart, FoundationPart::End > foundation
Foundation sprites (index into parent_sprites_to_draw).
Definition viewport.cpp:184
SpriteCombineMode combine_sprites
Current mode of "sprite combining".
Definition viewport.cpp:181
EnumIndexArray< int, FoundationPart, FoundationPart::End > last_foundation_child
Tail of ChildSprite list of the foundations. (index into child_screen_sprites_to_draw).
Definition viewport.cpp:185
FoundationPart foundation_part
Currently active foundation for ground sprite drawing.
Definition viewport.cpp:183
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:273
void SetWidgetDirty(WidgetID widget_index) const
Invalidate a widget, i.e.
Definition window.cpp:562
std::unique_ptr< ViewportData > viewport
Pointer to viewport data, if present.
Definition window_gui.h:318
void RaiseWidgetWhenLowered(WidgetID widget_index)
Marks a widget as raised and dirty (redraw), when it is marked as lowered.
Definition window_gui.h:478
virtual void OnPlaceObjectAbort()
The user cancelled a tile highlight mode that has been set.
Definition window_gui.h:828
WindowClass window_class
Window class.
Definition window_gui.h:301
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:806
WindowIterator< false > IteratorToFront
Iterate in Z order towards front.
Definition window_gui.h:917
int left
x position of left edge of the window
Definition window_gui.h:309
int top
y position of top edge of the window
Definition window_gui.h:310
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:849
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:838
void SetWidgetDisabledState(WidgetID widget_index, bool disab_stat)
Sets the enabled/disabled status of a widget.
Definition window_gui.h:381
AllWindows< false > Iterate
Iterate all windows in whatever order is easiest.
Definition window_gui.h:939
int height
Height of the window (number of pixels down in y direction).
Definition window_gui.h:312
int width
width of the window (number of pixels to the right in x direction)
Definition window_gui.h:311
WindowNumber window_number
Window number within the window class.
Definition window_gui.h:302
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.
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...
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...
DisplayOptions _display_opt
What do we want to draw/do?
void ViewportAddVehicles(DrawPixelInfo *dpi)
Add the vehicle sprites that should be drawn at a part of the screen.
Definition vehicle.cpp:1148
Vehicle * CheckClickOnVehicle(const Viewport &vp, int x, int y)
Find the vehicle close to the clicked coordinates.
Definition vehicle.cpp:1242
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
@ None
Neither foundation nor groundsprite drawn yet.
Definition viewport.cpp:145
@ End
End marker.
Definition viewport.cpp:148
@ Normal
First part (normal foundation or no foundation).
Definition viewport.cpp:146
@ Halftile
Second part (halftile foundation).
Definition viewport.cpp:147
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.
static void DrawAutorailSelection(const TileInfo *ti, HighLightStyle highlight_style)
Draws autorail highlights.
Definition viewport.cpp:976
void OffsetGroundSprite(int x, int y)
Called when a foundation has been drawn for the current tile.
Definition viewport.cpp:605
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:803
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:916
void StartSpriteCombine()
Starts a block of sprites, which are "combined" into a single bounding box.
Definition viewport.cpp:765
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:629
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 maximum 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:791
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
SpriteCombineMode
Mode of "sprite combining".
Definition viewport.cpp:155
@ Pending
Sprite combining will start with the next unclipped sprite.
Definition viewport.cpp:157
@ Active
Sprite combining is active. AddSortableSpriteToDraw outputs child sprites.
Definition viewport.cpp:158
@ 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.
TileHighlightType
Types of tile highlight.
@ White
Indicates a tile which is part of the highlighted station, or a station tile within the highlighted t...
@ None
No tile highlight.
@ Blue
Indicates a tile which is in the catchment area of the highlighted station.
@ Red
Indicates a house tile which is not in any station catchment area of the highlighted town.
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:664
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:775
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:826
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:899
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.
static FoundationPart GetNextFoundationPart(FoundationPart foundation_part)
Get the next foundation part.
Definition viewport.cpp:589
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:872
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.
EnumBitSet< ViewportStringFlag, uint8_t > ViewportStringFlags
Bitset of ViewportStringFlag elements.
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:1201
Window * GetMainWindow()
Get the main window, i.e.
Definition window.cpp:1187
Window * FindWindowFromPt(int x, int y)
Do a search for a window at specific coordinates.
Definition window.cpp:1850
SpecialMouseMode _special_mouse_mode
Mode of the mouse.
Definition window.cpp:88
Window * FindWindowById(WindowClass cls, WindowNumber number)
Find a window by its class and window number.
Definition window.cpp:1158
void SetWindowDirty(WindowClass cls, WindowNumber number)
Mark window as dirty (in need of repainting).
Definition window.cpp:3193
Window functions not directly related to making/drawing windows.
Functions, definitions and such used only by the GUI.
@ ExitViewport
Close the tooltip when leaving the viewport.
Definition window_gui.h:267
@ Transparent
Makes the background transparent if set.
Definition window_gui.h:25
@ DragDrop
Drag&drop an object.
@ None
No special mouse mode.
@ Dragging
Dragging mode (trees).
@ Sizing
Sizing mode.
@ Presize
Presizing mode (docks, tunnels).
int WidgetID
Widget ID.
Definition window_type.h:21
EventState
State of handling an event.
@ Handled
The passed event is handled.
@ NotHandled
The passed event is not handled.
@ 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
@ 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