441 lines
15 KiB
C++
441 lines
15 KiB
C++
/** @file zoning_cmd.cpp */
|
|
#include "stdafx.h"
|
|
#include "station_base.h"
|
|
#include "industry.h"
|
|
#include "viewport_func.h"
|
|
#include "town.h"
|
|
#include "zoning.h"
|
|
#include "genworld.h"
|
|
#include <algorithm>
|
|
#include <vector>
|
|
|
|
#include "citymania/cm_highlight.hpp"
|
|
#include "citymania/cm_game.hpp"
|
|
#include "citymania/cm_main.hpp"
|
|
|
|
|
|
Zoning _zoning = {CHECKNOTHING, CHECKNOTHING};
|
|
static const SpriteID INVALID_SPRITE_ID = UINT_MAX;
|
|
//RED GREEN BLACK LIGHT_BLUE ORANGE WHITE YELLOW PURPLE
|
|
|
|
TileIndex _closest_cache_ref = INVALID_TILE;
|
|
const uint CLOSEST_CACHE_THRESHOLD = 128;
|
|
typedef std::pair<uint, Town*>ClosestTownRecord;
|
|
std::vector<ClosestTownRecord> _closest_cache;
|
|
|
|
|
|
void RebuildClosestHash(TileIndex tile) {
|
|
_closest_cache_ref = INVALID_TILE;
|
|
_closest_cache.clear();
|
|
for (Town *t : Town::Iterate()) {
|
|
_closest_cache.push_back(std::make_pair(
|
|
DistanceManhattan(t->xy, tile), t));
|
|
}
|
|
std::sort(
|
|
_closest_cache.begin(), _closest_cache.end(),
|
|
[](ClosestTownRecord &a, ClosestTownRecord &b) -> bool {
|
|
return a.first < b.first;
|
|
}
|
|
);
|
|
|
|
_closest_cache_ref = tile;
|
|
}
|
|
|
|
|
|
Town *CMCalcClosestTownFromTile(TileIndex tile, uint threshold = INT_MAX)
|
|
{
|
|
if (_closest_cache_ref == INVALID_TILE
|
|
|| DistanceManhattan(_closest_cache_ref, tile) > CLOSEST_CACHE_THRESHOLD) {
|
|
RebuildClosestHash(tile);
|
|
// RebuildClosestHash(TileXY(
|
|
// TileX(tile) + CLOSEST_CACHE_THRESHOLD / 2,
|
|
// TileY(tile) + CLOSEST_CACHE_THRESHOLD / 2));
|
|
}
|
|
int ref_dist = DistanceManhattan(_closest_cache_ref, tile);
|
|
|
|
uint best = threshold;
|
|
Town *best_town = NULL;
|
|
|
|
for (auto p: _closest_cache) {
|
|
if (p.first > best + ref_dist)
|
|
break;
|
|
uint dist = DistanceManhattan(tile, p.second->xy);
|
|
if (dist < best) {
|
|
best = dist;
|
|
best_town = p.second;
|
|
}
|
|
}
|
|
|
|
return best_town;
|
|
}
|
|
|
|
// Copy ClosestTownFromTile but uses CMCalcClosestTownFromTile
|
|
Town *CMClosestTownFromTile(TileIndex tile, uint threshold)
|
|
{
|
|
switch (GetTileType(tile)) {
|
|
case MP_ROAD:
|
|
if (IsRoadDepot(tile)) return CalcClosestTownFromTile(tile, threshold);
|
|
|
|
if (!HasTownOwnedRoad(tile)) {
|
|
TownID tid = GetTownIndex(tile);
|
|
|
|
if (tid == (TownID)INVALID_TOWN) {
|
|
/* in the case we are generating "many random towns", this value may be INVALID_TOWN */
|
|
if (_generating_world) return CalcClosestTownFromTile(tile, threshold);
|
|
assert(Town::GetNumItems() == 0);
|
|
return NULL;
|
|
}
|
|
|
|
assert(Town::IsValidID(tid));
|
|
Town *town = Town::Get(tid);
|
|
|
|
if (DistanceManhattan(tile, town->xy) >= threshold) town = NULL;
|
|
|
|
return town;
|
|
}
|
|
FALLTHROUGH;
|
|
|
|
case MP_HOUSE:
|
|
return Town::GetByTile(tile);
|
|
|
|
default:
|
|
return CMCalcClosestTownFromTile(tile, threshold);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Draw the zoning sprites.
|
|
* @param SpriteID image
|
|
* the image
|
|
* @param SpriteID colour
|
|
* the colour of the zoning
|
|
* @param TileInfo ti
|
|
* the tile
|
|
*/
|
|
const byte _tileh_to_sprite[32] = {
|
|
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 17, 0, 15, 18, 0,
|
|
};
|
|
|
|
void DrawZoningSprites(SpriteID image, SpriteID colour, const TileInfo *ti) {
|
|
if (colour != INVALID_SPRITE_ID){
|
|
AddSortableSpriteToDraw(image + _tileh_to_sprite[ti->tileh], colour, ti->x, ti->y, 0x10, 0x10, 1, ti->z + 7);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Detect whether this area is within the acceptance of any station.
|
|
* @param TileArea area
|
|
* the area to search by
|
|
* @return true if a station is found
|
|
*/
|
|
bool IsAreaWithinAcceptanceZoneOfStation(TileArea area) {
|
|
int catchment = _settings_game.station.station_spread + (_settings_game.station.modified_catchment ? MAX_CATCHMENT : CA_UNMODIFIED);
|
|
|
|
StationFinder morestations(TileArea(TileXY(TileX(area.tile) - catchment / 2, TileY(area.tile) - catchment / 2),
|
|
TileX(area.tile) + area.w + catchment, TileY(area.tile) + area.h + catchment));
|
|
|
|
for (Station *st: *morestations.GetStations()) {
|
|
Rect rect = st->GetCatchmentRect();
|
|
return TileArea(TileXY(rect.left, rect.top), TileXY(rect.right, rect.bottom)).Intersects(area);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Detect whether this tile is within the acceptance of any station.
|
|
* @param TileIndex tile
|
|
* the tile to search by
|
|
* @return true if a station is found
|
|
*/
|
|
bool IsTileWithinAcceptanceZoneOfStation(TileIndex tile) {
|
|
StationFinder morestations(TileArea(tile, 1, 1));
|
|
|
|
for (Station *st: *morestations.GetStations()) {
|
|
if (st->TileIsInCatchment(tile))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//Check the opinion of the local authority in the tile.
|
|
SpriteID TileZoneCheckOpinionEvaluation(TileIndex tile, Owner owner) {
|
|
Town *town = CMClosestTownFromTile(tile, _settings_game.economy.dist_local_authority);
|
|
|
|
if (town == NULL) return INVALID_SPRITE_ID; // no town
|
|
else if (HasBit(town->have_ratings, owner)) { // good : bad
|
|
int16 rating = town->ratings[owner];
|
|
if(rating <= RATING_APPALLING) return SPR_PALETTE_ZONING_RED;
|
|
if(rating <= RATING_POOR) return SPR_PALETTE_ZONING_ORANGE;
|
|
if(rating <= RATING_MEDIOCRE) return SPR_PALETTE_ZONING_YELLOW;
|
|
if(rating <= RATING_GOOD) return SPR_PALETTE_ZONING_WHITE;
|
|
if(rating <= RATING_VERYGOOD) return SPR_PALETTE_ZONING_PURPLE;
|
|
if(rating <= RATING_EXCELLENT) return SPR_PALETTE_ZONING_LIGHT_BLUE;
|
|
return SPR_PALETTE_ZONING_GREEN;
|
|
}
|
|
else {
|
|
return SPR_PALETTE_ZONING_BLACK; // no opinion
|
|
}
|
|
}
|
|
|
|
//Check whether the player can build in tile.
|
|
SpriteID TileZoneCheckBuildEvaluation(TileIndex tile, Owner owner) {
|
|
/* Let's first check for the obvious things you cannot build on */
|
|
switch ( GetTileType(tile) ) {
|
|
case MP_INDUSTRY:
|
|
case MP_OBJECT:
|
|
case MP_HOUSE:
|
|
return SPR_PALETTE_ZONING_RED; //can't build
|
|
case MP_STATION:
|
|
case MP_TUNNELBRIDGE:
|
|
case MP_ROAD:
|
|
case MP_RAILWAY: {
|
|
if (GetTileOwner(tile) != owner) return SPR_PALETTE_ZONING_RED; //can't build
|
|
else return INVALID_SPRITE_ID;
|
|
}
|
|
default: return INVALID_SPRITE_ID;
|
|
}
|
|
}
|
|
|
|
//Detect whether the tile is within the catchment zone of a station.
|
|
//black if within, light blue if only in acceptance zone and nothing if no nearby station.
|
|
SpriteID TileZoneCheckStationCatchmentEvaluation(TileIndex tile) {
|
|
// Never on a station.
|
|
if (IsTileType(tile, MP_STATION)){
|
|
return INVALID_SPRITE_ID;
|
|
}
|
|
return INVALID_SPRITE_ID;
|
|
}
|
|
|
|
//Detect whether a building is unserved by a station of owner.
|
|
//return red if unserved, orange if only accepting, nothing if served or not a building
|
|
SpriteID TileZoneCheckUnservedBuildingsEvaluation(TileIndex tile) {
|
|
CargoArray dat;
|
|
|
|
if (IsTileType (tile, MP_HOUSE))
|
|
//&& ( ( memset(&dat, 0, sizeof(dat)), AddAcceptedCargo(tile, dat, NULL), (dat[CT_MAIL] + dat[CT_PASSENGERS] > 0) )
|
|
// || ( memset(&dat, 0, sizeof(dat)), AddProducedCargo(tile, dat), (dat[CT_MAIL] + dat[CT_PASSENGERS] > 0) ) ) )
|
|
{
|
|
StationFinder stations(TileArea(tile, 1, 1));
|
|
|
|
if (!stations.GetStations()->empty()) {
|
|
return INVALID_SPRITE_ID;
|
|
}
|
|
return SPR_PALETTE_ZONING_RED;
|
|
}
|
|
return INVALID_SPRITE_ID;
|
|
}
|
|
|
|
//Detect whether an industry is unserved by a station of owner.
|
|
//return red if unserved, orange if only accepting, nothing if served or not a building
|
|
SpriteID TileZoneCheckUnservedIndustriesEvaluation(TileIndex tile) {
|
|
if (IsTileType(tile, MP_INDUSTRY)) {
|
|
Industry *ind = Industry::GetByTile(tile);
|
|
StationFinder stations(ind->location);
|
|
|
|
if (!stations.GetStations()->empty()){
|
|
return INVALID_SPRITE_ID;
|
|
}
|
|
|
|
return SPR_PALETTE_ZONING_RED;
|
|
}
|
|
return INVALID_SPRITE_ID;
|
|
}
|
|
|
|
//Check which town zone tile belongs to.
|
|
SpriteID TileZoneCheckTownZones(TileIndex tile) {
|
|
HouseZonesBits next_zone = HZB_BEGIN, tz = HZB_END;
|
|
|
|
for (Town *town : Town::Iterate()) {
|
|
uint dist = DistanceSquare(tile, town->xy);
|
|
// town code uses <= for checking town borders (tz0) but < for other zones
|
|
while (next_zone < HZB_END
|
|
&& (town->cache.squared_town_zone_radius[next_zone] == 0
|
|
|| dist <= town->cache.squared_town_zone_radius[next_zone] - (next_zone == HZB_BEGIN ? 0 : 1))
|
|
){
|
|
if(town->cache.squared_town_zone_radius[next_zone] != 0) tz = next_zone;
|
|
next_zone++;
|
|
}
|
|
}
|
|
|
|
switch (tz) {
|
|
case HZB_TOWN_EDGE: return SPR_PALETTE_ZONING_LIGHT_BLUE; // Tz0
|
|
case HZB_TOWN_OUTSKIRT: return SPR_PALETTE_ZONING_RED; // Tz1
|
|
case HZB_TOWN_OUTER_SUBURB: return SPR_PALETTE_ZONING_YELLOW; // Tz2
|
|
case HZB_TOWN_INNER_SUBURB: return SPR_PALETTE_ZONING_GREEN; // Tz3
|
|
case HZB_TOWN_CENTRE: return SPR_PALETTE_ZONING_WHITE; // Tz4 - center
|
|
default: return INVALID_SPRITE_ID; // no town
|
|
}
|
|
return INVALID_SPRITE_ID;
|
|
}
|
|
|
|
//Check CB town acceptance area
|
|
SpriteID TileZoneCheckNewCBBorders(TileIndex tile) {
|
|
for (Town *town : Town::Iterate()) {
|
|
if (DistanceSquare(tile, town->xy) <= town->cache.squared_town_zone_radius[0] + 30)
|
|
return SPR_PALETTE_ZONING_BLACK;
|
|
}
|
|
return INVALID_SPRITE_ID;
|
|
}
|
|
|
|
//Check CB town acceptance area
|
|
SpriteID TileZoneCheckCBBorders(TileIndex tile) {
|
|
for (Town *town : Town::Iterate()) {
|
|
if (DistanceMax(town->xy, tile) <= _settings_client.gui.cb_distance_check)
|
|
return SPR_PALETTE_ZONING_LIGHT_BLUE;
|
|
}
|
|
return INVALID_SPRITE_ID; // no town
|
|
}
|
|
|
|
//Check whether the tile is within citybuilder server town border (where houses could be built)
|
|
SpriteID TileZoneCheckCBTownBorders(TileIndex tile) {
|
|
for (Town *town : Town::Iterate()) {
|
|
uint32 distMax = DistanceMax(town->xy, tile);
|
|
if (distMax * distMax < town->cache.squared_town_zone_radius[0]){
|
|
return SPR_PALETTE_ZONING_GREEN;
|
|
}
|
|
}
|
|
return INVALID_SPRITE_ID;
|
|
}
|
|
|
|
//Check which advertisement zone(small, medium, large) tile belongs to
|
|
SpriteID TileZoneCheckTownAdvertisementZones(TileIndex tile) {
|
|
Town *town = CMCalcClosestTownFromTile(tile, 21U);
|
|
if (town == NULL) return INVALID_SPRITE_ID; //nothing
|
|
|
|
uint dist = DistanceManhattan(town->xy, tile);
|
|
|
|
if (dist <= 10) return SPR_PALETTE_ZONING_GREEN;
|
|
if (dist <= 15) return SPR_PALETTE_ZONING_YELLOW;
|
|
if (dist <= 20) return SPR_PALETTE_ZONING_LIGHT_BLUE;
|
|
return INVALID_SPRITE_ID;
|
|
}
|
|
|
|
//Checks for tile in growth tiles info
|
|
SpriteID TileZoneCheckTownsGrowthTiles(TileIndex tile) {
|
|
switch (citymania::_game->get_town_growth_tile(tile)) {
|
|
// case TGTS_CB_HOUSE_REMOVED_NOGROW: return SPR_PALETTE_ZONING_LIGHT_BLUE;
|
|
case citymania::TownGrowthTileState::RH_REMOVED: return SPR_PALETTE_ZONING_LIGHT_BLUE;
|
|
case citymania::TownGrowthTileState::RH_REBUILT: return SPR_PALETTE_ZONING_WHITE;
|
|
case citymania::TownGrowthTileState::NEW_HOUSE: return SPR_PALETTE_ZONING_GREEN;
|
|
case citymania::TownGrowthTileState::CS: return SPR_PALETTE_ZONING_ORANGE;
|
|
case citymania::TownGrowthTileState::HS: return SPR_PALETTE_ZONING_YELLOW;
|
|
case citymania::TownGrowthTileState::HR: return SPR_PALETTE_ZONING_RED;
|
|
default: return INVALID_SPRITE_ID;
|
|
}
|
|
}
|
|
|
|
// For station shows whether it is active
|
|
SpriteID TileZoneCheckActiveStations(TileIndex tile) {
|
|
// Never on a station.
|
|
if (!IsTileType(tile, MP_STATION)){
|
|
return INVALID_SPRITE_ID;
|
|
}
|
|
|
|
Station *st = Station::GetByTile(tile);
|
|
if (st == NULL) {
|
|
return INVALID_SPRITE_ID;
|
|
}
|
|
|
|
if (st->time_since_load <= 20 || st->time_since_unload <= 20) {
|
|
return SPR_PALETTE_ZONING_GREEN;
|
|
}
|
|
|
|
return SPR_PALETTE_ZONING_RED;
|
|
}
|
|
|
|
|
|
/**
|
|
* General evaluation function; calls all the other functions depending on
|
|
* evaluation mode.
|
|
* @param TileIndex tile
|
|
* Tile to be evaluated.
|
|
* @param EvaluationMode ev_mode
|
|
* The current evaluation mode.
|
|
* @return The colour returned by the evaluation functions (none if no ev_mode).
|
|
*/
|
|
SpriteID TileZoningSpriteEvaluation(TileIndex tile, Owner owner, EvaluationMode ev_mode) {
|
|
switch (ev_mode) {
|
|
case CHECKOPINION: return TileZoneCheckOpinionEvaluation(tile, owner);
|
|
case CHECKBUILD: return TileZoneCheckBuildEvaluation(tile, owner);
|
|
case CHECKSTACATCH: return TileZoneCheckStationCatchmentEvaluation(tile);
|
|
case CHECKBULUNSER: return TileZoneCheckUnservedBuildingsEvaluation(tile);
|
|
case CHECKINDUNSER: return TileZoneCheckUnservedIndustriesEvaluation(tile);
|
|
case CHECKTOWNZONES: return TileZoneCheckTownZones(tile);
|
|
case CHECKCBACCEPTANCE: return TileZoneCheckCBBorders(tile);
|
|
case CHECKCBTOWNLIMIT: return TileZoneCheckNewCBBorders(tile);
|
|
case CHECKTOWNADZONES: return TileZoneCheckTownAdvertisementZones(tile);
|
|
case CHECKTOWNGROWTHTILES: return TileZoneCheckTownsGrowthTiles(tile);
|
|
case CHECKACTIVESTATIONS: return TileZoneCheckActiveStations(tile);
|
|
default: return INVALID_SPRITE_ID;
|
|
}
|
|
}
|
|
|
|
SpriteID GetTownZoneBorderColor(uint8 zone) {
|
|
switch (zone) {
|
|
default: return SPR_PALETTE_ZONING_WHITE; // Tz0
|
|
case 2: return SPR_PALETTE_ZONING_YELLOW; // Tz1
|
|
case 3: return SPR_PALETTE_ZONING_ORANGE; // Tz2
|
|
case 4: return SPR_PALETTE_ZONING_ORANGE; // Tz3
|
|
case 5: return SPR_PALETTE_ZONING_RED; // Tz4 - center
|
|
};
|
|
switch (zone) {
|
|
default: return SPR_PALETTE_ZONING_LIGHT_BLUE; // Tz0
|
|
case 2: return SPR_PALETTE_ZONING_RED; // Tz1
|
|
case 3: return SPR_PALETTE_ZONING_YELLOW; // Tz2
|
|
case 4: return SPR_PALETTE_ZONING_GREEN; // Tz3
|
|
case 5: return SPR_PALETTE_ZONING_WHITE; // Tz4 - center
|
|
};
|
|
}
|
|
|
|
void DrawBorderSprites(const TileInfo *ti, citymania::ZoningBorder border, SpriteID color) {
|
|
auto b = (uint8)border & 15;
|
|
auto tile_sprite = SPR_BORDER_HIGHLIGHT_BASE + _tileh_to_sprite[ti->tileh] * 19;
|
|
if (b) {
|
|
AddSortableSpriteToDraw(tile_sprite + b - 1,
|
|
color, ti->x, ti->y, 0x10, 0x10, 1, ti->z + 7);
|
|
}
|
|
if (border & citymania::ZoningBorder::TOP_CORNER)
|
|
AddSortableSpriteToDraw(tile_sprite + 15,
|
|
color, ti->x, ti->y, 0x10, 0x10, 1, ti->z + 7);
|
|
if (border & citymania::ZoningBorder::RIGHT_CORNER)
|
|
AddSortableSpriteToDraw(tile_sprite + 16,
|
|
color, ti->x, ti->y, 0x10, 0x10, 1, ti->z + 7);
|
|
if (border & citymania::ZoningBorder::BOTTOM_CORNER)
|
|
AddSortableSpriteToDraw(tile_sprite + 17,
|
|
color, ti->x, ti->y, 0x10, 0x10, 1, ti->z + 7);
|
|
if (border & citymania::ZoningBorder::LEFT_CORNER)
|
|
AddSortableSpriteToDraw(tile_sprite + 18,
|
|
color, ti->x, ti->y, 0x10, 0x10, 1, ti->z + 7);
|
|
|
|
}
|
|
|
|
/**
|
|
* Draw the the zoning on the tile.
|
|
* @param TileInfo ti
|
|
* the tile to draw on.
|
|
*/
|
|
void DrawTileZoning(const TileInfo *ti) {
|
|
|
|
if(_zoning.outer == CHECKNOTHING && _zoning.inner == CHECKNOTHING) return; //nothing to do
|
|
if (_game_mode != GM_NORMAL || ti->tile >= MapSize() || IsTileType(ti->tile, MP_VOID)) return; //check invalid
|
|
if (_zoning.outer != CHECKNOTHING){
|
|
if (_zoning.outer == CHECKTOWNZONES ||
|
|
_zoning.outer == CHECKBULUNSER ||
|
|
_zoning.outer == CHECKINDUNSER ||
|
|
_zoning.outer == CHECKTOWNADZONES ||
|
|
_zoning.outer == CHECKSTACATCH ||
|
|
_zoning.outer == CHECKCBACCEPTANCE ||
|
|
_zoning.outer == CHECKCBTOWNLIMIT ||
|
|
_zoning.outer == CHECKACTIVESTATIONS ||
|
|
_zoning.outer == CHECKTOWNGROWTHTILES) {
|
|
// handled by citymania zoning
|
|
} else {
|
|
DrawZoningSprites(SPR_SELECT_TILE, TileZoningSpriteEvaluation(ti->tile, _local_company, _zoning.outer), ti);
|
|
}
|
|
}
|
|
if (_zoning.inner != CHECKNOTHING){
|
|
DrawZoningSprites(SPR_INNER_HIGHLIGHT_BASE, TileZoningSpriteEvaluation(ti->tile, _local_company, _zoning.inner), ti);
|
|
}
|
|
}
|