From f8531bc64b8c149f4e5272c1b588f8805d52c323 Mon Sep 17 00:00:00 2001 From: Peter Nelson Date: Fri, 25 Jul 2025 08:56:51 +0100 Subject: [PATCH] Feature: Custom company colours, picked from HSV colour picker. --- src/company_cmd.cpp | 59 +++- src/company_func.h | 5 + src/company_gui.cpp | 492 +++++++++++++++++++++++++--- src/company_manager_face.h | 2 +- src/console_cmds.cpp | 2 +- src/error_gui.cpp | 3 +- src/group.h | 1 + src/group_cmd.cpp | 19 +- src/lang/english.txt | 6 + src/livery.h | 4 + src/network/network_admin.cpp | 4 +- src/newgrf.cpp | 3 + src/newgrf_airporttiles.cpp | 9 +- src/news_gui.cpp | 2 +- src/palette.cpp | 42 +++ src/palette_func.h | 3 + src/saveload/afterload.cpp | 2 + src/settings_type.h | 1 + src/sprite.h | 2 +- src/table/settings/gui_settings.ini | 8 + src/vehicle.cpp | 38 ++- src/widget.cpp | 2 - src/widgets/company_widget.h | 19 ++ src/window_type.h | 6 + 24 files changed, 661 insertions(+), 73 deletions(-) diff --git a/src/company_cmd.cpp b/src/company_cmd.cpp index e1c38ee285..21bba24a5e 100644 --- a/src/company_cmd.cpp +++ b/src/company_cmd.cpp @@ -35,6 +35,7 @@ #include "smallmap_gui.h" #include "game/game.hpp" #include "goal_base.h" +#include "spritecache.h" #include "story_base.h" #include "company_cmd.h" #include "timer/timer.h" @@ -55,6 +56,7 @@ void UpdateObjectColours(const Company *c); CompanyID _local_company; ///< Company controlled by the human player at this client. Can also be #COMPANY_SPECTATOR. CompanyID _current_company; ///< Company currently doing an action. TypedIndexContainer, CompanyID> _company_colours; ///< NOSAVE: can be determined from company structs. +TypedIndexContainer, CompanyID> _company_palettes; ///< NOSAVE: can be determined from company structs. std::string _company_manager_face; ///< for company manager face storage in openttd.cfg uint _cur_company_tick_index; ///< used to generate a name for one company that doesn't have a name yet per tick @@ -163,7 +165,7 @@ TextColour GetDrawStringCompanyColour(CompanyID company) */ PaletteID GetCompanyPalette(CompanyID company) { - return GetColourPalette(_company_colours[company]); + return _company_palettes[company]; } /** @@ -513,7 +515,7 @@ static Colours GenerateCompanyColour() /* Move the colours that look similar to each company's colour to the side */ for (const Company *c : Company::Iterate()) { - Colours pcolour = c->colour; + Colours pcolour = Colours(c->colour & 0xF); for (uint i = 0; i < COLOUR_END; i++) { if (colours[i] == pcolour) { @@ -566,6 +568,40 @@ restart:; } } +void ClearLivery(Livery &livery) +{ + DeallocateDynamicSprite(livery.cached_pal_1cc); + DeallocateDynamicSprite(livery.cached_pal_2cc); + DeallocateDynamicSprite(livery.cached_pal_2cr); + + livery.cached_pal_1cc = PALETTE_RECOLOUR_START + GB(livery.colour1, 0, 4); + livery.cached_pal_2cc = SPR_2CCMAP_BASE + GB(livery.colour1, 0, 4) + GB(livery.colour2, 0, 4) * 16; + livery.cached_pal_2cr = SPR_2CCMAP_BASE + GB(livery.colour2, 0, 4) + GB(livery.colour1, 0, 4) * 16; +} + +/** + * Update cached palettes for a livery. + * @param livery Livery to update. + * @param always_update Always update instead of clearing livery (for LS_DEFAULT which is always needed). + */ +void UpdateLivery(Livery &livery, bool always_update) +{ + if ((always_update || livery.in_use != 0) && (ColoursPacker(livery.colour1).IsCustom() || ColoursPacker(livery.colour2).IsCustom())) { + PaletteID pal_1cc = PALETTE_RECOLOUR_START + GB(livery.colour1, 0, 4); + livery.cached_pal_1cc = CreateCompanyColourRemap(livery.colour1, livery.colour1, false, pal_1cc, livery.cached_pal_1cc); + + if (_loaded_newgrf_features.has_2CC) { + PaletteID pal_2cc = SPR_2CCMAP_BASE + GB(livery.colour1, 0, 4) + GB(livery.colour2, 0, 4) * 16; + livery.cached_pal_2cc = CreateCompanyColourRemap(livery.colour1, livery.colour2, true, pal_2cc, livery.cached_pal_2cc); + + PaletteID pal_2cr = SPR_2CCMAP_BASE + GB(livery.colour2, 0, 4) + GB(livery.colour1, 0, 4) * 16; + livery.cached_pal_2cr = CreateCompanyColourRemap(livery.colour2, livery.colour1, true, pal_2cr, livery.cached_pal_2cr); + } + } else { + ClearLivery(livery); + } +} + /** * Reset the livery schemes to the company's primary colour. * This is used on loading games without livery information and on new company start up. @@ -577,6 +613,7 @@ void ResetCompanyLivery(Company *c) c->livery[scheme].in_use = 0; c->livery[scheme].colour1 = c->colour; c->livery[scheme].colour2 = c->colour; + UpdateLivery(c->livery[scheme], scheme == LS_DEFAULT); } for (Group *g : Group::Iterate()) { @@ -584,8 +621,12 @@ void ResetCompanyLivery(Company *c) g->livery.in_use = 0; g->livery.colour1 = c->colour; g->livery.colour2 = c->colour; + UpdateLivery(g->livery, false); } } + + _company_colours[c->index] = c->livery[LS_DEFAULT].colour1; + _company_palettes[c->index] = c->livery[LS_DEFAULT].cached_pal_1cc; } /** @@ -613,7 +654,6 @@ Company *DoStartupNewCompany(bool is_ai, CompanyID company = CompanyID::Invalid( c->colour = colour; ResetCompanyLivery(c); - _company_colours[c->index] = c->colour; /* Scale the initial loan based on the inflation rounded down to the loan interval. The maximum loan has already been inflation adjusted. */ c->money = c->current_loan = std::min((INITIAL_LOAN * _economy.inflation_prices >> 16) / LOAN_INTERVAL * LOAN_INTERVAL, _economy.max_loan); @@ -1076,11 +1116,16 @@ CommandCost CmdSetCompanyManagerFace(DoCommandFlags flags, uint32_t bits, uint s */ void UpdateCompanyLiveries(Company *c) { + UpdateLivery(c->livery[LS_DEFAULT], true); for (int i = 1; i < LS_END; i++) { if (!HasBit(c->livery[i].in_use, 0)) c->livery[i].colour1 = c->livery[LS_DEFAULT].colour1; if (!HasBit(c->livery[i].in_use, 1)) c->livery[i].colour2 = c->livery[LS_DEFAULT].colour2; + UpdateLivery(c->livery[i], false); } UpdateCompanyGroupLiveries(c); + + _company_colours[c->index] = c->livery[LS_DEFAULT].colour1; + _company_palettes[c->index] = c->livery[LS_DEFAULT].cached_pal_1cc; } /** @@ -1093,7 +1138,7 @@ void UpdateCompanyLiveries(Company *c) */ CommandCost CmdSetCompanyColour(DoCommandFlags flags, LiveryScheme scheme, bool primary, Colours colour) { - if (scheme >= LS_END || (colour >= COLOUR_END && colour != INVALID_COLOUR)) return CMD_ERROR; + if (scheme >= LS_END) return CMD_ERROR; /* Default scheme can't be reset to invalid. */ if (scheme == LS_DEFAULT && colour == INVALID_COLOUR) return CMD_ERROR; @@ -1117,9 +1162,11 @@ CommandCost CmdSetCompanyColour(DoCommandFlags flags, LiveryScheme scheme, bool * original and cached company colours too. */ if (scheme == LS_DEFAULT) { UpdateCompanyLiveries(c); - _company_colours[_current_company] = colour; + /* Update cached colour/palette for company */ c->colour = colour; CompanyAdminUpdate(c); + } else { + UpdateLivery(c->livery[scheme], false); } } else { if (scheme != LS_DEFAULT) AssignBit(c->livery[scheme].in_use, 1, colour != INVALID_COLOUR); @@ -1128,6 +1175,8 @@ CommandCost CmdSetCompanyColour(DoCommandFlags flags, LiveryScheme scheme, bool if (scheme == LS_DEFAULT) { UpdateCompanyLiveries(c); + } else { + UpdateLivery(c->livery[scheme], false); } } diff --git a/src/company_func.h b/src/company_func.h index 3a68556f37..8fe31f24db 100644 --- a/src/company_func.h +++ b/src/company_func.h @@ -14,6 +14,7 @@ #include "company_type.h" #include "gfx_type.h" #include "vehicle_type.h" +#include "livery.h" bool CheckTakeoverVehicleLimit(CompanyID cbig, CompanyID small); void ChangeOwnershipOfCompanyItems(Owner old_owner, Owner new_owner); @@ -24,6 +25,8 @@ void CompanyAdminUpdate(const Company *company); void CompanyAdminBankrupt(CompanyID company_id); void UpdateLandscapingLimits(); void UpdateCompanyLiveries(Company *c); +void ClearLivery(Livery &livery); +void UpdateLivery(Livery &livery, bool always_update); Money GetAvailableMoney(CompanyID company); Money GetAvailableMoneyForCommand(); @@ -37,7 +40,9 @@ extern CompanyID _local_company; extern CompanyID _current_company; extern TypedIndexContainer, CompanyID> _company_colours; +extern TypedIndexContainer, CompanyID> _company_palettes; extern std::string _company_manager_face; + PaletteID GetCompanyPalette(CompanyID company); /** diff --git a/src/company_gui.cpp b/src/company_gui.cpp index abdf287d8e..b38a4bc9c9 100644 --- a/src/company_gui.cpp +++ b/src/company_gui.cpp @@ -7,6 +7,7 @@ /** @file company_gui.cpp %Company related GUIs. */ +#include "palette_func.h" #include "stdafx.h" #include "currency.h" #include "error.h" @@ -47,6 +48,7 @@ #include "timer/timer.h" #include "timer/timer_window.h" #include "core/string_consumer.hpp" +#include "blitter/factory.hpp" #include "widgets/company_widget.h" @@ -584,6 +586,13 @@ static const LiveryClass _livery_class[LS_END] = { LC_ROAD, LC_ROAD, }; +StringID GetColourString(Colours colour) +{ + if (ColoursPacker(colour).IsCustom()) return STR_COLOUR_CUSTOM; + if (colour == INVALID_COLOUR) return STR_COLOUR_DEFAULT; + return STR_COLOUR_DARK_BLUE + colour; +} + /** * Colour selection list item, with icon and string components. * @tparam TSprite Recolourable sprite to draw as icon. @@ -591,12 +600,372 @@ static const LiveryClass _livery_class[LS_END] = { template class DropDownListColourItem : public DropDownIcon> { public: - DropDownListColourItem(int colour, bool masked) : - DropDownIcon>(TSprite, GetColourPalette(static_cast(colour % COLOUR_END)), GetString((Colours)colour < COLOUR_END ? (STR_COLOUR_DARK_BLUE + colour) : STR_COLOUR_DEFAULT), colour, masked) + DropDownListColourItem(PaletteID pal, StringID str, int value, bool masked) : + DropDownIcon>(TSprite, pal, GetString(str), value, masked) { } }; +class SelectCustomColourWindow : public Window { + /* Preserved values from parent window when this window was opened. */ + uint32_t sel; + LiveryClass lc; + bool ctrl_pressed; + bool primary; + bool group; + + Scrollbar *hue, *sat, *val, *con; + Colours current; + + HsvColour current_hsv; + uint8_t contrast; + + static inline const int BUTTON_SIZE = 10; + static inline const int CON_MAX = UINT8_MAX; + + void SetPresetColours() + { + for (uint i = 0; i < std::size(_settings_client.gui.preset_colours); ++i) { + this->GetWidget(WID_SCC_PRESETS + i)->colour = _settings_client.gui.preset_colours[i]; + } + } + + Colour AdjustBrightness(const Colour &rgb, ColourShade shade, int contrast) const + { + int level = (shade - SHADE_NORMAL) * contrast / (SHADE_END / 2); + return { + ClampTo(std::max(1, rgb.r + level)), + ClampTo(std::max(1, rgb.g + level)), + ClampTo(std::max(1, rgb.b + level)) + }; + } + + bool SetScrollbarPositions() + { + bool changed = false; + changed |= this->hue->SetPosition(HsvColour::HUE_MAX - this->current_hsv.h); + changed |= this->sat->SetPosition(this->current_hsv.s); + changed |= this->val->SetPosition(HsvColour::VAL_MAX - this->current_hsv.v); + changed |= this->con->SetPosition(CON_MAX - this->contrast); + return changed; + } + +public: + SelectCustomColourWindow(WindowDesc &desc, int window_number, CompanyID company, Colours colour, uint32_t sel, LiveryClass lc, bool ctrl, bool primary, bool group) : + Window(desc), sel(sel), lc(lc), ctrl_pressed(ctrl), primary(primary), group(group) + { + this->InitNested(window_number); + this->owner = (Owner)company; + + this->current = colour; + + ColoursPacker colourp(colour); + if ((colourp.GetHue() | colourp.GetSaturation() | colourp.GetValue()) == 0) { + auto [rgba, contrast] = GetCompanyColourRGB(colour); + this->current_hsv = ConvertRgbToHsv(rgba); + this->contrast = contrast; + } else { + this->current_hsv = colourp.Hsv(); + this->contrast = colourp.GetContrast(); + } + + this->hue = this->GetScrollbar(WID_SCC_SCROLLBAR_HUE); + this->hue->SetCapacity(HsvColour::HUE_MAX / BUTTON_SIZE); + this->hue->SetCount(HsvColour::HUE_MAX + HsvColour::HUE_MAX / BUTTON_SIZE); + this->sat = this->GetScrollbar(WID_SCC_SCROLLBAR_SAT); + this->sat->SetCapacity(HsvColour::SAT_MAX / BUTTON_SIZE); + this->sat->SetCount(HsvColour::SAT_MAX + HsvColour::SAT_MAX / BUTTON_SIZE); + this->val = this->GetScrollbar(WID_SCC_SCROLLBAR_VAL); + this->val->SetCapacity(HsvColour::VAL_MAX / BUTTON_SIZE); + this->val->SetCount(HsvColour::VAL_MAX + HsvColour::VAL_MAX / BUTTON_SIZE); + this->con = this->GetScrollbar(WID_SCC_SCROLLBAR_CON); + this->con->SetCapacity(CON_MAX / BUTTON_SIZE); + this->con->SetCount(CON_MAX + CON_MAX / BUTTON_SIZE); + + this->SetScrollbarPositions(); + this->SetPresetColours(); + } + + std::string GetWidgetString(WidgetID widget, StringID stringid) const override + { + switch (widget) { + case WID_SCC_CAPTION: + return GetString(stringid, this->owner); + + default: + return this->Window::GetWidgetString(widget, stringid); + } + } + + void DrawWidget(const Rect &r, WidgetID widget) const override + { + HsvColour hsv = this->current_hsv; + switch (widget) { + case WID_SCC_HUE: { + int range = r.Height() - 1; + int mid = CentreBounds(r.left, r.right, 0); + hsv.s = HsvColour::SAT_MAX; + for (int i = 0; i <= range; i++) { + hsv.h = HsvColour::HUE_MAX * i / range; + hsv.v = HsvColour::VAL_MAX; + GfxFillRect(r.left, r.bottom - i, mid - 1, r.bottom - i, ConvertHsvToRgb(hsv), FILLRECT_OPAQUE); + hsv.v = HsvColour::VAL_MAX * 3 / 4; + GfxFillRect(mid, r.bottom - i, r.right, r.bottom - i, ConvertHsvToRgb(hsv), FILLRECT_OPAQUE); + } + + /* Mark current value. */ + GfxDrawLine(r.left, r.bottom - this->current_hsv.h * range / HsvColour::HUE_MAX, r.right, r.bottom - this->current_hsv.h * range / HsvColour::HUE_MAX, PC_WHITE, 1, 1); + break; + } + + case WID_SCC_SAT: { + int width = r.Width() - 1; + int height = r.Height() - 1; + + for (int x = 0; x <= width; x++) { + hsv.s = HsvColour::SAT_MAX * x / width; + for (int y = 0; y <= height; y++) { + hsv.v = HsvColour::VAL_MAX * y / height; + GfxFillRect(r.left + x, r.bottom - y, r.left + x, r.bottom - y, ConvertHsvToRgb(hsv), FILLRECT_OPAQUE); + } + } + + /* Mark current value. */ + GfxDrawLine(r.left + this->current_hsv.s * width / HsvColour::SAT_MAX, r.top, r.left + this->current_hsv.s * width / HsvColour::SAT_MAX, r.bottom, PC_WHITE, 1, 1); + GfxDrawLine(r.left, r.bottom - this->current_hsv.v * height / HsvColour::VAL_MAX, r.right, r.bottom - this->current_hsv.v * height / HsvColour::VAL_MAX, PC_WHITE, 1, 1); + break; + } + + case WID_SCC_CON: { + int range = r.Height() - 1; + int mid = CentreBounds(r.left, r.right, 0); + Colour rgb = ConvertHsvToRgb(HsvColour{0, 0, HsvColour::VAL_MAX / 2}); + for (int i = 0; i <= range; i++) { + int contrast = i * CON_MAX / width; + GfxFillRect(r.left, r.bottom - i, mid - 1, r.bottom - i, AdjustBrightness(rgb, SHADE_LIGHTEST, contrast), FILLRECT_OPAQUE); + GfxFillRect(mid, r.bottom - i, r.right, r.bottom - i, AdjustBrightness(rgb, SHADE_DARKEST, contrast), FILLRECT_OPAQUE); + } + + /* Mark current value. */ + GfxDrawLine(r.left, r.bottom - this->contrast * range / CON_MAX, r.right, r.bottom - this->contrast * range / CON_MAX, PC_WHITE, 1, 1); + break; + } + + case WID_SCC_OUTPUT: { + int range = r.Height(); + /* Pack and unpack to colours, to produce the result. */ + Colours colour = this->PackColour(); + ColoursPacker colourp(colour); + HsvColour hsv = colourp.Hsv(); + uint8_t con = colourp.GetContrast(); + for (ColourShade shade = SHADE_BEGIN; shade != SHADE_END; ++shade) { + Colour c = ConvertHsvToRgb(AdjustHsvColourBrightness(hsv, shade, con)); + GfxFillRect(r.left, r.bottom - (shade + 1) * range / SHADE_END + 1, r.right, r.bottom - shade * range / SHADE_END, c, FILLRECT_OPAQUE); + } + break; + } + } + } + + void OnClick(Point pt, int widget, int click_count) override + { + bool changed = false; + + if (widget >= WID_SCC_DEFAULT && widget <= WID_SCC_DEFAULT_LAST && !_ctrl_pressed) { + /* Select colour from default preset. */ + this->current = this->GetWidget(widget)->colour; + + auto [rgba, contrast] = GetCompanyColourRGB(this->current); + this->current_hsv = ConvertRgbToHsv(rgba); + this->contrast = contrast; + + changed = this->SetScrollbarPositions(); + } + + if (widget >= WID_SCC_PRESETS && widget <= WID_SCC_PRESETS_LAST) { + if (_ctrl_pressed) { + /* Save colour to preset. */ + _settings_client.gui.preset_colours[widget - WID_SCC_PRESETS] = this->PackColour(); + this->SetPresetColours(); + } else { + /* Select colour from preset. */ + ColoursPacker cp(_settings_client.gui.preset_colours[widget - WID_SCC_PRESETS]); + this->current_hsv = cp.Hsv(); + this->contrast = cp.GetContrast(); + + changed = this->SetScrollbarPositions(); + } + } + + if (widget == WID_SCC_HUE) { + /* Click and drag on hue widget */ + const NWidgetCore *wi = this->GetWidget(widget); + this->current_hsv.h = std::clamp(HsvColour::HUE_MAX - (pt.y - wi->pos_y) * HsvColour::HUE_MAX / (int)wi->current_y, 0, HsvColour::HUE_MAX); + this->contrast = CON_MAX - this->con->GetPosition(); + + changed = this->SetScrollbarPositions(); + if (click_count > 0) this->mouse_capture_widget = widget; + } + + if (widget == WID_SCC_SAT) { + /* Click and drag on saturation/value widget */ + const NWidgetCore *wi = this->GetWidget(widget); + this->current_hsv.s = std::clamp((pt.x - wi->pos_x) * HsvColour::SAT_MAX / (int)wi->current_x, 0, HsvColour::SAT_MAX); + this->current_hsv.v = std::clamp(HsvColour::VAL_MAX - (pt.y - wi->pos_y) * HsvColour::VAL_MAX / (int)wi->current_y, 0, HsvColour::VAL_MAX); + this->contrast = CON_MAX - this->con->GetPosition(); + + changed = this->SetScrollbarPositions(); + if (click_count > 0) this->mouse_capture_widget = widget; + } + + if (widget == WID_SCC_CON) { + /* Click and drag on contrast widget */ + const NWidgetCore *wi = this->GetWidget(widget); + this->contrast = std::clamp(CON_MAX - (pt.y - wi->pos_y) * CON_MAX / (int)wi->current_y, 0, CON_MAX); + + changed = this->SetScrollbarPositions(); + if (click_count > 0) this->mouse_capture_widget = widget; + } + + if (!changed) return; + + this->SetDirty(); + this->SetTimeout(); + } + + void OnScrollbarScroll(WidgetID) override + { + /* Update colour from new scrollbar positions. */ + this->current_hsv.h = HsvColour::HUE_MAX - this->hue->GetPosition(); + this->current_hsv.s = this->sat->GetPosition(); + this->current_hsv.v = HsvColour::VAL_MAX - this->val->GetPosition(); + this->contrast = CON_MAX - this->con->GetPosition(); + + this->SetTimeout(); + } + + Colours PackColour() const + { + Colours colour; + ColoursPacker cp(colour); + cp.SetIndex(this->current); + cp.SetCustom(true); + cp.SetHue(this->current_hsv.h); + cp.SetSaturation(this->current_hsv.s); + cp.SetValue(this->current_hsv.v); + cp.SetContrast(this->contrast); + return colour; + } + + void OnTimeout() override + { + Colours colour = this->PackColour(); + if (colour != this->current) { + this->current = colour; + + if (this->group) { + Command::Post((GroupID)this->sel, primary, this->current); + } else { + for (LiveryScheme scheme = LS_DEFAULT; scheme < LS_END; scheme++) { + /* Changed colour for the selected scheme, or all visible schemes if CTRL is pressed. */ + if (HasBit(this->sel, scheme) || (this->ctrl_pressed && _livery_class[scheme] == this->lc && HasBit(_loaded_newgrf_features.used_liveries, scheme))) { + Command::Post(scheme, primary, this->current); + } + } + } + } + } +}; + +static constexpr NWidgetPart _nested_select_custom_colour_widgets[] = { + NWidget(NWID_HORIZONTAL), + NWidget(WWT_CLOSEBOX, COLOUR_GREY), + NWidget(WWT_CAPTION, COLOUR_GREY, WID_SCC_CAPTION), SetStringTip(STR_LIVERY_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS), + EndContainer(), + NWidget(WWT_PANEL, COLOUR_GREY), SetFill(1, 1), + NWidget(NWID_HORIZONTAL), SetPIP(0, WidgetDimensions::unscaled.hsep_wide, 0), SetPadding(WidgetDimensions::unscaled.frametext), + NWidget(NWID_HORIZONTAL, NWidContainerFlag::EqualSize), + NWidget(NWID_VERTICAL, NWidContainerFlag::EqualSize), + NWidget(WWT_PUSHTXTBTN, COLOUR_DARK_BLUE, WID_SCC_DEFAULT + 0), SetFill(1, 1), SetMinimalSize(16, 0), + NWidget(WWT_PUSHTXTBTN, COLOUR_PALE_GREEN, WID_SCC_DEFAULT + 1), SetFill(1, 1), + NWidget(WWT_PUSHTXTBTN, COLOUR_PINK, WID_SCC_DEFAULT + 2), SetFill(1, 1), + NWidget(WWT_PUSHTXTBTN, COLOUR_YELLOW, WID_SCC_DEFAULT + 3), SetFill(1, 1), + NWidget(WWT_PUSHTXTBTN, COLOUR_RED, WID_SCC_DEFAULT + 4), SetFill(1, 1), + NWidget(WWT_PUSHTXTBTN, COLOUR_LIGHT_BLUE, WID_SCC_DEFAULT + 5), SetFill(1, 1), + NWidget(WWT_PUSHTXTBTN, COLOUR_GREEN, WID_SCC_DEFAULT + 6), SetFill(1, 1), + NWidget(WWT_PUSHTXTBTN, COLOUR_DARK_GREEN, WID_SCC_DEFAULT + 7), SetFill(1, 1), + EndContainer(), + NWidget(NWID_VERTICAL, NWidContainerFlag::EqualSize), + NWidget(WWT_PUSHTXTBTN, COLOUR_BLUE, WID_SCC_DEFAULT + 8), SetFill(1, 1), SetMinimalSize(16, 0), + NWidget(WWT_PUSHTXTBTN, COLOUR_CREAM, WID_SCC_DEFAULT + 9), SetFill(1, 1), + NWidget(WWT_PUSHTXTBTN, COLOUR_MAUVE, WID_SCC_DEFAULT + 10), SetFill(1, 1), + NWidget(WWT_PUSHTXTBTN, COLOUR_PURPLE, WID_SCC_DEFAULT + 11), SetFill(1, 1), + NWidget(WWT_PUSHTXTBTN, COLOUR_ORANGE, WID_SCC_DEFAULT + 12), SetFill(1, 1), + NWidget(WWT_PUSHTXTBTN, COLOUR_BROWN, WID_SCC_DEFAULT + 13), SetFill(1, 1), + NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SCC_DEFAULT + 14), SetFill(1, 1), + NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, WID_SCC_DEFAULT + 15), SetFill(1, 1), + EndContainer(), + EndContainer(), + NWidget(NWID_HORIZONTAL, NWidContainerFlag::EqualSize), + NWidget(NWID_VERTICAL, NWidContainerFlag::EqualSize), + NWidget(WWT_PUSHTXTBTN, COLOUR_DARK_BLUE, WID_SCC_PRESETS + 0), SetFill(1, 1), SetMinimalSize(16, 0), + NWidget(WWT_PUSHTXTBTN, COLOUR_PALE_GREEN, WID_SCC_PRESETS + 1), SetFill(1, 1), + NWidget(WWT_PUSHTXTBTN, COLOUR_PINK, WID_SCC_PRESETS + 2), SetFill(1, 1), + NWidget(WWT_PUSHTXTBTN, COLOUR_YELLOW, WID_SCC_PRESETS + 3), SetFill(1, 1), + NWidget(WWT_PUSHTXTBTN, COLOUR_RED, WID_SCC_PRESETS + 4), SetFill(1, 1), + NWidget(WWT_PUSHTXTBTN, COLOUR_LIGHT_BLUE, WID_SCC_PRESETS + 5), SetFill(1, 1), + NWidget(WWT_PUSHTXTBTN, COLOUR_GREEN, WID_SCC_PRESETS + 6), SetFill(1, 1), + NWidget(WWT_PUSHTXTBTN, COLOUR_DARK_GREEN, WID_SCC_PRESETS + 7), SetFill(1, 1), + EndContainer(), + NWidget(NWID_VERTICAL, NWidContainerFlag::EqualSize), + NWidget(WWT_PUSHTXTBTN, COLOUR_BLUE, WID_SCC_PRESETS + 8), SetFill(1, 1), SetMinimalSize(16, 0), + NWidget(WWT_PUSHTXTBTN, COLOUR_CREAM, WID_SCC_PRESETS + 9), SetFill(1, 1), + NWidget(WWT_PUSHTXTBTN, COLOUR_MAUVE, WID_SCC_PRESETS + 10), SetFill(1, 1), + NWidget(WWT_PUSHTXTBTN, COLOUR_PURPLE, WID_SCC_PRESETS + 11), SetFill(1, 1), + NWidget(WWT_PUSHTXTBTN, COLOUR_ORANGE, WID_SCC_PRESETS + 12), SetFill(1, 1), + NWidget(WWT_PUSHTXTBTN, COLOUR_BROWN, WID_SCC_PRESETS + 13), SetFill(1, 1), + NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SCC_PRESETS + 14), SetFill(1, 1), + NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, WID_SCC_PRESETS + 15), SetFill(1, 1), + EndContainer(), + EndContainer(), + NWidget(NWID_HORIZONTAL), + NWidget(WWT_INSET, COLOUR_GREY), + NWidget(WWT_EMPTY, INVALID_COLOUR, WID_SCC_HUE), SetMinimalSize(16, 0), + EndContainer(), + NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_SCC_SCROLLBAR_HUE), + EndContainer(), + NWidget(NWID_VERTICAL), + NWidget(NWID_HORIZONTAL), + NWidget(WWT_INSET, COLOUR_GREY), + NWidget(WWT_EMPTY, INVALID_COLOUR, WID_SCC_SAT), SetMinimalSize(160, 160), + EndContainer(), + NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_SCC_SCROLLBAR_VAL), + EndContainer(), + NWidget(NWID_HORIZONTAL), + NWidget(NWID_HSCROLLBAR, COLOUR_GREY, WID_SCC_SCROLLBAR_SAT), + NWidget(NWID_SPACER), SetMinimalSize(11, 11), + EndContainer(), + EndContainer(), + NWidget(NWID_HORIZONTAL), + NWidget(WWT_INSET, COLOUR_GREY), + NWidget(WWT_EMPTY, INVALID_COLOUR, WID_SCC_CON), SetMinimalSize(16, 0), + EndContainer(), + NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_SCC_SCROLLBAR_CON), + EndContainer(), + NWidget(WWT_INSET, COLOUR_GREY), + NWidget(WWT_EMPTY, INVALID_COLOUR, WID_SCC_OUTPUT), SetFill(1, 1), SetMinimalSize(16, 0), + EndContainer(), + EndContainer(), + EndContainer(), +}; + +static WindowDesc _select_custom_colour_desc( + WDP_AUTO, {}, 0, 0, + WC_CUSTOM_COLOUR, WC_NONE, + {}, + _nested_select_custom_colour_widgets +); + /** Company livery colour scheme window. */ struct SelectCompanyLiveryWindow : public Window { private: @@ -608,57 +977,86 @@ private: GUIGroupList groups{}; Scrollbar *vscroll = nullptr; - void ShowColourDropDownMenu(uint32_t widget) + /** + * Get the first selected livery. + */ + const Livery *GetSelectedLivery() const { - uint32_t used_colours = 0; - const Livery *livery, *default_livery = nullptr; - bool primary = widget == WID_SCL_PRI_COL_DROPDOWN; - uint8_t default_col = 0; - - /* Disallow other company colours for the primary colour */ - if (this->livery_class < LC_GROUP_RAIL && HasBit(this->sel, LS_DEFAULT) && primary) { - for (const Company *c : Company::Iterate()) { - if (c->index != _local_company) SetBit(used_colours, c->colour); - } - } - - const Company *c = Company::Get(this->window_number); - if (this->livery_class < LC_GROUP_RAIL) { /* Get the first selected livery to use as the default dropdown item */ LiveryScheme scheme; for (scheme = LS_BEGIN; scheme < LS_END; scheme++) { if (HasBit(this->sel, scheme)) break; } + if (scheme == LS_END) scheme = LS_DEFAULT; - livery = &c->livery[scheme]; - if (scheme != LS_DEFAULT) default_livery = &c->livery[LS_DEFAULT]; + + const Company *c = Company::Get((CompanyID)this->window_number); + return &c->livery[scheme]; + } else { + const Group *g = Group::Get(this->sel); + return &g->livery; + } + } + + /** + * Get the default selected livery. + */ + const Livery *GetDefaultLivery() const + { + const Company *c = Company::Get((CompanyID)this->window_number); + + if (this->livery_class < LC_GROUP_RAIL) { + if (!HasBit(this->sel, LS_DEFAULT)) return &c->livery[LS_DEFAULT]; } else { const Group *g = Group::Get(this->sel); - livery = &g->livery; if (g->parent == GroupID::Invalid()) { - default_livery = &c->livery[LS_DEFAULT]; + return &c->livery[LS_DEFAULT]; } else { const Group *pg = Group::Get(g->parent); - default_livery = &pg->livery; + return &pg->livery; } } + return nullptr; + } + + + void ShowColourDropDownMenu(uint32_t widget) + { + uint32_t used_colours = 0; + bool primary = widget == WID_SCL_PRI_COL_DROPDOWN; + + /* Disallow other company colours for the primary colour */ + if (this->livery_class < LC_GROUP_RAIL && HasBit(this->sel, LS_DEFAULT) && primary) { + for (const Company *c : Company::Iterate()) { + if (c->index != _local_company) SetBit(used_colours, c->colour & 0xF); + } + } + + /* Get the first selected livery to use as the default dropdown item */ + const Livery *livery = GetSelectedLivery(); + const Livery *default_livery = GetDefaultLivery(); DropDownList list; if (default_livery != nullptr) { /* Add COLOUR_END to put the colour out of range, but also allow us to show what the default is */ - default_col = (primary ? default_livery->colour1 : default_livery->colour2) + COLOUR_END; - list.push_back(std::make_unique>(default_col, false)); + list.push_back(std::make_unique>(primary ? default_livery->cached_pal_1cc : default_livery->cached_pal_2cr, STR_COLOUR_DEFAULT, INVALID_COLOUR, false)); } for (Colours colour = COLOUR_BEGIN; colour != COLOUR_END; colour++) { - list.push_back(std::make_unique>(colour, HasBit(used_colours, colour))); + list.push_back(std::make_unique>(GetColourPalette(colour), GetColourString(colour), colour, HasBit(used_colours, colour))); } + list.push_back(std::make_unique>(primary ? livery->cached_pal_1cc : livery->cached_pal_2cr, STR_COLOUR_CUSTOM, 0xFF, BlitterFactory::GetCurrentBlitter()->GetScreenDepth() != 32)); - uint8_t sel; + int sel; if (default_livery == nullptr || HasBit(livery->in_use, primary ? 0 : 1)) { - sel = primary ? livery->colour1 : livery->colour2; + Colours col = primary ? livery->colour1 : livery->colour2; + if (ColoursPacker(col).IsCustom()) { + sel = 0xFF; + } else { + sel = ColoursPacker(col).GetIndex(); + } } else { - sel = default_col; + sel = INVALID_COLOUR; } ShowDropDownList(this, std::move(list), sel, widget); } @@ -820,7 +1218,7 @@ public: if (scheme == LS_END) scheme = LS_DEFAULT; const Livery *livery = &c->livery[scheme]; if (scheme == LS_DEFAULT || HasBit(livery->in_use, primary ? 0 : 1)) { - colour = STR_COLOUR_DARK_BLUE + (primary ? livery->colour1 : livery->colour2); + colour = GetColourString(primary ? livery->colour1 : livery->colour2); } } } else { @@ -828,7 +1226,7 @@ public: const Group *g = Group::Get(this->sel); const Livery *livery = &g->livery; if (HasBit(livery->in_use, primary ? 0 : 1)) { - colour = STR_COLOUR_DARK_BLUE + (primary ? livery->colour1 : livery->colour2); + colour = GetColourString(primary ? livery->colour1 : livery->colour2); } } } @@ -868,26 +1266,26 @@ public: int y = ir.top; + const Company *c = Company::Get((CompanyID)this->window_number); + /* Helper function to draw livery info. */ auto draw_livery = [&](std::string_view str, const Livery &livery, bool is_selected, bool is_default_scheme, int indent) { /* Livery Label. */ DrawString(sch.left + (rtl ? 0 : indent), sch.right - (rtl ? indent : 0), y + text_offs, str, is_selected ? TC_WHITE : TC_BLACK); /* Text below the first dropdown. */ - DrawSprite(SPR_SQUARE, GetColourPalette(livery.colour1), pri_squ.left, y + square_offs); - DrawString(pri.left, pri.right, y + text_offs, (is_default_scheme || HasBit(livery.in_use, 0)) ? STR_COLOUR_DARK_BLUE + livery.colour1 : STR_COLOUR_DEFAULT, is_selected ? TC_WHITE : TC_GOLD); + DrawSprite(SPR_SQUARE, HasBit(livery.in_use, 0) ? livery.cached_pal_1cc : c->livery[LS_DEFAULT].cached_pal_1cc, pri_squ.left, y + square_offs); + DrawString(pri.left, pri.right, y + text_offs, (is_default_scheme || HasBit(livery.in_use, 0)) ? GetColourString(livery.colour1) : STR_COLOUR_DEFAULT, is_selected ? TC_WHITE : TC_GOLD); /* Text below the second dropdown. */ if (sec.right > sec.left) { // Second dropdown has non-zero size. - DrawSprite(SPR_SQUARE, GetColourPalette(livery.colour2), sec_squ.left, y + square_offs); - DrawString(sec.left, sec.right, y + text_offs, (is_default_scheme || HasBit(livery.in_use, 1)) ? STR_COLOUR_DARK_BLUE + livery.colour2 : STR_COLOUR_DEFAULT, is_selected ? TC_WHITE : TC_GOLD); + DrawSprite(SPR_SQUARE, HasBit(livery.in_use, 1) ? livery.cached_pal_2cr : c->livery[LS_DEFAULT].cached_pal_2cr, sec_squ.left, y + square_offs); + DrawString(sec.left, sec.right, y + text_offs, (is_default_scheme || HasBit(livery.in_use, 1)) ? GetColourString(livery.colour2) : STR_COLOUR_DEFAULT, is_selected ? TC_WHITE : TC_GOLD); } y += this->line_height; }; - const Company *c = Company::Get(this->window_number); - if (livery_class < LC_GROUP_RAIL) { int pos = this->vscroll->GetPosition(); for (LiveryScheme scheme = LS_DEFAULT; scheme < LS_END; scheme++) { @@ -998,6 +1396,16 @@ public: bool local = this->window_number == _local_company; if (!local) return; + if (index == 0xFF) { + /* Special case for 'Custom...' option */ + int number = ((widget == WID_SCL_PRI_COL_DROPDOWN) ? 1 : 0) | (this->window_number << 1); + if (BringWindowToFrontById(WC_CUSTOM_COLOUR, number)) return; + + const Livery *livery = GetSelectedLivery(); + new SelectCustomColourWindow(_select_custom_colour_desc, number, (CompanyID)this->window_number, (widget == WID_SCL_PRI_COL_DROPDOWN) ? livery->colour1 : livery->colour2, this->sel, this->livery_class, _ctrl_pressed, widget == WID_SCL_PRI_COL_DROPDOWN, this->livery_class >= LC_GROUP_RAIL); + return; + } + Colours colour = static_cast(index); if (colour >= COLOUR_END) colour = INVALID_COLOUR; @@ -1114,10 +1522,10 @@ void ShowCompanyLiveryWindow(CompanyID company, GroupID group) /** * Draws the face of a company manager's face. * @param cmf the company manager's face - * @param colour the (background) colour of the gradient + * @param palette the (background) colour of the gradient * @param r position to draw the face */ -void DrawCompanyManagerFace(const CompanyManagerFace &cmf, Colours colour, const Rect &r) +void DrawCompanyManagerFace(const CompanyManagerFace &cmf, PaletteID palette, const Rect &r) { /* Determine offset from centre of drawing rect. */ Dimension d = GetSpriteSize(SPR_GRADIENT); @@ -1148,7 +1556,7 @@ void DrawCompanyManagerFace(const CompanyManagerFace &cmf, Colours colour, const } /* Draw the gradient (background) */ - DrawSprite(SPR_GRADIENT, GetColourPalette(colour), x, y); + DrawSprite(SPR_GRADIENT, palette, x, y); /* Thirdly, draw sprites. */ for (auto var : SetBitIterator(active_vars)) { @@ -2105,7 +2513,7 @@ struct CompanyWindow : Window const Company *c = Company::Get(this->window_number); switch (widget) { case WID_C_FACE: - DrawCompanyManagerFace(c->face, c->colour, r); + DrawCompanyManagerFace(c->face, GetCompanyPalette(c->index), r); break; case WID_C_FACE_TITLE: @@ -2359,8 +2767,8 @@ struct BuyCompanyWindow : Window { { switch (widget) { case WID_BC_FACE: { - const Company *c = Company::Get(this->window_number); - DrawCompanyManagerFace(c->face, c->colour, r); + const Company *c = Company::Get((CompanyID)this->window_number); + DrawCompanyManagerFace(c->face, GetCompanyPalette(c->index), r); break; } diff --git a/src/company_manager_face.h b/src/company_manager_face.h index 663da09a11..21d2848e70 100644 --- a/src/company_manager_face.h +++ b/src/company_manager_face.h @@ -180,6 +180,6 @@ uint32_t MaskCompanyManagerFaceBits(const CompanyManagerFace &cmf, FaceVars vars std::string FormatCompanyManagerFaceCode(const CompanyManagerFace &cmf); std::optional ParseCompanyManagerFaceCode(std::string_view str); -void DrawCompanyManagerFace(const CompanyManagerFace &cmf, Colours colour, const Rect &r); +void DrawCompanyManagerFace(const CompanyManagerFace &cmf, PaletteID palette, const Rect &r); #endif /* COMPANY_MANAGER_FACE_H */ diff --git a/src/console_cmds.cpp b/src/console_cmds.cpp index 52dfc9cffa..f17cf86116 100644 --- a/src/console_cmds.cpp +++ b/src/console_cmds.cpp @@ -1949,7 +1949,7 @@ static bool ConCompanies(std::span argv) /* Grab the company name */ std::string company_name = GetString(STR_COMPANY_NAME, c->index); - std::string colour = GetString(STR_COLOUR_DARK_BLUE + _company_colours[c->index]); + std::string colour = GetString(STR_COLOUR_DARK_BLUE + _company_colours[c->index] & 0xF); IConsolePrint(CC_INFO, "#:{}({}) Company Name: '{}' Year Founded: {} Money: {} Loan: {} Value: {} (T:{}, R:{}, P:{}, S:{}) {}", c->index + 1, colour, company_name, c->inaugurated_year, (int64_t)c->money, (int64_t)c->current_loan, (int64_t)CalculateCompanyValue(c), diff --git a/src/error_gui.cpp b/src/error_gui.cpp index 182ac7229f..a6204579cc 100644 --- a/src/error_gui.cpp +++ b/src/error_gui.cpp @@ -18,6 +18,7 @@ #include "company_base.h" #include "company_func.h" #include "company_manager_face.h" +#include "sprite.h" #include "strings_func.h" #include "zoom_func.h" #include "window_func.h" @@ -188,7 +189,7 @@ public: switch (widget) { case WID_EM_FACE: { const Company *c = Company::Get(this->company); - DrawCompanyManagerFace(c->face, c->colour, r); + DrawCompanyManagerFace(c->face, GetCompanyPalette(c->index), r); break; } diff --git a/src/group.h b/src/group.h index 28de518012..c9f650bca8 100644 --- a/src/group.h +++ b/src/group.h @@ -87,6 +87,7 @@ struct Group : GroupPool::PoolItem<&_group_pool> { Group() {} Group(CompanyID owner, VehicleType vehicle_type) : owner(owner), vehicle_type(vehicle_type) {} + ~Group(); }; diff --git a/src/group_cmd.cpp b/src/group_cmd.cpp index 84527d9b9f..bc15b66924 100644 --- a/src/group_cmd.cpp +++ b/src/group_cmd.cpp @@ -19,6 +19,7 @@ #include "core/pool_func.hpp" #include "order_backup.h" #include "group_cmd.h" +#include "spritecache.h" #include "table/strings.h" @@ -308,6 +309,7 @@ static void PropagateChildLivery(const Group *g, bool reset_cache) Group *cg = Group::Get(childgroup); if (!HasBit(cg->livery.in_use, 0)) cg->livery.colour1 = g->livery.colour1; if (!HasBit(cg->livery.in_use, 1)) cg->livery.colour2 = g->livery.colour2; + UpdateLivery(cg->livery, false); PropagateChildLivery(cg, reset_cache); } } @@ -323,11 +325,20 @@ void UpdateCompanyGroupLiveries(const Company *c) if (g->owner == c->index && g->parent == GroupID::Invalid()) { if (!HasBit(g->livery.in_use, 0)) g->livery.colour1 = c->livery[LS_DEFAULT].colour1; if (!HasBit(g->livery.in_use, 1)) g->livery.colour2 = c->livery[LS_DEFAULT].colour2; + UpdateLivery(g->livery, false); PropagateChildLivery(g, false); } } } +Group::~Group() +{ + if (CleaningPool()) return; + + DeallocateDynamicSprite(this->livery.cached_pal_1cc); + DeallocateDynamicSprite(this->livery.cached_pal_2cc); + DeallocateDynamicSprite(this->livery.cached_pal_2cr); +} /** * Create a new vehicle group. @@ -483,8 +494,9 @@ CommandCost CmdAlterGroup(DoCommandFlags flags, AlterGroupMode mode, GroupID gro if (!HasBit(g->livery.in_use, 0) || !HasBit(g->livery.in_use, 1)) { /* Update livery with new parent's colours if either colour is default. */ const Livery *livery = GetParentLivery(g); - if (!HasBit(g->livery.in_use, 0)) g->livery.colour1 = livery->colour1; - if (!HasBit(g->livery.in_use, 1)) g->livery.colour2 = livery->colour2; + if (!HasBit(g->livery.in_use, 0)) if (!HasBit(g->livery.in_use, 0)) g->livery.colour1 = livery->colour1; + if (!HasBit(g->livery.in_use, 1)) if (!HasBit(g->livery.in_use, 1)) g->livery.colour2 = livery->colour2; + UpdateLivery(g->livery, false); PropagateChildLivery(g, true); MarkWholeScreenDirty(); @@ -680,8 +692,6 @@ CommandCost CmdSetGroupLivery(DoCommandFlags flags, GroupID group_id, bool prima if (g == nullptr || g->owner != _current_company) return CMD_ERROR; - if (colour >= COLOUR_END && colour != INVALID_COLOUR) return CMD_ERROR; - if (flags.Test(DoCommandFlag::Execute)) { if (primary) { AssignBit(g->livery.in_use, 0, colour != INVALID_COLOUR); @@ -692,6 +702,7 @@ CommandCost CmdSetGroupLivery(DoCommandFlags flags, GroupID group_id, bool prima if (colour == INVALID_COLOUR) colour = GetParentLivery(g)->colour2; g->livery.colour2 = colour; } + UpdateLivery(g->livery, false); PropagateChildLivery(g, true); MarkWholeScreenDirty(); diff --git a/src/lang/english.txt b/src/lang/english.txt index 41531939f3..bc15217e7c 100644 --- a/src/lang/english.txt +++ b/src/lang/english.txt @@ -170,6 +170,7 @@ STR_ITEMS :{COMMA}{NBSP}it STR_CRATES :{COMMA}{NBSP}crate{P "" s} STR_COLOUR_DEFAULT :Default +STR_COLOUR_CUSTOM :Custom... ###length 17 STR_COLOUR_DARK_BLUE :Dark Blue STR_COLOUR_PALE_GREEN :Pale Green @@ -2325,6 +2326,11 @@ STR_LIVERY_LARGE_PLANE :Large Aeroplane STR_LIVERY_PASSENGER_TRAM :Passenger Tram STR_LIVERY_FREIGHT_TRAM :Freight Tram +STR_LIVERY_HUE :{BLACK}Hue +STR_LIVERY_SATURATION :{BLACK}Saturation +STR_LIVERY_LIGHTNESS :{BLACK}Lightness +STR_LIVERY_CONTRAST :{BLACK}Contrast + # Face selection window STR_FACE_CAPTION :{WHITE}Face Selection STR_FACE_CANCEL_TOOLTIP :{BLACK}Cancel new face selection diff --git a/src/livery.h b/src/livery.h index 2de27555d9..501e4a540d 100644 --- a/src/livery.h +++ b/src/livery.h @@ -79,6 +79,10 @@ struct Livery { uint8_t in_use = 0; ///< Bit 0 set if this livery should override the default livery first colour, Bit 1 for the second colour. Colours colour1 = COLOUR_BEGIN; ///< First colour, for all vehicles. Colours colour2 = COLOUR_BEGIN; ///< Second colour, for vehicles with 2CC support. + + PaletteID cached_pal_1cc = 0; ///< NOSAVE: cached 1CC palette. + PaletteID cached_pal_2cc = 0; ///< NOSAVE: cached 2CC palette. + PaletteID cached_pal_2cr = 0; ///< NOSAVE: cached reversed 2CC palette. }; void ResetCompanyLivery(Company *c); diff --git a/src/network/network_admin.cpp b/src/network/network_admin.cpp index 82b7bd5383..c6508d1716 100644 --- a/src/network/network_admin.cpp +++ b/src/network/network_admin.cpp @@ -325,7 +325,7 @@ NetworkRecvStatus ServerNetworkAdminSocketHandler::SendCompanyInfo(const Company p->Send_uint8 (c->index); p->Send_string(GetString(STR_COMPANY_NAME, c->index)); p->Send_string(GetString(STR_PRESIDENT_NAME, c->index)); - p->Send_uint8 (c->colour); + p->Send_uint8 (c->colour & 0xF); p->Send_bool (true); p->Send_uint32(c->inaugurated_year.base()); p->Send_bool (c->is_ai); @@ -348,7 +348,7 @@ NetworkRecvStatus ServerNetworkAdminSocketHandler::SendCompanyUpdate(const Compa p->Send_uint8 (c->index); p->Send_string(GetString(STR_COMPANY_NAME, c->index)); p->Send_string(GetString(STR_PRESIDENT_NAME, c->index)); - p->Send_uint8 (c->colour); + p->Send_uint8 (c->colour & 0xF); p->Send_bool (true); p->Send_uint8 (CeilDiv(c->months_of_bankruptcy, 3)); // send as quarters_of_bankruptcy diff --git a/src/newgrf.cpp b/src/newgrf.cpp index e9a82e3c9e..e3538e927b 100644 --- a/src/newgrf.cpp +++ b/src/newgrf.cpp @@ -1678,6 +1678,9 @@ static void AfterLoadGRFs() InitRailTypes(); InitRoadTypes(); + /* Force cached palettes to be refreshed */ + ResetVehicleColourMap(); + for (Engine *e : Engine::IterateType(VEH_ROAD)) { if (_gted[e->index].rv_max_speed != 0) { /* Set RV maximum speed from the mph/0.8 unit value */ diff --git a/src/newgrf_airporttiles.cpp b/src/newgrf_airporttiles.cpp index bc2520718a..e5e96eeaa7 100644 --- a/src/newgrf_airporttiles.cpp +++ b/src/newgrf_airporttiles.cpp @@ -17,6 +17,7 @@ #include "water.h" #include "landscape.h" #include "company_base.h" +#include "company_func.h" #include "town.h" #include "newgrf_animation_base.h" @@ -241,7 +242,7 @@ static uint16_t GetAirportTileCallback(CallbackID callback, uint32_t param1, uin return object.ResolveCallback(regs100); } -static void AirportDrawTileLayout(const TileInfo *ti, const DrawTileSpriteSpan &dts, Colours colour) +static void AirportDrawTileLayout(const TileInfo *ti, const DrawTileSpriteSpan &dts, PaletteID cc_pal) { SpriteID image = dts.ground.sprite; SpriteID pal = dts.ground.pal; @@ -250,11 +251,11 @@ static void AirportDrawTileLayout(const TileInfo *ti, const DrawTileSpriteSpan & if (image == SPR_FLAT_WATER_TILE && IsTileOnWater(ti->tile)) { DrawWaterClassGround(ti); } else { - DrawGroundSprite(image, GroundSpritePaletteTransform(image, pal, GetColourPalette(colour))); + DrawGroundSprite(image, GroundSpritePaletteTransform(image, pal, cc_pal)); } } - DrawNewGRFTileSeq(ti, &dts, TO_BUILDINGS, 0, GetColourPalette(colour)); + DrawNewGRFTileSeq(ti, &dts, TO_BUILDINGS, 0, cc_pal); } bool DrawNewAirportTile(TileInfo *ti, Station *st, const AirportTileSpec *airts) @@ -278,7 +279,7 @@ bool DrawNewAirportTile(TileInfo *ti, Station *st, const AirportTileSpec *airts) auto processor = group->ProcessRegisters(object, nullptr); auto dts = processor.GetLayout(); - AirportDrawTileLayout(ti, dts, Company::Get(st->owner)->colour); + AirportDrawTileLayout(ti, dts, GetCompanyPalette(st->owner)); return true; } diff --git a/src/news_gui.cpp b/src/news_gui.cpp index 351f559561..9639848d02 100644 --- a/src/news_gui.cpp +++ b/src/news_gui.cpp @@ -528,7 +528,7 @@ struct NewsWindow : Window { case WID_N_MGR_FACE: { const CompanyNewsInformation *cni = static_cast(this->ni->data.get()); - DrawCompanyManagerFace(cni->face, cni->colour, r); + DrawCompanyManagerFace(cni->face, cni->colour, r); // XXX TODO this should be a palette GfxFillRect(r, PALETTE_NEWSPAPER, FILLRECT_RECOLOUR); break; } diff --git a/src/palette.cpp b/src/palette.cpp index daa483308c..6fd482fe98 100644 --- a/src/palette.cpp +++ b/src/palette.cpp @@ -15,6 +15,7 @@ #include "landscape_type.h" #include "palette_func.h" #include "settings_type.h" +#include "sprite.h" #include "thread.h" #include "table/palettes.h" @@ -84,6 +85,7 @@ static uint CalculateColourDistance(const Colour &col1, int r2, int g2, int b2) /* Palette indexes for conversion. See docs/palettes/palette_key.png */ const uint8_t PALETTE_INDEX_CC_START = 198; ///< Palette index of start of company colour remap area. const uint8_t PALETTE_INDEX_CC_END = PALETTE_INDEX_CC_START + 8; ///< Palette index of end of company colour remap area. +const uint8_t PALETTE_INDEX_CC2_START = 80; ///< Palette index of start of second company colour remap area. const uint8_t PALETTE_INDEX_START = 1; ///< Palette index of start of defined palette. const uint8_t PALETTE_INDEX_END = 215; ///< Palette index of end of defined palette. @@ -499,3 +501,43 @@ TextColour PixelColour::ToTextColour() const } return tc; } + +std::pair GetCompanyColourRGB(Colours colour) +{ + static constexpr uint8_t CC_PALETTE_CONTRAST = 90; + + PaletteID pal = GetColourPalette(colour); + const RecolourSprite *map = GetRecolourSprite(pal); + + Colour rgb = _palette.palette[map->palette[PALETTE_INDEX_CC_START + SHADE_NORMAL]]; + return {rgb, CC_PALETTE_CONTRAST}; +} + +PaletteID CreateCompanyColourRemap(Colours colour1, Colours colour2, bool twocc, PaletteID basemap, PaletteID hint) +{ + DeallocateDynamicSprite(hint); + + PaletteID pal = AllocateDynamicSprite(); + const RecolourSprite *base = GetRecolourSprite(basemap); + RecolourSpriteRGBA *p = new (InjectSprite(SpriteType::Recolour, pal, sizeof(RecolourSpriteRGBA)).data()) RecolourSpriteRGBA(); + + /* Copy base remap */ + p->palette = base->palette; + std::ranges::transform(p->palette, p->rgba.begin(), [](const uint8_t &col) { return _palette.palette[col]; }); + + auto apply_colour = [](RecolourSpriteRGBA &remap, uint8_t index, Colours colour) { + ColoursPacker cp{colour}; + if (!cp.IsCustom()) return; + + HsvColour hsv = cp.Hsv(); + uint8_t con = cp.GetContrast(); + for (ColourShade shade = SHADE_BEGIN; shade != SHADE_END; ++shade) { + remap.rgba[index + shade] = ConvertHsvToRgb(AdjustHsvColourBrightness(hsv, shade, con)); + } + }; + + apply_colour(*p, PALETTE_INDEX_CC_START, colour1); + if (twocc) apply_colour(*p, PALETTE_INDEX_CC2_START, colour2); + + return pal; +} diff --git a/src/palette_func.h b/src/palette_func.h index e07ffe2cbb..6c0f325375 100644 --- a/src/palette_func.h +++ b/src/palette_func.h @@ -90,6 +90,9 @@ HsvColour AdjustHsvColourBrightness(HsvColour hsv, ColourShade shade, int contra PixelColour GetColourGradient(Colours colour, ColourShade shade); void SetColourGradient(Colours colour, ColourShade shade, PixelColour palette_colour); +std::pair GetCompanyColourRGB(Colours colour); +PaletteID CreateCompanyColourRemap(Colours rgb1, Colours rgb2, bool twocc, PaletteID basemap, PaletteID hint); + /** * Return the colour for a particular greyscale level. * @param level Intensity, 0 = black, 15 = white diff --git a/src/saveload/afterload.cpp b/src/saveload/afterload.cpp index da1386915f..2881249d76 100644 --- a/src/saveload/afterload.cpp +++ b/src/saveload/afterload.cpp @@ -3383,6 +3383,8 @@ bool AfterLoadGame() } for (Company *c : Company::Iterate()) { + UpdateLivery(c->livery[LS_DEFAULT], true); + _company_palettes[c->index] = c->livery[LS_DEFAULT].cached_pal_1cc; UpdateCompanyLiveries(c); } diff --git a/src/settings_type.h b/src/settings_type.h index 255038e537..b2da605881 100644 --- a/src/settings_type.h +++ b/src/settings_type.h @@ -210,6 +210,7 @@ struct GUISettings { uint8_t osk_activation; ///< Mouse gesture to trigger the OSK. Colours starting_colour; ///< default color scheme for the company to start a new game with Colours starting_colour_secondary; ///< default secondary color scheme for the company to start a new game with + Colours preset_colours[16]; ///< custom colour presets. bool show_newgrf_name; ///< Show the name of the NewGRF in the build vehicle window bool show_cargo_in_vehicle_lists; ///< Show the cargoes the vehicles can carry in the list windows bool auto_remove_signals; ///< automatically remove signals when in the way during rail construction diff --git a/src/sprite.h b/src/sprite.h index 504bc3a4e7..9ace06fd41 100644 --- a/src/sprite.h +++ b/src/sprite.h @@ -185,6 +185,6 @@ inline PaletteID GroundSpritePaletteTransform(SpriteID image, PaletteID pal, Pal * @param colour Colour. * @return Recolour palette. */ -static inline PaletteID GetColourPalette(Colours colour) { return PALETTE_RECOLOUR_START + colour; } +static inline PaletteID GetColourPalette(Colours colour) { return PALETTE_RECOLOUR_START + (colour % COLOUR_END); } #endif /* SPRITE_H */ diff --git a/src/table/settings/gui_settings.ini b/src/table/settings/gui_settings.ini index 8d12afa7f1..bce6a1b787 100644 --- a/src/table/settings/gui_settings.ini +++ b/src/table/settings/gui_settings.ini @@ -26,6 +26,7 @@ static const SettingVariant _gui_settings_table[] = { }; [templates] SDTC_BOOL = SDTC_BOOL( $var, SettingFlags({$flags}), $def, $str, $strhelp, $strval, $pre_cb, $post_cb, $str_cb, $help_cb, $val_cb, $def_cb, $from, $to, $cat, $extra, $startup), +SDTC_LIST = SDTC_LIST( $var, $type, SettingFlags({$flags}), $def, $from, $to, $cat, $extra, $startup), SDTC_OMANY = SDTC_OMANY( $var, $type, SettingFlags({$flags}), $def, $max, $full, $str, $strhelp, $strval, $pre_cb, $post_cb, $str_cb, $help_cb, $val_cb, $def_cb, $from, $to, $cat, $extra, $startup), SDTC_VAR = SDTC_VAR( $var, $type, SettingFlags({$flags}), $def, $min, $max, $interval, $str, $strhelp, $strval, $pre_cb, $post_cb, $str_cb, $help_cb, $val_cb, $def_cb, $range_cb, $from, $to, $cat, $extra, $startup), @@ -368,6 +369,13 @@ str = STR_CONFIG_SETTING_COMPANY_STARTING_COLOUR_SECONDARY strhelp = STR_CONFIG_SETTING_COMPANY_STARTING_COLOUR_SECONDARY_HELPTEXT strval = STR_COLOUR_SECONDARY_DARK_BLUE +[SDTC_LIST] +var = gui.preset_colours +type = SLE_UINT32 +flags = SettingFlag::NotInSave, SettingFlag::NoNetworkSync +def = """" +cat = SC_BASIC + [SDTC_BOOL] var = gui.auto_remove_signals flags = SettingFlag::NotInSave, SettingFlag::NoNetworkSync diff --git a/src/vehicle.cpp b/src/vehicle.cpp index b94670cbac..71c1285c76 100644 --- a/src/vehicle.cpp +++ b/src/vehicle.cpp @@ -2045,7 +2045,6 @@ LiveryScheme GetEngineLiveryScheme(EngineID engine_type, EngineID parent_engine_ const Livery *GetEngineLivery(EngineID engine_type, CompanyID company, EngineID parent_engine_type, const Vehicle *v, uint8_t livery_setting) { const Company *c = Company::Get(company); - LiveryScheme scheme = LS_DEFAULT; if (livery_setting == LIT_ALL || (livery_setting == LIT_COMPANY && company == _local_company)) { if (v != nullptr) { @@ -2063,11 +2062,12 @@ const Livery *GetEngineLivery(EngineID engine_type, CompanyID company, EngineID * whether any _other_ liveries are in use. */ if (c->livery[LS_DEFAULT].in_use != 0) { /* Determine the livery scheme to use */ - scheme = GetEngineLiveryScheme(engine_type, parent_engine_type, v); + LiveryScheme scheme = GetEngineLiveryScheme(engine_type, parent_engine_type, v); + if (c->livery[scheme].in_use != 0) return &c->livery[scheme]; } } - return &c->livery[scheme]; + return &c->livery[LS_DEFAULT]; } @@ -2080,6 +2080,15 @@ static PaletteID GetEngineColourMap(EngineID engine_type, CompanyID company, Eng const Engine *e = Engine::Get(engine_type); + /* Default livery for spectators */ + static const Livery default_livery = { + 0, COLOUR_GREY, COLOUR_GREY, + PALETTE_RECOLOUR_START, SPR_2CCMAP_BASE, SPR_2CCMAP_BASE, + }; + + bool twocc = e->info.misc_flags.Test(EngineMiscFlag::Uses2CC); + const Livery *livery = Company::IsValidID(company) ? GetEngineLivery(engine_type, company, parent_engine_type, v, _settings_client.gui.liveries) : &default_livery; + /* Check if we should use the colour map callback */ if (e->info.callback_mask.Test(VehicleCallbackMask::ColourRemap)) { uint16_t callback = GetVehicleCallback(CBID_VEHICLE_COLOUR_MAPPING, 0, 0, engine_type, v); @@ -2090,6 +2099,15 @@ static PaletteID GetEngineColourMap(EngineID engine_type, CompanyID company, Eng /* If bit 14 is set, then the company colours are applied to the * map else it's returned as-is. */ if (!HasBit(callback, 14)) { + /* Test if this is the standard remap/reversed remap */ + if (map == PALETTE_RECOLOUR_START + (livery->colour1 & 0xF)) { + map = livery->cached_pal_1cc; + } else if (map == SPR_2CCMAP_BASE + (livery->colour1 & 0xF) + (livery->colour2 & 0xF) * 16) { + map = livery->cached_pal_2cc; + } else if (map == SPR_2CCMAP_BASE + (livery->colour2 & 0xF) + (livery->colour1 & 0xF) * 16) { + map = livery->cached_pal_2cr; + } + /* Update cache */ if (v != nullptr) const_cast(v)->colourmap = map; return map; @@ -2097,17 +2115,19 @@ static PaletteID GetEngineColourMap(EngineID engine_type, CompanyID company, Eng } } - bool twocc = e->info.misc_flags.Test(EngineMiscFlag::Uses2CC); - if (map == PAL_NONE) map = twocc ? (PaletteID)SPR_2CCMAP_BASE : (PaletteID)PALETTE_RECOLOUR_START; /* Spectator has news shown too, but has invalid company ID - as well as dedicated server */ if (!Company::IsValidID(company)) return map; - const Livery *livery = GetEngineLivery(engine_type, company, parent_engine_type, v, _settings_client.gui.liveries); - - map += livery->colour1; - if (twocc) map += livery->colour2 * 16; + if (map == PALETTE_RECOLOUR_START || map == SPR_2CCMAP_BASE) { + /* Use cached palette when using default remaps */ + map = twocc ? livery->cached_pal_2cc : livery->cached_pal_1cc; + } else { + /* Add offsets for custom NewGRF remaps */ + map += livery->colour1; + if (twocc) map += livery->colour2 * 16; + } /* Update cache */ if (v != nullptr) const_cast(v)->colourmap = map; diff --git a/src/widget.cpp b/src/widget.cpp index 034c289ea4..545fd91153 100644 --- a/src/widget.cpp +++ b/src/widget.cpp @@ -300,8 +300,6 @@ void DrawFrameRect(int left, int top, int right, int bottom, Colours colour, Fra if (flags.Test(FrameFlag::Transparent)) { GfxFillRect(left, top, right, bottom, PALETTE_TO_TRANSPARENT, FILLRECT_RECOLOUR); } else { - assert(colour < COLOUR_END); - const PixelColour dark = GetColourGradient(colour, SHADE_DARK); const PixelColour medium_dark = GetColourGradient(colour, SHADE_LIGHT); const PixelColour medium_light = GetColourGradient(colour, SHADE_LIGHTER); diff --git a/src/widgets/company_widget.h b/src/widgets/company_widget.h index 2964b341c0..a4b2121493 100644 --- a/src/widgets/company_widget.h +++ b/src/widgets/company_widget.h @@ -75,6 +75,25 @@ enum CompanyFinancesWidgets : WidgetID { }; +/** Widgets of the #SelectCustomColourWindow class. */ +enum SelectCustomColourWidgets { + WID_SCC_CAPTION, ///< Caption of window. + WID_SCC_HUE, + WID_SCC_SCROLLBAR_HUE, ///< Hue scrollbar. + WID_SCC_SAT, + WID_SCC_SCROLLBAR_SAT, ///< Saturation scrollbar. + WID_SCC_VAL, + WID_SCC_SCROLLBAR_VAL, ///< Value scrollbar. + WID_SCC_CON, + WID_SCC_SCROLLBAR_CON, ///< Contrast scrollbar. + WID_SCC_OUTPUT, + WID_SCC_DEFAULT, ///< First default colour. + WID_SCC_DEFAULT_LAST = WID_SCC_DEFAULT + COLOUR_END - 1, + WID_SCC_PRESETS, ///< First preset colour. + WID_SCC_PRESETS_LAST = WID_SCC_PRESETS + COLOUR_END - 1, +}; + + /** Widgets of the #SelectCompanyLiveryWindow class. */ enum SelectCompanyLiveryWidgets : WidgetID { WID_SCL_CAPTION, ///< Caption of window. diff --git a/src/window_type.h b/src/window_type.h index 28f72486b7..735ac3a19b 100644 --- a/src/window_type.h +++ b/src/window_type.h @@ -231,6 +231,12 @@ enum WindowClass : uint16_t { */ WC_COMPANY_COLOUR, + /** + * Custom colour selection; %Window numbers: + * - #CompanyID = #SelectCustomColourWidgets + */ + WC_CUSTOM_COLOUR, + /** * Alter company face window; %Window numbers: * - #CompanyID = #SelectCompanyManagerFaceWidgets