From d6a261439be110f2215e9f6f4529fc48a283d1c1 Mon Sep 17 00:00:00 2001 From: glx22 Date: Fri, 14 Feb 2025 02:19:36 +0100 Subject: [PATCH] Add: [Script] Framework for loading/saving selected ScriptObject --- cmake/scripts/SquirrelExport.cmake | 2 +- src/saveload/saveload.h | 1 + src/script/api/script_includes.hpp.in | 10 +++++ src/script/api/script_object.hpp | 16 +++++++ src/script/script_instance.cpp | 61 ++++++++++++++++++++++++--- src/script/script_instance.hpp | 1 + src/script/squirrel.hpp | 1 + 7 files changed, 86 insertions(+), 6 deletions(-) diff --git a/cmake/scripts/SquirrelExport.cmake b/cmake/scripts/SquirrelExport.cmake index 501ff95480..6549078844 100644 --- a/cmake/scripts/SquirrelExport.cmake +++ b/cmake/scripts/SquirrelExport.cmake @@ -347,7 +347,7 @@ foreach(LINE IN LISTS SOURCE_LINES) string(APPEND SQUIRREL_EXPORT "\nvoid SQ${API_CLS}_Register(Squirrel *engine)") string(APPEND SQUIRREL_EXPORT "\n{") string(APPEND SQUIRREL_EXPORT "\n DefSQClass<${CLS}, ScriptType::${APIUC}> SQ${API_CLS}(\"${API_CLS}\");") - if("${SUPER_CLS}" STREQUAL "Text" OR "${SUPER_CLS}" STREQUAL "ScriptObject") + if("${SUPER_CLS}" STREQUAL "Text") string(APPEND SQUIRREL_EXPORT "\n SQ${API_CLS}.PreRegister(engine);") else() string(APPEND SQUIRREL_EXPORT "\n SQ${API_CLS}.PreRegister(engine, \"${API_SUPER_CLS}\");") diff --git a/src/saveload/saveload.h b/src/saveload/saveload.h index 59c78f8d52..b665661d7d 100644 --- a/src/saveload/saveload.h +++ b/src/saveload/saveload.h @@ -399,6 +399,7 @@ enum SaveLoadVersion : uint16_t { SLV_ENCODED_STRING_FORMAT, ///< 350 PR#13499 Encoded String format changed. SLV_PROTECT_PLACED_HOUSES, ///< 351 PR#13270 Houses individually placed by players can be protected from town/AI removal. + SLV_SCRIPT_SAVE_INSTANCES, ///< 352 PR#13556 Scripts are allowed to save instances. SL_MAX_VERSION, ///< Highest possible saveload version }; diff --git a/src/script/api/script_includes.hpp.in b/src/script/api/script_includes.hpp.in index 75f1249ef4..a24e66458a 100644 --- a/src/script/api/script_includes.hpp.in +++ b/src/script/api/script_includes.hpp.in @@ -9,7 +9,17 @@ ${SQUIRREL_INCLUDES} +static SQInteger ${APIUC}ObjectConstructor(HSQUIRRELVM vm) +{ + return sq_throwerror(vm, "${APIUC}Object is not instantiable"); +} + void SQ${APIUC}_RegisterAll(Squirrel *engine) { + DefSQClass SQ${APIUC}Object("${APIUC}Object"); + SQ${APIUC}Object.PreRegister(engine); + SQ${APIUC}Object.DefSQAdvancedStaticMethod(engine, &${APIUC}ObjectConstructor, "constructor"); + SQ${APIUC}Object.PostRegister(engine); + ${SQUIRREL_REGISTER} } diff --git a/src/script/api/script_object.hpp b/src/script/api/script_object.hpp index e804425c70..f6436822e7 100644 --- a/src/script/api/script_object.hpp +++ b/src/script/api/script_object.hpp @@ -84,6 +84,22 @@ protected: static ScriptInstance *active; ///< The global current active instance. }; + /** + * Save this object. + * Must push 2 elements on the stack: + * - the name (classname without "Script") of the object (OT_STRING) + * - the data for the object (any supported types) + * @return True iff saving this type is supported. + */ + virtual bool SaveObject(HSQUIRRELVM) { return false; } + + /** + * Load this object. + * The data for the object must be pushed on the stack before the call. + * @return True iff loading this type is supported. + */ + virtual bool LoadObject(HSQUIRRELVM) { return false; } + public: /** * Store the latest result of a DoCommand per company. diff --git a/src/script/script_instance.cpp b/src/script/script_instance.cpp index dcd93805c9..0ca603cf54 100644 --- a/src/script/script_instance.cpp +++ b/src/script/script_instance.cpp @@ -480,6 +480,27 @@ static const SaveLoad _script_byte[] = { return true; } + case OT_INSTANCE:{ + if (!test) { + _script_sl_byte = SQSL_INSTANCE; + SlObject(nullptr, _script_byte); + } + SQInteger top = sq_gettop(vm); + try { + ScriptObject *obj = static_cast(Squirrel::GetRealInstance(vm, -1, "Object")); + if (!obj->SaveObject(vm)) throw std::exception(); + if (sq_gettop(vm) != top + 2) throw std::exception(); + if (sq_gettype(vm, -2) != OT_STRING || !SaveObject(vm, -2, max_depth - 1, test)) throw std::exception(); + if (!SaveObject(vm, -1, max_depth - 1, test)) throw std::exception(); + sq_settop(vm, top); + return true; + } catch (...) { + ScriptLog::Error("You tried to save an unsupported type. No data saved."); + sq_settop(vm, top); + return false; + } + } + default: ScriptLog::Error("You tried to save an unsupported type. No data saved."); return false; @@ -588,7 +609,7 @@ bool ScriptInstance::IsPaused() case SQSL_INT: { int64_t value; SlCopy(&value, 1, IsSavegameVersionBefore(SLV_SCRIPT_INT64) ? SLE_FILE_I32 | SLE_VAR_I64 : SLE_INT64); - if (data != nullptr) data->push_back((SQInteger)value); + if (data != nullptr) data->push_back(static_cast(value)); return true; } @@ -602,24 +623,29 @@ bool ScriptInstance::IsPaused() case SQSL_ARRAY: case SQSL_TABLE: { - if (data != nullptr) data->push_back((SQSaveLoadType)_script_sl_byte); + if (data != nullptr) data->push_back(static_cast(_script_sl_byte)); while (LoadObjects(data)); return true; } case SQSL_BOOL: { SlObject(nullptr, _script_byte); - if (data != nullptr) data->push_back((SQBool)(_script_sl_byte != 0)); + if (data != nullptr) data->push_back(static_cast(_script_sl_byte != 0)); return true; } case SQSL_NULL: { - if (data != nullptr) data->push_back((SQSaveLoadType)_script_sl_byte); + if (data != nullptr) data->push_back(static_cast(_script_sl_byte)); + return true; + } + + case SQSL_INSTANCE: { + if (data != nullptr) data->push_back(static_cast(_script_sl_byte)); return true; } case SQSL_ARRAY_TABLE_END: { - if (data != nullptr) data->push_back((SQSaveLoadType)_script_sl_byte); + if (data != nullptr) data->push_back(static_cast(_script_sl_byte)); return false; } @@ -663,6 +689,31 @@ bool ScriptInstance::IsPaused() sq_pushnull(this->vm); return true; + case SQSL_INSTANCE: { + SQInteger top = sq_gettop(this->vm); + LoadObjects(this->vm, this->data); + const SQChar *buf; + sq_getstring(this->vm, -1, &buf); + Squirrel *engine = static_cast(sq_getforeignptr(this->vm)); + std::string class_name = fmt::format("{}{}", engine->GetAPIName(), buf); + sq_pushroottable(this->vm); + sq_pushstring(this->vm, class_name); + if (SQ_FAILED(sq_get(this->vm, -2))) throw Script_FatalError(fmt::format("'{}' doesn't exist", class_name)); + sq_pushroottable(vm); + if (SQ_FAILED(sq_call(this->vm, 1, SQTrue, SQFalse))) throw Script_FatalError(fmt::format("Failed to instantiate '{}'", class_name)); + HSQOBJECT res; + sq_getstackobj(vm, -1, &res); + sq_addref(vm, &res); + sq_settop(this->vm, top); + sq_pushobject(vm, res); + sq_release(vm, &res); + ScriptObject *obj = static_cast(Squirrel::GetRealInstance(vm, -1, "Object")); + LoadObjects(this->vm, this->data); + if (!obj->LoadObject(vm)) throw Script_FatalError(fmt::format("Failed to load '{}'", class_name)); + sq_pop(this->vm, 1); + return true; + } + case SQSL_ARRAY_TABLE_END: return false; diff --git a/src/script/script_instance.hpp b/src/script/script_instance.hpp index 1d772fc084..4b969a3398 100644 --- a/src/script/script_instance.hpp +++ b/src/script/script_instance.hpp @@ -32,6 +32,7 @@ private: SQSL_TABLE = 0x03, ///< The following data is an table. SQSL_BOOL = 0x04, ///< The following data is a boolean. SQSL_NULL = 0x05, ///< A null variable. + SQSL_INSTANCE = 0x06, ///< The following data is an instance. SQSL_ARRAY_TABLE_END = 0xFF, ///< Marks the end of an array or table, no data follows. }; diff --git a/src/script/squirrel.hpp b/src/script/squirrel.hpp index 0868182bd2..a36261835a 100644 --- a/src/script/squirrel.hpp +++ b/src/script/squirrel.hpp @@ -23,6 +23,7 @@ struct ScriptAllocator; class Squirrel { friend class ScriptAllocatorScope; + friend class ScriptInstance; private: typedef void (SQPrintFunc)(bool error_msg, const std::string &message);