Go to the documentation of this file.
10 #include "../stdafx.h"
12 #include "../saveload/saveload.h"
14 #include "../script/squirrel_class.hpp"
21 #include "api/script_controller.hpp"
22 #include "api/script_error.hpp"
23 #include "api/script_event.hpp"
24 #include "api/script_log.hpp"
26 #include "../company_base.h"
27 #include "../company_func.h"
28 #include "../fileio_func.h"
29 #include "../league_type.h"
30 #include "../misc/endian_buffer.hpp"
32 #include "../safeguards.h"
34 ScriptStorage::~ScriptStorage()
37 if (
event_data !=
nullptr) ScriptEventController::FreeEventPointer();
38 if (
log_data !=
nullptr) ScriptLog::FreeLogPointer();
46 static void PrintFunc(
bool error_msg,
const SQChar *message)
49 ScriptController::Print(error_msg, message);
60 is_save_data_on_stack(false),
73 ScriptObject::ActiveInstance active(
this);
75 this->
controller =
new ScriptController(company);
82 ScriptObject::SetAllowDoCommand(
false);
86 }
else if (!this->
engine->
LoadScript(main_script) || this->engine->IsSuspended()) {
87 if (this->
engine->
IsSuspended()) ScriptLog::Error(
"This script took too long to load script. AI is not started.");
102 ScriptObject::SetAllowDoCommand(
true);
119 char script_name[32];
120 seprintf(script_name,
lastof(script_name),
"compat_%s.nut", api_version);
122 std::string buf = FioGetDirectory(sp, dir);
128 ScriptLog::Error(
"Failed to load API compatibility script");
129 Debug(script, 0,
"Error compiling / running API compatibility script: {}", buf);
133 ScriptLog::Warning(
"API compatibility script not found");
137 ScriptInstance::~ScriptInstance()
139 ScriptObject::ActiveInstance active(
this);
157 Debug(script, 0,
"The script died unexpectedly.");
172 ScriptObject::ActiveInstance active(
this);
174 if (this->
IsDead())
return;
185 if (--this->
suspend > 0)
return;
210 ScriptObject::SetAllowDoCommand(
false);
214 if (this->
engine->
IsSuspended()) ScriptLog::Error(
"This script took too long to initialize. Script is not started.");
220 if (this->
engine->
IsSuspended()) ScriptLog::Error(
"This script took too long in the Load function. Script is not started.");
224 ScriptObject::SetAllowDoCommand(
true);
262 ScriptObject::ActiveInstance active(
this);
269 instance->engine->InsertResult(ScriptObject::GetLastCommandRes());
274 instance->engine->InsertResult(EndianBufferReader::ToValue<VehicleID>(ScriptObject::GetLastCommandResData()));
279 instance->engine->InsertResult(EndianBufferReader::ToValue<SignID>(ScriptObject::GetLastCommandResData()));
284 instance->engine->InsertResult(EndianBufferReader::ToValue<GroupID>(ScriptObject::GetLastCommandResData()));
289 instance->engine->InsertResult(EndianBufferReader::ToValue<GoalID>(ScriptObject::GetLastCommandResData()));
294 instance->engine->InsertResult(EndianBufferReader::ToValue<StoryPageID>(ScriptObject::GetLastCommandResData()));
299 instance->engine->InsertResult(EndianBufferReader::ToValue<StoryPageElementID>(ScriptObject::GetLastCommandResData()));
304 instance->engine->InsertResult(EndianBufferReader::ToValue<LeagueTableElementID>(ScriptObject::GetLastCommandResData()));
309 instance->engine->InsertResult(EndianBufferReader::ToValue<LeagueTableID>(ScriptObject::GetLastCommandResData()));
320 ScriptObject::ActiveInstance active(
this);
322 return ScriptObject::GetLogPointer();
355 if (max_depth == 0) {
356 ScriptLog::Error(
"Savedata can only be nested to 25 deep. No data saved.");
360 switch (sq_gettype(vm, index)) {
367 sq_getinteger(vm, index, &res);
369 int64 value = (int64)res;
370 SlCopy(&value, 1, SLE_INT64);
381 sq_getstring(vm, index, &buf);
382 size_t len = strlen(buf) + 1;
384 ScriptLog::Error(
"Maximum string length is 254 chars. No data saved.");
390 SlCopy(
const_cast<char *
>(buf), len, SLE_CHAR);
401 while (SQ_SUCCEEDED(sq_next(vm, index - 1))) {
403 bool res =
SaveObject(vm, -1, max_depth - 1, test);
424 while (SQ_SUCCEEDED(sq_next(vm, index - 1))) {
447 sq_getbool(vm, index, &res);
464 ScriptLog::Error(
"You tried to save an unsupported type. No data saved.");
477 ScriptObject::ActiveInstance active(
this);
497 bool backup_allow = ScriptObject::GetAllowDoCommand();
498 ScriptObject::SetAllowDoCommand(
false);
520 ScriptObject::SetAllowDoCommand(backup_allow);
522 if (!sq_istable(savedata)) {
523 ScriptLog::Error(this->
engine->
IsSuspended() ?
"This script took too long to Save." :
"Save function should return a table.");
528 sq_pushobject(vm, savedata);
539 ScriptLog::Warning(
"Save function is not implemented");
571 if (data !=
nullptr) data->push_back((SQInteger)value);
577 static char buf[std::numeric_limits<decltype(
_script_sl_byte)>::max()];
580 if (data !=
nullptr) data->push_back(std::string(buf));
613 ScriptDataVariant value = data->front();
616 if (std::holds_alternative<SQInteger>(value)) {
617 sq_pushinteger(vm, std::get<SQInteger>(value));
621 if (std::holds_alternative<std::string>(value)) {
622 sq_pushstring(vm, std::get<std::string>(value).c_str(), -1);
626 if (std::holds_alternative<SQBool>(value)) {
627 sq_pushbool(vm, std::get<SQBool>(value));
631 switch (std::get<SQSaveLoadType>(value)) {
635 sq_arrayappend(vm, -2);
660 default: NOT_REACHED();
684 ScriptData *data =
new ScriptData();
685 data->push_back((SQInteger)version);
692 ScriptObject::ActiveInstance active(
this);
694 if (this->
IsDead() || data ==
nullptr)
return;
698 ScriptDataVariant version = data->front();
700 sq_pushinteger(vm, std::get<SQInteger>(version));
714 ScriptLog::Warning(
"Loading failed: there was data for the script to load, but the script does not have a Load() function.");
724 sq_pushstring(vm,
"Load", -1);
735 if (SQ_FAILED(sq_call(vm, 3, SQFalse, SQFalse,
MAX_SL_OPS)))
return false;
749 ScriptObject::ActiveInstance active(
this);
751 if (!ScriptObject::CheckLastCommand(data, cmd)) {
752 Debug(script, 1,
"DoCommandCallback terminating a script, last command does not match expected command");
756 ScriptObject::SetLastCommandRes(result.
Succeeded());
757 ScriptObject::SetLastCommandResData(std::move(result_data));
760 ScriptObject::SetLastError(ScriptError::StringToError(result.
GetErrorMessage()));
762 ScriptObject::IncreaseDoCommandCosts(result.
GetCost());
763 ScriptObject::SetLastCost(result.
GetCost());
766 ScriptObject::SetLastCommand({},
CMD_END);
773 ScriptObject::ActiveInstance active(
this);
775 ScriptEventController::InsertEvent(event);
778 size_t ScriptInstance::GetAllocatedMemory()
const
@ SQSL_ARRAY_TABLE_END
Marks the end of an array or table, no data follows.
virtual void LoadDummyScript()=0
Load the dummy script.
A throw-class that is given when the script wants to suspend.
void * log_data
Pointer to the log data storage.
static bool SaveObject(HSQUIRRELVM vm, SQInteger index, int max_depth, bool test)
Save one object (int / string / array / table) to the savegame.
class Squirrel * engine
A wrapper around the squirrel vm.
static byte _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 SQChar *message)
Callback called by squirrel when a script uses "print" and for error messages.
size_t last_allocated_memory
Last known allocated memory value (for display for crashed scripts)
The storage for each script.
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.
void InsertEvent(class ScriptEvent *event)
Insert an event for this script.
void GameLoop()
Run the GameLoop of a script.
void SetPrintFunction(SQPrintFunc *func)
Set a custom print function, so you can handle outputs from SQ yourself.
int GetSuspendTime()
Get the amount of ticks the script should be suspended.
class ScriptController * controller
The script main class.
void Save()
Call the script Save function and save all data in the savegame.
Commands
List of commands.
std::vector< byte > CommandDataBuffer
Storage buffer for serialized command data.
class Squirrel * engine
The engine we're scanning with.
void SlCopy(void *object, size_t length, VarType conv)
Copy a list of SL_VARs to/from a savegame.
Runtime information about a script like a pointer to the squirrel vm and the current state.
ScriptInstance(const char *APIName)
Create a new script.
void Unpause()
Resume execution of the script.
static void DoCommandReturnStoryPageElementID(ScriptInstance *instance)
Return a StoryPageElementID reply for a DoCommand.
Searchpath
Types of searchpaths OpenTTD might use.
SQObject * instance
Squirrel-pointer to the script main class.
@ SQSL_STRING
The following data is an string.
bool is_save_data_on_stack
Is the save data still on the squirrel stack?
HSQUIRRELVM GetVM()
Get the squirrel VM.
bool is_paused
Is the script paused? (a paused script will not be executed until unpaused)
static void DoCommandReturnLeagueTableElementID(ScriptInstance *instance)
Return a LeagueTableElementID reply for a DoCommand.
const std::string & GetErrorMessage() const
The error message associated with the fatal error.
Owner
Enum for all companies/owners.
static void DoCommandReturnLeagueTableID(ScriptInstance *instance)
Return a LeagueTableID reply for a DoCommand.
StringID GetErrorMessage() const
Returns the error message of a command.
@ CMD_END
Must ALWAYS be on the end of this list!! (period)
bool Succeeded() const
Did this command succeed?
SQInteger GetOpsTillSuspend()
Get the number of operations the script can execute before being suspended.
void Pause()
Suspends the script for the current tick and then pause the execution of script.
static void DoCommandReturnGoalID(ScriptInstance *instance)
Return a GoalID reply for a DoCommand.
void ReleaseSQObject(HSQOBJECT *obj)
Decrease the ref count of a squirrel object.
class ScriptStorage * storage
Some global information for each running script.
SQInteger GetOpsTillSuspend()
How many operations can we execute till suspension?
bool HasScriptCrashed()
Find out if the squirrel script made an error before.
bool LoadScript(const char *script)
Load a script.
static void DoCommandReturnSignID(ScriptInstance *instance)
Return a SignID reply for a DoCommand.
Common return value for all commands.
bool in_shutdown
Is this instance currently being destructed?
static const SaveLoad _script_byte[]
SaveLoad array that saves/loads exactly one byte.
void ThrowError(const char *error)
Throw a Squirrel error that will be nicely displayed to the user.
ScriptSettings script
settings for scripts
void StrMakeValidInPlace(char *str, const char *last, StringValidationSettings settings)
Scans the string for invalid characters and replaces then with a question mark '?' (if not ignored).
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 bool LoadObjects(ScriptData *data)
Load all objects from a savegame.
bool FileExists(const std::string &filename)
Test whether the given filename exists.
static ScriptData * Load(int version)
Load data from a savegame.
#define SLEG_VAR(name, variable, type)
Storage of a global variable in every savegame version.
void SetGlobalPointer(void *ptr)
Sets a pointer in the VM that is reachable from where ever you are in SQ.
bool Failed() const
Did this command fail?
bool IsDead() const
Return the "this script died" value.
void * event_data
Pointer to the event data storage.
size_t GetAllocatedMemory() const noexcept
Get number of bytes allocated by this VM.
GameSettings _settings_game
Game settings of a running game or the scenario editor.
@ SQSL_TABLE
The following data is an table.
Money GetCost() const
The costs as made up to this moment.
bool is_started
Is the scripts constructor executed?
static bool IsSavegameVersionBefore(SaveLoadVersion major, byte minor=0)
Checks whether the savegame is below major.
static void DoCommandReturnStoryPageID(ScriptInstance *instance)
Return a StoryPageID reply for a DoCommand.
virtual void RegisterAPI()
Register all API functions to the VM.
bool CallMethod(HSQOBJECT instance, const char *method_name, HSQOBJECT *ret, int suspend)
Call a method of an instance, in various flavors.
void Initialize(const char *main_script, const char *instance_name, CompanyID company)
Initialize the script and prepare it for its first run.
std::string main_script
The full path of the script.
static void LoadEmpty()
Load and discard data from a savegame.
static void DoCommandReturnGroupID(ScriptInstance *instance)
Return a GroupID reply for a DoCommand.
Script_SuspendCallbackProc * GetSuspendCallback()
Get the callback to call when the script can run again.
void squirrel_register_std(Squirrel *engine)
Register all standard functions we want to give to a script.
@ SLV_SCRIPT_INT64
296 PR#9415 SQInteger is 64bit but was saved as 32bit.
void ResumeError()
Resume the VM with an error so it prints a stack trace.
bool CreateClassInstance(const char *class_name, void *real_instance, HSQOBJECT *instance)
Exactly the same as CreateClassInstanceVM, only callable without instance of Squirrel.
CompanyID _current_company
Company currently doing an action.
static const int MAX_CONSTRUCTOR_OPS
The maximum number of operations for initial start of a script.
void CollectGarbage()
Tell the VM to do a garbage collection run.
Script_SuspendCallbackProc * callback
Callback that should be called in the next tick the script runs.
bool MethodExists(HSQOBJECT instance, const char *method_name)
Check if a method exists in an instance.
@ SQSL_ARRAY
The following data is an array.
bool DoCommandCallback(const CommandCost &result, const CommandDataBuffer &data, CommandDataBuffer result_data, Commands cmd)
DoCommand callback function for all commands executed by scripts.
void CrashOccurred()
Set the script status to crashed.
void NORETURN SlErrorCorrupt(const char *msg)
Error handler for corrupt savegames.
@ SQSL_NULL
A null variable.
@ SQSL_BOOL
The following data is a boolean.
uint32 script_max_opcode_till_suspend
max opcode calls till scripts will suspend
static void DoCommandReturnVehicleID(ScriptInstance *instance)
Return a VehicleID reply for a DoCommand.
class ScriptStorage * GetStorage()
Get the storage of this script.
void LoadOnStack(ScriptData *data)
Store loaded data on the stack.
int CDECL seprintf(char *str, const char *last, const char *format,...)
Safer implementation of snprintf; same as snprintf except:
SQSaveLoadType
The type of the data that follows in the savegame.
Subdirectory
The different kinds of subdirectories OpenTTD uses.
virtual void Died()
Tell the script it died.
int suspend
The amount of ticks to suspend this script before it's allowed to continue.
bool Resume(int suspend=-1)
Resume a VM when it was suspended via a throw.
A throw-class that is given when the script made a fatal error.
void Continue()
A script in multiplayer waits for the server to handle its DoCommand.
#define Debug(name, level, format_string,...)
Ouptut a line of debugging information.
void ReleaseObject(HSQOBJECT *ptr)
Release a SQ object.
@ SQSL_INT
The following data is an integer.
bool IsPaused()
Checks if the script is paused.
bool IsSuspended()
Did the squirrel code suspend or return normally.
bool LoadCompatibilityScripts(const char *api_version, Subdirectory dir)
Load squirrel scripts to emulate an older API.
void CollectGarbage()
Let the VM collect any garbage.
void SlObject(void *object, const SaveLoadTable &slt)
Main SaveLoad function.
static void DecreaseOps(HSQUIRRELVM vm, int amount)
Tell the VM to remove amount ops from the number of ops till suspend.
#define lastof(x)
Get the last element of an fixed size array.
void * GetLogPointer()
Get the log pointer of this script.
static const uint SQUIRREL_MAX_DEPTH
The maximum recursive depth for items stored in the savegame.
static const int MAX_SL_OPS
The maximum number of operations for saving or loading the data of a script.