1220 lines
31 KiB
C++
1220 lines
31 KiB
C++
#include "ScriptComponent.h"
|
||
#include "../core/utils/Logging.h"
|
||
#include "../core/utils/ExceptionHandler.h"
|
||
#include "../utils/GameObjectsList.h"
|
||
|
||
#include "../Entitys/Object.h"
|
||
#include "../Components/SpriteComponent.h"
|
||
#include "../Components/CameraComponent.h"
|
||
#include "../Components/LightComponent.h"
|
||
#include "../Components/TextComponent.h"
|
||
#include "../Components/TilemapComponent.h"
|
||
#include "../Components/AnimationComponent.h"
|
||
#include "../Components/AudioPlayerComponent.h"
|
||
|
||
#include "../core/utils/Profiler.h"
|
||
#include "../core/utils/utils.h"
|
||
#include "../core/utils/keycode.h"
|
||
#include "../core/utils/input.h"
|
||
|
||
#include "../core/types/vec2.h"
|
||
#include "../core/types/vec3.h"
|
||
|
||
#include "../core/scripts/LuaGlobalBridge.h"
|
||
#include "../core/scripts/ScriptCore.h"
|
||
|
||
|
||
void PushVector3(lua_State* L, float x, float y, float z);
|
||
|
||
static int Lua_SetGlobal(lua_State *L)
|
||
{
|
||
const char *key = luaL_checkstring(L, 1);
|
||
|
||
switch (lua_type(L, 2))
|
||
{
|
||
case LUA_TNUMBER:
|
||
LuaGlobalBridge::Set(key, lua_tonumber(L, 2));
|
||
break;
|
||
case LUA_TBOOLEAN:
|
||
LuaGlobalBridge::Set(key, (bool)lua_toboolean(L, 2));
|
||
break;
|
||
case LUA_TSTRING:
|
||
LuaGlobalBridge::Set(key, std::string(lua_tostring(L, 2)));
|
||
break;
|
||
default:
|
||
luaL_error(L, "[Engine.SetGlobal] Unsupported value type");
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
static int Lua_GetGlobal(lua_State *L)
|
||
{
|
||
const char *key = luaL_checkstring(L, 1);
|
||
auto value = LuaGlobalBridge::Get(key);
|
||
|
||
if (!value.has_value())
|
||
{
|
||
lua_pushnil(L);
|
||
return 1;
|
||
}
|
||
|
||
std::visit([L](auto &&val)
|
||
{
|
||
using T = std::decay_t<decltype(val)>;
|
||
if constexpr (std::is_same_v<T, double>)
|
||
lua_pushnumber(L, val);
|
||
else if constexpr (std::is_same_v<T, bool>)
|
||
lua_pushboolean(L, val);
|
||
else if constexpr (std::is_same_v<T, std::string>)
|
||
lua_pushstring(L, val.c_str()); }, *value);
|
||
|
||
return 1;
|
||
}
|
||
|
||
static bool luaDebugEnabled = false;
|
||
static bool old_state = false;
|
||
|
||
#define SET_KC(name) \
|
||
lua_pushinteger(L, static_cast<int>(Keycode::name)); \
|
||
lua_setfield(L, -2, #name);
|
||
|
||
struct LuaObjectWrapper
|
||
{
|
||
Object *obj;
|
||
};
|
||
#define LUA_OBJECT_MT "LuaObjectMeta"
|
||
|
||
struct LuaVector2
|
||
{
|
||
float x, y;
|
||
};
|
||
struct LuaVector3
|
||
{
|
||
float x, y, z;
|
||
};
|
||
|
||
#define LUA_VECTOR3_MT "LuaVector3Meta"
|
||
#define LUA_VECTOR2_MT "LuaVector2Meta"
|
||
|
||
struct LuaAnimationWrapper
|
||
{
|
||
AnimationComponent *comp;
|
||
};
|
||
struct LuaLightWrapper
|
||
{
|
||
LightComponent *comp;
|
||
};
|
||
struct LuaSoundWrapper
|
||
{
|
||
AudioPlayerComponent *comp;
|
||
};
|
||
|
||
#define LUA_SOUND_MT "LuaSoundMeta"
|
||
#define LUA_LIGHT_MT "LuaLightMeta"
|
||
#define LUA_ANIMATION_MT "LuaAnimationMeta"
|
||
|
||
static core::types::Vec3 GetLuaVector3(lua_State *L, int index);
|
||
|
||
ScriptComponent::ScriptComponent(Object *owner) : Component(owner), L(nullptr) {}
|
||
ScriptComponent::~ScriptComponent()
|
||
{
|
||
if (L)
|
||
{
|
||
ScriptCore::UnregisterState(L);
|
||
lua_close(L);
|
||
}
|
||
}
|
||
|
||
void ScriptComponent::SetScriptPath(const std::string &path)
|
||
{
|
||
scriptPath = path;
|
||
ReloadScript();
|
||
}
|
||
const std::string &ScriptComponent::GetScriptPath() const { return scriptPath; }
|
||
|
||
void ScriptComponent::Hook(lua_State *L, lua_Debug *ar)
|
||
{
|
||
if (!g_engineConfig.settings.profile_deep)
|
||
return;
|
||
|
||
// Quickly fetch component from Lua extraspace
|
||
ScriptComponent *self = *reinterpret_cast<ScriptComponent **>(lua_getextraspace(L));
|
||
if (!self || (ar->event != LUA_HOOKCALL && ar->event != LUA_HOOKRET))
|
||
return;
|
||
|
||
// Only fetch debug info if needed
|
||
if (!lua_getinfo(L, "nS", ar))
|
||
return;
|
||
|
||
// Only build label if entering a function
|
||
if (ar->event == LUA_HOOKCALL)
|
||
{
|
||
const char *name = ar->name;
|
||
std::string label;
|
||
|
||
if (name && *name)
|
||
{
|
||
label = name;
|
||
}
|
||
else if (ar->short_src && *ar->short_src)
|
||
{
|
||
label = GetFilenameFromPath(ar->short_src);
|
||
}
|
||
else
|
||
{
|
||
label = "unknown";
|
||
}
|
||
|
||
self->luaCallStack.emplace_back(label);
|
||
profiler.BeginDeepSection(label);
|
||
}
|
||
else if (ar->event == LUA_HOOKRET && !self->luaCallStack.empty())
|
||
{
|
||
profiler.EndDeepSection();
|
||
self->luaCallStack.pop_back();
|
||
}
|
||
}
|
||
|
||
static int Lua_LogInfo(lua_State *L)
|
||
{
|
||
PROFILE_DEEP_SCOPE("Lua_LogInfo");
|
||
Logger::LogInfo("[Lua] %s", lua_tostring(L, 1));
|
||
return 0;
|
||
}
|
||
static int Lua_LogError(lua_State *L)
|
||
{
|
||
PROFILE_DEEP_SCOPE("Lua_LogError");
|
||
Logger::LogError("[Lua] %s", lua_tostring(L, 1));
|
||
return 0;
|
||
}
|
||
static int Lua_LogDebug(lua_State *L)
|
||
{
|
||
PROFILE_DEEP_SCOPE("Lua_LogDebug");
|
||
Logger::LogDebug("[Lua] %s", lua_tostring(L, 1));
|
||
return 0;
|
||
}
|
||
static int Lua_DebugLua(lua_State *L)
|
||
{
|
||
PROFILE_DEEP_SCOPE("Lua_DebugLua");
|
||
luaDebugEnabled = lua_toboolean(L, 1);
|
||
if (old_state != luaDebugEnabled)
|
||
Logger::LogInfo("[Lua] DebugLua(%s)", luaDebugEnabled ? "true" : "false");
|
||
old_state = luaDebugEnabled;
|
||
return 0;
|
||
}
|
||
|
||
static Component *GetComponentByName(Object *obj, const std::string &type)
|
||
{
|
||
PROFILE_DEEP_SCOPE("GetComponentByName");
|
||
|
||
using GetterFn = std::function<Component *(Object *)>;
|
||
static const std::unordered_map<std::string, GetterFn> table = {
|
||
{"SpriteComponent", [](Object *o)
|
||
{ return o->GetComponent<SpriteComponent>().get(); }},
|
||
{"CameraComponent", [](Object *o)
|
||
{ return o->GetComponent<CameraComponent>().get(); }},
|
||
{"LightComponent", [](Object *o)
|
||
{ return o->GetComponent<LightComponent>().get(); }},
|
||
{"TilemapComponent", [](Object *o)
|
||
{ return o->GetComponent<TilemapComponent>().get(); }},
|
||
{"TextComponent", [](Object *o)
|
||
{ return o->GetComponent<TextComponent>().get(); }},
|
||
{"ScriptComponent", [](Object *o)
|
||
{ return o->GetComponent<ScriptComponent>().get(); }},
|
||
{"AnimationComponent", [](Object *o)
|
||
{ return o->GetComponent<AnimationComponent>().get(); }},
|
||
{"AudioPlayerComponent", [](Object *o)
|
||
{ return o->GetComponent<AudioPlayerComponent>().get(); }},
|
||
};
|
||
|
||
auto it = table.find(type);
|
||
if (it != table.end())
|
||
return it->second(obj);
|
||
|
||
return nullptr;
|
||
}
|
||
|
||
static int Lua_GetMousePos(lua_State *L)
|
||
{
|
||
PROFILE_DEEP_SCOPE("Engine::GetMousePos");
|
||
|
||
core::types::Vec2 m = Input::GetMousePosition();
|
||
|
||
LuaVector2 *vec = (LuaVector2 *)lua_newuserdata(L, sizeof(LuaVector2));
|
||
vec->x = m.x;
|
||
vec->y = m.y;
|
||
|
||
luaL_getmetatable(L, LUA_VECTOR2_MT);
|
||
lua_setmetatable(L, -2);
|
||
|
||
return 1;
|
||
}
|
||
|
||
static int Lua_Object_GetComponent(lua_State *L)
|
||
{
|
||
PROFILE_DEEP_SCOPE("Object::GetComponent");
|
||
auto *wrapper = (LuaObjectWrapper *)luaL_checkudata(L, 1, LUA_OBJECT_MT);
|
||
const char *type = luaL_checkstring(L, 2);
|
||
if (strcmp(type, "AnimationComponent") == 0)
|
||
{
|
||
auto compPtr = wrapper->obj->GetComponent<AnimationComponent>();
|
||
if (compPtr)
|
||
{
|
||
LuaAnimationWrapper *aw = (LuaAnimationWrapper *)lua_newuserdata(L, sizeof(LuaAnimationWrapper));
|
||
aw->comp = compPtr.get();
|
||
luaL_getmetatable(L, LUA_ANIMATION_MT);
|
||
lua_setmetatable(L, -2);
|
||
return 1;
|
||
}
|
||
lua_pushnil(L);
|
||
return 1;
|
||
}
|
||
if (strcmp(type, "LightComponent") == 0)
|
||
{
|
||
auto compPtr = wrapper->obj->GetComponent<LightComponent>();
|
||
if (compPtr)
|
||
{
|
||
LuaLightWrapper *lw = (LuaLightWrapper *)lua_newuserdata(L, sizeof(LuaLightWrapper));
|
||
lw->comp = compPtr.get();
|
||
luaL_getmetatable(L, LUA_LIGHT_MT);
|
||
lua_setmetatable(L, -2);
|
||
return 1;
|
||
}
|
||
lua_pushnil(L);
|
||
return 1;
|
||
}
|
||
if (strcmp(type, "SoundComponent") == 0)
|
||
{
|
||
auto compPtr = wrapper->obj->GetComponent<AudioPlayerComponent>();
|
||
if (compPtr)
|
||
{
|
||
LuaSoundWrapper *sw = (LuaSoundWrapper *)lua_newuserdata(L, sizeof(LuaSoundWrapper));
|
||
sw->comp = compPtr.get();
|
||
luaL_getmetatable(L, LUA_SOUND_MT);
|
||
lua_setmetatable(L, -2);
|
||
return 1;
|
||
}
|
||
lua_pushnil(L);
|
||
return 1;
|
||
}
|
||
|
||
Component *comp = GetComponentByName(wrapper->obj, type);
|
||
lua_pushlightuserdata(L, comp ? comp : nullptr);
|
||
return 1;
|
||
}
|
||
|
||
static AudioPlayerComponent *CheckSound(lua_State *L)
|
||
{
|
||
return ((LuaSoundWrapper *)luaL_checkudata(L, 1, LUA_SOUND_MT))->comp;
|
||
}
|
||
|
||
static int Lua_Sound_Play(lua_State *L)
|
||
{
|
||
CheckSound(L)->Play();
|
||
return 0;
|
||
}
|
||
|
||
static int Lua_Sound_Stop(lua_State *L)
|
||
{
|
||
CheckSound(L)->Stop();
|
||
return 0;
|
||
}
|
||
|
||
static int Lua_Sound_Pause(lua_State *L)
|
||
{
|
||
CheckSound(L)->Pause();
|
||
return 0;
|
||
}
|
||
|
||
static int Lua_Sound_SetVolume(lua_State *L)
|
||
{
|
||
float vol = static_cast<float>(luaL_checknumber(L, 2));
|
||
CheckSound(L)->SetVolume(vol);
|
||
return 0;
|
||
}
|
||
|
||
static int Lua_Sound_Index(lua_State *L)
|
||
{
|
||
luaL_getmetatable(L, LUA_SOUND_MT);
|
||
lua_pushvalue(L, 2);
|
||
lua_rawget(L, -2);
|
||
return 1;
|
||
}
|
||
|
||
static int Lua_Object_GetPosition(lua_State *L)
|
||
{
|
||
PROFILE_DEEP_SCOPE("Object::GetPosition");
|
||
auto *wrapper = (LuaObjectWrapper *)luaL_checkudata(L, 1, LUA_OBJECT_MT);
|
||
glm::vec2 pos = wrapper->obj->GetLocalPosition();
|
||
LuaVector2 *vec = (LuaVector2 *)lua_newuserdata(L, sizeof(LuaVector2));
|
||
vec->x = pos.x;
|
||
vec->y = pos.y;
|
||
luaL_getmetatable(L, LUA_VECTOR2_MT);
|
||
lua_setmetatable(L, -2);
|
||
return 1;
|
||
}
|
||
|
||
static int Lua_Object_SetPosition(lua_State *L)
|
||
{
|
||
PROFILE_DEEP_SCOPE("Object::SetPosition");
|
||
auto *wrapper = (LuaObjectWrapper *)luaL_checkudata(L, 1, LUA_OBJECT_MT);
|
||
auto *vec = (LuaVector2 *)luaL_checkudata(L, 2, LUA_VECTOR2_MT);
|
||
wrapper->obj->SetLocalPosition({vec->x, vec->y});
|
||
return 0;
|
||
}
|
||
|
||
static int Lua_Object_Index(lua_State *L)
|
||
{
|
||
PROFILE_DEEP_SCOPE("Object::__index");
|
||
const char *key = luaL_checkstring(L, 2);
|
||
lua_getfield(L, lua_upvalueindex(1), key);
|
||
return 1;
|
||
}
|
||
|
||
void RegisterObjectType(lua_State *L)
|
||
{
|
||
PROFILE_DEEP_SCOPE("RegisterObjectType");
|
||
|
||
luaL_newmetatable(L, LUA_OBJECT_MT);
|
||
lua_newtable(L);
|
||
lua_pushcfunction(L, Lua_Object_GetComponent);
|
||
lua_setfield(L, -2, "GetComponent");
|
||
lua_pushcfunction(L, Lua_Object_GetPosition);
|
||
lua_setfield(L, -2, "GetPosition");
|
||
lua_pushcfunction(L, Lua_Object_SetPosition);
|
||
lua_setfield(L, -2, "SetPosition");
|
||
lua_pushcclosure(L, Lua_Object_Index, 1);
|
||
lua_setfield(L, -2, "__index");
|
||
lua_pop(L, 1);
|
||
}
|
||
|
||
static void PushObject(lua_State *L, Object *obj)
|
||
{
|
||
PROFILE_DEEP_SCOPE("PushObject");
|
||
auto *wrapper = (LuaObjectWrapper *)lua_newuserdata(L, sizeof(LuaObjectWrapper));
|
||
wrapper->obj = obj;
|
||
luaL_getmetatable(L, LUA_OBJECT_MT);
|
||
lua_setmetatable(L, -2);
|
||
}
|
||
|
||
static int Lua_GetObjectByTag(lua_State *L)
|
||
{
|
||
PROFILE_DEEP_SCOPE("GetObjectByTag");
|
||
const char *name = luaL_checkstring(L, 1);
|
||
for (const auto &obj : objects)
|
||
{
|
||
auto found = FindByTagRecursive(obj, name);
|
||
if (found)
|
||
{
|
||
PushObject(L, found.get());
|
||
return 1;
|
||
}
|
||
}
|
||
lua_pushnil(L);
|
||
return 1;
|
||
}
|
||
|
||
static LightComponent *CheckLight(lua_State *L)
|
||
{
|
||
return ((LuaLightWrapper *)luaL_checkudata(L, 1, LUA_LIGHT_MT))->comp;
|
||
}
|
||
|
||
static int Lua_Light_GetColor(lua_State *L)
|
||
{
|
||
auto *light = CheckLight(L);
|
||
glm::vec3 c = light->GetColor();
|
||
LuaVector3 *vec = (LuaVector3 *)lua_newuserdata(L, sizeof(LuaVector3));
|
||
vec->x = c.x;
|
||
vec->y = c.y;
|
||
vec->z = c.z;
|
||
luaL_getmetatable(L, LUA_VECTOR3_MT);
|
||
lua_setmetatable(L, -2);
|
||
return 1;
|
||
}
|
||
|
||
static int Lua_Light_SetColor(lua_State *L)
|
||
{
|
||
auto *light = CheckLight(L);
|
||
core::types::Vec3 color = GetLuaVector3(L, 2);
|
||
light->SetColor(color);
|
||
return 0;
|
||
}
|
||
|
||
static int Lua_Light_GetIntensity(lua_State *L)
|
||
{
|
||
lua_pushnumber(L, CheckLight(L)->GetIntensity());
|
||
return 1;
|
||
}
|
||
|
||
static int Lua_Light_SetIntensity(lua_State *L)
|
||
{
|
||
CheckLight(L)->SetIntensity(static_cast<float>(luaL_checknumber(L, 2)));
|
||
return 0;
|
||
}
|
||
|
||
static int Lua_Light_GetRadius(lua_State *L)
|
||
{
|
||
lua_pushnumber(L, CheckLight(L)->GetRadius());
|
||
return 1;
|
||
}
|
||
|
||
static int Lua_Light_SetRadius(lua_State *L)
|
||
{
|
||
CheckLight(L)->SetRadius(static_cast<float>(luaL_checknumber(L, 2)));
|
||
return 0;
|
||
}
|
||
|
||
static int Lua_Light_GetFalloff(lua_State *L)
|
||
{
|
||
lua_pushnumber(L, CheckLight(L)->GetFalloff());
|
||
return 1;
|
||
}
|
||
|
||
static int Lua_Light_SetFalloff(lua_State *L)
|
||
{
|
||
CheckLight(L)->SetFalloff(static_cast<float>(luaL_checknumber(L, 2)));
|
||
return 0;
|
||
}
|
||
|
||
static int Lua_Light_GetType(lua_State *L)
|
||
{
|
||
lua_pushinteger(L, CheckLight(L)->GetType());
|
||
return 1;
|
||
}
|
||
|
||
static int Lua_Light_SetType(lua_State *L)
|
||
{
|
||
CheckLight(L)->SetType(luaL_checkinteger(L, 2));
|
||
return 0;
|
||
}
|
||
|
||
static int Lua_Light_Index(lua_State *L)
|
||
{
|
||
luaL_getmetatable(L, LUA_LIGHT_MT);
|
||
lua_pushvalue(L, 2);
|
||
lua_rawget(L, -2);
|
||
return 1;
|
||
}
|
||
|
||
void RegisterSoundType(lua_State *L)
|
||
{
|
||
luaL_newmetatable(L, LUA_SOUND_MT);
|
||
|
||
lua_pushcfunction(L, Lua_Sound_Play);
|
||
lua_setfield(L, -2, "Play");
|
||
lua_pushcfunction(L, Lua_Sound_Stop);
|
||
lua_setfield(L, -2, "Stop");
|
||
lua_pushcfunction(L, Lua_Sound_Pause);
|
||
lua_setfield(L, -2, "Pause");
|
||
lua_pushcfunction(L, Lua_Sound_SetVolume);
|
||
lua_setfield(L, -2, "SetVolume");
|
||
|
||
lua_pushcfunction(L, Lua_Sound_Index);
|
||
lua_setfield(L, -2, "__index");
|
||
|
||
lua_pop(L, 1);
|
||
}
|
||
|
||
void RegisterLightType(lua_State *L)
|
||
{
|
||
luaL_newmetatable(L, LUA_LIGHT_MT);
|
||
|
||
lua_pushcfunction(L, Lua_Light_GetColor);
|
||
lua_setfield(L, -2, "GetColor");
|
||
lua_pushcfunction(L, Lua_Light_SetColor);
|
||
lua_setfield(L, -2, "SetColor");
|
||
|
||
lua_pushcfunction(L, Lua_Light_GetIntensity);
|
||
lua_setfield(L, -2, "GetIntensity");
|
||
lua_pushcfunction(L, Lua_Light_SetIntensity);
|
||
lua_setfield(L, -2, "SetIntensity");
|
||
|
||
lua_pushcfunction(L, Lua_Light_GetRadius);
|
||
lua_setfield(L, -2, "GetRadius");
|
||
lua_pushcfunction(L, Lua_Light_SetRadius);
|
||
lua_setfield(L, -2, "SetRadius");
|
||
|
||
lua_pushcfunction(L, Lua_Light_GetFalloff);
|
||
lua_setfield(L, -2, "GetFalloff");
|
||
lua_pushcfunction(L, Lua_Light_SetFalloff);
|
||
lua_setfield(L, -2, "SetFalloff");
|
||
|
||
lua_pushcfunction(L, Lua_Light_GetType);
|
||
lua_setfield(L, -2, "GetType");
|
||
lua_pushcfunction(L, Lua_Light_SetType);
|
||
lua_setfield(L, -2, "SetType");
|
||
|
||
lua_pushcfunction(L, Lua_Light_Index);
|
||
lua_setfield(L, -2, "__index");
|
||
|
||
lua_pop(L, 1);
|
||
}
|
||
|
||
// --- AnimationComponent Lua bindings ---
|
||
static AnimationComponent *CheckAnimationComponent(lua_State *L, int idx)
|
||
{
|
||
void *ptr = lua_touserdata(L, idx);
|
||
return static_cast<AnimationComponent *>(ptr);
|
||
}
|
||
|
||
static int Lua_Animation_GetCurrentFrame(lua_State *L)
|
||
{
|
||
PROFILE_DEEP_SCOPE("Animation::GetCurrentFrame");
|
||
AnimationComponent *anim = CheckAnimationComponent(L, 1);
|
||
lua_pushinteger(L, anim ? anim->GetCurrentFrame() : -1);
|
||
return 1;
|
||
}
|
||
|
||
static int Lua_Animation_SetCurrentFrame(lua_State *L)
|
||
{
|
||
PROFILE_DEEP_SCOPE("Animation::SetCurrentFrame");
|
||
AnimationComponent *anim = CheckAnimationComponent(L, 1);
|
||
int frame = luaL_checkinteger(L, 2);
|
||
if (anim)
|
||
anim->SetFrame(frame);
|
||
return 0;
|
||
}
|
||
|
||
static int Lua_Animation_IsPlaying(lua_State *L)
|
||
{
|
||
PROFILE_DEEP_SCOPE("Animation::IsPlaying");
|
||
AnimationComponent *anim = CheckAnimationComponent(L, 1);
|
||
lua_pushboolean(L, anim ? anim->IsPlaying() : false);
|
||
return 1;
|
||
}
|
||
|
||
static int Lua_Animation_SetPlaying(lua_State *L)
|
||
{
|
||
PROFILE_DEEP_SCOPE("Animation::SetPlaying");
|
||
AnimationComponent *anim = CheckAnimationComponent(L, 1);
|
||
bool play = lua_toboolean(L, 2);
|
||
if (anim)
|
||
anim->SetPlaying(play);
|
||
return 0;
|
||
}
|
||
|
||
static int Lua_Animation_GetStartFrame(lua_State *L)
|
||
{
|
||
PROFILE_DEEP_SCOPE("Animation::GetStartFrame");
|
||
AnimationComponent *anim = CheckAnimationComponent(L, 1);
|
||
lua_pushinteger(L, anim ? anim->GetStartFrame() : 0);
|
||
return 1;
|
||
}
|
||
|
||
static int Lua_Animation_SetStartFrame(lua_State *L)
|
||
{
|
||
PROFILE_DEEP_SCOPE("Animation::SetStartFrame");
|
||
AnimationComponent *anim = CheckAnimationComponent(L, 1);
|
||
int start = luaL_checkinteger(L, 2);
|
||
if (anim)
|
||
anim->SetStartFrame(start);
|
||
return 0;
|
||
}
|
||
|
||
static int Lua_Animation_GetEndFrame(lua_State *L)
|
||
{
|
||
PROFILE_DEEP_SCOPE("Animation::GetEndFrame");
|
||
AnimationComponent *anim = CheckAnimationComponent(L, 1);
|
||
lua_pushinteger(L, anim ? anim->GetEndFrame() : 0);
|
||
return 1;
|
||
}
|
||
|
||
static int Lua_Animation_SetEndFrame(lua_State *L)
|
||
{
|
||
PROFILE_DEEP_SCOPE("Animation::SetEndFrame");
|
||
AnimationComponent *anim = CheckAnimationComponent(L, 1);
|
||
int end = luaL_checkinteger(L, 2);
|
||
if (anim)
|
||
anim->SetEndFrame(end);
|
||
return 0;
|
||
}
|
||
|
||
static int Lua_Animation_Index(lua_State *L)
|
||
{
|
||
// stack: [1]=userdata, [2]=key
|
||
luaL_getmetatable(L, LUA_ANIMATION_MT);
|
||
lua_pushvalue(L, 2);
|
||
lua_rawget(L, -2);
|
||
return 1;
|
||
}
|
||
|
||
static int l_ShortNumberFormat(lua_State *L)
|
||
{
|
||
double num = 0.0;
|
||
if (lua_isnumber(L, 1))
|
||
num = lua_tonumber(L, 1);
|
||
else if (lua_isstring(L, 1))
|
||
num = std::strtod(lua_tostring(L, 1), nullptr);
|
||
else
|
||
return luaL_error(L, "Expected number or string as first argument");
|
||
|
||
int precision = 0;
|
||
if (lua_gettop(L) >= 2 && lua_isnumber(L, 2))
|
||
{
|
||
precision = (int)lua_tointeger(L, 2);
|
||
if (precision < 0)
|
||
precision = 0;
|
||
if (precision > 9)
|
||
precision = 9;
|
||
}
|
||
|
||
bool negative = false;
|
||
if (num < 0)
|
||
{
|
||
negative = true;
|
||
num = -num;
|
||
}
|
||
|
||
static const char *suffixes[] = {"", "k", "M", "B", "T", "Q", "E", "Z", "Y"};
|
||
int suffixIndex = 0;
|
||
while (num >= 1000.0 && suffixIndex < 8)
|
||
{
|
||
num /= 1000.0;
|
||
++suffixIndex;
|
||
}
|
||
|
||
char fmt[16];
|
||
std::snprintf(fmt, sizeof(fmt), "%%s%%.%df%%s", precision);
|
||
|
||
char out[64];
|
||
std::snprintf(out, sizeof(out), fmt, negative ? "-" : "", num, suffixes[suffixIndex]);
|
||
|
||
lua_pushstring(L, out);
|
||
return 1;
|
||
}
|
||
|
||
static int l_HSVtoRGB(lua_State *L)
|
||
{
|
||
float h = luaL_checknumber(L, 1);
|
||
float s = luaL_checknumber(L, 2);
|
||
float v = luaL_checknumber(L, 3);
|
||
|
||
float c = v * s;
|
||
float x = c * (1 - std::fabs(fmod(h / 60.0f, 2) - 1));
|
||
float m = v - c;
|
||
float r = 0, g = 0, b = 0;
|
||
|
||
if (h < 60) { r = c; g = x; }
|
||
else if (h < 120) { r = x; g = c; }
|
||
else if (h < 180) { g = c; b = x; }
|
||
else if (h < 240) { g = x; b = c; }
|
||
else if (h < 300) { r = x; b = c; }
|
||
else { r = c; b = x; }
|
||
|
||
PushVector3(L, r + m, g + m, b + m);
|
||
return 1;
|
||
}
|
||
|
||
static int l_RGBtoHSV(lua_State *L)
|
||
{
|
||
float r = luaL_checknumber(L, 1);
|
||
float g = luaL_checknumber(L, 2);
|
||
float b = luaL_checknumber(L, 3);
|
||
|
||
float max = std::fmax(r, std::fmax(g, b));
|
||
float min = std::fmin(r, std::fmin(g, b));
|
||
float d = max - min;
|
||
|
||
float h = 0.0f;
|
||
if (d != 0)
|
||
{
|
||
if (max == r) h = fmod((g - b) / d, 6.0f);
|
||
else if (max == g) h = ((b - r) / d) + 2.0f;
|
||
else h = ((r - g) / d) + 4.0f;
|
||
h *= 60.0f;
|
||
if (h < 0) h += 360.0f;
|
||
}
|
||
|
||
float s = (max == 0) ? 0 : d / max;
|
||
float v = max;
|
||
|
||
PushVector3(L, h, s, v);
|
||
return 1;
|
||
}
|
||
|
||
|
||
void RegisterUtils(lua_State *L)
|
||
{
|
||
lua_newtable(L);
|
||
lua_pushcfunction(L, l_ShortNumberFormat);
|
||
lua_setfield(L, -2, "ShortNumberFormat");
|
||
lua_pushcfunction(L, l_HSVtoRGB);
|
||
lua_setfield(L, -2, "HSVtoRGB");
|
||
lua_pushcfunction(L, l_RGBtoHSV);
|
||
lua_setfield(L, -2, "RGBtoHSV");
|
||
lua_setfield(L, -2, "Utils"); // Engine.Utils
|
||
}
|
||
|
||
static int keycode_loader(lua_State *L)
|
||
{
|
||
lua_newtable(L);
|
||
|
||
// Printable
|
||
SET_KC(Space)
|
||
SET_KC(Apostrophe)
|
||
SET_KC(Comma)
|
||
SET_KC(Minus)
|
||
SET_KC(Period)
|
||
SET_KC(Slash)
|
||
SET_KC(Key0)
|
||
SET_KC(Key1)
|
||
SET_KC(Key2)
|
||
SET_KC(Key3)
|
||
SET_KC(Key4)
|
||
SET_KC(Key5)
|
||
SET_KC(Key6)
|
||
SET_KC(Key7)
|
||
SET_KC(Key8)
|
||
SET_KC(Key9)
|
||
SET_KC(Semicolon)
|
||
SET_KC(Equal)
|
||
SET_KC(A)
|
||
SET_KC(B)
|
||
SET_KC(C)
|
||
SET_KC(D)
|
||
SET_KC(E)
|
||
SET_KC(F)
|
||
SET_KC(G)
|
||
SET_KC(H)
|
||
SET_KC(I)
|
||
SET_KC(J)
|
||
SET_KC(K)
|
||
SET_KC(L)
|
||
SET_KC(M)
|
||
SET_KC(N)
|
||
SET_KC(O)
|
||
SET_KC(P)
|
||
SET_KC(Q)
|
||
SET_KC(R)
|
||
SET_KC(S)
|
||
SET_KC(T)
|
||
SET_KC(U)
|
||
SET_KC(V)
|
||
SET_KC(W)
|
||
SET_KC(X)
|
||
SET_KC(Y)
|
||
SET_KC(Z)
|
||
SET_KC(LeftBracket)
|
||
SET_KC(Backslash)
|
||
SET_KC(RightBracket)
|
||
SET_KC(GraveAccent)
|
||
SET_KC(World1)
|
||
SET_KC(World2)
|
||
|
||
// Function & Navigation
|
||
SET_KC(Escape)
|
||
SET_KC(Enter)
|
||
SET_KC(Tab)
|
||
SET_KC(Backspace)
|
||
SET_KC(Insert)
|
||
SET_KC(Delete)
|
||
SET_KC(Right)
|
||
SET_KC(Left)
|
||
SET_KC(Down)
|
||
SET_KC(Up)
|
||
SET_KC(PageUp)
|
||
SET_KC(PageDown)
|
||
SET_KC(Home)
|
||
SET_KC(End)
|
||
SET_KC(CapsLock)
|
||
SET_KC(ScrollLock)
|
||
SET_KC(NumLock)
|
||
SET_KC(PrintScreen)
|
||
SET_KC(Pause)
|
||
SET_KC(F1)
|
||
SET_KC(F2)
|
||
SET_KC(F3)
|
||
SET_KC(F4)
|
||
SET_KC(F5)
|
||
SET_KC(F6)
|
||
SET_KC(F7)
|
||
SET_KC(F8)
|
||
SET_KC(F9)
|
||
SET_KC(F10)
|
||
SET_KC(F11)
|
||
SET_KC(F12)
|
||
SET_KC(F13)
|
||
SET_KC(F14)
|
||
SET_KC(F15)
|
||
SET_KC(F16)
|
||
SET_KC(F17)
|
||
SET_KC(F18)
|
||
SET_KC(F19)
|
||
SET_KC(F20)
|
||
SET_KC(F21)
|
||
SET_KC(F22)
|
||
SET_KC(F23)
|
||
SET_KC(F24)
|
||
SET_KC(F25)
|
||
|
||
// Keypad
|
||
SET_KC(KP0)
|
||
SET_KC(KP1)
|
||
SET_KC(KP2)
|
||
SET_KC(KP3)
|
||
SET_KC(KP4)
|
||
SET_KC(KP5)
|
||
SET_KC(KP6)
|
||
SET_KC(KP7)
|
||
SET_KC(KP8)
|
||
SET_KC(KP9)
|
||
SET_KC(KPDecimal)
|
||
SET_KC(KPDivide)
|
||
SET_KC(KPMultiply)
|
||
SET_KC(KPSubtract)
|
||
SET_KC(KPAdd)
|
||
SET_KC(KPEnter)
|
||
SET_KC(KPEqual)
|
||
|
||
// Modifiers
|
||
SET_KC(LeftShift)
|
||
SET_KC(LeftControl)
|
||
SET_KC(LeftAlt)
|
||
SET_KC(LeftSuper)
|
||
SET_KC(RightShift)
|
||
SET_KC(RightControl)
|
||
SET_KC(RightAlt)
|
||
SET_KC(RightSuper)
|
||
SET_KC(Menu)
|
||
|
||
#undef SET_KC
|
||
|
||
lua_setglobal(L, "Keycode");
|
||
|
||
return 1;
|
||
}
|
||
|
||
static int Lua_KeyDown(lua_State *L)
|
||
{
|
||
// match your other deep‐profile scopes
|
||
PROFILE_DEEP_SCOPE("Engine::KeyDown");
|
||
|
||
// grab the integer keycode from Lua
|
||
lua_Integer code = luaL_checkinteger(L, 1);
|
||
|
||
// cast into your strongly‐typed enum and query
|
||
bool down = Input::IsKeyDown(static_cast<Keycode>(code));
|
||
|
||
// return a Lua boolean
|
||
lua_pushboolean(L, down);
|
||
return 1;
|
||
}
|
||
|
||
static core::types::Vec3 GetLuaVector3(lua_State *L, int index)
|
||
{
|
||
if (luaL_testudata(L, index, LUA_VECTOR3_MT))
|
||
{
|
||
auto *vec = static_cast<LuaVector3 *>(lua_touserdata(L, index));
|
||
return core::types::Vec3(vec->x, vec->y, vec->z);
|
||
}
|
||
else if (lua_istable(L, index))
|
||
{
|
||
core::types::Vec3 result;
|
||
|
||
static const char *keys1[3] = {"x", "y", "z"};
|
||
static const char *keys2[3] = {"r", "g", "b"};
|
||
|
||
const char **keys = keys1;
|
||
|
||
lua_pushstring(L, "x");
|
||
lua_rawget(L, index);
|
||
if (lua_isnil(L, -1))
|
||
{
|
||
keys = keys2;
|
||
lua_pop(L, 1);
|
||
}
|
||
|
||
for (int i = 0; i < 3; ++i)
|
||
{
|
||
lua_pushstring(L, keys[i]);
|
||
lua_rawget(L, index);
|
||
result[i] = static_cast<float>(lua_tonumber(L, -1));
|
||
lua_pop(L, 1);
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
luaL_error(L, "Expected Vector3 userdata or table with x/y/z or r/g/b");
|
||
return core::types::Vec3(0, 0, 0);
|
||
}
|
||
|
||
void PushVector3(lua_State* L, float x, float y, float z)
|
||
{
|
||
LuaVector3* vec = (LuaVector3*)lua_newuserdata(L, sizeof(LuaVector3));
|
||
vec->x = x;
|
||
vec->y = y;
|
||
vec->z = z;
|
||
luaL_setmetatable(L, LUA_VECTOR3_MT);
|
||
}
|
||
|
||
|
||
|
||
static int Lua_Vector3_New(lua_State *L)
|
||
{
|
||
PROFILE_DEEP_SCOPE("Vector3()");
|
||
LuaVector3 *vec = (LuaVector3 *)lua_newuserdata(L, sizeof(LuaVector3));
|
||
int nargs = lua_gettop(L);
|
||
vec->x = nargs >= 1 ? static_cast<float>(lua_tonumber(L, 1)) : 0.0f;
|
||
vec->y = nargs >= 2 ? static_cast<float>(lua_tonumber(L, 2)) : 0.0f;
|
||
vec->z = nargs >= 3 ? static_cast<float>(lua_tonumber(L, 3)) : 0.0f;
|
||
luaL_setmetatable(L, LUA_VECTOR3_MT);
|
||
return 1;
|
||
}
|
||
|
||
static int Lua_Vector3_Index(lua_State *L)
|
||
{
|
||
auto *vec = (LuaVector3 *)luaL_checkudata(L, 1, LUA_VECTOR3_MT);
|
||
const char *key = lua_tostring(L, 2);
|
||
if (!strcmp(key, "x"))
|
||
lua_pushnumber(L, vec->x);
|
||
else if (!strcmp(key, "y"))
|
||
lua_pushnumber(L, vec->y);
|
||
else if (!strcmp(key, "z"))
|
||
lua_pushnumber(L, vec->z);
|
||
else
|
||
lua_pushnil(L);
|
||
return 1;
|
||
}
|
||
|
||
static int Lua_Vector3_NewIndex(lua_State *L)
|
||
{
|
||
auto *vec = (LuaVector3 *)luaL_checkudata(L, 1, LUA_VECTOR3_MT);
|
||
const char *key = lua_tostring(L, 2);
|
||
float value = static_cast<float>(luaL_checknumber(L, 3));
|
||
if (!strcmp(key, "x"))
|
||
vec->x = value;
|
||
else if (!strcmp(key, "y"))
|
||
vec->y = value;
|
||
else if (!strcmp(key, "z"))
|
||
vec->z = value;
|
||
return 0;
|
||
}
|
||
|
||
void RegisterVector3Type(lua_State *L)
|
||
{
|
||
luaL_newmetatable(L, LUA_VECTOR3_MT);
|
||
lua_pushcfunction(L, Lua_Vector3_Index);
|
||
lua_setfield(L, -2, "__index");
|
||
lua_pushcfunction(L, Lua_Vector3_NewIndex);
|
||
lua_setfield(L, -2, "__newindex");
|
||
lua_pop(L, 1);
|
||
|
||
lua_pushcfunction(L, Lua_Vector3_New);
|
||
lua_setglobal(L, "Vector3");
|
||
}
|
||
|
||
static int Lua_Vector2_New(lua_State *L)
|
||
{
|
||
PROFILE_DEEP_SCOPE("Vector2()");
|
||
LuaVector2 *vec = static_cast<LuaVector2 *>(lua_newuserdata(L, sizeof(LuaVector2)));
|
||
int nargs = lua_gettop(L);
|
||
vec->x = nargs >= 1 ? static_cast<float>(lua_tonumber(L, 1)) : 0.0f;
|
||
vec->y = nargs >= 2 ? static_cast<float>(lua_tonumber(L, 2)) : 0.0f;
|
||
luaL_setmetatable(L, LUA_VECTOR2_MT);
|
||
return 1;
|
||
}
|
||
|
||
static int Lua_Vector2_Index(lua_State *L)
|
||
{
|
||
PROFILE_DEEP_SCOPE("Vector2::index");
|
||
auto *vec = static_cast<LuaVector2 *>(luaL_checkudata(L, 1, LUA_VECTOR2_MT));
|
||
const char *key = lua_tostring(L, 2); // safer and faster than luaL_checkstring for profiling edge cases
|
||
|
||
// Fast string comparison
|
||
if (key[0] == 'x' && key[1] == '\0')
|
||
{
|
||
lua_pushnumber(L, vec->x);
|
||
}
|
||
else if (key[0] == 'y' && key[1] == '\0')
|
||
{
|
||
lua_pushnumber(L, vec->y);
|
||
}
|
||
else
|
||
{
|
||
lua_pushnil(L);
|
||
}
|
||
return 1;
|
||
}
|
||
|
||
static int Lua_Vector2_NewIndex(lua_State *L)
|
||
{
|
||
PROFILE_DEEP_SCOPE("Vector2::newindex");
|
||
auto *vec = static_cast<LuaVector2 *>(luaL_checkudata(L, 1, LUA_VECTOR2_MT));
|
||
const char *key = lua_tostring(L, 2);
|
||
float value = static_cast<float>(luaL_checknumber(L, 3));
|
||
|
||
if (key[0] == 'x' && key[1] == '\0')
|
||
{
|
||
vec->x = value;
|
||
}
|
||
else if (key[0] == 'y' && key[1] == '\0')
|
||
{
|
||
vec->y = value;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
void RegisterVector2Type(lua_State *L)
|
||
{
|
||
PROFILE_DEEP_SCOPE("RegisterVector2Type");
|
||
|
||
luaL_newmetatable(L, LUA_VECTOR2_MT);
|
||
lua_pushcfunction(L, Lua_Vector2_Index);
|
||
lua_setfield(L, -2, "__index");
|
||
|
||
lua_pushcfunction(L, Lua_Vector2_NewIndex);
|
||
lua_setfield(L, -2, "__newindex");
|
||
|
||
lua_pop(L, 1);
|
||
|
||
lua_pushcfunction(L, Lua_Vector2_New);
|
||
lua_setglobal(L, "Vector2");
|
||
}
|
||
|
||
// Register the AnimationComponent metatable
|
||
void RegisterAnimationType(lua_State *L)
|
||
{
|
||
PROFILE_DEEP_SCOPE("RegisterAnimationType");
|
||
luaL_newmetatable(L, LUA_ANIMATION_MT);
|
||
// methods
|
||
lua_pushcfunction(L, Lua_Animation_GetCurrentFrame);
|
||
lua_setfield(L, -2, "GetCurrentFrame");
|
||
lua_pushcfunction(L, Lua_Animation_SetCurrentFrame);
|
||
lua_setfield(L, -2, "SetCurrentFrame");
|
||
lua_pushcfunction(L, Lua_Animation_IsPlaying);
|
||
lua_setfield(L, -2, "IsPlaying");
|
||
lua_pushcfunction(L, Lua_Animation_SetPlaying);
|
||
lua_setfield(L, -2, "SetPlaying");
|
||
lua_pushcfunction(L, Lua_Animation_GetStartFrame);
|
||
lua_setfield(L, -2, "GetStartFrame");
|
||
lua_pushcfunction(L, Lua_Animation_SetStartFrame);
|
||
lua_setfield(L, -2, "SetStartFrame");
|
||
lua_pushcfunction(L, Lua_Animation_GetEndFrame);
|
||
lua_setfield(L, -2, "GetEndFrame");
|
||
lua_pushcfunction(L, Lua_Animation_SetEndFrame);
|
||
lua_setfield(L, -2, "SetEndFrame");
|
||
|
||
// __index
|
||
lua_pushcfunction(L, Lua_Animation_Index);
|
||
lua_setfield(L, -2, "__index");
|
||
lua_pop(L, 1);
|
||
}
|
||
|
||
void ScriptComponent::RegisterEngineBindings()
|
||
{
|
||
PROFILE_DEEP_SCOPE("RegisterEngineBindings");
|
||
|
||
lua_newtable(L);
|
||
lua_pushcfunction(L, Lua_LogInfo);
|
||
lua_setfield(L, -2, "LogInfo");
|
||
lua_pushcfunction(L, Lua_LogInfo);
|
||
lua_setfield(L, -2, "Log");
|
||
lua_pushcfunction(L, Lua_LogError);
|
||
lua_setfield(L, -2, "LogError");
|
||
lua_pushcfunction(L, Lua_LogDebug);
|
||
lua_setfield(L, -2, "LogDebug");
|
||
lua_pushcfunction(L, Lua_GetObjectByTag);
|
||
lua_setfield(L, -2, "GetObjectByTag");
|
||
lua_pushcfunction(L, Lua_DebugLua);
|
||
lua_setfield(L, -2, "DebugLua");
|
||
lua_pushcfunction(L, Lua_KeyDown);
|
||
lua_setfield(L, -2, "KeyDown");
|
||
|
||
lua_pushcfunction(L, Lua_GetMousePos);
|
||
lua_setfield(L, -2, "GetMousePos");
|
||
|
||
lua_pushcfunction(L, Lua_SetGlobal);
|
||
lua_setfield(L, -2, "SetGlobal");
|
||
lua_pushcfunction(L, Lua_GetGlobal);
|
||
lua_setfield(L, -2, "GetGlobal");
|
||
|
||
RegisterUtils(L); // called here so its under Engine table
|
||
|
||
lua_setglobal(L, "Engine");
|
||
}
|
||
|
||
void ScriptComponent::ReloadScript()
|
||
{
|
||
PROFILE_DEEP_SCOPE("ScriptComponent::ReloadScript");
|
||
if (scriptPath.empty())
|
||
return;
|
||
if (L)
|
||
{
|
||
ScriptCore::UnregisterState(L);
|
||
lua_close(L);
|
||
}
|
||
|
||
L = luaL_newstate();
|
||
*(ScriptComponent **)lua_getextraspace(L) = this;
|
||
luaL_openlibs(L);
|
||
lua_sethook(L, Hook, LUA_MASKCALL | LUA_MASKRET, 0);
|
||
|
||
ScriptCore::RegisterState(L, scriptPath, GetFilenameNoExtension(scriptPath), this);
|
||
|
||
RegisterObjectType(L);
|
||
RegisterVector2Type(L);
|
||
RegisterVector3Type(L);
|
||
RegisterAnimationType(L);
|
||
RegisterEngineBindings();
|
||
RegisterLightType(L);
|
||
RegisterSoundType(L);
|
||
|
||
keycode_loader(L);
|
||
|
||
Logger::LogVerbose("[Lua] Loading Script from file.");
|
||
if (luaL_dofile(L, scriptPath.c_str()))
|
||
{
|
||
Logger::LogError("[Lua] %s", lua_tostring(L, -1));
|
||
RecoverableError("Failed to load Lua script: " + scriptPath, Create::Exceptions::ComponentLoad).Handle();
|
||
return;
|
||
}
|
||
}
|
||
|
||
void ScriptComponent::OnUpdate(float dt)
|
||
{
|
||
PROFILE_DEEP_SCOPE("ScriptComponent::OnUpdate");
|
||
|
||
if (!L)
|
||
return;
|
||
lua_getglobal(L, "OnUpdate");
|
||
if (lua_isfunction(L, -1))
|
||
{
|
||
lua_pushnumber(L, dt);
|
||
if (lua_pcall(L, 1, 0, 0) != LUA_OK)
|
||
{
|
||
Logger::LogError("[Lua] %s", lua_tostring(L, -1));
|
||
RecoverableError("OnUpdate failed in: " + scriptPath, Create::Exceptions::ComponentLoad).Handle();
|
||
}
|
||
}
|
||
else
|
||
lua_pop(L, 1);
|
||
}
|
||
|
||
void ScriptComponent::Save(YAML::Emitter &out) const
|
||
{
|
||
out << YAML::BeginMap;
|
||
out << YAML::Key << "type" << YAML::Value << "ScriptComponent";
|
||
out << YAML::Key << "scriptPath" << YAML::Value << scriptPath;
|
||
out << YAML::EndMap;
|
||
}
|
||
|
||
void ScriptComponent::Load(const YAML::Node &node)
|
||
{
|
||
try
|
||
{
|
||
if (!node["scriptPath"])
|
||
{
|
||
RecoverableError("Missing 'scriptPath' in ScriptComponent", Create::Exceptions::MissingField).Handle();
|
||
return;
|
||
}
|
||
|
||
scriptPath = node["scriptPath"].as<std::string>();
|
||
ReloadScript();
|
||
}
|
||
catch (const YAML::Exception &e)
|
||
{
|
||
RecoverableError("YAML error in ScriptComponent::Load: " + std::string(e.what()), Create::Exceptions::ComponentLoad).Handle();
|
||
}
|
||
} |