diff --git a/bin/data/CMakeLists.txt b/bin/data/CMakeLists.txt index f1b7d731f1..8c41e98c9a 100644 --- a/bin/data/CMakeLists.txt +++ b/bin/data/CMakeLists.txt @@ -1,5 +1,5 @@ set(DATA_SOURCE_FILES - ${CMAKE_CURRENT_SOURCE_DIR}/cmclient-5.grf + ${CMAKE_CURRENT_SOURCE_DIR}/cmclient-6.grf ) foreach(DATA_SOURCE_FILE IN LISTS DATA_SOURCE_FILES) diff --git a/bin/data/cmclient-5.grf b/bin/data/cmclient-5.grf deleted file mode 100644 index 5ba1aaa2da..0000000000 Binary files a/bin/data/cmclient-5.grf and /dev/null differ diff --git a/bin/data/cmclient-6.grf b/bin/data/cmclient-6.grf new file mode 100644 index 0000000000..c3bc8eed3b Binary files /dev/null and b/bin/data/cmclient-6.grf differ diff --git a/grf/cmclient/gencmclientgrf.py b/grf/cmclient/gencmclientgrf.py index 19119f4b99..5e6d31e1ca 100644 --- a/grf/cmclient/gencmclientgrf.py +++ b/grf/cmclient/gencmclientgrf.py @@ -152,6 +152,15 @@ for p in TILEDATA: y += h + SPRITE_MARGIN +tileicon_png = grf.ImageFile('sprites/tileicons.png') +sprite = lambda name, *args, **kw: g.add(grf.FileSprite(tileicon_png, *args, name=name, **kw)) +sprite('icon_house_removed', 2, 2, 20, 20, xofs=-9, yofs=10) +sprite('icon_house_replaced', 24, 2, 20, 20, xofs=-9, yofs=10) +sprite('icon_house_new', 46, 2, 20, 20, xofs=-9, yofs=10) +sprite('icon_dead_end', 68, 2, 20, 20, xofs=-9, yofs=10) +sprite('icon_road', 90, 2, 20, 20, xofs=-9, yofs=10) +sprite('icon_house_denied', 112, 2, 20, 20, xofs=-9, yofs=10) + def gen_tint(tint, ratio): tint = grf.srgb_to_oklab(np.array(tint) * 255) @@ -190,26 +199,9 @@ def gen_oklab_tint(tint, ratio): # (0.7433, 0, 0.15) # (0.7418, -0.09, 0.15) - remap = lambda f: g.add(grf.PaletteRemap.oklab_from_function(f, remap_range=grf.ALL_COLOURS)) -remap(gen_tint((1, 0, 0), 0.6)) # deep red tint -remap(gen_tint((1, 0.5, 0), 0.65)) # deep orange tint -remap(gen_tint((0, 1, 0), 0.65)) # deep green tint -remap(gen_tint((0, 1, 1), 0.65)) # deep cyan tint -remap(gen_tint((1, 0, 0), 0.4)) # red tint -remap(gen_tint((1, 0.5, 0), 0.4)) # orange tint -remap(gen_tint((1.0, 1.0, 0), 0.4)) # yellow tint -# remap(gen_oklab_tint((0.5498, 0.17, 0.1), 0.5)) # red tint -# remap(gen_oklab_tint((0.7433, 0.09, 0.15), 0.5)) # orange tint -# remap(gen_oklab_tint((0.7433, 0, 0.15), 0.5)) # yellow tint -remap(gen_tint((1.0, 1.0, 0.5), 0.4)) # yellow white tint -remap(gen_white_tint_contrast()) # white tint -# remap(gen_oklab_tint((0.7418, -0.09, 0.15), 0.5)) # white tint -remap(gen_tint((0, 1.0, 0), 0.4)) # green tint -remap(gen_tint((0, 1.0, 1.0), 0.4)) # cyan tint -remap(gen_tint((0.5, 1.0, 1.0), 0.4)) # cyan white tint -remap(gen_tint((0, 0, 1.0), 0.4)) # blue tint +# Tree shades B = 22.2 remap(gen_brightness(21.7 - B)) # shade N remap(gen_brightness(24.2 - B)) # shade NE 27.2 @@ -220,7 +212,38 @@ remap(gen_brightness(18.4 - B)) # shade SW remap(gen_brightness(17.1 - B)) # shade W remap(gen_brightness(17.5 - B)) # shade NW -grf.main(g, '../../bin/data/cmclient-5.grf') +BASE_TINTS = { + 'red_deep': gen_tint((1, 0, 0), 0.6), + 'orange_deep': gen_tint((1, 0.5, 0), 0.65), + #'green_deep': gen_tint((0, 1, 0), 0.65), + #'cyan_deep': gen_tint((0, 1, 1), 0.65), + 'red': gen_tint((1, 0, 0), 0.4), + 'orange': gen_tint((1, 0.5, 0), 0.4), + 'yellow': gen_tint((1.0, 1.0, 0), 0.4), + 'white': gen_white_tint_contrast(), + 'green': gen_tint((0, 1.0, 0), 0.4), + 'cyan': gen_tint((0, 1.0, 1.0), 0.4), + 'blue': gen_tint((0, 0, 1.0), 0.4), +} +# remap(gen_tint((0.5, 1.0, 1.0), 0.4)) # cyan white tint + +for f in BASE_TINTS.values(): + remap(f) + +for k1, f1 in BASE_TINTS.items(): + for k2, f2 in BASE_TINTS.items(): + if k1 == 'cyan' and k2 == 'white': + remap(gen_tint((0.5, 1.0, 1.0), 0.4)) + else: + remap(lambda x: f2(f1(x))) + +# Only white can be mixed over any combination +white = BASE_TINTS['white'] +for f1 in BASE_TINTS.values(): + for f2 in BASE_TINTS.values(): + remap(lambda x: white(f2(f1(x)))) + +grf.main(g, '../../bin/data/cmclient-6.grf') # func = gen_brightness(17.1 - B) # grf.make_palette_image([grf.oklab_to_srgb(func(x)) for x in grf.OKLAB_PALETTE]).show() diff --git a/src/citymania/cm_blueprint.cpp b/src/citymania/cm_blueprint.cpp index 85559ec89f..b9ce5a4f11 100644 --- a/src/citymania/cm_blueprint.cpp +++ b/src/citymania/cm_blueprint.cpp @@ -572,7 +572,7 @@ void SetBlueprintHighlight(const TileInfo *ti, TileHighlight &th) { return; if (_active_blueprint.second->HasSourceTile(ti->tile)) { - th.ground_pal = th.structure_pal = CM_PALETTE_TINT_BLUE; + th.tint_all(CM_PALETTE_TINT_BLUE); } } diff --git a/src/citymania/cm_game.cpp b/src/citymania/cm_game.cpp index 6612c4afee..03880fd5d7 100644 --- a/src/citymania/cm_game.cpp +++ b/src/citymania/cm_game.cpp @@ -3,6 +3,7 @@ #include "cm_game.hpp" #include "../company_base.h" +#include "../timer/timer.h" #include "../safeguards.h" diff --git a/src/citymania/cm_highlight.cpp b/src/citymania/cm_highlight.cpp index 2ce17d3d09..fcdda00cf2 100644 --- a/src/citymania/cm_highlight.cpp +++ b/src/citymania/cm_highlight.cpp @@ -156,6 +156,38 @@ struct std::hash { }; namespace citymania { + +TileArea ClampToVisibleMap(const TileArea &area) { + if (area.tile >= Map::Size()) return {}; + auto x = TileX(area.tile); + auto y = TileY(area.tile); + uint16_t w = area.w; + uint16_t h = area.h; + uint16_t border = 0; + if (_settings_game.construction.freeform_edges) { + if (x == 0) { + x = 1; + if (w == 0) return {}; + w -= 1; + } else if (x >= Map::SizeX() - 1) + return {}; + + if (y == 0) { + y = 1; + if (h == 0) return {}; + h -= 1; + } else if (y >= Map::SizeY() - 1) + return {}; + + border = 1; + } + return TileArea{ + TileXY(x, y), + std::min(w, Map::SizeX() - border - x), + std::min(h, Map::SizeY() - border - y) + }; +} + extern CargoArray GetProductionAroundTiles(TileIndex tile, int w, int h, int rad); extern void (*DrawTileSelectionRect)(const TileInfo *ti, PaletteID pal); @@ -377,7 +409,7 @@ bool ObjectTileHighlight::operator==(const ObjectTileHighlight &oh) const { return true; } -bool ObjectTileHighlight::SetTileHighlight(TileHighlight &th, const TileInfo *) const { +bool ObjectTileHighlight::SetTileHighlight(TileHighlight &th, const TileInfo *ti) const { switch (this->type) { case ObjectTileHighlight::Type::RAIL_DEPOT: // case ObjectTileHighlight::Type::RAIL_TRACK: Depot track shouldn't remove foundation @@ -391,14 +423,13 @@ bool ObjectTileHighlight::SetTileHighlight(TileHighlight &th, const TileInfo *) case ObjectTileHighlight::Type::INDUSTRY_TILE: case ObjectTileHighlight::Type::DOCK_SLOPE: case ObjectTileHighlight::Type::DOCK_FLAT: - th.structure_pal = CM_PALETTE_HIDE_SPRITE; - th.highlight_ground_pal = th.highlight_structure_pal = this->palette; + th.set_structure(this->palette); return true; case ObjectTileHighlight::Type::TINT: - th.ground_pal = th.structure_pal = this->palette; + th.tint_all(this->palette); return true; case ObjectTileHighlight::Type::STRUCT_TINT: - th.structure_pal = this->palette; + th.tint_structure_prio(this->palette); return true; default: @@ -437,20 +468,22 @@ ObjectHighlight ObjectHighlight::make_rail_depot(TileIndex tile, DiagDirection d return oh; } -ObjectHighlight ObjectHighlight::make_rail_station(TileIndex start_tile, TileIndex end_tile, Axis axis, StationClassID station_class, uint16_t station_type) { +ObjectHighlight ObjectHighlight::make_rail_station(TileIndex start_tile, uint16_t w, uint16_t h, Axis axis, StationClassID station_class, uint16_t station_type) { auto oh = ObjectHighlight{ObjectHighlight::Type::RAIL_STATION}; oh.tile = start_tile; - oh.end_tile = end_tile; + oh.w = w; + oh.h = h; oh.axis = axis; oh.rail_station_class = station_class; oh.rail_station_type = station_type; return oh; } -ObjectHighlight ObjectHighlight::make_road_stop(TileIndex start_tile, TileIndex end_tile, RoadType roadtype, DiagDirection orientation, bool is_truck, RoadStopClassID spec_class, uint16_t spec_index) { +ObjectHighlight ObjectHighlight::make_road_stop(TileIndex start_tile, uint16_t w, uint16_t h, RoadType roadtype, DiagDirection orientation, bool is_truck, RoadStopClassID spec_class, uint16_t spec_index) { auto oh = ObjectHighlight{ObjectHighlight::Type::ROAD_STOP}; oh.tile = start_tile; - oh.end_tile = end_tile; + oh.w = w; + oh.h = h; oh.ddir = orientation; oh.roadtype = roadtype; oh.is_truck = is_truck; @@ -540,6 +573,13 @@ static const DiagDirection _place_depot_extra_dir[12] = { void ObjectHighlight::AddTile(TileIndex tile, ObjectTileHighlight &&oh) { if (tile >= Map::Size()) return; + if (_settings_game.construction.freeform_edges) { + auto x = TileX(tile); + auto y = TileY(tile); + if (x == 0 || x >= Map::SizeX() - 1) return; + if (y == 0 || y >= Map::SizeY() - 1) return; + } + this->tiles.insert(std::make_pair(tile, std::move(oh))); } @@ -548,6 +588,9 @@ uint16_t GetPurchaseStationCallback(CallbackID callback, uint32_t param1, uint32 std::map, std::vector> _station_layout_cache; std::vector &GetPreviewStationLayout(const StationSpec *statspec, Axis axis, TileArea area) { + static std::vector _empty_layout; + if (area.CMIsEmpty()) return _empty_layout; + std::tuple key{statspec, axis, area.tile, area.w, area.h}; auto it = _station_layout_cache.find(key); if (it != _station_layout_cache.end()) return it->second; @@ -613,7 +656,7 @@ void ObjectHighlight::UpdateTiles() { break; } case Type::RAIL_STATION: { - auto ta = OrthogonalTileArea(this->tile, this->end_tile); + auto ta = OrthogonalTileArea(this->tile, this->w, this->h); auto numtracks = ta.w; auto plat_len = ta.h; if (this->axis == AXIS_X) std::swap(numtracks, plat_len); @@ -631,6 +674,8 @@ void ObjectHighlight::UpdateTiles() { ).test(); auto palette = (this->cost.Succeeded() ? CM_PALETTE_TINT_WHITE : CM_PALETTE_TINT_RED_DEEP); + ta = ClampToVisibleMap(ta); + /* Note: since ta is clamped to map preview may not be accurate, but it's even worse with wrapping. */ const StationSpec *statspec = StationClass::Get(this->rail_station_class)->GetSpec(this->rail_station_type); auto layout = GetPreviewStationLayout(statspec, this->axis, ta); auto it = layout.begin(); @@ -648,7 +693,7 @@ void ObjectHighlight::UpdateTiles() { break; } case Type::ROAD_STOP: { - auto ta = OrthogonalTileArea(this->tile, this->end_tile); + auto ta = OrthogonalTileArea(this->tile, this->w, this->h); this->cost = cmd::BuildRoadStop( this->tile, ta.w, @@ -663,6 +708,7 @@ void ObjectHighlight::UpdateTiles() { true ).test(); auto palette = (this->cost.Succeeded() ? CM_PALETTE_TINT_WHITE : CM_PALETTE_TINT_RED_DEEP); + ta = ClampToVisibleMap(ta); for (TileIndex tile : ta) { this->AddTile(tile, ObjectTileHighlight::make_road_stop(palette, this->roadtype, this->ddir, this->is_truck, this->road_stop_spec_class, this->road_stop_spec_index)); } @@ -694,7 +740,12 @@ void ObjectHighlight::UpdateTiles() { if (!as->IsAvailable() || this->airport_layout >= as->layouts.size()) break; Direction rotation = as->layouts[this->airport_layout].rotation; if (rotation == INVALID_DIR) break; + uint16_t w = as->size_x; + uint16_t h = as->size_y; + if (rotation == DIR_E || rotation == DIR_W) std::swap(w, h); + auto ta = ClampToVisibleMap(TileArea{this->tile, w, h}); for (AirportTileTableIterator iter(as->layouts[this->airport_layout].tiles, tile); iter != INVALID_TILE; ++iter) { + if (!ta.Contains(iter)) continue; this->AddTile(iter, ObjectTileHighlight::make_airport_tile(palette, iter.GetStationGfx())); } break; @@ -971,12 +1022,44 @@ void HighlightMap::AddTilesBorder(const std::set &tiles, SpriteID pal } } +SpriteID MixTints(SpriteID bottom, SpriteID top) { + if (top == PAL_NONE) return bottom; + if (bottom == PAL_NONE) return top; + assert (bottom >= CM_PALETTE_TINT_BASE && bottom < CM_PALETTE_TINT_END); + assert (top >= CM_PALETTE_TINT_BASE && top < CM_PALETTE_TINT_MIXES); + if (bottom < CM_PALETTE_TINT_MIXES) { + // Single tint -> use mixed + return ( + CM_PALETTE_TINT_MIXES + + (bottom - CM_PALETTE_TINT_BASE) * CM_PALETTE_TINT_BASE_COUNT + + (top - CM_PALETTE_TINT_BASE) + ); + } + // White mix can't be mixed again + if (bottom >= CM_PALETTE_TINT_MIXES_WHITE) { + Debug(misc, 0, "White highlights can't be stacked on white mixes"); + return bottom; + } + if (top == CM_PALETTE_TINT_WHITE) { + // Use same mix but in the white range + return bottom - CM_PALETTE_TINT_MIXES + CM_PALETTE_TINT_MIXES_WHITE; + } + + // Mix two last tints + auto last_mixed = (bottom - CM_PALETTE_TINT_MIXES) % CM_PALETTE_TINT_BASE_COUNT; + return ( + CM_PALETTE_TINT_MIXES + + last_mixed * CM_PALETTE_TINT_BASE_COUNT + + (top - CM_PALETTE_TINT_BASE) + ); +} + 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_GREEN: return CM_PALETTE_TINT_GREEN; + case CM_SPR_PALETTE_ZONING_LIGHT_BLUE: return 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; @@ -992,10 +1075,8 @@ SpriteID GetSelectionColourByTint(SpriteID colour) { 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: @@ -1007,6 +1088,19 @@ SpriteID GetSelectionColourByTint(SpriteID colour) { } } +void TileHighlight::set_old_selection(SpriteID sprite) { + this->selection = sprite; + this->tint_ground(GetTintBySelectionColour(sprite)); +} + +void TileHighlight::tint_ground(SpriteID colour) { + this->ground_pal = MixTints(this->ground_pal, colour); +} + +void TileHighlight::tint_structure(SpriteID colour) { + this->structure_pal = MixTints(this->structure_pal, colour); +} + void DrawTrainDepotSprite(SpriteID palette, const TileInfo *ti, RailType railtype, DiagDirection ddir) { const DrawTileSprites *dts = &_depot_gfx_table[ddir]; @@ -1155,13 +1249,13 @@ struct PreviewStationScopeResolver : public StationScopeResolver { struct StationPreivewResolverObject : public StationResolverObject { PreviewStationScopeResolver preview_station_scope; TileIndex tile; - TileIndexDiffC offset; + TileIndexDiffC offset; // TODO remove? StationPreivewResolverObject(const StationSpec *statspec, TileIndex tile, TileArea area, StationGfx gfx, Axis axis, bool purchase, CallbackID callback = CBID_NO_CALLBACK, uint32_t callback_param1 = 0, uint32_t callback_param2 = 0) : StationResolverObject(statspec, nullptr, tile, callback, callback_param1, callback_param2), preview_station_scope{*this, statspec, tile, area, gfx, axis, purchase}, - tile{tile}, offset{offset} { + tile{tile}, offset{} { CargoType ctype = (purchase ? CargoGRFFileProps::SG_PURCHASE : CargoGRFFileProps::SG_DEFAULT_NA); this->root_spritegroup = statspec->grf_prop.GetSpriteGroup(ctype); @@ -1222,7 +1316,7 @@ SpriteID GetCustomPreviewStationRelocation(const StationSpec *statspec, uint32_t void DrawTrainStationSprite(SpriteID palette, const TileInfo *ti, RailType railtype, Axis axis, uint8_t section, StationClassID spec_class, uint16_t spec_index, TileArea area) { int32 total_offset = 0; - StationGfx gfx = section + (axis == AXIS_X ? 0 : 1); + StationGfx gfx = (section & ~1) + (axis == AXIS_X ? 0 : 1); const StationSpec *statspec = StationClass::Get(spec_class)->GetSpec(spec_index); const NewGRFSpriteLayout *layout = nullptr; const DrawTileSprites *t = nullptr; @@ -1832,15 +1926,13 @@ TileHighlight ObjectHighlight::GetTileHighlight(const TileInfo *ti) { return th; } -HighlightMap ObjectHighlight::GetHighlightMap(SpriteID palette) { +void ObjectHighlight::AddToHighlightMap(HighlightMap &hlmap, SpriteID palette) { // TODO remove the need to convert (maybe replace HighlightMap with multimap?) - HighlightMap res; for (auto &[tile, oth] : this->tiles) { auto othp = oth; othp.palette = palette; - res.Add(tile, othp); + hlmap.Add(tile, othp); } - return res; } std::optional ObjectHighlight::GetArea() { @@ -1855,7 +1947,7 @@ std::optional ObjectHighlight::GetArea() { return TileArea{this->tile, 1, 1}; case Type::RAIL_STATION: case Type::ROAD_STOP: - return TileArea{this->tile, this->end_tile}; + return TileArea{this->tile, this->w, this->h}; case Type::AIRPORT: { const AirportSpec *as = AirportSpec::Get(this->airport_type); if (!as->IsAvailable() || this->airport_layout >= as->layouts.size()) return std::nullopt; @@ -2060,14 +2152,11 @@ static void SetStationSelectionHighlight(const TileInfo *ti, TileHighlight &th) 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); if (_thd.make_square_red) { - th.ground_pal = th.structure_pal = CM_PALETTE_TINT_RED; - th.highlight_structure_pal = th.highlight_ground_pal = CM_PALETTE_TINT_RED; + th.tint_ground(CM_PALETTE_TINT_RED); + th.set_structure(CM_PALETTE_TINT_RED); } else { - th.ground_pal = PAL_NONE; - th.structure_pal = CM_PALETTE_HIDE_SPRITE; - th.highlight_structure_pal = th.highlight_ground_pal = PAL_NONE; + th.hide_structure(); } return; } @@ -2083,11 +2172,10 @@ static void SetStationSelectionHighlight(const TileInfo *ti, TileHighlight &th) }; 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]; + th.tint_all(pal2[b.second]); } } @@ -2162,14 +2250,6 @@ void CalcCBTownLimitBorder(TileHighlight &th, TileIndex tile, SpriteID border_pa TileHighlight GetTileHighlight(const TileInfo *ti, TileType tile_type) { TileHighlight th; - auto hl = _at.tiles.GetForTile(ti->tile); - if (hl.has_value()) { - for (auto &oth : hl.value().get()) { - oth.SetTileHighlight(th, ti); - } - return th; - } - th = _thd.cm.GetTileHighlight(ti);; if (ti->tile == INVALID_TILE || tile_type == MP_VOID) return th; if (_zoning.outer == citymania::EvaluationMode::CHECKTOWNZONES) { @@ -2184,7 +2264,7 @@ TileHighlight GetTileHighlight(const TileInfo *ti, TileType tile_type) { 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); + th.tint_all(GetTintBySelectionColour(color)); if (CB_Enabled()) CalcCBTownLimitBorder(th, ti->tile, CM_SPR_PALETTE_ZONING_RED, PAL_NONE); } else if (_zoning.outer == citymania::EvaluationMode::CHECKSTACATCH) { @@ -2195,39 +2275,42 @@ TileHighlight GetTileHighlight(const TileInfo *ti, TileType tile_type) { 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; + th.set_icon(CM_SPR_TILE_ICON_HOUSE_REMOVED, CM_PALETTE_TINT_CYAN); + th.tint_ground(CM_PALETTE_TINT_CYAN); break; case TownGrowthTileState::RH_REBUILT: - th.selection = CM_SPR_PALETTE_ZONING_WHITE; - th.structure_pal = CM_PALETTE_TINT_WHITE; + th.set_icon(CM_SPR_TILE_ICON_HOUSE_REPLACED, CM_PALETTE_TINT_BLUE); + th.tint_ground(CM_PALETTE_TINT_BLUE); break; case TownGrowthTileState::NEW_HOUSE: - th.selection = CM_SPR_PALETTE_ZONING_GREEN; - th.structure_pal = CM_PALETTE_TINT_GREEN; + th.set_icon(CM_SPR_TILE_ICON_HOUSE_NEW, CM_PALETTE_TINT_GREEN); + th.tint_ground(CM_PALETTE_TINT_GREEN); break; case TownGrowthTileState::CS: - th.selection = CM_SPR_PALETTE_ZONING_ORANGE; + th.set_icon(CM_SPR_TILE_ICON_DEAD_END, CM_PALETTE_TINT_ORANGE); + th.tint_ground(CM_PALETTE_TINT_ORANGE); break; case TownGrowthTileState::HS: - th.selection = CM_SPR_PALETTE_ZONING_YELLOW; + th.set_icon(CM_SPR_TILE_ICON_ROAD, CM_PALETTE_TINT_YELLOW); + th.tint_ground(CM_PALETTE_TINT_YELLOW); break; case TownGrowthTileState::HR: - th.selection = CM_SPR_PALETTE_ZONING_RED; + th.set_icon(CM_SPR_TILE_ICON_HOUSE_DENIED, CM_PALETTE_TINT_RED); + th.tint_ground(CM_PALETTE_TINT_RED); break; - default: th.selection = PAL_NONE; + default: break; } - if (th.selection) th.ground_pal = GetTintBySelectionColour(th.selection); } else if (_zoning.outer == citymania::EvaluationMode::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; + th.tint_all(CM_PALETTE_TINT_RED_DEEP); } } else if (_zoning.outer == citymania::EvaluationMode::CHECKINDUNSER) { auto pal = GetIndustryZoningPalette(ti->tile); - if (pal) th.ground_pal = th.structure_pal = CM_PALETTE_TINT_RED_DEEP; + if (pal) th.tint_all(CM_PALETTE_TINT_RED_DEEP); } else if (_zoning.outer == citymania::EvaluationMode::CHECKTOWNADZONES) { auto getter = [](TileIndex t) { return _mz[t.base()].advertisement_zone; }; auto b = CalcTileBorders(ti->tile, getter); @@ -2239,7 +2322,7 @@ TileHighlight GetTileHighlight(const TileInfo *ti, TileType tile_type) { if (station) check_tile = station->xy; } auto z = getter(check_tile); - if (z) th.ground_pal = th.structure_pal = GetTintBySelectionColour(pal[z]); + if (z) th.tint_all(GetTintBySelectionColour(pal[z])); } else if (_zoning.outer == citymania::EvaluationMode::CHECKCBACCEPTANCE) { CalcCBAcceptanceBorders(th, ti->tile, CM_SPR_PALETTE_ZONING_WHITE, CM_PALETTE_TINT_WHITE); } else if (_zoning.outer == citymania::EvaluationMode::CHECKCBTOWNLIMIT) { @@ -2257,7 +2340,7 @@ TileHighlight GetTileHighlight(const TileInfo *ti, TileType tile_type) { 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 (z) th.tint_all(GetTintBySelectionColour(pal[z])); } if (_settings_client.gui.cm_show_industry_forbidden_tiles && @@ -2265,19 +2348,25 @@ TileHighlight GetTileHighlight(const TileInfo *ti, TileType tile_type) { 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; + th.tint_all(CM_PALETTE_TINT_RED); } SetStationSelectionHighlight(ti, th); SetBlueprintHighlight(ti, th); + auto hl = _at.tiles.GetForTile(ti->tile); + if (hl.has_value()) { + for (auto &oth : hl->get()) { + oth.SetTileHighlight(th, ti); + } + } 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]); + DrawBorderSprites(ti, th.border[i], th.border_colour[i]); if (th.sprite) { DrawSelectionSprite(th.sprite, PAL_NONE, ti, 0, FOUNDATION_PART_NORMAL); } @@ -2395,21 +2484,20 @@ HighLightStyle UpdateTileSelection(HighLightStyle new_drawstyle) { || _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, Map::SizeX()) - 1, - std::min((_thd.new_pos.y + _thd.new_size.y) / TILE_SIZE, Map::SizeY()) - 1 - ); + auto w = _thd.new_size.x / TILE_SIZE; + auto h = _thd.new_size.y / TILE_SIZE; if (_thd.select_proc == DDSP_BUILD_STATION) _thd.cm_new = ObjectHighlight::make_rail_station( start_tile, - end_tile, + w, + h, _station_gui.axis, _station_gui.sel_class, _station_gui.sel_type ); else if (_thd.select_proc == DDSP_BUILD_BUSSTOP || _thd.select_proc == DDSP_BUILD_TRUCKSTOP) { auto ddir = _roadstop_gui.orientation; - auto ta = TileArea(start_tile, end_tile); + auto ta = TileArea(start_tile, w, h); if (pt.x != -1) { if (ddir >= DIAGDIR_END && ddir < STATIONDIR_AUTO) { // When placed on road autorotate anyway @@ -2428,7 +2516,8 @@ HighLightStyle UpdateTileSelection(HighLightStyle new_drawstyle) { } _thd.cm_new = ObjectHighlight::make_road_stop( start_tile, - end_tile, + w, + h, _cur_roadtype, ddir, _thd.select_proc == DDSP_BUILD_TRUCKSTOP, diff --git a/src/citymania/cm_highlight.hpp b/src/citymania/cm_highlight.hpp index 45e48754f2..7f8ee3df18 100644 --- a/src/citymania/cm_highlight.hpp +++ b/src/citymania/cm_highlight.hpp @@ -29,6 +29,7 @@ namespace citymania { // MEDIUM = 2, // SMALL = 3, // }; +SpriteID MixTints(SpriteID bottom, SpriteID top); TileHighlight GetTileHighlight(const TileInfo *ti, TileType tile_type); void DrawTileZoning(const TileInfo *ti, const TileHighlight &th, TileType tile_type); diff --git a/src/citymania/cm_highlight_type.hpp b/src/citymania/cm_highlight_type.hpp index 0787e49e68..ac1c6c6a3d 100644 --- a/src/citymania/cm_highlight_type.hpp +++ b/src/citymania/cm_highlight_type.hpp @@ -42,29 +42,72 @@ enum ZoningBorder: uint8 { }; DECLARE_ENUM_AS_BIT_SET(ZoningBorder); +TileArea ClampToVisibleMap(const TileArea &area); class TileHighlight { public: SpriteID ground_pal = PAL_NONE; SpriteID structure_pal = PAL_NONE; - SpriteID highlight_ground_pal = PAL_NONE; - SpriteID highlight_structure_pal = PAL_NONE; + SpriteID structure_pal_prio = PAL_NONE; // TODO rename as this is priority highlight not just structure + SpriteID icon = PAL_NONE; + SpriteID icon_pal = PAL_NONE; + bool structure_hidden = false; + SpriteID highlight_pal = PAL_NONE; SpriteID sprite = 0; SpriteID selection = PAL_NONE; ZoningBorder border[4] = {}; - SpriteID border_color[4] = {}; + SpriteID border_colour[4] = {}; uint border_count = 0; - void add_border(ZoningBorder border, SpriteID color) { - if (border == ZoningBorder::NONE || !color) return; + std::pair get_structure_pal() { + auto pal = this->structure_pal_prio; + if (pal == PAL_NONE) pal = this->structure_pal; + return {!this->structure_hidden, pal}; + } + + SpriteID pick_ground_pal(SpriteID colour) { + // TODO mix with colour? + if (this->structure_pal_prio != PAL_NONE) return this->structure_pal_prio; + if (this->ground_pal != PAL_NONE) return this->ground_pal; + return colour; + } + + SpriteID get_icon() { + if (this->structure_pal_prio != PAL_NONE) return PAL_NONE; + return this->icon; + } + + void add_border(ZoningBorder border, SpriteID colour) { + if (border == ZoningBorder::NONE || !colour) return; this->border[this->border_count] = border; - this->border_color[this->border_count] = color; + this->border_colour[this->border_count] = colour; this->border_count++; } - void tint_all(SpriteID color) { - if (!color) return; - this->ground_pal = this->structure_pal = color; + void set_old_selection(SpriteID sprite); + void tint_ground(SpriteID colour); + void tint_structure(SpriteID colour); + void tint_structure_prio(SpriteID colour) { + this->structure_pal_prio = colour; + } + + void tint_all(SpriteID colour) { + this->tint_ground(colour); + this->tint_structure(colour); + } + + void hide_structure() { + this->structure_hidden = true; + } + + void set_structure(SpriteID colour) { + this->hide_structure(); + this->highlight_pal = colour; + } + + void set_icon(SpriteID icon, SpriteID pal) { + this->icon = icon; + this->icon_pal = pal; } void clear_borders() { @@ -350,6 +393,8 @@ public: RoadType roadtype = INVALID_ROADTYPE; bool is_truck = false; RoadStopClassID road_stop_spec_class; + uint16_t w; + uint16_t h; uint16_t road_stop_spec_index; StationClassID rail_station_class; uint16_t rail_station_type; @@ -377,8 +422,8 @@ public: bool operator!=(const ObjectHighlight& oh) const; static ObjectHighlight make_rail_depot(TileIndex tile, DiagDirection ddir); - static ObjectHighlight make_rail_station(TileIndex start_tile, TileIndex end_tile, Axis axis, StationClassID station_class, uint16_t station_type); - static ObjectHighlight make_road_stop(TileIndex start_tile, TileIndex end_tile, RoadType roadtype, DiagDirection orientation, bool is_truck, RoadStopClassID spec_class, uint16_t spec_index); + static ObjectHighlight make_rail_station(TileIndex start_tile, uint16_t w, uint16_t h, Axis axis, StationClassID station_class, uint16_t station_type); + static ObjectHighlight make_road_stop(TileIndex start_tile, uint16_t w, uint16_t h, RoadType roadtype, DiagDirection orientation, bool is_truck, RoadStopClassID spec_class, uint16_t spec_index); static ObjectHighlight make_road_depot(TileIndex tile, RoadType roadtype, DiagDirection orientation); static ObjectHighlight make_airport(TileIndex start_tile, int airport_type, uint8_t airport_layout); static ObjectHighlight make_blueprint(TileIndex tile, sp blueprint); @@ -389,7 +434,7 @@ public: static ObjectHighlight make_dock(TileIndex tile, DiagDirection orientation); TileHighlight GetTileHighlight(const TileInfo *ti); - HighlightMap GetHighlightMap(SpriteID palette); + void AddToHighlightMap(HighlightMap &hlmap, SpriteID palette); std::optional GetArea(); void Draw(const TileInfo *ti); void DrawSelectionOverlay(DrawPixelInfo *dpi); diff --git a/src/citymania/cm_main.cpp b/src/citymania/cm_main.cpp index f0b528e024..ed511de0aa 100644 --- a/src/citymania/cm_main.cpp +++ b/src/citymania/cm_main.cpp @@ -14,6 +14,10 @@ namespace citymania { up _game = nullptr; +static IntervalTimer _event_new_month({TimerGameCalendar::MONTH, TimerGameCalendar::Priority::NONE}, [](auto) { + Emit(event::NewMonth{}); +}); + void ResetGame() { _game = make_up(); ResetEffectiveActionCounter(); diff --git a/src/citymania/cm_station_gui.cpp b/src/citymania/cm_station_gui.cpp index a8dcc78f66..01b883a2d1 100644 --- a/src/citymania/cm_station_gui.cpp +++ b/src/citymania/cm_station_gui.cpp @@ -650,10 +650,73 @@ bool HasSelectedStationHighlight() { return _station_highlight_mode != StationHighlightMode::None; } +static void UpdateStationAction(std::optional area, up cmdptr) { + if (UseImprovedStationJoin()) return; + + _station_action = StationAction::Create{}; + + if (!area.has_value()) return; + + if (_fn_mod) { + if (!_settings_game.station.distant_join_stations) return; + // TODO ctrl with overbuilding errors out in vanilla, shows empty picker here + _station_action = StationAction::Picker{}; + return; + } + + auto cmd = dynamic_cast(cmdptr.get()); + if (cmd == nullptr) return; + + cmd->station_to_join = NEW_STATION; + bool new_valid = cmd->test().Succeeded(); // TODO rarely needed + + std::set checked_joins; + std::vector valid_joins; // TODO probably don't need the whole vector + area->Expand(1); + area->ClampToMap(); + for (auto tile : *area) { + if (IsTileType(tile, MP_STATION) && GetTileOwner(tile) == _local_company) { + Station *st = Station::GetByTile(tile); + + auto it = checked_joins.find(st->index); + if (st == nullptr || it != checked_joins.end()) continue; + checked_joins.insert(it, st->index); + + cmd->station_to_join = st->index; + if (cmd->test().Succeeded()) valid_joins.push_back(st->index); + } + } + + // TODO w/o distant join and no ctrl -> adjacent=false + + // No nearby stations -> create new (default) + if (checked_joins.empty()) return; + + if (valid_joins.empty()) { + if (_settings_game.station.distant_join_stations && new_valid) { + // We can build a new one but require a picker for that + _station_action = StationAction::Picker{}; + } + // Error out, create action will do that + return; + } + + if (valid_joins.size() > 1) { + if (_settings_game.station.distant_join_stations) { + _station_action = StationAction::Picker{}; + } + // W/o distant join just error out, create action will do that + return; + } + + // Only one valid join -> do it + _station_action = StationAction::Join{valid_joins[0]}; +} + static HighlightMap PrepareHighilightMap(Station *st_join, std::optional ohl, SpriteID pal, bool show_join_area, bool show_coverage, uint rad) { bool add_current = true; // FIXME - auto hlmap = ohl.has_value() ? ohl->GetHighlightMap(pal) : HighlightMap{}; + HighlightMap hlmap{}; TileArea join_area; std::set coverage_area; @@ -665,8 +728,7 @@ static HighlightMap PrepareHighilightMap(Station *st_join, std::optionalcatchment_tiles) { - auto pal = join_area.Contains(t) ? CM_PALETTE_TINT_CYAN_WHITE : CM_PALETTE_TINT_WHITE; - hlmap.Add(t, ObjectTileHighlight::make_tint(pal)); + hlmap.Add(t, ObjectTileHighlight::make_tint(CM_PALETTE_TINT_WHITE)); coverage_area.insert(t); } } @@ -675,16 +737,16 @@ static HighlightMap PrepareHighilightMap(Station *st_join, std::optional rad_area = std::nullopt; if (area.has_value()) { - auto xarea = area.value(); + auto xarea = *area; xarea.Expand(rad); - xarea.ClampToMap(); + xarea = ClampToVisibleMap(xarea); rad_area = xarea; } if (show_coverage && add_current && rad_area.has_value()) { // Add current station coverage - for (auto t : rad_area.value()) { - auto pal = join_area.Contains(t) ? CM_PALETTE_TINT_CYAN_WHITE : CM_PALETTE_TINT_WHITE; - hlmap.Add(t, ObjectTileHighlight::make_tint(pal)); + for (auto t : *rad_area) { + if (coverage_area.find(t) != coverage_area.end()) continue; + hlmap.Add(t, ObjectTileHighlight::make_tint(CM_PALETTE_TINT_WHITE)); coverage_area.insert(t); } } @@ -702,6 +764,8 @@ static HighlightMap PrepareHighilightMap(Station *st_join, std::optionalAddToHighlightMap(hlmap, pal); + return hlmap; } @@ -771,21 +835,34 @@ ToolGUIInfo PlacementAction::PrepareGUIInfo(std::optional ohl, ohl.value().UpdateTiles(); auto palette = CM_PALETTE_TINT_WHITE; auto area = ohl.value().GetArea(); + StationID to_join = StationID::Invalid(); - auto cost = cmd->test(); - if (std::holds_alternative(_station_action)) { - palette = CM_PALETTE_TINT_YELLOW; - } else { + std::visit(Overload{ + [&](StationAction::Join &a) { + to_join = a.station; + }, + [&](StationAction::Create &) { + to_join = NEW_STATION; + }, + [&](StationAction::Picker &) { + palette = CM_PALETTE_TINT_YELLOW; + } + }, _station_action); + + if (to_join != StationID::Invalid()) { + auto station_cmd = dynamic_cast(cmd.get()); + if (station_cmd != nullptr) station_cmd->station_to_join = to_join; + } + + CommandCost cost = cmd->test(); + if (palette != CM_PALETTE_TINT_YELLOW) { palette = cost.Succeeded() ? CM_PALETTE_TINT_WHITE : CM_PALETTE_TINT_RED_DEEP; } bool show_coverage = _settings_client.gui.station_show_coverage; - Station *to_join = nullptr; - if (auto mode = std::get_if(&_station_action)) - to_join = Station::GetIfValid(mode->station); auto hlmap = PrepareHighilightMap( - to_join, + Station::GetIfValid(to_join), ohl.value(), palette, true, @@ -901,57 +978,7 @@ ToolGUIInfo PlacementAction::PrepareGUIInfo(std::optional ohl, void SizedPlacementAction::Update(Point, TileIndex tile) { this->cur_tile = tile; - if (UseImprovedStationJoin()) return; - - _station_action = StationAction::Create{}; - - auto area = this->GetArea(); - if (!area.has_value()) return; - auto cmdptr = this->GetCommand(tile, StationID::Invalid()); - auto cmd = dynamic_cast(cmdptr.get()); - if (cmd == nullptr) return; - - if (!_settings_game.station.distant_join_stations && _fn_mod) return; - - area->Expand(1); - area->ClampToMap(); - StationID to_join = StationID::Invalid(); - bool ambigous_join = false; - for (auto tile : area.value()) { - if (IsTileType(tile, MP_STATION) && GetTileOwner(tile) == _local_company) { - Station *st = Station::GetByTile(tile); - if (st == nullptr || st->index == to_join) continue; - if (to_join != StationID::Invalid()) { - to_join = StationID::Invalid(); - if (_settings_game.station.distant_join_stations) - ambigous_join = true; - break; - } - to_join = st->index; - // TODO check for command to return multiple? but also check each to - // see if they can be built - // if (this->GetCommand(true, st->index)->test().Succeeded()) { - // if (this->station_to_join != INVALID_STATION) { - // this->station_to_join = INVALID_STATION; - // this->palette = CM_PALETTE_TINT_YELLOW; - // break; - // } else this->station_to_join = st->index; - // } - } - } - - if (!_settings_game.station.distant_join_stations) return; - - if (ambigous_join) _station_action = StationAction::Picker{}; - else if (to_join != StationID::Invalid()) _station_action = StationAction::Join{to_join}; - else _station_action = StationAction::Create{}; - - // cmd->station_to_join = NEW_STATION; - // cmd->adjacent = true; - - // if (StationBuildTool::station_to_join == INVALID_STATION && !cmd->test().Succeeded()) { - // StationBuildTool::ambigous_join = false; - // } + UpdateStationAction(this->GetArea(), std::move(this->GetCommand(tile, StationID::Invalid()))); } bool SizedPlacementAction::HandleMousePress() { @@ -987,6 +1014,9 @@ std::optional DragNDropPlacementAction::GetArea() const { void DragNDropPlacementAction::Update(Point, TileIndex tile) { this->cur_tile = tile; + auto area = this->GetArea(); + if (!area.has_value()) return; + UpdateStationAction(area, std::move(this->GetCommand(*area, StationID::Invalid()))); } bool DragNDropPlacementAction::HandleMousePress() { @@ -1052,7 +1082,7 @@ ToolGUIInfo StationSelectAction::GetGUIInfo() { } void StationSelectAction::OnStationRemoved(const Station *station) { - // if (this->selected_station == station->index) this->selected_station = INVALID_STATION; + // if (this->selected_station == station->index) this->selected_station = StationID::Invalid(); } // --- Misc functions --- @@ -1088,17 +1118,14 @@ template bool ExecuteBuildCommand(Taction *action, Tcallback callback, Targ arg) { std::visit(Overload{ [&](StationAction::Join &a) { - Debug(misc, 0, "Join to {}", a.station); auto cmd = action->GetCommand(arg, a.station); return cmd ? cmd->post(callback) : false; }, [&](StationAction::Create &) { - Debug(misc, 0, "Create new station"); auto cmd = action->GetCommand(arg, NEW_STATION); return cmd ? cmd->post(callback) : false; }, [&](StationAction::Picker &) { - Debug(misc, 0, "Show picker"); auto cmd = action->GetCommand(arg, StationID::Invalid()); auto proc = [cmd=sp{std::move(cmd)}, callback](bool test, StationID to_join) -> bool { if (!cmd) return false; @@ -1204,17 +1231,14 @@ bool RailStationBuildTool::DragNDropPlacementAction::Execute(TileArea area) { } std::optional RailStationBuildTool::DragNDropPlacementAction::GetObjectHighlight(TileArea area) { - // Debug(misc, 0, "GetObjectHighlight {} {} ", _railstation.station_class, _railstation.station_type); - return ObjectHighlight::make_rail_station(area.tile, area.CMGetEndTile(), _station_gui.axis, _station_gui.sel_class, _station_gui.sel_type); + return ObjectHighlight::make_rail_station(area.tile, area.w, area.h, _station_gui.axis, _station_gui.sel_class, _station_gui.sel_type); } std::optional RailStationBuildTool::SizedPlacementAction::GetObjectHighlight(TileIndex tile) { - TileIndex end_tile; - if (_station_gui.axis == AXIS_X) - end_tile = TileAddXY(tile, _settings_client.gui.station_platlength - 1, _settings_client.gui.station_numtracks - 1); - else - end_tile = TileAddXY(tile, _settings_client.gui.station_numtracks - 1, _settings_client.gui.station_platlength - 1); - return ObjectHighlight::make_rail_station(tile, end_tile, _station_gui.axis, _station_gui.sel_class, _station_gui.sel_type); + uint16_t w = _settings_client.gui.station_numtracks; + uint16_t h = _settings_client.gui.station_platlength; + if (_station_gui.axis == AXIS_X) std::swap(w, h); + return ObjectHighlight::make_rail_station(tile, w, h, _station_gui.axis, _station_gui.sel_class, _station_gui.sel_type); } // --- RailStationBuildTool implementation --- @@ -1307,7 +1331,8 @@ bool RoadStopBuildTool::DragNDropPlacementAction::Execute(TileArea area) { std::optional RoadStopBuildTool::DragNDropPlacementAction::GetObjectHighlight(TileArea area) { return ObjectHighlight::make_road_stop( area.tile, - area.CMGetEndTile(), + area.w, + area.h, this->road_type, this->ddir, this->stop_type == RoadStopType::Truck, diff --git a/src/citymania/cm_zoning_cmd.cpp b/src/citymania/cm_zoning_cmd.cpp index e69602bb31..e5c0d4e66c 100644 --- a/src/citymania/cm_zoning_cmd.cpp +++ b/src/citymania/cm_zoning_cmd.cpp @@ -378,13 +378,6 @@ SpriteID GetTownZoneBorderColor(uint8 zone) { case 4: return CM_SPR_PALETTE_ZONING_ORANGE; // Tz3 case 5: return CM_SPR_PALETTE_ZONING_RED; // Tz4 - center }; - switch (zone) { - default: return CM_SPR_PALETTE_ZONING_LIGHT_BLUE; // Tz0 - case 2: return CM_SPR_PALETTE_ZONING_RED; // Tz1 - case 3: return CM_SPR_PALETTE_ZONING_YELLOW; // Tz2 - case 4: return CM_SPR_PALETTE_ZONING_GREEN; // Tz3 - case 5: return CM_SPR_PALETTE_ZONING_WHITE; // Tz4 - center - }; } diff --git a/src/gfx.cpp b/src/gfx.cpp index 0a6d524d2b..20df3092fa 100644 --- a/src/gfx.cpp +++ b/src/gfx.cpp @@ -1000,35 +1000,14 @@ Dimension GetSpriteSize(SpriteID sprid, Point *offset, ZoomLevel zoom) */ static BlitterMode GetBlitterMode(PaletteID pal) { + + if (pal >= CM_PALETTE_TINT_BASE && pal < CM_PALETTE_TINT_END) + return BlitterMode::CMTintRemap; + switch (pal) { case PAL_NONE: return BlitterMode::Normal; case PALETTE_CRASH: return BlitterMode::CrashRemap; case PALETTE_ALL_BLACK: return BlitterMode::BlackRemap; - - case CM_PALETTE_TINT_RED_DEEP: - case CM_PALETTE_TINT_ORANGE_DEEP: - case CM_PALETTE_TINT_GREEN_DEEP: - case CM_PALETTE_TINT_CYAN_DEEP: - case CM_PALETTE_TINT_RED: - case CM_PALETTE_TINT_ORANGE: - case CM_PALETTE_TINT_YELLOW: - case CM_PALETTE_TINT_YELLOW_WHITE: - case CM_PALETTE_TINT_WHITE: - case CM_PALETTE_TINT_GREEN: - case CM_PALETTE_TINT_CYAN: - case CM_PALETTE_TINT_CYAN_WHITE: - case CM_PALETTE_TINT_BLUE: - case CM_PALETTE_SHADE_N: - case CM_PALETTE_SHADE_NE: - case CM_PALETTE_SHADE_E: - case CM_PALETTE_SHADE_SE: - case CM_PALETTE_SHADE_S: - case CM_PALETTE_SHADE_SW: - case CM_PALETTE_SHADE_W: - case CM_PALETTE_SHADE_NW: - case CM_PALETTE_TINT_COUNT: - return BlitterMode::CMTintRemap; - default: return BlitterMode::ColourRemap; } } diff --git a/src/gfxinit.cpp b/src/gfxinit.cpp index f57f45ddfa..8d175eed40 100644 --- a/src/gfxinit.cpp +++ b/src/gfxinit.cpp @@ -195,7 +195,7 @@ static void LoadSpriteTables() PAL_DOS != used_set->palette ); } - LoadGrfFile("cmclient-5.grf", CM_SPR_CITYMANIA_BASE, PAL_DOS != used_set->palette); + LoadGrfFile("cmclient-6.grf", CM_SPR_CITYMANIA_BASE, PAL_DOS != used_set->palette); /* Initialize the unicode to sprite mapping table */ InitializeUnicodeGlyphMap(); diff --git a/src/table/sprites.h b/src/table/sprites.h index cab12f041f..77600d7631 100644 --- a/src/table/sprites.h +++ b/src/table/sprites.h @@ -351,32 +351,48 @@ static const SpriteID CM_SPR_INNER_HIGHLIGHT_COUNT = 30; static const SpriteID CM_SPR_BORDER_HIGHLIGHT_BASE = CM_SPR_INNER_HIGHLIGHT_BASE + CM_SPR_INNER_HIGHLIGHT_COUNT; static const SpriteID CM_SPR_BORDER_HIGHLIGHT_COUNT = 19 * 19; -static const SpriteID CM_PALETTE_TINT_BASE = CM_SPR_BORDER_HIGHLIGHT_BASE + CM_SPR_BORDER_HIGHLIGHT_COUNT; + +static const SpriteID CM_SPR_TILE_ICON_BASE = CM_SPR_BORDER_HIGHLIGHT_BASE + CM_SPR_BORDER_HIGHLIGHT_COUNT; +static const SpriteID CM_SPR_TILE_ICON_HOUSE_REMOVED = CM_SPR_TILE_ICON_BASE; +static const SpriteID CM_SPR_TILE_ICON_HOUSE_REPLACED = CM_SPR_TILE_ICON_BASE + 1; +static const SpriteID CM_SPR_TILE_ICON_HOUSE_NEW = CM_SPR_TILE_ICON_BASE + 2; +static const SpriteID CM_SPR_TILE_ICON_DEAD_END = CM_SPR_TILE_ICON_BASE + 3; +static const SpriteID CM_SPR_TILE_ICON_ROAD = CM_SPR_TILE_ICON_BASE + 4; +static const SpriteID CM_SPR_TILE_ICON_HOUSE_DENIED = CM_SPR_TILE_ICON_BASE + 5; +static const SpriteID CM_SPR_TILE_ICON_COUNT = 6; + +/* Tree shades */ +static const SpriteID CM_PALETTE_SHADE_BASE = CM_SPR_TILE_ICON_BASE + CM_SPR_TILE_ICON_COUNT; +static const SpriteID CM_PALETTE_SHADE_N = CM_PALETTE_SHADE_BASE; +static const SpriteID CM_PALETTE_SHADE_NE = CM_PALETTE_SHADE_BASE + 1; +static const SpriteID CM_PALETTE_SHADE_E = CM_PALETTE_SHADE_BASE + 2; +static const SpriteID CM_PALETTE_SHADE_SE = CM_PALETTE_SHADE_BASE + 3; +static const SpriteID CM_PALETTE_SHADE_S = CM_PALETTE_SHADE_BASE + 4; +static const SpriteID CM_PALETTE_SHADE_SW = CM_PALETTE_SHADE_BASE + 5; +static const SpriteID CM_PALETTE_SHADE_W = CM_PALETTE_SHADE_BASE + 6; +static const SpriteID CM_PALETTE_SHADE_NW = CM_PALETTE_SHADE_BASE + 7; + +/* Tint remaps */ +static const SpriteID CM_PALETTE_TINT_BASE = CM_PALETTE_SHADE_BASE + 8; static const SpriteID CM_PALETTE_TINT_RED_DEEP = CM_PALETTE_TINT_BASE; static const SpriteID CM_PALETTE_TINT_ORANGE_DEEP = CM_PALETTE_TINT_BASE + 1; -static const SpriteID CM_PALETTE_TINT_GREEN_DEEP = CM_PALETTE_TINT_BASE + 2; -static const SpriteID CM_PALETTE_TINT_CYAN_DEEP = CM_PALETTE_TINT_BASE + 3; -static const SpriteID CM_PALETTE_TINT_RED = CM_PALETTE_TINT_BASE + 4; -static const SpriteID CM_PALETTE_TINT_ORANGE = CM_PALETTE_TINT_BASE + 5; -static const SpriteID CM_PALETTE_TINT_YELLOW = CM_PALETTE_TINT_BASE + 6; -static const SpriteID CM_PALETTE_TINT_YELLOW_WHITE = CM_PALETTE_TINT_BASE + 7; -static const SpriteID CM_PALETTE_TINT_WHITE = CM_PALETTE_TINT_BASE + 8; -static const SpriteID CM_PALETTE_TINT_GREEN = CM_PALETTE_TINT_BASE + 9; -static const SpriteID CM_PALETTE_TINT_CYAN = CM_PALETTE_TINT_BASE + 10; -static const SpriteID CM_PALETTE_TINT_CYAN_WHITE = CM_PALETTE_TINT_BASE + 11; -static const SpriteID CM_PALETTE_TINT_BLUE = CM_PALETTE_TINT_BASE + 12; -static const SpriteID CM_PALETTE_SHADE_N = CM_PALETTE_TINT_BASE + 13; -static const SpriteID CM_PALETTE_SHADE_NE = CM_PALETTE_TINT_BASE + 14; -static const SpriteID CM_PALETTE_SHADE_E = CM_PALETTE_TINT_BASE + 15; -static const SpriteID CM_PALETTE_SHADE_SE = CM_PALETTE_TINT_BASE + 16; -static const SpriteID CM_PALETTE_SHADE_S = CM_PALETTE_TINT_BASE + 17; -static const SpriteID CM_PALETTE_SHADE_SW = CM_PALETTE_TINT_BASE + 18; -static const SpriteID CM_PALETTE_SHADE_W = CM_PALETTE_TINT_BASE + 19; -static const SpriteID CM_PALETTE_SHADE_NW = CM_PALETTE_TINT_BASE + 20; -static const SpriteID CM_PALETTE_TINT_COUNT = 13 + 8; -static const SpriteID CM_PALETTE_HIDE_SPRITE = CM_PALETTE_TINT_BASE + CM_PALETTE_TINT_COUNT; +static const SpriteID CM_PALETTE_TINT_RED = CM_PALETTE_TINT_BASE + 2; +static const SpriteID CM_PALETTE_TINT_ORANGE = CM_PALETTE_TINT_BASE + 3; +static const SpriteID CM_PALETTE_TINT_YELLOW = CM_PALETTE_TINT_BASE + 4; +static const SpriteID CM_PALETTE_TINT_WHITE = CM_PALETTE_TINT_BASE + 5; +static const SpriteID CM_PALETTE_TINT_GREEN = CM_PALETTE_TINT_BASE + 6; +static const SpriteID CM_PALETTE_TINT_CYAN = CM_PALETTE_TINT_BASE + 7; +static const SpriteID CM_PALETTE_TINT_BLUE = CM_PALETTE_TINT_BASE + 8; +static const SpriteID CM_PALETTE_TINT_BASE_COUNT = 9; +static const SpriteID CM_PALETTE_TINT_MIXES = CM_PALETTE_TINT_BASE + CM_PALETTE_TINT_BASE_COUNT; +static const SpriteID CM_PALETTE_TINT_MIXES_WHITE = CM_PALETTE_TINT_BASE + CM_PALETTE_TINT_BASE_COUNT + CM_PALETTE_TINT_BASE_COUNT * CM_PALETTE_TINT_BASE_COUNT; +static const SpriteID CM_PALETTE_TINT_COUNT = CM_PALETTE_TINT_BASE_COUNT + 2 * CM_PALETTE_TINT_BASE_COUNT * CM_PALETTE_TINT_BASE_COUNT; + +/* Special flag to hide sprite */ +static const SpriteID CM_PALETTE_TINT_END = CM_PALETTE_TINT_BASE + CM_PALETTE_TINT_COUNT; + /* From where can we start putting NewGRFs? */ -static const SpriteID SPR_NEWGRFS_BASE = CM_PALETTE_TINT_BASE + CM_PALETTE_TINT_COUNT + 1; +static const SpriteID SPR_NEWGRFS_BASE = CM_PALETTE_TINT_END; /* Manager face sprites */ static const SpriteID SPR_GRADIENT = 874; // background gradient behind manager face diff --git a/src/tilearea_type.h b/src/tilearea_type.h index d19b7c876c..5a89360277 100644 --- a/src/tilearea_type.h +++ b/src/tilearea_type.h @@ -69,6 +69,11 @@ struct OrthogonalTileArea { return TileAddXY(this->tile, this->w - 1, this->h - 1); } + bool CMIsEmpty() const + { + return this->tile >= Map::Size() || this->w == 0 || this->h == 0; + } + OrthogonalTileIterator begin() const; OrthogonalTileIterator end() const; diff --git a/src/viewport.cpp b/src/viewport.cpp index 3e5e7bd056..fde86913c6 100644 --- a/src/viewport.cpp +++ b/src/viewport.cpp @@ -63,6 +63,7 @@ #include "stdafx.h" #include "core/backup_type.hpp" #include "landscape.h" +#include "table/sprites.h" #include "viewport_func.h" #include "station_base.h" #include "waypoint_base.h" @@ -621,7 +622,7 @@ void DrawGroundSpriteAt(SpriteID image, PaletteID pal, int32_t x, int32_t y, int { /* Switch to first foundation part, if no foundation was drawn */ if (_vd.foundation_part == FOUNDATION_PART_NONE) _vd.foundation_part = FOUNDATION_PART_NORMAL; - if (_vd.cm_highlight.ground_pal) pal = _vd.cm_highlight.ground_pal; + pal = _vd.cm_highlight.pick_ground_pal(pal); if (_vd.foundation[_vd.foundation_part] != -1) { Point pt = RemapCoords(x, y, z); AddChildSpriteToFoundation(image, pal, sub, _vd.foundation_part, pt.x + extra_offs_x * ZOOM_BASE, pt.y + extra_offs_y * ZOOM_BASE); @@ -730,10 +731,6 @@ void AddSortableSpriteToDraw(SpriteID image, PaletteID pal, int x, int y, int z, assert((image & SPRITE_MASK) < MAX_SPRITES); - if (!ignore_highlight_pal) { - if (_vd.cm_highlight.structure_pal) pal = _vd.cm_highlight.structure_pal; - } - /* Move to bounding box. */ x += bounds.origin.x; y += bounds.origin.y; @@ -743,6 +740,10 @@ void AddSortableSpriteToDraw(SpriteID image, PaletteID pal, int x, int y, int z, if (transparent) { SetBit(image, PALETTE_MODIFIER_TRANSPARENT); pal = PALETTE_TO_TRANSPARENT; + } else if (!ignore_highlight_pal) { + auto [draw, new_pal] = _vd.cm_highlight.get_structure_pal(); + if (!draw) return; + if (new_pal != PAL_NONE) pal = new_pal; } if (_vd.combine_sprites == SPRITE_COMBINE_ACTIVE) { @@ -751,7 +752,6 @@ void AddSortableSpriteToDraw(SpriteID image, PaletteID pal, int x, int y, int z, } _vd.last_child = LAST_CHILD_NONE; - if (!ignore_highlight_pal && pal == CM_PALETTE_HIDE_SPRITE) return; Point pt = RemapCoords(x + bounds.offset.x, y + bounds.offset.y, z + bounds.offset.z); int tmp_left, tmp_top, tmp_x = pt.x, tmp_y = pt.y; @@ -1367,9 +1367,17 @@ static void ViewportAddLandscape() _tile_type_procs[tile_type]->draw_tile_proc(&_cur_ti); + _vd.cm_highlight.structure_pal_prio = PAL_NONE; + _vd.cm_highlight.structure_hidden = false; + + auto icon = _vd.cm_highlight.get_icon(); + if (icon != PAL_NONE) { + _vd.cm_highlight.structure_pal = PAL_NONE; + AddSortableSpriteToDraw(icon, _vd.cm_highlight.icon_pal, _cur_ti, {{}, {1, 1, BB_HEIGHT_UNDER_BRIDGE}, {}}); + } + if (_cur_ti.tile != INVALID_TILE) { // CM TODO why is this check here? - _vd.cm_highlight.ground_pal = _vd.cm_highlight.highlight_ground_pal; - _vd.cm_highlight.structure_pal = _vd.cm_highlight.highlight_structure_pal; + _vd.cm_highlight.structure_pal = _vd.cm_highlight.highlight_pal; citymania::DrawTileZoning(&_cur_ti); // old zoning patch citymania::DrawTileZoning(&_cur_ti, _vd.cm_highlight, tile_type); DrawTileSelection(&_cur_ti);