diff --git a/src/dusk/script/module/entity/component/interactable/moduleinteractable.h b/src/dusk/script/module/entity/component/interactable/moduleinteractable.h new file mode 100644 index 00000000..cea01cb9 --- /dev/null +++ b/src/dusk/script/module/entity/component/interactable/moduleinteractable.h @@ -0,0 +1,145 @@ +/** + * 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/entity/modulecomponent.h" +#include "entity/component/overworld/entityinteractable.h" +#include + +static scriptproto_t MODULE_INTERACTABLE_PROTO; + +moduleBaseFunction(moduleInteractableCtor) { + (void)callInfo; (void)args; (void)argc; + return moduleBaseThrow("Interactable cannot be instantiated with new"); +} + +static inline jscomponent_t *moduleInteractableSelf( + const jerry_call_info_t *callInfo +) { + return (jscomponent_t *)scriptProtoGetValue( + &MODULE_INTERACTABLE_PROTO, callInfo->this_value + ); +} + +static void moduleInteractableCb( + const entityid_t entityId, + const componentid_t componentId, + void *user +) { + (void)entityId; (void)componentId; + jerry_value_t fn = *((jerry_value_t *)user); + jerry_value_t ret = jerry_call(fn, jerry_undefined(), NULL, 0); + jerry_value_free(ret); +} + +moduleBaseFunction(moduleInteractableGetEntity) { + jscomponent_t *c = moduleInteractableSelf(callInfo); + if(!c) return jerry_undefined(); + return jerry_number((double)c->entityId); +} + +moduleBaseFunction(moduleInteractableGetId) { + jscomponent_t *c = moduleInteractableSelf(callInfo); + if(!c) return jerry_undefined(); + return jerry_number((double)c->componentId); +} + +/* + * onInteract getter — reads back the pinned JS function stored on this._cb. + */ +moduleBaseFunction(moduleInteractableGetOnInteract) { + (void)args; (void)argc; + jerry_value_t key = jerry_string_sz("_cb"); + jerry_value_t val = jerry_object_get(callInfo->this_value, key); + jerry_value_free(key); + return val; +} + +/* + * onInteract setter — pins the JS function on this._cb for GC safety, and + * registers a C trampoline that calls it when the player interacts. + * Passing null/undefined clears the callback. + */ +moduleBaseFunction(moduleInteractableSetOnInteract) { + jscomponent_t *c = moduleInteractableSelf(callInfo); + if(!c) return jerry_undefined(); + entityinteractable_t *d = entityInteractableGet(c->entityId, c->componentId); + if(!d) return jerry_undefined(); + + /* Free previously stored JS reference */ + if(d->user) { + jerry_value_free(*((jerry_value_t *)d->user)); + free(d->user); + d->user = NULL; + } + + jerry_value_t pin = (argc > 0) ? args[0] : jerry_undefined(); + jerry_value_t pinKey = jerry_string_sz("_cb"); + + if(jerry_value_is_function(pin)) { + jerry_value_t *stored = (jerry_value_t *)malloc(sizeof(jerry_value_t)); + *stored = jerry_value_copy(pin); + entityInteractableSetCallback( + c->entityId, c->componentId, moduleInteractableCb, stored + ); + jerry_object_set(callInfo->this_value, pinKey, pin); + } else { + entityInteractableSetCallback(c->entityId, c->componentId, NULL, NULL); + jerry_value_t undef = jerry_undefined(); + jerry_object_set(callInfo->this_value, pinKey, undef); + jerry_value_free(undef); + } + + jerry_value_free(pinKey); + return jerry_undefined(); +} + +moduleBaseFunction(moduleInteractableTrigger) { + (void)args; (void)argc; + jscomponent_t *c = moduleInteractableSelf(callInfo); + if(!c) return jerry_undefined(); + entityInteractableTrigger(c->entityId, c->componentId); + return jerry_undefined(); +} + +moduleBaseFunction(moduleInteractableToString) { + jscomponent_t *c = moduleInteractableSelf(callInfo); + if(!c) return jerry_string_sz("Interactable:invalid"); + char_t buf[32]; + snprintf(buf, sizeof(buf), "Interactable(%u)", (unsigned)c->componentId); + return jerry_string_sz(buf); +} + +static void moduleInteractableInit(void) { + scriptProtoInit( + &MODULE_INTERACTABLE_PROTO, "Interactable", + sizeof(jscomponent_t), moduleInteractableCtor + ); + + scriptProtoDefineProp( + &MODULE_INTERACTABLE_PROTO, "entity", moduleInteractableGetEntity, NULL + ); + scriptProtoDefineProp( + &MODULE_INTERACTABLE_PROTO, "id", moduleInteractableGetId, NULL + ); + scriptProtoDefineProp( + &MODULE_INTERACTABLE_PROTO, "onInteract", + moduleInteractableGetOnInteract, moduleInteractableSetOnInteract + ); + scriptProtoDefineFunc( + &MODULE_INTERACTABLE_PROTO, "trigger", moduleInteractableTrigger + ); + scriptProtoDefineToString( + &MODULE_INTERACTABLE_PROTO, moduleInteractableToString + ); +} + +static void moduleInteractableDispose(void) { + scriptProtoDispose(&MODULE_INTERACTABLE_PROTO); +} diff --git a/src/dusk/script/module/entity/component/modulecomponentlist.h b/src/dusk/script/module/entity/component/modulecomponentlist.h index c5531ef9..921165cd 100644 --- a/src/dusk/script/module/entity/component/modulecomponentlist.h +++ b/src/dusk/script/module/entity/component/modulecomponentlist.h @@ -8,7 +8,12 @@ #pragma once #include "script/module/entity/modulecomponent.h" #include "camera/modulecamera.h" +#include "interactable/moduleinteractable.h" +#include "overworld/moduleoverworld.h" +#include "overworldcamera/moduleoverworldcamera.h" +#include "overworldtrigger/moduleoverworldtrigger.h" #include "physics/modulephysics.h" +#include "player/moduleplayer.h" #include "position/moduleposition.h" #include "renderable/modulerenderable.h" #include "trigger/moduletrigger.h" @@ -24,8 +29,18 @@ static jerry_value_t moduleComponentListCreateInstance( switch(type) { case COMPONENT_TYPE_CAMERA: return scriptProtoCreateValue(&MODULE_CAMERA_PROTO, comp); + case COMPONENT_TYPE_INTERACTABLE: + return scriptProtoCreateValue(&MODULE_INTERACTABLE_PROTO, comp); + case COMPONENT_TYPE_OVERWORLD: + return scriptProtoCreateValue(&MODULE_OVERWORLD_PROTO, comp); + case COMPONENT_TYPE_OVERWORLD_CAMERA: + return scriptProtoCreateValue(&MODULE_OVERWORLD_CAMERA_PROTO, comp); + case COMPONENT_TYPE_OVERWORLD_TRIGGER: + return scriptProtoCreateValue(&MODULE_OVERWORLD_TRIGGER_PROTO, comp); case COMPONENT_TYPE_PHYSICS: return scriptProtoCreateValue(&MODULE_PHYSICS_PROTO, comp); + case COMPONENT_TYPE_PLAYER: + return scriptProtoCreateValue(&MODULE_PLAYER_PROTO, comp); case COMPONENT_TYPE_POSITION: return scriptProtoCreateValue(&MODULE_POSITION_PROTO, comp); case COMPONENT_TYPE_RENDERABLE: @@ -39,7 +54,12 @@ static jerry_value_t moduleComponentListCreateInstance( static void moduleComponentListInit(void) { moduleCameraInit(); + moduleInteractableInit(); + moduleOverworldInit(); + moduleOverworldCameraInit(); + moduleOverworldTriggerInit(); modulePhysicsInit(); + modulePlayerInit(); modulePositionInit(); moduleRenderableInit(); moduleTriggerInit(); @@ -49,6 +69,11 @@ static void moduleComponentListDispose(void) { moduleTriggerDispose(); moduleRenderableDispose(); modulePositionDispose(); + modulePlayerDispose(); modulePhysicsDispose(); + moduleOverworldTriggerDispose(); + moduleOverworldCameraDispose(); + moduleOverworldDispose(); + moduleInteractableDispose(); moduleCameraDispose(); } diff --git a/src/dusk/script/module/entity/component/overworld/moduleoverworld.h b/src/dusk/script/module/entity/component/overworld/moduleoverworld.h new file mode 100644 index 00000000..e2fac7bb --- /dev/null +++ b/src/dusk/script/module/entity/component/overworld/moduleoverworld.h @@ -0,0 +1,168 @@ +/** + * 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/entity/modulecomponent.h" +#include "entity/component/overworld/entityoverworld.h" +#include "overworld/facingdir.h" + +static scriptproto_t MODULE_OVERWORLD_PROTO; + +moduleBaseFunction(moduleOverworldCtor) { + (void)callInfo; (void)args; (void)argc; + return moduleBaseThrow("Overworld cannot be instantiated with new"); +} + +static inline jscomponent_t *moduleOverworldSelf( + const jerry_call_info_t *callInfo +) { + return (jscomponent_t *)scriptProtoGetValue( + &MODULE_OVERWORLD_PROTO, callInfo->this_value + ); +} + +moduleBaseFunction(moduleOverworldGetEntity) { + jscomponent_t *c = moduleOverworldSelf(callInfo); + if(!c) return jerry_undefined(); + return jerry_number((double)c->entityId); +} + +moduleBaseFunction(moduleOverworldGetId) { + jscomponent_t *c = moduleOverworldSelf(callInfo); + if(!c) return jerry_undefined(); + return jerry_number((double)c->componentId); +} + +moduleBaseFunction(moduleOverworldGetType) { + jscomponent_t *c = moduleOverworldSelf(callInfo); + if(!c) return jerry_undefined(); + entityoverworld_t *o = entityOverworldGet(c->entityId, c->componentId); + if(!o) return jerry_undefined(); + return jerry_number((double)o->type); +} + +moduleBaseFunction(moduleOverworldSetType) { + moduleBaseRequireArgs(1); + jscomponent_t *c = moduleOverworldSelf(callInfo); + if(!c) return jerry_undefined(); + entityOverworldSetType( + c->entityId, c->componentId, + (entityoverworldtype_t)moduleBaseArgInt(0) + ); + return jerry_undefined(); +} + +moduleBaseFunction(moduleOverworldGetFacing) { + jscomponent_t *c = moduleOverworldSelf(callInfo); + if(!c) return jerry_undefined(); + entityoverworld_t *o = entityOverworldGet(c->entityId, c->componentId); + if(!o) return jerry_undefined(); + return jerry_number((double)o->facing); +} + +moduleBaseFunction(moduleOverworldSetFacing) { + moduleBaseRequireArgs(1); + jscomponent_t *c = moduleOverworldSelf(callInfo); + if(!c) return jerry_undefined(); + entityoverworld_t *o = entityOverworldGet(c->entityId, c->componentId); + if(!o) return jerry_undefined(); + o->facing = (facingdir_t)moduleBaseArgInt(0); + return jerry_undefined(); +} + +moduleBaseFunction(moduleOverworldGetRenderCompId) { + jscomponent_t *c = moduleOverworldSelf(callInfo); + if(!c) return jerry_undefined(); + entityoverworld_t *o = entityOverworldGet(c->entityId, c->componentId); + if(!o) return jerry_undefined(); + return jerry_number((double)o->renderCompId); +} + +moduleBaseFunction(moduleOverworldGetPhysCompId) { + jscomponent_t *c = moduleOverworldSelf(callInfo); + if(!c) return jerry_undefined(); + entityoverworld_t *o = entityOverworldGet(c->entityId, c->componentId); + if(!o) return jerry_undefined(); + return jerry_number((double)o->physCompId); +} + +moduleBaseFunction(moduleOverworldToString) { + jscomponent_t *c = moduleOverworldSelf(callInfo); + if(!c) return jerry_string_sz("Overworld:invalid"); + char_t buf[32]; + snprintf(buf, sizeof(buf), "Overworld(%u)", (unsigned)c->componentId); + return jerry_string_sz(buf); +} + +static void moduleOverworldInit(void) { + scriptProtoInit( + &MODULE_OVERWORLD_PROTO, "Overworld", + sizeof(jscomponent_t), moduleOverworldCtor + ); + + scriptProtoDefineProp( + &MODULE_OVERWORLD_PROTO, "entity", moduleOverworldGetEntity, NULL + ); + scriptProtoDefineProp( + &MODULE_OVERWORLD_PROTO, "id", moduleOverworldGetId, NULL + ); + scriptProtoDefineProp( + &MODULE_OVERWORLD_PROTO, "type", + moduleOverworldGetType, moduleOverworldSetType + ); + scriptProtoDefineProp( + &MODULE_OVERWORLD_PROTO, "facing", + moduleOverworldGetFacing, moduleOverworldSetFacing + ); + scriptProtoDefineProp( + &MODULE_OVERWORLD_PROTO, "renderComponentId", + moduleOverworldGetRenderCompId, NULL + ); + scriptProtoDefineProp( + &MODULE_OVERWORLD_PROTO, "physicsComponentId", + moduleOverworldGetPhysCompId, NULL + ); + scriptProtoDefineToString(&MODULE_OVERWORLD_PROTO, moduleOverworldToString); + + jerry_value_t ctor = MODULE_OVERWORLD_PROTO.constructor; + + struct { const char_t *name; int val; } types[] = { + { "PLAYER", OVERWORLD_ENTITY_TYPE_PLAYER }, + { "NPC", OVERWORLD_ENTITY_TYPE_NPC }, + }; + for(int i = 0; i < 2; i++) { + jerry_value_t k = jerry_string_sz(types[i].name); + jerry_value_t v = jerry_number((double)types[i].val); + jerry_object_set(ctor, k, v); + jerry_value_free(v); + jerry_value_free(k); + } + + struct { const char_t *name; int val; } dirs[] = { + { "FACING_DOWN", FACING_DIR_DOWN }, + { "FACING_UP", FACING_DIR_UP }, + { "FACING_LEFT", FACING_DIR_LEFT }, + { "FACING_RIGHT", FACING_DIR_RIGHT }, + { "FACING_SOUTH", FACING_DIR_SOUTH }, + { "FACING_NORTH", FACING_DIR_NORTH }, + { "FACING_WEST", FACING_DIR_WEST }, + { "FACING_EAST", FACING_DIR_EAST }, + }; + for(int i = 0; i < 8; i++) { + jerry_value_t k = jerry_string_sz(dirs[i].name); + jerry_value_t v = jerry_number((double)dirs[i].val); + jerry_object_set(ctor, k, v); + jerry_value_free(v); + jerry_value_free(k); + } +} + +static void moduleOverworldDispose(void) { + scriptProtoDispose(&MODULE_OVERWORLD_PROTO); +} diff --git a/src/dusk/script/module/entity/component/overworldcamera/moduleoverworldcamera.h b/src/dusk/script/module/entity/component/overworldcamera/moduleoverworldcamera.h new file mode 100644 index 00000000..e155eaa3 --- /dev/null +++ b/src/dusk/script/module/entity/component/overworldcamera/moduleoverworldcamera.h @@ -0,0 +1,201 @@ +/** + * 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/modulevec3.h" +#include "script/module/entity/modulecomponent.h" +#include "entity/component/overworld/entityoverworldcamera.h" + +static scriptproto_t MODULE_OVERWORLD_CAMERA_PROTO; + +moduleBaseFunction(moduleOverworldCameraCtor) { + (void)callInfo; (void)args; (void)argc; + return moduleBaseThrow("OverworldCamera cannot be instantiated with new"); +} + +static inline jscomponent_t *moduleOverworldCameraSelf( + const jerry_call_info_t *callInfo +) { + return (jscomponent_t *)scriptProtoGetValue( + &MODULE_OVERWORLD_CAMERA_PROTO, callInfo->this_value + ); +} + +moduleBaseFunction(moduleOverworldCameraGetEntity) { + jscomponent_t *c = moduleOverworldCameraSelf(callInfo); + if(!c) return jerry_undefined(); + return jerry_number((double)c->entityId); +} + +moduleBaseFunction(moduleOverworldCameraGetId) { + jscomponent_t *c = moduleOverworldCameraSelf(callInfo); + if(!c) return jerry_undefined(); + return jerry_number((double)c->componentId); +} + +moduleBaseFunction(moduleOverworldCameraGetTargetEntity) { + jscomponent_t *c = moduleOverworldCameraSelf(callInfo); + if(!c) return jerry_undefined(); + entityoverworldcamera_t *cam = entityOverworldCameraGet(c->entityId, c->componentId); + if(!cam) return jerry_undefined(); + return jerry_number((double)cam->targetEntityId); +} + +moduleBaseFunction(moduleOverworldCameraSetTargetEntity) { + moduleBaseRequireArgs(1); + jscomponent_t *c = moduleOverworldCameraSelf(callInfo); + if(!c) return jerry_undefined(); + entityoverworldcamera_t *cam = entityOverworldCameraGet(c->entityId, c->componentId); + if(!cam) return jerry_undefined(); + cam->targetEntityId = (entityid_t)moduleBaseArgInt(0); + return jerry_undefined(); +} + +moduleBaseFunction(moduleOverworldCameraGetTargetPosComp) { + jscomponent_t *c = moduleOverworldCameraSelf(callInfo); + if(!c) return jerry_undefined(); + entityoverworldcamera_t *cam = entityOverworldCameraGet(c->entityId, c->componentId); + if(!cam) return jerry_undefined(); + return jerry_number((double)cam->targetPosCompId); +} + +moduleBaseFunction(moduleOverworldCameraSetTargetPosComp) { + moduleBaseRequireArgs(1); + jscomponent_t *c = moduleOverworldCameraSelf(callInfo); + if(!c) return jerry_undefined(); + entityoverworldcamera_t *cam = entityOverworldCameraGet(c->entityId, c->componentId); + if(!cam) return jerry_undefined(); + cam->targetPosCompId = (componentid_t)moduleBaseArgInt(0); + return jerry_undefined(); +} + +moduleBaseFunction(moduleOverworldCameraGetTargetOffset) { + jscomponent_t *c = moduleOverworldCameraSelf(callInfo); + if(!c) return jerry_undefined(); + entityoverworldcamera_t *cam = entityOverworldCameraGet(c->entityId, c->componentId); + if(!cam) return jerry_undefined(); + return moduleVec3Push(cam->targetOffset); +} + +moduleBaseFunction(moduleOverworldCameraSetTargetOffset) { + moduleBaseRequireArgs(1); + jscomponent_t *c = moduleOverworldCameraSelf(callInfo); + if(!c) return jerry_undefined(); + float_t *v = moduleVec3From(args[0]); + if(!v) return moduleBaseThrow("OverworldCamera.targetOffset: expected Vec3"); + entityoverworldcamera_t *cam = entityOverworldCameraGet(c->entityId, c->componentId); + if(!cam) return jerry_undefined(); + glm_vec3_copy(v, cam->targetOffset); + return jerry_undefined(); +} + +moduleBaseFunction(moduleOverworldCameraGetEyeOffset) { + jscomponent_t *c = moduleOverworldCameraSelf(callInfo); + if(!c) return jerry_undefined(); + entityoverworldcamera_t *cam = entityOverworldCameraGet(c->entityId, c->componentId); + if(!cam) return jerry_undefined(); + return moduleVec3Push(cam->eyeOffset); +} + +moduleBaseFunction(moduleOverworldCameraSetEyeOffset) { + moduleBaseRequireArgs(1); + jscomponent_t *c = moduleOverworldCameraSelf(callInfo); + if(!c) return jerry_undefined(); + float_t *v = moduleVec3From(args[0]); + if(!v) return moduleBaseThrow("OverworldCamera.eyeOffset: expected Vec3"); + entityoverworldcamera_t *cam = entityOverworldCameraGet(c->entityId, c->componentId); + if(!cam) return jerry_undefined(); + glm_vec3_copy(v, cam->eyeOffset); + return jerry_undefined(); +} + +moduleBaseFunction(moduleOverworldCameraGetScale) { + jscomponent_t *c = moduleOverworldCameraSelf(callInfo); + if(!c) return jerry_undefined(); + entityoverworldcamera_t *cam = entityOverworldCameraGet(c->entityId, c->componentId); + if(!cam) return jerry_undefined(); + return jerry_number((double)cam->scale); +} + +moduleBaseFunction(moduleOverworldCameraSetScale) { + moduleBaseRequireArgs(1); + jscomponent_t *c = moduleOverworldCameraSelf(callInfo); + if(!c) return jerry_undefined(); + entityoverworldcamera_t *cam = entityOverworldCameraGet(c->entityId, c->componentId); + if(!cam) return jerry_undefined(); + cam->scale = moduleBaseArgFloat(0); + return jerry_undefined(); +} + +moduleBaseFunction(moduleOverworldCameraSetTarget) { + moduleBaseRequireArgs(2); + jscomponent_t *c = moduleOverworldCameraSelf(callInfo); + if(!c) return jerry_undefined(); + entityOverworldCameraSetTarget( + c->entityId, c->componentId, + (entityid_t)moduleBaseArgInt(0), + (componentid_t)moduleBaseArgInt(1) + ); + return jerry_undefined(); +} + +moduleBaseFunction(moduleOverworldCameraToString) { + jscomponent_t *c = moduleOverworldCameraSelf(callInfo); + if(!c) return jerry_string_sz("OverworldCamera:invalid"); + char_t buf[32]; + snprintf(buf, sizeof(buf), "OverworldCamera(%u)", (unsigned)c->componentId); + return jerry_string_sz(buf); +} + +static void moduleOverworldCameraInit(void) { + scriptProtoInit( + &MODULE_OVERWORLD_CAMERA_PROTO, "OverworldCamera", + sizeof(jscomponent_t), moduleOverworldCameraCtor + ); + + scriptProtoDefineProp( + &MODULE_OVERWORLD_CAMERA_PROTO, "entity", + moduleOverworldCameraGetEntity, NULL + ); + scriptProtoDefineProp( + &MODULE_OVERWORLD_CAMERA_PROTO, "id", + moduleOverworldCameraGetId, NULL + ); + scriptProtoDefineProp( + &MODULE_OVERWORLD_CAMERA_PROTO, "targetEntity", + moduleOverworldCameraGetTargetEntity, moduleOverworldCameraSetTargetEntity + ); + scriptProtoDefineProp( + &MODULE_OVERWORLD_CAMERA_PROTO, "targetPositionComponent", + moduleOverworldCameraGetTargetPosComp, moduleOverworldCameraSetTargetPosComp + ); + scriptProtoDefineProp( + &MODULE_OVERWORLD_CAMERA_PROTO, "targetOffset", + moduleOverworldCameraGetTargetOffset, moduleOverworldCameraSetTargetOffset + ); + scriptProtoDefineProp( + &MODULE_OVERWORLD_CAMERA_PROTO, "eyeOffset", + moduleOverworldCameraGetEyeOffset, moduleOverworldCameraSetEyeOffset + ); + scriptProtoDefineProp( + &MODULE_OVERWORLD_CAMERA_PROTO, "scale", + moduleOverworldCameraGetScale, moduleOverworldCameraSetScale + ); + scriptProtoDefineFunc( + &MODULE_OVERWORLD_CAMERA_PROTO, "setTarget", + moduleOverworldCameraSetTarget + ); + scriptProtoDefineToString( + &MODULE_OVERWORLD_CAMERA_PROTO, moduleOverworldCameraToString + ); +} + +static void moduleOverworldCameraDispose(void) { + scriptProtoDispose(&MODULE_OVERWORLD_CAMERA_PROTO); +} diff --git a/src/dusk/script/module/entity/component/overworldtrigger/moduleoverworldtrigger.h b/src/dusk/script/module/entity/component/overworldtrigger/moduleoverworldtrigger.h new file mode 100644 index 00000000..b03c2375 --- /dev/null +++ b/src/dusk/script/module/entity/component/overworldtrigger/moduleoverworldtrigger.h @@ -0,0 +1,374 @@ +/** + * 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/modulevec3.h" +#include "script/module/entity/modulecomponent.h" +#include "entity/component/overworld/entityoverworldtrigger.h" +#include + +static scriptproto_t MODULE_OVERWORLD_TRIGGER_PROTO; + +/** + * Heap-allocated struct stored in entityoverworldtrigger_t.user. + * Holds jerry_value_t copies of the four JS callback functions. + */ +typedef struct { + jerry_value_t onEnter; + jerry_value_t onExit; + jerry_value_t onStay; + jerry_value_t onOutside; +} jsoverworldtriggercbs_t; + +moduleBaseFunction(moduleOverworldTriggerCtor) { + (void)callInfo; (void)args; (void)argc; + return moduleBaseThrow("OverworldTrigger cannot be instantiated with new"); +} + +static inline jscomponent_t *moduleOverworldTriggerSelf( + const jerry_call_info_t *callInfo +) { + return (jscomponent_t *)scriptProtoGetValue( + &MODULE_OVERWORLD_TRIGGER_PROTO, callInfo->this_value + ); +} + +/** Lazily allocates the callback struct and stores it in the trigger's user. */ +static jsoverworldtriggercbs_t *moduleOverworldTriggerGetCbs( + entityoverworldtrigger_t *t +) { + if(t->user) return (jsoverworldtriggercbs_t *)t->user; + jsoverworldtriggercbs_t *cbs = (jsoverworldtriggercbs_t *)malloc( + sizeof(jsoverworldtriggercbs_t) + ); + cbs->onEnter = jerry_undefined(); + cbs->onExit = jerry_undefined(); + cbs->onStay = jerry_undefined(); + cbs->onOutside = jerry_undefined(); + t->user = (void *)cbs; + return cbs; +} + +static void moduleOverworldTriggerOnEnterCb( + const entityid_t entityId, + const componentid_t componentId, + void *user +) { + (void)entityId; (void)componentId; + jsoverworldtriggercbs_t *cbs = (jsoverworldtriggercbs_t *)user; + if(!jerry_value_is_function(cbs->onEnter)) return; + jerry_value_t ret = jerry_call(cbs->onEnter, jerry_undefined(), NULL, 0); + jerry_value_free(ret); +} + +static void moduleOverworldTriggerOnExitCb( + const entityid_t entityId, + const componentid_t componentId, + void *user +) { + (void)entityId; (void)componentId; + jsoverworldtriggercbs_t *cbs = (jsoverworldtriggercbs_t *)user; + if(!jerry_value_is_function(cbs->onExit)) return; + jerry_value_t ret = jerry_call(cbs->onExit, jerry_undefined(), NULL, 0); + jerry_value_free(ret); +} + +static void moduleOverworldTriggerOnStayCb( + const entityid_t entityId, + const componentid_t componentId, + void *user +) { + (void)entityId; (void)componentId; + jsoverworldtriggercbs_t *cbs = (jsoverworldtriggercbs_t *)user; + if(!jerry_value_is_function(cbs->onStay)) return; + jerry_value_t ret = jerry_call(cbs->onStay, jerry_undefined(), NULL, 0); + jerry_value_free(ret); +} + +static void moduleOverworldTriggerOnOutsideCb( + const entityid_t entityId, + const componentid_t componentId, + void *user +) { + (void)entityId; (void)componentId; + jsoverworldtriggercbs_t *cbs = (jsoverworldtriggercbs_t *)user; + if(!jerry_value_is_function(cbs->onOutside)) return; + jerry_value_t ret = jerry_call(cbs->onOutside, jerry_undefined(), NULL, 0); + jerry_value_free(ret); +} + +// ---- entity / id ---- + +moduleBaseFunction(moduleOverworldTriggerGetEntity) { + jscomponent_t *c = moduleOverworldTriggerSelf(callInfo); + if(!c) return jerry_undefined(); + return jerry_number((double)c->entityId); +} + +moduleBaseFunction(moduleOverworldTriggerGetId) { + jscomponent_t *c = moduleOverworldTriggerSelf(callInfo); + if(!c) return jerry_undefined(); + return jerry_number((double)c->componentId); +} + +// ---- min / max ---- + +moduleBaseFunction(moduleOverworldTriggerGetMin) { + jscomponent_t *c = moduleOverworldTriggerSelf(callInfo); + if(!c) return jerry_undefined(); + entityoverworldtrigger_t *t = entityOverworldTriggerGet(c->entityId, c->componentId); + if(!t) return jerry_undefined(); + return moduleVec3Push(t->min); +} + +moduleBaseFunction(moduleOverworldTriggerSetMin) { + moduleBaseRequireArgs(1); + jscomponent_t *c = moduleOverworldTriggerSelf(callInfo); + if(!c) return jerry_undefined(); + float_t *v = moduleVec3From(args[0]); + if(!v) return moduleBaseThrow("OverworldTrigger.min: expected Vec3"); + entityoverworldtrigger_t *t = entityOverworldTriggerGet(c->entityId, c->componentId); + if(!t) return jerry_undefined(); + glm_vec3_copy(v, t->min); + return jerry_undefined(); +} + +moduleBaseFunction(moduleOverworldTriggerGetMax) { + jscomponent_t *c = moduleOverworldTriggerSelf(callInfo); + if(!c) return jerry_undefined(); + entityoverworldtrigger_t *t = entityOverworldTriggerGet(c->entityId, c->componentId); + if(!t) return jerry_undefined(); + return moduleVec3Push(t->max); +} + +moduleBaseFunction(moduleOverworldTriggerSetMax) { + moduleBaseRequireArgs(1); + jscomponent_t *c = moduleOverworldTriggerSelf(callInfo); + if(!c) return jerry_undefined(); + float_t *v = moduleVec3From(args[0]); + if(!v) return moduleBaseThrow("OverworldTrigger.max: expected Vec3"); + entityoverworldtrigger_t *t = entityOverworldTriggerGet(c->entityId, c->componentId); + if(!t) return jerry_undefined(); + glm_vec3_copy(v, t->max); + return jerry_undefined(); +} + +// ---- playerInside ---- + +moduleBaseFunction(moduleOverworldTriggerGetPlayerInside) { + jscomponent_t *c = moduleOverworldTriggerSelf(callInfo); + if(!c) return jerry_undefined(); + entityoverworldtrigger_t *t = entityOverworldTriggerGet(c->entityId, c->componentId); + if(!t) return jerry_undefined(); + return jerry_boolean(t->playerInside); +} + +// ---- setBounds ---- + +moduleBaseFunction(moduleOverworldTriggerSetBounds) { + moduleBaseRequireArgs(2); + jscomponent_t *c = moduleOverworldTriggerSelf(callInfo); + if(!c) return jerry_undefined(); + float_t *minV = moduleVec3From(args[0]); + float_t *maxV = moduleVec3From(args[1]); + if(!minV) return moduleBaseThrow("OverworldTrigger.setBounds: expected Vec3 for min"); + if(!maxV) return moduleBaseThrow("OverworldTrigger.setBounds: expected Vec3 for max"); + entityOverworldTriggerSetBounds(c->entityId, c->componentId, minV, maxV); + return jerry_undefined(); +} + +// ---- callback helpers ---- + +/* + * Pins the JS function on this._onXxx for GC safety, stores a copied + * jerry_value_t reference in the cbs struct for the C trampoline, and + * wires up the C callback pointer. Passing null/undefined clears it. + */ +static void moduleOverworldTriggerSetCb( + const jerry_call_info_t *callInfo, + const jerry_value_t *newArgs, + const jerry_length_t newArgc, + jerry_value_t *slot, + entityoverworldtriggercallback_t *cslot, + entityoverworldtriggercallback_t trampolineFn, + const char_t *pinKey +) { + jerry_value_free(*slot); + jerry_value_t pin = (newArgc > 0) ? newArgs[0] : jerry_undefined(); + jerry_value_t keyVal = jerry_string_sz(pinKey); + if(jerry_value_is_function(pin)) { + *slot = jerry_value_copy(pin); + *cslot = trampolineFn; + jerry_object_set(callInfo->this_value, keyVal, pin); + } else { + *slot = jerry_undefined(); + *cslot = NULL; + jerry_value_t undef = jerry_undefined(); + jerry_object_set(callInfo->this_value, keyVal, undef); + jerry_value_free(undef); + } + jerry_value_free(keyVal); +} + +static jerry_value_t moduleOverworldTriggerGetPinnedCb( + const jerry_call_info_t *callInfo, + const char_t *pinKey +) { + jerry_value_t key = jerry_string_sz(pinKey); + jerry_value_t val = jerry_object_get(callInfo->this_value, key); + jerry_value_free(key); + return val; +} + +// ---- onEnter ---- + +moduleBaseFunction(moduleOverworldTriggerGetOnEnter) { + (void)args; (void)argc; + return moduleOverworldTriggerGetPinnedCb(callInfo, "_onEnter"); +} + +moduleBaseFunction(moduleOverworldTriggerSetOnEnter) { + jscomponent_t *c = moduleOverworldTriggerSelf(callInfo); + if(!c) return jerry_undefined(); + entityoverworldtrigger_t *t = entityOverworldTriggerGet(c->entityId, c->componentId); + if(!t) return jerry_undefined(); + jsoverworldtriggercbs_t *cbs = moduleOverworldTriggerGetCbs(t); + moduleOverworldTriggerSetCb( + callInfo, args, argc, + &cbs->onEnter, &t->onEnter, + moduleOverworldTriggerOnEnterCb, "_onEnter" + ); + return jerry_undefined(); +} + +// ---- onExit ---- + +moduleBaseFunction(moduleOverworldTriggerGetOnExit) { + (void)args; (void)argc; + return moduleOverworldTriggerGetPinnedCb(callInfo, "_onExit"); +} + +moduleBaseFunction(moduleOverworldTriggerSetOnExit) { + jscomponent_t *c = moduleOverworldTriggerSelf(callInfo); + if(!c) return jerry_undefined(); + entityoverworldtrigger_t *t = entityOverworldTriggerGet(c->entityId, c->componentId); + if(!t) return jerry_undefined(); + jsoverworldtriggercbs_t *cbs = moduleOverworldTriggerGetCbs(t); + moduleOverworldTriggerSetCb( + callInfo, args, argc, + &cbs->onExit, &t->onExit, + moduleOverworldTriggerOnExitCb, "_onExit" + ); + return jerry_undefined(); +} + +// ---- onStay ---- + +moduleBaseFunction(moduleOverworldTriggerGetOnStay) { + (void)args; (void)argc; + return moduleOverworldTriggerGetPinnedCb(callInfo, "_onStay"); +} + +moduleBaseFunction(moduleOverworldTriggerSetOnStay) { + jscomponent_t *c = moduleOverworldTriggerSelf(callInfo); + if(!c) return jerry_undefined(); + entityoverworldtrigger_t *t = entityOverworldTriggerGet(c->entityId, c->componentId); + if(!t) return jerry_undefined(); + jsoverworldtriggercbs_t *cbs = moduleOverworldTriggerGetCbs(t); + moduleOverworldTriggerSetCb( + callInfo, args, argc, + &cbs->onStay, &t->onStay, + moduleOverworldTriggerOnStayCb, "_onStay" + ); + return jerry_undefined(); +} + +// ---- onOutside ---- + +moduleBaseFunction(moduleOverworldTriggerGetOnOutside) { + (void)args; (void)argc; + return moduleOverworldTriggerGetPinnedCb(callInfo, "_onOutside"); +} + +moduleBaseFunction(moduleOverworldTriggerSetOnOutside) { + jscomponent_t *c = moduleOverworldTriggerSelf(callInfo); + if(!c) return jerry_undefined(); + entityoverworldtrigger_t *t = entityOverworldTriggerGet(c->entityId, c->componentId); + if(!t) return jerry_undefined(); + jsoverworldtriggercbs_t *cbs = moduleOverworldTriggerGetCbs(t); + moduleOverworldTriggerSetCb( + callInfo, args, argc, + &cbs->onOutside, &t->onOutside, + moduleOverworldTriggerOnOutsideCb, "_onOutside" + ); + return jerry_undefined(); +} + +moduleBaseFunction(moduleOverworldTriggerToString) { + jscomponent_t *c = moduleOverworldTriggerSelf(callInfo); + if(!c) return jerry_string_sz("OverworldTrigger:invalid"); + char_t buf[32]; + snprintf(buf, sizeof(buf), "OverworldTrigger(%u)", (unsigned)c->componentId); + return jerry_string_sz(buf); +} + +static void moduleOverworldTriggerInit(void) { + scriptProtoInit( + &MODULE_OVERWORLD_TRIGGER_PROTO, "OverworldTrigger", + sizeof(jscomponent_t), moduleOverworldTriggerCtor + ); + + scriptProtoDefineProp( + &MODULE_OVERWORLD_TRIGGER_PROTO, "entity", + moduleOverworldTriggerGetEntity, NULL + ); + scriptProtoDefineProp( + &MODULE_OVERWORLD_TRIGGER_PROTO, "id", + moduleOverworldTriggerGetId, NULL + ); + scriptProtoDefineProp( + &MODULE_OVERWORLD_TRIGGER_PROTO, "min", + moduleOverworldTriggerGetMin, moduleOverworldTriggerSetMin + ); + scriptProtoDefineProp( + &MODULE_OVERWORLD_TRIGGER_PROTO, "max", + moduleOverworldTriggerGetMax, moduleOverworldTriggerSetMax + ); + scriptProtoDefineProp( + &MODULE_OVERWORLD_TRIGGER_PROTO, "playerInside", + moduleOverworldTriggerGetPlayerInside, NULL + ); + scriptProtoDefineFunc( + &MODULE_OVERWORLD_TRIGGER_PROTO, "setBounds", + moduleOverworldTriggerSetBounds + ); + scriptProtoDefineProp( + &MODULE_OVERWORLD_TRIGGER_PROTO, "onEnter", + moduleOverworldTriggerGetOnEnter, moduleOverworldTriggerSetOnEnter + ); + scriptProtoDefineProp( + &MODULE_OVERWORLD_TRIGGER_PROTO, "onExit", + moduleOverworldTriggerGetOnExit, moduleOverworldTriggerSetOnExit + ); + scriptProtoDefineProp( + &MODULE_OVERWORLD_TRIGGER_PROTO, "onStay", + moduleOverworldTriggerGetOnStay, moduleOverworldTriggerSetOnStay + ); + scriptProtoDefineProp( + &MODULE_OVERWORLD_TRIGGER_PROTO, "onOutside", + moduleOverworldTriggerGetOnOutside, moduleOverworldTriggerSetOnOutside + ); + scriptProtoDefineToString( + &MODULE_OVERWORLD_TRIGGER_PROTO, moduleOverworldTriggerToString + ); +} + +static void moduleOverworldTriggerDispose(void) { + scriptProtoDispose(&MODULE_OVERWORLD_TRIGGER_PROTO); +} diff --git a/src/dusk/script/module/entity/component/player/moduleplayer.h b/src/dusk/script/module/entity/component/player/moduleplayer.h new file mode 100644 index 00000000..a7d717c2 --- /dev/null +++ b/src/dusk/script/module/entity/component/player/moduleplayer.h @@ -0,0 +1,110 @@ +/** + * 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/entity/modulecomponent.h" +#include "entity/component/overworld/entityplayer.h" + +static scriptproto_t MODULE_PLAYER_PROTO; + +moduleBaseFunction(modulePlayerCtor) { + (void)callInfo; (void)args; (void)argc; + return moduleBaseThrow("Player cannot be instantiated with new"); +} + +static inline jscomponent_t *modulePlayerSelf( + const jerry_call_info_t *callInfo +) { + return (jscomponent_t *)scriptProtoGetValue( + &MODULE_PLAYER_PROTO, callInfo->this_value + ); +} + +moduleBaseFunction(modulePlayerGetEntity) { + jscomponent_t *c = modulePlayerSelf(callInfo); + if(!c) return jerry_undefined(); + return jerry_number((double)c->entityId); +} + +moduleBaseFunction(modulePlayerGetId) { + jscomponent_t *c = modulePlayerSelf(callInfo); + if(!c) return jerry_undefined(); + return jerry_number((double)c->componentId); +} + +moduleBaseFunction(modulePlayerGetSpeed) { + jscomponent_t *c = modulePlayerSelf(callInfo); + if(!c) return jerry_undefined(); + entityplayer_t *p = entityPlayerGet(c->entityId, c->componentId); + if(!p) return jerry_undefined(); + return jerry_number((double)p->speed); +} + +moduleBaseFunction(modulePlayerSetSpeed) { + moduleBaseRequireArgs(1); + jscomponent_t *c = modulePlayerSelf(callInfo); + if(!c) return jerry_undefined(); + entityplayer_t *p = entityPlayerGet(c->entityId, c->componentId); + if(!p) return jerry_undefined(); + p->speed = moduleBaseArgFloat(0); + return jerry_undefined(); +} + +moduleBaseFunction(modulePlayerGetRunSpeed) { + jscomponent_t *c = modulePlayerSelf(callInfo); + if(!c) return jerry_undefined(); + entityplayer_t *p = entityPlayerGet(c->entityId, c->componentId); + if(!p) return jerry_undefined(); + return jerry_number((double)p->runSpeed); +} + +moduleBaseFunction(modulePlayerSetRunSpeed) { + moduleBaseRequireArgs(1); + jscomponent_t *c = modulePlayerSelf(callInfo); + if(!c) return jerry_undefined(); + entityplayer_t *p = entityPlayerGet(c->entityId, c->componentId); + if(!p) return jerry_undefined(); + p->runSpeed = moduleBaseArgFloat(0); + return jerry_undefined(); +} + +moduleBaseFunction(modulePlayerToString) { + jscomponent_t *c = modulePlayerSelf(callInfo); + if(!c) return jerry_string_sz("Player:invalid"); + char_t buf[32]; + snprintf(buf, sizeof(buf), "Player(%u)", (unsigned)c->componentId); + return jerry_string_sz(buf); +} + +static void modulePlayerInit(void) { + scriptProtoInit( + &MODULE_PLAYER_PROTO, "Player", + sizeof(jscomponent_t), modulePlayerCtor + ); + + scriptProtoDefineProp( + &MODULE_PLAYER_PROTO, "entity", modulePlayerGetEntity, NULL + ); + scriptProtoDefineProp( + &MODULE_PLAYER_PROTO, "id", modulePlayerGetId, NULL + ); + scriptProtoDefineProp( + &MODULE_PLAYER_PROTO, "speed", + modulePlayerGetSpeed, modulePlayerSetSpeed + ); + scriptProtoDefineProp( + &MODULE_PLAYER_PROTO, "runSpeed", + modulePlayerGetRunSpeed, modulePlayerSetRunSpeed + ); + scriptProtoDefineToString(&MODULE_PLAYER_PROTO, modulePlayerToString); +} + +static void modulePlayerDispose(void) { + scriptProtoDispose(&MODULE_PLAYER_PROTO); +} diff --git a/types/entity/component/interactable.d.ts b/types/entity/component/interactable.d.ts new file mode 100644 index 00000000..0e7154bf --- /dev/null +++ b/types/entity/component/interactable.d.ts @@ -0,0 +1,26 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +/** + * Interactable component. Fires a callback when the player activates it. + * + * Assign a JS function to `onInteract` to handle the event from script. + * Call `trigger()` to fire the callback imperatively from C or JS. + */ +interface Interactable extends Component { + /** Called when the player interacts with this entity. Set to null to clear. */ + onInteract: (() => void) | null; + /** Fires the registered callback immediately (no-op if none is set). */ + trigger(): void; + toString(): string; +} + +interface InteractableConstructor { + new(): never; +} + +declare var Interactable: InteractableConstructor; diff --git a/types/entity/component/overworld.d.ts b/types/entity/component/overworld.d.ts new file mode 100644 index 00000000..91696332 --- /dev/null +++ b/types/entity/component/overworld.d.ts @@ -0,0 +1,41 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +/** Overworld character component (player or NPC). */ +interface Overworld extends Component { + /** + * Entity type — `Overworld.PLAYER` or `Overworld.NPC`. + */ + type: number; + /** + * Facing direction — one of the `Overworld.FACING_*` constants. + */ + facing: number; + /** Component ID of the linked Renderable, or INVALID if none. */ + readonly renderComponentId: number; + /** Component ID of the linked Physics body, or INVALID if none. */ + readonly physicsComponentId: number; + toString(): string; +} + +interface OverworldConstructor { + readonly PLAYER: number; + readonly NPC: number; + + readonly FACING_DOWN: number; + readonly FACING_UP: number; + readonly FACING_LEFT: number; + readonly FACING_RIGHT: number; + readonly FACING_SOUTH: number; + readonly FACING_NORTH: number; + readonly FACING_WEST: number; + readonly FACING_EAST: number; + + new(): never; +} + +declare var Overworld: OverworldConstructor; diff --git a/types/entity/component/overworldcamera.d.ts b/types/entity/component/overworldcamera.d.ts new file mode 100644 index 00000000..ecfa243f --- /dev/null +++ b/types/entity/component/overworldcamera.d.ts @@ -0,0 +1,35 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +/** + * Overworld follow-camera component. + * Smoothly tracks a target entity's Position each frame. + */ +interface OverworldCamera extends Component { + /** Entity ID to follow. */ + targetEntity: number; + /** Position component ID on the target entity. */ + targetPositionComponent: number; + /** World-space offset added to the target's position before placing the camera. */ + targetOffset: Vec3; + /** Eye-space offset applied on top of targetOffset. */ + eyeOffset: Vec3; + /** Orthographic scale factor (larger = wider view). */ + scale: number; + /** + * Convenience setter — equivalent to assigning `targetEntity` and + * `targetPositionComponent` individually. + */ + setTarget(targetEntityId: number, targetPositionComponentId: number): void; + toString(): string; +} + +interface OverworldCameraConstructor { + new(): never; +} + +declare var OverworldCamera: OverworldCameraConstructor; diff --git a/types/entity/component/overworldtrigger.d.ts b/types/entity/component/overworldtrigger.d.ts new file mode 100644 index 00000000..461fb5ea --- /dev/null +++ b/types/entity/component/overworldtrigger.d.ts @@ -0,0 +1,38 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +/** + * Overworld AABB trigger. Fires callbacks as the player enters, stays inside, + * exits, or remains outside the defined bounding box each frame. + */ +interface OverworldTrigger extends Component { + /** Minimum corner of the trigger AABB. */ + min: Vec3; + /** Maximum corner of the trigger AABB. */ + max: Vec3; + /** `true` while the player is inside the trigger bounds. */ + readonly playerInside: boolean; + + /** Fired once when the player first enters the bounds. */ + onEnter: (() => void) | null; + /** Fired once when the player leaves the bounds. */ + onExit: (() => void) | null; + /** Fired every frame while the player remains inside the bounds. */ + onStay: (() => void) | null; + /** Fired every frame while the player remains outside the bounds. */ + onOutside: (() => void) | null; + + /** Convenience setter for both corners at once. */ + setBounds(min: Vec3, max: Vec3): void; + toString(): string; +} + +interface OverworldTriggerConstructor { + new(): never; +} + +declare var OverworldTrigger: OverworldTriggerConstructor; diff --git a/types/entity/component/player.d.ts b/types/entity/component/player.d.ts new file mode 100644 index 00000000..1b79a987 --- /dev/null +++ b/types/entity/component/player.d.ts @@ -0,0 +1,21 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +/** Player movement component. Controls walk and run speeds. */ +interface Player extends Component { + /** Walk speed in world units per second. */ + speed: number; + /** Run speed in world units per second. */ + runSpeed: number; + toString(): string; +} + +interface PlayerConstructor { + new(): never; +} + +declare var Player: PlayerConstructor; diff --git a/types/index.d.ts b/types/index.d.ts index f9a497df..41942f41 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -36,7 +36,12 @@ // entity / components /// /// +/// +/// +/// +/// /// +/// /// /// ///