1
0
Fork 0

Change: New company face definition system and UI. (#14319)

Bits used by company faces are now defined by a variable system instead of being hardcoded, allowing future expansion.

The four face types covering gender and skin colour are now separate face styles with their own definitions.
pull/12131/head
Peter Nelson 2025-06-24 07:59:49 +01:00 committed by GitHub
parent ebc74c8905
commit a46a3a97f3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 785 additions and 763 deletions

View File

@ -28,6 +28,7 @@
#include "sound_func.h"
#include "rail.h"
#include "core/pool_func.hpp"
#include "core/string_consumer.hpp"
#include "settings_func.h"
#include "vehicle_base.h"
#include "vehicle_func.h"
@ -44,6 +45,7 @@
#include "widgets/statusbar_widget.h"
#include "table/strings.h"
#include "table/company_face.h"
#include "safeguards.h"
@ -53,7 +55,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<std::array<Colours, MAX_COMPANIES>, CompanyID> _company_colours; ///< NOSAVE: can be determined from company structs.
CompanyManagerFace _company_manager_face; ///< for company manager face storage in openttd.cfg
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
CompanyPool _company_pool("Company"); ///< Pool of companies.
@ -181,24 +183,12 @@ void DrawCompanyIcon(CompanyID c, int x, int y)
*/
static bool IsValidCompanyManagerFace(CompanyManagerFace cmf)
{
if (!AreCompanyManagerFaceBitsValid(cmf, CMFV_GEN_ETHN, GE_WM)) return false;
if (cmf.style >= GetNumCompanyManagerFaceStyles()) return false;
GenderEthnicity ge = (GenderEthnicity)GetCompanyManagerFaceBits(cmf, CMFV_GEN_ETHN, GE_WM);
bool has_moustache = !HasBit(ge, GENDER_FEMALE) && GetCompanyManagerFaceBits(cmf, CMFV_HAS_MOUSTACHE, ge) != 0;
bool has_tie_earring = !HasBit(ge, GENDER_FEMALE) || GetCompanyManagerFaceBits(cmf, CMFV_HAS_TIE_EARRING, ge) != 0;
bool has_glasses = GetCompanyManagerFaceBits(cmf, CMFV_HAS_GLASSES, ge) != 0;
if (!AreCompanyManagerFaceBitsValid(cmf, CMFV_EYE_COLOUR, ge)) return false;
for (CompanyManagerFaceVariable cmfv = CMFV_CHEEKS; cmfv < CMFV_END; cmfv++) {
switch (cmfv) {
case CMFV_MOUSTACHE: if (!has_moustache) continue; break;
case CMFV_LIPS:
case CMFV_NOSE: if (has_moustache) continue; break;
case CMFV_TIE_EARRING: if (!has_tie_earring) continue; break;
case CMFV_GLASSES: if (!has_glasses) continue; break;
default: break;
}
if (!AreCompanyManagerFaceBitsValid(cmf, cmfv, ge)) return false;
/* Test if each enabled part is valid. */
FaceVars vars = GetCompanyManagerFaceVars(cmf.style);
for (uint var : SetBitIterator(GetActiveFaceVars(cmf, vars))) {
if (!vars[var].IsValid(cmf)) return false;
}
return true;
@ -633,11 +623,15 @@ Company *DoStartupNewCompany(bool is_ai, CompanyID company = CompanyID::Invalid(
/* If starting a player company in singleplayer and a favorite company manager face is selected, choose it. Otherwise, use a random face.
* In a network game, we'll choose the favorite face later in CmdCompanyCtrl to sync it to all clients. */
if (_company_manager_face != 0 && !is_ai && !_networking) {
c->face = _company_manager_face;
} else {
RandomCompanyManagerFaceBits(c->face, (GenderEthnicity)Random(), false, _random);
bool randomise_face = true;
if (!_company_manager_face.empty() && !is_ai && !_networking) {
auto cmf = ParseCompanyManagerFaceCode(_company_manager_face);
if (cmf.has_value()) {
randomise_face = false;
c->face = std::move(*cmf);
}
}
if (randomise_face) RandomiseCompanyManagerFace(c->face, _random);
SetDefaultCompanySettings(c->index);
ClearEnginesHiddenFlagOfCompany(c->index);
@ -926,7 +920,12 @@ CommandCost CmdCompanyCtrl(DoCommandFlags flags, CompanyCtrlAction cca, CompanyI
* its configuration and we are currently in the execution of a command, we have
* to circumvent the normal ::Post logic for commands and just send the command.
*/
if (_company_manager_face != 0) Command<CMD_SET_COMPANY_MANAGER_FACE>::SendNet(STR_NULL, c->index, _company_manager_face);
if (!_company_manager_face.empty()) {
auto cmf = ParseCompanyManagerFaceCode(_company_manager_face);
if (cmf.has_value()) {
Command<CMD_SET_COMPANY_MANAGER_FACE>::SendNet(STR_NULL, c->index, cmf->bits, cmf->style);
}
}
/* Now that we have a new company, broadcast our company settings to
* all clients so everything is in sync */
@ -1049,15 +1048,20 @@ CommandCost CmdCompanyAllowListCtrl(DoCommandFlags flags, CompanyAllowListCtrlAc
/**
* Change the company manager's face.
* @param flags operation to perform
* @param cmf face bitmasked
* @param bits The bits of company manager face.
* @param style The style of the company manager face.
* @return the cost of this operation or an error
*/
CommandCost CmdSetCompanyManagerFace(DoCommandFlags flags, CompanyManagerFace cmf)
CommandCost CmdSetCompanyManagerFace(DoCommandFlags flags, uint32_t bits, uint style)
{
if (!IsValidCompanyManagerFace(cmf)) return CMD_ERROR;
CompanyManagerFace tmp_face{style, bits, {}};
if (!IsValidCompanyManagerFace(tmp_face)) return CMD_ERROR;
if (flags.Test(DoCommandFlag::Execute)) {
Company::Get(_current_company)->face = cmf;
CompanyManagerFace &cmf = Company::Get(_current_company)->face;
SetCompanyManagerFaceStyle(cmf, style);
cmf.bits = tmp_face.bits;
MarkWholeScreenDirty();
}
return CommandCost();
@ -1375,3 +1379,157 @@ CompanyID GetFirstPlayableCompanyID()
return CompanyID::Begin();
}
static std::vector<FaceSpec> _faces; ///< All company manager face styles.
/**
* Reset company manager face styles to default.
*/
void ResetFaces()
{
_faces.clear();
_faces.assign(std::begin(_original_faces), std::end(_original_faces));
}
/**
* Get the number of company manager face styles.
* @return Number of face styles.
*/
uint GetNumCompanyManagerFaceStyles()
{
return static_cast<uint>(std::size(_faces));
}
/**
* Get the definition of a company manager face style.
* @param style_index Face style to get.
* @return Definition of face style.
*/
const FaceSpec *GetCompanyManagerFaceSpec(uint style_index)
{
if (style_index < GetNumCompanyManagerFaceStyles()) return &_faces[style_index];
return nullptr;
}
/**
* Find a company manager face style by label.
* @param label Label to find.
* @return Index of face style if label is found, otherwise std::nullopt.
*/
std::optional<uint> FindCompanyManagerFaceLabel(std::string_view label)
{
auto it = std::ranges::find(_faces, label, &FaceSpec::label);
if (it == std::end(_faces)) return std::nullopt;
return static_cast<uint>(std::distance(std::begin(_faces), it));
}
/**
* Get the face variables for a face style.
* @param style_index Face style to get variables for.
* @return Variables for the face style.
*/
FaceVars GetCompanyManagerFaceVars(uint style)
{
const FaceSpec *spec = GetCompanyManagerFaceSpec(style);
if (spec == nullptr) return {};
return spec->GetFaceVars();
}
/**
* Set a company face style.
* Changes both the style index and the label.
* @param cmf The CompanyManagerFace to change.
* @param style The style to set.
*/
void SetCompanyManagerFaceStyle(CompanyManagerFace &cmf, uint style)
{
const FaceSpec *spec = GetCompanyManagerFaceSpec(style);
assert(spec != nullptr);
cmf.style = style;
cmf.style_label = spec->label;
}
/**
* Completely randomise a company manager face, including style.
* @note randomizer should passed be appropriate for server-side or client-side usage.
* @param cmf The CompanyManagerFace to randomise.
* @param randomizer The randomizer to use.
*/
void RandomiseCompanyManagerFace(CompanyManagerFace &cmf, Randomizer &randomizer)
{
SetCompanyManagerFaceStyle(cmf, randomizer.Next(GetNumCompanyManagerFaceStyles()));
RandomiseCompanyManagerFaceBits(cmf, GetCompanyManagerFaceVars(cmf.style), randomizer);
}
/**
* Mask company manager face bits to ensure they are all within range.
* @note Does not update the CompanyManagerFace itself. Unused bits are cleared.
* @param cmf The CompanyManagerFace.
* @param style The face variables.
* @return The masked face bits.
*/
uint32_t MaskCompanyManagerFaceBits(const CompanyManagerFace &cmf, FaceVars vars)
{
CompanyManagerFace face{};
for (auto var : SetBitIterator(GetActiveFaceVars(cmf, vars))) {
vars[var].SetBits(face, vars[var].GetBits(cmf));
}
return face.bits;
}
/**
* Get a face code representation of a company manager face.
* @param cmf The company manager face.
* @return String containing face code.
*/
std::string FormatCompanyManagerFaceCode(const CompanyManagerFace &cmf)
{
uint32_t masked_face_bits = MaskCompanyManagerFaceBits(cmf, GetCompanyManagerFaceVars(cmf.style));
return fmt::format("{}:{}", cmf.style_label, masked_face_bits);
}
/**
* Parse a face code into a company manager face.
* @param str Face code to parse.
* @return Company manager face, or std::nullopt if it could not be parsed.
*/
std::optional<CompanyManagerFace> ParseCompanyManagerFaceCode(std::string_view str)
{
if (str.empty()) return std::nullopt;
CompanyManagerFace cmf;
StringConsumer consumer{str};
if (consumer.FindChar(':') != StringConsumer::npos) {
auto label = consumer.ReadUntilChar(':', StringConsumer::SKIP_ONE_SEPARATOR);
/* Read numeric part and ensure it's valid. */
auto bits = consumer.TryReadIntegerBase<uint32_t>(10, true);
if (!bits.has_value() || consumer.AnyBytesLeft()) return std::nullopt;
/* Ensure style laberl is valid. */
auto style = FindCompanyManagerFaceLabel(label);
if (!style.has_value()) return std::nullopt;
SetCompanyManagerFaceStyle(cmf, *style);
cmf.bits = *bits;
} else {
/* No ':' included, treat as numeric-only. This allows old-style codes to be entered. */
auto bits = ParseInteger(str, 10, true);
if (!bits.has_value()) return std::nullopt;
/* Old codes use bits 0..1 to represent face style. These map directly to the default face styles. */
SetCompanyManagerFaceStyle(cmf, GB(*bits, 0, 2));
cmf.bits = *bits;
}
/* Force the face bits to be valid. */
FaceVars vars = GetCompanyManagerFaceVars(cmf.style);
ScaleAllCompanyManagerFaceBits(cmf, vars);
cmf.bits = MaskCompanyManagerFaceBits(cmf, vars);
return cmf;
}

View File

@ -22,7 +22,7 @@ CommandCost CmdCompanyAllowListCtrl(DoCommandFlags flags, CompanyAllowListCtrlAc
CommandCost CmdGiveMoney(DoCommandFlags flags, Money money, CompanyID dest_company);
CommandCost CmdRenameCompany(DoCommandFlags flags, const std::string &text);
CommandCost CmdRenamePresident(DoCommandFlags flags, const std::string &text);
CommandCost CmdSetCompanyManagerFace(DoCommandFlags flags, CompanyManagerFace cmf);
CommandCost CmdSetCompanyManagerFace(DoCommandFlags flags, uint style, uint32_t bits);
CommandCost CmdSetCompanyColour(DoCommandFlags flags, LiveryScheme scheme, bool primary, Colours colour);
DEF_CMD_TRAIT(CMD_COMPANY_CTRL, CmdCompanyCtrl, CommandFlags({CommandFlag::Spectator, CommandFlag::ClientID, CommandFlag::NoEst}), CMDT_SERVER_SETTING)

View File

@ -37,7 +37,7 @@ extern CompanyID _local_company;
extern CompanyID _current_company;
extern TypedIndexContainer<std::array<Colours, MAX_COMPANIES>, CompanyID> _company_colours;
extern CompanyManagerFace _company_manager_face;
extern std::string _company_manager_face;
PaletteID GetCompanyPalette(CompanyID company);
/**

View File

@ -11,6 +11,7 @@
#include "currency.h"
#include "error.h"
#include "gui.h"
#include "settings_gui.h"
#include "window_gui.h"
#include "textbuf_gui.h"
#include "viewport_func.h"
@ -1116,45 +1117,46 @@ void ShowCompanyLiveryWindow(CompanyID company, GroupID group)
* @param colour the (background) colour of the gradient
* @param r position to draw the face
*/
void DrawCompanyManagerFace(CompanyManagerFace cmf, Colours colour, const Rect &r)
void DrawCompanyManagerFace(const CompanyManagerFace &cmf, Colours colour, const Rect &r)
{
GenderEthnicity ge = (GenderEthnicity)GetCompanyManagerFaceBits(cmf, CMFV_GEN_ETHN, GE_WM);
/* Determine offset from centre of drawing rect. */
Dimension d = GetSpriteSize(SPR_GRADIENT);
int x = CentreBounds(r.left, r.right, d.width);
int y = CentreBounds(r.top, r.bottom, d.height);
bool has_moustache = !HasBit(ge, GENDER_FEMALE) && GetCompanyManagerFaceBits(cmf, CMFV_HAS_MOUSTACHE, ge) != 0;
bool has_tie_earring = !HasBit(ge, GENDER_FEMALE) || GetCompanyManagerFaceBits(cmf, CMFV_HAS_TIE_EARRING, ge) != 0;
bool has_glasses = GetCompanyManagerFaceBits(cmf, CMFV_HAS_GLASSES, ge) != 0;
PaletteID pal;
FaceVars vars = GetCompanyManagerFaceVars(cmf.style);
/* Modify eye colour palette only if 2 or more valid values exist */
if (_cmf_info[CMFV_EYE_COLOUR].valid_values[ge] < 2) {
pal = PAL_NONE;
} else {
switch (GetCompanyManagerFaceBits(cmf, CMFV_EYE_COLOUR, ge)) {
/* First determine which parts are enabled. */
uint64_t active_vars = GetActiveFaceVars(cmf, vars);
std::unordered_map<uint8_t, PaletteID> palettes;
/* Second, get palettes. */
for (auto var : SetBitIterator(active_vars)) {
if (vars[var].type != FaceVarType::Palette) continue;
PaletteID pal = PAL_NONE;
switch (vars[var].GetBits(cmf)) {
default: NOT_REACHED();
case 0: pal = PALETTE_TO_BROWN; break;
case 1: pal = PALETTE_TO_BLUE; break;
case 2: pal = PALETTE_TO_GREEN; break;
}
for (uint8_t affected_var : SetBitIterator(std::get<uint64_t>(vars[var].data))) {
palettes[affected_var] = pal;
}
}
/* Draw the gradient (background) */
DrawSprite(SPR_GRADIENT, GetColourPalette(colour), x, y);
for (CompanyManagerFaceVariable cmfv = CMFV_CHEEKS; cmfv < CMFV_END; cmfv++) {
switch (cmfv) {
case CMFV_MOUSTACHE: if (!has_moustache) continue; break;
case CMFV_LIPS:
case CMFV_NOSE: if (has_moustache) continue; break;
case CMFV_TIE_EARRING: if (!has_tie_earring) continue; break;
case CMFV_GLASSES: if (!has_glasses) continue; break;
default: break;
}
DrawSprite(GetCompanyManagerFaceSprite(cmf, cmfv, ge), (cmfv == CMFV_EYEBROWS) ? pal : PAL_NONE, x, y);
/* Thirdly, draw sprites. */
for (auto var : SetBitIterator(active_vars)) {
if (vars[var].type != FaceVarType::Sprite) continue;
auto it = palettes.find(var);
PaletteID pal = (it == std::end(palettes)) ? PAL_NONE : it->second;
DrawSprite(vars[var].GetSprite(cmf), pal, x, y);
}
}
@ -1165,153 +1167,40 @@ static constexpr NWidgetPart _nested_select_company_manager_face_widgets[] = {
NWidget(WWT_CAPTION, COLOUR_GREY, WID_SCMF_CAPTION), SetStringTip(STR_FACE_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
NWidget(WWT_IMGBTN, COLOUR_GREY, WID_SCMF_TOGGLE_LARGE_SMALL), SetSpriteTip(SPR_LARGE_SMALL_WINDOW, STR_FACE_ADVANCED_TOOLTIP), SetAspect(WidgetDimensions::ASPECT_TOGGLE_SIZE),
EndContainer(),
NWidget(WWT_PANEL, COLOUR_GREY, WID_SCMF_SELECT_FACE),
NWidget(NWID_HORIZONTAL), SetPIP(0, WidgetDimensions::unscaled.hsep_normal, 0), SetPadding(2),
NWidget(NWID_HORIZONTAL),
NWidget(WWT_PANEL, COLOUR_GREY, WID_SCMF_SELECT_FACE),
/* Left side */
NWidget(NWID_VERTICAL), SetPIP(0, WidgetDimensions::unscaled.vsep_normal, 0),
NWidget(NWID_HORIZONTAL), SetPIPRatio(1, 0, 1),
NWidget(WWT_EMPTY, INVALID_COLOUR, WID_SCMF_FACE), SetMinimalSize(92, 119), SetFill(1, 0),
EndContainer(),
NWidget(NWID_VERTICAL), SetPIP(0, WidgetDimensions::unscaled.vsep_normal, 0), SetPadding(4),
NWidget(WWT_EMPTY, INVALID_COLOUR, WID_SCMF_FACE), SetMinimalSize(92, 119), SetFill(1, 0),
NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SCMF_RANDOM_NEW_FACE), SetFill(1, 0), SetStringTip(STR_FACE_NEW_FACE_BUTTON, STR_FACE_NEW_FACE_TOOLTIP),
NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SCMF_TOGGLE_LARGE_SMALL_BUTTON), SetFill(1, 0), SetStringTip(STR_FACE_ADVANCED, STR_FACE_ADVANCED_TOOLTIP),
NWidget(NWID_SELECTION, INVALID_COLOUR, WID_SCMF_SEL_LOADSAVE), // Load/number/save buttons under the portrait in the advanced view.
NWidget(NWID_VERTICAL), SetPIP(0, 0, 0), SetPIPRatio(1, 0, 1),
NWidget(NWID_VERTICAL),
NWidget(NWID_SPACER), SetFill(1, 1), SetResize(0, 1),
NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SCMF_LOAD), SetFill(1, 0), SetStringTip(STR_FACE_LOAD, STR_FACE_LOAD_TOOLTIP),
NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SCMF_FACECODE), SetFill(1, 0), SetStringTip(STR_FACE_FACECODE, STR_FACE_FACECODE_TOOLTIP),
NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SCMF_SAVE), SetFill(1, 0), SetStringTip(STR_FACE_SAVE, STR_FACE_SAVE_TOOLTIP),
EndContainer(),
EndContainer(),
EndContainer(),
/* Right side */
NWidget(NWID_VERTICAL), SetPIP(0, WidgetDimensions::unscaled.vsep_normal, 0),
NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SCMF_TOGGLE_LARGE_SMALL_BUTTON), SetFill(1, 0), SetStringTip(STR_FACE_ADVANCED, STR_FACE_ADVANCED_TOOLTIP),
NWidget(NWID_SELECTION, INVALID_COLOUR, WID_SCMF_SEL_MALEFEMALE), // Simple male/female face setting.
NWidget(NWID_VERTICAL), SetPIPRatio(1, 0, 1),
NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_SCMF_MALE), SetFill(1, 0), SetStringTip(STR_FACE_MALE_BUTTON, STR_FACE_MALE_TOOLTIP),
NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_SCMF_FEMALE), SetFill(1, 0), SetStringTip(STR_FACE_FEMALE_BUTTON, STR_FACE_FEMALE_TOOLTIP),
EndContainer(),
EndContainer(),
NWidget(NWID_SELECTION, INVALID_COLOUR, WID_SCMF_SEL_PARTS), // Advanced face parts setting.
NWidget(NWID_VERTICAL), SetPIP(0, WidgetDimensions::unscaled.vsep_normal, 0),
NWidget(NWID_HORIZONTAL, NWidContainerFlag::EqualSize),
NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_SCMF_MALE2), SetFill(1, 0), SetStringTip(STR_FACE_MALE_BUTTON, STR_FACE_MALE_TOOLTIP),
NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_SCMF_FEMALE2), SetFill(1, 0), SetStringTip(STR_FACE_FEMALE_BUTTON, STR_FACE_FEMALE_TOOLTIP),
EndContainer(),
NWidget(NWID_HORIZONTAL, NWidContainerFlag::EqualSize),
NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_SCMF_ETHNICITY_EUR), SetFill(1, 0), SetStringTip(STR_FACE_EUROPEAN, STR_FACE_EUROPEAN_TOOLTIP),
NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_SCMF_ETHNICITY_AFR), SetFill(1, 0), SetStringTip(STR_FACE_AFRICAN, STR_FACE_AFRICAN_TOOLTIP),
EndContainer(),
NWidget(NWID_VERTICAL),
NWidget(NWID_HORIZONTAL), SetPIP(0, WidgetDimensions::unscaled.hsep_normal, 0),
NWidget(WWT_TEXT, INVALID_COLOUR, WID_SCMF_HAS_MOUSTACHE_EARRING_TEXT), SetFill(1, 0),
SetStringTip(STR_FACE_EYECOLOUR), SetTextStyle(TC_GOLD), SetAlignment(SA_VERT_CENTER | SA_RIGHT),
NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SCMF_HAS_MOUSTACHE_EARRING), SetToolTip(STR_FACE_MOUSTACHE_EARRING_TOOLTIP), SetTextStyle(TC_WHITE),
EndContainer(),
NWidget(NWID_HORIZONTAL), SetPIP(0, WidgetDimensions::unscaled.hsep_normal, 0),
NWidget(WWT_TEXT, INVALID_COLOUR, WID_SCMF_HAS_GLASSES_TEXT), SetFill(1, 0),
SetStringTip(STR_FACE_GLASSES), SetTextStyle(TC_GOLD), SetAlignment(SA_VERT_CENTER | SA_RIGHT),
NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SCMF_HAS_GLASSES), SetToolTip(STR_FACE_GLASSES_TOOLTIP), SetTextStyle(TC_WHITE),
EndContainer(),
EndContainer(),
NWidget(NWID_VERTICAL),
NWidget(NWID_HORIZONTAL), SetPIP(0, WidgetDimensions::unscaled.hsep_normal, 0),
NWidget(WWT_TEXT, INVALID_COLOUR, WID_SCMF_HAIR_TEXT), SetFill(1, 0),
SetStringTip(STR_FACE_HAIR), SetTextStyle(TC_GOLD), SetAlignment(SA_VERT_CENTER | SA_RIGHT),
NWidget(NWID_HORIZONTAL),
NWidget(WWT_PUSHARROWBTN, COLOUR_GREY, WID_SCMF_HAIR_L), SetArrowWidgetTypeTip(AWV_DECREASE, STR_FACE_HAIR_TOOLTIP),
NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SCMF_HAIR), SetToolTip(STR_FACE_HAIR_TOOLTIP), SetTextStyle(TC_WHITE),
NWidget(WWT_PUSHARROWBTN, COLOUR_GREY, WID_SCMF_HAIR_R), SetArrowWidgetTypeTip(AWV_INCREASE, STR_FACE_HAIR_TOOLTIP),
EndContainer(),
EndContainer(),
NWidget(NWID_HORIZONTAL), SetPIP(0, WidgetDimensions::unscaled.hsep_normal, 0),
NWidget(WWT_TEXT, INVALID_COLOUR, WID_SCMF_EYEBROWS_TEXT), SetFill(1, 0),
SetStringTip(STR_FACE_EYEBROWS), SetTextStyle(TC_GOLD), SetAlignment(SA_VERT_CENTER | SA_RIGHT),
NWidget(NWID_HORIZONTAL),
NWidget(WWT_PUSHARROWBTN, COLOUR_GREY, WID_SCMF_EYEBROWS_L), SetArrowWidgetTypeTip(AWV_DECREASE, STR_FACE_EYEBROWS_TOOLTIP),
NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SCMF_EYEBROWS), SetToolTip(STR_FACE_EYEBROWS_TOOLTIP), SetTextStyle(TC_WHITE),
NWidget(WWT_PUSHARROWBTN, COLOUR_GREY, WID_SCMF_EYEBROWS_R), SetArrowWidgetTypeTip(AWV_INCREASE, STR_FACE_EYEBROWS_TOOLTIP),
EndContainer(),
EndContainer(),
NWidget(NWID_HORIZONTAL), SetPIP(0, WidgetDimensions::unscaled.hsep_normal, 0),
NWidget(WWT_TEXT, INVALID_COLOUR, WID_SCMF_EYECOLOUR_TEXT), SetFill(1, 0),
SetStringTip(STR_FACE_EYECOLOUR), SetTextStyle(TC_GOLD), SetAlignment(SA_VERT_CENTER | SA_RIGHT),
NWidget(NWID_HORIZONTAL),
NWidget(WWT_PUSHARROWBTN, COLOUR_GREY, WID_SCMF_EYECOLOUR_L), SetArrowWidgetTypeTip(AWV_DECREASE, STR_FACE_EYECOLOUR_TOOLTIP),
NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SCMF_EYECOLOUR), SetToolTip(STR_FACE_EYECOLOUR_TOOLTIP), SetTextStyle(TC_WHITE),
NWidget(WWT_PUSHARROWBTN, COLOUR_GREY, WID_SCMF_EYECOLOUR_R), SetArrowWidgetTypeTip(AWV_INCREASE, STR_FACE_EYECOLOUR_TOOLTIP),
EndContainer(),
EndContainer(),
NWidget(NWID_HORIZONTAL), SetPIP(0, WidgetDimensions::unscaled.hsep_normal, 0),
NWidget(WWT_TEXT, INVALID_COLOUR, WID_SCMF_GLASSES_TEXT), SetFill(1, 0),
SetStringTip(STR_FACE_GLASSES), SetTextStyle(TC_GOLD), SetAlignment(SA_VERT_CENTER | SA_RIGHT),
NWidget(NWID_HORIZONTAL),
NWidget(WWT_PUSHARROWBTN, COLOUR_GREY, WID_SCMF_GLASSES_L), SetArrowWidgetTypeTip(AWV_DECREASE, STR_FACE_GLASSES_TOOLTIP_2),
NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SCMF_GLASSES), SetToolTip(STR_FACE_GLASSES_TOOLTIP_2), SetTextStyle(TC_WHITE),
NWidget(WWT_PUSHARROWBTN, COLOUR_GREY, WID_SCMF_GLASSES_R), SetArrowWidgetTypeTip(AWV_INCREASE, STR_FACE_GLASSES_TOOLTIP_2),
EndContainer(),
EndContainer(),
NWidget(NWID_HORIZONTAL), SetPIP(0, WidgetDimensions::unscaled.hsep_normal, 0),
NWidget(WWT_TEXT, INVALID_COLOUR, WID_SCMF_NOSE_TEXT), SetFill(1, 0),
SetStringTip(STR_FACE_NOSE), SetTextStyle(TC_GOLD), SetAlignment(SA_VERT_CENTER | SA_RIGHT),
NWidget(NWID_HORIZONTAL),
NWidget(WWT_PUSHARROWBTN, COLOUR_GREY, WID_SCMF_NOSE_L), SetArrowWidgetTypeTip(AWV_DECREASE, STR_FACE_NOSE_TOOLTIP),
NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SCMF_NOSE), SetToolTip(STR_FACE_NOSE_TOOLTIP), SetTextStyle(TC_WHITE),
NWidget(WWT_PUSHARROWBTN, COLOUR_GREY, WID_SCMF_NOSE_R), SetArrowWidgetTypeTip(AWV_INCREASE, STR_FACE_NOSE_TOOLTIP),
EndContainer(),
EndContainer(),
NWidget(NWID_HORIZONTAL), SetPIP(0, WidgetDimensions::unscaled.hsep_normal, 0),
NWidget(WWT_TEXT, INVALID_COLOUR, WID_SCMF_LIPS_MOUSTACHE_TEXT), SetFill(1, 0),
SetStringTip(STR_FACE_MOUSTACHE), SetTextStyle(TC_GOLD), SetAlignment(SA_VERT_CENTER | SA_RIGHT),
NWidget(NWID_HORIZONTAL),
NWidget(WWT_PUSHARROWBTN, COLOUR_GREY, WID_SCMF_LIPS_MOUSTACHE_L), SetArrowWidgetTypeTip(AWV_DECREASE, STR_FACE_LIPS_MOUSTACHE_TOOLTIP),
NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SCMF_LIPS_MOUSTACHE), SetToolTip(STR_FACE_LIPS_MOUSTACHE_TOOLTIP), SetTextStyle(TC_WHITE),
NWidget(WWT_PUSHARROWBTN, COLOUR_GREY, WID_SCMF_LIPS_MOUSTACHE_R), SetArrowWidgetTypeTip(AWV_INCREASE, STR_FACE_LIPS_MOUSTACHE_TOOLTIP),
EndContainer(),
EndContainer(),
NWidget(NWID_HORIZONTAL), SetPIP(0, WidgetDimensions::unscaled.hsep_normal, 0),
NWidget(WWT_TEXT, INVALID_COLOUR, WID_SCMF_CHIN_TEXT), SetFill(1, 0),
SetStringTip(STR_FACE_CHIN), SetTextStyle(TC_GOLD), SetAlignment(SA_VERT_CENTER | SA_RIGHT),
NWidget(NWID_HORIZONTAL),
NWidget(WWT_PUSHARROWBTN, COLOUR_GREY, WID_SCMF_CHIN_L), SetArrowWidgetTypeTip(AWV_DECREASE, STR_FACE_CHIN_TOOLTIP),
NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SCMF_CHIN), SetToolTip(STR_FACE_CHIN_TOOLTIP), SetTextStyle(TC_WHITE),
NWidget(WWT_PUSHARROWBTN, COLOUR_GREY, WID_SCMF_CHIN_R), SetArrowWidgetTypeTip(AWV_INCREASE, STR_FACE_CHIN_TOOLTIP),
EndContainer(),
EndContainer(),
NWidget(NWID_HORIZONTAL), SetPIP(0, WidgetDimensions::unscaled.hsep_normal, 0),
NWidget(WWT_TEXT, INVALID_COLOUR, WID_SCMF_JACKET_TEXT), SetFill(1, 0),
SetStringTip(STR_FACE_JACKET), SetTextStyle(TC_GOLD), SetAlignment(SA_VERT_CENTER | SA_RIGHT),
NWidget(NWID_HORIZONTAL),
NWidget(WWT_PUSHARROWBTN, COLOUR_GREY, WID_SCMF_JACKET_L), SetArrowWidgetTypeTip(AWV_DECREASE, STR_FACE_JACKET_TOOLTIP),
NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SCMF_JACKET), SetToolTip(STR_FACE_JACKET_TOOLTIP), SetTextStyle(TC_WHITE),
NWidget(WWT_PUSHARROWBTN, COLOUR_GREY, WID_SCMF_JACKET_R), SetArrowWidgetTypeTip(AWV_INCREASE, STR_FACE_JACKET_TOOLTIP),
EndContainer(),
EndContainer(),
NWidget(NWID_HORIZONTAL), SetPIP(0, WidgetDimensions::unscaled.hsep_normal, 0),
NWidget(WWT_TEXT, INVALID_COLOUR, WID_SCMF_COLLAR_TEXT), SetFill(1, 0),
SetStringTip(STR_FACE_COLLAR), SetTextStyle(TC_GOLD), SetAlignment(SA_VERT_CENTER | SA_RIGHT),
NWidget(NWID_HORIZONTAL),
NWidget(WWT_PUSHARROWBTN, COLOUR_GREY, WID_SCMF_COLLAR_L), SetArrowWidgetTypeTip(AWV_DECREASE, STR_FACE_COLLAR_TOOLTIP),
NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SCMF_COLLAR), SetToolTip(STR_FACE_COLLAR_TOOLTIP), SetTextStyle(TC_WHITE),
NWidget(WWT_PUSHARROWBTN, COLOUR_GREY, WID_SCMF_COLLAR_R), SetArrowWidgetTypeTip(AWV_INCREASE, STR_FACE_COLLAR_TOOLTIP),
EndContainer(),
EndContainer(),
NWidget(NWID_HORIZONTAL), SetPIP(0, WidgetDimensions::unscaled.hsep_normal, 0),
NWidget(WWT_TEXT, INVALID_COLOUR, WID_SCMF_TIE_EARRING_TEXT), SetFill(1, 0),
SetStringTip(STR_FACE_EARRING), SetTextStyle(TC_GOLD), SetAlignment(SA_VERT_CENTER | SA_RIGHT),
NWidget(NWID_HORIZONTAL),
NWidget(WWT_PUSHARROWBTN, COLOUR_GREY, WID_SCMF_TIE_EARRING_L), SetArrowWidgetTypeTip(AWV_DECREASE, STR_FACE_TIE_EARRING_TOOLTIP),
NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SCMF_TIE_EARRING), SetToolTip(STR_FACE_TIE_EARRING_TOOLTIP), SetTextStyle(TC_WHITE),
NWidget(WWT_PUSHARROWBTN, COLOUR_GREY, WID_SCMF_TIE_EARRING_R), SetArrowWidgetTypeTip(AWV_INCREASE, STR_FACE_TIE_EARRING_TOOLTIP),
EndContainer(),
EndContainer(),
EndContainer(),
EndContainer(),
EndContainer(),
/* Right side */
NWidget(NWID_SELECTION, INVALID_COLOUR, WID_SCMF_SEL_PARTS), // Advanced face parts setting.
NWidget(NWID_VERTICAL),
NWidget(WWT_MATRIX, COLOUR_GREY, WID_SCMF_STYLE), SetResize(1, 0), SetFill(1, 0), SetMatrixDataTip(1, 1),
NWidget(NWID_HORIZONTAL),
NWidget(WWT_MATRIX, COLOUR_GREY, WID_SCMF_PARTS), SetResize(1, 1), SetFill(1, 1), SetMatrixDataTip(1, 0), SetScrollbar(WID_SCMF_PARTS_SCROLLBAR),
NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_SCMF_PARTS_SCROLLBAR),
EndContainer(),
EndContainer(),
EndContainer(),
EndContainer(),
NWidget(NWID_HORIZONTAL, NWidContainerFlag::EqualSize),
NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SCMF_CANCEL), SetFill(1, 0), SetStringTip(STR_BUTTON_CANCEL, STR_FACE_CANCEL_TOOLTIP),
NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SCMF_ACCEPT), SetFill(1, 0), SetStringTip(STR_BUTTON_OK, STR_FACE_OK_TOOLTIP),
NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SCMF_CANCEL), SetFill(1, 0), SetResize(1, 0), SetStringTip(STR_BUTTON_CANCEL, STR_FACE_CANCEL_TOOLTIP),
NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SCMF_ACCEPT), SetFill(1, 0), SetResize(1, 0), SetStringTip(STR_BUTTON_OK, STR_FACE_OK_TOOLTIP),
NWidget(NWID_SELECTION, INVALID_COLOUR, WID_SCMF_SEL_RESIZE),
NWidget(WWT_RESIZEBOX, COLOUR_GREY),
EndContainer(),
EndContainer(),
};
@ -1320,43 +1209,34 @@ class SelectCompanyManagerFaceWindow : public Window
{
CompanyManagerFace face{}; ///< company manager face bits
bool advanced = false; ///< advanced company manager face selection window
uint selected_var = UINT_MAX; ///< Currently selected face variable. `UINT_MAX` for none, `UINT_MAX - 1` means style is clicked instead.
uint click_state = 0; ///< Click state on selected face variable.
int line_height = 0; ///< Height of each face variable row.
GenderEthnicity ge{}; ///< Gender and ethnicity.
bool is_female = false; ///< Female face.
bool is_moust_male = false; ///< Male face with a moustache.
Dimension yesno_dim{}; ///< Dimension of a yes/no button of a part in the advanced face window.
Dimension number_dim{}; ///< Dimension of a number widget of a part in the advanced face window.
std::vector<const FaceVar *> face_vars; ///< Visible face variables.
/**
* Get the string for the value of face control buttons.
*
* @param widget_index index of this widget in the window
* @param stringid formatting string for the button.
* @param val the value which will be displayed
* @param is_bool_widget is it a bool button
* Make face bits valid and update visible face variables.
*/
std::string GetFaceString(WidgetID widget_index, uint8_t val, bool is_bool_widget) const
{
const NWidgetCore *nwi_widget = this->GetWidget<NWidgetCore>(widget_index);
if (nwi_widget->IsDisabled()) return {};
/* If it a bool button write yes or no. */
if (is_bool_widget) return GetString((val != 0) ? STR_FACE_YES : STR_FACE_NO);
/* Else write the value + 1. */
return GetString(STR_JUST_INT, val + 1);
}
void UpdateData()
{
this->ge = (GenderEthnicity)GB(this->face, _cmf_info[CMFV_GEN_ETHN].offset, _cmf_info[CMFV_GEN_ETHN].length); // get the gender and ethnicity
this->is_female = HasBit(this->ge, GENDER_FEMALE); // get the gender: 0 == male and 1 == female
this->is_moust_male = !is_female && GetCompanyManagerFaceBits(this->face, CMFV_HAS_MOUSTACHE, this->ge) != 0; // is a male face with moustache
FaceVars vars = GetCompanyManagerFaceVars(this->face.style);
ScaleAllCompanyManagerFaceBits(this->face, vars);
this->GetWidget<NWidgetCore>(WID_SCMF_HAS_MOUSTACHE_EARRING_TEXT)->SetString(this->is_female ? STR_FACE_EARRING : STR_FACE_MOUSTACHE);
this->GetWidget<NWidgetCore>(WID_SCMF_TIE_EARRING_TEXT)->SetString(this->is_female ? STR_FACE_EARRING : STR_FACE_TIE);
this->GetWidget<NWidgetCore>(WID_SCMF_LIPS_MOUSTACHE_TEXT)->SetString(this->is_moust_male ? STR_FACE_MOUSTACHE : STR_FACE_LIPS);
uint64_t active_vars = GetActiveFaceVars(this->face, vars);
/* Exclude active parts which have no string. */
for (auto var : SetBitIterator(active_vars)) {
if (vars[var].name == STR_NULL) ClrBit(active_vars, var);
}
/* Rebuild the sorted list of face variable pointers. */
this->face_vars.clear();
for (auto var : SetBitIterator(active_vars)) {
this->face_vars.emplace_back(&vars[var]);
}
std::ranges::sort(this->face_vars, std::less{}, &FaceVar::position);
this->GetScrollbar(WID_SCMF_PARTS_SCROLLBAR)->SetCount(std::size(this->face_vars));
}
public:
@ -1372,16 +1252,20 @@ public:
this->UpdateData();
}
void OnInit() override
{
this->line_height = std::max(SETTING_BUTTON_HEIGHT, GetCharacterHeight(FS_NORMAL)) + WidgetDimensions::scaled.matrix.Vertical();
}
/**
* Select planes to display to the user with the #NWID_SELECTION widgets #WID_SCMF_SEL_LOADSAVE, #WID_SCMF_SEL_MALEFEMALE, and #WID_SCMF_SEL_PARTS.
* @param advanced Display advanced face management window.
*/
void SelectDisplayPlanes(bool advanced)
{
this->GetWidget<NWidgetStacked>(WID_SCMF_SEL_LOADSAVE)->SetDisplayedPlane(advanced ? 0 : SZSP_NONE);
this->GetWidget<NWidgetStacked>(WID_SCMF_SEL_LOADSAVE)->SetDisplayedPlane(advanced ? 0 : SZSP_HORIZONTAL);
this->GetWidget<NWidgetStacked>(WID_SCMF_SEL_PARTS)->SetDisplayedPlane(advanced ? 0 : SZSP_NONE);
this->GetWidget<NWidgetStacked>(WID_SCMF_SEL_MALEFEMALE)->SetDisplayedPlane(advanced ? SZSP_NONE : 0);
this->GetWidget<NWidgetCore>(WID_SCMF_RANDOM_NEW_FACE)->SetString(advanced ? STR_FACE_RANDOM : STR_FACE_NEW_FACE_BUTTON);
this->GetWidget<NWidgetStacked>(WID_SCMF_SEL_RESIZE)->SetDisplayedPlane(advanced ? 0 : SZSP_NONE);
NWidgetCore *wi = this->GetWidget<NWidgetCore>(WID_SCMF_TOGGLE_LARGE_SMALL_BUTTON);
if (advanced) {
@ -1391,185 +1275,99 @@ public:
}
}
void OnInit() override
static StringID GetLongestString(StringID a, StringID b)
{
/* Size of the boolean yes/no button. */
Dimension yesno_dim = maxdim(GetStringBoundingBox(STR_FACE_YES), GetStringBoundingBox(STR_FACE_NO));
yesno_dim.width += WidgetDimensions::scaled.framerect.Horizontal();
yesno_dim.height += WidgetDimensions::scaled.framerect.Vertical();
/* Size of the number button + arrows. */
Dimension number_dim = {0, 0};
for (int val = 1; val <= 12; val++) {
number_dim = maxdim(number_dim, GetStringBoundingBox(GetString(STR_JUST_INT, val)));
}
uint arrows_width = GetSpriteSize(SPR_ARROW_LEFT).width + GetSpriteSize(SPR_ARROW_RIGHT).width + 2 * (WidgetDimensions::scaled.imgbtn.Horizontal());
number_dim.width += WidgetDimensions::scaled.framerect.Horizontal() + arrows_width;
number_dim.height += WidgetDimensions::scaled.framerect.Vertical();
/* Compute width of both buttons. */
yesno_dim.width = std::max(yesno_dim.width, number_dim.width);
number_dim.width = yesno_dim.width - arrows_width;
return GetStringBoundingBox(a).width > GetStringBoundingBox(b).width ? a : b;
}
this->yesno_dim = yesno_dim;
this->number_dim = number_dim;
static uint GetMaximumFacePartsWidth()
{
StringID yes_no = GetLongestString(STR_FACE_YES, STR_FACE_NO);
uint width = 0;
for (uint style_index = 0; style_index != GetNumCompanyManagerFaceStyles(); ++style_index) {
FaceVars vars = GetCompanyManagerFaceVars(style_index);
for (const auto &info : vars) {
if (info.name == STR_NULL) continue;
if (info.type == FaceVarType::Toggle) {
width = std::max(width, GetStringBoundingBox(GetString(STR_FACE_SETTING_TOGGLE, info.name, yes_no)).width);
} else {
uint64_t max_digits = GetParamMaxValue(info.valid_values);
width = std::max(width, GetStringBoundingBox(GetString(STR_FACE_SETTING_NUMERIC, info.name, max_digits, max_digits)).width);
}
}
}
/* Include width of button and spacing. */
width += SETTING_BUTTON_WIDTH + WidgetDimensions::scaled.hsep_wide + WidgetDimensions::scaled.frametext.Horizontal();
return width;
}
void UpdateWidgetSize(WidgetID widget, Dimension &size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension &fill, [[maybe_unused]] Dimension &resize) override
{
switch (widget) {
case WID_SCMF_HAS_MOUSTACHE_EARRING_TEXT:
size = maxdim(size, GetStringBoundingBox(STR_FACE_EARRING));
size = maxdim(size, GetStringBoundingBox(STR_FACE_MOUSTACHE));
break;
case WID_SCMF_TIE_EARRING_TEXT:
size = maxdim(size, GetStringBoundingBox(STR_FACE_EARRING));
size = maxdim(size, GetStringBoundingBox(STR_FACE_TIE));
break;
case WID_SCMF_LIPS_MOUSTACHE_TEXT:
size = maxdim(size, GetStringBoundingBox(STR_FACE_LIPS));
size = maxdim(size, GetStringBoundingBox(STR_FACE_MOUSTACHE));
break;
case WID_SCMF_FACE:
size = maxdim(size, GetScaledSpriteSize(SPR_GRADIENT));
break;
case WID_SCMF_HAS_MOUSTACHE_EARRING:
case WID_SCMF_HAS_GLASSES:
size = this->yesno_dim;
case WID_SCMF_STYLE:
size.height = this->line_height;
break;
case WID_SCMF_EYECOLOUR:
case WID_SCMF_CHIN:
case WID_SCMF_EYEBROWS:
case WID_SCMF_LIPS_MOUSTACHE:
case WID_SCMF_NOSE:
case WID_SCMF_HAIR:
case WID_SCMF_JACKET:
case WID_SCMF_COLLAR:
case WID_SCMF_TIE_EARRING:
case WID_SCMF_GLASSES:
size = this->number_dim;
case WID_SCMF_PARTS:
fill.height = resize.height = this->line_height;
size.width = GetMaximumFacePartsWidth();
size.height = resize.height * 5;
break;
}
}
void OnPaint() override
{
/* lower the non-selected gender button */
this->SetWidgetsLoweredState(!this->is_female, WID_SCMF_MALE, WID_SCMF_MALE2);
this->SetWidgetsLoweredState( this->is_female, WID_SCMF_FEMALE, WID_SCMF_FEMALE2);
/* advanced company manager face selection window */
/* lower the non-selected ethnicity button */
this->SetWidgetLoweredState(WID_SCMF_ETHNICITY_EUR, !HasBit(this->ge, ETHNICITY_BLACK));
this->SetWidgetLoweredState(WID_SCMF_ETHNICITY_AFR, HasBit(this->ge, ETHNICITY_BLACK));
/* Disable dynamically the widgets which CompanyManagerFaceVariable has less than 2 options
* (or in other words you haven't any choice).
* If the widgets depend on a HAS-variable and this is false the widgets will be disabled, too. */
/* Eye colour buttons */
this->SetWidgetsDisabledState(_cmf_info[CMFV_EYE_COLOUR].valid_values[this->ge] < 2,
WID_SCMF_EYECOLOUR, WID_SCMF_EYECOLOUR_L, WID_SCMF_EYECOLOUR_R);
/* Chin buttons */
this->SetWidgetsDisabledState(_cmf_info[CMFV_CHIN].valid_values[this->ge] < 2,
WID_SCMF_CHIN, WID_SCMF_CHIN_L, WID_SCMF_CHIN_R);
/* Eyebrows buttons */
this->SetWidgetsDisabledState(_cmf_info[CMFV_EYEBROWS].valid_values[this->ge] < 2,
WID_SCMF_EYEBROWS, WID_SCMF_EYEBROWS_L, WID_SCMF_EYEBROWS_R);
/* Lips or (if it a male face with a moustache) moustache buttons */
this->SetWidgetsDisabledState(_cmf_info[this->is_moust_male ? CMFV_MOUSTACHE : CMFV_LIPS].valid_values[this->ge] < 2,
WID_SCMF_LIPS_MOUSTACHE, WID_SCMF_LIPS_MOUSTACHE_L, WID_SCMF_LIPS_MOUSTACHE_R);
/* Nose buttons | male faces with moustache haven't any nose options */
this->SetWidgetsDisabledState(_cmf_info[CMFV_NOSE].valid_values[this->ge] < 2 || this->is_moust_male,
WID_SCMF_NOSE, WID_SCMF_NOSE_L, WID_SCMF_NOSE_R);
/* Hair buttons */
this->SetWidgetsDisabledState(_cmf_info[CMFV_HAIR].valid_values[this->ge] < 2,
WID_SCMF_HAIR, WID_SCMF_HAIR_L, WID_SCMF_HAIR_R);
/* Jacket buttons */
this->SetWidgetsDisabledState(_cmf_info[CMFV_JACKET].valid_values[this->ge] < 2,
WID_SCMF_JACKET, WID_SCMF_JACKET_L, WID_SCMF_JACKET_R);
/* Collar buttons */
this->SetWidgetsDisabledState(_cmf_info[CMFV_COLLAR].valid_values[this->ge] < 2,
WID_SCMF_COLLAR, WID_SCMF_COLLAR_L, WID_SCMF_COLLAR_R);
/* Tie/earring buttons | female faces without earring haven't any earring options */
this->SetWidgetsDisabledState(_cmf_info[CMFV_TIE_EARRING].valid_values[this->ge] < 2 ||
(this->is_female && GetCompanyManagerFaceBits(this->face, CMFV_HAS_TIE_EARRING, this->ge) == 0),
WID_SCMF_TIE_EARRING, WID_SCMF_TIE_EARRING_L, WID_SCMF_TIE_EARRING_R);
/* Glasses buttons | faces without glasses haven't any glasses options */
this->SetWidgetsDisabledState(_cmf_info[CMFV_GLASSES].valid_values[this->ge] < 2 || GetCompanyManagerFaceBits(this->face, CMFV_HAS_GLASSES, this->ge) == 0,
WID_SCMF_GLASSES, WID_SCMF_GLASSES_L, WID_SCMF_GLASSES_R);
this->DrawWidgets();
}
std::string GetWidgetString(WidgetID widget, StringID stringid) const override
{
switch (widget) {
case WID_SCMF_HAS_MOUSTACHE_EARRING: {
CompanyManagerFaceVariable facepart = this->is_female ? CMFV_HAS_TIE_EARRING : CMFV_HAS_MOUSTACHE;
return this->GetFaceString(widget, GetCompanyManagerFaceBits(this->face, facepart, this->ge), true);
}
case WID_SCMF_TIE_EARRING:
return this->GetFaceString(widget, GetCompanyManagerFaceBits(this->face, CMFV_TIE_EARRING, this->ge), false);
case WID_SCMF_LIPS_MOUSTACHE: {
CompanyManagerFaceVariable facepart = this->is_moust_male ? CMFV_MOUSTACHE : CMFV_LIPS;
return this->GetFaceString(widget, GetCompanyManagerFaceBits(this->face, facepart, this->ge), false);
}
case WID_SCMF_HAS_GLASSES:
return this->GetFaceString(widget, GetCompanyManagerFaceBits(this->face, CMFV_HAS_GLASSES, this->ge), true );
case WID_SCMF_HAIR:
return this->GetFaceString(widget, GetCompanyManagerFaceBits(this->face, CMFV_HAIR, this->ge), false);
case WID_SCMF_EYEBROWS:
return this->GetFaceString(widget, GetCompanyManagerFaceBits(this->face, CMFV_EYEBROWS, this->ge), false);
case WID_SCMF_EYECOLOUR:
return this->GetFaceString(widget, GetCompanyManagerFaceBits(this->face, CMFV_EYE_COLOUR, this->ge), false);
case WID_SCMF_GLASSES:
return this->GetFaceString(widget, GetCompanyManagerFaceBits(this->face, CMFV_GLASSES, this->ge), false);
case WID_SCMF_NOSE:
return this->GetFaceString(widget, GetCompanyManagerFaceBits(this->face, CMFV_NOSE, this->ge), false);
case WID_SCMF_CHIN:
return this->GetFaceString(widget, GetCompanyManagerFaceBits(this->face, CMFV_CHIN, this->ge), false);
case WID_SCMF_JACKET:
return this->GetFaceString(widget, GetCompanyManagerFaceBits(this->face, CMFV_JACKET, this->ge), false);
case WID_SCMF_COLLAR:
return this->GetFaceString(widget, GetCompanyManagerFaceBits(this->face, CMFV_COLLAR, this->ge), false);
default:
return this->Window::GetWidgetString(widget, stringid);
}
}
void DrawWidget(const Rect &r, WidgetID widget) const override
{
switch (widget) {
case WID_SCMF_FACE:
DrawCompanyManagerFace(this->face, Company::Get(this->window_number)->colour, r);
break;
case WID_SCMF_STYLE: {
Rect ir = r.Shrink(WidgetDimensions::scaled.frametext, RectPadding::zero).WithHeight(this->line_height);
bool rtl = _current_text_dir == TD_RTL;
Rect br = ir.CentreTo(ir.Width(), SETTING_BUTTON_HEIGHT).WithWidth(SETTING_BUTTON_WIDTH, rtl);
Rect tr = ir.Shrink(RectPadding::zero, WidgetDimensions::scaled.matrix).CentreTo(ir.Width(), GetCharacterHeight(FS_NORMAL)).Indent(SETTING_BUTTON_WIDTH + WidgetDimensions::scaled.hsep_wide, rtl);
DrawArrowButtons(br.left, br.top, COLOUR_YELLOW, this->selected_var == UINT_MAX - 1 ? this->click_state : 0, true, true);
DrawString(tr, GetString(STR_FACE_SETTING_NUMERIC, STR_FACE_STYLE, this->face.style + 1, GetNumCompanyManagerFaceStyles()), TC_WHITE);
break;
}
case WID_SCMF_PARTS: {
Rect ir = r.Shrink(WidgetDimensions::scaled.frametext, RectPadding::zero).WithHeight(this->line_height);
bool rtl = _current_text_dir == TD_RTL;
FaceVars vars = GetCompanyManagerFaceVars(this->face.style);
auto [first, last] = this->GetScrollbar(WID_SCMF_PARTS_SCROLLBAR)->GetVisibleRangeIterators(this->face_vars);
for (auto it = first; it != last; ++it) {
const uint8_t var = static_cast<uint8_t>(*it - vars.data());
const FaceVar &facevar = **it;
Rect br = ir.CentreTo(ir.Width(), SETTING_BUTTON_HEIGHT).WithWidth(SETTING_BUTTON_WIDTH, rtl);
Rect tr = ir.Shrink(RectPadding::zero, WidgetDimensions::scaled.matrix).CentreTo(ir.Width(), GetCharacterHeight(FS_NORMAL)).Indent(SETTING_BUTTON_WIDTH + WidgetDimensions::scaled.hsep_wide, rtl);
uint val = vars[var].GetBits(this->face);
if (facevar.type == FaceVarType::Toggle) {
DrawBoolButton(br.left, br.top, COLOUR_YELLOW, COLOUR_GREY, val == 1, true);
DrawString(tr, GetString(STR_FACE_SETTING_TOGGLE, facevar.name, val == 1 ? STR_FACE_YES : STR_FACE_NO), TC_WHITE);
} else {
DrawArrowButtons(br.left, br.top, COLOUR_YELLOW, this->selected_var == var ? this->click_state : 0, true, true);
DrawString(tr, GetString(STR_FACE_SETTING_NUMERIC, facevar.name, val + 1, facevar.valid_values), TC_WHITE);
}
ir = ir.Translate(0, this->line_height);
}
break;
}
}
}
@ -1586,7 +1384,7 @@ public:
/* OK button */
case WID_SCMF_ACCEPT:
Command<CMD_SET_COMPANY_MANAGER_FACE>::Post(this->face);
Command<CMD_SET_COMPANY_MANAGER_FACE>::Post(this->face.bits, this->face.style);
[[fallthrough]];
/* Cancel button */
@ -1595,101 +1393,119 @@ public:
break;
/* Load button */
case WID_SCMF_LOAD:
this->face = _company_manager_face;
ScaleAllCompanyManagerFaceBits(this->face);
case WID_SCMF_LOAD: {
auto cmf = ParseCompanyManagerFaceCode(_company_manager_face);
if (cmf.has_value()) this->face = *cmf;
ShowErrorMessage(GetEncodedString(STR_FACE_LOAD_DONE), {}, WL_INFO);
this->UpdateData();
this->SetDirty();
break;
}
/* 'Company manager face number' button, view and/or set company manager face number */
case WID_SCMF_FACECODE:
ShowQueryString(GetString(STR_JUST_INT, this->face), STR_FACE_FACECODE_CAPTION, 10 + 1, this, CS_NUMERAL, {});
ShowQueryString(FormatCompanyManagerFaceCode(this->face), STR_FACE_FACECODE_CAPTION, 128, this, CS_ALPHANUMERAL, {});
break;
/* Save button */
case WID_SCMF_SAVE:
_company_manager_face = this->face;
_company_manager_face = FormatCompanyManagerFaceCode(this->face);
ShowErrorMessage(GetEncodedString(STR_FACE_SAVE_DONE), {}, WL_INFO);
break;
/* Toggle gender (male/female) button */
case WID_SCMF_MALE:
case WID_SCMF_FEMALE:
case WID_SCMF_MALE2:
case WID_SCMF_FEMALE2:
SetCompanyManagerFaceBits(this->face, CMFV_GENDER, this->ge, (widget == WID_SCMF_FEMALE || widget == WID_SCMF_FEMALE2));
ScaleAllCompanyManagerFaceBits(this->face);
this->UpdateData();
this->SetDirty();
break;
/* Randomize face button */
case WID_SCMF_RANDOM_NEW_FACE:
RandomCompanyManagerFaceBits(this->face, this->ge, this->advanced, _interactive_random);
RandomiseCompanyManagerFace(this->face, _interactive_random);
this->UpdateData();
this->SetDirty();
break;
/* Toggle ethnicity (european/african) button */
case WID_SCMF_ETHNICITY_EUR:
case WID_SCMF_ETHNICITY_AFR:
SetCompanyManagerFaceBits(this->face, CMFV_ETHNICITY, this->ge, widget - WID_SCMF_ETHNICITY_EUR);
ScaleAllCompanyManagerFaceBits(this->face);
this->UpdateData();
this->SetDirty();
break;
case WID_SCMF_STYLE: {
bool rtl = _current_text_dir == TD_RTL;
Rect ir = this->GetWidget<NWidgetCore>(widget)->GetCurrentRect().Shrink(WidgetDimensions::scaled.frametext, RectPadding::zero);
Rect br = ir.WithWidth(SETTING_BUTTON_WIDTH, rtl);
default:
/* Here all buttons from WID_SCMF_HAS_MOUSTACHE_EARRING to WID_SCMF_GLASSES_R are handled.
* First it checks which CompanyManagerFaceVariable is being changed, and then either
* a: invert the value for boolean variables, or
* b: it checks inside of IncreaseCompanyManagerFaceBits() if a left (_L) butten is pressed and then decrease else increase the variable */
if (widget >= WID_SCMF_HAS_MOUSTACHE_EARRING && widget <= WID_SCMF_GLASSES_R) {
CompanyManagerFaceVariable cmfv; // which CompanyManagerFaceVariable shall be edited
if (widget < WID_SCMF_EYECOLOUR_L) { // Bool buttons
switch (widget - WID_SCMF_HAS_MOUSTACHE_EARRING) {
default: NOT_REACHED();
case 0: cmfv = this->is_female ? CMFV_HAS_TIE_EARRING : CMFV_HAS_MOUSTACHE; break; // Has earring/moustache button
case 1: cmfv = CMFV_HAS_GLASSES; break; // Has glasses button
}
SetCompanyManagerFaceBits(this->face, cmfv, this->ge, !GetCompanyManagerFaceBits(this->face, cmfv, this->ge));
ScaleAllCompanyManagerFaceBits(this->face);
} else { // Value buttons
switch ((widget - WID_SCMF_EYECOLOUR_L) / 3) {
default: NOT_REACHED();
case 0: cmfv = CMFV_EYE_COLOUR; break; // Eye colour buttons
case 1: cmfv = CMFV_CHIN; break; // Chin buttons
case 2: cmfv = CMFV_EYEBROWS; break; // Eyebrows buttons
case 3: cmfv = this->is_moust_male ? CMFV_MOUSTACHE : CMFV_LIPS; break; // Moustache or lips buttons
case 4: cmfv = CMFV_NOSE; break; // Nose buttons
case 5: cmfv = CMFV_HAIR; break; // Hair buttons
case 6: cmfv = CMFV_JACKET; break; // Jacket buttons
case 7: cmfv = CMFV_COLLAR; break; // Collar buttons
case 8: cmfv = CMFV_TIE_EARRING; break; // Tie/earring buttons
case 9: cmfv = CMFV_GLASSES; break; // Glasses buttons
}
/* 0 == left (_L), 1 == middle or 2 == right (_R) - button click */
IncreaseCompanyManagerFaceBits(this->face, cmfv, this->ge, (((widget - WID_SCMF_EYECOLOUR_L) % 3) != 0) ? 1 : -1);
}
this->UpdateData();
this->SetDirty();
uint num_styles = GetNumCompanyManagerFaceStyles();
this->selected_var = UINT_MAX - 1;
if (IsInsideBS(pt.x, br.left, SETTING_BUTTON_WIDTH / 2)) {
SetCompanyManagerFaceStyle(this->face, (this->face.style + num_styles - 1) % num_styles);
this->click_state = 1;
} else if (IsInsideBS(pt.x, br.left + SETTING_BUTTON_WIDTH / 2, SETTING_BUTTON_WIDTH / 2)) {
SetCompanyManagerFaceStyle(this->face, (this->face.style + 1) % num_styles);
this->click_state = 2;
}
this->UpdateData();
this->SetTimeout();
this->SetDirty();
break;
}
case WID_SCMF_PARTS: {
bool rtl = _current_text_dir == TD_RTL;
Rect ir = this->GetWidget<NWidgetCore>(widget)->GetCurrentRect().Shrink(WidgetDimensions::scaled.frametext, RectPadding::zero);
Rect br = ir.WithWidth(SETTING_BUTTON_WIDTH, rtl);
this->selected_var = UINT_MAX;
FaceVars vars = GetCompanyManagerFaceVars(this->face.style);
auto it = this->GetScrollbar(WID_SCMF_PARTS_SCROLLBAR)->GetScrolledItemFromWidget(this->face_vars, pt.y, this, widget, 0, this->line_height);
if (it == std::end(this->face_vars)) break;
this->selected_var = static_cast<uint8_t>(*it - vars.data());
const auto &facevar = **it;
if (facevar.type == FaceVarType::Toggle) {
if (!IsInsideBS(pt.x, br.left, SETTING_BUTTON_WIDTH)) break;
facevar.ChangeBits(this->face, 1);
this->UpdateData();
} else {
if (IsInsideBS(pt.x, br.left, SETTING_BUTTON_WIDTH / 2)) {
facevar.ChangeBits(this->face, -1);
this->click_state = 1;
} else if (IsInsideBS(pt.x, br.left + SETTING_BUTTON_WIDTH / 2, SETTING_BUTTON_WIDTH / 2)) {
facevar.ChangeBits(this->face, 1);
this->click_state = 2;
} else {
break;
}
}
this->SetTimeout();
this->SetDirty();
break;
}
}
}
void OnResize() override
{
if (auto *wid = this->GetWidget<NWidgetResizeBase>(WID_SCMF_PARTS); wid != nullptr) {
/* Workaround for automatic widget sizing ignoring resize steps. Manually ensure parts matrix is a
* multiple of its resize step. This trick only works here as the window itself is not resizable. */
if (wid->UpdateVerticalSize((wid->current_y + wid->resize_y - 1) / wid->resize_y * wid->resize_y)) {
this->ReInit();
return;
}
}
this->GetScrollbar(WID_SCMF_PARTS_SCROLLBAR)->SetCapacityFromWidget(this, WID_SCMF_PARTS);
}
void OnTimeout() override
{
this->click_state = 0;
this->selected_var = UINT_MAX;
this->SetDirty();
}
void OnQueryTextFinished(std::optional<std::string> str) override
{
if (!str.has_value()) return;
/* Set a new company manager face number */
if (!str->empty()) {
auto val = ParseInteger(*str, 10, true);
if (!val.has_value()) return;
this->face = *val;
ScaleAllCompanyManagerFaceBits(this->face);
/* Parse new company manager face number */
auto cmf = ParseCompanyManagerFaceCode(*str);
if (cmf.has_value()) {
this->face = *cmf;
ShowErrorMessage(GetEncodedString(STR_FACE_FACECODE_SET), {}, WL_INFO);
this->UpdateData();
this->SetDirty();

View File

@ -12,158 +12,142 @@
#include "core/random_func.hpp"
#include "core/bitmath_func.hpp"
#include "table/sprites.h"
#include "strings_type.h"
#include "company_type.h"
#include "gfx_type.h"
/** The gender/race combinations that we have faces for */
enum GenderEthnicity : uint8_t {
GENDER_FEMALE = 0, ///< This bit set means a female, otherwise male
ETHNICITY_BLACK = 1, ///< This bit set means black, otherwise white
#include "table/strings.h"
GE_WM = 0, ///< A male of Caucasian origin (white)
GE_WF = 1 << GENDER_FEMALE, ///< A female of Caucasian origin (white)
GE_BM = 1 << ETHNICITY_BLACK, ///< A male of African origin (black)
GE_BF = 1 << ETHNICITY_BLACK | 1 << GENDER_FEMALE, ///< A female of African origin (black)
GE_END,
enum class FaceVarType : uint8_t {
Sprite,
Palette,
Toggle,
};
DECLARE_ENUM_AS_BIT_SET(GenderEthnicity) ///< See GenderRace as a bitset
/** Bitgroups of the CompanyManagerFace variable */
enum CompanyManagerFaceVariable : uint8_t {
CMFV_GENDER,
CMFV_ETHNICITY,
CMFV_GEN_ETHN,
CMFV_HAS_MOUSTACHE,
CMFV_HAS_TIE_EARRING,
CMFV_HAS_GLASSES,
CMFV_EYE_COLOUR,
CMFV_CHEEKS,
CMFV_CHIN,
CMFV_EYEBROWS,
CMFV_MOUSTACHE,
CMFV_LIPS,
CMFV_NOSE,
CMFV_HAIR,
CMFV_COLLAR,
CMFV_JACKET,
CMFV_TIE_EARRING,
CMFV_GLASSES,
CMFV_END,
};
DECLARE_INCREMENT_DECREMENT_OPERATORS(CompanyManagerFaceVariable)
/** Information about the valid values of CompanyManagerFace bitgroups as well as the sprites to draw */
struct CompanyManagerFaceBitsInfo {
uint8_t offset; ///< Offset in bits into the CompanyManagerFace
uint8_t length; ///< Number of bits used in the CompanyManagerFace
uint8_t valid_values[GE_END]; ///< The number of valid values per gender/ethnicity
SpriteID first_sprite[GE_END]; ///< The first sprite per gender/ethnicity
};
struct FaceVar {
FaceVarType type;
uint8_t position; ///< Position in UI.
uint8_t offset; ///< Offset in bits into the CompanyManagerFace
uint8_t length; ///< Number of bits used in the CompanyManagerFace
uint8_t valid_values; ///< The number of valid values
std::variant<SpriteID, uint64_t, std::pair<uint64_t, uint64_t>> data; ///< The first sprite
StringID name = STR_NULL;
/** Lookup table for indices into the CompanyManagerFace, valid ranges and sprites */
static const CompanyManagerFaceBitsInfo _cmf_info[] = {
/* Index off len WM WF BM BF WM WF BM BF
* CMFV_GENDER */ { 0, 1, { 2, 2, 2, 2 }, { 0, 0, 0, 0 } }, ///< 0 = male, 1 = female
/* CMFV_ETHNICITY */ { 1, 2, { 2, 2, 2, 2 }, { 0, 0, 0, 0 } }, ///< 0 = (Western-)Caucasian, 1 = African(-American)/Black
/* CMFV_GEN_ETHN */ { 0, 3, { 4, 4, 4, 4 }, { 0, 0, 0, 0 } }, ///< Shortcut to get/set gender _and_ ethnicity
/* CMFV_HAS_MOUSTACHE */ { 3, 1, { 2, 0, 2, 0 }, { 0, 0, 0, 0 } }, ///< Females do not have a moustache
/* CMFV_HAS_TIE_EARRING */ { 3, 1, { 0, 2, 0, 2 }, { 0, 0, 0, 0 } }, ///< Draw the earring for females or not. For males the tie is always drawn.
/* CMFV_HAS_GLASSES */ { 4, 1, { 2, 2, 2, 2 }, { 0, 0, 0, 0 } }, ///< Whether to draw glasses or not
/* CMFV_EYE_COLOUR */ { 5, 2, { 3, 3, 1, 1 }, { 0, 0, 0, 0 } }, ///< Palette modification
/* CMFV_CHEEKS */ { 0, 0, { 1, 1, 1, 1 }, { 0x325, 0x326, 0x390, 0x3B0 } }, ///< Cheeks are only indexed by their gender/ethnicity
/* CMFV_CHIN */ { 7, 2, { 4, 1, 2, 2 }, { 0x327, 0x327, 0x391, 0x3B1 } },
/* CMFV_EYEBROWS */ { 9, 4, { 12, 16, 11, 16 }, { 0x32B, 0x337, 0x39A, 0x3B8 } },
/* CMFV_MOUSTACHE */ { 13, 2, { 3, 0, 3, 0 }, { 0x367, 0, 0x397, 0 } }, ///< Depends on CMFV_HAS_MOUSTACHE
/* CMFV_LIPS */ { 13, 4, { 12, 10, 9, 9 }, { 0x35B, 0x351, 0x3A5, 0x3C8 } }, ///< Depends on !CMFV_HAS_MOUSTACHE
/* CMFV_NOSE */ { 17, 3, { 8, 4, 4, 5 }, { 0x349, 0x34C, 0x393, 0x3B3 } }, ///< Depends on !CMFV_HAS_MOUSTACHE
/* CMFV_HAIR */ { 20, 4, { 9, 5, 5, 5 }, { 0x382, 0x38B, 0x3D4, 0x3D9 } },
/* CMFV_COLLAR */ { 26, 2, { 4, 4, 4, 4 }, { 0x36E, 0x37B, 0x36E, 0x37B } },
/* CMFV_JACKET */ { 24, 2, { 3, 3, 3, 3 }, { 0x36B, 0x378, 0x36B, 0x378 } },
/* CMFV_TIE_EARRING */ { 28, 3, { 6, 3, 6, 3 }, { 0x372, 0x37F, 0x372, 0x3D1 } }, ///< Depends on CMFV_HAS_TIE_EARRING
/* CMFV_GLASSES */ { 31, 1, { 2, 2, 2, 2 }, { 0x347, 0x347, 0x3AE, 0x3AE } } ///< Depends on CMFV_HAS_GLASSES
};
/** Make sure the table's size is right. */
static_assert(lengthof(_cmf_info) == CMFV_END);
/**
* Gets the company manager's face bits for the given company manager's face variable
* @param cmf the face to extract the bits from
* @param cmfv the face variable to get the data of
* @param ge the gender and ethnicity of the face
* @pre _cmf_info[cmfv].valid_values[ge] != 0
* @return the requested bits
*/
inline uint GetCompanyManagerFaceBits(CompanyManagerFace cmf, CompanyManagerFaceVariable cmfv, [[maybe_unused]] GenderEthnicity ge)
{
assert(_cmf_info[cmfv].valid_values[ge] != 0);
return GB(cmf, _cmf_info[cmfv].offset, _cmf_info[cmfv].length);
}
/**
* Sets the company manager's face bits for the given company manager's face variable
* @param cmf the face to write the bits to
* @param cmfv the face variable to write the data of
* @param ge the gender and ethnicity of the face
* @param val the new value
* @pre val < _cmf_info[cmfv].valid_values[ge]
*/
inline void SetCompanyManagerFaceBits(CompanyManagerFace &cmf, CompanyManagerFaceVariable cmfv, [[maybe_unused]] GenderEthnicity ge, uint val)
{
assert(val < _cmf_info[cmfv].valid_values[ge]);
SB(cmf, _cmf_info[cmfv].offset, _cmf_info[cmfv].length, val);
}
/**
* Increase/Decrease the company manager's face variable by the given amount.
* The value wraps around to stay in the valid range.
*
* @param cmf the company manager face to write the bits to
* @param cmfv the company manager face variable to write the data of
* @param ge the gender and ethnicity of the company manager's face
* @param amount the amount which change the value
*
* @pre 0 <= val < _cmf_info[cmfv].valid_values[ge]
*/
inline void IncreaseCompanyManagerFaceBits(CompanyManagerFace &cmf, CompanyManagerFaceVariable cmfv, GenderEthnicity ge, int8_t amount)
{
int8_t val = GetCompanyManagerFaceBits(cmf, cmfv, ge) + amount; // the new value for the cmfv
/* scales the new value to the correct scope */
while (val < 0) {
val += _cmf_info[cmfv].valid_values[ge];
/**
* Gets the company manager's face bits.
* @param cmf The face to extract the bits from.
* @return the requested bits
*/
inline uint GetBits(const CompanyManagerFace &cmf) const
{
return GB(cmf.bits, this->offset, this->length);
}
val %= _cmf_info[cmfv].valid_values[ge];
SetCompanyManagerFaceBits(cmf, cmfv, ge, val); // save the new value
}
/**
* Sets the company manager's face bits.
* @param cmf The face to write the bits to.
* @param val The new value.
*/
inline void SetBits(CompanyManagerFace &cmf, uint val) const
{
SB(cmf.bits, this->offset, this->length, val);
}
/**
* Increase/Decrease the company manager's face variable by the given amount.
* The value wraps around to stay in the valid range.
* @param cmf The face to write the bits to.
* @param amount the amount to change the value
*/
inline void ChangeBits(CompanyManagerFace &cmf, int8_t amount) const
{
int8_t val = this->GetBits(cmf) + amount; // the new value for the cmfv
/* scales the new value to the correct scope */
while (val < 0) {
val += this->valid_values;
}
val %= this->valid_values;
this->SetBits(cmf, val); // save the new value
}
/**
* Checks whether the company manager's face bits have a valid range
* @param cmf The face to check.
* @return true if and only if the bits are valid
*/
inline bool IsValid(const CompanyManagerFace &cmf) const
{
return GB(cmf.bits, this->offset, this->length) < this->valid_values;
}
/**
* Scales a company manager's face bits variable to the correct scope
* @param vars The face variables of the face style.
* @pre val < (1U << length), i.e. val has a value of 0..2^(bits used for this variable)-1
* @return the scaled value
*/
inline uint ScaleBits(uint val) const
{
assert(val < (1U << this->length));
return (val * this->valid_values) >> this->length;
}
/**
* Gets the sprite to draw.
* @param cmf The face to extract the data from
* @pre vars[var].type == FaceVarType::Sprite.
* @return sprite to draw
*/
inline SpriteID GetSprite(const CompanyManagerFace &cmf) const
{
assert(this->type == FaceVarType::Sprite);
return std::get<SpriteID>(this->data) + this->GetBits(cmf);
}
};
using FaceVars = std::span<const FaceVar>;
struct FaceSpec {
std::string label;
std::variant<FaceVars, std::vector<FaceVar>> face_vars;
inline FaceVars GetFaceVars() const
{
struct visitor {
FaceVars operator()(FaceVars vars) const { return vars; }
FaceVars operator()(const std::vector<FaceVar> &vars) const { return vars; }
};
return std::visit(visitor{}, this->face_vars);
}
};
void ResetFaces();
uint GetNumCompanyManagerFaceStyles();
std::optional<uint> FindCompanyManagerFaceLabel(std::string_view label);
const FaceSpec *GetCompanyManagerFaceSpec(uint style_index);
FaceVars GetCompanyManagerFaceVars(uint style_index);
/**
* Checks whether the company manager's face bits have a valid range
* @param cmf the face to extract the bits from
* @param cmfv the face variable to get the data of
* @param ge the gender and ethnicity of the face
* @return true if and only if the bits are valid
* Get a bitmask of currently active face variables.
* Face variables can be inactive due to toggles in the face variables.
* @param cmf The company manager face.
* @param vars The face variables of the face.
* @return Currently active face variables for the face.
*/
inline bool AreCompanyManagerFaceBitsValid(CompanyManagerFace cmf, CompanyManagerFaceVariable cmfv, GenderEthnicity ge)
inline uint64_t GetActiveFaceVars(const CompanyManagerFace &cmf, FaceVars vars)
{
return GB(cmf, _cmf_info[cmfv].offset, _cmf_info[cmfv].length) < _cmf_info[cmfv].valid_values[ge];
}
uint64_t active_vars = (1ULL << std::size(vars)) - 1ULL;
/**
* Scales a company manager's face bits variable to the correct scope
* @param cmfv the face variable to write the data of
* @param ge the gender and ethnicity of the face
* @param val the to value to scale
* @pre val < (1U << _cmf_info[cmfv].length), i.e. val has a value of 0..2^(bits used for this variable)-1
* @return the scaled value
*/
inline uint ScaleCompanyManagerFaceValue(CompanyManagerFaceVariable cmfv, GenderEthnicity ge, uint val)
{
assert(val < (1U << _cmf_info[cmfv].length));
for (const auto &info : vars) {
if (info.type != FaceVarType::Toggle) continue;
const auto &[off, on] = std::get<std::pair<uint64_t, uint64_t>>(info.data);
active_vars &= ~(HasBit(cmf.bits, info.offset) ? on : off);
}
return (val * _cmf_info[cmfv].valid_values[ge]) >> _cmf_info[cmfv].length;
return active_vars;
}
/**
@ -171,69 +155,31 @@ inline uint ScaleCompanyManagerFaceValue(CompanyManagerFaceVariable cmfv, Gender
*
* @param cmf the company manager's face to write the bits to
*/
inline void ScaleAllCompanyManagerFaceBits(CompanyManagerFace &cmf)
inline void ScaleAllCompanyManagerFaceBits(CompanyManagerFace &cmf, FaceVars vars)
{
IncreaseCompanyManagerFaceBits(cmf, CMFV_ETHNICITY, GE_WM, 0); // scales the ethnicity
GenderEthnicity ge = (GenderEthnicity)GB(cmf, _cmf_info[CMFV_GEN_ETHN].offset, _cmf_info[CMFV_GEN_ETHN].length); // gender & ethnicity of the face
/* Is a male face with moustache. Need to reduce CPU load in the loop. */
bool is_moust_male = !HasBit(ge, GENDER_FEMALE) && GetCompanyManagerFaceBits(cmf, CMFV_HAS_MOUSTACHE, ge) != 0;
for (CompanyManagerFaceVariable cmfv = CMFV_EYE_COLOUR; cmfv < CMFV_END; cmfv++) { // scales all other variables
/* The moustache variable will be scaled only if it is a male face with has a moustache */
if (cmfv != CMFV_MOUSTACHE || is_moust_male) {
IncreaseCompanyManagerFaceBits(cmf, cmfv, ge, 0);
}
for (auto var : SetBitIterator(GetActiveFaceVars(cmf, vars))) {
vars[var].ChangeBits(cmf, 0);
}
}
/**
* Make a random new face.
* If it is for the advanced company manager's face window then the new face have the same gender
* and ethnicity as the old one, else the gender is equal and the ethnicity is random.
*
* @param cmf the company manager's face to write the bits to
* @param ge the gender and ethnicity of the old company manager's face
* @param adv if it for the advanced company manager's face window
* @param randomizer the source of random to use for creating the manager face
*
* @pre scale 'ge' to a valid gender/ethnicity combination
* Make a random new face without changing the face style.
* @param cmf The company manager's face to write the bits to
* @param vars The face variables.
* @param randomizer The source of random to use for creating the manager face
*/
inline void RandomCompanyManagerFaceBits(CompanyManagerFace &cmf, GenderEthnicity ge, bool adv, Randomizer &randomizer)
inline void RandomiseCompanyManagerFaceBits(CompanyManagerFace &cmf, FaceVars vars, Randomizer &randomizer)
{
cmf = randomizer.Next(); // random all company manager's face bits
/* scale ge: 0 == GE_WM, 1 == GE_WF, 2 == GE_BM, 3 == GE_BF (and maybe in future: ...) */
ge = (GenderEthnicity)((uint)ge % GE_END);
/* set the gender (and ethnicity) for the new company manager's face */
if (adv) {
SetCompanyManagerFaceBits(cmf, CMFV_GEN_ETHN, ge, ge);
} else {
SetCompanyManagerFaceBits(cmf, CMFV_GENDER, ge, HasBit(ge, GENDER_FEMALE));
}
/* scales all company manager's face bits to the correct scope */
ScaleAllCompanyManagerFaceBits(cmf);
cmf.bits = randomizer.Next();
ScaleAllCompanyManagerFaceBits(cmf, vars);
}
/**
* Gets the sprite to draw for the given company manager's face variable
* @param cmf the face to extract the data from
* @param cmfv the face variable to get the sprite of
* @param ge the gender and ethnicity of the face
* @pre _cmf_info[cmfv].valid_values[ge] != 0
* @return sprite to draw
*/
inline SpriteID GetCompanyManagerFaceSprite(CompanyManagerFace cmf, CompanyManagerFaceVariable cmfv, GenderEthnicity ge)
{
assert(_cmf_info[cmfv].valid_values[ge] != 0);
void SetCompanyManagerFaceStyle(CompanyManagerFace &cmf, uint style);
void RandomiseCompanyManagerFace(CompanyManagerFace &cmf, Randomizer &randomizer);
uint32_t MaskCompanyManagerFaceBits(const CompanyManagerFace &cmf, FaceVars vars);
std::string FormatCompanyManagerFaceCode(const CompanyManagerFace &cmf);
std::optional<CompanyManagerFace> ParseCompanyManagerFaceCode(std::string_view str);
return _cmf_info[cmfv].first_sprite[ge] + GB(cmf, _cmf_info[cmfv].offset, _cmf_info[cmfv].length);
}
void DrawCompanyManagerFace(CompanyManagerFace face, Colours colour, const Rect &r);
void DrawCompanyManagerFace(const CompanyManagerFace &cmf, Colours colour, const Rect &r);
#endif /* COMPANY_MANAGER_FACE_H */

View File

@ -49,7 +49,12 @@ public:
};
struct Company;
typedef uint32_t CompanyManagerFace; ///< Company manager face bits, info see in company_manager_face.h
struct CompanyManagerFace {
uint style = 0; ///< Company manager face style.
uint32_t bits = 0; ///< Company manager face bits, meaning is dependent on style.
std::string style_label; ///< Face style label.
};
/** The reason why the company was removed. */
enum CompanyRemoveReason : uint8_t {

View File

@ -28,6 +28,7 @@
#include "widgets/error_widget.h"
#include "table/sprites.h"
#include "table/strings.h"
#include "safeguards.h"

View File

@ -2318,12 +2318,7 @@ STR_LIVERY_FREIGHT_TRAM :Freight Tram
STR_FACE_CAPTION :{WHITE}Face Selection
STR_FACE_CANCEL_TOOLTIP :{BLACK}Cancel new face selection
STR_FACE_OK_TOOLTIP :{BLACK}Accept new face selection
STR_FACE_RANDOM :{BLACK}Randomise
STR_FACE_MALE_BUTTON :{BLACK}Male
STR_FACE_MALE_TOOLTIP :{BLACK}Select male faces
STR_FACE_FEMALE_BUTTON :{BLACK}Female
STR_FACE_FEMALE_TOOLTIP :{BLACK}Select female faces
STR_FACE_NEW_FACE_BUTTON :{BLACK}New Face
STR_FACE_NEW_FACE_TOOLTIP :{BLACK}Generate random new face
STR_FACE_ADVANCED :{BLACK}Advanced
@ -2333,44 +2328,31 @@ STR_FACE_SIMPLE_TOOLTIP :{BLACK}Simple f
STR_FACE_LOAD :{BLACK}Load
STR_FACE_LOAD_TOOLTIP :{BLACK}Load favourite face
STR_FACE_LOAD_DONE :{WHITE}Your favourite face has been loaded from the OpenTTD configuration file
STR_FACE_FACECODE :{BLACK}Player face no.
STR_FACE_FACECODE_TOOLTIP :{BLACK}View and/or set face number of the company president
STR_FACE_FACECODE_CAPTION :{WHITE}View and/or set president face number
STR_FACE_FACECODE_SET :{WHITE}New face number code has been set
STR_FACE_FACECODE_ERR :{WHITE}Couldn't set president face number - must be a number between 0 and 4,294,967,295!
STR_FACE_FACECODE :{BLACK}Player face code
STR_FACE_FACECODE_TOOLTIP :{BLACK}View and/or set face code of the company president
STR_FACE_FACECODE_CAPTION :{WHITE}View and/or set president face code
STR_FACE_FACECODE_SET :{WHITE}New president face has been set
STR_FACE_FACECODE_ERR :{WHITE}Couldn't set president face code - must be a valid label and number
STR_FACE_SAVE :{BLACK}Save
STR_FACE_SAVE_TOOLTIP :{BLACK}Save favourite face
STR_FACE_SAVE_DONE :{WHITE}This face will be saved as your favourite in the OpenTTD configuration file
STR_FACE_EUROPEAN :{BLACK}European
STR_FACE_EUROPEAN_TOOLTIP :{BLACK}Select European faces
STR_FACE_AFRICAN :{BLACK}African
STR_FACE_AFRICAN_TOOLTIP :{BLACK}Select African faces
STR_FACE_SETTING_TOGGLE :{STRING} {ORANGE}{STRING}
STR_FACE_SETTING_NUMERIC :{STRING} {ORANGE}{NUM} / {NUM}
STR_FACE_YES :Yes
STR_FACE_NO :No
STR_FACE_MOUSTACHE_EARRING_TOOLTIP :{BLACK}Enable moustache or earring
STR_FACE_STYLE :Style:
STR_FACE_HAIR :Hair:
STR_FACE_HAIR_TOOLTIP :{BLACK}Change hair
STR_FACE_EYEBROWS :Eyebrows:
STR_FACE_EYEBROWS_TOOLTIP :{BLACK}Change eyebrows
STR_FACE_EYECOLOUR :Eye colour:
STR_FACE_EYECOLOUR_TOOLTIP :{BLACK}Change eye colour
STR_FACE_GLASSES :Glasses:
STR_FACE_GLASSES_TOOLTIP :{BLACK}Enable glasses
STR_FACE_GLASSES_TOOLTIP_2 :{BLACK}Change glasses
STR_FACE_NOSE :Nose:
STR_FACE_NOSE_TOOLTIP :{BLACK}Change nose
STR_FACE_LIPS :Lips:
STR_FACE_MOUSTACHE :Moustache:
STR_FACE_LIPS_MOUSTACHE_TOOLTIP :{BLACK}Change lips or moustache
STR_FACE_CHIN :Chin:
STR_FACE_CHIN_TOOLTIP :{BLACK}Change chin
STR_FACE_JACKET :Jacket:
STR_FACE_JACKET_TOOLTIP :{BLACK}Change jacket
STR_FACE_COLLAR :Collar:
STR_FACE_COLLAR_TOOLTIP :{BLACK}Change collar
STR_FACE_TIE :Tie:
STR_FACE_EARRING :Earring:
STR_FACE_TIE_EARRING_TOOLTIP :{BLACK}Change tie or earring
# Matches ServerGameType
###length 3

View File

@ -10,6 +10,7 @@
#include "stdafx.h"
#include "core/backup_type.hpp"
#include "core/container_func.hpp"
#include "company_manager_face.h"
#include "debug.h"
#include "fileio_func.h"
#include "engine_func.h"
@ -463,6 +464,8 @@ void ResetNewGRFData()
/* Reset canal sprite groups and flags */
_water_feature.fill({});
ResetFaces();
/* Reset the snowline table. */
ClearSnowLine();

View File

@ -11,6 +11,7 @@
#define NEWS_TYPE_H
#include "core/enum_type.hpp"
#include "company_type.h"
#include "engine_type.h"
#include "industry_type.h"
#include "gfx_type.h"
@ -164,7 +165,7 @@ struct CompanyNewsInformation : NewsAllocatedData {
std::string other_company_name; ///< The name of the company taking over this one
StringID title;
uint32_t face; ///< The face of the president
CompanyManagerFace face; ///< The face of the president
Colours colour; ///< The colour related to the company
CompanyNewsInformation(StringID title, const struct Company *c, const struct Company *other = nullptr);

View File

@ -1625,7 +1625,28 @@ bool AfterLoadGame()
}
}
if (IsSavegameVersionBefore(SLV_49)) for (Company *c : Company::Iterate()) c->face = ConvertFromOldCompanyManagerFace(c->face);
if (IsSavegameVersionBefore(SLV_49)) {
/* Perform conversion of very old face bits. */
for (Company *c : Company::Iterate()) {
c->face = ConvertFromOldCompanyManagerFace(c->face.bits);
}
} else if (IsSavegameVersionBefore(SLV_FACE_STYLES)) {
/* Convert old gender and ethnicity bits to face style. */
for (Company *c : Company::Iterate()) {
SetCompanyManagerFaceStyle(c->face, GB(c->face.bits, 0, 2));
}
} else {
/* Look up each company face style by its label. */
for (Company *c : Company::Iterate()) {
auto style = FindCompanyManagerFaceLabel(c->face.style_label);
if (style.has_value()) {
SetCompanyManagerFaceStyle(c->face, *style);
} else {
/* Style no longer exists, pick an entirely new face. */
RandomiseCompanyManagerFace(c->face, _random);
}
}
}
if (IsSavegameVersionBefore(SLV_52)) {
for (auto t : Map::Iterate()) {

View File

@ -24,6 +24,20 @@
#include "../safeguards.h"
/**
* Search for a face variable by type and name.
* @param style Face style to find variable in.
* @param type Type of variable to look up.
* @param name Name (string) of variable to look up.
* @return Face variable if present, otherwise nullptr.
*/
static const FaceVar *FindFaceVar(FaceVars style, FaceVarType type, StringID name)
{
auto it = std::ranges::find_if(style, [type, name](const FaceVar &facevar) { return facevar.type == type && facevar.name == name; });
if (it != std::end(style)) return &*it;
return nullptr;
}
/**
* Converts an old company manager's face format to the new company manager's face format
*
@ -44,49 +58,65 @@
*/
CompanyManagerFace ConvertFromOldCompanyManagerFace(uint32_t face)
{
CompanyManagerFace cmf = 0;
GenderEthnicity ge = GE_WM;
CompanyManagerFace cmf{};
if (HasBit(face, 31)) SetBit(ge, GENDER_FEMALE);
if (HasBit(face, 27) && (HasBit(face, 26) == HasBit(face, 19))) SetBit(ge, ETHNICITY_BLACK);
if (HasBit(face, 31)) cmf.style += 1;
if (HasBit(face, 27) && (HasBit(face, 26) == HasBit(face, 19))) cmf.style += 2;
SetCompanyManagerFaceBits(cmf, CMFV_GEN_ETHN, ge, ge);
SetCompanyManagerFaceBits(cmf, CMFV_HAS_GLASSES, ge, GB(face, 28, 3) <= 1);
SetCompanyManagerFaceBits(cmf, CMFV_EYE_COLOUR, ge, HasBit(ge, ETHNICITY_BLACK) ? 0 : ClampU(GB(face, 20, 3), 5, 7) - 5);
SetCompanyManagerFaceBits(cmf, CMFV_CHIN, ge, ScaleCompanyManagerFaceValue(CMFV_CHIN, ge, GB(face, 4, 2)));
SetCompanyManagerFaceBits(cmf, CMFV_EYEBROWS, ge, ScaleCompanyManagerFaceValue(CMFV_EYEBROWS, ge, GB(face, 6, 4)));
SetCompanyManagerFaceBits(cmf, CMFV_HAIR, ge, ScaleCompanyManagerFaceValue(CMFV_HAIR, ge, GB(face, 16, 4)));
SetCompanyManagerFaceBits(cmf, CMFV_JACKET, ge, ScaleCompanyManagerFaceValue(CMFV_JACKET, ge, GB(face, 20, 2)));
SetCompanyManagerFaceBits(cmf, CMFV_COLLAR, ge, ScaleCompanyManagerFaceValue(CMFV_COLLAR, ge, GB(face, 22, 2)));
SetCompanyManagerFaceBits(cmf, CMFV_GLASSES, ge, GB(face, 28, 1));
const FaceSpec *spec = GetCompanyManagerFaceSpec(cmf.style);
FaceVars vars = spec->GetFaceVars();
cmf.style_label = spec->label;
if (auto var = FindFaceVar(vars, FaceVarType::Toggle, STR_FACE_GLASSES); var != nullptr) var->SetBits(cmf, GB(face, 28, 3) <= 1);
if (auto var = FindFaceVar(vars, FaceVarType::Palette, STR_FACE_EYECOLOUR); var != nullptr) var->SetBits(cmf, ClampU(GB(face, 20, 3), 5, 7) - 5);
if (auto var = FindFaceVar(vars, FaceVarType::Sprite, STR_FACE_CHIN); var != nullptr) var->SetBits(cmf, var->ScaleBits(GB(face, 4, 2)));
if (auto var = FindFaceVar(vars, FaceVarType::Sprite, STR_FACE_EYEBROWS); var != nullptr) var->SetBits(cmf, var->ScaleBits(GB(face, 6, 4)));
if (auto var = FindFaceVar(vars, FaceVarType::Sprite, STR_FACE_HAIR); var != nullptr) var->SetBits(cmf, var->ScaleBits(GB(face, 16, 4)));
if (auto var = FindFaceVar(vars, FaceVarType::Sprite, STR_FACE_JACKET); var != nullptr) var->SetBits(cmf, var->ScaleBits(GB(face, 20, 2)));
if (auto var = FindFaceVar(vars, FaceVarType::Sprite, STR_FACE_COLLAR); var != nullptr) var->SetBits(cmf, var->ScaleBits(GB(face, 22, 2)));
if (auto var = FindFaceVar(vars, FaceVarType::Sprite, STR_FACE_GLASSES); var != nullptr) var->SetBits(cmf, GB(face, 28, 1));
uint lips = GB(face, 10, 4);
if (!HasBit(ge, GENDER_FEMALE) && lips < 4) {
SetCompanyManagerFaceBits(cmf, CMFV_HAS_MOUSTACHE, ge, true);
SetCompanyManagerFaceBits(cmf, CMFV_MOUSTACHE, ge, std::max(lips, 1U) - 1);
if (cmf.style != 1 && cmf.style != 3 && lips < 4) {
if (auto var = FindFaceVar(vars, FaceVarType::Toggle, STR_FACE_MOUSTACHE); var != nullptr) var->SetBits(cmf, true);
if (auto var = FindFaceVar(vars, FaceVarType::Sprite, STR_FACE_MOUSTACHE); var != nullptr) var->SetBits(cmf, std::max(lips, 1U) - 1);
} else {
if (!HasBit(ge, GENDER_FEMALE)) {
lips = lips * 15 / 16;
lips -= 3;
if (HasBit(ge, ETHNICITY_BLACK) && lips > 8) lips = 0;
} else {
lips = ScaleCompanyManagerFaceValue(CMFV_LIPS, ge, lips);
if (auto var = FindFaceVar(vars, FaceVarType::Sprite, STR_FACE_LIPS); var != nullptr) {
if (cmf.style == 0 || cmf.style == 2) {
lips = lips * 15 / 16;
lips -= 3;
if (cmf.style == 2 && lips > 8) lips = 0;
} else {
lips = var->ScaleBits(lips);
}
var->SetBits(cmf, lips);
}
SetCompanyManagerFaceBits(cmf, CMFV_LIPS, ge, lips);
uint nose = GB(face, 13, 3);
if (ge == GE_WF) {
nose = (nose * 3 >> 3) * 3 >> 2; // There is 'hole' in the nose sprites for females
} else {
nose = ScaleCompanyManagerFaceValue(CMFV_NOSE, ge, nose);
if (auto var = FindFaceVar(vars, FaceVarType::Sprite, STR_FACE_NOSE); var != nullptr) {
uint nose = GB(face, 13, 3);
if (cmf.style == 1) {
nose = (nose * 3 >> 3) * 3 >> 2; // There is 'hole' in the nose sprites for women
} else {
nose = var->ScaleBits(nose);
}
var->SetBits(cmf, nose);
}
SetCompanyManagerFaceBits(cmf, CMFV_NOSE, ge, nose);
}
uint tie_earring = GB(face, 24, 4);
if (!HasBit(ge, GENDER_FEMALE) || tie_earring < 3) { // Not all females have an earring
if (HasBit(ge, GENDER_FEMALE)) SetCompanyManagerFaceBits(cmf, CMFV_HAS_TIE_EARRING, ge, true);
SetCompanyManagerFaceBits(cmf, CMFV_TIE_EARRING, ge, HasBit(ge, GENDER_FEMALE) ? tie_earring : ScaleCompanyManagerFaceValue(CMFV_TIE_EARRING, ge, tie_earring / 2));
if (auto var = FindFaceVar(vars, FaceVarType::Sprite, STR_FACE_TIE); var != nullptr) {
uint tie = GB(face, 24, 4);
var->SetBits(cmf, var->ScaleBits(tie / 2));
}
if (auto var = FindFaceVar(vars, FaceVarType::Sprite, STR_FACE_EARRING); var != nullptr) {
uint earring = GB(face, 24, 4);
if (earring < 3) { // Not all women have an earring
if (auto has_earring = FindFaceVar(vars, FaceVarType::Toggle, STR_FACE_EARRING)) {
has_earring->SetBits(cmf, true);
var->SetBits(cmf, earring);
}
}
}
return cmf;
@ -470,7 +500,8 @@ static const SaveLoad _company_desc[] = {
SLE_CONDVECTOR(CompanyProperties, allow_list, SLE_STR, SLV_COMPANY_ALLOW_LIST, SLV_COMPANY_ALLOW_LIST_V2),
SLEG_CONDSTRUCTLIST("allow_list", SlAllowListData, SLV_COMPANY_ALLOW_LIST_V2, SL_MAX_VERSION),
SLE_VAR(CompanyProperties, face, SLE_UINT32),
SLE_VARNAME(CompanyProperties, face.bits, "face", SLE_UINT32),
SLE_CONDSSTRNAME(CompanyProperties, face.style_label, "face_style", SLE_STR, SLV_FACE_STYLES, SL_MAX_VERSION),
/* money was changed to a 64 bit field in savegame version 1. */
SLE_CONDVAR(CompanyProperties, money, SLE_VAR_I64 | SLE_FILE_I32, SL_MIN_VERSION, SLV_1),

View File

@ -939,7 +939,7 @@ static bool LoadOldCompanyEconomy(LoadgameState &ls, int)
static const OldChunks _company_chunk[] = {
OCL_VAR ( OC_UINT16, 1, &_old_string_id ),
OCL_SVAR( OC_UINT32, Company, name_2 ),
OCL_SVAR( OC_UINT32, Company, face ),
OCL_SVAR( OC_UINT32, Company, face.bits ),
OCL_VAR ( OC_UINT16, 1, &_old_string_id_2 ),
OCL_SVAR( OC_UINT32, Company, president_name_2 ),
@ -992,9 +992,9 @@ static bool LoadOldCompany(LoadgameState &ls, int num)
if (_savegame_type == SGT_TTO) {
/* adjust manager's face */
if (HasBit(c->face, 27) && GB(c->face, 26, 1) == GB(c->face, 19, 1)) {
if (HasBit(c->face.bits, 27) && GB(c->face.bits, 26, 1) == GB(c->face.bits, 19, 1)) {
/* if face would be black in TTD, adjust tie colour and thereby face colour */
ClrBit(c->face, 27);
ClrBit(c->face.bits, 27);
}
/* Company name */

View File

@ -403,6 +403,8 @@ enum SaveLoadVersion : uint16_t {
SLV_FIX_SCC_ENCODED_NEGATIVE, ///< 353 PR#14049 Fix encoding of negative parameters.
SLV_ORDERS_OWNED_BY_ORDERLIST, ///< 354 PR#13948 Orders stored in OrderList, pool removed.
SLV_FACE_STYLES, ///< 355 PR#14319 Addition of face styles, replacing gender and ethnicity.
SL_MAX_VERSION, ///< Highest possible saveload version
};

View File

@ -108,12 +108,18 @@
EnforcePrecondition(false, gender == GENDER_MALE || gender == GENDER_FEMALE);
EnforcePrecondition(false, GetPresidentGender(ScriptCompany::COMPANY_SELF) != gender);
Randomizer &randomizer = ScriptObject::GetRandomizer();
CompanyManagerFace cmf;
GenderEthnicity ge = (GenderEthnicity)((gender == GENDER_FEMALE ? (1 << ::GENDER_FEMALE) : 0) | (randomizer.Next() & (1 << ETHNICITY_BLACK)));
RandomCompanyManagerFaceBits(cmf, ge, false, randomizer);
assert(GetNumCompanyManagerFaceStyles() >= 2); /* At least two styles are needed to fake a gender. */
return ScriptObject::Command<CMD_SET_COMPANY_MANAGER_FACE>::Do(cmf);
/* Company faces no longer have a defined gender, so pick a random face style instead. */
Randomizer &randomizer = ScriptObject::GetRandomizer();
CompanyManagerFace cmf{};
do {
cmf.style = randomizer.Next(GetNumCompanyManagerFaceStyles());
} while ((HasBit(cmf.style, 0) ? GENDER_FEMALE : GENDER_MALE) != gender);
RandomiseCompanyManagerFaceBits(cmf, GetCompanyManagerFaceVars(cmf.style), randomizer);
return ScriptObject::Command<CMD_SET_COMPANY_MANAGER_FACE>::Do(cmf.style, cmf.bits);
}
/* static */ ScriptCompany::Gender ScriptCompany::GetPresidentGender(ScriptCompany::CompanyID company)
@ -121,8 +127,10 @@
company = ResolveCompanyID(company);
if (company == ScriptCompany::COMPANY_INVALID) return GENDER_INVALID;
GenderEthnicity ge = (GenderEthnicity)GetCompanyManagerFaceBits(Company::Get(ScriptCompany::FromScriptCompanyID(company))->face, CMFV_GEN_ETHN, GE_WM);
return HasBit(ge, ::GENDER_FEMALE) ? GENDER_FEMALE : GENDER_MALE;
/* Company faces no longer have a defined gender, so fake one based on the style index. This might not match
* the face appearance. */
const auto &cmf = ::Company::Get(ScriptCompany::FromScriptCompanyID(company))->face;
return HasBit(cmf.style, 0) ? GENDER_FEMALE : GENDER_MALE;
}
/* static */ Money ScriptCompany::GetQuarterlyIncome(ScriptCompany::CompanyID company, SQInteger quarter)

View File

@ -11,6 +11,7 @@ add_files(
build_industry.h
cargo_const.h
clear_land.h
company_face.h
control_codes.h
elrail_data.h
engines.h

View File

@ -0,0 +1,98 @@
/*
* This file is part of OpenTTD.
* OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
* OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* 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 <http://www.gnu.org/licenses/>.
*/
/**
* @file table/company_face.h
* This file contains all definitions for default company faces.
*/
#ifndef TABLE_COMPANY_FACE_H
#define TABLE_COMPANY_FACE_H
#include "../company_manager_face.h"
/* Definitions for default face variables.
* Faces are drawn in the listed order, so sprite layers must be ordered
* according to how the face should be rendered. */
/** Variables of first masculine face. */
static constexpr FaceVar _face_style_1[] = {
{FaceVarType::Toggle, 3, 3, 1, 2, std::pair{1ULL << 6, 1ULL << 7 | 1ULL << 8}, STR_FACE_MOUSTACHE},
{FaceVarType::Toggle, 11, 4, 1, 2, std::pair{1ULL << 13, 0ULL}, STR_FACE_GLASSES},
{FaceVarType::Palette, 2, 5, 2, 3, uint64_t{1ULL << 5}, STR_FACE_EYECOLOUR},
{FaceVarType::Sprite, 0, 0, 0, 1, SpriteID{0x325}},
{FaceVarType::Sprite, 7, 7, 2, 4, SpriteID{0x327}, STR_FACE_CHIN},
{FaceVarType::Sprite, 1, 9, 4, 12, SpriteID{0x32B}, STR_FACE_EYEBROWS},
{FaceVarType::Sprite, 4, 13, 2, 3, SpriteID{0x367}, STR_FACE_MOUSTACHE},
{FaceVarType::Sprite, 6, 13, 4, 12, SpriteID{0x35B}, STR_FACE_LIPS},
{FaceVarType::Sprite, 5, 17, 3, 8, SpriteID{0x349}, STR_FACE_NOSE},
{FaceVarType::Sprite, 0, 20, 4, 9, SpriteID{0x382}, STR_FACE_HAIR},
{FaceVarType::Sprite, 9, 26, 2, 4, SpriteID{0x36E}, STR_FACE_COLLAR},
{FaceVarType::Sprite, 8, 24, 2, 3, SpriteID{0x36B}, STR_FACE_JACKET},
{FaceVarType::Sprite, 10, 28, 3, 6, SpriteID{0x372}, STR_FACE_TIE},
{FaceVarType::Sprite, 12, 31, 1, 2, SpriteID{0x347}, STR_FACE_GLASSES},
};
/** Variables of first feminine face. */
static constexpr FaceVar _face_style_2[] = {
{FaceVarType::Toggle, 9, 3, 1, 2, std::pair{1ULL << 11, 0}, STR_FACE_EARRING},
{FaceVarType::Toggle, 7, 4, 1, 2, std::pair{1ULL << 12, 0}, STR_FACE_GLASSES},
{FaceVarType::Palette, 2, 5, 2, 3, uint64_t{1ULL << 5}, STR_FACE_EYECOLOUR},
{FaceVarType::Sprite, 0, 0, 0, 1, SpriteID{0x326}},
{FaceVarType::Sprite, 0, 0, 0, 1, SpriteID{0x327}},
{FaceVarType::Sprite, 1, 9, 4, 16, SpriteID{0x337}, STR_FACE_EYEBROWS},
{FaceVarType::Sprite, 4, 13, 4, 10, SpriteID{0x351}, STR_FACE_LIPS},
{FaceVarType::Sprite, 3, 17, 3, 4, SpriteID{0x34C}, STR_FACE_NOSE},
{FaceVarType::Sprite, 0, 20, 4, 5, SpriteID{0x38B}, STR_FACE_HAIR},
{FaceVarType::Sprite, 6, 26, 2, 4, SpriteID{0x37B}, STR_FACE_COLLAR},
{FaceVarType::Sprite, 5, 24, 2, 3, SpriteID{0x378}, STR_FACE_JACKET},
{FaceVarType::Sprite, 10, 28, 3, 3, SpriteID{0x37F}, STR_FACE_EARRING},
{FaceVarType::Sprite, 8, 31, 1, 2, SpriteID{0x347}, STR_FACE_GLASSES},
};
/** Variables of second masculine face. */
static constexpr FaceVar _face_style_3[] = {
{FaceVarType::Toggle, 2, 3, 1, 2, std::pair{1ULL << 5, 1ULL << 6 | 1ULL << 7}, STR_FACE_MOUSTACHE},
{FaceVarType::Toggle, 10, 4, 1, 2, std::pair{1ULL << 12, 0ULL}, STR_FACE_GLASSES},
{FaceVarType::Sprite, 0, 0, 0, 1, SpriteID{0x390}},
{FaceVarType::Sprite, 6, 7, 2, 2, SpriteID{0x391}, STR_FACE_CHIN},
{FaceVarType::Sprite, 1, 9, 4, 11, SpriteID{0x39A}, STR_FACE_EYEBROWS},
{FaceVarType::Sprite, 3, 13, 2, 3, SpriteID{0x397}, STR_FACE_MOUSTACHE},
{FaceVarType::Sprite, 5, 13, 4, 9, SpriteID{0x3A5}, STR_FACE_LIPS},
{FaceVarType::Sprite, 4, 17, 3, 4, SpriteID{0x393}, STR_FACE_NOSE},
{FaceVarType::Sprite, 0, 20, 4, 5, SpriteID{0x3D4}, STR_FACE_HAIR},
{FaceVarType::Sprite, 8, 26, 2, 4, SpriteID{0x36E}, STR_FACE_COLLAR},
{FaceVarType::Sprite, 7, 24, 2, 3, SpriteID{0x36B}, STR_FACE_JACKET},
{FaceVarType::Sprite, 9, 28, 3, 6, SpriteID{0x372}, STR_FACE_TIE},
{FaceVarType::Sprite, 11, 31, 1, 2, SpriteID{0x3AE}, STR_FACE_GLASSES},
};
/** Variables of second feminine face. */
static constexpr FaceVar _face_style_4[] = {
{FaceVarType::Toggle, 9, 3, 1, 2, std::pair{1ULL << 10, 0ULL}, STR_FACE_EARRING},
{FaceVarType::Toggle, 7, 4, 1, 2, std::pair{1ULL << 11, 0ULL}, STR_FACE_GLASSES},
{FaceVarType::Sprite, 0, 0, 0, 1, SpriteID{0x3B0}},
{FaceVarType::Sprite, 4, 7, 2, 2, SpriteID{0x3B1}, STR_FACE_CHIN},
{FaceVarType::Sprite, 1, 9, 4, 16, SpriteID{0x3B8}, STR_FACE_EYEBROWS},
{FaceVarType::Sprite, 3, 13, 4, 9, SpriteID{0x3C8}, STR_FACE_LIPS},
{FaceVarType::Sprite, 2, 17, 3, 5, SpriteID{0x3B3}, STR_FACE_NOSE},
{FaceVarType::Sprite, 0, 20, 4, 5, SpriteID{0x3D9}, STR_FACE_HAIR},
{FaceVarType::Sprite, 6, 26, 2, 4, SpriteID{0x37B}, STR_FACE_COLLAR},
{FaceVarType::Sprite, 5, 24, 2, 3, SpriteID{0x378}, STR_FACE_JACKET},
{FaceVarType::Sprite, 10, 28, 3, 3, SpriteID{0x3D1}, STR_FACE_EARRING},
{FaceVarType::Sprite, 8, 31, 1, 2, SpriteID{0x3AE}, STR_FACE_GLASSES},
};
/** Original face styles. */
static FaceSpec _original_faces[] = {
{"default/face1", _face_style_1},
{"default/face2", _face_style_2},
{"default/face3", _face_style_3},
{"default/face4", _face_style_4},
};
#endif /* TABLE_COMPANY_FACE_H */

View File

@ -268,13 +268,11 @@ min = 1
max = 512
cat = SC_EXPERT
[SDTG_VAR]
[SDTG_SSTR]
name = ""player_face""
type = SLE_UINT32
type = SLE_STR
var = _company_manager_face
def = 0
min = 0
max = 0xFFFFFFFF
def = """"
cat = SC_BASIC
[SDTG_VAR]

View File

@ -97,8 +97,6 @@ enum SelectCompanyLiveryWidgets : WidgetID {
/**
* Widgets of the #SelectCompanyManagerFaceWindow class.
* Do not change the order of the widgets from WID_SCMF_HAS_MOUSTACHE_EARRING to WID_SCMF_GLASSES_R,
* this order is needed for the WE_CLICK event of DrawFaceStringLabel().
*/
enum SelectCompanyManagerFaceWidgets : WidgetID {
WID_SCMF_CAPTION, ///< Caption of window.
@ -106,65 +104,18 @@ enum SelectCompanyManagerFaceWidgets : WidgetID {
WID_SCMF_SELECT_FACE, ///< Select face.
WID_SCMF_CANCEL, ///< Cancel.
WID_SCMF_ACCEPT, ///< Accept.
WID_SCMF_MALE, ///< Male button in the simple view.
WID_SCMF_FEMALE, ///< Female button in the simple view.
WID_SCMF_MALE2, ///< Male button in the advanced view.
WID_SCMF_FEMALE2, ///< Female button in the advanced view.
WID_SCMF_SEL_LOADSAVE, ///< Selection to display the load/save/number buttons in the advanced view.
WID_SCMF_SEL_MALEFEMALE, ///< Selection to display the male/female buttons in the simple view.
WID_SCMF_SEL_PARTS, ///< Selection to display the buttons for setting each part of the face in the advanced view.
WID_SCMF_SEL_PARTS, ///< Selection to display the buttons for setting each part of the face in the advanced view.
WID_SCMF_SEL_RESIZE, ///< Selection to display the resize button.
WID_SCMF_RANDOM_NEW_FACE, ///< Create random new face.
WID_SCMF_TOGGLE_LARGE_SMALL_BUTTON, ///< Toggle for large or small.
WID_SCMF_FACE, ///< Current face.
WID_SCMF_LOAD, ///< Load face.
WID_SCMF_FACECODE, ///< Get the face code.
WID_SCMF_SAVE, ///< Save face.
WID_SCMF_HAS_MOUSTACHE_EARRING_TEXT, ///< Text about moustache and earring.
WID_SCMF_TIE_EARRING_TEXT, ///< Text about tie and earring.
WID_SCMF_LIPS_MOUSTACHE_TEXT, ///< Text about lips and moustache.
WID_SCMF_HAS_GLASSES_TEXT, ///< Text about glasses.
WID_SCMF_HAIR_TEXT, ///< Text about hair.
WID_SCMF_EYEBROWS_TEXT, ///< Text about eyebrows.
WID_SCMF_EYECOLOUR_TEXT, ///< Text about eyecolour.
WID_SCMF_GLASSES_TEXT, ///< Text about glasses.
WID_SCMF_NOSE_TEXT, ///< Text about nose.
WID_SCMF_CHIN_TEXT, ///< Text about chin.
WID_SCMF_JACKET_TEXT, ///< Text about jacket.
WID_SCMF_COLLAR_TEXT, ///< Text about collar.
WID_SCMF_ETHNICITY_EUR, ///< Text about ethnicity european.
WID_SCMF_ETHNICITY_AFR, ///< Text about ethnicity african.
WID_SCMF_HAS_MOUSTACHE_EARRING, ///< Has moustache or earring.
WID_SCMF_HAS_GLASSES, ///< Has glasses.
WID_SCMF_EYECOLOUR_L, ///< Eyecolour left.
WID_SCMF_EYECOLOUR, ///< Eyecolour.
WID_SCMF_EYECOLOUR_R, ///< Eyecolour right.
WID_SCMF_CHIN_L, ///< Chin left.
WID_SCMF_CHIN, ///< Chin.
WID_SCMF_CHIN_R, ///< Chin right.
WID_SCMF_EYEBROWS_L, ///< Eyebrows left.
WID_SCMF_EYEBROWS, ///< Eyebrows.
WID_SCMF_EYEBROWS_R, ///< Eyebrows right.
WID_SCMF_LIPS_MOUSTACHE_L, ///< Lips / Moustache left.
WID_SCMF_LIPS_MOUSTACHE, ///< Lips / Moustache.
WID_SCMF_LIPS_MOUSTACHE_R, ///< Lips / Moustache right.
WID_SCMF_NOSE_L, ///< Nose left.
WID_SCMF_NOSE, ///< Nose.
WID_SCMF_NOSE_R, ///< Nose right.
WID_SCMF_HAIR_L, ///< Hair left.
WID_SCMF_HAIR, ///< Hair.
WID_SCMF_HAIR_R, ///< Hair right.
WID_SCMF_JACKET_L, ///< Jacket left.
WID_SCMF_JACKET, ///< Jacket.
WID_SCMF_JACKET_R, ///< Jacket right.
WID_SCMF_COLLAR_L, ///< Collar left.
WID_SCMF_COLLAR, ///< Collar.
WID_SCMF_COLLAR_R, ///< Collar right.
WID_SCMF_TIE_EARRING_L, ///< Tie / Earring left.
WID_SCMF_TIE_EARRING, ///< Tie / Earring.
WID_SCMF_TIE_EARRING_R, ///< Tie / Earring right.
WID_SCMF_GLASSES_L, ///< Glasses left.
WID_SCMF_GLASSES, ///< Glasses.
WID_SCMF_GLASSES_R, ///< Glasses right.
WID_SCMF_STYLE, ///< Style selector widget.
WID_SCMF_PARTS, ///< Face configuration parts widget.
WID_SCMF_PARTS_SCROLLBAR, ///< Scrollbar for configuration parts widget.
};
/** Widgets of the #CompanyInfrastructureWindow class. */