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.
159 IFV_GAME_TYPE
, ///< 2 PR#9515 Convert server_advertise to server_game_type.
161 IFV_MAX_VERSION
, ///< Highest possible ini-file version.
164 const uint16 INIFILE_VERSION
= (IniFileVersion
)(IFV_MAX_VERSION
- 1); ///< Current ini-file version of OpenTTD.
167 * Find the index value of a ONEofMANY type in a string separated by |
168 * @param str the current value of the setting for which a value needs found
169 * @param len length of the string
170 * @param many full domain of values the ONEofMANY setting can have
171 * @return the integer index of the full-list, or -1 if not found
173 size_t OneOfManySettingDesc::ParseSingleValue(const char *str
, size_t len
, const std::vector
<std::string
> &many
)
175 /* check if it's an integer */
176 if (isdigit(*str
)) return strtoul(str
, nullptr, 0);
179 for (auto one
: many
) {
180 if (one
.size() == len
&& strncmp(one
.c_str(), str
, len
) == 0) return idx
;
188 * Find the set-integer value MANYofMANY type in a string
189 * @param many full domain of values the MANYofMANY setting can have
190 * @param str the current string value of the setting, each individual
191 * of separated by a whitespace,tab or | character
192 * @return the 'fully' set integer, or -1 if a set is not found
194 static size_t LookupManyOfMany(const std::vector
<std::string
> &many
, const char *str
)
201 /* skip "whitespace" */
202 while (*str
== ' ' || *str
== '\t' || *str
== '|') str
++;
203 if (*str
== 0) break;
206 while (*s
!= 0 && *s
!= ' ' && *s
!= '\t' && *s
!= '|') s
++;
208 r
= OneOfManySettingDesc::ParseSingleValue(str
, s
- str
, many
);
209 if (r
== (size_t)-1) return r
;
211 SetBit(res
, (uint8
)r
); // value found, set it
219 * Parse an integerlist string and set each found value
220 * @param p the string to be parsed. Each element in the list is separated by a
221 * comma or a space character
222 * @param items pointer to the integerlist-array that will be filled with values
223 * @param maxitems the maximum number of elements the integerlist-array has
224 * @return returns the number of items found, or -1 on an error
227 static int ParseIntList(const char *p
, T
*items
, int maxitems
)
229 int n
= 0; // number of items read so far
230 bool comma
= false; // do we accept comma?
235 /* Do not accept multiple commas between numbers */
236 if (!comma
) return -1;
245 if (n
== maxitems
) return -1; // we don't accept that many numbers
247 unsigned long v
= strtoul(p
, &end
, 0);
248 if (p
== end
) return -1; // invalid character (not a number)
249 if (sizeof(T
) < sizeof(v
)) v
= Clamp
<unsigned long>(v
, std::numeric_limits
<T
>::min(), std::numeric_limits
<T
>::max());
251 p
= end
; // first non-number
252 comma
= true; // we accept comma now
258 /* If we have read comma but no number after it, fail.
259 * We have read comma when (n != 0) and comma is not allowed */
260 if (n
!= 0 && !comma
) return -1;
266 * Load parsed string-values into an integer-array (intlist)
267 * @param str the string that contains the values (and will be parsed)
268 * @param array pointer to the integer-arrays that will be filled
269 * @param nelems the number of elements the array holds. Maximum is 64 elements
270 * @param type the type of elements the array holds (eg INT8, UINT16, etc.)
271 * @return return true on success and false on error
273 static bool LoadIntList(const char *str
, void *array
, int nelems
, VarType type
)
275 unsigned long items
[64];
278 if (str
== nullptr) {
279 memset(items
, 0, sizeof(items
));
282 nitems
= ParseIntList(str
, items
, lengthof(items
));
283 if (nitems
!= nelems
) return false;
290 for (i
= 0; i
!= nitems
; i
++) ((byte
*)array
)[i
] = items
[i
];
295 for (i
= 0; i
!= nitems
; i
++) ((uint16
*)array
)[i
] = items
[i
];
300 for (i
= 0; i
!= nitems
; i
++) ((uint32
*)array
)[i
] = items
[i
];
303 default: NOT_REACHED();
310 * Convert an integer-array (intlist) to a string representation. Each value
311 * is separated by a comma or a space character
312 * @param buf output buffer where the string-representation will be stored
313 * @param last last item to write to in the output buffer
314 * @param array pointer to the integer-arrays that is read from
315 * @param nelems the number of elements the array holds.
316 * @param type the type of elements the array holds (eg INT8, UINT16, etc.)
318 void ListSettingDesc::FormatValue(char *buf
, const char *last
, const void *object
) const
320 const byte
*p
= static_cast<const byte
*>(GetVariableAddress(object
, this->save
));
323 for (i
= 0; i
!= this->save
.length
; i
++) {
324 switch (GetVarMemType(this->save
.conv
)) {
326 case SLE_VAR_I8
: v
= *(const int8
*)p
; p
+= 1; break;
327 case SLE_VAR_U8
: v
= *(const uint8
*)p
; p
+= 1; break;
328 case SLE_VAR_I16
: v
= *(const int16
*)p
; p
+= 2; break;
329 case SLE_VAR_U16
: v
= *(const uint16
*)p
; p
+= 2; break;
330 case SLE_VAR_I32
: v
= *(const int32
*)p
; p
+= 4; break;
331 case SLE_VAR_U32
: v
= *(const uint32
*)p
; p
+= 4; break;
332 default: NOT_REACHED();
334 if (IsSignedVarMemType(this->save
.conv
)) {
335 buf
+= seprintf(buf
, last
, (i
== 0) ? "%d" : ",%d", v
);
337 buf
+= seprintf(buf
, last
, (i
== 0) ? "%u" : ",%u", v
);
342 char *OneOfManySettingDesc::FormatSingleValue(char *buf
, const char *last
, uint id
) const
344 if (id
>= this->many
.size()) {
345 return buf
+ seprintf(buf
, last
, "%d", id
);
347 return strecpy(buf
, this->many
[id
].c_str(), last
);
350 void OneOfManySettingDesc::FormatValue(char *buf
, const char *last
, const void *object
) const
352 uint id
= (uint
)this->Read(object
);
353 this->FormatSingleValue(buf
, last
, id
);
356 void ManyOfManySettingDesc::FormatValue(char *buf
, const char *last
, const void *object
) const
358 uint bitmask
= (uint
)this->Read(object
);
360 for (uint id
: SetBitIterator(bitmask
)) {
361 if (!first
) buf
= strecpy(buf
, "|", last
);
362 buf
= this->FormatSingleValue(buf
, last
, id
);
368 * Convert a string representation (external) of an integer-like setting to an integer.
369 * @param str Input string that will be parsed based on the type of desc.
370 * @return The value from the parse string, or the default value of the setting.
372 size_t IntSettingDesc::ParseValue(const char *str
) const
375 size_t val
= strtoul(str
, &end
, 0);
377 ErrorMessageData
msg(STR_CONFIG_ERROR
, STR_CONFIG_ERROR_INVALID_VALUE
);
378 msg
.SetDParamStr(0, str
);
379 msg
.SetDParamStr(1, this->GetName());
380 _settings_error_list
.push_back(msg
);
384 ErrorMessageData
msg(STR_CONFIG_ERROR
, STR_CONFIG_ERROR_TRAILING_CHARACTERS
);
385 msg
.SetDParamStr(0, this->GetName());
386 _settings_error_list
.push_back(msg
);
391 size_t OneOfManySettingDesc::ParseValue(const char *str
) const
393 size_t r
= OneOfManySettingDesc::ParseSingleValue(str
, strlen(str
), this->many
);
394 /* if the first attempt of conversion from string to the appropriate value fails,
395 * look if we have defined a converter from old value to new value. */
396 if (r
== (size_t)-1 && this->many_cnvt
!= nullptr) r
= this->many_cnvt(str
);
397 if (r
!= (size_t)-1) return r
; // and here goes converted value
399 ErrorMessageData
msg(STR_CONFIG_ERROR
, STR_CONFIG_ERROR_INVALID_VALUE
);
400 msg
.SetDParamStr(0, str
);
401 msg
.SetDParamStr(1, this->GetName());
402 _settings_error_list
.push_back(msg
);
406 size_t ManyOfManySettingDesc::ParseValue(const char *str
) const
408 size_t r
= LookupManyOfMany(this->many
, str
);
409 if (r
!= (size_t)-1) return r
;
410 ErrorMessageData
msg(STR_CONFIG_ERROR
, STR_CONFIG_ERROR_INVALID_VALUE
);
411 msg
.SetDParamStr(0, str
);
412 msg
.SetDParamStr(1, this->GetName());
413 _settings_error_list
.push_back(msg
);
417 size_t BoolSettingDesc::ParseValue(const char *str
) const
419 if (strcmp(str
, "true") == 0 || strcmp(str
, "on") == 0 || strcmp(str
, "1") == 0) return true;
420 if (strcmp(str
, "false") == 0 || strcmp(str
, "off") == 0 || strcmp(str
, "0") == 0) return false;
422 ErrorMessageData
msg(STR_CONFIG_ERROR
, STR_CONFIG_ERROR_INVALID_VALUE
);
423 msg
.SetDParamStr(0, str
);
424 msg
.SetDParamStr(1, this->GetName());
425 _settings_error_list
.push_back(msg
);
430 * Make the value valid and then write it to the setting.
431 * See #MakeValidValid and #Write for more details.
432 * @param object The object the setting is to be saved in.
433 * @param val Signed version of the new value.
435 void IntSettingDesc::MakeValueValidAndWrite(const void *object
, int32 val
) const
437 this->MakeValueValid(val
);
438 this->Write(object
, val
);
442 * Make the value valid given the limitations of this setting.
444 * In the case of int settings this is ensuring the value is between the minimum and
445 * maximum value, with a special case for 0 if SF_GUI_0_IS_SPECIAL is set.
446 * This is generally done by clamping the value so it is within the allowed value range.
447 * However, for SF_GUI_DROPDOWN the default is used when the value is not valid.
448 * @param val The value to make valid.
450 void IntSettingDesc::MakeValueValid(int32
&val
) const
452 /* We need to take special care of the uint32 type as we receive from the function
453 * a signed integer. While here also bail out on 64-bit settings as those are not
454 * supported. Unsigned 8 and 16-bit variables are safe since they fit into a signed
456 * TODO: Support 64-bit settings/variables; requires 64 bit over command protocol! */
457 switch (GetVarMemType(this->save
.conv
)) {
458 case SLE_VAR_NULL
: return;
465 /* Override the minimum value. No value below this->min, except special value 0 */
466 if (!(this->flags
& SF_GUI_0_IS_SPECIAL
) || val
!= 0) {
467 if (!(this->flags
& SF_GUI_DROPDOWN
)) {
468 /* Clamp value-type setting to its valid range */
469 val
= Clamp(val
, this->min
, this->max
);
470 } else if (val
< this->min
|| val
> (int32
)this->max
) {
471 /* Reset invalid discrete setting (where different values change gameplay) to its default value */
478 /* Override the minimum value. No value below this->min, except special value 0 */
479 uint32 uval
= (uint32
)val
;
480 if (!(this->flags
& SF_GUI_0_IS_SPECIAL
) || uval
!= 0) {
481 if (!(this->flags
& SF_GUI_DROPDOWN
)) {
482 /* Clamp value-type setting to its valid range */
483 uval
= ClampU(uval
, this->min
, this->max
);
484 } else if (uval
< (uint
)this->min
|| uval
> this->max
) {
485 /* Reset invalid discrete setting to its default value */
486 uval
= (uint32
)this->def
;
494 default: NOT_REACHED();
499 * Set the value of a setting.
500 * @param object The object the setting is to be saved in.
501 * @param val Signed version of the new value.
503 void IntSettingDesc::Write(const void *object
, int32 val
) const
505 void *ptr
= GetVariableAddress(object
, this->save
);
506 WriteValue(ptr
, this->save
.conv
, (int64
)val
);
510 * Read the integer from the the actual setting.
511 * @param object The object the setting is to be saved in.
512 * @return The value of the saved integer.
514 int32
IntSettingDesc::Read(const void *object
) const
516 void *ptr
= GetVariableAddress(object
, this->save
);
517 return (int32
)ReadValue(ptr
, this->save
.conv
);
521 * Make the value valid given the limitations of this setting.
523 * In the case of string settings this is ensuring the string contains only accepted
524 * Utf8 characters and is at most the maximum length defined in this setting.
525 * @param str The string to make valid.
527 void StringSettingDesc::MakeValueValid(std::string
&str
) const
529 if (this->max_length
== 0 || str
.size() < this->max_length
) return;
531 /* In case a maximum length is imposed by the setting, the length
532 * includes the '\0' termination for network transfer purposes.
533 * Also ensure the string is valid after chopping of some bytes. */
534 std::string
stdstr(str
, this->max_length
- 1);
535 str
.assign(StrMakeValid(stdstr
, SVS_NONE
));
539 * Write a string to the actual setting.
540 * @param object The object the setting is to be saved in.
541 * @param str The string to save.
543 void StringSettingDesc::Write(const void *object
, const std::string
&str
) const
545 reinterpret_cast<std::string
*>(GetVariableAddress(object
, this->save
))->assign(str
);
549 * Read the string from the the actual setting.
550 * @param object The object the setting is to be saved in.
551 * @return The value of the saved string.
553 const std::string
&StringSettingDesc::Read(const void *object
) const
555 return *reinterpret_cast<std::string
*>(GetVariableAddress(object
, this->save
));
559 * Load values from a group of an IniFile structure into the internal representation
560 * @param ini pointer to IniFile structure that holds administrative information
561 * @param settings_table table with SettingDesc structures whose internally pointed variables will
563 * @param grpname the group of the IniFile to search in for the new values
564 * @param object pointer to the object been loaded
565 * @param only_startup load only the startup settings set
567 static void IniLoadSettings(IniFile
&ini
, const SettingTable
&settings_table
, const char *grpname
, void *object
, bool only_startup
)
570 IniGroup
*group_def
= ini
.GetGroup(grpname
);
572 for (auto &desc
: settings_table
) {
573 const SettingDesc
*sd
= GetSettingDesc(desc
);
574 if (!SlIsObjectCurrentlyValid(sd
->save
.version_from
, sd
->save
.version_to
)) continue;
575 if (sd
->startup
!= only_startup
) continue;
577 /* For settings.xx.yy load the settings from [xx] yy = ? */
578 std::string s
{ sd
->GetName() };
579 auto sc
= s
.find('.');
580 if (sc
!= std::string::npos
) {
581 group
= ini
.GetGroup(s
.substr(0, sc
));
582 s
= s
.substr(sc
+ 1);
587 IniItem
*item
= group
->GetItem(s
, false);
588 if (item
== nullptr && group
!= group_def
) {
589 /* For settings.xx.yy load the settings from [settings] yy = ? in case the previous
590 * did not exist (e.g. loading old config files with a [settings] section */
591 item
= group_def
->GetItem(s
, false);
593 if (item
== nullptr) {
594 /* For settings.xx.zz.yy load the settings from [zz] yy = ? in case the previous
595 * did not exist (e.g. loading old config files with a [yapf] section */
597 if (sc
!= std::string::npos
) item
= ini
.GetGroup(s
.substr(0, sc
))->GetItem(s
.substr(sc
+ 1), false);
600 sd
->ParseValue(item
, object
);
604 void IntSettingDesc::ParseValue(const IniItem
*item
, void *object
) const
606 size_t val
= (item
== nullptr) ? this->def
: this->ParseValue(item
->value
.has_value() ? item
->value
->c_str() : "");
607 this->MakeValueValidAndWrite(object
, (int32
)val
);
610 void StringSettingDesc::ParseValue(const IniItem
*item
, void *object
) const
612 std::string str
= (item
== nullptr) ? this->def
: item
->value
.value_or("");
613 this->MakeValueValid(str
);
614 this->Write(object
, str
);
617 void ListSettingDesc::ParseValue(const IniItem
*item
, void *object
) const
619 const char *str
= (item
== nullptr) ? this->def
: item
->value
.has_value() ? item
->value
->c_str() : nullptr;
620 void *ptr
= GetVariableAddress(object
, this->save
);
621 if (!LoadIntList(str
, ptr
, this->save
.length
, GetVarMemType(this->save
.conv
))) {
622 ErrorMessageData
msg(STR_CONFIG_ERROR
, STR_CONFIG_ERROR_ARRAY
);
623 msg
.SetDParamStr(0, this->GetName());
624 _settings_error_list
.push_back(msg
);
627 LoadIntList(this->def
, ptr
, this->save
.length
, GetVarMemType(this->save
.conv
));
632 * Save the values of settings to the inifile.
633 * @param ini pointer to IniFile structure
634 * @param sd read-only SettingDesc structure which contains the unmodified,
635 * loaded values of the configuration file and various information about it
636 * @param grpname holds the name of the group (eg. [network]) where these will be saved
637 * @param object pointer to the object been saved
638 * The function works as follows: for each item in the SettingDesc structure we
639 * have a look if the value has changed since we started the game (the original
640 * values are reloaded when saving). If settings indeed have changed, we get
641 * these and save them.
643 static void IniSaveSettings(IniFile
&ini
, const SettingTable
&settings_table
, const char *grpname
, void *object
, bool)
645 IniGroup
*group_def
= nullptr, *group
;
649 for (auto &desc
: settings_table
) {
650 const SettingDesc
*sd
= GetSettingDesc(desc
);
651 /* If the setting is not saved to the configuration
652 * file, just continue with the next setting */
653 if (!SlIsObjectCurrentlyValid(sd
->save
.version_from
, sd
->save
.version_to
)) continue;
654 if (sd
->flags
& SF_NOT_IN_CONFIG
) continue;
656 /* XXX - wtf is this?? (group override?) */
657 std::string s
{ sd
->GetName() };
658 auto sc
= s
.find('.');
659 if (sc
!= std::string::npos
) {
660 group
= ini
.GetGroup(s
.substr(0, sc
));
661 s
= s
.substr(sc
+ 1);
663 if (group_def
== nullptr) group_def
= ini
.GetGroup(grpname
);
667 item
= group
->GetItem(s
, true);
669 if (!item
->value
.has_value() || !sd
->IsSameValue(item
, object
)) {
670 /* Value has changed, get the new value and put it into a buffer */
671 sd
->FormatValue(buf
, lastof(buf
), object
);
673 /* The value is different, that means we have to write it to the ini */
674 item
->value
.emplace(buf
);
679 void IntSettingDesc::FormatValue(char *buf
, const char *last
, const void *object
) const
681 uint32 i
= (uint32
)this->Read(object
);
682 seprintf(buf
, last
, IsSignedVarMemType(this->save
.conv
) ? "%d" : "%u", i
);
685 void BoolSettingDesc::FormatValue(char *buf
, const char *last
, const void *object
) const
687 bool val
= this->Read(object
) != 0;
688 strecpy(buf
, val
? "true" : "false", last
);
691 bool IntSettingDesc::IsSameValue(const IniItem
*item
, void *object
) const
693 int32 item_value
= (int32
)this->ParseValue(item
->value
->c_str());
694 int32 object_value
= this->Read(object
);
695 return item_value
== object_value
;
698 void StringSettingDesc::FormatValue(char *buf
, const char *last
, const void *object
) const
700 const std::string
&str
= this->Read(object
);
701 switch (GetVarMemType(this->save
.conv
)) {
702 case SLE_VAR_STR
: strecpy(buf
, str
.c_str(), last
); break;
708 seprintf(buf
, last
, "\"%s\"", str
.c_str());
712 default: NOT_REACHED();
716 bool StringSettingDesc::IsSameValue(const IniItem
*item
, void *object
) const
718 /* The ini parsing removes the quotes, which are needed to retain the spaces in STRQs,
719 * so those values are always different in the parsed ini item than they should be. */
720 if (GetVarMemType(this->save
.conv
) == SLE_VAR_STRQ
) return false;
722 const std::string
&str
= this->Read(object
);
723 return item
->value
->compare(str
) == 0;
726 bool ListSettingDesc::IsSameValue(const IniItem
*item
, void *object
) const
728 /* Checking for equality is way more expensive than just writing the value. */
733 * Loads all items from a 'grpname' section into a list
734 * The list parameter can be a nullptr pointer, in this case nothing will be
735 * saved and a callback function should be defined that will take over the
736 * list-handling and store the data itself somewhere.
737 * @param ini IniFile handle to the ini file with the source data
738 * @param grpname character string identifying the section-header of the ini file that will be parsed
739 * @param list new list with entries of the given section
741 static void IniLoadSettingList(IniFile
&ini
, const char *grpname
, StringList
&list
)
743 IniGroup
*group
= ini
.GetGroup(grpname
);
745 if (group
== nullptr) return;
749 for (const IniItem
*item
= group
->item
; item
!= nullptr; item
= item
->next
) {
750 if (!item
->name
.empty()) list
.push_back(item
->name
);
755 * Saves all items from a list into the 'grpname' section
756 * The list parameter can be a nullptr pointer, in this case a callback function
757 * should be defined that will provide the source data to be saved.
758 * @param ini IniFile handle to the ini file where the destination data is saved
759 * @param grpname character string identifying the section-header of the ini file
760 * @param list pointer to an string(pointer) array that will be used as the
761 * source to be saved into the relevant ini section
763 static void IniSaveSettingList(IniFile
&ini
, const char *grpname
, StringList
&list
)
765 IniGroup
*group
= ini
.GetGroup(grpname
);
767 if (group
== nullptr) return;
770 for (const auto &iter
: list
) {
771 group
->GetItem(iter
, true)->SetValue("");
776 * Load a WindowDesc from config.
777 * @param ini IniFile handle to the ini file with the source data
778 * @param grpname character string identifying the section-header of the ini file that will be parsed
779 * @param desc Destination WindowDesc
781 void IniLoadWindowSettings(IniFile
&ini
, const char *grpname
, void *desc
)
783 IniLoadSettings(ini
, _window_settings
, grpname
, desc
, false);
787 * Save a WindowDesc to config.
788 * @param ini IniFile handle to the ini file where the destination data is saved
789 * @param grpname character string identifying the section-header of the ini file
790 * @param desc Source WindowDesc
792 void IniSaveWindowSettings(IniFile
&ini
, const char *grpname
, void *desc
)
794 IniSaveSettings(ini
, _window_settings
, grpname
, desc
, false);
798 * Check whether the setting is editable in the current gamemode.
799 * @param do_command true if this is about checking a command from the server.
800 * @return true if editable.
802 bool SettingDesc::IsEditable(bool do_command
) const
804 if (!do_command
&& !(this->flags
& SF_NO_NETWORK_SYNC
) && _networking
&& !_network_server
&& !(this->flags
& SF_PER_COMPANY
)) return false;
805 if ((this->flags
& SF_NETWORK_ONLY
) && !_networking
&& _game_mode
!= GM_MENU
) return false;
806 if ((this->flags
& SF_NO_NETWORK
) && _networking
) return false;
807 if ((this->flags
& SF_NEWGAME_ONLY
) &&
808 (_game_mode
== GM_NORMAL
||
809 (_game_mode
== GM_EDITOR
&& !(this->flags
& SF_SCENEDIT_TOO
)))) return false;
810 if ((this->flags
& SF_SCENEDIT_ONLY
) && _game_mode
!= GM_EDITOR
) return false;
815 * Return the type of the setting.
816 * @return type of setting
818 SettingType
SettingDesc::GetType() const
820 if (this->flags
& SF_PER_COMPANY
) return ST_COMPANY
;
821 return (this->flags
& SF_NOT_IN_SAVE
) ? ST_CLIENT
: ST_GAME
;
825 * Get the setting description of this setting as an integer setting.
826 * @return The integer setting description.
828 const IntSettingDesc
*SettingDesc::AsIntSetting() const
830 assert(this->IsIntSetting());
831 return static_cast<const IntSettingDesc
*>(this);
835 * Get the setting description of this setting as a string setting.
836 * @return The string setting description.
838 const StringSettingDesc
*SettingDesc::AsStringSetting() const
840 assert(this->IsStringSetting());
841 return static_cast<const StringSettingDesc
*>(this);
844 void PrepareOldDiffCustom();
845 void HandleOldDiffCustom(bool savegame
);
848 /** Checks if any settings are set to incorrect values, and sets them to correct values in that case. */
849 static void ValidateSettings()
851 /* Do not allow a custom sea level with the original land generator. */
852 if (_settings_newgame
.game_creation
.land_generator
== LG_ORIGINAL
&&
853 _settings_newgame
.difficulty
.quantity_sea_lakes
== CUSTOM_SEA_LEVEL_NUMBER_DIFFICULTY
) {
854 _settings_newgame
.difficulty
.quantity_sea_lakes
= CUSTOM_SEA_LEVEL_MIN_PERCENTAGE
;
858 static void AILoadConfig(IniFile
&ini
, const char *grpname
)
860 IniGroup
*group
= ini
.GetGroup(grpname
);
863 /* Clean any configured AI */
864 for (CompanyID c
= COMPANY_FIRST
; c
< MAX_COMPANIES
; c
++) {
865 AIConfig::GetConfig(c
, AIConfig::SSS_FORCE_NEWGAME
)->Change(nullptr);
868 /* If no group exists, return */
869 if (group
== nullptr) return;
871 CompanyID c
= COMPANY_FIRST
;
872 for (item
= group
->item
; c
< MAX_COMPANIES
&& item
!= nullptr; c
++, item
= item
->next
) {
873 AIConfig
*config
= AIConfig::GetConfig(c
, AIConfig::SSS_FORCE_NEWGAME
);
875 config
->Change(item
->name
.c_str());
876 if (!config
->HasScript()) {
877 if (item
->name
!= "none") {
878 Debug(script
, 0, "The AI by the name '{}' was no longer found, and removed from the list.", item
->name
);
882 if (item
->value
.has_value()) config
->StringToSettings(*item
->value
);
886 static void GameLoadConfig(IniFile
&ini
, const char *grpname
)
888 IniGroup
*group
= ini
.GetGroup(grpname
);
891 /* Clean any configured GameScript */
892 GameConfig::GetConfig(GameConfig::SSS_FORCE_NEWGAME
)->Change(nullptr);
894 /* If no group exists, return */
895 if (group
== nullptr) return;
898 if (item
== nullptr) return;
900 GameConfig
*config
= GameConfig::GetConfig(AIConfig::SSS_FORCE_NEWGAME
);
902 config
->Change(item
->name
.c_str());
903 if (!config
->HasScript()) {
904 if (item
->name
!= "none") {
905 Debug(script
, 0, "The GameScript by the name '{}' was no longer found, and removed from the list.", item
->name
);
909 if (item
->value
.has_value()) config
->StringToSettings(*item
->value
);
913 * Convert a character to a hex nibble value, or \c -1 otherwise.
914 * @param c Character to convert.
915 * @return Hex value of the character, or \c -1 if not a hex digit.
917 static int DecodeHexNibble(char c
)
919 if (c
>= '0' && c
<= '9') return c
- '0';
920 if (c
>= 'A' && c
<= 'F') return c
+ 10 - 'A';
921 if (c
>= 'a' && c
<= 'f') return c
+ 10 - 'a';
926 * Parse a sequence of characters (supposedly hex digits) into a sequence of bytes.
927 * After the hex number should be a \c '|' character.
928 * @param pos First character to convert.
929 * @param[out] dest Output byte array to write the bytes.
930 * @param dest_size Number of bytes in \a dest.
931 * @return Whether reading was successful.
933 static bool DecodeHexText(const char *pos
, uint8
*dest
, size_t dest_size
)
935 while (dest_size
> 0) {
936 int hi
= DecodeHexNibble(pos
[0]);
937 int lo
= (hi
>= 0) ? DecodeHexNibble(pos
[1]) : -1;
938 if (lo
< 0) return false;
939 *dest
++ = (hi
<< 4) | lo
;
947 * Load a GRF configuration
948 * @param ini The configuration to read from.
949 * @param grpname Group name containing the configuration of the GRF.
950 * @param is_static GRF is static.
952 static GRFConfig
*GRFLoadConfig(IniFile
&ini
, const char *grpname
, bool is_static
)
954 IniGroup
*group
= ini
.GetGroup(grpname
);
956 GRFConfig
*first
= nullptr;
957 GRFConfig
**curr
= &first
;
959 if (group
== nullptr) return nullptr;
962 for (item
= group
->item
; item
!= nullptr; item
= item
->next
) {
963 GRFConfig
*c
= nullptr;
965 uint8 grfid_buf
[4], md5sum
[16];
966 const char *filename
= item
->name
.c_str();
967 bool has_grfid
= false;
968 bool has_md5sum
= false;
970 /* Try reading "<grfid>|" and on success, "<md5sum>|". */
971 has_grfid
= DecodeHexText(filename
, grfid_buf
, lengthof(grfid_buf
));
973 filename
+= 1 + 2 * lengthof(grfid_buf
);
974 has_md5sum
= DecodeHexText(filename
, md5sum
, lengthof(md5sum
));
975 if (has_md5sum
) filename
+= 1 + 2 * lengthof(md5sum
);
977 uint32 grfid
= grfid_buf
[0] | (grfid_buf
[1] << 8) | (grfid_buf
[2] << 16) | (grfid_buf
[3] << 24);
979 const GRFConfig
*s
= FindGRFConfig(grfid
, FGCM_EXACT
, md5sum
);
980 if (s
!= nullptr) c
= new GRFConfig(*s
);
982 if (c
== nullptr && !FioCheckFileExists(filename
, NEWGRF_DIR
)) {
983 const GRFConfig
*s
= FindGRFConfig(grfid
, FGCM_NEWEST_VALID
);
984 if (s
!= nullptr) c
= new GRFConfig(*s
);
987 if (c
== nullptr) c
= new GRFConfig(filename
);
989 /* Parse parameters */
990 if (item
->value
.has_value() && !item
->value
->empty()) {
991 int count
= ParseIntList(item
->value
->c_str(), c
->param
, lengthof(c
->param
));
993 SetDParamStr(0, filename
);
994 ShowErrorMessage(STR_CONFIG_ERROR
, STR_CONFIG_ERROR_ARRAY
, WL_CRITICAL
);
997 c
->num_params
= count
;
1000 /* Check if item is valid */
1001 if (!FillGRFDetails(c
, is_static
) || HasBit(c
->flags
, GCF_INVALID
)) {
1002 if (c
->status
== GCS_NOT_FOUND
) {
1003 SetDParam(1, STR_CONFIG_ERROR_INVALID_GRF_NOT_FOUND
);
1004 } else if (HasBit(c
->flags
, GCF_UNSAFE
)) {
1005 SetDParam(1, STR_CONFIG_ERROR_INVALID_GRF_UNSAFE
);
1006 } else if (HasBit(c
->flags
, GCF_SYSTEM
)) {
1007 SetDParam(1, STR_CONFIG_ERROR_INVALID_GRF_SYSTEM
);
1008 } else if (HasBit(c
->flags
, GCF_INVALID
)) {
1009 SetDParam(1, STR_CONFIG_ERROR_INVALID_GRF_INCOMPATIBLE
);
1011 SetDParam(1, STR_CONFIG_ERROR_INVALID_GRF_UNKNOWN
);
1014 SetDParamStr(0, StrEmpty(filename
) ? item
->name
: filename
);
1015 ShowErrorMessage(STR_CONFIG_ERROR
, STR_CONFIG_ERROR_INVALID_GRF
, WL_CRITICAL
);
1020 /* Check for duplicate GRFID (will also check for duplicate filenames) */
1021 bool duplicate
= false;
1022 for (const GRFConfig
*gc
= first
; gc
!= nullptr; gc
= gc
->next
) {
1023 if (gc
->ident
.grfid
== c
->ident
.grfid
) {
1024 SetDParamStr(0, c
->filename
);
1025 SetDParamStr(1, gc
->filename
);
1026 ShowErrorMessage(STR_CONFIG_ERROR
, STR_CONFIG_ERROR_DUPLICATE_GRFID
, WL_CRITICAL
);
1037 /* Mark file as static to avoid saving in savegame. */
1038 SetBit(c
->flags
, GCF_STATIC
);
1039 } else if (++num_grfs
> NETWORK_MAX_GRF_COUNT
) {
1040 /* Check we will not load more non-static NewGRFs than allowed. This could trigger issues for game servers. */
1041 ShowErrorMessage(STR_CONFIG_ERROR
, STR_NEWGRF_ERROR_TOO_MANY_NEWGRFS_LOADED
, WL_CRITICAL
);
1045 /* Add item to list */
1053 static IniFileVersion
LoadVersionFromConfig(IniFile
&ini
)
1055 IniGroup
*group
= ini
.GetGroup("version");
1057 auto version_number
= group
->GetItem("ini_version", false);
1058 /* Older ini-file versions don't have this key yet. */
1059 if (version_number
== nullptr || !version_number
->value
.has_value()) return IFV_0
;
1062 std::from_chars(version_number
->value
->data(), version_number
->value
->data() + version_number
->value
->size(), version
);
1064 return static_cast<IniFileVersion
>(version
);
1067 static void AISaveConfig(IniFile
&ini
, const char *grpname
)
1069 IniGroup
*group
= ini
.GetGroup(grpname
);
1071 if (group
== nullptr) return;
1074 for (CompanyID c
= COMPANY_FIRST
; c
< MAX_COMPANIES
; c
++) {
1075 AIConfig
*config
= AIConfig::GetConfig(c
, AIConfig::SSS_FORCE_NEWGAME
);
1077 std::string value
= config
->SettingsToString();
1079 if (config
->HasScript()) {
1080 name
= config
->GetName();
1085 IniItem
*item
= new IniItem(group
, name
);
1086 item
->SetValue(value
);
1090 static void GameSaveConfig(IniFile
&ini
, const char *grpname
)
1092 IniGroup
*group
= ini
.GetGroup(grpname
);
1094 if (group
== nullptr) return;
1097 GameConfig
*config
= GameConfig::GetConfig(AIConfig::SSS_FORCE_NEWGAME
);
1099 std::string value
= config
->SettingsToString();
1101 if (config
->HasScript()) {
1102 name
= config
->GetName();
1107 IniItem
*item
= new IniItem(group
, name
);
1108 item
->SetValue(value
);
1112 * Save the version of OpenTTD to the ini file.
1113 * @param ini the ini to write to
1115 static void SaveVersionInConfig(IniFile
&ini
)
1117 IniGroup
*group
= ini
.GetGroup("version");
1118 group
->GetItem("version_string", true)->SetValue(_openttd_revision
);
1119 group
->GetItem("version_number", true)->SetValue(fmt::format("{:08X}", _openttd_newgrf_version
));
1120 group
->GetItem("ini_version", true)->SetValue(std::to_string(INIFILE_VERSION
));
1123 /* Save a GRF configuration to the given group name */
1124 static void GRFSaveConfig(IniFile
&ini
, const char *grpname
, const GRFConfig
*list
)
1126 ini
.RemoveGroup(grpname
);
1127 IniGroup
*group
= ini
.GetGroup(grpname
);
1130 for (c
= list
; c
!= nullptr; c
= c
->next
) {
1131 /* Hex grfid (4 bytes in nibbles), "|", hex md5sum (16 bytes in nibbles), "|", file system path. */
1132 char key
[4 * 2 + 1 + 16 * 2 + 1 + MAX_PATH
];
1134 GRFBuildParamList(params
, c
, lastof(params
));
1136 char *pos
= key
+ seprintf(key
, lastof(key
), "%08X|", BSWAP32(c
->ident
.grfid
));
1137 pos
= md5sumToString(pos
, lastof(key
), c
->ident
.md5sum
);
1138 seprintf(pos
, lastof(key
), "|%s", c
->filename
);
1139 group
->GetItem(key
, true)->SetValue(params
);
1143 /* Common handler for saving/loading variables to the configuration file */
1144 static void HandleSettingDescs(IniFile
&generic_ini
, IniFile
&private_ini
, IniFile
&secrets_ini
, SettingDescProc
*proc
, SettingDescProcList
*proc_list
, bool only_startup
= false)
1146 proc(generic_ini
, _misc_settings
, "misc", nullptr, only_startup
);
1147 #if defined(_WIN32) && !defined(DEDICATED)
1148 proc(generic_ini
, _win32_settings
, "win32", nullptr, only_startup
);
1151 /* The name "patches" is a fallback, as every setting should sets its own group. */
1153 for (auto &table
: GenericSettingTables()) {
1154 proc(generic_ini
, table
, "patches", &_settings_newgame
, only_startup
);
1156 for (auto &table
: PrivateSettingTables()) {
1157 proc(private_ini
, table
, "patches", &_settings_newgame
, only_startup
);
1159 for (auto &table
: SecretSettingTables()) {
1160 proc(secrets_ini
, table
, "patches", &_settings_newgame
, only_startup
);
1163 proc(generic_ini
, _currency_settings
, "currency", &_custom_currency
, only_startup
);
1164 proc(generic_ini
, _company_settings
, "company", &_settings_client
.company
, only_startup
);
1166 if (!only_startup
) {
1167 proc_list(private_ini
, "server_bind_addresses", _network_bind_list
);
1168 proc_list(private_ini
, "servers", _network_host_list
);
1169 proc_list(private_ini
, "bans", _network_ban_list
);
1174 * Remove all entries from a settings table from an ini-file.
1176 * This is only useful if those entries are moved to another file, and you
1177 * want to clean up what is left behind.
1179 * @param ini The ini file to remove the entries from.
1180 * @param table The table to look for entries to remove.
1182 static void RemoveEntriesFromIni(IniFile
&ini
, const SettingTable
&table
)
1184 for (auto &desc
: table
) {
1185 const SettingDesc
*sd
= GetSettingDesc(desc
);
1187 /* For settings.xx.yy load the settings from [xx] yy = ? */
1188 std::string s
{ sd
->GetName() };
1189 auto sc
= s
.find('.');
1190 if (sc
== std::string::npos
) continue;
1192 IniGroup
*group
= ini
.GetGroup(s
.substr(0, sc
));
1193 s
= s
.substr(sc
+ 1);
1195 group
->RemoveItem(s
);
1200 * Load the values from the configuration files
1201 * @param startup Load the minimal amount of the configuration to "bootstrap" the blitter and such.
1203 void LoadFromConfig(bool startup
)
1205 ConfigIniFile
generic_ini(_config_file
);
1206 ConfigIniFile
private_ini(_private_file
);
1207 ConfigIniFile
secrets_ini(_secrets_file
);
1209 if (!startup
) ResetCurrencies(false); // Initialize the array of currencies, without preserving the custom one
1211 IniFileVersion generic_version
= LoadVersionFromConfig(generic_ini
);
1213 /* Before the split of private/secrets, we have to look in the generic for these settings. */
1214 if (generic_version
< IFV_PRIVATE_SECRETS
) {
1215 HandleSettingDescs(generic_ini
, generic_ini
, generic_ini
, IniLoadSettings
, IniLoadSettingList
, startup
);
1217 HandleSettingDescs(generic_ini
, private_ini
, secrets_ini
, IniLoadSettings
, IniLoadSettingList
, startup
);
1220 /* Load basic settings only during bootstrap, load other settings not during bootstrap */
1222 /* Convert network.server_advertise to network.server_game_type, but only if network.server_game_type is set to default value. */
1223 if (generic_version
< IFV_GAME_TYPE
) {
1224 if (_settings_client
.network
.server_game_type
== SERVER_GAME_TYPE_LOCAL
) {
1225 IniGroup
*network
= generic_ini
.GetGroup("network", false);
1226 if (network
!= nullptr) {
1227 IniItem
*server_advertise
= network
->GetItem("server_advertise", false);
1228 if (server_advertise
!= nullptr && server_advertise
->value
== "true") {
1229 _settings_client
.network
.server_game_type
= SERVER_GAME_TYPE_PUBLIC
;
1235 _grfconfig_newgame
= GRFLoadConfig(generic_ini
, "newgrf", false);
1236 _grfconfig_static
= GRFLoadConfig(generic_ini
, "newgrf-static", true);
1237 AILoadConfig(generic_ini
, "ai_players");
1238 GameLoadConfig(generic_ini
, "game_scripts");
1240 PrepareOldDiffCustom();
1241 IniLoadSettings(generic_ini
, _old_gameopt_settings
, "gameopt", &_settings_newgame
, false);
1242 HandleOldDiffCustom(false);
1245 DebugReconsiderSendRemoteMessages();
1247 /* Display scheduled errors */
1248 extern void ScheduleErrorMessage(ErrorList
&datas
);
1249 ScheduleErrorMessage(_settings_error_list
);
1250 if (FindWindowById(WC_ERRMSG
, 0) == nullptr) ShowFirstError();
1254 /** Save the values to the configuration file */
1257 ConfigIniFile
generic_ini(_config_file
);
1258 ConfigIniFile
private_ini(_private_file
);
1259 ConfigIniFile
secrets_ini(_secrets_file
);
1261 IniFileVersion generic_version
= LoadVersionFromConfig(generic_ini
);
1263 /* If we newly create the private/secrets file, add a dummy group on top
1264 * just so we can add a comment before it (that is how IniFile works).
1265 * This to explain what the file is about. After doing it once, never touch
1266 * it again, as otherwise we might be reverting user changes. */
1267 if (!private_ini
.GetGroup("private", false)) private_ini
.GetGroup("private")->comment
= "; This file possibly contains private information which can identify you as person.\n";
1268 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";
1270 if (generic_version
== IFV_0
) {
1271 /* Remove some obsolete groups. These have all been loaded into other groups. */
1272 generic_ini
.RemoveGroup("patches");
1273 generic_ini
.RemoveGroup("yapf");
1274 generic_ini
.RemoveGroup("gameopt");
1276 /* Remove all settings from the generic ini that are now in the private ini. */
1277 generic_ini
.RemoveGroup("server_bind_addresses");
1278 generic_ini
.RemoveGroup("servers");
1279 generic_ini
.RemoveGroup("bans");
1280 for (auto &table
: PrivateSettingTables()) {
1281 RemoveEntriesFromIni(generic_ini
, table
);
1284 /* Remove all settings from the generic ini that are now in the secrets ini. */
1285 for (auto &table
: SecretSettingTables()) {
1286 RemoveEntriesFromIni(generic_ini
, table
);
1290 /* Remove network.server_advertise. */
1291 if (generic_version
< IFV_GAME_TYPE
) {
1292 IniGroup
*network
= generic_ini
.GetGroup("network", false);
1293 if (network
!= nullptr) {
1294 network
->RemoveItem("server_advertise");
1298 HandleSettingDescs(generic_ini
, private_ini
, secrets_ini
, IniSaveSettings
, IniSaveSettingList
);
1299 GRFSaveConfig(generic_ini
, "newgrf", _grfconfig_newgame
);
1300 GRFSaveConfig(generic_ini
, "newgrf-static", _grfconfig_static
);
1301 AISaveConfig(generic_ini
, "ai_players");
1302 GameSaveConfig(generic_ini
, "game_scripts");
1304 SaveVersionInConfig(generic_ini
);
1305 SaveVersionInConfig(private_ini
);
1306 SaveVersionInConfig(secrets_ini
);
1308 generic_ini
.SaveToDisk(_config_file
);
1309 private_ini
.SaveToDisk(_private_file
);
1310 secrets_ini
.SaveToDisk(_secrets_file
);
1314 * Get the list of known NewGrf presets.
1315 * @returns List of preset names.
1317 StringList
GetGRFPresetList()
1321 ConfigIniFile
ini(_config_file
);
1322 for (IniGroup
*group
= ini
.group
; group
!= nullptr; group
= group
->next
) {
1323 if (group
->name
.compare(0, 7, "preset-") == 0) {
1324 list
.push_back(group
->name
.substr(7));
1332 * Load a NewGRF configuration by preset-name.
1333 * @param config_name Name of the preset.
1334 * @return NewGRF configuration.
1335 * @see GetGRFPresetList
1337 GRFConfig
*LoadGRFPresetFromConfig(const char *config_name
)
1339 size_t len
= strlen(config_name
) + 8;
1340 char *section
= (char*)alloca(len
);
1341 seprintf(section
, section
+ len
- 1, "preset-%s", config_name
);
1343 ConfigIniFile
ini(_config_file
);
1344 GRFConfig
*config
= GRFLoadConfig(ini
, section
, false);
1350 * Save a NewGRF configuration with a preset name.
1351 * @param config_name Name of the preset.
1352 * @param config NewGRF configuration to save.
1353 * @see GetGRFPresetList
1355 void SaveGRFPresetToConfig(const char *config_name
, GRFConfig
*config
)
1357 size_t len
= strlen(config_name
) + 8;
1358 char *section
= (char*)alloca(len
);
1359 seprintf(section
, section
+ len
- 1, "preset-%s", config_name
);
1361 ConfigIniFile
ini(_config_file
);
1362 GRFSaveConfig(ini
, section
, config
);
1363 ini
.SaveToDisk(_config_file
);
1367 * Delete a NewGRF configuration by preset name.
1368 * @param config_name Name of the preset.
1370 void DeleteGRFPresetFromConfig(const char *config_name
)
1372 size_t len
= strlen(config_name
) + 8;
1373 char *section
= (char*)alloca(len
);
1374 seprintf(section
, section
+ len
- 1, "preset-%s", config_name
);
1376 ConfigIniFile
ini(_config_file
);
1377 ini
.RemoveGroup(section
);
1378 ini
.SaveToDisk(_config_file
);
1382 * Handle changing a value. This performs validation of the input value and
1383 * calls the appropriate callbacks, and saves it when the value is changed.
1384 * @param object The object the setting is in.
1385 * @param newval The new value for the setting.
1387 void IntSettingDesc::ChangeValue(const void *object
, int32 newval
) const
1389 int32 oldval
= this->Read(object
);
1390 this->MakeValueValid(newval
);
1391 if (this->pre_check
!= nullptr && !this->pre_check(newval
)) return;
1392 if (oldval
== newval
) return;
1394 this->Write(object
, newval
);
1395 if (this->post_callback
!= nullptr) this->post_callback(newval
);
1397 if (this->flags
& SF_NO_NETWORK
) {
1398 GamelogStartAction(GLAT_SETTING
);
1399 GamelogSetting(this->GetName(), oldval
, newval
);
1400 GamelogStopAction();
1403 SetWindowClassesDirty(WC_GAME_OPTIONS
);
1405 if (_save_config
) SaveToConfig();
1409 * Given a name of setting, return a setting description from the table.
1410 * @param name Name of the setting to return a setting description of.
1411 * @param settings Table to look in for the setting.
1412 * @return Pointer to the setting description of setting \a name if it can be found,
1413 * \c nullptr indicates failure to obtain the description.
1415 static const SettingDesc
*GetSettingFromName(const std::string_view name
, const SettingTable
&settings
)
1417 /* First check all full names */
1418 for (auto &desc
: settings
) {
1419 const SettingDesc
*sd
= GetSettingDesc(desc
);
1420 if (!SlIsObjectCurrentlyValid(sd
->save
.version_from
, sd
->save
.version_to
)) continue;
1421 if (sd
->GetName() == name
) return sd
;
1424 /* Then check the shortcut variant of the name. */
1425 std::string short_name_suffix
= std::string
{ "." }.append(name
);
1426 for (auto &desc
: settings
) {
1427 const SettingDesc
*sd
= GetSettingDesc(desc
);
1428 if (!SlIsObjectCurrentlyValid(sd
->save
.version_from
, sd
->save
.version_to
)) continue;
1429 if (StrEndsWith(sd
->GetName(), short_name_suffix
)) return sd
;
1436 * Get the SaveLoad for all settings in the settings table.
1437 * @param settings The settings table to get the SaveLoad objects from.
1438 * @param saveloads A vector to store the result in.
1440 void GetSaveLoadFromSettingTable(SettingTable settings
, std::vector
<SaveLoad
> &saveloads
)
1442 for (auto &desc
: settings
) {
1443 const SettingDesc
*sd
= GetSettingDesc(desc
);
1444 if (!SlIsObjectCurrentlyValid(sd
->save
.version_from
, sd
->save
.version_to
)) continue;
1445 saveloads
.push_back(sd
->save
);
1450 * Given a name of setting, return a company setting description of it.
1451 * @param name Name of the company setting to return a setting description of.
1452 * @return Pointer to the setting description of setting \a name if it can be found,
1453 * \c nullptr indicates failure to obtain the description.
1455 static const SettingDesc
*GetCompanySettingFromName(std::string_view name
)
1457 static const std::string_view company_prefix
= "company.";
1458 if (StrStartsWith(name
, company_prefix
)) name
.remove_prefix(company_prefix
.size());
1459 return GetSettingFromName(name
, _company_settings
);
1463 * Given a name of any setting, return any setting description of it.
1464 * @param name Name of the setting to return a setting description of.
1465 * @return Pointer to the setting description of setting \a name if it can be found,
1466 * \c nullptr indicates failure to obtain the description.
1468 const SettingDesc
*GetSettingFromName(const std::string_view name
)
1470 for (auto &table
: GenericSettingTables()) {
1471 auto sd
= GetSettingFromName(name
, table
);
1472 if (sd
!= nullptr) return sd
;
1474 for (auto &table
: PrivateSettingTables()) {
1475 auto sd
= GetSettingFromName(name
, table
);
1476 if (sd
!= nullptr) return sd
;
1478 for (auto &table
: SecretSettingTables()) {
1479 auto sd
= GetSettingFromName(name
, table
);
1480 if (sd
!= nullptr) return sd
;
1483 return GetCompanySettingFromName(name
);
1487 * Network-safe changing of settings (server-only).
1488 * @param tile unused
1489 * @param flags operation to perform
1491 * @param p2 the new value for the setting
1492 * The new value is properly clamped to its minimum/maximum when setting
1493 * @param text the name of the setting to change
1494 * @return the cost of this operation or an error
1497 CommandCost
CmdChangeSetting(TileIndex tile
, DoCommandFlag flags
, uint32 p1
, uint32 p2
, const std::string
&text
)
1499 if (text
.empty()) return CMD_ERROR
;
1500 const SettingDesc
*sd
= GetSettingFromName(text
);
1502 if (sd
== nullptr) return CMD_ERROR
;
1503 if (!SlIsObjectCurrentlyValid(sd
->save
.version_from
, sd
->save
.version_to
)) return CMD_ERROR
;
1504 if (!sd
->IsIntSetting()) return CMD_ERROR
;
1506 if (!sd
->IsEditable(true)) return CMD_ERROR
;
1508 if (flags
& DC_EXEC
) {
1509 sd
->AsIntSetting()->ChangeValue(&GetGameSettings(), p2
);
1512 return CommandCost();
1516 * Change one of the per-company settings.
1517 * @param tile unused
1518 * @param flags operation to perform
1520 * @param p2 the new value for the setting
1521 * The new value is properly clamped to its minimum/maximum when setting
1522 * @param text the name of the company setting to change
1523 * @return the cost of this operation or an error
1525 CommandCost
CmdChangeCompanySetting(TileIndex tile
, DoCommandFlag flags
, uint32 p1
, uint32 p2
, const std::string
&text
)
1527 if (text
.empty()) return CMD_ERROR
;
1528 const SettingDesc
*sd
= GetCompanySettingFromName(text
.c_str());
1530 if (sd
== nullptr) return CMD_ERROR
;
1531 if (!sd
->IsIntSetting()) return CMD_ERROR
;
1533 if (flags
& DC_EXEC
) {
1534 sd
->AsIntSetting()->ChangeValue(&Company::Get(_current_company
)->settings
, p2
);
1537 return CommandCost();
1541 * Top function to save the new value of an element of the Settings struct
1542 * @param index offset in the SettingDesc array of the Settings struct which
1543 * identifies the setting member we want to change
1544 * @param value new value of the setting
1545 * @param force_newgame force the newgame settings
1547 bool SetSettingValue(const IntSettingDesc
*sd
, int32 value
, bool force_newgame
)
1549 const IntSettingDesc
*setting
= sd
->AsIntSetting();
1550 if ((setting
->flags
& SF_PER_COMPANY
) != 0) {
1551 if (Company::IsValidID(_local_company
) && _game_mode
!= GM_MENU
) {
1552 return DoCommandP(0, 0, value
, CMD_CHANGE_COMPANY_SETTING
, nullptr, setting
->GetName());
1555 setting
->ChangeValue(&_settings_client
.company
, value
);
1559 /* If an item is company-based, we do not send it over the network
1560 * (if any) to change. Also *hack*hack* we update the _newgame version
1561 * of settings because changing a company-based setting in a game also
1562 * changes its defaults. At least that is the convention we have chosen */
1563 if (setting
->flags
& SF_NO_NETWORK_SYNC
) {
1564 if (_game_mode
!= GM_MENU
) {
1565 setting
->ChangeValue(&_settings_newgame
, value
);
1567 setting
->ChangeValue(&GetGameSettings(), value
);
1571 if (force_newgame
) {
1572 setting
->ChangeValue(&_settings_newgame
, value
);
1576 /* send non-company-based settings over the network */
1577 if (!_networking
|| (_networking
&& _network_server
)) {
1578 return DoCommandP(0, 0, value
, CMD_CHANGE_SETTING
, nullptr, setting
->GetName());
1584 * Set the company settings for a new company to their default values.
1586 void SetDefaultCompanySettings(CompanyID cid
)
1588 Company
*c
= Company::Get(cid
);
1589 for (auto &desc
: _company_settings
) {
1590 const IntSettingDesc
*int_setting
= GetSettingDesc(desc
)->AsIntSetting();
1591 int_setting
->MakeValueValidAndWrite(&c
->settings
, int_setting
->def
);
1596 * Sync all company settings in a multiplayer game.
1598 void SyncCompanySettings()
1600 const void *old_object
= &Company::Get(_current_company
)->settings
;
1601 const void *new_object
= &_settings_client
.company
;
1602 for (auto &desc
: _company_settings
) {
1603 const SettingDesc
*sd
= GetSettingDesc(desc
);
1604 uint32 old_value
= (uint32
)sd
->AsIntSetting()->Read(new_object
);
1605 uint32 new_value
= (uint32
)sd
->AsIntSetting()->Read(old_object
);
1606 if (old_value
!= new_value
) NetworkSendCommand(0, 0, new_value
, CMD_CHANGE_COMPANY_SETTING
, nullptr, sd
->GetName(), _local_company
);
1611 * Set a setting value with a string.
1612 * @param sd the setting to change.
1613 * @param value the value to write
1614 * @param force_newgame force the newgame settings
1615 * @note Strings WILL NOT be synced over the network
1617 bool SetSettingValue(const StringSettingDesc
*sd
, std::string value
, bool force_newgame
)
1619 assert(sd
->flags
& SF_NO_NETWORK_SYNC
);
1621 if (GetVarMemType(sd
->save
.conv
) == SLE_VAR_STRQ
&& value
.compare("(null)") == 0) {
1625 const void *object
= (_game_mode
== GM_MENU
|| force_newgame
) ? &_settings_newgame
: &_settings_game
;
1626 sd
->AsStringSetting()->ChangeValue(object
, value
);
1631 * Handle changing a string value. This performs validation of the input value
1632 * and calls the appropriate callbacks, and saves it when the value is changed.
1633 * @param object The object the setting is in.
1634 * @param newval The new value for the setting.
1636 void StringSettingDesc::ChangeValue(const void *object
, std::string
&newval
) const
1638 this->MakeValueValid(newval
);
1639 if (this->pre_check
!= nullptr && !this->pre_check(newval
)) return;
1641 this->Write(object
, newval
);
1642 if (this->post_callback
!= nullptr) this->post_callback(newval
);
1644 if (_save_config
) SaveToConfig();
1647 /* Those 2 functions need to be here, else we have to make some stuff non-static
1648 * and besides, it is also better to keep stuff like this at the same place */
1649 void IConsoleSetSetting(const char *name
, const char *value
, bool force_newgame
)
1651 const SettingDesc
*sd
= GetSettingFromName(name
);
1652 if (sd
== nullptr) {
1653 IConsolePrint(CC_ERROR
, "'{}' is an unknown setting.", name
);
1657 bool success
= true;
1658 if (sd
->IsStringSetting()) {
1659 success
= SetSettingValue(sd
->AsStringSetting(), value
, force_newgame
);
1660 } else if (sd
->IsIntSetting()) {
1661 const IntSettingDesc
*isd
= sd
->AsIntSetting();
1662 size_t val
= isd
->ParseValue(value
);
1663 if (!_settings_error_list
.empty()) {
1664 IConsolePrint(CC_ERROR
, "'{}' is not a valid value for this setting.", value
);
1665 _settings_error_list
.clear();
1668 success
= SetSettingValue(isd
, (int32
)val
, force_newgame
);
1672 if (_network_server
) {
1673 IConsolePrint(CC_ERROR
, "This command/variable is not available during network games.");
1675 IConsolePrint(CC_ERROR
, "This command/variable is only available to a network server.");
1680 void IConsoleSetSetting(const char *name
, int value
)
1682 const SettingDesc
*sd
= GetSettingFromName(name
);
1683 assert(sd
!= nullptr);
1684 SetSettingValue(sd
->AsIntSetting(), value
);
1688 * Output value of a specific setting to the console
1689 * @param name Name of the setting to output its value
1690 * @param force_newgame force the newgame settings
1692 void IConsoleGetSetting(const char *name
, bool force_newgame
)
1694 const SettingDesc
*sd
= GetSettingFromName(name
);
1695 if (sd
== nullptr) {
1696 IConsolePrint(CC_ERROR
, "'{}' is an unknown setting.", name
);
1700 const void *object
= (_game_mode
== GM_MENU
|| force_newgame
) ? &_settings_newgame
: &_settings_game
;
1702 if (sd
->IsStringSetting()) {
1703 IConsolePrint(CC_INFO
, "Current value for '{}' is '{}'.", sd
->GetName(), sd
->AsStringSetting()->Read(object
));
1704 } else if (sd
->IsIntSetting()) {
1706 sd
->FormatValue(value
, lastof(value
), object
);
1707 const IntSettingDesc
*int_setting
= sd
->AsIntSetting();
1708 IConsolePrint(CC_INFO
, "Current value for '{}' is '{}' (min: {}{}, max: {}).",
1709 sd
->GetName(), value
, (sd
->flags
& SF_GUI_0_IS_SPECIAL
) ? "(0) " : "", int_setting
->min
, int_setting
->max
);
1713 static void IConsoleListSettingsTable(const SettingTable
&table
, const char *prefilter
)
1715 for (auto &desc
: table
) {
1716 const SettingDesc
*sd
= GetSettingDesc(desc
);
1717 if (!SlIsObjectCurrentlyValid(sd
->save
.version_from
, sd
->save
.version_to
)) continue;
1718 if (prefilter
!= nullptr && sd
->GetName().find(prefilter
) == std::string::npos
) continue;
1720 sd
->FormatValue(value
, lastof(value
), &GetGameSettings());
1721 IConsolePrint(CC_DEFAULT
, "{} = {}", sd
->GetName(), value
);
1726 * List all settings and their value to the console
1728 * @param prefilter If not \c nullptr, only list settings with names that begin with \a prefilter prefix
1730 void IConsoleListSettings(const char *prefilter
)
1732 IConsolePrint(CC_HELP
, "All settings with their current value:");
1734 for (auto &table
: GenericSettingTables()) {
1735 IConsoleListSettingsTable(table
, prefilter
);
1737 for (auto &table
: PrivateSettingTables()) {
1738 IConsoleListSettingsTable(table
, prefilter
);
1740 for (auto &table
: SecretSettingTables()) {
1741 IConsoleListSettingsTable(table
, prefilter
);
1744 IConsolePrint(CC_HELP
, "Use 'setting' command to change a value.");