#pragma once #include "squirrelclasstypes.h" #include "squirrelautobind.h" #include "core/math/vector.h" #include "plugins/plugin_abi.h" #include "mods/modmanager.h" /* definitions from hell required to function */ template <ScriptContext context, typename T> inline void SqRecurseArgs(FunctionVector& v, T& arg); template <ScriptContext context, typename T, typename... Args> inline void SqRecurseArgs(FunctionVector& v, T& arg, Args... args); /* sanity below */ // stolen from ttf2sdk: sqvm types typedef float SQFloat; typedef long SQInteger; typedef unsigned long SQUnsignedInteger; typedef char SQChar; typedef SQUnsignedInteger SQBool; static constexpr int operator&(ScriptContext first, ScriptContext second) { return first == second; } static constexpr int operator&(int first, ScriptContext second) { return first & (1 << static_cast<int>(second)); } static constexpr int operator|(ScriptContext first, ScriptContext second) { return (1 << static_cast<int>(first)) + (1 << static_cast<int>(second)); } static constexpr int operator|(int first, ScriptContext second) { return first + (1 << static_cast<int>(second)); } const char* GetContextName(ScriptContext context); const char* GetContextName_Short(ScriptContext context); eSQReturnType SQReturnTypeFromString(const char* pReturnType); const char* SQTypeNameFromID(const int iTypeId); void AsyncCall_External(ScriptContext context, const char* func_name, SquirrelMessage_External_Pop function); ScriptContext ScriptContextFromString(std::string string); namespace NS::log { template <ScriptContext context> std::shared_ptr<spdlog::logger> squirrel_logger(); }; // namespace NS::log // This base class means that only the templated functions have to be rebuilt for each template instance // Cuts down on compile time by ~5 seconds class SquirrelManagerBase { protected: std::vector<SQFuncRegistration*> m_funcRegistrations; public: CSquirrelVM* m_pSQVM; std::map<std::string, SQFunction> m_funcOverrides = {}; std::map<std::string, SQFunction> m_funcOriginals = {}; bool m_bFatalCompilationErrors = false; std::shared_ptr<spdlog::logger> logger; #pragma region SQVM funcs RegisterSquirrelFuncType RegisterSquirrelFunc; sq_defconstType __sq_defconst; sq_compilebufferType __sq_compilebuffer; sq_callType __sq_call; sq_raiseerrorType __sq_raiseerror; sq_compilefileType __sq_compilefile; sq_newarrayType __sq_newarray; sq_arrayappendType __sq_arrayappend; sq_newtableType __sq_newtable; sq_newslotType __sq_newslot; sq_pushroottableType __sq_pushroottable; sq_pushstringType __sq_pushstring; sq_pushintegerType __sq_pushinteger; sq_pushfloatType __sq_pushfloat; sq_pushboolType __sq_pushbool; sq_pushassetType __sq_pushasset; sq_pushvectorType __sq_pushvector; sq_pushobjectType __sq_pushobject; sq_getstringType __sq_getstring; sq_getintegerType __sq_getinteger; sq_getfloatType __sq_getfloat; sq_getboolType __sq_getbool; sq_getType __sq_get; sq_getassetType __sq_getasset; sq_getuserdataType __sq_getuserdata; sq_getvectorType __sq_getvector; sq_getthisentityType __sq_getthisentity; sq_getobjectType __sq_getobject; sq_stackinfosType __sq_stackinfos; sq_createuserdataType __sq_createuserdata; sq_setuserdatatypeidType __sq_setuserdatatypeid; sq_getfunctionType __sq_getfunction; sq_getentityfrominstanceType __sq_getentityfrominstance; sq_GetEntityConstantType __sq_GetEntityConstant_CBaseEntity; sq_pushnewstructinstanceType __sq_pushnewstructinstance; sq_sealstructslotType __sq_sealstructslot; #pragma endregion #pragma region SQVM func wrappers inline void defconst(CSquirrelVM* sqvm, const SQChar* pName, int nValue) { __sq_defconst(sqvm, pName, nValue); } inline SQRESULT compilebuffer(CompileBufferState* bufferState, const SQChar* bufferName = "unnamedbuffer", const SQBool bShouldThrowError = false) { return __sq_compilebuffer(m_pSQVM->sqvm, bufferState, bufferName, -1, bShouldThrowError); } inline SQRESULT _call(HSquirrelVM* sqvm, const SQInteger args) { return __sq_call(sqvm, args + 1, false, false); } inline SQInteger raiseerror(HSquirrelVM* sqvm, const SQChar* sError) { return __sq_raiseerror(sqvm, sError); } inline bool compilefile(CSquirrelVM* sqvm, const char* path, const char* name, int a4) { return __sq_compilefile(sqvm, path, name, a4); } inline void newarray(HSquirrelVM* sqvm, const SQInteger stackpos = 0) { __sq_newarray(sqvm, stackpos); } inline SQRESULT arrayappend(HSquirrelVM* sqvm, const SQInteger stackpos) { return __sq_arrayappend(sqvm, stackpos); } inline SQRESULT newtable(HSquirrelVM* sqvm) { return __sq_newtable(sqvm); } inline SQRESULT newslot(HSquirrelVM* sqvm, SQInteger idx, SQBool bStatic) { return __sq_newslot(sqvm, idx, bStatic); } inline void pushroottable(HSquirrelVM* sqvm) { __sq_pushroottable(sqvm); } inline void pushstring(HSquirrelVM* sqvm, const SQChar* sVal, int length = -1) { __sq_pushstring(sqvm, sVal, length); } inline void pushinteger(HSquirrelVM* sqvm, const SQInteger iVal) { __sq_pushinteger(sqvm, iVal); } inline void pushfloat(HSquirrelVM* sqvm, const SQFloat flVal) { __sq_pushfloat(sqvm, flVal); } inline void pushbool(HSquirrelVM* sqvm, const SQBool bVal) { __sq_pushbool(sqvm, bVal); } inline void pushasset(HSquirrelVM* sqvm, const SQChar* sVal, int length = -1) { __sq_pushasset(sqvm, sVal, length); } inline void pushvector(HSquirrelVM* sqvm, const Vector3 pVal) { __sq_pushvector(sqvm, (float*)&pVal); } inline void pushobject(HSquirrelVM* sqvm, SQObject* obj) { __sq_pushobject(sqvm, obj); } inline const SQChar* getstring(HSquirrelVM* sqvm, const SQInteger stackpos) { return __sq_getstring(sqvm, stackpos); } inline SQInteger getinteger(HSquirrelVM* sqvm, const SQInteger stackpos) { return __sq_getinteger(sqvm, stackpos); } inline SQFloat getfloat(HSquirrelVM* sqvm, const SQInteger stackpos) { return __sq_getfloat(sqvm, stackpos); } inline SQBool getbool(HSquirrelVM* sqvm, const SQInteger stackpos) { return __sq_getbool(sqvm, stackpos); } inline SQRESULT get(HSquirrelVM* sqvm, const SQInteger stackpos) { return __sq_get(sqvm, stackpos); } inline Vector3 getvector(HSquirrelVM* sqvm, const SQInteger stackpos) { return *(Vector3*)__sq_getvector(sqvm, stackpos); } inline int sq_getfunction(HSquirrelVM* sqvm, const char* name, SQObject* returnObj, const char* signature) { return __sq_getfunction(sqvm, name, returnObj, signature); } inline SQRESULT getasset(HSquirrelVM* sqvm, const SQInteger stackpos, const char** result) { return __sq_getasset(sqvm, stackpos, result); } inline long long sq_stackinfos(HSquirrelVM* sqvm, int level, SQStackInfos& out) { return __sq_stackinfos(sqvm, level, &out, sqvm->_callstacksize); } inline Mod* getcallingmod(HSquirrelVM* sqvm, int depth = 0) { SQStackInfos stackInfo {}; if (1 + depth >= sqvm->_callstacksize) { return nullptr; } sq_stackinfos(sqvm, 1 + depth, stackInfo); std::string sourceName = stackInfo._sourceName; std::replace(sourceName.begin(), sourceName.end(), '/', '\\'); std::string filename = g_pModManager->NormaliseModFilePath(fs::path("scripts\\vscripts\\" + sourceName)); if (auto res = g_pModManager->m_ModFiles.find(filename); res != g_pModManager->m_ModFiles.end()) { return res->second.m_pOwningMod; } return nullptr; } template <typename T> inline SQRESULT getuserdata(HSquirrelVM* sqvm, const SQInteger stackpos, T* data, uint64_t* typeId) { return __sq_getuserdata(sqvm, stackpos, (void**)data, typeId); // this sometimes crashes idk } template <typename T> inline T* createuserdata(HSquirrelVM* sqvm, SQInteger size) { void* ret = __sq_createuserdata(sqvm, size); memset(ret, 0, size); return (T*)ret; } inline SQRESULT setuserdatatypeid(HSquirrelVM* sqvm, const SQInteger stackpos, uint64_t typeId) { return __sq_setuserdatatypeid(sqvm, stackpos, typeId); } template <typename T> inline SQBool getthisentity(HSquirrelVM* sqvm, T* ppEntity) { return __sq_getthisentity(sqvm, (void**)ppEntity); } template <typename T> inline T* getentity(HSquirrelVM* sqvm, SQInteger iStackPos) { SQObject obj; __sq_getobject(sqvm, iStackPos, &obj); // there are entity constants for other types, but seemingly CBaseEntity's is the only one needed return (T*)__sq_getentityfrominstance(m_pSQVM, &obj, __sq_GetEntityConstant_CBaseEntity()); } inline SQRESULT pushnewstructinstance(HSquirrelVM* sqvm, const int fieldCount) { return __sq_pushnewstructinstance(sqvm, fieldCount); } inline SQRESULT sealstructslot(HSquirrelVM* sqvm, const int fieldIndex) { return __sq_sealstructslot(sqvm, fieldIndex); } #pragma endregion }; template <ScriptContext context> class SquirrelManager : public virtual SquirrelManagerBase { public: #pragma region MessageBuffer SquirrelMessageBuffer* messageBuffer; template <typename... Args> SquirrelMessage AsyncCall(std::string funcname, Args... args) { // This function schedules a call to be executed on the next frame // This is useful for things like threads and plugins, which do not run on the main thread FunctionVector functionVector; SqRecurseArgs<context>(functionVector, args...); SquirrelMessage message = {funcname, functionVector}; messageBuffer->push(message); return message; } SquirrelMessage AsyncCall(std::string funcname) { // This function schedules a call to be executed on the next frame // This is useful for things like threads and plugins, which do not run on the main thread FunctionVector functionVector = {}; SquirrelMessage message = {funcname, functionVector}; messageBuffer->push(message); return message; } SQRESULT Call(const char* funcname) { // Warning! // This function assumes the squirrel VM is stopped/blocked at the moment of call // Calling this function while the VM is running is likely to result in a crash due to stack destruction // If you want to call into squirrel asynchronously, use `AsyncCall` instead if (!m_pSQVM || !m_pSQVM->sqvm) { spdlog::error( "{} was called on context {} while VM was not initialized. This will crash", __FUNCTION__, GetContextName(context)); } SQObject functionobj {}; int result = sq_getfunction(m_pSQVM->sqvm, funcname, &functionobj, 0); if (result != 0) // This func returns 0 on success for some reason { NS::log::squirrel_logger<context>()->error("Call was unable to find function with name '{}'. Is it global?", funcname); return SQRESULT_ERROR; } pushobject(m_pSQVM->sqvm, &functionobj); // Push the function object pushroottable(m_pSQVM->sqvm); // Push root table return _call(m_pSQVM->sqvm, 0); } template <typename... Args> SQRESULT Call(const char* funcname, Args... args) { // Warning! // This function assumes the squirrel VM is stopped/blocked at the moment of call // Calling this function while the VM is running is likely to result in a crash due to stack destruction // If you want to call into squirrel asynchronously, use `schedule_call` instead if (!m_pSQVM || !m_pSQVM->sqvm) { spdlog::error( "{} was called on context {} while VM was not initialized. This will crash", __FUNCTION__, GetContextName(context)); } SQObject functionobj {}; int result = sq_getfunction(m_pSQVM->sqvm, funcname, &functionobj, 0); if (result != 0) // This func returns 0 on success for some reason { NS::log::squirrel_logger<context>()->error("Call was unable to find function with name '{}'. Is it global?", funcname); return SQRESULT_ERROR; } pushobject(m_pSQVM->sqvm, &functionobj); // Push the function object pushroottable(m_pSQVM->sqvm); // Push root table FunctionVector functionVector; SqRecurseArgs<context>(functionVector, args...); for (auto& v : functionVector) { // Execute lambda to push arg to stack v(); } return _call(m_pSQVM->sqvm, functionVector.size()); } #pragma endregion public: SquirrelManager() { m_pSQVM = nullptr; } void VMCreated(CSquirrelVM* newSqvm); void VMDestroyed(); void ExecuteCode(const char* code); void AddFuncRegistration(std::string returnType, std::string name, std::string argTypes, std::string helpText, SQFunction func); SQRESULT setupfunc(const SQChar* funcname); void AddFuncOverride(std::string name, SQFunction func); void ProcessMessageBuffer(); void GenerateSquirrelFunctionsStruct(SquirrelFunctions* s); }; template <ScriptContext context> SquirrelManager<context>* g_pSquirrel; void InitialiseSquirrelManagers(); /* Beware all ye who enter below. This place is not a place of honor... no highly esteemed deed is commemorated here... nothing valued is here. What is here was dangerous and repulsive to us. This message is a warning about danger. */ #pragma region MessageBuffer templates // Clang-formatting makes this whole thing unreadable // clang-format off #ifndef MessageBufferFuncs #define MessageBufferFuncs // Bools template <ScriptContext context, typename T> requires std::convertible_to<T, bool> && (!std::is_floating_point_v<T>) && (!std::convertible_to<T, std::string>) && (!std::convertible_to<T, int>) inline VoidFunction SQMessageBufferPushArg(T& arg) { return [arg]{ g_pSquirrel<context>->pushbool(g_pSquirrel<context>->m_pSQVM->sqvm, static_cast<bool>(arg)); }; } // Vectors template <ScriptContext context> inline VoidFunction SQMessageBufferPushArg(Vector3& arg) { return [arg]{ g_pSquirrel<context>->pushvector(g_pSquirrel<context>->m_pSQVM->sqvm, arg); }; } // Vectors template <ScriptContext context> inline VoidFunction SQMessageBufferPushArg(SQObject* arg) { return [arg]{ g_pSquirrel<context>->pushSQObject(g_pSquirrel<context>->m_pSQVM->sqvm, arg); }; } // Ints template <ScriptContext context, typename T> requires std::convertible_to<T, int> && (!std::is_floating_point_v<T>) inline VoidFunction SQMessageBufferPushArg(T& arg) { return [arg]{ g_pSquirrel<context>->pushinteger(g_pSquirrel<context>->m_pSQVM->sqvm, static_cast<int>(arg)); }; } // Floats template <ScriptContext context, typename T> requires std::convertible_to<T, float> && (std::is_floating_point_v<T>) inline VoidFunction SQMessageBufferPushArg(T& arg) { return [arg]{ g_pSquirrel<context>->pushfloat(g_pSquirrel<context>->m_pSQVM->sqvm, static_cast<float>(arg)); }; } // Strings template <ScriptContext context, typename T> requires (std::convertible_to<T, std::string> || std::is_constructible_v<std::string, T>) inline VoidFunction SQMessageBufferPushArg(T& arg) { auto converted = std::string(arg); return [converted]{ g_pSquirrel<context>->pushstring(g_pSquirrel<context>->m_pSQVM->sqvm, converted.c_str(), converted.length()); }; } // Assets template <ScriptContext context> inline VoidFunction SQMessageBufferPushArg(SquirrelAsset& arg) { return [arg]{ g_pSquirrel<context>->pushasset(g_pSquirrel<context>->m_pSQVM->sqvm, arg.path.c_str(), arg.path.length()); }; } // Maps template <ScriptContext context, typename T> requires is_iterable<T> inline VoidFunction SQMessageBufferPushArg(T& arg) { FunctionVector localv = {}; localv.push_back([]{g_pSquirrel<context>->newarray(g_pSquirrel<context>->m_pSQVM->sqvm, 0);}); for (const auto& item : arg) { localv.push_back(SQMessageBufferPushArg<context>(item)); localv.push_back([]{g_pSquirrel<context>->arrayappend(g_pSquirrel<context>->m_pSQVM->sqvm, -2);}); } return [localv] { for (auto& func : localv) { func(); } }; } // Vectors template <ScriptContext context, typename T> requires is_map<T> inline VoidFunction SQMessageBufferPushArg(T& map) { FunctionVector localv = {}; localv.push_back([]{g_pSquirrel<context>->newtable(g_pSquirrel<context>->m_pSQVM->sqvm);}); for (const auto& item : map) { localv.push_back(SQMessageBufferPushArg<context>(item.first)); localv.push_back(SQMessageBufferPushArg<context>(item.second)); localv.push_back([]{g_pSquirrel<context>->newslot(g_pSquirrel<context>->m_pSQVM->sqvm, -3, false);}); } return [localv]{ for (auto& func : localv) { func(); } }; } template <ScriptContext context, typename T> inline void SqRecurseArgs(FunctionVector& v, T& arg) { v.push_back(SQMessageBufferPushArg<context>(arg)); } // This function is separated from the PushArg function so as to not generate too many template instances // This is the main function responsible for unrolling the argument pack template <ScriptContext context, typename T, typename... Args> inline void SqRecurseArgs(FunctionVector& v, T& arg, Args... args) { v.push_back(SQMessageBufferPushArg<context>(arg)); SqRecurseArgs<context>(v, args...); } // clang-format on #endif #pragma endregion