#include "../stdafx.h" #include "cm_highlight.hpp" #include "cm_blueprint.hpp" #include "cm_main.hpp" #include "cm_station_gui.hpp" #include "cm_zoning.hpp" #include "../core/math_func.hpp" #include "../table/bridge_land.h" #include "../command_func.h" #include "../house.h" #include "../industry.h" #include "../landscape.h" #include "../newgrf_airporttiles.h" #include "../newgrf_railtype.h" #include "../newgrf_roadtype.h" #include "../newgrf_station.h" #include "../town.h" #include "../town_kdtree.h" #include "../tilearea_type.h" #include "../tilehighlight_type.h" #include "../tilehighlight_func.h" #include "../viewport_func.h" // #include "../zoning.h" #include "../table/airporttile_ids.h" #include "../table/track_land.h" #include /** Enumeration of multi-part foundations */ enum FoundationPart { FOUNDATION_PART_NONE = 0xFF, ///< Neither foundation nor groundsprite drawn yet. FOUNDATION_PART_NORMAL = 0, ///< First part (normal foundation or no foundation) FOUNDATION_PART_HALFTILE = 1, ///< Second part (halftile foundation) FOUNDATION_PART_END }; extern void DrawSelectionSprite(SpriteID image, PaletteID pal, const TileInfo *ti, int z_offset, FoundationPart foundation_part); // viewport.cpp extern const Station *_viewport_highlight_station; extern TileHighlightData _thd; extern bool IsInsideSelectedRectangle(int x, int y); extern RailType _cur_railtype; extern RoadType _cur_roadtype; extern AirportClassID _selected_airport_class; ///< the currently visible airport class extern int _selected_airport_index; extern byte _selected_airport_layout; extern DiagDirection _build_depot_direction; ///< Currently selected depot direction extern DiagDirection _road_station_picker_orientation; extern DiagDirection _road_depot_orientation; extern uint32 _realtime_tick; extern void GetStationLayout(byte *layout, uint numtracks, uint plat_len, const StationSpec *statspec); struct RailStationGUISettings { Axis orientation; ///< Currently selected rail station orientation bool newstations; ///< Are custom station definitions available? StationClassID station_class; ///< Currently selected custom station class (if newstations is \c true ) byte station_type; ///< %Station type within the currently selected custom station class (if newstations is \c true ) byte station_count; ///< Number of custom stations (if newstations is \c true ) }; extern RailStationGUISettings _railstation; ///< Settings of the station builder GUI namespace citymania { extern void (*DrawTileSelectionRect)(const TileInfo *ti, PaletteID pal); extern void (*DrawAutorailSelection)(const TileInfo *ti, HighLightStyle autorail_type, PaletteID pal); struct TileZoning { uint8 town_zone : 3; uint8 industry_fund_result : 2; uint8 advertisement_zone : 2; // IndustryType industry_fund_type; uint8 industry_fund_update; }; static TileZoning *_mz = nullptr; static IndustryType _industry_forbidden_tiles = INVALID_INDUSTRYTYPE; extern StationBuildingStatus _station_building_status; extern const Station *_station_to_join; extern const Station *_highlight_station_to_join; extern TileArea _highlight_join_area; std::set, std::greater>> _town_cache; // struct { // int w; // int h; // int catchment; // } _station_select; 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, }; ObjectTileHighlight ObjectTileHighlight::make_rail_depot(SpriteID palette, DiagDirection ddir) { auto oh = ObjectTileHighlight(Type::RAIL_DEPOT, palette); oh.u.rail.depot.ddir = ddir; return oh; } ObjectTileHighlight ObjectTileHighlight::make_rail_track(SpriteID palette, Track track) { auto oh = ObjectTileHighlight(Type::RAIL_TRACK, palette); oh.u.rail.track = track; return oh; } ObjectTileHighlight ObjectTileHighlight::make_rail_station(SpriteID palette, Axis axis, byte section) { auto oh = ObjectTileHighlight(Type::RAIL_STATION, palette); oh.u.rail.station.axis = axis; oh.u.rail.station.section = section; return oh; } ObjectTileHighlight ObjectTileHighlight::make_rail_signal(SpriteID palette, uint pos, SignalType type, SignalVariant variant) { auto oh = ObjectTileHighlight(Type::RAIL_SIGNAL, palette); oh.u.rail.signal.pos = pos; oh.u.rail.signal.type = type; oh.u.rail.signal.variant = variant; return oh; } ObjectTileHighlight ObjectTileHighlight::make_rail_bridge_head(SpriteID palette, DiagDirection ddir, BridgeType type) { auto oh = ObjectTileHighlight(Type::RAIL_BRIDGE_HEAD, palette); oh.u.rail.bridge_head.ddir = ddir; oh.u.rail.bridge_head.type = type; return oh; } ObjectTileHighlight ObjectTileHighlight::make_rail_tunnel_head(SpriteID palette, DiagDirection ddir) { auto oh = ObjectTileHighlight(Type::RAIL_TUNNEL_HEAD, palette); oh.u.rail.tunnel_head.ddir = ddir; return oh; } ObjectTileHighlight ObjectTileHighlight::make_road_stop(SpriteID palette, RoadType roadtype, DiagDirection ddir, bool is_truck) { auto oh = ObjectTileHighlight(Type::ROAD_STOP, palette); oh.u.road.stop.roadtype = roadtype; oh.u.road.stop.ddir = ddir; oh.u.road.stop.is_truck = is_truck; return oh; } ObjectTileHighlight ObjectTileHighlight::make_road_depot(SpriteID palette, RoadType roadtype, DiagDirection ddir) { auto oh = ObjectTileHighlight(Type::ROAD_DEPOT, palette); oh.u.road.depot.roadtype = roadtype; oh.u.road.depot.ddir = ddir; return oh; } ObjectTileHighlight ObjectTileHighlight::make_airport_tile(SpriteID palette, StationGfx gfx) { auto oh = ObjectTileHighlight(Type::AIRPORT_TILE, palette); oh.u.airport_tile.gfx = gfx; return oh; } bool ObjectHighlight::operator==(const ObjectHighlight& oh) { if (this->type != oh.type) return false; return (this->tile == oh.tile && this->end_tile == oh.end_tile && this->axis == oh.axis && this->ddir == oh.ddir && this->roadtype == oh.roadtype && this->is_truck == oh.is_truck && this->airport_type == oh.airport_type && this->airport_layout == oh.airport_layout && this->blueprint == oh.blueprint); // switch (this->type) { // case Type::RAIL_DEPOT: return this->tile == oh.tile && this->ddir == oh.ddir; // default: return true; // } // return true; } bool ObjectHighlight::operator!=(const ObjectHighlight& oh) { return !(*this == oh); } ObjectHighlight ObjectHighlight::make_rail_depot(TileIndex tile, DiagDirection ddir) { auto oh = ObjectHighlight{ObjectHighlight::Type::RAIL_DEPOT}; oh.tile = tile; oh.ddir = ddir; return oh; } ObjectHighlight ObjectHighlight::make_rail_station(TileIndex start_tile, TileIndex end_tile, Axis axis) { auto oh = ObjectHighlight{ObjectHighlight::Type::RAIL_STATION}; oh.tile = start_tile; oh.end_tile = end_tile; oh.axis = axis; return oh; } ObjectHighlight ObjectHighlight::make_road_stop(TileIndex start_tile, TileIndex end_tile, RoadType roadtype, DiagDirection orientation, bool is_truck) { auto oh = ObjectHighlight{ObjectHighlight::Type::ROAD_STOP}; oh.tile = start_tile; oh.end_tile = end_tile; oh.ddir = orientation; oh.roadtype = roadtype; oh.is_truck = is_truck; return oh; } ObjectHighlight ObjectHighlight::make_road_depot(TileIndex tile, RoadType roadtype, DiagDirection orientation) { auto oh = ObjectHighlight{ObjectHighlight::Type::ROAD_DEPOT}; oh.tile = tile; oh.ddir = orientation; oh.roadtype = roadtype; return oh; } ObjectHighlight ObjectHighlight::make_airport(TileIndex start_tile, int airport_type, byte airport_layout) { auto oh = ObjectHighlight{ObjectHighlight::Type::AIRPORT}; oh.tile = start_tile; oh.airport_type = airport_type; oh.airport_layout = airport_layout; return oh; } ObjectHighlight ObjectHighlight::make_blueprint(TileIndex tile, sp blueprint) { auto oh = ObjectHighlight{ObjectHighlight::Type::BLUEPRINT}; oh.tile = tile; oh.blueprint = blueprint; return oh; } /** * Try to add an additional rail-track at the entrance of a depot * @param tile Tile to use for adding the rail-track * @param dir Direction to check for already present tracks * @param track Track to add * @see CcRailDepot() */ void ObjectHighlight::PlaceExtraDepotRail(TileIndex tile, DiagDirection dir, Track track) { if (GetRailTileType(tile) != RAIL_TILE_NORMAL) return; if ((GetTrackBits(tile) & DiagdirReachesTracks(dir)) == 0) return; this->tiles.insert(std::make_pair(tile, ObjectTileHighlight::make_rail_track(CM_PALETTE_TINT_WHITE, track))); } /** Additional pieces of track to add at the entrance of a depot. */ static const Track _place_depot_extra_track[12] = { TRACK_LEFT, TRACK_UPPER, TRACK_UPPER, TRACK_RIGHT, // First additional track for directions 0..3 TRACK_X, TRACK_Y, TRACK_X, TRACK_Y, // Second additional track TRACK_LOWER, TRACK_LEFT, TRACK_RIGHT, TRACK_LOWER, // Third additional track }; /** Direction to check for existing track pieces. */ static const DiagDirection _place_depot_extra_dir[12] = { DIAGDIR_SE, DIAGDIR_SW, DIAGDIR_SE, DIAGDIR_SW, DIAGDIR_SW, DIAGDIR_NW, DIAGDIR_NE, DIAGDIR_SE, DIAGDIR_NW, DIAGDIR_NE, DIAGDIR_NW, DIAGDIR_NE, }; bool CanBuild(TileIndex tile, uint32 p1, uint32 p2, uint32 cmd) { return DoCommandPInternal( tile, p1, p2, cmd, nullptr, // callback "", // text true, // my_cmd true // estimate_only ).Succeeded(); } bool CanBuild(const CommandContainer &cc) { return CanBuild(cc.tile, cc.p1, cc.p2, cc.cmd); } void ObjectHighlight::UpdateTiles() { this->tiles.clear(); switch (this->type) { case Type::NONE: break; case Type::RAIL_DEPOT: { auto dir = this->ddir; auto palette = (CanBuild( this->tile, _cur_railtype, dir, CMD_BUILD_TRAIN_DEPOT ) ? CM_PALETTE_TINT_WHITE : CM_PALETTE_TINT_RED_DEEP); this->tiles.insert(std::make_pair(this->tile, ObjectTileHighlight::make_rail_depot(palette, dir))); auto tile = this->tile + TileOffsByDiagDir(dir); if (IsTileType(tile, MP_RAILWAY) && IsCompatibleRail(GetRailType(tile), _cur_railtype)) { this->PlaceExtraDepotRail(tile, _place_depot_extra_dir[dir], _place_depot_extra_track[dir]); this->PlaceExtraDepotRail(tile, _place_depot_extra_dir[dir + 4], _place_depot_extra_track[dir + 4]); this->PlaceExtraDepotRail(tile, _place_depot_extra_dir[dir + 8], _place_depot_extra_track[dir + 8]); } break; } case Type::RAIL_STATION: { auto ta = OrthogonalTileArea(this->tile, this->end_tile); auto numtracks = ta.w; auto plat_len = ta.h; if (this->axis == AXIS_X) Swap(numtracks, plat_len); auto palette = (CanBuild( this->tile, _cur_railtype | (this->axis << 6) | ((uint32)numtracks << 8) | ((uint32)plat_len << 16), NEW_STATION << 16, CMD_BUILD_RAIL_STATION ) ? CM_PALETTE_TINT_WHITE : CM_PALETTE_TINT_RED_DEEP); auto layout_ptr = AllocaM(byte, (int)numtracks * plat_len); GetStationLayout(layout_ptr, numtracks, plat_len, nullptr); // TODO statspec auto tile_delta = (this->axis == AXIS_X ? TileDiffXY(1, 0) : TileDiffXY(0, 1)); TileIndex tile_track = this->tile; do { TileIndex tile = tile_track; int w = plat_len; do { byte layout = *layout_ptr++; this->tiles.insert(std::make_pair(tile, ObjectTileHighlight::make_rail_station(palette, this->axis, layout & ~1))); tile += tile_delta; } while (--w); tile_track += tile_delta ^ TileDiffXY(1, 1); // perpendicular to tile_delta } while (--numtracks); break; } case Type::ROAD_STOP: { auto ta = OrthogonalTileArea(this->tile, this->end_tile); auto palette = (CanBuild( this->tile, (uint32)(ta.w | ta.h << 8), (this->is_truck ? 1 : 0) | (this->ddir >= DIAGDIR_END ? 2 : 0) | (((uint)this->ddir % 4) << 3) | (NEW_STATION << 16), CMD_BUILD_ROAD_STOP ) ? CM_PALETTE_TINT_WHITE : CM_PALETTE_TINT_RED_DEEP); TileIndex tile; for (TileIndex tile : ta) { this->tiles.insert(std::make_pair(tile, ObjectTileHighlight::make_road_stop(palette, this->roadtype, this->ddir, this->is_truck))); } break; } case Type::ROAD_DEPOT: { auto palette = (CanBuild( this->tile, this->roadtype << 2 | this->ddir, 0, CMD_BUILD_ROAD_DEPOT ) ? CM_PALETTE_TINT_WHITE : CM_PALETTE_TINT_RED_DEEP); this->tiles.insert(std::make_pair(this->tile, ObjectTileHighlight::make_road_depot(palette, this->roadtype, this->ddir))); break; } case Type::AIRPORT: { auto palette = (CanBuild( this->tile, this->airport_type | ((uint)this->airport_layout << 8), 1 | (NEW_STATION << 16), CMD_BUILD_AIRPORT ) ? CM_PALETTE_TINT_WHITE : CM_PALETTE_TINT_RED_DEEP); const AirportSpec *as = AirportSpec::Get(this->airport_type); if (!as->IsAvailable() || this->airport_layout >= as->num_table) break; Direction rotation = as->rotation[this->airport_layout]; int w = as->size_x; int h = as->size_y; if (rotation == DIR_E || rotation == DIR_W) Swap(w, h); TileArea airport_area = TileArea(this->tile, w, h); for (AirportTileTableIterator iter(as->table[this->airport_layout], this->tile); iter != INVALID_TILE; ++iter) { this->tiles.insert(std::make_pair(iter, ObjectTileHighlight::make_airport_tile(palette, iter.GetStationGfx()))); } break; } case Type::BLUEPRINT: if (this->blueprint && this->tile != INVALID_TILE) this->tiles = this->blueprint->GetTiles(this->tile); break; default: NOT_REACHED(); } } void ObjectHighlight::MarkDirty() { this->UpdateTiles(); for (const auto &kv: this->tiles) { MarkTileDirtyByTile(kv.first); } if (this->type == ObjectHighlight::Type::BLUEPRINT && this->blueprint) { // TODO why && blueprint check is needed? for (auto tile : this->blueprint->source_tiles) { MarkTileDirtyByTile(tile); } } } SpriteID GetTintBySelectionColour(SpriteID colour, bool deep=false) { switch(colour) { case CM_SPR_PALETTE_ZONING_RED: return (deep ? CM_PALETTE_TINT_RED_DEEP : CM_PALETTE_TINT_RED); case CM_SPR_PALETTE_ZONING_ORANGE: return (deep ? CM_PALETTE_TINT_ORANGE_DEEP : CM_PALETTE_TINT_ORANGE); case CM_SPR_PALETTE_ZONING_GREEN: return (deep ? CM_PALETTE_TINT_GREEN_DEEP : CM_PALETTE_TINT_GREEN); case CM_SPR_PALETTE_ZONING_LIGHT_BLUE: return (deep ? CM_PALETTE_TINT_CYAN_DEEP : CM_PALETTE_TINT_CYAN); case CM_SPR_PALETTE_ZONING_YELLOW: return CM_PALETTE_TINT_YELLOW; // case SPR_PALETTE_ZONING__: return PALETTE_TINT_YELLOW_WHITE; case CM_SPR_PALETTE_ZONING_WHITE: return CM_PALETTE_TINT_WHITE; default: return PAL_NONE; } } SpriteID GetSelectionColourByTint(SpriteID colour) { switch(colour) { case CM_PALETTE_TINT_RED_DEEP: case CM_PALETTE_TINT_RED: return CM_SPR_PALETTE_ZONING_RED; case CM_PALETTE_TINT_ORANGE_DEEP: case CM_PALETTE_TINT_ORANGE: return CM_SPR_PALETTE_ZONING_ORANGE; case CM_PALETTE_TINT_GREEN_DEEP: case CM_PALETTE_TINT_GREEN: return CM_SPR_PALETTE_ZONING_GREEN; case CM_PALETTE_TINT_CYAN_DEEP: case CM_PALETTE_TINT_CYAN: return CM_SPR_PALETTE_ZONING_LIGHT_BLUE; case CM_PALETTE_TINT_YELLOW: return CM_SPR_PALETTE_ZONING_YELLOW; // returnase SPR_PALETTE_ZONING__: return PALETTE_TINT_YELLOW_WHITE; case CM_PALETTE_TINT_WHITE: return CM_SPR_PALETTE_ZONING_WHITE; default: return PAL_NONE; } } void DrawTrainDepotSprite(SpriteID palette, const TileInfo *ti, RailType railtype, DiagDirection ddir) { const DrawTileSprites *dts = &_depot_gfx_table[ddir]; const RailtypeInfo *rti = GetRailTypeInfo(railtype); SpriteID image = rti->UsesOverlay() ? SPR_FLAT_GRASS_TILE : dts->ground.sprite; uint32 offset = rti->GetRailtypeSpriteOffset(); if (image != SPR_FLAT_GRASS_TILE) image += offset; // PaletteID palette = COMPANY_SPRITE_COLOUR(_local_company); // DrawSprite(image, PAL_NONE, x, y); switch (ddir) { case DIAGDIR_SW: DrawAutorailSelection(ti, HT_DIR_X, GetSelectionColourByTint(palette)); break; case DIAGDIR_SE: DrawAutorailSelection(ti, HT_DIR_Y, GetSelectionColourByTint(palette)); break; default: break; } // if (rti->UsesOverlay()) { // SpriteID ground = GetCustomRailSprite(rti, INVALID_TILE, RTSG_GROUND); // switch (ddir) { // case DIAGDIR_SW: DrawSprite(ground + RTO_X, PALETTE_TINT_WHITE, x, y); break; // case DIAGDIR_SE: DrawSprite(ground + RTO_Y, PALETTE_TINT_WHITE, x, y); break; // default: break; // } // } int depot_sprite = GetCustomRailSprite(rti, INVALID_TILE, RTSG_DEPOT); if (depot_sprite != 0) offset = depot_sprite - SPR_RAIL_DEPOT_SE_1; DrawRailTileSeq(ti, dts, TO_INVALID, offset, 0, palette); } void DrawTrainStationSprite(SpriteID palette, const TileInfo *ti, RailType railtype, Axis axis, byte section) { int32 total_offset = 0; const DrawTileSprites *t = GetStationTileLayout(STATION_RAIL, section + (axis == AXIS_X ? 0 : 1)); const RailtypeInfo *rti = nullptr; if (railtype != INVALID_RAILTYPE) { rti = GetRailTypeInfo(railtype); total_offset = rti->GetRailtypeSpriteOffset(); } DrawAutorailSelection(ti, (axis == AXIS_X ? HT_DIR_X : HT_DIR_Y), GetSelectionColourByTint(palette)); // if (roadtype != INVALID_ROADTYPE) { // const RoadTypeInfo* rti = GetRoadTypeInfo(roadtype); // if (image >= 4) { // /* Drive-through stop */ // uint sprite_offset = 5 - image; // /* Road underlay takes precedence over tram */ // if (rti->UsesOverlay()) { // SpriteID ground = GetCustomRoadSprite(rti, INVALID_TILE, ROTSG_GROUND); // DrawSprite(ground + sprite_offset, PAL_NONE, x, y); // SpriteID overlay = GetCustomRoadSprite(rti, INVALID_TILE, ROTSG_OVERLAY); // if (overlay) DrawSprite(overlay + sprite_offset, PAL_NONE, x, y); // } else if (RoadTypeIsTram(roadtype)) { // DrawSprite(SPR_TRAMWAY_TRAM + sprite_offset, PAL_NONE, x, y); // } // } else { // /* Drive-in stop */ // if (RoadTypeIsRoad(roadtype) && rti->UsesOverlay()) { // SpriteID ground = GetCustomRoadSprite(rti, INVALID_TILE, ROTSG_ROADSTOP); // DrawSprite(ground + image, PAL_NONE, x, y); // } // } // } /* Default waypoint has no railtype specific sprites */ // DrawRailTileSeq(ti, t, TO_INVALID, (st == STATION_WAYPOINT ? 0 : total_offset), 0, PALETTE_TINT_WHITE); DrawRailTileSeq(ti, t, TO_INVALID, total_offset, 0, palette); } void DrawRoadStop(SpriteID palette, const TileInfo *ti, RoadType roadtype, DiagDirection orientation, bool is_truck) { int32 total_offset = 0; const RoadTypeInfo* rti = GetRoadTypeInfo(roadtype); uint image = (uint)orientation; if (image >= 4) { /* Drive-through stop */ uint sprite_offset = 5 - image; /* Road underlay takes precedence over tram */ if (rti->UsesOverlay()) { SpriteID ground = GetCustomRoadSprite(rti, INVALID_TILE, ROTSG_GROUND); DrawSprite(ground + sprite_offset, PAL_NONE, ti->x, ti->y); SpriteID overlay = GetCustomRoadSprite(rti, INVALID_TILE, ROTSG_OVERLAY); // if (overlay) DrawSprite(overlay + sprite_offset, PAL_NONE, x, y); if (overlay) AddSortableSpriteToDraw(overlay + sprite_offset, palette, ti->x, ti->y, 1, 1, BB_HEIGHT_UNDER_BRIDGE, ti->z); } else if (RoadTypeIsTram(roadtype)) { // DrawSprite(SPR_TRAMWAY_TRAM + sprite_offset, PAL_NONE, x, y); AddSortableSpriteToDraw(SPR_TRAMWAY_TRAM + sprite_offset, palette, ti->x, ti->y, 1, 1, BB_HEIGHT_UNDER_BRIDGE, ti->z); } } else { /* Drive-in stop */ if (RoadTypeIsRoad(roadtype) && rti->UsesOverlay()) { SpriteID ground = GetCustomRoadSprite(rti, INVALID_TILE, ROTSG_ROADSTOP); // DrawSprite(, PAL_NONE, x, y); AddSortableSpriteToDraw(ground + image, palette, ti->x, ti->y, 1, 1, BB_HEIGHT_UNDER_BRIDGE, ti->z); } } const DrawTileSprites *t = GetStationTileLayout(is_truck ? STATION_TRUCK : STATION_BUS, image); AddSortableSpriteToDraw(t->ground.sprite, palette, ti->x, ti->y, 1, 1, BB_HEIGHT_UNDER_BRIDGE, ti->z); DrawRailTileSeq(ti, t, TO_INVALID, total_offset, 0, palette); /* Draw road, tram catenary */ // DrawRoadCatenary(ti); } struct DrawRoadTileStruct { uint16 image; byte subcoord_x; byte subcoord_y; }; #include "../table/road_land.h" // copied from road_gui.cpp static uint GetRoadSpriteOffset(Slope slope, RoadBits bits) { if (slope != SLOPE_FLAT) { switch (slope) { case SLOPE_NE: return 11; case SLOPE_SE: return 12; case SLOPE_SW: return 13; case SLOPE_NW: return 14; default: NOT_REACHED(); } } else { static const uint offsets[] = { 0, 18, 17, 7, 16, 0, 10, 5, 15, 8, 1, 4, 9, 3, 6, 2 }; return offsets[bits]; } } void DrawRoadDepot(SpriteID palette, const TileInfo *ti, RoadType roadtype, DiagDirection orientation) { const RoadTypeInfo* rti = GetRoadTypeInfo(roadtype); int relocation = GetCustomRoadSprite(rti, INVALID_TILE, ROTSG_DEPOT); bool default_gfx = relocation == 0; if (default_gfx) { if (HasBit(rti->flags, ROTF_CATENARY)) { if (_loaded_newgrf_features.tram == TRAMWAY_REPLACE_DEPOT_WITH_TRACK && RoadTypeIsTram(roadtype) && !rti->UsesOverlay()) { /* Sprites with track only work for default tram */ relocation = SPR_TRAMWAY_DEPOT_WITH_TRACK - SPR_ROAD_DEPOT; default_gfx = false; } else { /* Sprites without track are always better, if provided */ relocation = SPR_TRAMWAY_DEPOT_NO_TRACK - SPR_ROAD_DEPOT; } } } else { relocation -= SPR_ROAD_DEPOT; } const DrawTileSprites *dts = &_road_depot[orientation]; AddSortableSpriteToDraw(dts->ground.sprite, palette, ti->x, ti->y, 1, 1, BB_HEIGHT_UNDER_BRIDGE, ti->z); if (default_gfx) { uint offset = GetRoadSpriteOffset(SLOPE_FLAT, DiagDirToRoadBits(orientation)); if (rti->UsesOverlay()) { SpriteID ground = GetCustomRoadSprite(rti, INVALID_TILE, ROTSG_OVERLAY); if (ground != 0) AddSortableSpriteToDraw(ground + offset, palette, ti->x, ti->y, 1, 1, BB_HEIGHT_UNDER_BRIDGE, ti->z); } else if (RoadTypeIsTram(roadtype)) { AddSortableSpriteToDraw(SPR_TRAMWAY_OVERLAY + offset, palette, ti->x, ti->y, 1, 1, BB_HEIGHT_UNDER_BRIDGE, ti->z); } } DrawRailTileSeq(ti, dts, TO_INVALID, relocation, 0, palette); } #include "../table/station_land.h" void DrawAirportTile(SpriteID palette, const TileInfo *ti, StationGfx gfx) { int32 total_offset = 0; const DrawTileSprites *t = nullptr; gfx = GetTranslatedAirportTileID(gfx); if (gfx >= NEW_AIRPORTTILE_OFFSET) { const AirportTileSpec *ats = AirportTileSpec::Get(gfx); if (ats->grf_prop.spritegroup[0] != nullptr /* && DrawNewAirportTile(ti, Station::GetByTile(ti->tile), gfx, ats) */) { return; } /* No sprite group (or no valid one) found, meaning no graphics associated. * Use the substitute one instead */ assert(ats->grf_prop.subst_id != INVALID_AIRPORTTILE); gfx = ats->grf_prop.subst_id; } switch (gfx) { case APT_RADAR_GRASS_FENCE_SW: t = &_station_display_datas_airport_radar_grass_fence_sw[0]; break; case APT_GRASS_FENCE_NE_FLAG: t = &_station_display_datas_airport_flag_grass_fence_ne[0]; break; case APT_RADAR_FENCE_SW: t = &_station_display_datas_airport_radar_fence_sw[0]; break; case APT_RADAR_FENCE_NE: t = &_station_display_datas_airport_radar_fence_ne[0]; break; case APT_GRASS_FENCE_NE_FLAG_2: t = &_station_display_datas_airport_flag_grass_fence_ne_2[0]; break; } if (t == nullptr || t->seq == nullptr) t = GetStationTileLayout(STATION_AIRPORT, gfx); if (t) { AddSortableSpriteToDraw(t->ground.sprite, palette, ti->x, ti->y, 1, 1, BB_HEIGHT_UNDER_BRIDGE, ti->z); DrawRailTileSeq(ti, t, TO_INVALID, total_offset, 0, palette); } } enum SignalOffsets { // from rail_cmd.cpp SIGNAL_TO_SOUTHWEST, SIGNAL_TO_NORTHEAST, SIGNAL_TO_SOUTHEAST, SIGNAL_TO_NORTHWEST, SIGNAL_TO_EAST, SIGNAL_TO_WEST, SIGNAL_TO_SOUTH, SIGNAL_TO_NORTH, }; /** * copied from rail_cmd.cpp * Get surface height in point (x,y) * On tiles with halftile foundations move (x,y) to a safe point wrt. track */ static uint GetSaveSlopeZ(uint x, uint y, Track track) { switch (track) { case TRACK_UPPER: x &= ~0xF; y &= ~0xF; break; case TRACK_LOWER: x |= 0xF; y |= 0xF; break; case TRACK_LEFT: x |= 0xF; y &= ~0xF; break; case TRACK_RIGHT: x &= ~0xF; y |= 0xF; break; default: break; } return GetSlopePixelZ(x, y); } void DrawSignal(SpriteID palette, const TileInfo *ti, RailType railtype, uint pos, SignalType type, SignalVariant variant) { // reference: DraawSingleSignal in rail_cmd.cpp bool side; switch (_settings_game.construction.train_signal_side) { case 0: side = false; break; // left case 2: side = true; break; // right default: side = _settings_game.vehicle.road_side != 0; break; // driving side } static const Point SignalPositions[2][12] = { { // Signals on the left side /* LEFT LEFT RIGHT RIGHT UPPER UPPER */ { 8, 5}, {14, 1}, { 1, 14}, { 9, 11}, { 1, 0}, { 3, 10}, /* LOWER LOWER X X Y Y */ {11, 4}, {14, 14}, {11, 3}, { 4, 13}, { 3, 4}, {11, 13} }, { // Signals on the right side /* LEFT LEFT RIGHT RIGHT UPPER UPPER */ {14, 1}, {12, 10}, { 4, 6}, { 1, 14}, {10, 4}, { 0, 1}, /* LOWER LOWER X X Y Y */ {14, 14}, { 5, 12}, {11, 13}, { 4, 3}, {13, 4}, { 3, 11} } }; uint x = TileX(ti->tile) * TILE_SIZE + SignalPositions[side][pos].x; uint y = TileY(ti->tile) * TILE_SIZE + SignalPositions[side][pos].y; static const Track pos_track[] = { TRACK_LEFT, TRACK_LEFT, TRACK_RIGHT, TRACK_RIGHT, TRACK_UPPER, TRACK_UPPER, TRACK_LOWER, TRACK_LOWER, TRACK_X, TRACK_X, TRACK_Y, TRACK_Y, }; static const SignalOffsets pos_offset[] = { SIGNAL_TO_NORTH, SIGNAL_TO_SOUTH, SIGNAL_TO_NORTH, SIGNAL_TO_SOUTH, SIGNAL_TO_WEST, SIGNAL_TO_EAST, SIGNAL_TO_WEST, SIGNAL_TO_EAST, SIGNAL_TO_SOUTHWEST, SIGNAL_TO_NORTHEAST, SIGNAL_TO_SOUTHEAST, SIGNAL_TO_NORTHWEST, }; auto track = pos_track[pos]; auto image = pos_offset[pos]; static const SignalState condition = SIGNAL_STATE_GREEN; auto rti = GetRailTypeInfo(railtype); SpriteID sprite = GetCustomSignalSprite(rti, ti->tile, type, variant, condition); if (sprite != 0) { sprite += image; } else { /* Normal electric signals are stored in a different sprite block than all other signals. */ sprite = (type == SIGTYPE_NORMAL && variant == SIG_ELECTRIC) ? SPR_ORIGINAL_SIGNALS_BASE : SPR_SIGNALS_BASE - 16; sprite += type * 16 + variant * 64 + image * 2 + condition + (type > SIGTYPE_LAST_NOPBS ? 64 : 0); } AddSortableSpriteToDraw(sprite, palette, x, y, 1, 1, BB_HEIGHT_UNDER_BRIDGE, GetSaveSlopeZ(x, y, track)); } // copied from tunnelbridge_cmd.cpp static inline const PalSpriteID *GetBridgeSpriteTable(int index, BridgePieces table) { const BridgeSpec *bridge = GetBridgeSpec(index); assert(table < BRIDGE_PIECE_INVALID); if (bridge->sprite_table == nullptr || bridge->sprite_table[table] == nullptr) { return _bridge_sprite_table[index][table]; } else { return bridge->sprite_table[table]; } } void DrawBridgeHead(SpriteID palette, const TileInfo *ti, RailType railtype, DiagDirection ddir, BridgeType type) { auto rti = GetRailTypeInfo(railtype); int base_offset = rti->bridge_offset; const PalSpriteID *psid; /* HACK Wizardry to convert the bridge ramp direction into a sprite offset */ base_offset += (6 - ddir) % 4; /* Table number BRIDGE_PIECE_HEAD always refers to the bridge heads for any bridge type */ if (ti->tileh == SLOPE_FLAT) base_offset += 4; // sloped bridge head psid = &GetBridgeSpriteTable(type, BRIDGE_PIECE_HEAD)[base_offset]; AddSortableSpriteToDraw(psid->sprite, palette, ti->x, ti->y, 16, 16, ti->tileh == SLOPE_FLAT ? 0 : 8, ti->z); // DrawAutorailSelection(ti, (ddir == DIAGDIR_SW || ddir == DIAGDIR_NE ? HT_DIR_X : HT_DIR_Y), PAL_NONE); } void DrawTunnelHead(SpriteID palette, const TileInfo *ti, RailType railtype, DiagDirection ddir) { auto rti = GetRailTypeInfo(railtype); SpriteID image; SpriteID railtype_overlay = 0; image = rti->base_sprites.tunnel; if (rti->UsesOverlay()) { /* Check if the railtype has custom tunnel portals. */ railtype_overlay = GetCustomRailSprite(rti, ti->tile, RTSG_TUNNEL_PORTAL); if (railtype_overlay != 0) image = SPR_RAILTYPE_TUNNEL_BASE; // Draw blank grass tunnel base. } image += ddir * 2; AddSortableSpriteToDraw(image, palette, ti->x, ti->y, 16, 16, 0, ti->z); } void ObjectHighlight::Draw(const TileInfo *ti) { auto range = this->tiles.equal_range(ti->tile); for (auto t = range.first; t != range.second; t++) { auto &oth = t->second; switch (oth.type) { case ObjectTileHighlight::Type::RAIL_DEPOT: DrawTrainDepotSprite(oth.palette, ti, _cur_railtype, oth.u.rail.depot.ddir); break; case ObjectTileHighlight::Type::RAIL_TRACK: { auto hs = (HighLightStyle)oth.u.rail.track; DrawAutorailSelection(ti, hs, GetSelectionColourByTint(oth.palette)); break; } case ObjectTileHighlight::Type::RAIL_STATION: DrawTrainStationSprite(oth.palette, ti, _cur_railtype, oth.u.rail.station.axis, oth.u.rail.station.section); break; case ObjectTileHighlight::Type::RAIL_SIGNAL: DrawSignal(oth.palette, ti, _cur_railtype, oth.u.rail.signal.pos, oth.u.rail.signal.type, oth.u.rail.signal.variant); break; case ObjectTileHighlight::Type::RAIL_BRIDGE_HEAD: DrawBridgeHead(oth.palette, ti, _cur_railtype, oth.u.rail.bridge_head.ddir, oth.u.rail.bridge_head.type); break; case ObjectTileHighlight::Type::RAIL_TUNNEL_HEAD: DrawTunnelHead(oth.palette, ti, _cur_railtype, oth.u.rail.tunnel_head.ddir); break; case ObjectTileHighlight::Type::ROAD_STOP: DrawRoadStop(oth.palette, ti, oth.u.road.stop.roadtype, oth.u.road.stop.ddir, oth.u.road.stop.is_truck); break; case ObjectTileHighlight::Type::ROAD_DEPOT: DrawRoadDepot(oth.palette, ti, oth.u.road.depot.roadtype, oth.u.road.depot.ddir); break; case ObjectTileHighlight::Type::AIRPORT_TILE: DrawAirportTile(oth.palette, ti, oth.u.airport_tile.gfx); break; default: break; } } // fprintf(stderr, "TILEH DRAW %d %d %d\n", ti->tile, (int)i, (int)this->tiles.size()); } template uint8 Get(uint32 x, uint32 y, F getter) { if (x >= MapSizeX() || y >= MapSizeY()) return 0; return getter(TileXY(x, y)); } template std::pair CalcTileBorders(TileIndex tile, F getter) { auto x = TileX(tile), y = TileY(tile); ZoningBorder res = ZoningBorder::NONE; auto z = getter(tile); if (z == 0) return std::make_pair(res, 0); auto tr = Get(x - 1, y, getter); auto tl = Get(x, y - 1, getter); auto bl = Get(x + 1, y, getter); auto br = Get(x, y + 1, getter); if (tr < z) res |= ZoningBorder::TOP_RIGHT; if (tl < z) res |= ZoningBorder::TOP_LEFT; if (bl < z) res |= ZoningBorder::BOTTOM_LEFT; if (br < z) res |= ZoningBorder::BOTTOM_RIGHT; if (tr == z && tl == z && Get(x - 1, y - 1, getter) < z) res |= ZoningBorder::TOP_CORNER; if (tr == z && br == z && Get(x - 1, y + 1, getter) < z) res |= ZoningBorder::RIGHT_CORNER; if (br == z && bl == z && Get(x + 1, y + 1, getter) < z) res |= ZoningBorder::BOTTOM_CORNER; if (tl == z && bl == z && Get(x + 1, y - 1, getter) < z) res |= ZoningBorder::LEFT_CORNER; return std::make_pair(res, z); } static uint8 _industry_highlight_hash = 0; void UpdateIndustryHighlight() { _industry_highlight_hash++; } bool CanBuildIndustryOnTileCached(IndustryType type, TileIndex tile) { // if (_mz[tile].industry_fund_type != type || !_mz[tile].industry_fund_result) { if (_mz[tile].industry_fund_update != _industry_highlight_hash || !_mz[tile].industry_fund_result) { bool res = CanBuildIndustryOnTile(type, tile); // _mz[tile].industry_fund_type = type; _mz[tile].industry_fund_update = _industry_highlight_hash; _mz[tile].industry_fund_result = res ? 2 : 1; return res; } return (_mz[tile].industry_fund_result == 2); } void DrawBorderSprites(const TileInfo *ti, ZoningBorder border, SpriteID color) { auto b = (uint8)border & 15; auto tile_sprite = CM_SPR_BORDER_HIGHLIGHT_BASE + _tileh_to_sprite[ti->tileh] * 19; if (b) { DrawSelectionSprite(tile_sprite + b - 1, color, ti, 7, FOUNDATION_PART_NORMAL); } if (border & ZoningBorder::TOP_CORNER) DrawSelectionSprite(tile_sprite + 15, color, ti, 7, FOUNDATION_PART_NORMAL); if (border & ZoningBorder::RIGHT_CORNER) DrawSelectionSprite(tile_sprite + 16, color, ti, 7, FOUNDATION_PART_NORMAL); if (border & ZoningBorder::BOTTOM_CORNER) DrawSelectionSprite(tile_sprite + 17, color, ti, 7, FOUNDATION_PART_NORMAL); if (border & ZoningBorder::LEFT_CORNER) DrawSelectionSprite(tile_sprite + 18, color, ti, 7, FOUNDATION_PART_NORMAL); } SpriteID GetIndustryZoningPalette(TileIndex tile) { if (!IsTileType(tile, MP_INDUSTRY)) return PAL_NONE; Industry *ind = Industry::GetByTile(tile); auto n_produced = 0; auto n_serviced = 0; for (auto j = 0; j < INDUSTRY_NUM_OUTPUTS; j++) { if (ind->produced_cargo[j] == CT_INVALID) continue; if (ind->last_month_production[j] == 0 && ind->this_month_production[j] == 0) continue; n_produced++; if (ind->last_month_transported[j] > 0 || ind->last_month_transported[j] > 0) n_serviced++; } if (n_serviced < n_produced) return (n_serviced == 0 ? CM_PALETTE_TINT_RED_DEEP : CM_PALETTE_TINT_ORANGE_DEEP); return PAL_NONE; } static void SetStationSelectionHighlight(const TileInfo *ti, TileHighlight &th) { bool draw_selection = ((_thd.drawstyle & HT_DRAG_MASK) == HT_RECT && _thd.outersize.x > 0); const Station *highlight_station = _viewport_highlight_station; if (_highlight_station_to_join) highlight_station = _highlight_station_to_join; if (draw_selection) { // const SpriteID pal[] = {SPR_PALETTE_ZONING_RED, SPR_PALETTE_ZONING_YELLOW, SPR_PALETTE_ZONING_LIGHT_BLUE, SPR_PALETTE_ZONING_GREEN}; // auto color = pal[(int)_station_building_status]; // if (_thd.make_square_red) color = SPR_PALETTE_ZONING_RED; if (_thd.make_square_red) { auto b = CalcTileBorders(ti->tile, [](TileIndex t) { auto x = TileX(t) * TILE_SIZE, y = TileY(t) * TILE_SIZE; return IsInsideSelectedRectangle(x, y); }); if (b.first != ZoningBorder::NONE) th.add_border(b.first, CM_SPR_PALETTE_ZONING_RED); } if (IsInsideSelectedRectangle(TileX(ti->tile) * TILE_SIZE, TileY(ti->tile) * TILE_SIZE)) { // th.ground_pal = GetTintBySelectionColour(color); th.ground_pal = th.structure_pal = (_thd.make_square_red ? CM_PALETTE_TINT_RED : PAL_NONE); return; } } auto coverage_getter = [draw_selection, highlight_station](TileIndex t) { auto x = TileX(t) * TILE_SIZE, y = TileY(t) * TILE_SIZE; if (highlight_station && IsTileType(t, MP_STATION) && GetStationIndex(t) == highlight_station->index) return 2; if (_settings_client.gui.station_show_coverage && highlight_station && highlight_station->TileIsInCatchment(t)) return 1; if (draw_selection && _settings_client.gui.station_show_coverage && IsInsideBS(x, _thd.pos.x + _thd.offs.x, _thd.size.x + _thd.outersize.x) && IsInsideBS(y, _thd.pos.y + _thd.offs.y, _thd.size.y + _thd.outersize.y)) return 1; return 0; }; auto b = CalcTileBorders(ti->tile, coverage_getter); if (b.second) { // const SpriteID pal[] = {PAL_NONE, SPR_PALETTE_ZONING_WHITE, SPR_PALETTE_ZONING_LIGHT_BLUE}; const SpriteID pal[] = {PAL_NONE, CM_SPR_PALETTE_ZONING_WHITE, PAL_NONE}; th.add_border(b.first, pal[b.second]); const SpriteID pal2[] = {PAL_NONE, CM_PALETTE_TINT_WHITE, CM_PALETTE_TINT_BLUE}; th.ground_pal = th.structure_pal = pal2[b.second]; } if (_highlight_join_area.tile != INVALID_TILE) { auto b = CalcTileBorders(ti->tile, [](TileIndex t) { return _highlight_join_area.Contains(t) ? 1 : 0; }); th.add_border(b.first, CM_SPR_PALETTE_ZONING_LIGHT_BLUE); if (b.second) { switch (th.ground_pal) { case CM_PALETTE_TINT_WHITE: th.ground_pal = th.structure_pal = CM_PALETTE_TINT_CYAN_WHITE; break; case CM_PALETTE_TINT_BLUE: break; default: th.ground_pal = th.structure_pal = CM_PALETTE_TINT_CYAN; break; } } } } void CalcCBAcceptanceBorders(TileHighlight &th, TileIndex tile, SpriteID border_pal, SpriteID ground_pal) { int tx = TileX(tile), ty = TileY(tile); uint16 radius = _settings_client.gui.cm_cb_distance; bool in_zone = false; ZoningBorder border = ZoningBorder::NONE; _town_kdtree.FindContained( (uint16)std::max(0, tx - radius), (uint16)std::max(0, ty - radius), (uint16)std::min(tx + radius + 1, MapSizeX()), (uint16)std::min(ty + radius + 1, MapSizeY()), [tx, ty, radius, &in_zone, &border] (TownID tid) { Town *town = Town::GetIfValid(tid); if (!town || town->larger_town) return; int dx = TileX(town->xy) - tx; int dy = TileY(town->xy) - ty; in_zone = in_zone || (std::max(abs(dx), abs(dy)) <= radius); if (dx == radius) border |= ZoningBorder::TOP_RIGHT; if (dx == -radius) border |= ZoningBorder::BOTTOM_LEFT; if (dy == radius) border |= ZoningBorder::TOP_LEFT; if (dy == -radius) border |= ZoningBorder::BOTTOM_RIGHT; } ); th.add_border(border, border_pal); if (in_zone) th.tint_all(ground_pal); } void AddTownCBLimitBorder(TileIndex tile, const Town *town, ZoningBorder &border, bool &in_zone) { auto sq = town->cache.squared_town_zone_radius[0] + 30; auto x = CalcTileBorders(tile, [town, sq] (TileIndex tile) { return DistanceSquare(tile, town->xy) <= sq ? 1 : 0; }); border |= x.first; in_zone = in_zone || x.second; } void CalcCBTownLimitBorder(TileHighlight &th, TileIndex tile, SpriteID border_pal, SpriteID ground_pal) { auto n = Town::GetNumItems(); uint32 sq = 0; uint i = 0; ZoningBorder border = ZoningBorder::NONE; bool in_zone = false; for (auto &p : _town_cache) { sq = p.second->cache.squared_town_zone_radius[0] + 30; if (4 * sq * n < MapSize() * i) break; AddTownCBLimitBorder(tile, p.second, border, in_zone); i++; } uint radius = IntSqrt(sq); int tx = TileX(tile), ty = TileY(tile); _town_kdtree.FindContained( (uint16)std::max(0, tx - radius), (uint16)std::max(0, ty - radius), (uint16)std::min(tx + radius + 1, MapSizeX()), (uint16)std::min(ty + radius + 1, MapSizeY()), [tile, &in_zone, &border] (TownID tid) { Town *town = Town::GetIfValid(tid); if (!town || town->larger_town) return; AddTownCBLimitBorder(tile, town, border, in_zone); } ); th.add_border(border, border_pal); if (in_zone) th.tint_all(ground_pal); } TileHighlight GetTileHighlight(const TileInfo *ti, TileType tile_type) { TileHighlight th; if (ti->tile == INVALID_TILE || tile_type == MP_VOID) return th; if (_zoning.outer == CHECKTOWNZONES) { auto p = GetTownZoneBorder(ti->tile); auto color = PAL_NONE; switch (p.second) { default: break; // Tz0 case 1: color = CM_SPR_PALETTE_ZONING_WHITE; break; // Tz0 case 2: color = CM_SPR_PALETTE_ZONING_YELLOW; break; // Tz1 case 3: color = CM_SPR_PALETTE_ZONING_ORANGE; break; // Tz2 case 4: color = CM_SPR_PALETTE_ZONING_ORANGE; break; // Tz3 case 5: color = CM_SPR_PALETTE_ZONING_RED; break; // Tz4 - center }; th.add_border(p.first, color); th.ground_pal = th.structure_pal = GetTintBySelectionColour(color); if (CB_Enabled()) CalcCBTownLimitBorder(th, ti->tile, CM_SPR_PALETTE_ZONING_RED, PAL_NONE); } else if (_zoning.outer == CHECKSTACATCH) { th.add_border(citymania::GetAnyStationCatchmentBorder(ti->tile), CM_SPR_PALETTE_ZONING_LIGHT_BLUE); } else if (_zoning.outer == CHECKTOWNGROWTHTILES) { // if (tgt == TGTS_NEW_HOUSE) th.sprite = SPR_IMG_HOUSE_NEW; switch (_game->get_town_growth_tile(ti->tile)) { // case TGTS_CB_HOUSE_REMOVED_NOGROW: case TownGrowthTileState::RH_REMOVED: th.selection = CM_SPR_PALETTE_ZONING_LIGHT_BLUE; break; case TownGrowthTileState::RH_REBUILT: th.selection = CM_SPR_PALETTE_ZONING_WHITE; th.structure_pal = CM_PALETTE_TINT_WHITE; break; case TownGrowthTileState::NEW_HOUSE: th.selection = CM_SPR_PALETTE_ZONING_GREEN; th.structure_pal = CM_PALETTE_TINT_GREEN; break; case TownGrowthTileState::CS: th.selection = CM_SPR_PALETTE_ZONING_ORANGE; break; case TownGrowthTileState::HS: th.selection = CM_SPR_PALETTE_ZONING_YELLOW; break; case TownGrowthTileState::HR: th.selection = CM_SPR_PALETTE_ZONING_RED; break; default: th.selection = PAL_NONE; } if (th.selection) th.ground_pal = GetTintBySelectionColour(th.selection); } else if (_zoning.outer == CHECKBULUNSER) { if (IsTileType (ti->tile, MP_HOUSE)) { StationFinder stations(TileArea(ti->tile, 1, 1)); // TODO check cargos if (stations.GetStations()->empty()) th.ground_pal = th.structure_pal = CM_PALETTE_TINT_RED_DEEP; } } else if (_zoning.outer == CHECKINDUNSER) { auto pal = GetIndustryZoningPalette(ti->tile); if (pal) th.ground_pal = th.structure_pal = CM_PALETTE_TINT_RED_DEEP; } else if (_zoning.outer == CHECKTOWNADZONES) { auto getter = [](TileIndex t) { return _mz[t].advertisement_zone; }; auto b = CalcTileBorders(ti->tile, getter); const SpriteID pal[] = {PAL_NONE, CM_SPR_PALETTE_ZONING_YELLOW, CM_SPR_PALETTE_ZONING_ORANGE, CM_SPR_PALETTE_ZONING_RED}; th.add_border(b.first, pal[b.second]); auto check_tile = ti->tile; if (IsTileType (ti->tile, MP_STATION)) { auto station = Station::GetByTile(ti->tile); if (station) check_tile = station->xy; } auto z = getter(check_tile); if (z) th.ground_pal = th.structure_pal = GetTintBySelectionColour(pal[z]); } else if (_zoning.outer == CHECKCBACCEPTANCE) { CalcCBAcceptanceBorders(th, ti->tile, CM_SPR_PALETTE_ZONING_WHITE, CM_PALETTE_TINT_WHITE); } else if (_zoning.outer == CHECKCBTOWNLIMIT) { CalcCBTownLimitBorder(th, ti->tile, CM_SPR_PALETTE_ZONING_WHITE, CM_PALETTE_TINT_WHITE); } else if (_zoning.outer == CHECKACTIVESTATIONS) { auto getter = [](TileIndex t) { if (!IsTileType (t, MP_STATION)) return 0; Station *st = Station::GetByTile(t); if (!st) return 0; if (st->time_since_load <= 20 || st->time_since_unload <= 20) return 1; return 2; }; auto b = CalcTileBorders(ti->tile, getter); const SpriteID pal[] = {PAL_NONE, CM_SPR_PALETTE_ZONING_GREEN, CM_SPR_PALETTE_ZONING_RED}; th.add_border(b.first, pal[b.second]); auto z = getter(ti->tile); if (z) th.ground_pal = th.structure_pal = GetTintBySelectionColour(pal[z]); } if (_settings_client.gui.cm_show_industry_forbidden_tiles && _industry_forbidden_tiles != INVALID_INDUSTRYTYPE) { auto b = CalcTileBorders(ti->tile, [](TileIndex t) { return !CanBuildIndustryOnTileCached(_industry_forbidden_tiles, t); }); th.add_border(b.first, CM_SPR_PALETTE_ZONING_RED); if (!CanBuildIndustryOnTileCached(_industry_forbidden_tiles, ti->tile)) th.ground_pal = th.structure_pal = CM_PALETTE_TINT_RED; } SetStationSelectionHighlight(ti, th); SetBlueprintHighlight(ti, th); return th; } void DrawTileZoning(const TileInfo *ti, const TileHighlight &th, TileType tile_type) { if (ti->tile == INVALID_TILE || tile_type == MP_VOID) return; for (uint i = 0; i < th.border_count; i++) DrawBorderSprites(ti, th.border[i], th.border_color[i]); if (th.sprite) { DrawSelectionSprite(th.sprite, PAL_NONE, ti, 0, FOUNDATION_PART_NORMAL); } if (th.selection) { DrawBorderSprites(ti, ZoningBorder::FULL, th.selection); // DrawSelectionSprite(SPR_SELECT_TILE + _tileh_to_sprite[ti->tileh], // th.selection, ti, 0, FOUNDATION_PART_NORMAL); } } bool DrawTileSelection(const TileInfo *ti, const TileHighlightType &tht) { if (ti->tile == INVALID_TILE || IsTileType(ti->tile, MP_VOID)) return false; _thd.cm.Draw(ti); if (_thd.drawstyle == CM_HT_BLUEPRINT_PLACE) return true; if (_thd.select_proc == DDSP_BUILD_STATION || _thd.select_proc == DDSP_BUILD_BUSSTOP || _thd.select_proc == DDSP_BUILD_TRUCKSTOP || _thd.select_proc == CM_DDSP_BUILD_AIRPORT || _thd.select_proc == CM_DDSP_BUILD_ROAD_DEPOT || _thd.select_proc == CM_DDSP_BUILD_RAIL_DEPOT) { // handled by DrawTileZoning return true; } return false; } TileIndex _autodetection_tile = INVALID_TILE; DiagDirDiff _autodetection_rotation = DIAGDIRDIFF_SAME; static DiagDirDiff GetAutodetectionRotation() { auto pt = GetTileBelowCursor(); auto tile = TileVirtXY(pt.x, pt.y); if (tile != _autodetection_tile) { _autodetection_tile = tile; _autodetection_rotation = DIAGDIRDIFF_SAME; } return _autodetection_rotation; } void RotateAutodetection() { auto rotation = GetAutodetectionRotation(); if (rotation == DIAGDIRDIFF_90LEFT) rotation = DIAGDIRDIFF_SAME; else rotation++; _autodetection_rotation = rotation; ::UpdateTileSelection(); } void ResetRotateAutodetection() { _autodetection_tile = INVALID_TILE; _autodetection_rotation = DIAGDIRDIFF_SAME; } DiagDirection AddAutodetectionRotation(DiagDirection ddir) { if (ddir >= DIAGDIR_END) return (DiagDirection)(((uint)ddir + (uint)GetAutodetectionRotation()) % 2 + DIAGDIR_END); return ChangeDiagDir(ddir, GetAutodetectionRotation()); } HighLightStyle UpdateTileSelection(HighLightStyle new_drawstyle) { _thd.cm_new = ObjectHighlight(ObjectHighlight::Type::NONE); auto pt = GetTileBelowCursor(); auto tile = (pt.x == -1 ? INVALID_TILE : TileVirtXY(pt.x, pt.y)); // fprintf(stderr, "UPDATE %d %d %d %d\n", tile, _thd.size.x, _thd.size.y, (int)((_thd.place_mode & HT_DRAG_MASK) == HT_RECT)); if (_thd.place_mode == CM_HT_BLUEPRINT_PLACE) { UpdateBlueprintTileSelection(pt, tile); new_drawstyle = CM_HT_BLUEPRINT_PLACE; } else if (pt.x == -1) { } else if (_thd.make_square_red) { } else if (_thd.select_proc == CM_DDSP_BUILD_ROAD_DEPOT) { auto dir = _road_depot_orientation; if (dir == DEPOTDIR_AUTO) { dir = AddAutodetectionRotation(AutodetectRoadObjectDirection(tile, pt, _cur_roadtype)); } _thd.cm_new = ObjectHighlight::make_road_depot(tile, _cur_roadtype, dir); new_drawstyle = HT_RECT; } else if (_thd.select_proc == CM_DDSP_BUILD_RAIL_DEPOT) { auto dir = _build_depot_direction; if (dir >= DiagDirection::DIAGDIR_END) { dir = AddAutodetectionRotation(AutodetectRailObjectDirection(tile, pt)); } _thd.cm_new = ObjectHighlight::make_rail_depot(tile, dir); // } else if (((_thd.place_mode & HT_DRAG_MASK) == HT_RECT || ((_thd.place_mode & HT_DRAG_MASK) == HT_SPECIAL && (_thd.next_drawstyle & HT_DRAG_MASK) == HT_RECT)) && _thd.new_outersize.x > 0 && !_thd.make_square_red) { // station } else if (_thd.select_proc == CM_DDSP_BUILD_AIRPORT) { auto tile = TileXY(_thd.new_pos.x / TILE_SIZE, _thd.new_pos.y / TILE_SIZE); if (_selected_airport_index != -1) { const AirportSpec *as = AirportClass::Get(_selected_airport_class)->GetSpec(_selected_airport_index); _thd.cm_new = ObjectHighlight::make_airport(tile, as->GetIndex(), _selected_airport_layout); new_drawstyle = HT_RECT; } } else if (_thd.select_proc == DDSP_BUILD_STATION || _thd.select_proc == DDSP_BUILD_BUSSTOP || _thd.select_proc == DDSP_BUILD_TRUCKSTOP) { // station if (_thd.size.x >= (int)TILE_SIZE && _thd.size.y >= (int)TILE_SIZE) { auto start_tile = TileXY(_thd.new_pos.x / TILE_SIZE, _thd.new_pos.y / TILE_SIZE); auto end_tile = TileXY( std::min((_thd.new_pos.x + _thd.new_size.x) / TILE_SIZE, MapSizeX()) - 1, std::min((_thd.new_pos.y + _thd.new_size.y) / TILE_SIZE, MapSizeY()) - 1 ); if (_thd.select_proc == DDSP_BUILD_STATION) _thd.cm_new = ObjectHighlight::make_rail_station(start_tile, end_tile, _railstation.orientation); else if (_thd.select_proc == DDSP_BUILD_BUSSTOP || _thd.select_proc == DDSP_BUILD_TRUCKSTOP) { auto ddir = _road_station_picker_orientation; auto ta = TileArea(start_tile, end_tile); if (pt.x != -1) { if (ddir >= DIAGDIR_END && ddir < STATIONDIR_AUTO) { // When placed on road autorotate anyway if (ddir == STATIONDIR_X) { if (!CheckDriveThroughRoadStopDirection(ta, ROAD_X)) ddir = STATIONDIR_Y; } else { if (!CheckDriveThroughRoadStopDirection(ta, ROAD_Y)) ddir = STATIONDIR_X; } } else if (ddir == STATIONDIR_AUTO) { ddir = AddAutodetectionRotation(AutodetectRoadObjectDirection(start_tile, pt, _cur_roadtype)); } else if (ddir == STATIONDIR_AUTO_XY) { ddir = AddAutodetectionRotation(AutodetectDriveThroughRoadStopDirection(ta, pt, _cur_roadtype)); } } _thd.cm_new = ObjectHighlight::make_road_stop(start_tile, end_tile, _cur_roadtype, ddir, _thd.select_proc == DDSP_BUILD_TRUCKSTOP); } } new_drawstyle = HT_RECT; } if (_thd.cm != _thd.cm_new) { _thd.cm.MarkDirty(); _thd.cm = _thd.cm_new; _thd.cm.MarkDirty(); } return new_drawstyle; } void AllocateZoningMap(uint map_size) { free(_mz); _mz = CallocT(map_size); } uint8 GetTownZone(Town *town, TileIndex tile) { auto dist = DistanceSquare(tile, town->xy); if (dist > town->cache.squared_town_zone_radius[HZB_TOWN_EDGE]) return 0; uint8 z = 1; for (HouseZonesBits i = HZB_TOWN_OUTSKIRT; i < HZB_END; i++) if (dist < town->cache.squared_town_zone_radius[i]) z = (uint8)i + 1; else if (town->cache.squared_town_zone_radius[i] != 0) break; return z; } uint8 GetAnyTownZone(TileIndex tile) { HouseZonesBits next_zone = HZB_BEGIN; uint8 z = 0; 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) z = (uint8)next_zone + 1; next_zone++; } } return z; } void UpdateTownZoning(Town *town, uint32 prev_edge) { auto edge = town->cache.squared_town_zone_radius[HZB_TOWN_EDGE]; if (prev_edge && edge == prev_edge) return; auto area = OrthogonalTileArea(town->xy, 1, 1); bool recalc; if (edge < prev_edge) { area.Expand(IntSqrt(prev_edge)); recalc = true; } else { area.Expand(IntSqrt(edge)); recalc = false; } // TODO mark dirty only if zoning is on for(TileIndex tile : area) { uint8 group = GetTownZone(town, tile); if (_mz[tile].town_zone != group) _mz[tile].industry_fund_result = 0; if (_mz[tile].town_zone > group) { if (recalc) { _mz[tile].town_zone = GetAnyTownZone(tile); if (_zoning.outer == CHECKTOWNZONES) MarkTileDirtyByTile(tile); } } else if (_mz[tile].town_zone < group) { _mz[tile].town_zone = group; if (_zoning.outer == CHECKTOWNZONES) MarkTileDirtyByTile(tile); } } } void UpdateAdvertisementZoning(TileIndex center, uint radius, uint8 zone) { uint16 x1, y1, x2, y2; x1 = (uint16)std::max(0, TileX(center) - radius); x2 = (uint16)std::min(TileX(center) + radius + 1, MapSizeX()); y1 = (uint16)std::max(0, TileY(center) - radius); y2 = (uint16)std::min(TileY(center) + radius + 1, MapSizeY()); for (uint16 y = y1; y < y2; y++) { for (uint16 x = x1; x < x2; x++) { auto tile = TileXY(x, y); if (DistanceManhattan(tile, center) > radius) continue; _mz[tile].advertisement_zone = std::max(_mz[tile].advertisement_zone, zone); } } } void UpdateZoningTownHouses(const Town *town, uint32 old_houses) { if (!town->larger_town) return; _town_cache.erase(std::make_pair(old_houses, town)); _town_cache.insert(std::make_pair(town->cache.num_houses, town)); } void InitializeZoningMap() { _town_cache.clear(); for (Town *t : Town::Iterate()) { UpdateTownZoning(t, 0); UpdateAdvertisementZoning(t->xy, 10, 3); UpdateAdvertisementZoning(t->xy, 15, 2); UpdateAdvertisementZoning(t->xy, 20, 1); UpdateZoningTownHouses(t, 0); if (!t->larger_town) _town_cache.insert(std::make_pair(t->cache.num_houses, t)); } } std::pair GetTownZoneBorder(TileIndex tile) { return CalcTileBorders(tile, [](TileIndex t) { return _mz[t].town_zone; }); } ZoningBorder GetAnyStationCatchmentBorder(TileIndex tile) { ZoningBorder border = ZoningBorder::NONE; StationFinder morestations(TileArea(tile, 1, 1)); for (Station *st: *morestations.GetStations()) { border |= CalcTileBorders(tile, [st](TileIndex t) {return st->TileIsInCatchment(t) ? 1 : 0; }).first; } if (border & ZoningBorder::TOP_CORNER && border & (ZoningBorder::TOP_LEFT | ZoningBorder::TOP_RIGHT)) border &= ~ZoningBorder::TOP_CORNER; return border; } void SetIndustryForbiddenTilesHighlight(IndustryType type) { if (_settings_client.gui.cm_show_industry_forbidden_tiles && _industry_forbidden_tiles != type) { MarkWholeScreenDirty(); } _industry_forbidden_tiles = type; UpdateIndustryHighlight(); } PaletteID GetTreeShadePal(TileIndex tile) { if (_settings_client.gui.cm_shaded_trees != 1) return PAL_NONE; Slope slope = GetTileSlope(tile); switch (slope) { case SLOPE_STEEP_N: case SLOPE_N: return CM_PALETTE_SHADE_S; case SLOPE_NE: return CM_PALETTE_SHADE_SW; case SLOPE_E: case SLOPE_STEEP_E: return CM_PALETTE_SHADE_W; case SLOPE_SE: return CM_PALETTE_SHADE_NW; case SLOPE_STEEP_S: case SLOPE_S: return CM_PALETTE_SHADE_N; case SLOPE_SW: return CM_PALETTE_SHADE_NE; case SLOPE_STEEP_W: case SLOPE_W: return CM_PALETTE_SHADE_E; case SLOPE_NW: return CM_PALETTE_SHADE_SE; default: return PAL_NONE; } } } // namespace citymania