Show snow on depot tiles. (Looks weird my ass)
[openttd-joker.git] / src / zoning_cmd.cpp
blobceed9c92bb3b1fd5bd50065f023ce01095fb3d86
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"
14 #include "openttd.h"
15 #include "station_type.h"
16 #include "station_base.h"
17 #include "industry.h"
18 #include "gfx_func.h"
19 #include "viewport_func.h"
20 #include "map_func.h"
21 #include "company_func.h"
22 #include "town_map.h"
23 #include "table/sprites.h"
24 #include "station_func.h"
25 #include "station_map.h"
26 #include "tracerestrict.h"
27 #include "town.h"
28 #include "zoning.h"
29 #include "3rdparty/cpp-btree/btree_set.h"
31 Zoning _zoning;
32 static const SpriteID ZONING_INVALID_SPRITE_ID = UINT_MAX;
34 static btree::btree_set<uint32> _zoning_cache_inner;
35 static btree::btree_set<uint32> _zoning_cache_outer;
37 /**
38 * Draw the zoning sprites.
40 * @param SpriteID image
41 * the image
42 * @param SpriteID colour
43 * the colour of the zoning
44 * @param TileInfo ti
45 * the tile
47 void DrawZoningSprites(SpriteID image, SpriteID colour, const TileInfo *ti)
49 if (colour != ZONING_INVALID_SPRITE_ID) {
50 AddSortableSpriteToDraw(image + ti->tileh, colour, ti->x, ti->y, 0x10, 0x10, 1, ti->z + 7);
54 /**
55 * Detect whether this area is within the acceptance of any station.
57 * @param TileArea area
58 * the area to search by
59 * @param Owner owner
60 * the owner of the stations which we need to match again
61 * @param StationFacility facility_mask
62 * one or more facilities in the mask must be present for a station to be used
63 * @return true if a station is found
65 bool IsAreaWithinAcceptanceZoneOfStation(TileArea area, Owner owner, StationFacility facility_mask)
67 int catchment = _settings_game.station.station_spread + (_settings_game.station.modified_catchment ? MAX_CATCHMENT : CA_UNMODIFIED);
69 StationFinder morestations(TileArea(TileXY(TileX(area.tile) - (catchment / 2), TileY(area.tile) - (catchment / 2)),
70 TileX(area.tile) + area.w + catchment, TileY(area.tile) + area.h + catchment));
72 for (Station * const *st_iter = morestations.GetStations()->Begin(); st_iter != morestations.GetStations()->End(); ++st_iter) {
73 const Station *st = *st_iter;
74 if (st->owner != owner || !(st->facilities & facility_mask)) continue;
75 Rect rect = st->GetCatchmentRect();
76 return TileArea(TileXY(rect.left, rect.top), TileXY(rect.right, rect.bottom)).Intersects(area);
79 return false;
82 /**
83 * Detect whether this tile is within the acceptance of any station.
85 * @param TileIndex tile
86 * the tile to search by
87 * @param Owner owner
88 * the owner of the stations
89 * @param StationFacility facility_mask
90 * one or more facilities in the mask must be present for a station to be used
91 * @return true if a station is found
93 bool IsTileWithinAcceptanceZoneOfStation(TileIndex tile, Owner owner, StationFacility facility_mask)
95 int catchment = _settings_game.station.station_spread + (_settings_game.station.modified_catchment ? MAX_CATCHMENT : CA_UNMODIFIED);
97 StationFinder morestations(TileArea(TileXY(TileX(tile) - (catchment / 2), TileY(tile) - (catchment / 2)),
98 catchment, catchment));
100 for (Station * const *st_iter = morestations.GetStations()->Begin(); st_iter != morestations.GetStations()->End(); ++st_iter) {
101 const Station *st = *st_iter;
102 if (st->owner != owner || !(st->facilities & facility_mask)) continue;
103 Rect rect = st->GetCatchmentRect();
104 if ((uint)rect.left <= TileX(tile) && TileX(tile) <= (uint)rect.right
105 && (uint)rect.top <= TileY(tile) && TileY(tile) <= (uint)rect.bottom) {
106 return true;
110 return false;
114 * Check whether the player can build in tile.
116 * @param TileIndex tile
117 * @param Owner owner
118 * @return red if they cannot
120 SpriteID TileZoneCheckBuildEvaluation(TileIndex tile, Owner owner)
122 /* Let's first check for the obvious things you cannot build on */
123 switch (GetTileType(tile)) {
124 case MP_INDUSTRY:
125 case MP_OBJECT:
126 case MP_STATION:
127 case MP_HOUSE:
128 case MP_TUNNELBRIDGE:
129 return SPR_ZONING_INNER_HIGHLIGHT_RED;
131 /* There are only two things you can own (or some else
132 * can own) that you can still build on. i.e. roads and
133 * railways.
134 * @todo
135 * Add something more intelligent, check what tool the
136 * user is currently using (and if none, assume some
137 * standards), then check it against if owned by some-
138 * one else (e.g. railway on someone else's road).
139 * While that being said, it should also check if it
140 * is not possible to build railway/road on someone
141 * else's/your own road/railway (e.g. the railway track
142 * is curved or a cross).
144 case MP_ROAD:
145 case MP_RAILWAY:
146 if (GetTileOwner(tile) != owner) {
147 return SPR_ZONING_INNER_HIGHLIGHT_RED;
148 } else {
149 return ZONING_INVALID_SPRITE_ID;
152 default:
153 return ZONING_INVALID_SPRITE_ID;
158 * Check the opinion of the local authority in the tile.
160 * @param TileIndex tile
161 * @param Owner owner
162 * @return black if no opinion, orange if bad,
163 * light blue if good or invalid if no town
165 SpriteID TileZoneCheckOpinionEvaluation(TileIndex tile, Owner owner)
167 int opinion = 0; // 0: no town, 1: no opinion, 2: bad, 3: good
168 Town *town = ClosestTownFromTile(tile, _settings_game.economy.dist_local_authority);
170 if (town != nullptr) {
171 if (HasBit(town->have_ratings, owner)) {
172 opinion = (town->ratings[owner] > 0) ? 3 : 2;
173 } else {
174 opinion = 1;
178 switch (opinion) {
179 case 1: return SPR_ZONING_INNER_HIGHLIGHT_BLACK; // no opinion
180 case 2: return SPR_ZONING_INNER_HIGHLIGHT_ORANGE; // bad
181 case 3: return SPR_ZONING_INNER_HIGHLIGHT_LIGHT_BLUE; // good
182 default: return ZONING_INVALID_SPRITE_ID; // no town
187 * Detect whether the tile is within the catchment zone of a station.
189 * @param TileIndex tile
190 * @param Owner owner
191 * @return black if within, light blue if only in acceptance zone
192 * and nothing if no nearby station.
194 SpriteID TileZoneCheckStationCatchmentEvaluation(TileIndex tile, Owner owner)
196 // Never on a station.
197 if (IsTileType(tile, MP_STATION)) {
198 return ZONING_INVALID_SPRITE_ID;
201 // For provided goods
202 StationFinder stations(TileArea(tile, 1, 1));
204 for (Station * const *st_iter = stations.GetStations()->Begin(); st_iter != stations.GetStations()->End(); ++st_iter) {
205 const Station *st = *st_iter;
206 if (st->owner == owner) {
207 return SPR_ZONING_INNER_HIGHLIGHT_BLACK;
211 // For accepted goods
212 if (IsTileWithinAcceptanceZoneOfStation(tile, owner, ~FACIL_NONE)) {
213 return SPR_ZONING_INNER_HIGHLIGHT_LIGHT_BLUE;
216 return ZONING_INVALID_SPRITE_ID;
220 * Detect whether a building is unserved by a station of owner.
222 * @param TileIndex tile
223 * @param Owner owner
224 * @return red if unserved, orange if only accepting, nothing if served or not
225 * a building
227 SpriteID TileZoneCheckUnservedBuildingsEvaluation(TileIndex tile, Owner owner)
229 if (!IsTileType(tile, MP_HOUSE)) {
230 return ZONING_INVALID_SPRITE_ID;
233 CargoArray dat;
235 memset(&dat, 0, sizeof(dat));
236 AddAcceptedCargo(tile, dat, nullptr);
237 if (dat[CT_MAIL] + dat[CT_PASSENGERS] == 0) {
238 // nothing is accepted, so now test if cargo is produced
239 AddProducedCargo(tile, dat);
240 if (dat[CT_MAIL] + dat[CT_PASSENGERS] == 0) {
241 // total is still 0, so give up
242 return ZONING_INVALID_SPRITE_ID;
246 StationFinder stations(TileArea(tile, 1, 1));
248 for (Station * const *st_iter = stations.GetStations()->Begin(); st_iter != stations.GetStations()->End(); ++st_iter) {
249 const Station *st = *st_iter;
250 if (st->owner == owner) {
251 return ZONING_INVALID_SPRITE_ID;
255 // For accepted goods
256 if (IsTileWithinAcceptanceZoneOfStation(tile, owner, ~FACIL_NONE)) {
257 return SPR_ZONING_INNER_HIGHLIGHT_ORANGE;
260 return SPR_ZONING_INNER_HIGHLIGHT_RED;
264 * Detect whether an industry is unserved by a station of owner.
266 * @param TileIndex tile
267 * @param Owner owner
268 * @return red if unserved, orange if only accepting, nothing if served or not
269 * a building
271 SpriteID TileZoneCheckUnservedIndustriesEvaluation(TileIndex tile, Owner owner)
273 if (IsTileType(tile, MP_INDUSTRY)) {
274 Industry *ind = Industry::GetByTile(tile);
275 StationFinder stations(ind->location);
277 for (Station * const *st_iter = stations.GetStations()->Begin(); st_iter != stations.GetStations()->End(); ++st_iter) {
278 const Station *st = *st_iter;
279 if (st->owner == owner && st->facilities & (~FACIL_BUS_STOP)) {
280 return ZONING_INVALID_SPRITE_ID;
284 // For accepted goods
285 if (IsAreaWithinAcceptanceZoneOfStation(ind->location, owner, ~FACIL_BUS_STOP)) {
286 return SPR_ZONING_INNER_HIGHLIGHT_ORANGE;
289 return SPR_ZONING_INNER_HIGHLIGHT_RED;
292 return ZONING_INVALID_SPRITE_ID;
296 * Detect whether a tile is a restricted signal tile
298 * @param TileIndex tile
299 * @param Owner owner
300 * @return red if a restricted signal, nothing otherwise
302 SpriteID TileZoneCheckTraceRestrictEvaluation(TileIndex tile, Owner owner)
304 if (IsTileType(tile, MP_RAILWAY) && HasSignals(tile) && IsRestrictedSignal(tile)) {
305 return SPR_ZONING_INNER_HIGHLIGHT_RED;
308 return ZONING_INVALID_SPRITE_ID;
313 * General evaluation function; calls all the other functions depending on
314 * evaluation mode.
316 * @param TileIndex tile
317 * Tile to be evaluated.
318 * @param Owner owner
319 * The current player
320 * @param ZoningEvaluationMode ev_mode
321 * The current evaluation mode.
322 * @return The colour returned by the evaluation functions (none if no ev_mode).
324 SpriteID TileZoningSpriteEvaluation(TileIndex tile, Owner owner, ZoningEvaluationMode ev_mode)
326 switch (ev_mode) {
327 case ZEM_CAN_BUILD: return TileZoneCheckBuildEvaluation(tile, owner);
328 case ZEM_AUTHORITY: return TileZoneCheckOpinionEvaluation(tile, owner);
329 case ZEM_STA_CATCH: return TileZoneCheckStationCatchmentEvaluation(tile, owner);
330 case ZEM_BUL_UNSER: return TileZoneCheckUnservedBuildingsEvaluation(tile, owner);
331 case ZEM_IND_UNSER: return TileZoneCheckUnservedIndustriesEvaluation(tile, owner);
332 case ZEM_TRACERESTRICT: return TileZoneCheckTraceRestrictEvaluation(tile, owner);
333 default: return ZONING_INVALID_SPRITE_ID;
337 inline SpriteID TileZoningSpriteEvaluationCached(TileIndex tile, Owner owner, ZoningEvaluationMode ev_mode, bool is_inner)
339 if (ev_mode == ZEM_BUL_UNSER && !IsTileType(tile, MP_HOUSE)) return ZONING_INVALID_SPRITE_ID;
340 if (ev_mode == ZEM_IND_UNSER && !IsTileType(tile, MP_INDUSTRY)) return ZONING_INVALID_SPRITE_ID;
341 if (ev_mode >= ZEM_STA_CATCH && ev_mode <= ZEM_IND_UNSER) {
342 // cacheable
343 btree::btree_set<uint32> &cache = is_inner ? _zoning_cache_inner : _zoning_cache_outer;
344 auto iter = cache.lower_bound(tile << 3);
345 if (iter != cache.end() && *iter >> 3 == tile) {
346 switch (*iter & 7) {
347 case 0: return ZONING_INVALID_SPRITE_ID;
348 case 1: return SPR_ZONING_INNER_HIGHLIGHT_RED;
349 case 2: return SPR_ZONING_INNER_HIGHLIGHT_ORANGE;
350 case 3: return SPR_ZONING_INNER_HIGHLIGHT_BLACK;
351 case 4: return SPR_ZONING_INNER_HIGHLIGHT_LIGHT_BLUE;
352 default: NOT_REACHED();
354 } else {
355 SpriteID s = TileZoningSpriteEvaluation(tile, owner, ev_mode);
356 uint val = tile << 3;
357 switch (s) {
358 case ZONING_INVALID_SPRITE_ID: val |= 0; break;
359 case SPR_ZONING_INNER_HIGHLIGHT_RED: val |= 1; break;
360 case SPR_ZONING_INNER_HIGHLIGHT_ORANGE: val |= 2; break;
361 case SPR_ZONING_INNER_HIGHLIGHT_BLACK: val |= 3; break;
362 case SPR_ZONING_INNER_HIGHLIGHT_LIGHT_BLUE: val |= 4; break;
363 default: NOT_REACHED();
365 cache.insert(iter, val);
366 return s;
368 } else {
369 return TileZoningSpriteEvaluation(tile, owner, ev_mode);
374 * Draw the the zoning on the tile.
376 * @param TileInfo ti
377 * the tile to draw on.
379 void DrawTileZoning(const TileInfo *ti)
381 if (IsTileType(ti->tile, MP_VOID) || _game_mode != GM_NORMAL) {
382 return;
385 if (_zoning.outer != ZEM_NOTHING) {
386 DrawZoningSprites(SPR_SELECT_TILE, TileZoningSpriteEvaluationCached(ti->tile, _local_company, _zoning.outer, false), ti);
389 if (_zoning.inner != ZEM_NOTHING) {
390 DrawZoningSprites(SPR_ZONING_INNER_HIGHLIGHT_BASE, TileZoningSpriteEvaluationCached(ti->tile, _local_company, _zoning.inner, true), ti);
394 static uint GetZoningModeDependantStationCoverageRadius(const Station *st, ZoningEvaluationMode ev_mode)
396 switch (ev_mode) {
397 case ZEM_STA_CATCH: return st->GetCatchmentRadius();
398 case ZEM_BUL_UNSER: return st->GetCatchmentRadius();
399 case ZEM_IND_UNSER: return st->GetCatchmentRadius() + 10; // this is to wholly update industries partially within the region
400 default: return 0;
405 * Mark dirty the coverage area around a station if the current zoning mode depends on station coverage
407 * @param const Station *st
408 * The station to use
410 void ZoningMarkDirtyStationCoverageArea(const Station *st, ZoningModeMask mask)
412 if (st->rect.IsEmpty()) return;
414 uint outer_radius = mask & ZMM_OUTER ? GetZoningModeDependantStationCoverageRadius(st, _zoning.outer) : 0;
415 uint inner_radius = mask & ZMM_INNER ? GetZoningModeDependantStationCoverageRadius(st, _zoning.inner) : 0;
416 uint radius = max<uint>(outer_radius, inner_radius);
418 if (radius > 0) {
419 Rect rect = st->GetCatchmentRectUsingRadius(radius);
420 for (int y = rect.top; y <= rect.bottom; y++) {
421 for (int x = rect.left; x <= rect.right; x++) {
422 MarkTileDirtyByTile(TileXY(x, y));
425 auto invalidate_cache_rect = [&](btree::btree_set<uint32> &cache) {
426 for (int y = rect.top; y <= rect.bottom; y++) {
427 auto iter = cache.lower_bound(TileXY(rect.left, y) << 3);
428 auto end_iter = iter;
429 uint end = TileXY(rect.right, y) << 3;
430 while (end_iter != cache.end() && *end_iter < end) ++end_iter;
431 cache.erase(iter, end_iter);
434 if (outer_radius) invalidate_cache_rect(_zoning_cache_outer);
435 if (inner_radius) invalidate_cache_rect(_zoning_cache_inner);
439 void ClearZoningCaches()
441 _zoning_cache_inner.clear();
442 _zoning_cache_outer.clear();
445 void SetZoningMode(bool inner, ZoningEvaluationMode mode)
447 ZoningEvaluationMode &current_mode = inner ? _zoning.inner : _zoning.outer;
448 btree::btree_set<uint32> &cache = inner ? _zoning_cache_inner : _zoning_cache_outer;
450 if (current_mode == mode) return;
452 current_mode = mode;
453 cache.clear();
454 MarkWholeScreenDirty();