12#include "../saveload/saveload.h"
14#include "../script/squirrel_class.hpp"
15#include "../script/squirrel_std.hpp"
22#include "api/script_controller.hpp"
23#include "api/script_error.hpp"
24#include "api/script_event.hpp"
25#include "api/script_log.hpp"
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"
33#include "../safeguards.h"
35ScriptStorage::~ScriptStorage()
38 if (
event_data !=
nullptr) ScriptEventController::FreeEventPointer();
46static void PrintFunc(
bool error_msg,
const std::string &message)
49 ScriptController::Print(error_msg, message);
61 ScriptObject::ActiveInstance active(
this);
63 this->
controller =
new ScriptController(company);
74 ScriptObject::SetAllowDoCommand(
false);
76 if (main_script ==
"%_dummy") {
78 }
else if (!this->
engine->
LoadScript(main_script) || this->engine->IsSuspended()) {
79 if (this->
engine->
IsSuspended()) ScriptLog::Error(
"This script took too long to load script. AI is not started.");
94 ScriptObject::SetAllowDoCommand(
true);
110 std::string script_name = fmt::format(
"compat_{}.nut", api_version);
113 std::string buf = FioGetDirectory(sp, dir);
119 ScriptLog::Error(fmt::format(
"Failed to load API compatibility script for {}", api_version));
120 Debug(script, 0,
"Error compiling / running API compatibility script: {}", buf);
124 ScriptLog::Warning(fmt::format(
"API compatibility script for {} not found", api_version));
131 if (this->
versionAPI == std::rbegin(api_versions)->data())
return true;
133 ScriptLog::Info(fmt::format(
"Downgrading API to be compatible with version {}", this->
versionAPI));
137 for (
auto it = std::rbegin(api_versions) + 1; it != std::rend(api_versions); ++it) {
146ScriptInstance::~ScriptInstance()
148 ScriptObject::ActiveInstance active(
this);
166 Debug(script, 0,
"The script died unexpectedly.");
181 ScriptObject::ActiveInstance active(
this);
183 if (this->
IsDead())
return;
194 if (--this->
suspend > 0)
return;
219 ScriptObject::SetAllowDoCommand(
false);
223 if (this->
engine->
IsSuspended()) ScriptLog::Error(
"This script took too long to initialize. Script is not started.");
229 if (this->
engine->
IsSuspended()) ScriptLog::Error(
"This script took too long in the Load function. Script is not started.");
233 ScriptObject::SetAllowDoCommand(
true);
271 ScriptObject::ActiveInstance active(
this);
278 instance->engine->InsertResult(ScriptObject::GetLastCommandRes());
283 instance->engine->InsertResult(EndianBufferReader::ToValue<VehicleID>(ScriptObject::GetLastCommandResData()));
288 instance->engine->InsertResult(EndianBufferReader::ToValue<SignID>(ScriptObject::GetLastCommandResData()));
293 instance->engine->InsertResult(EndianBufferReader::ToValue<GroupID>(ScriptObject::GetLastCommandResData()));
298 instance->engine->InsertResult(EndianBufferReader::ToValue<GoalID>(ScriptObject::GetLastCommandResData()));
303 instance->engine->InsertResult(EndianBufferReader::ToValue<StoryPageID>(ScriptObject::GetLastCommandResData()));
308 instance->engine->InsertResult(EndianBufferReader::ToValue<StoryPageElementID>(ScriptObject::GetLastCommandResData()));
313 instance->engine->InsertResult(EndianBufferReader::ToValue<LeagueTableElementID>(ScriptObject::GetLastCommandResData()));
318 instance->engine->InsertResult(EndianBufferReader::ToValue<LeagueTableID>(ScriptObject::GetLastCommandResData()));
329 ScriptObject::ActiveInstance active(
this);
331 return ScriptObject::GetLogData();
364 if (max_depth == 0) {
365 ScriptLog::Error(
"Savedata can only be nested to 25 deep. No data saved.");
369 switch (sq_gettype(vm, index)) {
376 sq_getinteger(vm, index, &res);
378 int64_t value = (int64_t)res;
379 SlCopy(&value, 1, SLE_INT64);
390 sq_getstring(vm, index, &buf);
391 size_t len = strlen(buf) + 1;
393 ScriptLog::Error(
"Maximum string length is 254 chars. No data saved.");
399 SlCopy(
const_cast<char *
>(buf), len, SLE_CHAR);
410 while (SQ_SUCCEEDED(sq_next(vm, index - 1))) {
412 bool res =
SaveObject(vm, -1, max_depth - 1, test);
433 while (SQ_SUCCEEDED(sq_next(vm, index - 1))) {
456 sq_getbool(vm, index, &res);
477 SQInteger top = sq_gettop(vm);
480 if (!obj->SaveObject(vm))
throw std::exception();
481 if (sq_gettop(vm) != top + 2)
throw std::exception();
482 if (sq_gettype(vm, -2) != OT_STRING || !
SaveObject(vm, -2, max_depth - 1, test))
throw std::exception();
483 if (!
SaveObject(vm, -1, max_depth - 1, test))
throw std::exception();
487 ScriptLog::Error(
"You tried to save an unsupported type. No data saved.");
494 ScriptLog::Error(
"You tried to save an unsupported type. No data saved.");
507 ScriptObject::ActiveInstance active(
this);
527 bool backup_allow = ScriptObject::GetAllowDoCommand();
528 ScriptObject::SetAllowDoCommand(
false);
550 ScriptObject::SetAllowDoCommand(backup_allow);
552 if (!sq_istable(savedata)) {
553 ScriptLog::Error(this->
engine->
IsSuspended() ?
"This script took too long to Save." :
"Save function should return a table.");
558 sq_pushobject(vm, savedata);
569 ScriptLog::Warning(
"Save function is not implemented");
601 if (data !=
nullptr) data->push_back(
static_cast<SQInteger
>(value));
607 static char buf[std::numeric_limits<
decltype(
_script_sl_byte)>::max()];
622 if (data !=
nullptr) data->push_back(
static_cast<SQBool
>(
_script_sl_byte != 0));
647 ScriptDataVariant value = data->front();
654 bool operator()(
const SQInteger &value) { sq_pushinteger(this->vm, value);
return true; }
655 bool operator()(
const std::string &value) { sq_pushstring(this->vm, value, -1);
return true; }
656 bool operator()(
const SQBool &value) { sq_pushbool(this->vm, value);
return true; }
657 bool operator()(
const SQSaveLoadType &type)
661 sq_newarray(this->vm, 0);
662 while (LoadObjects(this->vm, this->data)) {
663 sq_arrayappend(this->vm, -2);
669 sq_newtable(this->vm);
670 while (LoadObjects(this->vm, this->data)) {
671 LoadObjects(this->vm, this->data);
672 sq_rawset(this->vm, -3);
678 sq_pushnull(this->vm);
681 case SQSL_INSTANCE: {
682 SQInteger top = sq_gettop(this->vm);
683 LoadObjects(this->vm, this->data);
685 sq_getstring(this->vm, -1, &buf);
687 std::string class_name = fmt::format(
"{}{}", engine->
GetAPIName(), buf);
688 sq_pushroottable(this->vm);
689 sq_pushstring(this->vm, class_name);
690 if (SQ_FAILED(sq_get(this->vm, -2)))
throw Script_FatalError(fmt::format(
"'{}' doesn't exist", class_name));
691 sq_pushroottable(vm);
692 if (SQ_FAILED(sq_call(this->vm, 1, SQTrue, SQFalse)))
throw Script_FatalError(fmt::format(
"Failed to instantiate '{}'", class_name));
694 sq_getstackobj(vm, -1, &res);
696 sq_settop(this->vm, top);
697 sq_pushobject(vm, res);
698 sq_release(vm, &res);
700 LoadObjects(this->vm, this->data);
701 if (!obj->LoadObject(vm))
throw Script_FatalError(fmt::format(
"Failed to load '{}'", class_name));
706 case SQSL_ARRAY_TABLE_END:
709 default: NOT_REACHED();
714 return std::visit(visitor{vm, data}, value);
737 ScriptData *data =
new ScriptData();
738 data->push_back((SQInteger)version);
745 ScriptObject::ActiveInstance active(
this);
747 if (this->
IsDead() || data ==
nullptr)
return;
749 HSQUIRRELVM vm = this->engine->
GetVM();
751 ScriptDataVariant version = data->front();
753 SQInteger top = sq_gettop(vm);
755 sq_pushinteger(vm, std::get<SQInteger>(version));
759 ScriptLog::Warning(fmt::format(
"Loading failed: {}", e.
GetErrorMessage()));
767 HSQUIRRELVM vm = this->engine->
GetVM();
773 if (!this->engine->
MethodExists(*this->instance,
"Load")) {
774 ScriptLog::Warning(
"Loading failed: there was data for the script to load, but the script does not have a Load() function.");
784 sq_pushstring(vm,
"Load", -1);
795 if (SQ_FAILED(sq_call(vm, 3, SQFalse, SQTrue,
MAX_SL_OPS)))
return false;
809 ScriptObject::ActiveInstance active(
this);
811 if (!ScriptObject::CheckLastCommand(data, cmd)) {
812 Debug(script, 1,
"DoCommandCallback terminating a script, last command does not match expected command");
816 ScriptObject::SetLastCommandRes(result.
Succeeded());
817 ScriptObject::SetLastCommandResData(std::move(result_data));
820 ScriptObject::SetLastError(ScriptError::StringToError(result.
GetErrorMessage()));
822 ScriptObject::IncreaseDoCommandCosts(result.
GetCost());
823 ScriptObject::SetLastCost(result.
GetCost());
826 ScriptObject::SetLastCommand({},
CMD_END);
833 ScriptObject::ActiveInstance active(
this);
835 ScriptEventController::InsertEvent(event);
838size_t ScriptInstance::GetAllocatedMemory()
const
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.
bool LoadCompatibilityScripts(Subdirectory dir, std::span< const std::string_view > api_versions)
Load squirrel scripts to emulate an older API.
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.
@ SQSL_INSTANCE
The following data is an instance.
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.
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.
std::string versionAPI
Current API used by this script.
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.
bool LoadCompatibilityScript(std::string_view api_version, Subdirectory dir)
Load squirrel script for a specific version to emulate an older API.
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.
void SetPrintFunction(SQPrintFunc *func)
Set a custom print function, so you can handle outputs from SQ yourself.
void CollectGarbage()
Tell the VM to do a garbage collection run.
SQInteger GetOpsTillSuspend()
How many operations can we execute till suspension?
bool LoadScript(const std::string &script)
Load a script.
bool IsSuspended()
Did the squirrel code suspend or return normally.
void ReleaseObject(HSQOBJECT *ptr)
Release a SQ object.
void ThrowError(const std::string_view error)
Throw a Squirrel error that will be nicely displayed to the user.
size_t GetAllocatedMemory() const noexcept
Get number of bytes allocated by this VM.
void SetGlobalPointer(void *ptr)
Sets a pointer in the VM that is reachable from where ever you are in SQ.
static void DecreaseOps(HSQUIRRELVM vm, int amount)
Tell the VM to remove amount ops from the number of ops till suspend.
bool HasScriptCrashed()
Find out if the squirrel script made an error before.
void CrashOccurred()
Set the script status to crashed.
HSQUIRRELVM GetVM()
Get the squirrel VM.
static SQUserPointer GetRealInstance(HSQUIRRELVM vm, int index, const char *tag)
Get the real-instance pointer.
bool CallMethod(HSQOBJECT instance, const char *method_name, HSQOBJECT *ret, int suspend)
Call a method of an instance, in various flavors.
const char * GetAPIName()
Get the API name.
void ResumeError()
Resume the VM with an error so it prints a stack trace.
bool MethodExists(HSQOBJECT instance, const char *method_name)
Check if a method exists in an instance.
bool CreateClassInstance(const std::string &class_name, void *real_instance, HSQOBJECT *instance)
Exactly the same as CreateClassInstanceVM, only callable without instance of Squirrel.
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.
#define Debug(category, level, format_string,...)
Output a line of debugging information.
bool FileExists(const std::string &filename)
Test whether the given filename exists.
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.
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.
bool IsSavegameVersionBefore(SaveLoadVersion major, uint8_t minor=0)
Checks whether the savegame is below major.
@ SLV_SCRIPT_INT64
296 PR#9415 SQInteger is 64bit but was saved as 32bit.
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.
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.
ScriptSettings script
settings for scripts
uint32_t script_max_opcode_till_suspend
max opcode calls till scripts will suspend