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/>.
11 /** @file zoning_cmd.cpp */
15 #include "station_type.h"
16 #include "station_base.h"
19 #include "viewport_func.h"
21 #include "company_func.h"
23 #include "table/sprites.h"
24 #include "station_func.h"
25 #include "station_map.h"
26 #include "tracerestrict.h"
29 #include "3rdparty/cpp-btree/btree_set.h"
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
;
38 * Draw the zoning sprites.
40 * @param SpriteID image
42 * @param SpriteID colour
43 * the colour of the zoning
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);
55 * Detect whether this area is within the acceptance of any station.
57 * @param TileArea area
58 * the area to search by
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
);
83 * Detect whether this tile is within the acceptance of any station.
85 * @param TileIndex tile
86 * the tile to search by
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
) {
114 * Check whether the player can build in tile.
116 * @param TileIndex tile
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
)) {
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
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).
146 if (GetTileOwner(tile
) != owner
) {
147 return SPR_ZONING_INNER_HIGHLIGHT_RED
;
149 return ZONING_INVALID_SPRITE_ID
;
153 return ZONING_INVALID_SPRITE_ID
;
158 * Check the opinion of the local authority in the tile.
160 * @param TileIndex tile
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;
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
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
224 * @return red if unserved, orange if only accepting, nothing if served or not
227 SpriteID
TileZoneCheckUnservedBuildingsEvaluation(TileIndex tile
, Owner owner
)
229 if (!IsTileType(tile
, MP_HOUSE
)) {
230 return ZONING_INVALID_SPRITE_ID
;
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
268 * @return red if unserved, orange if only accepting, nothing if served or not
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
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
316 * @param TileIndex tile
317 * Tile to be evaluated.
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
)
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
) {
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
) {
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();
355 SpriteID s
= TileZoningSpriteEvaluation(tile
, owner
, ev_mode
);
356 uint val
= tile
<< 3;
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
);
369 return TileZoningSpriteEvaluation(tile
, owner
, ev_mode
);
374 * Draw the the zoning on the tile.
377 * the tile to draw on.
379 void DrawTileZoning(const TileInfo
*ti
)
381 if (IsTileType(ti
->tile
, MP_VOID
) || _game_mode
!= GM_NORMAL
) {
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
)
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
405 * Mark dirty the coverage area around a station if the current zoning mode depends on station coverage
407 * @param const Station *st
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
);
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
¤t_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;
454 MarkWholeScreenDirty();