From 23960d0f2c82a634e0ca1be720c229ac22f14962 Mon Sep 17 00:00:00 2001 From: PeterN Date: Thu, 31 Jan 2019 13:57:44 +0000 Subject: [PATCH] Feature: Group liveries, and livery window usability enhancements. (#7108) * Change: Replace checkbox in livery selection window with Default option in drop down selection. This reduces clutter in the UI and allows for primary/secondary colours to independently follow the default scheme if desired. * Feature: Add vehicle group liveries. --- bin/baseset/openttd.grf | Bin 501534 -> 502521 bytes media/extra_grf/openttd.nfo | 2 + media/extra_grf/openttdgui.nfo | 6 +- media/extra_grf/openttdgui_group_livery.png | Bin 0 -> 1426 bytes src/command.cpp | 2 + src/command_type.h | 1 + src/company_cmd.cpp | 100 ++--- src/company_gui.cpp | 415 +++++++++++++++----- src/company_gui.h | 2 + src/gfx_type.h | 2 +- src/group.h | 2 + src/group_cmd.cpp | 104 +++++ src/group_gui.cpp | 9 + src/lang/english.txt | 2 + src/livery.h | 6 +- src/saveload/company_sl.cpp | 11 +- src/saveload/group_sl.cpp | 10 + src/saveload/saveload.cpp | 3 +- src/script/api/script_window.hpp | 6 + src/settings.cpp | 2 +- src/table/sprites.h | 7 +- src/vehicle.cpp | 26 +- src/widgets/company_widget.h | 5 + src/widgets/group_widget.h | 1 + 24 files changed, 574 insertions(+), 150 deletions(-) create mode 100644 media/extra_grf/openttdgui_group_livery.png diff --git a/bin/baseset/openttd.grf b/bin/baseset/openttd.grf index 6c9afef2abe232b629d5d3241d3da59aa7dfb350..ea5f55ce1dcb859d86f74620b4869a07158a1f9a 100644 GIT binary patch delta 1015 zcmY+@ONbmr7zgnAs_WH{-N1IYL=tj|n2v)4K~GzZONKpoOQR^h;>_%@IY=&EJc)#4 zl-%Me<3)@UAqNk$>`t~ls3XEo%`0gLLLDpUq2}b~=qXmuBkI?uYP$LQ$N%5oJUH|B z?`JSBJ)K^|EetJ;TG(k}+{2`M8TJo*m&t1%aSCg`9CCI0$xq~Bz%e4se9-@ld;IYA zpUKl#Ztwj;qR-_?B8tggKma-<+uPgKYCmS+G61oy5*ZWl3Gq!xrW0{uy*Rs+xaJ(0 zjKzA8pT_{4WSfo>h-#DGzBy)&t3*FPEPM!)FuCs_e^9VOLTF^^<}gqM~HD%&*x;FsL<|NdUKKgN1LsvN0wShK{bBMQzS=9 z!X%FzxZX1N`O%;lIaCQQ!HqbJ&~PLp`_#_r=2Llq}}dBHV5Wfr;q?n#C0OBHMn9VJ-MywCe%mt7z?# zfU*99d4+)IG0?9*3pR9Y*7iJ)Rwjsfx@14{{-&u#P`3Z8tJyTUvsVLs&PORVl>|a?rP- ze!B?xRn~?|T&7Rhuo@{xX$<+^Of3)mlx{r1M}lmnBgAz!`o_}yE{of}_Pj>%!=nCkog03SGR Ad;kCd delta 80 zcmV-W0I&b~)gGSC9s~pc|H**_g#-bG1OkNw1BC_-1RQ_> diff --git a/media/extra_grf/openttd.nfo b/media/extra_grf/openttd.nfo index 7ece5c7149..460007d68e 100644 --- a/media/extra_grf/openttd.nfo +++ b/media/extra_grf/openttd.nfo @@ -14,6 +14,8 @@ // allowing it to be used. // +//@@WARNING DISABLE 60 + // // Number of sprites, it is wrong, but GRFcodec automagically gets it right. // diff --git a/media/extra_grf/openttdgui.nfo b/media/extra_grf/openttdgui.nfo index 1b17b8651b..d0fbba0e70 100644 --- a/media/extra_grf/openttdgui.nfo +++ b/media/extra_grf/openttdgui.nfo @@ -7,7 +7,7 @@ // See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . // -1 * 0 0C "OpenTTD GUI graphics" - -1 * 3 05 15 \b 175 // OPENTTD_SPRITE_COUNT + -1 * 3 05 15 \b 179 // OPENTTD_SPRITE_COUNT -1 sprites/openttdgui.png 8bpp 66 8 64 31 -31 7 normal -1 sprites/openttdgui.png 8bpp 146 8 64 31 -31 7 normal -1 sprites/openttdgui.png 8bpp 226 8 64 31 -31 7 normal @@ -183,3 +183,7 @@ -1 sprites/openttdgui.png 8bpp 440 440 20 20 0 0 normal -1 sprites/openttdgui.png 8bpp 466 440 20 20 0 0 normal -1 sprites/openttdgui.png 8bpp 490 440 20 20 0 0 normal + -1 sprites/openttdgui_group_livery.png 8bpp 0 0 20 20 0 0 normal + -1 sprites/openttdgui_group_livery.png 8bpp 21 0 20 20 0 0 normal + -1 sprites/openttdgui_group_livery.png 8bpp 42 0 20 20 0 0 normal + -1 sprites/openttdgui_group_livery.png 8bpp 63 0 20 20 0 0 normal diff --git a/media/extra_grf/openttdgui_group_livery.png b/media/extra_grf/openttdgui_group_livery.png new file mode 100644 index 0000000000000000000000000000000000000000..f45be670cb764012f3262295f526d2ddde0a3ae8 GIT binary patch literal 1426 zcmWkse{hR;9RAR(4T6~5EoZq2tuEFe^qsyc<3+l?)zsb*ym~oa_8zgtEhZ*M#@kAH zxr}hJm1ZQwU3Rj{9FZ@6H_x!kf?)l^Sb%jGJSiXaGzqF9zSo6RPvU@pwl^2SNx_lNzm_W=zIR#a`ob`vbvnv^Akp3JA4`(n=V;%wi#&PBsv* zMWa4aDWKI71EsVY$QqkI=(fZ{&hA8j00Au!o5XTnj=6DdNN0>1tO=VV9&z`u$C@ zSW+P|5lRP5_zl{yg-+TH$jzgW4}sJOc{ae@Kn8({Ni7N7(LwtPj>1t_3GVHU+}gi>0RXOLi4nZ~Zr zdvQyMtckP!q~3)Y1B5lAbtLu4ZWKxNolc}|p*UUR?}qLd@S+ELdZD)u`d-4ze&~M% zuMnWu@Ol6S(va3lg+`^+L1F=;?vMRjGZT>xkO7btkQ0y}P#B2Jn*mfRAPAr+NT)%r zG3AkFh1Q~^tSTKxvJTefi+3Z2^J*A-I-PD_TIK9CK-T zNzUCt!^OGpJu04(4={K_sb-E2Z$5sY?8`fZXvd`O?d6*;`X5YRPL=$0B$r#ie&4~L zv$m{R0QzysrTgEWx^TAfcHRl$(A_7!joYr&^G83v)Ad0l_gL~EH?_KRSw8G^tvmZ? zOa7+^e{d`kW*;tGIj(Zf?}9OHZQJ=-{!2n~|GfDJZfL8@E_zCeOR-aREnEHktboq5 zx_C!j#q2ppWyQ5?PZ9Cyx35}Ss(TKJRz0{%Kh5Q@lqFk7EXx0S-m?1oLv!1PVO3c9 zxWk?W-{tZou{-Nh)<6S0_Nix6;83o#a^c`5iws_R&$Ne+1`n5P3psDUs_lK4U0gZe zGpMk#qM$qMX}FspiW)W*w~sW7s>h!g%~!S;;v&i8qQ@=8Gph>Dwl{7T*6C-QcxT6} z8(*Ewk!Dxi8`$^qeseqV^i1`-R*XHPoD{-4&K0$G{=;W)PCVIjX`gMwiV=)+3v;kfziGkb-8azp;PR9o%xMB8M%x*+v@ zR@Lg8%#p=6u#GoeH4}y$|ModP>aBlA9~(dV*LPjx{^}jIrQ9(w$gVBi`G$|3ws*}g zI6r&Nt#7jHAL)PGd;ZuA+voW5M#r@^UCIYJe~+oGzc}*z-a(1Y+aLBnX&kG*J@(qj iQ*W-Ag1#8ogmUI}jk>V7W&7dGhoH1%fu?RQQ}rJlivery[scheme].in_use = false; + c->livery[scheme].in_use = 0; c->livery[scheme].colour1 = c->colour; c->livery[scheme].colour2 = c->colour; } + + Group *g; + FOR_ALL_GROUPS(g) { + if (g->owner == c->index) { + g->livery.in_use = 0; + g->livery.colour1 = c->colour; + g->livery.colour2 = c->colour; + } + } } /** @@ -946,23 +955,26 @@ CommandCost CmdSetCompanyManagerFace(TileIndex tile, DoCommandFlag flags, uint32 * @param flags operation to perform * @param p1 bitstuffed: * p1 bits 0-7 scheme to set - * p1 bits 8-9 set in use state or first/second colour + * p1 bit 8 set first/second colour * @param p2 new colour for vehicles, property, etc. * @param text unused * @return the cost of this operation or an error */ CommandCost CmdSetCompanyColour(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text) { - Colours colour = Extract(p2); + Colours colour = Extract(p2); LiveryScheme scheme = Extract(p1); - byte state = GB(p1, 8, 2); + bool second = HasBit(p1, 8); - if (scheme >= LS_END || state >= 3 || colour == INVALID_COLOUR) return CMD_ERROR; + if (scheme >= LS_END || (colour >= COLOUR_END && colour != INVALID_COLOUR)) return CMD_ERROR; + + /* Default scheme can't be reset to invalid. */ + if (scheme == LS_DEFAULT && colour == INVALID_COLOUR) return CMD_ERROR; Company *c = Company::Get(_current_company); /* Ensure no two companies have the same primary colour */ - if (scheme == LS_DEFAULT && state == 0) { + if (scheme == LS_DEFAULT && !second) { const Company *cc; FOR_ALL_COMPANIES(cc) { if (cc != c && cc->colour == colour) return CMD_ERROR; @@ -970,52 +982,48 @@ CommandCost CmdSetCompanyColour(TileIndex tile, DoCommandFlag flags, uint32 p1, } if (flags & DC_EXEC) { - switch (state) { - case 0: - c->livery[scheme].colour1 = colour; - - /* If setting the first colour of the default scheme, adjust the - * original and cached company colours too. */ - if (scheme == LS_DEFAULT) { - _company_colours[_current_company] = colour; - c->colour = colour; - CompanyAdminUpdate(c); + if (!second) { + if (scheme != LS_DEFAULT) SB(c->livery[scheme].in_use, 0, 1, colour != INVALID_COLOUR); + if (colour == INVALID_COLOUR) colour = (Colours)c->livery[LS_DEFAULT].colour1; + c->livery[scheme].colour1 = colour; + + /* If setting the first colour of the default scheme, adjust the + * original and cached company colours too. */ + if (scheme == LS_DEFAULT) { + for (int i = 1; i < LS_END; i++) { + if (!HasBit(c->livery[i].in_use, 0)) c->livery[i].colour1 = colour; } - break; - - case 1: - c->livery[scheme].colour2 = colour; - break; - - case 2: - c->livery[scheme].in_use = colour != 0; - - /* Now handle setting the default scheme's in_use flag. - * This is different to the other schemes, as it signifies if any - * scheme is active at all. If this flag is not set, then no - * processing of vehicle types occurs at all, and only the default - * colours will be used. */ + _company_colours[_current_company] = colour; + c->colour = colour; + CompanyAdminUpdate(c); + } + } else { + if (scheme != LS_DEFAULT) SB(c->livery[scheme].in_use, 1, 1, colour != INVALID_COLOUR); + if (colour == INVALID_COLOUR) colour = (Colours)c->livery[LS_DEFAULT].colour2; + c->livery[scheme].colour2 = colour; - /* If enabling a scheme, set the default scheme to be in use too */ - if (colour != 0) { - c->livery[LS_DEFAULT].in_use = true; - break; + if (scheme == LS_DEFAULT) { + for (int i = 1; i < LS_END; i++) { + if (!HasBit(c->livery[i].in_use, 1)) c->livery[i].colour2 = colour; } + } + } - /* Else loop through all schemes to see if any are left enabled. - * If not, disable the default scheme too. */ - c->livery[LS_DEFAULT].in_use = false; - for (scheme = LS_DEFAULT; scheme < LS_END; scheme++) { - if (c->livery[scheme].in_use) { - c->livery[LS_DEFAULT].in_use = true; - break; - } + if (c->livery[scheme].in_use != 0) { + /* If enabling a scheme, set the default scheme to be in use too */ + c->livery[LS_DEFAULT].in_use = 1; + } else { + /* Else loop through all schemes to see if any are left enabled. + * If not, disable the default scheme too. */ + c->livery[LS_DEFAULT].in_use = 0; + for (scheme = LS_DEFAULT; scheme < LS_END; scheme++) { + if (c->livery[scheme].in_use != 0) { + c->livery[LS_DEFAULT].in_use = 1; + break; } - break; - - default: - break; + } } + ResetVehicleColourMap(); MarkWholeScreenDirty(); diff --git a/src/company_gui.cpp b/src/company_gui.cpp index dcf1f3695d..ae7c8d6c63 100644 --- a/src/company_gui.cpp +++ b/src/company_gui.cpp @@ -36,6 +36,7 @@ #include "water.h" #include "station_func.h" #include "zoom_func.h" +#include "sortlist_type.h" #include "widgets/company_widget.h" @@ -522,7 +523,7 @@ public: StringID String() const { - return _colour_dropdown[this->result]; + return this->result >= COLOUR_END ? STR_COLOUR_DEFAULT : _colour_dropdown[this->result]; } uint Height(uint width) const @@ -541,7 +542,7 @@ public: int height = bottom - top; int icon_y_offset = height / 2; int text_y_offset = (height - FONT_HEIGHT_NORMAL) / 2 + 1; - DrawSprite(SPR_VEH_BUS_SIDE_VIEW, PALETTE_RECOLOUR_START + this->result, + DrawSprite(SPR_VEH_BUS_SIDE_VIEW, PALETTE_RECOLOUR_START + (this->result % COLOUR_END), rtl ? right - 2 - ScaleGUITrad(14) : left + ScaleGUITrad(14) + 2, top + icon_y_offset); DrawString(rtl ? left + 2 : left + ScaleGUITrad(28) + 4, @@ -550,60 +551,200 @@ public: } }; +static const int LEVEL_WIDTH = 10; ///< Indenting width of a sub-group in pixels + +typedef GUIList GUIGroupList; + /** Company livery colour scheme window. */ struct SelectCompanyLiveryWindow : public Window { private: uint32 sel; LiveryClass livery_class; Dimension square; - Dimension box; + uint livery_height; uint line_height; + GUIGroupList groups; + SmallVector indents; + Scrollbar *vscroll; void ShowColourDropDownMenu(uint32 widget) { uint32 used_colours = 0; - const Livery *livery; - LiveryScheme scheme; + const Company *c; + const Livery *livery, *default_livery = NULL; + bool primary = widget == WID_SCL_PRI_COL_DROPDOWN; + byte default_col; /* Disallow other company colours for the primary colour */ - if (HasBit(this->sel, LS_DEFAULT) && widget == WID_SCL_PRI_COL_DROPDOWN) { - const Company *c; + if (this->livery_class < LC_GROUP_RAIL && HasBit(this->sel, LS_DEFAULT) && primary) { FOR_ALL_COMPANIES(c) { if (c->index != _local_company) SetBit(used_colours, c->colour); } } - /* Get the first selected livery to use as the default dropdown item */ - for (scheme = LS_BEGIN; scheme < LS_END; scheme++) { - if (HasBit(this->sel, scheme)) break; + c = Company::Get((CompanyID)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]; + } else { + const Group *g = Group::Get(this->sel); + livery = &g->livery; + if (g->parent == INVALID_GROUP) { + default_livery = &c->livery[LS_DEFAULT]; + } else { + const Group *pg = Group::Get(g->parent); + default_livery = &pg->livery; + } } - if (scheme == LS_END) scheme = LS_DEFAULT; - livery = &Company::Get((CompanyID)this->window_number)->livery[scheme]; DropDownList *list = new DropDownList(); + if (default_livery != NULL) { + /* 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->Append() = new DropDownListColourItem(default_col, false); + } for (uint i = 0; i < lengthof(_colour_dropdown); i++) { *list->Append() = new DropDownListColourItem(i, HasBit(used_colours, i)); } - ShowDropDownList(this, list, widget == WID_SCL_PRI_COL_DROPDOWN ? livery->colour1 : livery->colour2, widget); + byte sel = (default_livery == NULL || HasBit(livery->in_use, primary ? 0 : 1)) ? (primary ? livery->colour1 : livery->colour2) : default_col; + ShowDropDownList(this, list, sel, widget); + } + + static int CDECL GroupNameSorter(const Group * const *a, const Group * const *b) + { + static const Group *last_group[2] = { NULL, NULL }; + static char last_name[2][64] = { "", "" }; + + if (*a != last_group[0]) { + last_group[0] = *a; + SetDParam(0, (*a)->index); + GetString(last_name[0], STR_GROUP_NAME, lastof(last_name[0])); + } + + if (*b != last_group[1]) { + last_group[1] = *b; + SetDParam(0, (*b)->index); + GetString(last_name[1], STR_GROUP_NAME, lastof(last_name[1])); + } + + int r = strnatcmp(last_name[0], last_name[1]); // Sort by name (natural sorting). + if (r == 0) return (*a)->index - (*b)->index; + return r; + } + + void AddChildren(GUIGroupList *source, GroupID parent, int indent) + { + for (const Group **g = source->Begin(); g != source->End(); g++) { + if ((*g)->parent != parent) continue; + *this->groups.Append() = *g; + *this->indents.Append() = indent; + AddChildren(source, (*g)->index, indent + 1); + } + } + + void BuildGroupList(CompanyID owner) + { + if (!this->groups.NeedRebuild()) return; + + this->groups.Clear(); + this->indents.Clear(); + + if (this->livery_class >= LC_GROUP_RAIL) { + GUIGroupList list; + VehicleType vtype = (VehicleType)(this->livery_class - LC_GROUP_RAIL); + + const Group *g; + FOR_ALL_GROUPS(g) { + if (g->owner == owner && g->vehicle_type == vtype) { + *list.Append() = g; + } + } + + list.ForceResort(); + list.Sort(&GroupNameSorter); + + AddChildren(&list, INVALID_GROUP, 0); + } + + this->groups.Compact(); + this->groups.RebuildDone(); + } + + void SetLiveryHeight() + { + if (this->livery_class < LC_GROUP_RAIL) { + this->livery_height = 0; + for (LiveryScheme scheme = LS_DEFAULT; scheme < LS_END; scheme++) { + if (_livery_class[scheme] == this->livery_class && HasBit(_loaded_newgrf_features.used_liveries, scheme)) { + this->livery_height++; + } + } + } else { + this->livery_height = this->groups.Length(); + } + + this->vscroll->SetCount(this->livery_height); } public: - SelectCompanyLiveryWindow(WindowDesc *desc, CompanyID company) : Window(desc) + SelectCompanyLiveryWindow(WindowDesc *desc, CompanyID company, GroupID group) : Window(desc) { - this->livery_class = LC_OTHER; - this->sel = 1; + this->CreateNestedTree(); + this->vscroll = this->GetScrollbar(WID_SCL_MATRIX_SCROLLBAR); this->square = GetSpriteSize(SPR_SQUARE); - this->box = maxdim(GetSpriteSize(SPR_BOX_CHECKED), GetSpriteSize(SPR_BOX_EMPTY)); - this->line_height = max(max(this->square.height, this->box.height), (uint)FONT_HEIGHT_NORMAL) + 4; + this->line_height = max(this->square.height, (uint)FONT_HEIGHT_NORMAL) + 4; + + if (group == INVALID_GROUP) { + this->livery_class = LC_OTHER; + this->sel = 1; + this->LowerWidget(WID_SCL_CLASS_GENERAL); + this->BuildGroupList(company); + this->SetLiveryHeight(); + } else { + this->SetSelectedGroup(group); + } - this->InitNested(company); + this->FinishInitNested(company); this->owner = company; - this->LowerWidget(WID_SCL_CLASS_GENERAL); this->InvalidateData(1); } + void SetSelectedGroup(GroupID group) + { + this->RaiseWidget(this->livery_class + WID_SCL_CLASS_GENERAL); + const Group *g = Group::Get(group); + switch (g->vehicle_type) { + case VEH_TRAIN: this->livery_class = LC_GROUP_RAIL; break; + case VEH_ROAD: this->livery_class = LC_GROUP_ROAD; break; + case VEH_SHIP: this->livery_class = LC_GROUP_SHIP; break; + case VEH_AIRCRAFT: this->livery_class = LC_GROUP_AIRCRAFT; break; + default: NOT_REACHED(); + } + this->sel = group; + this->LowerWidget(this->livery_class + WID_SCL_CLASS_GENERAL); + + this->groups.ForceRebuild(); + this->BuildGroupList((CompanyID)this->window_number); + this->SetLiveryHeight(); + + /* Position scrollbar to selected group */ + for (uint i = 0; i < this->livery_height; i++) { + if (this->groups[i]->index == sel) { + this->vscroll->SetPosition(Clamp(i - this->vscroll->GetCapacity() / 2, 0, max(this->vscroll->GetCount() - this->vscroll->GetCapacity(), 0))); + break; + } + } + } + virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize) { switch (widget) { @@ -613,19 +754,25 @@ public: for (LiveryScheme scheme = LS_DEFAULT; scheme < LS_END; scheme++) { d = maxdim(d, GetStringBoundingBox(STR_LIVERY_DEFAULT + scheme)); } - size->width = max(size->width, 5 + this->box.width + d.width + WD_FRAMERECT_RIGHT); + + /* And group names */ + const Group *g; + FOR_ALL_GROUPS(g) { + if (g->owner == (CompanyID)this->window_number) { + SetDParam(0, g->index); + d = maxdim(d, GetStringBoundingBox(STR_GROUP_NAME)); + } + } + + size->width = max(size->width, 5 + d.width + WD_FRAMERECT_RIGHT); break; } case WID_SCL_MATRIX: { - uint livery_height = 0; - for (LiveryScheme scheme = LS_DEFAULT; scheme < LS_END; scheme++) { - if (_livery_class[scheme] == this->livery_class && HasBit(_loaded_newgrf_features.used_liveries, scheme)) { - livery_height++; - } - } - size->height = livery_height * this->line_height; - this->GetWidget(WID_SCL_MATRIX)->widget_data = (livery_height << MAT_ROW_START) | (1 << MAT_COL_START); + /* 11 items in the default rail class */ + size->height = 11 * this->line_height; + resize->width = 1; + resize->height = this->line_height; break; } @@ -641,6 +788,7 @@ public: for (const StringID *id = _colour_dropdown; id != endof(_colour_dropdown); id++) { size->width = max(size->width, GetStringBoundingBox(*id).width + padding); } + size->width = max(size->width, GetStringBoundingBox(STR_COLOUR_DEFAULT).width + padding); break; } } @@ -651,8 +799,11 @@ public: bool local = (CompanyID)this->window_number == _local_company; /* Disable dropdown controls if no scheme is selected */ - this->SetWidgetDisabledState(WID_SCL_PRI_COL_DROPDOWN, !local || this->sel == 0); - this->SetWidgetDisabledState(WID_SCL_SEC_COL_DROPDOWN, !local || this->sel == 0); + bool disabled = this->livery_class < LC_GROUP_RAIL ? (this->sel == 0) : (this->sel == INVALID_GROUP); + this->SetWidgetDisabledState(WID_SCL_PRI_COL_DROPDOWN, !local || disabled); + this->SetWidgetDisabledState(WID_SCL_SEC_COL_DROPDOWN, !local || disabled); + + this->BuildGroupList((CompanyID)this->window_number); this->DrawWidgets(); } @@ -667,15 +818,31 @@ public: case WID_SCL_PRI_COL_DROPDOWN: case WID_SCL_SEC_COL_DROPDOWN: { const Company *c = Company::Get((CompanyID)this->window_number); - LiveryScheme scheme = LS_DEFAULT; - - if (this->sel != 0) { - for (scheme = LS_BEGIN; scheme < LS_END; scheme++) { - if (HasBit(this->sel, scheme)) break; + bool primary = widget == WID_SCL_PRI_COL_DROPDOWN; + StringID colour = STR_COLOUR_DEFAULT; + + if (this->livery_class < LC_GROUP_RAIL) { + if (this->sel != 0) { + LiveryScheme scheme = LS_DEFAULT; + for (scheme = LS_BEGIN; scheme < LS_END; scheme++) { + if (HasBit(this->sel, scheme)) break; + } + 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); + } + } + } else { + if (this->sel != INVALID_GROUP) { + 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); + } } - if (scheme == LS_END) scheme = LS_DEFAULT; } - SetDParam(0, STR_COLOUR_DARK_BLUE + ((widget == WID_SCL_PRI_COL_DROPDOWN) ? c->livery[scheme].colour1 : c->livery[scheme].colour2)); + SetDParam(0, colour); break; } } @@ -700,36 +867,45 @@ public: int sec_left = nwi->pos_x; int sec_right = sec_left + nwi->current_x - 1; - int text_left = (rtl ? (uint)WD_FRAMERECT_LEFT : (this->box.width + 5)); - int text_right = (rtl ? (this->box.width + 5) : (uint)WD_FRAMERECT_RIGHT); + int text_left = (rtl ? (uint)WD_FRAMERECT_LEFT : (this->square.width + 5)); + int text_right = (rtl ? (this->square.width + 5) : (uint)WD_FRAMERECT_RIGHT); - int box_offs = (this->line_height - this->box.height) / 2; int square_offs = (this->line_height - this->square.height) / 2 + 1; int text_offs = (this->line_height - FONT_HEIGHT_NORMAL) / 2 + 1; int y = r.top; - const Company *c = Company::Get((CompanyID)this->window_number); - for (LiveryScheme scheme = LS_DEFAULT; scheme < LS_END; scheme++) { - if (_livery_class[scheme] == this->livery_class && HasBit(_loaded_newgrf_features.used_liveries, scheme)) { - bool sel = HasBit(this->sel, scheme) != 0; - /* Optional check box + scheme name. */ - if (scheme != LS_DEFAULT) { - DrawSprite(c->livery[scheme].in_use ? SPR_BOX_CHECKED : SPR_BOX_EMPTY, PAL_NONE, (rtl ? sch_right - (this->box.width + 5) + WD_FRAMERECT_RIGHT : sch_left) + WD_FRAMERECT_LEFT, y + box_offs); - } - DrawString(sch_left + text_left, sch_right - text_right, y + text_offs, STR_LIVERY_DEFAULT + scheme, sel ? TC_WHITE : TC_BLACK); + /* Helper function to draw livery info. */ + auto draw_livery = [&](StringID str, const Livery &liv, bool sel, bool def, int indent) { + /* Livery Label. */ + DrawString(sch_left + WD_FRAMERECT_LEFT + (rtl ? 0 : indent), sch_right - WD_FRAMERECT_RIGHT - (rtl ? indent : 0), y + text_offs, str, sel ? TC_WHITE : TC_BLACK); - /* Text below the first dropdown. */ - DrawSprite(SPR_SQUARE, GENERAL_SPRITE_COLOUR(c->livery[scheme].colour1), (rtl ? pri_right - (this->box.width + 5) + WD_FRAMERECT_RIGHT : pri_left) + WD_FRAMERECT_LEFT, y + square_offs); - DrawString(pri_left + text_left, pri_right - text_right, y + text_offs, STR_COLOUR_DARK_BLUE + c->livery[scheme].colour1, sel ? TC_WHITE : TC_GOLD); + /* Text below the first dropdown. */ + DrawSprite(SPR_SQUARE, GENERAL_SPRITE_COLOUR(liv.colour1), (rtl ? pri_right - (this->square.width + 5) + WD_FRAMERECT_RIGHT : pri_left) + WD_FRAMERECT_LEFT, y + square_offs); + DrawString(pri_left + text_left, pri_right - text_right, y + text_offs, (def || HasBit(liv.in_use, 0)) ? STR_COLOUR_DARK_BLUE + liv.colour1 : STR_COLOUR_DEFAULT, sel ? TC_WHITE : TC_GOLD); - /* Text below the second dropdown. */ - if (sec_right > sec_left) { // Second dropdown has non-zero size. - DrawSprite(SPR_SQUARE, GENERAL_SPRITE_COLOUR(c->livery[scheme].colour2), (rtl ? sec_right - (this->box.width + 5) + WD_FRAMERECT_RIGHT : sec_left) + WD_FRAMERECT_LEFT, y + square_offs); - DrawString(sec_left + text_left, sec_right - text_right, y + text_offs, STR_COLOUR_DARK_BLUE + c->livery[scheme].colour2, sel ? TC_WHITE : TC_GOLD); - } + /* Text below the second dropdown. */ + if (sec_right > sec_left) { // Second dropdown has non-zero size. + DrawSprite(SPR_SQUARE, GENERAL_SPRITE_COLOUR(liv.colour2), (rtl ? sec_right - (this->square.width + 5) + WD_FRAMERECT_RIGHT : sec_left) + WD_FRAMERECT_LEFT, y + square_offs); + DrawString(sec_left + text_left, sec_right - text_right, y + text_offs, (def || HasBit(liv.in_use, 1)) ? STR_COLOUR_DARK_BLUE + liv.colour2 : STR_COLOUR_DEFAULT, sel ? TC_WHITE : TC_GOLD); + } + + y += this->line_height; + }; - y += this->line_height; + if (livery_class < LC_GROUP_RAIL) { + const Company *c = Company::Get((CompanyID)this->window_number); + for (LiveryScheme scheme = LS_DEFAULT; scheme < LS_END; scheme++) { + if (_livery_class[scheme] == this->livery_class && HasBit(_loaded_newgrf_features.used_liveries, scheme)) { + draw_livery(STR_LIVERY_DEFAULT + scheme, c->livery[scheme], HasBit(this->sel, scheme), scheme == LS_DEFAULT, 0); + } + } + } else { + uint max = min(this->vscroll->GetPosition() + this->vscroll->GetCapacity(), this->groups.Length()); + for (uint i = this->vscroll->GetPosition(); i < max; ++i) { + const Group *g = this->groups[i]; + SetDParam(0, g->index); + draw_livery(STR_GROUP_NAME, g->livery, this->sel == g->index, false, this->indents[i] * LEVEL_WIDTH); } } } @@ -743,20 +919,35 @@ public: case WID_SCL_CLASS_ROAD: case WID_SCL_CLASS_SHIP: case WID_SCL_CLASS_AIRCRAFT: + case WID_SCL_GROUPS_RAIL: + case WID_SCL_GROUPS_ROAD: + case WID_SCL_GROUPS_SHIP: + case WID_SCL_GROUPS_AIRCRAFT: this->RaiseWidget(this->livery_class + WID_SCL_CLASS_GENERAL); this->livery_class = (LiveryClass)(widget - WID_SCL_CLASS_GENERAL); this->LowerWidget(this->livery_class + WID_SCL_CLASS_GENERAL); /* Select the first item in the list */ - this->sel = 0; - for (LiveryScheme scheme = LS_DEFAULT; scheme < LS_END; scheme++) { - if (_livery_class[scheme] == this->livery_class && HasBit(_loaded_newgrf_features.used_liveries, scheme)) { - this->sel = 1 << scheme; - break; + if (this->livery_class < LC_GROUP_RAIL) { + this->sel = 0; + for (LiveryScheme scheme = LS_DEFAULT; scheme < LS_END; scheme++) { + if (_livery_class[scheme] == this->livery_class && HasBit(_loaded_newgrf_features.used_liveries, scheme)) { + this->sel = 1 << scheme; + break; + } + } + } else { + this->sel = INVALID_GROUP; + this->groups.ForceRebuild(); + this->BuildGroupList((CompanyID)this->window_number); + + if (this->groups.Length() > 0) { + this->sel = this->groups[0]->index; } } - this->ReInit(); + this->SetLiveryHeight(); + this->SetDirty(); break; case WID_SCL_PRI_COL_DROPDOWN: // First colour dropdown @@ -769,23 +960,27 @@ public: case WID_SCL_MATRIX: { const NWidgetBase *wid = this->GetWidget(WID_SCL_MATRIX); - LiveryScheme j = (LiveryScheme)((pt.y - wid->pos_y) / this->line_height); + if (this->livery_class < LC_GROUP_RAIL) { + LiveryScheme j = (LiveryScheme)((pt.y - wid->pos_y) / this->line_height); - for (LiveryScheme scheme = LS_BEGIN; scheme <= j; scheme++) { - if (_livery_class[scheme] != this->livery_class || !HasBit(_loaded_newgrf_features.used_liveries, scheme)) j++; - if (scheme >= LS_END) return; - } - if (j >= LS_END) return; + if (j >= this->livery_height) return; - /* If clicking on the left edge, toggle using the livery */ - if (_current_text_dir == TD_RTL ? pt.x - wid->pos_x > wid->current_x - (this->box.width + 5) : pt.x - wid->pos_x < (this->box.width + 5)) { - DoCommandP(0, j | (2 << 8), !Company::Get((CompanyID)this->window_number)->livery[j].in_use, CMD_SET_COMPANY_COLOUR); - } + for (LiveryScheme scheme = LS_BEGIN; scheme <= j; scheme++) { + if (_livery_class[scheme] != this->livery_class || !HasBit(_loaded_newgrf_features.used_liveries, scheme)) j++; + if (scheme >= LS_END) return; + } + if (j >= LS_END) return; - if (_ctrl_pressed) { - ToggleBit(this->sel, j); + if (_ctrl_pressed) { + ToggleBit(this->sel, j); + } else { + this->sel = 1 << j; + } } else { - this->sel = 1 << j; + uint id_g = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_SCL_MATRIX, 0, this->line_height); + if (id_g >= this->groups.Length()) return; + + this->sel = this->groups[id_g]->index; } this->SetDirty(); break; @@ -793,16 +988,29 @@ public: } } + virtual void OnResize() + { + this->vscroll->SetCapacityFromWidget(this, WID_SCL_MATRIX); + } + virtual void OnDropdownSelect(int widget, int index) { bool local = (CompanyID)this->window_number == _local_company; if (!local) return; - 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) || (_ctrl_pressed && _livery_class[scheme] == this->livery_class && HasBit(_loaded_newgrf_features.used_liveries, scheme))) { - DoCommandP(0, scheme | (widget == WID_SCL_PRI_COL_DROPDOWN ? 0 : 256), index, CMD_SET_COMPANY_COLOUR); + if (index >= COLOUR_END) index = INVALID_COLOUR; + + if (this->livery_class < LC_GROUP_RAIL) { + /* Set company colour livery */ + 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) || (_ctrl_pressed && _livery_class[scheme] == this->livery_class && HasBit(_loaded_newgrf_features.used_liveries, scheme))) { + DoCommandP(0, scheme | (widget == WID_SCL_PRI_COL_DROPDOWN ? 0 : 256), index, CMD_SET_COMPANY_COLOUR); + } } + } else { + /* Setting group livery */ + DoCommandP(0, this->sel, (widget == WID_SCL_PRI_COL_DROPDOWN ? 0 : 256) | (index << 16), CMD_SET_GROUP_LIVERY); } } @@ -814,15 +1022,27 @@ public: virtual void OnInvalidateData(int data = 0, bool gui_scope = true) { if (!gui_scope) return; + + if (data != -1) { + /* data contains a VehicleType, rebuild list if it displayed */ + if (this->livery_class == data + LC_GROUP_RAIL) { + if (!Group::IsValidID(this->sel)) this->sel = INVALID_GROUP; + this->groups.ForceRebuild(); + this->BuildGroupList((CompanyID)this->window_number); + this->SetDirty(); + } + return; + } + this->SetWidgetsDisabledState(true, WID_SCL_CLASS_RAIL, WID_SCL_CLASS_ROAD, WID_SCL_CLASS_SHIP, WID_SCL_CLASS_AIRCRAFT, WIDGET_LIST_END); - bool current_class_valid = this->livery_class == LC_OTHER; + bool current_class_valid = this->livery_class == LC_OTHER || this->livery_class >= LC_GROUP_RAIL; if (_settings_client.gui.liveries == LIT_ALL || (_settings_client.gui.liveries == LIT_COMPANY && this->window_number == _local_company)) { for (LiveryScheme scheme = LS_DEFAULT; scheme < LS_END; scheme++) { if (HasBit(_loaded_newgrf_features.used_liveries, scheme)) { if (_livery_class[scheme] == this->livery_class) current_class_valid = true; this->EnableWidget(WID_SCL_CLASS_GENERAL + _livery_class[scheme]); - } else { + } else if (this->livery_class < LC_GROUP_RAIL) { ClrBit(this->sel, scheme); } } @@ -831,8 +1051,6 @@ public: if (!current_class_valid) { Point pt = {0, 0}; this->OnClick(pt, WID_SCL_CLASS_GENERAL, 1); - } else if (data == 0) { - this->ReInit(); } } }; @@ -848,6 +1066,10 @@ static const NWidgetPart _nested_select_company_livery_widgets [] = { NWidget(WWT_IMGBTN, COLOUR_GREY, WID_SCL_CLASS_ROAD), SetMinimalSize(22, 22), SetFill(0, 1), SetDataTip(SPR_IMG_TRUCKLIST, STR_LIVERY_ROAD_VEHICLE_TOOLTIP), NWidget(WWT_IMGBTN, COLOUR_GREY, WID_SCL_CLASS_SHIP), SetMinimalSize(22, 22), SetFill(0, 1), SetDataTip(SPR_IMG_SHIPLIST, STR_LIVERY_SHIP_TOOLTIP), NWidget(WWT_IMGBTN, COLOUR_GREY, WID_SCL_CLASS_AIRCRAFT), SetMinimalSize(22, 22), SetFill(0, 1), SetDataTip(SPR_IMG_AIRPLANESLIST, STR_LIVERY_AIRCRAFT_TOOLTIP), + NWidget(WWT_IMGBTN, COLOUR_GREY, WID_SCL_GROUPS_RAIL), SetMinimalSize(22, 22), SetFill(0, 1), SetDataTip(SPR_GROUP_LIVERY_TRAIN, STR_LIVERY_TRAIN_TOOLTIP), + NWidget(WWT_IMGBTN, COLOUR_GREY, WID_SCL_GROUPS_ROAD), SetMinimalSize(22, 22), SetFill(0, 1), SetDataTip(SPR_GROUP_LIVERY_ROADVEH, STR_LIVERY_ROAD_VEHICLE_TOOLTIP), + NWidget(WWT_IMGBTN, COLOUR_GREY, WID_SCL_GROUPS_SHIP), SetMinimalSize(22, 22), SetFill(0, 1), SetDataTip(SPR_GROUP_LIVERY_SHIP, STR_LIVERY_SHIP_TOOLTIP), + NWidget(WWT_IMGBTN, COLOUR_GREY, WID_SCL_GROUPS_AIRCRAFT), SetMinimalSize(22, 22), SetFill(0, 1), SetDataTip(SPR_GROUP_LIVERY_AIRCRAFT, STR_LIVERY_AIRCRAFT_TOOLTIP), NWidget(WWT_PANEL, COLOUR_GREY), SetMinimalSize(90, 22), SetFill(1, 1), EndContainer(), EndContainer(), NWidget(NWID_HORIZONTAL), @@ -856,7 +1078,13 @@ static const NWidgetPart _nested_select_company_livery_widgets [] = { NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_SCL_SEC_COL_DROPDOWN), SetMinimalSize(125, 12), SetFill(0, 1), SetDataTip(STR_BLACK_STRING, STR_LIVERY_SECONDARY_TOOLTIP), EndContainer(), - NWidget(WWT_MATRIX, COLOUR_GREY, WID_SCL_MATRIX), SetMinimalSize(275, 15), SetFill(1, 0), SetMatrixDataTip(1, 1, STR_LIVERY_PANEL_TOOLTIP), + NWidget(NWID_HORIZONTAL), + NWidget(WWT_MATRIX, COLOUR_GREY, WID_SCL_MATRIX), SetMinimalSize(275, 0), SetResize(1, 0), SetFill(1, 1), SetMatrixDataTip(1, 0, STR_LIVERY_PANEL_TOOLTIP), SetScrollbar(WID_SCL_MATRIX_SCROLLBAR), + NWidget(NWID_VERTICAL), + NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_SCL_MATRIX_SCROLLBAR), + NWidget(WWT_RESIZEBOX, COLOUR_GREY), + EndContainer(), + EndContainer(), }; static WindowDesc _select_company_livery_desc( @@ -866,6 +1094,16 @@ static WindowDesc _select_company_livery_desc( _nested_select_company_livery_widgets, lengthof(_nested_select_company_livery_widgets) ); +void ShowCompanyLiveryWindow(CompanyID company, GroupID group) +{ + SelectCompanyLiveryWindow *w = (SelectCompanyLiveryWindow *)BringWindowToFrontById(WC_COMPANY_COLOUR, company); + if (w == NULL) { + new SelectCompanyLiveryWindow(&_select_company_livery_desc, company, group); + } else if (group != INVALID_GROUP) { + w->SetSelectedGroup(group); + } +} + /** * Draws the face of a company manager's face. * @param cmf the company manager's face @@ -2291,8 +2529,7 @@ struct CompanyWindow : Window case WID_C_NEW_FACE: DoSelectCompanyManagerFace(this); break; case WID_C_COLOUR_SCHEME: - if (BringWindowToFrontById(WC_COMPANY_COLOUR, this->window_number)) break; - new SelectCompanyLiveryWindow(&_select_company_livery_desc, (CompanyID)this->window_number); + ShowCompanyLiveryWindow((CompanyID)this->window_number, INVALID_GROUP); break; case WID_C_PRESIDENT_NAME: diff --git a/src/company_gui.h b/src/company_gui.h index f4964f3dc7..c5593d15d3 100644 --- a/src/company_gui.h +++ b/src/company_gui.h @@ -13,11 +13,13 @@ #define COMPANY_GUI_H #include "company_type.h" +#include "group.h" #include "gfx_type.h" TextColour GetDrawStringCompanyColour(CompanyID company); void DrawCompanyIcon(CompanyID c, int x, int y); +void ShowCompanyLiveryWindow(CompanyID company, GroupID group); void ShowCompanyStations(CompanyID company); void ShowCompanyFinances(CompanyID company); void ShowCompany(CompanyID company); diff --git a/src/gfx_type.h b/src/gfx_type.h index 4cfc149a86..7eeddb4078 100644 --- a/src/gfx_type.h +++ b/src/gfx_type.h @@ -241,7 +241,7 @@ enum Colours { COLOUR_END, INVALID_COLOUR = 0xFF, }; -template <> struct EnumPropsT : MakeEnumPropsT {}; +template <> struct EnumPropsT : MakeEnumPropsT {}; /** Colour of the strings, see _string_colourmap in table/string_colours.h or docs/ottd-colourtext-palette.png */ enum TextColour { diff --git a/src/group.h b/src/group.h index 91ee77e601..ea4f7e130e 100644 --- a/src/group.h +++ b/src/group.h @@ -17,6 +17,7 @@ #include "company_type.h" #include "vehicle_type.h" #include "engine_type.h" +#include "livery.h" typedef Pool GroupPool; extern GroupPool _group_pool; ///< Pool of groups. @@ -69,6 +70,7 @@ struct Group : GroupPool::PoolItem<&_group_pool> { VehicleTypeByte vehicle_type; ///< Vehicle type of the group bool replace_protection; ///< If set to true, the global autoreplace have no effect on the group + Livery livery; ///< Custom colour scheme for vehicles in this group GroupStatistics statistics; ///< NOSAVE: Statistics and caches on the vehicles in the group. GroupID parent; ///< Parent group diff --git a/src/group_cmd.cpp b/src/group_cmd.cpp index 3edaf76271..7795d917d7 100644 --- a/src/group_cmd.cpp +++ b/src/group_cmd.cpp @@ -255,6 +255,45 @@ static inline void UpdateNumEngineGroup(const Vehicle *v, GroupID old_g, GroupID } +const Livery *GetParentLivery(const Group *g) +{ + if (g->parent == INVALID_GROUP) { + const Company *c = Company::Get(g->owner); + return &c->livery[LS_DEFAULT]; + } + + const Group *pg = Group::Get(g->parent); + return &pg->livery; +} + + +/** + * Propagate a livery change to a group's children. + * @param g Group. + */ +void PropagateChildLivery(const Group *g) +{ + /* Company colour data is indirectly cached. */ + Vehicle *v; + FOR_ALL_VEHICLES(v) { + if (v->group_id == g->index && (!v->IsGroundVehicle() || v->IsFrontEngine())) { + for (Vehicle *u = v; u != NULL; u = u->Next()) { + u->colourmap = PAL_NONE; + u->InvalidateNewGRFCache(); + } + } + } + + Group *cg; + FOR_ALL_GROUPS(cg) { + if (cg->parent == g->index) { + 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; + PropagateChildLivery(cg); + } + } +} + Group::Group(Owner owner) { @@ -289,9 +328,14 @@ CommandCost CmdCreateGroup(TileIndex tile, DoCommandFlag flags, uint32 p1, uint3 g->vehicle_type = vt; g->parent = INVALID_GROUP; + const Company *c = Company::Get(_current_company); + g->livery.colour1 = c->livery[LS_DEFAULT].colour1; + g->livery.colour2 = c->livery[LS_DEFAULT].colour2; + _new_group_id = g->index; InvalidateWindowData(GetWindowClassForVehicleType(vt), VehicleListIdentifier(VL_GROUP_LIST, vt, _current_company).Pack()); + InvalidateWindowData(WC_COMPANY_COLOUR, g->owner, g->vehicle_type); } return CommandCost(); @@ -346,6 +390,7 @@ CommandCost CmdDeleteGroup(TileIndex tile, DoCommandFlag flags, uint32 p1, uint3 delete g; InvalidateWindowData(GetWindowClassForVehicleType(vt), VehicleListIdentifier(VL_GROUP_LIST, vt, _current_company).Pack()); + InvalidateWindowData(WC_COMPANY_COLOUR, _current_company, vt); } return CommandCost(); @@ -410,12 +455,22 @@ CommandCost CmdAlterGroup(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 if (flags & DC_EXEC) { g->parent = (pg == NULL) ? INVALID_GROUP : pg->index; GroupStatistics::UpdateAutoreplace(g->owner); + + if (g->livery.in_use == 0) { + const Livery *livery = GetParentLivery(g); + g->livery.colour1 = livery->colour1; + g->livery.colour2 = livery->colour2; + + PropagateChildLivery(g); + MarkWholeScreenDirty(); + } } } if (flags & DC_EXEC) { InvalidateWindowData(WC_REPLACE_VEHICLE, g->vehicle_type, 1); InvalidateWindowData(GetWindowClassForVehicleType(g->vehicle_type), VehicleListIdentifier(VL_GROUP_LIST, g->vehicle_type, _current_company).Pack()); + InvalidateWindowData(WC_COMPANY_COLOUR, g->owner, g->vehicle_type); } return CommandCost(); @@ -442,6 +497,11 @@ static void AddVehicleToGroup(Vehicle *v, GroupID new_g) case VEH_AIRCRAFT: if (v->IsEngineCountable()) UpdateNumEngineGroup(v, v->group_id, new_g); v->group_id = new_g; + for (Vehicle *u = v; u != NULL; u = u->Next()) { + u->colourmap = PAL_NONE; + u->InvalidateNewGRFCache(); + u->UpdateViewport(true); + } break; } @@ -496,6 +556,9 @@ CommandCost CmdAddVehicleGroup(TileIndex tile, DoCommandFlag flags, uint32 p1, u /* Update the Replace Vehicle Windows */ SetWindowDirty(WC_REPLACE_VEHICLE, v->type); + SetWindowDirty(WC_VEHICLE_DEPOT, v->tile); + SetWindowDirty(WC_VEHICLE_VIEW, v->index); + SetWindowDirty(WC_VEHICLE_DETAILS, v->index); InvalidateWindowData(GetWindowClassForVehicleType(v->type), VehicleListIdentifier(VL_GROUP_LIST, v->type, _current_company).Pack()); } @@ -577,6 +640,42 @@ CommandCost CmdRemoveAllVehiclesGroup(TileIndex tile, DoCommandFlag flags, uint3 return CommandCost(); } +/** + * Set the livery for a vehicle group. + * @param tile Unused. + * @param flags Command flags. + * @param p1 + * - p1 bit 0-15 Group ID. + * @param p2 + * - p2 bit 8 Set secondary instead of primary colour + * - p2 bit 16-23 Colour. + */ +CommandCost CmdSetGroupLivery(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text) +{ + Group *g = Group::GetIfValid(p1); + bool primary = !HasBit(p2, 8); + Colours colour = Extract(p2); + + if (g == NULL || g->owner != _current_company) return CMD_ERROR; + + if (flags & DC_EXEC) { + if (primary) { + SB(g->livery.in_use, 0, 1, colour != INVALID_COLOUR); + if (colour == INVALID_COLOUR) colour = (Colours)GetParentLivery(g)->colour1; + g->livery.colour1 = colour; + } else { + SB(g->livery.in_use, 1, 1, colour != INVALID_COLOUR); + if (colour == INVALID_COLOUR) colour = (Colours)GetParentLivery(g)->colour2; + g->livery.colour2 = colour; + } + + PropagateChildLivery(g); + MarkWholeScreenDirty(); + } + + return CommandCost(); +} + /** * Set replace protection for a group and its sub-groups. * @param g initial group. @@ -652,6 +751,9 @@ void SetTrainGroupID(Train *v, GroupID new_g) if (u->IsEngineCountable()) UpdateNumEngineGroup(u, u->group_id, new_g); u->group_id = new_g; + u->colourmap = PAL_NONE; + u->InvalidateNewGRFCache(); + u->UpdateViewport(true); } /* Update the Replace Vehicle Windows */ @@ -676,6 +778,8 @@ void UpdateTrainGroupID(Train *v) if (u->IsEngineCountable()) UpdateNumEngineGroup(u, u->group_id, new_g); u->group_id = new_g; + u->colourmap = PAL_NONE; + u->InvalidateNewGRFCache(); } /* Update the Replace Vehicle Windows */ diff --git a/src/group_gui.cpp b/src/group_gui.cpp index a884920842..ce607c3264 100644 --- a/src/group_gui.cpp +++ b/src/group_gui.cpp @@ -25,6 +25,7 @@ #include "vehicle_gui_base.h" #include "core/geometry_func.hpp" #include "company_base.h" +#include "company_gui.h" #include "widgets/group_widget.h" @@ -63,6 +64,8 @@ static const NWidgetPart _nested_group_widgets[] = { SetDataTip(SPR_GROUP_DELETE_TRAIN, STR_GROUP_DELETE_TOOLTIP), NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_GL_RENAME_GROUP), SetFill(0, 1), SetDataTip(SPR_GROUP_RENAME_TRAIN, STR_GROUP_RENAME_TOOLTIP), + NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_GL_LIVERY_GROUP), SetFill(0, 1), + SetDataTip(SPR_GROUP_LIVERY_TRAIN, STR_GROUP_LIVERY_TOOLTIP), NWidget(WWT_PANEL, COLOUR_GREY), SetFill(1, 1), EndContainer(), NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_GL_REPLACE_PROTECTION), SetFill(0, 1), SetDataTip(SPR_GROUP_REPLACE_OFF_TRAIN, STR_GROUP_REPLACE_PROTECTION_TOOLTIP), @@ -344,6 +347,7 @@ public: this->GetWidget(WID_GL_CREATE_GROUP)->widget_data += this->vli.vtype; this->GetWidget(WID_GL_RENAME_GROUP)->widget_data += this->vli.vtype; this->GetWidget(WID_GL_DELETE_GROUP)->widget_data += this->vli.vtype; + this->GetWidget(WID_GL_LIVERY_GROUP)->widget_data += this->vli.vtype; this->GetWidget(WID_GL_REPLACE_PROTECTION)->widget_data += this->vli.vtype; this->FinishInitNested(window_number); @@ -499,6 +503,7 @@ public: this->SetWidgetsDisabledState(IsDefaultGroupID(this->vli.index) || IsAllGroupID(this->vli.index) || _local_company != this->vli.company, WID_GL_DELETE_GROUP, WID_GL_RENAME_GROUP, + WID_GL_LIVERY_GROUP, WID_GL_REPLACE_PROTECTION, WIDGET_LIST_END); @@ -696,6 +701,10 @@ public: this->ShowRenameGroupWindow(this->vli.index, false); break; + case WID_GL_LIVERY_GROUP: // Set group livery + ShowCompanyLiveryWindow(this->owner, this->vli.index); + break; + case WID_GL_AVAILABLE_VEHICLES: ShowBuildVehicleWindow(INVALID_TILE, this->vli.vtype); break; diff --git a/src/lang/english.txt b/src/lang/english.txt index 83406296cc..be465cf229 100644 --- a/src/lang/english.txt +++ b/src/lang/english.txt @@ -190,6 +190,7 @@ STR_COLOUR_BROWN :Brown STR_COLOUR_GREY :Grey STR_COLOUR_WHITE :White STR_COLOUR_RANDOM :Random +STR_COLOUR_DEFAULT :Default # Units used in OpenTTD STR_UNITS_VELOCITY_IMPERIAL :{COMMA}{NBSP}mph @@ -3420,6 +3421,7 @@ STR_GROUPS_CLICK_ON_GROUP_FOR_TOOLTIP :{BLACK}Groups - STR_GROUP_CREATE_TOOLTIP :{BLACK}Click to create a group STR_GROUP_DELETE_TOOLTIP :{BLACK}Delete the selected group STR_GROUP_RENAME_TOOLTIP :{BLACK}Rename the selected group +STR_GROUP_LIVERY_TOOLTIP :{BLACK}Change livery of the selected group STR_GROUP_REPLACE_PROTECTION_TOOLTIP :{BLACK}Click to protect this group from global autoreplace STR_QUERY_GROUP_DELETE_CAPTION :{WHITE}Delete Group diff --git a/src/livery.h b/src/livery.h index 5845ffabb4..d6ed5bfdc7 100644 --- a/src/livery.h +++ b/src/livery.h @@ -69,12 +69,16 @@ enum LiveryClass { LC_ROAD, LC_SHIP, LC_AIRCRAFT, + LC_GROUP_RAIL, + LC_GROUP_ROAD, + LC_GROUP_SHIP, + LC_GROUP_AIRCRAFT, LC_END }; /** Information about a particular livery. */ struct Livery { - bool in_use; ///< Set if this livery should be used instead of the default livery. + byte in_use; ///< Bit 0 set if this livery should override the default livery first colour, Bit 1 for the second colour. byte colour1; ///< First colour, for all vehicles. byte colour2; ///< Second colour, for vehicles with 2CC support. }; diff --git a/src/saveload/company_sl.cpp b/src/saveload/company_sl.cpp index ce388e2141..1782e7c28a 100644 --- a/src/saveload/company_sl.cpp +++ b/src/saveload/company_sl.cpp @@ -403,7 +403,7 @@ static const SaveLoad _company_ai_build_rec_desc[] = { }; static const SaveLoad _company_livery_desc[] = { - SLE_CONDVAR(Livery, in_use, SLE_BOOL, 34, SL_MAX_VERSION), + SLE_CONDVAR(Livery, in_use, SLE_UINT8, 34, SL_MAX_VERSION), SLE_CONDVAR(Livery, colour1, SLE_UINT8, 34, SL_MAX_VERSION), SLE_CONDVAR(Livery, colour2, SLE_UINT8, 34, SL_MAX_VERSION), SLE_END() @@ -443,9 +443,18 @@ static void SaveLoad_PLYR_common(Company *c, CompanyProperties *cprops) /* Write each livery entry. */ int num_liveries = IsSavegameVersionBefore(63) ? LS_END - 4 : (IsSavegameVersionBefore(85) ? LS_END - 2: LS_END); + bool update_in_use = IsSavegameVersionBefore(205); if (c != NULL) { for (i = 0; i < num_liveries; i++) { SlObject(&c->livery[i], _company_livery_desc); + if (update_in_use && i != LS_DEFAULT) { + if (c->livery[i].in_use == 0) { + c->livery[i].colour1 = c->livery[LS_DEFAULT].colour1; + c->livery[i].colour2 = c->livery[LS_DEFAULT].colour2; + } else { + c->livery[i].in_use = 3; + } + } } if (num_liveries < LS_END) { diff --git a/src/saveload/group_sl.cpp b/src/saveload/group_sl.cpp index 93734f80f6..27112a5431 100644 --- a/src/saveload/group_sl.cpp +++ b/src/saveload/group_sl.cpp @@ -11,6 +11,7 @@ #include "../stdafx.h" #include "../group.h" +#include "../company_base.h" #include "saveload.h" @@ -23,6 +24,9 @@ static const SaveLoad _group_desc[] = { SLE_VAR(Group, owner, SLE_UINT8), SLE_VAR(Group, vehicle_type, SLE_UINT8), SLE_VAR(Group, replace_protection, SLE_BOOL), + SLE_CONDVAR(Group, livery.in_use, SLE_UINT8, 205, SL_MAX_VERSION), + SLE_CONDVAR(Group, livery.colour1, SLE_UINT8, 205, SL_MAX_VERSION), + SLE_CONDVAR(Group, livery.colour2, SLE_UINT8, 205, SL_MAX_VERSION), SLE_CONDVAR(Group, parent, SLE_UINT16, 189, SL_MAX_VERSION), SLE_END() }; @@ -47,6 +51,12 @@ static void Load_GRPS() SlObject(g, _group_desc); if (IsSavegameVersionBefore(189)) g->parent = INVALID_GROUP; + + if (IsSavegameVersionBefore(205)) { + const Company *c = Company::Get(g->owner); + g->livery.colour1 = c->livery[LS_DEFAULT].colour1; + g->livery.colour2 = c->livery[LS_DEFAULT].colour2; + } } } diff --git a/src/saveload/saveload.cpp b/src/saveload/saveload.cpp index 348ba93ba0..e75e40d74e 100644 --- a/src/saveload/saveload.cpp +++ b/src/saveload/saveload.cpp @@ -274,8 +274,9 @@ * 202 #6867 Increase industry cargo slots to 16 in, 16 out * 203 #7072 Add path cache for ships * 204 #7065 Add extra rotation stages for ships. + * 205 #7108 Livery storage change and group liveries. */ -extern const uint16 SAVEGAME_VERSION = 204; ///< Current savegame version of OpenTTD. +extern const uint16 SAVEGAME_VERSION = 205; ///< Current savegame version of OpenTTD. SavegameType _savegame_type; ///< type of savegame we are loading FileToSaveLoad _file_to_saveload; ///< File to save or load in the openttd loop. diff --git a/src/script/api/script_window.hpp b/src/script/api/script_window.hpp index e11742bafb..8dfeafc4c5 100644 --- a/src/script/api/script_window.hpp +++ b/src/script/api/script_window.hpp @@ -1080,10 +1080,15 @@ public: WID_SCL_CLASS_ROAD = ::WID_SCL_CLASS_ROAD, ///< Class road. WID_SCL_CLASS_SHIP = ::WID_SCL_CLASS_SHIP, ///< Class ship. WID_SCL_CLASS_AIRCRAFT = ::WID_SCL_CLASS_AIRCRAFT, ///< Class aircraft. + WID_SCL_GROUPS_RAIL = ::WID_SCL_GROUPS_RAIL, ///< Rail groups. + WID_SCL_GROUPS_ROAD = ::WID_SCL_GROUPS_ROAD, ///< Road groups. + WID_SCL_GROUPS_SHIP = ::WID_SCL_GROUPS_SHIP, ///< Ship groups. + WID_SCL_GROUPS_AIRCRAFT = ::WID_SCL_GROUPS_AIRCRAFT, ///< Aircraft groups. WID_SCL_SPACER_DROPDOWN = ::WID_SCL_SPACER_DROPDOWN, ///< Spacer for dropdown. WID_SCL_PRI_COL_DROPDOWN = ::WID_SCL_PRI_COL_DROPDOWN, ///< Dropdown for primary colour. WID_SCL_SEC_COL_DROPDOWN = ::WID_SCL_SEC_COL_DROPDOWN, ///< Dropdown for secondary colour. WID_SCL_MATRIX = ::WID_SCL_MATRIX, ///< Matrix. + WID_SCL_MATRIX_SCROLLBAR = ::WID_SCL_MATRIX_SCROLLBAR, ///< Matrix scrollbar. }; /** @@ -1471,6 +1476,7 @@ public: WID_GL_CREATE_GROUP = ::WID_GL_CREATE_GROUP, ///< Create group button. WID_GL_DELETE_GROUP = ::WID_GL_DELETE_GROUP, ///< Delete group button. WID_GL_RENAME_GROUP = ::WID_GL_RENAME_GROUP, ///< Rename group button. + WID_GL_LIVERY_GROUP = ::WID_GL_LIVERY_GROUP, ///< Group livery button. WID_GL_REPLACE_PROTECTION = ::WID_GL_REPLACE_PROTECTION, ///< Replace protection button. WID_GL_INFO = ::WID_GL_INFO, ///< Group info. }; diff --git a/src/settings.cpp b/src/settings.cpp index ae797e09b9..220628b006 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -1095,7 +1095,7 @@ static bool InvalidateNewGRFChangeWindows(int32 p1) static bool InvalidateCompanyLiveryWindow(int32 p1) { - InvalidateWindowClassesData(WC_COMPANY_COLOUR); + InvalidateWindowClassesData(WC_COMPANY_COLOUR, -1); return RedrawScreen(p1); } diff --git a/src/table/sprites.h b/src/table/sprites.h index 81d5388f99..da3bc76271 100644 --- a/src/table/sprites.h +++ b/src/table/sprites.h @@ -56,7 +56,7 @@ static const SpriteID SPR_LARGE_SMALL_WINDOW = 682; /** Extra graphic spritenumbers */ static const SpriteID SPR_OPENTTD_BASE = 4896; -static const uint16 OPENTTD_SPRITE_COUNT = 175; +static const uint16 OPENTTD_SPRITE_COUNT = 179; /* Halftile-selection sprites */ static const SpriteID SPR_HALFTILE_SELECTION_FLAT = SPR_OPENTTD_BASE; @@ -149,6 +149,11 @@ static const SpriteID SPR_GROUP_REPLACE_OFF_ROADVEH = SPR_OPENTTD_BASE + 131; static const SpriteID SPR_GROUP_REPLACE_OFF_SHIP = SPR_OPENTTD_BASE + 132; static const SpriteID SPR_GROUP_REPLACE_OFF_AIRCRAFT = SPR_OPENTTD_BASE + 133; +static const SpriteID SPR_GROUP_LIVERY_TRAIN = SPR_OPENTTD_BASE + 175; +static const SpriteID SPR_GROUP_LIVERY_ROADVEH = SPR_OPENTTD_BASE + 176; +static const SpriteID SPR_GROUP_LIVERY_SHIP = SPR_OPENTTD_BASE + 177; +static const SpriteID SPR_GROUP_LIVERY_AIRCRAFT = SPR_OPENTTD_BASE + 178; + static const SpriteID SPR_TOWN_RATING_NA = SPR_OPENTTD_BASE + 162; static const SpriteID SPR_TOWN_RATING_APALLING = SPR_OPENTTD_BASE + 163; static const SpriteID SPR_TOWN_RATING_MEDIOCRE = SPR_OPENTTD_BASE + 164; diff --git a/src/vehicle.cpp b/src/vehicle.cpp index 46141ce265..a5fe2ffa7a 100644 --- a/src/vehicle.cpp +++ b/src/vehicle.cpp @@ -1886,14 +1886,24 @@ const Livery *GetEngineLivery(EngineID engine_type, CompanyID company, EngineID const Company *c = Company::Get(company); LiveryScheme scheme = LS_DEFAULT; - /* The default livery is always available for use, but its in_use flag determines - * whether any _other_ liveries are in use. */ - if (c->livery[LS_DEFAULT].in_use && (livery_setting == LIT_ALL || (livery_setting == LIT_COMPANY && company == _local_company))) { - /* Determine the livery scheme to use */ - scheme = GetEngineLiveryScheme(engine_type, parent_engine_type, v); - - /* Switch back to the default scheme if the resolved scheme is not in use */ - if (!c->livery[scheme].in_use) scheme = LS_DEFAULT; + if (livery_setting == LIT_ALL || (livery_setting == LIT_COMPANY && company == _local_company)) { + if (v != NULL) { + const Group *g = Group::GetIfValid(v->First()->group_id); + if (g != NULL) { + /* Traverse parents until we find a livery or reach the top */ + while (g->livery.in_use == 0 && g->parent != INVALID_GROUP) { + g = Group::Get(g->parent); + } + if (g->livery.in_use != 0) return &g->livery; + } + } + + /* The default livery is always available for use, but its in_use flag determines + * 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); + } } return &c->livery[scheme]; diff --git a/src/widgets/company_widget.h b/src/widgets/company_widget.h index 4c1a5df4b4..ceb81ae195 100644 --- a/src/widgets/company_widget.h +++ b/src/widgets/company_widget.h @@ -86,10 +86,15 @@ enum SelectCompanyLiveryWidgets { WID_SCL_CLASS_ROAD, ///< Class road. WID_SCL_CLASS_SHIP, ///< Class ship. WID_SCL_CLASS_AIRCRAFT, ///< Class aircraft. + WID_SCL_GROUPS_RAIL, ///< Rail groups. + WID_SCL_GROUPS_ROAD, ///< Road groups. + WID_SCL_GROUPS_SHIP, ///< Ship groups. + WID_SCL_GROUPS_AIRCRAFT, ///< Aircraft groups. WID_SCL_SPACER_DROPDOWN, ///< Spacer for dropdown. WID_SCL_PRI_COL_DROPDOWN, ///< Dropdown for primary colour. WID_SCL_SEC_COL_DROPDOWN, ///< Dropdown for secondary colour. WID_SCL_MATRIX, ///< Matrix. + WID_SCL_MATRIX_SCROLLBAR, ///< Matrix scrollbar. }; diff --git a/src/widgets/group_widget.h b/src/widgets/group_widget.h index 41e0bcd45a..fe5ae81174 100644 --- a/src/widgets/group_widget.h +++ b/src/widgets/group_widget.h @@ -31,6 +31,7 @@ enum GroupListWidgets { WID_GL_CREATE_GROUP, ///< Create group button. WID_GL_DELETE_GROUP, ///< Delete group button. WID_GL_RENAME_GROUP, ///< Rename group button. + WID_GL_LIVERY_GROUP, ///< Group livery button. WID_GL_REPLACE_PROTECTION, ///< Replace protection button. WID_GL_INFO, ///< Group info. };