OpenTTD Source  20241121-master-g67a0fccfad
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 "../3rdparty/fmt/format.h"
18 
19 #include "../safeguards.h"
20 
21 bool ScriptInfo::CheckMethod(const char *name) const
22 {
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;
26  }
27  return true;
28 }
29 
30 /* static */ SQInteger ScriptInfo::Constructor(HSQUIRRELVM vm, ScriptInfo *info)
31 {
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);
36 
38  info->engine = info->scanner->GetEngine();
39 
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",
49  };
50  for (const auto &required_function : required_functions) {
51  if (!info->CheckMethod(required_function)) return SQ_ERROR;
52  }
53 
54  /* Get location information of the scanner */
55  info->main_script = info->scanner->GetMainScript();
56  info->tar_file = info->scanner->GetTarFile();
57 
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;
66 
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;
70  }
71 
72  /* Check if we have settings */
73  if (info->engine->MethodExists(info->SQ_instance, "GetSettings")) {
74  if (!info->GetSettings()) return SQ_ERROR;
75  }
76 
77  return 0;
78 }
79 
81 {
82  return this->engine->CallMethod(this->SQ_instance, "GetSettings", nullptr, MAX_GET_SETTING_OPS);
83 }
84 
85 SQInteger ScriptInfo::AddSetting(HSQUIRRELVM vm)
86 {
87  ScriptConfigItem config;
88  uint items = 0;
89 
90  int medium_value = INT32_MIN;
91 
92  /* Read the table, and find all properties we care about */
93  sq_pushnull(vm);
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);
98 
99  if (key == "name") {
100  const SQChar *sqvalue;
101  if (SQ_FAILED(sq_getstring(vm, -1, &sqvalue))) return SQ_ERROR;
102 
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, '_');
108  items |= 0x001;
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);
113  items |= 0x002;
114  } else if (key == "min_value") {
115  SQInteger res;
116  if (SQ_FAILED(sq_getinteger(vm, -1, &res))) return SQ_ERROR;
117  config.min_value = ClampTo<int32_t>(res);
118  items |= 0x004;
119  } else if (key == "max_value") {
120  SQInteger res;
121  if (SQ_FAILED(sq_getinteger(vm, -1, &res))) return SQ_ERROR;
122  config.max_value = ClampTo<int32_t>(res);
123  items |= 0x008;
124  } else if (key == "easy_value") {
125  // No longer parsed.
126  items |= 0x010;
127  } else if (key == "medium_value") {
128  SQInteger res;
129  if (SQ_FAILED(sq_getinteger(vm, -1, &res))) return SQ_ERROR;
130  medium_value = ClampTo<int32_t>(res);
131  items |= 0x020;
132  } else if (key == "hard_value") {
133  // No longer parsed.
134  items |= 0x040;
135  } else if (key == "custom_value") {
136  // No longer parsed.
137  } else if (key == "default_value") {
138  SQInteger res;
139  if (SQ_FAILED(sq_getinteger(vm, -1, &res))) return SQ_ERROR;
140  config.default_value = ClampTo<int32_t>(res);
141  items |= 0x080;
142  } else if (key == "random_deviation") {
143  // No longer parsed.
144  } else if (key == "step_size") {
145  SQInteger res;
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") {
149  SQInteger res;
150  if (SQ_FAILED(sq_getinteger(vm, -1, &res))) return SQ_ERROR;
151  config.flags = (ScriptConfigFlags)res;
152  items |= 0x100;
153  } else {
154  this->engine->ThrowError(fmt::format("unknown setting property '{}'", key));
155  return SQ_ERROR;
156  }
157 
158  sq_pop(vm, 2);
159  }
160  sq_pop(vm, 1);
161 
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)");
169  return SQ_ERROR;
170  }
171 
172  config.default_value = medium_value;
173  items |= 0x080;
174  } else {
175  /* For compatibility, also act like the default sets the easy/medium/hard. */
176  items |= 0x010 | 0x020 | 0x040;
177  }
178 
179  /* Make sure all properties are defined */
180  uint mask = (config.flags & SCRIPTCONFIG_BOOLEAN) ? 0x1F3 : 0x1FF;
181  if (items != mask) {
182  this->engine->ThrowError("please define all properties of a setting (min/max not allowed for booleans)");
183  return SQ_ERROR;
184  }
185 
186  this->config_list.emplace_back(config);
187  return 0;
188 }
189 
190 SQInteger ScriptInfo::AddLabels(HSQUIRRELVM vm)
191 {
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);
195 
196  ScriptConfigItem *config = nullptr;
197  for (auto &item : this->config_list) {
198  if (item.name == setting_name) config = &item;
199  }
200 
201  if (config == nullptr) {
202  this->engine->ThrowError(fmt::format("Trying to add labels for non-defined setting '{}'", setting_name));
203  return SQ_ERROR;
204  }
205  if (!config->labels.empty()) return SQ_ERROR;
206 
207  /* Read the table and find all labels */
208  sq_pushnull(vm);
209  while (SQ_SUCCEEDED(sq_next(vm, -2))) {
210  const SQChar *key_string;
211  const SQChar *label;
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. */
216  key_string++;
217  int sign = 1;
218  if (*key_string == '_') {
219  /* When the second character is '_', it indicates the value is negative. */
220  sign = -1;
221  key_string++;
222  }
223  int key = atoi(key_string) * sign;
224  config->labels[key] = StrMakeValid(label);
225 
226  sq_pop(vm, 2);
227  }
228  sq_pop(vm, 1);
229 
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;
235  break;
236  }
237  }
238 
239  return 0;
240 }
241 
243 {
244  return &this->config_list;
245 }
246 
247 const ScriptConfigItem *ScriptInfo::GetConfigItem(const std::string_view name) const
248 {
249  for (const auto &item : this->config_list) {
250  if (item.name == name) return &item;
251  }
252  return nullptr;
253 }
254 
255 int ScriptInfo::GetSettingDefaultValue(const std::string &name) const
256 {
257  for (const auto &item : this->config_list) {
258  if (item.name != name) continue;
259  return item.default_value;
260  }
261 
262  /* There is no such setting */
263  return -1;
264 }
All static information from an Script like name, version, etc.
Definition: script_info.hpp:30
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.
static SQInteger Constructor(HSQUIRRELVM vm, ScriptInfo *info)
Process the creation of a FileInfo object.
Definition: script_info.cpp:30
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.
bool CheckMethod(const char *name) const
Check if a given method exists.
Definition: script_info.cpp:21
class Squirrel * engine
Engine used to register for Squirrel.
std::string name
Full name of the script.
SQInteger AddSetting(HSQUIRRELVM vm)
Set a setting.
Definition: script_info.cpp:85
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.
Definition: script_info.cpp:80
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.
const ScriptConfigItem * GetConfigItem(const std::string_view name) const
Get the description of a certain Script config option.
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.
void ThrowError(const std::string_view error)
Throw a Squirrel error that will be nicely displayed to the user.
Definition: squirrel.hpp:236
static bool GetInstance(HSQUIRRELVM vm, HSQOBJECT *ptr, int pos=1)
Get the Squirrel-instance pointer.
Definition: squirrel.hpp:200
bool CallMethod(HSQOBJECT instance, const char *method_name, HSQOBJECT *ret, int suspend)
Call a method of an instance, in various flavors.
Definition: squirrel.cpp:398
bool MethodExists(HSQOBJECT instance, const char *method_name)
Check if a method exists in an instance.
Definition: squirrel.cpp:345
static void * GetGlobalPointer(HSQUIRRELVM vm)
Get the pointer as set by SetGlobalPointer.
Definition: squirrel.hpp:226
ScriptConfigFlags
Bitmask of flags for Script settings.
@ SCRIPTCONFIG_BOOLEAN
This value is a boolean (either 0 (false) or 1 (true) ).
std::vector< ScriptConfigItem > ScriptConfigItemList
List of ScriptConfig items.
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.
Definition: script_info.hpp:23
static const int MAX_GET_SETTING_OPS
Maximum number of operations allowed for getting a particular setting.
Definition: script_info.hpp:27
static const int MAX_GET_OPS
Number of operations to get the author and similar information.
Definition: script_info.hpp:25
Declarations of the class for the script scanner.
declarations and parts of the implementation of the class for convert code
static void StrMakeValid(T &dst, const char *str, const char *last, StringValidationSettings settings)
Copies the valid (UTF-8) characters from str up to last to the dst.
Definition: string.cpp:107
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.