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 (const auto &required_function
: required_functions
) {
51 if (!info
->CheckMethod(required_function
)) 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 medium_value
= INT32_MIN
;
92 /* Read the table, and find all properties we care about */
94 while (SQ_SUCCEEDED(sq_next(vm
, -2))) {
95 const SQChar
*key_string
;
96 if (SQ_FAILED(sq_getstring(vm
, -2, &key_string
))) return SQ_ERROR
;
97 std::string key
= StrMakeValid(key_string
);
100 const SQChar
*sqvalue
;
101 if (SQ_FAILED(sq_getstring(vm
, -1, &sqvalue
))) return SQ_ERROR
;
103 /* Don't allow '=' and ',' in configure setting names, as we need those
104 * 2 chars to nicely store the settings as a string. */
105 auto replace_with_underscore
= [](auto c
) { return c
== '=' || c
== ','; };
106 config
.name
= StrMakeValid(sqvalue
);
107 std::replace_if(config
.name
.begin(), config
.name
.end(), replace_with_underscore
, '_');
109 } else if (key
== "description") {
110 const SQChar
*sqdescription
;
111 if (SQ_FAILED(sq_getstring(vm
, -1, &sqdescription
))) return SQ_ERROR
;
112 config
.description
= StrMakeValid(sqdescription
);
114 } else if (key
== "min_value") {
116 if (SQ_FAILED(sq_getinteger(vm
, -1, &res
))) return SQ_ERROR
;
117 config
.min_value
= ClampTo
<int32_t>(res
);
119 } else if (key
== "max_value") {
121 if (SQ_FAILED(sq_getinteger(vm
, -1, &res
))) return SQ_ERROR
;
122 config
.max_value
= ClampTo
<int32_t>(res
);
124 } else if (key
== "easy_value") {
127 } else if (key
== "medium_value") {
129 if (SQ_FAILED(sq_getinteger(vm
, -1, &res
))) return SQ_ERROR
;
130 medium_value
= ClampTo
<int32_t>(res
);
132 } else if (key
== "hard_value") {
135 } else if (key
== "custom_value") {
137 } else if (key
== "default_value") {
139 if (SQ_FAILED(sq_getinteger(vm
, -1, &res
))) return SQ_ERROR
;
140 config
.default_value
= ClampTo
<int32_t>(res
);
142 } else if (key
== "random_deviation") {
144 } else if (key
== "step_size") {
146 if (SQ_FAILED(sq_getinteger(vm
, -1, &res
))) return SQ_ERROR
;
147 config
.step_size
= ClampTo
<int32_t>(res
);
148 } else if (key
== "flags") {
150 if (SQ_FAILED(sq_getinteger(vm
, -1, &res
))) return SQ_ERROR
;
151 config
.flags
= (ScriptConfigFlags
)res
;
154 this->engine
->ThrowError(fmt::format("unknown setting property '{}'", key
));
162 /* Check if default_value is set. Although required, this was changed with
163 * 14.0, and as such, older AIs don't use it yet. So we convert the older
164 * values into a default_value. */
165 if ((items
& 0x080) == 0) {
166 /* Easy/medium/hard should all three be defined. */
167 if ((items
& 0x010) == 0 || (items
& 0x020) == 0 || (items
& 0x040) == 0) {
168 this->engine
->ThrowError("please define all properties of a setting (min/max not allowed for booleans)");
172 config
.default_value
= medium_value
;
175 /* For compatibility, also act like the default sets the easy/medium/hard. */
176 items
|= 0x010 | 0x020 | 0x040;
179 /* Make sure all properties are defined */
180 uint mask
= (config
.flags
& SCRIPTCONFIG_BOOLEAN
) ? 0x1F3 : 0x1FF;
182 this->engine
->ThrowError("please define all properties of a setting (min/max not allowed for booleans)");
186 this->config_list
.emplace_back(config
);
190 SQInteger
ScriptInfo::AddLabels(HSQUIRRELVM vm
)
192 const SQChar
*setting_name_str
;
193 if (SQ_FAILED(sq_getstring(vm
, -2, &setting_name_str
))) return SQ_ERROR
;
194 std::string setting_name
= StrMakeValid(setting_name_str
);
196 ScriptConfigItem
*config
= nullptr;
197 for (auto &item
: this->config_list
) {
198 if (item
.name
== setting_name
) config
= &item
;
201 if (config
== nullptr) {
202 this->engine
->ThrowError(fmt::format("Trying to add labels for non-defined setting '{}'", setting_name
));
205 if (!config
->labels
.empty()) return SQ_ERROR
;
207 /* Read the table and find all labels */
209 while (SQ_SUCCEEDED(sq_next(vm
, -2))) {
210 const SQChar
*key_string
;
212 if (SQ_FAILED(sq_getstring(vm
, -2, &key_string
))) return SQ_ERROR
;
213 if (SQ_FAILED(sq_getstring(vm
, -1, &label
))) return SQ_ERROR
;
214 /* Because squirrel doesn't support identifiers starting with a digit,
215 * we skip the first character. */
218 if (*key_string
== '_') {
219 /* When the second character is '_', it indicates the value is negative. */
223 int key
= atoi(key_string
) * sign
;
224 config
->labels
[key
] = StrMakeValid(label
);
230 /* Check labels for completeness */
231 config
->complete_labels
= true;
232 for (int value
= config
->min_value
; value
<= config
->max_value
; value
++) {
233 if (config
->labels
.find(value
) == config
->labels
.end()) {
234 config
->complete_labels
= false;
242 const ScriptConfigItemList
*ScriptInfo::GetConfigList() const
244 return &this->config_list
;
247 const ScriptConfigItem
*ScriptInfo::GetConfigItem(const std::string_view name
) const
249 for (const auto &item
: this->config_list
) {
250 if (item
.name
== name
) return &item
;
255 int ScriptInfo::GetSettingDefaultValue(const std::string
&name
) const
257 for (const auto &item
: this->config_list
) {
258 if (item
.name
!= name
) continue;
259 return item
.default_value
;
262 /* There is no such setting */