OpenTTD Source 20250523-master-g321f7e8683
script_info.cpp
Go to the documentation of this file.
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 */
7
10#include "../stdafx.h"
11#include "../settings_type.h"
12
13#include "squirrel_helper.hpp"
14
15#include "script_info.hpp"
16#include "script_scanner.hpp"
17#include "../core/string_consumer.hpp"
18#include "../3rdparty/fmt/format.h"
19
20#include "../safeguards.h"
21
22bool ScriptInfo::CheckMethod(std::string_view name) const
23{
24 if (!this->engine->MethodExists(this->SQ_instance, name)) {
25 this->engine->ThrowError(fmt::format("your info.nut/library.nut doesn't have the method '{}'", name));
26 return false;
27 }
28 return true;
29}
30
31/* static */ SQInteger ScriptInfo::Constructor(HSQUIRRELVM vm, ScriptInfo &info)
32{
33 /* Set some basic info from the parent */
35 /* Make sure the instance stays alive over time */
36 sq_addref(vm, &info.SQ_instance);
37
39 info.engine = info.scanner->GetEngine();
40
41 /* Ensure the mandatory functions exist */
42 static const std::string_view required_functions[] = {
43 "GetAuthor",
44 "GetName",
45 "GetShortName",
46 "GetDescription",
47 "GetVersion",
48 "GetDate",
49 "CreateInstance",
50 };
51 for (const auto &required_function : required_functions) {
52 if (!info.CheckMethod(required_function)) return SQ_ERROR;
53 }
54
55 /* Get location information of the scanner */
56 info.main_script = info.scanner->GetMainScript();
57 info.tar_file = info.scanner->GetTarFile();
58
59 /* Cache the data the info file gives us. */
60 if (!info.engine->CallStringMethod(info.SQ_instance, "GetAuthor", &info.author, MAX_GET_OPS)) return SQ_ERROR;
61 if (!info.engine->CallStringMethod(info.SQ_instance, "GetName", &info.name, MAX_GET_OPS)) return SQ_ERROR;
62 if (!info.engine->CallStringMethod(info.SQ_instance, "GetShortName", &info.short_name, MAX_GET_OPS)) return SQ_ERROR;
63 if (!info.engine->CallStringMethod(info.SQ_instance, "GetDescription", &info.description, MAX_GET_OPS)) return SQ_ERROR;
64 if (!info.engine->CallStringMethod(info.SQ_instance, "GetDate", &info.date, MAX_GET_OPS)) return SQ_ERROR;
65 if (!info.engine->CallIntegerMethod(info.SQ_instance, "GetVersion", &info.version, MAX_GET_OPS)) return SQ_ERROR;
66 if (info.version < 0) return SQ_ERROR;
67 if (!info.engine->CallStringMethod(info.SQ_instance, "CreateInstance", &info.instance_name, MAX_CREATEINSTANCE_OPS)) return SQ_ERROR;
68
69 /* The GetURL function is optional. */
70 if (info.engine->MethodExists(info.SQ_instance, "GetURL")) {
71 if (!info.engine->CallStringMethod(info.SQ_instance, "GetURL", &info.url, MAX_GET_OPS)) return SQ_ERROR;
72 }
73
74 /* Check if we have settings */
75 if (info.engine->MethodExists(info.SQ_instance, "GetSettings")) {
76 if (!info.GetSettings()) return SQ_ERROR;
77 }
78
79 return 0;
80}
81
83{
84 return this->engine->CallMethod(this->SQ_instance, "GetSettings", nullptr, MAX_GET_SETTING_OPS);
85}
86
87enum class ScriptConfigItemKey : uint8_t {
88 Name,
89 Description,
90 MinValue,
91 MaxValue,
92 MediumValue,
93 DefaultValue,
94 Flags,
95};
97
98SQInteger ScriptInfo::AddSetting(HSQUIRRELVM vm)
99{
100 ScriptConfigItem config;
101 ScriptConfigItemKeys present{};
102
103 int medium_value = INT32_MIN;
104
105 /* Read the table, and find all properties we care about */
106 sq_pushnull(vm);
107 while (SQ_SUCCEEDED(sq_next(vm, -2))) {
108 std::string_view key_string;
109 if (SQ_FAILED(sq_getstring(vm, -2, key_string))) return SQ_ERROR;
110 std::string key = StrMakeValid(key_string);
111
112 if (key == "name") {
113 std::string_view sqvalue;
114 if (SQ_FAILED(sq_getstring(vm, -1, sqvalue))) return SQ_ERROR;
115
116 /* Don't allow '=' and ',' in configure setting names, as we need those
117 * 2 chars to nicely store the settings as a string. */
118 auto replace_with_underscore = [](auto c) { return c == '=' || c == ','; };
119 config.name = StrMakeValid(sqvalue);
120 std::replace_if(config.name.begin(), config.name.end(), replace_with_underscore, '_');
121 present.Set(ScriptConfigItemKey::Name);
122 } else if (key == "description") {
123 std::string_view sqdescription;
124 if (SQ_FAILED(sq_getstring(vm, -1, sqdescription))) return SQ_ERROR;
125 config.description = StrMakeValid(sqdescription);
126 present.Set(ScriptConfigItemKey::Description);
127 } else if (key == "min_value") {
128 SQInteger res;
129 if (SQ_FAILED(sq_getinteger(vm, -1, &res))) return SQ_ERROR;
130 config.min_value = ClampTo<int32_t>(res);
131 present.Set(ScriptConfigItemKey::MinValue);
132 } else if (key == "max_value") {
133 SQInteger res;
134 if (SQ_FAILED(sq_getinteger(vm, -1, &res))) return SQ_ERROR;
135 config.max_value = ClampTo<int32_t>(res);
136 present.Set(ScriptConfigItemKey::MaxValue);
137 } else if (key == "easy_value") {
138 /* No longer parsed. */
139 } else if (key == "medium_value") {
140 SQInteger res;
141 if (SQ_FAILED(sq_getinteger(vm, -1, &res))) return SQ_ERROR;
142 medium_value = ClampTo<int32_t>(res);
143 present.Set(ScriptConfigItemKey::MediumValue);
144 } else if (key == "hard_value") {
145 /* No longer parsed. */
146 } else if (key == "custom_value") {
147 /* No longer parsed. */
148 } else if (key == "default_value") {
149 SQInteger res;
150 if (SQ_FAILED(sq_getinteger(vm, -1, &res))) return SQ_ERROR;
151 config.default_value = ClampTo<int32_t>(res);
152 present.Set(ScriptConfigItemKey::DefaultValue);
153 } else if (key == "random_deviation") {
154 /* No longer parsed. */
155 } else if (key == "step_size") {
156 SQInteger res;
157 if (SQ_FAILED(sq_getinteger(vm, -1, &res))) return SQ_ERROR;
158 config.step_size = ClampTo<int32_t>(res);
159 } else if (key == "flags") {
160 SQInteger res;
161 if (SQ_FAILED(sq_getinteger(vm, -1, &res))) return SQ_ERROR;
162 config.flags = static_cast<ScriptConfigFlags>(res);
163 present.Set(ScriptConfigItemKey::Flags);
164 } else {
165 this->engine->ThrowError(fmt::format("unknown setting property '{}'", key));
166 return SQ_ERROR;
167 }
168
169 sq_pop(vm, 2);
170 }
171 sq_pop(vm, 1);
172
173 /* Check if default_value is set. Although required, this was changed with
174 * 14.0, and as such, older AIs don't use it yet. So we convert the older
175 * values into a default_value. */
176 if (!present.Test(ScriptConfigItemKey::DefaultValue)) {
177 /* Easy/medium/hard should all three be defined. */
178 if (!present.Test(ScriptConfigItemKey::MediumValue)) {
179 this->engine->ThrowError("please define all properties of a setting (min/max not allowed for booleans)");
180 return SQ_ERROR;
181 }
182
183 config.default_value = medium_value;
184 present.Set(ScriptConfigItemKey::DefaultValue);
185 }
186
187 /* Make sure all required properties are defined */
188 ScriptConfigItemKeys required = {ScriptConfigItemKey::Name, ScriptConfigItemKey::Description, ScriptConfigItemKey::DefaultValue, ScriptConfigItemKey::Flags};
189 if (!config.flags.Test(ScriptConfigFlag::Boolean)) required.Set({ScriptConfigItemKey::MinValue, ScriptConfigItemKey::MaxValue});
190
191 if (!present.All(required)) {
192 this->engine->ThrowError("please define all properties of a setting (min/max not allowed for booleans)");
193 return SQ_ERROR;
194 }
195
196 this->config_list.emplace_back(config);
197 return 0;
198}
199
200SQInteger ScriptInfo::AddLabels(HSQUIRRELVM vm)
201{
202 std::string_view setting_name_view;
203 if (SQ_FAILED(sq_getstring(vm, -2, setting_name_view))) return SQ_ERROR;
204 std::string setting_name = StrMakeValid(setting_name_view);
205
206 ScriptConfigItem *config = nullptr;
207 for (auto &item : this->config_list) {
208 if (item.name == setting_name) config = &item;
209 }
210
211 if (config == nullptr) {
212 this->engine->ThrowError(fmt::format("Trying to add labels for non-defined setting '{}'", setting_name));
213 return SQ_ERROR;
214 }
215 if (!config->labels.empty()) return SQ_ERROR;
216
217 /* Read the table and find all labels */
218 sq_pushnull(vm);
219 while (SQ_SUCCEEDED(sq_next(vm, -2))) {
220 std::string_view key_string;
221 std::string_view label;
222 if (SQ_FAILED(sq_getstring(vm, -2, key_string))) return SQ_ERROR;
223 if (SQ_FAILED(sq_getstring(vm, -1, label))) return SQ_ERROR;
224 /* Because squirrel doesn't support identifiers starting with a digit,
225 * we skip the first character. */
226 key_string.remove_prefix(1);
227 int sign = 1;
228 if (key_string.starts_with('_')) {
229 /* When the second character is '_', it indicates the value is negative. */
230 sign = -1;
231 key_string.remove_prefix(1);
232 }
233 auto key = ParseInteger<int>(key_string);
234 if (!key.has_value()) return SQ_ERROR;
235 config->labels[*key * sign] = StrMakeValid(label);
236
237 sq_pop(vm, 2);
238 }
239 sq_pop(vm, 1);
240
241 /* Check labels for completeness */
242 config->complete_labels = true;
243 for (int value = config->min_value; value <= config->max_value; value++) {
244 if (config->labels.find(value) == config->labels.end()) {
245 config->complete_labels = false;
246 break;
247 }
248 }
249
250 return 0;
251}
252
254{
255 return &this->config_list;
256}
257
258const ScriptConfigItem *ScriptInfo::GetConfigItem(std::string_view name) const
259{
260 for (const auto &item : this->config_list) {
261 if (item.name == name) return &item;
262 }
263 return nullptr;
264}
265
266int ScriptInfo::GetSettingDefaultValue(const std::string &name) const
267{
268 for (const auto &item : this->config_list) {
269 if (item.name != name) continue;
270 return item.default_value;
271 }
272
273 /* There is no such setting */
274 return -1;
275}
constexpr bool Test(Tvalue_type value) const
Test if the value-th bit is set.
constexpr Timpl & Set()
Set all bits.
Enum-as-bit-set wrapper.
All static information from an Script like name, version, etc.
bool CheckMethod(std::string_view name) const
Check if a given method exists.
std::string short_name
Short name (4 chars) which uniquely identifies the script.
int version
Version of the script.
class ScriptScanner * scanner
ScriptScanner object that was used to scan this script info.
std::string description
Small description of the script.
ScriptConfigItemList config_list
List of settings from this Script.
const ScriptConfigItemList * GetConfigList() const
Get the config list for this Script.
class Squirrel * engine
Engine used to register for Squirrel.
std::string name
Full name of the script.
static SQInteger Constructor(HSQUIRRELVM vm, ScriptInfo &info)
Process the creation of a FileInfo object.
SQInteger AddSetting(HSQUIRRELVM vm)
Set a setting.
const ScriptConfigItem * GetConfigItem(std::string_view name) const
Get the description of a certain Script config option.
std::string main_script
The full path of the script.
SQInteger AddLabels(HSQUIRRELVM vm)
Add labels for a setting.
std::string instance_name
Name of the main class in the script.
bool GetSettings()
Get the settings of the Script.
HSQOBJECT SQ_instance
The Squirrel instance created for this info.
std::string author
Author of the script.
int GetSettingDefaultValue(const std::string &name) const
Get the default value for a setting.
std::string tar_file
If, which tar file the script was in.
std::string url
URL of the script.
std::string date
The date the script was written at.
Scanner to help finding scripts.
std::string GetTarFile()
Get the current tar file the ScanDir is currently tracking.
class Squirrel * GetEngine()
Get the engine of the main squirrel handler (it indexes all available scripts).
std::string GetMainScript()
Get the current main script the ScanDir is currently tracking.
static void * GetGlobalPointer(HSQUIRRELVM vm)
Get the pointer as set by SetGlobalPointer.
Definition squirrel.hpp:231
void ThrowError(std::string_view error)
Throw a Squirrel error that will be nicely displayed to the user.
Definition squirrel.hpp:241
static bool GetInstance(HSQUIRRELVM vm, HSQOBJECT *ptr, int pos=1)
Get the Squirrel-instance pointer.
Definition squirrel.hpp:205
bool CallMethod(HSQOBJECT instance, std::string_view method_name, HSQOBJECT *ret, int suspend)
Call a method of an instance, in various flavors.
Definition squirrel.cpp:376
bool MethodExists(HSQOBJECT instance, std::string_view method_name)
Check if a method exists in an instance.
Definition squirrel.cpp:323
@ Name
Engine name.
std::vector< ScriptConfigItem > ScriptConfigItemList
List of ScriptConfig items.
@ Boolean
This value is a boolean (either 0 (false) or 1 (true) ).
ScriptInfo keeps track of all information of a script, like Author, Description, ....
static const int MAX_CREATEINSTANCE_OPS
Number of operations to create an instance of a script.
static const int MAX_GET_SETTING_OPS
Maximum number of operations allowed for getting a particular setting.
static const int MAX_GET_OPS
Number of operations to get the author and similar information.
Declarations of the class for the script scanner.
declarations and parts of the implementation of the class for convert code
static void StrMakeValid(Builder &builder, StringConsumer &consumer, StringValidationSettings settings)
Copies the valid (UTF-8) characters from consumer to the builder.
Definition string.cpp:117
Info about a single Script setting.
ScriptConfigFlags flags
Flags for the configuration setting.
LabelMapping labels
Text labels for the integer values.
std::string name
The name of the configuration setting.
int default_value
The default value of this configuration setting.
int min_value
The minimal value this configuration setting can have.
int max_value
The maximal value this configuration setting can have.
int step_size
The step size in the gui.
std::string description
The description of the configuration setting.
bool complete_labels
True if all values have a label.