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"
18 #include "../safeguards.h"
20 ScriptInfo::~ScriptInfo()
22 /* Free all allocated strings */
23 for (const auto &item
: this->config_list
) {
25 free(item
.description
);
26 if (item
.labels
!= nullptr) {
27 for (auto &lbl_map
: *item
.labels
) {
33 this->config_list
.clear();
37 free(this->short_name
);
38 free(this->description
);
40 free(this->instance_name
);
42 free(this->SQ_instance
);
45 bool ScriptInfo::CheckMethod(const char *name
) const
47 if (!this->engine
->MethodExists(*this->SQ_instance
, name
)) {
49 seprintf(error
, lastof(error
), "your info.nut/library.nut doesn't have the method '%s'", name
);
50 this->engine
->ThrowError(error
);
56 /* static */ SQInteger
ScriptInfo::Constructor(HSQUIRRELVM vm
, ScriptInfo
*info
)
58 /* Set some basic info from the parent */
59 info
->SQ_instance
= MallocT
<SQObject
>(1);
60 Squirrel::GetInstance(vm
, info
->SQ_instance
, 2);
61 /* Make sure the instance stays alive over time */
62 sq_addref(vm
, info
->SQ_instance
);
64 info
->scanner
= (ScriptScanner
*)Squirrel::GetGlobalPointer(vm
);
65 info
->engine
= info
->scanner
->GetEngine();
67 /* Ensure the mandatory functions exist */
68 static const char * const required_functions
[] = {
77 for (size_t i
= 0; i
< lengthof(required_functions
); i
++) {
78 if (!info
->CheckMethod(required_functions
[i
])) return SQ_ERROR
;
81 /* Get location information of the scanner */
82 info
->main_script
= info
->scanner
->GetMainScript();
83 info
->tar_file
= info
->scanner
->GetTarFile();
85 /* Cache the data the info file gives us. */
86 if (!info
->engine
->CallStringMethodStrdup(*info
->SQ_instance
, "GetAuthor", &info
->author
, MAX_GET_OPS
)) return SQ_ERROR
;
87 if (!info
->engine
->CallStringMethodStrdup(*info
->SQ_instance
, "GetName", &info
->name
, MAX_GET_OPS
)) return SQ_ERROR
;
88 if (!info
->engine
->CallStringMethodStrdup(*info
->SQ_instance
, "GetShortName", &info
->short_name
, MAX_GET_OPS
)) return SQ_ERROR
;
89 if (!info
->engine
->CallStringMethodStrdup(*info
->SQ_instance
, "GetDescription", &info
->description
, MAX_GET_OPS
)) return SQ_ERROR
;
90 if (!info
->engine
->CallStringMethodStrdup(*info
->SQ_instance
, "GetDate", &info
->date
, MAX_GET_OPS
)) return SQ_ERROR
;
91 if (!info
->engine
->CallIntegerMethod(*info
->SQ_instance
, "GetVersion", &info
->version
, MAX_GET_OPS
)) return SQ_ERROR
;
92 if (!info
->engine
->CallStringMethodStrdup(*info
->SQ_instance
, "CreateInstance", &info
->instance_name
, MAX_CREATEINSTANCE_OPS
)) return SQ_ERROR
;
94 /* The GetURL function is optional. */
95 if (info
->engine
->MethodExists(*info
->SQ_instance
, "GetURL")) {
96 if (!info
->engine
->CallStringMethodStrdup(*info
->SQ_instance
, "GetURL", &info
->url
, MAX_GET_OPS
)) return SQ_ERROR
;
99 /* Check if we have settings */
100 if (info
->engine
->MethodExists(*info
->SQ_instance
, "GetSettings")) {
101 if (!info
->GetSettings()) return SQ_ERROR
;
107 bool ScriptInfo::GetSettings()
109 return this->engine
->CallMethod(*this->SQ_instance
, "GetSettings", nullptr, MAX_GET_SETTING_OPS
);
112 SQInteger
ScriptInfo::AddSetting(HSQUIRRELVM vm
)
114 ScriptConfigItem config
;
115 memset(&config
, 0, sizeof(config
));
116 config
.max_value
= 1;
117 config
.step_size
= 1;
120 /* Read the table, and find all properties we care about */
122 while (SQ_SUCCEEDED(sq_next(vm
, -2))) {
124 if (SQ_FAILED(sq_getstring(vm
, -2, &key
))) return SQ_ERROR
;
125 StrMakeValidInPlace(const_cast<char *>(key
));
127 if (strcmp(key
, "name") == 0) {
128 const SQChar
*sqvalue
;
129 if (SQ_FAILED(sq_getstring(vm
, -1, &sqvalue
))) return SQ_ERROR
;
130 char *name
= stredup(sqvalue
);
132 StrMakeValidInPlace(name
);
134 /* Don't allow '=' and ',' in configure setting names, as we need those
135 * 2 chars to nicely store the settings as a string. */
136 while ((s
= strchr(name
, '=')) != nullptr) *s
= '_';
137 while ((s
= strchr(name
, ',')) != nullptr) *s
= '_';
140 } else if (strcmp(key
, "description") == 0) {
141 const SQChar
*sqdescription
;
142 if (SQ_FAILED(sq_getstring(vm
, -1, &sqdescription
))) return SQ_ERROR
;
143 config
.description
= stredup(sqdescription
);
144 StrMakeValidInPlace(const_cast<char *>(config
.description
));
146 } else if (strcmp(key
, "min_value") == 0) {
148 if (SQ_FAILED(sq_getinteger(vm
, -1, &res
))) return SQ_ERROR
;
149 config
.min_value
= res
;
151 } else if (strcmp(key
, "max_value") == 0) {
153 if (SQ_FAILED(sq_getinteger(vm
, -1, &res
))) return SQ_ERROR
;
154 config
.max_value
= res
;
156 } else if (strcmp(key
, "easy_value") == 0) {
158 if (SQ_FAILED(sq_getinteger(vm
, -1, &res
))) return SQ_ERROR
;
159 config
.easy_value
= res
;
161 } else if (strcmp(key
, "medium_value") == 0) {
163 if (SQ_FAILED(sq_getinteger(vm
, -1, &res
))) return SQ_ERROR
;
164 config
.medium_value
= res
;
166 } else if (strcmp(key
, "hard_value") == 0) {
168 if (SQ_FAILED(sq_getinteger(vm
, -1, &res
))) return SQ_ERROR
;
169 config
.hard_value
= res
;
171 } else if (strcmp(key
, "random_deviation") == 0) {
173 if (SQ_FAILED(sq_getinteger(vm
, -1, &res
))) return SQ_ERROR
;
174 config
.random_deviation
= res
;
176 } else if (strcmp(key
, "custom_value") == 0) {
178 if (SQ_FAILED(sq_getinteger(vm
, -1, &res
))) return SQ_ERROR
;
179 config
.custom_value
= res
;
181 } else if (strcmp(key
, "step_size") == 0) {
183 if (SQ_FAILED(sq_getinteger(vm
, -1, &res
))) return SQ_ERROR
;
184 config
.step_size
= res
;
185 } else if (strcmp(key
, "flags") == 0) {
187 if (SQ_FAILED(sq_getinteger(vm
, -1, &res
))) return SQ_ERROR
;
188 config
.flags
= (ScriptConfigFlags
)res
;
192 seprintf(error
, lastof(error
), "unknown setting property '%s'", key
);
193 this->engine
->ThrowError(error
);
201 /* Don't allow both random_deviation and SCRIPTCONFIG_RANDOM to
202 * be set for the same config item. */
203 if ((items
& 0x200) != 0 && (config
.flags
& SCRIPTCONFIG_RANDOM
) != 0) {
205 seprintf(error
, lastof(error
), "Setting both random_deviation and SCRIPTCONFIG_RANDOM is not allowed");
206 this->engine
->ThrowError(error
);
209 /* Reset the bit for random_deviation as it's optional. */
212 /* Make sure all properties are defined */
213 uint mask
= (config
.flags
& SCRIPTCONFIG_BOOLEAN
) ? 0x1F3 : 0x1FF;
216 seprintf(error
, lastof(error
), "please define all properties of a setting (min/max not allowed for booleans)");
217 this->engine
->ThrowError(error
);
221 this->config_list
.push_back(config
);
225 SQInteger
ScriptInfo::AddLabels(HSQUIRRELVM vm
)
227 const SQChar
*setting_name
;
228 if (SQ_FAILED(sq_getstring(vm
, -2, &setting_name
))) return SQ_ERROR
;
229 StrMakeValidInPlace(const_cast<char *>(setting_name
));
231 ScriptConfigItem
*config
= nullptr;
232 for (auto &item
: this->config_list
) {
233 if (strcmp(item
.name
, setting_name
) == 0) config
= &item
;
236 if (config
== nullptr) {
238 seprintf(error
, lastof(error
), "Trying to add labels for non-defined setting '%s'", setting_name
);
239 this->engine
->ThrowError(error
);
242 if (config
->labels
!= nullptr) return SQ_ERROR
;
244 config
->labels
= new LabelMapping
;
246 /* Read the table and find all labels */
248 while (SQ_SUCCEEDED(sq_next(vm
, -2))) {
249 const SQChar
*key_string
;
251 if (SQ_FAILED(sq_getstring(vm
, -2, &key_string
))) return SQ_ERROR
;
252 if (SQ_FAILED(sq_getstring(vm
, -1, &label
))) return SQ_ERROR
;
253 /* Because squirrel doesn't support identifiers starting with a digit,
254 * we skip the first character. */
255 int key
= atoi(key_string
+ 1);
256 StrMakeValidInPlace(const_cast<char *>(label
));
258 /* !Contains() prevents stredup from leaking. */
259 if (!config
->labels
->Contains(key
)) config
->labels
->Insert(key
, stredup(label
));
265 /* Check labels for completeness */
266 config
->complete_labels
= true;
267 for (int value
= config
->min_value
; value
<= config
->max_value
; value
++) {
268 if (!config
->labels
->Contains(value
)) {
269 config
->complete_labels
= false;
277 const ScriptConfigItemList
*ScriptInfo::GetConfigList() const
279 return &this->config_list
;
282 const ScriptConfigItem
*ScriptInfo::GetConfigItem(const char *name
) const
284 for (const auto &item
: this->config_list
) {
285 if (strcmp(item
.name
, name
) == 0) return &item
;
290 int ScriptInfo::GetSettingDefaultValue(const char *name
) const
292 for (const auto &item
: this->config_list
) {
293 if (strcmp(item
.name
, name
) != 0) continue;
294 /* The default value depends on the difficulty level */
295 switch (GetGameSettings().script
.settings_profile
) {
296 case SP_EASY
: return item
.easy_value
;
297 case SP_MEDIUM
: return item
.medium_value
;
298 case SP_HARD
: return item
.hard_value
;
299 case SP_CUSTOM
: return item
.custom_value
;
300 default: NOT_REACHED();
304 /* There is no such setting */