we ball I guess

This commit is contained in:
2026-06-07 19:51:54 -05:00
parent f8c9d33df2
commit 51388c90d5
42 changed files with 2233 additions and 30 deletions
+417
View File
@@ -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<platform>/` in the matching subsystem folder.
- Gate any core call-site with the appropriate `#ifdef DUSK_<PLATFORM>`
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/<category>/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/<category>/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/<category>/mymod.d.ts` and add a
`/// <reference path="..." />` 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`.
+51
View File
@@ -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;
+50 -25
View File
@@ -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();
};
+1
View File
@@ -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);
+8 -1
View File
@@ -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)
add_subdirectory(story)
add_subdirectory(system)
add_subdirectory(ui)
@@ -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
)
@@ -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);
}
@@ -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);
@@ -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);
}
@@ -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);
@@ -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();
}
@@ -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
)
@@ -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);
}
@@ -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 0255.
*/
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 (1255).
*/
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);
+47
View File
@@ -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 <string.h>
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);
}
+55
View File
@@ -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);
@@ -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
)
@@ -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 <math.h>
#include <string.h>
#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);
}
@@ -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);
+27
View File
@@ -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();
@@ -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
)
@@ -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);
}
@@ -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);
@@ -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
)
+61
View File
@@ -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);
}
+67
View File
@@ -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);
@@ -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
)
@@ -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 <string.h>
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);
}
@@ -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 0255.
*/
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);
+9
View File
@@ -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
)
+110
View File
@@ -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);
}
+65
View File
@@ -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);
+61
View File
@@ -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;
+22
View File
@@ -31,6 +31,28 @@
/// <reference path="./asset/assetbatch.d.ts" />
/// <reference path="./asset/asset.d.ts" />
// animation
/// <reference path="./animation/animation.d.ts" />
// overworld
/// <reference path="./overworld/overworld.d.ts" />
// item system
/// <reference path="./item/item.d.ts" />
/// <reference path="./item/backpack.d.ts" />
// story system
/// <reference path="./story/story.d.ts" />
// locale
/// <reference path="./locale/locale.d.ts" />
// save
/// <reference path="./save/save.d.ts" />
// ui
/// <reference path="./ui/textbox.d.ts" />
// engine systems
/// <reference path="./console/console.d.ts" />
/// <reference path="./engine/engine.d.ts" />
+63
View File
@@ -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, 0255.
*/
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, 1255.
*/
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;
+39
View File
@@ -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;
+31
View File
@@ -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 | number>
): string;
/**
* Switches the active locale.
* @param name - Locale name string, e.g. `"en-US"`.
*/
setLocale(name: string): void;
}
declare var Locale: LocaleNamespace;
+40
View File
@@ -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;
+2 -3
View File
@@ -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');
+38
View File
@@ -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 (02).
*/
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 (02).
*/
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 (02).
*/
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 (02).
*/
delete(slot: number): void;
}
declare var Save: SaveNamespace;
+30
View File
@@ -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 (0255).
* @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, 0255.
*/
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;
+50
View File
@@ -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;