From 2a9667feca8e8223ba46f677bb979cdb227ad740 Mon Sep 17 00:00:00 2001 From: Dominic Masters Date: Tue, 14 Apr 2026 16:36:50 -0500 Subject: [PATCH] Module updating --- assets/entity/test.lua | 14 + src/dusk/engine/engine.c | 22 +- src/dusk/entity/component/CMakeLists.txt | 3 +- .../entity/component/display/entitycamera.c | 59 +++ .../entity/component/display/entitycamera.h | 69 ++++ .../entity/component/physics/entityphysics.c | 38 ++ .../entity/component/physics/entityphysics.h | 50 +++ .../entity/component/script/CMakeLists.txt | 10 + .../entity/component/script/entityscript.c | 61 +++ .../entity/component/script/entityscript.h | 64 +++ src/dusk/entity/componentlist.h | 4 +- src/dusk/script/module/CMakeLists.txt | 3 +- src/dusk/script/module/entity/CMakeLists.txt | 16 + src/dusk/script/module/entity/moduleentity.c | 108 +++++ src/dusk/script/module/entity/moduleentity.h | 44 ++ .../script/module/entity/moduleentitycamera.c | 228 +++++++++++ .../script/module/entity/moduleentitycamera.h | 62 +++ .../module/entity/moduleentitymaterial.c | 209 ++++++++++ .../module/entity/moduleentitymaterial.h | 52 +++ .../script/module/entity/moduleentitymesh.c | 70 ++++ .../script/module/entity/moduleentitymesh.h | 27 ++ .../module/entity/moduleentityphysics.c | 383 ++++++++++++++++++ .../module/entity/moduleentityphysics.h | 64 +++ .../module/entity/moduleentityposition.c | 172 ++++++++ .../module/entity/moduleentityposition.h | 53 +++ .../script/module/entity/moduleentityscript.c | 77 ++++ .../script/module/entity/moduleentityscript.h | 29 ++ src/dusk/script/scriptmodule.c | 14 + 28 files changed, 1985 insertions(+), 20 deletions(-) create mode 100644 assets/entity/test.lua create mode 100644 src/dusk/entity/component/script/CMakeLists.txt create mode 100644 src/dusk/entity/component/script/entityscript.c create mode 100644 src/dusk/entity/component/script/entityscript.h create mode 100644 src/dusk/script/module/entity/CMakeLists.txt create mode 100644 src/dusk/script/module/entity/moduleentity.c create mode 100644 src/dusk/script/module/entity/moduleentity.h create mode 100644 src/dusk/script/module/entity/moduleentitycamera.c create mode 100644 src/dusk/script/module/entity/moduleentitycamera.h create mode 100644 src/dusk/script/module/entity/moduleentitymaterial.c create mode 100644 src/dusk/script/module/entity/moduleentitymaterial.h create mode 100644 src/dusk/script/module/entity/moduleentitymesh.c create mode 100644 src/dusk/script/module/entity/moduleentitymesh.h create mode 100644 src/dusk/script/module/entity/moduleentityphysics.c create mode 100644 src/dusk/script/module/entity/moduleentityphysics.h create mode 100644 src/dusk/script/module/entity/moduleentityposition.c create mode 100644 src/dusk/script/module/entity/moduleentityposition.h create mode 100644 src/dusk/script/module/entity/moduleentityscript.c create mode 100644 src/dusk/script/module/entity/moduleentityscript.h diff --git a/assets/entity/test.lua b/assets/entity/test.lua new file mode 100644 index 00000000..4c83052d --- /dev/null +++ b/assets/entity/test.lua @@ -0,0 +1,14 @@ +module('entity') +module('entityposition') +module('entitymaterial') +module('glm') +module('color') + +-- Position +local posComp = entityAddComponent(ENTITY_ID, COMPONENT_TYPE_POSITION) +entityPositionSetPosition(ENTITY_ID, posComp, vec3(1, 2, 3)) + +-- Material +local matComp = entityAddComponent(ENTITY_ID, COMPONENT_TYPE_MATERIAL) +local material = entityMaterialGetShaderMaterial(ENTITY_ID, matComp) +material.unlit.color = COLOR_BLUE \ No newline at end of file diff --git a/src/dusk/engine/engine.c b/src/dusk/engine/engine.c index 181939ed..ffb7d952 100644 --- a/src/dusk/engine/engine.c +++ b/src/dusk/engine/engine.c @@ -81,19 +81,14 @@ errorret_t engineInit(const int32_t argc, const char_t **argv) { floorPhysData->shape.data.plane.normal[2] = 0.0f; floorPhysData->shape.data.plane.distance = 0.0f; - /* ---- Dynamic box ---- */ + // Test Box phBoxEnt = entityManagerAdd(); - componentid_t boxPos = entityAddComponent(phBoxEnt, COMPONENT_TYPE_POSITION); componentid_t boxMesh = entityAddComponent(phBoxEnt, COMPONENT_TYPE_MESH); - componentid_t boxMat = entityAddComponent(phBoxEnt, COMPONENT_TYPE_MATERIAL); - phBoxPhys = entityAddComponent(phBoxEnt, COMPONENT_TYPE_PHYSICS); - + // componentid_t boxMat = entityAddComponent(phBoxEnt, COMPONENT_TYPE_MATERIAL); entityMeshSetMesh(phBoxEnt, boxMesh, &CUBE_MESH_SIMPLE); - entityMaterialGetShaderMaterial(phBoxEnt, boxMat)->unlit.color = COLOR_RED; - - /* Physics position lives in the POSITION component. CUBE_MESH_SIMPLE is - * centred at origin (-0.5..0.5), so entity position == physics centre. */ - entityPositionSetPosition(phBoxEnt, boxPos, (vec3){ 0.0f, 4.0f, 0.0f }); + // entityMaterialGetShaderMaterial(phBoxEnt, boxMat)->unlit.color = COLOR_RED; + componentid_t boxScript = entityAddComponent(phBoxEnt, COMPONENT_TYPE_SCRIPT); + errorChain(entityScriptExecAsset(phBoxEnt, boxScript, "entity/test.lua")); /* Run the init script. */ scriptcontext_t ctx; @@ -111,13 +106,6 @@ errorret_t engineUpdate(void) { uiUpdate(); errorChain(sceneUpdate()); - /* Reset the box to its start position on demand. */ - if(inputIsDown(INPUT_ACTION_ACCEPT)) { - componentid_t posComp = entityGetComponent(phBoxEnt, COMPONENT_TYPE_POSITION); - entityPositionSetPosition(phBoxEnt, posComp, (vec3){ 0.0f, 4.0f, 0.0f }); - entityPhysicsSetVelocity(phBoxEnt, phBoxPhys, (vec3){ 0.0f, 0.0f, 0.0f }); - } - /* Step physics: positions are updated directly on POSITION components. */ physicsManagerUpdate(); diff --git a/src/dusk/entity/component/CMakeLists.txt b/src/dusk/entity/component/CMakeLists.txt index e2bf96f6..e5d8792f 100644 --- a/src/dusk/entity/component/CMakeLists.txt +++ b/src/dusk/entity/component/CMakeLists.txt @@ -4,4 +4,5 @@ # https://opensource.org/licenses/MIT add_subdirectory(display) -add_subdirectory(physics) \ No newline at end of file +add_subdirectory(physics) +add_subdirectory(script) \ No newline at end of file diff --git a/src/dusk/entity/component/display/entitycamera.c b/src/dusk/entity/component/display/entitycamera.c index fd1b238a..302f8f91 100644 --- a/src/dusk/entity/component/display/entitycamera.c +++ b/src/dusk/entity/component/display/entitycamera.c @@ -55,6 +55,65 @@ void entityCameraSetZFar( cam->farClip = zFar; } +entitycameraprojectiontype_t entityCameraGetProjType( + const entityid_t ent, + const componentid_t comp +) { + entitycamera_t *cam = (entitycamera_t *)componentGetData( + ent, comp, COMPONENT_TYPE_CAMERA + ); + return cam->projType; +} + +void entityCameraSetProjType( + const entityid_t ent, + const componentid_t comp, + const entitycameraprojectiontype_t type +) { + entitycamera_t *cam = (entitycamera_t *)componentGetData( + ent, comp, COMPONENT_TYPE_CAMERA + ); + cam->projType = type; +} + +float_t entityCameraGetFov( + const entityid_t ent, + const componentid_t comp +) { + entitycamera_t *cam = (entitycamera_t *)componentGetData( + ent, comp, COMPONENT_TYPE_CAMERA + ); + return cam->perspective.fov; +} + +void entityCameraSetFov( + const entityid_t ent, + const componentid_t comp, + const float_t fov +) { + entitycamera_t *cam = (entitycamera_t *)componentGetData( + ent, comp, COMPONENT_TYPE_CAMERA + ); + cam->perspective.fov = fov; +} + +void entityCameraSetOrthographic( + const entityid_t ent, + const componentid_t comp, + const float_t left, + const float_t right, + const float_t top, + const float_t bottom +) { + entitycamera_t *cam = (entitycamera_t *)componentGetData( + ent, comp, COMPONENT_TYPE_CAMERA + ); + cam->orthographic.left = left; + cam->orthographic.right = right; + cam->orthographic.top = top; + cam->orthographic.bottom = bottom; +} + void entityCameraGetProjection( const entityid_t ent, const componentid_t comp, diff --git a/src/dusk/entity/component/display/entitycamera.h b/src/dusk/entity/component/display/entitycamera.h index a258262e..1c5dc820 100644 --- a/src/dusk/entity/component/display/entitycamera.h +++ b/src/dusk/entity/component/display/entitycamera.h @@ -96,4 +96,73 @@ void entityCameraSetZFar( const entityid_t ent, const componentid_t comp, const float_t zFar +); + +/** + * Gets the projection type of a camera. + * + * @param ent The entity ID. + * @param comp The component ID. + * @return The projection type. + */ +entitycameraprojectiontype_t entityCameraGetProjType( + const entityid_t ent, + const componentid_t comp +); + +/** + * Sets the projection type of a camera. + * + * @param ent The entity ID. + * @param comp The component ID. + * @param type The projection type. + */ +void entityCameraSetProjType( + const entityid_t ent, + const componentid_t comp, + const entitycameraprojectiontype_t type +); + +/** + * Gets the field of view (in radians) of a perspective camera. + * + * @param ent The entity ID. + * @param comp The component ID. + * @return The field of view in radians. + */ +float_t entityCameraGetFov( + const entityid_t ent, + const componentid_t comp +); + +/** + * Sets the field of view (in radians) of a perspective camera. + * + * @param ent The entity ID. + * @param comp The component ID. + * @param fov The field of view in radians. + */ +void entityCameraSetFov( + const entityid_t ent, + const componentid_t comp, + const float_t fov +); + +/** + * Sets the orthographic projection bounds of a camera. + * + * @param ent The entity ID. + * @param comp The component ID. + * @param left Left bound. + * @param right Right bound. + * @param top Top bound. + * @param bottom Bottom bound. + */ +void entityCameraSetOrthographic( + const entityid_t ent, + const componentid_t comp, + const float_t left, + const float_t right, + const float_t top, + const float_t bottom ); \ No newline at end of file diff --git a/src/dusk/entity/component/physics/entityphysics.c b/src/dusk/entity/component/physics/entityphysics.c index 631baece..88d159e1 100644 --- a/src/dusk/entity/component/physics/entityphysics.c +++ b/src/dusk/entity/component/physics/entityphysics.c @@ -100,6 +100,44 @@ bool_t entityPhysicsIsOnGround( return phys->onGround; } +physicsbodytype_t entityPhysicsGetBodyType( + const entityid_t entityId, + const componentid_t componentId +) { + entityphysics_t *phys = entityPhysicsGet(entityId, componentId); + assertNotNull(phys, "Failed to get physics component data"); + return phys->type; +} + +void entityPhysicsSetBodyType( + const entityid_t entityId, + const componentid_t componentId, + const physicsbodytype_t type +) { + entityphysics_t *phys = entityPhysicsGet(entityId, componentId); + assertNotNull(phys, "Failed to get physics component data"); + phys->type = type; +} + +float_t entityPhysicsGetGravityScale( + const entityid_t entityId, + const componentid_t componentId +) { + entityphysics_t *phys = entityPhysicsGet(entityId, componentId); + assertNotNull(phys, "Failed to get physics component data"); + return phys->gravityScale; +} + +void entityPhysicsSetGravityScale( + const entityid_t entityId, + const componentid_t componentId, + const float_t scale +) { + entityphysics_t *phys = entityPhysicsGet(entityId, componentId); + assertNotNull(phys, "Failed to get physics component data"); + phys->gravityScale = scale; +} + void entityPhysicsDispose( const entityid_t entityId, const componentid_t componentId diff --git a/src/dusk/entity/component/physics/entityphysics.h b/src/dusk/entity/component/physics/entityphysics.h index 069f97c3..cc1b1cf1 100644 --- a/src/dusk/entity/component/physics/entityphysics.h +++ b/src/dusk/entity/component/physics/entityphysics.h @@ -122,6 +122,56 @@ bool_t entityPhysicsIsOnGround( const componentid_t componentId ); +/** + * Gets the body type of the entity's physics body. + * + * @param entityId The entity ID. + * @param componentId The component ID. + * @return The body type (static, dynamic, kinematic). + */ +physicsbodytype_t entityPhysicsGetBodyType( + const entityid_t entityId, + const componentid_t componentId +); + +/** + * Sets the body type of the entity's physics body. + * + * @param entityId The entity ID. + * @param componentId The component ID. + * @param type The new body type. + */ +void entityPhysicsSetBodyType( + const entityid_t entityId, + const componentid_t componentId, + const physicsbodytype_t type +); + +/** + * Gets the gravity scale of the entity's physics body. + * + * @param entityId The entity ID. + * @param componentId The component ID. + * @return The gravity scale factor. + */ +float_t entityPhysicsGetGravityScale( + const entityid_t entityId, + const componentid_t componentId +); + +/** + * Sets the gravity scale of the entity's physics body. + * + * @param entityId The entity ID. + * @param componentId The component ID. + * @param scale The new gravity scale factor. + */ +void entityPhysicsSetGravityScale( + const entityid_t entityId, + const componentid_t componentId, + const float_t scale +); + /** * Releases the body slot back to PHYSICS_WORLD. Called automatically when * the component is disposed via the component system. diff --git a/src/dusk/entity/component/script/CMakeLists.txt b/src/dusk/entity/component/script/CMakeLists.txt new file mode 100644 index 00000000..8212a9e2 --- /dev/null +++ b/src/dusk/entity/component/script/CMakeLists.txt @@ -0,0 +1,10 @@ +# Copyright (c) 2026 Dominic Masters +# +# This software is released under the MIT License. +# https://opensource.org/licenses/MIT + +# Sources +target_sources(${DUSK_LIBRARY_TARGET_NAME} + PUBLIC + entityscript.c +) \ No newline at end of file diff --git a/src/dusk/entity/component/script/entityscript.c b/src/dusk/entity/component/script/entityscript.c new file mode 100644 index 00000000..9c7e4028 --- /dev/null +++ b/src/dusk/entity/component/script/entityscript.c @@ -0,0 +1,61 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "entityscript.h" +#include "entity/component.h" +#include "assert/assert.h" + +void entityScriptInit( + const entityid_t entityId, + const componentid_t componentId +) { + entityscript_t *script = (entityscript_t*)componentGetData( + entityId, componentId, COMPONENT_TYPE_SCRIPT + ); + scriptContextInit(&script->scriptContext); + + // Define script globals. + char_t strScript[64]; + snprintf(strScript, sizeof(strScript), "ENTITY_ID = %d\n", entityId); + errorret_t ret = scriptContextExec(&script->scriptContext, strScript); + if(ret.code != ERROR_OK) { + errorCatch(errorPrint(ret)); + assertUnreachable("Failed to set up script globals"); + } +} + +errorret_t entityScriptExec( + const entityid_t entityId, + const componentid_t componentId, + const char_t *script +) { + entityscript_t *entityScript = (entityscript_t*)componentGetData( + entityId, componentId, COMPONENT_TYPE_SCRIPT + ); + return scriptContextExec(&entityScript->scriptContext, script); +} + +errorret_t entityScriptExecAsset( + const entityid_t entityId, + const componentid_t componentId, + const char_t *assetName +) { + entityscript_t *entityScript = (entityscript_t*)componentGetData( + entityId, componentId, COMPONENT_TYPE_SCRIPT + ); + return scriptContextExecFile(&entityScript->scriptContext, assetName); +} + +void entityScriptDispose( + const entityid_t entityId, + const componentid_t componentId +) { + entityscript_t *script = (entityscript_t*)componentGetData( + entityId, componentId, COMPONENT_TYPE_SCRIPT + ); + scriptContextDispose(&script->scriptContext); +} \ No newline at end of file diff --git a/src/dusk/entity/component/script/entityscript.h b/src/dusk/entity/component/script/entityscript.h new file mode 100644 index 00000000..feb6341e --- /dev/null +++ b/src/dusk/entity/component/script/entityscript.h @@ -0,0 +1,64 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "entity/entitybase.h" +#include "script/scriptcontext.h" + +typedef struct { + scriptcontext_t scriptContext; +} entityscript_t; + +/** + * Initializes the script entity component. + * + * @param entityId The entity ID. + * @param componentId The component ID. + */ +void entityScriptInit( + const entityid_t entityId, + const componentid_t componentId +); + +/** + * Executes a script on the entity's script component. + * + * @param entityId The entity ID. + * @param componentId The component ID. + * @param script The script to execute. + * @return The error return value. + */ +errorret_t entityScriptExec( + const entityid_t entityId, + const componentid_t componentId, + const char_t *script +); + +/** + * Executes a script from an asset on the entity's script component. + * + * @param entityId The entity ID. + * @param componentId The component ID. + * @param assetName The name of the script asset to execute. + * @return The error return value. + */ +errorret_t entityScriptExecAsset( + const entityid_t entityId, + const componentid_t componentId, + const char_t *assetName +); + +/** + * Disposes of the script entity component. + * + * @param entityId The entity ID. + * @param componentId The component ID. + */ +void entityScriptDispose( + const entityid_t entityId, + const componentid_t componentId +); \ No newline at end of file diff --git a/src/dusk/entity/componentlist.h b/src/dusk/entity/componentlist.h index c8991eb8..db6d9127 100644 --- a/src/dusk/entity/componentlist.h +++ b/src/dusk/entity/componentlist.h @@ -10,9 +10,11 @@ #include "entity/component/display/entitymesh.h" #include "entity/component/display/entitymaterial.h" #include "entity/component/physics/entityphysics.h" +#include "entity/component/script/entityscript.h" X(POSITION, entityposition_t, position, entityPositionInit, NULL) X(CAMERA, entitycamera_t, camera, entityCameraInit, NULL) X(MESH, entitymesh_t, mesh, entityMeshInit, NULL) X(MATERIAL, entitymaterial_t, material, entityMaterialInit, NULL) -X(PHYSICS, entityphysics_t, physics, entityPhysicsInit, entityPhysicsDispose) \ No newline at end of file +X(PHYSICS, entityphysics_t, physics, entityPhysicsInit, entityPhysicsDispose) +X(SCRIPT, entityscript_t, script, entityScriptInit, entityScriptDispose) \ No newline at end of file diff --git a/src/dusk/script/module/CMakeLists.txt b/src/dusk/script/module/CMakeLists.txt index d171e675..017f3894 100644 --- a/src/dusk/script/module/CMakeLists.txt +++ b/src/dusk/script/module/CMakeLists.txt @@ -11,4 +11,5 @@ add_subdirectory(locale) add_subdirectory(system) add_subdirectory(scene) add_subdirectory(time) -add_subdirectory(ui) \ No newline at end of file +add_subdirectory(ui) +add_subdirectory(entity) \ No newline at end of file diff --git a/src/dusk/script/module/entity/CMakeLists.txt b/src/dusk/script/module/entity/CMakeLists.txt new file mode 100644 index 00000000..3ad17b88 --- /dev/null +++ b/src/dusk/script/module/entity/CMakeLists.txt @@ -0,0 +1,16 @@ +# Copyright (c) 2026 Dominic Masters +# +# This software is released under the MIT License. +# https://opensource.org/licenses/MIT + +# Sources +target_sources(${DUSK_LIBRARY_TARGET_NAME} + PUBLIC + moduleentity.c + moduleentityposition.c + moduleentitycamera.c + moduleentitymesh.c + moduleentitymaterial.c + moduleentityphysics.c + moduleentityscript.c +) diff --git a/src/dusk/script/module/entity/moduleentity.c b/src/dusk/script/module/entity/moduleentity.c new file mode 100644 index 00000000..380d0f72 --- /dev/null +++ b/src/dusk/script/module/entity/moduleentity.c @@ -0,0 +1,108 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "moduleentity.h" +#include "assert/assert.h" +#include "entity/entitymanager.h" +#include "entity/entity.h" +#include "entity/component.h" + +void moduleEntity(scriptcontext_t *ctx) { + assertNotNull(ctx, "Script context cannot be NULL"); + + lua_State *l = ctx->luaState; + + // Component type constants + lua_pushnumber(l, COMPONENT_TYPE_POSITION); + lua_setglobal(l, "COMPONENT_TYPE_POSITION"); + + lua_pushnumber(l, COMPONENT_TYPE_CAMERA); + lua_setglobal(l, "COMPONENT_TYPE_CAMERA"); + + lua_pushnumber(l, COMPONENT_TYPE_MESH); + lua_setglobal(l, "COMPONENT_TYPE_MESH"); + + lua_pushnumber(l, COMPONENT_TYPE_MATERIAL); + lua_setglobal(l, "COMPONENT_TYPE_MATERIAL"); + + lua_pushnumber(l, COMPONENT_TYPE_PHYSICS); + lua_setglobal(l, "COMPONENT_TYPE_PHYSICS"); + + lua_pushnumber(l, COMPONENT_TYPE_SCRIPT); + lua_setglobal(l, "COMPONENT_TYPE_SCRIPT"); + + // Entity functions + lua_register(l, "entityCreate", moduleEntityCreate); + lua_register(l, "entityAddComponent", moduleEntityAddComponent); + lua_register(l, "entityGetComponent", moduleEntityGetComponent); + lua_register(l, "entityDispose", moduleEntityDispose); +} + +int moduleEntityCreate(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + + entityid_t id = entityManagerAdd(); + entityInit(id); + lua_pushnumber(L, id); + return 1; +} + +int moduleEntityAddComponent(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + + if(!lua_isnumber(L, 1)) { + luaL_error(L, "entityAddComponent: entityId must be a number"); + return 0; + } + if(!lua_isnumber(L, 2)) { + luaL_error(L, "entityAddComponent: componentType must be a number"); + return 0; + } + + entityid_t entityId = (entityid_t)lua_tonumber(L, 1); + componenttype_t type = (componenttype_t)lua_tonumber(L, 2); + componentid_t compId = entityAddComponent(entityId, type); + lua_pushnumber(L, compId); + return 1; +} + +int moduleEntityGetComponent(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + + if(!lua_isnumber(L, 1)) { + luaL_error(L, "entityGetComponent: entityId must be a number"); + return 0; + } + if(!lua_isnumber(L, 2)) { + luaL_error(L, "entityGetComponent: componentType must be a number"); + return 0; + } + + entityid_t entityId = (entityid_t)lua_tonumber(L, 1); + componenttype_t type = (componenttype_t)lua_tonumber(L, 2); + componentid_t compId = entityGetComponent(entityId, type); + + if(compId == 0xFF) { + lua_pushnil(L); + } else { + lua_pushnumber(L, compId); + } + return 1; +} + +int moduleEntityDispose(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + + if(!lua_isnumber(L, 1)) { + luaL_error(L, "entityDispose: entityId must be a number"); + return 0; + } + + entityid_t entityId = (entityid_t)lua_tonumber(L, 1); + entityDispose(entityId); + return 0; +} diff --git a/src/dusk/script/module/entity/moduleentity.h b/src/dusk/script/module/entity/moduleentity.h new file mode 100644 index 00000000..3e85d592 --- /dev/null +++ b/src/dusk/script/module/entity/moduleentity.h @@ -0,0 +1,44 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "script/scriptcontext.h" + +/** + * Registers the entity module within the given script context. + * Provides: entityCreate, entityAddComponent, entityGetComponent, entityDispose + * and all COMPONENT_TYPE_* constants. + * + * @param ctx The script context to register the module in. + */ +void moduleEntity(scriptcontext_t *ctx); + +/** + * Lua binding: reserves and initializes a new entity. + * Returns: entityId (integer) + */ +int moduleEntityCreate(lua_State *L); + +/** + * Lua binding: adds a component of the given type to an entity. + * Args: entityId (integer), componentType (integer) + * Returns: componentId (integer) + */ +int moduleEntityAddComponent(lua_State *L); + +/** + * Lua binding: gets the component ID for the given type on an entity. + * Args: entityId (integer), componentType (integer) + * Returns: componentId (integer), or nil if the entity lacks the component + */ +int moduleEntityGetComponent(lua_State *L); + +/** + * Lua binding: disposes of the entity with the given ID. + * Args: entityId (integer) + */ +int moduleEntityDispose(lua_State *L); diff --git a/src/dusk/script/module/entity/moduleentitycamera.c b/src/dusk/script/module/entity/moduleentitycamera.c new file mode 100644 index 00000000..f8bb9770 --- /dev/null +++ b/src/dusk/script/module/entity/moduleentitycamera.c @@ -0,0 +1,228 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "moduleentitycamera.h" +#include "assert/assert.h" +#include "entity/component/display/entitycamera.h" + +void moduleEntityCamera(scriptcontext_t *ctx) { + assertNotNull(ctx, "Script context cannot be NULL"); + + lua_State *l = ctx->luaState; + + // Projection type constants + lua_pushnumber(l, ENTITY_CAMERA_PROJECTION_TYPE_PERSPECTIVE); + lua_setglobal(l, "ENTITY_CAMERA_PROJECTION_PERSPECTIVE"); + + lua_pushnumber(l, ENTITY_CAMERA_PROJECTION_TYPE_PERSPECTIVE_FLIPPED); + lua_setglobal(l, "ENTITY_CAMERA_PROJECTION_PERSPECTIVE_FLIPPED"); + + lua_pushnumber(l, ENTITY_CAMERA_PROJECTION_TYPE_ORTHOGRAPHIC); + lua_setglobal(l, "ENTITY_CAMERA_PROJECTION_ORTHOGRAPHIC"); + + // Camera functions + lua_register(l, "entityCameraGetZNear", moduleEntityCameraGetZNear); + lua_register(l, "entityCameraSetZNear", moduleEntityCameraSetZNear); + lua_register(l, "entityCameraGetZFar", moduleEntityCameraGetZFar); + lua_register(l, "entityCameraSetZFar", moduleEntityCameraSetZFar); + lua_register(l, "entityCameraGetProjType", moduleEntityCameraGetProjType); + lua_register(l, "entityCameraSetProjType", moduleEntityCameraSetProjType); + lua_register(l, "entityCameraGetFov", moduleEntityCameraGetFov); + lua_register(l, "entityCameraSetFov", moduleEntityCameraSetFov); + lua_register(l, "entityCameraSetOrthographic", moduleEntityCameraSetOrthographic); +} + +int moduleEntityCameraGetZNear(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + + if(!lua_isnumber(L, 1)) { + luaL_error(L, "entityCameraGetZNear: entityId must be a number"); + return 0; + } + if(!lua_isnumber(L, 2)) { + luaL_error(L, "entityCameraGetZNear: componentId must be a number"); + return 0; + } + + entityid_t eid = (entityid_t)lua_tonumber(L, 1); + componentid_t cid = (componentid_t)lua_tonumber(L, 2); + lua_pushnumber(L, entityCameraGetZNear(eid, cid)); + return 1; +} + +int moduleEntityCameraSetZNear(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + + if(!lua_isnumber(L, 1)) { + luaL_error(L, "entityCameraSetZNear: entityId must be a number"); + return 0; + } + if(!lua_isnumber(L, 2)) { + luaL_error(L, "entityCameraSetZNear: componentId must be a number"); + return 0; + } + if(!lua_isnumber(L, 3)) { + luaL_error(L, "entityCameraSetZNear: zNear must be a number"); + return 0; + } + + entityid_t eid = (entityid_t)lua_tonumber(L, 1); + componentid_t cid = (componentid_t)lua_tonumber(L, 2); + float_t zNear = (float_t)lua_tonumber(L, 3); + entityCameraSetZNear(eid, cid, zNear); + return 0; +} + +int moduleEntityCameraGetZFar(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + + if(!lua_isnumber(L, 1)) { + luaL_error(L, "entityCameraGetZFar: entityId must be a number"); + return 0; + } + if(!lua_isnumber(L, 2)) { + luaL_error(L, "entityCameraGetZFar: componentId must be a number"); + return 0; + } + + entityid_t eid = (entityid_t)lua_tonumber(L, 1); + componentid_t cid = (componentid_t)lua_tonumber(L, 2); + lua_pushnumber(L, entityCameraGetZFar(eid, cid)); + return 1; +} + +int moduleEntityCameraSetZFar(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + + if(!lua_isnumber(L, 1)) { + luaL_error(L, "entityCameraSetZFar: entityId must be a number"); + return 0; + } + if(!lua_isnumber(L, 2)) { + luaL_error(L, "entityCameraSetZFar: componentId must be a number"); + return 0; + } + if(!lua_isnumber(L, 3)) { + luaL_error(L, "entityCameraSetZFar: zFar must be a number"); + return 0; + } + + entityid_t eid = (entityid_t)lua_tonumber(L, 1); + componentid_t cid = (componentid_t)lua_tonumber(L, 2); + float_t zFar = (float_t)lua_tonumber(L, 3); + entityCameraSetZFar(eid, cid, zFar); + return 0; +} + +int moduleEntityCameraGetProjType(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + + if(!lua_isnumber(L, 1)) { + luaL_error(L, "entityCameraGetProjType: entityId must be a number"); + return 0; + } + if(!lua_isnumber(L, 2)) { + luaL_error(L, "entityCameraGetProjType: componentId must be a number"); + return 0; + } + + entityid_t eid = (entityid_t)lua_tonumber(L, 1); + componentid_t cid = (componentid_t)lua_tonumber(L, 2); + lua_pushnumber(L, entityCameraGetProjType(eid, cid)); + return 1; +} + +int moduleEntityCameraSetProjType(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + + if(!lua_isnumber(L, 1)) { + luaL_error(L, "entityCameraSetProjType: entityId must be a number"); + return 0; + } + if(!lua_isnumber(L, 2)) { + luaL_error(L, "entityCameraSetProjType: componentId must be a number"); + return 0; + } + if(!lua_isnumber(L, 3)) { + luaL_error(L, "entityCameraSetProjType: projType must be a number"); + return 0; + } + + entityid_t eid = (entityid_t)lua_tonumber(L, 1); + componentid_t cid = (componentid_t)lua_tonumber(L, 2); + entitycameraprojectiontype_t type = (entitycameraprojectiontype_t)lua_tonumber(L, 3); + entityCameraSetProjType(eid, cid, type); + return 0; +} + +int moduleEntityCameraGetFov(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + + if(!lua_isnumber(L, 1)) { + luaL_error(L, "entityCameraGetFov: entityId must be a number"); + return 0; + } + if(!lua_isnumber(L, 2)) { + luaL_error(L, "entityCameraGetFov: componentId must be a number"); + return 0; + } + + entityid_t eid = (entityid_t)lua_tonumber(L, 1); + componentid_t cid = (componentid_t)lua_tonumber(L, 2); + lua_pushnumber(L, entityCameraGetFov(eid, cid)); + return 1; +} + +int moduleEntityCameraSetFov(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + + if(!lua_isnumber(L, 1)) { + luaL_error(L, "entityCameraSetFov: entityId must be a number"); + return 0; + } + if(!lua_isnumber(L, 2)) { + luaL_error(L, "entityCameraSetFov: componentId must be a number"); + return 0; + } + if(!lua_isnumber(L, 3)) { + luaL_error(L, "entityCameraSetFov: fov must be a number"); + return 0; + } + + entityid_t eid = (entityid_t)lua_tonumber(L, 1); + componentid_t cid = (componentid_t)lua_tonumber(L, 2); + float_t fov = (float_t)lua_tonumber(L, 3); + entityCameraSetFov(eid, cid, fov); + return 0; +} + +int moduleEntityCameraSetOrthographic(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + + if(!lua_isnumber(L, 1)) { + luaL_error(L, "entityCameraSetOrthographic: entityId must be a number"); + return 0; + } + if(!lua_isnumber(L, 2)) { + luaL_error(L, "entityCameraSetOrthographic: componentId must be a number"); + return 0; + } + if(!lua_isnumber(L, 3) || !lua_isnumber(L, 4) || + !lua_isnumber(L, 5) || !lua_isnumber(L, 6)) { + luaL_error(L, "entityCameraSetOrthographic: left/right/top/bottom must be numbers"); + return 0; + } + + entityid_t eid = (entityid_t)lua_tonumber(L, 1); + componentid_t cid = (componentid_t)lua_tonumber(L, 2); + float_t left = (float_t)lua_tonumber(L, 3); + float_t right = (float_t)lua_tonumber(L, 4); + float_t top = (float_t)lua_tonumber(L, 5); + float_t bottom = (float_t)lua_tonumber(L, 6); + entityCameraSetOrthographic(eid, cid, left, right, top, bottom); + return 0; +} diff --git a/src/dusk/script/module/entity/moduleentitycamera.h b/src/dusk/script/module/entity/moduleentitycamera.h new file mode 100644 index 00000000..c90ca3cd --- /dev/null +++ b/src/dusk/script/module/entity/moduleentitycamera.h @@ -0,0 +1,62 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "script/scriptcontext.h" + +/** + * Registers the entitycamera module within the given script context. + * Provides get/set functions for znear, zfar, and projection type constants. + * + * @param ctx The script context to register the module in. + */ +void moduleEntityCamera(scriptcontext_t *ctx); + +/** + * Lua binding: entityCameraGetZNear(entityId, componentId) -> number + */ +int moduleEntityCameraGetZNear(lua_State *L); + +/** + * Lua binding: entityCameraSetZNear(entityId, componentId, number) + */ +int moduleEntityCameraSetZNear(lua_State *L); + +/** + * Lua binding: entityCameraGetZFar(entityId, componentId) -> number + */ +int moduleEntityCameraGetZFar(lua_State *L); + +/** + * Lua binding: entityCameraSetZFar(entityId, componentId, number) + */ +int moduleEntityCameraSetZFar(lua_State *L); + +/** + * Lua binding: entityCameraGetProjType(entityId, componentId) -> number + */ +int moduleEntityCameraGetProjType(lua_State *L); + +/** + * Lua binding: entityCameraSetProjType(entityId, componentId, number) + */ +int moduleEntityCameraSetProjType(lua_State *L); + +/** + * Lua binding: entityCameraGetFov(entityId, componentId) -> number (radians) + */ +int moduleEntityCameraGetFov(lua_State *L); + +/** + * Lua binding: entityCameraSetFov(entityId, componentId, number) + */ +int moduleEntityCameraSetFov(lua_State *L); + +/** + * Lua binding: entityCameraSetOrthographic(entityId, componentId, left, right, top, bottom) + */ +int moduleEntityCameraSetOrthographic(lua_State *L); diff --git a/src/dusk/script/module/entity/moduleentitymaterial.c b/src/dusk/script/module/entity/moduleentitymaterial.c new file mode 100644 index 00000000..22fb3e23 --- /dev/null +++ b/src/dusk/script/module/entity/moduleentitymaterial.c @@ -0,0 +1,209 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "moduleentitymaterial.h" +#include "assert/assert.h" +#include "entity/component/display/entitymaterial.h" +#include "display/shader/shaderunlit.h" +#include "util/string.h" + +void moduleEntityMaterial(scriptcontext_t *ctx) { + assertNotNull(ctx, "Script context cannot be NULL"); + + lua_State *l = ctx->luaState; + + // shadermaterial_mt: wraps shadermaterial_t* pointer + if(luaL_newmetatable(l, "shadermaterial_mt")) { + lua_pushcfunction(l, moduleEntityMaterialShaderMaterialIndex); + lua_setfield(l, -2, "__index"); + } + lua_pop(l, 1); + + // shaderunlitmaterial_mt: wraps shaderunlitmaterial_t* pointer + if(luaL_newmetatable(l, "shaderunlitmaterial_mt")) { + lua_pushcfunction(l, moduleEntityMaterialUnlitIndex); + lua_setfield(l, -2, "__index"); + lua_pushcfunction(l, moduleEntityMaterialUnlitNewIndex); + lua_setfield(l, -2, "__newindex"); + } + lua_pop(l, 1); + + lua_register(l, "entityMaterialGetShader", moduleEntityMaterialGetShader); + lua_register(l, "entityMaterialSetShader", moduleEntityMaterialSetShader); + lua_register(l, "entityMaterialGetShaderMaterial", moduleEntityMaterialGetShaderMaterial); +} + +int moduleEntityMaterialGetShader(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + + if(!lua_isnumber(L, 1)) { + luaL_error(L, "Entity ID must be a number"); + return 0; + } + if(!lua_isnumber(L, 2)) { + luaL_error(L, "Component ID must be a number"); + return 0; + } + + entityid_t eid = (entityid_t)lua_tonumber(L, 1); + componentid_t cid = (componentid_t)lua_tonumber(L, 2); + shader_t *shader = entityMaterialGetShader(eid, cid); + + if(shader == NULL) { + lua_pushnil(L); + } else { + lua_pushlightuserdata(L, shader); + } + return 1; +} + +int moduleEntityMaterialSetShader(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + + if(!lua_isnumber(L, 1)) { + luaL_error(L, "Entity ID must be a number"); + return 0; + } + if(!lua_isnumber(L, 2)) { + luaL_error(L, "Component ID must be a number"); + return 0; + } + + entityid_t eid = (entityid_t)lua_tonumber(L, 1); + componentid_t cid = (componentid_t)lua_tonumber(L, 2); + + shader_t *shader = NULL; + if(!lua_isnil(L, 3)) { + if(!lua_isuserdata(L, 3)) { + luaL_error(L, "Shader must be a userdata or nil"); + return 0; + } + shader = (shader_t *)lua_touserdata(L, 3); + } + + entityMaterialSetShader(eid, cid, shader); + return 0; +} + +int moduleEntityMaterialGetShaderMaterial(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + + if(!lua_isnumber(L, 1)) { + luaL_error(L, "entityMaterialGetShaderMaterial: entityId must be a number"); + return 0; + } + if(!lua_isnumber(L, 2)) { + luaL_error(L, "entityMaterialGetShaderMaterial: componentId must be a number"); + return 0; + } + + entityid_t eid = (entityid_t)lua_tonumber(L, 1); + componentid_t cid = (componentid_t)lua_tonumber(L, 2); + shadermaterial_t *mat = entityMaterialGetShaderMaterial(eid, cid); + + if(mat == NULL) { + lua_pushnil(L); + return 1; + } + + shadermaterial_t **pmat = (shadermaterial_t **)lua_newuserdata( + L, sizeof(shadermaterial_t *) + ); + *pmat = mat; + luaL_getmetatable(L, "shadermaterial_mt"); + lua_setmetatable(L, -2); + return 1; +} + +int moduleEntityMaterialShaderMaterialIndex(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + + shadermaterial_t *mat = *(shadermaterial_t **)luaL_checkudata( + L, 1, "shadermaterial_mt" + ); + assertNotNull(mat, "Shader material pointer cannot be NULL"); + + const char_t *key = lua_tostring(L, 2); + if(key != NULL && stringCompare(key, "unlit") == 0) { + shaderunlitmaterial_t **punlit = (shaderunlitmaterial_t **)lua_newuserdata( + L, sizeof(shaderunlitmaterial_t *) + ); + *punlit = &(mat->unlit); + luaL_getmetatable(L, "shaderunlitmaterial_mt"); + lua_setmetatable(L, -2); + return 1; + } + + lua_pushnil(L); + return 1; +} + +int moduleEntityMaterialUnlitIndex(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + + shaderunlitmaterial_t *unlit = *(shaderunlitmaterial_t **)luaL_checkudata( + L, 1, "shaderunlitmaterial_mt" + ); + assertNotNull(unlit, "Unlit material pointer cannot be NULL"); + + const char_t *key = lua_tostring(L, 2); + if(key == NULL) { + lua_pushnil(L); + return 1; + } + + if(stringCompare(key, "color") == 0) { + color_t *color = (color_t *)lua_newuserdata(L, sizeof(color_t)); + *color = unlit->color; + luaL_getmetatable(L, "color_mt"); + lua_setmetatable(L, -2); + return 1; + } + + if(stringCompare(key, "texture") == 0) { + if(unlit->texture == NULL) { + lua_pushnil(L); + } else { + lua_pushlightuserdata(L, unlit->texture); + } + return 1; + } + + lua_pushnil(L); + return 1; +} + +int moduleEntityMaterialUnlitNewIndex(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + + shaderunlitmaterial_t *unlit = *(shaderunlitmaterial_t **)luaL_checkudata( + L, 1, "shaderunlitmaterial_mt" + ); + assertNotNull(unlit, "Unlit material pointer cannot be NULL"); + + const char_t *key = lua_tostring(L, 2); + if(key == NULL) return 0; + + if(stringCompare(key, "color") == 0) { + color_t *color = (color_t *)luaL_checkudata(L, 3, "color_mt"); + unlit->color = *color; + return 0; + } + + if(stringCompare(key, "texture") == 0) { + if(lua_isnil(L, 3)) { + unlit->texture = NULL; + } else if(lua_isuserdata(L, 3)) { + unlit->texture = (texture_t *)lua_touserdata(L, 3); + } else { + luaL_error(L, "shaderunlitmaterial texture must be a userdata or nil"); + } + return 0; + } + + return 0; +} diff --git a/src/dusk/script/module/entity/moduleentitymaterial.h b/src/dusk/script/module/entity/moduleentitymaterial.h new file mode 100644 index 00000000..aac9149d --- /dev/null +++ b/src/dusk/script/module/entity/moduleentitymaterial.h @@ -0,0 +1,52 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "script/scriptcontext.h" + +/** + * Registers the entitymaterial module within the given script context. + * Provides get/set functions for the shader pointer (lightuserdata). + * + * @param ctx The script context to register the module in. + */ +void moduleEntityMaterial(scriptcontext_t *ctx); + +/** + * Lua binding: entityMaterialGetShader(entityId, componentId) -> lightuserdata or nil + */ +int moduleEntityMaterialGetShader(lua_State *L); + +/** + * Lua binding: entityMaterialSetShader(entityId, componentId, shader_lightuserdata|nil) + */ +int moduleEntityMaterialSetShader(lua_State *L); + +/** + * Lua binding: entityMaterialGetShaderMaterial(entityId, componentId) + * Returns a shadermaterial userdata (shadermaterial_mt) holding a pointer + * into the entity's material component, or nil on failure. + */ +int moduleEntityMaterialGetShaderMaterial(lua_State *L); + +/** + * __index metamethod for shadermaterial_mt userdata. + * Supports field: "unlit" -> shaderunlitmaterial_mt userdata. + */ +int moduleEntityMaterialShaderMaterialIndex(lua_State *L); + +/** + * __index metamethod for shaderunlitmaterial_mt userdata. + * Supports fields: "color" -> color_mt copy, "texture" -> lightuserdata or nil. + */ +int moduleEntityMaterialUnlitIndex(lua_State *L); + +/** + * __newindex metamethod for shaderunlitmaterial_mt userdata. + * Supports fields: "color" <- color_mt, "texture" <- lightuserdata or nil. + */ +int moduleEntityMaterialUnlitNewIndex(lua_State *L); diff --git a/src/dusk/script/module/entity/moduleentitymesh.c b/src/dusk/script/module/entity/moduleentitymesh.c new file mode 100644 index 00000000..28f4ea26 --- /dev/null +++ b/src/dusk/script/module/entity/moduleentitymesh.c @@ -0,0 +1,70 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "moduleentitymesh.h" +#include "assert/assert.h" +#include "entity/component/display/entitymesh.h" + +void moduleEntityMesh(scriptcontext_t *ctx) { + assertNotNull(ctx, "Script context cannot be NULL"); + + lua_State *l = ctx->luaState; + lua_register(l, "entityMeshGetMesh", moduleEntityMeshGetMesh); + lua_register(l, "entityMeshSetMesh", moduleEntityMeshSetMesh); +} + +int moduleEntityMeshGetMesh(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + + if(!lua_isnumber(L, 1)) { + luaL_error(L, "entityMeshGetMesh: entityId must be a number"); + return 0; + } + if(!lua_isnumber(L, 2)) { + luaL_error(L, "entityMeshGetMesh: componentId must be a number"); + return 0; + } + + entityid_t eid = (entityid_t)lua_tonumber(L, 1); + componentid_t cid = (componentid_t)lua_tonumber(L, 2); + mesh_t *mesh = entityMeshGetMesh(eid, cid); + + if(mesh == NULL) { + lua_pushnil(L); + } else { + lua_pushlightuserdata(L, mesh); + } + return 1; +} + +int moduleEntityMeshSetMesh(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + + if(!lua_isnumber(L, 1)) { + luaL_error(L, "entityMeshSetMesh: entityId must be a number"); + return 0; + } + if(!lua_isnumber(L, 2)) { + luaL_error(L, "entityMeshSetMesh: componentId must be a number"); + return 0; + } + + entityid_t eid = (entityid_t)lua_tonumber(L, 1); + componentid_t cid = (componentid_t)lua_tonumber(L, 2); + + mesh_t *mesh = NULL; + if(!lua_isnil(L, 3)) { + if(!lua_isuserdata(L, 3)) { + luaL_error(L, "entityMeshSetMesh: mesh must be a userdata or nil"); + return 0; + } + mesh = (mesh_t *)lua_touserdata(L, 3); + } + + entityMeshSetMesh(eid, cid, mesh); + return 0; +} diff --git a/src/dusk/script/module/entity/moduleentitymesh.h b/src/dusk/script/module/entity/moduleentitymesh.h new file mode 100644 index 00000000..f14bd459 --- /dev/null +++ b/src/dusk/script/module/entity/moduleentitymesh.h @@ -0,0 +1,27 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "script/scriptcontext.h" + +/** + * Registers the entitymesh module within the given script context. + * Provides get/set functions for the mesh pointer (lightuserdata). + * + * @param ctx The script context to register the module in. + */ +void moduleEntityMesh(scriptcontext_t *ctx); + +/** + * Lua binding: entityMeshGetMesh(entityId, componentId) -> lightuserdata or nil + */ +int moduleEntityMeshGetMesh(lua_State *L); + +/** + * Lua binding: entityMeshSetMesh(entityId, componentId, mesh_lightuserdata|nil) + */ +int moduleEntityMeshSetMesh(lua_State *L); diff --git a/src/dusk/script/module/entity/moduleentityphysics.c b/src/dusk/script/module/entity/moduleentityphysics.c new file mode 100644 index 00000000..80725a91 --- /dev/null +++ b/src/dusk/script/module/entity/moduleentityphysics.c @@ -0,0 +1,383 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "moduleentityphysics.h" +#include "assert/assert.h" +#include "util/memory.h" +#include "entity/component/physics/entityphysics.h" + +void moduleEntityPhysics(scriptcontext_t *ctx) { + assertNotNull(ctx, "Script context cannot be NULL"); + + lua_State *l = ctx->luaState; + + // Body type constants + lua_pushnumber(l, PHYSICS_BODY_STATIC); + lua_setglobal(l, "PHYSICS_BODY_STATIC"); + + lua_pushnumber(l, PHYSICS_BODY_DYNAMIC); + lua_setglobal(l, "PHYSICS_BODY_DYNAMIC"); + + lua_pushnumber(l, PHYSICS_BODY_KINEMATIC); + lua_setglobal(l, "PHYSICS_BODY_KINEMATIC"); + + // Shape type constants + lua_pushnumber(l, PHYSICS_SHAPE_CUBE); + lua_setglobal(l, "PHYSICS_SHAPE_CUBE"); + + lua_pushnumber(l, PHYSICS_SHAPE_SPHERE); + lua_setglobal(l, "PHYSICS_SHAPE_SPHERE"); + + lua_pushnumber(l, PHYSICS_SHAPE_CAPSULE); + lua_setglobal(l, "PHYSICS_SHAPE_CAPSULE"); + + lua_pushnumber(l, PHYSICS_SHAPE_PLANE); + lua_setglobal(l, "PHYSICS_SHAPE_PLANE"); + + // Physics shape metatable + if(luaL_newmetatable(l, "physicsshape_mt")) { + lua_pushcfunction(l, moduleEntityPhysicsShapeToString); + lua_setfield(l, -2, "__tostring"); + } + lua_pop(l, 1); + + // Shape constructors + lua_register(l, "physicsShapeCube", modulePhysicsShapeCube); + lua_register(l, "physicsShapeSphere", modulePhysicsShapeSphere); + lua_register(l, "physicsShapeCapsule", modulePhysicsShapeCapsule); + lua_register(l, "physicsShapePlane", modulePhysicsShapePlane); + + // Entity physics functions + lua_register(l, "entityPhysicsGetVelocity", moduleEntityPhysicsGetVelocity); + lua_register(l, "entityPhysicsSetVelocity", moduleEntityPhysicsSetVelocity); + lua_register(l, "entityPhysicsApplyImpulse", moduleEntityPhysicsApplyImpulse); + lua_register(l, "entityPhysicsIsOnGround", moduleEntityPhysicsIsOnGround); + lua_register(l, "entityPhysicsGetShape", moduleEntityPhysicsGetShape); + lua_register(l, "entityPhysicsSetShape", moduleEntityPhysicsSetShape); + lua_register(l, "entityPhysicsGetBodyType", moduleEntityPhysicsGetBodyType); + lua_register(l, "entityPhysicsSetBodyType", moduleEntityPhysicsSetBodyType); + lua_register(l, "entityPhysicsGetGravityScale", moduleEntityPhysicsGetGravityScale); + lua_register(l, "entityPhysicsSetGravityScale", moduleEntityPhysicsSetGravityScale); +} + +int moduleEntityPhysicsShapeToString(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + + physicsshape_t *shape = (physicsshape_t *)luaL_checkudata(L, 1, "physicsshape_mt"); + assertNotNull(shape, "Physics shape pointer cannot be NULL"); + + const char_t *typeName = "unknown"; + switch(shape->type) { + case PHYSICS_SHAPE_CUBE: typeName = "cube"; break; + case PHYSICS_SHAPE_SPHERE: typeName = "sphere"; break; + case PHYSICS_SHAPE_CAPSULE: typeName = "capsule"; break; + case PHYSICS_SHAPE_PLANE: typeName = "plane"; break; + } + lua_pushfstring(L, "physicsshape(%s)", typeName); + return 1; +} + +int moduleEntityPhysicsGetVelocity(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + + if(!lua_isnumber(L, 1)) { + luaL_error(L, "entityPhysicsGetVelocity: entityId must be a number"); + return 0; + } + if(!lua_isnumber(L, 2)) { + luaL_error(L, "entityPhysicsGetVelocity: componentId must be a number"); + return 0; + } + + entityid_t eid = (entityid_t)lua_tonumber(L, 1); + componentid_t cid = (componentid_t)lua_tonumber(L, 2); + + vec3 *v = (vec3 *)lua_newuserdata(L, sizeof(vec3)); + memoryZero(v, sizeof(vec3)); + entityPhysicsGetVelocity(eid, cid, *v); + luaL_getmetatable(L, "vec3_mt"); + lua_setmetatable(L, -2); + return 1; +} + +int moduleEntityPhysicsSetVelocity(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + + if(!lua_isnumber(L, 1)) { + luaL_error(L, "entityPhysicsSetVelocity: entityId must be a number"); + return 0; + } + if(!lua_isnumber(L, 2)) { + luaL_error(L, "entityPhysicsSetVelocity: componentId must be a number"); + return 0; + } + + entityid_t eid = (entityid_t)lua_tonumber(L, 1); + componentid_t cid = (componentid_t)lua_tonumber(L, 2); + vec3 *v = (vec3 *)luaL_checkudata(L, 3, "vec3_mt"); + entityPhysicsSetVelocity(eid, cid, *v); + return 0; +} + +int moduleEntityPhysicsApplyImpulse(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + + if(!lua_isnumber(L, 1)) { + luaL_error(L, "entityPhysicsApplyImpulse: entityId must be a number"); + return 0; + } + if(!lua_isnumber(L, 2)) { + luaL_error(L, "entityPhysicsApplyImpulse: componentId must be a number"); + return 0; + } + + entityid_t eid = (entityid_t)lua_tonumber(L, 1); + componentid_t cid = (componentid_t)lua_tonumber(L, 2); + vec3 *v = (vec3 *)luaL_checkudata(L, 3, "vec3_mt"); + entityPhysicsApplyImpulse(eid, cid, *v); + return 0; +} + +int moduleEntityPhysicsIsOnGround(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + + if(!lua_isnumber(L, 1)) { + luaL_error(L, "entityPhysicsIsOnGround: entityId must be a number"); + return 0; + } + if(!lua_isnumber(L, 2)) { + luaL_error(L, "entityPhysicsIsOnGround: componentId must be a number"); + return 0; + } + + entityid_t eid = (entityid_t)lua_tonumber(L, 1); + componentid_t cid = (componentid_t)lua_tonumber(L, 2); + lua_pushboolean(L, entityPhysicsIsOnGround(eid, cid) ? 1 : 0); + return 1; +} + +int moduleEntityPhysicsSetShape(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + + if(!lua_isnumber(L, 1)) { + luaL_error(L, "entityPhysicsSetShape: entityId must be a number"); + return 0; + } + if(!lua_isnumber(L, 2)) { + luaL_error(L, "entityPhysicsSetShape: componentId must be a number"); + return 0; + } + + entityid_t eid = (entityid_t)lua_tonumber(L, 1); + componentid_t cid = (componentid_t)lua_tonumber(L, 2); + physicsshape_t *shape = (physicsshape_t *)luaL_checkudata(L, 3, "physicsshape_mt"); + entityPhysicsSetShape(eid, cid, *shape); + return 0; +} + +int moduleEntityPhysicsGetShape(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + + if(!lua_isnumber(L, 1)) { + luaL_error(L, "entityPhysicsGetShape: entityId must be a number"); + return 0; + } + if(!lua_isnumber(L, 2)) { + luaL_error(L, "entityPhysicsGetShape: componentId must be a number"); + return 0; + } + + entityid_t eid = (entityid_t)lua_tonumber(L, 1); + componentid_t cid = (componentid_t)lua_tonumber(L, 2); + + physicsshape_t *shape = (physicsshape_t *)lua_newuserdata(L, sizeof(physicsshape_t)); + *shape = entityPhysicsGetShape(eid, cid); + luaL_getmetatable(L, "physicsshape_mt"); + lua_setmetatable(L, -2); + return 1; +} + +int moduleEntityPhysicsGetBodyType(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + + if(!lua_isnumber(L, 1)) { + luaL_error(L, "entityPhysicsGetBodyType: entityId must be a number"); + return 0; + } + if(!lua_isnumber(L, 2)) { + luaL_error(L, "entityPhysicsGetBodyType: componentId must be a number"); + return 0; + } + + entityid_t eid = (entityid_t)lua_tonumber(L, 1); + componentid_t cid = (componentid_t)lua_tonumber(L, 2); + lua_pushnumber(L, entityPhysicsGetBodyType(eid, cid)); + return 1; +} + +int moduleEntityPhysicsSetBodyType(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + + if(!lua_isnumber(L, 1)) { + luaL_error(L, "entityPhysicsSetBodyType: entityId must be a number"); + return 0; + } + if(!lua_isnumber(L, 2)) { + luaL_error(L, "entityPhysicsSetBodyType: componentId must be a number"); + return 0; + } + if(!lua_isnumber(L, 3)) { + luaL_error(L, "entityPhysicsSetBodyType: bodyType must be a number"); + return 0; + } + + entityid_t eid = (entityid_t)lua_tonumber(L, 1); + componentid_t cid = (componentid_t)lua_tonumber(L, 2); + physicsbodytype_t type = (physicsbodytype_t)lua_tonumber(L, 3); + entityPhysicsSetBodyType(eid, cid, type); + return 0; +} + +int moduleEntityPhysicsGetGravityScale(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + + if(!lua_isnumber(L, 1)) { + luaL_error(L, "entityPhysicsGetGravityScale: entityId must be a number"); + return 0; + } + if(!lua_isnumber(L, 2)) { + luaL_error(L, "entityPhysicsGetGravityScale: componentId must be a number"); + return 0; + } + + entityid_t eid = (entityid_t)lua_tonumber(L, 1); + componentid_t cid = (componentid_t)lua_tonumber(L, 2); + lua_pushnumber(L, entityPhysicsGetGravityScale(eid, cid)); + return 1; +} + +int moduleEntityPhysicsSetGravityScale(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + + if(!lua_isnumber(L, 1)) { + luaL_error(L, "entityPhysicsSetGravityScale: entityId must be a number"); + return 0; + } + if(!lua_isnumber(L, 2)) { + luaL_error(L, "entityPhysicsSetGravityScale: componentId must be a number"); + return 0; + } + if(!lua_isnumber(L, 3)) { + luaL_error(L, "entityPhysicsSetGravityScale: scale must be a number"); + return 0; + } + + entityid_t eid = (entityid_t)lua_tonumber(L, 1); + componentid_t cid = (componentid_t)lua_tonumber(L, 2); + float_t scale = (float_t)lua_tonumber(L, 3); + entityPhysicsSetGravityScale(eid, cid, scale); + return 0; +} + +int modulePhysicsShapeCube(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + + float_t hx = lua_isnumber(L, 1) ? (float_t)lua_tonumber(L, 1) : 0.5f; + float_t hy = lua_isnumber(L, 2) ? (float_t)lua_tonumber(L, 2) : 0.5f; + float_t hz = lua_isnumber(L, 3) ? (float_t)lua_tonumber(L, 3) : 0.5f; + + physicsshape_t *shape = (physicsshape_t *)lua_newuserdata(L, sizeof(physicsshape_t)); + memoryZero(shape, sizeof(physicsshape_t)); + shape->type = PHYSICS_SHAPE_CUBE; + shape->data.cube.halfExtents[0] = hx; + shape->data.cube.halfExtents[1] = hy; + shape->data.cube.halfExtents[2] = hz; + luaL_getmetatable(L, "physicsshape_mt"); + lua_setmetatable(L, -2); + return 1; +} + +int modulePhysicsShapeSphere(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + + if(!lua_isnumber(L, 1)) { + luaL_error(L, "physicsShapeSphere: radius must be a number"); + return 0; + } + + float_t radius = (float_t)lua_tonumber(L, 1); + + physicsshape_t *shape = (physicsshape_t *)lua_newuserdata(L, sizeof(physicsshape_t)); + memoryZero(shape, sizeof(physicsshape_t)); + shape->type = PHYSICS_SHAPE_SPHERE; + shape->data.sphere.radius = radius; + luaL_getmetatable(L, "physicsshape_mt"); + lua_setmetatable(L, -2); + return 1; +} + +int modulePhysicsShapeCapsule(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + + if(!lua_isnumber(L, 1)) { + luaL_error(L, "physicsShapeCapsule: radius must be a number"); + return 0; + } + if(!lua_isnumber(L, 2)) { + luaL_error(L, "physicsShapeCapsule: halfHeight must be a number"); + return 0; + } + + float_t radius = (float_t)lua_tonumber(L, 1); + float_t halfHeight = (float_t)lua_tonumber(L, 2); + + physicsshape_t *shape = (physicsshape_t *)lua_newuserdata(L, sizeof(physicsshape_t)); + memoryZero(shape, sizeof(physicsshape_t)); + shape->type = PHYSICS_SHAPE_CAPSULE; + shape->data.capsule.radius = radius; + shape->data.capsule.halfHeight = halfHeight; + luaL_getmetatable(L, "physicsshape_mt"); + lua_setmetatable(L, -2); + return 1; +} + +int modulePhysicsShapePlane(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + + if(!lua_isnumber(L, 1)) { + luaL_error(L, "physicsShapePlane: normalX must be a number"); + return 0; + } + if(!lua_isnumber(L, 2)) { + luaL_error(L, "physicsShapePlane: normalY must be a number"); + return 0; + } + if(!lua_isnumber(L, 3)) { + luaL_error(L, "physicsShapePlane: normalZ must be a number"); + return 0; + } + if(!lua_isnumber(L, 4)) { + luaL_error(L, "physicsShapePlane: distance must be a number"); + return 0; + } + + float_t nx = (float_t)lua_tonumber(L, 1); + float_t ny = (float_t)lua_tonumber(L, 2); + float_t nz = (float_t)lua_tonumber(L, 3); + float_t distance = (float_t)lua_tonumber(L, 4); + + physicsshape_t *shape = (physicsshape_t *)lua_newuserdata(L, sizeof(physicsshape_t)); + memoryZero(shape, sizeof(physicsshape_t)); + shape->type = PHYSICS_SHAPE_PLANE; + shape->data.plane.normal[0] = nx; + shape->data.plane.normal[1] = ny; + shape->data.plane.normal[2] = nz; + shape->data.plane.distance = distance; + luaL_getmetatable(L, "physicsshape_mt"); + lua_setmetatable(L, -2); + return 1; +} diff --git a/src/dusk/script/module/entity/moduleentityphysics.h b/src/dusk/script/module/entity/moduleentityphysics.h new file mode 100644 index 00000000..82b7a7ce --- /dev/null +++ b/src/dusk/script/module/entity/moduleentityphysics.h @@ -0,0 +1,64 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "script/scriptcontext.h" + +/** + * Registers the entityphysics module within the given script context. + * Provides velocity, impulse, onGround, and shape functions, plus body type + * and shape type constants. Shape constructors: physicsShapeCube, + * physicsShapeSphere, physicsShapeCapsule, physicsShapePlane. + * + * @param ctx The script context to register the module in. + */ +void moduleEntityPhysics(scriptcontext_t *ctx); + +/** Lua binding: entityPhysicsGetVelocity(entityId, componentId) -> vec3 */ +int moduleEntityPhysicsGetVelocity(lua_State *L); + +/** Lua binding: entityPhysicsSetVelocity(entityId, componentId, vec3) */ +int moduleEntityPhysicsSetVelocity(lua_State *L); + +/** Lua binding: entityPhysicsApplyImpulse(entityId, componentId, vec3) */ +int moduleEntityPhysicsApplyImpulse(lua_State *L); + +/** Lua binding: entityPhysicsIsOnGround(entityId, componentId) -> boolean */ +int moduleEntityPhysicsIsOnGround(lua_State *L); + +/** Lua binding: entityPhysicsSetShape(entityId, componentId, physicsshape) */ +int moduleEntityPhysicsSetShape(lua_State *L); + +/** Lua binding: physicsShapeCube(halfX, halfY, halfZ) -> physicsshape */ +int modulePhysicsShapeCube(lua_State *L); + +/** Lua binding: physicsShapeSphere(radius) -> physicsshape */ +int modulePhysicsShapeSphere(lua_State *L); + +/** Lua binding: physicsShapeCapsule(radius, halfHeight) -> physicsshape */ +int modulePhysicsShapeCapsule(lua_State *L); + +/** Lua binding: physicsShapePlane(normalX, normalY, normalZ, distance) -> physicsshape */ +int modulePhysicsShapePlane(lua_State *L); + +/** Lua binding: entityPhysicsGetShape(entityId, componentId) -> physicsshape */ +int moduleEntityPhysicsGetShape(lua_State *L); + +/** Lua binding: entityPhysicsGetBodyType(entityId, componentId) -> number */ +int moduleEntityPhysicsGetBodyType(lua_State *L); + +/** Lua binding: entityPhysicsSetBodyType(entityId, componentId, number) */ +int moduleEntityPhysicsSetBodyType(lua_State *L); + +/** Lua binding: entityPhysicsGetGravityScale(entityId, componentId) -> number */ +int moduleEntityPhysicsGetGravityScale(lua_State *L); + +/** Lua binding: entityPhysicsSetGravityScale(entityId, componentId, number) */ +int moduleEntityPhysicsSetGravityScale(lua_State *L); + +/** Metatable __tostring for physicsshape userdata */ +int moduleEntityPhysicsShapeToString(lua_State *L); diff --git a/src/dusk/script/module/entity/moduleentityposition.c b/src/dusk/script/module/entity/moduleentityposition.c new file mode 100644 index 00000000..5ced3191 --- /dev/null +++ b/src/dusk/script/module/entity/moduleentityposition.c @@ -0,0 +1,172 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "moduleentityposition.h" +#include "assert/assert.h" +#include "util/memory.h" +#include "entity/component/display/entityposition.h" + +void moduleEntityPosition(scriptcontext_t *ctx) { + assertNotNull(ctx, "Script context cannot be NULL"); + + lua_State *l = ctx->luaState; + + lua_register(l, "entityPositionGetPosition", moduleEntityPositionGetPosition); + lua_register(l, "entityPositionSetPosition", moduleEntityPositionSetPosition); + lua_register(l, "entityPositionGetRotation", moduleEntityPositionGetRotation); + lua_register(l, "entityPositionSetRotation", moduleEntityPositionSetRotation); + lua_register(l, "entityPositionGetScale", moduleEntityPositionGetScale); + lua_register(l, "entityPositionSetScale", moduleEntityPositionSetScale); + lua_register(l, "entityPositionLookAt", moduleEntityPositionLookAt); +} + +int moduleEntityPositionGetPosition(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + + if(!lua_isnumber(L, 1)) { + luaL_error(L, "entityPositionGetPosition: entityId must be a number"); + return 0; + } + if(!lua_isnumber(L, 2)) { + luaL_error(L, "entityPositionGetPosition: componentId must be a number"); + return 0; + } + + entityid_t eid = (entityid_t)lua_tonumber(L, 1); + componentid_t cid = (componentid_t)lua_tonumber(L, 2); + + vec3 *v = (vec3 *)lua_newuserdata(L, sizeof(vec3)); + memoryZero(v, sizeof(vec3)); + entityPositionGetPosition(eid, cid, *v); + luaL_getmetatable(L, "vec3_mt"); + lua_setmetatable(L, -2); + return 1; +} + +int moduleEntityPositionSetPosition(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + + if(!lua_isnumber(L, 1)) { + luaL_error(L, "entityPositionSetPosition: entityId must be a number"); + return 0; + } + if(!lua_isnumber(L, 2)) { + luaL_error(L, "entityPositionSetPosition: componentId must be a number"); + return 0; + } + + entityid_t eid = (entityid_t)lua_tonumber(L, 1); + componentid_t cid = (componentid_t)lua_tonumber(L, 2); + vec3 *v = (vec3 *)luaL_checkudata(L, 3, "vec3_mt"); + entityPositionSetPosition(eid, cid, *v); + return 0; +} + +int moduleEntityPositionGetRotation(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + + if(!lua_isnumber(L, 1)) { + luaL_error(L, "entityPositionGetRotation: entityId must be a number"); + return 0; + } + if(!lua_isnumber(L, 2)) { + luaL_error(L, "entityPositionGetRotation: componentId must be a number"); + return 0; + } + + entityid_t eid = (entityid_t)lua_tonumber(L, 1); + componentid_t cid = (componentid_t)lua_tonumber(L, 2); + + vec3 *v = (vec3 *)lua_newuserdata(L, sizeof(vec3)); + memoryZero(v, sizeof(vec3)); + entityPositionGetRotation(eid, cid, *v); + luaL_getmetatable(L, "vec3_mt"); + lua_setmetatable(L, -2); + return 1; +} + +int moduleEntityPositionSetRotation(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + + if(!lua_isnumber(L, 1)) { + luaL_error(L, "entityPositionSetRotation: entityId must be a number"); + return 0; + } + if(!lua_isnumber(L, 2)) { + luaL_error(L, "entityPositionSetRotation: componentId must be a number"); + return 0; + } + + entityid_t eid = (entityid_t)lua_tonumber(L, 1); + componentid_t cid = (componentid_t)lua_tonumber(L, 2); + vec3 *v = (vec3 *)luaL_checkudata(L, 3, "vec3_mt"); + entityPositionSetRotation(eid, cid, *v); + return 0; +} + +int moduleEntityPositionGetScale(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + + if(!lua_isnumber(L, 1)) { + luaL_error(L, "entityPositionGetScale: entityId must be a number"); + return 0; + } + if(!lua_isnumber(L, 2)) { + luaL_error(L, "entityPositionGetScale: componentId must be a number"); + return 0; + } + + entityid_t eid = (entityid_t)lua_tonumber(L, 1); + componentid_t cid = (componentid_t)lua_tonumber(L, 2); + + vec3 *v = (vec3 *)lua_newuserdata(L, sizeof(vec3)); + memoryZero(v, sizeof(vec3)); + entityPositionGetScale(eid, cid, *v); + luaL_getmetatable(L, "vec3_mt"); + lua_setmetatable(L, -2); + return 1; +} + +int moduleEntityPositionSetScale(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + + if(!lua_isnumber(L, 1)) { + luaL_error(L, "entityPositionSetScale: entityId must be a number"); + return 0; + } + if(!lua_isnumber(L, 2)) { + luaL_error(L, "entityPositionSetScale: componentId must be a number"); + return 0; + } + + entityid_t eid = (entityid_t)lua_tonumber(L, 1); + componentid_t cid = (componentid_t)lua_tonumber(L, 2); + vec3 *v = (vec3 *)luaL_checkudata(L, 3, "vec3_mt"); + entityPositionSetScale(eid, cid, *v); + return 0; +} + +int moduleEntityPositionLookAt(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + + if(!lua_isnumber(L, 1)) { + luaL_error(L, "entityPositionLookAt: entityId must be a number"); + return 0; + } + if(!lua_isnumber(L, 2)) { + luaL_error(L, "entityPositionLookAt: componentId must be a number"); + return 0; + } + + entityid_t eid = (entityid_t)lua_tonumber(L, 1); + componentid_t cid = (componentid_t)lua_tonumber(L, 2); + vec3 *target = (vec3 *)luaL_checkudata(L, 3, "vec3_mt"); + vec3 *up = (vec3 *)luaL_checkudata(L, 4, "vec3_mt"); + vec3 *eye = (vec3 *)luaL_checkudata(L, 5, "vec3_mt"); + entityPositionLookAt(eid, cid, *target, *up, *eye); + return 0; +} diff --git a/src/dusk/script/module/entity/moduleentityposition.h b/src/dusk/script/module/entity/moduleentityposition.h new file mode 100644 index 00000000..1027f329 --- /dev/null +++ b/src/dusk/script/module/entity/moduleentityposition.h @@ -0,0 +1,53 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "script/scriptcontext.h" + +/** + * Registers the entityposition module within the given script context. + * Provides get/set functions for position, rotation, scale, and lookAt. + * + * @param ctx The script context to register the module in. + */ +void moduleEntityPosition(scriptcontext_t *ctx); + +/** + * Lua binding: entityPositionGetPosition(entityId, componentId) -> vec3 + */ +int moduleEntityPositionGetPosition(lua_State *L); + +/** + * Lua binding: entityPositionSetPosition(entityId, componentId, vec3) + */ +int moduleEntityPositionSetPosition(lua_State *L); + +/** + * Lua binding: entityPositionGetRotation(entityId, componentId) -> vec3 (radians) + */ +int moduleEntityPositionGetRotation(lua_State *L); + +/** + * Lua binding: entityPositionSetRotation(entityId, componentId, vec3) (radians) + */ +int moduleEntityPositionSetRotation(lua_State *L); + +/** + * Lua binding: entityPositionGetScale(entityId, componentId) -> vec3 + */ +int moduleEntityPositionGetScale(lua_State *L); + +/** + * Lua binding: entityPositionSetScale(entityId, componentId, vec3) + */ +int moduleEntityPositionSetScale(lua_State *L); + +/** + * Lua binding: entityPositionLookAt(entityId, componentId, target, up, eye) + * All vec3 arguments. + */ +int moduleEntityPositionLookAt(lua_State *L); diff --git a/src/dusk/script/module/entity/moduleentityscript.c b/src/dusk/script/module/entity/moduleentityscript.c new file mode 100644 index 00000000..268cfcec --- /dev/null +++ b/src/dusk/script/module/entity/moduleentityscript.c @@ -0,0 +1,77 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "moduleentityscript.h" +#include "assert/assert.h" +#include "error/error.h" +#include "entity/component/script/entityscript.h" + +void moduleEntityScript(scriptcontext_t *ctx) { + assertNotNull(ctx, "Script context cannot be NULL"); + + lua_State *l = ctx->luaState; + lua_register(l, "entityScriptExec", moduleEntityScriptExec); + lua_register(l, "entityScriptExecAsset", moduleEntityScriptExecAsset); +} + +int moduleEntityScriptExec(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + + if(!lua_isnumber(L, 1)) { + luaL_error(L, "entityScriptExec: entityId must be a number"); + return 0; + } + if(!lua_isnumber(L, 2)) { + luaL_error(L, "entityScriptExec: componentId must be a number"); + return 0; + } + if(!lua_isstring(L, 3)) { + luaL_error(L, "entityScriptExec: script must be a string"); + return 0; + } + + entityid_t eid = (entityid_t)lua_tonumber(L, 1); + componentid_t cid = (componentid_t)lua_tonumber(L, 2); + const char_t *script = lua_tostring(L, 3); + + errorret_t ret = entityScriptExec(eid, cid, script); + if(ret.code != ERROR_OK) { + errorCatch(errorPrint(ret)); + luaL_error(L, "entityScriptExec failed"); + return 0; + } + return 0; +} + +int moduleEntityScriptExecAsset(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + + if(!lua_isnumber(L, 1)) { + luaL_error(L, "entityScriptExecAsset: entityId must be a number"); + return 0; + } + if(!lua_isnumber(L, 2)) { + luaL_error(L, "entityScriptExecAsset: componentId must be a number"); + return 0; + } + if(!lua_isstring(L, 3)) { + luaL_error(L, "entityScriptExecAsset: assetName must be a string"); + return 0; + } + + entityid_t eid = (entityid_t)lua_tonumber(L, 1); + componentid_t cid = (componentid_t)lua_tonumber(L, 2); + const char_t *assetName = lua_tostring(L, 3); + + errorret_t ret = entityScriptExecAsset(eid, cid, assetName); + if(ret.code != ERROR_OK) { + errorCatch(errorPrint(ret)); + luaL_error(L, "entityScriptExecAsset failed"); + return 0; + } + return 0; +} diff --git a/src/dusk/script/module/entity/moduleentityscript.h b/src/dusk/script/module/entity/moduleentityscript.h new file mode 100644 index 00000000..fd49e046 --- /dev/null +++ b/src/dusk/script/module/entity/moduleentityscript.h @@ -0,0 +1,29 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "script/scriptcontext.h" + +/** + * Registers the entityscript module within the given script context. + * Provides functions to execute Lua code on another entity's script component. + * + * @param ctx The script context to register the module in. + */ +void moduleEntityScript(scriptcontext_t *ctx); + +/** + * Lua binding: entityScriptExec(entityId, componentId, scriptString) + * Executes an inline Lua string on the target entity's script component. + */ +int moduleEntityScriptExec(lua_State *L); + +/** + * Lua binding: entityScriptExecAsset(entityId, componentId, assetName) + * Executes a Lua script file from assets on the target entity's script component. + */ +int moduleEntityScriptExecAsset(lua_State *L); diff --git a/src/dusk/script/scriptmodule.c b/src/dusk/script/scriptmodule.c index 59d0ce68..0f795759 100644 --- a/src/dusk/script/scriptmodule.c +++ b/src/dusk/script/scriptmodule.c @@ -22,6 +22,13 @@ #include "script/module/display/modulescreen.h" #include "script/module/display/moduletexture.h" #include "script/module/display/moduletileset.h" +#include "script/module/entity/moduleentity.h" +#include "script/module/entity/moduleentityposition.h" +#include "script/module/entity/moduleentitycamera.h" +#include "script/module/entity/moduleentitymesh.h" +#include "script/module/entity/moduleentitymaterial.h" +#include "script/module/entity/moduleentityphysics.h" +#include "script/module/entity/moduleentityscript.h" #include "script/scriptgame.h" #include "util/string.h" @@ -42,6 +49,13 @@ const scriptmodule_t SCRIPT_MODULE_LIST[] = { { .name = "texture", .callback = moduleTexture }, { .name = "tileset", .callback = moduleTileset }, { .name = "shader", .callback = moduleShader }, + { .name = "entity", .callback = moduleEntity }, + { .name = "entityposition", .callback = moduleEntityPosition }, + { .name = "entitycamera", .callback = moduleEntityCamera }, + { .name = "entitymesh", .callback = moduleEntityMesh }, + { .name = "entitymaterial", .callback = moduleEntityMaterial }, + { .name = "entityphysics", .callback = moduleEntityPhysics }, + { .name = "entityscript", .callback = moduleEntityScript }, #ifdef SCRIPT_GAME_LIST SCRIPT_GAME_LIST