Files
openttd-cmclient/src/zoning_cmd.cpp

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);
}
}