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/>.
8 /** @file script_info.cpp Implementation of ScriptInfo. */
10 #include "../stdafx.h"
11 #include "../settings_type.h"
13 #include "squirrel_helper.hpp"
15 #include "script_info.hpp"
16 #include "script_scanner.hpp"
17 #include "../3rdparty/fmt/format.h"
19 #include "../safeguards.h"
21 bool ScriptInfo::CheckMethod(const char *name
) const
23 if (!this->engine
->MethodExists(this->SQ_instance
, name
)) {
24 this->engine
->ThrowError(fmt::format("your info.nut/library.nut doesn't have the method '{}'", name
));
30 /* static */ SQInteger
ScriptInfo::Constructor(HSQUIRRELVM vm
, ScriptInfo
*info
)
32 /* Set some basic info from the parent */
33 Squirrel::GetInstance(vm
, &info
->SQ_instance
, 2);
34 /* Make sure the instance stays alive over time */
35 sq_addref(vm
, &info
->SQ_instance
);
37 info
->scanner
= (ScriptScanner
*)Squirrel::GetGlobalPointer(vm
);
38 info
->engine
= info
->scanner
->GetEngine();
40 /* Ensure the mandatory functions exist */
41 static const char * const required_functions
[] = {
50 for (size_t i
= 0; i
< lengthof(required_functions
); i
++) {
51 if (!info
->CheckMethod(required_functions
[i
])) return SQ_ERROR
;
54 /* Get location information of the scanner */
55 info
->main_script
= info
->scanner
->GetMainScript();
56 info
->tar_file
= info
->scanner
->GetTarFile();
58 /* Cache the data the info file gives us. */
59 if (!info
->engine
->CallStringMethod(info
->SQ_instance
, "GetAuthor", &info
->author
, MAX_GET_OPS
)) return SQ_ERROR
;
60 if (!info
->engine
->CallStringMethod(info
->SQ_instance
, "GetName", &info
->name
, MAX_GET_OPS
)) return SQ_ERROR
;
61 if (!info
->engine
->CallStringMethod(info
->SQ_instance
, "GetShortName", &info
->short_name
, MAX_GET_OPS
)) return SQ_ERROR
;
62 if (!info
->engine
->CallStringMethod(info
->SQ_instance
, "GetDescription", &info
->description
, MAX_GET_OPS
)) return SQ_ERROR
;
63 if (!info
->engine
->CallStringMethod(info
->SQ_instance
, "GetDate", &info
->date
, MAX_GET_OPS
)) return SQ_ERROR
;
64 if (!info
->engine
->CallIntegerMethod(info
->SQ_instance
, "GetVersion", &info
->version
, MAX_GET_OPS
)) return SQ_ERROR
;
65 if (!info
->engine
->CallStringMethod(info
->SQ_instance
, "CreateInstance", &info
->instance_name
, MAX_CREATEINSTANCE_OPS
)) return SQ_ERROR
;
67 /* The GetURL function is optional. */
68 if (info
->engine
->MethodExists(info
->SQ_instance
, "GetURL")) {
69 if (!info
->engine
->CallStringMethod(info
->SQ_instance
, "GetURL", &info
->url
, MAX_GET_OPS
)) return SQ_ERROR
;
72 /* Check if we have settings */
73 if (info
->engine
->MethodExists(info
->SQ_instance
, "GetSettings")) {
74 if (!info
->GetSettings()) return SQ_ERROR
;
80 bool ScriptInfo::GetSettings()
82 return this->engine
->CallMethod(this->SQ_instance
, "GetSettings", nullptr, MAX_GET_SETTING_OPS
);
85 SQInteger
ScriptInfo::AddSetting(HSQUIRRELVM vm
)
87 ScriptConfigItem config
;
90 int easy_value
= INT32_MIN
;
91 int medium_value
= INT32_MIN
;
92 int hard_value
= INT32_MIN
;
94 /* Read the table, and find all properties we care about */
96 while (SQ_SUCCEEDED(sq_next(vm
, -2))) {
97 const SQChar
*key_string
;
98 if (SQ_FAILED(sq_getstring(vm
, -2, &key_string
))) return SQ_ERROR
;
99 std::string key
= StrMakeValid(key_string
);
102 const SQChar
*sqvalue
;
103 if (SQ_FAILED(sq_getstring(vm
, -1, &sqvalue
))) return SQ_ERROR
;
105 /* Don't allow '=' and ',' in configure setting names, as we need those
106 * 2 chars to nicely store the settings as a string. */
107 auto replace_with_underscore
= [](auto c
) { return c
== '=' || c
== ','; };
108 config
.name
= StrMakeValid(sqvalue
);
109 std::replace_if(config
.name
.begin(), config
.name
.end(), replace_with_underscore
, '_');
111 } else if (key
== "description") {
112 const SQChar
*sqdescription
;
113 if (SQ_FAILED(sq_getstring(vm
, -1, &sqdescription
))) return SQ_ERROR
;
114 config
.description
= StrMakeValid(sqdescription
);
116 } else if (key
== "min_value") {
118 if (SQ_FAILED(sq_getinteger(vm
, -1, &res
))) return SQ_ERROR
;
119 config
.min_value
= ClampTo
<int32_t>(res
);
121 } else if (key
== "max_value") {
123 if (SQ_FAILED(sq_getinteger(vm
, -1, &res
))) return SQ_ERROR
;
124 config
.max_value
= ClampTo
<int32_t>(res
);
126 } else if (key
== "easy_value") {
128 if (SQ_FAILED(sq_getinteger(vm
, -1, &res
))) return SQ_ERROR
;
129 easy_value
= ClampTo
<int32_t>(res
);
131 } else if (key
== "medium_value") {
133 if (SQ_FAILED(sq_getinteger(vm
, -1, &res
))) return SQ_ERROR
;
134 medium_value
= ClampTo
<int32_t>(res
);
136 } else if (key
== "hard_value") {
138 if (SQ_FAILED(sq_getinteger(vm
, -1, &res
))) return SQ_ERROR
;
139 hard_value
= ClampTo
<int32_t>(res
);
141 } else if (key
== "custom_value") {
143 } else if (key
== "default_value") {
145 if (SQ_FAILED(sq_getinteger(vm
, -1, &res
))) return SQ_ERROR
;
146 config
.default_value
= ClampTo
<int32_t>(res
);
148 } else if (key
== "random_deviation") {
150 if (SQ_FAILED(sq_getinteger(vm
, -1, &res
))) return SQ_ERROR
;
151 config
.random_deviation
= ClampTo
<int32_t>(abs(res
));
153 } else if (key
== "step_size") {
155 if (SQ_FAILED(sq_getinteger(vm
, -1, &res
))) return SQ_ERROR
;
156 config
.step_size
= ClampTo
<int32_t>(res
);
157 } else if (key
== "flags") {
159 if (SQ_FAILED(sq_getinteger(vm
, -1, &res
))) return SQ_ERROR
;
160 config
.flags
= (ScriptConfigFlags
)res
;
163 this->engine
->ThrowError(fmt::format("unknown setting property '{}'", key
));
171 /* Check if default_value is set. Although required, this was changed with
172 * 14.0, and as such, older AIs don't use it yet. So we convert the older
173 * values into a default_value. */
174 if ((items
& 0x080) == 0) {
175 /* Easy/medium/hard should all three be defined. */
176 if ((items
& 0x010) == 0 || (items
& 0x020) == 0 || (items
& 0x040) == 0) {
177 this->engine
->ThrowError("please define all properties of a setting (min/max not allowed for booleans)");
181 config
.default_value
= medium_value
;
182 /* If not boolean and no random deviation set, calculate it based on easy/hard difference. */
183 if ((config
.flags
& SCRIPTCONFIG_BOOLEAN
) == 0 && (items
& 0x200) == 0) {
184 config
.random_deviation
= abs(hard_value
- easy_value
) / 2;
189 /* For compatibility, also act like the default sets the easy/medium/hard. */
190 items
|= 0x010 | 0x020 | 0x040;
193 /* Don't allow both random_deviation and SCRIPTCONFIG_BOOLEAN to
194 * be set for the same config item. */
195 if ((items
& 0x200) != 0 && (config
.flags
& SCRIPTCONFIG_BOOLEAN
) != 0) {
196 this->engine
->ThrowError("setting both random_deviation and CONFIG_BOOLEAN is not allowed");
200 /* Reset the bit for random_deviation as it's optional. */
203 /* Make sure all properties are defined */
204 uint mask
= (config
.flags
& SCRIPTCONFIG_BOOLEAN
) ? 0x1F3 : 0x1FF;
206 this->engine
->ThrowError("please define all properties of a setting (min/max not allowed for booleans)");
210 this->config_list
.emplace_back(config
);
214 SQInteger
ScriptInfo::AddLabels(HSQUIRRELVM vm
)
216 const SQChar
*setting_name_str
;
217 if (SQ_FAILED(sq_getstring(vm
, -2, &setting_name_str
))) return SQ_ERROR
;
218 std::string setting_name
= StrMakeValid(setting_name_str
);
220 ScriptConfigItem
*config
= nullptr;
221 for (auto &item
: this->config_list
) {
222 if (item
.name
== setting_name
) config
= &item
;
225 if (config
== nullptr) {
226 this->engine
->ThrowError(fmt::format("Trying to add labels for non-defined setting '{}'", setting_name
));
229 if (!config
->labels
.empty()) return SQ_ERROR
;
231 /* Read the table and find all labels */
233 while (SQ_SUCCEEDED(sq_next(vm
, -2))) {
234 const SQChar
*key_string
;
236 if (SQ_FAILED(sq_getstring(vm
, -2, &key_string
))) return SQ_ERROR
;
237 if (SQ_FAILED(sq_getstring(vm
, -1, &label
))) return SQ_ERROR
;
238 /* Because squirrel doesn't support identifiers starting with a digit,
239 * we skip the first character. */
242 if (*key_string
== '_') {
243 /* When the second character is '_', it indicates the value is negative. */
247 int key
= atoi(key_string
) * sign
;
248 config
->labels
[key
] = StrMakeValid(label
);
254 /* Check labels for completeness */
255 config
->complete_labels
= true;
256 for (int value
= config
->min_value
; value
<= config
->max_value
; value
++) {
257 if (config
->labels
.find(value
) == config
->labels
.end()) {
258 config
->complete_labels
= false;
266 const ScriptConfigItemList
*ScriptInfo::GetConfigList() const
268 return &this->config_list
;
271 const ScriptConfigItem
*ScriptInfo::GetConfigItem(const std::string_view name
) const
273 for (const auto &item
: this->config_list
) {
274 if (item
.name
== name
) return &item
;
279 int ScriptInfo::GetSettingDefaultValue(const std::string
&name
) const
281 for (const auto &item
: this->config_list
) {
282 if (item
.name
!= name
) continue;
283 return item
.default_value
;
286 /* There is no such setting */