Increase the number of road and tram subtypes to 32.
[openttd-joker.git] / src / zoning_cmd.cpp
blobc2acbf575080bd615d38fa88da08376c330b0bcf
2 /* $Id$ */
4 /*
5 * This file is part of OpenTTD.
6 * 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.
7 * 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.
8 * 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/>.
9 */
11 /** @file zoning_cmd.cpp */
13 #include "stdafx.h"
15 #include "company_func.h"
16 #include "gfx_func.h"
17 #include "industry.h"
18 #include "map_func.h"
19 #include "openttd.h"
20 #include "station_base.h"
21 #include "station_type.h"
22 #include "town.h"
23 #include "town_map.h"
24 #include "tracerestrict.h"
25 #include "viewport_func.h"
26 #include "zoning.h"
28 #include "table/sprites.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> _zoning_cache_inner;
36 static btree::btree_set<uint32> _zoning_cache_outer;
38 //! Enumeration of multi-part foundations.
39 enum FoundationPart
41 FOUNDATION_PART_NONE = 0xFF,
42 //!< Neither foundation nor groundsprite drawn yet.
43 FOUNDATION_PART_NORMAL = 0,
44 //!< First part (normal foundation or no foundation)
45 FOUNDATION_PART_HALFTILE = 1,
46 //!< Second part (halftile foundation)
47 FOUNDATION_PART_END
50 void DrawTileSelectionRect(const TileInfo* ti, PaletteID pal);
51 void DrawSelectionSprite(SpriteID image, PaletteID pal, const TileInfo* ti, int z_offset, FoundationPart foundation_part);
53 //! Detect whether this area is within the acceptance of any station.
54 //!
55 //! @param area the area to search by
56 //! @param owner the owner of the stations which we need to match again
57 //! @param facility_mask one or more facilities in the mask must be present for a station to be used
58 //! @return true if a station is found
59 bool IsAreaWithinAcceptanceZoneOfStation(TileArea area, Owner owner, StationFacility facility_mask)
61 const int catchment = _settings_game.station.station_spread + (_settings_game.station.modified_catchment ? MAX_CATCHMENT : CA_UNMODIFIED);
63 StationFinder station_finder(TileArea(TileXY(TileX(area.tile) - (catchment / 2), TileY(area.tile) - (catchment / 2)),
64 TileX(area.tile) + area.w + catchment, TileY(area.tile) + area.h + catchment));
66 for (auto iter = station_finder.GetStations()->Begin(); iter != station_finder.GetStations()->End(); ++iter) {
67 const Station* station = *iter;
69 if (station->owner != owner || !(station->facilities & facility_mask)) continue;
71 const Rect rect = station->GetCatchmentRect();
73 return TileArea(TileXY(rect.left, rect.top), TileXY(rect.right, rect.bottom)).Intersects(area);
76 return false;
79 //! Detect whether this tile is within the acceptance of any station.
80 //!
81 //! @param tile the tile to search by
82 //! @param owner the owner of the stations
83 //! @param facility_mask one or more facilities in the mask must be present for a station to be used
84 //! @return true if a station is found
85 bool IsTileWithinAcceptanceZoneOfStation(TileIndex tile, Owner owner, StationFacility facility_mask)
87 const int catchment = _settings_game.station.station_spread + (_settings_game.station.modified_catchment ? MAX_CATCHMENT : CA_UNMODIFIED);
89 StationFinder station_finder(TileArea(TileXY(TileX(tile) - (catchment / 2), TileY(tile) - (catchment / 2)),
90 catchment, catchment));
92 for (auto iter = station_finder.GetStations()->Begin(); iter != station_finder.GetStations()->End(); ++iter) {
93 const Station* station = *iter;
95 if (station->owner != owner || !(station->facilities & facility_mask)) continue;
97 const Rect rect = station->GetCatchmentRect();
99 if (uint(rect.left) <= TileX(tile) && TileX(tile) <= uint(rect.right)
100 && uint(rect.top) <= TileY(tile) && TileY(tile) <= uint(rect.bottom)) {
101 return true;
105 return false;
108 //! Check whether the player can build in tile.
109 //!
110 //! @param tile the tile to check
111 //! @param owner the company to check for
112 //! @return red if they cannot
113 SpriteID TileZoneCheckBuildEvaluation(TileIndex tile, Owner owner)
115 // Let's first check for the obvious things you cannot build on.
116 switch (GetTileType(tile)) {
117 case MP_INDUSTRY:
118 case MP_OBJECT:
119 case MP_STATION:
120 case MP_HOUSE:
121 case MP_TUNNELBRIDGE:
122 return SPR_ZONING_INNER_HIGHLIGHT_RED;
124 // There are only two things you can own (or some else
125 // can own) that you can still build on. i.e. roads and
126 // railways.
127 // TODO
128 // Add something more intelligent, check what tool the
129 // user is currently using (and if none, assume some
130 // standards), then check it against if owned by some-
131 // one else (e.g. railway on someone else's road).
132 // While that being said, it should also check if it
133 // is not possible to build railway/road on someone
134 // else's/your own road/railway (e.g. the railway track
135 // is curved or a cross).
136 case MP_ROAD:
137 case MP_RAILWAY:
138 if (GetTileOwner(tile) != owner) {
139 return SPR_ZONING_INNER_HIGHLIGHT_RED;
140 } else {
141 return ZONING_INVALID_SPRITE_ID;
144 default:
145 return ZONING_INVALID_SPRITE_ID;
149 //! Check the opinion of the local authority in the tile.
150 //!
151 //! @param tile the tile to check
152 //! @param owner the company to check for
153 //! @return black if no opinion, orange if bad,
154 //! light blue if good or invalid if no town
155 SpriteID TileZoneCheckOpinionEvaluation(TileIndex tile, Owner owner)
157 int opinion = 0; // 0: No town, 1: No opinion, 2: Bad, 3: Good
158 Town* town = ClosestTownFromTile(tile, _settings_game.economy.dist_local_authority);
160 if (town != nullptr) {
161 if (HasBit(town->have_ratings, owner)) {
162 opinion = (town->ratings[owner] > 0) ? 3 : 2;
163 } else {
164 opinion = 1;
168 switch (opinion) {
169 case 1:
170 return SPR_ZONING_INNER_HIGHLIGHT_BLACK; // No opinion
171 case 2:
172 return SPR_ZONING_INNER_HIGHLIGHT_ORANGE; // Bad
173 case 3:
174 return SPR_ZONING_INNER_HIGHLIGHT_LIGHT_BLUE; // Good
175 default:
176 return ZONING_INVALID_SPRITE_ID; // No town
180 //! Detect whether the tile is within the catchment zone of a station.
181 //!
182 //! @param tile the tile to check
183 //! @param owner the company owning the stations
184 //! @return black if within, light blue if only in acceptance zone
185 //! and nothing if no nearby station.
186 SpriteID TileZoneCheckStationCatchmentEvaluation(TileIndex tile, Owner owner)
188 // Never on a station.
189 if (IsTileType(tile, MP_STATION)) {
190 return ZONING_INVALID_SPRITE_ID;
193 // For provided goods
194 StationFinder stations(TileArea(tile, 1, 1));
196 for (auto iter = stations.GetStations()->Begin(); iter != stations.GetStations()->End(); ++iter) {
197 const Station* station = *iter;
198 if (station->owner == owner) {
199 return SPR_ZONING_INNER_HIGHLIGHT_BLACK;
203 // For accepted goods
204 if (IsTileWithinAcceptanceZoneOfStation(tile, owner, ~FACIL_NONE)) {
205 return SPR_ZONING_INNER_HIGHLIGHT_LIGHT_BLUE;
208 return ZONING_INVALID_SPRITE_ID;
211 //! Detect whether a building is unserved by a station of owner.
212 //!
213 //! @param tile the tile to check
214 //! @param owner the company to check for
215 //! @return red if unserved, orange if only accepting, nothing if served or not a building
216 SpriteID TileZoneCheckUnservedBuildingsEvaluation(TileIndex tile, Owner owner)
218 if (!IsTileType(tile, MP_HOUSE)) {
219 return ZONING_INVALID_SPRITE_ID;
222 CargoArray dat;
224 memset(&dat, 0, sizeof(dat));
225 AddAcceptedCargo(tile, dat, nullptr);
227 if (dat[CT_MAIL] + dat[CT_PASSENGERS] == 0) {
228 // Nothing is accepted, so now test if cargo is produced
229 AddProducedCargo(tile, dat);
231 if (dat[CT_MAIL] + dat[CT_PASSENGERS] == 0) {
232 // Total is still 0, so give up
233 return ZONING_INVALID_SPRITE_ID;
237 StationFinder stations(TileArea(tile, 1, 1));
239 for (auto iter = stations.GetStations()->Begin(); iter != stations.GetStations()->End(); ++iter) {
240 const Station* station = *iter;
241 if (station->owner == owner) {
242 return ZONING_INVALID_SPRITE_ID;
246 // For accepted goods
247 if (IsTileWithinAcceptanceZoneOfStation(tile, owner, ~FACIL_NONE)) {
248 return SPR_ZONING_INNER_HIGHLIGHT_ORANGE;
251 return SPR_ZONING_INNER_HIGHLIGHT_RED;
254 //! Detect whether an industry is unserved by a station of owner.
255 //!
256 //! @param tile the tile to check
257 //! @param owner the company to check for
258 //! @return red if unserved, orange if only accepting, nothing if served or not a building
259 SpriteID TileZoneCheckUnservedIndustriesEvaluation(TileIndex tile, Owner owner)
261 if (IsTileType(tile, MP_INDUSTRY)) {
262 Industry* industry = Industry::GetByTile(tile);
263 StationFinder stations(industry->location);
265 for (auto iter = stations.GetStations()->Begin(); iter != stations.GetStations()->End(); ++iter) {
266 const Station* station = *iter;
267 if (station->owner == owner && station->facilities & (~FACIL_BUS_STOP)) {
268 return ZONING_INVALID_SPRITE_ID;
272 // For accepted goods
273 if (IsAreaWithinAcceptanceZoneOfStation(industry->location, owner, ~FACIL_BUS_STOP)) {
274 return SPR_ZONING_INNER_HIGHLIGHT_ORANGE;
277 return SPR_ZONING_INNER_HIGHLIGHT_RED;
280 return ZONING_INVALID_SPRITE_ID;
283 //! Detect whether a tile is a restricted signal tile
284 //!
285 //! @param tile the tile to check
286 //! @param owner the company to check for
287 //! @return red if a restricted signal, nothing otherwise
288 SpriteID TileZoneCheckTraceRestrictEvaluation(TileIndex tile, Owner owner)
290 if (IsTileType(tile, MP_RAILWAY) && HasSignals(tile) && IsRestrictedSignal(tile)) {
291 return SPR_ZONING_INNER_HIGHLIGHT_RED;
294 return ZONING_INVALID_SPRITE_ID;
297 //! General evaluation function; calls all the other functions depending on
298 //! evaluation mode.
299 //!
300 //! @param tile Tile to be evaluated.
301 //! @param owner The current player
302 //! @param evaluation_mode The current evaluation mode.
303 //! @return The colour returned by the evaluation functions (none if no evaluation_mode).
304 SpriteID TileZoningSpriteEvaluation(TileIndex tile, Owner owner, ZoningEvaluationMode evaluation_mode)
306 switch (evaluation_mode) {
307 case ZEM_CAN_BUILD:
308 return TileZoneCheckBuildEvaluation(tile, owner);
309 case ZEM_AUTHORITY:
310 return TileZoneCheckOpinionEvaluation(tile, owner);
311 case ZEM_STA_CATCH:
312 return TileZoneCheckStationCatchmentEvaluation(tile, owner);
313 case ZEM_BUL_UNSER:
314 return TileZoneCheckUnservedBuildingsEvaluation(tile, owner);
315 case ZEM_IND_UNSER:
316 return TileZoneCheckUnservedIndustriesEvaluation(tile, owner);
317 case ZEM_TRACERESTRICT:
318 return TileZoneCheckTraceRestrictEvaluation(tile, owner);
319 default:
320 return ZONING_INVALID_SPRITE_ID;
324 inline SpriteID TileZoningSpriteEvaluationCached(TileIndex tile, Owner owner, ZoningEvaluationMode ev_mode, bool is_inner)
326 if (ev_mode == ZEM_BUL_UNSER && !IsTileType(tile, MP_HOUSE)) return ZONING_INVALID_SPRITE_ID;
327 if (ev_mode == ZEM_IND_UNSER && !IsTileType(tile, MP_INDUSTRY)) return ZONING_INVALID_SPRITE_ID;
329 if (ev_mode >= ZEM_STA_CATCH && ev_mode <= ZEM_IND_UNSER) {
330 // Cacheable
331 btree::btree_set<uint32>& cache = is_inner ? _zoning_cache_inner : _zoning_cache_outer;
332 const auto lower_bound = cache.lower_bound(tile << 3);
334 if (lower_bound != cache.end() && *lower_bound >> 3 == tile) {
335 switch (*lower_bound & 7) {
336 case 0:
337 return ZONING_INVALID_SPRITE_ID;
338 case 1:
339 return SPR_ZONING_INNER_HIGHLIGHT_RED;
340 case 2:
341 return SPR_ZONING_INNER_HIGHLIGHT_ORANGE;
342 case 3:
343 return SPR_ZONING_INNER_HIGHLIGHT_BLACK;
344 case 4:
345 return SPR_ZONING_INNER_HIGHLIGHT_LIGHT_BLUE;
346 default: NOT_REACHED();
350 const SpriteID sprite = TileZoningSpriteEvaluation(tile, owner, ev_mode);
351 uint val = tile << 3;
353 switch (sprite) {
354 case ZONING_INVALID_SPRITE_ID:
355 val |= 0;
356 break;
357 case SPR_ZONING_INNER_HIGHLIGHT_RED:
358 val |= 1;
359 break;
360 case SPR_ZONING_INNER_HIGHLIGHT_ORANGE:
361 val |= 2;
362 break;
363 case SPR_ZONING_INNER_HIGHLIGHT_BLACK:
364 val |= 3;
365 break;
366 case SPR_ZONING_INNER_HIGHLIGHT_LIGHT_BLUE:
367 val |= 4;
368 break;
369 default: NOT_REACHED();
372 cache.insert(lower_bound, val);
374 return sprite;
377 return TileZoningSpriteEvaluation(tile, owner, ev_mode);
380 //! Draw the the zoning on the tile.
381 //!
382 //! @param tile_info the tile to draw on.
383 void DrawTileZoning(const TileInfo* tile_info)
385 if (IsTileType(tile_info->tile, MP_VOID) || _game_mode != GM_NORMAL) {
386 return;
389 if (_zoning.outer != ZEM_NOTHING) {
390 const auto colour = TileZoningSpriteEvaluationCached(tile_info->tile, _local_company, _zoning.outer, false);
392 if (colour != ZONING_INVALID_SPRITE_ID) {
393 DrawTileSelectionRect(tile_info, colour);
397 if (_zoning.inner != ZEM_NOTHING) {
398 const auto colour = TileZoningSpriteEvaluationCached(tile_info->tile, _local_company, _zoning.inner, true);
400 if (colour != ZONING_INVALID_SPRITE_ID) {
401 auto sprite = SPR_ZONING_INNER_HIGHLIGHT_BASE;
403 if (IsHalftileSlope(tile_info->tileh)) {
404 DrawSelectionSprite(sprite, colour, tile_info, 7 + TILE_HEIGHT, FOUNDATION_PART_HALFTILE);
405 } else {
406 sprite += SlopeToSpriteOffset(tile_info->tileh);
409 DrawSelectionSprite(sprite, colour, tile_info, 7, FOUNDATION_PART_NORMAL);
414 static uint GetZoningModeDependantStationCoverageRadius(const Station* station, ZoningEvaluationMode evaluation_mode)
416 switch (evaluation_mode) {
417 case ZEM_STA_CATCH:
418 return station->GetCatchmentRadius();
419 case ZEM_BUL_UNSER:
420 return station->GetCatchmentRadius();
421 case ZEM_IND_UNSER:
422 // This is to wholly update industries partially within the region
423 return station->GetCatchmentRadius() + 10;
424 default:
425 return 0;
429 //! Mark dirty the coverage area around a station if the current zoning mode depends on station coverage
430 //!
431 //! @param station The station to use
432 //! @param mask The zoning mode mask
433 void ZoningMarkDirtyStationCoverageArea(const Station* station, ZoningModeMask mask)
435 if (station->rect.IsEmpty()) return;
437 const uint outer_radius = mask & ZMM_OUTER ? GetZoningModeDependantStationCoverageRadius(station, _zoning.outer) : 0;
438 const uint inner_radius = mask & ZMM_INNER ? GetZoningModeDependantStationCoverageRadius(station, _zoning.inner) : 0;
439 const uint radius = max<uint>(outer_radius, inner_radius);
441 if (radius > 0) {
442 Rect rect = station->GetCatchmentRectUsingRadius(radius);
443 for (int y = rect.top; y <= rect.bottom; y++) {
444 for (int x = rect.left; x <= rect.right; x++) {
445 MarkTileDirtyByTile(TileXY(x, y));
448 const auto invalidate_cache_rect = [&](btree::btree_set<uint32>& cache) {
449 for (int y = rect.top; y <= rect.bottom; y++) {
450 const auto lower_bound = cache.lower_bound(TileXY(rect.left, y) << 3);
451 auto end_iter = lower_bound;
452 const uint end = TileXY(rect.right, y) << 3;
453 while (end_iter != cache.end() && *end_iter < end) ++end_iter;
454 cache.erase(lower_bound, end_iter);
458 if (outer_radius) invalidate_cache_rect(_zoning_cache_outer);
459 if (inner_radius) invalidate_cache_rect(_zoning_cache_inner);
463 void ClearZoningCaches()
465 _zoning_cache_inner.clear();
466 _zoning_cache_outer.clear();
469 void SetZoningMode(bool inner, ZoningEvaluationMode mode)
471 ZoningEvaluationMode& current_mode = inner ? _zoning.inner : _zoning.outer;
472 btree::btree_set<uint32>& cache = inner ? _zoning_cache_inner : _zoning_cache_outer;
474 if (current_mode == mode) return;
476 current_mode = mode;
477 cache.clear();
478 MarkWholeScreenDirty();