OpenTTD Source 20260129-master-g2bb01bd0e4
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 <https://www.gnu.org/licenses/old-licenses/gpl-2.0>.
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
88enum class ScriptConfigItemKey : uint8_t {
89 Name,
91 MinValue,
92 MaxValue,
95 Flags,
96};
98
99SQInteger ScriptInfo::AddSetting(HSQUIRRELVM vm)
100{
101 ScriptConfigItem config;
102 ScriptConfigItemKeys present{};
103
104 int medium_value = INT32_MIN;
105
106 /* Read the table, and find all properties we care about */
107 sq_pushnull(vm);
108 while (SQ_SUCCEEDED(sq_next(vm, -2))) {
109 std::string_view key_string;
110 if (SQ_FAILED(sq_getstring(vm, -2, key_string))) return SQ_ERROR;
111 std::string key = StrMakeValid(key_string);
112
113 if (key == "name") {
114 std::string_view sqvalue;
115 if (SQ_FAILED(sq_getstring(vm, -1, sqvalue))) return SQ_ERROR;
116
117 /* Don't allow '=' and ',' in configure setting names, as we need those
118 * 2 chars to nicely store the settings as a string. */
119 auto replace_with_underscore = [](auto c) { return c == '=' || c == ','; };
120 config.name = StrMakeValid(sqvalue);
121 std::replace_if(config.name.begin(), config.name.end(), replace_with_underscore, '_');
122 present.Set(ScriptConfigItemKey::Name);
123 } else if (key == "description") {
124 std::string_view sqdescription;
125 if (SQ_FAILED(sq_getstring(vm, -1, sqdescription))) return SQ_ERROR;
126 config.description = StrMakeValid(sqdescription);
128 } else if (key == "min_value") {
129 SQInteger res;
130 if (SQ_FAILED(sq_getinteger(vm, -1, &res))) return SQ_ERROR;
131 config.min_value = ClampTo<int32_t>(res);
133 } else if (key == "max_value") {
134 SQInteger res;
135 if (SQ_FAILED(sq_getinteger(vm, -1, &res))) return SQ_ERROR;
136 config.max_value = ClampTo<int32_t>(res);
138 } else if (key == "easy_value") {
139 /* No longer parsed. */
140 } else if (key == "medium_value") {
141 SQInteger res;
142 if (SQ_FAILED(sq_getinteger(vm, -1, &res))) return SQ_ERROR;
143 medium_value = ClampTo<int32_t>(res);
145 } else if (key == "hard_value") {
146 /* No longer parsed. */
147 } else if (key == "custom_value") {
148 /* No longer parsed. */
149 } else if (key == "default_value") {
150 SQInteger res;
151 if (SQ_FAILED(sq_getinteger(vm, -1, &res))) return SQ_ERROR;
152 config.default_value = ClampTo<int32_t>(res);
154 } else if (key == "random_deviation") {
155 /* No longer parsed. */
156 } else if (key == "step_size") {
157 SQInteger res;
158 if (SQ_FAILED(sq_getinteger(vm, -1, &res))) return SQ_ERROR;
159 config.step_size = ClampTo<int32_t>(res);
160 } else if (key == "flags") {
161 SQInteger res;
162 if (SQ_FAILED(sq_getinteger(vm, -1, &res))) return SQ_ERROR;
163 config.flags = static_cast<ScriptConfigFlags>(res);
165 } else {
166 this->engine->ThrowError(fmt::format("unknown setting property '{}'", key));
167 return SQ_ERROR;
168 }
169
170 sq_pop(vm, 2);
171 }
172 sq_pop(vm, 1);
173
174 /* Check if default_value is set. Although required, this was changed with
175 * 14.0, and as such, older AIs don't use it yet. So we convert the older
176 * values into a default_value. */
177 if (!present.Test(ScriptConfigItemKey::DefaultValue)) {
178 /* Easy/medium/hard should all three be defined. */
179 if (!present.Test(ScriptConfigItemKey::MediumValue)) {
180 this->engine->ThrowError("please define all properties of a setting (min/max not allowed for booleans)");
181 return SQ_ERROR;
182 }
183
184 config.default_value = medium_value;
186 }
187
188 /* Make sure all required properties are defined */
191
192 if (!present.All(required)) {
193 this->engine->ThrowError("please define all properties of a setting (min/max not allowed for booleans)");
194 return SQ_ERROR;
195 }
196
197 this->config_list.emplace_back(config);
198 return 0;
199}
200
201SQInteger ScriptInfo::AddLabels(HSQUIRRELVM vm)
202{
203 std::string_view setting_name_view;
204 if (SQ_FAILED(sq_getstring(vm, -2, setting_name_view))) return SQ_ERROR;
205 std::string setting_name = StrMakeValid(setting_name_view);
206
207 ScriptConfigItem *config = nullptr;
208 for (auto &item : this->config_list) {
209 if (item.name == setting_name) config = &item;
210 }
211
212 if (config == nullptr) {
213 this->engine->ThrowError(fmt::format("Trying to add labels for non-defined setting '{}'", setting_name));
214 return SQ_ERROR;
215 }
216 if (!config->labels.empty()) return SQ_ERROR;
217
218 /* Read the table and find all labels */
219 sq_pushnull(vm);
220 while (SQ_SUCCEEDED(sq_next(vm, -2))) {
221 std::string_view key_string;
222 std::string_view label;
223 if (SQ_FAILED(sq_getstring(vm, -2, key_string))) return SQ_ERROR;
224 if (SQ_FAILED(sq_getstring(vm, -1, label))) return SQ_ERROR;
225 /* Because squirrel doesn't support identifiers starting with a digit,
226 * we skip the first character. */
227 key_string.remove_prefix(1);
228 int sign = 1;
229 if (key_string.starts_with('_')) {
230 /* When the second character is '_', it indicates the value is negative. */
231 sign = -1;
232 key_string.remove_prefix(1);
233 }
234 auto key = ParseInteger<int>(key_string);
235 if (!key.has_value()) return SQ_ERROR;
236 config->labels[*key * sign] = StrMakeValid(label);
237
238 sq_pop(vm, 2);
239 }
240 sq_pop(vm, 1);
241
242 /* Check labels for completeness */
243 config->complete_labels = true;
244 for (int value = config->min_value; value <= config->max_value; value++) {
245 if (config->labels.find(value) == config->labels.end()) {
246 config->complete_labels = false;
247 break;
248 }
249 }
250
251 return 0;
252}
253
255{
256 return &this->config_list;
257}
258
259const ScriptConfigItem *ScriptInfo::GetConfigItem(std::string_view name) const
260{
261 for (const auto &item : this->config_list) {
262 if (item.name == name) return &item;
263 }
264 return nullptr;
265}
266
267int ScriptInfo::GetSettingDefaultValue(const std::string &name) const
268{
269 for (const auto &item : this->config_list) {
270 if (item.name != name) continue;
271 return item.default_value;
272 }
273
274 /* There is no such setting */
275 return -1;
276}
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:237
void ThrowError(std::string_view error)
Throw a Squirrel error that will be nicely displayed to the user.
Definition squirrel.hpp:247
static bool GetInstance(HSQUIRRELVM vm, HSQOBJECT *ptr, int pos=1)
Get the Squirrel-instance pointer.
Definition squirrel.hpp:211
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:396
bool MethodExists(HSQOBJECT instance, std::string_view method_name)
Check if a method exists in an instance.
Definition squirrel.cpp:343
std::vector< ScriptConfigItem > ScriptConfigItemList
List of ScriptConfig items.
@ Boolean
This value is a boolean (either 0 (false) or 1 (true) ).
ScriptConfigItemKey
Configuration items for a script.
@ MinValue
Minimum value.
@ DefaultValue
Default value when nothing is entered.
@ Name
Name of the configuration.
@ Flags
ScriptConfigFlags defining how/when to use this configuration.
@ MediumValue
Used for reading the old medium difficulty setting, which is used as default when that does not exist...
@ MaxValue
Maximum value.
@ Description
Description.
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:119
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.