Fix #10490: Allow ships to exit depots if another is not moving at the exit point...
[openttd-github.git] / src / script / script_info.cpp
blobee2c31ff5a523d2638e005e4b2e4950facc877b7
1 /*
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/>.
6 */
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));
25 return false;
27 return true;
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[] = {
42 "GetAuthor",
43 "GetName",
44 "GetShortName",
45 "GetDescription",
46 "GetVersion",
47 "GetDate",
48 "CreateInstance",
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;
77 return 0;
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;
88 uint items = 0;
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 */
95 sq_pushnull(vm);
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);
101 if (key == "name") {
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, '_');
110 items |= 0x001;
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);
115 items |= 0x002;
116 } else if (key == "min_value") {
117 SQInteger res;
118 if (SQ_FAILED(sq_getinteger(vm, -1, &res))) return SQ_ERROR;
119 config.min_value = ClampTo<int32_t>(res);
120 items |= 0x004;
121 } else if (key == "max_value") {
122 SQInteger res;
123 if (SQ_FAILED(sq_getinteger(vm, -1, &res))) return SQ_ERROR;
124 config.max_value = ClampTo<int32_t>(res);
125 items |= 0x008;
126 } else if (key == "easy_value") {
127 SQInteger res;
128 if (SQ_FAILED(sq_getinteger(vm, -1, &res))) return SQ_ERROR;
129 easy_value = ClampTo<int32_t>(res);
130 items |= 0x010;
131 } else if (key == "medium_value") {
132 SQInteger res;
133 if (SQ_FAILED(sq_getinteger(vm, -1, &res))) return SQ_ERROR;
134 medium_value = ClampTo<int32_t>(res);
135 items |= 0x020;
136 } else if (key == "hard_value") {
137 SQInteger res;
138 if (SQ_FAILED(sq_getinteger(vm, -1, &res))) return SQ_ERROR;
139 hard_value = ClampTo<int32_t>(res);
140 items |= 0x040;
141 } else if (key == "custom_value") {
142 // No longer parsed.
143 } else if (key == "default_value") {
144 SQInteger res;
145 if (SQ_FAILED(sq_getinteger(vm, -1, &res))) return SQ_ERROR;
146 config.default_value = ClampTo<int32_t>(res);
147 items |= 0x080;
148 } else if (key == "random_deviation") {
149 SQInteger res;
150 if (SQ_FAILED(sq_getinteger(vm, -1, &res))) return SQ_ERROR;
151 config.random_deviation = ClampTo<int32_t>(abs(res));
152 items |= 0x200;
153 } else if (key == "step_size") {
154 SQInteger res;
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") {
158 SQInteger res;
159 if (SQ_FAILED(sq_getinteger(vm, -1, &res))) return SQ_ERROR;
160 config.flags = (ScriptConfigFlags)res;
161 items |= 0x100;
162 } else {
163 this->engine->ThrowError(fmt::format("unknown setting property '{}'", key));
164 return SQ_ERROR;
167 sq_pop(vm, 2);
169 sq_pop(vm, 1);
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)");
178 return SQ_ERROR;
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;
185 items |= 0x200;
187 items |= 0x080;
188 } else {
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");
197 return SQ_ERROR;
200 /* Reset the bit for random_deviation as it's optional. */
201 items &= ~0x200;
203 /* Make sure all properties are defined */
204 uint mask = (config.flags & SCRIPTCONFIG_BOOLEAN) ? 0x1F3 : 0x1FF;
205 if (items != mask) {
206 this->engine->ThrowError("please define all properties of a setting (min/max not allowed for booleans)");
207 return SQ_ERROR;
210 this->config_list.emplace_back(config);
211 return 0;
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));
227 return SQ_ERROR;
229 if (!config->labels.empty()) return SQ_ERROR;
231 /* Read the table and find all labels */
232 sq_pushnull(vm);
233 while (SQ_SUCCEEDED(sq_next(vm, -2))) {
234 const SQChar *key_string;
235 const SQChar *label;
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. */
240 key_string++;
241 int sign = 1;
242 if (*key_string == '_') {
243 /* When the second character is '_', it indicates the value is negative. */
244 sign = -1;
245 key_string++;
247 int key = atoi(key_string) * sign;
248 config->labels[key] = StrMakeValid(label);
250 sq_pop(vm, 2);
252 sq_pop(vm, 1);
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;
259 break;
263 return 0;
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;
276 return nullptr;
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 */
287 return -1;