OpenTTD Source 20241224-master-gf74b0cf984
script_instance.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 "../debug.h"
12#include "../saveload/saveload.h"
13
14#include "../script/squirrel_class.hpp"
15#include "../script/squirrel_std.hpp"
16
17#include "script_fatalerror.hpp"
18#include "script_storage.hpp"
19#include "script_info.hpp"
20#include "script_instance.hpp"
21
22#include "api/script_controller.hpp"
23#include "api/script_error.hpp"
24#include "api/script_event.hpp"
25#include "api/script_log.hpp"
26
27#include "../company_base.h"
28#include "../company_func.h"
29#include "../fileio_func.h"
30#include "../league_type.h"
31#include "../misc/endian_buffer.hpp"
32
33#include "../safeguards.h"
34
35ScriptStorage::~ScriptStorage()
36{
37 /* Free our pointers */
38 if (event_data != nullptr) ScriptEventController::FreeEventPointer();
39}
40
46static void PrintFunc(bool error_msg, const std::string &message)
47{
48 /* Convert to OpenTTD internal capable string */
49 ScriptController::Print(error_msg, message);
50}
51
52ScriptInstance::ScriptInstance(const char *APIName) :
53 engine(nullptr),
54 controller(nullptr),
55 storage(nullptr),
56 instance(nullptr),
57 is_started(false),
58 is_dead(false),
59 is_save_data_on_stack(false),
60 suspend(0),
61 is_paused(false),
62 in_shutdown(false),
63 callback(nullptr)
64{
65 this->storage = new ScriptStorage();
66 this->engine = new Squirrel(APIName);
68}
69
70void ScriptInstance::Initialize(const std::string &main_script, const std::string &instance_name, CompanyID company)
71{
72 ScriptObject::ActiveInstance active(this);
73
74 this->controller = new ScriptController(company);
75
76 /* Register the API functions and classes */
77 this->engine->SetGlobalPointer(this->engine);
78 this->RegisterAPI();
79 if (this->IsDead()) {
80 /* Failed to register API; a message has already been logged. */
81 return;
82 }
83
84 try {
85 ScriptObject::SetAllowDoCommand(false);
86 /* Load and execute the script for this script */
87 if (main_script == "%_dummy") {
88 this->LoadDummyScript();
89 } else if (!this->engine->LoadScript(main_script) || this->engine->IsSuspended()) {
90 if (this->engine->IsSuspended()) ScriptLog::Error("This script took too long to load script. AI is not started.");
91 this->Died();
92 return;
93 }
94
95 /* Create the main-class */
96 this->instance = new SQObject();
97 if (!this->engine->CreateClassInstance(instance_name, this->controller, this->instance)) {
98 /* If CreateClassInstance has returned false instance has not been
99 * registered with squirrel, so avoid trying to Release it by clearing it now */
100 delete this->instance;
101 this->instance = nullptr;
102 this->Died();
103 return;
104 }
105 ScriptObject::SetAllowDoCommand(true);
106 } catch (Script_FatalError &e) {
107 this->is_dead = true;
109 this->engine->ResumeError();
110 this->Died();
111 }
112}
113
118
119bool ScriptInstance::LoadCompatibilityScripts(const std::string &api_version, Subdirectory dir)
120{
121 std::string script_name = fmt::format("compat_{}.nut", api_version);
122 for (Searchpath sp : _valid_searchpaths) {
123 std::string buf = FioGetDirectory(sp, dir);
124 buf += script_name;
125 if (!FileExists(buf)) continue;
126
127 if (this->engine->LoadScript(buf)) return true;
128
129 ScriptLog::Error("Failed to load API compatibility script");
130 Debug(script, 0, "Error compiling / running API compatibility script: {}", buf);
131 return false;
132 }
133
134 ScriptLog::Warning("API compatibility script not found");
135 return true;
136}
137
138ScriptInstance::~ScriptInstance()
139{
140 ScriptObject::ActiveInstance active(this);
141 this->in_shutdown = true;
142
143 if (instance != nullptr) this->engine->ReleaseObject(this->instance);
144 if (engine != nullptr) delete this->engine;
145 delete this->storage;
146 delete this->controller;
147 delete this->instance;
148}
149
151{
152 assert(this->suspend < 0);
153 this->suspend = -this->suspend - 1;
154}
155
157{
158 Debug(script, 0, "The script died unexpectedly.");
159 this->is_dead = true;
160 this->in_shutdown = true;
161
162 this->last_allocated_memory = this->GetAllocatedMemory(); // Update cache
163
164 if (this->instance != nullptr) this->engine->ReleaseObject(this->instance);
165 delete this->instance;
166 delete this->engine;
167 this->instance = nullptr;
168 this->engine = nullptr;
169}
170
172{
173 ScriptObject::ActiveInstance active(this);
174
175 if (this->IsDead()) return;
176 if (this->engine->HasScriptCrashed()) {
177 /* The script crashed during saving, kill it here. */
178 this->Died();
179 return;
180 }
181 if (this->is_paused) return;
182 this->controller->ticks++;
183
184 if (this->suspend < -1) this->suspend++; // Multiplayer suspend, increase up to -1.
185 if (this->suspend < 0) return; // Multiplayer suspend, wait for Continue().
186 if (--this->suspend > 0) return; // Singleplayer suspend, decrease to 0.
187
188 _current_company = ScriptObject::GetCompany();
189
190 /* If there is a callback to call, call that first */
191 if (this->callback != nullptr) {
192 if (this->is_save_data_on_stack) {
193 sq_poptop(this->engine->GetVM());
194 this->is_save_data_on_stack = false;
195 }
196 try {
197 this->callback(this);
198 } catch (Script_Suspend &e) {
199 this->suspend = e.GetSuspendTime();
200 this->callback = e.GetSuspendCallback();
201
202 return;
203 }
204 }
205
206 this->suspend = 0;
207 this->callback = nullptr;
208
209 if (!this->is_started) {
210 try {
211 ScriptObject::SetAllowDoCommand(false);
212 /* Run the constructor if it exists. Don't allow any DoCommands in it. */
213 if (this->engine->MethodExists(*this->instance, "constructor")) {
214 if (!this->engine->CallMethod(*this->instance, "constructor", MAX_CONSTRUCTOR_OPS) || this->engine->IsSuspended()) {
215 if (this->engine->IsSuspended()) ScriptLog::Error("This script took too long to initialize. Script is not started.");
216 this->Died();
217 return;
218 }
219 }
220 if (!this->CallLoad() || this->engine->IsSuspended()) {
221 if (this->engine->IsSuspended()) ScriptLog::Error("This script took too long in the Load function. Script is not started.");
222 this->Died();
223 return;
224 }
225 ScriptObject::SetAllowDoCommand(true);
226 /* Start the script by calling Start() */
227 if (!this->engine->CallMethod(*this->instance, "Start", _settings_game.script.script_max_opcode_till_suspend) || !this->engine->IsSuspended()) this->Died();
228 } catch (Script_Suspend &e) {
229 this->suspend = e.GetSuspendTime();
230 this->callback = e.GetSuspendCallback();
231 } catch (Script_FatalError &e) {
232 this->is_dead = true;
233 this->engine->ThrowError(e.GetErrorMessage());
234 this->engine->ResumeError();
235 this->Died();
236 }
237
238 this->is_started = true;
239 return;
240 }
241 if (this->is_save_data_on_stack) {
242 sq_poptop(this->engine->GetVM());
243 this->is_save_data_on_stack = false;
244 }
245
246 /* Continue the VM */
247 try {
249 } catch (Script_Suspend &e) {
250 this->suspend = e.GetSuspendTime();
251 this->callback = e.GetSuspendCallback();
252 } catch (Script_FatalError &e) {
253 this->is_dead = true;
254 this->engine->ThrowError(e.GetErrorMessage());
255 this->engine->ResumeError();
256 this->Died();
257 }
258}
259
261{
262 if (this->is_started && !this->IsDead()) {
263 ScriptObject::ActiveInstance active(this);
264 this->engine->CollectGarbage();
265 }
266}
267
269{
270 instance->engine->InsertResult(ScriptObject::GetLastCommandRes());
271}
272
274{
275 instance->engine->InsertResult(EndianBufferReader::ToValue<VehicleID>(ScriptObject::GetLastCommandResData()));
276}
277
279{
280 instance->engine->InsertResult(EndianBufferReader::ToValue<SignID>(ScriptObject::GetLastCommandResData()));
281}
282
284{
285 instance->engine->InsertResult(EndianBufferReader::ToValue<GroupID>(ScriptObject::GetLastCommandResData()));
286}
287
289{
290 instance->engine->InsertResult(EndianBufferReader::ToValue<GoalID>(ScriptObject::GetLastCommandResData()));
291}
292
294{
295 instance->engine->InsertResult(EndianBufferReader::ToValue<StoryPageID>(ScriptObject::GetLastCommandResData()));
296}
297
299{
300 instance->engine->InsertResult(EndianBufferReader::ToValue<StoryPageElementID>(ScriptObject::GetLastCommandResData()));
301}
302
304{
305 instance->engine->InsertResult(EndianBufferReader::ToValue<LeagueTableElementID>(ScriptObject::GetLastCommandResData()));
306}
307
309{
310 instance->engine->InsertResult(EndianBufferReader::ToValue<LeagueTableID>(ScriptObject::GetLastCommandResData()));
311}
312
313
315{
316 return this->storage;
317}
318
319ScriptLogTypes::LogData &ScriptInstance::GetLogData()
320{
321 ScriptObject::ActiveInstance active(this);
322
323 return ScriptObject::GetLogData();
324}
325
326/*
327 * All data is stored in the following format:
328 * First 1 byte indicating if there is a data blob at all.
329 * 1 byte indicating the type of data.
330 * The data itself, this differs per type:
331 * - integer: a binary representation of the integer (int32_t).
332 * - string: First one byte with the string length, then a 0-terminated char
333 * array. The string can't be longer than 255 bytes (including
334 * terminating '\0').
335 * - array: All data-elements of the array are saved recursive in this
336 * format, and ended with an element of the type
337 * SQSL_ARRAY_TABLE_END.
338 * - table: All key/value pairs are saved in this format (first key 1, then
339 * value 1, then key 2, etc.). All keys and values can have an
340 * arbitrary type (as long as it is supported by the save function
341 * of course). The table is ended with an element of the type
342 * SQSL_ARRAY_TABLE_END.
343 * - bool: A single byte with value 1 representing true and 0 false.
344 * - null: No data.
345 */
346
347static uint8_t _script_sl_byte;
348
350static const SaveLoad _script_byte[] = {
351 SLEG_VAR("type", _script_sl_byte, SLE_UINT8),
352};
353
354/* static */ bool ScriptInstance::SaveObject(HSQUIRRELVM vm, SQInteger index, int max_depth, bool test)
355{
356 if (max_depth == 0) {
357 ScriptLog::Error("Savedata can only be nested to 25 deep. No data saved."); // SQUIRREL_MAX_DEPTH = 25
358 return false;
359 }
360
361 switch (sq_gettype(vm, index)) {
362 case OT_INTEGER: {
363 if (!test) {
365 SlObject(nullptr, _script_byte);
366 }
367 SQInteger res;
368 sq_getinteger(vm, index, &res);
369 if (!test) {
370 int64_t value = (int64_t)res;
371 SlCopy(&value, 1, SLE_INT64);
372 }
373 return true;
374 }
375
376 case OT_STRING: {
377 if (!test) {
379 SlObject(nullptr, _script_byte);
380 }
381 const SQChar *buf;
382 sq_getstring(vm, index, &buf);
383 size_t len = strlen(buf) + 1;
384 if (len >= 255) {
385 ScriptLog::Error("Maximum string length is 254 chars. No data saved.");
386 return false;
387 }
388 if (!test) {
389 _script_sl_byte = (uint8_t)len;
390 SlObject(nullptr, _script_byte);
391 SlCopy(const_cast<char *>(buf), len, SLE_CHAR);
392 }
393 return true;
394 }
395
396 case OT_ARRAY: {
397 if (!test) {
399 SlObject(nullptr, _script_byte);
400 }
401 sq_pushnull(vm);
402 while (SQ_SUCCEEDED(sq_next(vm, index - 1))) {
403 /* Store the value */
404 bool res = SaveObject(vm, -1, max_depth - 1, test);
405 sq_pop(vm, 2);
406 if (!res) {
407 sq_pop(vm, 1);
408 return false;
409 }
410 }
411 sq_pop(vm, 1);
412 if (!test) {
414 SlObject(nullptr, _script_byte);
415 }
416 return true;
417 }
418
419 case OT_TABLE: {
420 if (!test) {
422 SlObject(nullptr, _script_byte);
423 }
424 sq_pushnull(vm);
425 while (SQ_SUCCEEDED(sq_next(vm, index - 1))) {
426 /* Store the key + value */
427 bool res = SaveObject(vm, -2, max_depth - 1, test) && SaveObject(vm, -1, max_depth - 1, test);
428 sq_pop(vm, 2);
429 if (!res) {
430 sq_pop(vm, 1);
431 return false;
432 }
433 }
434 sq_pop(vm, 1);
435 if (!test) {
437 SlObject(nullptr, _script_byte);
438 }
439 return true;
440 }
441
442 case OT_BOOL: {
443 if (!test) {
445 SlObject(nullptr, _script_byte);
446 }
447 SQBool res;
448 sq_getbool(vm, index, &res);
449 if (!test) {
450 _script_sl_byte = res ? 1 : 0;
451 SlObject(nullptr, _script_byte);
452 }
453 return true;
454 }
455
456 case OT_NULL: {
457 if (!test) {
459 SlObject(nullptr, _script_byte);
460 }
461 return true;
462 }
463
464 default:
465 ScriptLog::Error("You tried to save an unsupported type. No data saved.");
466 return false;
467 }
468}
469
470/* static */ void ScriptInstance::SaveEmpty()
471{
472 _script_sl_byte = 0;
473 SlObject(nullptr, _script_byte);
474}
475
477{
478 ScriptObject::ActiveInstance active(this);
479
480 /* Don't save data if the script didn't start yet or if it crashed. */
481 if (this->engine == nullptr || this->engine->HasScriptCrashed()) {
482 SaveEmpty();
483 return;
484 }
485
486 HSQUIRRELVM vm = this->engine->GetVM();
487 if (this->is_save_data_on_stack) {
488 _script_sl_byte = 1;
489 SlObject(nullptr, _script_byte);
490 /* Save the data that was just loaded. */
491 SaveObject(vm, -1, SQUIRREL_MAX_DEPTH, false);
492 } else if (!this->is_started) {
493 SaveEmpty();
494 return;
495 } else if (this->engine->MethodExists(*this->instance, "Save")) {
496 HSQOBJECT savedata;
497 /* We don't want to be interrupted during the save function. */
498 bool backup_allow = ScriptObject::GetAllowDoCommand();
499 ScriptObject::SetAllowDoCommand(false);
500 try {
501 if (!this->engine->CallMethod(*this->instance, "Save", &savedata, MAX_SL_OPS)) {
502 /* The script crashed in the Save function. We can't kill
503 * it here, but do so in the next script tick. */
504 SaveEmpty();
505 this->engine->CrashOccurred();
506 return;
507 }
508 } catch (Script_FatalError &e) {
509 /* If we don't mark the script as dead here cleaning up the squirrel
510 * stack could throw Script_FatalError again. */
511 this->is_dead = true;
513 this->engine->ResumeError();
514 SaveEmpty();
515 /* We can't kill the script here, so mark it as crashed (not dead) and
516 * kill it in the next script tick. */
517 this->is_dead = false;
518 this->engine->CrashOccurred();
519 return;
520 }
521 ScriptObject::SetAllowDoCommand(backup_allow);
522
523 if (!sq_istable(savedata)) {
524 ScriptLog::Error(this->engine->IsSuspended() ? "This script took too long to Save." : "Save function should return a table.");
525 SaveEmpty();
526 this->engine->CrashOccurred();
527 return;
528 }
529 sq_pushobject(vm, savedata);
530 if (SaveObject(vm, -1, SQUIRREL_MAX_DEPTH, true)) {
531 _script_sl_byte = 1;
532 SlObject(nullptr, _script_byte);
533 SaveObject(vm, -1, SQUIRREL_MAX_DEPTH, false);
534 this->is_save_data_on_stack = true;
535 } else {
536 SaveEmpty();
537 this->engine->CrashOccurred();
538 }
539 } else {
540 ScriptLog::Warning("Save function is not implemented");
541 _script_sl_byte = 0;
542 SlObject(nullptr, _script_byte);
543 }
544}
545
547{
548 /* Suspend script. */
549 HSQUIRRELVM vm = this->engine->GetVM();
551
552 this->is_paused = true;
553}
554
556{
557 this->is_paused = false;
558}
559
561{
562 return this->is_paused;
563}
564
565/* static */ bool ScriptInstance::LoadObjects(ScriptData *data)
566{
567 SlObject(nullptr, _script_byte);
568 switch (_script_sl_byte) {
569 case SQSL_INT: {
570 int64_t value;
571 SlCopy(&value, 1, IsSavegameVersionBefore(SLV_SCRIPT_INT64) ? SLE_FILE_I32 | SLE_VAR_I64 : SLE_INT64);
572 if (data != nullptr) data->push_back((SQInteger)value);
573 return true;
574 }
575
576 case SQSL_STRING: {
577 SlObject(nullptr, _script_byte);
578 static char buf[std::numeric_limits<decltype(_script_sl_byte)>::max()];
579 SlCopy(buf, _script_sl_byte, SLE_CHAR);
580 if (data != nullptr) data->push_back(StrMakeValid(std::string_view(buf, _script_sl_byte)));
581 return true;
582 }
583
584 case SQSL_ARRAY:
585 case SQSL_TABLE: {
586 if (data != nullptr) data->push_back((SQSaveLoadType)_script_sl_byte);
587 while (LoadObjects(data));
588 return true;
589 }
590
591 case SQSL_BOOL: {
592 SlObject(nullptr, _script_byte);
593 if (data != nullptr) data->push_back((SQBool)(_script_sl_byte != 0));
594 return true;
595 }
596
597 case SQSL_NULL: {
598 if (data != nullptr) data->push_back((SQSaveLoadType)_script_sl_byte);
599 return true;
600 }
601
603 if (data != nullptr) data->push_back((SQSaveLoadType)_script_sl_byte);
604 return false;
605 }
606
607 default: SlErrorCorrupt("Invalid script data type");
608 }
609}
610
611/* static */ bool ScriptInstance::LoadObjects(HSQUIRRELVM vm, ScriptData *data)
612{
613 ScriptDataVariant value = data->front();
614 data->pop_front();
615
616 struct visitor {
617 HSQUIRRELVM vm;
618 ScriptData *data;
619
620 bool operator()(const SQInteger &value) { sq_pushinteger(this->vm, value); return true; }
621 bool operator()(const std::string &value) { sq_pushstring(this->vm, value, -1); return true; }
622 bool operator()(const SQBool &value) { sq_pushbool(this->vm, value); return true; }
623 bool operator()(const SQSaveLoadType &type)
624 {
625 switch (type) {
626 case SQSL_ARRAY:
627 sq_newarray(this->vm, 0);
628 while (LoadObjects(this->vm, this->data)) {
629 sq_arrayappend(this->vm, -2);
630 /* The value is popped from the stack by squirrel. */
631 }
632 return true;
633
634 case SQSL_TABLE:
635 sq_newtable(this->vm);
636 while (LoadObjects(this->vm, this->data)) {
637 LoadObjects(this->vm, this->data);
638 sq_rawset(this->vm, -3);
639 /* The key (-2) and value (-1) are popped from the stack by squirrel. */
640 }
641 return true;
642
643 case SQSL_NULL:
644 sq_pushnull(this->vm);
645 return true;
646
647 case SQSL_ARRAY_TABLE_END:
648 return false;
649
650 default: NOT_REACHED();
651 }
652 }
653 };
654
655 return std::visit(visitor{vm, data}, value);
656}
657
658/* static */ void ScriptInstance::LoadEmpty()
659{
660 SlObject(nullptr, _script_byte);
661 /* Check if there was anything saved at all. */
662 if (_script_sl_byte == 0) return;
663
664 LoadObjects(nullptr);
665}
666
667/* static */ ScriptInstance::ScriptData *ScriptInstance::Load(int version)
668{
669 if (version == -1) {
670 LoadEmpty();
671 return nullptr;
672 }
673
674 SlObject(nullptr, _script_byte);
675 /* Check if there was anything saved at all. */
676 if (_script_sl_byte == 0) return nullptr;
677
678 ScriptData *data = new ScriptData();
679 data->push_back((SQInteger)version);
680 LoadObjects(data);
681 return data;
682}
683
684void ScriptInstance::LoadOnStack(ScriptData *data)
685{
686 ScriptObject::ActiveInstance active(this);
687
688 if (this->IsDead() || data == nullptr) return;
689
690 HSQUIRRELVM vm = this->engine->GetVM();
691
692 ScriptDataVariant version = data->front();
693 data->pop_front();
694 SQInteger top = sq_gettop(vm);
695 try {
696 sq_pushinteger(vm, std::get<SQInteger>(version));
697 LoadObjects(vm, data);
698 this->is_save_data_on_stack = true;
699 } catch (Script_FatalError &e) {
700 ScriptLog::Warning(fmt::format("Loading failed: {}", e.GetErrorMessage()));
701 /* Discard partially loaded savegame data and version. */
702 sq_settop(vm, top);
703 }
704}
705
707{
708 HSQUIRRELVM vm = this->engine->GetVM();
709 /* Is there save data that we should load? */
710 if (!this->is_save_data_on_stack) return true;
711 /* Whatever happens, after CallLoad the savegame data is removed from the stack. */
712 this->is_save_data_on_stack = false;
713
714 if (!this->engine->MethodExists(*this->instance, "Load")) {
715 ScriptLog::Warning("Loading failed: there was data for the script to load, but the script does not have a Load() function.");
716
717 /* Pop the savegame data and version. */
718 sq_pop(vm, 2);
719 return true;
720 }
721
722 /* Go to the instance-root */
723 sq_pushobject(vm, *this->instance);
724 /* Find the function-name inside the script */
725 sq_pushstring(vm, "Load", -1);
726 /* Change the "Load" string in a function pointer */
727 sq_get(vm, -2);
728 /* Push the main instance as "this" object */
729 sq_pushobject(vm, *this->instance);
730 /* Push the version data and savegame data as arguments */
731 sq_push(vm, -5);
732 sq_push(vm, -5);
733
734 /* Call the script load function. sq_call removes the arguments (but not the
735 * function pointer) from the stack. */
736 if (SQ_FAILED(sq_call(vm, 3, SQFalse, SQFalse, MAX_SL_OPS))) return false;
737
738 /* Pop 1) The version, 2) the savegame data, 3) the object instance, 4) the function pointer. */
739 sq_pop(vm, 4);
740 return true;
741}
742
744{
745 return this->engine->GetOpsTillSuspend();
746}
747
749{
750 ScriptObject::ActiveInstance active(this);
751
752 if (!ScriptObject::CheckLastCommand(data, cmd)) {
753 Debug(script, 1, "DoCommandCallback terminating a script, last command does not match expected command");
754 return false;
755 }
756
757 ScriptObject::SetLastCommandRes(result.Succeeded());
758 ScriptObject::SetLastCommandResData(std::move(result_data));
759
760 if (result.Failed()) {
761 ScriptObject::SetLastError(ScriptError::StringToError(result.GetErrorMessage()));
762 } else {
763 ScriptObject::IncreaseDoCommandCosts(result.GetCost());
764 ScriptObject::SetLastCost(result.GetCost());
765 }
766
767 ScriptObject::SetLastCommand({}, CMD_END);
768
769 return true;
770}
771
772void ScriptInstance::InsertEvent(class ScriptEvent *event)
773{
774 ScriptObject::ActiveInstance active(this);
775
776 ScriptEventController::InsertEvent(event);
777}
778
779size_t ScriptInstance::GetAllocatedMemory() const
780{
781 if (this->engine == nullptr) return this->last_allocated_memory;
782 return this->engine->GetAllocatedMemory();
783}
784
786{
787 if (!this->in_shutdown) this->engine->ReleaseObject(obj);
788}
Common return value for all commands.
bool Succeeded() const
Did this command succeed?
Money GetCost() const
The costs as made up to this moment.
bool Failed() const
Did this command fail?
StringID GetErrorMessage() const
Returns the error message of a command.
Runtime information about a script like a pointer to the squirrel vm and the current state.
static ScriptData * Load(int version)
Load data from a savegame.
virtual void RegisterAPI()
Register all API functions to the VM.
void InsertEvent(class ScriptEvent *event)
Insert an event for this script.
void Unpause()
Resume execution of the script.
class ScriptStorage * GetStorage()
Get the storage of this script.
bool IsPaused()
Checks if the script is paused.
virtual void Died()
Tell the script it died.
static bool LoadObjects(ScriptData *data)
Load all objects from a savegame.
void ReleaseSQObject(HSQOBJECT *obj)
Decrease the ref count of a squirrel object.
static void DoCommandReturnLeagueTableElementID(ScriptInstance *instance)
Return a LeagueTableElementID reply for a DoCommand.
void CollectGarbage()
Let the VM collect any garbage.
class Squirrel * engine
A wrapper around the squirrel vm.
SQSaveLoadType
The type of the data that follows in the savegame.
@ SQSL_BOOL
The following data is a boolean.
@ SQSL_STRING
The following data is an string.
@ SQSL_TABLE
The following data is an table.
@ SQSL_ARRAY_TABLE_END
Marks the end of an array or table, no data follows.
@ SQSL_ARRAY
The following data is an array.
@ SQSL_NULL
A null variable.
@ SQSL_INT
The following data is an integer.
static void DoCommandReturn(ScriptInstance *instance)
Return a true/false reply for a DoCommand.
static void SaveEmpty()
Don't save any data in the savegame.
static void DoCommandReturnStoryPageElementID(ScriptInstance *instance)
Return a StoryPageElementID reply for a DoCommand.
void LoadOnStack(ScriptData *data)
Store loaded data on the stack.
void Save()
Call the script Save function and save all data in the savegame.
bool is_save_data_on_stack
Is the save data still on the squirrel stack?
bool in_shutdown
Is this instance currently being destructed?
virtual void LoadDummyScript()=0
Load the dummy script.
bool LoadCompatibilityScripts(const std::string &api_version, Subdirectory dir)
Load squirrel scripts to emulate an older API.
static void DoCommandReturnVehicleID(ScriptInstance *instance)
Return a VehicleID reply for a DoCommand.
static void DoCommandReturnGoalID(ScriptInstance *instance)
Return a GoalID reply for a DoCommand.
static void DoCommandReturnGroupID(ScriptInstance *instance)
Return a GroupID reply for a DoCommand.
bool DoCommandCallback(const CommandCost &result, const CommandDataBuffer &data, CommandDataBuffer result_data, Commands cmd)
DoCommand callback function for all commands executed by scripts.
SQObject * instance
Squirrel-pointer to the script main class.
ScriptLogTypes::LogData & GetLogData()
Get the log pointer of this script.
void Pause()
Suspends the script for the current tick and then pause the execution of script.
Script_SuspendCallbackProc * callback
Callback that should be called in the next tick the script runs.
void Initialize(const std::string &main_script, const std::string &instance_name, CompanyID company)
Initialize the script and prepare it for its first run.
static bool SaveObject(HSQUIRRELVM vm, SQInteger index, int max_depth, bool test)
Save one object (int / string / array / table) to the savegame.
bool CallLoad()
Call the script Load function if it exists and data was loaded from a savegame.
bool is_dead
True if the script has been stopped.
bool IsDead() const
Return the "this script died" value.
size_t last_allocated_memory
Last known allocated memory value (for display for crashed scripts)
void Continue()
A script in multiplayer waits for the server to handle its DoCommand.
static void LoadEmpty()
Load and discard data from a savegame.
void GameLoop()
Run the GameLoop of a script.
int suspend
The amount of ticks to suspend this script before it's allowed to continue.
static void DoCommandReturnLeagueTableID(ScriptInstance *instance)
Return a LeagueTableID reply for a DoCommand.
class ScriptController * controller
The script main class.
static void DoCommandReturnSignID(ScriptInstance *instance)
Return a SignID reply for a DoCommand.
SQInteger GetOpsTillSuspend()
Get the number of operations the script can execute before being suspended.
bool is_started
Is the scripts constructor executed?
static void DoCommandReturnStoryPageID(ScriptInstance *instance)
Return a StoryPageID reply for a DoCommand.
class ScriptStorage * storage
Some global information for each running script.
ScriptInstance(const char *APIName)
Create a new script.
bool is_paused
Is the script paused? (a paused script will not be executed until unpaused)
The storage for each script.
void * event_data
Pointer to the event data storage.
A throw-class that is given when the script made a fatal error.
const std::string & GetErrorMessage() const
The error message associated with the fatal error.
A throw-class that is given when the script wants to suspend.
int GetSuspendTime()
Get the amount of ticks the script should be suspended.
Script_SuspendCallbackProc * GetSuspendCallback()
Get the callback to call when the script can run again.
bool Resume(int suspend=-1)
Resume a VM when it was suspended via a throw.
Definition squirrel.cpp:363
void SetPrintFunction(SQPrintFunc *func)
Set a custom print function, so you can handle outputs from SQ yourself.
Definition squirrel.hpp:231
void CollectGarbage()
Tell the VM to do a garbage collection run.
Definition squirrel.cpp:392
SQInteger GetOpsTillSuspend()
How many operations can we execute till suspension?
Definition squirrel.cpp:807
bool LoadScript(const std::string &script)
Load a script.
Definition squirrel.cpp:723
bool IsSuspended()
Did the squirrel code suspend or return normally.
Definition squirrel.cpp:786
void ReleaseObject(HSQOBJECT *ptr)
Release a SQ object.
Definition squirrel.hpp:241
void ThrowError(const std::string_view error)
Throw a Squirrel error that will be nicely displayed to the user.
Definition squirrel.hpp:236
size_t GetAllocatedMemory() const noexcept
Get number of bytes allocated by this VM.
Definition squirrel.cpp:197
void SetGlobalPointer(void *ptr)
Sets a pointer in the VM that is reachable from where ever you are in SQ.
Definition squirrel.hpp:221
static void DecreaseOps(HSQUIRRELVM vm, int amount)
Tell the VM to remove amount ops from the number of ops till suspend.
Definition squirrel.cpp:781
bool HasScriptCrashed()
Find out if the squirrel script made an error before.
Definition squirrel.cpp:791
void CrashOccurred()
Set the script status to crashed.
Definition squirrel.cpp:796
HSQUIRRELVM GetVM()
Get the squirrel VM.
Definition squirrel.hpp:80
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
void ResumeError()
Resume the VM with an error so it prints a stack trace.
Definition squirrel.cpp:385
bool MethodExists(HSQOBJECT instance, const char *method_name)
Check if a method exists in an instance.
Definition squirrel.cpp:345
bool CreateClassInstance(const std::string &class_name, void *real_instance, HSQOBJECT *instance)
Exactly the same as CreateClassInstanceVM, only callable without instance of Squirrel.
Definition squirrel.cpp:507
std::vector< uint8_t > CommandDataBuffer
Storage buffer for serialized command data.
Commands
List of commands.
@ CMD_END
Must ALWAYS be on the end of this list!! (period)
CompanyID _current_company
Company currently doing an action.
Owner
Enum for all companies/owners.
#define Debug(category, level, format_string,...)
Ouptut a line of debugging information.
Definition debug.h:37
bool FileExists(const std::string &filename)
Test whether the given filename exists.
Definition fileio.cpp:132
Searchpath
Types of searchpaths OpenTTD might use.
Subdirectory
The different kinds of subdirectories OpenTTD uses.
void SlCopy(void *object, size_t length, VarType conv)
Copy a list of SL_VARs to/from a savegame.
void SlErrorCorrupt(const std::string &msg)
Error handler for corrupt savegames.
Definition saveload.cpp:351
void SlObject(void *object, const SaveLoadTable &slt)
Main SaveLoad function.
#define SLEG_VAR(name, variable, type)
Storage of a global variable in every savegame version.
Definition saveload.h:1186
bool IsSavegameVersionBefore(SaveLoadVersion major, uint8_t minor=0)
Checks whether the savegame is below major.
Definition saveload.h:1263
@ SLV_SCRIPT_INT64
296 PR#9415 SQInteger is 64bit but was saved as 32bit.
Definition saveload.h:335
The definition of Script_FatalError.
ScriptInfo keeps track of all information of a script, like Author, Description, ....
static const int MAX_SL_OPS
The maximum number of operations for saving or loading the data of a script.
static const int MAX_CONSTRUCTOR_OPS
The maximum number of operations for initial start of a script.
static uint8_t _script_sl_byte
Used as source/target by the script saveload code to store/load a single byte.
static void PrintFunc(bool error_msg, const std::string &message)
Callback called by squirrel when a script uses "print" and for error messages.
static const SaveLoad _script_byte[]
SaveLoad array that saves/loads exactly one byte.
The ScriptInstance tracks a script.
static const uint SQUIRREL_MAX_DEPTH
The maximum recursive depth for items stored in the savegame.
Defines ScriptStorage and includes all files required for it.
GameSettings _settings_game
Game settings of a running game or the scenario editor.
Definition settings.cpp:57
void squirrel_register_std(Squirrel *engine)
Register all standard functions we want to give to a script.
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
ScriptSettings script
settings for scripts
SaveLoad type struct.
Definition saveload.h:717
uint32_t script_max_opcode_till_suspend
max opcode calls till scripts will suspend