diff --git a/CMakeLists.txt b/CMakeLists.txt index 736b9a2f..33f0bc60 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,8 +9,8 @@ cmake_minimum_required(VERSION 3.13) set(CMAKE_C_STANDARD 11) set(CMAKE_C_STANDARD_REQUIRED ON) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules) -# [cmake] This is allowed only when policy CMP0079 is set to NEW. cmake_policy(SET CMP0079 NEW) +set(FETCHCONTENT_UPDATES_DISCONNECTED ON) option(DUSK_BUILD_TESTS "Enable tests" OFF) diff --git a/assets/entities/CubeEntity.js b/assets/entities/CubeEntity.js index 8bc0f3d7..d9aa5ca1 100644 --- a/assets/entities/CubeEntity.js +++ b/assets/entities/CubeEntity.js @@ -5,6 +5,9 @@ function CubeEntity() { this.add(MESH); this.add(MATERIAL); + + this.cubeMesh = Mesh.createCube(); + this.mesh.mesh = this.cubeMesh; } CubeEntity.prototype = Object.create(OverworldEntity.prototype); diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 74b611a4..093f4719 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -4,7 +4,6 @@ # https://opensource.org/licenses/MIT add_subdirectory(dusk) -add_subdirectory(duskrpg) if(DUSK_TARGET_SYSTEM STREQUAL "linux" OR DUSK_TARGET_SYSTEM STREQUAL "knulli") add_subdirectory(dusklinux) diff --git a/src/dusk/CMakeLists.txt b/src/dusk/CMakeLists.txt index 9cb16d47..11682e65 100644 --- a/src/dusk/CMakeLists.txt +++ b/src/dusk/CMakeLists.txt @@ -56,6 +56,8 @@ target_sources(${DUSK_BINARY_TARGET_NAME} # Subdirs add_subdirectory(assert) add_subdirectory(asset) +add_subdirectory(item) +add_subdirectory(story) add_subdirectory(console) add_subdirectory(display) add_subdirectory(log) diff --git a/src/dusk/engine/engine.c b/src/dusk/engine/engine.c index 4bd4cc55..03ad998c 100644 --- a/src/dusk/engine/engine.c +++ b/src/dusk/engine/engine.c @@ -18,11 +18,11 @@ #include "assert/assert.h" #include "entity/entitymanager.h" #include "entity/component/physics/entityphysics.h" -#include "game/game.h" #include "physics/physicsmanager.h" #include "network/network.h" #include "system/system.h" #include "console/console.h" +#include "item/backpack.h" double jerry_port_current_time(void) { dusktimeepoch_t epoch = timeGetEpoch(); @@ -54,9 +54,9 @@ errorret_t engineInit(const int32_t argc, const char_t **argv) { errorChain(uiInit()); errorChain(sceneInit()); entityManagerInit(); + backpackInit(); physicsManagerInit(); errorChain(networkInit()); - errorChain(gameInit()); /* Run the init script. */ consolePrint("Engine initialized"); @@ -73,7 +73,6 @@ errorret_t engineUpdate(void) { consoleUpdate(); uiUpdate(); physicsManagerUpdate(); - errorChain(gameUpdate()); errorChain(displayUpdate()); if(inputPressed(INPUT_ACTION_RAGEQUIT)) ENGINE.running = false; @@ -91,7 +90,6 @@ void engineExit(void) { errorret_t engineDispose(void) { sceneDispose(); - errorChain(gameDispose()); errorChain(networkDispose()); entityManagerDispose(); localeManagerDispose(); diff --git a/src/dusk/entity/component/display/entitymesh.c b/src/dusk/entity/component/display/entitymesh.c index 8f173735..c0fd2c2a 100644 --- a/src/dusk/entity/component/display/entitymesh.c +++ b/src/dusk/entity/component/display/entitymesh.c @@ -7,11 +7,6 @@ #include "entitymesh.h" #include "entity/entitymanager.h" -#include "assert/assert.h" -#include "util/memory.h" -#include "display/mesh/cube.h" -#include "display/mesh/plane.h" -#include "display/mesh/capsule.h" void entityMeshInit( const entityid_t entityId, @@ -20,9 +15,7 @@ void entityMeshInit( entitymesh_t *comp = componentGetData( entityId, componentId, COMPONENT_TYPE_MESH ); - comp->mesh = &CUBE_MESH_SIMPLE; - comp->ownedVertices = NULL; - comp->ownsData = false; + comp->mesh = NULL; } mesh_t * entityMeshGetMesh( @@ -50,98 +43,4 @@ void entityMeshDispose( const entityid_t entityId, const componentid_t componentId ) { - entitymesh_t *comp = componentGetData( - entityId, componentId, COMPONENT_TYPE_MESH - ); - if(!comp->ownsData) return; - (void)meshDispose(&comp->ownedMesh); - memoryFree(comp->ownedVertices); - comp->ownedVertices = NULL; - comp->ownsData = false; - comp->mesh = NULL; } - -errorret_t entityMeshGeneratePlane( - const entityid_t entityId, - const componentid_t componentId, - const float_t width, - const float_t height -) { - entitymesh_t *comp = componentGetData( - entityId, componentId, COMPONENT_TYPE_MESH - ); - entityMeshDispose(entityId, componentId); - - comp->ownedVertices = memoryAllocate( - PLANE_VERTEX_COUNT * sizeof(meshvertex_t) - ); - assertNotNull(comp->ownedVertices, "Failed to allocate plane vertices"); - - vec3 min = { -width * 0.5f, 0.0f, -height * 0.5f }; - vec3 max = { width * 0.5f, 0.0f, height * 0.5f }; - vec2 uvMin = { 0.0f, 0.0f }; - vec2 uvMax = { 1.0f, 1.0f }; - planeBuffer( - comp->ownedVertices, - PLANE_AXIS_XZ, - min, - max - #if MESH_ENABLE_COLOR - , COLOR_WHITE_4B - #endif - , uvMin, - uvMax - ); - - errorChain(meshInit( - &comp->ownedMesh, - PLANE_PRIMITIVE_TYPE, - PLANE_VERTEX_COUNT, - comp->ownedVertices - )); - - comp->mesh = &comp->ownedMesh; - comp->ownsData = true; - errorOk(); -} - -errorret_t entityMeshGenerateCapsule( - const entityid_t entityId, - const componentid_t componentId, - const float_t radius, - const float_t halfHeight -) { - entitymesh_t *comp = componentGetData( - entityId, componentId, COMPONENT_TYPE_MESH - ); - entityMeshDispose(entityId, componentId); - - comp->ownedVertices = memoryAllocate( - CAPSULE_VERTEX_COUNT * sizeof(meshvertex_t) - ); - assertNotNull(comp->ownedVertices, "Failed to allocate capsule vertices"); - - vec3 center = { 0.0f, 0.0f, 0.0f }; - capsuleBuffer( - comp->ownedVertices, - center, - radius, - halfHeight, - CAPSULE_CAP_RINGS, - CAPSULE_SECTORS - #if MESH_ENABLE_COLOR - , COLOR_WHITE_4B - #endif - ); - - errorChain(meshInit( - &comp->ownedMesh, - CAPSULE_PRIMITIVE_TYPE, - CAPSULE_VERTEX_COUNT, - comp->ownedVertices - )); - - comp->mesh = &comp->ownedMesh; - comp->ownsData = true; - errorOk(); -} \ No newline at end of file diff --git a/src/dusk/entity/component/display/entitymesh.h b/src/dusk/entity/component/display/entitymesh.h index 1f058d04..008cdc1d 100644 --- a/src/dusk/entity/component/display/entitymesh.h +++ b/src/dusk/entity/component/display/entitymesh.h @@ -11,9 +11,6 @@ typedef struct { mesh_t *mesh; - mesh_t ownedMesh; - meshvertex_t *ownedVertices; - bool_t ownsData; } entitymesh_t; /** @@ -53,7 +50,7 @@ void entityMeshSetMesh( ); /** - * Disposes the entity mesh component, freeing any owned mesh data. + * Disposes the entity mesh component. * * @param entityId The entity ID. * @param componentId The component ID. @@ -62,35 +59,3 @@ void entityMeshDispose( const entityid_t entityId, const componentid_t componentId ); - -/** - * Generates an XZ-aligned plane mesh owned by the component. - * - * @param entityId The entity ID. - * @param componentId The component ID. - * @param width Width of the plane along the X axis. - * @param height Height of the plane along the Z axis. - * @return Error return value. - */ -errorret_t entityMeshGeneratePlane( - const entityid_t entityId, - const componentid_t componentId, - const float_t width, - const float_t height -); - -/** - * Generates a Y-axis capsule mesh owned by the component. - * - * @param entityId The entity ID. - * @param componentId The component ID. - * @param radius Radius of the cylinder and hemisphere caps. - * @param halfHeight Half-height of the cylindrical section. - * @return Error return value. - */ -errorret_t entityMeshGenerateCapsule( - const entityid_t entityId, - const componentid_t componentId, - const float_t radius, - const float_t halfHeight -); \ No newline at end of file diff --git a/src/duskrpg/item/CMakeLists.txt b/src/dusk/item/CMakeLists.txt similarity index 100% rename from src/duskrpg/item/CMakeLists.txt rename to src/dusk/item/CMakeLists.txt diff --git a/src/duskrpg/item/backpack.c b/src/dusk/item/backpack.c similarity index 100% rename from src/duskrpg/item/backpack.c rename to src/dusk/item/backpack.c diff --git a/src/duskrpg/item/backpack.h b/src/dusk/item/backpack.h similarity index 100% rename from src/duskrpg/item/backpack.h rename to src/dusk/item/backpack.h diff --git a/src/duskrpg/item/inventory.c b/src/dusk/item/inventory.c similarity index 100% rename from src/duskrpg/item/inventory.c rename to src/dusk/item/inventory.c diff --git a/src/duskrpg/item/inventory.h b/src/dusk/item/inventory.h similarity index 100% rename from src/duskrpg/item/inventory.h rename to src/dusk/item/inventory.h diff --git a/src/duskrpg/item/item.csv b/src/dusk/item/item.csv similarity index 100% rename from src/duskrpg/item/item.csv rename to src/dusk/item/item.csv diff --git a/src/dusk/script/module/display/modulemesh.h b/src/dusk/script/module/display/modulemesh.h new file mode 100644 index 00000000..b81e6c21 --- /dev/null +++ b/src/dusk/script/module/display/modulemesh.h @@ -0,0 +1,452 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "script/module/modulebase.h" +#include "script/scriptproto.h" +#include "script/module/math/modulevec3ref.h" +#include "display/mesh/mesh.h" +#include "display/mesh/cube.h" +#include "display/mesh/quad.h" +#include "display/mesh/sphere.h" +#include "display/mesh/plane.h" +#include "display/mesh/capsule.h" +#include "display/mesh/triprism.h" + +typedef struct { + mesh_t mesh; + meshvertex_t *vertices; + int32_t vertexCount; + bool_t initialized; +} meshscript_t; + +typedef struct { + meshvertex_t *vertex; +} meshvertexscript_t; + +static scriptproto_t MODULE_MESH_PROTO; +static scriptproto_t MODULE_MESH_VERTEX_PROTO; + +static inline meshscript_t * moduleMeshGet( + const jerry_call_info_t *callInfo +) { + return (meshscript_t*)scriptProtoGetValue( + &MODULE_MESH_PROTO, callInfo->this_value + ); +} + +static inline meshscript_t * moduleMeshFrom(const jerry_value_t val) { + return (meshscript_t*)scriptProtoGetValue(&MODULE_MESH_PROTO, val); +} + +static void moduleMeshFreeData( + void *ptr, + jerry_object_native_info_t *info +) { + (void)info; + meshscript_t *ms = (meshscript_t*)ptr; + if(ms->initialized) (void)meshDispose(&ms->mesh); + if(ms->vertices) memoryFree(ms->vertices); + memoryFree(ptr); +} + +// Creates a new JS Mesh object from an already-filled heap-allocated meshscript_t. +static jerry_value_t moduleMeshWrapNew(meshscript_t *ms) { + jerry_value_t obj = jerry_object(); + jerry_object_set_native_ptr(obj, &MODULE_MESH_PROTO.info, ms); + jerry_object_set_proto(obj, MODULE_MESH_PROTO.prototype); + + jerry_value_t arr = jerry_array((jerry_length_t)ms->vertexCount); + for(int32_t i = 0; i < ms->vertexCount; i++) { + meshvertexscript_t mv = { .vertex = &ms->vertices[i] }; + jerry_value_t vobj = scriptProtoCreateValue(&MODULE_MESH_VERTEX_PROTO, &mv); + jerry_value_t res = jerry_object_set_index(arr, (uint32_t)i, vobj); + jerry_value_free(res); + jerry_value_free(vobj); + } + jerry_value_t key = jerry_string_sz("_verts"); + jerry_object_set(obj, key, arr); + jerry_value_free(key); + jerry_value_free(arr); + + return obj; +} + +// Allocates a meshscript_t for the given vertex count and buffers it. +// Caller is responsible for populating ms->vertices before calling meshInit. +static meshscript_t * moduleMeshAlloc(const int32_t vertexCount) { + meshscript_t *ms = (meshscript_t*)memoryAllocate(sizeof(meshscript_t)); + memoryZero(ms, sizeof(meshscript_t)); + ms->vertices = (meshvertex_t*)memoryAllocate( + (size_t)vertexCount * sizeof(meshvertex_t) + ); + memoryZero(ms->vertices, (size_t)vertexCount * sizeof(meshvertex_t)); + ms->vertexCount = vertexCount; + return ms; +} + +// Finalizes a meshscript_t: uploads to GPU and wraps in a JS object. +static jerry_value_t moduleMeshFinalize( + meshscript_t *ms, + const meshprimitivetype_t type +) { + (void)meshInit(&ms->mesh, type, ms->vertexCount, ms->vertices); + ms->initialized = true; + return moduleMeshWrapNew(ms); +} + +// ---- MeshVertex ---- + +moduleBaseFunction(moduleMeshVertexGetPosition) { + meshvertexscript_t *mv = (meshvertexscript_t*)scriptProtoGetValue( + &MODULE_MESH_VERTEX_PROTO, callInfo->this_value + ); + if(!mv) return jerry_undefined(); + return moduleVec3RefPush(mv->vertex->pos, NULL, NULL); +} + +moduleBaseFunction(moduleMeshVertexSetPosition) { + if(argc < 1) return moduleBaseThrow("Expected position argument"); + meshvertexscript_t *mv = (meshvertexscript_t*)scriptProtoGetValue( + &MODULE_MESH_VERTEX_PROTO, callInfo->this_value + ); + if(!mv) return jerry_undefined(); + vec3 v; + if(!moduleVec3AnyCheck(args[0], v)) return moduleBaseThrow("Expected Vec3"); + glm_vec3_copy(v, mv->vertex->pos); + return jerry_undefined(); +} + +// ---- Mesh instance ---- + +moduleBaseFunction(moduleMeshConstructor) { + if(argc < 1) return moduleBaseThrow("Expected vertex count"); + moduleBaseRequireNumber(0); + + int32_t vertexCount = (int32_t)jerry_value_as_number(args[0]); + if(vertexCount <= 0) return moduleBaseThrow("Vertex count must be > 0"); + + meshscript_t *ms = moduleMeshAlloc(vertexCount); + jerry_object_set_native_ptr( + callInfo->this_value, &MODULE_MESH_PROTO.info, ms + ); + + jerry_value_t arr = jerry_array((jerry_length_t)vertexCount); + for(int32_t i = 0; i < vertexCount; i++) { + meshvertexscript_t mv = { .vertex = &ms->vertices[i] }; + jerry_value_t vobj = scriptProtoCreateValue(&MODULE_MESH_VERTEX_PROTO, &mv); + jerry_value_t res = jerry_object_set_index(arr, (uint32_t)i, vobj); + jerry_value_free(res); + jerry_value_free(vobj); + } + jerry_value_t key = jerry_string_sz("_verts"); + jerry_object_set(callInfo->this_value, key, arr); + jerry_value_free(key); + jerry_value_free(arr); + + return jerry_undefined(); +} + +moduleBaseFunction(moduleMeshGetVertices) { + jerry_value_t key = jerry_string_sz("_verts"); + jerry_value_t arr = jerry_object_get(callInfo->this_value, key); + jerry_value_free(key); + return arr; +} + +moduleBaseFunction(moduleMeshGetVertexCount) { + meshscript_t *ms = moduleMeshGet(callInfo); + if(!ms) return jerry_undefined(); + return jerry_number((double)ms->vertexCount); +} + +moduleBaseFunction(moduleMeshFlush) { + meshscript_t *ms = moduleMeshGet(callInfo); + if(!ms) return jerry_undefined(); + if(!ms->initialized) { + (void)meshInit( + &ms->mesh, + MESH_PRIMITIVE_TYPE_TRIANGLES, + ms->vertexCount, + ms->vertices + ); + ms->initialized = true; + } else { + (void)meshFlush(&ms->mesh, 0, -1); + } + return jerry_undefined(); +} + +moduleBaseFunction(moduleMeshDispose) { + meshscript_t *ms = moduleMeshGet(callInfo); + if(!ms) return jerry_undefined(); + if(ms->initialized) { + (void)meshDispose(&ms->mesh); + ms->initialized = false; + } + if(ms->vertices) { + memoryFree(ms->vertices); + ms->vertices = NULL; + } + return jerry_undefined(); +} + +moduleBaseFunction(moduleMeshToString) { + meshscript_t *ms = moduleMeshGet(callInfo); + if(!ms) return jerry_string_sz("Mesh(?)"); + char_t buf[64]; + stringFormat(buf, sizeof(buf), "Mesh(%d)", ms->vertexCount); + return jerry_string_sz(buf); +} + +// ---- Static defaults (engine-owned, not GC'd) ---- + +moduleBaseFunction(moduleMeshDefaultCube) { + return moduleBaseWrapPointer(&CUBE_MESH_SIMPLE); +} +moduleBaseFunction(moduleMeshDefaultQuad) { + return moduleBaseWrapPointer(&QUAD_MESH_SIMPLE); +} +moduleBaseFunction(moduleMeshDefaultSphere) { + return moduleBaseWrapPointer(&SPHERE_MESH_SIMPLE); +} +moduleBaseFunction(moduleMeshDefaultPlane) { + return moduleBaseWrapPointer(&PLANE_MESH_SIMPLE); +} +moduleBaseFunction(moduleMeshDefaultCapsule) { + return moduleBaseWrapPointer(&CAPSULE_MESH_SIMPLE); +} +moduleBaseFunction(moduleMeshDefaultTriPrism) { + return moduleBaseWrapPointer(&TRIPRISM_MESH_SIMPLE); +} + +// ---- Static factory methods ---- + +// Mesh.createCube(min?, max?) — defaults to (-0.5,-0.5,-0.5)..(0.5,0.5,0.5) +moduleBaseFunction(moduleMeshCreateCube) { + vec3 min = { -0.5f, -0.5f, -0.5f }; + vec3 max = { 0.5f, 0.5f, 0.5f }; + if(argc >= 2) { + if(!moduleVec3AnyCheck(args[0], min)) { + return moduleBaseThrow("Mesh.createCube: expected Vec3 min"); + } + if(!moduleVec3AnyCheck(args[1], max)) { + return moduleBaseThrow("Mesh.createCube: expected Vec3 max"); + } + } + meshscript_t *ms = moduleMeshAlloc(CUBE_VERTEX_COUNT); + cubeBuffer( + ms->vertices, min, max + #if MESH_ENABLE_COLOR + , COLOR_WHITE_4B + #endif + ); + return moduleMeshFinalize(ms, CUBE_PRIMITIVE_TYPE); +} + +// Mesh.createQuad(minX, minY, maxX, maxY) — 2D XY quad, u0-u1=0-1 v0-v1=0-1 +moduleBaseFunction(moduleMeshCreateQuad) { + float_t minX = -0.5f, minY = -0.5f, maxX = 0.5f, maxY = 0.5f; + if(argc >= 4) { + if(!jerry_value_is_number(args[0]) || !jerry_value_is_number(args[1]) || + !jerry_value_is_number(args[2]) || !jerry_value_is_number(args[3])) { + return moduleBaseThrow("Mesh.createQuad: expected (minX, minY, maxX, maxY)"); + } + minX = (float_t)jerry_value_as_number(args[0]); + minY = (float_t)jerry_value_as_number(args[1]); + maxX = (float_t)jerry_value_as_number(args[2]); + maxY = (float_t)jerry_value_as_number(args[3]); + } + meshscript_t *ms = moduleMeshAlloc(QUAD_VERTEX_COUNT); + quadBuffer( + ms->vertices, + minX, minY, maxX, maxY, + 0.0f, 0.0f, 1.0f, 1.0f + #if MESH_ENABLE_COLOR + , COLOR_WHITE_4B + #endif + ); + return moduleMeshFinalize(ms, QUAD_PRIMITIVE_TYPE); +} + +// Mesh.createSphere(radius?, stacks?, sectors?) +moduleBaseFunction(moduleMeshCreateSphere) { + float_t radius = 0.5f; + int32_t stacks = SPHERE_STACKS; + int32_t sectors = SPHERE_SECTORS; + if(argc >= 1 && jerry_value_is_number(args[0])) { + radius = (float_t)jerry_value_as_number(args[0]); + } + if(argc >= 2 && jerry_value_is_number(args[1])) { + stacks = (int32_t)jerry_value_as_number(args[1]); + } + if(argc >= 3 && jerry_value_is_number(args[2])) { + sectors = (int32_t)jerry_value_as_number(args[2]); + } + int32_t vertexCount = stacks * sectors * 6; + vec3 center = { 0.0f, 0.0f, 0.0f }; + meshscript_t *ms = moduleMeshAlloc(vertexCount); + sphereBuffer( + ms->vertices, center, radius, stacks, sectors + #if MESH_ENABLE_COLOR + , COLOR_WHITE_4B + #endif + ); + return moduleMeshFinalize(ms, SPHERE_PRIMITIVE_TYPE); +} + +// Mesh.createPlane(width?, height?) — XZ-aligned, centered at origin +moduleBaseFunction(moduleMeshCreatePlane) { + float_t width = 1.0f, height = 1.0f; + if(argc >= 1 && jerry_value_is_number(args[0])) { + width = (float_t)jerry_value_as_number(args[0]); + } + if(argc >= 2 && jerry_value_is_number(args[1])) { + height = (float_t)jerry_value_as_number(args[1]); + } + vec3 min = { -width * 0.5f, 0.0f, -height * 0.5f }; + vec3 max = { width * 0.5f, 0.0f, height * 0.5f }; + vec2 uvMin = { 0.0f, 0.0f }; + vec2 uvMax = { 1.0f, 1.0f }; + meshscript_t *ms = moduleMeshAlloc(PLANE_VERTEX_COUNT); + planeBuffer( + ms->vertices, PLANE_AXIS_XZ, min, max + #if MESH_ENABLE_COLOR + , COLOR_WHITE_4B + #endif + , uvMin, uvMax + ); + return moduleMeshFinalize(ms, PLANE_PRIMITIVE_TYPE); +} + +// Mesh.createCapsule(radius?, halfHeight?, capRings?, sectors?) +moduleBaseFunction(moduleMeshCreateCapsule) { + float_t radius = 0.5f; + float_t halfHeight = 0.5f; + int32_t capRings = CAPSULE_CAP_RINGS; + int32_t sectors = CAPSULE_SECTORS; + if(argc >= 1 && jerry_value_is_number(args[0])) { + radius = (float_t)jerry_value_as_number(args[0]); + } + if(argc >= 2 && jerry_value_is_number(args[1])) { + halfHeight = (float_t)jerry_value_as_number(args[1]); + } + if(argc >= 3 && jerry_value_is_number(args[2])) { + capRings = (int32_t)jerry_value_as_number(args[2]); + } + if(argc >= 4 && jerry_value_is_number(args[3])) { + sectors = (int32_t)jerry_value_as_number(args[3]); + } + int32_t vertexCount = (2 * capRings + 1) * sectors * 6; + vec3 center = { 0.0f, 0.0f, 0.0f }; + meshscript_t *ms = moduleMeshAlloc(vertexCount); + capsuleBuffer( + ms->vertices, center, radius, halfHeight, capRings, sectors + #if MESH_ENABLE_COLOR + , COLOR_WHITE_4B + #endif + ); + return moduleMeshFinalize(ms, CAPSULE_PRIMITIVE_TYPE); +} + +// Mesh.createTriPrism(x0, y0, x1, y1, x2, y2, minZ, maxZ) +moduleBaseFunction(moduleMeshCreateTriPrism) { + if(argc < 8) { + return moduleBaseThrow( + "Mesh.createTriPrism: expected (x0,y0, x1,y1, x2,y2, minZ, maxZ)" + ); + } + float_t x0 = (float_t)jerry_value_as_number(args[0]); + float_t y0 = (float_t)jerry_value_as_number(args[1]); + float_t x1 = (float_t)jerry_value_as_number(args[2]); + float_t y1 = (float_t)jerry_value_as_number(args[3]); + float_t x2 = (float_t)jerry_value_as_number(args[4]); + float_t y2 = (float_t)jerry_value_as_number(args[5]); + float_t minZ = (float_t)jerry_value_as_number(args[6]); + float_t maxZ = (float_t)jerry_value_as_number(args[7]); + meshscript_t *ms = moduleMeshAlloc(TRIPRISM_VERTEX_COUNT); + triPrismBuffer( + ms->vertices, x0, y0, x1, y1, x2, y2, minZ, maxZ + #if MESH_ENABLE_COLOR + , COLOR_WHITE_4B + #endif + ); + return moduleMeshFinalize(ms, TRIPRISM_PRIMITIVE_TYPE); +} + +// ---- Registration ---- + +static void moduleMesh(void) { + // MeshVertex - internal type, no global constructor + scriptProtoInit( + &MODULE_MESH_VERTEX_PROTO, NULL, + sizeof(meshvertexscript_t), NULL + ); + scriptProtoDefineProp( + &MODULE_MESH_VERTEX_PROTO, "position", + moduleMeshVertexGetPosition, moduleMeshVertexSetPosition + ); + + // Mesh - global constructor: new Mesh(vertexCount) + scriptProtoInit( + &MODULE_MESH_PROTO, "Mesh", + sizeof(meshscript_t), moduleMeshConstructor + ); + MODULE_MESH_PROTO.info.free_cb = moduleMeshFreeData; + + scriptProtoDefineToString(&MODULE_MESH_PROTO, moduleMeshToString); + scriptProtoDefineProp( + &MODULE_MESH_PROTO, "vertices", + moduleMeshGetVertices, NULL + ); + scriptProtoDefineProp( + &MODULE_MESH_PROTO, "vertexCount", + moduleMeshGetVertexCount, NULL + ); + scriptProtoDefineFunc(&MODULE_MESH_PROTO, "flush", moduleMeshFlush); + scriptProtoDefineFunc(&MODULE_MESH_PROTO, "dispose", moduleMeshDispose); + + // Static default mesh references + scriptProtoDefineStaticProp( + &MODULE_MESH_PROTO, "DEFAULT_CUBE", moduleMeshDefaultCube, NULL + ); + scriptProtoDefineStaticProp( + &MODULE_MESH_PROTO, "DEFAULT_QUAD", moduleMeshDefaultQuad, NULL + ); + scriptProtoDefineStaticProp( + &MODULE_MESH_PROTO, "DEFAULT_SPHERE", moduleMeshDefaultSphere, NULL + ); + scriptProtoDefineStaticProp( + &MODULE_MESH_PROTO, "DEFAULT_PLANE", moduleMeshDefaultPlane, NULL + ); + scriptProtoDefineStaticProp( + &MODULE_MESH_PROTO, "DEFAULT_CAPSULE", moduleMeshDefaultCapsule, NULL + ); + scriptProtoDefineStaticProp( + &MODULE_MESH_PROTO, "DEFAULT_TRIPRISM", moduleMeshDefaultTriPrism, NULL + ); + + // Static factory methods + scriptProtoDefineStaticFunc( + &MODULE_MESH_PROTO, "createCube", moduleMeshCreateCube + ); + scriptProtoDefineStaticFunc( + &MODULE_MESH_PROTO, "createQuad", moduleMeshCreateQuad + ); + scriptProtoDefineStaticFunc( + &MODULE_MESH_PROTO, "createSphere", moduleMeshCreateSphere + ); + scriptProtoDefineStaticFunc( + &MODULE_MESH_PROTO, "createPlane", moduleMeshCreatePlane + ); + scriptProtoDefineStaticFunc( + &MODULE_MESH_PROTO, "createCapsule", moduleMeshCreateCapsule + ); + scriptProtoDefineStaticFunc( + &MODULE_MESH_PROTO, "createTriPrism", moduleMeshCreateTriPrism + ); +} diff --git a/src/dusk/script/module/entity/component/moduleentitymesh.h b/src/dusk/script/module/entity/component/moduleentitymesh.h index a9eda630..85f57028 100644 --- a/src/dusk/script/module/entity/component/moduleentitymesh.h +++ b/src/dusk/script/module/entity/component/moduleentitymesh.h @@ -8,6 +8,7 @@ #pragma once #include "script/module/modulebase.h" #include "script/scriptproto.h" +#include "script/module/display/modulemesh.h" #include "entity/entity.h" #include "entity/component/display/entitymesh.h" #include "moduleentityposition.h" @@ -24,16 +25,32 @@ static entitymesh_t * moduleEntityMeshGet( return (entitymesh_t*)componentGetData(h->eid, h->cid, COMPONENT_TYPE_MESH); } -moduleBaseFunction(moduleEntityMeshAdd) { - if(argc < 1) { - return moduleBaseThrow("Expected at least 1 argument"); +moduleBaseFunction(moduleEntityMeshGetMesh) { + entitymesh_t *comp = moduleEntityMeshGet(callInfo); + if(!comp || !comp->mesh) return jerry_undefined(); + return moduleBaseWrapPointer(comp->mesh); +} + +moduleBaseFunction(moduleEntityMeshSetMesh) { + if(argc < 1) return moduleBaseThrow("Expected a Mesh argument"); + entitymesh_t *comp = moduleEntityMeshGet(callInfo); + if(!comp) return jerry_undefined(); + + // User-created Mesh JS object (meshscript_t) + meshscript_t *ms = moduleMeshFrom(args[0]); + if(ms) { + comp->mesh = &ms->mesh; + return jerry_undefined(); } - moduleBaseRequireNumber(0); - - entityid_t id = (entityid_t)jerry_value_as_number(args[0]); - componentid_t comp = entityAddComponent(id, COMPONENT_TYPE_MESH); - componenthandle_t h = { .eid = id, .cid = comp }; - return scriptProtoCreateValue(&MODULE_ENTITY_MESH_PROTO, &h); + + // Engine-owned default mesh (Mesh.DEFAULT_CUBE etc.) — raw mesh_t* + mesh_t *raw = (mesh_t*)moduleBaseUnwrapPointer(args[0]); + if(raw) { + comp->mesh = raw; + return jerry_undefined(); + } + + return moduleBaseThrow("Expected a Mesh object or mesh constant"); } static void moduleEntityMESH(void) { @@ -41,4 +58,8 @@ static void moduleEntityMESH(void) { &MODULE_ENTITY_MESH_PROTO, NULL, sizeof(componenthandle_t), NULL ); + scriptProtoDefineProp( + &MODULE_ENTITY_MESH_PROTO, "mesh", + moduleEntityMeshGetMesh, moduleEntityMeshSetMesh + ); } diff --git a/src/dusk/script/module/item/moduleitem.h b/src/dusk/script/module/item/moduleitem.h new file mode 100644 index 00000000..6c279441 --- /dev/null +++ b/src/dusk/script/module/item/moduleitem.h @@ -0,0 +1,152 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "script/module/modulebase.h" +#include "script/scriptproto.h" +#include "item/inventory.h" +#include "item/backpack.h" +#include "item/item.h" + +typedef struct { + inventory_t *inventory; +} inventoryscript_t; + +static scriptproto_t MODULE_INVENTORY_PROTO; + +static inline inventory_t * moduleInventoryGet( + const jerry_call_info_t *callInfo +) { + inventoryscript_t *h = (inventoryscript_t*)scriptProtoGetValue( + &MODULE_INVENTORY_PROTO, callInfo->this_value + ); + return h ? h->inventory : NULL; +} + +// inventory.add(itemId, quantity) +moduleBaseFunction(moduleInventoryAdd) { + if(argc < 2) return moduleBaseThrow("Expected (itemId, quantity)"); + moduleBaseRequireNumber(0); + moduleBaseRequireNumber(1); + inventory_t *inv = moduleInventoryGet(callInfo); + if(!inv) return jerry_undefined(); + inventoryAdd( + inv, + (itemid_t)jerry_value_as_number(args[0]), + (uint8_t)jerry_value_as_number(args[1]) + ); + return jerry_undefined(); +} + +// inventory.remove(itemId) +moduleBaseFunction(moduleInventoryRemove) { + if(argc < 1) return moduleBaseThrow("Expected itemId"); + moduleBaseRequireNumber(0); + inventory_t *inv = moduleInventoryGet(callInfo); + if(!inv) return jerry_undefined(); + inventoryRemove(inv, (itemid_t)jerry_value_as_number(args[0])); + return jerry_undefined(); +} + +// inventory.set(itemId, quantity) +moduleBaseFunction(moduleInventorySet) { + if(argc < 2) return moduleBaseThrow("Expected (itemId, quantity)"); + moduleBaseRequireNumber(0); + moduleBaseRequireNumber(1); + inventory_t *inv = moduleInventoryGet(callInfo); + if(!inv) return jerry_undefined(); + inventorySet( + inv, + (itemid_t)jerry_value_as_number(args[0]), + (uint8_t)jerry_value_as_number(args[1]) + ); + return jerry_undefined(); +} + +// inventory.count(itemId) → number +moduleBaseFunction(moduleInventoryCount) { + if(argc < 1) return moduleBaseThrow("Expected itemId"); + moduleBaseRequireNumber(0); + inventory_t *inv = moduleInventoryGet(callInfo); + if(!inv) return jerry_undefined(); + return jerry_number( + (double)inventoryGetCount(inv, (itemid_t)jerry_value_as_number(args[0])) + ); +} + +// inventory.has(itemId) → boolean +moduleBaseFunction(moduleInventoryHas) { + if(argc < 1) return moduleBaseThrow("Expected itemId"); + moduleBaseRequireNumber(0); + inventory_t *inv = moduleInventoryGet(callInfo); + if(!inv) return jerry_undefined(); + return jerry_boolean( + inventoryItemExists(inv, (itemid_t)jerry_value_as_number(args[0])) + ); +} + +// inventory.isItemFull(itemId) → boolean +moduleBaseFunction(moduleInventoryIsItemFull) { + if(argc < 1) return moduleBaseThrow("Expected itemId"); + moduleBaseRequireNumber(0); + inventory_t *inv = moduleInventoryGet(callInfo); + if(!inv) return jerry_undefined(); + return jerry_boolean( + inventoryItemFull(inv, (itemid_t)jerry_value_as_number(args[0])) + ); +} + +// inventory.sort(sortBy, reverse?) +moduleBaseFunction(moduleInventorySort) { + if(argc < 1) return moduleBaseThrow("Expected sortBy"); + moduleBaseRequireNumber(0); + inventory_t *inv = moduleInventoryGet(callInfo); + if(!inv) return jerry_undefined(); + inventorysort_t sortBy = (inventorysort_t)jerry_value_as_number(args[0]); + bool_t reverse = (argc >= 2 && jerry_value_is_true(args[1])); + inventorySort(inv, sortBy, reverse); + return jerry_undefined(); +} + +// inventory.full → boolean (read-only property) +moduleBaseFunction(moduleInventoryGetFull) { + inventory_t *inv = moduleInventoryGet(callInfo); + if(!inv) return jerry_undefined(); + return jerry_boolean(inventoryIsFull(inv)); +} + +static void moduleItem(void) { + // ITEM_ID_* and ITEM_TYPE_* numeric constants + moduleBaseEval(ITEM_SCRIPT); + + // Sort constants + moduleBaseSetInt("INVENTORY_SORT_BY_ID", INVENTORY_SORT_BY_ID); + moduleBaseSetInt("INVENTORY_SORT_BY_TYPE", INVENTORY_SORT_BY_TYPE); + + // Inventory prototype — not a public constructor, only used via singletons + scriptProtoInit( + &MODULE_INVENTORY_PROTO, NULL, + sizeof(inventoryscript_t), NULL + ); + scriptProtoDefineProp( + &MODULE_INVENTORY_PROTO, "full", + moduleInventoryGetFull, NULL + ); + scriptProtoDefineFunc(&MODULE_INVENTORY_PROTO, "add", moduleInventoryAdd); + scriptProtoDefineFunc(&MODULE_INVENTORY_PROTO, "remove", moduleInventoryRemove); + scriptProtoDefineFunc(&MODULE_INVENTORY_PROTO, "set", moduleInventorySet); + scriptProtoDefineFunc(&MODULE_INVENTORY_PROTO, "count", moduleInventoryCount); + scriptProtoDefineFunc(&MODULE_INVENTORY_PROTO, "has", moduleInventoryHas); + scriptProtoDefineFunc(&MODULE_INVENTORY_PROTO, "isItemFull", moduleInventoryIsItemFull); + scriptProtoDefineFunc(&MODULE_INVENTORY_PROTO, "sort", moduleInventorySort); + + // Backpack — global singleton Inventory wrapping the engine BACKPACK + inventoryscript_t h = { .inventory = &BACKPACK }; + jerry_value_t backpackObj = scriptProtoCreateValue(&MODULE_INVENTORY_PROTO, &h); + moduleBaseSetValue("Backpack", backpackObj); + jerry_value_free(backpackObj); +} diff --git a/src/dusk/script/module/module.h b/src/dusk/script/module/module.h index ec6ab6f7..660e0487 100644 --- a/src/dusk/script/module/module.h +++ b/src/dusk/script/module/module.h @@ -13,16 +13,20 @@ #include "script/module/moduleplatform.h" #include "script/module/time/moduletime.h" #include "script/module/display/modulecolor.h" +#include "script/module/display/modulemesh.h" #include "script/module/display/modulescreen.h" #include "script/module/display/modulespritebatch.h" #include "script/module/display/moduletext.h" #include "script/module/scene/modulescene.h" #include "script/module/console/moduleconsole.h" #include "script/module/engine/moduleengine.h" +#include "script/module/item/moduleitem.h" +#include "script/module/story/modulestory.h" static void moduleRegister(void) { moduleInclude(); moduleMath(); + moduleMesh(); moduleEntity(); moduleInput(); modulePlatform(); @@ -34,4 +38,6 @@ static void moduleRegister(void) { moduleScene(); moduleConsole(); moduleEngine(); + moduleItem(); + moduleStory(); } diff --git a/src/dusk/script/module/story/modulestory.h b/src/dusk/script/module/story/modulestory.h new file mode 100644 index 00000000..11497227 --- /dev/null +++ b/src/dusk/script/module/story/modulestory.h @@ -0,0 +1,54 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "script/module/modulebase.h" +#include "script/scriptproto.h" +#include "story/storyflag.h" + +static scriptproto_t MODULE_STORY_FLAG_PROTO; + +// StoryFlag.get(flagId) → number +moduleBaseFunction(moduleStoryFlagGet) { + if(argc < 1) return moduleBaseThrow("Expected flagId"); + moduleBaseRequireNumber(0); + storyflag_t flag = (storyflag_t)jerry_value_as_number(args[0]); + if(flag <= STORY_FLAG_NULL || flag >= STORY_FLAG_COUNT) { + return moduleBaseThrow("StoryFlag.get: invalid flag ID"); + } + return jerry_number((double)storyFlagGet(flag)); +} + +// StoryFlag.set(flagId, value) +moduleBaseFunction(moduleStoryFlagSet) { + if(argc < 2) return moduleBaseThrow("Expected (flagId, value)"); + moduleBaseRequireNumber(0); + moduleBaseRequireNumber(1); + storyflag_t flag = (storyflag_t)jerry_value_as_number(args[0]); + if(flag <= STORY_FLAG_NULL || flag >= STORY_FLAG_COUNT) { + return moduleBaseThrow("StoryFlag.set: invalid flag ID"); + } + storyFlagSet(flag, (storyflagvalue_t)jerry_value_as_number(args[1])); + return jerry_undefined(); +} + +static void moduleStory(void) { + // STORY_FLAG_* numeric constants + moduleBaseEval(STORY_FLAG_SCRIPT); + + // StoryFlag — global static namespace, no instances + scriptProtoInit( + &MODULE_STORY_FLAG_PROTO, "StoryFlag", + sizeof(uint8_t), NULL + ); + scriptProtoDefineStaticFunc( + &MODULE_STORY_FLAG_PROTO, "get", moduleStoryFlagGet + ); + scriptProtoDefineStaticFunc( + &MODULE_STORY_FLAG_PROTO, "set", moduleStoryFlagSet + ); +} diff --git a/src/duskrpg/story/CMakeLists.txt b/src/dusk/story/CMakeLists.txt similarity index 100% rename from src/duskrpg/story/CMakeLists.txt rename to src/dusk/story/CMakeLists.txt diff --git a/src/duskrpg/story/storyflag.c b/src/dusk/story/storyflag.c similarity index 100% rename from src/duskrpg/story/storyflag.c rename to src/dusk/story/storyflag.c diff --git a/src/duskrpg/story/storyflag.csv b/src/dusk/story/storyflag.csv similarity index 100% rename from src/duskrpg/story/storyflag.csv rename to src/dusk/story/storyflag.csv diff --git a/src/duskrpg/story/storyflag.h b/src/dusk/story/storyflag.h similarity index 100% rename from src/duskrpg/story/storyflag.h rename to src/dusk/story/storyflag.h diff --git a/src/duskrpg/story/storyflagdefs.h b/src/dusk/story/storyflagdefs.h similarity index 100% rename from src/duskrpg/story/storyflagdefs.h rename to src/dusk/story/storyflagdefs.h diff --git a/src/duskrpg/CMakeLists.txt b/src/duskrpg/CMakeLists.txt deleted file mode 100644 index 81f49835..00000000 --- a/src/duskrpg/CMakeLists.txt +++ /dev/null @@ -1,15 +0,0 @@ -# Copyright (c) 2026 Dominic Masters -# -# This software is released under the MIT License. -# https://opensource.org/licenses/MIT - -# Includes -target_include_directories(${DUSK_LIBRARY_TARGET_NAME} - PUBLIC - ${CMAKE_CURRENT_LIST_DIR} -) - -# Subdirs -add_subdirectory(game) -add_subdirectory(item) -add_subdirectory(story) \ No newline at end of file diff --git a/src/duskrpg/game/CMakeLists.txt b/src/duskrpg/game/CMakeLists.txt deleted file mode 100644 index 3121dec3..00000000 --- a/src/duskrpg/game/CMakeLists.txt +++ /dev/null @@ -1,10 +0,0 @@ -# Copyright (c) 2025 Dominic Masters -# -# This software is released under the MIT License. -# https://opensource.org/licenses/MIT - -# Sources -target_sources(${DUSK_LIBRARY_TARGET_NAME} - PUBLIC - game.c -) \ No newline at end of file diff --git a/src/duskrpg/game/game.c b/src/duskrpg/game/game.c deleted file mode 100644 index 37222d51..00000000 --- a/src/duskrpg/game/game.c +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "game.h" - -errorret_t gameInit(void) { - errorOk(); -} - -errorret_t gameUpdate(void) { - errorOk(); -} - -errorret_t gameDispose(void) { - errorOk(); -} \ No newline at end of file diff --git a/src/duskrpg/game/game.h b/src/duskrpg/game/game.h deleted file mode 100644 index 18677fda..00000000 --- a/src/duskrpg/game/game.h +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "error/error.h" - -/** - * Initializes the game systems and loads the initial game state. - * - * @return An error code indicating success or failure. - */ -errorret_t gameInit(void); - -/** - * Updates the game state. This should be called every frame. - * - * @return An error code indicating success or failure. - */ -errorret_t gameUpdate(void); - -/** - * Disposes of game resources and shuts down the game systems. - * - * @return An error code indicating success or failure. - */ -errorret_t gameDispose(void); \ No newline at end of file diff --git a/tools/story/csv/__main__.py b/tools/story/csv/__main__.py index f7223836..d3ebed93 100644 --- a/tools/story/csv/__main__.py +++ b/tools/story/csv/__main__.py @@ -42,7 +42,14 @@ out += [ ] for flag in flags: out.append(f" [{flag_enum(flag['id'])}] = {flag['initial']},") -out += ["};", ""] +out += [ + "};", + "", + "static const char_t *STORY_FLAG_SCRIPT =", +] +for i, flag in enumerate(flags): + out.append(f" \"{flag_enum(flag['id'])} = {i + 1}\\n\"") +out += [";", ""] os.makedirs(os.path.dirname(args.output), exist_ok=True) with open(args.output, "w", encoding="utf-8") as f: