OpenTTD Source 20260911-master-gee2b2ac12a
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 + to_underlying(halftile_corner);
925
926 Corner opposite_corner = OppositeCorner(halftile_corner);
927 if (IsSteepSlope(ti->tileh)) {
929 } else {
931 }
932 sel += to_underlying(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) {
1033 if (_viewport_highlight_station_rect->spread.Contains(t)) return TileHighlightType::Blue;
1034 }
1035
1036 if (_viewport_highlight_waypoint != nullptr) {
1038 }
1039
1040 if (_viewport_highlight_waypoint_rect != nullptr) {
1042 if (_viewport_highlight_waypoint_rect->spread.Contains(t)) return TileHighlightType::Blue;
1043 }
1044
1045 if (_viewport_highlight_town != nullptr) {
1046 if (IsTileType(t, TileType::House)) {
1047 if (GetTownIndex(t) == _viewport_highlight_town->index) {
1048 for (const Station *st : _viewport_highlight_town->stations_near) {
1049 if (st->owner != _current_company) continue;
1050 if (st->TileIsInCatchment(t)) return TileHighlightType::Blue;
1051 }
1053 }
1054 } else if (IsTileType(t, TileType::Station)) {
1055 for (const Station *st : _viewport_highlight_town->stations_near) {
1056 if (st->owner != _current_company) continue;
1057 if (GetStationIndex(t) == st->index) return TileHighlightType::White;
1058 }
1059 }
1060 }
1061
1063}
1064
1071{
1072 switch (tht) {
1073 default:
1074 case TileHighlightType::None: break;
1075 case TileHighlightType::White: DrawTileSelectionRect(ti, PAL_NONE); break;
1078 }
1079}
1080
1086{
1087 /* Going through cases in order of computational time. */
1088
1089 if (_town_local_authority_kdtree.Count() == 0) return;
1090
1091 /* Tile belongs to town regardless of distance from town. */
1092 if (GetTileType(ti->tile) == TileType::House) {
1093 if (!Town::GetByTile(ti->tile)->show_zone) return;
1094
1096 return;
1097 }
1098
1099 /* If the closest town in the highlighted list is far, we can stop searching. */
1100 TownID tid = _town_local_authority_kdtree.FindNearest(TileX(ti->tile), TileY(ti->tile));
1101 Town *closest_highlighted_town = Town::Get(tid);
1102
1103 if (DistanceManhattan(ti->tile, closest_highlighted_town->xy) >= _settings_game.economy.dist_local_authority) return;
1104
1105 /* Tile is inside of the local autrhority distance of a highlighted town,
1106 but it is possible that a non-highlighted town is even closer. */
1107 Town *closest_town = ClosestTownFromTile(ti->tile, _settings_game.economy.dist_local_authority);
1108
1109 if (closest_town->show_zone) {
1111 }
1112
1113}
1114
1119static void DrawTileSelection(const TileInfo *ti)
1120{
1121 /* Highlight tiles inside local authority of selected towns. */
1123
1124 /* Draw a red error square? */
1125 bool is_redsq = _thd.redsq == ti->tile;
1127
1129 DrawTileHighlightType(ti, tht);
1130
1131 /* No tile selection active? */
1132 if ((_thd.drawstyle & HT_DRAG_MASK) == HT_NONE) return;
1133
1134 if (_thd.diagonal) { // We're drawing a 45 degrees rotated (diagonal) rectangle
1135 if (IsInsideRotatedRectangle((int)ti->x, (int)ti->y)) goto draw_inner;
1136 return;
1137 }
1138
1139 /* Inside the inner area? */
1140 if (IsInsideBS(ti->x, _thd.pos.x, _thd.size.x) &&
1141 IsInsideBS(ti->y, _thd.pos.y, _thd.size.y)) {
1142draw_inner:
1143 if (_thd.drawstyle & HT_RECT) {
1144 if (!is_redsq) DrawTileSelectionRect(ti, _thd.make_square_red ? PALETTE_SEL_TILE_RED : PAL_NONE);
1145 } else if (_thd.drawstyle & HT_POINT) {
1146 /* Figure out the Z coordinate for the single dot. */
1147 int z = 0;
1148 FoundationPart foundation_part = FoundationPart::Normal;
1149 if (ti->tileh & SLOPE_N) {
1150 z += TILE_HEIGHT;
1152 }
1153 if (IsHalftileSlope(ti->tileh)) {
1154 Corner halftile_corner = GetHalftileSlopeCorner(ti->tileh);
1155 if ((halftile_corner == Corner::W) || (halftile_corner == Corner::E)) z += TILE_HEIGHT;
1156 if (halftile_corner != Corner::S) {
1157 foundation_part = FoundationPart::Halftile;
1158 if (IsSteepSlope(ti->tileh)) z -= TILE_HEIGHT;
1159 }
1160 }
1161 DrawSelectionSprite(SPR_DOT, PAL_NONE, ti, z, foundation_part);
1162 } else if (_thd.drawstyle & HT_RAIL) {
1163 /* autorail highlight piece under cursor */
1164 HighLightStyle type = _thd.drawstyle & HT_DIR_MASK;
1165 assert(type < HT_DIR_END);
1166 DrawAutorailSelection(ti, _autorail_type[type][0]);
1167 } else if (IsPartOfAutoLine(ti->x, ti->y)) {
1168 /* autorail highlighting long line */
1169 HighLightStyle dir = _thd.drawstyle & HT_DIR_MASK;
1170 uint side;
1171
1172 if (dir == HT_DIR_X || dir == HT_DIR_Y) {
1173 side = 0;
1174 } else {
1175 TileIndex start = TileVirtXY(_thd.selstart.x, _thd.selstart.y);
1176 side = Delta(Delta(TileX(start), TileX(ti->tile)), Delta(TileY(start), TileY(ti->tile)));
1177 }
1178
1179 DrawAutorailSelection(ti, _autorail_type[dir][side]);
1180 }
1181 return;
1182 }
1183
1184 /* Check if it's inside the outer area? */
1185 if (!is_redsq && (tht == TileHighlightType::None || tht == TileHighlightType::Red) && _thd.outersize.x > 0 &&
1186 IsInsideBS(ti->x, _thd.pos.x + _thd.offs.x, _thd.size.x + _thd.outersize.x) &&
1187 IsInsideBS(ti->y, _thd.pos.y + _thd.offs.y, _thd.size.y + _thd.outersize.y)) {
1188 /* Draw a blue rect. */
1190 return;
1191 }
1192}
1193
1200static int GetViewportY(Point tile)
1201{
1202 /* Each increment in X or Y direction moves down by half a tile, i.e. TILE_PIXELS / 2. */
1203 return (tile.y * (int)(TILE_PIXELS / 2) + tile.x * (int)(TILE_PIXELS / 2) - TilePixelHeightOutsideMap(tile.x, tile.y)) << ZOOM_BASE_SHIFT;
1204}
1205
1210{
1211 assert(_vd.dpi.top <= _vd.dpi.top + _vd.dpi.height);
1212 assert(_vd.dpi.left <= _vd.dpi.left + _vd.dpi.width);
1213
1214 Point upper_left = InverseRemapCoords(_vd.dpi.left, _vd.dpi.top);
1215 Point upper_right = InverseRemapCoords(_vd.dpi.left + _vd.dpi.width, _vd.dpi.top);
1216
1217 /* Transformations between tile coordinates and viewport rows/columns: See vp_column_row
1218 * column = y - x
1219 * row = x + y
1220 * x = (row - column) / 2
1221 * y = (row + column) / 2
1222 * Note: (row, columns) pairs are only valid, if they are both even or both odd.
1223 */
1224
1225 /* Columns overlap with neighbouring columns by a half tile.
1226 * - Left column is column of upper_left (rounded down) and one column to the left.
1227 * - Right column is column of upper_right (rounded up) and one column to the right.
1228 * Note: Integer-division does not round down for negative numbers, so ensure rounding with another increment/decrement.
1229 */
1230 int left_column = (upper_left.y - upper_left.x) / (int)TILE_SIZE - 2;
1231 int right_column = (upper_right.y - upper_right.x) / (int)TILE_SIZE + 2;
1232
1233 int potential_bridge_height = ZOOM_BASE * TILE_HEIGHT * _settings_game.construction.max_bridge_height;
1234
1235 /* Rows overlap with neighbouring rows by a half tile.
1236 * The first row that could possibly be visible is the row above upper_left (if it is at height 0).
1237 * Due to integer-division not rounding down for negative numbers, we need another decrement.
1238 */
1239 int row = (upper_left.x + upper_left.y) / (int)TILE_SIZE - 2;
1240 bool last_row = false;
1241 for (; !last_row; row++) {
1242 last_row = true;
1243 for (int column = left_column; column <= right_column; column++) {
1244 /* Valid row/column? */
1245 if ((row + column) % 2 != 0) continue;
1246
1247 Point tilecoord;
1248 tilecoord.x = (row - column) / 2;
1249 tilecoord.y = (row + column) / 2;
1250 assert(column == tilecoord.y - tilecoord.x);
1251 assert(row == tilecoord.y + tilecoord.x);
1252
1253 TileType tile_type;
1254 _cur_ti.x = tilecoord.x * TILE_SIZE;
1255 _cur_ti.y = tilecoord.y * TILE_SIZE;
1256
1257 if (IsInsideBS(tilecoord.x, 0, Map::SizeX()) && IsInsideBS(tilecoord.y, 0, Map::SizeY())) {
1258 /* This includes the south border at Map::MaxX / Map::MaxY. When terraforming we still draw tile selections there. */
1259 _cur_ti.tile = TileXY(tilecoord.x, tilecoord.y);
1260 tile_type = GetTileType(_cur_ti.tile);
1261 } else {
1262 _cur_ti.tile = INVALID_TILE;
1263 tile_type = TileType::Void;
1264 }
1265
1266 if (tile_type != TileType::Void) {
1267 /* We are inside the map => paint landscape. */
1268 std::tie(_cur_ti.tileh, _cur_ti.z) = GetTilePixelSlope(_cur_ti.tile);
1269 } else {
1270 /* We are outside the map => paint black. */
1271 std::tie(_cur_ti.tileh, _cur_ti.z) = GetTilePixelSlopeOutsideMap(tilecoord.x, tilecoord.y);
1272 }
1273
1274 int viewport_y = GetViewportY(tilecoord);
1275
1276 if (viewport_y + MAX_TILE_EXTENT_BOTTOM < _vd.dpi.top) {
1277 /* The tile in this column is not visible yet.
1278 * Tiles in other columns may be visible, but we need more rows in any case. */
1279 last_row = false;
1280 continue;
1281 }
1282
1283 int min_visible_height = viewport_y - (_vd.dpi.top + _vd.dpi.height);
1284 bool tile_visible = min_visible_height <= 0;
1285
1286 if (tile_type != TileType::Void) {
1287 /* Is tile with buildings visible? */
1288 if (min_visible_height < MAX_TILE_EXTENT_TOP) tile_visible = true;
1289
1290 if (IsBridgeAbove(_cur_ti.tile)) {
1291 /* Is the bridge visible? */
1292 TileIndex bridge_tile = GetNorthernBridgeEnd(_cur_ti.tile);
1293 int bridge_height = ZOOM_BASE * (GetBridgePixelHeight(bridge_tile) - TilePixelHeight(_cur_ti.tile));
1294 if (min_visible_height < bridge_height + MAX_TILE_EXTENT_TOP) tile_visible = true;
1295 }
1296
1297 /* Would a higher bridge on a more southern tile be visible?
1298 * If yes, we need to loop over more rows to possibly find one. */
1299 if (min_visible_height < potential_bridge_height + MAX_TILE_EXTENT_TOP) last_row = false;
1300 } else {
1301 /* Outside of map. If we are on the north border of the map, there may still be a bridge visible,
1302 * so we need to loop over more rows to possibly find one. */
1303 if ((tilecoord.x <= 0 || tilecoord.y <= 0) && min_visible_height < potential_bridge_height + MAX_TILE_EXTENT_TOP) last_row = false;
1304 }
1305
1306 if (tile_visible) {
1307 last_row = false;
1308 _vd.foundation_part = FoundationPart::None;
1309 _vd.foundation[FoundationPart::Normal] = -1;
1310 _vd.foundation[FoundationPart::Halftile] = -1;
1311 _vd.last_foundation_child[FoundationPart::Normal] = LAST_CHILD_NONE;
1312 _vd.last_foundation_child[FoundationPart::Halftile] = LAST_CHILD_NONE;
1313
1314 _tile_type_procs[tile_type]->draw_tile_proc(&_cur_ti);
1315 if (_cur_ti.tile != INVALID_TILE) DrawTileSelection(&_cur_ti);
1316 }
1317 }
1318 }
1319}
1320
1329std::string *ViewportAddString(const DrawPixelInfo *dpi, const ViewportSign *sign, ViewportStringFlags flags, Colours colour)
1330{
1331 int left = dpi->left;
1332 int top = dpi->top;
1333 int right = left + dpi->width;
1334 int bottom = top + dpi->height;
1335
1336 bool small = flags.Test(ViewportStringFlag::Small);
1337 int sign_height = ScaleByZoom(WidgetDimensions::scaled.fullbevel.top + GetCharacterHeight(small ? FontSize::Small : FontSize::Normal) + WidgetDimensions::scaled.fullbevel.bottom, dpi->zoom);
1338 int sign_half_width = ScaleByZoom((small ? sign->width_small : sign->width_normal) / 2, dpi->zoom);
1339
1340 if (bottom < sign->top ||
1341 top > sign->top + sign_height ||
1342 right < sign->center - sign_half_width ||
1343 left > sign->center + sign_half_width) {
1344 return nullptr;
1345 }
1346
1347 return &AddStringToDraw(sign->center - sign_half_width, sign->top, colour, flags, small ? sign->width_small : sign->width_normal);
1348}
1349
1350static Rect ExpandRectWithViewportSignMargins(Rect r, ZoomLevel zoom)
1351{
1353 const int max_tw = _viewport_sign_maxwidth / 2 + 1;
1354 const int expand_y = ScaleByZoom(WidgetDimensions::scaled.fullbevel.top + fh + WidgetDimensions::scaled.fullbevel.bottom, zoom);
1355 const int expand_x = ScaleByZoom(WidgetDimensions::scaled.fullbevel.left + max_tw + WidgetDimensions::scaled.fullbevel.right, zoom);
1356
1357 r.left -= expand_x;
1358 r.right += expand_x;
1359 r.top -= expand_y;
1360 r.bottom += expand_y;
1361
1362 return r;
1363}
1364
1371static void ViewportAddTownStrings(DrawPixelInfo *dpi, const std::vector<const Town *> &towns, bool small)
1372{
1373 ViewportStringFlags flags{};
1375
1376 StringID stringid_town = !small && _settings_client.gui.population_in_label ? STR_VIEWPORT_TOWN_POP : STR_TOWN_NAME;
1377 StringID stringid_town_city = stringid_town;
1378 if (!small) {
1379 stringid_town_city = _settings_client.gui.population_in_label ? STR_VIEWPORT_TOWN_CITY_POP : STR_VIEWPORT_TOWN_CITY;
1380 }
1381
1382 for (const Town *t : towns) {
1383 std::string *str = ViewportAddString(dpi, &t->cache.sign, flags, Colours::Invalid);
1384 if (str == nullptr) continue;
1385
1386 if (t->larger_town) {
1387 *str = GetString(stringid_town_city, t->index, t->cache.population);
1388 } else {
1389 *str = GetString(stringid_town, t->index, t->cache.population);
1390 }
1391 }
1392}
1393
1400static void ViewportAddSignStrings(DrawPixelInfo *dpi, const std::vector<const Sign *> &signs, bool small)
1401{
1402 ViewportStringFlags flags{};
1403 if (small) flags.Set(ViewportStringFlag::Small);
1404
1405 /* Signs placed by a game script don't have a frame. */
1406 ViewportStringFlags deity_flags{ flags };
1408
1410
1411 for (const Sign *si : signs) {
1412 /* Workaround to make sure white is actually white. The string drawing logic changes all
1413 * colours that are not Colours::Invalid slightly, turning white into a light gray. */
1414 const Colours deity_colour = si->text_colour == Colours::White ? Colours::Invalid : si->text_colour;
1415
1416 std::string *str = ViewportAddString(dpi, &si->sign, (si->owner == OWNER_DEITY) ? deity_flags : flags,
1417 (si->owner == OWNER_NONE) ? Colours::Grey : (si->owner == OWNER_DEITY ? deity_colour : _company_colours[si->owner]));
1418 if (str == nullptr) continue;
1419
1420 *str = GetString(STR_SIGN_NAME, si->index);
1421 }
1422}
1423
1430static void ViewportAddStationStrings(DrawPixelInfo *dpi, const std::vector<const BaseStation *> &stations, bool small)
1431{
1432 /* Transparent station signs have colour text instead of a colour panel. */
1434 if (small) flags.Set(ViewportStringFlag::Small);
1435
1436 for (const BaseStation *st : stations) {
1437 std::string *str = ViewportAddString(dpi, &st->sign, flags, (st->owner == OWNER_NONE || !st->IsInUse()) ? Colours::Grey : _company_colours[st->owner]);
1438 if (str == nullptr) continue;
1439
1440 if (Station::IsExpected(st)) { /* Station */
1441 *str = GetString(small ? STR_STATION_NAME : STR_VIEWPORT_STATION, st->index, st->facilities);
1442 } else { /* Waypoint */
1443 *str = GetString(STR_WAYPOINT_NAME, st->index);
1444 }
1445 }
1446}
1447
1448static void ViewportAddKdtreeSigns(DrawPixelInfo *dpi)
1449{
1450 Rect search_rect{ dpi->left, dpi->top, dpi->left + dpi->width, dpi->top + dpi->height };
1451 search_rect = ExpandRectWithViewportSignMargins(search_rect, dpi->zoom);
1452
1453 bool show_stations = _display_opt.Test(DisplayOption::ShowStationNames) && _game_mode != GameMode::Menu;
1454 bool show_waypoints = _display_opt.Test(DisplayOption::ShowWaypointNames) && _game_mode != GameMode::Menu;
1455 bool show_towns = _display_opt.Test(DisplayOption::ShowTownNames) && _game_mode != GameMode::Menu;
1457 bool show_competitors = _display_opt.Test(DisplayOption::ShowCompetitorSigns);
1458
1459 /* Collect all the items first and draw afterwards, to ensure layering */
1460 std::vector<const BaseStation *> stations;
1461 std::vector<const Town *> towns;
1462 std::vector<const Sign *> signs;
1463
1464 _viewport_sign_kdtree.FindContained(search_rect.left, search_rect.top, search_rect.right, search_rect.bottom, [&](const ViewportSignKdtreeItem & item) {
1465 switch (item.type) {
1466 case ViewportSignKdtreeItem::VKI_STATION: {
1467 if (!show_stations) break;
1468 const BaseStation *st = BaseStation::Get(std::get<StationID>(item.id));
1469
1470 /* If no facilities are present the station is a ghost station. */
1471 StationFacilities facilities = st->facilities;
1472 if (facilities.None()) facilities = STATION_FACILITY_GHOST;
1473
1474 if (!facilities.Any(_facility_display_opt)) break;
1475
1476 /* Don't draw if station is owned by another company and competitor station names are hidden. Stations owned by none are never ignored. */
1477 if (!show_competitors && _local_company != st->owner && st->owner != OWNER_NONE) break;
1478
1479 stations.push_back(st);
1480 break;
1481 }
1482
1483 case ViewportSignKdtreeItem::VKI_WAYPOINT: {
1484 if (!show_waypoints) break;
1485 const BaseStation *st = BaseStation::Get(std::get<StationID>(item.id));
1486
1487 /* Don't draw if station is owned by another company and competitor station names are hidden. Stations owned by none are never ignored. */
1488 if (!show_competitors && _local_company != st->owner && st->owner != OWNER_NONE) break;
1489
1490 stations.push_back(st);
1491 break;
1492 }
1493
1494 case ViewportSignKdtreeItem::VKI_TOWN:
1495 if (!show_towns) break;
1496 towns.push_back(Town::Get(std::get<TownID>(item.id)));
1497 break;
1498
1499 case ViewportSignKdtreeItem::VKI_SIGN: {
1500 if (!show_signs) break;
1501 const Sign *si = Sign::Get(std::get<SignID>(item.id));
1502
1503 /* Don't draw if sign is owned by another company and competitor signs should be hidden.
1504 * Note: It is intentional that also signs owned by OWNER_NONE are hidden. Bankrupt
1505 * companies can leave OWNER_NONE signs after them. */
1506 if (!show_competitors && _local_company != si->owner && si->owner != OWNER_DEITY) break;
1507
1508 signs.push_back(si);
1509 break;
1510 }
1511
1512 default:
1513 NOT_REACHED();
1514 }
1515 });
1516
1517 /* Small versions of signs are used zoom level 4X and higher. */
1518 bool small = dpi->zoom >= ZoomLevel::Out4x;
1519
1520 /* Layering order (bottom to top): Town names, signs, stations */
1521 ViewportAddTownStrings(dpi, towns, small);
1522
1523 /* Do not draw signs nor station names if they are set invisible */
1525
1526 ViewportAddSignStrings(dpi, signs, small);
1527 ViewportAddStationStrings(dpi, stations, small);
1528}
1529
1530
1538void ViewportSign::UpdatePosition(int center, int top, std::string_view str, std::string_view str_small)
1539{
1540 if (this->width_normal != 0) this->MarkDirty();
1541
1542 this->top = top;
1543
1544 this->width_normal = WidgetDimensions::scaled.fullbevel.left + Align(GetStringBoundingBox(str).width, 2) + WidgetDimensions::scaled.fullbevel.right;
1545 this->center = center;
1546
1547 /* zoomed out version */
1548 if (str_small.empty()) str_small = str;
1549 this->width_small = WidgetDimensions::scaled.fullbevel.left + Align(GetStringBoundingBox(str_small, FontSize::Small).width, 2) + WidgetDimensions::scaled.fullbevel.right;
1550
1551 this->MarkDirty();
1552}
1553
1561{
1563
1564 /* We don't know which size will be drawn, so mark the largest area dirty. */
1565 const uint half_width = std::max(this->width_normal, this->width_small) / 2 + 1;
1566 const uint height = WidgetDimensions::scaled.fullbevel.top + std::max(GetCharacterHeight(FontSize::Normal), GetCharacterHeight(FontSize::Small)) + WidgetDimensions::scaled.fullbevel.bottom + 1;
1567
1568 for (ZoomLevel zoom : EnumRange(ZoomLevel::End)) {
1569 zoomlevels[zoom].left = this->center - ScaleByZoom(half_width, zoom);
1570 zoomlevels[zoom].top = this->top - ScaleByZoom(1, zoom);
1571 zoomlevels[zoom].right = this->center + ScaleByZoom(half_width, zoom);
1572 zoomlevels[zoom].bottom = this->top + ScaleByZoom(height, zoom);
1573 }
1574
1575 for (const Window *w : Window::Iterate()) {
1576 if (w->viewport == nullptr) continue;
1577
1578 Viewport &vp = *w->viewport;
1579 if (vp.zoom <= maxzoom) {
1580 assert(vp.width != 0);
1581 Rect &zl = zoomlevels[vp.zoom];
1582 MarkViewportDirty(vp, zl.left, zl.top, zl.right, zl.bottom);
1583 }
1584 }
1585}
1586
1587static void ViewportDrawTileSprites(const TileSpriteToDrawVector *tstdv)
1588{
1589 for (const TileSpriteToDraw &ts : *tstdv) {
1590 DrawSpriteViewport(ts.image, ts.pal, ts.x, ts.y, ts.sub);
1591 }
1592}
1593
1598static void ViewportSortParentSprites(ParentSpriteToSortVector *psdv)
1599{
1600 if (psdv->size() < 2) return;
1601
1602 /* We rely on sprites being, for the most part, already ordered.
1603 * So we don't need to move many of them and can keep track of their
1604 * order efficiently by using stack. We always move sprites to the front
1605 * of the current position, i.e. to the top of the stack.
1606 * Also use special constants to indicate sorting state without
1607 * adding extra fields to ParentSpriteToDraw structure.
1608 */
1609 const uint32_t ORDER_COMPARED = UINT32_MAX; // Sprite was compared but we still need to compare the ones preceding it
1610 const uint32_t ORDER_RETURNED = UINT32_MAX - 1; // Mark sorted sprite in case there are other occurrences of it in the stack
1611 std::stack<ParentSpriteToDraw *> sprite_order;
1612 uint32_t next_order = 0;
1613
1614 std::forward_list<std::pair<int64_t, ParentSpriteToDraw *>> sprite_list; // We store sprites in a list sorted by xmin+ymin
1615
1616 /* Initialize sprite list and order. */
1617 for (auto p = psdv->rbegin(); p != psdv->rend(); p++) {
1618 sprite_list.emplace_front((*p)->xmin + (*p)->ymin, *p);
1619 sprite_order.push(*p);
1620 (*p)->order = next_order++;
1621 }
1622
1623 sprite_list.sort();
1624
1625 std::vector<ParentSpriteToDraw *> preceding; // Temporarily stores sprites that precede current and their position in the list
1626 auto preceding_prev = sprite_list.begin(); // Store iterator in case we need to delete a single preceding sprite
1627 auto out = psdv->begin(); // Iterator to output sorted sprites
1628
1629 while (!sprite_order.empty()) {
1630
1631 auto s = sprite_order.top();
1632 sprite_order.pop();
1633
1634 /* Sprite is already sorted, ignore it. */
1635 if (s->order == ORDER_RETURNED) continue;
1636
1637 /* Sprite was already compared, just need to output it. */
1638 if (s->order == ORDER_COMPARED) {
1639 *(out++) = s;
1640 s->order = ORDER_RETURNED;
1641 continue;
1642 }
1643
1644 preceding.clear();
1645
1646 /* We only need sprites with xmin <= s->xmax && ymin <= s->ymax && zmin <= s->zmax
1647 * So by iterating sprites with xmin + ymin <= s->xmax + s->ymax
1648 * we get all we need and some more that we filter out later.
1649 * We don't include zmin into the sum as there are usually more neighbours on x and y than z
1650 * so including it will actually increase the amount of false positives.
1651 * Also min coordinates can be > max so using max(xmin, xmax) + max(ymin, ymax)
1652 * to ensure that we iterate the current sprite as we need to remove it from the list.
1653 */
1654 auto ssum = std::max(s->xmax, s->xmin) + std::max(s->ymax, s->ymin);
1655 auto prev = sprite_list.before_begin();
1656 auto x = sprite_list.begin();
1657 while (x != sprite_list.end() && x->first <= ssum) {
1658 auto p = x->second;
1659 if (p == s) {
1660 /* We found the current sprite, remove it and move on. */
1661 x = sprite_list.erase_after(prev);
1662 continue;
1663 }
1664
1665 auto p_prev = prev;
1666 prev = x++;
1667
1668 if (s->xmax < p->xmin || s->ymax < p->ymin || s->zmax < p->zmin) continue;
1669 if (s->xmin <= p->xmax && // overlap in X?
1670 s->ymin <= p->ymax && // overlap in Y?
1671 s->zmin <= p->zmax) { // overlap in Z?
1672 if (s->xmin + s->xmax + s->ymin + s->ymax + s->zmin + s->zmax <=
1673 p->xmin + p->xmax + p->ymin + p->ymax + p->zmin + p->zmax) {
1674 continue;
1675 }
1676 }
1677 preceding.push_back(p);
1678 preceding_prev = p_prev;
1679 }
1680
1681 if (preceding.empty()) {
1682 /* No preceding sprites, add current one to the output */
1683 *(out++) = s;
1684 s->order = ORDER_RETURNED;
1685 continue;
1686 }
1687
1688 /* Optimization for the case when we only have 1 sprite to move. */
1689 if (preceding.size() == 1) {
1690 auto p = preceding[0];
1691 /* We can only output the preceding sprite if there can't be any other sprites preceding it. */
1692 if (p->xmax <= s->xmax && p->ymax <= s->ymax && p->zmax <= s->zmax) {
1693 p->order = ORDER_RETURNED;
1694 s->order = ORDER_RETURNED;
1695 sprite_list.erase_after(preceding_prev);
1696 *(out++) = p;
1697 *(out++) = s;
1698 continue;
1699 }
1700 }
1701
1702 /* Sort all preceding sprites by order and assign new orders in reverse (as original sorter did). */
1703 std::sort(preceding.begin(), preceding.end(), [](const ParentSpriteToDraw *a, const ParentSpriteToDraw *b) {
1704 return a->order > b->order;
1705 });
1706
1707 s->order = ORDER_COMPARED;
1708 sprite_order.push(s); // Still need to output so push it back for now
1709
1710 for (auto p: preceding) {
1711 p->order = next_order++;
1712 sprite_order.push(p);
1713 }
1714 }
1715}
1716
1717
1718static void ViewportDrawParentSprites(const ParentSpriteToSortVector *psd, const ChildScreenSpriteToDrawVector *csstdv)
1719{
1720 for (const ParentSpriteToDraw *ps : *psd) {
1721 if (ps->image != SPR_EMPTY_BOUNDING_BOX) DrawSpriteViewport(ps->image, ps->pal, ps->x, ps->y, ps->sub);
1722
1723 int child_idx = ps->first_child;
1724 while (child_idx >= 0) {
1725 const ChildScreenSpriteToDraw *cs = &(*csstdv)[child_idx];
1726 child_idx = cs->next;
1727 if (cs->relative) {
1728 DrawSpriteViewport(cs->image, cs->pal, ps->left + cs->x, ps->top + cs->y, cs->sub);
1729 } else {
1730 DrawSpriteViewport(cs->image, cs->pal, ps->x + cs->x, ps->y + cs->y, cs->sub);
1731 }
1732 }
1733 }
1734}
1735
1740static void ViewportDrawBoundingBoxes(const ParentSpriteToSortVector *psd)
1741{
1742 for (const ParentSpriteToDraw *ps : *psd) {
1743 Point pt1 = RemapCoords(ps->xmax + 1, ps->ymax + 1, ps->zmax + 1); // top front corner
1744 Point pt2 = RemapCoords(ps->xmin , ps->ymax + 1, ps->zmax + 1); // top left corner
1745 Point pt3 = RemapCoords(ps->xmax + 1, ps->ymin , ps->zmax + 1); // top right corner
1746 Point pt4 = RemapCoords(ps->xmax + 1, ps->ymax + 1, ps->zmin ); // bottom front corner
1747
1748 DrawBox( pt1.x, pt1.y,
1749 pt2.x - pt1.x, pt2.y - pt1.y,
1750 pt3.x - pt1.x, pt3.y - pt1.y,
1751 pt4.x - pt1.x, pt4.y - pt1.y);
1752 }
1753}
1754
1759{
1761 const DrawPixelInfo *dpi = _cur_dpi;
1762 void *dst;
1763 int right = UnScaleByZoom(dpi->width, dpi->zoom);
1764 int bottom = UnScaleByZoom(dpi->height, dpi->zoom);
1765
1766 PixelColour colour = _string_colourmap[_dirty_block_colour & 0xF];
1767
1768 dst = dpi->dst_ptr;
1769
1770 uint8_t bo = UnScaleByZoom(dpi->left + dpi->top, dpi->zoom) & 1;
1771 do {
1772 for (int i = (bo ^= 1); i < right; i += 2) blitter->SetPixel(dst, i, 0, colour);
1773 dst = blitter->MoveTo(dst, 0, 1);
1774 } while (--bottom > 0);
1775}
1776
1777static void ViewportDrawStrings(ZoomLevel zoom, const StringSpriteToDrawVector *sstdv)
1778{
1779 for (const StringSpriteToDraw &ss : *sstdv) {
1780 bool small = ss.flags.Test(ViewportStringFlag::Small);
1781 int w = ss.width;
1782 int x = UnScaleByZoom(ss.x, zoom);
1783 int y = UnScaleByZoom(ss.y, zoom);
1784 int h = WidgetDimensions::scaled.fullbevel.top + GetCharacterHeight(small ? FontSize::Small : FontSize::Normal) + WidgetDimensions::scaled.fullbevel.bottom;
1785
1787 if (ss.flags.Test(ViewportStringFlag::ColourRect)) {
1788 if (ss.colour != Colours::Invalid) DrawFrameRect(x, y, x + w - 1, y + h - 1, ss.colour, {});
1789 colour = TextColour::Black;
1790 } else if (ss.flags.Test(ViewportStringFlag::TransparentRect)) {
1791 DrawFrameRect(x, y, x + w - 1, y + h - 1, ss.colour, FrameFlag::Transparent);
1792 }
1793
1794 if (ss.flags.Test(ViewportStringFlag::TextColour)) {
1795 if (ss.colour != Colours::Invalid) colour = ExtendedTextColour{GetColourGradient(ss.colour, Shade::Lighter)};
1796 }
1797
1798 int left = x + WidgetDimensions::scaled.fullbevel.left;
1799 int right = x + w - 1 - WidgetDimensions::scaled.fullbevel.right;
1800 int top = y + WidgetDimensions::scaled.fullbevel.top;
1801
1802 int shadow_offset = 0;
1803 if (small && ss.flags.Test(ViewportStringFlag::Shadow)) {
1804 /* Shadow needs to be shifted 1 pixel. */
1805 shadow_offset = WidgetDimensions::scaled.fullbevel.top;
1806 DrawString(left + shadow_offset, right + shadow_offset, top, ss.string, TextColour::Black, AlignmentH::Centre, false, FontSize::Small);
1807 }
1808
1809 DrawString(left, right, top - shadow_offset, ss.string, colour, AlignmentH::Centre, false, small ? FontSize::Small : FontSize::Normal);
1810 }
1811}
1812
1813void ViewportDoDraw(const Viewport &vp, int left, int top, int right, int bottom)
1814{
1815 _vd.dpi.zoom = vp.zoom;
1816 int mask = ScaleByZoom(-1, vp.zoom);
1817
1818 _vd.combine_sprites = SpriteCombineMode::None;
1819
1820 _vd.dpi.width = (right - left) & mask;
1821 _vd.dpi.height = (bottom - top) & mask;
1822 _vd.dpi.left = left & mask;
1823 _vd.dpi.top = top & mask;
1824 _vd.dpi.pitch = _cur_dpi->pitch;
1825 _vd.last_child = LAST_CHILD_NONE;
1826
1827 int x = UnScaleByZoom(_vd.dpi.left - (vp.virtual_left & mask), vp.zoom) + vp.left;
1828 int y = UnScaleByZoom(_vd.dpi.top - (vp.virtual_top & mask), vp.zoom) + vp.top;
1829
1830 _vd.dpi.dst_ptr = BlitterFactory::GetCurrentBlitter()->MoveTo(_cur_dpi->dst_ptr, x - _cur_dpi->left, y - _cur_dpi->top);
1831 AutoRestoreBackup dpi_backup(_cur_dpi, &_vd.dpi);
1832
1834 ViewportAddVehicles(&_vd.dpi);
1835
1836 ViewportAddKdtreeSigns(&_vd.dpi);
1837
1838 DrawTextEffects(&_vd.dpi);
1839
1840 if (!_vd.tile_sprites_to_draw.empty()) ViewportDrawTileSprites(&_vd.tile_sprites_to_draw);
1841
1842 for (auto &psd : _vd.parent_sprites_to_draw) {
1843 _vd.parent_sprites_to_sort.push_back(&psd);
1844 }
1845
1846 _vp_sprite_sorter(&_vd.parent_sprites_to_sort);
1847 ViewportDrawParentSprites(&_vd.parent_sprites_to_sort, &_vd.child_screen_sprites_to_draw);
1848
1849 if (_draw_bounding_boxes) ViewportDrawBoundingBoxes(&_vd.parent_sprites_to_sort);
1850 if (_draw_dirty_blocks) ViewportDrawDirtyBlocks();
1851
1852 DrawPixelInfo dp = _vd.dpi;
1853 ZoomLevel zoom = _vd.dpi.zoom;
1854 dp.zoom = ZoomLevel::Min;
1855 dp.width = UnScaleByZoom(dp.width, zoom);
1856 dp.height = UnScaleByZoom(dp.height, zoom);
1857 AutoRestoreBackup cur_dpi(_cur_dpi, &dp);
1858
1859 if (vp.overlay != nullptr && vp.overlay->GetCargoMask().Any() && vp.overlay->GetCompanyMask().Any()) {
1860 /* translate to window coordinates */
1861 dp.left = x;
1862 dp.top = y;
1863 vp.overlay->Draw(&dp);
1864 }
1865
1866 if (!_vd.string_sprites_to_draw.empty()) {
1867 /* translate to world coordinates */
1868 dp.left = UnScaleByZoom(_vd.dpi.left, zoom);
1869 dp.top = UnScaleByZoom(_vd.dpi.top, zoom);
1870 ViewportDrawStrings(zoom, &_vd.string_sprites_to_draw);
1871 }
1872
1873 _vd.string_sprites_to_draw.clear();
1874 _vd.tile_sprites_to_draw.clear();
1875 _vd.parent_sprites_to_draw.clear();
1876 _vd.parent_sprites_to_sort.clear();
1877 _vd.child_screen_sprites_to_draw.clear();
1878}
1879
1880static inline void ViewportDraw(const Viewport &vp, int left, int top, int right, int bottom)
1881{
1882 if (right <= vp.left || bottom <= vp.top) return;
1883
1884 if (left >= vp.left + vp.width) return;
1885
1886 if (left < vp.left) left = vp.left;
1887 if (right > vp.left + vp.width) right = vp.left + vp.width;
1888
1889 if (top >= vp.top + vp.height) return;
1890
1891 if (top < vp.top) top = vp.top;
1892 if (bottom > vp.top + vp.height) bottom = vp.top + vp.height;
1893
1894 ViewportDoDraw(vp,
1895 ScaleByZoom(left - vp.left, vp.zoom) + vp.virtual_left,
1896 ScaleByZoom(top - vp.top, vp.zoom) + vp.virtual_top,
1897 ScaleByZoom(right - vp.left, vp.zoom) + vp.virtual_left,
1898 ScaleByZoom(bottom - vp.top, vp.zoom) + vp.virtual_top
1899 );
1900}
1901
1906{
1908
1909 DrawPixelInfo *dpi = _cur_dpi;
1910
1911 dpi->left += this->left;
1912 dpi->top += this->top;
1913
1914 ViewportDraw(*this->viewport, dpi->left, dpi->top, dpi->left + dpi->width, dpi->top + dpi->height);
1915
1916 dpi->left -= this->left;
1917 dpi->top -= this->top;
1918}
1919
1930static inline void ClampViewportToMap(const Viewport &vp, int *scroll_x, int *scroll_y)
1931{
1932 /* Centre of the viewport is hot spot. */
1933 Point pt = {
1934 *scroll_x + vp.virtual_width / 2,
1935 *scroll_y + vp.virtual_height / 2
1936 };
1937
1938 /* Find nearest tile that is within borders of the map. */
1939 bool clamped;
1940 pt = InverseRemapCoords2(pt.x, pt.y, true, &clamped);
1941
1942 if (clamped) {
1943 /* Convert back to viewport coordinates and remove centering. */
1944 pt = RemapCoords2(pt.x, pt.y);
1945 *scroll_x = pt.x - vp.virtual_width / 2;
1946 *scroll_y = pt.y - vp.virtual_height / 2;
1947 }
1948}
1949
1962static void ClampSmoothScroll(uint32_t delta_ms, int64_t delta_hi, int64_t delta_lo, int &delta_hi_clamped, int &delta_lo_clamped)
1963{
1965 constexpr int PIXELS_PER_TILE = TILE_PIXELS * 2 * ZOOM_BASE;
1966
1967 assert(delta_hi != 0);
1968
1969 /* Move at most 75% of the distance every 30ms, for a smooth experience */
1970 int64_t delta_left = delta_hi * std::pow(0.75, delta_ms / 30.0);
1971 /* Move never more than 16 tiles per 30ms. */
1972 int max_scroll = Map::ScaleBySize1D(16 * PIXELS_PER_TILE * delta_ms / 30);
1973
1974 /* We never go over the max_scroll speed. */
1975 delta_hi_clamped = Clamp(delta_hi - delta_left, -max_scroll, max_scroll);
1976 /* The lower delta is in ratio of the higher delta, so we keep going straight at the destination. */
1977 delta_lo_clamped = delta_lo * delta_hi_clamped / delta_hi;
1978
1979 /* Ensure we always move (delta_hi can't be zero). */
1980 if (delta_hi_clamped == 0) {
1981 delta_hi_clamped = delta_hi > 0 ? 1 : -1;
1982 }
1983}
1984
1990void UpdateViewportPosition(Window *w, uint32_t delta_ms)
1991{
1992 ViewportData &vp = *w->viewport;
1993
1994 if (vp.follow_vehicle != VehicleID::Invalid()) {
1995 const Vehicle *veh = Vehicle::Get(vp.follow_vehicle)->GetMovingFront();
1996 Point pt = MapXYZToViewport(vp, veh->x_pos, veh->y_pos, veh->z_pos);
1997
1998 vp.scrollpos_x = pt.x;
1999 vp.scrollpos_y = pt.y;
2000 SetViewportPosition(w, pt.x, pt.y);
2001 } else {
2002 /* Ensure the destination location is within the map */
2004
2005 int delta_x = vp.dest_scrollpos_x - vp.scrollpos_x;
2006 int delta_y = vp.dest_scrollpos_y - vp.scrollpos_y;
2007
2008 int current_x = vp.scrollpos_x;
2009 int current_y = vp.scrollpos_y;
2010
2011 bool update_overlay = false;
2012 if (delta_x != 0 || delta_y != 0) {
2013 if (_settings_client.gui.smooth_scroll) {
2014 int delta_x_clamped;
2015 int delta_y_clamped;
2016
2017 if (abs(delta_x) > abs(delta_y)) {
2018 ClampSmoothScroll(delta_ms, delta_x, delta_y, delta_x_clamped, delta_y_clamped);
2019 } else {
2020 ClampSmoothScroll(delta_ms, delta_y, delta_x, delta_y_clamped, delta_x_clamped);
2021 }
2022
2023 vp.scrollpos_x += delta_x_clamped;
2024 vp.scrollpos_y += delta_y_clamped;
2025 } else {
2028 }
2029 update_overlay = (vp.scrollpos_x == vp.dest_scrollpos_x &&
2030 vp.scrollpos_y == vp.dest_scrollpos_y);
2031 }
2032
2034
2035 /* When moving small amounts around the border we can get stuck, and
2036 * not actually move. In those cases, teleport to the destination. */
2037 if ((delta_x != 0 || delta_y != 0) && current_x == vp.scrollpos_x && current_y == vp.scrollpos_y) {
2040 }
2041
2042 SetViewportPosition(w, vp.scrollpos_x, vp.scrollpos_y);
2043 if (update_overlay) RebuildViewportOverlay(w);
2044 }
2045}
2046
2057static bool MarkViewportDirty(const Viewport &vp, int left, int top, int right, int bottom)
2058{
2059 /* Rounding wrt. zoom-out level */
2060 right += (1 << to_underlying(vp.zoom)) - 1;
2061 bottom += (1 << to_underlying(vp.zoom)) - 1;
2062
2063 right -= vp.virtual_left;
2064 if (right <= 0) return false;
2065
2066 bottom -= vp.virtual_top;
2067 if (bottom <= 0) return false;
2068
2069 left = std::max(0, left - vp.virtual_left);
2070
2071 if (left >= vp.virtual_width) return false;
2072
2073 top = std::max(0, top - vp.virtual_top);
2074
2075 if (top >= vp.virtual_height) return false;
2076
2078 UnScaleByZoomLower(left, vp.zoom) + vp.left,
2079 UnScaleByZoomLower(top, vp.zoom) + vp.top,
2080 UnScaleByZoom(right, vp.zoom) + vp.left + 1,
2081 UnScaleByZoom(bottom, vp.zoom) + vp.top + 1
2082 );
2083
2084 return true;
2085}
2086
2096bool MarkAllViewportsDirty(int left, int top, int right, int bottom)
2097{
2098 bool dirty = false;
2099
2100 for (const Window *w : Window::Iterate()) {
2101 if (w->viewport != nullptr) {
2102 assert(w->viewport->width != 0);
2103 if (MarkViewportDirty(*w->viewport, left, top, right, bottom)) dirty = true;
2104 }
2105 }
2106
2107 return dirty;
2108}
2109
2110void ConstrainAllViewportsZoom()
2111{
2112 for (Window *w : Window::Iterate()) {
2113 if (w->viewport == nullptr) continue;
2114
2115 ZoomLevel zoom = Clamp(w->viewport->zoom, _settings_client.gui.zoom_min, _settings_client.gui.zoom_max);
2116 if (zoom != w->viewport->zoom) {
2117 while (w->viewport->zoom < zoom) DoZoomInOutWindow(ZOOM_OUT, w);
2118 while (w->viewport->zoom > zoom) DoZoomInOutWindow(ZOOM_IN, w);
2119 }
2120 }
2121}
2122
2130void MarkTileDirtyByTile(TileIndex tile, int bridge_level_offset, int tile_height_override)
2131{
2132 Point pt = RemapCoords(TileX(tile) * TILE_SIZE, TileY(tile) * TILE_SIZE, tile_height_override * TILE_HEIGHT);
2135 pt.y - MAX_TILE_EXTENT_TOP - ZOOM_BASE * TILE_HEIGHT * bridge_level_offset,
2138}
2139
2148{
2149 int x_size = _thd.size.x;
2150 int y_size = _thd.size.y;
2151
2152 if (!_thd.diagonal) { // Selecting in a straight rectangle (or a single square)
2153 int x_start = _thd.pos.x;
2154 int y_start = _thd.pos.y;
2155
2156 if (_thd.outersize.x != 0) {
2157 x_size += _thd.outersize.x;
2158 x_start += _thd.offs.x;
2159 y_size += _thd.outersize.y;
2160 y_start += _thd.offs.y;
2161 }
2162
2163 x_size -= TILE_SIZE;
2164 y_size -= TILE_SIZE;
2165
2166 assert(x_size >= 0);
2167 assert(y_size >= 0);
2168
2169 int x_end = Clamp(x_start + x_size, 0, Map::SizeX() * TILE_SIZE - TILE_SIZE);
2170 int y_end = Clamp(y_start + y_size, 0, Map::SizeY() * TILE_SIZE - TILE_SIZE);
2171
2172 x_start = Clamp(x_start, 0, Map::SizeX() * TILE_SIZE - TILE_SIZE);
2173 y_start = Clamp(y_start, 0, Map::SizeY() * TILE_SIZE - TILE_SIZE);
2174
2175 /* make sure everything is multiple of TILE_SIZE */
2176 assert((x_end | y_end | x_start | y_start) % TILE_SIZE == 0);
2177
2178 /* How it works:
2179 * Suppose we have to mark dirty rectangle of 3x4 tiles:
2180 * x
2181 * xxx
2182 * xxxxx
2183 * xxxxx
2184 * xxx
2185 * x
2186 * This algorithm marks dirty columns of tiles, so it is done in 3+4-1 steps:
2187 * 1) x 2) x
2188 * xxx Oxx
2189 * Oxxxx xOxxx
2190 * xxxxx Oxxxx
2191 * xxx xxx
2192 * x x
2193 * And so forth...
2194 */
2195
2196 int top_x = x_end; // coordinates of top dirty tile
2197 int top_y = y_start;
2198 int bot_x = top_x; // coordinates of bottom dirty tile
2199 int bot_y = top_y;
2200
2201 do {
2202 /* topmost dirty point */
2203 TileIndex top_tile = TileVirtXY(top_x, top_y);
2204 Point top = RemapCoords(top_x, top_y, GetTileMaxPixelZ(top_tile));
2205
2206 /* bottommost point */
2207 TileIndex bottom_tile = TileVirtXY(bot_x, bot_y);
2208 Point bot = RemapCoords(bot_x + TILE_SIZE, bot_y + TILE_SIZE, GetTilePixelZ(bottom_tile)); // bottommost point
2209
2210 /* the 'x' coordinate of 'top' and 'bot' is the same (and always in the same distance from tile middle),
2211 * tile height/slope affects only the 'y' on-screen coordinate! */
2212
2213 int l = top.x - TILE_PIXELS * ZOOM_BASE; // 'x' coordinate of left side of the dirty rectangle
2214 int t = top.y; // 'y' coordinate of top side of the dirty rectangle
2215 int r = top.x + TILE_PIXELS * ZOOM_BASE; // 'x' coordinate of right side of the dirty rectangle
2216 int b = bot.y; // 'y' coordinate of bottom side of the dirty rectangle
2217
2218 static const int OVERLAY_WIDTH = 4 * ZOOM_BASE; // part of selection sprites is drawn outside the selected area (in particular: terraforming)
2219
2220 /* For halftile foundations on SLOPE_STEEP_S the sprite extents some more towards the top */
2221 MarkAllViewportsDirty(l - OVERLAY_WIDTH, t - OVERLAY_WIDTH - TILE_HEIGHT * ZOOM_BASE, r + OVERLAY_WIDTH, b + OVERLAY_WIDTH);
2222
2223 /* haven't we reached the topmost tile yet? */
2224 if (top_x != x_start) {
2225 top_x -= TILE_SIZE;
2226 } else {
2227 top_y += TILE_SIZE;
2228 }
2229
2230 /* the way the bottom tile changes is different when we reach the bottommost tile */
2231 if (bot_y != y_end) {
2232 bot_y += TILE_SIZE;
2233 } else {
2234 bot_x -= TILE_SIZE;
2235 }
2236 } while (bot_x >= top_x);
2237 } else { // Selecting in a 45 degrees rotated (diagonal) rectangle.
2238 /* a_size, b_size describe a rectangle with rotated coordinates */
2239 int a_size = x_size + y_size, b_size = x_size - y_size;
2240
2241 int interval_a = a_size < 0 ? -(int)TILE_SIZE : (int)TILE_SIZE;
2242 int interval_b = b_size < 0 ? -(int)TILE_SIZE : (int)TILE_SIZE;
2243
2244 for (int a = -interval_a; a != a_size + interval_a; a += interval_a) {
2245 for (int b = -interval_b; b != b_size + interval_b; b += interval_b) {
2246 uint x = (_thd.pos.x + (a + b) / 2) / TILE_SIZE;
2247 uint y = (_thd.pos.y + (a - b) / 2) / TILE_SIZE;
2248
2249 if (x < Map::MaxX() && y < Map::MaxY()) {
2251 }
2252 }
2253 }
2254 }
2255}
2256
2257
2258void SetSelectionRed(bool b)
2259{
2260 _thd.make_square_red = b;
2262}
2263
2272static bool CheckClickOnViewportSign(const Viewport &vp, int x, int y, const ViewportSign *sign)
2273{
2274 bool small = (vp.zoom >= ZoomLevel::Out4x);
2275 int sign_half_width = ScaleByZoom((small ? sign->width_small : sign->width_normal) / 2, vp.zoom);
2276 int sign_height = ScaleByZoom(WidgetDimensions::scaled.fullbevel.top + GetCharacterHeight(small ? FontSize::Small : FontSize::Normal) + WidgetDimensions::scaled.fullbevel.bottom, vp.zoom);
2277
2278 return y >= sign->top && y < sign->top + sign_height &&
2279 x >= sign->center - sign_half_width && x < sign->center + sign_half_width;
2280}
2281
2282
2290static bool CheckClickOnViewportSign(const Viewport &vp, int x, int y)
2291{
2292 if (_game_mode == GameMode::Menu) return false;
2293
2294 x = ScaleByZoom(x - vp.left, vp.zoom) + vp.virtual_left;
2295 y = ScaleByZoom(y - vp.top, vp.zoom) + vp.virtual_top;
2296
2297 Rect search_rect{ x - 1, y - 1, x + 1, y + 1 };
2298 search_rect = ExpandRectWithViewportSignMargins(search_rect, vp.zoom);
2299
2302 bool show_towns = _display_opt.Test(DisplayOption::ShowTownNames);
2304 bool show_competitors = _display_opt.Test(DisplayOption::ShowCompetitorSigns);
2305
2306 /* Topmost of each type that was hit */
2307 BaseStation *st = nullptr, *last_st = nullptr;
2308 Town *t = nullptr, *last_t = nullptr;
2309 Sign *si = nullptr, *last_si = nullptr;
2310
2311 /* See ViewportAddKdtreeSigns() for details on the search logic */
2312 _viewport_sign_kdtree.FindContained(search_rect.left, search_rect.top, search_rect.right, search_rect.bottom, [&](const ViewportSignKdtreeItem & item) {
2313 switch (item.type) {
2314 case ViewportSignKdtreeItem::VKI_STATION: {
2315 if (!show_stations) break;
2316 st = BaseStation::Get(std::get<StationID>(item.id));
2317 if (!show_competitors && _local_company != st->owner && st->owner != OWNER_NONE) break;
2318
2319 StationFacilities facilities = st->facilities;
2320 if (facilities.None()) facilities = STATION_FACILITY_GHOST;
2321 if (!facilities.Any(_facility_display_opt)) break;
2322
2323 if (CheckClickOnViewportSign(vp, x, y, &st->sign)) last_st = st;
2324 break;
2325 }
2326
2327 case ViewportSignKdtreeItem::VKI_WAYPOINT:
2328 if (!show_waypoints) break;
2329 st = BaseStation::Get(std::get<StationID>(item.id));
2330 if (!show_competitors && _local_company != st->owner && st->owner != OWNER_NONE) break;
2331 if (CheckClickOnViewportSign(vp, x, y, &st->sign)) last_st = st;
2332 break;
2333
2334 case ViewportSignKdtreeItem::VKI_TOWN:
2335 if (!show_towns) break;
2336 t = Town::Get(std::get<TownID>(item.id));
2337 if (CheckClickOnViewportSign(vp, x, y, &t->cache.sign)) last_t = t;
2338 break;
2339
2340 case ViewportSignKdtreeItem::VKI_SIGN:
2341 if (!show_signs) break;
2342 si = Sign::Get(std::get<SignID>(item.id));
2343 if (!show_competitors && _local_company != si->owner && si->owner != OWNER_DEITY) break;
2344 if (CheckClickOnViewportSign(vp, x, y, &si->sign)) last_si = si;
2345 break;
2346
2347 default:
2348 NOT_REACHED();
2349 }
2350 });
2351
2352 /* Select which hit to handle based on priority */
2353 if (last_st != nullptr) {
2354 if (Station::IsExpected(last_st)) {
2355 ShowStationViewWindow(last_st->index);
2356 } else {
2358 }
2359 return true;
2360 } else if (last_t != nullptr) {
2361 ShowTownViewWindow(last_t->index);
2362 return true;
2363 } else if (last_si != nullptr) {
2364 HandleClickOnSign(last_si);
2365 return true;
2366 } else {
2367 return false;
2368 }
2369}
2370
2371
2372ViewportSignKdtreeItem ViewportSignKdtreeItem::MakeStation(StationID id)
2373{
2374 ViewportSignKdtreeItem item;
2375 item.type = VKI_STATION;
2376 item.id = id;
2377
2378 const Station *st = Station::Get(id);
2379 assert(st->sign.kdtree_valid);
2380 item.center = st->sign.center;
2381 item.top = st->sign.top;
2382
2383 /* Assume the sign can be a candidate for drawing, so measure its width */
2384 _viewport_sign_maxwidth = std::max<int>({_viewport_sign_maxwidth, st->sign.width_normal, st->sign.width_small});
2385
2386 return item;
2387}
2388
2389ViewportSignKdtreeItem ViewportSignKdtreeItem::MakeWaypoint(StationID id)
2390{
2391 ViewportSignKdtreeItem item;
2392 item.type = VKI_WAYPOINT;
2393 item.id = id;
2394
2395 const Waypoint *st = Waypoint::Get(id);
2396 assert(st->sign.kdtree_valid);
2397 item.center = st->sign.center;
2398 item.top = st->sign.top;
2399
2400 /* Assume the sign can be a candidate for drawing, so measure its width */
2401 _viewport_sign_maxwidth = std::max<int>({_viewport_sign_maxwidth, st->sign.width_normal, st->sign.width_small});
2402
2403 return item;
2404}
2405
2406ViewportSignKdtreeItem ViewportSignKdtreeItem::MakeTown(TownID id)
2407{
2408 ViewportSignKdtreeItem item;
2409 item.type = VKI_TOWN;
2410 item.id = id;
2411
2412 const Town *town = Town::Get(id);
2413 assert(town->cache.sign.kdtree_valid);
2414 item.center = town->cache.sign.center;
2415 item.top = town->cache.sign.top;
2416
2417 /* Assume the sign can be a candidate for drawing, so measure its width */
2418 _viewport_sign_maxwidth = std::max<int>({_viewport_sign_maxwidth, town->cache.sign.width_normal, town->cache.sign.width_small});
2419
2420 return item;
2421}
2422
2423ViewportSignKdtreeItem ViewportSignKdtreeItem::MakeSign(SignID id)
2424{
2425 ViewportSignKdtreeItem item;
2426 item.type = VKI_SIGN;
2427 item.id = id;
2428
2429 const Sign *sign = Sign::Get(id);
2430 assert(sign->sign.kdtree_valid);
2431 item.center = sign->sign.center;
2432 item.top = sign->sign.top;
2433
2434 /* Assume the sign can be a candidate for drawing, so measure its width */
2435 _viewport_sign_maxwidth = std::max<int>({_viewport_sign_maxwidth, sign->sign.width_normal, sign->sign.width_small});
2436
2437 return item;
2438}
2439
2440void RebuildViewportKdtree()
2441{
2442 /* Reset biggest size sign seen */
2443 _viewport_sign_maxwidth = 0;
2444
2445 std::vector<ViewportSignKdtreeItem> items;
2447
2448 for (const Station *st : Station::Iterate()) {
2449 if (st->sign.kdtree_valid) items.push_back(ViewportSignKdtreeItem::MakeStation(st->index));
2450 }
2451
2452 for (const Waypoint *wp : Waypoint::Iterate()) {
2453 if (wp->sign.kdtree_valid) items.push_back(ViewportSignKdtreeItem::MakeWaypoint(wp->index));
2454 }
2455
2456 for (const Town *town : Town::Iterate()) {
2457 if (town->cache.sign.kdtree_valid) items.push_back(ViewportSignKdtreeItem::MakeTown(town->index));
2458 }
2459
2460 for (const Sign *sign : Sign::Iterate()) {
2461 if (sign->sign.kdtree_valid) items.push_back(ViewportSignKdtreeItem::MakeSign(sign->index));
2462 }
2463
2464 _viewport_sign_kdtree.Build(items.begin(), items.end());
2465}
2466
2467
2468static bool CheckClickOnLandscape(const Viewport &vp, int x, int y)
2469{
2470 Point pt = TranslateXYToTileCoord(vp, x, y);
2471
2472 if (pt.x != -1) return ClickTile(TileVirtXY(pt.x, pt.y));
2473 return true;
2474}
2475
2476static void PlaceObject()
2477{
2478 Point pt;
2479 Window *w;
2480
2481 pt = GetTileBelowCursor();
2482 if (pt.x == -1) return;
2483
2484 if ((_thd.place_mode & HT_DRAG_MASK) == HT_POINT) {
2485 pt.x += TILE_SIZE / 2;
2486 pt.y += TILE_SIZE / 2;
2487 }
2488
2489 _tile_fract_coords.x = pt.x & TILE_UNIT_MASK;
2490 _tile_fract_coords.y = pt.y & TILE_UNIT_MASK;
2491
2492 w = _thd.GetCallbackWnd();
2493 if (w != nullptr) w->OnPlaceObject(pt, TileVirtXY(pt.x, pt.y));
2494}
2495
2496
2497bool HandleViewportClicked(const Viewport &vp, int x, int y)
2498{
2499 const Vehicle *v = CheckClickOnVehicle(vp, x, y);
2500
2501 if (_thd.place_mode & HT_VEHICLE) {
2502 if (v != nullptr && VehicleClicked(v)) return true;
2503 }
2504
2505 /* Vehicle placement mode already handled above. */
2506 if ((_thd.place_mode & HT_DRAG_MASK) != HT_NONE) {
2507 PlaceObject();
2508 return true;
2509 }
2510
2511 if (CheckClickOnViewportSign(vp, x, y)) return true;
2512 bool result = CheckClickOnLandscape(vp, x, y);
2513
2514 if (v != nullptr) {
2515 Debug(Facility::Misc, Severity::Warning, "Vehicle {} (index {}) at {}", v->unitnumber, v->index, fmt::ptr(v));
2517 v = v->First();
2518 if (_ctrl_pressed && v->owner == _local_company) {
2519 StartStopVehicle(v, true);
2520 } else {
2522 }
2523 }
2524 return true;
2525 }
2526 return result;
2527}
2528
2529void RebuildViewportOverlay(Window *w)
2530{
2531 if (w->viewport->overlay != nullptr &&
2532 w->viewport->overlay->GetCompanyMask().Any() &&
2533 w->viewport->overlay->GetCargoMask().Any()) {
2534 w->viewport->overlay->SetDirty();
2535 w->SetDirty();
2536 }
2537}
2538
2548bool ScrollWindowTo(int x, int y, int z, Window *w, bool instant)
2549{
2550 /* The slope cannot be acquired outside of the map, so make sure we are always within the map. */
2551 if (z == -1) {
2552 if ( x >= 0 && x <= (int)Map::SizeX() * (int)TILE_SIZE - 1
2553 && y >= 0 && y <= (int)Map::SizeY() * (int)TILE_SIZE - 1) {
2554 z = GetSlopePixelZ(x, y);
2555 } else {
2556 z = TileHeightOutsideMap(x / (int)TILE_SIZE, y / (int)TILE_SIZE);
2557 }
2558 }
2559
2560 Point pt = MapXYZToViewport(*w->viewport, x, y, z);
2561 w->viewport->CancelFollow(*w);
2562
2563 if (w->viewport->dest_scrollpos_x == pt.x && w->viewport->dest_scrollpos_y == pt.y) return false;
2564
2565 if (instant) {
2566 w->viewport->scrollpos_x = pt.x;
2567 w->viewport->scrollpos_y = pt.y;
2568 RebuildViewportOverlay(w);
2569 }
2570
2571 w->viewport->dest_scrollpos_x = pt.x;
2572 w->viewport->dest_scrollpos_y = pt.y;
2573 return true;
2574}
2575
2583bool ScrollWindowToTile(TileIndex tile, Window *w, bool instant)
2584{
2585 return ScrollWindowTo(TileX(tile) * TILE_SIZE, TileY(tile) * TILE_SIZE, -1, w, instant);
2586}
2587
2594bool ScrollMainWindowToTile(TileIndex tile, bool instant)
2595{
2596 return ScrollMainWindowTo(TileX(tile) * TILE_SIZE + TILE_SIZE / 2, TileY(tile) * TILE_SIZE + TILE_SIZE / 2, -1, instant);
2597}
2598
2604{
2605 TileIndex old;
2606
2607 old = _thd.redsq;
2608 _thd.redsq = tile;
2609
2610 if (tile != old) {
2611 if (tile != INVALID_TILE) MarkTileDirtyByTile(tile);
2612 if (old != INVALID_TILE) MarkTileDirtyByTile(old);
2613 }
2614}
2615
2621void SetTileSelectSize(int w, int h)
2622{
2623 _thd.new_size.x = w * TILE_SIZE;
2624 _thd.new_size.y = h * TILE_SIZE;
2625 _thd.new_outersize.x = 0;
2626 _thd.new_outersize.y = 0;
2627}
2628
2629void SetTileSelectBigSize(int ox, int oy, int sx, int sy)
2630{
2631 _thd.offs.x = ox * TILE_SIZE;
2632 _thd.offs.y = oy * TILE_SIZE;
2633 _thd.new_outersize.x = sx * TILE_SIZE;
2634 _thd.new_outersize.y = sy * TILE_SIZE;
2635}
2636
2643static HighLightStyle GetAutorailHT(int x, int y)
2644{
2646}
2647
2652{
2653 this->pos.x = 0;
2654 this->pos.y = 0;
2655 this->new_pos.x = 0;
2656 this->new_pos.y = 0;
2657}
2658
2667
2676
2677
2678
2687{
2688 int x1;
2689 int y1;
2690
2691 if (_thd.freeze) return;
2692
2693 HighLightStyle new_drawstyle = HT_NONE;
2694 bool new_diagonal = false;
2695
2696 if ((_thd.place_mode & HT_DRAG_MASK) == HT_SPECIAL) {
2697 x1 = _thd.selend.x;
2698 y1 = _thd.selend.y;
2699 if (x1 != -1) {
2700 int x2 = _thd.selstart.x & ~TILE_UNIT_MASK;
2701 int y2 = _thd.selstart.y & ~TILE_UNIT_MASK;
2702 x1 &= ~TILE_UNIT_MASK;
2703 y1 &= ~TILE_UNIT_MASK;
2704
2705 if (_thd.IsDraggingDiagonal()) {
2706 new_diagonal = true;
2707 } else {
2708 if (x1 >= x2) std::swap(x1, x2);
2709 if (y1 >= y2) std::swap(y1, y2);
2710 }
2711 _thd.new_pos.x = x1;
2712 _thd.new_pos.y = y1;
2713 _thd.new_size.x = x2 - x1;
2714 _thd.new_size.y = y2 - y1;
2715 if (!new_diagonal) {
2716 _thd.new_size.x += TILE_SIZE;
2717 _thd.new_size.y += TILE_SIZE;
2718 }
2719 new_drawstyle = _thd.next_drawstyle;
2720 }
2721 } else if ((_thd.place_mode & HT_DRAG_MASK) != HT_NONE) {
2722 Point pt = GetTileBelowCursor();
2723 x1 = pt.x;
2724 y1 = pt.y;
2725 if (x1 != -1) {
2726 switch (_thd.place_mode & HT_DRAG_MASK) {
2727 case HT_RECT:
2728 new_drawstyle = HT_RECT;
2729 break;
2730 case HT_POINT:
2731 new_drawstyle = HT_POINT;
2732 x1 += TILE_SIZE / 2;
2733 y1 += TILE_SIZE / 2;
2734 break;
2735 case HT_RAIL:
2736 /* Draw one highlighted tile in any direction */
2737 new_drawstyle = GetAutorailHT(pt.x, pt.y);
2738 break;
2739 case HT_LINE:
2740 switch (_thd.place_mode & HT_DIR_MASK) {
2741 case HT_DIR_X: new_drawstyle = HT_LINE | HT_DIR_X; break;
2742 case HT_DIR_Y: new_drawstyle = HT_LINE | HT_DIR_Y; break;
2743
2744 case HT_DIR_HU:
2745 case HT_DIR_HL:
2746 new_drawstyle = (pt.x & TILE_UNIT_MASK) + (pt.y & TILE_UNIT_MASK) <= TILE_SIZE ? HT_LINE | HT_DIR_HU : HT_LINE | HT_DIR_HL;
2747 break;
2748
2749 case HT_DIR_VL:
2750 case HT_DIR_VR:
2751 new_drawstyle = (pt.x & TILE_UNIT_MASK) > (pt.y & TILE_UNIT_MASK) ? HT_LINE | HT_DIR_VL : HT_LINE | HT_DIR_VR;
2752 break;
2753
2754 default: NOT_REACHED();
2755 }
2756 _thd.selstart.x = x1 & ~TILE_UNIT_MASK;
2757 _thd.selstart.y = y1 & ~TILE_UNIT_MASK;
2758 break;
2759 default:
2760 NOT_REACHED();
2761 }
2762 _thd.new_pos.x = x1 & ~TILE_UNIT_MASK;
2763 _thd.new_pos.y = y1 & ~TILE_UNIT_MASK;
2764 }
2765 }
2766
2767 /* redraw selection */
2768 if (_thd.drawstyle != new_drawstyle ||
2769 _thd.pos.x != _thd.new_pos.x || _thd.pos.y != _thd.new_pos.y ||
2770 _thd.size.x != _thd.new_size.x || _thd.size.y != _thd.new_size.y ||
2771 _thd.outersize.x != _thd.new_outersize.x ||
2772 _thd.outersize.y != _thd.new_outersize.y ||
2773 _thd.diagonal != new_diagonal) {
2774 /* Clear the old tile selection? */
2775 if ((_thd.drawstyle & HT_DRAG_MASK) != HT_NONE) SetSelectionTilesDirty();
2776
2777 _thd.drawstyle = new_drawstyle;
2778 _thd.pos = _thd.new_pos;
2779 _thd.size = _thd.new_size;
2780 _thd.outersize = _thd.new_outersize;
2781 _thd.diagonal = new_diagonal;
2782 _thd.dirty = 0xff;
2783
2784 /* Draw the new tile selection? */
2785 if ((new_drawstyle & HT_DRAG_MASK) != HT_NONE) SetSelectionTilesDirty();
2786 }
2787}
2788
2793static inline void ShowMeasurementTooltips(EncodedString &&text)
2794{
2795 if (!_settings_client.gui.measure_tooltip) return;
2796 GuiShowTooltips(_thd.GetCallbackWnd(), std::move(text), TooltipCloseCondition::ExitViewport);
2797}
2798
2799static void HideMeasurementTooltips()
2800{
2801 CloseWindowById(WindowClass::ToolTips, 0);
2802}
2803
2811{
2812 _thd.select_method = method;
2813 _thd.select_proc = process;
2814 _thd.selend.x = TileX(tile) * TILE_SIZE;
2815 _thd.selstart.x = TileX(tile) * TILE_SIZE;
2816 _thd.selend.y = TileY(tile) * TILE_SIZE;
2817 _thd.selstart.y = TileY(tile) * TILE_SIZE;
2818
2819 /* Needed so several things (road, autoroad, bridges, ...) are placed correctly.
2820 * In effect, placement starts from the centre of a tile
2821 */
2822 if (method == VPM_X_OR_Y || method == VPM_FIX_X || method == VPM_FIX_Y) {
2823 _thd.selend.x += TILE_SIZE / 2;
2824 _thd.selend.y += TILE_SIZE / 2;
2825 _thd.selstart.x += TILE_SIZE / 2;
2826 _thd.selstart.y += TILE_SIZE / 2;
2827 }
2828
2829 HighLightStyle others = _thd.place_mode & ~(HT_DRAG_MASK | HT_DIR_MASK);
2830 if ((_thd.place_mode & HT_DRAG_MASK) == HT_RECT) {
2831 _thd.place_mode = HT_SPECIAL | others;
2832 _thd.next_drawstyle = HT_RECT | others;
2833 } else if (_thd.place_mode & (HT_RAIL | HT_LINE)) {
2834 _thd.place_mode = HT_SPECIAL | others;
2835 _thd.next_drawstyle = _thd.drawstyle | others;
2836 } else {
2837 _thd.place_mode = HT_SPECIAL | others;
2838 _thd.next_drawstyle = HT_POINT | others;
2839 }
2841}
2842
2848{
2849 _thd.select_method = VPM_X_AND_Y;
2850 _thd.select_proc = process;
2851 _thd.selstart.x = 0;
2852 _thd.selstart.y = 0;
2853 _thd.next_drawstyle = HT_RECT;
2854
2856}
2857
2858void VpSetPlaceSizingLimit(int limit)
2859{
2860 _thd.sizelimit = limit;
2861}
2862
2869{
2870 uint64_t distance = DistanceManhattan(from, to) + 1;
2871
2872 _thd.selend.x = TileX(to) * TILE_SIZE;
2873 _thd.selend.y = TileY(to) * TILE_SIZE;
2874 _thd.selstart.x = TileX(from) * TILE_SIZE;
2875 _thd.selstart.y = TileY(from) * TILE_SIZE;
2876 _thd.next_drawstyle = HT_RECT;
2877
2878 /* show measurement only if there is any length to speak of */
2879 if (distance > 1) {
2880 ShowMeasurementTooltips(GetEncodedString(STR_MEASURE_LENGTH, distance));
2881 } else {
2882 HideMeasurementTooltips();
2883 }
2884}
2885
2886static void VpStartPreSizing()
2887{
2888 _thd.selend.x = -1;
2890}
2891
2900{
2901 int fxpy = _tile_fract_coords.x + _tile_fract_coords.y;
2902 int sxpy = (_thd.selend.x & TILE_UNIT_MASK) + (_thd.selend.y & TILE_UNIT_MASK);
2903 int fxmy = _tile_fract_coords.x - _tile_fract_coords.y;
2904 int sxmy = (_thd.selend.x & TILE_UNIT_MASK) - (_thd.selend.y & TILE_UNIT_MASK);
2905
2906 switch (direction) {
2907 default: NOT_REACHED();
2908 case DiagDirection::SE: // end piece is lower right
2909 if (fxpy >= 20 && sxpy <= 12) return HT_DIR_HL;
2910 if (fxmy < -3 && sxmy > 3) return HT_DIR_VR;
2911 return HT_DIR_Y;
2912
2913 case DiagDirection::NW:
2914 if (fxmy > 3 && sxmy < -3) return HT_DIR_VL;
2915 if (fxpy <= 12 && sxpy >= 20) return HT_DIR_HU;
2916 return HT_DIR_Y;
2917
2918 case DiagDirection::SW:
2919 if (fxmy > 3 && sxmy < -3) return HT_DIR_VL;
2920 if (fxpy >= 20 && sxpy <= 12) return HT_DIR_HL;
2921 return HT_DIR_X;
2922
2923 case DiagDirection::NE:
2924 if (fxmy < -3 && sxmy > 3) return HT_DIR_VR;
2925 if (fxpy <= 12 && sxpy >= 20) return HT_DIR_HU;
2926 return HT_DIR_X;
2927 }
2928}
2929
2943static bool SwapDirection(HighLightStyle style, TileIndex start_tile, TileIndex end_tile)
2944{
2945 uint start_x = TileX(start_tile);
2946 uint start_y = TileY(start_tile);
2947 uint end_x = TileX(end_tile);
2948 uint end_y = TileY(end_tile);
2949
2950 switch (style & HT_DRAG_MASK) {
2951 case HT_RAIL:
2952 case HT_LINE: return (end_x > start_x || (end_x == start_x && end_y > start_y));
2953
2954 case HT_RECT:
2955 case HT_POINT: return (end_x != start_x && end_y < start_y);
2956 default: NOT_REACHED();
2957 }
2958
2959 return false;
2960}
2961
2977static int CalcHeightdiff(HighLightStyle style, uint distance, TileIndex start_tile, TileIndex end_tile)
2978{
2979 bool swap = SwapDirection(style, start_tile, end_tile);
2980 uint h0, h1; // Start height and end height.
2981
2982 if (start_tile == end_tile) return 0;
2983 if (swap) std::swap(start_tile, end_tile);
2984
2985 switch (style & HT_DRAG_MASK) {
2986 case HT_RECT:
2987 /* In the case of an area we can determine whether we were dragging south or
2988 * east by checking the X-coordinates of the tiles */
2989 if (TileX(end_tile) > TileX(start_tile)) {
2990 /* Dragging south does not need to change the start tile. */
2991 end_tile = TileAddByDir(end_tile, Direction::S);
2992 } else {
2993 /* Dragging east. */
2994 start_tile = TileAddByDir(start_tile, Direction::SW);
2995 end_tile = TileAddByDir(end_tile, Direction::SE);
2996 }
2997 [[fallthrough]];
2998
2999 case HT_POINT:
3000 h0 = TileHeight(start_tile);
3001 h1 = TileHeight(end_tile);
3002 break;
3003 default: { // All other types, this is mostly only line/autorail
3004 static const HighLightStyle flip_style_direction[] = {
3006 };
3007 static const std::pair<TileIndexDiffC, TileIndexDiffC> start_heightdiff_line_by_dir[] = {
3008 { {1, 0}, {1, 1} }, // HT_DIR_X
3009 { {0, 1}, {1, 1} }, // HT_DIR_Y
3010 { {1, 0}, {0, 0} }, // HT_DIR_HU
3011 { {1, 0}, {1, 1} }, // HT_DIR_HL
3012 { {1, 0}, {1, 1} }, // HT_DIR_VL
3013 { {0, 1}, {1, 1} }, // HT_DIR_VR
3014 };
3015 static const std::pair<TileIndexDiffC, TileIndexDiffC> end_heightdiff_line_by_dir[] = {
3016 { {0, 1}, {0, 0} }, // HT_DIR_X
3017 { {1, 0}, {0, 0} }, // HT_DIR_Y
3018 { {0, 1}, {0, 0} }, // HT_DIR_HU
3019 { {1, 1}, {0, 1} }, // HT_DIR_HL
3020 { {1, 0}, {0, 0} }, // HT_DIR_VL
3021 { {0, 0}, {0, 1} }, // HT_DIR_VR
3022 };
3023 static_assert(std::size(start_heightdiff_line_by_dir) == HT_DIR_END);
3024 static_assert(std::size(end_heightdiff_line_by_dir) == HT_DIR_END);
3025
3026 distance %= 2; // we're only interested if the distance is even or uneven
3027 style &= HT_DIR_MASK;
3028 assert(style < HT_DIR_END);
3029
3030 /* To handle autorail, we do some magic to be able to use a lookup table.
3031 * Firstly if we drag the other way around, we switch start&end, and if needed
3032 * also flip the drag-position. Eg if it was on the left, and the distance is even
3033 * that means the end, which is now the start is on the right */
3034 if (swap && distance == 0) style = flip_style_direction[style];
3035
3036 /* Lambda to help calculating the height at one side of the line. */
3037 auto get_height = [](auto &tile, auto &heightdiffs) {
3038 return std::max(
3039 TileHeight(TileAdd(tile, ToTileIndexDiff(heightdiffs.first))),
3040 TileHeight(TileAdd(tile, ToTileIndexDiff(heightdiffs.second))));
3041 };
3042
3043 /* Use lookup table for start-tile based on HighLightStyle direction */
3044 h0 = get_height(start_tile, start_heightdiff_line_by_dir[style]);
3045
3046 /* Use lookup table for end-tile based on HighLightStyle direction
3047 * flip around side (lower/upper, left/right) based on distance */
3048 if (distance == 0) style = flip_style_direction[style];
3049 h1 = get_height(end_tile, end_heightdiff_line_by_dir[style]);
3050 break;
3051 }
3052 }
3053
3054 if (swap) std::swap(h0, h1);
3055 return (int)(h1 - h0) * TILE_HEIGHT_STEP;
3056}
3057
3064static void CheckUnderflow(int &test, int &other, int mult)
3065{
3066 if (test >= 0) return;
3067
3068 other += mult * test;
3069 test = 0;
3070}
3071
3079static void CheckOverflow(int &test, int &other, int max, int mult)
3080{
3081 if (test <= max) return;
3082
3083 other += mult * (test - max);
3084 test = max;
3085}
3086
3093static void CalcRaildirsDrawstyle(int x, int y, int method)
3094{
3096
3097 int dx = _thd.selstart.x - (_thd.selend.x & ~TILE_UNIT_MASK);
3098 int dy = _thd.selstart.y - (_thd.selend.y & ~TILE_UNIT_MASK);
3099 uint w = abs(dx) + TILE_SIZE;
3100 uint h = abs(dy) + TILE_SIZE;
3101
3102 if (method & ~(VPM_RAILDIRS | VPM_SIGNALDIRS)) {
3103 /* We 'force' a selection direction; first four rail buttons. */
3104 method &= ~(VPM_RAILDIRS | VPM_SIGNALDIRS);
3105 int raw_dx = _thd.selstart.x - _thd.selend.x;
3106 int raw_dy = _thd.selstart.y - _thd.selend.y;
3107 switch (method) {
3108 case VPM_FIX_X:
3109 b = HT_LINE | HT_DIR_Y;
3110 x = _thd.selstart.x;
3111 break;
3112
3113 case VPM_FIX_Y:
3114 b = HT_LINE | HT_DIR_X;
3115 y = _thd.selstart.y;
3116 break;
3117
3118 case VPM_FIX_HORIZONTAL:
3119 if (dx == -dy) {
3120 /* We are on a straight horizontal line. Determine the 'rail'
3121 * to build based the sub tile location. */
3123 } else {
3124 /* We are not on a straight line. Determine the rail to build
3125 * based on whether we are above or below it. */
3126 b = dx + dy >= (int)TILE_SIZE ? HT_LINE | HT_DIR_HU : HT_LINE | HT_DIR_HL;
3127
3128 /* Calculate where a horizontal line through the start point and
3129 * a vertical line from the selected end point intersect and
3130 * use that point as the end point. */
3131 int offset = (raw_dx - raw_dy) / 2;
3132 x = _thd.selstart.x - (offset & ~TILE_UNIT_MASK);
3133 y = _thd.selstart.y + (offset & ~TILE_UNIT_MASK);
3134
3135 /* 'Build' the last half rail tile if needed */
3136 if ((offset & TILE_UNIT_MASK) > (TILE_SIZE / 2)) {
3137 if (dx + dy >= (int)TILE_SIZE) {
3138 x -= (int)TILE_SIZE;
3139 } else {
3140 y += (int)TILE_SIZE;
3141 }
3142 }
3143
3144 /* Make sure we do not overflow the map! */
3145 CheckUnderflow(x, y, 1);
3146 CheckUnderflow(y, x, 1);
3147 CheckOverflow(x, y, (Map::MaxX() - 1) * TILE_SIZE, 1);
3148 CheckOverflow(y, x, (Map::MaxY() - 1) * TILE_SIZE, 1);
3149 assert(x >= 0 && y >= 0 && x <= (int)(Map::MaxX() * TILE_SIZE) && y <= (int)(Map::MaxY() * TILE_SIZE));
3150 }
3151 break;
3152
3153 case VPM_FIX_VERTICAL:
3154 if (dx == dy) {
3155 /* We are on a straight vertical line. Determine the 'rail'
3156 * to build based the sub tile location. */
3158 } else {
3159 /* We are not on a straight line. Determine the rail to build
3160 * based on whether we are left or right from it. */
3161 b = dx < dy ? HT_LINE | HT_DIR_VL : HT_LINE | HT_DIR_VR;
3162
3163 /* Calculate where a vertical line through the start point and
3164 * a horizontal line from the selected end point intersect and
3165 * use that point as the end point. */
3166 int offset = (raw_dx + raw_dy + (int)TILE_SIZE) / 2;
3167 x = _thd.selstart.x - (offset & ~TILE_UNIT_MASK);
3168 y = _thd.selstart.y - (offset & ~TILE_UNIT_MASK);
3169
3170 /* 'Build' the last half rail tile if needed */
3171 if ((offset & TILE_UNIT_MASK) > (TILE_SIZE / 2)) {
3172 if (dx < dy) {
3173 y -= (int)TILE_SIZE;
3174 } else {
3175 x -= (int)TILE_SIZE;
3176 }
3177 }
3178
3179 /* Make sure we do not overflow the map! */
3180 CheckUnderflow(x, y, -1);
3181 CheckUnderflow(y, x, -1);
3182 CheckOverflow(x, y, (Map::MaxX() - 1) * TILE_SIZE, -1);
3183 CheckOverflow(y, x, (Map::MaxY() - 1) * TILE_SIZE, -1);
3184 assert(x >= 0 && y >= 0 && x <= (int)(Map::MaxX() * TILE_SIZE) && y <= (int)(Map::MaxY() * TILE_SIZE));
3185 }
3186 break;
3187
3188 default:
3189 NOT_REACHED();
3190 }
3191 } else if (TileVirtXY(_thd.selstart.x, _thd.selstart.y) == TileVirtXY(x, y)) { // check if we're only within one tile
3192 if (method & VPM_RAILDIRS) {
3193 b = GetAutorailHT(x, y);
3194 } else { // rect for autosignals on one tile
3195 b = HT_RECT;
3196 }
3197 } else if (h == TILE_SIZE) { // Is this in X direction?
3198 if (dx == (int)TILE_SIZE) { // 2x1 special handling
3200 } else if (dx == -(int)TILE_SIZE) {
3202 } else {
3203 b = HT_LINE | HT_DIR_X;
3204 }
3205 y = _thd.selstart.y;
3206 } else if (w == TILE_SIZE) { // Or Y direction?
3207 if (dy == (int)TILE_SIZE) { // 2x1 special handling
3209 } else if (dy == -(int)TILE_SIZE) { // 2x1 other direction
3211 } else {
3212 b = HT_LINE | HT_DIR_Y;
3213 }
3214 x = _thd.selstart.x;
3215 } else if (w > h * 2) { // still count as x dir?
3216 b = HT_LINE | HT_DIR_X;
3217 y = _thd.selstart.y;
3218 } else if (h > w * 2) { // still count as y dir?
3219 b = HT_LINE | HT_DIR_Y;
3220 x = _thd.selstart.x;
3221 } else { // complicated direction
3222 int d = w - h;
3223 _thd.selend.x = _thd.selend.x & ~TILE_UNIT_MASK;
3224 _thd.selend.y = _thd.selend.y & ~TILE_UNIT_MASK;
3225
3226 /* four cases. */
3227 if (x > _thd.selstart.x) {
3228 if (y > _thd.selstart.y) {
3229 /* south */
3230 if (d == 0) {
3232 } else if (d >= 0) {
3233 x = _thd.selstart.x + h;
3234 b = HT_LINE | HT_DIR_VL;
3235 } else {
3236 y = _thd.selstart.y + w;
3237 b = HT_LINE | HT_DIR_VR;
3238 }
3239 } else {
3240 /* west */
3241 if (d == 0) {
3243 } else if (d >= 0) {
3244 x = _thd.selstart.x + h;
3245 b = HT_LINE | HT_DIR_HL;
3246 } else {
3247 y = _thd.selstart.y - w;
3248 b = HT_LINE | HT_DIR_HU;
3249 }
3250 }
3251 } else {
3252 if (y > _thd.selstart.y) {
3253 /* east */
3254 if (d == 0) {
3256 } else if (d >= 0) {
3257 x = _thd.selstart.x - h;
3258 b = HT_LINE | HT_DIR_HU;
3259 } else {
3260 y = _thd.selstart.y + w;
3261 b = HT_LINE | HT_DIR_HL;
3262 }
3263 } else {
3264 /* north */
3265 if (d == 0) {
3267 } else if (d >= 0) {
3268 x = _thd.selstart.x - h;
3269 b = HT_LINE | HT_DIR_VR;
3270 } else {
3271 y = _thd.selstart.y - w;
3272 b = HT_LINE | HT_DIR_VL;
3273 }
3274 }
3275 }
3276 }
3277
3278 if (_settings_client.gui.measure_tooltip) {
3279 TileIndex t0 = TileVirtXY(_thd.selstart.x, _thd.selstart.y);
3280 TileIndex t1 = TileVirtXY(x, y);
3281 uint distance = DistanceManhattan(t0, t1) + 1;
3282
3283 if (distance == 1) {
3284 HideMeasurementTooltips();
3285 } else {
3286 int heightdiff = CalcHeightdiff(b, distance, t0, t1);
3287 /* If we are showing a tooltip for horizontal or vertical drags,
3288 * 2 tiles have a length of 1. To bias towards the ceiling we add
3289 * one before division. It feels more natural to count 3 lengths as 2 */
3290 if ((b & HT_DIR_MASK) != HT_DIR_X && (b & HT_DIR_MASK) != HT_DIR_Y) {
3291 distance = CeilDiv(distance, 2);
3292 }
3293
3294 if (heightdiff == 0) {
3295 ShowMeasurementTooltips(GetEncodedString(STR_MEASURE_LENGTH, distance));
3296 } else {
3297 ShowMeasurementTooltips(GetEncodedString(STR_MEASURE_LENGTH_HEIGHTDIFF, distance, heightdiff));
3298 }
3299 }
3300 }
3301
3302 _thd.selend.x = x;
3303 _thd.selend.y = y;
3304 _thd.next_drawstyle = b;
3305}
3306
3315{
3316 int sx, sy;
3317 HighLightStyle style;
3318
3319 if (x == -1) {
3320 _thd.selend.x = -1;
3321 return;
3322 }
3323
3324 /* Special handling of drag in any (8-way) direction */
3325 if (method & (VPM_RAILDIRS | VPM_SIGNALDIRS)) {
3326 _thd.selend.x = x;
3327 _thd.selend.y = y;
3328 CalcRaildirsDrawstyle(x, y, method);
3329 return;
3330 }
3331
3332 /* Needed so level-land is placed correctly */
3333 if ((_thd.next_drawstyle & HT_DRAG_MASK) == HT_POINT) {
3334 x += TILE_SIZE / 2;
3335 y += TILE_SIZE / 2;
3336 }
3337
3338 sx = _thd.selstart.x;
3339 sy = _thd.selstart.y;
3340
3341 int limit = 0;
3342
3343 switch (method) {
3344 case VPM_X_OR_Y: // drag in X or Y direction
3345 if (abs(sy - y) < abs(sx - x)) {
3346 y = sy;
3347 style = HT_DIR_X;
3348 } else {
3349 x = sx;
3350 style = HT_DIR_Y;
3351 }
3352 goto calc_heightdiff_single_direction;
3353
3354 case VPM_X_LIMITED: // Drag in X direction (limited size).
3355 limit = (_thd.sizelimit - 1) * TILE_SIZE;
3356 [[fallthrough]];
3357
3358 case VPM_FIX_X: // drag in Y direction
3359 x = sx;
3360 style = HT_DIR_Y;
3361 goto calc_heightdiff_single_direction;
3362
3363 case VPM_Y_LIMITED: // Drag in Y direction (limited size).
3364 limit = (_thd.sizelimit - 1) * TILE_SIZE;
3365 [[fallthrough]];
3366
3367 case VPM_FIX_Y: // drag in X direction
3368 y = sy;
3369 style = HT_DIR_X;
3370
3371calc_heightdiff_single_direction:;
3372 if (limit > 0) {
3373 x = sx + Clamp(x - sx, -limit, limit);
3374 y = sy + Clamp(y - sy, -limit, limit);
3375 }
3376 if (_settings_client.gui.measure_tooltip) {
3377 TileIndex t0 = TileVirtXY(sx, sy);
3378 TileIndex t1 = TileVirtXY(x, y);
3379 uint distance = DistanceManhattan(t0, t1) + 1;
3380
3381 if (distance == 1) {
3382 HideMeasurementTooltips();
3383 } else {
3384 /* With current code passing a HT_LINE style to calculate the height
3385 * difference is enough. However if/when a point-tool is created
3386 * with this method, function should be called with new_style (below)
3387 * instead of HT_LINE | style case HT_POINT is handled specially
3388 * new_style := (_thd.next_drawstyle & HT_RECT) ? HT_LINE | style : _thd.next_drawstyle; */
3389 int heightdiff = CalcHeightdiff(HT_LINE | style, 0, t0, t1);
3390
3391 if (heightdiff == 0) {
3392 ShowMeasurementTooltips(GetEncodedString(STR_MEASURE_LENGTH, distance));
3393 } else {
3394 ShowMeasurementTooltips(GetEncodedString(STR_MEASURE_LENGTH_HEIGHTDIFF, distance, heightdiff));
3395 }
3396 }
3397 }
3398 break;
3399
3400 case VPM_X_AND_Y_LIMITED: // Drag an X by Y constrained rect area.
3401 limit = (_thd.sizelimit - 1) * TILE_SIZE;
3402 x = sx + Clamp(x - sx, -limit, limit);
3403 y = sy + Clamp(y - sy, -limit, limit);
3404 [[fallthrough]];
3405
3406 case VPM_X_AND_Y: // drag an X by Y area
3407 if (_settings_client.gui.measure_tooltip) {
3408 TileIndex t0 = TileVirtXY(sx, sy);
3409 TileIndex t1 = TileVirtXY(x, y);
3410 uint dx = Delta(TileX(t0), TileX(t1)) + 1;
3411 uint dy = Delta(TileY(t0), TileY(t1)) + 1;
3412
3413 /* If dragging an area (eg dynamite tool) and it is actually a single
3414 * row/column, change the type to 'line' to get proper calculation for height */
3415 style = (HighLightStyle)_thd.next_drawstyle;
3416 if (_thd.IsDraggingDiagonal()) {
3417 /* Determine the "area" of the diagonal dragged selection.
3418 * We assume the area is the number of tiles along the X
3419 * edge and the number of tiles along the Y edge. However,
3420 * multiplying these two numbers does not give the exact
3421 * number of tiles; basically we are counting the black
3422 * squares on a chess board and ignore the white ones to
3423 * make the tile counts at the edges match up. There is no
3424 * other way to make a proper count though.
3425 *
3426 * First convert to the rotated coordinate system. */
3427 int dist_x = TileX(t0) - TileX(t1);
3428 int dist_y = TileY(t0) - TileY(t1);
3429 int a_max = dist_x + dist_y;
3430 int b_max = dist_y - dist_x;
3431
3432 /* Now determine the size along the edge, but due to the
3433 * chess board principle this counts double. */
3434 a_max = abs(a_max + (a_max > 0 ? 2 : -2)) / 2;
3435 b_max = abs(b_max + (b_max > 0 ? 2 : -2)) / 2;
3436
3437 /* We get a 1x1 on normal 2x1 rectangles, due to it being
3438 * a seen as two sides. As the result for actual building
3439 * will be the same as non-diagonal dragging revert to that
3440 * behaviour to give it a more normally looking size. */
3441 if (a_max != 1 || b_max != 1) {
3442 dx = a_max;
3443 dy = b_max;
3444 }
3445 } else if (style & HT_RECT) {
3446 if (dx == 1) {
3447 style = HT_LINE | HT_DIR_Y;
3448 } else if (dy == 1) {
3449 style = HT_LINE | HT_DIR_X;
3450 }
3451 }
3452
3453 if (dx != 1 || dy != 1) {
3454 int heightdiff = CalcHeightdiff(style, 0, t0, t1);
3455
3456 dx -= (style & HT_POINT ? 1 : 0);
3457 dy -= (style & HT_POINT ? 1 : 0);
3458
3459 if (heightdiff == 0) {
3460 ShowMeasurementTooltips(GetEncodedString(STR_MEASURE_AREA, dx, dy));
3461 } else {
3462 ShowMeasurementTooltips(GetEncodedString(STR_MEASURE_AREA_HEIGHTDIFF, dx, dy, heightdiff));
3463 }
3464 }
3465 }
3466 break;
3467
3468 default: NOT_REACHED();
3469 }
3470
3471 _thd.selend.x = x;
3472 _thd.selend.y = y;
3473}
3474
3480{
3482
3483 /* stop drag mode if the window has been closed */
3484 Window *w = _thd.GetCallbackWnd();
3485 if (w == nullptr) {
3487 return EventState::Handled;
3488 }
3489
3490 /* while dragging execute the drag procedure of the corresponding window (mostly VpSelectTilesWithMethod() ) */
3491 if (_left_button_down) {
3493 /* Only register a drag event when the mouse moved. */
3494 if (_thd.new_pos.x == _thd.selstart.x && _thd.new_pos.y == _thd.selstart.y) return EventState::Handled;
3495 _thd.selstart.x = _thd.new_pos.x;
3496 _thd.selstart.y = _thd.new_pos.y;
3497 }
3498
3499 w->OnPlaceDrag(_thd.select_method, _thd.select_proc, GetTileBelowCursor());
3500 return EventState::Handled;
3501 }
3502
3503 /* Mouse button released. */
3506
3507 /* Keep the selected tool, but reset it to the original mode. */
3508 HighLightStyle others = _thd.place_mode & ~(HT_DRAG_MASK | HT_DIR_MASK);
3509 if ((_thd.next_drawstyle & HT_DRAG_MASK) == HT_RECT) {
3510 _thd.place_mode = HT_RECT | others;
3511 } else if (_thd.select_method & VPM_SIGNALDIRS) {
3512 _thd.place_mode = HT_RECT | others;
3513 } else if (_thd.select_method & VPM_RAILDIRS) {
3514 _thd.place_mode = (_thd.select_method & ~VPM_RAILDIRS) ? _thd.next_drawstyle : (HT_RAIL | others);
3515 } else {
3516 _thd.place_mode = HT_POINT | others;
3517 }
3518 SetTileSelectSize(1, 1);
3519
3520 HideMeasurementTooltips();
3521 w->OnPlaceMouseUp(_thd.select_method, _thd.select_proc, _thd.selend, TileVirtXY(_thd.selstart.x, _thd.selstart.y), TileVirtXY(_thd.selend.x, _thd.selend.y));
3522
3523 return EventState::Handled;
3524}
3525
3534{
3535 SetObjectToPlace(icon, pal, mode, w->window_class, w->window_number);
3536}
3537
3538#include "table/animcursors.h"
3539
3548void SetObjectToPlace(CursorID icon, PaletteID pal, HighLightStyle mode, WindowClass window_class, WindowNumber window_num)
3549{
3550 if (_thd.window_class != WindowClass::Invalid) {
3551 /* Undo clicking on button and drag & drop */
3552 Window *w = _thd.GetCallbackWnd();
3553 /* Call the abort function, but set the window class to something
3554 * that will never be used to avoid infinite loops. Setting it to
3555 * the 'next' window class must not be done because recursion into
3556 * this function might in some cases reset the newly set object to
3557 * place or not properly reset the original selection. */
3558 _thd.window_class = WindowClass::Invalid;
3559 if (w != nullptr) {
3560 w->OnPlaceObjectAbort();
3561 HideMeasurementTooltips();
3562 }
3563 }
3564
3565 /* Mark the old selection dirty, in case the selection shape or colour changes */
3566 if ((_thd.drawstyle & HT_DRAG_MASK) != HT_NONE) SetSelectionTilesDirty();
3567
3568 SetTileSelectSize(1, 1);
3569
3570 _thd.make_square_red = false;
3571
3572 if (mode == HT_DRAG) { // HT_DRAG is for dragdropping trains in the depot window
3573 mode = HT_NONE;
3575 } else {
3577 }
3578
3579 _thd.place_mode = mode;
3580 _thd.window_class = window_class;
3581 _thd.window_number = window_num;
3582
3583 if ((mode & HT_DRAG_MASK) == HT_SPECIAL) { // special tools, like tunnels or docks start with presizing mode
3584 VpStartPreSizing();
3585 }
3586
3587 SetCursor(icon, pal);
3588}
3589
3592{
3593 SetObjectToPlace(SPR_CURSOR_MOUSE, PAL_NONE, HT_NONE, WindowClass::MainWindow, 0);
3594}
3595
3596Point GetViewportStationMiddle(const Viewport &vp, const Station *st)
3597{
3598 int x = TileX(st->xy) * TILE_SIZE;
3599 int y = TileY(st->xy) * TILE_SIZE;
3600 int z = GetSlopePixelZ(Clamp(x, 0, Map::SizeX() * TILE_SIZE - 1), Clamp(y, 0, Map::SizeY() * TILE_SIZE - 1));
3601
3602 Point p = RemapCoords(x, y, z);
3603 p.x = UnScaleByZoom(p.x - vp.virtual_left, vp.zoom) + vp.left;
3604 p.y = UnScaleByZoom(p.y - vp.virtual_top, vp.zoom) + vp.top;
3605 return p;
3606}
3607
3613
3616#ifdef WITH_SSE
3617 { &ViewportSortParentSpritesSSE41Checker, &ViewportSortParentSpritesSSE41 },
3618#endif
3619 { []() { return true; /* Always available */ }, &ViewportSortParentSprites }
3620};
3621
3624{
3625 for (const auto &sprite_sorter : _vp_sprite_sorters) {
3626 if (sprite_sorter.fct_checker()) {
3627 _vp_sprite_sorter = sprite_sorter.fct_sorter;
3628 break;
3629 }
3630 }
3631 assert(_vp_sprite_sorter != nullptr);
3632}
3633
3643{
3644 if (_current_company != OWNER_DEITY) return CMD_ERROR;
3645 switch (target) {
3647 break;
3649 if (_local_company != (CompanyID)ref) return CommandCost();
3650 break;
3652 if (_network_own_client_id != (ClientID)ref) return CommandCost();
3653 break;
3654 default:
3655 return CMD_ERROR;
3656 }
3657
3658 if (flags.Test(DoCommandFlag::Execute)) {
3661 }
3662 return CommandCost();
3663}
3664
3665void MarkCatchmentTilesDirty()
3666{
3667 if (_viewport_highlight_town != nullptr) {
3669 return;
3670 }
3671
3672 if (_viewport_highlight_station != nullptr) {
3673 if (_viewport_highlight_station->catchment_tiles.IsEmpty()) {
3676 } else {
3678 for (TileIndex tile = it; tile != INVALID_TILE; tile = ++it) {
3679 MarkTileDirtyByTile(tile);
3680 }
3681 }
3682 }
3683
3684 if (_viewport_highlight_station_rect != nullptr) {
3685 if (!_viewport_highlight_station_rect->IsInUse()) {
3687 }
3689 }
3690
3691 if (_viewport_highlight_waypoint != nullptr) {
3692 if (!_viewport_highlight_waypoint->IsInUse()) {
3694 }
3696 }
3697
3698 if (_viewport_highlight_waypoint_rect != nullptr) {
3699 if (!_viewport_highlight_waypoint_rect->IsInUse()) {
3701 }
3703 }
3704}
3705
3706static void SetWindowDirtyForViewportCatchment()
3707{
3708 if (_viewport_highlight_station != nullptr) SetWindowDirty(WindowClass::StationView, _viewport_highlight_station->index);
3709 if (_viewport_highlight_station_rect != nullptr) SetWindowDirty(WindowClass::StationView, _viewport_highlight_station_rect->index);
3710 if (_viewport_highlight_waypoint != nullptr) SetWindowDirty(WindowClass::WaypointView, _viewport_highlight_waypoint->index);
3711 if (_viewport_highlight_waypoint_rect != nullptr) SetWindowDirty(WindowClass::WaypointView, _viewport_highlight_waypoint_rect->index);
3712 if (_viewport_highlight_town != nullptr) SetWindowDirty(WindowClass::TownView, _viewport_highlight_town->index);
3713}
3714
3715static void ClearViewportCatchment()
3716{
3717 MarkCatchmentTilesDirty();
3722 _viewport_highlight_town = nullptr;
3723}
3724
3731void SetViewportCatchmentStation(const Station *st, bool sel)
3732{
3733 SetWindowDirtyForViewportCatchment();
3734 /* Mark tiles dirty for redrawing and update selected station if a different station is already highlighted. */
3735 if (sel && _viewport_highlight_station != st) {
3736 ClearViewportCatchment();
3738 MarkCatchmentTilesDirty();
3739 /* Mark tiles dirty for redrawing and clear station selection if deselecting highlight. */
3740 } else if (!sel && _viewport_highlight_station == st) {
3741 MarkCatchmentTilesDirty();
3743 }
3744 /* Redraw the currently selected station window */
3745 if (_viewport_highlight_station != nullptr) SetWindowDirty(WindowClass::StationView, _viewport_highlight_station->index);
3746}
3747
3754void SetViewportStationRect(const Station *st, bool sel)
3755{
3756 SetWindowDirtyForViewportCatchment();
3757 /* Mark tiles dirty for redrawing and update selected station if a different station is already highlighted. */
3758 if (sel && _viewport_highlight_station_rect != st) {
3759 ClearViewportCatchment();
3761 MarkCatchmentTilesDirty();
3762 /* Mark tiles dirty for redrawing and clear station selection if deselecting highlight. */
3763 } else if (!sel && _viewport_highlight_station_rect == st) {
3764 MarkCatchmentTilesDirty();
3766 }
3767 /* Redraw the currently selected station window */
3768 if (_viewport_highlight_station_rect != nullptr) SetWindowDirty(WindowClass::StationView, _viewport_highlight_station_rect->index);
3769}
3770
3778{
3779 SetWindowDirtyForViewportCatchment();
3780 /* Mark tiles dirty for redrawing and update selected waypoint if a different waypoint is already highlighted. */
3781 if (sel && _viewport_highlight_waypoint != wp) {
3782 ClearViewportCatchment();
3784 MarkCatchmentTilesDirty();
3785 /* Mark tiles dirty for redrawing and clear waypoint selection if deselecting highlight. */
3786 } else if (!sel && _viewport_highlight_waypoint == wp) {
3787 MarkCatchmentTilesDirty();
3789 }
3790 /* Redraw the currently selected waypoint window */
3791 if (_viewport_highlight_waypoint != nullptr) SetWindowDirty(WindowClass::WaypointView, _viewport_highlight_waypoint->index);
3792}
3793
3800void SetViewportWaypointRect(const Waypoint *wp, bool sel)
3801{
3802 SetWindowDirtyForViewportCatchment();
3803 /* Mark tiles dirty for redrawing and update selected waypoint if a different waypoint is already highlighted. */
3804 if (sel && _viewport_highlight_waypoint_rect != wp) {
3805 ClearViewportCatchment();
3807 MarkCatchmentTilesDirty();
3808 /* Mark tiles dirty for redrawing and clear waypoint selection if deselecting highlight. */
3809 } else if (!sel && _viewport_highlight_waypoint_rect == wp) {
3810 MarkCatchmentTilesDirty();
3812 }
3813 /* Redraw the currently selected waypoint window */
3814 if (_viewport_highlight_waypoint_rect != nullptr) SetWindowDirty(WindowClass::WaypointView, _viewport_highlight_waypoint_rect->index);
3815}
3816
3823void SetViewportCatchmentTown(const Town *t, bool sel)
3824{
3825 SetWindowDirtyForViewportCatchment();
3826 /* Mark tiles dirty for redrawing and update selected town if a different town is already highlighted. */
3827 if (sel && _viewport_highlight_town != t) {
3828 ClearViewportCatchment();
3831 /* Mark tiles dirty for redrawing and clear town selection if deselecting highlight. */
3832 } else if (!sel && _viewport_highlight_town == t) {
3833 _viewport_highlight_town = nullptr;
3835 }
3836 /* Redraw the currently selected town window */
3837 if (_viewport_highlight_town != nullptr) SetWindowDirty(WindowClass::TownView, _viewport_highlight_town->index);
3838}
3839
3844void ViewportData::CancelFollow(const Window &viewport_window)
3845{
3846 if (this->follow_vehicle == VehicleID::Invalid()) return;
3847
3848 if (viewport_window.window_class == WindowClass::MainWindow) {
3849 /* We're cancelling follow in the main viewport, so we need to check for a vehicle view window
3850 * to raise the location follow widget. */
3851 Window *vehicle_window = FindWindowById(WindowClass::VehicleView, this->follow_vehicle);
3852 if (vehicle_window != nullptr) vehicle_window->RaiseWidgetWhenLowered(WID_VV_LOCATION);
3853 }
3854
3855 this->dest_scrollpos_x = this->scrollpos_x;
3856 this->dest_scrollpos_y = this->scrollpos_y;
3857 this->follow_vehicle = VehicleID::Invalid();
3858}
This file defines all the the animated cursors.
Highlight/sprite information for autorail.
static constexpr SlopeIndexArray< std::array< int, 6 > > _autorail_slope_sprite_offsets
Table maps each of the six rail directions and tileh combinations to a sprite offset.
Definition autorail.h:25
static const HighLightStyle _autorail_piece[][16]
Maps each pixel of a tile (16x16) to a selection type (0,0) is the top corner, (16,...
Definition autorail.h:63
Class for backupping variables and making sure they are restored later.
constexpr T SetBit(T &x, const uint8_t y)
Set a bit in a variable.
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.
Definition bitmap_type.h:94
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(facility, severity, format_string,...)
Output a line of debugging information.
Definition debug.h:37
@ Misc
Misc message facility.
Definition debug_type.h:32
@ Warning
Warning, wrong but okay if you don't know.
Definition debug_type.h:17
@ 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.
@ ViewportDrawing
Time spent drawing world viewports in GUI.
@ Centre
Align to the centre.
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
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
int DrawString(int left, int right, int top, std::string_view str, ExtendedTextColour colour, Alignment align, bool underline, FontSize fontsize)
Draw string, possibly truncated to make it fit in its allocated space.
Definition gfx.cpp:668
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:405
@ 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
uint32_t PaletteID
The number of the palette.
Definition gfx_type.h:18
Colours
One of 16 base colours used for companies and windows/widgets.
Definition gfx_type.h:283
@ White
White.
Definition gfx_type.h:300
@ Invalid
Invalid marker.
Definition gfx_type.h:302
@ Grey
Grey.
Definition gfx_type.h:299
@ White
White colour.
Definition gfx_type.h:330
@ Black
Black colour.
Definition gfx_type.h:334
@ Town
Destination is a town.
Definition goal_type.h:55
static const CursorID SPR_CURSOR_MOUSE
Definition sprites.h:1568
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:975
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:689
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:216
Corner OppositeCorner(Corner corner)
Returns the opposite corner.
Definition slope_func.h:194
static constexpr Corner GetHalftileSlopeCorner(Slope s)
Returns the leveled halftile of a halftile slope.
Definition slope_func.h:158
static constexpr Slope RemoveHalftileSlope(Slope s)
Removes a halftile slope from a slope.
Definition slope_func.h:70
uint SlopeToSpriteOffset(Slope s)
Returns the Sprite offset for a given Slope.
Definition slope_func.h:423
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:57
Slope SlopeWithOneCornerRaised(Corner corner)
Returns the slope with a specific corner raised.
Definition slope_func.h:109
Corner
Enumeration of tile corners.
Definition slope_type.h:21
@ E
East tile corner.
Definition slope_type.h:24
@ S
South tile corner.
Definition slope_type.h:23
@ W
West tile corner.
Definition slope_type.h:22
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 const SpriteID SPR_DOT
Corner marker for lower/raise land.
Definition sprites.h:45
static constexpr uint32_t MAX_SPRITES
Masks needed for sprite operations.
Definition sprites.h:1742
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:1756
static const PaletteID PALETTE_SEL_TILE_RED
makes a square red. is used when removing rails or other stuff
Definition sprites.h:1757
static constexpr uint32_t SPRITE_MASK
The mask to for the main sprite.
Definition sprites.h:1743
static constexpr uint8_t PALETTE_MODIFIER_TRANSPARENT
when a sprite is to be displayed transparently, this bit needs to be set.
Definition sprites.h:1734
static const PaletteID PALETTE_CRASH
Recolour sprite greying of crashed vehicles.
Definition sprites.h:1794
static const PaletteID PALETTE_SEL_TILE_BLUE
This draws a blueish square (catchment areas for example).
Definition sprites.h:1758
static const SpriteID SPR_HALFTILE_SELECTION_FLAT
Definition sprites.h:66
static const SpriteID SPR_HALFTILE_SELECTION_UP
Definition sprites.h:68
static const SpriteID SPR_HALFTILE_SELECTION_DOWN
Definition sprites.h:67
static const PaletteID PALETTE_TO_TRANSPARENT
This sets the sprite to transparent.
Definition sprites.h:1791
static const SpriteID SPR_EMPTY_BOUNDING_BOX
Definition sprites.h:339
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.
StrongType::Typedef< uint32_t, struct StringIDTag, StrongType::Compare, StrongType::Integer > 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:349
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:307
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.
Station data structure.
Used to only draw a part of the sprite.
Definition gfx_type.h:278
Metadata about the current highlighting.
Point new_pos
New value for pos; used to determine whether to redraw the selection.
Window * GetCallbackWnd()
Get the window that started the current highlighting.
bool IsDraggingDiagonal()
Is the user dragging a 'diagonal rectangle'?
HighLightStyle place_mode
Method which is used to place the selection.
WindowClass window_class
The WindowClass of the window that is responsible for the selection mode.
Point pos
Location, in tile "units", of the northern tile of the selected area.
void Reset()
Reset tile highlighting.
WindowNumber window_number
The WindowNumber of the window that is responsible for the selection mode.
Tile information, used while rendering the tile.
Definition tile_cmd.h: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:565
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:1149
Vehicle * CheckClickOnVehicle(const Viewport &vp, int x, int y)
Find the vehicle close to the clicked coordinates.
Definition vehicle.cpp:1243
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.
@ Company
All players in specific company.
@ Client
Single player.
@ Everyone
All players.
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:308
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:1204
Window * GetMainWindow()
Get the main window, i.e.
Definition window.cpp:1190
Window * FindWindowFromPt(int x, int y)
Do a search for a window at specific coordinates.
Definition window.cpp:1853
SpecialMouseMode _special_mouse_mode
Mode of the mouse.
Definition window.cpp:91
Window * FindWindowById(WindowClass cls, WindowNumber number)
Find a window by its class and window number.
Definition window.cpp:1161
void SetWindowDirty(WindowClass cls, WindowNumber number)
Mark window as dirty (in need of repainting).
Definition window.cpp:3212
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