OpenTTD
script_info.cpp
Go to the documentation of this file.
1 /* $Id: script_info.cpp 26771 2014-09-06 17:30:33Z rubidium $ */
2 
3 /*
4  * This file is part of OpenTTD.
5  * 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.
6  * 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.
7  * 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/>.
8  */
9 
12 #include "../stdafx.h"
13 #include "../settings_type.h"
14 
15 #include "squirrel_helper.hpp"
16 
17 #include "script_info.hpp"
18 #include "script_scanner.hpp"
19 
20 #include "../safeguards.h"
21 
22 ScriptInfo::~ScriptInfo()
23 {
24  /* Free all allocated strings */
25  for (ScriptConfigItemList::iterator it = this->config_list.begin(); it != this->config_list.end(); it++) {
26  free((*it).name);
27  free((*it).description);
28  if (it->labels != NULL) {
29  for (LabelMapping::iterator it2 = (*it).labels->Begin(); it2 != (*it).labels->End(); it2++) {
30  free(it2->second);
31  }
32  delete it->labels;
33  }
34  }
35  this->config_list.clear();
36 
37  free(this->author);
38  free(this->name);
39  free(this->short_name);
40  free(this->description);
41  free(this->date);
42  free(this->instance_name);
43  free(this->url);
44  free(this->main_script);
45  free(this->tar_file);
46  free(this->SQ_instance);
47 }
48 
49 bool ScriptInfo::CheckMethod(const char *name) const
50 {
51  if (!this->engine->MethodExists(*this->SQ_instance, name)) {
52  char error[1024];
53  seprintf(error, lastof(error), "your info.nut/library.nut doesn't have the method '%s'", name);
54  this->engine->ThrowError(error);
55  return false;
56  }
57  return true;
58 }
59 
60 /* static */ SQInteger ScriptInfo::Constructor(HSQUIRRELVM vm, ScriptInfo *info)
61 {
62  /* Set some basic info from the parent */
63  info->SQ_instance = MallocT<SQObject>(1);
64  Squirrel::GetInstance(vm, info->SQ_instance, 2);
65  /* Make sure the instance stays alive over time */
66  sq_addref(vm, info->SQ_instance);
67 
69  info->engine = info->scanner->GetEngine();
70 
71  /* Ensure the mandatory functions exist */
72  static const char * const required_functions[] = {
73  "GetAuthor",
74  "GetName",
75  "GetShortName",
76  "GetDescription",
77  "GetVersion",
78  "GetDate",
79  "CreateInstance",
80  };
81  for (size_t i = 0; i < lengthof(required_functions); i++) {
82  if (!info->CheckMethod(required_functions[i])) return SQ_ERROR;
83  }
84 
85  /* Get location information of the scanner */
86  info->main_script = stredup(info->scanner->GetMainScript());
87  const char *tar_name = info->scanner->GetTarFile();
88  if (tar_name != NULL) info->tar_file = stredup(tar_name);
89 
90  /* Cache the data the info file gives us. */
91  if (!info->engine->CallStringMethodStrdup(*info->SQ_instance, "GetAuthor", &info->author, MAX_GET_OPS)) return SQ_ERROR;
92  if (!info->engine->CallStringMethodStrdup(*info->SQ_instance, "GetName", &info->name, MAX_GET_OPS)) return SQ_ERROR;
93  if (!info->engine->CallStringMethodStrdup(*info->SQ_instance, "GetShortName", &info->short_name, MAX_GET_OPS)) return SQ_ERROR;
94  if (!info->engine->CallStringMethodStrdup(*info->SQ_instance, "GetDescription", &info->description, MAX_GET_OPS)) return SQ_ERROR;
95  if (!info->engine->CallStringMethodStrdup(*info->SQ_instance, "GetDate", &info->date, MAX_GET_OPS)) return SQ_ERROR;
96  if (!info->engine->CallIntegerMethod(*info->SQ_instance, "GetVersion", &info->version, MAX_GET_OPS)) return SQ_ERROR;
97  if (!info->engine->CallStringMethodStrdup(*info->SQ_instance, "CreateInstance", &info->instance_name, MAX_CREATEINSTANCE_OPS)) return SQ_ERROR;
98 
99  /* The GetURL function is optional. */
100  if (info->engine->MethodExists(*info->SQ_instance, "GetURL")) {
101  if (!info->engine->CallStringMethodStrdup(*info->SQ_instance, "GetURL", &info->url, MAX_GET_OPS)) return SQ_ERROR;
102  }
103 
104  /* Check if we have settings */
105  if (info->engine->MethodExists(*info->SQ_instance, "GetSettings")) {
106  if (!info->GetSettings()) return SQ_ERROR;
107  }
108 
109  return 0;
110 }
111 
113 {
114  return this->engine->CallMethod(*this->SQ_instance, "GetSettings", NULL, MAX_GET_SETTING_OPS);
115 }
116 
117 SQInteger ScriptInfo::AddSetting(HSQUIRRELVM vm)
118 {
119  ScriptConfigItem config;
120  memset(&config, 0, sizeof(config));
121  config.max_value = 1;
122  config.step_size = 1;
123  uint items = 0;
124 
125  /* Read the table, and find all properties we care about */
126  sq_pushnull(vm);
127  while (SQ_SUCCEEDED(sq_next(vm, -2))) {
128  const SQChar *key;
129  if (SQ_FAILED(sq_getstring(vm, -2, &key))) return SQ_ERROR;
130  ValidateString(key);
131 
132  if (strcmp(key, "name") == 0) {
133  const SQChar *sqvalue;
134  if (SQ_FAILED(sq_getstring(vm, -1, &sqvalue))) return SQ_ERROR;
135  char *name = stredup(sqvalue);
136  char *s;
137  ValidateString(name);
138 
139  /* Don't allow '=' and ',' in configure setting names, as we need those
140  * 2 chars to nicely store the settings as a string. */
141  while ((s = strchr(name, '=')) != NULL) *s = '_';
142  while ((s = strchr(name, ',')) != NULL) *s = '_';
143  config.name = name;
144  items |= 0x001;
145  } else if (strcmp(key, "description") == 0) {
146  const SQChar *sqdescription;
147  if (SQ_FAILED(sq_getstring(vm, -1, &sqdescription))) return SQ_ERROR;
148  config.description = stredup(sqdescription);
149  ValidateString(config.description);
150  items |= 0x002;
151  } else if (strcmp(key, "min_value") == 0) {
152  SQInteger res;
153  if (SQ_FAILED(sq_getinteger(vm, -1, &res))) return SQ_ERROR;
154  config.min_value = res;
155  items |= 0x004;
156  } else if (strcmp(key, "max_value") == 0) {
157  SQInteger res;
158  if (SQ_FAILED(sq_getinteger(vm, -1, &res))) return SQ_ERROR;
159  config.max_value = res;
160  items |= 0x008;
161  } else if (strcmp(key, "easy_value") == 0) {
162  SQInteger res;
163  if (SQ_FAILED(sq_getinteger(vm, -1, &res))) return SQ_ERROR;
164  config.easy_value = res;
165  items |= 0x010;
166  } else if (strcmp(key, "medium_value") == 0) {
167  SQInteger res;
168  if (SQ_FAILED(sq_getinteger(vm, -1, &res))) return SQ_ERROR;
169  config.medium_value = res;
170  items |= 0x020;
171  } else if (strcmp(key, "hard_value") == 0) {
172  SQInteger res;
173  if (SQ_FAILED(sq_getinteger(vm, -1, &res))) return SQ_ERROR;
174  config.hard_value = res;
175  items |= 0x040;
176  } else if (strcmp(key, "random_deviation") == 0) {
177  SQInteger res;
178  if (SQ_FAILED(sq_getinteger(vm, -1, &res))) return SQ_ERROR;
179  config.random_deviation = res;
180  items |= 0x200;
181  } else if (strcmp(key, "custom_value") == 0) {
182  SQInteger res;
183  if (SQ_FAILED(sq_getinteger(vm, -1, &res))) return SQ_ERROR;
184  config.custom_value = res;
185  items |= 0x080;
186  } else if (strcmp(key, "step_size") == 0) {
187  SQInteger res;
188  if (SQ_FAILED(sq_getinteger(vm, -1, &res))) return SQ_ERROR;
189  config.step_size = res;
190  } else if (strcmp(key, "flags") == 0) {
191  SQInteger res;
192  if (SQ_FAILED(sq_getinteger(vm, -1, &res))) return SQ_ERROR;
193  config.flags = (ScriptConfigFlags)res;
194  items |= 0x100;
195  } else {
196  char error[1024];
197  seprintf(error, lastof(error), "unknown setting property '%s'", key);
198  this->engine->ThrowError(error);
199  return SQ_ERROR;
200  }
201 
202  sq_pop(vm, 2);
203  }
204  sq_pop(vm, 1);
205 
206  /* Don't allow both random_deviation and SCRIPTCONFIG_RANDOM to
207  * be set for the same config item. */
208  if ((items & 0x200) != 0 && (config.flags & SCRIPTCONFIG_RANDOM) != 0) {
209  char error[1024];
210  seprintf(error, lastof(error), "Setting both random_deviation and SCRIPTCONFIG_RANDOM is not allowed");
211  this->engine->ThrowError(error);
212  return SQ_ERROR;
213  }
214  /* Reset the bit for random_deviation as it's optional. */
215  items &= ~0x200;
216 
217  /* Make sure all properties are defined */
218  uint mask = (config.flags & SCRIPTCONFIG_BOOLEAN) ? 0x1F3 : 0x1FF;
219  if (items != mask) {
220  char error[1024];
221  seprintf(error, lastof(error), "please define all properties of a setting (min/max not allowed for booleans)");
222  this->engine->ThrowError(error);
223  return SQ_ERROR;
224  }
225 
226  this->config_list.push_back(config);
227  return 0;
228 }
229 
230 SQInteger ScriptInfo::AddLabels(HSQUIRRELVM vm)
231 {
232  const SQChar *setting_name;
233  if (SQ_FAILED(sq_getstring(vm, -2, &setting_name))) return SQ_ERROR;
234  ValidateString(setting_name);
235 
236  ScriptConfigItem *config = NULL;
237  for (ScriptConfigItemList::iterator it = this->config_list.begin(); it != this->config_list.end(); it++) {
238  if (strcmp((*it).name, setting_name) == 0) config = &(*it);
239  }
240 
241  if (config == NULL) {
242  char error[1024];
243  seprintf(error, lastof(error), "Trying to add labels for non-defined setting '%s'", setting_name);
244  this->engine->ThrowError(error);
245  return SQ_ERROR;
246  }
247  if (config->labels != NULL) return SQ_ERROR;
248 
249  config->labels = new LabelMapping;
250 
251  /* Read the table and find all labels */
252  sq_pushnull(vm);
253  while (SQ_SUCCEEDED(sq_next(vm, -2))) {
254  const SQChar *key_string;
255  const SQChar *label;
256  if (SQ_FAILED(sq_getstring(vm, -2, &key_string))) return SQ_ERROR;
257  if (SQ_FAILED(sq_getstring(vm, -1, &label))) return SQ_ERROR;
258  /* Because squirrel doesn't support identifiers starting with a digit,
259  * we skip the first character. */
260  int key = atoi(key_string + 1);
261  ValidateString(label);
262 
263  /* !Contains() prevents stredup from leaking. */
264  if (!config->labels->Contains(key)) config->labels->Insert(key, stredup(label));
265 
266  sq_pop(vm, 2);
267  }
268  sq_pop(vm, 1);
269 
270  /* Check labels for completeness */
271  config->complete_labels = true;
272  for (int value = config->min_value; value <= config->max_value; value++) {
273  if (!config->labels->Contains(value)) {
274  config->complete_labels = false;
275  break;
276  }
277  }
278 
279  return 0;
280 }
281 
283 {
284  return &this->config_list;
285 }
286 
287 const ScriptConfigItem *ScriptInfo::GetConfigItem(const char *name) const
288 {
289  for (ScriptConfigItemList::const_iterator it = this->config_list.begin(); it != this->config_list.end(); it++) {
290  if (strcmp((*it).name, name) == 0) return &(*it);
291  }
292  return NULL;
293 }
294 
295 int ScriptInfo::GetSettingDefaultValue(const char *name) const
296 {
297  for (ScriptConfigItemList::const_iterator it = this->config_list.begin(); it != this->config_list.end(); it++) {
298  if (strcmp((*it).name, name) != 0) continue;
299  /* The default value depends on the difficulty level */
301  case SP_EASY: return (*it).easy_value;
302  case SP_MEDIUM: return (*it).medium_value;
303  case SP_HARD: return (*it).hard_value;
304  case SP_CUSTOM: return (*it).custom_value;
305  default: NOT_REACHED();
306  }
307  }
308 
309  /* There is no such setting */
310  return -1;
311 }