OpenTTD Source: src/script/script_instance.cpp Source File

OpenTTD Source 14.0-beta1

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
35 ScriptStorage::~ScriptStorage()
36 {
37 /* Free our pointers */
38 if (event_data != nullptr) ScriptEventController::FreeEventPointer();
39 }
40
46 static 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
52 ScriptInstance::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);
67 this->engine->SetPrintFunction(&PrintFunc);
68 }
69
70 void 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;
108 this->engine->ThrowError(e.GetErrorMessage());
109 this->engine->ResumeError();
110 this->Died();
111 }
112 }
113
114 void ScriptInstance::RegisterAPI()
115 {
116 squirrel_register_std(this->engine);
117 }
118
119 bool 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
138 ScriptInstance::~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
150 void ScriptInstance::Continue()
151 {
152 assert(this->suspend < 0);
153 this->suspend = -this->suspend - 1;
154 }
155
156 void ScriptInstance::Died()
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
171 void ScriptInstance::GameLoop()
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 {
248 if (!this->engine->Resume(_settings_game.script.script_max_opcode_till_suspend)) this->Died();
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
260 void ScriptInstance::CollectGarbage()
261 {
262 if (this->is_started && !this->IsDead()) {
263 ScriptObject::ActiveInstance active(this);
264 this->engine->CollectGarbage();
265 }
266 }
267
268 /* static */ void ScriptInstance::DoCommandReturn(ScriptInstance *instance)
269 {
270 instance->engine->InsertResult(ScriptObject::GetLastCommandRes());
271 }
272
273 /* static */ void ScriptInstance::DoCommandReturnVehicleID(ScriptInstance *instance)
274 {
275 instance->engine->InsertResult(EndianBufferReader::ToValue<VehicleID>(ScriptObject::GetLastCommandResData()));
276 }
277
278 /* static */ void ScriptInstance::DoCommandReturnSignID(ScriptInstance *instance)
279 {
280 instance->engine->InsertResult(EndianBufferReader::ToValue<SignID>(ScriptObject::GetLastCommandResData()));
281 }
282
283 /* static */ void ScriptInstance::DoCommandReturnGroupID(ScriptInstance *instance)
284 {
285 instance->engine->InsertResult(EndianBufferReader::ToValue<GroupID>(ScriptObject::GetLastCommandResData()));
286 }
287
288 /* static */ void ScriptInstance::DoCommandReturnGoalID(ScriptInstance *instance)
289 {
290 instance->engine->InsertResult(EndianBufferReader::ToValue<GoalID>(ScriptObject::GetLastCommandResData()));
291 }
292
293 /* static */ void ScriptInstance::DoCommandReturnStoryPageID(ScriptInstance *instance)
294 {
295 instance->engine->InsertResult(EndianBufferReader::ToValue<StoryPageID>(ScriptObject::GetLastCommandResData()));
296 }
297
298 /* static */ void ScriptInstance::DoCommandReturnStoryPageElementID(ScriptInstance *instance)
299 {
300 instance->engine->InsertResult(EndianBufferReader::ToValue<StoryPageElementID>(ScriptObject::GetLastCommandResData()));
301 }
302
303 /* static */ void ScriptInstance::DoCommandReturnLeagueTableElementID(ScriptInstance *instance)
304 {
305 instance->engine->InsertResult(EndianBufferReader::ToValue<LeagueTableElementID>(ScriptObject::GetLastCommandResData()));
306 }
307
308 /* static */ void ScriptInstance::DoCommandReturnLeagueTableID(ScriptInstance *instance)
309 {
310 instance->engine->InsertResult(EndianBufferReader::ToValue<LeagueTableID>(ScriptObject::GetLastCommandResData()));
311 }
312
313
314 ScriptStorage *ScriptInstance::GetStorage()
315 {
316 return this->storage;
317 }
318
319 ScriptLogTypes::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
347 static byte _script_sl_byte;
348
350 static 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) {
364 _script_sl_byte = SQSL_INT;
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) {
378 _script_sl_byte = SQSL_STRING;
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 = (byte)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) {
398 _script_sl_byte = SQSL_ARRAY;
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) {
413 _script_sl_byte = SQSL_ARRAY_TABLE_END;
414 SlObject(nullptr, _script_byte);
415 }
416 return true;
417 }
418
419 case OT_TABLE: {
420 if (!test) {
421 _script_sl_byte = SQSL_TABLE;
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) {
436 _script_sl_byte = SQSL_ARRAY_TABLE_END;
437 SlObject(nullptr, _script_byte);
438 }
439 return true;
440 }
441
442 case OT_BOOL: {
443 if (!test) {
444 _script_sl_byte = SQSL_BOOL;
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) {
458 _script_sl_byte = SQSL_NULL;
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
476 void ScriptInstance::Save()
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;
512 this->engine->ThrowError(e.GetErrorMessage());
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
546 void ScriptInstance::Pause()
547 {
548 /* Suspend script. */
549 HSQUIRRELVM vm = this->engine->GetVM();
550 Squirrel::DecreaseOps(vm, _settings_game.script.script_max_opcode_till_suspend);
551
552 this->is_paused = true;
553 }
554
555 void ScriptInstance::Unpause()
556 {
557 this->is_paused = false;
558 }
559
560 bool ScriptInstance::IsPaused()
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
602 case SQSL_ARRAY_TABLE_END: {
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 if (std::holds_alternative<SQInteger>(value)) {
617 sq_pushinteger(vm, std::get<SQInteger>(value));
618 return true;
619 }
620
621 if (std::holds_alternative<std::string>(value)) {
622 sq_pushstring(vm, std::get<std::string>(value), -1);
623 return true;
624 }
625
626 if (std::holds_alternative<SQBool>(value)) {
627 sq_pushbool(vm, std::get<SQBool>(value));
628 return true;
629 }
630
631 switch (std::get<SQSaveLoadType>(value)) {
632 case SQSL_ARRAY: {
633 sq_newarray(vm, 0);
634 while (LoadObjects(vm, data)) {
635 sq_arrayappend(vm, -2);
636 /* The value is popped from the stack by squirrel. */
637 }
638 return true;
639 }
640
641 case SQSL_TABLE: {
642 sq_newtable(vm);
643 while (LoadObjects(vm, data)) {
644 LoadObjects(vm, data);
645 sq_rawset(vm, -3);
646 /* The key (-2) and value (-1) are popped from the stack by squirrel. */
647 }
648 return true;
649 }
650
651 case SQSL_NULL: {
652 sq_pushnull(vm);
653 return true;
654 }
655
656 case SQSL_ARRAY_TABLE_END: {
657 return false;
658 }
659
660 default: NOT_REACHED();
661 }
662 }
663
664 /* static */ void ScriptInstance::LoadEmpty()
665 {
666 SlObject(nullptr, _script_byte);
667 /* Check if there was anything saved at all. */
668 if (_script_sl_byte == 0) return;
669
670 LoadObjects(nullptr);
671 }
672
673 /* static */ ScriptInstance::ScriptData *ScriptInstance::Load(int version)
674 {
675 if (version == -1) {
676 LoadEmpty();
677 return nullptr;
678 }
679
680 SlObject(nullptr, _script_byte);
681 /* Check if there was anything saved at all. */
682 if (_script_sl_byte == 0) return nullptr;
683
684 ScriptData *data = new ScriptData();
685 data->push_back((SQInteger)version);
686 LoadObjects(data);
687 return data;
688 }
689
690 void ScriptInstance::LoadOnStack(ScriptData *data)
691 {
692 ScriptObject::ActiveInstance active(this);
693
694 if (this->IsDead() || data == nullptr) return;
695
696 HSQUIRRELVM vm = this->engine->GetVM();
697
698 ScriptDataVariant version = data->front();
699 data->pop_front();
700 SQInteger top = sq_gettop(vm);
701 try {
702 sq_pushinteger(vm, std::get<SQInteger>(version));
703 LoadObjects(vm, data);
704 this->is_save_data_on_stack = true;
705 } catch (Script_FatalError &e) {
706 ScriptLog::Warning(fmt::format("Loading failed: {}", e.GetErrorMessage()));
707 /* Discard partially loaded savegame data and version. */
708 sq_settop(vm, top);
709 }
710 }
711
712 bool ScriptInstance::CallLoad()
713 {
714 HSQUIRRELVM vm = this->engine->GetVM();
715 /* Is there save data that we should load? */
716 if (!this->is_save_data_on_stack) return true;
717 /* Whatever happens, after CallLoad the savegame data is removed from the stack. */
718 this->is_save_data_on_stack = false;
719
720 if (!this->engine->MethodExists(*this->instance, "Load")) {
721 ScriptLog::Warning("Loading failed: there was data for the script to load, but the script does not have a Load() function.");
722
723 /* Pop the savegame data and version. */
724 sq_pop(vm, 2);
725 return true;
726 }
727
728 /* Go to the instance-root */
729 sq_pushobject(vm, *this->instance);
730 /* Find the function-name inside the script */
731 sq_pushstring(vm, "Load", -1);
732 /* Change the "Load" string in a function pointer */
733 sq_get(vm, -2);
734 /* Push the main instance as "this" object */
735 sq_pushobject(vm, *this->instance);
736 /* Push the version data and savegame data as arguments */
737 sq_push(vm, -5);
738 sq_push(vm, -5);
739
740 /* Call the script load function. sq_call removes the arguments (but not the
741 * function pointer) from the stack. */
742 if (SQ_FAILED(sq_call(vm, 3, SQFalse, SQFalse, MAX_SL_OPS))) return false;
743
744 /* Pop 1) The version, 2) the savegame data, 3) the object instance, 4) the function pointer. */
745 sq_pop(vm, 4);
746 return true;
747 }
748
749 SQInteger ScriptInstance::GetOpsTillSuspend()
750 {
751 return this->engine->GetOpsTillSuspend();
752 }
753
754 bool ScriptInstance::DoCommandCallback(const CommandCost &result, const CommandDataBuffer &data, CommandDataBuffer result_data, Commands cmd)
755 {
756 ScriptObject::ActiveInstance active(this);
757
758 if (!ScriptObject::CheckLastCommand(data, cmd)) {
759 Debug(script, 1, "DoCommandCallback terminating a script, last command does not match expected command");
760 return false;
761 }
762
763 ScriptObject::SetLastCommandRes(result.Succeeded());
764 ScriptObject::SetLastCommandResData(std::move(result_data));
765
766 if (result.Failed()) {
767 ScriptObject::SetLastError(ScriptError::StringToError(result.GetErrorMessage()));
768 } else {
769 ScriptObject::IncreaseDoCommandCosts(result.GetCost());
770 ScriptObject::SetLastCost(result.GetCost());
771 }
772
773 ScriptObject::SetLastCommand({}, CMD_END);
774
775 return true;
776 }
777
778 void ScriptInstance::InsertEvent(class ScriptEvent *event)
779 {
780 ScriptObject::ActiveInstance active(this);
781
782 ScriptEventController::InsertEvent(event);
783 }
784
785 size_t ScriptInstance::GetAllocatedMemory() const
786 {
787 if (this->engine == nullptr) return this->last_allocated_memory;
788 return this->engine->GetAllocatedMemory();
789 }
790
791 void ScriptInstance::ReleaseSQObject(HSQOBJECT *obj)
792 {
793 if (!this->in_shutdown) this->engine->ReleaseObject(obj);
794 }
ScriptInstance::SQSL_ARRAY_TABLE_END
@ SQSL_ARRAY_TABLE_END
Marks the end of an array or table, no data follows.
Definition: script_instance.hpp:35
ScriptInstance::LoadDummyScript
virtual void LoadDummyScript()=0
Load the dummy script.
Script_Suspend
A throw-class that is given when the script wants to suspend.
Definition: script_suspend.hpp:21
ScriptInstance::SaveObject
static bool SaveObject(HSQUIRRELVM vm, SQInteger index, int max_depth, bool test)
Save one object (int / string / array / table) to the savegame.
Definition: script_instance.cpp:354
ScriptInstance::engine
class Squirrel * engine
A wrapper around the squirrel vm.
Definition: script_instance.hpp:254
_script_sl_byte
static byte _script_sl_byte
Used as source/target by the script saveload code to store/load a single byte.
Definition: script_instance.cpp:347
ScriptInstance::last_allocated_memory
size_t last_allocated_memory
Last known allocated memory value (for display for crashed scripts)
Definition: script_instance.hpp:297
ScriptStorage
The storage for each script.
Definition: script_storage.hpp:37
ScriptInstance::CallLoad
bool CallLoad()
Call the script Load function if it exists and data was loaded from a savegame.
Definition: script_instance.cpp:712
ScriptInstance::is_dead
bool is_dead
True if the script has been stopped.
Definition: script_instance.hpp:291
ScriptInstance::InsertEvent
void InsertEvent(class ScriptEvent *event)
Insert an event for this script.
Definition: script_instance.cpp:778
ScriptInstance::GameLoop
void GameLoop()
Run the GameLoop of a script.
Definition: script_instance.cpp:171
Squirrel::SetPrintFunction
void SetPrintFunction(SQPrintFunc *func)
Set a custom print function, so you can handle outputs from SQ yourself.
Definition: squirrel.hpp:231
Script_Suspend::GetSuspendTime
int GetSuspendTime()
Get the amount of ticks the script should be suspended.
Definition: script_suspend.hpp:37
ScriptInstance::controller
class ScriptController * controller
The script main class.
Definition: script_instance.hpp:286
ScriptInstance::Save
void Save()
Call the script Save function and save all data in the savegame.
Definition: script_instance.cpp:476
CommandDataBuffer
std::vector< byte > CommandDataBuffer
Storage buffer for serialized command data.
Definition: command_type.h:465
ScriptScanner::engine
class Squirrel * engine
The engine we're scanning with.
Definition: script_scanner.hpp:87
SlCopy
void SlCopy(void *object, size_t length, VarType conv)
Copy a list of SL_VARs to/from a savegame.
Definition: saveload.cpp:1018
ScriptInstance
Runtime information about a script like a pointer to the squirrel vm and the current state.
Definition: script_instance.hpp:25
ScriptInstance::ScriptInstance
ScriptInstance(const char *APIName)
Create a new script.
Definition: script_instance.cpp:52
ScriptInstance::Unpause
void Unpause()
Resume execution of the script.
Definition: script_instance.cpp:555
ScriptInstance::DoCommandReturnStoryPageElementID
static void DoCommandReturnStoryPageElementID(ScriptInstance *instance)
Return a StoryPageElementID reply for a DoCommand.
Definition: script_instance.cpp:298
SlErrorCorrupt
void SlErrorCorrupt(const std::string &msg)
Error handler for corrupt savegames.
Definition: saveload.cpp:362
Searchpath
Searchpath
Types of searchpaths OpenTTD might use.
Definition: fileio_type.h:132
ScriptInstance::instance
SQObject * instance
Squirrel-pointer to the script main class.
Definition: script_instance.hpp:288
StrMakeValid
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:114
ScriptInstance::SQSL_STRING
@ SQSL_STRING
The following data is an string.
Definition: script_instance.hpp:30
ScriptInstance::is_save_data_on_stack
bool is_save_data_on_stack
Is the save data still on the squirrel stack?
Definition: script_instance.hpp:292
Squirrel::GetVM
HSQUIRRELVM GetVM()
Get the squirrel VM.
Definition: squirrel.hpp:80
ScriptInstance::is_paused
bool is_paused
Is the script paused? (a paused script will not be executed until unpaused)
Definition: script_instance.hpp:294
ScriptInstance::GetLogData
ScriptLogTypes::LogData & GetLogData()
Get the log pointer of this script.
Definition: script_instance.cpp:319
ScriptInstance::DoCommandReturnLeagueTableElementID
static void DoCommandReturnLeagueTableElementID(ScriptInstance *instance)
Return a LeagueTableElementID reply for a DoCommand.
Definition: script_instance.cpp:303
Script_FatalError::GetErrorMessage
const std::string & GetErrorMessage() const
The error message associated with the fatal error.
Definition: script_fatalerror.hpp:30
Owner
Owner
Enum for all companies/owners.
Definition: company_type.h:18
ScriptInstance::DoCommandReturnLeagueTableID
static void DoCommandReturnLeagueTableID(ScriptInstance *instance)
Return a LeagueTableID reply for a DoCommand.
Definition: script_instance.cpp:308
CommandCost::GetErrorMessage
StringID GetErrorMessage() const
Returns the error message of a command.
Definition: command_type.h:142
Debug
#define Debug(category, level, format_string,...)
Ouptut a line of debugging information.
Definition: debug.h:37
CommandCost::Succeeded
bool Succeeded() const
Did this command succeed?
Definition: command_type.h:162
ScriptInstance::GetOpsTillSuspend
SQInteger GetOpsTillSuspend()
Get the number of operations the script can execute before being suspended.
Definition: script_instance.cpp:749
ScriptInstance::Pause
void Pause()
Suspends the script for the current tick and then pause the execution of script.
Definition: script_instance.cpp:546
ScriptInstance::DoCommandReturnGoalID
static void DoCommandReturnGoalID(ScriptInstance *instance)
Return a GoalID reply for a DoCommand.
Definition: script_instance.cpp:288
ScriptInstance::ReleaseSQObject
void ReleaseSQObject(HSQOBJECT *obj)
Decrease the ref count of a squirrel object.
Definition: script_instance.cpp:791
ScriptInstance::storage
class ScriptStorage * storage
Some global information for each running script.
Definition: script_instance.hpp:287
Squirrel::GetOpsTillSuspend
SQInteger GetOpsTillSuspend()
How many operations can we execute till suspension?
Definition: squirrel.cpp:815
Squirrel
Definition: squirrel.hpp:23
Squirrel::HasScriptCrashed
bool HasScriptCrashed()
Find out if the squirrel script made an error before.
Definition: squirrel.cpp:799
ScriptInstance::DoCommandReturnSignID
static void DoCommandReturnSignID(ScriptInstance *instance)
Return a SignID reply for a DoCommand.
Definition: script_instance.cpp:278
CommandCost
Common return value for all commands.
Definition: command_type.h:23
ScriptInstance::in_shutdown
bool in_shutdown
Is this instance currently being destructed?
Definition: script_instance.hpp:295
script_storage.hpp
_script_byte
static const SaveLoad _script_byte[]
SaveLoad array that saves/loads exactly one byte.
Definition: script_instance.cpp:350
IsSavegameVersionBefore
bool IsSavegameVersionBefore(SaveLoadVersion major, byte minor=0)
Checks whether the savegame is below major.
Definition: saveload.h:1203
GameSettings::script
ScriptSettings script
settings for scripts
Definition: settings_type.h:622
ScriptInstance::DoCommandReturn
static void DoCommandReturn(ScriptInstance *instance)
Return a true/false reply for a DoCommand.
Definition: script_instance.cpp:268
ScriptInstance::SaveEmpty
static void SaveEmpty()
Don't save any data in the savegame.
Definition: script_instance.cpp:470
ScriptInstance::LoadObjects
static bool LoadObjects(ScriptData *data)
Load all objects from a savegame.
Definition: script_instance.cpp:565
FileExists
bool FileExists(const std::string &filename)
Test whether the given filename exists.
Definition: fileio.cpp:140
ScriptInstance::Load
static ScriptData * Load(int version)
Load data from a savegame.
Definition: script_instance.cpp:673
SLEG_VAR
#define SLEG_VAR(name, variable, type)
Storage of a global variable in every savegame version.
Definition: saveload.h:1124
Squirrel::SetGlobalPointer
void SetGlobalPointer(void *ptr)
Sets a pointer in the VM that is reachable from where ever you are in SQ.
Definition: squirrel.hpp:221
CommandCost::Failed
bool Failed() const
Did this command fail?
Definition: command_type.h:171
PrintFunc
static void PrintFunc(bool error_msg, const std::string &message)
Callback called by squirrel when a script uses "print" and for error messages.
Definition: script_instance.cpp:46
ScriptInstance::IsDead
bool IsDead() const
Return the "this script died" value.
Definition: script_instance.hpp:153
ScriptStorage::event_data
void * event_data
Pointer to the event data storage.
Definition: script_storage.hpp:65
Squirrel::GetAllocatedMemory
size_t GetAllocatedMemory() const noexcept
Get number of bytes allocated by this VM.
Definition: squirrel.cpp:197
_settings_game
GameSettings _settings_game
Game settings of a running game or the scenario editor.
Definition: settings.cpp:55
ScriptInstance::SQSL_TABLE
@ SQSL_TABLE
The following data is an table.
Definition: script_instance.hpp:32
CommandCost::GetCost
Money GetCost() const
The costs as made up to this moment.
Definition: command_type.h:83
ScriptInstance::is_started
bool is_started
Is the scripts constructor executed?
Definition: script_instance.hpp:290
Squirrel::ThrowError
void ThrowError(const std::string_view error)
Throw a Squirrel error that will be nicely displayed to the user.
Definition: squirrel.hpp:236
ScriptInstance::DoCommandReturnStoryPageID
static void DoCommandReturnStoryPageID(ScriptInstance *instance)
Return a StoryPageID reply for a DoCommand.
Definition: script_instance.cpp:293
ScriptInstance::RegisterAPI
virtual void RegisterAPI()
Register all API functions to the VM.
Definition: script_instance.cpp:114
Squirrel::CallMethod
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
ScriptScanner::main_script
std::string main_script
The full path of the script.
Definition: script_scanner.hpp:88
ScriptInstance::LoadEmpty
static void LoadEmpty()
Load and discard data from a savegame.
Definition: script_instance.cpp:664
ScriptInstance::DoCommandReturnGroupID
static void DoCommandReturnGroupID(ScriptInstance *instance)
Return a GroupID reply for a DoCommand.
Definition: script_instance.cpp:283
Script_Suspend::GetSuspendCallback
Script_SuspendCallbackProc * GetSuspendCallback()
Get the callback to call when the script can run again.
Definition: script_suspend.hpp:43
squirrel_register_std
void squirrel_register_std(Squirrel *engine)
Register all standard functions we want to give to a script.
Definition: squirrel_std.cpp:97
Squirrel::ResumeError
void ResumeError()
Resume the VM with an error so it prints a stack trace.
Definition: squirrel.cpp:385
Squirrel::LoadScript
bool LoadScript(const std::string &script)
Load a script.
Definition: squirrel.cpp:731
_current_company
CompanyID _current_company
Company currently doing an action.
Definition: company_cmd.cpp:50
ScriptInstance::LoadCompatibilityScripts
bool LoadCompatibilityScripts(const std::string &api_version, Subdirectory dir)
Load squirrel scripts to emulate an older API.
Definition: script_instance.cpp:119
MAX_CONSTRUCTOR_OPS
static const int MAX_CONSTRUCTOR_OPS
The maximum number of operations for initial start of a script.
Definition: script_info.hpp:21
Squirrel::CollectGarbage
void CollectGarbage()
Tell the VM to do a garbage collection run.
Definition: squirrel.cpp:392
ScriptInstance::callback
Script_SuspendCallbackProc * callback
Callback that should be called in the next tick the script runs.
Definition: script_instance.hpp:296
Squirrel::MethodExists
bool MethodExists(HSQOBJECT instance, const char *method_name)
Check if a method exists in an instance.
Definition: squirrel.cpp:345
SLV_SCRIPT_INT64
@ SLV_SCRIPT_INT64
296 PR#9415 SQInteger is 64bit but was saved as 32bit.
Definition: saveload.h:335
CMD_END
@ CMD_END
Must ALWAYS be on the end of this list!! (period)
Definition: command_type.h:361
ScriptInstance::SQSL_ARRAY
@ SQSL_ARRAY
The following data is an array.
Definition: script_instance.hpp:31
ScriptInstance::DoCommandCallback
bool DoCommandCallback(const CommandCost &result, const CommandDataBuffer &data, CommandDataBuffer result_data, Commands cmd)
DoCommand callback function for all commands executed by scripts.
Definition: script_instance.cpp:754
Squirrel::CrashOccurred
void CrashOccurred()
Set the script status to crashed.
Definition: squirrel.cpp:804
ScriptInstance::SQSL_NULL
@ SQSL_NULL
A null variable.
Definition: script_instance.hpp:34
ScriptInstance::SQSL_BOOL
@ SQSL_BOOL
The following data is a boolean.
Definition: script_instance.hpp:33
script_info.hpp
ScriptInstance::Initialize
void Initialize(const std::string &main_script, const std::string &instance_name, CompanyID company)
Initialize the script and prepare it for its first run.
Definition: script_instance.cpp:70
ScriptInstance::DoCommandReturnVehicleID
static void DoCommandReturnVehicleID(ScriptInstance *instance)
Return a VehicleID reply for a DoCommand.
Definition: script_instance.cpp:273
ScriptInstance::GetStorage
class ScriptStorage * GetStorage()
Get the storage of this script.
Definition: script_instance.cpp:314
ScriptInstance::LoadOnStack
void LoadOnStack(ScriptData *data)
Store loaded data on the stack.
Definition: script_instance.cpp:690
script_instance.hpp
ScriptInstance::SQSaveLoadType
SQSaveLoadType
The type of the data that follows in the savegame.
Definition: script_instance.hpp:28
Subdirectory
Subdirectory
The different kinds of subdirectories OpenTTD uses.
Definition: fileio_type.h:108
ScriptInstance::Died
virtual void Died()
Tell the script it died.
Definition: script_instance.cpp:156
ScriptInstance::suspend
int suspend
The amount of ticks to suspend this script before it's allowed to continue.
Definition: script_instance.hpp:293
Squirrel::Resume
bool Resume(int suspend=-1)
Resume a VM when it was suspended via a throw.
Definition: squirrel.cpp:363
Script_FatalError
A throw-class that is given when the script made a fatal error.
Definition: script_fatalerror.hpp:16
ScriptInstance::Continue
void Continue()
A script in multiplayer waits for the server to handle its DoCommand.
Definition: script_instance.cpp:150
script_fatalerror.hpp
Squirrel::ReleaseObject
void ReleaseObject(HSQOBJECT *ptr)
Release a SQ object.
Definition: squirrel.hpp:241
ScriptInstance::SQSL_INT
@ SQSL_INT
The following data is an integer.
Definition: script_instance.hpp:29
ScriptInstance::IsPaused
bool IsPaused()
Checks if the script is paused.
Definition: script_instance.cpp:560
Squirrel::IsSuspended
bool IsSuspended()
Did the squirrel code suspend or return normally.
Definition: squirrel.cpp:794
ScriptInstance::CollectGarbage
void CollectGarbage()
Let the VM collect any garbage.
Definition: script_instance.cpp:260
Commands
Commands
List of commands.
Definition: command_type.h:187
SlObject
void SlObject(void *object, const SaveLoadTable &slt)
Main SaveLoad function.
Definition: saveload.cpp:1652
ScriptSettings::script_max_opcode_till_suspend
uint32_t script_max_opcode_till_suspend
max opcode calls till scripts will suspend
Definition: settings_type.h:408
SaveLoad
SaveLoad type struct.
Definition: saveload.h:694
Squirrel::DecreaseOps
static void DecreaseOps(HSQUIRRELVM vm, int amount)
Tell the VM to remove amount ops from the number of ops till suspend.
Definition: squirrel.cpp:789
Squirrel::CreateClassInstance
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
SQUIRREL_MAX_DEPTH
static const uint SQUIRREL_MAX_DEPTH
The maximum recursive depth for items stored in the savegame.
Definition: script_instance.hpp:22
MAX_SL_OPS
static const int MAX_SL_OPS
The maximum number of operations for saving or loading the data of a script.
Definition: script_info.hpp:19