Implement palette mixing in highlights

This commit is contained in:
dP
2025-11-29 01:19:34 +05:00
parent f356de80b1
commit e4ac251f42
9 changed files with 112 additions and 77 deletions

View File

@@ -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)

Binary file not shown.

View File

@@ -221,9 +221,12 @@ BASE_TINTS = {
for f in BASE_TINTS.values():
remap(f)
for f1 in BASE_TINTS.values():
for f2 in BASE_TINTS.values():
remap(lambda x: f2(f1(x)))
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']

View File

@@ -589,7 +589,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);
}
}

View File

@@ -408,7 +408,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
@@ -422,14 +422,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:
@@ -1050,7 +1049,7 @@ SpriteID MixTints(SpriteID bottom, SpriteID top) {
}
// White mix can't be mixed again
if (bottom >= CM_PALETTE_TINT_MIXES_WHITE) {
Debug(misc, 0, "White highlights can't be stacked");
Debug(misc, 0, "White highlights can't be stacked on white mixes");
return bottom;
}
if (top == CM_PALETTE_TINT_WHITE) {
@@ -1101,6 +1100,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];
@@ -1249,13 +1261,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{} {
CargoID ctype = (purchase ? SpriteGroupCargo::SG_PURCHASE : SpriteGroupCargo::SG_DEFAULT_NA);
this->root_spritegroup = statspec->grf_prop.spritegroup[ctype];
@@ -1937,15 +1949,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<TileArea> ObjectHighlight::GetArea() {
@@ -2166,14 +2176,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;
}
@@ -2189,11 +2196,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]);
}
}
@@ -2268,15 +2274,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->get()) {
oth.SetTileHighlight(th, ti);
Debug(misc, 0, "GetTileHighlight {} {}:{}", ti->tile, oth.type, th.structure_pal == CM_PALETTE_HIDE_SPRITE);
}
return th;
}
th = _thd.cm.GetTileHighlight(ti);;
if (ti->tile == INVALID_TILE || tile_type == MP_VOID) return th;
if (_zoning.outer == CHECKTOWNZONES) {
@@ -2291,7 +2288,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 == CHECKSTACATCH) {
@@ -2302,39 +2299,38 @@ 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_old_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;
th.set_old_selection(CM_SPR_PALETTE_ZONING_WHITE);
th.tint_structure(CM_PALETTE_TINT_WHITE);
break;
case TownGrowthTileState::NEW_HOUSE:
th.selection = CM_SPR_PALETTE_ZONING_GREEN;
th.structure_pal = CM_PALETTE_TINT_GREEN;
th.set_old_selection(CM_SPR_PALETTE_ZONING_GREEN);
th.tint_structure(CM_PALETTE_TINT_GREEN);
break;
case TownGrowthTileState::CS:
th.selection = CM_SPR_PALETTE_ZONING_ORANGE;
th.set_old_selection(CM_SPR_PALETTE_ZONING_ORANGE);
break;
case TownGrowthTileState::HS:
th.selection = CM_SPR_PALETTE_ZONING_YELLOW;
th.set_old_selection(CM_SPR_PALETTE_ZONING_YELLOW);
break;
case TownGrowthTileState::HR:
th.selection = CM_SPR_PALETTE_ZONING_RED;
th.set_old_selection(CM_SPR_PALETTE_ZONING_RED);
break;
default: th.selection = PAL_NONE;
default: th.set_old_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;
th.tint_all(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;
if (pal) th.tint_all(CM_PALETTE_TINT_RED_DEEP);
} else if (_zoning.outer == CHECKTOWNADZONES) {
auto getter = [](TileIndex t) { return _mz[t.base()].advertisement_zone; };
auto b = CalcTileBorders(ti->tile, getter);
@@ -2346,7 +2342,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 == CHECKCBACCEPTANCE) {
CalcCBAcceptanceBorders(th, ti->tile, CM_SPR_PALETTE_ZONING_WHITE, CM_PALETTE_TINT_WHITE);
} else if (_zoning.outer == CHECKCBTOWNLIMIT) {
@@ -2364,7 +2360,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 &&
@@ -2372,19 +2368,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);
}

View File

@@ -48,24 +48,54 @@ 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;
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<bool, SpriteID> 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;
}
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 clear_borders() {
@@ -392,7 +422,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<TileArea> GetArea();
void Draw(const TileInfo *ti);
void DrawSelectionOverlay(DrawPixelInfo *dpi);

View File

@@ -690,7 +690,7 @@ static void UpdateStationAction(std::optional<TileArea> area, up<Command> cmdptr
static HighlightMap PrepareHighilightMap(Station *st_join, std::optional<ObjectHighlight> 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<TileIndex> coverage_area;
@@ -702,8 +702,7 @@ static HighlightMap PrepareHighilightMap(Station *st_join, std::optional<ObjectH
if (show_coverage && st_join != nullptr) {
// Add joining station coverage
for (auto t : st_join->catchment_tiles) {
auto pal = join_area.Contains(t) ? MixTints(CM_PALETTE_TINT_CYAN, CM_PALETTE_TINT_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);
}
}
@@ -720,8 +719,8 @@ static HighlightMap PrepareHighilightMap(Station *st_join, std::optional<ObjectH
if (show_coverage && add_current && rad_area.has_value()) {
// Add current station coverage
for (auto t : *rad_area) {
auto pal = join_area.Contains(t) ? MixTints(CM_PALETTE_TINT_CYAN, CM_PALETTE_TINT_WHITE) : CM_PALETTE_TINT_WHITE;
hlmap.Add(t, ObjectTileHighlight::make_tint(pal));
if (coverage_area.find(t) != coverage_area.end()) continue;
hlmap.Add(t, ObjectTileHighlight::make_tint(CM_PALETTE_TINT_WHITE));
coverage_area.insert(t);
}
}
@@ -739,6 +738,8 @@ static HighlightMap PrepareHighilightMap(Station *st_join, std::optional<ObjectH
}
}
if (ohl.has_value()) ohl->AddToHighlightMap(hlmap, pal);
return hlmap;
}

View File

@@ -366,11 +366,10 @@ static const SpriteID CM_PALETTE_TINT_MIXES_WHITE = CM_PALETTE_TINT_BASE +
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_HIDE_SPRITE = CM_PALETTE_TINT_BASE + CM_PALETTE_TINT_COUNT;
static const SpriteID CM_PALETTE_TINT_END = CM_PALETTE_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

View File

@@ -622,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_LVL_BASE, pt.y + extra_offs_y * ZOOM_LVL_BASE);
@@ -731,14 +731,14 @@ void AddSortableSpriteToDraw(SpriteID image, PaletteID pal, int x, int y, int w,
assert((image & SPRITE_MASK) < MAX_SPRITES);
if (!ignore_highlight_pal) {
if (_vd.cm_highlight.structure_pal) pal = _vd.cm_highlight.structure_pal;
}
/* make the sprites transparent with the right palette */
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) {
@@ -747,7 +747,6 @@ void AddSortableSpriteToDraw(SpriteID image, PaletteID pal, int x, int y, int w,
}
_vd.last_child = nullptr;
if (!ignore_highlight_pal && pal == CM_PALETTE_HIDE_SPRITE) return;
Point pt = RemapCoords(x, y, z);
int tmp_left, tmp_top, tmp_x = pt.x, tmp_y = pt.y;
@@ -1350,8 +1349,9 @@ static void ViewportAddLandscape()
_tile_type_procs[tile_type]->draw_tile_proc(&_cur_ti);
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;
_vd.cm_highlight.structure_pal_prio = PAL_NONE;
_vd.cm_highlight.structure_hidden = false;
citymania::DrawTileZoning(&_cur_ti); // old zoning patch
citymania::DrawTileZoning(&_cur_ti, _vd.cm_highlight, tile_type);
DrawTileSelection(&_cur_ti);