Merge remote-tracking branch 'upstream/master'

This commit is contained in:
dP
2025-09-28 02:33:49 +05:00
926 changed files with 37902 additions and 27369 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"
@@ -39,15 +40,18 @@
#include "timer/timer.h"
#include "timer/timer_game_economy.h"
#include "timer/timer_game_tick.h"
#include "timer/timer_window.h"
#include "widgets/statusbar_widget.h"
#include "table/strings.h"
#include "table/company_face.h"
#include "cargo_type.h"
#include "citymania/cm_hotkeys.hpp"
#include "citymania/cm_console_cmds.hpp"
#include "safeguards.h"
void ClearEnginesHiddenFlagOfCompany(CompanyID cid);
@@ -55,8 +59,8 @@ 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.
ReferenceThroughBaseContainer<std::array<Colours, MAX_COMPANIES>> _company_colours; ///< NOSAVE: can be determined from company structs.
CompanyManagerFace _company_manager_face; ///< for company manager face storage in openttd.cfg
TypedIndexContainer<std::array<Colours, MAX_COMPANIES>, CompanyID> _company_colours; ///< NOSAVE: can be determined from company structs.
std::string _company_manager_face; ///< for company manager face storage in openttd.cfg
uint _cur_company_tick_index; ///< used to generate a name for one company that doesn't have a name yet per tick
CompanyPool _company_pool("Company"); ///< Pool of companies.
@@ -147,6 +151,8 @@ void SetLocalCompany(CompanyID new_company)
MarkWholeScreenDirty();
InvalidateWindowClassesData(WC_SIGN_LIST, -1);
InvalidateWindowClassesData(WC_GOALS_LIST);
InvalidateWindowClassesData(WC_COMPANY_COLOUR, -1);
ResetVehicleColourMap();
}
/**
@@ -156,8 +162,18 @@ void SetLocalCompany(CompanyID new_company)
*/
TextColour GetDrawStringCompanyColour(CompanyID company)
{
if (!Company::IsValidID(company)) return (TextColour)GetColourGradient(COLOUR_WHITE, SHADE_NORMAL) | TC_IS_PALETTE_COLOUR;
return (TextColour)GetColourGradient(_company_colours[company], SHADE_NORMAL) | TC_IS_PALETTE_COLOUR;
if (!Company::IsValidID(company)) return GetColourGradient(COLOUR_WHITE, SHADE_NORMAL).ToTextColour();
return GetColourGradient(_company_colours[company], SHADE_NORMAL).ToTextColour();
}
/**
* Get the palette for recolouring with a company colour.
* @param company Company to get the colour of.
* @return Palette for recolouring.
*/
PaletteID GetCompanyPalette(CompanyID company)
{
return GetColourPalette(_company_colours[company]);
}
/**
@@ -168,7 +184,7 @@ TextColour GetDrawStringCompanyColour(CompanyID company)
*/
void DrawCompanyIcon(CompanyID c, int x, int y)
{
DrawSprite(SPR_COMPANY_ICON, COMPANY_SPRITE_COLOUR(c), x, y);
DrawSprite(SPR_COMPANY_ICON, GetCompanyPalette(c), x, y);
}
/**
@@ -179,41 +195,49 @@ 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;
}
static CompanyMask _dirty_company_finances{}; ///< Bitmask of company finances that should be marked dirty.
/**
* Refresh all windows owned by a company.
* Mark all finance windows owned by a company as needing a refresh.
* The actual refresh is deferred until the end of the gameloop to reduce duplicated work.
* @param company Company that changed, and needs its windows refreshed.
*/
void InvalidateCompanyWindows(const Company *company)
{
CompanyID cid = company->index;
if (cid == _local_company) SetWindowWidgetDirty(WC_STATUS_BAR, 0, WID_S_RIGHT);
SetWindowDirty(WC_FINANCES, cid);
_dirty_company_finances.Set(cid);
}
/**
* Refresh all company finance windows previously marked dirty.
*/
static const IntervalTimer<TimerWindow> invalidate_company_windows_interval(std::chrono::milliseconds(1), [](auto) {
for (CompanyID cid : _dirty_company_finances) {
if (cid == _local_company) SetWindowWidgetDirty(WC_STATUS_BAR, 0, WID_S_RIGHT);
Window *w = FindWindowById(WC_FINANCES, cid);
if (w != nullptr) {
w->SetWidgetDirty(WID_CF_EXPS_PRICE3);
w->SetWidgetDirty(WID_CF_OWN_VALUE);
w->SetWidgetDirty(WID_CF_LOAN_VALUE);
w->SetWidgetDirty(WID_CF_BALANCE_VALUE);
w->SetWidgetDirty(WID_CF_MAXLOAN_VALUE);
}
SetWindowWidgetDirty(WC_COMPANY, cid, WID_C_DESC_COMPANY_VALUE);
}
_dirty_company_finances.Reset();
});
/**
* Get the amount of money that a company has available, or INT64_MAX
* if there is no such valid company.
@@ -611,11 +635,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);
@@ -790,7 +818,7 @@ void OnTick_Companies()
* A year has passed, update the economic data of all companies, and perhaps show the
* financial overview window of the local company.
*/
static IntervalTimer<TimerGameEconomy> _economy_companies_yearly({TimerGameEconomy::YEAR, TimerGameEconomy::Priority::COMPANY}, [](auto)
static const IntervalTimer<TimerGameEconomy> _economy_companies_yearly({TimerGameEconomy::YEAR, TimerGameEconomy::Priority::COMPANY}, [](auto)
{
/* Copy statistics */
for (Company *c : Company::Iterate()) {
@@ -904,7 +932,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 */
@@ -1028,15 +1061,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();
@@ -1355,3 +1393,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 label 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;
}