2 * This file is part of OpenTTD.
3 * 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.
4 * 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.
5 * 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/>.
10 * All actions handling saving and loading of the settings/configuration goes on in this file.
11 * The file consists of three parts:
13 * <li>Parsing the configuration file (openttd.cfg). This is achieved with the ini_ functions which
14 * handle various types, such as normal 'key = value' pairs, lists and value combinations of
15 * lists, strings, integers, 'bit'-masks and element selections.
16 * <li>Handle reading and writing to the setting-structures from inside the game either from
17 * the console for example or through the gui with CMD_ functions.
18 * <li>Handle saving/loading of the PATS chunk inside the savegame.
26 #include "settings_table.h"
29 #include "network/network.h"
30 #include "network/network_func.h"
31 #include "network/core/config.h"
32 #include "command_func.h"
33 #include "console_func.h"
35 #include "window_func.h"
36 #include "company_func.h"
40 #include "settings_func.h"
42 #include "ai/ai_config.hpp"
43 #include "game/game_config.hpp"
44 #include "newgrf_config.h"
46 #include "fileio_func.h"
48 #include "table/strings.h"
50 #include "safeguards.h"
52 ClientSettings _settings_client
;
53 GameSettings _settings_game
; ///< Game settings of a running game or the scenario editor.
54 GameSettings _settings_newgame
; ///< Game settings for new games (updated from the intro screen).
55 VehicleDefaultSettings _old_vds
; ///< Used for loading default vehicles settings from old savegames.
56 std::string _config_file
; ///< Configuration file of OpenTTD.
57 std::string _private_file
; ///< Private configuration file of OpenTTD.
58 std::string _secrets_file
; ///< Secrets configuration file of OpenTTD.
60 typedef std::list
<ErrorMessageData
> ErrorList
;
61 static ErrorList _settings_error_list
; ///< Errors while loading minimal settings.
64 * List of all the generic setting tables.
66 * There are a few tables that are special and not processed like the rest:
67 * - _currency_settings
71 * As such, they are not part of this list.
73 static auto &GenericSettingTables()
75 static const SettingTable _generic_setting_tables
[] = {
84 _news_display_settings
,
85 _pathfinding_settings
,
89 return _generic_setting_tables
;
93 * List of all the private setting tables.
95 static auto &PrivateSettingTables()
97 static const SettingTable _private_setting_tables
[] = {
98 _network_private_settings
,
100 return _private_setting_tables
;
104 * List of all the secrets setting tables.
106 static auto &SecretSettingTables()
108 static const SettingTable _secrets_setting_tables
[] = {
109 _network_secrets_settings
,
111 return _secrets_setting_tables
;
114 typedef void SettingDescProc(IniFile
&ini
, const SettingTable
&desc
, const char *grpname
, void *object
, bool only_startup
);
115 typedef void SettingDescProcList(IniFile
&ini
, const char *grpname
, StringList
&list
);
117 static bool IsSignedVarMemType(VarType vt
)
119 switch (GetVarMemType(vt
)) {
130 * IniFile to store a configuration.
132 class ConfigIniFile
: public IniFile
{
134 inline static const char * const list_group_names
[] = {
138 "server_bind_addresses",
143 ConfigIniFile(const std::string
&filename
) : IniFile(list_group_names
)
145 this->LoadFromDisk(filename
, NO_DIRECTORY
);
152 * Sometimes we move settings between different ini-files, as we need to know
153 * when we have to load/remove it from the old versus reading it from the new
154 * location. These versions assist with situations like that.
156 enum IniFileVersion
: uint32
{
157 IFV_0
, ///< 0 All versions prior to introduction.
158 IFV_PRIVATE_SECRETS
, ///< 1 PR#9298 Moving of settings from openttd.cfg to private.cfg / secrets.cfg.
160 IFV_MAX_VERSION
, ///< Highest possible ini-file version.
163 const uint16 INIFILE_VERSION
= (IniFileVersion
)(IFV_MAX_VERSION
- 1); ///< Current ini-file version of OpenTTD.
166 * Find the index value of a ONEofMANY type in a string separated by |
167 * @param str the current value of the setting for which a value needs found
168 * @param len length of the string
169 * @param many full domain of values the ONEofMANY setting can have
170 * @return the integer index of the full-list, or -1 if not found
172 size_t OneOfManySettingDesc::ParseSingleValue(const char *str
, size_t len
, const std::vector
<std::string
> &many
)
174 /* check if it's an integer */
175 if (isdigit(*str
)) return strtoul(str
, nullptr, 0);
178 for (auto one
: many
) {
179 if (one
.size() == len
&& strncmp(one
.c_str(), str
, len
) == 0) return idx
;
187 * Find the set-integer value MANYofMANY type in a string
188 * @param many full domain of values the MANYofMANY setting can have
189 * @param str the current string value of the setting, each individual
190 * of separated by a whitespace,tab or | character
191 * @return the 'fully' set integer, or -1 if a set is not found
193 static size_t LookupManyOfMany(const std::vector
<std::string
> &many
, const char *str
)
200 /* skip "whitespace" */
201 while (*str
== ' ' || *str
== '\t' || *str
== '|') str
++;
202 if (*str
== 0) break;
205 while (*s
!= 0 && *s
!= ' ' && *s
!= '\t' && *s
!= '|') s
++;
207 r
= OneOfManySettingDesc::ParseSingleValue(str
, s
- str
, many
);
208 if (r
== (size_t)-1) return r
;
210 SetBit(res
, (uint8
)r
); // value found, set it
218 * Parse an integerlist string and set each found value
219 * @param p the string to be parsed. Each element in the list is separated by a
220 * comma or a space character
221 * @param items pointer to the integerlist-array that will be filled with values
222 * @param maxitems the maximum number of elements the integerlist-array has
223 * @return returns the number of items found, or -1 on an error
226 static int ParseIntList(const char *p
, T
*items
, int maxitems
)
228 int n
= 0; // number of items read so far
229 bool comma
= false; // do we accept comma?
234 /* Do not accept multiple commas between numbers */
235 if (!comma
) return -1;
244 if (n
== maxitems
) return -1; // we don't accept that many numbers
246 unsigned long v
= strtoul(p
, &end
, 0);
247 if (p
== end
) return -1; // invalid character (not a number)
248 if (sizeof(T
) < sizeof(v
)) v
= Clamp
<unsigned long>(v
, std::numeric_limits
<T
>::min(), std::numeric_limits
<T
>::max());
250 p
= end
; // first non-number
251 comma
= true; // we accept comma now
257 /* If we have read comma but no number after it, fail.
258 * We have read comma when (n != 0) and comma is not allowed */
259 if (n
!= 0 && !comma
) return -1;
265 * Load parsed string-values into an integer-array (intlist)
266 * @param str the string that contains the values (and will be parsed)
267 * @param array pointer to the integer-arrays that will be filled
268 * @param nelems the number of elements the array holds. Maximum is 64 elements
269 * @param type the type of elements the array holds (eg INT8, UINT16, etc.)
270 * @return return true on success and false on error
272 static bool LoadIntList(const char *str
, void *array
, int nelems
, VarType type
)
274 unsigned long items
[64];
277 if (str
== nullptr) {
278 memset(items
, 0, sizeof(items
));
281 nitems
= ParseIntList(str
, items
, lengthof(items
));
282 if (nitems
!= nelems
) return false;
289 for (i
= 0; i
!= nitems
; i
++) ((byte
*)array
)[i
] = items
[i
];
294 for (i
= 0; i
!= nitems
; i
++) ((uint16
*)array
)[i
] = items
[i
];
299 for (i
= 0; i
!= nitems
; i
++) ((uint32
*)array
)[i
] = items
[i
];
302 default: NOT_REACHED();
309 * Convert an integer-array (intlist) to a string representation. Each value
310 * is separated by a comma or a space character
311 * @param buf output buffer where the string-representation will be stored
312 * @param last last item to write to in the output buffer
313 * @param array pointer to the integer-arrays that is read from
314 * @param nelems the number of elements the array holds.
315 * @param type the type of elements the array holds (eg INT8, UINT16, etc.)
317 void ListSettingDesc::FormatValue(char *buf
, const char *last
, const void *object
) const
319 const byte
*p
= static_cast<const byte
*>(GetVariableAddress(object
, this->save
));
322 for (i
= 0; i
!= this->save
.length
; i
++) {
323 switch (GetVarMemType(this->save
.conv
)) {
325 case SLE_VAR_I8
: v
= *(const int8
*)p
; p
+= 1; break;
326 case SLE_VAR_U8
: v
= *(const uint8
*)p
; p
+= 1; break;
327 case SLE_VAR_I16
: v
= *(const int16
*)p
; p
+= 2; break;
328 case SLE_VAR_U16
: v
= *(const uint16
*)p
; p
+= 2; break;
329 case SLE_VAR_I32
: v
= *(const int32
*)p
; p
+= 4; break;
330 case SLE_VAR_U32
: v
= *(const uint32
*)p
; p
+= 4; break;
331 default: NOT_REACHED();
333 if (IsSignedVarMemType(this->save
.conv
)) {
334 buf
+= seprintf(buf
, last
, (i
== 0) ? "%d" : ",%d", v
);
336 buf
+= seprintf(buf
, last
, (i
== 0) ? "%u" : ",%u", v
);
341 char *OneOfManySettingDesc::FormatSingleValue(char *buf
, const char *last
, uint id
) const
343 if (id
>= this->many
.size()) {
344 return buf
+ seprintf(buf
, last
, "%d", id
);
346 return strecpy(buf
, this->many
[id
].c_str(), last
);
349 void OneOfManySettingDesc::FormatValue(char *buf
, const char *last
, const void *object
) const
351 uint id
= (uint
)this->Read(object
);
352 this->FormatSingleValue(buf
, last
, id
);
355 void ManyOfManySettingDesc::FormatValue(char *buf
, const char *last
, const void *object
) const
357 uint bitmask
= (uint
)this->Read(object
);
359 for (uint id
: SetBitIterator(bitmask
)) {
360 if (!first
) buf
= strecpy(buf
, "|", last
);
361 buf
= this->FormatSingleValue(buf
, last
, id
);
367 * Convert a string representation (external) of an integer-like setting to an integer.
368 * @param str Input string that will be parsed based on the type of desc.
369 * @return The value from the parse string, or the default value of the setting.
371 size_t IntSettingDesc::ParseValue(const char *str
) const
374 size_t val
= strtoul(str
, &end
, 0);
376 ErrorMessageData
msg(STR_CONFIG_ERROR
, STR_CONFIG_ERROR_INVALID_VALUE
);
377 msg
.SetDParamStr(0, str
);
378 msg
.SetDParamStr(1, this->GetName());
379 _settings_error_list
.push_back(msg
);
383 ErrorMessageData
msg(STR_CONFIG_ERROR
, STR_CONFIG_ERROR_TRAILING_CHARACTERS
);
384 msg
.SetDParamStr(0, this->GetName());
385 _settings_error_list
.push_back(msg
);
390 size_t OneOfManySettingDesc::ParseValue(const char *str
) const
392 size_t r
= OneOfManySettingDesc::ParseSingleValue(str
, strlen(str
), this->many
);
393 /* if the first attempt of conversion from string to the appropriate value fails,
394 * look if we have defined a converter from old value to new value. */
395 if (r
== (size_t)-1 && this->many_cnvt
!= nullptr) r
= this->many_cnvt(str
);
396 if (r
!= (size_t)-1) return r
; // and here goes converted value
398 ErrorMessageData
msg(STR_CONFIG_ERROR
, STR_CONFIG_ERROR_INVALID_VALUE
);
399 msg
.SetDParamStr(0, str
);
400 msg
.SetDParamStr(1, this->GetName());
401 _settings_error_list
.push_back(msg
);
405 size_t ManyOfManySettingDesc::ParseValue(const char *str
) const
407 size_t r
= LookupManyOfMany(this->many
, str
);
408 if (r
!= (size_t)-1) return r
;
409 ErrorMessageData
msg(STR_CONFIG_ERROR
, STR_CONFIG_ERROR_INVALID_VALUE
);
410 msg
.SetDParamStr(0, str
);
411 msg
.SetDParamStr(1, this->GetName());
412 _settings_error_list
.push_back(msg
);
416 size_t BoolSettingDesc::ParseValue(const char *str
) const
418 if (strcmp(str
, "true") == 0 || strcmp(str
, "on") == 0 || strcmp(str
, "1") == 0) return true;
419 if (strcmp(str
, "false") == 0 || strcmp(str
, "off") == 0 || strcmp(str
, "0") == 0) return false;
421 ErrorMessageData
msg(STR_CONFIG_ERROR
, STR_CONFIG_ERROR_INVALID_VALUE
);
422 msg
.SetDParamStr(0, str
);
423 msg
.SetDParamStr(1, this->GetName());
424 _settings_error_list
.push_back(msg
);
429 * Make the value valid and then write it to the setting.
430 * See #MakeValidValid and #Write for more details.
431 * @param object The object the setting is to be saved in.
432 * @param val Signed version of the new value.
434 void IntSettingDesc::MakeValueValidAndWrite(const void *object
, int32 val
) const
436 this->MakeValueValid(val
);
437 this->Write(object
, val
);
441 * Make the value valid given the limitations of this setting.
443 * In the case of int settings this is ensuring the value is between the minimum and
444 * maximum value, with a special case for 0 if SF_GUI_0_IS_SPECIAL is set.
445 * This is generally done by clamping the value so it is within the allowed value range.
446 * However, for SF_GUI_DROPDOWN the default is used when the value is not valid.
447 * @param val The value to make valid.
449 void IntSettingDesc::MakeValueValid(int32
&val
) const
451 /* We need to take special care of the uint32 type as we receive from the function
452 * a signed integer. While here also bail out on 64-bit settings as those are not
453 * supported. Unsigned 8 and 16-bit variables are safe since they fit into a signed
455 * TODO: Support 64-bit settings/variables; requires 64 bit over command protocol! */
456 switch (GetVarMemType(this->save
.conv
)) {
457 case SLE_VAR_NULL
: return;
464 /* Override the minimum value. No value below this->min, except special value 0 */
465 if (!(this->flags
& SF_GUI_0_IS_SPECIAL
) || val
!= 0) {
466 if (!(this->flags
& SF_GUI_DROPDOWN
)) {
467 /* Clamp value-type setting to its valid range */
468 val
= Clamp(val
, this->min
, this->max
);
469 } else if (val
< this->min
|| val
> (int32
)this->max
) {
470 /* Reset invalid discrete setting (where different values change gameplay) to its default value */
477 /* Override the minimum value. No value below this->min, except special value 0 */
478 uint32 uval
= (uint32
)val
;
479 if (!(this->flags
& SF_GUI_0_IS_SPECIAL
) || uval
!= 0) {
480 if (!(this->flags
& SF_GUI_DROPDOWN
)) {
481 /* Clamp value-type setting to its valid range */
482 uval
= ClampU(uval
, this->min
, this->max
);
483 } else if (uval
< (uint
)this->min
|| uval
> this->max
) {
484 /* Reset invalid discrete setting to its default value */
485 uval
= (uint32
)this->def
;
493 default: NOT_REACHED();
498 * Set the value of a setting.
499 * @param object The object the setting is to be saved in.
500 * @param val Signed version of the new value.
502 void IntSettingDesc::Write(const void *object
, int32 val
) const
504 void *ptr
= GetVariableAddress(object
, this->save
);
505 WriteValue(ptr
, this->save
.conv
, (int64
)val
);
509 * Read the integer from the the actual setting.
510 * @param object The object the setting is to be saved in.
511 * @return The value of the saved integer.
513 int32
IntSettingDesc::Read(const void *object
) const
515 void *ptr
= GetVariableAddress(object
, this->save
);
516 return (int32
)ReadValue(ptr
, this->save
.conv
);
520 * Make the value valid given the limitations of this setting.
522 * In the case of string settings this is ensuring the string contains only accepted
523 * Utf8 characters and is at most the maximum length defined in this setting.
524 * @param str The string to make valid.
526 void StringSettingDesc::MakeValueValid(std::string
&str
) const
528 if (this->max_length
== 0 || str
.size() < this->max_length
) return;
530 /* In case a maximum length is imposed by the setting, the length
531 * includes the '\0' termination for network transfer purposes.
532 * Also ensure the string is valid after chopping of some bytes. */
533 std::string
stdstr(str
, this->max_length
- 1);
534 str
.assign(StrMakeValid(stdstr
, SVS_NONE
));
538 * Write a string to the actual setting.
539 * @param object The object the setting is to be saved in.
540 * @param str The string to save.
542 void StringSettingDesc::Write(const void *object
, const std::string
&str
) const
544 reinterpret_cast<std::string
*>(GetVariableAddress(object
, this->save
))->assign(str
);
548 * Read the string from the the actual setting.
549 * @param object The object the setting is to be saved in.
550 * @return The value of the saved string.
552 const std::string
&StringSettingDesc::Read(const void *object
) const
554 return *reinterpret_cast<std::string
*>(GetVariableAddress(object
, this->save
));
558 * Load values from a group of an IniFile structure into the internal representation
559 * @param ini pointer to IniFile structure that holds administrative information
560 * @param settings_table table with SettingDesc structures whose internally pointed variables will
562 * @param grpname the group of the IniFile to search in for the new values
563 * @param object pointer to the object been loaded
564 * @param only_startup load only the startup settings set
566 static void IniLoadSettings(IniFile
&ini
, const SettingTable
&settings_table
, const char *grpname
, void *object
, bool only_startup
)
569 IniGroup
*group_def
= ini
.GetGroup(grpname
);
571 for (auto &desc
: settings_table
) {
572 const SettingDesc
*sd
= GetSettingDesc(desc
);
573 if (!SlIsObjectCurrentlyValid(sd
->save
.version_from
, sd
->save
.version_to
)) continue;
574 if (sd
->startup
!= only_startup
) continue;
576 /* For settings.xx.yy load the settings from [xx] yy = ? */
577 std::string s
{ sd
->GetName() };
578 auto sc
= s
.find('.');
579 if (sc
!= std::string::npos
) {
580 group
= ini
.GetGroup(s
.substr(0, sc
));
581 s
= s
.substr(sc
+ 1);
586 IniItem
*item
= group
->GetItem(s
, false);
587 if (item
== nullptr && group
!= group_def
) {
588 /* For settings.xx.yy load the settings from [settings] yy = ? in case the previous
589 * did not exist (e.g. loading old config files with a [settings] section */
590 item
= group_def
->GetItem(s
, false);
592 if (item
== nullptr) {
593 /* For settings.xx.zz.yy load the settings from [zz] yy = ? in case the previous
594 * did not exist (e.g. loading old config files with a [yapf] section */
596 if (sc
!= std::string::npos
) item
= ini
.GetGroup(s
.substr(0, sc
))->GetItem(s
.substr(sc
+ 1), false);
599 sd
->ParseValue(item
, object
);
603 void IntSettingDesc::ParseValue(const IniItem
*item
, void *object
) const
605 size_t val
= (item
== nullptr) ? this->def
: this->ParseValue(item
->value
.has_value() ? item
->value
->c_str() : "");
606 this->MakeValueValidAndWrite(object
, (int32
)val
);
609 void StringSettingDesc::ParseValue(const IniItem
*item
, void *object
) const
611 std::string str
= (item
== nullptr) ? this->def
: item
->value
.value_or("");
612 this->MakeValueValid(str
);
613 this->Write(object
, str
);
616 void ListSettingDesc::ParseValue(const IniItem
*item
, void *object
) const
618 const char *str
= (item
== nullptr) ? this->def
: item
->value
.has_value() ? item
->value
->c_str() : nullptr;
619 void *ptr
= GetVariableAddress(object
, this->save
);
620 if (!LoadIntList(str
, ptr
, this->save
.length
, GetVarMemType(this->save
.conv
))) {
621 ErrorMessageData
msg(STR_CONFIG_ERROR
, STR_CONFIG_ERROR_ARRAY
);
622 msg
.SetDParamStr(0, this->GetName());
623 _settings_error_list
.push_back(msg
);
626 LoadIntList(this->def
, ptr
, this->save
.length
, GetVarMemType(this->save
.conv
));
631 * Save the values of settings to the inifile.
632 * @param ini pointer to IniFile structure
633 * @param sd read-only SettingDesc structure which contains the unmodified,
634 * loaded values of the configuration file and various information about it
635 * @param grpname holds the name of the group (eg. [network]) where these will be saved
636 * @param object pointer to the object been saved
637 * The function works as follows: for each item in the SettingDesc structure we
638 * have a look if the value has changed since we started the game (the original
639 * values are reloaded when saving). If settings indeed have changed, we get
640 * these and save them.
642 static void IniSaveSettings(IniFile
&ini
, const SettingTable
&settings_table
, const char *grpname
, void *object
, bool)
644 IniGroup
*group_def
= nullptr, *group
;
648 for (auto &desc
: settings_table
) {
649 const SettingDesc
*sd
= GetSettingDesc(desc
);
650 /* If the setting is not saved to the configuration
651 * file, just continue with the next setting */
652 if (!SlIsObjectCurrentlyValid(sd
->save
.version_from
, sd
->save
.version_to
)) continue;
653 if (sd
->flags
& SF_NOT_IN_CONFIG
) continue;
655 /* XXX - wtf is this?? (group override?) */
656 std::string s
{ sd
->GetName() };
657 auto sc
= s
.find('.');
658 if (sc
!= std::string::npos
) {
659 group
= ini
.GetGroup(s
.substr(0, sc
));
660 s
= s
.substr(sc
+ 1);
662 if (group_def
== nullptr) group_def
= ini
.GetGroup(grpname
);
666 item
= group
->GetItem(s
, true);
668 if (!item
->value
.has_value() || !sd
->IsSameValue(item
, object
)) {
669 /* Value has changed, get the new value and put it into a buffer */
670 sd
->FormatValue(buf
, lastof(buf
), object
);
672 /* The value is different, that means we have to write it to the ini */
673 item
->value
.emplace(buf
);
678 void IntSettingDesc::FormatValue(char *buf
, const char *last
, const void *object
) const
680 uint32 i
= (uint32
)this->Read(object
);
681 seprintf(buf
, last
, IsSignedVarMemType(this->save
.conv
) ? "%d" : "%u", i
);
684 void BoolSettingDesc::FormatValue(char *buf
, const char *last
, const void *object
) const
686 bool val
= this->Read(object
) != 0;
687 strecpy(buf
, val
? "true" : "false", last
);
690 bool IntSettingDesc::IsSameValue(const IniItem
*item
, void *object
) const
692 int32 item_value
= (int32
)this->ParseValue(item
->value
->c_str());
693 int32 object_value
= this->Read(object
);
694 return item_value
== object_value
;
697 void StringSettingDesc::FormatValue(char *buf
, const char *last
, const void *object
) const
699 const std::string
&str
= this->Read(object
);
700 switch (GetVarMemType(this->save
.conv
)) {
701 case SLE_VAR_STR
: strecpy(buf
, str
.c_str(), last
); break;
707 seprintf(buf
, last
, "\"%s\"", str
.c_str());
711 default: NOT_REACHED();
715 bool StringSettingDesc::IsSameValue(const IniItem
*item
, void *object
) const
717 /* The ini parsing removes the quotes, which are needed to retain the spaces in STRQs,
718 * so those values are always different in the parsed ini item than they should be. */
719 if (GetVarMemType(this->save
.conv
) == SLE_VAR_STRQ
) return false;
721 const std::string
&str
= this->Read(object
);
722 return item
->value
->compare(str
) == 0;
725 bool ListSettingDesc::IsSameValue(const IniItem
*item
, void *object
) const
727 /* Checking for equality is way more expensive than just writing the value. */
732 * Loads all items from a 'grpname' section into a list
733 * The list parameter can be a nullptr pointer, in this case nothing will be
734 * saved and a callback function should be defined that will take over the
735 * list-handling and store the data itself somewhere.
736 * @param ini IniFile handle to the ini file with the source data
737 * @param grpname character string identifying the section-header of the ini file that will be parsed
738 * @param list new list with entries of the given section
740 static void IniLoadSettingList(IniFile
&ini
, const char *grpname
, StringList
&list
)
742 IniGroup
*group
= ini
.GetGroup(grpname
);
744 if (group
== nullptr) return;
748 for (const IniItem
*item
= group
->item
; item
!= nullptr; item
= item
->next
) {
749 if (!item
->name
.empty()) list
.push_back(item
->name
);
754 * Saves all items from a list into the 'grpname' section
755 * The list parameter can be a nullptr pointer, in this case a callback function
756 * should be defined that will provide the source data to be saved.
757 * @param ini IniFile handle to the ini file where the destination data is saved
758 * @param grpname character string identifying the section-header of the ini file
759 * @param list pointer to an string(pointer) array that will be used as the
760 * source to be saved into the relevant ini section
762 static void IniSaveSettingList(IniFile
&ini
, const char *grpname
, StringList
&list
)
764 IniGroup
*group
= ini
.GetGroup(grpname
);
766 if (group
== nullptr) return;
769 for (const auto &iter
: list
) {
770 group
->GetItem(iter
, true)->SetValue("");
775 * Load a WindowDesc from config.
776 * @param ini IniFile handle to the ini file with the source data
777 * @param grpname character string identifying the section-header of the ini file that will be parsed
778 * @param desc Destination WindowDesc
780 void IniLoadWindowSettings(IniFile
&ini
, const char *grpname
, void *desc
)
782 IniLoadSettings(ini
, _window_settings
, grpname
, desc
, false);
786 * Save a WindowDesc to config.
787 * @param ini IniFile handle to the ini file where the destination data is saved
788 * @param grpname character string identifying the section-header of the ini file
789 * @param desc Source WindowDesc
791 void IniSaveWindowSettings(IniFile
&ini
, const char *grpname
, void *desc
)
793 IniSaveSettings(ini
, _window_settings
, grpname
, desc
, false);
797 * Check whether the setting is editable in the current gamemode.
798 * @param do_command true if this is about checking a command from the server.
799 * @return true if editable.
801 bool SettingDesc::IsEditable(bool do_command
) const
803 if (!do_command
&& !(this->flags
& SF_NO_NETWORK_SYNC
) && _networking
&& !_network_server
&& !(this->flags
& SF_PER_COMPANY
)) return false;
804 if ((this->flags
& SF_NETWORK_ONLY
) && !_networking
&& _game_mode
!= GM_MENU
) return false;
805 if ((this->flags
& SF_NO_NETWORK
) && _networking
) return false;
806 if ((this->flags
& SF_NEWGAME_ONLY
) &&
807 (_game_mode
== GM_NORMAL
||
808 (_game_mode
== GM_EDITOR
&& !(this->flags
& SF_SCENEDIT_TOO
)))) return false;
809 if ((this->flags
& SF_SCENEDIT_ONLY
) && _game_mode
!= GM_EDITOR
) return false;
814 * Return the type of the setting.
815 * @return type of setting
817 SettingType
SettingDesc::GetType() const
819 if (this->flags
& SF_PER_COMPANY
) return ST_COMPANY
;
820 return (this->flags
& SF_NOT_IN_SAVE
) ? ST_CLIENT
: ST_GAME
;
824 * Get the setting description of this setting as an integer setting.
825 * @return The integer setting description.
827 const IntSettingDesc
*SettingDesc::AsIntSetting() const
829 assert(this->IsIntSetting());
830 return static_cast<const IntSettingDesc
*>(this);
834 * Get the setting description of this setting as a string setting.
835 * @return The string setting description.
837 const StringSettingDesc
*SettingDesc::AsStringSetting() const
839 assert(this->IsStringSetting());
840 return static_cast<const StringSettingDesc
*>(this);
843 void PrepareOldDiffCustom();
844 void HandleOldDiffCustom(bool savegame
);
847 /** Checks if any settings are set to incorrect values, and sets them to correct values in that case. */
848 static void ValidateSettings()
850 /* Do not allow a custom sea level with the original land generator. */
851 if (_settings_newgame
.game_creation
.land_generator
== LG_ORIGINAL
&&
852 _settings_newgame
.difficulty
.quantity_sea_lakes
== CUSTOM_SEA_LEVEL_NUMBER_DIFFICULTY
) {
853 _settings_newgame
.difficulty
.quantity_sea_lakes
= CUSTOM_SEA_LEVEL_MIN_PERCENTAGE
;
857 static void AILoadConfig(IniFile
&ini
, const char *grpname
)
859 IniGroup
*group
= ini
.GetGroup(grpname
);
862 /* Clean any configured AI */
863 for (CompanyID c
= COMPANY_FIRST
; c
< MAX_COMPANIES
; c
++) {
864 AIConfig::GetConfig(c
, AIConfig::SSS_FORCE_NEWGAME
)->Change(nullptr);
867 /* If no group exists, return */
868 if (group
== nullptr) return;
870 CompanyID c
= COMPANY_FIRST
;
871 for (item
= group
->item
; c
< MAX_COMPANIES
&& item
!= nullptr; c
++, item
= item
->next
) {
872 AIConfig
*config
= AIConfig::GetConfig(c
, AIConfig::SSS_FORCE_NEWGAME
);
874 config
->Change(item
->name
.c_str());
875 if (!config
->HasScript()) {
876 if (item
->name
!= "none") {
877 Debug(script
, 0, "The AI by the name '{}' was no longer found, and removed from the list.", item
->name
);
881 if (item
->value
.has_value()) config
->StringToSettings(*item
->value
);
885 static void GameLoadConfig(IniFile
&ini
, const char *grpname
)
887 IniGroup
*group
= ini
.GetGroup(grpname
);
890 /* Clean any configured GameScript */
891 GameConfig::GetConfig(GameConfig::SSS_FORCE_NEWGAME
)->Change(nullptr);
893 /* If no group exists, return */
894 if (group
== nullptr) return;
897 if (item
== nullptr) return;
899 GameConfig
*config
= GameConfig::GetConfig(AIConfig::SSS_FORCE_NEWGAME
);
901 config
->Change(item
->name
.c_str());
902 if (!config
->HasScript()) {
903 if (item
->name
!= "none") {
904 Debug(script
, 0, "The GameScript by the name '{}' was no longer found, and removed from the list.", item
->name
);
908 if (item
->value
.has_value()) config
->StringToSettings(*item
->value
);
912 * Convert a character to a hex nibble value, or \c -1 otherwise.
913 * @param c Character to convert.
914 * @return Hex value of the character, or \c -1 if not a hex digit.
916 static int DecodeHexNibble(char c
)
918 if (c
>= '0' && c
<= '9') return c
- '0';
919 if (c
>= 'A' && c
<= 'F') return c
+ 10 - 'A';
920 if (c
>= 'a' && c
<= 'f') return c
+ 10 - 'a';
925 * Parse a sequence of characters (supposedly hex digits) into a sequence of bytes.
926 * After the hex number should be a \c '|' character.
927 * @param pos First character to convert.
928 * @param[out] dest Output byte array to write the bytes.
929 * @param dest_size Number of bytes in \a dest.
930 * @return Whether reading was successful.
932 static bool DecodeHexText(const char *pos
, uint8
*dest
, size_t dest_size
)
934 while (dest_size
> 0) {
935 int hi
= DecodeHexNibble(pos
[0]);
936 int lo
= (hi
>= 0) ? DecodeHexNibble(pos
[1]) : -1;
937 if (lo
< 0) return false;
938 *dest
++ = (hi
<< 4) | lo
;
946 * Load a GRF configuration
947 * @param ini The configuration to read from.
948 * @param grpname Group name containing the configuration of the GRF.
949 * @param is_static GRF is static.
951 static GRFConfig
*GRFLoadConfig(IniFile
&ini
, const char *grpname
, bool is_static
)
953 IniGroup
*group
= ini
.GetGroup(grpname
);
955 GRFConfig
*first
= nullptr;
956 GRFConfig
**curr
= &first
;
958 if (group
== nullptr) return nullptr;
961 for (item
= group
->item
; item
!= nullptr; item
= item
->next
) {
962 GRFConfig
*c
= nullptr;
964 uint8 grfid_buf
[4], md5sum
[16];
965 const char *filename
= item
->name
.c_str();
966 bool has_grfid
= false;
967 bool has_md5sum
= false;
969 /* Try reading "<grfid>|" and on success, "<md5sum>|". */
970 has_grfid
= DecodeHexText(filename
, grfid_buf
, lengthof(grfid_buf
));
972 filename
+= 1 + 2 * lengthof(grfid_buf
);
973 has_md5sum
= DecodeHexText(filename
, md5sum
, lengthof(md5sum
));
974 if (has_md5sum
) filename
+= 1 + 2 * lengthof(md5sum
);
976 uint32 grfid
= grfid_buf
[0] | (grfid_buf
[1] << 8) | (grfid_buf
[2] << 16) | (grfid_buf
[3] << 24);
978 const GRFConfig
*s
= FindGRFConfig(grfid
, FGCM_EXACT
, md5sum
);
979 if (s
!= nullptr) c
= new GRFConfig(*s
);
981 if (c
== nullptr && !FioCheckFileExists(filename
, NEWGRF_DIR
)) {
982 const GRFConfig
*s
= FindGRFConfig(grfid
, FGCM_NEWEST_VALID
);
983 if (s
!= nullptr) c
= new GRFConfig(*s
);
986 if (c
== nullptr) c
= new GRFConfig(filename
);
988 /* Parse parameters */
989 if (item
->value
.has_value() && !item
->value
->empty()) {
990 int count
= ParseIntList(item
->value
->c_str(), c
->param
, lengthof(c
->param
));
992 SetDParamStr(0, filename
);
993 ShowErrorMessage(STR_CONFIG_ERROR
, STR_CONFIG_ERROR_ARRAY
, WL_CRITICAL
);
996 c
->num_params
= count
;
999 /* Check if item is valid */
1000 if (!FillGRFDetails(c
, is_static
) || HasBit(c
->flags
, GCF_INVALID
)) {
1001 if (c
->status
== GCS_NOT_FOUND
) {
1002 SetDParam(1, STR_CONFIG_ERROR_INVALID_GRF_NOT_FOUND
);
1003 } else if (HasBit(c
->flags
, GCF_UNSAFE
)) {
1004 SetDParam(1, STR_CONFIG_ERROR_INVALID_GRF_UNSAFE
);
1005 } else if (HasBit(c
->flags
, GCF_SYSTEM
)) {
1006 SetDParam(1, STR_CONFIG_ERROR_INVALID_GRF_SYSTEM
);
1007 } else if (HasBit(c
->flags
, GCF_INVALID
)) {
1008 SetDParam(1, STR_CONFIG_ERROR_INVALID_GRF_INCOMPATIBLE
);
1010 SetDParam(1, STR_CONFIG_ERROR_INVALID_GRF_UNKNOWN
);
1013 SetDParamStr(0, StrEmpty(filename
) ? item
->name
: filename
);
1014 ShowErrorMessage(STR_CONFIG_ERROR
, STR_CONFIG_ERROR_INVALID_GRF
, WL_CRITICAL
);
1019 /* Check for duplicate GRFID (will also check for duplicate filenames) */
1020 bool duplicate
= false;
1021 for (const GRFConfig
*gc
= first
; gc
!= nullptr; gc
= gc
->next
) {
1022 if (gc
->ident
.grfid
== c
->ident
.grfid
) {
1023 SetDParamStr(0, c
->filename
);
1024 SetDParamStr(1, gc
->filename
);
1025 ShowErrorMessage(STR_CONFIG_ERROR
, STR_CONFIG_ERROR_DUPLICATE_GRFID
, WL_CRITICAL
);
1036 /* Mark file as static to avoid saving in savegame. */
1037 SetBit(c
->flags
, GCF_STATIC
);
1038 } else if (++num_grfs
> NETWORK_MAX_GRF_COUNT
) {
1039 /* Check we will not load more non-static NewGRFs than allowed. This could trigger issues for game servers. */
1040 ShowErrorMessage(STR_CONFIG_ERROR
, STR_NEWGRF_ERROR_TOO_MANY_NEWGRFS_LOADED
, WL_CRITICAL
);
1044 /* Add item to list */
1052 static IniFileVersion
LoadVersionFromConfig(IniFile
&ini
)
1054 IniGroup
*group
= ini
.GetGroup("version");
1056 auto version_number
= group
->GetItem("ini_version", false);
1057 /* Older ini-file versions don't have this key yet. */
1058 if (version_number
== nullptr || !version_number
->value
.has_value()) return IFV_0
;
1061 std::from_chars(version_number
->value
->data(), version_number
->value
->data() + version_number
->value
->size(), version
);
1063 return static_cast<IniFileVersion
>(version
);
1066 static void AISaveConfig(IniFile
&ini
, const char *grpname
)
1068 IniGroup
*group
= ini
.GetGroup(grpname
);
1070 if (group
== nullptr) return;
1073 for (CompanyID c
= COMPANY_FIRST
; c
< MAX_COMPANIES
; c
++) {
1074 AIConfig
*config
= AIConfig::GetConfig(c
, AIConfig::SSS_FORCE_NEWGAME
);
1076 std::string value
= config
->SettingsToString();
1078 if (config
->HasScript()) {
1079 name
= config
->GetName();
1084 IniItem
*item
= new IniItem(group
, name
);
1085 item
->SetValue(value
);
1089 static void GameSaveConfig(IniFile
&ini
, const char *grpname
)
1091 IniGroup
*group
= ini
.GetGroup(grpname
);
1093 if (group
== nullptr) return;
1096 GameConfig
*config
= GameConfig::GetConfig(AIConfig::SSS_FORCE_NEWGAME
);
1098 std::string value
= config
->SettingsToString();
1100 if (config
->HasScript()) {
1101 name
= config
->GetName();
1106 IniItem
*item
= new IniItem(group
, name
);
1107 item
->SetValue(value
);
1111 * Save the version of OpenTTD to the ini file.
1112 * @param ini the ini to write to
1114 static void SaveVersionInConfig(IniFile
&ini
)
1116 IniGroup
*group
= ini
.GetGroup("version");
1117 group
->GetItem("version_string", true)->SetValue(_openttd_revision
);
1118 group
->GetItem("version_number", true)->SetValue(fmt::format("{:08X}", _openttd_newgrf_version
));
1119 group
->GetItem("ini_version", true)->SetValue(std::to_string(INIFILE_VERSION
));
1122 /* Save a GRF configuration to the given group name */
1123 static void GRFSaveConfig(IniFile
&ini
, const char *grpname
, const GRFConfig
*list
)
1125 ini
.RemoveGroup(grpname
);
1126 IniGroup
*group
= ini
.GetGroup(grpname
);
1129 for (c
= list
; c
!= nullptr; c
= c
->next
) {
1130 /* Hex grfid (4 bytes in nibbles), "|", hex md5sum (16 bytes in nibbles), "|", file system path. */
1131 char key
[4 * 2 + 1 + 16 * 2 + 1 + MAX_PATH
];
1133 GRFBuildParamList(params
, c
, lastof(params
));
1135 char *pos
= key
+ seprintf(key
, lastof(key
), "%08X|", BSWAP32(c
->ident
.grfid
));
1136 pos
= md5sumToString(pos
, lastof(key
), c
->ident
.md5sum
);
1137 seprintf(pos
, lastof(key
), "|%s", c
->filename
);
1138 group
->GetItem(key
, true)->SetValue(params
);
1142 /* Common handler for saving/loading variables to the configuration file */
1143 static void HandleSettingDescs(IniFile
&generic_ini
, IniFile
&private_ini
, IniFile
&secrets_ini
, SettingDescProc
*proc
, SettingDescProcList
*proc_list
, bool only_startup
= false)
1145 proc(generic_ini
, _misc_settings
, "misc", nullptr, only_startup
);
1146 #if defined(_WIN32) && !defined(DEDICATED)
1147 proc(generic_ini
, _win32_settings
, "win32", nullptr, only_startup
);
1150 /* The name "patches" is a fallback, as every setting should sets its own group. */
1152 for (auto &table
: GenericSettingTables()) {
1153 proc(generic_ini
, table
, "patches", &_settings_newgame
, only_startup
);
1155 for (auto &table
: PrivateSettingTables()) {
1156 proc(private_ini
, table
, "patches", &_settings_newgame
, only_startup
);
1158 for (auto &table
: SecretSettingTables()) {
1159 proc(secrets_ini
, table
, "patches", &_settings_newgame
, only_startup
);
1162 proc(generic_ini
, _currency_settings
, "currency", &_custom_currency
, only_startup
);
1163 proc(generic_ini
, _company_settings
, "company", &_settings_client
.company
, only_startup
);
1165 if (!only_startup
) {
1166 proc_list(private_ini
, "server_bind_addresses", _network_bind_list
);
1167 proc_list(private_ini
, "servers", _network_host_list
);
1168 proc_list(private_ini
, "bans", _network_ban_list
);
1173 * Remove all entries from a settings table from an ini-file.
1175 * This is only useful if those entries are moved to another file, and you
1176 * want to clean up what is left behind.
1178 * @param ini The ini file to remove the entries from.
1179 * @param table The table to look for entries to remove.
1181 static void RemoveEntriesFromIni(IniFile
&ini
, const SettingTable
&table
)
1183 for (auto &desc
: table
) {
1184 const SettingDesc
*sd
= GetSettingDesc(desc
);
1186 /* For settings.xx.yy load the settings from [xx] yy = ? */
1187 std::string s
{ sd
->GetName() };
1188 auto sc
= s
.find('.');
1189 if (sc
== std::string::npos
) continue;
1191 IniGroup
*group
= ini
.GetGroup(s
.substr(0, sc
));
1192 s
= s
.substr(sc
+ 1);
1194 group
->RemoveItem(s
);
1199 * Load the values from the configuration files
1200 * @param startup Load the minimal amount of the configuration to "bootstrap" the blitter and such.
1202 void LoadFromConfig(bool startup
)
1204 ConfigIniFile
generic_ini(_config_file
);
1205 ConfigIniFile
private_ini(_private_file
);
1206 ConfigIniFile
secrets_ini(_secrets_file
);
1208 if (!startup
) ResetCurrencies(false); // Initialize the array of currencies, without preserving the custom one
1210 IniFileVersion generic_version
= LoadVersionFromConfig(generic_ini
);
1212 /* Before the split of private/secrets, we have to look in the generic for these settings. */
1213 if (generic_version
< IFV_PRIVATE_SECRETS
) {
1214 HandleSettingDescs(generic_ini
, generic_ini
, generic_ini
, IniLoadSettings
, IniLoadSettingList
, startup
);
1216 HandleSettingDescs(generic_ini
, private_ini
, secrets_ini
, IniLoadSettings
, IniLoadSettingList
, startup
);
1219 /* Load basic settings only during bootstrap, load other settings not during bootstrap */
1221 _grfconfig_newgame
= GRFLoadConfig(generic_ini
, "newgrf", false);
1222 _grfconfig_static
= GRFLoadConfig(generic_ini
, "newgrf-static", true);
1223 AILoadConfig(generic_ini
, "ai_players");
1224 GameLoadConfig(generic_ini
, "game_scripts");
1226 PrepareOldDiffCustom();
1227 IniLoadSettings(generic_ini
, _old_gameopt_settings
, "gameopt", &_settings_newgame
, false);
1228 HandleOldDiffCustom(false);
1232 /* Display scheduled errors */
1233 extern void ScheduleErrorMessage(ErrorList
&datas
);
1234 ScheduleErrorMessage(_settings_error_list
);
1235 if (FindWindowById(WC_ERRMSG
, 0) == nullptr) ShowFirstError();
1239 /** Save the values to the configuration file */
1242 ConfigIniFile
generic_ini(_config_file
);
1243 ConfigIniFile
private_ini(_private_file
);
1244 ConfigIniFile
secrets_ini(_secrets_file
);
1246 IniFileVersion generic_version
= LoadVersionFromConfig(generic_ini
);
1248 /* If we newly create the private/secrets file, add a dummy group on top
1249 * just so we can add a comment before it (that is how IniFile works).
1250 * This to explain what the file is about. After doing it once, never touch
1251 * it again, as otherwise we might be reverting user changes. */
1252 if (!private_ini
.GetGroup("private", false)) private_ini
.GetGroup("private")->comment
= "; This file possibly contains private information which can identify you as person.\n";
1253 if (!secrets_ini
.GetGroup("secrets", false)) secrets_ini
.GetGroup("secrets")->comment
= "; Do not share this file with others, not even if they claim to be technical support.\n; This file contains saved passwords and other secrets that should remain private to you!\n";
1255 if (generic_version
== IFV_0
) {
1256 /* Remove some obsolete groups. These have all been loaded into other groups. */
1257 generic_ini
.RemoveGroup("patches");
1258 generic_ini
.RemoveGroup("yapf");
1259 generic_ini
.RemoveGroup("gameopt");
1261 /* Remove all settings from the generic ini that are now in the private ini. */
1262 generic_ini
.RemoveGroup("server_bind_addresses");
1263 generic_ini
.RemoveGroup("servers");
1264 generic_ini
.RemoveGroup("bans");
1265 for (auto &table
: PrivateSettingTables()) {
1266 RemoveEntriesFromIni(generic_ini
, table
);
1269 /* Remove all settings from the generic ini that are now in the secrets ini. */
1270 for (auto &table
: SecretSettingTables()) {
1271 RemoveEntriesFromIni(generic_ini
, table
);
1275 HandleSettingDescs(generic_ini
, private_ini
, secrets_ini
, IniSaveSettings
, IniSaveSettingList
);
1276 GRFSaveConfig(generic_ini
, "newgrf", _grfconfig_newgame
);
1277 GRFSaveConfig(generic_ini
, "newgrf-static", _grfconfig_static
);
1278 AISaveConfig(generic_ini
, "ai_players");
1279 GameSaveConfig(generic_ini
, "game_scripts");
1281 SaveVersionInConfig(generic_ini
);
1282 SaveVersionInConfig(private_ini
);
1283 SaveVersionInConfig(secrets_ini
);
1285 generic_ini
.SaveToDisk(_config_file
);
1286 private_ini
.SaveToDisk(_private_file
);
1287 secrets_ini
.SaveToDisk(_secrets_file
);
1291 * Get the list of known NewGrf presets.
1292 * @returns List of preset names.
1294 StringList
GetGRFPresetList()
1298 ConfigIniFile
ini(_config_file
);
1299 for (IniGroup
*group
= ini
.group
; group
!= nullptr; group
= group
->next
) {
1300 if (group
->name
.compare(0, 7, "preset-") == 0) {
1301 list
.push_back(group
->name
.substr(7));
1309 * Load a NewGRF configuration by preset-name.
1310 * @param config_name Name of the preset.
1311 * @return NewGRF configuration.
1312 * @see GetGRFPresetList
1314 GRFConfig
*LoadGRFPresetFromConfig(const char *config_name
)
1316 size_t len
= strlen(config_name
) + 8;
1317 char *section
= (char*)alloca(len
);
1318 seprintf(section
, section
+ len
- 1, "preset-%s", config_name
);
1320 ConfigIniFile
ini(_config_file
);
1321 GRFConfig
*config
= GRFLoadConfig(ini
, section
, false);
1327 * Save a NewGRF configuration with a preset name.
1328 * @param config_name Name of the preset.
1329 * @param config NewGRF configuration to save.
1330 * @see GetGRFPresetList
1332 void SaveGRFPresetToConfig(const char *config_name
, GRFConfig
*config
)
1334 size_t len
= strlen(config_name
) + 8;
1335 char *section
= (char*)alloca(len
);
1336 seprintf(section
, section
+ len
- 1, "preset-%s", config_name
);
1338 ConfigIniFile
ini(_config_file
);
1339 GRFSaveConfig(ini
, section
, config
);
1340 ini
.SaveToDisk(_config_file
);
1344 * Delete a NewGRF configuration by preset name.
1345 * @param config_name Name of the preset.
1347 void DeleteGRFPresetFromConfig(const char *config_name
)
1349 size_t len
= strlen(config_name
) + 8;
1350 char *section
= (char*)alloca(len
);
1351 seprintf(section
, section
+ len
- 1, "preset-%s", config_name
);
1353 ConfigIniFile
ini(_config_file
);
1354 ini
.RemoveGroup(section
);
1355 ini
.SaveToDisk(_config_file
);
1359 * Handle changing a value. This performs validation of the input value and
1360 * calls the appropriate callbacks, and saves it when the value is changed.
1361 * @param object The object the setting is in.
1362 * @param newval The new value for the setting.
1364 void IntSettingDesc::ChangeValue(const void *object
, int32 newval
) const
1366 int32 oldval
= this->Read(object
);
1367 this->MakeValueValid(newval
);
1368 if (this->pre_check
!= nullptr && !this->pre_check(newval
)) return;
1369 if (oldval
== newval
) return;
1371 this->Write(object
, newval
);
1372 if (this->post_callback
!= nullptr) this->post_callback(newval
);
1374 if (this->flags
& SF_NO_NETWORK
) {
1375 GamelogStartAction(GLAT_SETTING
);
1376 GamelogSetting(this->GetName(), oldval
, newval
);
1377 GamelogStopAction();
1380 SetWindowClassesDirty(WC_GAME_OPTIONS
);
1382 if (_save_config
) SaveToConfig();
1386 * Given a name of setting, return a setting description from the table.
1387 * @param name Name of the setting to return a setting description of.
1388 * @param settings Table to look in for the setting.
1389 * @return Pointer to the setting description of setting \a name if it can be found,
1390 * \c nullptr indicates failure to obtain the description.
1392 static const SettingDesc
*GetSettingFromName(const std::string_view name
, const SettingTable
&settings
)
1394 /* First check all full names */
1395 for (auto &desc
: settings
) {
1396 const SettingDesc
*sd
= GetSettingDesc(desc
);
1397 if (!SlIsObjectCurrentlyValid(sd
->save
.version_from
, sd
->save
.version_to
)) continue;
1398 if (sd
->GetName() == name
) return sd
;
1401 /* Then check the shortcut variant of the name. */
1402 std::string short_name_suffix
= std::string
{ "." }.append(name
);
1403 for (auto &desc
: settings
) {
1404 const SettingDesc
*sd
= GetSettingDesc(desc
);
1405 if (!SlIsObjectCurrentlyValid(sd
->save
.version_from
, sd
->save
.version_to
)) continue;
1406 if (StrEndsWith(sd
->GetName(), short_name_suffix
)) return sd
;
1413 * Get the SaveLoad for all settings in the settings table.
1414 * @param settings The settings table to get the SaveLoad objects from.
1415 * @param saveloads A vector to store the result in.
1417 void GetSaveLoadFromSettingTable(SettingTable settings
, std::vector
<SaveLoad
> &saveloads
)
1419 for (auto &desc
: settings
) {
1420 const SettingDesc
*sd
= GetSettingDesc(desc
);
1421 if (!SlIsObjectCurrentlyValid(sd
->save
.version_from
, sd
->save
.version_to
)) continue;
1422 saveloads
.push_back(sd
->save
);
1427 * Given a name of setting, return a company setting description of it.
1428 * @param name Name of the company setting to return a setting description of.
1429 * @return Pointer to the setting description of setting \a name if it can be found,
1430 * \c nullptr indicates failure to obtain the description.
1432 static const SettingDesc
*GetCompanySettingFromName(std::string_view name
)
1434 static const std::string_view company_prefix
= "company.";
1435 if (StrStartsWith(name
, company_prefix
)) name
.remove_prefix(company_prefix
.size());
1436 return GetSettingFromName(name
, _company_settings
);
1440 * Given a name of any setting, return any setting description of it.
1441 * @param name Name of the setting to return a setting description of.
1442 * @return Pointer to the setting description of setting \a name if it can be found,
1443 * \c nullptr indicates failure to obtain the description.
1445 const SettingDesc
*GetSettingFromName(const std::string_view name
)
1447 for (auto &table
: GenericSettingTables()) {
1448 auto sd
= GetSettingFromName(name
, table
);
1449 if (sd
!= nullptr) return sd
;
1451 for (auto &table
: PrivateSettingTables()) {
1452 auto sd
= GetSettingFromName(name
, table
);
1453 if (sd
!= nullptr) return sd
;
1455 for (auto &table
: SecretSettingTables()) {
1456 auto sd
= GetSettingFromName(name
, table
);
1457 if (sd
!= nullptr) return sd
;
1460 return GetCompanySettingFromName(name
);
1464 * Network-safe changing of settings (server-only).
1465 * @param tile unused
1466 * @param flags operation to perform
1468 * @param p2 the new value for the setting
1469 * The new value is properly clamped to its minimum/maximum when setting
1470 * @param text the name of the setting to change
1471 * @return the cost of this operation or an error
1474 CommandCost
CmdChangeSetting(TileIndex tile
, DoCommandFlag flags
, uint32 p1
, uint32 p2
, const std::string
&text
)
1476 if (text
.empty()) return CMD_ERROR
;
1477 const SettingDesc
*sd
= GetSettingFromName(text
);
1479 if (sd
== nullptr) return CMD_ERROR
;
1480 if (!SlIsObjectCurrentlyValid(sd
->save
.version_from
, sd
->save
.version_to
)) return CMD_ERROR
;
1481 if (!sd
->IsIntSetting()) return CMD_ERROR
;
1483 if (!sd
->IsEditable(true)) return CMD_ERROR
;
1485 if (flags
& DC_EXEC
) {
1486 sd
->AsIntSetting()->ChangeValue(&GetGameSettings(), p2
);
1489 return CommandCost();
1493 * Change one of the per-company settings.
1494 * @param tile unused
1495 * @param flags operation to perform
1497 * @param p2 the new value for the setting
1498 * The new value is properly clamped to its minimum/maximum when setting
1499 * @param text the name of the company setting to change
1500 * @return the cost of this operation or an error
1502 CommandCost
CmdChangeCompanySetting(TileIndex tile
, DoCommandFlag flags
, uint32 p1
, uint32 p2
, const std::string
&text
)
1504 if (text
.empty()) return CMD_ERROR
;
1505 const SettingDesc
*sd
= GetCompanySettingFromName(text
.c_str());
1507 if (sd
== nullptr) return CMD_ERROR
;
1508 if (!sd
->IsIntSetting()) return CMD_ERROR
;
1510 if (flags
& DC_EXEC
) {
1511 sd
->AsIntSetting()->ChangeValue(&Company::Get(_current_company
)->settings
, p2
);
1514 return CommandCost();
1518 * Top function to save the new value of an element of the Settings struct
1519 * @param index offset in the SettingDesc array of the Settings struct which
1520 * identifies the setting member we want to change
1521 * @param value new value of the setting
1522 * @param force_newgame force the newgame settings
1524 bool SetSettingValue(const IntSettingDesc
*sd
, int32 value
, bool force_newgame
)
1526 const IntSettingDesc
*setting
= sd
->AsIntSetting();
1527 if ((setting
->flags
& SF_PER_COMPANY
) != 0) {
1528 if (Company::IsValidID(_local_company
) && _game_mode
!= GM_MENU
) {
1529 return DoCommandP(0, 0, value
, CMD_CHANGE_COMPANY_SETTING
, nullptr, setting
->GetName());
1532 setting
->ChangeValue(&_settings_client
.company
, value
);
1536 /* If an item is company-based, we do not send it over the network
1537 * (if any) to change. Also *hack*hack* we update the _newgame version
1538 * of settings because changing a company-based setting in a game also
1539 * changes its defaults. At least that is the convention we have chosen */
1540 if (setting
->flags
& SF_NO_NETWORK_SYNC
) {
1541 if (_game_mode
!= GM_MENU
) {
1542 setting
->ChangeValue(&_settings_newgame
, value
);
1544 setting
->ChangeValue(&GetGameSettings(), value
);
1548 if (force_newgame
) {
1549 setting
->ChangeValue(&_settings_newgame
, value
);
1553 /* send non-company-based settings over the network */
1554 if (!_networking
|| (_networking
&& _network_server
)) {
1555 return DoCommandP(0, 0, value
, CMD_CHANGE_SETTING
, nullptr, setting
->GetName());
1561 * Set the company settings for a new company to their default values.
1563 void SetDefaultCompanySettings(CompanyID cid
)
1565 Company
*c
= Company::Get(cid
);
1566 for (auto &desc
: _company_settings
) {
1567 const IntSettingDesc
*int_setting
= GetSettingDesc(desc
)->AsIntSetting();
1568 int_setting
->MakeValueValidAndWrite(&c
->settings
, int_setting
->def
);
1573 * Sync all company settings in a multiplayer game.
1575 void SyncCompanySettings()
1577 const void *old_object
= &Company::Get(_current_company
)->settings
;
1578 const void *new_object
= &_settings_client
.company
;
1579 for (auto &desc
: _company_settings
) {
1580 const SettingDesc
*sd
= GetSettingDesc(desc
);
1581 uint32 old_value
= (uint32
)sd
->AsIntSetting()->Read(new_object
);
1582 uint32 new_value
= (uint32
)sd
->AsIntSetting()->Read(old_object
);
1583 if (old_value
!= new_value
) NetworkSendCommand(0, 0, new_value
, CMD_CHANGE_COMPANY_SETTING
, nullptr, sd
->GetName(), _local_company
);
1588 * Set a setting value with a string.
1589 * @param sd the setting to change.
1590 * @param value the value to write
1591 * @param force_newgame force the newgame settings
1592 * @note Strings WILL NOT be synced over the network
1594 bool SetSettingValue(const StringSettingDesc
*sd
, std::string value
, bool force_newgame
)
1596 assert(sd
->flags
& SF_NO_NETWORK_SYNC
);
1598 if (GetVarMemType(sd
->save
.conv
) == SLE_VAR_STRQ
&& value
.compare("(null)") == 0) {
1602 const void *object
= (_game_mode
== GM_MENU
|| force_newgame
) ? &_settings_newgame
: &_settings_game
;
1603 sd
->AsStringSetting()->ChangeValue(object
, value
);
1608 * Handle changing a string value. This performs validation of the input value
1609 * and calls the appropriate callbacks, and saves it when the value is changed.
1610 * @param object The object the setting is in.
1611 * @param newval The new value for the setting.
1613 void StringSettingDesc::ChangeValue(const void *object
, std::string
&newval
) const
1615 this->MakeValueValid(newval
);
1616 if (this->pre_check
!= nullptr && !this->pre_check(newval
)) return;
1618 this->Write(object
, newval
);
1619 if (this->post_callback
!= nullptr) this->post_callback(newval
);
1621 if (_save_config
) SaveToConfig();
1624 /* Those 2 functions need to be here, else we have to make some stuff non-static
1625 * and besides, it is also better to keep stuff like this at the same place */
1626 void IConsoleSetSetting(const char *name
, const char *value
, bool force_newgame
)
1628 const SettingDesc
*sd
= GetSettingFromName(name
);
1629 if (sd
== nullptr) {
1630 IConsolePrint(CC_ERROR
, "'{}' is an unknown setting.", name
);
1634 bool success
= true;
1635 if (sd
->IsStringSetting()) {
1636 success
= SetSettingValue(sd
->AsStringSetting(), value
, force_newgame
);
1637 } else if (sd
->IsIntSetting()) {
1638 const IntSettingDesc
*isd
= sd
->AsIntSetting();
1639 size_t val
= isd
->ParseValue(value
);
1640 if (!_settings_error_list
.empty()) {
1641 IConsolePrint(CC_ERROR
, "'{}' is not a valid value for this setting.", value
);
1642 _settings_error_list
.clear();
1645 success
= SetSettingValue(isd
, (int32
)val
, force_newgame
);
1649 if (_network_server
) {
1650 IConsolePrint(CC_ERROR
, "This command/variable is not available during network games.");
1652 IConsolePrint(CC_ERROR
, "This command/variable is only available to a network server.");
1657 void IConsoleSetSetting(const char *name
, int value
)
1659 const SettingDesc
*sd
= GetSettingFromName(name
);
1660 assert(sd
!= nullptr);
1661 SetSettingValue(sd
->AsIntSetting(), value
);
1665 * Output value of a specific setting to the console
1666 * @param name Name of the setting to output its value
1667 * @param force_newgame force the newgame settings
1669 void IConsoleGetSetting(const char *name
, bool force_newgame
)
1671 const SettingDesc
*sd
= GetSettingFromName(name
);
1672 if (sd
== nullptr) {
1673 IConsolePrint(CC_ERROR
, "'{}' is an unknown setting.", name
);
1677 const void *object
= (_game_mode
== GM_MENU
|| force_newgame
) ? &_settings_newgame
: &_settings_game
;
1679 if (sd
->IsStringSetting()) {
1680 IConsolePrint(CC_INFO
, "Current value for '{}' is '{}'.", sd
->GetName(), sd
->AsStringSetting()->Read(object
));
1681 } else if (sd
->IsIntSetting()) {
1683 sd
->FormatValue(value
, lastof(value
), object
);
1684 const IntSettingDesc
*int_setting
= sd
->AsIntSetting();
1685 IConsolePrint(CC_INFO
, "Current value for '{}' is '{}' (min: {}{}, max: {}).",
1686 sd
->GetName(), value
, (sd
->flags
& SF_GUI_0_IS_SPECIAL
) ? "(0) " : "", int_setting
->min
, int_setting
->max
);
1690 static void IConsoleListSettingsTable(const SettingTable
&table
, const char *prefilter
)
1692 for (auto &desc
: table
) {
1693 const SettingDesc
*sd
= GetSettingDesc(desc
);
1694 if (!SlIsObjectCurrentlyValid(sd
->save
.version_from
, sd
->save
.version_to
)) continue;
1695 if (prefilter
!= nullptr && sd
->GetName().find(prefilter
) == std::string::npos
) continue;
1697 sd
->FormatValue(value
, lastof(value
), &GetGameSettings());
1698 IConsolePrint(CC_DEFAULT
, "{} = {}", sd
->GetName(), value
);
1703 * List all settings and their value to the console
1705 * @param prefilter If not \c nullptr, only list settings with names that begin with \a prefilter prefix
1707 void IConsoleListSettings(const char *prefilter
)
1709 IConsolePrint(CC_HELP
, "All settings with their current value:");
1711 for (auto &table
: GenericSettingTables()) {
1712 IConsoleListSettingsTable(table
, prefilter
);
1714 for (auto &table
: PrivateSettingTables()) {
1715 IConsoleListSettingsTable(table
, prefilter
);
1717 for (auto &table
: SecretSettingTables()) {
1718 IConsoleListSettingsTable(table
, prefilter
);
1721 IConsolePrint(CC_HELP
, "Use 'setting' command to change a value.");