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/>.
10 /** @file script_instance.cpp Implementation of ScriptInstance. */
12 #include "../stdafx.h"
14 #include "../saveload/saveload.h"
16 #include "../script/squirrel_class.hpp"
18 #include "script_fatalerror.hpp"
19 #include "script_storage.hpp"
20 #include "script_info.hpp"
21 #include "script_instance.hpp"
23 #include "api/script_controller.hpp"
24 #include "api/script_error.hpp"
25 #include "api/script_event.hpp"
26 #include "api/script_log.hpp"
28 #include "../company_base.h"
29 #include "../company_func.h"
30 #include "../fileio_func.h"
32 #include "../safeguards.h"
34 ScriptStorage::~ScriptStorage()
36 /* Free our pointers */
37 if (event_data
!= NULL
) ScriptEventController::FreeEventPointer();
38 if (log_data
!= NULL
) ScriptLog::FreeLogPointer();
42 * Callback called by squirrel when a script uses "print" and for error messages.
43 * @param error_msg Is this an error message?
44 * @param message The actual message text.
46 static void PrintFunc(bool error_msg
, const SQChar
*message
)
48 /* Convert to OpenTTD internal capable string */
49 ScriptController::Print(error_msg
, message
);
52 ScriptInstance::ScriptInstance(const char *APIName
) :
60 is_save_data_on_stack(false),
65 this->storage
= new ScriptStorage();
66 this->engine
= new Squirrel(APIName
);
67 this->engine
->SetPrintFunction(&PrintFunc
);
70 void ScriptInstance::Initialize(const char *main_script
, const char *instance_name
, CompanyID company
)
72 ScriptObject::ActiveInstance
active(this);
74 this->controller
= new ScriptController(company
);
76 /* Register the API functions and classes */
77 this->engine
->SetGlobalPointer(this->engine
);
81 ScriptObject::SetAllowDoCommand(false);
82 /* Load and execute the script for this script */
83 if (strcmp(main_script
, "%_dummy") == 0) {
84 this->LoadDummyScript();
85 } else if (!this->engine
->LoadScript(main_script
) || this->engine
->IsSuspended()) {
86 if (this->engine
->IsSuspended()) ScriptLog::Error("This script took too long to load script. AI is not started.");
91 /* Create the main-class */
92 this->instance
= MallocT
<SQObject
>(1);
93 if (!this->engine
->CreateClassInstance(instance_name
, this->controller
, this->instance
)) {
97 ScriptObject::SetAllowDoCommand(true);
98 } catch (Script_FatalError e
) {
100 this->engine
->ThrowError(e
.GetErrorMessage());
101 this->engine
->ResumeError();
106 void ScriptInstance::RegisterAPI()
108 extern void squirrel_register_std(Squirrel
*engine
);
109 squirrel_register_std(this->engine
);
112 bool ScriptInstance::LoadCompatibilityScripts(const char *api_version
, Subdirectory dir
)
114 char script_name
[32];
115 seprintf(script_name
, lastof(script_name
), "compat_%s.nut", api_version
);
118 FOR_ALL_SEARCHPATHS(sp
) {
119 FioAppendDirectory(buf
, lastof(buf
), sp
, dir
);
120 strecat(buf
, script_name
, lastof(buf
));
121 if (!FileExists(buf
)) continue;
123 if (this->engine
->LoadScript(buf
)) return true;
125 ScriptLog::Error("Failed to load API compatibility script");
126 DEBUG(script
, 0, "Error compiling / running API compatibility script: %s", buf
);
130 ScriptLog::Warning("API compatibility script not found");
134 ScriptInstance::~ScriptInstance()
136 ScriptObject::ActiveInstance
active(this);
138 if (instance
!= NULL
) this->engine
->ReleaseObject(this->instance
);
139 if (engine
!= NULL
) delete this->engine
;
140 delete this->storage
;
141 delete this->controller
;
142 free(this->instance
);
145 void ScriptInstance::Continue()
147 assert(this->suspend
< 0);
148 this->suspend
= -this->suspend
- 1;
151 void ScriptInstance::Died()
153 DEBUG(script
, 0, "The script died unexpectedly.");
154 this->is_dead
= true;
156 if (this->instance
!= NULL
) this->engine
->ReleaseObject(this->instance
);
158 this->instance
= NULL
;
162 void ScriptInstance::GameLoop()
164 ScriptObject::ActiveInstance
active(this);
166 if (this->IsDead()) return;
167 if (this->engine
->HasScriptCrashed()) {
168 /* The script crashed during saving, kill it here. */
172 if (this->is_paused
) return;
173 this->controller
->ticks
++;
175 if (this->suspend
< -1) this->suspend
++; // Multiplayer suspend, increase up to -1.
176 if (this->suspend
< 0) return; // Multiplayer suspend, wait for Continue().
177 if (--this->suspend
> 0) return; // Singleplayer suspend, decrease to 0.
179 _current_company
= ScriptObject::GetCompany();
181 /* If there is a callback to call, call that first */
182 if (this->callback
!= NULL
) {
183 if (this->is_save_data_on_stack
) {
184 sq_poptop(this->engine
->GetVM());
185 this->is_save_data_on_stack
= false;
188 this->callback(this);
189 } catch (Script_Suspend e
) {
190 this->suspend
= e
.GetSuspendTime();
191 this->callback
= e
.GetSuspendCallback();
198 this->callback
= NULL
;
200 if (!this->is_started
) {
202 ScriptObject::SetAllowDoCommand(false);
203 /* Run the constructor if it exists. Don't allow any DoCommands in it. */
204 if (this->engine
->MethodExists(*this->instance
, "constructor")) {
205 if (!this->engine
->CallMethod(*this->instance
, "constructor", MAX_CONSTRUCTOR_OPS
) || this->engine
->IsSuspended()) {
206 if (this->engine
->IsSuspended()) ScriptLog::Error("This script took too long to initialize. Script is not started.");
211 if (!this->CallLoad() || this->engine
->IsSuspended()) {
212 if (this->engine
->IsSuspended()) ScriptLog::Error("This script took too long in the Load function. Script is not started.");
216 ScriptObject::SetAllowDoCommand(true);
217 /* Start the script by calling Start() */
218 if (!this->engine
->CallMethod(*this->instance
, "Start", _settings_game
.script
.script_max_opcode_till_suspend
) || !this->engine
->IsSuspended()) this->Died();
219 } catch (Script_Suspend e
) {
220 this->suspend
= e
.GetSuspendTime();
221 this->callback
= e
.GetSuspendCallback();
222 } catch (Script_FatalError e
) {
223 this->is_dead
= true;
224 this->engine
->ThrowError(e
.GetErrorMessage());
225 this->engine
->ResumeError();
229 this->is_started
= true;
232 if (this->is_save_data_on_stack
) {
233 sq_poptop(this->engine
->GetVM());
234 this->is_save_data_on_stack
= false;
237 /* Continue the VM */
239 if (!this->engine
->Resume(_settings_game
.script
.script_max_opcode_till_suspend
)) this->Died();
240 } catch (Script_Suspend e
) {
241 this->suspend
= e
.GetSuspendTime();
242 this->callback
= e
.GetSuspendCallback();
243 } catch (Script_FatalError e
) {
244 this->is_dead
= true;
245 this->engine
->ThrowError(e
.GetErrorMessage());
246 this->engine
->ResumeError();
251 void ScriptInstance::CollectGarbage() const
253 if (this->is_started
&& !this->IsDead()) this->engine
->CollectGarbage();
256 /* static */ void ScriptInstance::DoCommandReturn(ScriptInstance
*instance
)
258 instance
->engine
->InsertResult(ScriptObject::GetLastCommandRes());
261 /* static */ void ScriptInstance::DoCommandReturnVehicleID(ScriptInstance
*instance
)
263 instance
->engine
->InsertResult(ScriptObject::GetNewVehicleID());
266 /* static */ void ScriptInstance::DoCommandReturnSignID(ScriptInstance
*instance
)
268 instance
->engine
->InsertResult(ScriptObject::GetNewSignID());
271 /* static */ void ScriptInstance::DoCommandReturnGroupID(ScriptInstance
*instance
)
273 instance
->engine
->InsertResult(ScriptObject::GetNewGroupID());
276 /* static */ void ScriptInstance::DoCommandReturnGoalID(ScriptInstance
*instance
)
278 instance
->engine
->InsertResult(ScriptObject::GetNewGoalID());
281 /* static */ void ScriptInstance::DoCommandReturnStoryPageID(ScriptInstance
*instance
)
283 instance
->engine
->InsertResult(ScriptObject::GetNewStoryPageID());
286 /* static */ void ScriptInstance::DoCommandReturnStoryPageElementID(ScriptInstance
*instance
)
288 instance
->engine
->InsertResult(ScriptObject::GetNewStoryPageElementID());
291 ScriptStorage
*ScriptInstance::GetStorage()
293 return this->storage
;
296 void *ScriptInstance::GetLogPointer()
298 ScriptObject::ActiveInstance
active(this);
300 return ScriptObject::GetLogPointer();
304 * All data is stored in the following format:
305 * First 1 byte indicating if there is a data blob at all.
306 * 1 byte indicating the type of data.
307 * The data itself, this differs per type:
308 * - integer: a binary representation of the integer (int32).
309 * - string: First one byte with the string length, then a 0-terminated char
310 * array. The string can't be longer than 255 bytes (including
312 * - array: All data-elements of the array are saved recursive in this
313 * format, and ended with an element of the type
314 * SQSL_ARRAY_TABLE_END.
315 * - table: All key/value pairs are saved in this format (first key 1, then
316 * value 1, then key 2, etc.). All keys and values can have an
317 * arbitrary type (as long as it is supported by the save function
318 * of course). The table is ended with an element of the type
319 * SQSL_ARRAY_TABLE_END.
320 * - bool: A single byte with value 1 representing true and 0 false.
324 /** The type of the data that follows in the savegame. */
325 enum SQSaveLoadType
{
326 SQSL_INT
= 0x00, ///< The following data is an integer.
327 SQSL_STRING
= 0x01, ///< The following data is an string.
328 SQSL_ARRAY
= 0x02, ///< The following data is an array.
329 SQSL_TABLE
= 0x03, ///< The following data is an table.
330 SQSL_BOOL
= 0x04, ///< The following data is a boolean.
331 SQSL_NULL
= 0x05, ///< A null variable.
332 SQSL_ARRAY_TABLE_END
= 0xFF, ///< Marks the end of an array or table, no data follows.
335 static byte _script_sl_byte
; ///< Used as source/target by the script saveload code to store/load a single byte.
337 /** SaveLoad array that saves/loads exactly one byte. */
338 static const SaveLoad _script_byte
[] = {
339 SLEG_VAR(_script_sl_byte
, SLE_UINT8
),
343 /* static */ bool ScriptInstance::SaveObject(HSQUIRRELVM vm
, SQInteger index
, int max_depth
, bool test
)
345 if (max_depth
== 0) {
346 ScriptLog::Error("Savedata can only be nested to 25 deep. No data saved."); // SQUIRREL_MAX_DEPTH = 25
350 switch (sq_gettype(vm
, index
)) {
353 _script_sl_byte
= SQSL_INT
;
354 SlObject(NULL
, _script_byte
);
357 sq_getinteger(vm
, index
, &res
);
359 int value
= (int)res
;
360 SlArray(&value
, 1, SLE_INT32
);
367 _script_sl_byte
= SQSL_STRING
;
368 SlObject(NULL
, _script_byte
);
371 sq_getstring(vm
, index
, &buf
);
372 size_t len
= strlen(buf
) + 1;
374 ScriptLog::Error("Maximum string length is 254 chars. No data saved.");
378 _script_sl_byte
= (byte
)len
;
379 SlObject(NULL
, _script_byte
);
380 SlArray(const_cast<char *>(buf
), len
, SLE_CHAR
);
387 _script_sl_byte
= SQSL_ARRAY
;
388 SlObject(NULL
, _script_byte
);
391 while (SQ_SUCCEEDED(sq_next(vm
, index
- 1))) {
392 /* Store the value */
393 bool res
= SaveObject(vm
, -1, max_depth
- 1, test
);
402 _script_sl_byte
= SQSL_ARRAY_TABLE_END
;
403 SlObject(NULL
, _script_byte
);
410 _script_sl_byte
= SQSL_TABLE
;
411 SlObject(NULL
, _script_byte
);
414 while (SQ_SUCCEEDED(sq_next(vm
, index
- 1))) {
415 /* Store the key + value */
416 bool res
= SaveObject(vm
, -2, max_depth
- 1, test
) && SaveObject(vm
, -1, max_depth
- 1, test
);
425 _script_sl_byte
= SQSL_ARRAY_TABLE_END
;
426 SlObject(NULL
, _script_byte
);
433 _script_sl_byte
= SQSL_BOOL
;
434 SlObject(NULL
, _script_byte
);
437 sq_getbool(vm
, index
, &res
);
439 _script_sl_byte
= res
? 1 : 0;
440 SlObject(NULL
, _script_byte
);
447 _script_sl_byte
= SQSL_NULL
;
448 SlObject(NULL
, _script_byte
);
454 ScriptLog::Error("You tried to save an unsupported type. No data saved.");
459 /* static */ void ScriptInstance::SaveEmpty()
462 SlObject(NULL
, _script_byte
);
465 void ScriptInstance::Save()
467 ScriptObject::ActiveInstance
active(this);
469 /* Don't save data if the script didn't start yet or if it crashed. */
470 if (this->engine
== NULL
|| this->engine
->HasScriptCrashed()) {
475 HSQUIRRELVM vm
= this->engine
->GetVM();
476 if (this->is_save_data_on_stack
) {
478 SlObject(NULL
, _script_byte
);
479 /* Save the data that was just loaded. */
480 SaveObject(vm
, -1, SQUIRREL_MAX_DEPTH
, false);
481 } else if (!this->is_started
) {
484 } else if (this->engine
->MethodExists(*this->instance
, "Save")) {
486 /* We don't want to be interrupted during the save function. */
487 bool backup_allow
= ScriptObject::GetAllowDoCommand();
488 ScriptObject::SetAllowDoCommand(false);
490 if (!this->engine
->CallMethod(*this->instance
, "Save", &savedata
, MAX_SL_OPS
)) {
491 /* The script crashed in the Save function. We can't kill
492 * it here, but do so in the next script tick. */
494 this->engine
->CrashOccurred();
497 } catch (Script_FatalError e
) {
498 /* If we don't mark the script as dead here cleaning up the squirrel
499 * stack could throw Script_FatalError again. */
500 this->is_dead
= true;
501 this->engine
->ThrowError(e
.GetErrorMessage());
502 this->engine
->ResumeError();
504 /* We can't kill the script here, so mark it as crashed (not dead) and
505 * kill it in the next script tick. */
506 this->is_dead
= false;
507 this->engine
->CrashOccurred();
510 ScriptObject::SetAllowDoCommand(backup_allow
);
512 if (!sq_istable(savedata
)) {
513 ScriptLog::Error(this->engine
->IsSuspended() ? "This script took too long to Save." : "Save function should return a table.");
515 this->engine
->CrashOccurred();
518 sq_pushobject(vm
, savedata
);
519 if (SaveObject(vm
, -1, SQUIRREL_MAX_DEPTH
, true)) {
521 SlObject(NULL
, _script_byte
);
522 SaveObject(vm
, -1, SQUIRREL_MAX_DEPTH
, false);
523 this->is_save_data_on_stack
= true;
526 this->engine
->CrashOccurred();
529 ScriptLog::Warning("Save function is not implemented");
531 SlObject(NULL
, _script_byte
);
535 void ScriptInstance::Pause()
537 /* Suspend script. */
538 HSQUIRRELVM vm
= this->engine
->GetVM();
539 Squirrel::DecreaseOps(vm
, _settings_game
.script
.script_max_opcode_till_suspend
);
541 this->is_paused
= true;
544 void ScriptInstance::Unpause()
546 this->is_paused
= false;
549 bool ScriptInstance::IsPaused()
551 return this->is_paused
;
554 /* static */ bool ScriptInstance::LoadObjects(HSQUIRRELVM vm
)
556 SlObject(NULL
, _script_byte
);
557 switch (_script_sl_byte
) {
560 SlArray(&value
, 1, SLE_INT32
);
561 if (vm
!= NULL
) sq_pushinteger(vm
, (SQInteger
)value
);
566 SlObject(NULL
, _script_byte
);
567 static char buf
[256];
568 SlArray(buf
, _script_sl_byte
, SLE_CHAR
);
569 if (vm
!= NULL
) sq_pushstring(vm
, buf
, -1);
574 if (vm
!= NULL
) sq_newarray(vm
, 0);
575 while (LoadObjects(vm
)) {
576 if (vm
!= NULL
) sq_arrayappend(vm
, -2);
577 /* The value is popped from the stack by squirrel. */
583 if (vm
!= NULL
) sq_newtable(vm
);
584 while (LoadObjects(vm
)) {
586 if (vm
!= NULL
) sq_rawset(vm
, -3);
587 /* The key (-2) and value (-1) are popped from the stack by squirrel. */
593 SlObject(NULL
, _script_byte
);
594 if (vm
!= NULL
) sq_pushbool(vm
, (SQBool
)(_script_sl_byte
!= 0));
599 if (vm
!= NULL
) sq_pushnull(vm
);
603 case SQSL_ARRAY_TABLE_END
: {
607 default: NOT_REACHED();
611 /* static */ void ScriptInstance::LoadEmpty()
613 SlObject(NULL
, _script_byte
);
614 /* Check if there was anything saved at all. */
615 if (_script_sl_byte
== 0) return;
620 void ScriptInstance::Load(int version
)
622 ScriptObject::ActiveInstance
active(this);
624 if (this->engine
== NULL
|| version
== -1) {
628 HSQUIRRELVM vm
= this->engine
->GetVM();
630 SlObject(NULL
, _script_byte
);
631 /* Check if there was anything saved at all. */
632 if (_script_sl_byte
== 0) return;
634 sq_pushinteger(vm
, version
);
636 this->is_save_data_on_stack
= true;
639 bool ScriptInstance::CallLoad()
641 HSQUIRRELVM vm
= this->engine
->GetVM();
642 /* Is there save data that we should load? */
643 if (!this->is_save_data_on_stack
) return true;
644 /* Whatever happens, after CallLoad the savegame data is removed from the stack. */
645 this->is_save_data_on_stack
= false;
647 if (!this->engine
->MethodExists(*this->instance
, "Load")) {
648 ScriptLog::Warning("Loading failed: there was data for the script to load, but the script does not have a Load() function.");
650 /* Pop the savegame data and version. */
655 /* Go to the instance-root */
656 sq_pushobject(vm
, *this->instance
);
657 /* Find the function-name inside the script */
658 sq_pushstring(vm
, "Load", -1);
659 /* Change the "Load" string in a function pointer */
661 /* Push the main instance as "this" object */
662 sq_pushobject(vm
, *this->instance
);
663 /* Push the version data and savegame data as arguments */
667 /* Call the script load function. sq_call removes the arguments (but not the
668 * function pointer) from the stack. */
669 if (SQ_FAILED(sq_call(vm
, 3, SQFalse
, SQFalse
, MAX_SL_OPS
))) return false;
671 /* Pop 1) The version, 2) the savegame data, 3) the object instance, 4) the function pointer. */
676 SQInteger
ScriptInstance::GetOpsTillSuspend()
678 return this->engine
->GetOpsTillSuspend();
681 void ScriptInstance::DoCommandCallback(const CommandCost
&result
, TileIndex tile
, uint32 p1
, uint32 p2
)
683 ScriptObject::ActiveInstance
active(this);
685 ScriptObject::SetLastCommandRes(result
.Succeeded());
687 if (result
.Failed()) {
688 ScriptObject::SetLastError(ScriptError::StringToError(result
.GetErrorMessage()));
690 ScriptObject::IncreaseDoCommandCosts(result
.GetCost());
691 ScriptObject::SetLastCost(result
.GetCost());
695 void ScriptInstance::InsertEvent(class ScriptEvent
*event
)
697 ScriptObject::ActiveInstance
active(this);
699 ScriptEventController::InsertEvent(event
);