OpenTTD
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 
18 #include "../safeguards.h"
19 
20 ScriptInfo::~ScriptInfo()
21 {
22  /* Free all allocated strings */
23  for (ScriptConfigItemList::iterator it = this->config_list.begin(); it != this->config_list.end(); it++) {
24  free((*it).name);
25  free((*it).description);
26  if (it->labels != nullptr) {
27  for (auto &lbl_map : *(*it).labels) {
28  free(lbl_map.second);
29  }
30  delete it->labels;
31  }
32  }
33  this->config_list.clear();
34 
35  free(this->author);
36  free(this->name);
37  free(this->short_name);
38  free(this->description);
39  free(this->date);
40  free(this->instance_name);
41  free(this->url);
42  free(this->main_script);
43  free(this->tar_file);
44  free(this->SQ_instance);
45 }
46 
47 bool ScriptInfo::CheckMethod(const char *name) const
48 {
49  if (!this->engine->MethodExists(*this->SQ_instance, name)) {
50  char error[1024];
51  seprintf(error, lastof(error), "your info.nut/library.nut doesn't have the method '%s'", name);
52  this->engine->ThrowError(error);
53  return false;
54  }
55  return true;
56 }
57 
58 /* static */ SQInteger ScriptInfo::Constructor(HSQUIRRELVM vm, ScriptInfo *info)
59 {
60  /* Set some basic info from the parent */
61  info->SQ_instance = MallocT<SQObject>(1);
62  Squirrel::GetInstance(vm, info->SQ_instance, 2);
63  /* Make sure the instance stays alive over time */
64  sq_addref(vm, info->SQ_instance);
65 
67  info->engine = info->scanner->GetEngine();
68 
69  /* Ensure the mandatory functions exist */
70  static const char * const required_functions[] = {
71  "GetAuthor",
72  "GetName",
73  "GetShortName",
74  "GetDescription",
75  "GetVersion",
76  "GetDate",
77  "CreateInstance",
78  };
79  for (size_t i = 0; i < lengthof(required_functions); i++) {
80  if (!info->CheckMethod(required_functions[i])) return SQ_ERROR;
81  }
82 
83  /* Get location information of the scanner */
84  info->main_script = stredup(info->scanner->GetMainScript());
85  const char *tar_name = info->scanner->GetTarFile();
86  if (tar_name != nullptr) info->tar_file = stredup(tar_name);
87 
88  /* Cache the data the info file gives us. */
89  if (!info->engine->CallStringMethodStrdup(*info->SQ_instance, "GetAuthor", &info->author, MAX_GET_OPS)) return SQ_ERROR;
90  if (!info->engine->CallStringMethodStrdup(*info->SQ_instance, "GetName", &info->name, MAX_GET_OPS)) return SQ_ERROR;
91  if (!info->engine->CallStringMethodStrdup(*info->SQ_instance, "GetShortName", &info->short_name, MAX_GET_OPS)) return SQ_ERROR;
92  if (!info->engine->CallStringMethodStrdup(*info->SQ_instance, "GetDescription", &info->description, MAX_GET_OPS)) return SQ_ERROR;
93  if (!info->engine->CallStringMethodStrdup(*info->SQ_instance, "GetDate", &info->date, MAX_GET_OPS)) return SQ_ERROR;
94  if (!info->engine->CallIntegerMethod(*info->SQ_instance, "GetVersion", &info->version, MAX_GET_OPS)) return SQ_ERROR;
95  if (!info->engine->CallStringMethodStrdup(*info->SQ_instance, "CreateInstance", &info->instance_name, MAX_CREATEINSTANCE_OPS)) return SQ_ERROR;
96 
97  /* The GetURL function is optional. */
98  if (info->engine->MethodExists(*info->SQ_instance, "GetURL")) {
99  if (!info->engine->CallStringMethodStrdup(*info->SQ_instance, "GetURL", &info->url, MAX_GET_OPS)) return SQ_ERROR;
100  }
101 
102  /* Check if we have settings */
103  if (info->engine->MethodExists(*info->SQ_instance, "GetSettings")) {
104  if (!info->GetSettings()) return SQ_ERROR;
105  }
106 
107  return 0;
108 }
109 
111 {
112  return this->engine->CallMethod(*this->SQ_instance, "GetSettings", nullptr, MAX_GET_SETTING_OPS);
113 }
114 
115 SQInteger ScriptInfo::AddSetting(HSQUIRRELVM vm)
116 {
117  ScriptConfigItem config;
118  memset(&config, 0, sizeof(config));
119  config.max_value = 1;
120  config.step_size = 1;
121  uint items = 0;
122 
123  /* Read the table, and find all properties we care about */
124  sq_pushnull(vm);
125  while (SQ_SUCCEEDED(sq_next(vm, -2))) {
126  const SQChar *key;
127  if (SQ_FAILED(sq_getstring(vm, -2, &key))) return SQ_ERROR;
128  ValidateString(key);
129 
130  if (strcmp(key, "name") == 0) {
131  const SQChar *sqvalue;
132  if (SQ_FAILED(sq_getstring(vm, -1, &sqvalue))) return SQ_ERROR;
133  char *name = stredup(sqvalue);
134  char *s;
135  ValidateString(name);
136 
137  /* Don't allow '=' and ',' in configure setting names, as we need those
138  * 2 chars to nicely store the settings as a string. */
139  while ((s = strchr(name, '=')) != nullptr) *s = '_';
140  while ((s = strchr(name, ',')) != nullptr) *s = '_';
141  config.name = name;
142  items |= 0x001;
143  } else if (strcmp(key, "description") == 0) {
144  const SQChar *sqdescription;
145  if (SQ_FAILED(sq_getstring(vm, -1, &sqdescription))) return SQ_ERROR;
146  config.description = stredup(sqdescription);
147  ValidateString(config.description);
148  items |= 0x002;
149  } else if (strcmp(key, "min_value") == 0) {
150  SQInteger res;
151  if (SQ_FAILED(sq_getinteger(vm, -1, &res))) return SQ_ERROR;
152  config.min_value = res;
153  items |= 0x004;
154  } else if (strcmp(key, "max_value") == 0) {
155  SQInteger res;
156  if (SQ_FAILED(sq_getinteger(vm, -1, &res))) return SQ_ERROR;
157  config.max_value = res;
158  items |= 0x008;
159  } else if (strcmp(key, "easy_value") == 0) {
160  SQInteger res;
161  if (SQ_FAILED(sq_getinteger(vm, -1, &res))) return SQ_ERROR;
162  config.easy_value = res;
163  items |= 0x010;
164  } else if (strcmp(key, "medium_value") == 0) {
165  SQInteger res;
166  if (SQ_FAILED(sq_getinteger(vm, -1, &res))) return SQ_ERROR;
167  config.medium_value = res;
168  items |= 0x020;
169  } else if (strcmp(key, "hard_value") == 0) {
170  SQInteger res;
171  if (SQ_FAILED(sq_getinteger(vm, -1, &res))) return SQ_ERROR;
172  config.hard_value = res;
173  items |= 0x040;
174  } else if (strcmp(key, "random_deviation") == 0) {
175  SQInteger res;
176  if (SQ_FAILED(sq_getinteger(vm, -1, &res))) return SQ_ERROR;
177  config.random_deviation = res;
178  items |= 0x200;
179  } else if (strcmp(key, "custom_value") == 0) {
180  SQInteger res;
181  if (SQ_FAILED(sq_getinteger(vm, -1, &res))) return SQ_ERROR;
182  config.custom_value = res;
183  items |= 0x080;
184  } else if (strcmp(key, "step_size") == 0) {
185  SQInteger res;
186  if (SQ_FAILED(sq_getinteger(vm, -1, &res))) return SQ_ERROR;
187  config.step_size = res;
188  } else if (strcmp(key, "flags") == 0) {
189  SQInteger res;
190  if (SQ_FAILED(sq_getinteger(vm, -1, &res))) return SQ_ERROR;
191  config.flags = (ScriptConfigFlags)res;
192  items |= 0x100;
193  } else {
194  char error[1024];
195  seprintf(error, lastof(error), "unknown setting property '%s'", key);
196  this->engine->ThrowError(error);
197  return SQ_ERROR;
198  }
199 
200  sq_pop(vm, 2);
201  }
202  sq_pop(vm, 1);
203 
204  /* Don't allow both random_deviation and SCRIPTCONFIG_RANDOM to
205  * be set for the same config item. */
206  if ((items & 0x200) != 0 && (config.flags & SCRIPTCONFIG_RANDOM) != 0) {
207  char error[1024];
208  seprintf(error, lastof(error), "Setting both random_deviation and SCRIPTCONFIG_RANDOM is not allowed");
209  this->engine->ThrowError(error);
210  return SQ_ERROR;
211  }
212  /* Reset the bit for random_deviation as it's optional. */
213  items &= ~0x200;
214 
215  /* Make sure all properties are defined */
216  uint mask = (config.flags & SCRIPTCONFIG_BOOLEAN) ? 0x1F3 : 0x1FF;
217  if (items != mask) {
218  char error[1024];
219  seprintf(error, lastof(error), "please define all properties of a setting (min/max not allowed for booleans)");
220  this->engine->ThrowError(error);
221  return SQ_ERROR;
222  }
223 
224  this->config_list.push_back(config);
225  return 0;
226 }
227 
228 SQInteger ScriptInfo::AddLabels(HSQUIRRELVM vm)
229 {
230  const SQChar *setting_name;
231  if (SQ_FAILED(sq_getstring(vm, -2, &setting_name))) return SQ_ERROR;
232  ValidateString(setting_name);
233 
234  ScriptConfigItem *config = nullptr;
235  for (ScriptConfigItemList::iterator it = this->config_list.begin(); it != this->config_list.end(); it++) {
236  if (strcmp((*it).name, setting_name) == 0) config = &(*it);
237  }
238 
239  if (config == nullptr) {
240  char error[1024];
241  seprintf(error, lastof(error), "Trying to add labels for non-defined setting '%s'", setting_name);
242  this->engine->ThrowError(error);
243  return SQ_ERROR;
244  }
245  if (config->labels != nullptr) return SQ_ERROR;
246 
247  config->labels = new LabelMapping;
248 
249  /* Read the table and find all labels */
250  sq_pushnull(vm);
251  while (SQ_SUCCEEDED(sq_next(vm, -2))) {
252  const SQChar *key_string;
253  const SQChar *label;
254  if (SQ_FAILED(sq_getstring(vm, -2, &key_string))) return SQ_ERROR;
255  if (SQ_FAILED(sq_getstring(vm, -1, &label))) return SQ_ERROR;
256  /* Because squirrel doesn't support identifiers starting with a digit,
257  * we skip the first character. */
258  int key = atoi(key_string + 1);
259  ValidateString(label);
260 
261  /* !Contains() prevents stredup from leaking. */
262  if (!config->labels->Contains(key)) config->labels->Insert(key, stredup(label));
263 
264  sq_pop(vm, 2);
265  }
266  sq_pop(vm, 1);
267 
268  /* Check labels for completeness */
269  config->complete_labels = true;
270  for (int value = config->min_value; value <= config->max_value; value++) {
271  if (!config->labels->Contains(value)) {
272  config->complete_labels = false;
273  break;
274  }
275  }
276 
277  return 0;
278 }
279 
281 {
282  return &this->config_list;
283 }
284 
286 {
287  for (ScriptConfigItemList::const_iterator it = this->config_list.begin(); it != this->config_list.end(); it++) {
288  if (strcmp((*it).name, name) == 0) return &(*it);
289  }
290  return nullptr;
291 }
292 
294 {
295  for (ScriptConfigItemList::const_iterator it = this->config_list.begin(); it != this->config_list.end(); it++) {
296  if (strcmp((*it).name, name) != 0) continue;
297  /* The default value depends on the difficulty level */
299  case SP_EASY: return (*it).easy_value;
300  case SP_MEDIUM: return (*it).medium_value;
301  case SP_HARD: return (*it).hard_value;
302  case SP_CUSTOM: return (*it).custom_value;
303  default: NOT_REACHED();
304  }
305  }
306 
307  /* There is no such setting */
308  return -1;
309 }
const char * GetMainScript()
Get the current main script the ScanDir is currently tracking.
const char * GetTarFile()
Get the current tar file the ScanDir is currently tracking.
int version
Version of the script.
class ScriptScanner * scanner
ScriptScanner object that was used to scan this script info.
HSQOBJECT * SQ_instance
The Squirrel instance created for this info.
int random_deviation
The maximum random deviation from the default value.
void ThrowError(const char *error)
Throw a Squirrel error that will be nicely displayed to the user.
Definition: squirrel.hpp:236
bool CallMethod(HSQOBJECT instance, const char *method_name, HSQOBJECT *ret, int suspend)
Call a method of an instance, in various flavors.
Definition: squirrel.cpp:345
bool CheckMethod(const char *name) const
Check if a given method exists.
Definition: script_info.cpp:47
int CDECL seprintf(char *str, const char *last, const char *format,...)
Safer implementation of snprintf; same as snprintf except:
Definition: string.cpp:407
const ScriptConfigItemList * GetConfigList() const
Get the config list for this Script.
int min_value
The minimal value this configuration setting can have.
const char * date
The date the script was written at.
std::list< ScriptConfigItem > ScriptConfigItemList
List of ScriptConfig items.
static bool GetInstance(HSQUIRRELVM vm, HSQOBJECT *ptr, int pos=1)
Get the Squirrel-instance pointer.
Definition: squirrel.hpp:200
const char * url
URL of the script.
Medium difficulty.
Definition: settings_type.h:28
ScriptConfigItemList config_list
List of settings from this Script.
No profile, special "custom" highscore.
Definition: settings_type.h:33
LabelMapping * labels
Text labels for the integer values.
const char * instance_name
Name of the main class in the script.
int max_value
The maximal value this configuration setting can have.
int GetSettingDefaultValue(const char *name) const
Get the default value for a setting.
#define lastof(x)
Get the last element of an fixed size array.
Definition: depend.cpp:48
static void * GetGlobalPointer(HSQUIRRELVM vm)
Get the pointer as set by SetGlobalPointer.
Definition: squirrel.hpp:226
declarations and parts of the implementation of the class for convert code
int medium_value
The default value on medium difficulty setting.
Scanner to help finding scripts.
ScriptConfigFlags
Bitmask of flags for Script settings.
const char * short_name
Short name (4 chars) which uniquely identifies the script.
SQInteger AddLabels(HSQUIRRELVM vm)
Add labels for a setting.
bool complete_labels
True if all values have a label.
class Squirrel * GetEngine()
Get the engine of the main squirrel handler (it indexes all available scripts).
All static information from an Script like name, version, etc.
Definition: script_info.hpp:30
bool Contains(const T &key) const
Tests whether a key is assigned in this map.
const char * author
Author of the script.
int easy_value
The default value on easy difficulty setting.
char * stredup(const char *s, const char *last)
Create a duplicate of the given string.
Definition: string.cpp:136
bool GetSettings()
Get the settings of the Script.
When randomizing the Script, pick any value between min_value and max_value when on custom difficulty...
bool Insert(const T &key, const U &data)
Adds new item to this map.
#define lengthof(x)
Return the length of an fixed size array.
Definition: depend.cpp:40
int custom_value
The default value on custom difficulty setting.
const ScriptConfigItem * GetConfigItem(const char *name) const
Get the description of a certain Script config option.
SmallMap< int, char * > LabelMapping
Map-type used to map the setting numbers to labels.
This value is a boolean (either 0 (false) or 1 (true) ).
const char * description
The description of the configuration setting.
ScriptSettings script
settings for scripts
const char * name
Full name of the script.
ScriptConfigFlags flags
Flags for the configuration setting.
SQInteger AddSetting(HSQUIRRELVM vm)
Set a setting.
Hard difficulty.
Definition: settings_type.h:29
Info about a single Script setting.
static GameSettings & GetGameSettings()
Get the settings-object applicable for the current situation: the newgame settings when we&#39;re in the ...
static const int MAX_GET_OPS
Number of operations to get the author and similar information.
Definition: script_info.hpp:25
bool MethodExists(HSQOBJECT instance, const char *method_name)
Check if a method exists in an instance.
Definition: squirrel.cpp:292
void CDECL error(const char *s,...)
Error handling for fatal non-user errors.
Definition: openttd.cpp:112
char * main_script
The full path of the script.
static const int MAX_CREATEINSTANCE_OPS
Number of operations to create an instance of a script.
Definition: script_info.hpp:23
Declarations of the class for the script scanner.
Easy difficulty.
Definition: settings_type.h:27
class Squirrel * engine
Engine used to register for Squirrel.
static SQInteger Constructor(HSQUIRRELVM vm, ScriptInfo *info)
Process the creation of a FileInfo object.
Definition: script_info.cpp:58
static void free(const void *ptr)
Version of the standard free that accepts const pointers.
Definition: depend.cpp:129
uint8 settings_profile
difficulty profile to set initial settings of scripts, esp. random AIs
const char * description
Small description of the script.
int hard_value
The default value on hard difficulty setting.
char * tar_file
If, which tar file the script was in.
static const int MAX_GET_SETTING_OPS
Maximum number of operations allowed for getting a particular setting.
Definition: script_info.hpp:27
void ValidateString(const char *str)
Scans the string for valid characters and if it finds invalid ones, replaces them with a question mar...
Definition: string.cpp:243
int step_size
The step size in the gui.
const char * name
The name of the configuration setting.
ScriptInfo keeps track of all information of a script, like Author, Description, ...