diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..75f6290f --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,417 @@ +# Dusk — Claude Code rules + +## File headers +Every C, H, and JS file starts with: + +```c +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ +``` + +JS files use `//` comment style instead. + +--- + +## C conventions + +### Types +Always use the project-defined aliases instead of bare C primitives: + +| Use | Not | +|-----------|--------------| +| `bool_t` | `bool` | +| `int_t` | `int` | +| `float_t` | `float` | +| `char_t` | `char` | + +Use `uint8_t`, `uint16_t`, `int32_t`, etc. for fixed-width integers. +All struct and enum types end in `_t` (`animation_t`, `errorret_t`, …). + +### Naming +- **Functions** — snake_case, prefixed with their module: + `assetLock()`, `entityPositionInit()`, `moduleAssetBatchCtor()` +- **Struct fields** — camelCase: `keyframeCount`, `localPosition` +- **Macros / constants** — UPPER_SNAKE_CASE: + `ENTITY_ID_INVALID`, `ERROR_OK`, `COMPONENT_TYPE_COUNT` +- **Files** — snake_case matching the primary type: `entityposition.c`, + `moduleassetbatch.c` + +### Header files (`.h`) +- Use `#pragma once` — no include guards. +- Declare every public function, `#define`, and `extern` global. +- Write a JSDoc block (`/** … */`) above every declaration explaining + purpose, `@param`s, and `@returns`. +- Only include headers that the `.h` file itself strictly requires for + the types it exposes. Move everything else to the `.c` file. + Do not use forward declarations as a workaround — use the real + include in the `.c` file instead. + +### Implementation files (`.c`) +- Contain function bodies only; no declarations. +- Pull in whatever additional includes the implementation needs. + +### Formatting +- Hard-wrap all lines at **80 characters**. + +### Error handling +Return `errorret_t` from fallible functions. Use these macros: + +```c +errorOk(); // return success +errorThrow("msg %d", val); // return failure with message +errorChain(someCall()); // propagate failure, continue on success +errorIsOk(ret) / errorIsNotOk(ret) // test a result +errorCatch(ret); // handle + free an error +``` + +Never return raw error codes or use `errno` for in-engine errors. + +### Memory +Use the project allocator — never raw `malloc`/`free`: + +```c +memoryAllocate(size) // allocate +memoryFree(ptr) // free +memoryZero(dest, size) // zero a block +memoryCopy(dest, src, size) // copy +``` + +### Asserts +Prefer specific assert macros over bare `assert()`: + +```c +assertNotNull(ptr, "msg"); +assertTrue(cond, "msg"); +assertFalse(cond, "msg"); +assertUnreachable("msg"); +assertIsMainThread("msg"); +``` + +--- + +## Build system +Each subdirectory has its own `CMakeLists.txt` that adds sources with: + +```cmake +target_sources(${DUSK_LIBRARY_TARGET_NAME} + PUBLIC + myfile.c +) +``` + +Never add source files to the root `CMakeLists.txt` directly. + +--- + +## Platform support + +### Targets +Set `DUSK_TARGET_SYSTEM` at CMake configure time to select a platform: + +| `DUSK_TARGET_SYSTEM` | Macro defined | Platform | +|----------------------|-------------------|------------------| +| `linux` | `DUSK_LINUX` | Linux desktop | +| `knulli` | `DUSK_KNULLI` | Knulli (handheld)| +| `psp` | `DUSK_PSP` | Sony PSP | +| `vita` | `DUSK_VITA` | PlayStation Vita | +| `gamecube` | `DUSK_GAMECUBE` | Nintendo GameCube| +| `wii` | `DUSK_WII` | Nintendo Wii | + +### Layer structure +``` +src/dusk/ core, platform-agnostic game logic +src/duskgl/ OpenGL abstraction (Linux, Knulli, PSP, Vita) +src/dusksdl2/ SDL2 window + input (Linux, Knulli, PSP, Vita) +src/dusklinux/ Linux + Knulli platform impl +src/duskpsp/ PSP platform impl +src/duskvita/ Vita platform impl +src/duskdolphin/ GameCube / Wii platform impl (no SDL2/OpenGL) +``` + +Dolphin is the only target that bypasses SDL2 and OpenGL entirely — +it uses native GameCube/Wii rendering and input APIs. + +### Platform guards +Use the compile-time macros for platform-specific code: + +```c +#ifdef DUSK_PSP + // PSP-only path +#elif defined(DUSK_GAMECUBE) || defined(DUSK_WII) + // GameCube / Wii path +#else + // Generic / Linux fallback +#endif +``` + +Additional capability macros set per-target: +`DUSK_SDL2`, `DUSK_OPENGL`, `DUSK_OPENGL_ES`, `DUSK_OPENGL_LEGACY`, +`DUSK_INPUT_GAMEPAD`, `DUSK_INPUT_KEYBOARD`, `DUSK_INPUT_POINTER`, +`DUSK_PLATFORM_ENDIAN_BIG` / `DUSK_PLATFORM_ENDIAN_LITTLE`. + +### Abstraction pattern +Platform-specific implementations are wired in via `#define` macros in +each platform's `displayplatform.h` / `inputplatform.h` etc., which +the core calls through. Functions that a platform does not support are +simply left undefined — the core guards calls with `#ifdef`. + +### Adding platform-specific code +- Put it under `src/dusk/` in the matching subsystem folder. +- Gate any core call-site with the appropriate `#ifdef DUSK_` + or capability macro. +- Keep the `src/dusk/` core free of platform ifdefs — delegate through + the platform header macros instead. + +--- + +## Adding a new asset loader type +1. Add an enum value to `assetloadertype_t` (before `_COUNT`) in + `src/dusk/asset/loader/assetloader.h`. +2. Add fields to the input/loading/output unions in `assetloader.h`. +3. Implement `assetXxxLoaderSync`, `assetXxxLoaderAsync`, and + `assetXxxDispose` in a new `src/dusk/asset/loader/xxx/` directory. +4. Register the three callbacks in `ASSET_LOADER_CALLBACKS[]` in + `src/dusk/asset/loader/assetloader.c`. +5. If user-facing, create a JS module (see below) and a `.d.ts` file. + +--- + +## Adding a new entity component +1. Create `src/dusk/entity/component//entityMyComp.h/.c` with + struct `entityMyComp_t`, `entityMyCompInit()`, and optionally + `entityMyCompDispose()`. +2. Add the include to `src/dusk/entity/componentlist.h` header block. +3. Add a row to `src/dusk/entity/componentlist.h`: + ```c + X(MYCOMP, entityMyComp_t, myComp, entityMyCompInit, NULL, NULL) + ``` + This auto-generates the enum, union field, and definition entry. +4. If JS-facing, create the script module and `.d.ts` (see below). + +--- + +## Adding a new script (JS) module +1. Create `src/dusk/script/module//moduleMyMod.h/.c`. + - Declare `extern scriptproto_t MODULE_MYMOD_PROTO;` in the header. + - Use `moduleBaseFunction(name)` to define JS-callable functions. + - Register props/funcs in `moduleMyModInit()` with + `scriptProtoDefineProp` / `scriptProtoDefineFunc` / + `scriptProtoDefineStaticFunc`. +2. `#include` the header in + `src/dusk/script/module/modulelist.c` and call + `moduleMyModInit()` in `moduleListInit()` (and `Dispose` in + `moduleListDispose()`). +3. For component modules also register in + `src/dusk/script/module/entity/component/modulecomponentlist.c` + so `entity.add()` returns the typed wrapper. +4. Create `types//mymod.d.ts` and add a + `/// ` line to `types/index.d.ts`. + +--- + +## Script module type declarations +Whenever a `src/dusk/script/module/**/*.c` file is created or modified, +check whether the corresponding `types/**/*.d.ts` needs updating and +apply any changes before finishing the task. + +--- + +## JavaScript (asset scripts) +- Use `var` for module-level state; `const` for values that never + change. +- Always use semicolons. +- Scene objects are plain objects (`var scene = {}`) with assigned + methods. +- Export via `module.exports = scene`. +- Async scene init should use `async function` and `await`. + +--- + +## Coding style + +### Indentation +2 spaces. No tabs. + +### Keyword and operator spacing +No space between a keyword or function name and its opening parenthesis: + +```c +if(!ptr) return; +for(uint8_t i = 0; i < count; i++) { +while(entry->state != DONE) { +switch(type) { +sizeof(assetbatch_t) +memoryZero(ptr, size) +``` + +Spaces around all binary operators and after every comma: + +```c +pos->flags |= ENTITY_POSITION_FLAG_WORLD_DIRTY; +(size_t)end - (size_t)start +foo(a, b, c) +``` + +### Braces +Opening brace on the **same line** as the statement (K&R style) for all +constructs — functions, `if`, `else`, `for`, `while`, `switch`: + +```c +void assetEntryLock(assetentry_t *entry) { + ... +} + +if(dirty) { + ... +} else { + ... +} +``` + +### Guard returns +Short guards go on one line with no braces: + +```c +if(!ptr) return; +if(!b || !b->batch) return jerry_undefined(); +if(!(flags & DIRTY)) return; +``` + +### Blank lines +- One blank line between functions; no blank line at the start or end of + a function body. +- One blank line between logical blocks inside a function body. +- No trailing blank lines at the end of a file. + +### Pointer placement +`*` is attached to the variable name, not the type: + +```c +assetentry_t *entry +const char_t *name +void *ptr +uint8_t *d = (uint8_t *)dest; +``` + +### Casts +Space between cast and operand: + +```c +(assetbatch_t *)user +(uint8_t *)dest +(textureformat_t)v +``` + +### Return +No parentheses around the return value: + +```c +return ptr; +return MEMORY_POINTERS_IN_USE; +``` + +### switch / case +`case` indented 2 spaces from `switch`; body indented 2 more from `case`: + +```c +switch(type) { + case ASSET_LOADER_TYPE_TEXTURE: + descs[i].input.texture = (textureformat_t)v; + break; + default: + break; +} +``` + +### Multi-line function signatures +When parameters don't fit on one line, put each on its own line indented +2 spaces; the closing `) {` (definition) or `);` (declaration) goes on +its own line at column 0: + +```c +void assetEntryInit( + assetentry_t *entry, + const char_t *name, + const assetloadertype_t type, + assetloaderinput_t *input +) { + +errorret_t memoryCompare( + const void *a, + const void *b, + const size_t size +); +``` + +### Structs and enums +Anonymous inner struct or enum with a `typedef`, `_t` suffix, closing +brace and name on the same line: + +```c +typedef struct { + errorcode_t code; + char_t *message; +} errorstate_t; + +typedef enum { + ASSET_LOADER_TYPE_NULL, + ASSET_LOADER_TYPE_COUNT +} assetloadertype_t; +``` + +### Designated initialisers +Spaces inside braces; `.field = value`: + +```c +jsassetentry_t e = { .entry = entry }; +assetbatchloadedpend_t init = { .batch = batch }; +``` + +### Ternary operator +Spaces around `?` and `:`: + +```c +const float val = psx > 0.0f ? pt[0][0] / psx : 0.0f; +``` + +### const placement +`const` before the type, `*` attached to the variable: + +```c +const char_t *name +const void *src +const size_t size +``` + +### Comments in `.c` files +- Block comments that describe a section use the divider style: + ```c + /* ---- Public API ---- */ + ``` +- Multi-line explanatory comments inside function bodies use `//` lines: + ```c + // Script modules are freed; orphaned JS wrapper objects now get GC'd + // so their finalizers fire before assetDispose() checks ref counts. + jerry_heap_gc(JERRY_GC_PRESSURE_HIGH); + ``` +- Do not use `/* */` for inline or inline-block comments inside `.c` + function bodies. + +### Comments in `.h` files +Every public declaration gets a Javadoc block (`/** … */`) with +`@param` and `@returns` where relevant. Keep it on the lines immediately +above the declaration with no blank line in between. + +--- + +## Tests +- Tests live in `test/` mirroring `src/dusk/` structure. +- Use cmocka; include `dusktest.h`. +- Test functions: `static void test_something(void **state)`. +- After each test, assert `memoryGetAllocatedCount() == 0` to catch + leaks. +- Build with `-DDUSK_BUILD_TESTS=ON`. diff --git a/assets/player.js b/assets/player.js new file mode 100644 index 00000000..ab05bbd2 --- /dev/null +++ b/assets/player.js @@ -0,0 +1,51 @@ +// Copyright (c) 2026 Dominic Masters +// +// This software is released under the MIT License. +// https://opensource.org/licenses/MIT + +const PLAYER_SPEED = 5.0; + +var player = {}; + +var _entity, _position, _physics; + +player.create = function(texEntry) { + _entity = Entity.create(); + _position = _entity.add(Component.POSITION); + _physics = _entity.add(Component.PHYSICS); + + _physics.bodyType = Physics.DYNAMIC; + _physics.shape = Physics.SHAPE_CUBE; + _physics.gravityScale = 1.0; + + var r = _entity.add(Component.RENDERABLE); + r.texture = texEntry.texture; + r.type = Renderable.SPRITEBATCH; + r.color = new Color(220, 80, 80); + // upright quad: (-0.5,0,0) → (0.5,1,0) in XY plane + r.sprites = [[-0.5, 0, 0, 0.5, 1, 0, 0, 0, 1, 1]]; + + _position.localPosition = new Vec3(0, 1, 0); +}; + +player.getPosition = function() { + return _position; +}; + +player.update = function() { + if(!_physics) return; + var vx = Input.axis(INPUT_ACTION_LEFT, INPUT_ACTION_RIGHT) * PLAYER_SPEED; + var vz = Input.axis(INPUT_ACTION_DOWN, INPUT_ACTION_UP) * PLAYER_SPEED; + // Preserve vertical velocity so gravity and landing work correctly. + var vy = _physics.velocity.y; + _physics.velocity = new Vec3(vx, vy, vz); +}; + +player.dispose = function() { + Entity.dispose(_entity); + _entity = null; + _position = null; + _physics = null; +}; + +module.exports = player; diff --git a/assets/testscene.js b/assets/testscene.js index 694745e4..270d39f5 100644 --- a/assets/testscene.js +++ b/assets/testscene.js @@ -1,47 +1,72 @@ -var scene = {}; +// Copyright (c) 2026 Dominic Masters +// +// This software is released under the MIT License. +// https://opensource.org/licenses/MIT + +var scene = {}; +var player = require('player.js'); var assets = AssetBatch([ { path: 'test.png', type: Asset.TYPE_TEXTURE, format: Texture.FORMAT_RGBA } ]); -var cam; -var camPos; -var testEntity; -var testPos; -var testRenderable; -var texEntry; +// Pokemon DS-style camera: ~34 degrees elevation (atan(6/9)). +// CAM_HEIGHT / CAM_DIST ratio controls the tilt — keep it under 0.7 for +// the characteristically shallow DS angle. +const CAM_HEIGHT = 6; +const CAM_DIST = 9; + +var cam, camPos; +var floorEntity; + +function updateCamera() { + var pp = player.getPosition().worldPosition; + // Position is offset above and behind the player; lookAt the exact player + // world position so the player projects to the center pixel every frame. + camPos.localPosition = new Vec3(pp.x, pp.y + CAM_HEIGHT, pp.z + CAM_DIST); + camPos.lookAt(new Vec3(pp.x, pp.y, pp.z)); +} scene.init = async function() { assets.lock(); await assets.loaded(); - Console.print('Scene Init'); - texEntry = assets.entry(0); + var texEntry = assets.entry(0); - // Camera at (3, 3, 3) looking at origin + // Camera cam = Entity.create(); camPos = cam.add(Component.POSITION); cam.add(Component.CAMERA); - camPos.localPosition = new Vec3(3, 3, 3); - camPos.lookAt(new Vec3(0, 0, 0)); - // Test entity with textured quad at origin - testEntity = Entity.create(); - testPos = testEntity.add(Component.POSITION); - testRenderable = testEntity.add(Component.RENDERABLE); + // Floor — infinite static plane at Y=0. Rendered as a large flat blue + // slab using the default SHADER_MATERIAL (no texture needed). + floorEntity = Entity.create(); + var floorPos = floorEntity.add(Component.POSITION); + var floorPhysics = floorEntity.add(Component.PHYSICS); + floorPhysics.bodyType = Physics.STATIC; + floorPhysics.shape = Physics.SHAPE_PLANE; - testRenderable.texture = texEntry.texture; - testRenderable.type = Renderable.SPRITEBATCH; - testRenderable.sprites = [ - [0, 0, 1, 1, 0, 1, 1, 0] - ]; - // testPos.localPosition = new Vec3(0, 0, 0); -} + var floorR = floorEntity.add(Component.RENDERABLE); + floorR.color = Color.BLUE; + floorPos.localScale = new Vec3(16, 0.2, 16); + floorPos.localPosition = new Vec3(0, -0.1, 0); + + // Player — spawns 1 unit above the floor so physics drops it cleanly. + player.create(texEntry); + + // Initialise camera at the correct angle from the player's spawn position. + updateCamera(); +}; + +scene.update = function() { + player.update(); + updateCamera(); +}; scene.dispose = function() { - Console.print('Scene Dispose'); + player.dispose(); + Entity.dispose(floorEntity); Entity.dispose(cam); - Entity.dispose(testEntity); assets.unlock(); }; diff --git a/src/dusk/console/console.c b/src/dusk/console/console.c index 3e37e8e5..5d50e1ed 100644 --- a/src/dusk/console/console.c +++ b/src/dusk/console/console.c @@ -20,6 +20,7 @@ console_t CONSOLE; void consoleInit(void) { memoryZero(&CONSOLE, sizeof(console_t)); + CONSOLE.visible = true; #ifdef DUSK_CONSOLE_POSIX threadMutexInit(&CONSOLE.printMutex); diff --git a/src/dusk/script/module/CMakeLists.txt b/src/dusk/script/module/CMakeLists.txt index a81eeaa9..746538c2 100644 --- a/src/dusk/script/module/CMakeLists.txt +++ b/src/dusk/script/module/CMakeLists.txt @@ -10,6 +10,7 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME} ) # Subdirs +add_subdirectory(animation) add_subdirectory(asset) add_subdirectory(console) add_subdirectory(display) @@ -17,7 +18,13 @@ add_subdirectory(engine) add_subdirectory(entity) add_subdirectory(event) add_subdirectory(input) +add_subdirectory(item) +add_subdirectory(locale) add_subdirectory(math) +add_subdirectory(overworld) add_subdirectory(require) +add_subdirectory(save) add_subdirectory(scene) -add_subdirectory(system) \ No newline at end of file +add_subdirectory(story) +add_subdirectory(system) +add_subdirectory(ui) \ No newline at end of file diff --git a/src/dusk/script/module/animation/CMakeLists.txt b/src/dusk/script/module/animation/CMakeLists.txt new file mode 100644 index 00000000..3483063d --- /dev/null +++ b/src/dusk/script/module/animation/CMakeLists.txt @@ -0,0 +1,10 @@ +# Copyright (c) 2026 Dominic Masters +# +# This software is released under the MIT License. +# https://opensource.org/licenses/MIT + +target_sources(${DUSK_LIBRARY_TARGET_NAME} + PUBLIC + moduleanimation.c + moduleeasing.c +) diff --git a/src/dusk/script/module/animation/moduleanimation.c b/src/dusk/script/module/animation/moduleanimation.c new file mode 100644 index 00000000..dcab0c14 --- /dev/null +++ b/src/dusk/script/module/animation/moduleanimation.c @@ -0,0 +1,88 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "moduleanimation.h" +#include "animation/animation.h" + +typedef struct { + keyframe_t keyframes[MODULE_ANIMATION_KEYFRAME_MAX]; + animation_t anim; +} moduleanimationdata_t; + +scriptproto_t MODULE_ANIMATION_PROTO; + +moduleBaseFunction(moduleAnimationCtor) { + if(argc < 1 || !jerry_value_is_array(args[0])) { + return moduleBaseThrow( + "Animation: expected array of keyframe objects" + ); + } + uint32_t len = jerry_array_length(args[0]); + if(len == 0) { + return moduleBaseThrow( + "Animation: keyframe array must not be empty" + ); + } + const uint16_t count = len > (uint32_t)MODULE_ANIMATION_KEYFRAME_MAX + ? (uint16_t)MODULE_ANIMATION_KEYFRAME_MAX + : (uint16_t)len; + + moduleanimationdata_t *d = (moduleanimationdata_t *)memoryAllocate( + sizeof(moduleanimationdata_t) + ); + + for(uint16_t i = 0; i < count; i++) { + jerry_value_t elem = jerry_object_get_index(args[0], (uint32_t)i); + jerry_value_t jtm = moduleBaseGetProp(elem, "time"); + jerry_value_t jvl = moduleBaseGetProp(elem, "value"); + jerry_value_t jea = moduleBaseGetProp(elem, "easing"); + + d->keyframes[i].time = moduleBaseValueFloat(jtm); + d->keyframes[i].value = moduleBaseValueFloat(jvl); + d->keyframes[i].easing = jerry_value_is_number(jea) + ? (easingtype_t)moduleBaseValueInt(jea) + : EASING_LINEAR; + + jerry_value_free(jea); + jerry_value_free(jvl); + jerry_value_free(jtm); + jerry_value_free(elem); + } + + animationInit(&d->anim, d->keyframes, count); + + jerry_object_set_native_ptr( + callInfo->this_value, &MODULE_ANIMATION_PROTO.info, d + ); + return jerry_undefined(); +} + +moduleBaseFunction(moduleAnimationGetValue) { + moduleBaseRequireArgs(1); + moduleBaseRequireNumber(0); + moduleanimationdata_t *d = (moduleanimationdata_t *)scriptProtoGetValue( + &MODULE_ANIMATION_PROTO, callInfo->this_value + ); + if(!d) return jerry_undefined(); + return jerry_number( + (double)animationGetValue(&d->anim, moduleBaseArgFloat(0)) + ); +} + +void moduleAnimationInit(void) { + scriptProtoInit( + &MODULE_ANIMATION_PROTO, "Animation", + sizeof(moduleanimationdata_t), moduleAnimationCtor + ); + scriptProtoDefineFunc( + &MODULE_ANIMATION_PROTO, "getValue", moduleAnimationGetValue + ); +} + +void moduleAnimationDispose(void) { + scriptProtoDispose(&MODULE_ANIMATION_PROTO); +} diff --git a/src/dusk/script/module/animation/moduleanimation.h b/src/dusk/script/module/animation/moduleanimation.h new file mode 100644 index 00000000..8de1cffa --- /dev/null +++ b/src/dusk/script/module/animation/moduleanimation.h @@ -0,0 +1,42 @@ +/** + * 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 "util/memory.h" + +/** Maximum keyframes per Animation instance. */ +#define MODULE_ANIMATION_KEYFRAME_MAX 64 + +extern scriptproto_t MODULE_ANIMATION_PROTO; + +/** + * new Animation(keyframes) — creates an Animation from a JS array of + * `{time, value, easing?}` descriptor objects. + * + * @param args[0] Array of keyframe descriptors. + */ +moduleBaseFunction(moduleAnimationCtor); + +/** + * animation.getValue(time) — interpolates the animation at `time`. + * + * @param args[0] Time in seconds (number). + * @return Interpolated float value. + */ +moduleBaseFunction(moduleAnimationGetValue); + +/** + * Initializes the Animation module and registers the Animation constructor. + */ +void moduleAnimationInit(void); + +/** + * Disposes the Animation module. + */ +void moduleAnimationDispose(void); diff --git a/src/dusk/script/module/animation/moduleeasing.c b/src/dusk/script/module/animation/moduleeasing.c new file mode 100644 index 00000000..65bdcaea --- /dev/null +++ b/src/dusk/script/module/animation/moduleeasing.c @@ -0,0 +1,51 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "moduleeasing.h" +#include "animation/easing.h" + +scriptproto_t MODULE_EASING_PROTO; + +moduleBaseFunction(moduleEasingApply) { + moduleBaseRequireArgs(2); + moduleBaseRequireNumber(0); + moduleBaseRequireNumber(1); + const int32_t type = moduleBaseArgInt(0); + if(type < 0 || type >= (int32_t)EASING_COUNT) { + return moduleBaseThrow("Easing.apply: invalid easing type"); + } + const float_t t = moduleBaseArgFloat(1); + return jerry_number((double)easingApply((easingtype_t)type, t)); +} + +void moduleEasingInit(void) { + scriptProtoInit(&MODULE_EASING_PROTO, "Easing", 0, NULL); + scriptProtoDefineStaticFunc( + &MODULE_EASING_PROTO, "apply", moduleEasingApply + ); + + moduleBaseSetInt("EASING_LINEAR", EASING_LINEAR); + moduleBaseSetInt("EASING_IN_SINE", EASING_IN_SINE); + moduleBaseSetInt("EASING_OUT_SINE", EASING_OUT_SINE); + moduleBaseSetInt("EASING_IN_OUT_SINE", EASING_IN_OUT_SINE); + moduleBaseSetInt("EASING_IN_QUAD", EASING_IN_QUAD); + moduleBaseSetInt("EASING_OUT_QUAD", EASING_OUT_QUAD); + moduleBaseSetInt("EASING_IN_OUT_QUAD", EASING_IN_OUT_QUAD); + moduleBaseSetInt("EASING_IN_CUBIC", EASING_IN_CUBIC); + moduleBaseSetInt("EASING_OUT_CUBIC", EASING_OUT_CUBIC); + moduleBaseSetInt("EASING_IN_OUT_CUBIC", EASING_IN_OUT_CUBIC); + moduleBaseSetInt("EASING_IN_QUART", EASING_IN_QUART); + moduleBaseSetInt("EASING_OUT_QUART", EASING_OUT_QUART); + moduleBaseSetInt("EASING_IN_OUT_QUART", EASING_IN_OUT_QUART); + moduleBaseSetInt("EASING_IN_BACK", EASING_IN_BACK); + moduleBaseSetInt("EASING_OUT_BACK", EASING_OUT_BACK); + moduleBaseSetInt("EASING_IN_OUT_BACK", EASING_IN_OUT_BACK); +} + +void moduleEasingDispose(void) { + scriptProtoDispose(&MODULE_EASING_PROTO); +} diff --git a/src/dusk/script/module/animation/moduleeasing.h b/src/dusk/script/module/animation/moduleeasing.h new file mode 100644 index 00000000..5824e877 --- /dev/null +++ b/src/dusk/script/module/animation/moduleeasing.h @@ -0,0 +1,32 @@ +/** + * 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" + +extern scriptproto_t MODULE_EASING_PROTO; + +/** + * Easing.apply(type, t) — applies easing function `type` to normalized + * time `t` and returns the eased value. + * + * @param args[0] Easing type constant (EASING_*). + * @param args[1] Normalized input time in [0, 1]. + * @return Eased value as a number. + */ +moduleBaseFunction(moduleEasingApply); + +/** + * Initializes the Easing module and injects all EASING_* constants. + */ +void moduleEasingInit(void); + +/** + * Disposes the Easing module. + */ +void moduleEasingDispose(void); diff --git a/src/dusk/script/module/entity/component/physics/modulephysics.c b/src/dusk/script/module/entity/component/physics/modulephysics.c index 0aa7db8e..ebd20035 100644 --- a/src/dusk/script/module/entity/component/physics/modulephysics.c +++ b/src/dusk/script/module/entity/component/physics/modulephysics.c @@ -6,6 +6,7 @@ */ #include "modulephysics.h" +#include "util/memory.h" scriptproto_t MODULE_PHYSICS_PROTO; @@ -64,8 +65,29 @@ moduleBaseFunction(modulePhysicsSetShape) { moduleBaseRequireArgs(1); jscomponent_t *c = modulePhysicsSelf(callInfo); if(!c) return jerry_undefined(); - physicsshape_t shape = entityPhysicsGetShape(c->entityId, c->componentId); + physicsshape_t shape; + memoryZero(&shape, sizeof(physicsshape_t)); shape.type = (physicshapetype_t)moduleBaseArgInt(0); + switch(shape.type) { + case PHYSICS_SHAPE_CUBE: + shape.data.cube.halfExtents[0] = 0.5f; + shape.data.cube.halfExtents[1] = 0.5f; + shape.data.cube.halfExtents[2] = 0.5f; + break; + case PHYSICS_SHAPE_SPHERE: + shape.data.sphere.radius = 0.5f; + break; + case PHYSICS_SHAPE_CAPSULE: + shape.data.capsule.radius = 0.5f; + shape.data.capsule.halfHeight = 0.5f; + break; + case PHYSICS_SHAPE_PLANE: + shape.data.plane.normal[1] = 1.0f; + shape.data.plane.distance = 0.0f; + break; + default: + break; + } entityPhysicsSetShape(c->entityId, c->componentId, shape); return jerry_undefined(); } diff --git a/src/dusk/script/module/item/CMakeLists.txt b/src/dusk/script/module/item/CMakeLists.txt new file mode 100644 index 00000000..1e292daf --- /dev/null +++ b/src/dusk/script/module/item/CMakeLists.txt @@ -0,0 +1,10 @@ +# Copyright (c) 2026 Dominic Masters +# +# This software is released under the MIT License. +# https://opensource.org/licenses/MIT + +target_sources(${DUSK_LIBRARY_TARGET_NAME} + PUBLIC + moduleitem.c + modulebackpack.c +) diff --git a/src/dusk/script/module/item/modulebackpack.c b/src/dusk/script/module/item/modulebackpack.c new file mode 100644 index 00000000..c0b0702a --- /dev/null +++ b/src/dusk/script/module/item/modulebackpack.c @@ -0,0 +1,122 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "modulebackpack.h" +#include "moduleitem.h" +#include "item/backpack.h" + +scriptproto_t MODULE_BACKPACK_PROTO; + +moduleBaseFunction(moduleBackpackGetIsFull) { + return jerry_boolean(inventoryIsFull(&BACKPACK)); +} + +moduleBaseFunction(moduleBackpackGetCount) { + moduleBaseRequireArgs(1); + moduleItemRequireId(0, "Backpack.getCount"); + const itemid_t id = (itemid_t)moduleBaseArgInt(0); + return jerry_number((double)inventoryGetCount(&BACKPACK, id)); +} + +moduleBaseFunction(moduleBackpackHas) { + moduleBaseRequireArgs(1); + moduleItemRequireId(0, "Backpack.has"); + const itemid_t id = (itemid_t)moduleBaseArgInt(0); + return jerry_boolean(inventoryItemExists(&BACKPACK, id)); +} + +moduleBaseFunction(moduleBackpackIsItemFull) { + moduleBaseRequireArgs(1); + moduleItemRequireId(0, "Backpack.isItemFull"); + const itemid_t id = (itemid_t)moduleBaseArgInt(0); + return jerry_boolean(inventoryItemFull(&BACKPACK, id)); +} + +moduleBaseFunction(moduleBackpackSet) { + moduleBaseRequireArgs(2); + moduleItemRequireId(0, "Backpack.set"); + moduleBaseRequireNumber(1); + const int32_t qty = moduleBaseArgInt(1); + if(qty < 0 || qty > (int32_t)ITEM_STACK_QUANTITY_MAX) { + return moduleBaseThrow("Backpack.set: quantity out of range (0-255)"); + } + inventorySet(&BACKPACK, (itemid_t)moduleBaseArgInt(0), (uint8_t)qty); + return jerry_undefined(); +} + +moduleBaseFunction(moduleBackpackAdd) { + moduleBaseRequireArgs(2); + moduleItemRequireId(0, "Backpack.add"); + moduleBaseRequireNumber(1); + const int32_t qty = moduleBaseArgInt(1); + if(qty < 1 || qty > (int32_t)ITEM_STACK_QUANTITY_MAX) { + return moduleBaseThrow("Backpack.add: quantity out of range (1-255)"); + } + inventoryAdd(&BACKPACK, (itemid_t)moduleBaseArgInt(0), (uint8_t)qty); + return jerry_undefined(); +} + +moduleBaseFunction(moduleBackpackRemove) { + moduleBaseRequireArgs(1); + moduleItemRequireId(0, "Backpack.remove"); + inventoryRemove(&BACKPACK, (itemid_t)moduleBaseArgInt(0)); + return jerry_undefined(); +} + +moduleBaseFunction(moduleBackpackSort) { + moduleBaseRequireArgs(1); + moduleBaseRequireNumber(0); + const inventorysort_t sortBy = (inventorysort_t)moduleBaseArgInt(0); + if((int32_t)sortBy < 0 || sortBy >= INVENTORY_SORT_COUNT) { + return moduleBaseThrow("Backpack.sort: invalid sort type"); + } + const bool_t reverse = (argc > 1 && jerry_value_is_true(args[1])); + inventorySort(&BACKPACK, sortBy, reverse); + return jerry_undefined(); +} + +void moduleBackpackInit(void) { + scriptProtoInit( + &MODULE_BACKPACK_PROTO, "Backpack", sizeof(uint8_t), NULL + ); + + scriptProtoDefineStaticProp( + &MODULE_BACKPACK_PROTO, "isFull", moduleBackpackGetIsFull, NULL + ); + scriptProtoDefineStaticFunc( + &MODULE_BACKPACK_PROTO, "getCount", moduleBackpackGetCount + ); + scriptProtoDefineStaticFunc( + &MODULE_BACKPACK_PROTO, "has", moduleBackpackHas + ); + scriptProtoDefineStaticFunc( + &MODULE_BACKPACK_PROTO, "isItemFull", moduleBackpackIsItemFull + ); + scriptProtoDefineStaticFunc( + &MODULE_BACKPACK_PROTO, "set", moduleBackpackSet + ); + scriptProtoDefineStaticFunc( + &MODULE_BACKPACK_PROTO, "add", moduleBackpackAdd + ); + scriptProtoDefineStaticFunc( + &MODULE_BACKPACK_PROTO, "remove", moduleBackpackRemove + ); + scriptProtoDefineStaticFunc( + &MODULE_BACKPACK_PROTO, "sort", moduleBackpackSort + ); + + moduleBaseSetInt( + "INVENTORY_SORT_BY_ID", (int32_t)INVENTORY_SORT_BY_ID + ); + moduleBaseSetInt( + "INVENTORY_SORT_BY_TYPE", (int32_t)INVENTORY_SORT_BY_TYPE + ); +} + +void moduleBackpackDispose(void) { + scriptProtoDispose(&MODULE_BACKPACK_PROTO); +} diff --git a/src/dusk/script/module/item/modulebackpack.h b/src/dusk/script/module/item/modulebackpack.h new file mode 100644 index 00000000..5428326b --- /dev/null +++ b/src/dusk/script/module/item/modulebackpack.h @@ -0,0 +1,81 @@ +/** + * 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" + +extern scriptproto_t MODULE_BACKPACK_PROTO; + +/** + * Backpack.isFull — true when all storage slots are occupied. + */ +moduleBaseFunction(moduleBackpackGetIsFull); + +/** + * Backpack.getCount(itemId) — quantity of itemId (0 if absent). + * + * @param args[0] Item ID constant (ITEM_ID_*). + */ +moduleBaseFunction(moduleBackpackGetCount); + +/** + * Backpack.has(itemId) — true if itemId is present with quantity > 0. + * + * @param args[0] Item ID constant (ITEM_ID_*). + */ +moduleBaseFunction(moduleBackpackHas); + +/** + * Backpack.isItemFull(itemId) — true if the stack is at max quantity. + * + * @param args[0] Item ID constant (ITEM_ID_*). + */ +moduleBaseFunction(moduleBackpackIsItemFull); + +/** + * Backpack.set(itemId, quantity) — set quantity; removes when 0. + * + * @param args[0] Item ID constant (ITEM_ID_*). + * @param args[1] Quantity 0–255. + */ +moduleBaseFunction(moduleBackpackSet); + +/** + * Backpack.add(itemId, quantity) — add quantity units of itemId. + * + * @param args[0] Item ID constant (ITEM_ID_*). + * @param args[1] Quantity to add (1–255). + */ +moduleBaseFunction(moduleBackpackAdd); + +/** + * Backpack.remove(itemId) — removes itemId entirely from the backpack. + * + * @param args[0] Item ID constant (ITEM_ID_*). + */ +moduleBaseFunction(moduleBackpackRemove); + +/** + * Backpack.sort(sortBy[, reverse]) — sort the backpack contents. + * + * @param args[0] Sort type (INVENTORY_SORT_BY_ID or + * INVENTORY_SORT_BY_TYPE). + * @param args[1] Optional boolean; true to reverse order. + */ +moduleBaseFunction(moduleBackpackSort); + +/** + * Initializes the Backpack module, registers all Backpack methods, and + * sets INVENTORY_SORT_BY_ID / INVENTORY_SORT_BY_TYPE as globals. + */ +void moduleBackpackInit(void); + +/** + * Disposes the Backpack module. + */ +void moduleBackpackDispose(void); diff --git a/src/dusk/script/module/item/moduleitem.c b/src/dusk/script/module/item/moduleitem.c new file mode 100644 index 00000000..05512814 --- /dev/null +++ b/src/dusk/script/module/item/moduleitem.c @@ -0,0 +1,47 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "moduleitem.h" +#include + +scriptproto_t MODULE_ITEM_PROTO; + +moduleBaseFunction(moduleItemGetName) { + moduleBaseRequireArgs(1); + moduleItemRequireId(0, "Item.getName"); + const itemid_t id = (itemid_t)moduleBaseArgInt(0); + return jerry_string_sz(ITEMS[id].name); +} + +moduleBaseFunction(moduleItemGetType) { + moduleBaseRequireArgs(1); + moduleItemRequireId(0, "Item.getType"); + const itemid_t id = (itemid_t)moduleBaseArgInt(0); + return jerry_number((double)ITEMS[id].type); +} + +void moduleItemInit(void) { + scriptProtoInit(&MODULE_ITEM_PROTO, "Item", sizeof(uint8_t), NULL); + + scriptProtoDefineStaticFunc( + &MODULE_ITEM_PROTO, "getName", moduleItemGetName + ); + scriptProtoDefineStaticFunc( + &MODULE_ITEM_PROTO, "getType", moduleItemGetType + ); + + jerry_value_t result = jerry_eval( + (const jerry_char_t *)ITEM_SCRIPT, + strlen(ITEM_SCRIPT), + JERRY_PARSE_NO_OPTS + ); + jerry_value_free(result); +} + +void moduleItemDispose(void) { + scriptProtoDispose(&MODULE_ITEM_PROTO); +} diff --git a/src/dusk/script/module/item/moduleitem.h b/src/dusk/script/module/item/moduleitem.h new file mode 100644 index 00000000..19bad73b --- /dev/null +++ b/src/dusk/script/module/item/moduleitem.h @@ -0,0 +1,55 @@ +/** + * 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/item.h" + +extern scriptproto_t MODULE_ITEM_PROTO; + +/** + * Validates an itemid_t argument and returns a type error if invalid. + * + * @param i Argument index. + * @param ctx Context string for error messages. + */ +#define moduleItemRequireId(i, ctx) do { \ + if(!jerry_value_is_number(args[(i)])) { \ + return moduleBaseThrow(ctx ": itemId must be a number"); \ + } \ + const itemid_t _id = (itemid_t)moduleBaseArgInt(i); \ + if(_id <= ITEM_ID_NULL || _id >= ITEM_ID_COUNT) { \ + return moduleBaseThrow(ctx ": invalid item ID"); \ + } \ +} while(0) + +/** + * Item.getName(itemId) — returns the item's string name. + * + * @param args[0] Item ID constant (ITEM_ID_*). + */ +moduleBaseFunction(moduleItemGetName); + +/** + * Item.getType(itemId) — returns the item's type constant. + * + * @param args[0] Item ID constant (ITEM_ID_*). + */ +moduleBaseFunction(moduleItemGetType); + +/** + * Initializes the Item module, registers Item.getName/getType, and + * evaluates ITEM_SCRIPT to populate ITEM_ID_* and ITEM_TYPE_* constants + * as globals in the script scope. + */ +void moduleItemInit(void); + +/** + * Disposes the Item module. + */ +void moduleItemDispose(void); diff --git a/src/dusk/script/module/locale/CMakeLists.txt b/src/dusk/script/module/locale/CMakeLists.txt new file mode 100644 index 00000000..7eb305af --- /dev/null +++ b/src/dusk/script/module/locale/CMakeLists.txt @@ -0,0 +1,9 @@ +# Copyright (c) 2026 Dominic Masters +# +# This software is released under the MIT License. +# https://opensource.org/licenses/MIT + +target_sources(${DUSK_LIBRARY_TARGET_NAME} + PUBLIC + modulelocale.c +) diff --git a/src/dusk/script/module/locale/modulelocale.c b/src/dusk/script/module/locale/modulelocale.c new file mode 100644 index 00000000..2380d9fe --- /dev/null +++ b/src/dusk/script/module/locale/modulelocale.c @@ -0,0 +1,114 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "modulelocale.h" +#include "asset/loader/locale/assetlocaleloader.h" +#include +#include + +#define MODULE_LOCALE_SUBSTITUTION_MAX 4 +#define MODULE_LOCALE_BUFFER_SIZE 512 +#define MODULE_LOCALE_ID_SIZE 256 + +scriptproto_t MODULE_LOCALE_PROTO; + +moduleBaseFunction(moduleLocaleGetText) { + moduleBaseRequireArgs(1); + moduleBaseRequireString(0); + + if(LOCALE.entry == NULL) { + return moduleBaseThrow("Locale.getText: no locale loaded"); + } + + char_t id[MODULE_LOCALE_ID_SIZE]; + moduleBaseToString(args[0], id, sizeof(id)); + + // Second arg is optional plural count. + int32_t plural = 1; + jerry_length_t subStart = 1; + if(argc > 1 && jerry_value_is_number(args[1])) { + plural = moduleBaseArgInt(1); + subStart = 2; + } + + // Collect up to MODULE_LOCALE_SUBSTITUTION_MAX substitution args. + assetlocalearg_t subArgs[MODULE_LOCALE_SUBSTITUTION_MAX]; + char_t subStrings[MODULE_LOCALE_SUBSTITUTION_MAX][128]; + size_t subCount = 0; + + for(jerry_length_t i = subStart; i < argc; i++) { + if(subCount >= MODULE_LOCALE_SUBSTITUTION_MAX) break; + + if(jerry_value_is_string(args[i])) { + moduleBaseToString( + args[i], subStrings[subCount], sizeof(subStrings[subCount]) + ); + subArgs[subCount].type = ASSET_LOCALE_ARG_STRING; + subArgs[subCount].stringValue = subStrings[subCount]; + } else if(jerry_value_is_number(args[i])) { + double n = jerry_value_as_number(args[i]); + if(floor(n) == n) { + subArgs[subCount].type = ASSET_LOCALE_ARG_INT; + subArgs[subCount].intValue = (int32_t)n; + } else { + subArgs[subCount].type = ASSET_LOCALE_ARG_FLOAT; + subArgs[subCount].floatValue = (float_t)n; + } + } else { + continue; + } + subCount++; + } + + char_t buf[MODULE_LOCALE_BUFFER_SIZE]; + assetLocaleGetStringWithArgs( + &LOCALE.entry->data.locale, + id, + plural, + buf, + sizeof(buf), + subCount > 0 ? subArgs : NULL, + subCount + ); + + return jerry_string_sz(buf); +} + +moduleBaseFunction(moduleLocaleSetLocale) { + moduleBaseRequireArgs(1); + moduleBaseRequireString(0); + + char_t name[64]; + moduleBaseToString(args[0], name, sizeof(name)); + + const localeinfo_t *locale = NULL; + if(strcmp(name, LOCALE_EN_US.name) == 0) locale = &LOCALE_EN_US; + + if(locale == NULL) { + return moduleBaseThrow("Locale.setLocale: unknown locale name"); + } + + localeManagerSetLocale(locale); + return jerry_undefined(); +} + +void moduleLocaleInit(void) { + scriptProtoInit( + &MODULE_LOCALE_PROTO, "Locale", sizeof(uint8_t), NULL + ); + + scriptProtoDefineStaticFunc( + &MODULE_LOCALE_PROTO, "getText", moduleLocaleGetText + ); + scriptProtoDefineStaticFunc( + &MODULE_LOCALE_PROTO, "setLocale", moduleLocaleSetLocale + ); +} + +void moduleLocaleDispose(void) { + scriptProtoDispose(&MODULE_LOCALE_PROTO); +} diff --git a/src/dusk/script/module/locale/modulelocale.h b/src/dusk/script/module/locale/modulelocale.h new file mode 100644 index 00000000..9ac5a4c6 --- /dev/null +++ b/src/dusk/script/module/locale/modulelocale.h @@ -0,0 +1,40 @@ +/** + * 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 "locale/localemanager.h" + +extern scriptproto_t MODULE_LOCALE_PROTO; + +/** + * Locale.getText(id[, pluralCount[, arg1, ...]]) — returns the + * translated string for the given message ID. + * + * @param args[0] Message ID string. + * @param args[1] Optional plural count (number). Defaults to 1. + * @param args[2..5] Optional substitution args (string or number). + */ +moduleBaseFunction(moduleLocaleGetText); + +/** + * Locale.setLocale(name) — switches the active locale by name. + * + * @param args[0] Locale name string, e.g. "en-US". + */ +moduleBaseFunction(moduleLocaleSetLocale); + +/** + * Initializes the Locale module and registers Locale.getText/setLocale. + */ +void moduleLocaleInit(void); + +/** + * Disposes the Locale module. + */ +void moduleLocaleDispose(void); diff --git a/src/dusk/script/module/modulelist.c b/src/dusk/script/module/modulelist.c index ea005e64..cb0da584 100644 --- a/src/dusk/script/module/modulelist.c +++ b/src/dusk/script/module/modulelist.c @@ -6,6 +6,8 @@ */ #include "modulelist.h" +#include "script/module/animation/moduleanimation.h" +#include "script/module/animation/moduleeasing.h" #include "script/module/asset/moduleasset.h" #include "script/module/console/moduleconsole.h" #include "script/module/display/modulecolor.h" @@ -16,13 +18,22 @@ #include "script/module/engine/moduletimeout.h" #include "script/module/entity/moduleentity.h" #include "script/module/input/moduleinput.h" +#include "script/module/item/moduleitem.h" +#include "script/module/item/modulebackpack.h" +#include "script/module/locale/modulelocale.h" +#include "script/module/save/modulesave.h" #include "script/module/math/modulevec3.h" +#include "script/module/overworld/moduleoverworld.h" #include "script/module/require/modulerequire.h" #include "script/module/scene/modulescene.h" +#include "script/module/story/modulestory.h" #include "script/module/system/modulesystem.h" +#include "script/module/ui/moduletextbox.h" void moduleListInit(void) { + moduleAnimationInit(); + moduleEasingInit(); moduleEventInit(); moduleTextureInit(); moduleColorInit(); @@ -35,9 +46,16 @@ void moduleListInit(void) { moduleVec3Init(); moduleEntityInit(); moduleInputInit(); + moduleItemInit(); + moduleBackpackInit(); + moduleLocaleInit(); + moduleSaveInit(); + moduleOverworldInit(); moduleRequireInit(); moduleSceneInit(); + moduleStoryInit(); moduleSystemInit(); + moduleTextboxInit(); } void moduleListUpdate(void) { @@ -46,9 +64,18 @@ void moduleListUpdate(void) { } void moduleListDispose(void) { + moduleTextboxDispose(); moduleSystemDispose(); + moduleOverworldDispose(); + moduleEasingDispose(); + moduleAnimationDispose(); + moduleStoryDispose(); moduleSceneDispose(); moduleRequireDispose(); + moduleSaveDispose(); + moduleLocaleDispose(); + moduleBackpackDispose(); + moduleItemDispose(); moduleInputDispose(); moduleEntityDispose(); moduleVec3Dispose(); diff --git a/src/dusk/script/module/overworld/CMakeLists.txt b/src/dusk/script/module/overworld/CMakeLists.txt new file mode 100644 index 00000000..10af18ad --- /dev/null +++ b/src/dusk/script/module/overworld/CMakeLists.txt @@ -0,0 +1,9 @@ +# Copyright (c) 2026 Dominic Masters +# +# This software is released under the MIT License. +# https://opensource.org/licenses/MIT + +target_sources(${DUSK_LIBRARY_TARGET_NAME} + PUBLIC + moduleoverworld.c +) diff --git a/src/dusk/script/module/overworld/moduleoverworld.c b/src/dusk/script/module/overworld/moduleoverworld.c new file mode 100644 index 00000000..8bf23d2f --- /dev/null +++ b/src/dusk/script/module/overworld/moduleoverworld.c @@ -0,0 +1,73 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "moduleoverworld.h" +#include "overworld/map.h" + +scriptproto_t MODULE_OVERWORLD_PROTO; + +moduleBaseFunction(moduleOverworldLoadMap) { + moduleBaseRequireArgs(1); + moduleBaseRequireString(0); + char_t handle[MAP_HANDLE_MAX]; + moduleBaseToString(args[0], handle, (jerry_size_t)MAP_HANDLE_MAX); + errorret_t err = mapLoad(handle); + if(errorIsNotOk(err)) return moduleBaseThrowError(err); + return jerry_undefined(); +} + +moduleBaseFunction(moduleOverworldIsLoaded) { + return jerry_boolean(mapIsLoaded()); +} + +moduleBaseFunction(moduleOverworldSetPosition) { + moduleBaseRequireArgs(3); + moduleBaseRequireNumber(0); + moduleBaseRequireNumber(1); + moduleBaseRequireNumber(2); + tilepos_t pos; + pos.x = (tileunit_t)moduleBaseArgInt(0); + pos.y = (tileunit_t)moduleBaseArgInt(1); + pos.z = (tileunit_t)moduleBaseArgInt(2); + errorret_t err = mapPositionSet(pos); + if(errorIsNotOk(err)) return moduleBaseThrowError(err); + return jerry_undefined(); +} + +moduleBaseFunction(moduleOverworldUpdate) { + errorret_t err = mapUpdate(); + if(errorIsNotOk(err)) return moduleBaseThrowError(err); + return jerry_undefined(); +} + +moduleBaseFunction(moduleOverworldJsDispose) { + mapDispose(); + return jerry_undefined(); +} + +void moduleOverworldInit(void) { + scriptProtoInit(&MODULE_OVERWORLD_PROTO, "Overworld", 0, NULL); + scriptProtoDefineStaticFunc( + &MODULE_OVERWORLD_PROTO, "loadMap", moduleOverworldLoadMap + ); + scriptProtoDefineStaticFunc( + &MODULE_OVERWORLD_PROTO, "isLoaded", moduleOverworldIsLoaded + ); + scriptProtoDefineStaticFunc( + &MODULE_OVERWORLD_PROTO, "setPosition", moduleOverworldSetPosition + ); + scriptProtoDefineStaticFunc( + &MODULE_OVERWORLD_PROTO, "update", moduleOverworldUpdate + ); + scriptProtoDefineStaticFunc( + &MODULE_OVERWORLD_PROTO, "dispose", moduleOverworldJsDispose + ); +} + +void moduleOverworldDispose(void) { + scriptProtoDispose(&MODULE_OVERWORLD_PROTO); +} diff --git a/src/dusk/script/module/overworld/moduleoverworld.h b/src/dusk/script/module/overworld/moduleoverworld.h new file mode 100644 index 00000000..ab747b0d --- /dev/null +++ b/src/dusk/script/module/overworld/moduleoverworld.h @@ -0,0 +1,57 @@ +/** + * 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" + +extern scriptproto_t MODULE_OVERWORLD_PROTO; + +/** + * Overworld.loadMap(handle) — loads the map with the given handle string. + * Disposes any currently loaded map first. + * + * @param args[0] Map handle string (max 31 chars). + */ +moduleBaseFunction(moduleOverworldLoadMap); + +/** + * Overworld.isLoaded() — returns true when a map is currently loaded. + * + * @return boolean + */ +moduleBaseFunction(moduleOverworldIsLoaded); + +/** + * Overworld.setPosition(x, y, z) — slides the chunk window to the tile + * position (x, y, z). + * + * @param args[0] Tile X (integer). + * @param args[1] Tile Y (integer). + * @param args[2] Tile Z (integer). + */ +moduleBaseFunction(moduleOverworldSetPosition); + +/** + * Overworld.update() — advances the map one tick. + */ +moduleBaseFunction(moduleOverworldUpdate); + +/** + * Overworld.dispose() — unloads the current map. + */ +moduleBaseFunction(moduleOverworldJsDispose); + +/** + * Initializes the Overworld module and registers the Overworld global. + */ +void moduleOverworldInit(void); + +/** + * Disposes the Overworld module. + */ +void moduleOverworldDispose(void); diff --git a/src/dusk/script/module/save/CMakeLists.txt b/src/dusk/script/module/save/CMakeLists.txt new file mode 100644 index 00000000..9121bce6 --- /dev/null +++ b/src/dusk/script/module/save/CMakeLists.txt @@ -0,0 +1,9 @@ +# Copyright (c) 2026 Dominic Masters +# +# This software is released under the MIT License. +# https://opensource.org/licenses/MIT + +target_sources(${DUSK_LIBRARY_TARGET_NAME} + PUBLIC + modulesave.c +) diff --git a/src/dusk/script/module/save/modulesave.c b/src/dusk/script/module/save/modulesave.c new file mode 100644 index 00000000..4ab0e8ca --- /dev/null +++ b/src/dusk/script/module/save/modulesave.c @@ -0,0 +1,61 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "modulesave.h" + +scriptproto_t MODULE_SAVE_PROTO; + +moduleBaseFunction(moduleSaveExists) { + moduleBaseRequireArgs(1); + moduleSaveRequireSlot(0, "Save.exists"); + return jerry_boolean(saveExists((uint8_t)moduleBaseArgInt(0))); +} + +moduleBaseFunction(moduleSaveLoad) { + moduleBaseRequireArgs(1); + moduleSaveRequireSlot(0, "Save.load"); + errorret_t err = saveLoad((uint8_t)moduleBaseArgInt(0)); + if(errorIsNotOk(err)) return moduleBaseThrowError(err); + return jerry_undefined(); +} + +moduleBaseFunction(moduleSaveWrite) { + moduleBaseRequireArgs(1); + moduleSaveRequireSlot(0, "Save.write"); + errorret_t err = saveWrite((uint8_t)moduleBaseArgInt(0)); + if(errorIsNotOk(err)) return moduleBaseThrowError(err); + return jerry_undefined(); +} + +moduleBaseFunction(moduleSaveDelete) { + moduleBaseRequireArgs(1); + moduleSaveRequireSlot(0, "Save.delete"); + errorret_t err = saveDelete((uint8_t)moduleBaseArgInt(0)); + if(errorIsNotOk(err)) return moduleBaseThrowError(err); + return jerry_undefined(); +} + +void moduleSaveInit(void) { + scriptProtoInit(&MODULE_SAVE_PROTO, "Save", sizeof(uint8_t), NULL); + + scriptProtoDefineStaticFunc( + &MODULE_SAVE_PROTO, "exists", moduleSaveExists + ); + scriptProtoDefineStaticFunc( + &MODULE_SAVE_PROTO, "load", moduleSaveLoad + ); + scriptProtoDefineStaticFunc( + &MODULE_SAVE_PROTO, "write", moduleSaveWrite + ); + scriptProtoDefineStaticFunc( + &MODULE_SAVE_PROTO, "delete", moduleSaveDelete + ); +} + +void moduleSaveDispose(void) { + scriptProtoDispose(&MODULE_SAVE_PROTO); +} diff --git a/src/dusk/script/module/save/modulesave.h b/src/dusk/script/module/save/modulesave.h new file mode 100644 index 00000000..4d0967c6 --- /dev/null +++ b/src/dusk/script/module/save/modulesave.h @@ -0,0 +1,67 @@ +/** + * 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 "save/save.h" + +extern scriptproto_t MODULE_SAVE_PROTO; + +/** + * Validates a save-slot argument and returns a type error if invalid. + * + * @param i Argument index. + * @param ctx Context string for error messages. + */ +#define moduleSaveRequireSlot(i, ctx) do { \ + if(!jerry_value_is_number(args[(i)])) { \ + return moduleBaseThrow(ctx ": slot must be a number"); \ + } \ + const int32_t _s = moduleBaseArgInt(i); \ + if(_s < 0 || _s >= (int32_t)SAVE_FILE_COUNT_MAX) { \ + return moduleBaseThrow(ctx ": invalid save slot"); \ + } \ +} while(0) + +/** + * Save.exists(slot) — true if a save file is present for the slot. + * + * @param args[0] Save slot index (0 to SAVE_FILE_COUNT_MAX - 1). + */ +moduleBaseFunction(moduleSaveExists); + +/** + * Save.load(slot) — loads the save file for the given slot. + * + * @param args[0] Save slot index (0 to SAVE_FILE_COUNT_MAX - 1). + */ +moduleBaseFunction(moduleSaveLoad); + +/** + * Save.write(slot) — writes the save file for the given slot. + * + * @param args[0] Save slot index (0 to SAVE_FILE_COUNT_MAX - 1). + */ +moduleBaseFunction(moduleSaveWrite); + +/** + * Save.delete(slot) — deletes the save file for the given slot. + * + * @param args[0] Save slot index (0 to SAVE_FILE_COUNT_MAX - 1). + */ +moduleBaseFunction(moduleSaveDelete); + +/** + * Initializes the Save module and registers Save.exists/load/write/delete. + */ +void moduleSaveInit(void); + +/** + * Disposes the Save module. + */ +void moduleSaveDispose(void); diff --git a/src/dusk/script/module/story/CMakeLists.txt b/src/dusk/script/module/story/CMakeLists.txt new file mode 100644 index 00000000..50da105d --- /dev/null +++ b/src/dusk/script/module/story/CMakeLists.txt @@ -0,0 +1,9 @@ +# Copyright (c) 2026 Dominic Masters +# +# This software is released under the MIT License. +# https://opensource.org/licenses/MIT + +target_sources(${DUSK_LIBRARY_TARGET_NAME} + PUBLIC + modulestory.c +) diff --git a/src/dusk/script/module/story/modulestory.c b/src/dusk/script/module/story/modulestory.c new file mode 100644 index 00000000..c87d1371 --- /dev/null +++ b/src/dusk/script/module/story/modulestory.c @@ -0,0 +1,55 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "modulestory.h" +#include + +scriptproto_t MODULE_STORY_PROTO; + +moduleBaseFunction(moduleStoryGet) { + moduleBaseRequireArgs(1); + moduleStoryRequireFlag(0, "Story.get"); + const storyflag_t flag = (storyflag_t)moduleBaseArgInt(0); + return jerry_number((double)storyFlagGet(flag)); +} + +moduleBaseFunction(moduleStorySet) { + moduleBaseRequireArgs(2); + moduleStoryRequireFlag(0, "Story.set"); + moduleBaseRequireNumber(1); + const int32_t val = moduleBaseArgInt(1); + if(val < 0 || val > 255) { + return moduleBaseThrow("Story.set: value out of range (0-255)"); + } + storyFlagSet( + (storyflag_t)moduleBaseArgInt(0), + (storyflagvalue_t)val + ); + return jerry_undefined(); +} + +void moduleStoryInit(void) { + scriptProtoInit(&MODULE_STORY_PROTO, "Story", sizeof(uint8_t), NULL); + + scriptProtoDefineStaticFunc( + &MODULE_STORY_PROTO, "get", moduleStoryGet + ); + scriptProtoDefineStaticFunc( + &MODULE_STORY_PROTO, "set", moduleStorySet + ); + + jerry_value_t result = jerry_eval( + (const jerry_char_t *)STORY_FLAG_SCRIPT, + strlen(STORY_FLAG_SCRIPT), + JERRY_PARSE_NO_OPTS + ); + jerry_value_free(result); +} + +void moduleStoryDispose(void) { + scriptProtoDispose(&MODULE_STORY_PROTO); +} diff --git a/src/dusk/script/module/story/modulestory.h b/src/dusk/script/module/story/modulestory.h new file mode 100644 index 00000000..0bcac442 --- /dev/null +++ b/src/dusk/script/module/story/modulestory.h @@ -0,0 +1,55 @@ +/** + * 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" + +extern scriptproto_t MODULE_STORY_PROTO; + +/** + * Validates a storyflag_t argument and returns a type error if invalid. + * + * @param i Argument index. + * @param ctx Context string for error messages. + */ +#define moduleStoryRequireFlag(i, ctx) do { \ + if(!jerry_value_is_number(args[(i)])) { \ + return moduleBaseThrow(ctx ": flagId must be a number"); \ + } \ + const storyflag_t _f = (storyflag_t)moduleBaseArgInt(i); \ + if(_f <= STORY_FLAG_NULL || _f >= STORY_FLAG_COUNT) { \ + return moduleBaseThrow(ctx ": invalid story flag ID"); \ + } \ +} while(0) + +/** + * Story.get(flagId) — returns the current uint8 value of a story flag. + * + * @param args[0] Story flag constant (STORY_FLAG_*). + */ +moduleBaseFunction(moduleStoryGet); + +/** + * Story.set(flagId, value) — sets a story flag to a uint8 value. + * + * @param args[0] Story flag constant (STORY_FLAG_*). + * @param args[1] Value 0–255. + */ +moduleBaseFunction(moduleStorySet); + +/** + * Initializes the Story module, registers Story.get/set, and evaluates + * STORY_FLAG_SCRIPT to inject STORY_FLAG_* constants as globals. + */ +void moduleStoryInit(void); + +/** + * Disposes the Story module. + */ +void moduleStoryDispose(void); diff --git a/src/dusk/script/module/ui/CMakeLists.txt b/src/dusk/script/module/ui/CMakeLists.txt new file mode 100644 index 00000000..a22320cb --- /dev/null +++ b/src/dusk/script/module/ui/CMakeLists.txt @@ -0,0 +1,9 @@ +# Copyright (c) 2026 Dominic Masters +# +# This software is released under the MIT License. +# https://opensource.org/licenses/MIT + +target_sources(${DUSK_LIBRARY_TARGET_NAME} + PUBLIC + moduletextbox.c +) diff --git a/src/dusk/script/module/ui/moduletextbox.c b/src/dusk/script/module/ui/moduletextbox.c new file mode 100644 index 00000000..459dd862 --- /dev/null +++ b/src/dusk/script/module/ui/moduletextbox.c @@ -0,0 +1,110 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "moduletextbox.h" +#include "ui/uitextbox.h" +#include "input/inputaction.h" + +scriptproto_t MODULE_TEXTBOX_PROTO; + +moduleBaseFunction(moduleTextboxSetText) { + moduleBaseRequireArgs(1); + moduleBaseRequireString(0); + char_t buf[UI_TEXTBOX_TEXT_MAX]; + moduleBaseToString(args[0], buf, sizeof(buf)); + uiTextboxSetText(buf); + return jerry_undefined(); +} + +moduleBaseFunction(moduleTextboxNextPage) { + uiTextboxNextPage(); + return jerry_undefined(); +} + +moduleBaseFunction(moduleTextboxUpdate) { + errorret_t err = uiTextboxUpdate(); + if(errorIsNotOk(err)) return moduleBaseThrowError(err); + return jerry_undefined(); +} + +moduleBaseFunction(moduleTextboxDraw) { + errorret_t err = uiTextboxDraw(); + if(errorIsNotOk(err)) return moduleBaseThrowError(err); + return jerry_undefined(); +} + +moduleBaseFunction(moduleTextboxSetAdvanceAction) { + moduleBaseRequireArgs(1); + moduleBaseRequireNumber(0); + const inputaction_t action = (inputaction_t)moduleBaseArgInt(0); + if(action <= INPUT_ACTION_NULL || action >= INPUT_ACTION_COUNT) { + return moduleBaseThrow( + "UITextbox.setAdvanceAction: invalid action" + ); + } + UI_TEXTBOX.advanceAction = action; + return jerry_undefined(); +} + +moduleBaseFunction(moduleTextboxGetIsPageComplete) { + return jerry_boolean(uiTextboxPageIsComplete()); +} + +moduleBaseFunction(moduleTextboxGetHasNextPage) { + return jerry_boolean(uiTextboxHasNextPage()); +} + +moduleBaseFunction(moduleTextboxGetCurrentPage) { + return jerry_number((double)UI_TEXTBOX.currentPage); +} + +moduleBaseFunction(moduleTextboxGetPageCount) { + return jerry_number((double)UI_TEXTBOX.pageCount); +} + +void moduleTextboxInit(void) { + scriptProtoInit( + &MODULE_TEXTBOX_PROTO, "UITextbox", sizeof(uint8_t), NULL + ); + + scriptProtoDefineStaticFunc( + &MODULE_TEXTBOX_PROTO, "setText", moduleTextboxSetText + ); + scriptProtoDefineStaticFunc( + &MODULE_TEXTBOX_PROTO, "nextPage", moduleTextboxNextPage + ); + scriptProtoDefineStaticFunc( + &MODULE_TEXTBOX_PROTO, "update", moduleTextboxUpdate + ); + scriptProtoDefineStaticFunc( + &MODULE_TEXTBOX_PROTO, "draw", moduleTextboxDraw + ); + scriptProtoDefineStaticFunc( + &MODULE_TEXTBOX_PROTO, "setAdvanceAction", + moduleTextboxSetAdvanceAction + ); + scriptProtoDefineStaticProp( + &MODULE_TEXTBOX_PROTO, "isPageComplete", + moduleTextboxGetIsPageComplete, NULL + ); + scriptProtoDefineStaticProp( + &MODULE_TEXTBOX_PROTO, "hasNextPage", + moduleTextboxGetHasNextPage, NULL + ); + scriptProtoDefineStaticProp( + &MODULE_TEXTBOX_PROTO, "currentPage", + moduleTextboxGetCurrentPage, NULL + ); + scriptProtoDefineStaticProp( + &MODULE_TEXTBOX_PROTO, "pageCount", + moduleTextboxGetPageCount, NULL + ); +} + +void moduleTextboxDispose(void) { + scriptProtoDispose(&MODULE_TEXTBOX_PROTO); +} diff --git a/src/dusk/script/module/ui/moduletextbox.h b/src/dusk/script/module/ui/moduletextbox.h new file mode 100644 index 00000000..59d28a10 --- /dev/null +++ b/src/dusk/script/module/ui/moduletextbox.h @@ -0,0 +1,65 @@ +/** + * 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" + +extern scriptproto_t MODULE_TEXTBOX_PROTO; + +/** + * UITextbox.setText(text) — sets the textbox content and rebuilds layout. + * + * @param args[0] Null-terminated text string. + */ +moduleBaseFunction(moduleTextboxSetText); + +/** + * UITextbox.nextPage() — advances to the next page; no-op on last page. + */ +moduleBaseFunction(moduleTextboxNextPage); + +/** + * UITextbox.update() — advances the typewriter scroll by one tick. + */ +moduleBaseFunction(moduleTextboxUpdate); + +/** + * UITextbox.draw() — draws the frame and visible text for this frame. + */ +moduleBaseFunction(moduleTextboxDraw); + +/** + * UITextbox.setAdvanceAction(action) — sets the input action that + * advances the textbox when held. + * + * @param args[0] Input action constant (INPUT_ACTION_*). + */ +moduleBaseFunction(moduleTextboxSetAdvanceAction); + +/** @return true when the typewriter has fully revealed the current page. */ +moduleBaseFunction(moduleTextboxGetIsPageComplete); + +/** @return true when at least one more page follows the current one. */ +moduleBaseFunction(moduleTextboxGetHasNextPage); + +/** @return Zero-based index of the current page. */ +moduleBaseFunction(moduleTextboxGetCurrentPage); + +/** @return Total number of pages for the current text. */ +moduleBaseFunction(moduleTextboxGetPageCount); + +/** + * Initializes the UITextbox module and registers all methods and + * properties on the UITextbox global. + */ +void moduleTextboxInit(void); + +/** + * Disposes the UITextbox module. + */ +void moduleTextboxDispose(void); diff --git a/types/animation/animation.d.ts b/types/animation/animation.d.ts new file mode 100644 index 00000000..6edd0aad --- /dev/null +++ b/types/animation/animation.d.ts @@ -0,0 +1,61 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +/** A single keyframe descriptor passed to the Animation constructor. */ +interface AnimationKeyframe { + /** Time offset in seconds. */ + time: number; + /** Value at this keyframe. */ + value: number; + /** Easing type (EASING_* constant). Defaults to EASING_LINEAR. */ + easing?: number; +} + +/** Keyframe-based float animation. */ +interface AnimationInstance { + /** + * Interpolates the animation at the given time. + * @param time Time in seconds. + * @returns Interpolated value. + */ + getValue(time: number): number; +} + +/** Constructs a new Animation from an array of keyframe descriptors. */ +declare var Animation: { + new (keyframes: AnimationKeyframe[]): AnimationInstance; +}; + +/** Easing function utilities. */ +interface EasingNamespace { + /** + * Applies the given easing function to normalized time t. + * @param type An EASING_* constant. + * @param t Normalized time in [0, 1]. + * @returns Eased value in [0, 1]. + */ + apply(type: number, t: number): number; +} + +declare var Easing: EasingNamespace; + +declare var EASING_LINEAR: number; +declare var EASING_IN_SINE: number; +declare var EASING_OUT_SINE: number; +declare var EASING_IN_OUT_SINE: number; +declare var EASING_IN_QUAD: number; +declare var EASING_OUT_QUAD: number; +declare var EASING_IN_OUT_QUAD: number; +declare var EASING_IN_CUBIC: number; +declare var EASING_OUT_CUBIC: number; +declare var EASING_IN_OUT_CUBIC: number; +declare var EASING_IN_QUART: number; +declare var EASING_OUT_QUART: number; +declare var EASING_IN_OUT_QUART: number; +declare var EASING_IN_BACK: number; +declare var EASING_OUT_BACK: number; +declare var EASING_IN_OUT_BACK: number; diff --git a/types/index.d.ts b/types/index.d.ts index 1c40d176..a5bd131f 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -31,6 +31,28 @@ /// /// +// animation +/// + +// overworld +/// + +// item system +/// +/// + +// story system +/// + +// locale +/// + +// save +/// + +// ui +/// + // engine systems /// /// diff --git a/types/item/backpack.d.ts b/types/item/backpack.d.ts new file mode 100644 index 00000000..d4eb9fd1 --- /dev/null +++ b/types/item/backpack.d.ts @@ -0,0 +1,63 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +/** The player's item backpack (a fixed 20-slot inventory). */ +interface BackpackNamespace { + /** `true` when all 20 storage slots are occupied by distinct items. */ + readonly isFull: boolean; + + /** + * Returns the quantity of `itemId` in the backpack (0 if absent). + * @param itemId - An `ITEM_ID_*` constant. + */ + getCount(itemId: ItemId): number; + + /** + * Returns `true` if `itemId` is present with quantity greater than 0. + * @param itemId - An `ITEM_ID_*` constant. + */ + has(itemId: ItemId): boolean; + + /** + * Returns `true` if the stack for `itemId` is at maximum quantity (255). + * @param itemId - An `ITEM_ID_*` constant. + */ + isItemFull(itemId: ItemId): boolean; + + /** + * Sets the quantity of `itemId`; passing 0 removes the stack entirely. + * @param itemId - An `ITEM_ID_*` constant. + * @param quantity - New quantity, 0–255. + */ + set(itemId: ItemId, quantity: number): void; + + /** + * Adds `quantity` units of `itemId` to the backpack. + * @param itemId - An `ITEM_ID_*` constant. + * @param quantity - Amount to add, 1–255. + */ + add(itemId: ItemId, quantity: number): void; + + /** + * Removes `itemId` entirely from the backpack. + * @param itemId - An `ITEM_ID_*` constant. + */ + remove(itemId: ItemId): void; + + /** + * Sorts the backpack contents. + * @param sortBy - `INVENTORY_SORT_BY_ID` or `INVENTORY_SORT_BY_TYPE`. + * @param reverse - Optional; pass `true` to reverse the sort order. + */ + sort(sortBy: number, reverse?: boolean): void; +} + +declare var Backpack: BackpackNamespace; + +// Inventory sort constants — injected as globals by the engine at startup. +declare var INVENTORY_SORT_BY_ID: number; +declare var INVENTORY_SORT_BY_TYPE: number; diff --git a/types/item/item.d.ts b/types/item/item.d.ts new file mode 100644 index 00000000..00566669 --- /dev/null +++ b/types/item/item.d.ts @@ -0,0 +1,39 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +/** Opaque type alias for item identifier constants. */ +type ItemId = number; + +/** Opaque type alias for item type constants. */ +type ItemType = number; + +/** Item data lookup. */ +interface ItemNamespace { + /** + * Returns the string name for the given item ID. + * @param itemId - An `ITEM_ID_*` constant. + */ + getName(itemId: ItemId): string; + + /** + * Returns the type constant for the given item ID. + * @param itemId - An `ITEM_ID_*` constant. + * @returns An `ITEM_TYPE_*` constant. + */ + getType(itemId: ItemId): ItemType; +} + +declare var Item: ItemNamespace; + +// Item ID constants — injected as globals by the engine at startup. +declare var ITEM_ID_POTION: ItemId; +declare var ITEM_ID_POTATO: ItemId; +declare var ITEM_ID_APPLE: ItemId; + +// Item type constants — injected as globals by the engine at startup. +declare var ITEM_TYPE_MEDICINE: ItemType; +declare var ITEM_TYPE_FOOD: ItemType; diff --git a/types/locale/locale.d.ts b/types/locale/locale.d.ts new file mode 100644 index 00000000..61000da4 --- /dev/null +++ b/types/locale/locale.d.ts @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +/** Locale string lookup and language switching. */ +interface LocaleNamespace { + /** + * Returns the translated string for the given message ID. + * + * @param id - PO message ID, e.g. `"ITEM_POTION_NAME"`. + * @param pluralCount - Optional count used to select the plural form. + * Defaults to 1 (singular). + * @param args - Optional substitution values (`%s`, `%d`, `%f`). + */ + getText( + id: string, + pluralCount?: number, + ...args: Array + ): string; + + /** + * Switches the active locale. + * @param name - Locale name string, e.g. `"en-US"`. + */ + setLocale(name: string): void; +} + +declare var Locale: LocaleNamespace; diff --git a/types/overworld/overworld.d.ts b/types/overworld/overworld.d.ts new file mode 100644 index 00000000..c3070bb2 --- /dev/null +++ b/types/overworld/overworld.d.ts @@ -0,0 +1,40 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +/** Map loading and world-position management. */ +interface OverworldNamespace { + /** + * Loads the map with the given handle, disposing any currently loaded map. + * @param handle Map handle string (max 31 chars). + */ + loadMap(handle: string): void; + + /** + * Returns `true` when a map is currently loaded. + */ + isLoaded(): boolean; + + /** + * Slides the loaded chunk window to the given tile-space position. + * @param x Tile X. + * @param y Tile Y. + * @param z Tile Z. + */ + setPosition(x: number, y: number, z: number): void; + + /** + * Advances the map one tick. Call once per frame. + */ + update(): void; + + /** + * Unloads the current map. + */ + dispose(): void; +} + +declare var Overworld: OverworldNamespace; diff --git a/types/require.d.ts b/types/require.d.ts index d61878f6..26632987 100644 --- a/types/require.d.ts +++ b/types/require.d.ts @@ -11,9 +11,8 @@ * Modules are cached after their first load. Subsequent calls with the same * resolved path return the cached exports without re-executing the file. * - * Path rules: `"./foo"` / `"../foo"` resolve relative to the calling script's - * directory; any other string resolves from the archive root. `.js` is - * appended automatically when missing. + * Path rules: the string is passed verbatim to the asset loader and resolved + * from the archive root. The `.js` extension must be included explicitly. * * @example * const NPC = require('./entities/NPC'); diff --git a/types/save/save.d.ts b/types/save/save.d.ts new file mode 100644 index 00000000..cfcd14f4 --- /dev/null +++ b/types/save/save.d.ts @@ -0,0 +1,38 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +/** Slot-based save file management. */ +interface SaveNamespace { + /** + * Returns `true` if a save file is present for the given slot. + * @param slot - Save slot index (0–2). + */ + exists(slot: number): boolean; + + /** + * Loads the save file for the given slot from persistent storage. + * Throws if the load fails. + * @param slot - Save slot index (0–2). + */ + load(slot: number): void; + + /** + * Writes the save file for the given slot to persistent storage. + * Throws if the write fails. + * @param slot - Save slot index (0–2). + */ + write(slot: number): void; + + /** + * Deletes the save file for the given slot from persistent storage. + * Throws if the delete fails. + * @param slot - Save slot index (0–2). + */ + delete(slot: number): void; +} + +declare var Save: SaveNamespace; diff --git a/types/story/story.d.ts b/types/story/story.d.ts new file mode 100644 index 00000000..9b555afe --- /dev/null +++ b/types/story/story.d.ts @@ -0,0 +1,30 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +/** Opaque type alias for story flag identifier constants. */ +type StoryFlag = number; + +/** Story flag read/write access. */ +interface StoryNamespace { + /** + * Returns the current value of a story flag (0–255). + * @param flagId - A `STORY_FLAG_*` constant. + */ + get(flagId: StoryFlag): number; + + /** + * Sets a story flag to a new value. + * @param flagId - A `STORY_FLAG_*` constant. + * @param value - New value, 0–255. + */ + set(flagId: StoryFlag, value: number): void; +} + +declare var Story: StoryNamespace; + +// Story flag constants — injected as globals by the engine at startup. +declare var STORY_FLAG_TEST: StoryFlag; diff --git a/types/ui/textbox.d.ts b/types/ui/textbox.d.ts new file mode 100644 index 00000000..1d255be0 --- /dev/null +++ b/types/ui/textbox.d.ts @@ -0,0 +1,50 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +/** VN-style typewriter textbox singleton. */ +interface UITextboxNamespace { + /** `true` when the typewriter has fully revealed the current page. */ + readonly isPageComplete: boolean; + /** `true` when at least one more page follows the current one. */ + readonly hasNextPage: boolean; + /** Zero-based index of the current page. */ + readonly currentPage: number; + /** Total number of pages for the current text. */ + readonly pageCount: number; + + /** + * Sets the textbox content and rebuilds the word-wrap layout. + * Resets to page 0 and scroll 0. + * @param text - Text to display (max 1024 chars). + */ + setText(text: string): void; + + /** + * Advances to the next page; no-op on the last page. + */ + nextPage(): void; + + /** + * Advances the typewriter scroll by one tick. + * Call once per frame before `draw()`. + */ + update(): void; + + /** + * Draws the frame and currently visible text. + * Call once per frame after `update()`. + */ + draw(): void; + + /** + * Sets the input action that auto-advances the textbox when held. + * @param action - An `INPUT_ACTION_*` constant. + */ + setAdvanceAction(action: InputAction): void; +} + +declare var UITextbox: UITextboxNamespace;