Departures: Add location button to window
[openttd-jgr.git] / src / zoning_cmd.cpp
blobf892243c423d92228caa3583a8378b0905404e83
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 <http://www.gnu.org/licenses/>.
6 */
8 /** @file zoning_cmd.cpp */
10 #include "stdafx.h"
11 #include "openttd.h"
12 #include "station_type.h"
13 #include "station_base.h"
14 #include "industry.h"
15 #include "gfx_func.h"
16 #include "viewport_func.h"
17 #include "map_func.h"
18 #include "company_func.h"
19 #include "town_map.h"
20 #include "table/sprites.h"
21 #include "station_func.h"
22 #include "station_map.h"
23 #include "town.h"
24 #include "tracerestrict.h"
25 #include "window_func.h"
26 #include "zoning.h"
27 #include "viewport_func.h"
28 #include "road_map.h"
29 #include "animated_tile.h"
30 #include "3rdparty/cpp-btree/btree_set.h"
32 Zoning _zoning;
33 static const SpriteID ZONING_INVALID_SPRITE_ID = UINT_MAX;
35 static btree::btree_set<uint32_t> _zoning_cache_inner;
36 static btree::btree_set<uint32_t> _zoning_cache_outer;
38 /**
39 * Draw the zoning sprites.
41 * @param SpriteID image
42 * the image
43 * @param SpriteID colour
44 * the colour of the zoning
45 * @param TileInfo ti
46 * the tile
48 void DrawZoningSprites(SpriteID image, SpriteID colour, const TileInfo *ti)
50 if (colour != ZONING_INVALID_SPRITE_ID) {
51 AddSortableSpriteToDraw(image + ti->tileh, colour, ti->x, ti->y, 0x10, 0x10, 1, ti->z + 7);
55 /**
56 * Detect whether this area is within the acceptance of any station.
58 * @param TileArea area
59 * the area to search by
60 * @param Owner owner
61 * the owner of the stations which we need to match again
62 * @param StationFacility facility_mask
63 * one or more facilities in the mask must be present for a station to be used
64 * @return true if a station is found
66 bool IsAreaWithinAcceptanceZoneOfStation(TileArea area, Owner owner, StationFacility facility_mask)
68 StationFinder morestations(area);
70 for (const Station *st : morestations.GetStations()) {
71 if (st->owner != owner || !(st->facilities & facility_mask)) continue;
72 Rect rect = st->GetCatchmentRect();
73 return TileArea(TileXY(rect.left, rect.top), TileXY(rect.right, rect.bottom)).Intersects(area);
76 return false;
79 /**
80 * Check whether the player can build in tile.
82 * @param TileIndex tile
83 * @param Owner owner
84 * @return red if they cannot
86 SpriteID TileZoneCheckBuildEvaluation(TileIndex tile, Owner owner)
88 /* Let's first check for the obvious things you cannot build on */
89 switch (GetTileType(tile)) {
90 case MP_INDUSTRY:
91 case MP_OBJECT:
92 case MP_STATION:
93 case MP_HOUSE:
94 case MP_TUNNELBRIDGE:
95 return SPR_ZONING_INNER_HIGHLIGHT_RED;
97 /* There are only two things you can own (or some else
98 * can own) that you can still build on. i.e. roads and
99 * railways.
100 * @todo
101 * Add something more intelligent, check what tool the
102 * user is currently using (and if none, assume some
103 * standards), then check it against if owned by some-
104 * one else (e.g. railway on someone else's road).
105 * While that being said, it should also check if it
106 * is not possible to build railway/road on someone
107 * else's/your own road/railway (e.g. the railway track
108 * is curved or a cross).
110 case MP_ROAD:
111 case MP_RAILWAY:
112 if (GetTileOwner(tile) != owner) {
113 return SPR_ZONING_INNER_HIGHLIGHT_RED;
114 } else {
115 return ZONING_INVALID_SPRITE_ID;
118 default:
119 return ZONING_INVALID_SPRITE_ID;
124 * Check the opinion of the local authority in the tile.
126 * @param TileIndex tile
127 * @param Owner owner
128 * @return black if no opinion, orange if bad,
129 * light blue if good or invalid if no town
131 SpriteID TileZoneCheckOpinionEvaluation(TileIndex tile, Owner owner)
133 int opinion = 0; // 0: no town, 1: no opinion, 2: bad, 3: good
134 Town *town = ClosestTownFromTile(tile, _settings_game.economy.dist_local_authority);
136 if (town != nullptr) {
137 if (HasBit(town->have_ratings, owner)) {
138 opinion = (town->ratings[owner] > 0) ? 3 : 2;
139 } else {
140 opinion = 1;
144 switch (opinion) {
145 case 1: return SPR_ZONING_INNER_HIGHLIGHT_BLACK; // no opinion
146 case 2: return SPR_ZONING_INNER_HIGHLIGHT_ORANGE; // bad
147 case 3: return SPR_ZONING_INNER_HIGHLIGHT_LIGHT_BLUE; // good
148 default: return ZONING_INVALID_SPRITE_ID; // no town
153 * Detect whether the tile is within the catchment zone of a station.
155 * @param TileIndex tile
156 * @param Owner owner
157 * @param open_window_only
158 * only use stations which have their station window open
159 * @return black if within, light blue if only in acceptance zone
160 * and nothing if no nearby station.
162 SpriteID TileZoneCheckStationCatchmentEvaluation(TileIndex tile, Owner owner, bool open_window_only)
164 // Never on a station.
165 if (IsTileType(tile, MP_STATION)) {
166 return ZONING_INVALID_SPRITE_ID;
169 StationFinder stations(TileArea(tile, 1, 1));
171 for (const Station *st : stations.GetStations()) {
172 if (st->owner == owner) {
173 if (!open_window_only || FindWindowById(WC_STATION_VIEW, st->index) != nullptr) {
174 return SPR_ZONING_INNER_HIGHLIGHT_LIGHT_BLUE;
179 return ZONING_INVALID_SPRITE_ID;
183 * Detect whether a building is unserved by a station of owner.
185 * @param TileIndex tile
186 * @param Owner owner
187 * @return red if unserved, orange if only accepting, nothing if served or not
188 * a building
190 SpriteID TileZoneCheckUnservedBuildingsEvaluation(TileIndex tile, Owner owner)
192 if (!IsTileType(tile, MP_HOUSE)) {
193 return ZONING_INVALID_SPRITE_ID;
196 auto has_town_cargo = [&](const CargoArray &dat) {
197 for (CargoID cid : SetCargoBitIterator(CargoSpec::town_production_cargo_mask[TPE_PASSENGERS] | CargoSpec::town_production_cargo_mask[TPE_MAIL])) {
198 if (dat[cid] > 0) return true;
200 return false;
203 CargoArray dat{};
204 dat.Clear();
205 AddAcceptedCargo(tile, dat, nullptr);
206 if (!has_town_cargo(dat)) {
207 /* nothing is accepted, so now test if cargo is produced */
208 AddProducedCargo(tile, dat);
209 if (!has_town_cargo(dat)) {
210 /* still don't have town cargo, so give up */
211 return ZONING_INVALID_SPRITE_ID;
215 StationFinder stations(TileArea(tile, 1, 1));
217 for (const Station *st : stations.GetStations()) {
218 if (st->owner == owner) {
219 return ZONING_INVALID_SPRITE_ID;
223 return SPR_ZONING_INNER_HIGHLIGHT_RED;
227 * Detect whether an industry is unserved by a station of owner.
229 * @param TileIndex tile
230 * @param Owner owner
231 * @return red if unserved, orange if only accepting, nothing if served or not
232 * a building
234 SpriteID TileZoneCheckUnservedIndustriesEvaluation(TileIndex tile, Owner owner)
236 if (IsTileType(tile, MP_INDUSTRY)) {
237 const Industry *ind = Industry::GetByTile(tile);
238 if (ind->neutral_station != nullptr) return ZONING_INVALID_SPRITE_ID;
240 for (const Station *st : ind->stations_near) {
241 if (st->owner == owner) {
242 if (st->facilities & (~(FACIL_BUS_STOP | FACIL_TRUCK_STOP)) || st->facilities == (FACIL_BUS_STOP | FACIL_TRUCK_STOP)) {
243 return ZONING_INVALID_SPRITE_ID;
244 } else if (st->facilities & (FACIL_BUS_STOP | FACIL_TRUCK_STOP)) {
245 for (const auto &p : ind->Produced()) {
246 if (p.cargo != INVALID_CARGO && st->facilities & (IsCargoInClass(p.cargo, CC_PASSENGERS) ? FACIL_BUS_STOP : FACIL_TRUCK_STOP)) {
247 return ZONING_INVALID_SPRITE_ID;
250 for (const auto &a : ind->Accepted()) {
251 if (a.cargo != INVALID_CARGO && st->facilities & (IsCargoInClass(a.cargo, CC_PASSENGERS) ? FACIL_BUS_STOP : FACIL_TRUCK_STOP)) {
252 return ZONING_INVALID_SPRITE_ID;
259 return SPR_ZONING_INNER_HIGHLIGHT_RED;
262 return ZONING_INVALID_SPRITE_ID;
266 * Detect whether a tile is a restricted signal tile
268 * @param TileIndex tile
269 * @param Owner owner
270 * @return red if a restricted signal, nothing otherwise
272 SpriteID TileZoneCheckTraceRestrictEvaluation(TileIndex tile, Owner owner)
274 if (IsTileType(tile, MP_RAILWAY) && HasSignals(tile) && IsRestrictedSignal(tile)) {
275 return SPR_ZONING_INNER_HIGHLIGHT_RED;
277 if (IsTunnelBridgeWithSignalSimulation(tile) && IsTunnelBridgeRestrictedSignal(tile)) {
278 return SPR_ZONING_INNER_HIGHLIGHT_RED;
281 return ZONING_INVALID_SPRITE_ID;
285 * Detect whether a tile is a restricted signal tile
287 * @param TileIndex tile
288 * @param Owner owner
289 * @return red if a restricted signal, nothing otherwise
291 inline SpriteID TileZoneCheckRoadGridEvaluation(TileIndex tile, uint grid_size)
293 const bool x_grid = (TileX(tile) % grid_size == 0);
294 const bool y_grid = (TileY(tile) % grid_size == 0);
295 if (x_grid || y_grid) {
296 return SPR_ZONING_INNER_HIGHLIGHT_LIGHT_BLUE;
297 } else {
298 return ZONING_INVALID_SPRITE_ID;
303 * Detect whether a tile is a one way road
305 * @param TileIndex tile
306 * @param Owner owner
307 * @return red if a restricted signal, nothing otherwise
309 inline SpriteID TileZoneCheckOneWayRoadEvaluation(TileIndex tile)
311 if (!MayHaveRoad(tile)) return ZONING_INVALID_SPRITE_ID;
313 RoadCachedOneWayState rcows = GetRoadCachedOneWayState(tile);
314 switch (rcows) {
315 default:
316 return ZONING_INVALID_SPRITE_ID;
317 case RCOWS_NO_ACCESS:
318 return SPR_ZONING_INNER_HIGHLIGHT_RED;
319 case RCOWS_NON_JUNCTION_A:
320 case RCOWS_NON_JUNCTION_B:
321 if (IsTileType(tile, MP_STATION)) {
322 return SPR_ZONING_INNER_HIGHLIGHT_GREEN;
323 } else if (IsNormalRoadTile(tile) && GetDisallowedRoadDirections(tile) != DRD_NONE) {
324 return SPR_ZONING_INNER_HIGHLIGHT_LIGHT_BLUE;
325 } else if (IsRoadBridgeTile(tile) && GetBridgeDisallowedRoadDirections(tile) != DRD_NONE) {
326 return SPR_ZONING_INNER_HIGHLIGHT_LIGHT_BLUE;
327 } else {
328 return SPR_ZONING_INNER_HIGHLIGHT_PURPLE;
330 case RCOWS_SIDE_JUNCTION:
331 return SPR_ZONING_INNER_HIGHLIGHT_ORANGE;
332 case RCOWS_SIDE_JUNCTION_NO_EXIT:
333 return SPR_ZONING_INNER_HIGHLIGHT_YELLOW;
337 inline SpriteID TileZoneDebugWaterFlood(TileIndex tile)
339 if (IsNonFloodingWaterTile(tile)) {
340 return SPR_ZONING_INNER_HIGHLIGHT_YELLOW;
342 return ZONING_INVALID_SPRITE_ID;
345 inline SpriteID TileZoneDebugWaterRegion(TileIndex tile)
347 extern uint GetWaterRegionTileDebugColourIndex(TileIndex tile);
348 uint colour_index = GetWaterRegionTileDebugColourIndex(tile);
349 if (colour_index == 0) {
350 return ZONING_INVALID_SPRITE_ID;
351 } else {
352 return std::min<SpriteID>(SPR_ZONING_INNER_HIGHLIGHT_RED + colour_index - 1, SPR_ZONING_INNER_HIGHLIGHT_YELLOW);
356 inline SpriteID TileZoneDebugTropicZone(TileIndex tile)
358 switch (GetTropicZone(tile)) {
359 case TROPICZONE_DESERT:
360 return SPR_ZONING_INNER_HIGHLIGHT_YELLOW;
361 case TROPICZONE_RAINFOREST:
362 return SPR_ZONING_INNER_HIGHLIGHT_LIGHT_BLUE;
363 default:
364 return ZONING_INVALID_SPRITE_ID;
368 inline SpriteID TileZoneDebugAnimatedTile(TileIndex tile)
370 if (_animated_tiles.find(tile) != _animated_tiles.end()) {
371 return SPR_ZONING_INNER_HIGHLIGHT_YELLOW;
373 return ZONING_INVALID_SPRITE_ID;
377 * General evaluation function; calls all the other functions depending on
378 * evaluation mode.
380 * @param TileIndex tile
381 * Tile to be evaluated.
382 * @param Owner owner
383 * The current player
384 * @param ZoningEvaluationMode ev_mode
385 * The current evaluation mode.
386 * @return The colour returned by the evaluation functions (none if no ev_mode).
388 SpriteID TileZoningSpriteEvaluation(TileIndex tile, Owner owner, ZoningEvaluationMode ev_mode)
390 switch (ev_mode) {
391 case ZEM_CAN_BUILD: return TileZoneCheckBuildEvaluation(tile, owner);
392 case ZEM_AUTHORITY: return TileZoneCheckOpinionEvaluation(tile, owner);
393 case ZEM_STA_CATCH: return TileZoneCheckStationCatchmentEvaluation(tile, owner, false);
394 case ZEM_STA_CATCH_WIN: return TileZoneCheckStationCatchmentEvaluation(tile, owner, true);
395 case ZEM_BUL_UNSER: return TileZoneCheckUnservedBuildingsEvaluation(tile, owner);
396 case ZEM_IND_UNSER: return TileZoneCheckUnservedIndustriesEvaluation(tile, owner);
397 case ZEM_TRACERESTRICT: return TileZoneCheckTraceRestrictEvaluation(tile, owner);
398 case ZEM_2x2_GRID: return TileZoneCheckRoadGridEvaluation(tile, 3);
399 case ZEM_3x3_GRID: return TileZoneCheckRoadGridEvaluation(tile, 4);
400 case ZEM_ONE_WAY_ROAD: return TileZoneCheckOneWayRoadEvaluation(tile);
402 case ZEM_DBG_WATER_FLOOD: return TileZoneDebugWaterFlood(tile);
403 case ZEM_DBG_WATER_REGION: return TileZoneDebugWaterRegion(tile);
404 case ZEM_DBG_TROPIC_ZONE: return TileZoneDebugTropicZone(tile);
405 case ZEM_DBG_ANIMATED_TILE: return TileZoneDebugAnimatedTile(tile);
407 default: return ZONING_INVALID_SPRITE_ID;
411 inline SpriteID TileZoningSpriteEvaluationCached(TileIndex tile, Owner owner, ZoningEvaluationMode ev_mode, bool is_inner)
413 if (owner == COMPANY_SPECTATOR && (ev_mode == ZEM_CAN_BUILD || (ev_mode >= ZEM_STA_CATCH && ev_mode <= ZEM_IND_UNSER))) return ZONING_INVALID_SPRITE_ID;
414 if (ev_mode == ZEM_BUL_UNSER && !IsTileType(tile, MP_HOUSE)) return ZONING_INVALID_SPRITE_ID;
415 if (ev_mode == ZEM_IND_UNSER && !IsTileType(tile, MP_INDUSTRY)) return ZONING_INVALID_SPRITE_ID;
416 if (ev_mode >= ZEM_STA_CATCH && ev_mode <= ZEM_IND_UNSER) {
417 // cacheable
418 btree::btree_set<uint32_t> &cache = is_inner ? _zoning_cache_inner : _zoning_cache_outer;
419 auto iter = cache.lower_bound(tile << 3);
420 if (iter != cache.end() && *iter >> 3 == tile) {
421 switch (*iter & 7) {
422 case 0: return ZONING_INVALID_SPRITE_ID;
423 case 1: return SPR_ZONING_INNER_HIGHLIGHT_RED;
424 case 2: return SPR_ZONING_INNER_HIGHLIGHT_ORANGE;
425 case 3: return SPR_ZONING_INNER_HIGHLIGHT_BLACK;
426 case 4: return SPR_ZONING_INNER_HIGHLIGHT_LIGHT_BLUE;
427 default: NOT_REACHED();
429 } else {
430 SpriteID s = TileZoningSpriteEvaluation(tile, owner, ev_mode);
431 uint val = tile << 3;
432 switch (s) {
433 case ZONING_INVALID_SPRITE_ID: val |= 0; break;
434 case SPR_ZONING_INNER_HIGHLIGHT_RED: val |= 1; break;
435 case SPR_ZONING_INNER_HIGHLIGHT_ORANGE: val |= 2; break;
436 case SPR_ZONING_INNER_HIGHLIGHT_BLACK: val |= 3; break;
437 case SPR_ZONING_INNER_HIGHLIGHT_LIGHT_BLUE: val |= 4; break;
438 default: NOT_REACHED();
440 cache.insert(iter, val);
441 return s;
443 } else {
444 return TileZoningSpriteEvaluation(tile, owner, ev_mode);
449 * Draw the the zoning on the tile.
451 * @param TileInfo ti
452 * the tile to draw on.
454 void DrawTileZoning(const TileInfo *ti)
456 if (IsTileType(ti->tile, MP_VOID) || _game_mode != GM_NORMAL) {
457 return;
460 if (_zoning.outer != ZEM_NOTHING) {
461 const SpriteID colour = TileZoningSpriteEvaluationCached(ti->tile, _local_company, _zoning.outer, false);
463 if (colour != ZONING_INVALID_SPRITE_ID) {
464 DrawTileSelectionRect(ti, colour);
468 if (_zoning.inner != ZEM_NOTHING) {
469 const SpriteID colour = TileZoningSpriteEvaluationCached(ti->tile, _local_company, _zoning.inner, true);
471 if (colour != ZONING_INVALID_SPRITE_ID) {
472 SpriteID sprite = SPR_ZONING_INNER_HIGHLIGHT_BASE;
474 if (IsHalftileSlope(ti->tileh)) {
475 const int INF = 1000;
476 static const SubSprite sub_sprites[4] = {
477 { -INF , -INF , 32 - 33, INF }, // CORNER_W, clip 33 pixels from right
478 { -INF , 0 + 22, INF , INF }, // CORNER_S, clip 22 pixels from top
479 { -31 + 34, -INF , INF , INF }, // CORNER_E, clip 34 pixels from left
480 { -INF , -INF , INF , 30 - 8 } // CORNER_N, clip 8 pixels from bottom
483 DrawSelectionSprite(sprite, colour, ti, 7 + TILE_HEIGHT, FOUNDATION_PART_HALFTILE, 0, 0, &(sub_sprites[GetHalftileSlopeCorner(ti->tileh)]));
484 } else {
485 sprite += SlopeToSpriteOffset(ti->tileh);
487 DrawSelectionSprite(sprite, colour, ti, 7, FOUNDATION_PART_NORMAL);
492 static uint GetZoningModeDependantStationCoverageRadius(const Station *st, ZoningEvaluationMode ev_mode)
494 switch (ev_mode) {
495 case ZEM_STA_CATCH: return st->GetCatchmentRadius();
496 case ZEM_STA_CATCH_WIN: return st->GetCatchmentRadius();
497 case ZEM_BUL_UNSER: return st->GetCatchmentRadius();
498 case ZEM_IND_UNSER: return st->GetCatchmentRadius() + 10; // this is to wholly update industries partially within the region
499 default: return 0;
504 * Mark dirty the coverage area around a station if the current zoning mode depends on station coverage
506 * @param const Station *st
507 * The station to use
509 void ZoningMarkDirtyStationCoverageArea(const Station *st, ZoningModeMask mask)
511 if (st->rect.IsEmpty()) return;
513 uint outer_radius = mask & ZMM_OUTER ? GetZoningModeDependantStationCoverageRadius(st, _zoning.outer) : 0;
514 uint inner_radius = mask & ZMM_INNER ? GetZoningModeDependantStationCoverageRadius(st, _zoning.inner) : 0;
515 uint radius = std::max<uint>(outer_radius, inner_radius);
517 extern const Station *_viewport_highlight_station;
518 if (_viewport_highlight_station == st) {
519 radius = std::max<uint>(radius, st->GetCatchmentRadius());
522 if (radius > 0) {
523 Rect rect = st->GetCatchmentRectUsingRadius(radius);
524 for (int y = rect.top; y <= rect.bottom; y++) {
525 for (int x = rect.left; x <= rect.right; x++) {
526 MarkTileDirtyByTile(TileXY(x, y), VMDF_NOT_MAP_MODE);
529 auto invalidate_cache_rect = [&](btree::btree_set<uint32_t> &cache) {
530 for (int y = rect.top; y <= rect.bottom; y++) {
531 auto iter = cache.lower_bound(TileXY(rect.left, y) << 3);
532 auto end_iter = iter;
533 uint end = (TileXY(rect.right, y) + 1) << 3;
534 while (end_iter != cache.end() && *end_iter < end) ++end_iter;
535 cache.erase(iter, end_iter);
538 if (outer_radius) invalidate_cache_rect(_zoning_cache_outer);
539 if (inner_radius) invalidate_cache_rect(_zoning_cache_inner);
543 void ZoningStationWindowOpenClose(const Station *st)
545 ZoningModeMask mask = ZMM_NOTHING;
546 if (_zoning.inner == ZEM_STA_CATCH_WIN) mask |= ZMM_INNER;
547 if (_zoning.outer == ZEM_STA_CATCH_WIN) mask |= ZMM_OUTER;
548 if (mask != ZMM_NOTHING) ZoningMarkDirtyStationCoverageArea(st, mask);
551 void ZoningTownAuthorityRatingChange()
553 ZoningModeMask mask = ZMM_NOTHING;
554 if (_zoning.inner == ZEM_AUTHORITY) mask |= ZMM_INNER;
555 if (_zoning.outer == ZEM_AUTHORITY) mask |= ZMM_OUTER;
556 if (mask != ZMM_NOTHING) {
557 MarkWholeNonMapViewportsDirty();
561 void ClearZoningCaches()
563 _zoning_cache_inner.clear();
564 _zoning_cache_outer.clear();
567 void SetZoningMode(bool inner, ZoningEvaluationMode mode)
569 ZoningEvaluationMode &current_mode = inner ? _zoning.inner : _zoning.outer;
570 btree::btree_set<uint32_t> &cache = inner ? _zoning_cache_inner : _zoning_cache_outer;
572 if (current_mode == mode) return;
574 current_mode = mode;
575 cache.clear();
576 MarkWholeNonMapViewportsDirty();
577 PostZoningModeChange();
580 void PostZoningModeChange()
582 extern bool _mark_tile_dirty_on_road_cache_one_way_state_update;
583 _mark_tile_dirty_on_road_cache_one_way_state_update = (_zoning.inner == ZEM_ONE_WAY_ROAD) || (_zoning.outer == ZEM_ONE_WAY_ROAD);