19 Commits

Author SHA1 Message Date
YourWishes d8fe0f6923 textbox 2026-05-06 22:42:28 -05:00
YourWishes 581dbc2b3c Update linux docker 2026-05-06 20:31:50 -05:00
YourWishes 7301d2ad76 luce bree 2026-05-06 20:24:16 -05:00
YourWishes 3232a14d1d Consistent build 2026-05-06 14:40:06 -05:00
YourWishes 84c1f88d42 Fix PSP blending issues 2026-05-06 11:17:34 -05:00
YourWishes 3695b10e4b Easing test 2026-05-05 22:24:25 -05:00
YourWishes 6da02b25fa Testing cutscenes 2026-05-05 22:10:47 -05:00
YourWishes 3bc544fba1 Re-enable build pulling fetched modules 2026-05-05 19:29:48 -05:00
YourWishes 368d370f49 Cleanup modules 2026-05-05 19:29:29 -05:00
YourWishes bb29c0edef Working on some script modules 2026-05-05 16:22:04 -05:00
YourWishes 6edcf75a0c add display state 2026-05-04 22:16:30 -05:00
YourWishes 31cc186424 Fixed small compile bugs 2026-05-04 08:39:47 -05:00
YourWishes 0e94c1fa6d Fixed a bunch of messy over 80 char lines 2026-05-04 08:29:43 -05:00
YourWishes 6d9e2dd3e1 UI first pass 2026-05-03 21:52:12 -05:00
YourWishes 4a4adeb3c8 Finally fixed linux asset weirdness 2026-05-02 15:18:49 -05:00
YourWishes ff77f8cfa0 Fix dolphin rendering 2026-05-01 23:11:59 -05:00
YourWishes 36db89c36e Nuke the old input system, use the new UI system 2026-05-01 15:21:46 -05:00
YourWishes a9948142ad Fix linux warning 2026-05-01 14:00:24 -05:00
YourWishes 8d05510584 Fix linux building 2026-05-01 13:58:05 -05:00
128 changed files with 3611 additions and 1416 deletions
+1 -1
View File
@@ -9,8 +9,8 @@ cmake_minimum_required(VERSION 3.13)
set(CMAKE_C_STANDARD 11) set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON) set(CMAKE_C_STANDARD_REQUIRED ON)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules)
# [cmake] This is allowed only when policy CMP0079 is set to NEW.
cmake_policy(SET CMP0079 NEW) cmake_policy(SET CMP0079 NEW)
# set(FETCHCONTENT_UPDATES_DISCONNECTED ON)
option(DUSK_BUILD_TESTS "Enable tests" OFF) option(DUSK_BUILD_TESTS "Enable tests" OFF)
-13
View File
@@ -1,13 +0,0 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "dusk.h"
typedef struct {
void *nothing;
} inventory_t;
+35
View File
@@ -0,0 +1,35 @@
var SPEED = 3.0;
var DURATION = 2.0;
function MoveCubeCutscene(params) {
Cutscene.call(this);
this.cube = params.cube;
var startX = this.cube.position.position.x;
this.animLeft = new Animation([
{ time: 0.0, value: startX, easing: Easing.outQuad },
{ time: DURATION, value: startX - SPEED * DURATION }
]);
this.animRight = new Animation([
{ time: 0.0, value: startX - SPEED * DURATION, easing: Easing.inOutQuad },
{ time: DURATION, value: startX }
]);
}
MoveCubeCutscene.prototype = Object.create(Cutscene.prototype);
MoveCubeCutscene.prototype.constructor = MoveCubeCutscene;
MoveCubeCutscene.prototype.update = function() {
if(!this.animLeft.complete) {
this.cube.position.position.x = this.animLeft.update(TIME.delta);
} else {
this.cube.position.position.x = this.animRight.update(TIME.delta);
if(this.animRight.complete) {
Cutscene.finish();
}
}
};
module = MoveCubeCutscene;
+4 -1
View File
@@ -5,6 +5,9 @@ function CubeEntity() {
this.add(MESH); this.add(MESH);
this.add(MATERIAL); this.add(MATERIAL);
this.cubeMesh = Mesh.createCube();
this.mesh.mesh = this.cubeMesh;
} }
CubeEntity.prototype = Object.create(OverworldEntity.prototype); CubeEntity.prototype = Object.create(OverworldEntity.prototype);
@@ -16,7 +19,7 @@ CubeEntity.prototype.update = function() {
var move = Input.axis2D(INPUT_ACTION_LEFT, INPUT_ACTION_RIGHT, INPUT_ACTION_UP, INPUT_ACTION_DOWN); var move = Input.axis2D(INPUT_ACTION_LEFT, INPUT_ACTION_RIGHT, INPUT_ACTION_UP, INPUT_ACTION_DOWN);
this.position.position.x += move.x * speed * TIME.delta; this.position.position.x += move.x * speed * TIME.delta;
this.position.position.z += move.y * speed * TIME.delta; this.position.position.z += move.y * speed * TIME.delta;
this.material.setColor(Color.rainbow()); this.material.color = Color.rainbow();
}; };
module = CubeEntity; module = CubeEntity;
+20 -2
View File
@@ -1,4 +1,5 @@
var CubeEntity = include('entities/CubeEntity.js'); var CubeEntity = include('entities/CubeEntity.js');
var MoveCubeCutscene = include('cutscenes/MoveCubeCutscene.js');
function CubeScene() { function CubeScene() {
this.cam = new Entity(); this.cam = new Entity();
@@ -13,17 +14,34 @@ function CubeScene() {
this.spriteEnt = new Entity(); this.spriteEnt = new Entity();
this.spriteEnt.add(POSITION); this.spriteEnt.add(POSITION);
this.spriteEnt.position.position = new Vec3(16, 16, 0); this.spriteEnt.position.position = new Vec3(16, 16, 0);
// this.spriteEnt.sprite.setTexture('ui/minogram.png');
this.inputEnabled = false;
var scene = this;
Cutscene.play(new MoveCubeCutscene({ cube: this.cube })).then(function() {
scene.inputEnabled = true;
});
Textbox.setText(
"Hello! This is a visual novel textbox. It automatically " +
"wraps long lines and splits into pages when the content " +
"is too tall to fit. Press advance to continue...\t" +
"This is a second paragraph on a new page."
);
} }
CubeScene.prototype = Object.create(Scene.prototype); CubeScene.prototype = Object.create(Scene.prototype);
CubeScene.prototype.constructor = CubeScene; CubeScene.prototype.constructor = CubeScene;
CubeScene.prototype.update = function() { CubeScene.prototype.update = function() {
this.cube.update(); if(this.inputEnabled) {
this.cube.update();
}
}; };
CubeScene.prototype.dispose = function() { CubeScene.prototype.dispose = function() {
Cutscene.stop();
this.cam.dispose(); this.cam.dispose();
this.cube.dispose(); this.cube.dispose();
this.spriteEnt.dispose(); this.spriteEnt.dispose();
+6
View File
@@ -0,0 +1,6 @@
module = {
render() {
Text.draw(0, 0, "Hello World");
SpriteBatch.flush();
}
};
+1 -1
View File
@@ -14,8 +14,8 @@ RUN apt-get install -y \
python3-dotenv \ python3-dotenv \
python3-pyqt5 \ python3-pyqt5 \
python3-opengl \ python3-opengl \
liblua5.3-dev \
xz-utils \ xz-utils \
liblzma-dev \
libbz2-dev \ libbz2-dev \
zlib1g-dev \ zlib1g-dev \
libzip-dev \ libzip-dev \
-1
View File
@@ -4,7 +4,6 @@
# https://opensource.org/licenses/MIT # https://opensource.org/licenses/MIT
add_subdirectory(dusk) add_subdirectory(dusk)
add_subdirectory(duskrpg)
if(DUSK_TARGET_SYSTEM STREQUAL "linux" OR DUSK_TARGET_SYSTEM STREQUAL "knulli") if(DUSK_TARGET_SYSTEM STREQUAL "linux" OR DUSK_TARGET_SYSTEM STREQUAL "knulli")
add_subdirectory(dusklinux) add_subdirectory(dusklinux)
+5 -2
View File
@@ -34,7 +34,7 @@ endif()
if(NOT jerryscript_FOUND) if(NOT jerryscript_FOUND)
find_package(jerryscript REQUIRED) find_package(jerryscript REQUIRED)
target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PRIVATE target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
jerryscript::core jerryscript::core
jerryscript::ext jerryscript::ext
jerryscript::port jerryscript::port
@@ -54,15 +54,18 @@ target_sources(${DUSK_BINARY_TARGET_NAME}
) )
# Subdirs # Subdirs
add_subdirectory(animation)
add_subdirectory(assert) add_subdirectory(assert)
add_subdirectory(asset) add_subdirectory(asset)
add_subdirectory(cutscene)
add_subdirectory(item)
add_subdirectory(story)
add_subdirectory(console) add_subdirectory(console)
add_subdirectory(display) add_subdirectory(display)
add_subdirectory(log) add_subdirectory(log)
add_subdirectory(engine) add_subdirectory(engine)
add_subdirectory(entity) add_subdirectory(entity)
add_subdirectory(error) add_subdirectory(error)
add_subdirectory(event)
add_subdirectory(input) add_subdirectory(input)
add_subdirectory(locale) add_subdirectory(locale)
add_subdirectory(physics) add_subdirectory(physics)
@@ -1,10 +1,10 @@
# Copyright (c) 2025 Dominic Masters # Copyright (c) 2026 Dominic Masters
# #
# This software is released under the MIT License. # This software is released under the MIT License.
# https://opensource.org/licenses/MIT # https://opensource.org/licenses/MIT
# Sources
target_sources(${DUSK_LIBRARY_TARGET_NAME} target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC PUBLIC
game.c easing.c
) animation.c
)
+75
View File
@@ -0,0 +1,75 @@
// Copyright (c) 2026 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
#include "animation.h"
#include "assert/assert.h"
#include "util/memory.h"
#include <math.h>
void animationInit(animation_t *anim) {
memoryZero(anim, sizeof(animation_t));
}
void animationAddKeyframe(
animation_t *anim,
float_t time,
float_t value,
easingtype_t easing
) {
assertTrue(
anim->keyframeCount < ANIMATION_KEYFRAME_COUNT_MAX,
"Keyframe count exceeds ANIMATION_KEYFRAME_COUNT_MAX"
);
uint8_t i = anim->keyframeCount++;
anim->keyframes[i].time = time;
anim->keyframes[i].value = value;
anim->keyframes[i].easing = easing;
if(time > anim->duration) anim->duration = time;
}
float_t animationGetValue(const animation_t *anim) {
if(anim->keyframeCount == 0) return 0.0f;
uint8_t last = anim->keyframeCount - 1;
if(anim->keyframeCount == 1) return anim->keyframes[0].value;
if(anim->time <= anim->keyframes[0].time) return anim->keyframes[0].value;
if(anim->time >= anim->keyframes[last].time) {
return anim->keyframes[last].value;
}
for(uint8_t i = 0; i < last; i++) {
const keyframe_t *a = &anim->keyframes[i];
const keyframe_t *b = &anim->keyframes[i + 1];
if(anim->time < a->time || anim->time >= b->time) continue;
float_t t = (anim->time - a->time) / (b->time - a->time);
t = easingApply(a->easing, t);
return a->value + (b->value - a->value) * t;
}
return anim->keyframes[last].value;
}
float_t animationUpdate(animation_t *anim, float_t delta) {
if(anim->complete && !anim->loop) return animationGetValue(anim);
anim->time += delta;
if(anim->duration > 0.0f && anim->time >= anim->duration) {
if(anim->loop) {
anim->time = fmodf(anim->time, anim->duration);
} else {
anim->time = anim->duration;
anim->complete = true;
}
}
return animationGetValue(anim);
}
void animationReset(animation_t *anim) {
anim->time = 0.0f;
anim->complete = false;
}
+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
#pragma once
#include "keyframe.h"
#include "error/error.h"
typedef struct {
keyframe_t keyframes[ANIMATION_KEYFRAME_COUNT_MAX];
uint8_t keyframeCount;
float_t time;
float_t duration;
bool_t loop;
bool_t complete;
} animation_t;
/**
* Zeroes an animation struct.
*
* @param anim The animation to initialize.
*/
void animationInit(animation_t *anim);
/**
* Appends a keyframe. Keyframes must be added in ascending time order.
*
* @param anim The animation to modify.
* @param time Time in seconds for this keyframe.
* @param value Value at this keyframe.
* @param easing Easing applied to the segment after this keyframe.
*/
void animationAddKeyframe(
animation_t *anim,
float_t time,
float_t value,
easingtype_t easing
);
/**
* Advances the animation by delta seconds and returns the current value.
*
* @param anim The animation to update.
* @param delta Seconds elapsed since last update.
* @return Interpolated value at the new time.
*/
float_t animationUpdate(animation_t *anim, float_t delta);
/**
* Returns the interpolated value at the current time without advancing.
*
* @param anim The animation to read.
* @return Interpolated value.
*/
float_t animationGetValue(const animation_t *anim);
/**
* Resets the animation to the beginning.
*
* @param anim The animation to reset.
*/
void animationReset(animation_t *anim);
+106
View File
@@ -0,0 +1,106 @@
// Copyright (c) 2026 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
#include "easing.h"
#include "assert/assert.h"
#include <math.h>
#define EASING_PI 3.14159265358979323846f
#define EASING_C1 1.70158f
#define EASING_C2 (EASING_C1 * 1.525f)
#define EASING_C3 (EASING_C1 + 1.0f)
float_t easingLinear(float_t t) {
return t;
}
float_t easingInSine(float_t t) {
return 1.0f - cosf(t * EASING_PI * 0.5f);
}
float_t easingOutSine(float_t t) {
return sinf(t * EASING_PI * 0.5f);
}
float_t easingInOutSine(float_t t) {
return -(cosf(EASING_PI * t) - 1.0f) * 0.5f;
}
float_t easingInQuad(float_t t) {
return t * t;
}
float_t easingOutQuad(float_t t) {
float_t u = 1.0f - t;
return 1.0f - u * u;
}
float_t easingInOutQuad(float_t t) {
if(t < 0.5f) return 2.0f * t * t;
float_t u = -2.0f * t + 2.0f;
return 1.0f - u * u * 0.5f;
}
float_t easingInCubic(float_t t) {
return t * t * t;
}
float_t easingOutCubic(float_t t) {
float_t u = 1.0f - t;
return 1.0f - u * u * u;
}
float_t easingInOutCubic(float_t t) {
if(t < 0.5f) return 4.0f * t * t * t;
float_t u = -2.0f * t + 2.0f;
return 1.0f - u * u * u * 0.5f;
}
float_t easingInQuart(float_t t) {
return t * t * t * t;
}
float_t easingOutQuart(float_t t) {
float_t u = 1.0f - t;
return 1.0f - u * u * u * u;
}
float_t easingInOutQuart(float_t t) {
if(t < 0.5f) return 8.0f * t * t * t * t;
float_t u = -2.0f * t + 2.0f;
return 1.0f - u * u * u * u * 0.5f;
}
float_t easingInBack(float_t t) {
return EASING_C3 * t * t * t - EASING_C1 * t * t;
}
float_t easingOutBack(float_t t) {
float_t u = t - 1.0f;
return 1.0f + EASING_C3 * u * u * u + EASING_C1 * u * u;
}
float_t easingInOutBack(float_t t) {
if(t < 0.5f) {
float_t u = 2.0f * t;
return u * u * ((EASING_C2 + 1.0f) * u - EASING_C2) * 0.5f;
}
float_t u = 2.0f * t - 2.0f;
return (u * u * ((EASING_C2 + 1.0f) * u + EASING_C2) + 2.0f) * 0.5f;
}
const easingfn_t EASING_FUNCTIONS[EASING_COUNT] = {
easingLinear,
easingInSine, easingOutSine, easingInOutSine,
easingInQuad, easingOutQuad, easingInOutQuad,
easingInCubic, easingOutCubic, easingInOutCubic,
easingInQuart, easingOutQuart, easingInOutQuart,
easingInBack, easingOutBack, easingInOutBack,
};
float_t easingApply(easingtype_t type, float_t t) {
assertTrue(type < EASING_COUNT, "Invalid easing type");
return EASING_FUNCTIONS[type](t);
}
+49
View File
@@ -0,0 +1,49 @@
// Copyright (c) 2026 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
#pragma once
#include "dusk.h"
#define EASING_LINEAR 0
#define EASING_IN_SINE 1
#define EASING_OUT_SINE 2
#define EASING_IN_OUT_SINE 3
#define EASING_IN_QUAD 4
#define EASING_OUT_QUAD 5
#define EASING_IN_OUT_QUAD 6
#define EASING_IN_CUBIC 7
#define EASING_OUT_CUBIC 8
#define EASING_IN_OUT_CUBIC 9
#define EASING_IN_QUART 10
#define EASING_OUT_QUART 11
#define EASING_IN_OUT_QUART 12
#define EASING_IN_BACK 13
#define EASING_OUT_BACK 14
#define EASING_IN_OUT_BACK 15
#define EASING_COUNT 16
typedef uint8_t easingtype_t;
typedef float_t (*easingfn_t)(float_t t);
extern const easingfn_t EASING_FUNCTIONS[EASING_COUNT];
float_t easingApply(easingtype_t type, float_t t);
float_t easingLinear(float_t t);
float_t easingInSine(float_t t);
float_t easingOutSine(float_t t);
float_t easingInOutSine(float_t t);
float_t easingInQuad(float_t t);
float_t easingOutQuad(float_t t);
float_t easingInOutQuad(float_t t);
float_t easingInCubic(float_t t);
float_t easingOutCubic(float_t t);
float_t easingInOutCubic(float_t t);
float_t easingInQuart(float_t t);
float_t easingOutQuart(float_t t);
float_t easingInOutQuart(float_t t);
float_t easingInBack(float_t t);
float_t easingOutBack(float_t t);
float_t easingInOutBack(float_t t);
+15
View File
@@ -0,0 +1,15 @@
// Copyright (c) 2026 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
#pragma once
#include "easing.h"
#define ANIMATION_KEYFRAME_COUNT_MAX 16
typedef struct {
float_t time;
float_t value;
easingtype_t easing;
} keyframe_t;
+14 -4
View File
@@ -146,7 +146,9 @@ size_t assetFileLineReaderUnreadBytes(const assetfilelinereader_t *reader) {
return reader->bufferEnd - reader->bufferStart; return reader->bufferEnd - reader->bufferStart;
} }
const uint8_t *assetFileLineReaderUnreadPtr(const assetfilelinereader_t *reader) { const uint8_t *assetFileLineReaderUnreadPtr(
const assetfilelinereader_t *reader
) {
assertNotNull(reader, "Reader cannot be NULL."); assertNotNull(reader, "Reader cannot be NULL.");
assertNotNull(reader->readBuffer, "Read buffer cannot be NULL."); assertNotNull(reader->readBuffer, "Read buffer cannot be NULL.");
return reader->readBuffer + reader->bufferStart; return reader->readBuffer + reader->bufferStart;
@@ -177,11 +179,16 @@ static errorret_t assetFileLineReaderAppend(
static void assetFileLineReaderTerminate(assetfilelinereader_t *reader) { static void assetFileLineReaderTerminate(assetfilelinereader_t *reader) {
assertNotNull(reader, "Reader cannot be NULL."); assertNotNull(reader, "Reader cannot be NULL.");
assertNotNull(reader->outBuffer, "Out buffer cannot be NULL."); assertNotNull(reader->outBuffer, "Out buffer cannot be NULL.");
assertTrue(reader->lineLength < reader->outBufferSize, "Line length exceeds out buffer."); assertTrue(
reader->lineLength < reader->outBufferSize,
"Line length exceeds out buffer."
);
reader->outBuffer[reader->lineLength] = '\0'; reader->outBuffer[reader->lineLength] = '\0';
} }
static ssize_t assetFileLineReaderFindNewline(const assetfilelinereader_t *reader) { static ssize_t assetFileLineReaderFindNewline(
const assetfilelinereader_t *reader
) {
size_t i; size_t i;
assertNotNull(reader, "Reader cannot be NULL."); assertNotNull(reader, "Reader cannot be NULL.");
@@ -284,7 +291,10 @@ errorret_t assetFileLineReaderNext(assetfilelinereader_t *reader) {
errorret_t ret; errorret_t ret;
/* strip CR in CRLF */ /* strip CR in CRLF */
if(chunkLength > 0 && reader->readBuffer[(size_t)newlineIndex - 1] == '\r') { if(
chunkLength > 0 &&
reader->readBuffer[(size_t)newlineIndex - 1] == '\r'
) {
chunkLength--; chunkLength--;
} }
@@ -152,7 +152,7 @@ errorret_t assetLocaleParseHeader(
if(pluralIndex >= localeFile->pluralStateCount - 1) { if(pluralIndex >= localeFile->pluralStateCount - 1) {
errorThrow( errorThrow(
"Too many plural conditions. Expected %d conditional clauses for nplurals=%d.", "Too many plural conditions. Expected %d clauses for nplurals=%d.",
localeFile->pluralStateCount - 1, localeFile->pluralStateCount - 1,
localeFile->pluralStateCount localeFile->pluralStateCount
); );
+1 -2
View File
@@ -136,8 +136,7 @@ errorret_t consoleDraw(void) {
&FONT_TEXTURE_DEFAULT &FONT_TEXTURE_DEFAULT
)); ));
} }
errorChain(spriteBatchFlush()); return spriteBatchFlush();
errorOk();
} }
void consoleDispose(void) { void consoleDispose(void) {
@@ -1,10 +1,9 @@
# Copyright (c) 2026 Dominic Masters # Copyright (c) 2026 Dominic Masters
# #
# This software is released under the MIT License. # This software is released under the MIT License.
# https://opensource.org/licenses/MIT # https://opensource.org/licenses/MIT
# Sources
target_sources(${DUSK_LIBRARY_TARGET_NAME} target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC PUBLIC
event.c cutscene.c
) )
+187
View File
@@ -0,0 +1,187 @@
// Copyright (c) 2026 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
#include "cutscene.h"
#include "assert/assert.h"
#include "util/memory.h"
#include "script/module/modulebase.h"
#include "script/module/cutscene/modulecutscene.h"
#include "console/console.h"
#include "time/time.h"
cutscene_t CUTSCENE;
static errorret_t cutsceneCallMethod(const char_t *method) {
if(CUTSCENE.scriptRef == CUTSCENE_SCRIPT_REF_NONE) errorOk();
jerry_value_t key = jerry_string_sz(method);
jerry_value_t fn = jerry_object_get(CUTSCENE.scriptRef, key);
jerry_value_free(key);
if(!jerry_value_is_function(fn)) {
jerry_value_free(fn);
errorOk();
}
jerry_value_t result = jerry_call(fn, CUTSCENE.scriptRef, NULL, 0);
jerry_value_free(fn);
if(jerry_value_is_exception(result)) {
cutsceneevent_t *event = &CUTSCENE.events[CUTSCENE.eventCurrent];
char_t errMsg[512];
moduleBaseExceptionMessage(result, errMsg, sizeof(errMsg));
jerry_value_free(result);
errorThrow(
"Cutscene event '%s' %s failed: %s",
event->script.script, method, errMsg
);
}
jerry_value_free(result);
errorOk();
}
static errorret_t cutsceneEventStart(void) {
cutsceneevent_t *event = &CUTSCENE.events[CUTSCENE.eventCurrent];
if(event->type == CUTSCENE_EVENT_TYPE_NATIVE) {
if(event->native.onStart) errorChain(event->native.onStart());
} else if(event->type == CUTSCENE_EVENT_TYPE_SCRIPT) {
jerry_value_t eventClass = CUTSCENE_SCRIPT_REF_NONE;
errorChain(scriptManagerExecFile(event->script.script, &eventClass));
if(!jerry_value_is_function(eventClass)) {
if(eventClass != CUTSCENE_SCRIPT_REF_NONE) jerry_value_free(eventClass);
errorThrow(
"Cutscene event '%s' must export a constructor function",
event->script.script
);
}
jerry_value_t eventObj = jerry_construct(eventClass, NULL, 0);
jerry_value_free(eventClass);
if(jerry_value_is_exception(eventObj)) {
char_t errMsg[512];
moduleBaseExceptionMessage(eventObj, errMsg, sizeof(errMsg));
jerry_value_free(eventObj);
errorThrow(
"Cutscene event '%s' constructor threw: %s",
event->script.script, errMsg
);
}
CUTSCENE.scriptRef = eventObj;
errorChain(cutsceneCallMethod("onStart"));
}
errorOk();
}
static errorret_t cutsceneEventEnd(void) {
cutsceneevent_t *event = &CUTSCENE.events[CUTSCENE.eventCurrent];
if(event->type == CUTSCENE_EVENT_TYPE_NATIVE) {
if(event->native.onEnd) errorChain(event->native.onEnd());
} else if(event->type == CUTSCENE_EVENT_TYPE_SCRIPT) {
errorret_t err = cutsceneCallMethod("onEnd");
if(CUTSCENE.scriptRef != CUTSCENE_SCRIPT_REF_NONE) {
jerry_value_free(CUTSCENE.scriptRef);
CUTSCENE.scriptRef = CUTSCENE_SCRIPT_REF_NONE;
}
errorChain(err);
}
errorOk();
}
errorret_t cutsceneInit(void) {
memoryZero(&CUTSCENE, sizeof(cutscene_t));
CUTSCENE.scriptRef = CUTSCENE_SCRIPT_REF_NONE;
CUTSCENE.activeRef = CUTSCENE_SCRIPT_REF_NONE;
consolePrint("Cutscene init");
errorOk();
}
errorret_t cutsceneUpdate(void) {
#ifdef DUSK_TIME_DYNAMIC
if(TIME.dynamicUpdate) {
errorOk();
}
#endif
errorChain(moduleCutsceneUpdate());
if(!CUTSCENE.active) errorOk();
cutsceneevent_t *event = &CUTSCENE.events[CUTSCENE.eventCurrent];
if(event->type == CUTSCENE_EVENT_TYPE_NATIVE) {
if(event->native.onUpdate) errorChain(event->native.onUpdate());
} else if(event->type == CUTSCENE_EVENT_TYPE_SCRIPT) {
errorChain(cutsceneCallMethod("onUpdate"));
}
errorOk();
}
errorret_t cutscenePlay(
const cutsceneevent_t *events,
const uint8_t eventCount
) {
assertNotNull(events, "Events cannot be null");
assertTrue(eventCount > 0, "Event count must be greater than zero");
assertTrue(
eventCount <= CUTSCENE_EVENT_COUNT_MAX,
"Event count exceeds CUTSCENE_EVENT_COUNT_MAX"
);
if(CUTSCENE.active) {
errorChain(cutsceneStop());
}
memoryCopy(CUTSCENE.events, events, sizeof(cutsceneevent_t) * eventCount);
CUTSCENE.eventCount = eventCount;
CUTSCENE.eventCurrent = 0;
CUTSCENE.active = true;
errorChain(cutsceneEventStart());
consolePrint("Cutscene play");
errorOk();
}
errorret_t cutsceneAdvance(void) {
if(!CUTSCENE.active) errorOk();
errorChain(cutsceneEventEnd());
CUTSCENE.eventCurrent++;
if(CUTSCENE.eventCurrent >= CUTSCENE.eventCount) {
CUTSCENE.active = false;
errorOk();
}
errorChain(cutsceneEventStart());
consolePrint("Cutscene advance");
errorOk();
}
errorret_t cutsceneStop(void) {
if(!CUTSCENE.active) errorOk();
errorChain(cutsceneEventEnd());
CUTSCENE.active = false;
consolePrint("Cutscene stop");
errorOk();
}
errorret_t cutsceneDispose(void) {
errorChain(cutsceneStop());
errorOk();
}
bool_t cutsceneIsActive(void) {
return CUTSCENE.active;
}
+105
View File
@@ -0,0 +1,105 @@
// Copyright (c) 2026 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
#pragma once
#include "script/scriptmanager.h"
#include "error/error.h"
#define CUTSCENE_EVENT_COUNT_MAX 16
#define CUTSCENE_SCRIPT_REF_NONE ((jerry_value_t)0)
typedef enum {
CUTSCENE_EVENT_TYPE_NULL,
CUTSCENE_EVENT_TYPE_NATIVE,
CUTSCENE_EVENT_TYPE_SCRIPT,
CUTSCENE_EVENT_TYPE_COUNT
} cutsceneeventtype_t;
typedef struct {
cutsceneeventtype_t type;
union {
struct {
errorret_t (*onStart)(void);
errorret_t (*onEnd)(void);
errorret_t (*onUpdate)(void);
} native;
struct {
const char_t *script;
} script;
};
} cutsceneevent_t;
typedef struct {
cutsceneevent_t events[CUTSCENE_EVENT_COUNT_MAX];
uint8_t eventCount;
uint8_t eventCurrent;
bool_t active;
jerry_value_t scriptRef;
jerry_value_t activeRef;
} cutscene_t;
extern cutscene_t CUTSCENE;
/**
* Initializes the cutscene manager.
*
* @return Any error state that happened.
*/
errorret_t cutsceneInit(void);
/**
* Ticks the active cutscene event, calling its onUpdate callback.
* Does nothing when no cutscene is playing.
*
* @return Any error state that happened.
*/
errorret_t cutsceneUpdate(void);
/**
* Copies the given event array and begins playing from the first
* event. If a cutscene is already playing it is stopped first.
*
* @param events Array of events to copy.
* @param eventCount Number of events. Must be > 0 and
* <= CUTSCENE_EVENT_COUNT_MAX.
* @return Any error state that happened.
*/
errorret_t cutscenePlay(
const cutsceneevent_t *events,
const uint8_t eventCount
);
/**
* Ends the current event and starts the next one.
* Marks the cutscene as inactive after the last event ends.
* Does nothing when no cutscene is playing.
*
* @return Any error state that happened.
*/
errorret_t cutsceneAdvance(void);
/**
* Ends the current event and stops the cutscene immediately.
* Does nothing when no cutscene is playing.
*
* @return Any error state that happened.
*/
errorret_t cutsceneStop(void);
/**
* Disposes of the cutscene manager, stopping any active cutscene.
*
* @return Any error state that happened.
*/
errorret_t cutsceneDispose(void);
/**
* Returns whether a cutscene is currently playing.
*
* @return true if a cutscene is active.
*/
bool_t cutsceneIsActive(void);
+8
View File
@@ -33,6 +33,7 @@ errorret_t displayInit(void) {
#ifdef displayPlatformInit #ifdef displayPlatformInit
errorChain(displayPlatformInit()); errorChain(displayPlatformInit());
#endif #endif
errorChain(displaySetState((displaystate_t){ .flags = 0 }));
errorChain(textureInit( errorChain(textureInit(
&TEXTURE_WHITE, 4, 4, &TEXTURE_WHITE, 4, 4,
TEXTURE_FORMAT_RGBA, (texturedata_t){ .rgbaColors = TEXTURE_WHITE_PIXELS } TEXTURE_FORMAT_RGBA, (texturedata_t){ .rgbaColors = TEXTURE_WHITE_PIXELS }
@@ -110,6 +111,13 @@ errorret_t displayUpdate(void) {
errorOk(); errorOk();
} }
errorret_t displaySetState(displaystate_t state) {
#ifdef displayPlatformSetState
errorChain(displayPlatformSetState(state));
#endif
errorOk();
}
errorret_t displayDispose(void) { errorret_t displayDispose(void) {
errorChain(shaderDispose(&SHADER_UNLIT)); errorChain(shaderDispose(&SHADER_UNLIT));
errorChain(spriteBatchDispose()); errorChain(spriteBatchDispose());
+11
View File
@@ -40,15 +40,26 @@ extern display_t DISPLAY;
/** /**
* Initializes the display system. * Initializes the display system.
* @return An errorret_t indicating success or failure.
*/ */
errorret_t displayInit(void); errorret_t displayInit(void);
/** /**
* Tells the display system to actually draw the frame. * Tells the display system to actually draw the frame.
* @return An errorret_t indicating success or failure.
*/ */
errorret_t displayUpdate(void); errorret_t displayUpdate(void);
/**
* Sets the display state.
*
* @param state The state to set.
* @return An errorret_t indicating success or failure.
*/
errorret_t displaySetState(displaystate_t state);
/** /**
* Disposes of the display system. * Disposes of the display system.
* @return An errorret_t indicating success or failure.
*/ */
errorret_t displayDispose(void); errorret_t displayDispose(void);
+17
View File
@@ -0,0 +1,17 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "dusk.h"
#define DISPLAY_STATE_FLAG_CULL (1 << 0)
#define DISPLAY_STATE_FLAG_DEPTH_TEST (1 << 1)
#define DISPLAY_STATE_FLAG_BLEND (1 << 2)
typedef struct {
uint8_t flags;
} displaystate_t;
+12 -4
View File
@@ -119,8 +119,12 @@ void capsuleBuffer(
{ {
const float_t yTop = cy + halfHeight; const float_t yTop = cy + halfHeight;
const float_t yBot = cy - halfHeight; const float_t yBot = cy - halfHeight;
const float_t vTop = 1.0f - (float_t)capRings / (float_t)(2 * capRings + 1); const float_t vTop = (
const float_t vBot = 1.0f - (float_t)(capRings + 1) / (float_t)(2 * capRings + 1); 1.0f - (float_t)capRings / (float_t)(2 * capRings + 1)
);
const float_t vBot = (
1.0f - (float_t)(capRings + 1) / (float_t)(2 * capRings + 1)
);
for(int32_t j = 0; j < sectors; j++) { for(int32_t j = 0; j < sectors; j++) {
const float_t t1 = (float_t)j * sectorStep; const float_t t1 = (float_t)j * sectorStep;
@@ -152,8 +156,12 @@ void capsuleBuffer(
const float_t lxz1 = radius * cosf(phi1); const float_t lxz1 = radius * cosf(phi1);
const float_t lxz2 = radius * cosf(phi2); const float_t lxz2 = radius * cosf(phi2);
const float_t v1 = 1.0f - (float_t)(capRings + 1 + i) / (float_t)(2 * capRings + 1); const float_t v1 = (
const float_t v2 = 1.0f - (float_t)(capRings + 1 + i + 1) / (float_t)(2 * capRings + 1); 1.0f - (float_t)(capRings + 1 + i) / (float_t)(2 * capRings + 1)
);
const float_t v2 = (
1.0f - (float_t)(capRings + 1 + i + 1) / (float_t)(2 * capRings + 1)
);
for(int32_t j = 0; j < sectors; j++) { for(int32_t j = 0; j < sectors; j++) {
const float_t t1 = (float_t)j * sectorStep; const float_t t1 = (float_t)j * sectorStep;
+1 -1
View File
@@ -28,7 +28,7 @@ errorret_t cubeInit();
* Buffers a 3D axis-aligned cube into the provided vertex array. * Buffers a 3D axis-aligned cube into the provided vertex array.
* Writes CUBE_VERTEX_COUNT vertices (6 faces x 6 vertices, CCW winding). * Writes CUBE_VERTEX_COUNT vertices (6 faces x 6 vertices, CCW winding).
* *
* @param vertices The vertex array to buffer into (must hold CUBE_VERTEX_COUNT). * @param vertices The vertex array to buffer into.
* @param min The minimum XYZ corner of the cube. * @param min The minimum XYZ corner of the cube.
* @param max The maximum XYZ corner of the cube. * @param max The maximum XYZ corner of the cube.
* @param color The color applied to all vertices. * @param color The color applied to all vertices.
+6 -2
View File
@@ -34,10 +34,14 @@ errorret_t meshFlush(
#ifdef meshFlushPlatform #ifdef meshFlushPlatform
assertNotNull(mesh, "Mesh cannot be NULL"); assertNotNull(mesh, "Mesh cannot be NULL");
assertTrue(vertexOffset >= 0, "Vertex offset must be non-negative."); assertTrue(vertexOffset >= 0, "Vertex offset must be non-negative.");
assertTrue(vertexCount == -1 || vertexCount > 0, "Vertex count incorrect."); assertTrue(
vertexCount == -1 || vertexCount > 0, "Vertex count incorrect."
);
int32_t vertCount = meshGetVertexCount(mesh); int32_t vertCount = meshGetVertexCount(mesh);
assertTrue(vertexOffset < (vertCount - 1), "Need at least one vert to draw"); assertTrue(
vertexOffset < (vertCount - 1), "Need at least one vert to draw"
);
int32_t drawCount = vertexCount; int32_t drawCount = vertexCount;
if(vertexCount == -1) { if(vertexCount == -1) {
+7 -13
View File
@@ -9,12 +9,6 @@
#include "display/mesh/mesh.h" #include "display/mesh/mesh.h"
#include "display/color.h" #include "display/color.h"
/**
* Vertex layout:
* 2 triangular end-caps (3 verts each) = 6
* 3 rectangular side faces (6 verts each) = 18
* Total = 24
*/
#define TRIPRISM_VERTEX_COUNT 24 #define TRIPRISM_VERTEX_COUNT 24
#define TRIPRISM_PRIMITIVE_TYPE MESH_PRIMITIVE_TYPE_TRIANGLES #define TRIPRISM_PRIMITIVE_TYPE MESH_PRIMITIVE_TYPE_TRIANGLES
@@ -35,13 +29,13 @@ errorret_t triPrismInit();
* the prism is extruded along the Z axis between minZ and maxZ. * the prism is extruded along the Z axis between minZ and maxZ.
* Writes TRIPRISM_VERTEX_COUNT (24) vertices (CCW winding). * Writes TRIPRISM_VERTEX_COUNT (24) vertices (CCW winding).
* *
* @param vertices Vertex array to write into (must hold TRIPRISM_VERTEX_COUNT). * @param vertices Vertex array to write into.
* @param x0,y0 First triangle vertex (XY). * @param x0,y0 First triangle vertex (XY).
* @param x1,y1 Second triangle vertex (XY). * @param x1,y1 Second triangle vertex (XY).
* @param x2,y2 Third triangle vertex (XY). * @param x2,y2 Third triangle vertex (XY).
* @param minZ Near Z extent of the prism. * @param minZ Near Z extent of the prism.
* @param maxZ Far Z extent of the prism. * @param maxZ Far Z extent of the prism.
* @param color Color applied to all vertices. * @param color Color applied to all vertices.
*/ */
void triPrismBuffer( void triPrismBuffer(
meshvertex_t *vertices, meshvertex_t *vertices,
+2 -2
View File
@@ -8,9 +8,9 @@
#pragma once #pragma once
#include "display/mesh/quad.h" #include "display/mesh/quad.h"
#define SPRITEBATCH_SPRITES_MAX 32 #define SPRITEBATCH_SPRITES_MAX 256
#define SPRITEBATCH_VERTEX_COUNT (SPRITEBATCH_SPRITES_MAX * QUAD_VERTEX_COUNT) #define SPRITEBATCH_VERTEX_COUNT (SPRITEBATCH_SPRITES_MAX * QUAD_VERTEX_COUNT)
#define SPRITEBATCH_FLUSH_COUNT 4 #define SPRITEBATCH_FLUSH_COUNT 8
#define SPRITEBATCH_SPRITES_MAX_PER_FLUSH (\ #define SPRITEBATCH_SPRITES_MAX_PER_FLUSH (\
SPRITEBATCH_SPRITES_MAX / SPRITEBATCH_FLUSH_COUNT \ SPRITEBATCH_SPRITES_MAX / SPRITEBATCH_FLUSH_COUNT \
) )
+12 -5
View File
@@ -12,17 +12,19 @@
#include "locale/localemanager.h" #include "locale/localemanager.h"
#include "display/display.h" #include "display/display.h"
#include "scene/scene.h" #include "scene/scene.h"
#include "cutscene/cutscene.h"
#include "asset/asset.h" #include "asset/asset.h"
#include "ui/ui.h" #include "ui/ui.h"
#include "ui/uitextbox.h"
#include "script/scriptmanager.h" #include "script/scriptmanager.h"
#include "assert/assert.h" #include "assert/assert.h"
#include "entity/entitymanager.h" #include "entity/entitymanager.h"
#include "entity/component/physics/entityphysics.h" #include "entity/component/physics/entityphysics.h"
#include "game/game.h"
#include "physics/physicsmanager.h" #include "physics/physicsmanager.h"
#include "network/network.h" #include "network/network.h"
#include "system/system.h" #include "system/system.h"
#include "console/console.h" #include "console/console.h"
#include "item/backpack.h"
double jerry_port_current_time(void) { double jerry_port_current_time(void) {
dusktimeepoch_t epoch = timeGetEpoch(); dusktimeepoch_t epoch = timeGetEpoch();
@@ -52,11 +54,14 @@ errorret_t engineInit(const int32_t argc, const char_t **argv) {
errorChain(scriptManagerInit()); errorChain(scriptManagerInit());
errorChain(displayInit()); errorChain(displayInit());
errorChain(uiInit()); errorChain(uiInit());
errorChain(uiTextboxInit());
errorChain(cutsceneInit());
errorChain(sceneInit()); errorChain(sceneInit());
entityManagerInit(); entityManagerInit();
backpackInit();
physicsManagerInit(); physicsManagerInit();
errorChain(networkInit()); errorChain(networkInit());
errorChain(gameInit());
/* Run the init script. */ /* Run the init script. */
consolePrint("Engine initialized"); consolePrint("Engine initialized");
@@ -72,14 +77,15 @@ errorret_t engineUpdate(void) {
inputUpdate(); inputUpdate();
consoleUpdate(); consoleUpdate();
uiUpdate(); uiUpdate();
errorChain(uiTextboxUpdate());
physicsManagerUpdate(); physicsManagerUpdate();
errorChain(gameUpdate());
errorChain(displayUpdate()); errorChain(displayUpdate());
if(inputPressed(INPUT_ACTION_RAGEQUIT)) ENGINE.running = false; if(inputPressed(INPUT_ACTION_RAGEQUIT)) ENGINE.running = false;
// Scene update occurs last because only after rendering would we want to do // Scene update occurs last because only after rendering would we want to do
// scene switching, refer to sceneSet() for information. // scene switching, refer to sceneSet() for information.
errorChain(cutsceneUpdate());
errorChain(sceneUpdate()); errorChain(sceneUpdate());
errorOk(); errorOk();
@@ -90,8 +96,9 @@ void engineExit(void) {
} }
errorret_t engineDispose(void) { errorret_t engineDispose(void) {
uiTextboxDispose();
cutsceneDispose();
sceneDispose(); sceneDispose();
errorChain(gameDispose());
errorChain(networkDispose()); errorChain(networkDispose());
entityManagerDispose(); entityManagerDispose();
localeManagerDispose(); localeManagerDispose();
@@ -99,6 +106,6 @@ errorret_t engineDispose(void) {
consoleDispose(); consoleDispose();
errorChain(displayDispose()); errorChain(displayDispose());
errorChain(assetDispose()); errorChain(assetDispose());
errorOk(); errorOk();
} }
+2 -1
View File
@@ -35,4 +35,5 @@ errorret_t engineUpdate(void);
/** /**
* Shuts down the engine. * Shuts down the engine.
*/ */
errorret_t engineDispose(void); errorret_t engineDispose(void);
+1 -1
View File
@@ -101,7 +101,7 @@ entityid_t componentGetEntitiesWithComponent(
"Component ID OOB in entitiesWithComponent lookup" "Component ID OOB in entitiesWithComponent lookup"
); );
assertTrue( assertTrue(
componentGetIndex(i, used) < ENTITY_COUNT_MAX * ENTITY_COMPONENT_COUNT_MAX, componentGetIndex(i,used) < ENTITY_COUNT_MAX*ENTITY_COMPONENT_COUNT_MAX,
"Component index OOB in entitiesWithComponent lookup" "Component index OOB in entitiesWithComponent lookup"
); );
assertTrue( assertTrue(
@@ -73,7 +73,8 @@ entityid_t entityCameraGetCurrent(void) {
void entityCameraGetForward(const entityid_t entityId, vec2 out) { void entityCameraGetForward(const entityid_t entityId, vec2 out) {
componentid_t posComp = entityGetComponent(entityId, COMPONENT_TYPE_POSITION); componentid_t posComp = entityGetComponent(entityId, COMPONENT_TYPE_POSITION);
entityposition_t *pos = entityPositionGet(entityId, posComp); entityposition_t *pos = entityPositionGet(entityId, posComp);
// View matrix column layout: M[col][row], forward = {-M[0][2], -M[1][2], -M[2][2]} // View matrix column layout: M[col][row],
// forward = {-M[0][2], -M[1][2], -M[2][2]}
float_t fx = -pos->transform[0][2]; float_t fx = -pos->transform[0][2];
float_t fz = -pos->transform[2][2]; float_t fz = -pos->transform[2][2];
float_t len = sqrtf(fx * fx + fz * fz); float_t len = sqrtf(fx * fx + fz * fz);
@@ -55,7 +55,8 @@ void entityCameraGetProjection(
); );
/** /**
* Returns the entity ID of the first active camera, or ENTITY_COUNT_MAX if none. * Returns the entity ID of the first active camera, or ENTITY_COUNT_MAX if
* none are active.
*/ */
entityid_t entityCameraGetCurrent(void); entityid_t entityCameraGetCurrent(void);
+1 -98
View File
@@ -7,11 +7,6 @@
#include "entitymesh.h" #include "entitymesh.h"
#include "entity/entitymanager.h" #include "entity/entitymanager.h"
#include "assert/assert.h"
#include "util/memory.h"
#include "display/mesh/cube.h"
#include "display/mesh/plane.h"
#include "display/mesh/capsule.h"
void entityMeshInit( void entityMeshInit(
const entityid_t entityId, const entityid_t entityId,
@@ -20,9 +15,7 @@ void entityMeshInit(
entitymesh_t *comp = componentGetData( entitymesh_t *comp = componentGetData(
entityId, componentId, COMPONENT_TYPE_MESH entityId, componentId, COMPONENT_TYPE_MESH
); );
comp->mesh = &CUBE_MESH_SIMPLE; comp->mesh = NULL;
comp->ownedVertices = NULL;
comp->ownsData = false;
} }
mesh_t * entityMeshGetMesh( mesh_t * entityMeshGetMesh(
@@ -50,94 +43,4 @@ void entityMeshDispose(
const entityid_t entityId, const entityid_t entityId,
const componentid_t componentId const componentid_t componentId
) { ) {
entitymesh_t *comp = componentGetData(
entityId, componentId, COMPONENT_TYPE_MESH
);
if(!comp->ownsData) return;
(void)meshDispose(&comp->ownedMesh);
memoryFree(comp->ownedVertices);
comp->ownedVertices = NULL;
comp->ownsData = false;
comp->mesh = NULL;
} }
errorret_t entityMeshGeneratePlane(
const entityid_t entityId,
const componentid_t componentId,
const float_t width,
const float_t height
) {
entitymesh_t *comp = componentGetData(
entityId, componentId, COMPONENT_TYPE_MESH
);
entityMeshDispose(entityId, componentId);
comp->ownedVertices = memoryAllocate(PLANE_VERTEX_COUNT * sizeof(meshvertex_t));
assertNotNull(comp->ownedVertices, "Failed to allocate plane vertices");
vec3 min = { -width * 0.5f, 0.0f, -height * 0.5f };
vec3 max = { width * 0.5f, 0.0f, height * 0.5f };
vec2 uvMin = { 0.0f, 0.0f };
vec2 uvMax = { 1.0f, 1.0f };
planeBuffer(
comp->ownedVertices,
PLANE_AXIS_XZ,
min,
max
#if MESH_ENABLE_COLOR
, COLOR_WHITE_4B
#endif
, uvMin,
uvMax
);
errorChain(meshInit(
&comp->ownedMesh,
PLANE_PRIMITIVE_TYPE,
PLANE_VERTEX_COUNT,
comp->ownedVertices
));
comp->mesh = &comp->ownedMesh;
comp->ownsData = true;
errorOk();
}
errorret_t entityMeshGenerateCapsule(
const entityid_t entityId,
const componentid_t componentId,
const float_t radius,
const float_t halfHeight
) {
entitymesh_t *comp = componentGetData(
entityId, componentId, COMPONENT_TYPE_MESH
);
entityMeshDispose(entityId, componentId);
comp->ownedVertices = memoryAllocate(CAPSULE_VERTEX_COUNT * sizeof(meshvertex_t));
assertNotNull(comp->ownedVertices, "Failed to allocate capsule vertices");
vec3 center = { 0.0f, 0.0f, 0.0f };
capsuleBuffer(
comp->ownedVertices,
center,
radius,
halfHeight,
CAPSULE_CAP_RINGS,
CAPSULE_SECTORS
#if MESH_ENABLE_COLOR
, COLOR_WHITE_4B
#endif
);
errorChain(meshInit(
&comp->ownedMesh,
CAPSULE_PRIMITIVE_TYPE,
CAPSULE_VERTEX_COUNT,
comp->ownedVertices
));
comp->mesh = &comp->ownedMesh;
comp->ownsData = true;
errorOk();
}
+2 -37
View File
@@ -11,9 +11,6 @@
typedef struct { typedef struct {
mesh_t *mesh; mesh_t *mesh;
mesh_t ownedMesh;
meshvertex_t *ownedVertices;
bool_t ownsData;
} entitymesh_t; } entitymesh_t;
/** /**
@@ -44,7 +41,7 @@ mesh_t * entityMeshGetMesh(
* *
* @param entityId The entity ID. * @param entityId The entity ID.
* @param componentId The component ID. * @param componentId The component ID.
* @param mesh A pointer to the mesh to associate with the entity mesh component. * @param mesh A pointer to the mesh to associate.
*/ */
void entityMeshSetMesh( void entityMeshSetMesh(
const entityid_t entityId, const entityid_t entityId,
@@ -53,7 +50,7 @@ void entityMeshSetMesh(
); );
/** /**
* Disposes the entity mesh component, freeing any owned mesh data. * Disposes the entity mesh component.
* *
* @param entityId The entity ID. * @param entityId The entity ID.
* @param componentId The component ID. * @param componentId The component ID.
@@ -62,35 +59,3 @@ void entityMeshDispose(
const entityid_t entityId, const entityid_t entityId,
const componentid_t componentId const componentid_t componentId
); );
/**
* Generates an XZ-aligned plane mesh owned by the component.
*
* @param entityId The entity ID.
* @param componentId The component ID.
* @param width Width of the plane along the X axis.
* @param height Height of the plane along the Z axis.
* @return Error return value.
*/
errorret_t entityMeshGeneratePlane(
const entityid_t entityId,
const componentid_t componentId,
const float_t width,
const float_t height
);
/**
* Generates a Y-axis capsule mesh owned by the component.
*
* @param entityId The entity ID.
* @param componentId The component ID.
* @param radius Radius of the cylinder and hemisphere caps.
* @param halfHeight Half-height of the cylindrical section.
* @return Error return value.
*/
errorret_t entityMeshGenerateCapsule(
const entityid_t entityId,
const componentid_t componentId,
const float_t radius,
const float_t halfHeight
);
+3 -1
View File
@@ -83,7 +83,9 @@ errorret_t errorChainImpl(
assertNotNull(retval.state->message, "Message cannot be NULL"); assertNotNull(retval.state->message, "Message cannot be NULL");
// Create a new line string. // Create a new line string.
int32_t newLineLen = snprintf(NULL, 0, ERROR_LINE_FORMAT, file, line, function); int32_t newLineLen = snprintf(
NULL, 0, ERROR_LINE_FORMAT, file, line, function
);
assertTrue(newLineLen >= 0, "Line formatting failed"); assertTrue(newLineLen >= 0, "Line formatting failed");
char_t *newLine = (char_t *)memoryAllocate(newLineLen + 1); char_t *newLine = (char_t *)memoryAllocate(newLineLen + 1);
snprintf(newLine, newLineLen + 1, ERROR_LINE_FORMAT, file, line, function); snprintf(newLine, newLineLen + 1, ERROR_LINE_FORMAT, file, line, function);
-239
View File
@@ -1,239 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "event.h"
#include "assert/assert.h"
#include "util/memory.h"
#include "console/console.h"
void eventInit(
event_t *event,
eventlistener_t *array,
const uint16_t maxListeners
) {
assertNotNull(event, "Event cannot be NULL");
assertNotNull(array, "Listener array cannot be NULL");
assertTrue(maxListeners > 0, "Max listeners must be greater than 0");
memoryZero(event, sizeof(event_t));
event->listenerArray = array;
event->maxListeners = maxListeners;
}
eventsub_t eventSubscribeUser(
event_t *event,
const eventtype_t type,
const eventuserdata_t user
) {
assertNotNull(event, "Event cannot be NULL");
assertTrue(
event->listenerCount < event->maxListeners,
"Maximum number of listeners reached"
);
if(type == EVENT_TYPE_C) {
assertNotNull(
user.c.callback,
"C event listener callback cannot be NULL"
);
} else if(type == EVENT_TYPE_SCRIPT) {
assertNotNull(
user.script.context,
"Script event listener context cannot be NULL"
);
assertTrue(
user.script.funcValue != 0,
"Script event listener function reference is invalid"
);
} else {
assertUnreachable("Unknown event listener type");
}
eventsub_t id = event->nextId++;
assertTrue(event->nextId != 0, "Event subscription ID overflow");
eventlistener_t *listener = &event->listenerArray[event->listenerCount++];
memoryZero(listener, sizeof(eventlistener_t));
listener->user = user;
listener->id = id;
listener->type = type;
return id;
}
eventsub_t eventSubscribe(
event_t *event,
const eventcallback_t callback,
const void *user
) {
return eventSubscribeUser(
event,
EVENT_TYPE_C,
(eventuserdata_t){ .c = { .callback = callback, .user = (void *)user } }
);
}
eventsub_t eventSubscribeScriptContext(
event_t *event,
scriptmanager_t *context,
jerry_value_t funcValue
) {
assertNotNull(context, "Script context cannot be NULL");
assertTrue(funcValue != 0, "Script function value is invalid");
bool_t alreadySubbed = false;
uint8_t i = 0;
do {
if(context->subscribedEvents[i] == event) {
alreadySubbed = true;
break;
}
i++;
} while(i < SCRIPT_MANAGER_MAX_EVENT_SUBSCRIPTIONS);
if(!alreadySubbed) {
i = 0;
do {
if(context->subscribedEvents[i] == NULL) {
context->subscribedEvents[i] = event;
break;
}
i++;
} while(i < SCRIPT_MANAGER_MAX_EVENT_SUBSCRIPTIONS);
assertTrue(
i < SCRIPT_MANAGER_MAX_EVENT_SUBSCRIPTIONS,
"Script context has reached maximum event subscriptions"
);
}
return eventSubscribeUser(
event,
EVENT_TYPE_SCRIPT,
(eventuserdata_t){
.script = {
.context = context,
.funcValue = funcValue
}
}
);
}
void eventUnsubscribe(event_t *event, const eventsub_t id) {
assertNotNull(event, "Event cannot be NULL");
assertFalse(event->isInvoking, "Cannot unsubscribe while invoking event");
if(event->listenerCount == 0) return;
uint16_t index = 0;
do {
if(event->listenerArray[index].id != id) {
index++;
continue;
}
if(event->listenerArray[index].type == EVENT_TYPE_SCRIPT) {
jerry_value_t funcVal = event->listenerArray[index].user.script.funcValue;
if(funcVal != 0) {
jerry_value_free(funcVal);
}
}
event->listenerArray[index] = event->listenerArray[--event->listenerCount];
return;
} while(index < event->listenerCount);
}
void eventUnsubscribeScriptContext(
event_t *event,
const scriptmanager_t *ctx
) {
assertNotNull(event, "Event cannot be NULL");
assertNotNull(ctx, "Script context cannot be NULL");
if(event->listenerCount == 0) return;
uint16_t i = 0;
do {
eventlistener_t *listener = &event->listenerArray[i];
if(
listener->type != EVENT_TYPE_SCRIPT ||
listener->user.script.context != ctx
) {
i++;
continue;
}
eventUnsubscribe(event, listener->id);
} while(i < event->listenerCount);
}
void eventInvoke(
event_t *event,
const void *eventParams,
const char_t *metatableName
) {
assertNotNull(event, "Event cannot be NULL");
if(event->listenerCount == 0) return;
event->isInvoking = true;
eventdata_t data = {
.event = event,
.eventParams = eventParams,
};
uint16_t i = 0;
do {
eventlistener_t *listener = &event->listenerArray[i];
if(listener->type == EVENT_TYPE_C) {
listener->user.c.callback(&data, listener->user.c);
} else if(listener->type == EVENT_TYPE_SCRIPT) {
jerry_value_t funcVal = listener->user.script.funcValue;
assertNotNull((void *)(uintptr_t)funcVal, "Script function value is NULL");
jerry_value_t callArgs[1];
jerry_length_t argCount = 0;
if(eventParams != NULL) {
callArgs[0] = jerry_object();
jerry_object_set_native_ptr(
callArgs[0], &JS_PTR_NATIVE_INFO, (void *)eventParams
);
argCount = 1;
}
jerry_value_t result = jerry_call(
funcVal, jerry_undefined(), callArgs, argCount
);
if(argCount > 0) jerry_value_free(callArgs[0]);
if(jerry_value_is_exception(result)) {
jerry_value_t errStr = jerry_value_to_string(
jerry_exception_value(result, false)
);
char_t buf[256];
jerry_size_t len = jerry_string_to_buffer(
errStr, JERRY_ENCODING_UTF8, (jerry_char_t *)buf, sizeof(buf) - 1
);
buf[len] = '\0';
jerry_value_free(errStr);
consolePrint("Error invoking script event listener:\n%s\n", buf);
}
jerry_value_free(result);
} else {
assertUnreachable("Unknown event listener type");
}
i++;
} while(i < event->listenerCount);
event->isInvoking = false;
}
-124
View File
@@ -1,124 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "eventuser.h"
typedef struct event_s event_t;
typedef struct eventdata_s {
const void *eventParams;
const event_t *event;
} eventdata_t;
typedef struct eventlistener_s {
eventuserdata_t user;
eventtype_t type;
uint16_t id;
} eventlistener_t;
typedef uint16_t eventsub_t;
typedef struct event_s {
eventlistener_t *listenerArray;
uint16_t listenerCount;
uint16_t maxListeners;
eventsub_t nextId;
bool_t isInvoking;
} event_t;
/**
* Initialize an event structure.
*
* @param event The event to initialize.
* @param array The array to hold event listeners.
* @param maxListeners The maximum number of listeners.
*/
void eventInit(
event_t *event,
eventlistener_t *array,
const uint16_t maxListeners
);
/**
* Subscribe to an event.
*
* @param event The event to subscribe to.
* @param type The type of the event (C or Lua).
* @param user User data to pass to the callback.
* @return The subscription ID, used to unsubscribe later.
*/
eventsub_t eventSubscribeUser(
event_t *event,
const eventtype_t type,
const eventuserdata_t user
);
/**
* Subscribe to an event with a C function callback.
*
* @param event The event to subscribe to.
* @param callback The C function callback.
* @param user User data pointer to pass to the callback.
* @return The subscription ID, used to unsubscribe later.
*/
eventsub_t eventSubscribe(
event_t *event,
const eventcallback_t callback,
const void *user
);
/**
* Subscribe to an event with a script function callback.
*
* @param event The event to subscribe to.
* @param context The script context.
* @param functionIndex The index of the Lua function on the stack.
* @return The subscription ID, used to unsubscribe later.
*/
eventsub_t eventSubscribeScriptContext(
event_t *event,
scriptmanager_t *context,
jerry_value_t funcValue
);
/**
* Unsubscribe from an event.
*
* @param event The event to unsubscribe from.
* @param subscription The subscription ID to remove.
*/
void eventUnsubscribe(event_t *event, const eventsub_t subscription);
/**
* Unsubscribe all event listeners associated with a script context.
*
* @param event The event to unsubscribe from.
* @param context The script context whose listeners should be removed.
*/
void eventUnsubscribeScriptContext(
event_t *event,
const scriptmanager_t *ctx
);
/**
* Invoke an event, calling all subscribed listeners. Optionally provide event
* parameters, and if desired pass a metatable name. Event listeners will be
* able to read event params, and if metatable name is provided the script
* listeners will lookup metatable information matching that name to access the
* params as a structure within the script.
*
* @param event The event to invoke.
* @param eventParams Parameters to pass to the event listeners.
* @param metatableName Metatable name. See details.
*/
void eventInvoke(
event_t *event,
const void *eventParams,
const char_t *metatableName
);
-14
View File
@@ -1,14 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "dusk.h"
typedef struct eventdata_s eventdata_t;
typedef struct eventc_s eventc_t;
typedef void (*eventcallback_t)(eventdata_t *data, eventc_t user);
-30
View File
@@ -1,30 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "eventcallback.h"
#include "script/scriptmanager.h"
typedef enum {
EVENT_TYPE_C = 0,
EVENT_TYPE_SCRIPT = 1
} eventtype_t;
typedef struct {
scriptmanager_t *context;
jerry_value_t funcValue;
} eventscript_t;
typedef struct eventc_s {
void *user;
eventcallback_t callback;
} eventc_t;
typedef union eventuserdata_u {
eventscript_t script;
eventc_t c;
} eventuserdata_t;
-29
View File
@@ -27,13 +27,6 @@ errorret_t inputInit(void) {
errorChain(inputInitPlatform()); errorChain(inputInitPlatform());
#endif #endif
eventInit(
&INPUT.eventPressed, INPUT.pressedListeners, INPUT_LISTENER_PRESSED_MAX
);
eventInit(
&INPUT.eventReleased, INPUT.releasedListeners, INPUT_LISTENER_RELEASED_MAX
);
errorOk(); errorOk();
} }
@@ -94,28 +87,6 @@ void inputUpdate(void) {
#ifdef DUSK_TIME_DYNAMIC #ifdef DUSK_TIME_DYNAMIC
if(TIME.dynamicUpdate) return; if(TIME.dynamicUpdate) return;
#endif #endif
// if(INPUT.eventPressed.listenerCount > 0) {
// action = &INPUT.actions[0];
// do {
// if(inputPressed(action->action)) {
// inputevent_t inputEvent = { .action = action->action };
// eventInvoke(&INPUT.eventPressed, &inputEvent, "input_mt");
// }
// action++;
// } while(action < &INPUT.actions[INPUT_ACTION_COUNT]);
// }
// if(INPUT.eventReleased.listenerCount > 0) {
// action = &INPUT.actions[0];
// do {
// if(inputReleased(action->action)) {
// inputevent_t inputEvent = { .action = action->action };
// eventInvoke(&INPUT.eventReleased, &inputEvent, "input_mt");
// }
// action++;
// } while(action < &INPUT.actions[INPUT_ACTION_COUNT]);
// }
} }
float_t inputGetCurrentValue(const inputaction_t action) { float_t inputGetCurrentValue(const inputaction_t action) {
-10
View File
@@ -9,23 +9,13 @@
#include "error/error.h" #include "error/error.h"
#include "inputbutton.h" #include "inputbutton.h"
#include "inputaction.h" #include "inputaction.h"
#include "event/event.h"
#define INPUT_LISTENER_PRESSED_MAX 16 #define INPUT_LISTENER_PRESSED_MAX 16
#define INPUT_LISTENER_RELEASED_MAX INPUT_LISTENER_PRESSED_MAX #define INPUT_LISTENER_RELEASED_MAX INPUT_LISTENER_PRESSED_MAX
typedef struct {
const inputaction_t action;
} inputevent_t;
typedef struct { typedef struct {
inputactiondata_t actions[INPUT_ACTION_COUNT]; inputactiondata_t actions[INPUT_ACTION_COUNT];
eventlistener_t pressedListeners[INPUT_LISTENER_PRESSED_MAX];
event_t eventPressed;
eventlistener_t releasedListeners[INPUT_LISTENER_RELEASED_MAX];
event_t eventReleased;
inputplatform_t platform; inputplatform_t platform;
} input_t; } input_t;
-6
View File
@@ -25,17 +25,11 @@ typedef enum {
typedef struct { typedef struct {
uint8_t ip[NETWORK_INFO_IPV4_OCTET_COUNT]; uint8_t ip[NETWORK_INFO_IPV4_OCTET_COUNT];
// uint8_t subnet[NETWORK_INFO_IPV4_OCTET_COUNT];
// uint8_t gateway[NETWORK_INFO_IPV4_OCTET_COUNT];
// uint8_t dns[NETWORK_INFO_IPV4_OCTET_COUNT][NETWORK_INFO_IPV4_DNS_COUNT_MAX];
} networkinfoipv4_t; } networkinfoipv4_t;
#ifdef DUSK_NETWORK_IPV6 #ifdef DUSK_NETWORK_IPV6
typedef struct { typedef struct {
uint8_t ip[NETWORK_INFO_IPV6_OCTET_COUNT]; uint8_t ip[NETWORK_INFO_IPV6_OCTET_COUNT];
// uint8_t subnet[NETWORK_INFO_IPV6_OCTET_COUNT];
// uint8_t gateway[NETWORK_INFO_IPV6_OCTET_COUNT];
// uint8_t dns[NETWORK_INFO_IPV6_OCTET_COUNT][NETWORK_INFO_IPV6_DNS_COUNT_MAX];
} networkinfoipv6_t; } networkinfoipv6_t;
#endif #endif
+1 -1
View File
@@ -45,7 +45,7 @@ void physicsWorldStep(const float_t dt) {
} }
/* Phase 1: integrate dynamic bodies (gravity + velocity → position). /* Phase 1: integrate dynamic bodies (gravity + velocity → position).
* Writes directly to pos->position matrix rebuilt at end. */ * Writes directly to pos->position, matrix rebuilt at end. */
for(entityid_t i = 0; i < physCount; i++) { for(entityid_t i = 0; i < physCount; i++) {
if(!positions[i]) continue; if(!positions[i]) continue;
entityphysics_t *phys = physBodies[i]; entityphysics_t *phys = physBodies[i];
+50 -164
View File
@@ -11,14 +11,13 @@
#include "display/screen/screen.h" #include "display/screen/screen.h"
#include "entity/entitymanager.h" #include "entity/entitymanager.h"
#include "display/shader/shaderunlit.h" #include "display/shader/shaderunlit.h"
#include "display/mesh/cube.h"
#include "display/spritebatch/spritebatch.h"
#include "display/text/text.h"
#include "display/screen/screen.h" #include "display/screen/screen.h"
#include "console/console.h" #include "console/console.h"
#include "util/string.h" #include "util/string.h"
#include "script/scriptmanager.h" #include "script/scriptmanager.h"
#include "script/module/scene/modulescene.h" #include "script/module/scene/modulescene.h"
#include "script/module/cutscene/modulecutscene.h"
#include "ui/ui.h"
scene_t SCENE; scene_t SCENE;
@@ -55,7 +54,13 @@ errorret_t sceneRender(void) {
COMPONENT_TYPE_CAMERA, camEnts, camComps COMPONENT_TYPE_CAMERA, camEnts, camComps
); );
shader_t *shaderCurrent = NULL;
mat4 view, proj, model; mat4 view, proj, model;
errorChain(displaySetState((displaystate_t){
// .flags = DISPLAY_STATE_FLAG_CULL | DISPLAY_STATE_FLAG_DEPTH_TEST
.flags = 0
}));
// For each camera // For each camera
for(entityid_t camIndex = 0; camIndex < camCount; camIndex++) { for(entityid_t camIndex = 0; camIndex < camCount; camIndex++) {
@@ -70,7 +75,8 @@ errorret_t sceneRender(void) {
entityCameraGetProjection(camEnt, camComp, proj); entityCameraGetProjection(camEnt, camComp, proj);
entityPositionGetTransform(camEnt, camPos, view); entityPositionGetTransform(camEnt, camPos, view);
// For each entity // For each entity (I could iterate only over entities with mesh/material)
// but in future I may have different renderable types.
for(entityid_t entityId = 0; entityId < ENTITY_COUNT_MAX; entityId++) { for(entityid_t entityId = 0; entityId < ENTITY_COUNT_MAX; entityId++) {
// Does this entity have a material? // Does this entity have a material?
componentid_t matComp = entityGetComponent( componentid_t matComp = entityGetComponent(
@@ -81,9 +87,13 @@ errorret_t sceneRender(void) {
componentid_t meshComp = entityGetComponent( componentid_t meshComp = entityGetComponent(
entityId, COMPONENT_TYPE_MESH entityId, COMPONENT_TYPE_MESH
); );
if(meshComp == 0xFF) { if(meshComp == 0xFF) {
logError("Entity with material component without mesh component found\n"); logError("Entity with material but no mesh found\n");
continue;
}
mesh_t *mesh = entityMeshGetMesh(entityId, meshComp);
if(mesh == NULL) {
logError("Entity with material but no mesh found\n");
continue; continue;
} }
@@ -93,14 +103,7 @@ errorret_t sceneRender(void) {
); );
shader_t *shader = entityMaterialGetShader(entityId, matComp); shader_t *shader = entityMaterialGetShader(entityId, matComp);
if(shader == NULL) { if(shader == NULL) {
logError("Entity with material component without shader found\n"); logError("Entity with material but no shader found\n");
continue;
}
// Get the mesh
mesh_t *mesh = entityMeshGetMesh(entityId, meshComp);
if(mesh == NULL) {
logError("Entity with material component without mesh found\n");
continue; continue;
} }
@@ -115,9 +118,13 @@ errorret_t sceneRender(void) {
} }
// Render the mesh. // Render the mesh.
errorChain(shaderBind(shader)); if(shaderCurrent != shader) {
errorChain(shaderSetMatrix(shader, SHADER_UNLIT_PROJECTION, proj)); shaderCurrent = shader;
errorChain(shaderSetMatrix(shader, SHADER_UNLIT_VIEW, view)); errorChain(shaderBind(shaderCurrent));
errorChain(shaderSetMatrix(shader, SHADER_UNLIT_PROJECTION, proj));
errorChain(shaderSetMatrix(shader, SHADER_UNLIT_VIEW, view));
}
errorChain(shaderSetMatrix(shader, SHADER_UNLIT_MODEL, model)); errorChain(shaderSetMatrix(shader, SHADER_UNLIT_MODEL, model));
errorChain(shaderSetMaterial(shader, material)); errorChain(shaderSetMaterial(shader, material));
errorChain(meshDraw(mesh, 0, -1)); errorChain(meshDraw(mesh, 0, -1));
@@ -128,154 +135,32 @@ errorret_t sceneRender(void) {
} }
} }
// UI Rendering
glm_ortho(
0.0f, SCREEN.width,
SCREEN.height, 0.0f,
0.1f, 100.0f,
proj
);
glm_lookat(
(vec3){ 0.0f, 0.0f, 1.0f },
(vec3){ 0.0f, 0.0f, 0.0f },
(vec3){ 0.0f, 1.0f, 0.0f },
view
);
glm_mat4_identity(model);
errorChain(shaderBind(&SHADER_UNLIT));
errorChain(shaderSetMatrix(&SHADER_UNLIT, SHADER_UNLIT_PROJECTION, proj));
errorChain(shaderSetMatrix(&SHADER_UNLIT, SHADER_UNLIT_VIEW, view));
errorChain(shaderSetMatrix(&SHADER_UNLIT, SHADER_UNLIT_MODEL, model));
errorChain(displaySetState((displaystate_t){
.flags = DISPLAY_STATE_FLAG_BLEND
}));
errorChain(uiRender());
errorOk(); errorOk();
// if(camCount > 0) {
// // For each entity
// for(entityid_t entityId = 0; entityId < ENTITY_COUNT_MAX; entityId++) {
// }
// entityid_t meshEnts[ENTITY_COUNT_MAX];
// componentid_t meshComps[ENTITY_COUNT_MAX];
// entityid_t meshCount = componentGetEntitiesWithComponent(
// COMPONENT_TYPE_MESH, meshEnts, meshComps
// );
// if(meshCount > 0) {
// errorChain(shaderBind(&SHADER_UNLIT));
// for(entityid_t camIndex = 0; camIndex < camCount; camIndex++) {
// entityid_t camEnt = camEnts[camIndex];
// componentid_t camComp = camComps[camIndex];
// componentid_t camPos = entityGetComponent(camEnt, COMPONENT_TYPE_POSITION);
// if(camPos == 0xFF) {
// logError("Camera entity without entity position found\n");
// continue;
// }
// entityCameraGetProjection(camEnt, camComp, proj);
// entityPositionGetTransform(camEnt, camPos, view);
// for(entityid_t meshIndex = 0; meshIndex < meshCount; meshIndex++) {
// entityid_t meshEnt = meshEnts[meshIndex];
// componentid_t meshComp = meshComps[meshIndex];
// mesh_t *mesh = entityMeshGetMesh(meshEnt, meshComp);
// if(mesh == NULL) continue;
// componentid_t meshPos = entityGetComponent(
// meshEnt, COMPONENT_TYPE_POSITION
// );
// if(meshPos == 0xFF) {
// logError("Mesh entity without entity position found\n");
// continue;
// }
// componentid_t meshMat = entityGetComponent(
// meshEnt, COMPONENT_TYPE_MATERIAL
// );
// if(meshMat == 0xFF) {
// logError("Mesh entity without material component found\n");
// continue;
// }
// shadermaterial_t *material = entityMaterialGetShaderMaterial(
// meshEnt, meshMat
// );
// shader_t *shader = entityMaterialGetShader(meshEnt, meshMat);
// if(shader == NULL) {
// logError("Mesh entity with material component without shader found\n");
// continue;
// }
// entityPositionGetTransform(meshEnt, meshPos, model);
// errorChain(shaderBind(shader));
// errorChain(shaderSetMatrix(shader, SHADER_UNLIT_PROJECTION, proj));
// errorChain(shaderSetMatrix(shader, SHADER_UNLIT_VIEW, view));
// errorChain(shaderSetMatrix(shader, SHADER_UNLIT_MODEL, model));
// errorChain(shaderSetMaterial(shader, material));
// errorChain(meshDraw(mesh, 0, -1));
// }
// }
// }
// }
// glm_ortho(
// 0.0f, SCREEN.width,
// SCREEN.height, 0.0f,
// 0.1f, 100.0f,
// proj
// );
// glm_lookat(
// (vec3){ 0.0f, 0.0f, 1.0f },
// (vec3){ 0.0f, 0.0f, 0.0f },
// (vec3){ 0.0f, 1.0f, 0.0f },
// view
// );
// glm_mat4_identity(model);
// errorChain(shaderBind(&SHADER_UNLIT));
// errorChain(shaderSetMatrix(&SHADER_UNLIT, SHADER_UNLIT_PROJECTION, proj));
// errorChain(shaderSetMatrix(&SHADER_UNLIT, SHADER_UNLIT_VIEW, view));
// errorChain(shaderSetMatrix(&SHADER_UNLIT, SHADER_UNLIT_MODEL, model));
// {
// entityid_t sprEnts[ENTITY_COUNT_MAX];
// componentid_t sprComps[ENTITY_COUNT_MAX];
// entityid_t sprCount = componentGetEntitiesWithComponent(
// COMPONENT_TYPE_SPRITE, sprEnts, sprComps
// );
// for(entityid_t si = 0; si < sprCount; si++) {
// entitysprite_t *spr = entitySpriteGet(sprEnts[si], sprComps[si]);
// vec3 pos = { 0.0f, 0.0f, 0.0f };
// componentid_t posComp = entityGetComponent(
// sprEnts[si], COMPONENT_TYPE_POSITION
// );
// if(posComp != 0xFF) {
// entityPositionGetPosition(sprEnts[si], posComp, pos);
// }
// errorChain(shaderSetTexture(
// &SHADER_UNLIT, SHADER_UNLIT_TEXTURE, spr->texture
// ));
// #if !MESH_ENABLE_COLOR
// errorChain(shaderSetColor(
// &SHADER_UNLIT, SHADER_UNLIT_COLOR, spr->color
// ));
// #endif
// errorChain(spriteBatchPush(
// pos[0], pos[1],
// pos[0] + spr->width, pos[1] + spr->height,
// #if MESH_ENABLE_COLOR
// spr->color,
// #endif
// spr->uv[0], spr->uv[1],
// spr->uv[2], spr->uv[3]
// ));
// errorChain(spriteBatchFlush());
// }
// }
// errorChain(consoleDraw());
// // FPS
// char_t fpsText[32];
// dusktimeepoch_t now = timeGetEpoch();
// double_t delta = now.time - LAST.time;
// LAST = now;
// double_t fps = delta > 0 ? 1.0 / delta : 0.0;
// snprintf(fpsText, sizeof(fpsText), "FPS: %.2f", fps);
// errorChain(spriteBatchFlush());
// errorChain(textDraw(
// 0, 0,
// fpsText, COLOR_WHITE,
// &FONT_TILESET_DEFAULT, &FONT_TEXTURE_DEFAULT
// ));
// errorChain(spriteBatchFlush());
// errorOk();
} }
errorret_t sceneSetImmediate(const char_t *scene) { errorret_t sceneSetImmediate(const char_t *scene) {
@@ -292,6 +177,7 @@ errorret_t sceneSetImmediate(const char_t *scene) {
SCENE.sceneActive = false; SCENE.sceneActive = false;
} }
moduleCutsceneReset();
moduleSceneReset(); moduleSceneReset();
stringCopy( stringCopy(
-1
View File
@@ -8,7 +8,6 @@
#pragma once #pragma once
#include "script/scriptmanager.h" #include "script/scriptmanager.h"
#include "asset/assetfile.h" #include "asset/assetfile.h"
#include "event/event.h"
#define SCENE_EVENT_UPDATE_MAX 16 #define SCENE_EVENT_UPDATE_MAX 16
@@ -0,0 +1,293 @@
// 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 "animation/animation.h"
#include "util/memory.h"
static scriptproto_t MODULE_ANIMATION_PROTO;
static inline animation_t *moduleAnimationGet(
const jerry_call_info_t *callInfo
) {
return (animation_t *)scriptProtoGetValue(
&MODULE_ANIMATION_PROTO, callInfo->this_value
);
}
// Extracts an easingtype_t from a JS value. Accepts either a plain number
// or an Easing.xxx function (which has a .type numeric property).
static easingtype_t moduleAnimationReadEasing(
const jerry_value_t val
) {
if(jerry_value_is_number(val)) {
return (easingtype_t)(uint32_t)jerry_value_as_number(val);
}
if(jerry_value_is_object(val) || jerry_value_is_function(val)) {
jerry_value_t key = jerry_string_sz("type");
jerry_value_t typeVal = jerry_object_get(val, key);
jerry_value_free(key);
easingtype_t result = EASING_LINEAR;
if(jerry_value_is_number(typeVal)) {
result = (easingtype_t)(uint32_t)jerry_value_as_number(typeVal);
}
jerry_value_free(typeVal);
return result;
}
return EASING_LINEAR;
}
// Fires onReach callbacks for any keyframes crossed between prevTime and
// newTime. Returns an exception value on error, or jerry_undefined().
static jerry_value_t moduleAnimationFireKeyframes(
const jerry_value_t thisVal,
float_t prevTime,
float_t newTime
) {
jerry_value_t kfKey = jerry_string_sz("_keyframes");
jerry_value_t kfArr = jerry_object_get(thisVal, kfKey);
jerry_value_free(kfKey);
if(!jerry_value_is_array(kfArr)) {
jerry_value_free(kfArr);
return jerry_undefined();
}
jerry_length_t len = jerry_array_length(kfArr);
for(jerry_length_t i = 0; i < len; i++) {
jerry_value_t kfObj = jerry_object_get_index(kfArr, i);
jerry_value_t tKey = jerry_string_sz("time");
jerry_value_t tVal = jerry_object_get(kfObj, tKey);
jerry_value_free(tKey);
float_t kfTime = (float_t)jerry_value_as_number(tVal);
jerry_value_free(tVal);
if(prevTime < kfTime && newTime >= kfTime) {
jerry_value_t cbKey = jerry_string_sz("onReach");
jerry_value_t cb = jerry_object_get(kfObj, cbKey);
jerry_value_free(cbKey);
if(jerry_value_is_function(cb)) {
jerry_value_t r = jerry_call(cb, thisVal, NULL, 0);
jerry_value_free(cb);
if(jerry_value_is_exception(r)) {
jerry_value_free(kfObj);
jerry_value_free(kfArr);
return r;
}
jerry_value_free(r);
} else {
jerry_value_free(cb);
}
}
jerry_value_free(kfObj);
}
jerry_value_free(kfArr);
return jerry_undefined();
}
// Fires onComplete once. Returns an exception on error or jerry_undefined().
static jerry_value_t moduleAnimationFireComplete(
const jerry_value_t thisVal
) {
jerry_value_t firedKey = jerry_string_sz("_completeFired");
jerry_value_t firedVal = jerry_object_get(thisVal, firedKey);
bool_t alreadyFired = jerry_value_is_true(firedVal);
jerry_value_free(firedVal);
if(alreadyFired) {
jerry_value_free(firedKey);
return jerry_undefined();
}
jerry_value_t trueVal = jerry_boolean(true);
jerry_object_set(thisVal, firedKey, trueVal);
jerry_value_free(firedKey);
jerry_value_free(trueVal);
jerry_value_t cbKey = jerry_string_sz("onComplete");
jerry_value_t cb = jerry_object_get(thisVal, cbKey);
jerry_value_free(cbKey);
if(jerry_value_is_function(cb)) {
jerry_value_t r = jerry_call(cb, thisVal, NULL, 0);
jerry_value_free(cb);
if(jerry_value_is_exception(r)) return r;
jerry_value_free(r);
} else {
jerry_value_free(cb);
}
return jerry_undefined();
}
moduleBaseFunction(moduleAnimationConstructor) {
animation_t *anim = (animation_t *)memoryAllocate(sizeof(animation_t));
animationInit(anim);
if(argc > 0 && jerry_value_is_boolean(args[argc - 1])) {
anim->loop = jerry_value_is_true(args[argc - 1]);
}
if(argc > 0 && jerry_value_is_array(args[0])) {
jerry_length_t len = jerry_array_length(args[0]);
for(jerry_length_t i = 0; i < len; i++) {
jerry_value_t kf = jerry_object_get_index(args[0], i);
jerry_value_t tKey = jerry_string_sz("time");
jerry_value_t vKey = jerry_string_sz("value");
jerry_value_t eKey = jerry_string_sz("easing");
float_t t = (float_t)jerry_value_as_number(
jerry_object_get(kf, tKey)
);
float_t v = (float_t)jerry_value_as_number(
jerry_object_get(kf, vKey)
);
jerry_value_t eVal = jerry_object_get(kf, eKey);
easingtype_t e = moduleAnimationReadEasing(eVal);
jerry_value_free(tKey);
jerry_value_free(vKey);
jerry_value_free(eKey);
jerry_value_free(eVal);
jerry_value_free(kf);
animationAddKeyframe(anim, t, v, e);
}
// Store the JS keyframes array for onReach callback detection.
jerry_value_t kfKey = jerry_string_sz("_keyframes");
jerry_object_set(callInfo->this_value, kfKey, args[0]);
jerry_value_free(kfKey);
}
jerry_value_t firedKey = jerry_string_sz("_completeFired");
jerry_value_t falseVal = jerry_boolean(false);
jerry_object_set(callInfo->this_value, firedKey, falseVal);
jerry_value_free(firedKey);
jerry_value_free(falseVal);
jerry_object_set_native_ptr(
callInfo->this_value, &MODULE_ANIMATION_PROTO.info, anim
);
return jerry_undefined();
}
moduleBaseFunction(moduleAnimationUpdate) {
animation_t *anim = moduleAnimationGet(callInfo);
if(!anim) return moduleBaseThrow("Invalid Animation instance");
if(argc < 1 || !jerry_value_is_number(args[0])) {
return moduleBaseThrow("update() expects a number delta");
}
float_t prevTime = anim->time;
float_t delta = (float_t)jerry_value_as_number(args[0]);
float_t value = animationUpdate(anim, delta);
jerry_value_t kfResult = moduleAnimationFireKeyframes(
callInfo->this_value, prevTime, anim->time
);
if(jerry_value_is_exception(kfResult)) return kfResult;
jerry_value_free(kfResult);
if(anim->complete) {
jerry_value_t cResult = moduleAnimationFireComplete(
callInfo->this_value
);
if(jerry_value_is_exception(cResult)) return cResult;
jerry_value_free(cResult);
}
return jerry_number((double)value);
}
moduleBaseFunction(moduleAnimationGetValue) {
animation_t *anim = moduleAnimationGet(callInfo);
if(!anim) return moduleBaseThrow("Invalid Animation instance");
return jerry_number((double)animationGetValue(anim));
}
moduleBaseFunction(moduleAnimationReset) {
animation_t *anim = moduleAnimationGet(callInfo);
if(!anim) return moduleBaseThrow("Invalid Animation instance");
animationReset(anim);
jerry_value_t key = jerry_string_sz("_completeFired");
jerry_value_t falseVal = jerry_boolean(false);
jerry_object_set(callInfo->this_value, key, falseVal);
jerry_value_free(key);
jerry_value_free(falseVal);
return jerry_undefined();
}
moduleBaseFunction(moduleAnimationGetComplete) {
animation_t *anim = moduleAnimationGet(callInfo);
return anim ? jerry_boolean(anim->complete) : jerry_boolean(false);
}
moduleBaseFunction(moduleAnimationGetLoop) {
animation_t *anim = moduleAnimationGet(callInfo);
return anim ? jerry_boolean(anim->loop) : jerry_boolean(false);
}
moduleBaseFunction(moduleAnimationSetLoop) {
animation_t *anim = moduleAnimationGet(callInfo);
if(!anim) return moduleBaseThrow("Invalid Animation instance");
if(argc < 1) return moduleBaseThrow("Expected boolean");
anim->loop = jerry_value_is_true(args[0]);
return jerry_undefined();
}
moduleBaseFunction(moduleAnimationGetTime) {
animation_t *anim = moduleAnimationGet(callInfo);
return anim ? jerry_number((double)anim->time) : jerry_number(0.0);
}
moduleBaseFunction(moduleAnimationGetDuration) {
animation_t *anim = moduleAnimationGet(callInfo);
return anim ? jerry_number((double)anim->duration) : jerry_number(0.0);
}
static void moduleAnimation(void) {
scriptProtoInit(
&MODULE_ANIMATION_PROTO,
"Animation",
sizeof(animation_t),
moduleAnimationConstructor
);
scriptProtoDefineFunc(
&MODULE_ANIMATION_PROTO, "update", moduleAnimationUpdate
);
scriptProtoDefineFunc(
&MODULE_ANIMATION_PROTO, "getValue", moduleAnimationGetValue
);
scriptProtoDefineFunc(
&MODULE_ANIMATION_PROTO, "reset", moduleAnimationReset
);
scriptProtoDefineProp(
&MODULE_ANIMATION_PROTO, "complete",
moduleAnimationGetComplete, NULL
);
scriptProtoDefineProp(
&MODULE_ANIMATION_PROTO, "loop",
moduleAnimationGetLoop, moduleAnimationSetLoop
);
scriptProtoDefineProp(
&MODULE_ANIMATION_PROTO, "time",
moduleAnimationGetTime, NULL
);
scriptProtoDefineProp(
&MODULE_ANIMATION_PROTO, "duration",
moduleAnimationGetDuration, NULL
);
}
@@ -0,0 +1,70 @@
// 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 "animation/easing.h"
static scriptproto_t MODULE_EASING_PROTO;
// Generate one handler per easing curve.
#define EASING_MODULE_TABLE \
X(Linear, "linear", EASING_LINEAR, easingLinear) \
X(InSine, "inSine", EASING_IN_SINE, easingInSine) \
X(OutSine, "outSine", EASING_OUT_SINE, easingOutSine) \
X(InOutSine, "inOutSine", EASING_IN_OUT_SINE, easingInOutSine) \
X(InQuad, "inQuad", EASING_IN_QUAD, easingInQuad) \
X(OutQuad, "outQuad", EASING_OUT_QUAD, easingOutQuad) \
X(InOutQuad, "inOutQuad", EASING_IN_OUT_QUAD, easingInOutQuad) \
X(InCubic, "inCubic", EASING_IN_CUBIC, easingInCubic) \
X(OutCubic, "outCubic", EASING_OUT_CUBIC, easingOutCubic) \
X(InOutCubic, "inOutCubic", EASING_IN_OUT_CUBIC, easingInOutCubic) \
X(InQuart, "inQuart", EASING_IN_QUART, easingInQuart) \
X(OutQuart, "outQuart", EASING_OUT_QUART, easingOutQuart) \
X(InOutQuart, "inOutQuart", EASING_IN_OUT_QUART, easingInOutQuart) \
X(InBack, "inBack", EASING_IN_BACK, easingInBack) \
X(OutBack, "outBack", EASING_OUT_BACK, easingOutBack) \
X(InOutBack, "inOutBack", EASING_IN_OUT_BACK, easingInOutBack)
#define X(CName, jsName, type, fn) \
moduleBaseFunction(moduleEasing##CName) { \
if(argc < 1 || !jerry_value_is_number(args[0])) { \
return moduleBaseThrow("Expected number t"); \
} \
float_t t = (float_t)jerry_value_as_number(args[0]); \
return jerry_number((double)fn(t)); \
}
EASING_MODULE_TABLE
#undef X
// Adds a callable easing function with a .type property to the Easing object.
static void moduleEasingRegister(
const char_t *jsName,
uint8_t type,
jerry_external_handler_t fn
) {
jerry_value_t fnVal = jerry_function_external(fn);
jerry_value_t typeKey = jerry_string_sz("type");
jerry_value_t typeVal = jerry_number((double)type);
jerry_object_set(fnVal, typeKey, typeVal);
jerry_value_free(typeKey);
jerry_value_free(typeVal);
jerry_value_t nameKey = jerry_string_sz(jsName);
jerry_object_set(MODULE_EASING_PROTO.prototype, nameKey, fnVal);
jerry_value_free(nameKey);
jerry_value_free(fnVal);
}
static void moduleEasing(void) {
scriptProtoInit(&MODULE_EASING_PROTO, "Easing", sizeof(uint8_t), NULL);
#define X(CName, jsName, type, fn) \
moduleEasingRegister(jsName, type, moduleEasing##CName);
EASING_MODULE_TABLE
#undef X
}
@@ -43,7 +43,7 @@ moduleBaseFunction(moduleConsoleGetVisible) {
} }
moduleBaseFunction(moduleConsoleSetVisible) { moduleBaseFunction(moduleConsoleSetVisible) {
moduleBaseRequireArgs(1); if(argc < 1) return moduleBaseThrow("Expected at least 1 argument");
if(!jerry_value_is_boolean(args[0])) { if(!jerry_value_is_boolean(args[0])) {
return moduleBaseThrow("Console.visible: expected boolean"); return moduleBaseThrow("Console.visible: expected boolean");
} }
@@ -0,0 +1,204 @@
// 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 "cutscene/cutscene.h"
#include "error/error.h"
static scriptproto_t MODULE_CUTSCENE_PROTO;
/**
* Disposes the active JS cutscene without firing the then callback.
* Safe to call when no cutscene is active.
*/
static void moduleCutsceneReset(void) {
if(CUTSCENE.activeRef == CUTSCENE_SCRIPT_REF_NONE) return;
jerry_value_t key = jerry_string_sz("dispose");
jerry_value_t fn = jerry_object_get(CUTSCENE.activeRef, key);
jerry_value_free(key);
if(jerry_value_is_function(fn)) {
jerry_value_t result = jerry_call(fn, CUTSCENE.activeRef, NULL, 0);
jerry_value_free(fn);
jerry_value_free(result);
} else {
jerry_value_free(fn);
}
jerry_value_free(CUTSCENE.activeRef);
CUTSCENE.activeRef = CUTSCENE_SCRIPT_REF_NONE;
}
/**
* Ticks the active JS cutscene. Called from cutsceneUpdate().
*
* @return Any error state that happened.
*/
static errorret_t moduleCutsceneUpdate(void) {
if(CUTSCENE.activeRef == CUTSCENE_SCRIPT_REF_NONE) errorOk();
jerry_value_t key = jerry_string_sz("update");
jerry_value_t fn = jerry_object_get(CUTSCENE.activeRef, key);
jerry_value_free(key);
if(!jerry_value_is_function(fn)) {
jerry_value_free(fn);
errorOk();
}
jerry_value_t result = jerry_call(fn, CUTSCENE.activeRef, NULL, 0);
jerry_value_free(fn);
if(jerry_value_is_exception(result)) {
char_t errMsg[512];
moduleBaseExceptionMessage(result, errMsg, sizeof(errMsg));
jerry_value_free(result);
errorThrow("Cutscene update failed: %s", errMsg);
}
jerry_value_free(result);
errorOk();
}
moduleBaseFunction(moduleCutsceneDefaultConstructor) {
return jerry_undefined();
}
moduleBaseFunction(moduleCutsceneDefaultUpdate) {
return jerry_undefined();
}
moduleBaseFunction(moduleCutsceneDefaultDispose) {
return jerry_undefined();
}
/**
* cutscene.then(callback)
*
* Sets the completion callback fired when Cutscene.finish() is called.
* Only one callback may be set; calling then() again replaces it.
* Returns the instance for optional chaining.
*/
moduleBaseFunction(moduleCutsceneThen) {
if(argc < 1 || !jerry_value_is_function(args[0])) {
return moduleBaseThrow("then() expects a function argument");
}
jerry_value_t key = jerry_string_sz("_onFinished");
jerry_object_set(callInfo->this_value, key, args[0]);
jerry_value_free(key);
return jerry_value_copy(callInfo->this_value);
}
/**
* Cutscene.play(instance)
*
* Registers a cutscene instance with the engine and begins calling its
* update() each tick. Stops any currently active cutscene first.
* Returns the instance to allow chaining .then() directly.
*/
moduleBaseFunction(moduleCutscenePlay) {
if(argc < 1 || !jerry_value_is_object(args[0])) {
return moduleBaseThrow("play() expects a cutscene instance");
}
if(CUTSCENE.activeRef != CUTSCENE_SCRIPT_REF_NONE) {
moduleCutsceneReset();
}
CUTSCENE.activeRef = jerry_value_copy(args[0]);
return jerry_value_copy(args[0]);
}
/**
* Cutscene.finish()
*
* Marks the active cutscene as complete: calls dispose(), clears the
* active ref, then fires the then() callback if one was set.
* Intended to be called from within the cutscene's own update().
*/
moduleBaseFunction(moduleCutsceneFinish) {
if(CUTSCENE.activeRef == CUTSCENE_SCRIPT_REF_NONE) {
return jerry_undefined();
}
jerry_value_t cbKey = jerry_string_sz("_onFinished");
jerry_value_t callback = jerry_object_get(CUTSCENE.activeRef, cbKey);
jerry_value_free(cbKey);
jerry_value_t dispKey = jerry_string_sz("dispose");
jerry_value_t dispFn = jerry_object_get(CUTSCENE.activeRef, dispKey);
jerry_value_free(dispKey);
if(jerry_value_is_function(dispFn)) {
jerry_value_t r = jerry_call(dispFn, CUTSCENE.activeRef, NULL, 0);
jerry_value_free(dispFn);
if(jerry_value_is_exception(r)) {
jerry_value_free(callback);
return r;
}
jerry_value_free(r);
} else {
jerry_value_free(dispFn);
}
jerry_value_free(CUTSCENE.activeRef);
CUTSCENE.activeRef = CUTSCENE_SCRIPT_REF_NONE;
if(jerry_value_is_function(callback)) {
jerry_value_t r = jerry_call(callback, jerry_undefined(), NULL, 0);
jerry_value_free(callback);
if(jerry_value_is_exception(r)) return r;
jerry_value_free(r);
} else {
jerry_value_free(callback);
}
return jerry_undefined();
}
/**
* Cutscene.stop()
*
* Immediately disposes the active cutscene without firing the then
* callback. No-op when no cutscene is active.
*/
moduleBaseFunction(moduleCutsceneStop) {
moduleCutsceneReset();
return jerry_undefined();
}
static void moduleCutscene(void) {
scriptProtoInit(
&MODULE_CUTSCENE_PROTO,
"Cutscene",
sizeof(uint8_t),
moduleCutsceneDefaultConstructor
);
scriptProtoDefineFunc(
&MODULE_CUTSCENE_PROTO, "update", moduleCutsceneDefaultUpdate
);
scriptProtoDefineFunc(
&MODULE_CUTSCENE_PROTO, "dispose", moduleCutsceneDefaultDispose
);
scriptProtoDefineFunc(
&MODULE_CUTSCENE_PROTO, "then", moduleCutsceneThen
);
scriptProtoDefineStaticFunc(
&MODULE_CUTSCENE_PROTO, "play", moduleCutscenePlay
);
scriptProtoDefineStaticFunc(
&MODULE_CUTSCENE_PROTO, "finish", moduleCutsceneFinish
);
scriptProtoDefineStaticFunc(
&MODULE_CUTSCENE_PROTO, "stop", moduleCutsceneStop
);
}
+20 -4
View File
@@ -42,7 +42,11 @@ moduleBaseFunction(moduleColorGetA) {
} }
moduleBaseFunction(moduleColorSetR) { moduleBaseFunction(moduleColorSetR) {
moduleBaseRequireArgs(1); moduleBaseRequireNumber(0); if(argc < 1) {
return moduleBaseThrow("Expected at least 1 argument");
}
moduleBaseRequireNumber(0);
color_t *c = moduleColorGet(callInfo); color_t *c = moduleColorGet(callInfo);
if(!c) return jerry_undefined(); if(!c) return jerry_undefined();
c->r = (colorchannel8_t)jerry_value_as_number(args[0]); c->r = (colorchannel8_t)jerry_value_as_number(args[0]);
@@ -50,7 +54,11 @@ moduleBaseFunction(moduleColorSetR) {
} }
moduleBaseFunction(moduleColorSetG) { moduleBaseFunction(moduleColorSetG) {
moduleBaseRequireArgs(1); moduleBaseRequireNumber(0); if(argc < 1) {
return moduleBaseThrow("Expected at least 1 argument");
}
moduleBaseRequireNumber(0);
color_t *c = moduleColorGet(callInfo); color_t *c = moduleColorGet(callInfo);
if(!c) return jerry_undefined(); if(!c) return jerry_undefined();
c->g = (colorchannel8_t)jerry_value_as_number(args[0]); c->g = (colorchannel8_t)jerry_value_as_number(args[0]);
@@ -58,7 +66,11 @@ moduleBaseFunction(moduleColorSetG) {
} }
moduleBaseFunction(moduleColorSetB) { moduleBaseFunction(moduleColorSetB) {
moduleBaseRequireArgs(1); moduleBaseRequireNumber(0); if(argc < 1) {
return moduleBaseThrow("Expected at least 1 argument");
}
moduleBaseRequireNumber(0);
color_t *c = moduleColorGet(callInfo); color_t *c = moduleColorGet(callInfo);
if(!c) return jerry_undefined(); if(!c) return jerry_undefined();
c->b = (colorchannel8_t)jerry_value_as_number(args[0]); c->b = (colorchannel8_t)jerry_value_as_number(args[0]);
@@ -66,7 +78,11 @@ moduleBaseFunction(moduleColorSetB) {
} }
moduleBaseFunction(moduleColorSetA) { moduleBaseFunction(moduleColorSetA) {
moduleBaseRequireArgs(1); moduleBaseRequireNumber(0); if(argc < 1) {
return moduleBaseThrow("Expected at least 1 argument");
}
moduleBaseRequireNumber(0);
color_t *c = moduleColorGet(callInfo); color_t *c = moduleColorGet(callInfo);
if(!c) return jerry_undefined(); if(!c) return jerry_undefined();
c->a = (colorchannel8_t)jerry_value_as_number(args[0]); c->a = (colorchannel8_t)jerry_value_as_number(args[0]);
+452
View File
@@ -0,0 +1,452 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "script/module/modulebase.h"
#include "script/scriptproto.h"
#include "script/module/math/modulevec3ref.h"
#include "display/mesh/mesh.h"
#include "display/mesh/cube.h"
#include "display/mesh/quad.h"
#include "display/mesh/sphere.h"
#include "display/mesh/plane.h"
#include "display/mesh/capsule.h"
#include "display/mesh/triprism.h"
typedef struct {
mesh_t mesh;
meshvertex_t *vertices;
int32_t vertexCount;
bool_t initialized;
} meshscript_t;
typedef struct {
meshvertex_t *vertex;
} meshvertexscript_t;
static scriptproto_t MODULE_MESH_PROTO;
static scriptproto_t MODULE_MESH_VERTEX_PROTO;
static inline meshscript_t * moduleMeshGet(
const jerry_call_info_t *callInfo
) {
return (meshscript_t*)scriptProtoGetValue(
&MODULE_MESH_PROTO, callInfo->this_value
);
}
static inline meshscript_t * moduleMeshFrom(const jerry_value_t val) {
return (meshscript_t*)scriptProtoGetValue(&MODULE_MESH_PROTO, val);
}
static void moduleMeshFreeData(
void *ptr,
jerry_object_native_info_t *info
) {
(void)info;
meshscript_t *ms = (meshscript_t*)ptr;
if(ms->initialized) (void)meshDispose(&ms->mesh);
if(ms->vertices) memoryFree(ms->vertices);
memoryFree(ptr);
}
// Creates a new JS Mesh object from an already-filled heap-allocated meshscript_t.
static jerry_value_t moduleMeshWrapNew(meshscript_t *ms) {
jerry_value_t obj = jerry_object();
jerry_object_set_native_ptr(obj, &MODULE_MESH_PROTO.info, ms);
jerry_object_set_proto(obj, MODULE_MESH_PROTO.prototype);
jerry_value_t arr = jerry_array((jerry_length_t)ms->vertexCount);
for(int32_t i = 0; i < ms->vertexCount; i++) {
meshvertexscript_t mv = { .vertex = &ms->vertices[i] };
jerry_value_t vobj = scriptProtoCreateValue(&MODULE_MESH_VERTEX_PROTO, &mv);
jerry_value_t res = jerry_object_set_index(arr, (uint32_t)i, vobj);
jerry_value_free(res);
jerry_value_free(vobj);
}
jerry_value_t key = jerry_string_sz("_verts");
jerry_object_set(obj, key, arr);
jerry_value_free(key);
jerry_value_free(arr);
return obj;
}
// Allocates a meshscript_t for the given vertex count and buffers it.
// Caller is responsible for populating ms->vertices before calling meshInit.
static meshscript_t * moduleMeshAlloc(const int32_t vertexCount) {
meshscript_t *ms = (meshscript_t*)memoryAllocate(sizeof(meshscript_t));
memoryZero(ms, sizeof(meshscript_t));
ms->vertices = (meshvertex_t*)memoryAllocate(
(size_t)vertexCount * sizeof(meshvertex_t)
);
memoryZero(ms->vertices, (size_t)vertexCount * sizeof(meshvertex_t));
ms->vertexCount = vertexCount;
return ms;
}
// Finalizes a meshscript_t: uploads to GPU and wraps in a JS object.
static jerry_value_t moduleMeshFinalize(
meshscript_t *ms,
const meshprimitivetype_t type
) {
(void)meshInit(&ms->mesh, type, ms->vertexCount, ms->vertices);
ms->initialized = true;
return moduleMeshWrapNew(ms);
}
// ---- MeshVertex ----
moduleBaseFunction(moduleMeshVertexGetPosition) {
meshvertexscript_t *mv = (meshvertexscript_t*)scriptProtoGetValue(
&MODULE_MESH_VERTEX_PROTO, callInfo->this_value
);
if(!mv) return jerry_undefined();
return moduleVec3RefPush(mv->vertex->pos, NULL, NULL);
}
moduleBaseFunction(moduleMeshVertexSetPosition) {
if(argc < 1) return moduleBaseThrow("Expected position argument");
meshvertexscript_t *mv = (meshvertexscript_t*)scriptProtoGetValue(
&MODULE_MESH_VERTEX_PROTO, callInfo->this_value
);
if(!mv) return jerry_undefined();
vec3 v;
if(!moduleVec3AnyCheck(args[0], v)) return moduleBaseThrow("Expected Vec3");
glm_vec3_copy(v, mv->vertex->pos);
return jerry_undefined();
}
// ---- Mesh instance ----
moduleBaseFunction(moduleMeshConstructor) {
if(argc < 1) return moduleBaseThrow("Expected vertex count");
moduleBaseRequireNumber(0);
int32_t vertexCount = (int32_t)jerry_value_as_number(args[0]);
if(vertexCount <= 0) return moduleBaseThrow("Vertex count must be > 0");
meshscript_t *ms = moduleMeshAlloc(vertexCount);
jerry_object_set_native_ptr(
callInfo->this_value, &MODULE_MESH_PROTO.info, ms
);
jerry_value_t arr = jerry_array((jerry_length_t)vertexCount);
for(int32_t i = 0; i < vertexCount; i++) {
meshvertexscript_t mv = { .vertex = &ms->vertices[i] };
jerry_value_t vobj = scriptProtoCreateValue(&MODULE_MESH_VERTEX_PROTO, &mv);
jerry_value_t res = jerry_object_set_index(arr, (uint32_t)i, vobj);
jerry_value_free(res);
jerry_value_free(vobj);
}
jerry_value_t key = jerry_string_sz("_verts");
jerry_object_set(callInfo->this_value, key, arr);
jerry_value_free(key);
jerry_value_free(arr);
return jerry_undefined();
}
moduleBaseFunction(moduleMeshGetVertices) {
jerry_value_t key = jerry_string_sz("_verts");
jerry_value_t arr = jerry_object_get(callInfo->this_value, key);
jerry_value_free(key);
return arr;
}
moduleBaseFunction(moduleMeshGetVertexCount) {
meshscript_t *ms = moduleMeshGet(callInfo);
if(!ms) return jerry_undefined();
return jerry_number((double)ms->vertexCount);
}
moduleBaseFunction(moduleMeshFlush) {
meshscript_t *ms = moduleMeshGet(callInfo);
if(!ms) return jerry_undefined();
if(!ms->initialized) {
(void)meshInit(
&ms->mesh,
MESH_PRIMITIVE_TYPE_TRIANGLES,
ms->vertexCount,
ms->vertices
);
ms->initialized = true;
} else {
(void)meshFlush(&ms->mesh, 0, -1);
}
return jerry_undefined();
}
moduleBaseFunction(moduleMeshDispose) {
meshscript_t *ms = moduleMeshGet(callInfo);
if(!ms) return jerry_undefined();
if(ms->initialized) {
(void)meshDispose(&ms->mesh);
ms->initialized = false;
}
if(ms->vertices) {
memoryFree(ms->vertices);
ms->vertices = NULL;
}
return jerry_undefined();
}
moduleBaseFunction(moduleMeshToString) {
meshscript_t *ms = moduleMeshGet(callInfo);
if(!ms) return jerry_string_sz("Mesh(?)");
char_t buf[64];
stringFormat(buf, sizeof(buf), "Mesh(%d)", ms->vertexCount);
return jerry_string_sz(buf);
}
// ---- Static defaults (engine-owned, not GC'd) ----
moduleBaseFunction(moduleMeshDefaultCube) {
return moduleBaseWrapPointer(&CUBE_MESH_SIMPLE);
}
moduleBaseFunction(moduleMeshDefaultQuad) {
return moduleBaseWrapPointer(&QUAD_MESH_SIMPLE);
}
moduleBaseFunction(moduleMeshDefaultSphere) {
return moduleBaseWrapPointer(&SPHERE_MESH_SIMPLE);
}
moduleBaseFunction(moduleMeshDefaultPlane) {
return moduleBaseWrapPointer(&PLANE_MESH_SIMPLE);
}
moduleBaseFunction(moduleMeshDefaultCapsule) {
return moduleBaseWrapPointer(&CAPSULE_MESH_SIMPLE);
}
moduleBaseFunction(moduleMeshDefaultTriPrism) {
return moduleBaseWrapPointer(&TRIPRISM_MESH_SIMPLE);
}
// ---- Static factory methods ----
// Mesh.createCube(min?, max?) — defaults to (-0.5,-0.5,-0.5)..(0.5,0.5,0.5)
moduleBaseFunction(moduleMeshCreateCube) {
vec3 min = { -0.5f, -0.5f, -0.5f };
vec3 max = { 0.5f, 0.5f, 0.5f };
if(argc >= 2) {
if(!moduleVec3AnyCheck(args[0], min)) {
return moduleBaseThrow("Mesh.createCube: expected Vec3 min");
}
if(!moduleVec3AnyCheck(args[1], max)) {
return moduleBaseThrow("Mesh.createCube: expected Vec3 max");
}
}
meshscript_t *ms = moduleMeshAlloc(CUBE_VERTEX_COUNT);
cubeBuffer(
ms->vertices, min, max
#if MESH_ENABLE_COLOR
, COLOR_WHITE_4B
#endif
);
return moduleMeshFinalize(ms, CUBE_PRIMITIVE_TYPE);
}
// Mesh.createQuad(minX, minY, maxX, maxY) — 2D XY quad, u0-u1=0-1 v0-v1=0-1
moduleBaseFunction(moduleMeshCreateQuad) {
float_t minX = -0.5f, minY = -0.5f, maxX = 0.5f, maxY = 0.5f;
if(argc >= 4) {
if(!jerry_value_is_number(args[0]) || !jerry_value_is_number(args[1]) ||
!jerry_value_is_number(args[2]) || !jerry_value_is_number(args[3])) {
return moduleBaseThrow("Mesh.createQuad: expected (minX, minY, maxX, maxY)");
}
minX = (float_t)jerry_value_as_number(args[0]);
minY = (float_t)jerry_value_as_number(args[1]);
maxX = (float_t)jerry_value_as_number(args[2]);
maxY = (float_t)jerry_value_as_number(args[3]);
}
meshscript_t *ms = moduleMeshAlloc(QUAD_VERTEX_COUNT);
quadBuffer(
ms->vertices,
minX, minY, maxX, maxY,
0.0f, 0.0f, 1.0f, 1.0f
#if MESH_ENABLE_COLOR
, COLOR_WHITE_4B
#endif
);
return moduleMeshFinalize(ms, QUAD_PRIMITIVE_TYPE);
}
// Mesh.createSphere(radius?, stacks?, sectors?)
moduleBaseFunction(moduleMeshCreateSphere) {
float_t radius = 0.5f;
int32_t stacks = SPHERE_STACKS;
int32_t sectors = SPHERE_SECTORS;
if(argc >= 1 && jerry_value_is_number(args[0])) {
radius = (float_t)jerry_value_as_number(args[0]);
}
if(argc >= 2 && jerry_value_is_number(args[1])) {
stacks = (int32_t)jerry_value_as_number(args[1]);
}
if(argc >= 3 && jerry_value_is_number(args[2])) {
sectors = (int32_t)jerry_value_as_number(args[2]);
}
int32_t vertexCount = stacks * sectors * 6;
vec3 center = { 0.0f, 0.0f, 0.0f };
meshscript_t *ms = moduleMeshAlloc(vertexCount);
sphereBuffer(
ms->vertices, center, radius, stacks, sectors
#if MESH_ENABLE_COLOR
, COLOR_WHITE_4B
#endif
);
return moduleMeshFinalize(ms, SPHERE_PRIMITIVE_TYPE);
}
// Mesh.createPlane(width?, height?) — XZ-aligned, centered at origin
moduleBaseFunction(moduleMeshCreatePlane) {
float_t width = 1.0f, height = 1.0f;
if(argc >= 1 && jerry_value_is_number(args[0])) {
width = (float_t)jerry_value_as_number(args[0]);
}
if(argc >= 2 && jerry_value_is_number(args[1])) {
height = (float_t)jerry_value_as_number(args[1]);
}
vec3 min = { -width * 0.5f, 0.0f, -height * 0.5f };
vec3 max = { width * 0.5f, 0.0f, height * 0.5f };
vec2 uvMin = { 0.0f, 0.0f };
vec2 uvMax = { 1.0f, 1.0f };
meshscript_t *ms = moduleMeshAlloc(PLANE_VERTEX_COUNT);
planeBuffer(
ms->vertices, PLANE_AXIS_XZ, min, max
#if MESH_ENABLE_COLOR
, COLOR_WHITE_4B
#endif
, uvMin, uvMax
);
return moduleMeshFinalize(ms, PLANE_PRIMITIVE_TYPE);
}
// Mesh.createCapsule(radius?, halfHeight?, capRings?, sectors?)
moduleBaseFunction(moduleMeshCreateCapsule) {
float_t radius = 0.5f;
float_t halfHeight = 0.5f;
int32_t capRings = CAPSULE_CAP_RINGS;
int32_t sectors = CAPSULE_SECTORS;
if(argc >= 1 && jerry_value_is_number(args[0])) {
radius = (float_t)jerry_value_as_number(args[0]);
}
if(argc >= 2 && jerry_value_is_number(args[1])) {
halfHeight = (float_t)jerry_value_as_number(args[1]);
}
if(argc >= 3 && jerry_value_is_number(args[2])) {
capRings = (int32_t)jerry_value_as_number(args[2]);
}
if(argc >= 4 && jerry_value_is_number(args[3])) {
sectors = (int32_t)jerry_value_as_number(args[3]);
}
int32_t vertexCount = (2 * capRings + 1) * sectors * 6;
vec3 center = { 0.0f, 0.0f, 0.0f };
meshscript_t *ms = moduleMeshAlloc(vertexCount);
capsuleBuffer(
ms->vertices, center, radius, halfHeight, capRings, sectors
#if MESH_ENABLE_COLOR
, COLOR_WHITE_4B
#endif
);
return moduleMeshFinalize(ms, CAPSULE_PRIMITIVE_TYPE);
}
// Mesh.createTriPrism(x0, y0, x1, y1, x2, y2, minZ, maxZ)
moduleBaseFunction(moduleMeshCreateTriPrism) {
if(argc < 8) {
return moduleBaseThrow(
"Mesh.createTriPrism: expected (x0,y0, x1,y1, x2,y2, minZ, maxZ)"
);
}
float_t x0 = (float_t)jerry_value_as_number(args[0]);
float_t y0 = (float_t)jerry_value_as_number(args[1]);
float_t x1 = (float_t)jerry_value_as_number(args[2]);
float_t y1 = (float_t)jerry_value_as_number(args[3]);
float_t x2 = (float_t)jerry_value_as_number(args[4]);
float_t y2 = (float_t)jerry_value_as_number(args[5]);
float_t minZ = (float_t)jerry_value_as_number(args[6]);
float_t maxZ = (float_t)jerry_value_as_number(args[7]);
meshscript_t *ms = moduleMeshAlloc(TRIPRISM_VERTEX_COUNT);
triPrismBuffer(
ms->vertices, x0, y0, x1, y1, x2, y2, minZ, maxZ
#if MESH_ENABLE_COLOR
, COLOR_WHITE_4B
#endif
);
return moduleMeshFinalize(ms, TRIPRISM_PRIMITIVE_TYPE);
}
// ---- Registration ----
static void moduleMesh(void) {
// MeshVertex - internal type, no global constructor
scriptProtoInit(
&MODULE_MESH_VERTEX_PROTO, NULL,
sizeof(meshvertexscript_t), NULL
);
scriptProtoDefineProp(
&MODULE_MESH_VERTEX_PROTO, "position",
moduleMeshVertexGetPosition, moduleMeshVertexSetPosition
);
// Mesh - global constructor: new Mesh(vertexCount)
scriptProtoInit(
&MODULE_MESH_PROTO, "Mesh",
sizeof(meshscript_t), moduleMeshConstructor
);
MODULE_MESH_PROTO.info.free_cb = moduleMeshFreeData;
scriptProtoDefineToString(&MODULE_MESH_PROTO, moduleMeshToString);
scriptProtoDefineProp(
&MODULE_MESH_PROTO, "vertices",
moduleMeshGetVertices, NULL
);
scriptProtoDefineProp(
&MODULE_MESH_PROTO, "vertexCount",
moduleMeshGetVertexCount, NULL
);
scriptProtoDefineFunc(&MODULE_MESH_PROTO, "flush", moduleMeshFlush);
scriptProtoDefineFunc(&MODULE_MESH_PROTO, "dispose", moduleMeshDispose);
// Static default mesh references
scriptProtoDefineStaticProp(
&MODULE_MESH_PROTO, "DEFAULT_CUBE", moduleMeshDefaultCube, NULL
);
scriptProtoDefineStaticProp(
&MODULE_MESH_PROTO, "DEFAULT_QUAD", moduleMeshDefaultQuad, NULL
);
scriptProtoDefineStaticProp(
&MODULE_MESH_PROTO, "DEFAULT_SPHERE", moduleMeshDefaultSphere, NULL
);
scriptProtoDefineStaticProp(
&MODULE_MESH_PROTO, "DEFAULT_PLANE", moduleMeshDefaultPlane, NULL
);
scriptProtoDefineStaticProp(
&MODULE_MESH_PROTO, "DEFAULT_CAPSULE", moduleMeshDefaultCapsule, NULL
);
scriptProtoDefineStaticProp(
&MODULE_MESH_PROTO, "DEFAULT_TRIPRISM", moduleMeshDefaultTriPrism, NULL
);
// Static factory methods
scriptProtoDefineStaticFunc(
&MODULE_MESH_PROTO, "createCube", moduleMeshCreateCube
);
scriptProtoDefineStaticFunc(
&MODULE_MESH_PROTO, "createQuad", moduleMeshCreateQuad
);
scriptProtoDefineStaticFunc(
&MODULE_MESH_PROTO, "createSphere", moduleMeshCreateSphere
);
scriptProtoDefineStaticFunc(
&MODULE_MESH_PROTO, "createPlane", moduleMeshCreatePlane
);
scriptProtoDefineStaticFunc(
&MODULE_MESH_PROTO, "createCapsule", moduleMeshCreateCapsule
);
scriptProtoDefineStaticFunc(
&MODULE_MESH_PROTO, "createTriPrism", moduleMeshCreateTriPrism
);
}
@@ -0,0 +1,169 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "script/module/modulebase.h"
#include "script/scriptproto.h"
#include "script/module/display/modulecolor.h"
#include "script/module/math/modulevec2.h"
#include "script/module/math/modulevec3.h"
#include "display/spritebatch/spritebatch.h"
static scriptproto_t MODULE_SPRITEBATCH_PROTO;
moduleBaseFunction(moduleSpriteBatchGetSpriteCount) {
return jerry_number(SPRITEBATCH.spriteCount);
}
moduleBaseFunction(moduleSpriteBatchPush) {
#if MESH_ENABLE_COLOR
if(argc < 9) return moduleBaseThrow("expected 9 arguments");
#else
if(argc < 8) return moduleBaseThrow("expected 8 arguments");
#endif
moduleBaseRequireNumber(0);
moduleBaseRequireNumber(1);
moduleBaseRequireNumber(2);
moduleBaseRequireNumber(3);
#if MESH_ENABLE_COLOR
if(!jerry_value_is_object(args[4])) {
return moduleBaseThrow("color must be a Color object");
}
color_t *col = (color_t *)scriptProtoGetValue(&MODULE_COLOR_PROTO, args[4]);
if(!col) {
return moduleBaseThrow("color must be a Color object");
}
moduleBaseRequireNumber(5);
moduleBaseRequireNumber(6);
moduleBaseRequireNumber(7);
moduleBaseRequireNumber(8);
spriteBatchPush(
(float_t)jerry_value_as_number(args[0]),
(float_t)jerry_value_as_number(args[1]),
(float_t)jerry_value_as_number(args[2]),
(float_t)jerry_value_as_number(args[3]),
*col,
(float_t)jerry_value_as_number(args[5]),
(float_t)jerry_value_as_number(args[6]),
(float_t)jerry_value_as_number(args[7]),
(float_t)jerry_value_as_number(args[8])
);
#else
moduleBaseRequireNumber(4);
moduleBaseRequireNumber(5);
moduleBaseRequireNumber(6);
moduleBaseRequireNumber(7);
spriteBatchPush(
(float_t)jerry_value_as_number(args[0]),
(float_t)jerry_value_as_number(args[1]),
(float_t)jerry_value_as_number(args[2]),
(float_t)jerry_value_as_number(args[3]),
(float_t)jerry_value_as_number(args[4]),
(float_t)jerry_value_as_number(args[5]),
(float_t)jerry_value_as_number(args[6]),
(float_t)jerry_value_as_number(args[7])
);
#endif
return jerry_undefined();
}
moduleBaseFunction(moduleSpriteBatchPush3D) {
#if MESH_ENABLE_COLOR
if(argc < 5) return moduleBaseThrow("expected 5 arguments");
#else
if(argc < 4) return moduleBaseThrow("expected 4 arguments");
#endif
if(!jerry_value_is_object(args[0])) {
return moduleBaseThrow("min must be a Vec3");
}
float_t *min = moduleVec3From(args[0]);
if(!min) return moduleBaseThrow("min must be a Vec3");
if(!jerry_value_is_object(args[1])) {
return moduleBaseThrow("max must be a Vec3");
}
float_t *max = moduleVec3From(args[1]);
if(!max) return moduleBaseThrow("max must be a Vec3");
#if MESH_ENABLE_COLOR
if(!jerry_value_is_object(args[2])) {
return moduleBaseThrow("color must be a Color object");
}
color_t *col = (color_t *)scriptProtoGetValue(&MODULE_COLOR_PROTO, args[2]);
if(!col) {
return moduleBaseThrow("color must be a Color object");
}
if(!jerry_value_is_object(args[3])) {
return moduleBaseThrow("uvMin must be a Vec2");
}
float_t *uvMin = moduleVec2From(args[3]);
if(!uvMin) return moduleBaseThrow("uvMin must be a Vec2");
if(!jerry_value_is_object(args[4])) {
return moduleBaseThrow("uvMax must be a Vec2");
}
float_t *uvMax = moduleVec2From(args[4]);
if(!uvMax) return moduleBaseThrow("uvMax must be a Vec2");
spriteBatchPush3D(min, max, *col, uvMin, uvMax);
#else
if(!jerry_value_is_object(args[2])) {
return moduleBaseThrow("uvMin must be a Vec2");
}
float_t *uvMin = moduleVec2From(args[2]);
if(!uvMin) return moduleBaseThrow("uvMin must be a Vec2");
if(!jerry_value_is_object(args[3])) {
return moduleBaseThrow("uvMax must be a Vec2");
}
float_t *uvMax = moduleVec2From(args[3]);
if(!uvMax) return moduleBaseThrow("uvMax must be a Vec2");
spriteBatchPush3D(min, max, uvMin, uvMax);
#endif
return jerry_undefined();
}
moduleBaseFunction(moduleSpriteBatchClear) {
spriteBatchClear();
return jerry_undefined();
}
moduleBaseFunction(moduleSpriteBatchFlush) {
spriteBatchFlush();
return jerry_undefined();
}
static void moduleSpriteBatch(void) {
scriptProtoInit(
&MODULE_SPRITEBATCH_PROTO, "SpriteBatch", sizeof(uint8_t), NULL
);
scriptProtoDefineStaticProp(
&MODULE_SPRITEBATCH_PROTO, "spriteCount",
moduleSpriteBatchGetSpriteCount, NULL
);
scriptProtoDefineStaticFunc(
&MODULE_SPRITEBATCH_PROTO, "push", moduleSpriteBatchPush
);
scriptProtoDefineStaticFunc(
&MODULE_SPRITEBATCH_PROTO, "push3D", moduleSpriteBatchPush3D
);
scriptProtoDefineStaticFunc(
&MODULE_SPRITEBATCH_PROTO, "clear", moduleSpriteBatchClear
);
scriptProtoDefineStaticFunc(
&MODULE_SPRITEBATCH_PROTO, "flush", moduleSpriteBatchFlush
);
}
@@ -0,0 +1,77 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "script/module/modulebase.h"
#include "script/scriptproto.h"
#include "script/module/display/modulecolor.h"
#include "display/text/text.h"
static scriptproto_t MODULE_TEXT_PROTO;
moduleBaseFunction(moduleTextDraw) {
if(argc < 3) return moduleBaseThrow("expected at least 3 arguments");
moduleBaseRequireNumber(0);
moduleBaseRequireNumber(1);
if(!jerry_value_is_string(args[2])) {
return moduleBaseThrow("text must be a string");
}
float_t x = (float_t)jerry_value_as_number(args[0]);
float_t y = (float_t)jerry_value_as_number(args[1]);
char_t text[1024];
moduleBaseToString(args[2], text, sizeof(text));
color_t col = COLOR_WHITE;
if(argc >= 4 && jerry_value_is_object(args[3])) {
color_t *c = (color_t *)scriptProtoGetValue(&MODULE_COLOR_PROTO, args[3]);
if(c) col = *c;
}
errorret_t err = textDraw(
x, y, text, col, &FONT_TILESET_DEFAULT, &FONT_TEXTURE_DEFAULT
);
if(err.code != ERROR_OK) {
errorCatch(errorPrint(err));
return moduleBaseThrow("Text draw failed");
}
return jerry_undefined();
}
moduleBaseFunction(moduleTextMeasure) {
if(argc < 1) return moduleBaseThrow("expected at least 1 argument");
if(!jerry_value_is_string(args[0])) {
return moduleBaseThrow("text must be a string");
}
char_t text[1024];
moduleBaseToString(args[0], text, sizeof(text));
int32_t w, h;
textMeasure(text, &FONT_TILESET_DEFAULT, &w, &h);
jerry_value_t obj = jerry_object();
jerry_value_t wKey = jerry_string_sz("width");
jerry_value_t hKey = jerry_string_sz("height");
jerry_value_t wVal = jerry_number(w);
jerry_value_t hVal = jerry_number(h);
jerry_object_set(obj, wKey, wVal);
jerry_object_set(obj, hKey, hVal);
jerry_value_free(wKey);
jerry_value_free(hKey);
jerry_value_free(wVal);
jerry_value_free(hVal);
return obj;
}
static void moduleText(void) {
scriptProtoInit(&MODULE_TEXT_PROTO, "Text", sizeof(uint8_t), NULL);
scriptProtoDefineStaticFunc(&MODULE_TEXT_PROTO, "draw", moduleTextDraw);
scriptProtoDefineStaticFunc(&MODULE_TEXT_PROTO, "measure", moduleTextMeasure);
}
@@ -188,7 +188,9 @@ moduleBaseFunction(moduleEntityCameraSetProjectionType) {
} }
moduleBaseFunction(moduleEntityCameraAdd) { moduleBaseFunction(moduleEntityCameraAdd) {
moduleBaseRequireArgs(1); moduleBaseRequireNumber(0); if(argc < 1) return moduleBaseThrow("Expected at least 1 argument");
moduleBaseRequireNumber(0);
entityid_t id = (entityid_t)jerry_value_as_number(args[0]); entityid_t id = (entityid_t)jerry_value_as_number(args[0]);
componentid_t comp = entityAddComponent(id, COMPONENT_TYPE_CAMERA); componentid_t comp = entityAddComponent(id, COMPONENT_TYPE_CAMERA);
componenthandle_t h = { .eid = id, .cid = comp }; componenthandle_t h = { .eid = id, .cid = comp };
@@ -7,10 +7,10 @@
#pragma once #pragma once
#include "script/module/modulebase.h" #include "script/module/modulebase.h"
#include "script/module/display/modulecolor.h"
#include "script/scriptproto.h" #include "script/scriptproto.h"
#include "entity/entity.h" #include "entity/entity.h"
#include "entity/component/display/entitymaterial.h" #include "entity/component/display/entitymaterial.h"
#include "display/color.h"
#include "moduleentityposition.h" #include "moduleentityposition.h"
static scriptproto_t MODULE_ENTITY_MATERIAL_PROTO; static scriptproto_t MODULE_ENTITY_MATERIAL_PROTO;
@@ -27,48 +27,30 @@ static entitymaterial_t * moduleEntityMaterialGet(
); );
} }
moduleBaseFunction(moduleEntityMaterialGetColor) {
entitymaterial_t *mat = moduleEntityMaterialGet(callInfo);
if(!mat) return jerry_undefined();
return moduleColorMakeObject(mat->material.unlit.color);
}
moduleBaseFunction(moduleEntityMaterialSetColor) { moduleBaseFunction(moduleEntityMaterialSetColor) {
moduleBaseRequireArgs(1); if(argc < 1 || !jerry_value_is_object(args[0])) {
if(!jerry_value_is_object(args[0])) { return moduleBaseThrow("Material.color: expected color object");
return moduleBaseThrow("expected color object"); }
color_t *color = (color_t*)scriptProtoGetValue(&MODULE_COLOR_PROTO, args[0]);
if(!color) {
return moduleBaseThrow("Material.color: expected valid color object");
} }
entitymaterial_t *mat = moduleEntityMaterialGet(callInfo); entitymaterial_t *mat = moduleEntityMaterialGet(callInfo);
if(!mat) return jerry_undefined(); if(!mat) return jerry_undefined();
memoryCopy(&mat->material.unlit.color, color, sizeof(color_t));
jerry_value_t key;
jerry_value_t v;
color_t col;
key = jerry_string_sz("r");
v = jerry_object_get(args[0], key);
jerry_value_free(key);
col.r = (colorchannel8_t)jerry_value_as_number(v);
jerry_value_free(v);
key = jerry_string_sz("g");
v = jerry_object_get(args[0], key);
jerry_value_free(key);
col.g = (colorchannel8_t)jerry_value_as_number(v);
jerry_value_free(v);
key = jerry_string_sz("b");
v = jerry_object_get(args[0], key);
jerry_value_free(key);
col.b = (colorchannel8_t)jerry_value_as_number(v);
jerry_value_free(v);
key = jerry_string_sz("a");
v = jerry_object_get(args[0], key);
jerry_value_free(key);
col.a = (colorchannel8_t)jerry_value_as_number(v);
jerry_value_free(v);
mat->material.unlit.color = col;
return jerry_undefined(); return jerry_undefined();
} }
moduleBaseFunction(moduleEntityMaterialAdd) { moduleBaseFunction(moduleEntityMaterialAdd) {
moduleBaseRequireArgs(1); moduleBaseRequireNumber(0); if(argc < 1 || !jerry_value_is_number(args[0])) {
return moduleBaseThrow("Expected at least 1 argument");
}
entityid_t id = (entityid_t)jerry_value_as_number(args[0]); entityid_t id = (entityid_t)jerry_value_as_number(args[0]);
componentid_t comp = entityAddComponent(id, COMPONENT_TYPE_MATERIAL); componentid_t comp = entityAddComponent(id, COMPONENT_TYPE_MATERIAL);
componenthandle_t h = { .eid = id, .cid = comp }; componenthandle_t h = { .eid = id, .cid = comp };
@@ -83,9 +65,8 @@ static void moduleEntityMATERIAL(void) {
NULL NULL
); );
scriptProtoDefineFunc( scriptProtoDefineProp(
&MODULE_ENTITY_MATERIAL_PROTO, &MODULE_ENTITY_MATERIAL_PROTO, "color",
"setColor", moduleEntityMaterialGetColor, moduleEntityMaterialSetColor
moduleEntityMaterialSetColor
); );
} }
@@ -8,6 +8,7 @@
#pragma once #pragma once
#include "script/module/modulebase.h" #include "script/module/modulebase.h"
#include "script/scriptproto.h" #include "script/scriptproto.h"
#include "script/module/display/modulemesh.h"
#include "entity/entity.h" #include "entity/entity.h"
#include "entity/component/display/entitymesh.h" #include "entity/component/display/entitymesh.h"
#include "moduleentityposition.h" #include "moduleentityposition.h"
@@ -24,12 +25,30 @@ static entitymesh_t * moduleEntityMeshGet(
return (entitymesh_t*)componentGetData(h->eid, h->cid, COMPONENT_TYPE_MESH); return (entitymesh_t*)componentGetData(h->eid, h->cid, COMPONENT_TYPE_MESH);
} }
moduleBaseFunction(moduleEntityMeshAdd) { moduleBaseFunction(moduleEntityMeshGetMesh) {
moduleBaseRequireArgs(1); moduleBaseRequireNumber(0); entitymesh_t *comp = moduleEntityMeshGet(callInfo);
entityid_t id = (entityid_t)jerry_value_as_number(args[0]); if(!comp || !comp->mesh) return jerry_undefined();
componentid_t comp = entityAddComponent(id, COMPONENT_TYPE_MESH); return moduleBaseWrapPointer(comp->mesh);
componenthandle_t h = { .eid = id, .cid = comp }; }
return scriptProtoCreateValue(&MODULE_ENTITY_MESH_PROTO, &h);
moduleBaseFunction(moduleEntityMeshSetMesh) {
if(argc < 1) return moduleBaseThrow("Expected a Mesh argument");
entitymesh_t *comp = moduleEntityMeshGet(callInfo);
if(!comp) return jerry_undefined();
meshscript_t *ms = moduleMeshFrom(args[0]);
if(ms) {
comp->mesh = &ms->mesh;
return jerry_undefined();
}
mesh_t *raw = (mesh_t*)moduleBaseUnwrapPointer(args[0]);
if(raw) {
comp->mesh = raw;
return jerry_undefined();
}
return moduleBaseThrow("Expected a Mesh object or mesh constant");
} }
static void moduleEntityMESH(void) { static void moduleEntityMESH(void) {
@@ -37,4 +56,8 @@ static void moduleEntityMESH(void) {
&MODULE_ENTITY_MESH_PROTO, NULL, &MODULE_ENTITY_MESH_PROTO, NULL,
sizeof(componenthandle_t), NULL sizeof(componenthandle_t), NULL
); );
scriptProtoDefineProp(
&MODULE_ENTITY_MESH_PROTO, "mesh",
moduleEntityMeshGetMesh, moduleEntityMeshSetMesh
);
} }
@@ -32,7 +32,7 @@ moduleBaseFunction(moduleEntityPhysicsGetVelocity) {
} }
moduleBaseFunction(moduleEntityPhysicsSetVelocity) { moduleBaseFunction(moduleEntityPhysicsSetVelocity) {
moduleBaseRequireArgs(1); if(argc < 1) return moduleBaseThrow("Expected at least 1 argument");
entityphysics_t *phys = moduleEntityPhysicsGet(callInfo); entityphysics_t *phys = moduleEntityPhysicsGet(callInfo);
if(!phys) return jerry_undefined(); if(!phys) return jerry_undefined();
vec3 v; vec3 v;
@@ -52,11 +52,15 @@ moduleBaseFunction(moduleEntityPhysicsGetOnGround) {
moduleBaseFunction(moduleEntityPhysicsGetBodyType) { moduleBaseFunction(moduleEntityPhysicsGetBodyType) {
entityphysics_t *phys = moduleEntityPhysicsGet(callInfo); entityphysics_t *phys = moduleEntityPhysicsGet(callInfo);
if(!phys) return jerry_undefined(); if(!phys) return jerry_undefined();
return jerry_number((double)phys->type); return jerry_number(phys->type);
} }
moduleBaseFunction(moduleEntityPhysicsSetBodyType) { moduleBaseFunction(moduleEntityPhysicsSetBodyType) {
moduleBaseRequireArgs(1); moduleBaseRequireNumber(0); if(argc < 1) {
return moduleBaseThrow("Expected at least 1 argument");
}
moduleBaseRequireNumber(0);
entityphysics_t *phys = moduleEntityPhysicsGet(callInfo); entityphysics_t *phys = moduleEntityPhysicsGet(callInfo);
if(!phys) return jerry_undefined(); if(!phys) return jerry_undefined();
phys->type = (physicsbodytype_t)(int32_t)jerry_value_as_number(args[0]); phys->type = (physicsbodytype_t)(int32_t)jerry_value_as_number(args[0]);
@@ -64,7 +68,7 @@ moduleBaseFunction(moduleEntityPhysicsSetBodyType) {
} }
moduleBaseFunction(moduleEntityPhysicsApplyImpulse) { moduleBaseFunction(moduleEntityPhysicsApplyImpulse) {
moduleBaseRequireArgs(1); if(argc < 1) return moduleBaseThrow("Expected at least 1 argument");
entityphysics_t *phys = moduleEntityPhysicsGet(callInfo); entityphysics_t *phys = moduleEntityPhysicsGet(callInfo);
if(!phys) return jerry_undefined(); if(!phys) return jerry_undefined();
if(phys->type == PHYSICS_BODY_STATIC) return jerry_undefined(); if(phys->type == PHYSICS_BODY_STATIC) return jerry_undefined();
@@ -77,7 +81,7 @@ moduleBaseFunction(moduleEntityPhysicsApplyImpulse) {
} }
moduleBaseFunction(moduleEntityPhysicsSetShapeCube) { moduleBaseFunction(moduleEntityPhysicsSetShapeCube) {
moduleBaseRequireArgs(1); if(argc < 1) return moduleBaseThrow("Expected at least 1 argument");
entityphysics_t *phys = moduleEntityPhysicsGet(callInfo); entityphysics_t *phys = moduleEntityPhysicsGet(callInfo);
if(!phys) return jerry_undefined(); if(!phys) return jerry_undefined();
vec3 half; vec3 half;
@@ -90,7 +94,11 @@ moduleBaseFunction(moduleEntityPhysicsSetShapeCube) {
} }
moduleBaseFunction(moduleEntityPhysicsSetShapeSphere) { moduleBaseFunction(moduleEntityPhysicsSetShapeSphere) {
moduleBaseRequireArgs(1); moduleBaseRequireNumber(0); if(argc < 1) {
return moduleBaseThrow("Expected at least 1 argument");
}
moduleBaseRequireNumber(0);
entityphysics_t *phys = moduleEntityPhysicsGet(callInfo); entityphysics_t *phys = moduleEntityPhysicsGet(callInfo);
if(!phys) return jerry_undefined(); if(!phys) return jerry_undefined();
phys->shape.type = PHYSICS_SHAPE_SPHERE; phys->shape.type = PHYSICS_SHAPE_SPHERE;
@@ -99,7 +107,7 @@ moduleBaseFunction(moduleEntityPhysicsSetShapeSphere) {
} }
moduleBaseFunction(moduleEntityPhysicsSetShapeCapsule) { moduleBaseFunction(moduleEntityPhysicsSetShapeCapsule) {
moduleBaseRequireArgs(2); if(argc < 2) return moduleBaseThrow("Expected at least 2 arguments");
moduleBaseRequireNumber(0); moduleBaseRequireNumber(1); moduleBaseRequireNumber(0); moduleBaseRequireNumber(1);
entityphysics_t *phys = moduleEntityPhysicsGet(callInfo); entityphysics_t *phys = moduleEntityPhysicsGet(callInfo);
if(!phys) return jerry_undefined(); if(!phys) return jerry_undefined();
@@ -111,7 +119,11 @@ moduleBaseFunction(moduleEntityPhysicsSetShapeCapsule) {
} }
moduleBaseFunction(moduleEntityPhysicsSetShapePlane) { moduleBaseFunction(moduleEntityPhysicsSetShapePlane) {
moduleBaseRequireArgs(2); moduleBaseRequireNumber(1); if(argc < 2) {
return moduleBaseThrow("Expected at least 2 arguments");
}
moduleBaseRequireNumber(1);
entityphysics_t *phys = moduleEntityPhysicsGet(callInfo); entityphysics_t *phys = moduleEntityPhysicsGet(callInfo);
if(!phys) return jerry_undefined(); if(!phys) return jerry_undefined();
vec3 normal; vec3 normal;
@@ -125,7 +137,11 @@ moduleBaseFunction(moduleEntityPhysicsSetShapePlane) {
} }
moduleBaseFunction(moduleEntityPhysicsAdd) { moduleBaseFunction(moduleEntityPhysicsAdd) {
moduleBaseRequireArgs(1); moduleBaseRequireNumber(0); if(argc < 1) {
return moduleBaseThrow("Expected at least 1 argument");
}
moduleBaseRequireNumber(0);
entityid_t id = (entityid_t)jerry_value_as_number(args[0]); entityid_t id = (entityid_t)jerry_value_as_number(args[0]);
componentid_t comp = entityAddComponent(id, COMPONENT_TYPE_PHYSICS); componentid_t comp = entityAddComponent(id, COMPONENT_TYPE_PHYSICS);
componenthandle_t h = { .eid = id, .cid = comp }; componenthandle_t h = { .eid = id, .cid = comp };
@@ -12,7 +12,6 @@
#include "entity/entity.h" #include "entity/entity.h"
#include "entity/component/display/entityposition.h" #include "entity/component/display/entityposition.h"
// Shared component handle struct — defined once, used by all component modules.
#ifndef COMPONENT_HANDLE_DEFINED #ifndef COMPONENT_HANDLE_DEFINED
#define COMPONENT_HANDLE_DEFINED #define COMPONENT_HANDLE_DEFINED
typedef struct { typedef struct {
@@ -42,7 +41,7 @@ moduleBaseFunction(moduleEntityPositionGetPosition) {
} }
moduleBaseFunction(moduleEntityPositionSetPosition) { moduleBaseFunction(moduleEntityPositionSetPosition) {
moduleBaseRequireArgs(1); if(argc < 1) return moduleBaseThrow("Expected at least 1 argument");
entityposition_t *pos = moduleEntityPositionGet(callInfo); entityposition_t *pos = moduleEntityPositionGet(callInfo);
if(!pos) return jerry_undefined(); if(!pos) return jerry_undefined();
vec3 v; vec3 v;
@@ -63,7 +62,7 @@ moduleBaseFunction(moduleEntityPositionGetRotation) {
} }
moduleBaseFunction(moduleEntityPositionSetRotation) { moduleBaseFunction(moduleEntityPositionSetRotation) {
moduleBaseRequireArgs(1); if(argc < 1) return moduleBaseThrow("Expected at least 1 argument");
entityposition_t *pos = moduleEntityPositionGet(callInfo); entityposition_t *pos = moduleEntityPositionGet(callInfo);
if(!pos) return jerry_undefined(); if(!pos) return jerry_undefined();
vec3 v; vec3 v;
@@ -84,7 +83,7 @@ moduleBaseFunction(moduleEntityPositionGetScale) {
} }
moduleBaseFunction(moduleEntityPositionSetScale) { moduleBaseFunction(moduleEntityPositionSetScale) {
moduleBaseRequireArgs(1); if(argc < 1) return moduleBaseThrow("Expected at least 1 argument");
entityposition_t *pos = moduleEntityPositionGet(callInfo); entityposition_t *pos = moduleEntityPositionGet(callInfo);
if(!pos) return jerry_undefined(); if(!pos) return jerry_undefined();
vec3 v; vec3 v;
@@ -97,7 +96,7 @@ moduleBaseFunction(moduleEntityPositionSetScale) {
} }
moduleBaseFunction(moduleEntityPositionLookAt) { moduleBaseFunction(moduleEntityPositionLookAt) {
moduleBaseRequireArgs(1); if(argc < 1) return moduleBaseThrow("Expected at least 1 argument");
entityposition_t *pos = moduleEntityPositionGet(callInfo); entityposition_t *pos = moduleEntityPositionGet(callInfo);
if(!pos) return jerry_undefined(); if(!pos) return jerry_undefined();
vec3 target; vec3 target;
@@ -114,7 +113,11 @@ moduleBaseFunction(moduleEntityPositionLookAt) {
} }
moduleBaseFunction(moduleEntityPositionAdd) { moduleBaseFunction(moduleEntityPositionAdd) {
moduleBaseRequireArgs(1); moduleBaseRequireNumber(0); if(argc < 1) {
return moduleBaseThrow("Expected at least 1 argument");
}
moduleBaseRequireNumber(0);
entityid_t id = (entityid_t)jerry_value_as_number(args[0]); entityid_t id = (entityid_t)jerry_value_as_number(args[0]);
componentid_t comp = entityAddComponent(id, COMPONENT_TYPE_POSITION); componentid_t comp = entityAddComponent(id, COMPONENT_TYPE_POSITION);
componenthandle_t h = { .eid = id, .cid = comp }; componenthandle_t h = { .eid = id, .cid = comp };
+23 -7
View File
@@ -15,7 +15,7 @@ static scriptproto_t MODULE_INPUT_PROTO;
// Static Methods // Static Methods
moduleBaseFunction(moduleInputBind) { moduleBaseFunction(moduleInputBind) {
moduleBaseRequireArgs(2); if(argc < 2) return moduleBaseThrow("Expected at least 2 arguments");
moduleBaseRequireString(0); moduleBaseRequireString(0);
moduleBaseRequireNumber(1); moduleBaseRequireNumber(1);
@@ -40,7 +40,11 @@ moduleBaseFunction(moduleInputBind) {
} }
moduleBaseFunction(moduleInputIsDown) { moduleBaseFunction(moduleInputIsDown) {
moduleBaseRequireArgs(1); moduleBaseRequireNumber(0); if(argc < 1) {
return moduleBaseThrow("Expected at least 1 argument");
}
moduleBaseRequireNumber(0);
const inputaction_t action = (inputaction_t)jerry_value_as_number(args[0]); const inputaction_t action = (inputaction_t)jerry_value_as_number(args[0]);
if(action <= INPUT_ACTION_NULL || action >= INPUT_ACTION_COUNT) { if(action <= INPUT_ACTION_NULL || action >= INPUT_ACTION_COUNT) {
return moduleBaseThrow("Input.isDown: invalid action"); return moduleBaseThrow("Input.isDown: invalid action");
@@ -49,7 +53,11 @@ moduleBaseFunction(moduleInputIsDown) {
} }
moduleBaseFunction(moduleInputPressed) { moduleBaseFunction(moduleInputPressed) {
moduleBaseRequireArgs(1); moduleBaseRequireNumber(0); if(argc < 1) {
return moduleBaseThrow("Expected at least 1 argument");
}
moduleBaseRequireNumber(0);
const inputaction_t action = (inputaction_t)jerry_value_as_number(args[0]); const inputaction_t action = (inputaction_t)jerry_value_as_number(args[0]);
if(action <= INPUT_ACTION_NULL || action >= INPUT_ACTION_COUNT) { if(action <= INPUT_ACTION_NULL || action >= INPUT_ACTION_COUNT) {
return moduleBaseThrow("Input.pressed: invalid action"); return moduleBaseThrow("Input.pressed: invalid action");
@@ -58,7 +66,11 @@ moduleBaseFunction(moduleInputPressed) {
} }
moduleBaseFunction(moduleInputReleased) { moduleBaseFunction(moduleInputReleased) {
moduleBaseRequireArgs(1); moduleBaseRequireNumber(0); if(argc < 1) {
return moduleBaseThrow("Expected at least 1 argument");
}
moduleBaseRequireNumber(0);
const inputaction_t action = (inputaction_t)jerry_value_as_number(args[0]); const inputaction_t action = (inputaction_t)jerry_value_as_number(args[0]);
if(action <= INPUT_ACTION_NULL || action >= INPUT_ACTION_COUNT) { if(action <= INPUT_ACTION_NULL || action >= INPUT_ACTION_COUNT) {
return moduleBaseThrow("Input.released: invalid action"); return moduleBaseThrow("Input.released: invalid action");
@@ -67,7 +79,11 @@ moduleBaseFunction(moduleInputReleased) {
} }
moduleBaseFunction(moduleInputGetValue) { moduleBaseFunction(moduleInputGetValue) {
moduleBaseRequireArgs(1); moduleBaseRequireNumber(0); if(argc < 1) {
return moduleBaseThrow("Expected at least 1 argument");
}
moduleBaseRequireNumber(0);
const inputaction_t action = (inputaction_t)jerry_value_as_number(args[0]); const inputaction_t action = (inputaction_t)jerry_value_as_number(args[0]);
if(action <= INPUT_ACTION_NULL || action >= INPUT_ACTION_COUNT) { if(action <= INPUT_ACTION_NULL || action >= INPUT_ACTION_COUNT) {
return moduleBaseThrow("Input.getValue: invalid action"); return moduleBaseThrow("Input.getValue: invalid action");
@@ -76,7 +92,7 @@ moduleBaseFunction(moduleInputGetValue) {
} }
moduleBaseFunction(moduleInputAxis) { moduleBaseFunction(moduleInputAxis) {
moduleBaseRequireArgs(2); if(argc < 2) return moduleBaseThrow("Expected at least 2 arguments");
moduleBaseRequireNumber(0); moduleBaseRequireNumber(1); moduleBaseRequireNumber(0); moduleBaseRequireNumber(1);
const inputaction_t neg = (inputaction_t)jerry_value_as_number(args[0]); const inputaction_t neg = (inputaction_t)jerry_value_as_number(args[0]);
const inputaction_t pos = (inputaction_t)jerry_value_as_number(args[1]); const inputaction_t pos = (inputaction_t)jerry_value_as_number(args[1]);
@@ -90,7 +106,7 @@ moduleBaseFunction(moduleInputAxis) {
} }
moduleBaseFunction(moduleInputAxis2D) { moduleBaseFunction(moduleInputAxis2D) {
moduleBaseRequireArgs(4); if(argc < 4) return moduleBaseThrow("Expected at least 4 arguments");
moduleBaseRequireNumber(0); moduleBaseRequireNumber(1); moduleBaseRequireNumber(0); moduleBaseRequireNumber(1);
moduleBaseRequireNumber(2); moduleBaseRequireNumber(3); moduleBaseRequireNumber(2); moduleBaseRequireNumber(3);
const inputaction_t negX = (inputaction_t)jerry_value_as_number(args[0]); const inputaction_t negX = (inputaction_t)jerry_value_as_number(args[0]);
+140
View File
@@ -0,0 +1,140 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "script/module/modulebase.h"
#include "script/scriptproto.h"
#include "item/inventory.h"
#include "item/backpack.h"
typedef struct {
inventory_t *inventory;
} inventoryscript_t;
static scriptproto_t MODULE_INVENTORY_PROTO;
static inline inventory_t * moduleInventoryGet(
const jerry_call_info_t *callInfo
) {
inventoryscript_t *h = (inventoryscript_t*)scriptProtoGetValue(
&MODULE_INVENTORY_PROTO, callInfo->this_value
);
return h ? h->inventory : NULL;
}
moduleBaseFunction(moduleInventoryAdd) {
if(argc < 2) return moduleBaseThrow("Expected (itemId, quantity)");
moduleBaseRequireNumber(0);
moduleBaseRequireNumber(1);
inventory_t *inv = moduleInventoryGet(callInfo);
if(!inv) return jerry_undefined();
inventoryAdd(
inv,
(itemid_t)jerry_value_as_number(args[0]),
(uint8_t)jerry_value_as_number(args[1])
);
return jerry_undefined();
}
moduleBaseFunction(moduleInventoryRemove) {
if(argc < 1) return moduleBaseThrow("Expected itemId");
moduleBaseRequireNumber(0);
inventory_t *inv = moduleInventoryGet(callInfo);
if(!inv) return jerry_undefined();
inventoryRemove(inv, (itemid_t)jerry_value_as_number(args[0]));
return jerry_undefined();
}
moduleBaseFunction(moduleInventorySet) {
if(argc < 2) return moduleBaseThrow("Expected (itemId, quantity)");
moduleBaseRequireNumber(0);
moduleBaseRequireNumber(1);
inventory_t *inv = moduleInventoryGet(callInfo);
if(!inv) return jerry_undefined();
inventorySet(
inv,
(itemid_t)jerry_value_as_number(args[0]),
(uint8_t)jerry_value_as_number(args[1])
);
return jerry_undefined();
}
moduleBaseFunction(moduleInventoryCount) {
if(argc < 1) return moduleBaseThrow("Expected itemId");
moduleBaseRequireNumber(0);
inventory_t *inv = moduleInventoryGet(callInfo);
if(!inv) return jerry_undefined();
return jerry_number(
(double)inventoryGetCount(inv, (itemid_t)jerry_value_as_number(args[0]))
);
}
moduleBaseFunction(moduleInventoryHas) {
if(argc < 1) return moduleBaseThrow("Expected itemId");
moduleBaseRequireNumber(0);
inventory_t *inv = moduleInventoryGet(callInfo);
if(!inv) return jerry_undefined();
return jerry_boolean(
inventoryItemExists(inv, (itemid_t)jerry_value_as_number(args[0]))
);
}
moduleBaseFunction(moduleInventoryIsItemFull) {
if(argc < 1) return moduleBaseThrow("Expected itemId");
moduleBaseRequireNumber(0);
inventory_t *inv = moduleInventoryGet(callInfo);
if(!inv) return jerry_undefined();
return jerry_boolean(
inventoryItemFull(inv, (itemid_t)jerry_value_as_number(args[0]))
);
}
moduleBaseFunction(moduleInventorySort) {
if(argc < 1) return moduleBaseThrow("Expected sortBy");
moduleBaseRequireNumber(0);
inventory_t *inv = moduleInventoryGet(callInfo);
if(!inv) return jerry_undefined();
inventorySort(
inv,
(inventorysort_t)jerry_value_as_number(args[0]),
(argc >= 2 && jerry_value_is_true(args[1]))
);
return jerry_undefined();
}
moduleBaseFunction(moduleInventoryGetFull) {
inventory_t *inv = moduleInventoryGet(callInfo);
if(!inv) return jerry_undefined();
return jerry_boolean(inventoryIsFull(inv));
}
static void moduleItem(void) {
moduleBaseEval(ITEM_SCRIPT);
moduleBaseSetInt("INVENTORY_SORT_BY_ID", INVENTORY_SORT_BY_ID);
moduleBaseSetInt("INVENTORY_SORT_BY_TYPE", INVENTORY_SORT_BY_TYPE);
scriptProtoInit(
&MODULE_INVENTORY_PROTO, NULL,
sizeof(inventoryscript_t), NULL
);
scriptProtoDefineProp(
&MODULE_INVENTORY_PROTO, "full",
moduleInventoryGetFull, NULL
);
scriptProtoDefineFunc(&MODULE_INVENTORY_PROTO, "add", moduleInventoryAdd);
scriptProtoDefineFunc(&MODULE_INVENTORY_PROTO, "remove", moduleInventoryRemove);
scriptProtoDefineFunc(&MODULE_INVENTORY_PROTO, "set", moduleInventorySet);
scriptProtoDefineFunc(&MODULE_INVENTORY_PROTO, "count", moduleInventoryCount);
scriptProtoDefineFunc(&MODULE_INVENTORY_PROTO, "has", moduleInventoryHas);
scriptProtoDefineFunc(&MODULE_INVENTORY_PROTO, "isItemFull", moduleInventoryIsItemFull);
scriptProtoDefineFunc(&MODULE_INVENTORY_PROTO, "sort", moduleInventorySort);
inventoryscript_t h = { .inventory = &BACKPACK };
jerry_value_t backpackObj = scriptProtoCreateValue(&MODULE_INVENTORY_PROTO, &h);
moduleBaseSetValue("Backpack", backpackObj);
jerry_value_free(backpackObj);
}
+7 -7
View File
@@ -28,7 +28,7 @@ moduleBaseFunction(moduleMatConstructor) {
} }
moduleBaseFunction(moduleMatMul) { moduleBaseFunction(moduleMatMul) {
moduleBaseRequireArgs(1); if(argc < 1) return moduleBaseThrow("Expected at least 1 argument");
float_t (*a)[4] = (float_t (*)[4])moduleMatGet(callInfo); float_t (*a)[4] = (float_t (*)[4])moduleMatGet(callInfo);
if(!a) return moduleBaseThrow("Mat4.mul: invalid this"); if(!a) return moduleBaseThrow("Mat4.mul: invalid this");
float_t (*b)[4] = (float_t (*)[4])scriptProtoGetValue( float_t (*b)[4] = (float_t (*)[4])scriptProtoGetValue(
@@ -63,7 +63,7 @@ moduleBaseFunction(moduleMatDeterminant) {
} }
moduleBaseFunction(moduleMatMulVec3) { moduleBaseFunction(moduleMatMulVec3) {
moduleBaseRequireArgs(1); if(argc < 1) return moduleBaseThrow("Expected at least 1 argument");
float_t (*m)[4] = (float_t (*)[4])moduleMatGet(callInfo); float_t (*m)[4] = (float_t (*)[4])moduleMatGet(callInfo);
if(!m) return moduleBaseThrow("Mat4.mulVec3: invalid this"); if(!m) return moduleBaseThrow("Mat4.mulVec3: invalid this");
vec3 vin; vec3 vin;
@@ -78,7 +78,7 @@ moduleBaseFunction(moduleMatMulVec3) {
} }
moduleBaseFunction(moduleMatMulVec4) { moduleBaseFunction(moduleMatMulVec4) {
moduleBaseRequireArgs(1); if(argc < 1) return moduleBaseThrow("Expected at least 1 argument");
float_t (*m)[4] = (float_t (*)[4])moduleMatGet(callInfo); float_t (*m)[4] = (float_t (*)[4])moduleMatGet(callInfo);
if(!m) return moduleBaseThrow("Mat4.mulVec4: invalid this"); if(!m) return moduleBaseThrow("Mat4.mulVec4: invalid this");
vec4 vin; vec4 vin;
@@ -91,7 +91,7 @@ moduleBaseFunction(moduleMatMulVec4) {
} }
moduleBaseFunction(moduleMatTranslate) { moduleBaseFunction(moduleMatTranslate) {
moduleBaseRequireArgs(1); if(argc < 1) return moduleBaseThrow("Expected at least 1 argument");
float_t (*m)[4] = (float_t (*)[4])moduleMatGet(callInfo); float_t (*m)[4] = (float_t (*)[4])moduleMatGet(callInfo);
if(!m) return moduleBaseThrow("Mat4.translate: invalid this"); if(!m) return moduleBaseThrow("Mat4.translate: invalid this");
vec3 tv; vec3 tv;
@@ -105,7 +105,7 @@ moduleBaseFunction(moduleMatTranslate) {
} }
moduleBaseFunction(moduleMatScale) { moduleBaseFunction(moduleMatScale) {
moduleBaseRequireArgs(1); if(argc < 1) return moduleBaseThrow("Expected at least 1 argument");
float_t (*m)[4] = (float_t (*)[4])moduleMatGet(callInfo); float_t (*m)[4] = (float_t (*)[4])moduleMatGet(callInfo);
if(!m) return moduleBaseThrow("Mat4.scale: invalid this"); if(!m) return moduleBaseThrow("Mat4.scale: invalid this");
vec3 sv; vec3 sv;
@@ -125,7 +125,7 @@ moduleBaseFunction(moduleMatStaticIdentity) {
} }
moduleBaseFunction(moduleMatStaticPerspective) { moduleBaseFunction(moduleMatStaticPerspective) {
moduleBaseRequireArgs(4); if(argc < 4) return moduleBaseThrow("Expected at least 4 arguments");
moduleBaseRequireNumber(0); moduleBaseRequireNumber(1); moduleBaseRequireNumber(0); moduleBaseRequireNumber(1);
moduleBaseRequireNumber(2); moduleBaseRequireNumber(3); moduleBaseRequireNumber(2); moduleBaseRequireNumber(3);
mat4 r; mat4 r;
@@ -140,7 +140,7 @@ moduleBaseFunction(moduleMatStaticPerspective) {
} }
moduleBaseFunction(moduleMatStaticLookAt) { moduleBaseFunction(moduleMatStaticLookAt) {
moduleBaseRequireArgs(3); if(argc < 3) return moduleBaseThrow("Expected at least 3 arguments");
vec3 eye, center, up; vec3 eye, center, up;
if(!moduleVec3Check(args[0], eye)) { if(!moduleVec3Check(args[0], eye)) {
return moduleBaseThrow("Mat4.lookAt: eye must be a Vec3"); return moduleBaseThrow("Mat4.lookAt: eye must be a Vec3");
+23 -15
View File
@@ -55,7 +55,7 @@ moduleBaseFunction(moduleVec2SetY) {
} }
moduleBaseFunction(moduleVec2Dot) { moduleBaseFunction(moduleVec2Dot) {
moduleBaseRequireArgs(1); if(argc < 1) return moduleBaseThrow("Expected at least 1 argument");
float_t *a = moduleVec2Get(callInfo); float_t *a = moduleVec2Get(callInfo);
if(!a) return moduleBaseThrow("Vec2.dot: invalid this"); if(!a) return moduleBaseThrow("Vec2.dot: invalid this");
float_t *b = moduleVec2From(args[0]); float_t *b = moduleVec2From(args[0]);
@@ -92,7 +92,7 @@ moduleBaseFunction(moduleVec2Negate) {
} }
moduleBaseFunction(moduleVec2Add) { moduleBaseFunction(moduleVec2Add) {
moduleBaseRequireArgs(1); if(argc < 1) return moduleBaseThrow("Expected at least 1 argument");
float_t *a = moduleVec2Get(callInfo); float_t *a = moduleVec2Get(callInfo);
if(!a) return moduleBaseThrow("Vec2.add: invalid this"); if(!a) return moduleBaseThrow("Vec2.add: invalid this");
float_t *b = moduleVec2From(args[0]); float_t *b = moduleVec2From(args[0]);
@@ -103,7 +103,7 @@ moduleBaseFunction(moduleVec2Add) {
} }
moduleBaseFunction(moduleVec2Sub) { moduleBaseFunction(moduleVec2Sub) {
moduleBaseRequireArgs(1); if(argc < 1) return moduleBaseThrow("Expected at least 1 argument");
float_t *a = moduleVec2Get(callInfo); float_t *a = moduleVec2Get(callInfo);
if(!a) return moduleBaseThrow("Vec2.sub: invalid this"); if(!a) return moduleBaseThrow("Vec2.sub: invalid this");
float_t *b = moduleVec2From(args[0]); float_t *b = moduleVec2From(args[0]);
@@ -114,7 +114,11 @@ moduleBaseFunction(moduleVec2Sub) {
} }
moduleBaseFunction(moduleVec2Scale) { moduleBaseFunction(moduleVec2Scale) {
moduleBaseRequireArgs(1); moduleBaseRequireNumber(0); if(argc < 1) {
return moduleBaseThrow("Expected at least 1 argument");
}
moduleBaseRequireNumber(0);
float_t *v = moduleVec2Get(callInfo); float_t *v = moduleVec2Get(callInfo);
if(!v) return moduleBaseThrow("Vec2.scale: invalid this"); if(!v) return moduleBaseThrow("Vec2.scale: invalid this");
vec2 r; vec2 r;
@@ -123,7 +127,11 @@ moduleBaseFunction(moduleVec2Scale) {
} }
moduleBaseFunction(moduleVec2Lerp) { moduleBaseFunction(moduleVec2Lerp) {
moduleBaseRequireArgs(2); moduleBaseRequireNumber(1); if(argc < 2) {
return moduleBaseThrow("Expected at least 2 arguments");
}
moduleBaseRequireNumber(1);
float_t *a = moduleVec2Get(callInfo); float_t *a = moduleVec2Get(callInfo);
if(!a) return moduleBaseThrow("Vec2.lerp: invalid this"); if(!a) return moduleBaseThrow("Vec2.lerp: invalid this");
float_t *b = moduleVec2From(args[0]); float_t *b = moduleVec2From(args[0]);
@@ -134,7 +142,7 @@ moduleBaseFunction(moduleVec2Lerp) {
} }
moduleBaseFunction(moduleVec2Distance) { moduleBaseFunction(moduleVec2Distance) {
moduleBaseRequireArgs(1); if(argc < 1) return moduleBaseThrow("Expected at least 1 argument");
float_t *a = moduleVec2Get(callInfo); float_t *a = moduleVec2Get(callInfo);
if(!a) return moduleBaseThrow("Vec2.distance: invalid this"); if(!a) return moduleBaseThrow("Vec2.distance: invalid this");
float_t *b = moduleVec2From(args[0]); float_t *b = moduleVec2From(args[0]);
@@ -176,14 +184,14 @@ static void moduleVec2(void) {
scriptProtoDefineToString(&MODULE_VEC2_PROTO, moduleVec2ToString); scriptProtoDefineToString(&MODULE_VEC2_PROTO, moduleVec2ToString);
scriptProtoDefineFunc(&MODULE_VEC2_PROTO, "dot", moduleVec2Dot); scriptProtoDefineFunc(&MODULE_VEC2_PROTO, "dot", moduleVec2Dot);
scriptProtoDefineFunc(&MODULE_VEC2_PROTO, "length", moduleVec2Length); scriptProtoDefineFunc(&MODULE_VEC2_PROTO, "length", moduleVec2Length);
scriptProtoDefineFunc(&MODULE_VEC2_PROTO, "lengthSq", moduleVec2LengthSq); scriptProtoDefineFunc(&MODULE_VEC2_PROTO, "lengthSq", moduleVec2LengthSq);
scriptProtoDefineFunc(&MODULE_VEC2_PROTO, "normalize", moduleVec2Normalize); scriptProtoDefineFunc(&MODULE_VEC2_PROTO, "normalize", moduleVec2Normalize);
scriptProtoDefineFunc(&MODULE_VEC2_PROTO, "negate", moduleVec2Negate); scriptProtoDefineFunc(&MODULE_VEC2_PROTO, "negate", moduleVec2Negate);
scriptProtoDefineFunc(&MODULE_VEC2_PROTO, "add", moduleVec2Add); scriptProtoDefineFunc(&MODULE_VEC2_PROTO, "add", moduleVec2Add);
scriptProtoDefineFunc(&MODULE_VEC2_PROTO, "sub", moduleVec2Sub); scriptProtoDefineFunc(&MODULE_VEC2_PROTO, "sub", moduleVec2Sub);
scriptProtoDefineFunc(&MODULE_VEC2_PROTO, "scale", moduleVec2Scale); scriptProtoDefineFunc(&MODULE_VEC2_PROTO, "scale", moduleVec2Scale);
scriptProtoDefineFunc(&MODULE_VEC2_PROTO, "lerp", moduleVec2Lerp); scriptProtoDefineFunc(&MODULE_VEC2_PROTO, "lerp", moduleVec2Lerp);
scriptProtoDefineFunc(&MODULE_VEC2_PROTO, "distance", moduleVec2Distance); scriptProtoDefineFunc(&MODULE_VEC2_PROTO, "distance", moduleVec2Distance);
} }
+15 -7
View File
@@ -67,7 +67,7 @@ moduleBaseFunction(moduleVec3SetZ) {
} }
moduleBaseFunction(moduleVec3Dot) { moduleBaseFunction(moduleVec3Dot) {
moduleBaseRequireArgs(1); if(argc < 1) return moduleBaseThrow("Expected at least 1 argument");
float_t *a = moduleVec3Get(callInfo); float_t *a = moduleVec3Get(callInfo);
if(!a) return moduleBaseThrow("Vec3.dot: invalid this"); if(!a) return moduleBaseThrow("Vec3.dot: invalid this");
float_t *b = moduleVec3From(args[0]); float_t *b = moduleVec3From(args[0]);
@@ -76,7 +76,7 @@ moduleBaseFunction(moduleVec3Dot) {
} }
moduleBaseFunction(moduleVec3Cross) { moduleBaseFunction(moduleVec3Cross) {
moduleBaseRequireArgs(1); if(argc < 1) return moduleBaseThrow("Expected at least 1 argument");
float_t *a = moduleVec3Get(callInfo); float_t *a = moduleVec3Get(callInfo);
if(!a) return moduleBaseThrow("Vec3.cross: invalid this"); if(!a) return moduleBaseThrow("Vec3.cross: invalid this");
float_t *b = moduleVec3From(args[0]); float_t *b = moduleVec3From(args[0]);
@@ -115,7 +115,7 @@ moduleBaseFunction(moduleVec3Negate) {
} }
moduleBaseFunction(moduleVec3Add) { moduleBaseFunction(moduleVec3Add) {
moduleBaseRequireArgs(1); if(argc < 1) return moduleBaseThrow("Expected at least 1 argument");
float_t *a = moduleVec3Get(callInfo); float_t *a = moduleVec3Get(callInfo);
if(!a) return moduleBaseThrow("Vec3.add: invalid this"); if(!a) return moduleBaseThrow("Vec3.add: invalid this");
float_t *b = moduleVec3From(args[0]); float_t *b = moduleVec3From(args[0]);
@@ -126,7 +126,7 @@ moduleBaseFunction(moduleVec3Add) {
} }
moduleBaseFunction(moduleVec3Sub) { moduleBaseFunction(moduleVec3Sub) {
moduleBaseRequireArgs(1); if(argc < 1) return moduleBaseThrow("Expected at least 1 argument");
float_t *a = moduleVec3Get(callInfo); float_t *a = moduleVec3Get(callInfo);
if(!a) return moduleBaseThrow("Vec3.sub: invalid this"); if(!a) return moduleBaseThrow("Vec3.sub: invalid this");
float_t *b = moduleVec3From(args[0]); float_t *b = moduleVec3From(args[0]);
@@ -137,7 +137,11 @@ moduleBaseFunction(moduleVec3Sub) {
} }
moduleBaseFunction(moduleVec3Scale) { moduleBaseFunction(moduleVec3Scale) {
moduleBaseRequireArgs(1); moduleBaseRequireNumber(0); if(argc < 1) {
return moduleBaseThrow("Expected at least 1 argument");
}
moduleBaseRequireNumber(0);
float_t *v = moduleVec3Get(callInfo); float_t *v = moduleVec3Get(callInfo);
if(!v) return moduleBaseThrow("Vec3.scale: invalid this"); if(!v) return moduleBaseThrow("Vec3.scale: invalid this");
vec3 r; vec3 r;
@@ -146,7 +150,11 @@ moduleBaseFunction(moduleVec3Scale) {
} }
moduleBaseFunction(moduleVec3Lerp) { moduleBaseFunction(moduleVec3Lerp) {
moduleBaseRequireArgs(2); moduleBaseRequireNumber(1); if(argc < 2) {
return moduleBaseThrow("Expected at least 2 arguments");
}
moduleBaseRequireNumber(1);
float_t *a = moduleVec3Get(callInfo); float_t *a = moduleVec3Get(callInfo);
if(!a) return moduleBaseThrow("Vec3.lerp: invalid this"); if(!a) return moduleBaseThrow("Vec3.lerp: invalid this");
float_t *b = moduleVec3From(args[0]); float_t *b = moduleVec3From(args[0]);
@@ -157,7 +165,7 @@ moduleBaseFunction(moduleVec3Lerp) {
} }
moduleBaseFunction(moduleVec3Distance) { moduleBaseFunction(moduleVec3Distance) {
moduleBaseRequireArgs(1); if(argc < 1) return moduleBaseThrow("Expected at least 1 argument");
float_t *a = moduleVec3Get(callInfo); float_t *a = moduleVec3Get(callInfo);
if(!a) return moduleBaseThrow("Vec3.distance: invalid this"); if(!a) return moduleBaseThrow("Vec3.distance: invalid this");
float_t *b = moduleVec3From(args[0]); float_t *b = moduleVec3From(args[0]);
+21 -13
View File
@@ -121,7 +121,7 @@ moduleBaseFunction(moduleVec4SetV1) {
} }
moduleBaseFunction(moduleVec4Dot) { moduleBaseFunction(moduleVec4Dot) {
moduleBaseRequireArgs(1); if(argc < 1) return moduleBaseThrow("Expected at least 1 argument");
float_t *a = moduleVec4Get(callInfo); float_t *a = moduleVec4Get(callInfo);
if(!a) return moduleBaseThrow("Vec4.dot: invalid this"); if(!a) return moduleBaseThrow("Vec4.dot: invalid this");
float_t *b = moduleVec4From(args[0]); float_t *b = moduleVec4From(args[0]);
@@ -158,7 +158,7 @@ moduleBaseFunction(moduleVec4Negate) {
} }
moduleBaseFunction(moduleVec4Add) { moduleBaseFunction(moduleVec4Add) {
moduleBaseRequireArgs(1); if(argc < 1) return moduleBaseThrow("Expected at least 1 argument");
float_t *a = moduleVec4Get(callInfo); float_t *a = moduleVec4Get(callInfo);
if(!a) return moduleBaseThrow("Vec4.add: invalid this"); if(!a) return moduleBaseThrow("Vec4.add: invalid this");
float_t *b = moduleVec4From(args[0]); float_t *b = moduleVec4From(args[0]);
@@ -169,7 +169,7 @@ moduleBaseFunction(moduleVec4Add) {
} }
moduleBaseFunction(moduleVec4Sub) { moduleBaseFunction(moduleVec4Sub) {
moduleBaseRequireArgs(1); if(argc < 1) return moduleBaseThrow("Expected at least 1 argument");
float_t *a = moduleVec4Get(callInfo); float_t *a = moduleVec4Get(callInfo);
if(!a) return moduleBaseThrow("Vec4.sub: invalid this"); if(!a) return moduleBaseThrow("Vec4.sub: invalid this");
float_t *b = moduleVec4From(args[0]); float_t *b = moduleVec4From(args[0]);
@@ -180,7 +180,11 @@ moduleBaseFunction(moduleVec4Sub) {
} }
moduleBaseFunction(moduleVec4Scale) { moduleBaseFunction(moduleVec4Scale) {
moduleBaseRequireArgs(1); moduleBaseRequireNumber(0); if(argc < 1) {
return moduleBaseThrow("Expected at least 1 argument");
}
moduleBaseRequireNumber(0);
float_t *v = moduleVec4Get(callInfo); float_t *v = moduleVec4Get(callInfo);
if(!v) return moduleBaseThrow("Vec4.scale: invalid this"); if(!v) return moduleBaseThrow("Vec4.scale: invalid this");
vec4 r; vec4 r;
@@ -189,7 +193,11 @@ moduleBaseFunction(moduleVec4Scale) {
} }
moduleBaseFunction(moduleVec4Lerp) { moduleBaseFunction(moduleVec4Lerp) {
moduleBaseRequireArgs(2); moduleBaseRequireNumber(1); if(argc < 2) {
return moduleBaseThrow("Expected at least 2 arguments");
}
moduleBaseRequireNumber(1);
float_t *a = moduleVec4Get(callInfo); float_t *a = moduleVec4Get(callInfo);
if(!a) return moduleBaseThrow("Vec4.lerp: invalid this"); if(!a) return moduleBaseThrow("Vec4.lerp: invalid this");
float_t *b = moduleVec4From(args[0]); float_t *b = moduleVec4From(args[0]);
@@ -256,13 +264,13 @@ static void moduleVec4(void) {
scriptProtoDefineToString(&MODULE_VEC4_PROTO, moduleVec4ToString); scriptProtoDefineToString(&MODULE_VEC4_PROTO, moduleVec4ToString);
scriptProtoDefineFunc(&MODULE_VEC4_PROTO, "dot", moduleVec4Dot); scriptProtoDefineFunc(&MODULE_VEC4_PROTO, "dot", moduleVec4Dot);
scriptProtoDefineFunc(&MODULE_VEC4_PROTO, "length", moduleVec4Length); scriptProtoDefineFunc(&MODULE_VEC4_PROTO, "length", moduleVec4Length);
scriptProtoDefineFunc(&MODULE_VEC4_PROTO, "lengthSq", moduleVec4LengthSq); scriptProtoDefineFunc(&MODULE_VEC4_PROTO, "lengthSq", moduleVec4LengthSq);
scriptProtoDefineFunc(&MODULE_VEC4_PROTO, "normalize", moduleVec4Normalize); scriptProtoDefineFunc(&MODULE_VEC4_PROTO, "normalize", moduleVec4Normalize);
scriptProtoDefineFunc(&MODULE_VEC4_PROTO, "negate", moduleVec4Negate); scriptProtoDefineFunc(&MODULE_VEC4_PROTO, "negate", moduleVec4Negate);
scriptProtoDefineFunc(&MODULE_VEC4_PROTO, "add", moduleVec4Add); scriptProtoDefineFunc(&MODULE_VEC4_PROTO, "add", moduleVec4Add);
scriptProtoDefineFunc(&MODULE_VEC4_PROTO, "sub", moduleVec4Sub); scriptProtoDefineFunc(&MODULE_VEC4_PROTO, "sub", moduleVec4Sub);
scriptProtoDefineFunc(&MODULE_VEC4_PROTO, "scale", moduleVec4Scale); scriptProtoDefineFunc(&MODULE_VEC4_PROTO, "scale", moduleVec4Scale);
scriptProtoDefineFunc(&MODULE_VEC4_PROTO, "lerp", moduleVec4Lerp); scriptProtoDefineFunc(&MODULE_VEC4_PROTO, "lerp", moduleVec4Lerp);
} }
+18
View File
@@ -13,21 +13,39 @@
#include "script/module/moduleplatform.h" #include "script/module/moduleplatform.h"
#include "script/module/time/moduletime.h" #include "script/module/time/moduletime.h"
#include "script/module/display/modulecolor.h" #include "script/module/display/modulecolor.h"
#include "script/module/display/modulemesh.h"
#include "script/module/display/modulescreen.h" #include "script/module/display/modulescreen.h"
#include "script/module/display/modulespritebatch.h"
#include "script/module/display/moduletext.h"
#include "script/module/animation/moduleeasing.h"
#include "script/module/animation/moduleanimation.h"
#include "script/module/scene/modulescene.h" #include "script/module/scene/modulescene.h"
#include "script/module/cutscene/modulecutscene.h"
#include "script/module/console/moduleconsole.h" #include "script/module/console/moduleconsole.h"
#include "script/module/engine/moduleengine.h" #include "script/module/engine/moduleengine.h"
#include "script/module/item/moduleitem.h"
#include "script/module/story/modulestory.h"
#include "script/module/ui/moduletextbox.h"
static void moduleRegister(void) { static void moduleRegister(void) {
moduleInclude(); moduleInclude();
moduleMath(); moduleMath();
moduleMesh();
moduleEntity(); moduleEntity();
moduleInput(); moduleInput();
modulePlatform(); modulePlatform();
moduleTime(); moduleTime();
moduleColor(); moduleColor();
moduleScreen(); moduleScreen();
moduleSpriteBatch();
moduleText();
moduleEasing();
moduleAnimation();
moduleScene(); moduleScene();
moduleCutscene();
moduleConsole(); moduleConsole();
moduleEngine(); moduleEngine();
moduleItem();
moduleStory();
moduleTextbox();
} }
-8
View File
@@ -213,14 +213,6 @@ static void moduleBaseCreateGlobalObject(
jerry_value_free(global); jerry_value_free(global);
} }
/**
* Assert at least n arguments were passed; return type error if not.
*/
#define moduleBaseRequireArgs(n) \
if((argc) < (n)) { \
return moduleBaseThrow("Expected at least " #n " arguments"); \
}
/** /**
* Assert an argument is a number; return type error if not. * Assert an argument is a number; return type error if not.
*/ */
+1 -1
View File
@@ -25,7 +25,7 @@ moduleBaseFunction(moduleSceneDefaultConstructor) {
} }
moduleBaseFunction(moduleSceneSet) { moduleBaseFunction(moduleSceneSet) {
moduleBaseRequireArgs(1); if(argc < 1) return moduleBaseThrow("Expected at least 1 argument");
moduleBaseRequireString(0); moduleBaseRequireString(0);
char_t name[ASSET_FILE_PATH_MAX]; char_t name[ASSET_FILE_PATH_MAX];
+3 -25
View File
@@ -7,6 +7,7 @@
#pragma once #pragma once
#include "script/module/modulebase.h" #include "script/module/modulebase.h"
#include "script/scriptmanager.h"
moduleBaseFunction(moduleIncludeInclude) { moduleBaseFunction(moduleIncludeInclude) {
if(argc < 1 || !jerry_value_is_string(args[0])) { if(argc < 1 || !jerry_value_is_string(args[0])) {
@@ -25,32 +26,9 @@ moduleBaseFunction(moduleIncludeInclude) {
return moduleBaseThrow("include: filename must end with .js"); return moduleBaseThrow("include: filename must end with .js");
} }
char_t buffer[1024]; jerry_value_t moduleVal = 0;
stringCopy(buffer, filename, sizeof(buffer)); errorret_t err = scriptInclude(filename, &moduleVal);
// Save and reset 'export' so the included module gets a clean undefined
// default. Saving lets nested includes each have their own export scope.
jerry_value_t global = jerry_current_realm();
jerry_value_t moduleKey = jerry_string_sz("module");
jerry_value_t prevModule = jerry_object_get(global, moduleKey);
jerry_value_t undef = jerry_undefined();
jerry_object_set(global, moduleKey, undef);
jerry_value_free(undef);
jerry_value_t result = 0;
errorret_t err = scriptManagerExecFile(buffer, &result);
if(result != 0) jerry_value_free(result);
// Capture whatever the module assigned to 'module', then restore the
// caller's value so nested includes don't clobber each other.
jerry_value_t moduleVal = jerry_object_get(global, moduleKey);
jerry_object_set(global, moduleKey, prevModule);
jerry_value_free(prevModule);
jerry_value_free(moduleKey);
jerry_value_free(global);
if(err.code != ERROR_OK) { if(err.code != ERROR_OK) {
jerry_value_free(moduleVal);
errorCatch(errorPrint(err)); errorCatch(errorPrint(err));
return moduleBaseThrow("Failed to include script file"); return moduleBaseThrow("Failed to include script file");
} }
@@ -0,0 +1,50 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "script/module/modulebase.h"
#include "script/scriptproto.h"
#include "story/storyflag.h"
static scriptproto_t MODULE_STORY_FLAG_PROTO;
moduleBaseFunction(moduleStoryFlagGet) {
if(argc < 1) return moduleBaseThrow("Expected flagId");
moduleBaseRequireNumber(0);
storyflag_t flag = (storyflag_t)jerry_value_as_number(args[0]);
if(flag <= STORY_FLAG_NULL || flag >= STORY_FLAG_COUNT) {
return moduleBaseThrow("StoryFlag.get: invalid flag ID");
}
return jerry_number((double)storyFlagGet(flag));
}
moduleBaseFunction(moduleStoryFlagSet) {
if(argc < 2) return moduleBaseThrow("Expected (flagId, value)");
moduleBaseRequireNumber(0);
moduleBaseRequireNumber(1);
storyflag_t flag = (storyflag_t)jerry_value_as_number(args[0]);
if(flag <= STORY_FLAG_NULL || flag >= STORY_FLAG_COUNT) {
return moduleBaseThrow("StoryFlag.set: invalid flag ID");
}
storyFlagSet(flag, (storyflagvalue_t)jerry_value_as_number(args[1]));
return jerry_undefined();
}
static void moduleStory(void) {
moduleBaseEval(STORY_FLAG_SCRIPT);
scriptProtoInit(
&MODULE_STORY_FLAG_PROTO, "StoryFlag",
sizeof(uint8_t), NULL
);
scriptProtoDefineStaticFunc(
&MODULE_STORY_FLAG_PROTO, "get", moduleStoryFlagGet
);
scriptProtoDefineStaticFunc(
&MODULE_STORY_FLAG_PROTO, "set", moduleStoryFlagSet
);
}
+122
View File
@@ -0,0 +1,122 @@
// 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 "ui/uitextbox.h"
static scriptproto_t MODULE_TEXTBOX_PROTO;
moduleBaseFunction(moduleTextboxSetText) {
if(argc < 1 || !jerry_value_is_string(args[0])) {
return moduleBaseThrow("Textbox.setText: expected string");
}
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) {
uiTextboxUpdate();
return jerry_undefined();
}
moduleBaseFunction(moduleTextboxDraw) {
uiTextboxDraw();
return jerry_undefined();
}
moduleBaseFunction(moduleTextboxGetScroll) {
return jerry_number((double)UI_TEXTBOX.scroll);
}
moduleBaseFunction(moduleTextboxSetScroll) {
if(argc < 1 || !jerry_value_is_number(args[0])) {
return moduleBaseThrow("Textbox.scroll: expected number");
}
UI_TEXTBOX.scroll = (int32_t)jerry_value_as_number(args[0]);
return jerry_undefined();
}
moduleBaseFunction(moduleTextboxGetAdvanceAction) {
return jerry_number((double)UI_TEXTBOX.advanceAction);
}
moduleBaseFunction(moduleTextboxSetAdvanceAction) {
if(argc < 1 || !jerry_value_is_number(args[0])) {
return moduleBaseThrow("Textbox.advanceAction: expected number");
}
UI_TEXTBOX.advanceAction = (inputaction_t)(
(int32_t)jerry_value_as_number(args[0])
);
return jerry_undefined();
}
moduleBaseFunction(moduleTextboxGetPageComplete) {
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);
}
static void moduleTextbox(void) {
scriptProtoInit(
&MODULE_TEXTBOX_PROTO, "Textbox", 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
);
scriptProtoDefineStaticProp(
&MODULE_TEXTBOX_PROTO, "scroll",
moduleTextboxGetScroll, moduleTextboxSetScroll
);
scriptProtoDefineStaticProp(
&MODULE_TEXTBOX_PROTO, "advanceAction",
moduleTextboxGetAdvanceAction, moduleTextboxSetAdvanceAction
);
scriptProtoDefineStaticProp(
&MODULE_TEXTBOX_PROTO, "pageComplete",
moduleTextboxGetPageComplete, NULL
);
scriptProtoDefineStaticProp(
&MODULE_TEXTBOX_PROTO, "hasNextPage",
moduleTextboxGetHasNextPage, NULL
);
scriptProtoDefineStaticProp(
&MODULE_TEXTBOX_PROTO, "currentPage",
moduleTextboxGetCurrentPage, NULL
);
scriptProtoDefineStaticProp(
&MODULE_TEXTBOX_PROTO, "pageCount",
moduleTextboxGetPageCount, NULL
);
}
+33 -6
View File
@@ -9,7 +9,6 @@
#include "assert/assert.h" #include "assert/assert.h"
#include "asset/asset.h" #include "asset/asset.h"
#include "util/memory.h" #include "util/memory.h"
#include "event/event.h"
#include "asset/loader/script/assetscriptloader.h" #include "asset/loader/script/assetscriptloader.h"
#include "script/module/module.h" #include "script/module/module.h"
@@ -78,13 +77,41 @@ errorret_t scriptManagerExecFile(
return assetScriptLoad(fname, resultOut); return assetScriptLoad(fname, resultOut);
} }
errorret_t scriptManagerDispose(void) { errorret_t scriptInclude(const char_t *filename, jerry_value_t *out) {
for(uint8_t i = 0; i < SCRIPT_MANAGER_MAX_EVENT_SUBSCRIPTIONS; i++) { assertNotNull(filename, "Filename cannot be NULL");
event_t *event = SCRIPT_MANAGER.subscribedEvents[i];
if(event == NULL) continue; jerry_value_t global = jerry_current_realm();
eventUnsubscribeScriptContext(event, &SCRIPT_MANAGER); jerry_value_t moduleKey = jerry_string_sz("module");
jerry_value_t prevModule = jerry_object_get(global, moduleKey);
jerry_value_t undef = jerry_undefined();
jerry_object_set(global, moduleKey, undef);
jerry_value_free(undef);
jerry_value_t execResult = 0;
errorret_t err = scriptManagerExecFile(filename, &execResult);
if(execResult != 0) jerry_value_free(execResult);
jerry_value_t moduleVal = jerry_object_get(global, moduleKey);
jerry_object_set(global, moduleKey, prevModule);
jerry_value_free(prevModule);
jerry_value_free(moduleKey);
jerry_value_free(global);
if(err.code != ERROR_OK) {
jerry_value_free(moduleVal);
return err;
} }
if(out != NULL) {
*out = moduleVal;
} else {
jerry_value_free(moduleVal);
}
errorOk();
}
errorret_t scriptManagerDispose(void) {
jerry_cleanup(); jerry_cleanup();
errorOk(); errorOk();
} }
+13 -3
View File
@@ -9,12 +9,10 @@
#include "error/error.h" #include "error/error.h"
#include "scriptvalue.h" #include "scriptvalue.h"
typedef struct event_s event_t;
#define SCRIPT_MANAGER_MAX_EVENT_SUBSCRIPTIONS 64 #define SCRIPT_MANAGER_MAX_EVENT_SUBSCRIPTIONS 64
typedef struct { typedef struct {
event_t *subscribedEvents[SCRIPT_MANAGER_MAX_EVENT_SUBSCRIPTIONS]; void* nothing;
} scriptmanager_t; } scriptmanager_t;
extern scriptmanager_t SCRIPT_MANAGER; extern scriptmanager_t SCRIPT_MANAGER;
@@ -58,6 +56,18 @@ errorret_t scriptManagerExecFile(
jerry_value_t *result jerry_value_t *result
); );
/**
* Execute a JS file using the module-export pattern and return the exported
* value. The script is expected to assign its public API to the global
* `module` variable. The caller owns the returned jerry_value_t and must
* call jerry_value_free() on it when done.
*
* @param filename Path to the .js asset file.
* @param out Receives the value assigned to `module` by the script.
* @return The error return value.
*/
errorret_t scriptInclude(const char_t *filename, jerry_value_t *out);
/** /**
* Dispose of the script manager. * Dispose of the script manager.
* *
+9 -3
View File
@@ -78,7 +78,9 @@ void scriptProtoDefineProp(
} }
jerry_value_t key = jerry_string_sz(name); jerry_value_t key = jerry_string_sz(name);
jerry_value_t result = jerry_object_define_own_prop(proto->prototype, key, &desc); jerry_value_t result = jerry_object_define_own_prop(
proto->prototype, key, &desc
);
jerry_value_free(result); jerry_value_free(result);
jerry_value_free(key); jerry_value_free(key);
jerry_value_free(desc.getter); jerry_value_free(desc.getter);
@@ -111,7 +113,9 @@ void scriptProtoDefineStaticProp(
assertStrLenMin(name, 1, "Property name must not be empty"); assertStrLenMin(name, 1, "Property name must not be empty");
assertNotNull(getter, "Getter must not be null"); assertNotNull(getter, "Getter must not be null");
jerry_value_t target = proto->constructor ? proto->constructor : proto->prototype; jerry_value_t target = (
proto->constructor ? proto->constructor : proto->prototype
);
jerry_property_descriptor_t desc; jerry_property_descriptor_t desc;
memoryZero(&desc, sizeof(desc)); memoryZero(&desc, sizeof(desc));
@@ -145,7 +149,9 @@ void scriptProtoDefineStaticFunc(
assertStrLenMin(name, 1, "Method name must not be empty"); assertStrLenMin(name, 1, "Method name must not be empty");
assertNotNull(fn, "Function handler must not be null"); assertNotNull(fn, "Function handler must not be null");
jerry_value_t target = proto->constructor ? proto->constructor : proto->prototype; jerry_value_t target = (
proto->constructor ? proto->constructor : proto->prototype
);
jerry_value_t key = jerry_string_sz(name); jerry_value_t key = jerry_string_sz(name);
jerry_value_t func = jerry_function_external(fn); jerry_value_t func = jerry_function_external(fn);
jerry_object_set(target, key, func); jerry_object_set(target, key, func);
+4
View File
@@ -7,4 +7,8 @@
target_sources(${DUSK_LIBRARY_TARGET_NAME} target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC PUBLIC
ui.c ui.c
uifps.c
uielement.c
uiframe.c
uitextbox.c
) )
+19 -11
View File
@@ -10,15 +10,19 @@
#include "assert/assert.h" #include "assert/assert.h"
#include "display/spritebatch/spritebatch.h" #include "display/spritebatch/spritebatch.h"
#include "display/screen/screen.h" #include "display/screen/screen.h"
#include "ui/uielement.h"
#include "log/log.h"
ui_t UI; ui_t UI;
errorret_t uiInit(void) { errorret_t uiInit(void) {
memoryZero(&UI, sizeof(ui_t)); memoryZero(&UI, sizeof(ui_t));
// cameraInitOrthographic(&UI.camera); uielement_t *element = &UI_ELEMENTS[0];
// UI.camera.orthographic.left = 0; while(element->type != UI_ELEMENT_TYPE_NULL) {
// UI.camera.orthographic.bottom = 0; errorChain(uiElementInit(element));
element++;
}
errorOk(); errorOk();
} }
@@ -26,15 +30,19 @@ errorret_t uiInit(void) {
void uiUpdate(void) { void uiUpdate(void) {
} }
void uiRender(void) { errorret_t uiRender(void) {
// UI.camera.orthographic.right = SCREEN.width; const uielement_t *element = &UI_ELEMENTS[0];
// UI.camera.orthographic.top = SCREEN.height; while(element->type != UI_ELEMENT_TYPE_NULL) {
errorChain(uiElementDraw(element));
// // cameraPushMatrix(&UI.camera); if(SPRITEBATCH.spriteCount > 0) {
// spriteBatchClear(); logDebug("Finished UI element but unflushed sprites remain.\n");
}
// spriteBatchFlush(); element++;
// // cameraPopMatrix(); }
errorOk();
} }
void uiDispose(void) { void uiDispose(void) {
+3 -1
View File
@@ -26,8 +26,10 @@ void uiUpdate(void);
/** /**
* Renders the UI system. * Renders the UI system.
*
* @return Any error that occurs.
*/ */
void uiRender(void); errorret_t uiRender(void);
/** /**
* Disposes of the UI system. * Disposes of the UI system.
+79
View File
@@ -0,0 +1,79 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "uielement.h"
#include "assert/assert.h"
#include "script/scriptmanager.h"
#include "console/console.h"
#include "ui/uifps.h"
#include "engine/engine.h"
#include "ui/uitextbox.h"
uielement_t UI_ELEMENTS[] = {
{ .type = UI_ELEMENT_TYPE_NATIVE, .native = { .draw = consoleDraw } },
{ .type = UI_ELEMENT_TYPE_NATIVE, .native = { .draw = uiFPSDraw } },
{ .type = UI_ELEMENT_TYPE_NATIVE, .native = { .draw = uiTextboxDraw } },
{ .type = UI_ELEMENT_TYPE_SCRIPT, .script = { .script = "ui/test.js" } },
{ .type = UI_ELEMENT_TYPE_NULL },
};
errorret_t uiElementInit(uielement_t *element) {
assertNotNull(element, "element must not be NULL");
if(element->type == UI_ELEMENT_TYPE_SCRIPT) {
errorChain(scriptInclude(element->script.script, &element->script.module));
}
errorOk();
}
errorret_t uiElementDraw(const uielement_t *element) {
switch(element->type) {
case UI_ELEMENT_TYPE_NATIVE:
errorChain(element->native.draw());
break;
case UI_ELEMENT_TYPE_SCRIPT: {
jerry_value_t renderKey = jerry_string_sz("render");
jerry_value_t renderFn = jerry_object_get(
element->script.module, renderKey
);
jerry_value_free(renderKey);
if(!jerry_value_is_function(renderFn)) {
jerry_value_free(renderFn);
errorThrow("UI script module has no render function");
}
jerry_value_t ret = jerry_call(renderFn, element->script.module, NULL, 0);
jerry_value_free(renderFn);
if(jerry_value_is_exception(ret)) {
jerry_value_t errVal = jerry_exception_value(ret, false);
jerry_value_t errStr = jerry_value_to_string(errVal);
char_t buf[256];
jerry_size_t len = jerry_string_to_buffer(
errStr, JERRY_ENCODING_UTF8, (jerry_char_t *)buf, sizeof(buf) - 1
);
buf[len] = '\0';
jerry_value_free(errStr);
jerry_value_free(errVal);
jerry_value_free(ret);
errorThrow("UI script render error: %s", buf);
}
jerry_value_free(ret);
break;
}
default:
assertUnreachable("Invalid UI element type");
}
errorOk();
}
+46
View File
@@ -0,0 +1,46 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "error/error.h"
typedef enum {
UI_ELEMENT_TYPE_NULL,
UI_ELEMENT_TYPE_NATIVE,
UI_ELEMENT_TYPE_SCRIPT,
UI_ELEMENT_TYPE_COUNT
} uielementtype_t;
typedef struct {
uielementtype_t type;
union {
struct {
errorret_t (*draw)();
} native;
struct {
const char_t *script;
jerry_value_t module;
} script;
};
} uielement_t;
extern uielement_t UI_ELEMENTS[];
/**
* Initializes a UI element.
*/
errorret_t uiElementInit(uielement_t *element);
/**
* Draws a UI element.
*
* @param element The element to render.
* @return Any error that occurs.
*/
errorret_t uiElementDraw(const uielement_t *element);
+58
View File
@@ -0,0 +1,58 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "uifps.h"
#include "time/time.h"
#include "display/spritebatch/spritebatch.h"
#include "display/text/text.h"
uifps_t UIFPS;
errorret_t uiFPSDraw() {
char_t fpsText[32];
// Get now.
dusktimeepoch_t now = timeGetEpoch();
double_t delta = now.time - UIFPS.lastTick.time;
UIFPS.lastTick = now;
// Raw current FPS
float_t fps = delta > 0 ? 1.0 / delta : 0.0;
// Average FPS using exponential moving average
const float_t alpha = 0.1f; // Smoothing factor
if(UIFPS.fpsAverage == 0.0f) {
UIFPS.fpsAverage = fps; // Initialize average on first run
} else {
UIFPS.fpsAverage = alpha * fps + (1.0f - alpha) * UIFPS.fpsAverage;
}
snprintf(
fpsText,
sizeof(fpsText),
"%.1f/%.1fms",
UIFPS.fpsAverage,
delta * 1000.0f
);
color_t textColor;
if(fps >= 55.0f) {
textColor = COLOR_GREEN;
} else if(fps >= 45.0f) {
textColor = COLOR_YELLOW;
} else {
textColor = COLOR_RED;
}
errorChain(textDraw(
0, 0,
fpsText, textColor,
&FONT_TILESET_DEFAULT, &FONT_TEXTURE_DEFAULT
));
return spriteBatchFlush();
}
+24
View File
@@ -0,0 +1,24 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "error/error.h"
#include "time/timeepoch.h"
typedef struct {
dusktimeepoch_t lastTick;
float_t fpsAverage;
} uifps_t;
extern uifps_t UIFPS;
/**
* Draws the FPS counter on the screen, and also does the update (for now).
*
* @return Any error that occurs.
*/
errorret_t uiFPSDraw();
+125
View File
@@ -0,0 +1,125 @@
// Copyright (c) 2026 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
#include "uiframe.h"
#include "assert/assert.h"
#include "util/memory.h"
#include "display/spritebatch/spritebatch.h"
#include "display/shader/shaderunlit.h"
#include "display/color.h"
errorret_t uiFrameInit(uiframe_t *frame) {
assertNotNull(frame, "frame must not be NULL");
memoryZero(frame, sizeof(uiframe_t));
errorOk();
}
errorret_t uiFrameDraw(
const uiframe_t *frame,
const float_t x,
const float_t y,
const float_t width,
const float_t height
) {
assertNotNull(frame, "frame must not be NULL");
assertNotNull(frame->texture, "frame texture must not be NULL");
errorChain(shaderSetTexture(
&SHADER_UNLIT, SHADER_UNLIT_TEXTURE, frame->texture
));
float_t tileW = (float_t)frame->tileset.tileWidth;
float_t tileH = (float_t)frame->tileset.tileHeight;
vec4 uv;
tilesetPositionGetUV(&frame->tileset, 0, 0, uv);
errorChain(spriteBatchPush(
x, y, x + tileW, y + tileH,
#if MESH_ENABLE_COLOR
COLOR_WHITE,
#endif
uv[0], uv[1], uv[2], uv[3]
));
tilesetPositionGetUV(&frame->tileset, 1, 0, uv);
errorChain(spriteBatchPush(
x + tileW, y, x + width - tileW, y + tileH,
#if MESH_ENABLE_COLOR
COLOR_WHITE,
#endif
uv[0], uv[1], uv[2], uv[3]
));
tilesetPositionGetUV(&frame->tileset, 2, 0, uv);
errorChain(spriteBatchPush(
x + width - tileW, y, x + width, y + tileH,
#if MESH_ENABLE_COLOR
COLOR_WHITE,
#endif
uv[0], uv[1], uv[2], uv[3]
));
tilesetPositionGetUV(&frame->tileset, 0, 1, uv);
errorChain(spriteBatchPush(
x, y + tileH, x + tileW, y + height - tileH,
#if MESH_ENABLE_COLOR
COLOR_WHITE,
#endif
uv[0], uv[1], uv[2], uv[3]
));
tilesetPositionGetUV(&frame->tileset, 1, 1, uv);
errorChain(spriteBatchPush(
x + tileW, y + tileH, x + width - tileW, y + height - tileH,
#if MESH_ENABLE_COLOR
COLOR_WHITE,
#endif
uv[0], uv[1], uv[2], uv[3]
));
tilesetPositionGetUV(&frame->tileset, 2, 1, uv);
errorChain(spriteBatchPush(
x + width - tileW, y + tileH, x + width, y + height - tileH,
#if MESH_ENABLE_COLOR
COLOR_WHITE,
#endif
uv[0], uv[1], uv[2], uv[3]
));
tilesetPositionGetUV(&frame->tileset, 0, 2, uv);
errorChain(spriteBatchPush(
x, y + height - tileH, x + tileW, y + height,
#if MESH_ENABLE_COLOR
COLOR_WHITE,
#endif
uv[0], uv[1], uv[2], uv[3]
));
tilesetPositionGetUV(&frame->tileset, 1, 2, uv);
errorChain(spriteBatchPush(
x + tileW, y + height - tileH, x + width - tileW, y + height,
#if MESH_ENABLE_COLOR
COLOR_WHITE,
#endif
uv[0], uv[1], uv[2], uv[3]
));
tilesetPositionGetUV(&frame->tileset, 2, 2, uv);
errorChain(spriteBatchPush(
x + width - tileW, y + height - tileH, x + width, y + height,
#if MESH_ENABLE_COLOR
COLOR_WHITE,
#endif
uv[0], uv[1], uv[2], uv[3]
));
errorOk();
}
void uiFrameDispose(uiframe_t *frame) {
assertNotNull(frame, "frame must not be NULL");
frame->texture = NULL;
}
+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
*/
#pragma once
#include "error/error.h"
#include "display/texture/texture.h"
#include "display/texture/tileset.h"
typedef struct {
tileset_t tileset;
texture_t *texture;
} uiframe_t;
/**
* Initializes a UI frame element.
*
* @param frame The frame to initialize.
* @return Any error that occurs.
*/
errorret_t uiFrameInit(uiframe_t *frame);
/**
* Draws a UI frame using 9-slice rendering from a 3x3 tileset.
* Pushes quads to the sprite batch without flushing.
*
* @param frame The frame to draw.
* @param x Screen x position.
* @param y Screen y position.
* @param width Total width of the frame.
* @param height Total height of the frame.
* @return Any error that occurs.
*/
errorret_t uiFrameDraw(
const uiframe_t *frame,
const float_t x,
const float_t y,
const float_t width,
const float_t height
);
/**
* Disposes of a UI frame element. Does not dispose the texture.
*
* @param frame The frame to dispose.
*/
void uiFrameDispose(uiframe_t *frame);
+260
View File
@@ -0,0 +1,260 @@
// Copyright (c) 2026 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
#include "uitextbox.h"
#include "assert/assert.h"
#include "util/memory.h"
#include "util/string.h"
#include "time/time.h"
#include "input/input.h"
#include "display/screen/screen.h"
#include "display/texture/texture.h"
#include "display/spritebatch/spritebatch.h"
#include "display/shader/shaderunlit.h"
uitextbox_t UI_TEXTBOX;
errorret_t uiTextboxInit(void) {
memoryZero(&UI_TEXTBOX, sizeof(uitextbox_t));
UI_TEXTBOX.textColor = COLOR_WHITE;
UI_TEXTBOX.advanceAction = INPUT_ACTION_ACCEPT;
float_t fontW = (float_t)FONT_TILESET_DEFAULT.tileWidth;
float_t fontH = (float_t)FONT_TILESET_DEFAULT.tileHeight;
float_t tbHeight = (
(float_t)UI_TEXTBOX_LINES_PER_PAGE_MAX * fontH +
(float_t)(UI_TEXTBOX_LINES_PER_PAGE_MAX - 1) * UI_TEXTBOX_LINE_SPACING +
2.0f * fontH
);
UI_TEXTBOX.x = 10.0f;
UI_TEXTBOX.y = (float_t)SCREEN.height - tbHeight - 10.0f;
UI_TEXTBOX.width = (float_t)SCREEN.width - 20.0f;
UI_TEXTBOX.height = tbHeight;
UI_TEXTBOX.frame.tileset.columns = 3;
UI_TEXTBOX.frame.tileset.rows = 3;
UI_TEXTBOX.frame.tileset.tileCount = 9;
UI_TEXTBOX.frame.tileset.tileWidth = FONT_TILESET_DEFAULT.tileWidth;
UI_TEXTBOX.frame.tileset.tileHeight = FONT_TILESET_DEFAULT.tileHeight;
UI_TEXTBOX.frame.tileset.uv[0] = 1.0f / 3.0f;
UI_TEXTBOX.frame.tileset.uv[1] = 1.0f / 3.0f;
UI_TEXTBOX.frame.texture = &TEXTURE_WHITE;
UI_TEXTBOX.textTileset = FONT_TILESET_DEFAULT;
UI_TEXTBOX.textTexture = &FONT_TEXTURE_DEFAULT;
errorOk();
}
void uiTextboxBuildLayout(void) {
UI_TEXTBOX.lineCount = 0;
UI_TEXTBOX.pageCount = 1;
float_t frameTileW = (float_t)UI_TEXTBOX.frame.tileset.tileWidth;
float_t frameTileH = (float_t)UI_TEXTBOX.frame.tileset.tileHeight;
float_t fontW = (float_t)UI_TEXTBOX.textTileset.tileWidth;
float_t fontH = (float_t)UI_TEXTBOX.textTileset.tileHeight;
if(fontW <= 0.0f || fontH <= 0.0f) return;
float_t contentW = UI_TEXTBOX.width - 2.0f * frameTileW;
float_t contentH = UI_TEXTBOX.height - 2.0f * frameTileH;
(void)contentH;
UI_TEXTBOX.charsPerLine = (int32_t)(contentW / fontW);
UI_TEXTBOX.linesPerPage = UI_TEXTBOX_LINES_PER_PAGE_MAX;
if(UI_TEXTBOX.charsPerLine <= 0 || UI_TEXTBOX.linesPerPage <= 0) return;
if(UI_TEXTBOX.text[0] == '\0') return;
char_t *src = UI_TEXTBOX.text;
int32_t i = 0;
while(src[i] != '\0' && UI_TEXTBOX.lineCount < UI_TEXTBOX_LINES_MAX) {
if(src[i] == '\t') {
i++;
int32_t rem = UI_TEXTBOX.lineCount % UI_TEXTBOX.linesPerPage;
int32_t pad = rem > 0 ? UI_TEXTBOX.linesPerPage - rem : 0;
while(pad > 0 && UI_TEXTBOX.lineCount < UI_TEXTBOX_LINES_MAX) {
UI_TEXTBOX.lines[UI_TEXTBOX.lineCount].start = i;
UI_TEXTBOX.lines[UI_TEXTBOX.lineCount].count = 0;
UI_TEXTBOX.lineCount++;
pad--;
}
continue;
}
int32_t lineStart = i;
int32_t lineWidth = 0;
while(src[i] != '\0') {
char_t c = src[i];
if(c == '\n') {
i++;
break;
}
if(c == '\t') break;
if(c == ' ') {
int32_t wordLen = 0;
int32_t j = i + 1;
while(
src[j] != ' ' && src[j] != '\n' &&
src[j] != '\t' && src[j] != '\0'
) {
wordLen++;
j++;
}
if(
lineWidth > 0 &&
lineWidth + 1 + wordLen > UI_TEXTBOX.charsPerLine
) {
i++;
break;
}
lineWidth++;
i++;
} else {
if(lineWidth >= UI_TEXTBOX.charsPerLine) break;
lineWidth++;
i++;
}
}
UI_TEXTBOX.lines[UI_TEXTBOX.lineCount].start = lineStart;
UI_TEXTBOX.lines[UI_TEXTBOX.lineCount].count = lineWidth;
UI_TEXTBOX.lineCount++;
}
if(UI_TEXTBOX.lineCount == 0) {
UI_TEXTBOX.pageCount = 1;
} else {
int32_t div = UI_TEXTBOX.lineCount + UI_TEXTBOX.linesPerPage - 1;
UI_TEXTBOX.pageCount = div / UI_TEXTBOX.linesPerPage;
}
}
void uiTextboxSetText(const char_t *text) {
assertNotNull(text, "text must not be NULL");
stringCopy(UI_TEXTBOX.text, text, UI_TEXTBOX_TEXT_MAX);
UI_TEXTBOX.currentPage = 0;
UI_TEXTBOX.scroll = 0;
uiTextboxBuildLayout();
}
errorret_t uiTextboxUpdate(void) {
#ifdef DUSK_TIME_DYNAMIC
if(TIME.dynamicUpdate) errorOk();
#endif
if(!uiTextboxPageIsComplete()) {
UI_TEXTBOX.scroll += UI_TEXTBOX_SCROLL_CHARS_PER_TICK;
}
if(inputPressed(UI_TEXTBOX.advanceAction)) {
if(!uiTextboxPageIsComplete()) {
UI_TEXTBOX.scroll = uiTextboxGetPageCharCount();
} else if(uiTextboxHasNextPage()) {
uiTextboxNextPage();
}
}
errorOk();
}
int32_t uiTextboxGetPageCharCount(void) {
int32_t first = UI_TEXTBOX.currentPage * UI_TEXTBOX.linesPerPage;
int32_t last = first + UI_TEXTBOX.linesPerPage;
if(last > UI_TEXTBOX.lineCount) last = UI_TEXTBOX.lineCount;
int32_t total = 0;
for(int32_t i = first; i < last; i++) {
total += UI_TEXTBOX.lines[i].count;
}
return total;
}
bool_t uiTextboxPageIsComplete(void) {
return UI_TEXTBOX.scroll >= uiTextboxGetPageCharCount();
}
bool_t uiTextboxHasNextPage(void) {
return UI_TEXTBOX.currentPage + 1 < UI_TEXTBOX.pageCount;
}
void uiTextboxNextPage(void) {
if(!uiTextboxHasNextPage()) return;
UI_TEXTBOX.currentPage++;
UI_TEXTBOX.scroll = 0;
}
errorret_t uiTextboxDraw(void) {
errorChain(uiFrameDraw(
&UI_TEXTBOX.frame,
UI_TEXTBOX.x, UI_TEXTBOX.y,
UI_TEXTBOX.width, UI_TEXTBOX.height
));
errorChain(spriteBatchFlush());
if(UI_TEXTBOX.lineCount == 0 || UI_TEXTBOX.text[0] == '\0') errorOk();
errorChain(shaderSetTexture(
&SHADER_UNLIT, SHADER_UNLIT_TEXTURE, UI_TEXTBOX.textTexture
));
#if MESH_ENABLE_COLOR
#else
errorChain(shaderSetColor(
&SHADER_UNLIT, SHADER_UNLIT_COLOR, UI_TEXTBOX.textColor
));
#endif
float_t frameTileW = (float_t)UI_TEXTBOX.frame.tileset.tileWidth;
float_t frameTileH = (float_t)UI_TEXTBOX.frame.tileset.tileHeight;
float_t fontW = (float_t)UI_TEXTBOX.textTileset.tileWidth;
float_t fontH = (float_t)UI_TEXTBOX.textTileset.tileHeight;
float_t contentX = UI_TEXTBOX.x + frameTileW;
float_t contentY = UI_TEXTBOX.y + frameTileH;
int32_t pageFirst = UI_TEXTBOX.currentPage * UI_TEXTBOX.linesPerPage;
int32_t pageLast = pageFirst + UI_TEXTBOX.linesPerPage;
if(pageLast > UI_TEXTBOX.lineCount) pageLast = UI_TEXTBOX.lineCount;
int32_t charsLeft = UI_TEXTBOX.scroll;
for(int32_t li = pageFirst; li < pageLast && charsLeft > 0; li++) {
uitextboxline_t *line = &UI_TEXTBOX.lines[li];
int32_t visible = line->count < charsLeft ? line->count : charsLeft;
float_t lineY = contentY;
lineY += (float_t)(li - pageFirst) * (fontH + UI_TEXTBOX_LINE_SPACING);
for(int32_t ci = 0; ci < visible; ci++) {
char_t c = UI_TEXTBOX.text[line->start + ci];
if(c == ' ') continue;
errorChain(textDrawChar(
contentX + (float_t)ci * fontW,
lineY,
c,
#if MESH_ENABLE_COLOR
UI_TEXTBOX.textColor,
#endif
&UI_TEXTBOX.textTileset,
UI_TEXTBOX.textTexture
));
}
charsLeft -= visible;
}
errorChain(spriteBatchFlush());
errorOk();
}
void uiTextboxDispose(void) {
uiFrameDispose(&UI_TEXTBOX.frame);
UI_TEXTBOX.textTexture = NULL;
}
+112
View File
@@ -0,0 +1,112 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "uiframe.h"
#include "display/text/text.h"
#include "input/inputaction.h"
#define UI_TEXTBOX_TEXT_MAX 1024
#define UI_TEXTBOX_LINES_MAX 64
#define UI_TEXTBOX_SCROLL_CHARS_PER_TICK 1
#define UI_TEXTBOX_LINES_PER_PAGE_MAX 3
#define UI_TEXTBOX_LINE_SPACING 0.0f
typedef struct {
int32_t start;
int32_t count;
} uitextboxline_t;
typedef struct {
uiframe_t frame;
tileset_t textTileset;
texture_t *textTexture;
color_t textColor;
float_t x, y, width, height;
char_t text[UI_TEXTBOX_TEXT_MAX];
uitextboxline_t lines[UI_TEXTBOX_LINES_MAX];
int32_t lineCount;
int32_t charsPerLine;
int32_t linesPerPage;
int32_t pageCount;
int32_t currentPage;
int32_t scroll;
inputaction_t advanceAction;
} uitextbox_t;
extern uitextbox_t UI_TEXTBOX;
/**
* Initializes UI_TEXTBOX. Sets position and size from current screen
* dimensions and wires up the default font and frame textures.
* Call after displayInit().
*
* @return Any error that occurs.
*/
errorret_t uiTextboxInit(void);
/**
* Rebuilds the word-wrap and page layout from the current text and dimensions.
* Called automatically by uiTextboxSetText.
*/
void uiTextboxBuildLayout(void);
/**
* Copies text into UI_TEXTBOX and rebuilds the word-wrap / page layout.
* Resets currentPage and scroll to 0.
*
* @param text Null-terminated source string.
*/
void uiTextboxSetText(const char_t *text);
/**
* Advances the typewriter scroll by UI_TEXTBOX_SCROLL_CHARS_PER_TICK.
* Skipped on dynamic ticks when DUSK_TIME_DYNAMIC is defined.
*
* @return Any error that occurs.
*/
errorret_t uiTextboxUpdate(void);
/**
* Draws the frame and the currently visible text, including a final flush.
*
* @return Any error that occurs.
*/
errorret_t uiTextboxDraw(void);
/**
* Returns the total char count for the current page.
*
* @return Total chars on current page.
*/
int32_t uiTextboxGetPageCharCount(void);
/**
* Returns true when scroll has fully revealed the current page.
*/
bool_t uiTextboxPageIsComplete(void);
/**
* Returns true when there is at least one more page after the current one.
*/
bool_t uiTextboxHasNextPage(void);
/**
* Advances to the next page and resets scroll to 0.
* Has no effect if already on the last page.
*/
void uiTextboxNextPage(void);
/**
* Disposes of UI_TEXTBOX, nulling out texture pointers.
*/
void uiTextboxDispose(void);
+6 -1
View File
@@ -163,7 +163,12 @@ bool_t stringToI16(const char_t *str, int16_t *out) {
char_t *endptr; char_t *endptr;
errno = 0; errno = 0;
long int result = strtol(str, &endptr, 10); long int result = strtol(str, &endptr, 10);
if(errno != 0 || *endptr != '\0' || result < INT16_MIN || result > INT16_MAX) { if(
errno != 0 ||
*endptr != '\0' ||
result < INT16_MIN ||
result > INT16_MAX
) {
return false; return false;
} }
*out = (int16_t)result; *out = (int16_t)result;
+26 -6
View File
@@ -75,10 +75,6 @@ errorret_t displayInitDolphin(void) {
) )
); );
// Setup cull modes
GX_SetCullMode(GX_CULL_NONE);
GX_SetBlendMode(GX_BM_BLEND, GX_BL_SRCALPHA, GX_BL_INVSRCALPHA, GX_LO_CLEAR);
GX_SetZMode(GX_TRUE, GX_ALWAYS, GX_FALSE);
GX_SetDispCopyGamma(GX_GM_1_0); GX_SetDispCopyGamma(GX_GM_1_0);
GX_SetColorUpdate(GX_TRUE); GX_SetColorUpdate(GX_TRUE);
@@ -103,12 +99,36 @@ errorret_t displayUpdateDolphin(void) {
errorOk(); errorOk();
} }
errorret_t displaySetStateDolphin(displaystate_t state) {
if(state.flags & DISPLAY_STATE_FLAG_CULL) {
GX_SetCullMode(GX_CULL_FRONT);
} else {
GX_SetCullMode(GX_CULL_NONE);
}
if(state.flags & DISPLAY_STATE_FLAG_DEPTH_TEST) {
GX_SetZMode(GX_TRUE, GX_LEQUAL, GX_TRUE);
} else {
GX_SetZMode(GX_FALSE, GX_ALWAYS, GX_FALSE);
}
if(state.flags & DISPLAY_STATE_FLAG_BLEND) {
GX_SetBlendMode(
GX_BM_BLEND, GX_BL_SRCALPHA, GX_BL_INVSRCALPHA, GX_LO_CLEAR
);
} else {
GX_SetBlendMode(
GX_BM_NONE, GX_BL_SRCALPHA, GX_BL_INVSRCALPHA, GX_LO_CLEAR
);
}
errorOk();
}
errorret_t displaySwapDolphin(void) { errorret_t displaySwapDolphin(void) {
GX_DrawDone(); GX_DrawDone();
DISPLAY.whichFrameBuffer ^= 1; DISPLAY.whichFrameBuffer ^= 1;
// GX_SetZMode(GX_TRUE, GX_LEQUAL, GX_TRUE);
// GX_SetColorUpdate(GX_TRUE);
GX_CopyDisp(DISPLAY.frameBuffer[DISPLAY.whichFrameBuffer], GX_TRUE); GX_CopyDisp(DISPLAY.frameBuffer[DISPLAY.whichFrameBuffer], GX_TRUE);
VIDEO_SetNextFramebuffer(DISPLAY.frameBuffer[DISPLAY.whichFrameBuffer]); VIDEO_SetNextFramebuffer(DISPLAY.frameBuffer[DISPLAY.whichFrameBuffer]);
VIDEO_Flush(); VIDEO_Flush();
+7 -1
View File
@@ -7,6 +7,7 @@
#pragma once #pragma once
#include "error/error.h" #include "error/error.h"
#include "display/displaystate.h"
#define DISPLAY_DOLPHIN_FIFO_SIZE (256*1024) #define DISPLAY_DOLPHIN_FIFO_SIZE (256*1024)
@@ -30,4 +31,9 @@ errorret_t displayUpdateDolphin(void);
/** /**
* Swaps the display buffers on Dolphin. * Swaps the display buffers on Dolphin.
*/ */
errorret_t displaySwapDolphin(void); errorret_t displaySwapDolphin(void);
/**
* Sets the display state on Dolphin.
*/
errorret_t displaySetStateDolphin(displaystate_t state);

Some files were not shown because too many files have changed in this diff Show More