Compare commits
19 Commits
playertest
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| d8fe0f6923 | |||
| 581dbc2b3c | |||
| 7301d2ad76 | |||
| 3232a14d1d | |||
| 84c1f88d42 | |||
| 3695b10e4b | |||
| 6da02b25fa | |||
| 3bc544fba1 | |||
| 368d370f49 | |||
| bb29c0edef | |||
| 6edcf75a0c | |||
| 31cc186424 | |||
| 0e94c1fa6d | |||
| 6d9e2dd3e1 | |||
| 4a4adeb3c8 | |||
| ff77f8cfa0 | |||
| 36db89c36e | |||
| a9948142ad | |||
| 8d05510584 |
+1
-1
@@ -9,8 +9,8 @@ cmake_minimum_required(VERSION 3.13)
|
||||
set(CMAKE_C_STANDARD 11)
|
||||
set(CMAKE_C_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules)
|
||||
# [cmake] This is allowed only when policy CMP0079 is set to NEW.
|
||||
cmake_policy(SET CMP0079 NEW)
|
||||
# set(FETCHCONTENT_UPDATES_DISCONNECTED ON)
|
||||
|
||||
option(DUSK_BUILD_TESTS "Enable tests" OFF)
|
||||
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -5,6 +5,9 @@ function CubeEntity() {
|
||||
|
||||
this.add(MESH);
|
||||
this.add(MATERIAL);
|
||||
|
||||
this.cubeMesh = Mesh.createCube();
|
||||
this.mesh.mesh = this.cubeMesh;
|
||||
}
|
||||
|
||||
CubeEntity.prototype = Object.create(OverworldEntity.prototype);
|
||||
@@ -16,7 +19,7 @@ CubeEntity.prototype.update = function() {
|
||||
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.z += move.y * speed * TIME.delta;
|
||||
this.material.setColor(Color.rainbow());
|
||||
this.material.color = Color.rainbow();
|
||||
};
|
||||
|
||||
module = CubeEntity;
|
||||
|
||||
+20
-2
@@ -1,4 +1,5 @@
|
||||
var CubeEntity = include('entities/CubeEntity.js');
|
||||
var MoveCubeCutscene = include('cutscenes/MoveCubeCutscene.js');
|
||||
|
||||
function CubeScene() {
|
||||
this.cam = new Entity();
|
||||
@@ -13,17 +14,34 @@ function CubeScene() {
|
||||
this.spriteEnt = new Entity();
|
||||
this.spriteEnt.add(POSITION);
|
||||
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.constructor = CubeScene;
|
||||
|
||||
CubeScene.prototype.update = function() {
|
||||
this.cube.update();
|
||||
if(this.inputEnabled) {
|
||||
this.cube.update();
|
||||
}
|
||||
};
|
||||
|
||||
CubeScene.prototype.dispose = function() {
|
||||
Cutscene.stop();
|
||||
this.cam.dispose();
|
||||
this.cube.dispose();
|
||||
this.spriteEnt.dispose();
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
module = {
|
||||
render() {
|
||||
Text.draw(0, 0, "Hello World");
|
||||
SpriteBatch.flush();
|
||||
}
|
||||
};
|
||||
@@ -14,8 +14,8 @@ RUN apt-get install -y \
|
||||
python3-dotenv \
|
||||
python3-pyqt5 \
|
||||
python3-opengl \
|
||||
liblua5.3-dev \
|
||||
xz-utils \
|
||||
liblzma-dev \
|
||||
libbz2-dev \
|
||||
zlib1g-dev \
|
||||
libzip-dev \
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
add_subdirectory(dusk)
|
||||
add_subdirectory(duskrpg)
|
||||
|
||||
if(DUSK_TARGET_SYSTEM STREQUAL "linux" OR DUSK_TARGET_SYSTEM STREQUAL "knulli")
|
||||
add_subdirectory(dusklinux)
|
||||
|
||||
@@ -34,7 +34,7 @@ endif()
|
||||
|
||||
if(NOT jerryscript_FOUND)
|
||||
find_package(jerryscript REQUIRED)
|
||||
target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PRIVATE
|
||||
target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
|
||||
jerryscript::core
|
||||
jerryscript::ext
|
||||
jerryscript::port
|
||||
@@ -54,15 +54,18 @@ target_sources(${DUSK_BINARY_TARGET_NAME}
|
||||
)
|
||||
|
||||
# Subdirs
|
||||
add_subdirectory(animation)
|
||||
add_subdirectory(assert)
|
||||
add_subdirectory(asset)
|
||||
add_subdirectory(cutscene)
|
||||
add_subdirectory(item)
|
||||
add_subdirectory(story)
|
||||
add_subdirectory(console)
|
||||
add_subdirectory(display)
|
||||
add_subdirectory(log)
|
||||
add_subdirectory(engine)
|
||||
add_subdirectory(entity)
|
||||
add_subdirectory(error)
|
||||
add_subdirectory(event)
|
||||
add_subdirectory(input)
|
||||
add_subdirectory(locale)
|
||||
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.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
# Sources
|
||||
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||
PUBLIC
|
||||
game.c
|
||||
easing.c
|
||||
animation.c
|
||||
)
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
@@ -146,7 +146,9 @@ size_t assetFileLineReaderUnreadBytes(const assetfilelinereader_t *reader) {
|
||||
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->readBuffer, "Read buffer cannot be NULL.");
|
||||
return reader->readBuffer + reader->bufferStart;
|
||||
@@ -177,11 +179,16 @@ static errorret_t assetFileLineReaderAppend(
|
||||
static void assetFileLineReaderTerminate(assetfilelinereader_t *reader) {
|
||||
assertNotNull(reader, "Reader 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';
|
||||
}
|
||||
|
||||
static ssize_t assetFileLineReaderFindNewline(const assetfilelinereader_t *reader) {
|
||||
static ssize_t assetFileLineReaderFindNewline(
|
||||
const assetfilelinereader_t *reader
|
||||
) {
|
||||
size_t i;
|
||||
|
||||
assertNotNull(reader, "Reader cannot be NULL.");
|
||||
@@ -284,7 +291,10 @@ errorret_t assetFileLineReaderNext(assetfilelinereader_t *reader) {
|
||||
errorret_t ret;
|
||||
|
||||
/* 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--;
|
||||
}
|
||||
|
||||
|
||||
@@ -152,7 +152,7 @@ errorret_t assetLocaleParseHeader(
|
||||
|
||||
if(pluralIndex >= localeFile->pluralStateCount - 1) {
|
||||
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
|
||||
);
|
||||
|
||||
@@ -136,8 +136,7 @@ errorret_t consoleDraw(void) {
|
||||
&FONT_TEXTURE_DEFAULT
|
||||
));
|
||||
}
|
||||
errorChain(spriteBatchFlush());
|
||||
errorOk();
|
||||
return spriteBatchFlush();
|
||||
}
|
||||
|
||||
void consoleDispose(void) {
|
||||
|
||||
@@ -3,8 +3,7 @@
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
# Sources
|
||||
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||
PUBLIC
|
||||
event.c
|
||||
cutscene.c
|
||||
)
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
@@ -33,6 +33,7 @@ errorret_t displayInit(void) {
|
||||
#ifdef displayPlatformInit
|
||||
errorChain(displayPlatformInit());
|
||||
#endif
|
||||
errorChain(displaySetState((displaystate_t){ .flags = 0 }));
|
||||
errorChain(textureInit(
|
||||
&TEXTURE_WHITE, 4, 4,
|
||||
TEXTURE_FORMAT_RGBA, (texturedata_t){ .rgbaColors = TEXTURE_WHITE_PIXELS }
|
||||
@@ -110,6 +111,13 @@ errorret_t displayUpdate(void) {
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t displaySetState(displaystate_t state) {
|
||||
#ifdef displayPlatformSetState
|
||||
errorChain(displayPlatformSetState(state));
|
||||
#endif
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t displayDispose(void) {
|
||||
errorChain(shaderDispose(&SHADER_UNLIT));
|
||||
errorChain(spriteBatchDispose());
|
||||
|
||||
@@ -40,15 +40,26 @@ extern display_t DISPLAY;
|
||||
|
||||
/**
|
||||
* Initializes the display system.
|
||||
* @return An errorret_t indicating success or failure.
|
||||
*/
|
||||
errorret_t displayInit(void);
|
||||
|
||||
/**
|
||||
* Tells the display system to actually draw the frame.
|
||||
* @return An errorret_t indicating success or failure.
|
||||
*/
|
||||
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.
|
||||
* @return An errorret_t indicating success or failure.
|
||||
*/
|
||||
errorret_t displayDispose(void);
|
||||
@@ -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;
|
||||
@@ -119,8 +119,12 @@ void capsuleBuffer(
|
||||
{
|
||||
const float_t yTop = 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 vBot = 1.0f - (float_t)(capRings + 1) / (float_t)(2 * capRings + 1);
|
||||
const float_t vTop = (
|
||||
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++) {
|
||||
const float_t t1 = (float_t)j * sectorStep;
|
||||
@@ -152,8 +156,12 @@ void capsuleBuffer(
|
||||
const float_t lxz1 = radius * cosf(phi1);
|
||||
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 v2 = 1.0f - (float_t)(capRings + 1 + i + 1) / (float_t)(2 * capRings + 1);
|
||||
const float_t v1 = (
|
||||
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++) {
|
||||
const float_t t1 = (float_t)j * sectorStep;
|
||||
|
||||
@@ -28,7 +28,7 @@ errorret_t cubeInit();
|
||||
* Buffers a 3D axis-aligned cube into the provided vertex array.
|
||||
* 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 max The maximum XYZ corner of the cube.
|
||||
* @param color The color applied to all vertices.
|
||||
|
||||
@@ -34,10 +34,14 @@ errorret_t meshFlush(
|
||||
#ifdef meshFlushPlatform
|
||||
assertNotNull(mesh, "Mesh cannot be NULL");
|
||||
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);
|
||||
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;
|
||||
if(vertexCount == -1) {
|
||||
|
||||
@@ -9,12 +9,6 @@
|
||||
#include "display/mesh/mesh.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_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.
|
||||
* Writes TRIPRISM_VERTEX_COUNT (24) vertices (CCW winding).
|
||||
*
|
||||
* @param vertices Vertex array to write into (must hold TRIPRISM_VERTEX_COUNT).
|
||||
* @param x0,y0 First triangle vertex (XY).
|
||||
* @param x1,y1 Second triangle vertex (XY).
|
||||
* @param x2,y2 Third triangle vertex (XY).
|
||||
* @param minZ Near Z extent of the prism.
|
||||
* @param maxZ Far Z extent of the prism.
|
||||
* @param color Color applied to all vertices.
|
||||
* @param vertices Vertex array to write into.
|
||||
* @param x0,y0 First triangle vertex (XY).
|
||||
* @param x1,y1 Second triangle vertex (XY).
|
||||
* @param x2,y2 Third triangle vertex (XY).
|
||||
* @param minZ Near Z extent of the prism.
|
||||
* @param maxZ Far Z extent of the prism.
|
||||
* @param color Color applied to all vertices.
|
||||
*/
|
||||
void triPrismBuffer(
|
||||
meshvertex_t *vertices,
|
||||
|
||||
@@ -8,9 +8,9 @@
|
||||
#pragma once
|
||||
#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_FLUSH_COUNT 4
|
||||
#define SPRITEBATCH_FLUSH_COUNT 8
|
||||
#define SPRITEBATCH_SPRITES_MAX_PER_FLUSH (\
|
||||
SPRITEBATCH_SPRITES_MAX / SPRITEBATCH_FLUSH_COUNT \
|
||||
)
|
||||
|
||||
@@ -12,17 +12,19 @@
|
||||
#include "locale/localemanager.h"
|
||||
#include "display/display.h"
|
||||
#include "scene/scene.h"
|
||||
#include "cutscene/cutscene.h"
|
||||
#include "asset/asset.h"
|
||||
#include "ui/ui.h"
|
||||
#include "ui/uitextbox.h"
|
||||
#include "script/scriptmanager.h"
|
||||
#include "assert/assert.h"
|
||||
#include "entity/entitymanager.h"
|
||||
#include "entity/component/physics/entityphysics.h"
|
||||
#include "game/game.h"
|
||||
#include "physics/physicsmanager.h"
|
||||
#include "network/network.h"
|
||||
#include "system/system.h"
|
||||
#include "console/console.h"
|
||||
#include "item/backpack.h"
|
||||
|
||||
double jerry_port_current_time(void) {
|
||||
dusktimeepoch_t epoch = timeGetEpoch();
|
||||
@@ -52,11 +54,14 @@ errorret_t engineInit(const int32_t argc, const char_t **argv) {
|
||||
errorChain(scriptManagerInit());
|
||||
errorChain(displayInit());
|
||||
errorChain(uiInit());
|
||||
errorChain(uiTextboxInit());
|
||||
|
||||
errorChain(cutsceneInit());
|
||||
errorChain(sceneInit());
|
||||
entityManagerInit();
|
||||
backpackInit();
|
||||
physicsManagerInit();
|
||||
errorChain(networkInit());
|
||||
errorChain(gameInit());
|
||||
|
||||
/* Run the init script. */
|
||||
consolePrint("Engine initialized");
|
||||
@@ -72,14 +77,15 @@ errorret_t engineUpdate(void) {
|
||||
inputUpdate();
|
||||
consoleUpdate();
|
||||
uiUpdate();
|
||||
errorChain(uiTextboxUpdate());
|
||||
physicsManagerUpdate();
|
||||
errorChain(gameUpdate());
|
||||
errorChain(displayUpdate());
|
||||
|
||||
if(inputPressed(INPUT_ACTION_RAGEQUIT)) ENGINE.running = false;
|
||||
|
||||
// Scene update occurs last because only after rendering would we want to do
|
||||
// scene switching, refer to sceneSet() for information.
|
||||
errorChain(cutsceneUpdate());
|
||||
errorChain(sceneUpdate());
|
||||
|
||||
errorOk();
|
||||
@@ -90,8 +96,9 @@ void engineExit(void) {
|
||||
}
|
||||
|
||||
errorret_t engineDispose(void) {
|
||||
uiTextboxDispose();
|
||||
cutsceneDispose();
|
||||
sceneDispose();
|
||||
errorChain(gameDispose());
|
||||
errorChain(networkDispose());
|
||||
entityManagerDispose();
|
||||
localeManagerDispose();
|
||||
|
||||
@@ -36,3 +36,4 @@ errorret_t engineUpdate(void);
|
||||
* Shuts down the engine.
|
||||
*/
|
||||
errorret_t engineDispose(void);
|
||||
|
||||
|
||||
@@ -101,7 +101,7 @@ entityid_t componentGetEntitiesWithComponent(
|
||||
"Component ID OOB in entitiesWithComponent lookup"
|
||||
);
|
||||
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"
|
||||
);
|
||||
assertTrue(
|
||||
|
||||
@@ -73,7 +73,8 @@ entityid_t entityCameraGetCurrent(void) {
|
||||
void entityCameraGetForward(const entityid_t entityId, vec2 out) {
|
||||
componentid_t posComp = entityGetComponent(entityId, COMPONENT_TYPE_POSITION);
|
||||
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 fz = -pos->transform[2][2];
|
||||
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);
|
||||
|
||||
|
||||
@@ -7,11 +7,6 @@
|
||||
|
||||
#include "entitymesh.h"
|
||||
#include "entity/entitymanager.h"
|
||||
#include "assert/assert.h"
|
||||
#include "util/memory.h"
|
||||
#include "display/mesh/cube.h"
|
||||
#include "display/mesh/plane.h"
|
||||
#include "display/mesh/capsule.h"
|
||||
|
||||
void entityMeshInit(
|
||||
const entityid_t entityId,
|
||||
@@ -20,9 +15,7 @@ void entityMeshInit(
|
||||
entitymesh_t *comp = componentGetData(
|
||||
entityId, componentId, COMPONENT_TYPE_MESH
|
||||
);
|
||||
comp->mesh = &CUBE_MESH_SIMPLE;
|
||||
comp->ownedVertices = NULL;
|
||||
comp->ownsData = false;
|
||||
comp->mesh = NULL;
|
||||
}
|
||||
|
||||
mesh_t * entityMeshGetMesh(
|
||||
@@ -50,94 +43,4 @@ void entityMeshDispose(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId
|
||||
) {
|
||||
entitymesh_t *comp = componentGetData(
|
||||
entityId, componentId, COMPONENT_TYPE_MESH
|
||||
);
|
||||
if(!comp->ownsData) return;
|
||||
(void)meshDispose(&comp->ownedMesh);
|
||||
memoryFree(comp->ownedVertices);
|
||||
comp->ownedVertices = NULL;
|
||||
comp->ownsData = false;
|
||||
comp->mesh = NULL;
|
||||
}
|
||||
|
||||
errorret_t entityMeshGeneratePlane(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId,
|
||||
const float_t width,
|
||||
const float_t height
|
||||
) {
|
||||
entitymesh_t *comp = componentGetData(
|
||||
entityId, componentId, COMPONENT_TYPE_MESH
|
||||
);
|
||||
entityMeshDispose(entityId, componentId);
|
||||
|
||||
comp->ownedVertices = memoryAllocate(PLANE_VERTEX_COUNT * sizeof(meshvertex_t));
|
||||
assertNotNull(comp->ownedVertices, "Failed to allocate plane vertices");
|
||||
|
||||
vec3 min = { -width * 0.5f, 0.0f, -height * 0.5f };
|
||||
vec3 max = { width * 0.5f, 0.0f, height * 0.5f };
|
||||
vec2 uvMin = { 0.0f, 0.0f };
|
||||
vec2 uvMax = { 1.0f, 1.0f };
|
||||
planeBuffer(
|
||||
comp->ownedVertices,
|
||||
PLANE_AXIS_XZ,
|
||||
min,
|
||||
max
|
||||
#if MESH_ENABLE_COLOR
|
||||
, COLOR_WHITE_4B
|
||||
#endif
|
||||
, uvMin,
|
||||
uvMax
|
||||
);
|
||||
|
||||
errorChain(meshInit(
|
||||
&comp->ownedMesh,
|
||||
PLANE_PRIMITIVE_TYPE,
|
||||
PLANE_VERTEX_COUNT,
|
||||
comp->ownedVertices
|
||||
));
|
||||
|
||||
comp->mesh = &comp->ownedMesh;
|
||||
comp->ownsData = true;
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t entityMeshGenerateCapsule(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId,
|
||||
const float_t radius,
|
||||
const float_t halfHeight
|
||||
) {
|
||||
entitymesh_t *comp = componentGetData(
|
||||
entityId, componentId, COMPONENT_TYPE_MESH
|
||||
);
|
||||
entityMeshDispose(entityId, componentId);
|
||||
|
||||
comp->ownedVertices = memoryAllocate(CAPSULE_VERTEX_COUNT * sizeof(meshvertex_t));
|
||||
assertNotNull(comp->ownedVertices, "Failed to allocate capsule vertices");
|
||||
|
||||
vec3 center = { 0.0f, 0.0f, 0.0f };
|
||||
capsuleBuffer(
|
||||
comp->ownedVertices,
|
||||
center,
|
||||
radius,
|
||||
halfHeight,
|
||||
CAPSULE_CAP_RINGS,
|
||||
CAPSULE_SECTORS
|
||||
#if MESH_ENABLE_COLOR
|
||||
, COLOR_WHITE_4B
|
||||
#endif
|
||||
);
|
||||
|
||||
errorChain(meshInit(
|
||||
&comp->ownedMesh,
|
||||
CAPSULE_PRIMITIVE_TYPE,
|
||||
CAPSULE_VERTEX_COUNT,
|
||||
comp->ownedVertices
|
||||
));
|
||||
|
||||
comp->mesh = &comp->ownedMesh;
|
||||
comp->ownsData = true;
|
||||
errorOk();
|
||||
}
|
||||
@@ -11,9 +11,6 @@
|
||||
|
||||
typedef struct {
|
||||
mesh_t *mesh;
|
||||
mesh_t ownedMesh;
|
||||
meshvertex_t *ownedVertices;
|
||||
bool_t ownsData;
|
||||
} entitymesh_t;
|
||||
|
||||
/**
|
||||
@@ -44,7 +41,7 @@ mesh_t * entityMeshGetMesh(
|
||||
*
|
||||
* @param entityId The entity 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(
|
||||
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 componentId The component ID.
|
||||
@@ -62,35 +59,3 @@ void entityMeshDispose(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId
|
||||
);
|
||||
|
||||
/**
|
||||
* Generates an XZ-aligned plane mesh owned by the component.
|
||||
*
|
||||
* @param entityId The entity ID.
|
||||
* @param componentId The component ID.
|
||||
* @param width Width of the plane along the X axis.
|
||||
* @param height Height of the plane along the Z axis.
|
||||
* @return Error return value.
|
||||
*/
|
||||
errorret_t entityMeshGeneratePlane(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId,
|
||||
const float_t width,
|
||||
const float_t height
|
||||
);
|
||||
|
||||
/**
|
||||
* Generates a Y-axis capsule mesh owned by the component.
|
||||
*
|
||||
* @param entityId The entity ID.
|
||||
* @param componentId The component ID.
|
||||
* @param radius Radius of the cylinder and hemisphere caps.
|
||||
* @param halfHeight Half-height of the cylindrical section.
|
||||
* @return Error return value.
|
||||
*/
|
||||
errorret_t entityMeshGenerateCapsule(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId,
|
||||
const float_t radius,
|
||||
const float_t halfHeight
|
||||
);
|
||||
@@ -83,7 +83,9 @@ errorret_t errorChainImpl(
|
||||
assertNotNull(retval.state->message, "Message cannot be NULL");
|
||||
|
||||
// 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");
|
||||
char_t *newLine = (char_t *)memoryAllocate(newLineLen + 1);
|
||||
snprintf(newLine, newLineLen + 1, ERROR_LINE_FORMAT, file, line, function);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
);
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
@@ -27,13 +27,6 @@ errorret_t inputInit(void) {
|
||||
errorChain(inputInitPlatform());
|
||||
#endif
|
||||
|
||||
eventInit(
|
||||
&INPUT.eventPressed, INPUT.pressedListeners, INPUT_LISTENER_PRESSED_MAX
|
||||
);
|
||||
eventInit(
|
||||
&INPUT.eventReleased, INPUT.releasedListeners, INPUT_LISTENER_RELEASED_MAX
|
||||
);
|
||||
|
||||
errorOk();
|
||||
}
|
||||
|
||||
@@ -94,28 +87,6 @@ void inputUpdate(void) {
|
||||
#ifdef DUSK_TIME_DYNAMIC
|
||||
if(TIME.dynamicUpdate) return;
|
||||
#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) {
|
||||
|
||||
@@ -9,23 +9,13 @@
|
||||
#include "error/error.h"
|
||||
#include "inputbutton.h"
|
||||
#include "inputaction.h"
|
||||
#include "event/event.h"
|
||||
|
||||
#define INPUT_LISTENER_PRESSED_MAX 16
|
||||
#define INPUT_LISTENER_RELEASED_MAX INPUT_LISTENER_PRESSED_MAX
|
||||
|
||||
typedef struct {
|
||||
const inputaction_t action;
|
||||
} inputevent_t;
|
||||
|
||||
typedef struct {
|
||||
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;
|
||||
} input_t;
|
||||
|
||||
|
||||
@@ -25,17 +25,11 @@ typedef enum {
|
||||
|
||||
typedef struct {
|
||||
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;
|
||||
|
||||
#ifdef DUSK_NETWORK_IPV6
|
||||
typedef struct {
|
||||
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;
|
||||
#endif
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ void physicsWorldStep(const float_t dt) {
|
||||
}
|
||||
|
||||
/* 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++) {
|
||||
if(!positions[i]) continue;
|
||||
entityphysics_t *phys = physBodies[i];
|
||||
|
||||
+50
-164
@@ -11,14 +11,13 @@
|
||||
#include "display/screen/screen.h"
|
||||
#include "entity/entitymanager.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 "console/console.h"
|
||||
#include "util/string.h"
|
||||
#include "script/scriptmanager.h"
|
||||
#include "script/module/scene/modulescene.h"
|
||||
#include "script/module/cutscene/modulecutscene.h"
|
||||
#include "ui/ui.h"
|
||||
|
||||
scene_t SCENE;
|
||||
|
||||
@@ -55,8 +54,14 @@ errorret_t sceneRender(void) {
|
||||
COMPONENT_TYPE_CAMERA, camEnts, camComps
|
||||
);
|
||||
|
||||
shader_t *shaderCurrent = NULL;
|
||||
mat4 view, proj, model;
|
||||
|
||||
errorChain(displaySetState((displaystate_t){
|
||||
// .flags = DISPLAY_STATE_FLAG_CULL | DISPLAY_STATE_FLAG_DEPTH_TEST
|
||||
.flags = 0
|
||||
}));
|
||||
|
||||
// For each camera
|
||||
for(entityid_t camIndex = 0; camIndex < camCount; camIndex++) {
|
||||
entityid_t camEnt = camEnts[camIndex];
|
||||
@@ -70,7 +75,8 @@ errorret_t sceneRender(void) {
|
||||
entityCameraGetProjection(camEnt, camComp, proj);
|
||||
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++) {
|
||||
// Does this entity have a material?
|
||||
componentid_t matComp = entityGetComponent(
|
||||
@@ -81,9 +87,13 @@ errorret_t sceneRender(void) {
|
||||
componentid_t meshComp = entityGetComponent(
|
||||
entityId, COMPONENT_TYPE_MESH
|
||||
);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -93,14 +103,7 @@ errorret_t sceneRender(void) {
|
||||
);
|
||||
shader_t *shader = entityMaterialGetShader(entityId, matComp);
|
||||
if(shader == NULL) {
|
||||
logError("Entity with material component without 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");
|
||||
logError("Entity with material but no shader found\n");
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -115,9 +118,13 @@ errorret_t sceneRender(void) {
|
||||
}
|
||||
|
||||
// Render the mesh.
|
||||
errorChain(shaderBind(shader));
|
||||
errorChain(shaderSetMatrix(shader, SHADER_UNLIT_PROJECTION, proj));
|
||||
errorChain(shaderSetMatrix(shader, SHADER_UNLIT_VIEW, view));
|
||||
if(shaderCurrent != shader) {
|
||||
shaderCurrent = shader;
|
||||
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(shaderSetMaterial(shader, material));
|
||||
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();
|
||||
|
||||
// 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) {
|
||||
@@ -292,6 +177,7 @@ errorret_t sceneSetImmediate(const char_t *scene) {
|
||||
SCENE.sceneActive = false;
|
||||
}
|
||||
|
||||
moduleCutsceneReset();
|
||||
moduleSceneReset();
|
||||
|
||||
stringCopy(
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
#pragma once
|
||||
#include "script/scriptmanager.h"
|
||||
#include "asset/assetfile.h"
|
||||
#include "event/event.h"
|
||||
|
||||
#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) {
|
||||
moduleBaseRequireArgs(1);
|
||||
if(argc < 1) return moduleBaseThrow("Expected at least 1 argument");
|
||||
if(!jerry_value_is_boolean(args[0])) {
|
||||
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
|
||||
);
|
||||
}
|
||||
@@ -42,7 +42,11 @@ moduleBaseFunction(moduleColorGetA) {
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleColorSetR) {
|
||||
moduleBaseRequireArgs(1); moduleBaseRequireNumber(0);
|
||||
if(argc < 1) {
|
||||
return moduleBaseThrow("Expected at least 1 argument");
|
||||
}
|
||||
moduleBaseRequireNumber(0);
|
||||
|
||||
color_t *c = moduleColorGet(callInfo);
|
||||
if(!c) return jerry_undefined();
|
||||
c->r = (colorchannel8_t)jerry_value_as_number(args[0]);
|
||||
@@ -50,7 +54,11 @@ moduleBaseFunction(moduleColorSetR) {
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleColorSetG) {
|
||||
moduleBaseRequireArgs(1); moduleBaseRequireNumber(0);
|
||||
if(argc < 1) {
|
||||
return moduleBaseThrow("Expected at least 1 argument");
|
||||
}
|
||||
moduleBaseRequireNumber(0);
|
||||
|
||||
color_t *c = moduleColorGet(callInfo);
|
||||
if(!c) return jerry_undefined();
|
||||
c->g = (colorchannel8_t)jerry_value_as_number(args[0]);
|
||||
@@ -58,7 +66,11 @@ moduleBaseFunction(moduleColorSetG) {
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleColorSetB) {
|
||||
moduleBaseRequireArgs(1); moduleBaseRequireNumber(0);
|
||||
if(argc < 1) {
|
||||
return moduleBaseThrow("Expected at least 1 argument");
|
||||
}
|
||||
moduleBaseRequireNumber(0);
|
||||
|
||||
color_t *c = moduleColorGet(callInfo);
|
||||
if(!c) return jerry_undefined();
|
||||
c->b = (colorchannel8_t)jerry_value_as_number(args[0]);
|
||||
@@ -66,7 +78,11 @@ moduleBaseFunction(moduleColorSetB) {
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleColorSetA) {
|
||||
moduleBaseRequireArgs(1); moduleBaseRequireNumber(0);
|
||||
if(argc < 1) {
|
||||
return moduleBaseThrow("Expected at least 1 argument");
|
||||
}
|
||||
moduleBaseRequireNumber(0);
|
||||
|
||||
color_t *c = moduleColorGet(callInfo);
|
||||
if(!c) return jerry_undefined();
|
||||
c->a = (colorchannel8_t)jerry_value_as_number(args[0]);
|
||||
|
||||
@@ -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) {
|
||||
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]);
|
||||
componentid_t comp = entityAddComponent(id, COMPONENT_TYPE_CAMERA);
|
||||
componenthandle_t h = { .eid = id, .cid = comp };
|
||||
|
||||
@@ -7,10 +7,10 @@
|
||||
|
||||
#pragma once
|
||||
#include "script/module/modulebase.h"
|
||||
#include "script/module/display/modulecolor.h"
|
||||
#include "script/scriptproto.h"
|
||||
#include "entity/entity.h"
|
||||
#include "entity/component/display/entitymaterial.h"
|
||||
#include "display/color.h"
|
||||
#include "moduleentityposition.h"
|
||||
|
||||
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) {
|
||||
moduleBaseRequireArgs(1);
|
||||
if(!jerry_value_is_object(args[0])) {
|
||||
return moduleBaseThrow("expected color object");
|
||||
if(argc < 1 || !jerry_value_is_object(args[0])) {
|
||||
return moduleBaseThrow("Material.color: 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);
|
||||
if(!mat) return jerry_undefined();
|
||||
|
||||
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;
|
||||
memoryCopy(&mat->material.unlit.color, color, sizeof(color_t));
|
||||
return jerry_undefined();
|
||||
}
|
||||
|
||||
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]);
|
||||
componentid_t comp = entityAddComponent(id, COMPONENT_TYPE_MATERIAL);
|
||||
componenthandle_t h = { .eid = id, .cid = comp };
|
||||
@@ -83,9 +65,8 @@ static void moduleEntityMATERIAL(void) {
|
||||
NULL
|
||||
);
|
||||
|
||||
scriptProtoDefineFunc(
|
||||
&MODULE_ENTITY_MATERIAL_PROTO,
|
||||
"setColor",
|
||||
moduleEntityMaterialSetColor
|
||||
scriptProtoDefineProp(
|
||||
&MODULE_ENTITY_MATERIAL_PROTO, "color",
|
||||
moduleEntityMaterialGetColor, moduleEntityMaterialSetColor
|
||||
);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#pragma once
|
||||
#include "script/module/modulebase.h"
|
||||
#include "script/scriptproto.h"
|
||||
#include "script/module/display/modulemesh.h"
|
||||
#include "entity/entity.h"
|
||||
#include "entity/component/display/entitymesh.h"
|
||||
#include "moduleentityposition.h"
|
||||
@@ -24,12 +25,30 @@ static entitymesh_t * moduleEntityMeshGet(
|
||||
return (entitymesh_t*)componentGetData(h->eid, h->cid, COMPONENT_TYPE_MESH);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleEntityMeshAdd) {
|
||||
moduleBaseRequireArgs(1); moduleBaseRequireNumber(0);
|
||||
entityid_t id = (entityid_t)jerry_value_as_number(args[0]);
|
||||
componentid_t comp = entityAddComponent(id, COMPONENT_TYPE_MESH);
|
||||
componenthandle_t h = { .eid = id, .cid = comp };
|
||||
return scriptProtoCreateValue(&MODULE_ENTITY_MESH_PROTO, &h);
|
||||
moduleBaseFunction(moduleEntityMeshGetMesh) {
|
||||
entitymesh_t *comp = moduleEntityMeshGet(callInfo);
|
||||
if(!comp || !comp->mesh) return jerry_undefined();
|
||||
return moduleBaseWrapPointer(comp->mesh);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleEntityMeshSetMesh) {
|
||||
if(argc < 1) return moduleBaseThrow("Expected a Mesh argument");
|
||||
entitymesh_t *comp = moduleEntityMeshGet(callInfo);
|
||||
if(!comp) return jerry_undefined();
|
||||
|
||||
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) {
|
||||
@@ -37,4 +56,8 @@ static void moduleEntityMESH(void) {
|
||||
&MODULE_ENTITY_MESH_PROTO, NULL,
|
||||
sizeof(componenthandle_t), NULL
|
||||
);
|
||||
scriptProtoDefineProp(
|
||||
&MODULE_ENTITY_MESH_PROTO, "mesh",
|
||||
moduleEntityMeshGetMesh, moduleEntityMeshSetMesh
|
||||
);
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ moduleBaseFunction(moduleEntityPhysicsGetVelocity) {
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleEntityPhysicsSetVelocity) {
|
||||
moduleBaseRequireArgs(1);
|
||||
if(argc < 1) return moduleBaseThrow("Expected at least 1 argument");
|
||||
entityphysics_t *phys = moduleEntityPhysicsGet(callInfo);
|
||||
if(!phys) return jerry_undefined();
|
||||
vec3 v;
|
||||
@@ -52,11 +52,15 @@ moduleBaseFunction(moduleEntityPhysicsGetOnGround) {
|
||||
moduleBaseFunction(moduleEntityPhysicsGetBodyType) {
|
||||
entityphysics_t *phys = moduleEntityPhysicsGet(callInfo);
|
||||
if(!phys) return jerry_undefined();
|
||||
return jerry_number((double)phys->type);
|
||||
return jerry_number(phys->type);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleEntityPhysicsSetBodyType) {
|
||||
moduleBaseRequireArgs(1); moduleBaseRequireNumber(0);
|
||||
if(argc < 1) {
|
||||
return moduleBaseThrow("Expected at least 1 argument");
|
||||
}
|
||||
moduleBaseRequireNumber(0);
|
||||
|
||||
entityphysics_t *phys = moduleEntityPhysicsGet(callInfo);
|
||||
if(!phys) return jerry_undefined();
|
||||
phys->type = (physicsbodytype_t)(int32_t)jerry_value_as_number(args[0]);
|
||||
@@ -64,7 +68,7 @@ moduleBaseFunction(moduleEntityPhysicsSetBodyType) {
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleEntityPhysicsApplyImpulse) {
|
||||
moduleBaseRequireArgs(1);
|
||||
if(argc < 1) return moduleBaseThrow("Expected at least 1 argument");
|
||||
entityphysics_t *phys = moduleEntityPhysicsGet(callInfo);
|
||||
if(!phys) return jerry_undefined();
|
||||
if(phys->type == PHYSICS_BODY_STATIC) return jerry_undefined();
|
||||
@@ -77,7 +81,7 @@ moduleBaseFunction(moduleEntityPhysicsApplyImpulse) {
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleEntityPhysicsSetShapeCube) {
|
||||
moduleBaseRequireArgs(1);
|
||||
if(argc < 1) return moduleBaseThrow("Expected at least 1 argument");
|
||||
entityphysics_t *phys = moduleEntityPhysicsGet(callInfo);
|
||||
if(!phys) return jerry_undefined();
|
||||
vec3 half;
|
||||
@@ -90,7 +94,11 @@ moduleBaseFunction(moduleEntityPhysicsSetShapeCube) {
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleEntityPhysicsSetShapeSphere) {
|
||||
moduleBaseRequireArgs(1); moduleBaseRequireNumber(0);
|
||||
if(argc < 1) {
|
||||
return moduleBaseThrow("Expected at least 1 argument");
|
||||
}
|
||||
moduleBaseRequireNumber(0);
|
||||
|
||||
entityphysics_t *phys = moduleEntityPhysicsGet(callInfo);
|
||||
if(!phys) return jerry_undefined();
|
||||
phys->shape.type = PHYSICS_SHAPE_SPHERE;
|
||||
@@ -99,7 +107,7 @@ moduleBaseFunction(moduleEntityPhysicsSetShapeSphere) {
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleEntityPhysicsSetShapeCapsule) {
|
||||
moduleBaseRequireArgs(2);
|
||||
if(argc < 2) return moduleBaseThrow("Expected at least 2 arguments");
|
||||
moduleBaseRequireNumber(0); moduleBaseRequireNumber(1);
|
||||
entityphysics_t *phys = moduleEntityPhysicsGet(callInfo);
|
||||
if(!phys) return jerry_undefined();
|
||||
@@ -111,7 +119,11 @@ moduleBaseFunction(moduleEntityPhysicsSetShapeCapsule) {
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleEntityPhysicsSetShapePlane) {
|
||||
moduleBaseRequireArgs(2); moduleBaseRequireNumber(1);
|
||||
if(argc < 2) {
|
||||
return moduleBaseThrow("Expected at least 2 arguments");
|
||||
}
|
||||
moduleBaseRequireNumber(1);
|
||||
|
||||
entityphysics_t *phys = moduleEntityPhysicsGet(callInfo);
|
||||
if(!phys) return jerry_undefined();
|
||||
vec3 normal;
|
||||
@@ -125,7 +137,11 @@ moduleBaseFunction(moduleEntityPhysicsSetShapePlane) {
|
||||
}
|
||||
|
||||
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]);
|
||||
componentid_t comp = entityAddComponent(id, COMPONENT_TYPE_PHYSICS);
|
||||
componenthandle_t h = { .eid = id, .cid = comp };
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
#include "entity/entity.h"
|
||||
#include "entity/component/display/entityposition.h"
|
||||
|
||||
// Shared component handle struct — defined once, used by all component modules.
|
||||
#ifndef COMPONENT_HANDLE_DEFINED
|
||||
#define COMPONENT_HANDLE_DEFINED
|
||||
typedef struct {
|
||||
@@ -42,7 +41,7 @@ moduleBaseFunction(moduleEntityPositionGetPosition) {
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleEntityPositionSetPosition) {
|
||||
moduleBaseRequireArgs(1);
|
||||
if(argc < 1) return moduleBaseThrow("Expected at least 1 argument");
|
||||
entityposition_t *pos = moduleEntityPositionGet(callInfo);
|
||||
if(!pos) return jerry_undefined();
|
||||
vec3 v;
|
||||
@@ -63,7 +62,7 @@ moduleBaseFunction(moduleEntityPositionGetRotation) {
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleEntityPositionSetRotation) {
|
||||
moduleBaseRequireArgs(1);
|
||||
if(argc < 1) return moduleBaseThrow("Expected at least 1 argument");
|
||||
entityposition_t *pos = moduleEntityPositionGet(callInfo);
|
||||
if(!pos) return jerry_undefined();
|
||||
vec3 v;
|
||||
@@ -84,7 +83,7 @@ moduleBaseFunction(moduleEntityPositionGetScale) {
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleEntityPositionSetScale) {
|
||||
moduleBaseRequireArgs(1);
|
||||
if(argc < 1) return moduleBaseThrow("Expected at least 1 argument");
|
||||
entityposition_t *pos = moduleEntityPositionGet(callInfo);
|
||||
if(!pos) return jerry_undefined();
|
||||
vec3 v;
|
||||
@@ -97,7 +96,7 @@ moduleBaseFunction(moduleEntityPositionSetScale) {
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleEntityPositionLookAt) {
|
||||
moduleBaseRequireArgs(1);
|
||||
if(argc < 1) return moduleBaseThrow("Expected at least 1 argument");
|
||||
entityposition_t *pos = moduleEntityPositionGet(callInfo);
|
||||
if(!pos) return jerry_undefined();
|
||||
vec3 target;
|
||||
@@ -114,7 +113,11 @@ moduleBaseFunction(moduleEntityPositionLookAt) {
|
||||
}
|
||||
|
||||
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]);
|
||||
componentid_t comp = entityAddComponent(id, COMPONENT_TYPE_POSITION);
|
||||
componenthandle_t h = { .eid = id, .cid = comp };
|
||||
|
||||
@@ -15,7 +15,7 @@ static scriptproto_t MODULE_INPUT_PROTO;
|
||||
|
||||
// Static Methods
|
||||
moduleBaseFunction(moduleInputBind) {
|
||||
moduleBaseRequireArgs(2);
|
||||
if(argc < 2) return moduleBaseThrow("Expected at least 2 arguments");
|
||||
moduleBaseRequireString(0);
|
||||
moduleBaseRequireNumber(1);
|
||||
|
||||
@@ -40,7 +40,11 @@ moduleBaseFunction(moduleInputBind) {
|
||||
}
|
||||
|
||||
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]);
|
||||
if(action <= INPUT_ACTION_NULL || action >= INPUT_ACTION_COUNT) {
|
||||
return moduleBaseThrow("Input.isDown: invalid action");
|
||||
@@ -49,7 +53,11 @@ moduleBaseFunction(moduleInputIsDown) {
|
||||
}
|
||||
|
||||
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]);
|
||||
if(action <= INPUT_ACTION_NULL || action >= INPUT_ACTION_COUNT) {
|
||||
return moduleBaseThrow("Input.pressed: invalid action");
|
||||
@@ -58,7 +66,11 @@ moduleBaseFunction(moduleInputPressed) {
|
||||
}
|
||||
|
||||
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]);
|
||||
if(action <= INPUT_ACTION_NULL || action >= INPUT_ACTION_COUNT) {
|
||||
return moduleBaseThrow("Input.released: invalid action");
|
||||
@@ -67,7 +79,11 @@ moduleBaseFunction(moduleInputReleased) {
|
||||
}
|
||||
|
||||
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]);
|
||||
if(action <= INPUT_ACTION_NULL || action >= INPUT_ACTION_COUNT) {
|
||||
return moduleBaseThrow("Input.getValue: invalid action");
|
||||
@@ -76,7 +92,7 @@ moduleBaseFunction(moduleInputGetValue) {
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleInputAxis) {
|
||||
moduleBaseRequireArgs(2);
|
||||
if(argc < 2) return moduleBaseThrow("Expected at least 2 arguments");
|
||||
moduleBaseRequireNumber(0); moduleBaseRequireNumber(1);
|
||||
const inputaction_t neg = (inputaction_t)jerry_value_as_number(args[0]);
|
||||
const inputaction_t pos = (inputaction_t)jerry_value_as_number(args[1]);
|
||||
@@ -90,7 +106,7 @@ moduleBaseFunction(moduleInputAxis) {
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleInputAxis2D) {
|
||||
moduleBaseRequireArgs(4);
|
||||
if(argc < 4) return moduleBaseThrow("Expected at least 4 arguments");
|
||||
moduleBaseRequireNumber(0); moduleBaseRequireNumber(1);
|
||||
moduleBaseRequireNumber(2); moduleBaseRequireNumber(3);
|
||||
const inputaction_t negX = (inputaction_t)jerry_value_as_number(args[0]);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -28,7 +28,7 @@ moduleBaseFunction(moduleMatConstructor) {
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleMatMul) {
|
||||
moduleBaseRequireArgs(1);
|
||||
if(argc < 1) return moduleBaseThrow("Expected at least 1 argument");
|
||||
float_t (*a)[4] = (float_t (*)[4])moduleMatGet(callInfo);
|
||||
if(!a) return moduleBaseThrow("Mat4.mul: invalid this");
|
||||
float_t (*b)[4] = (float_t (*)[4])scriptProtoGetValue(
|
||||
@@ -63,7 +63,7 @@ moduleBaseFunction(moduleMatDeterminant) {
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleMatMulVec3) {
|
||||
moduleBaseRequireArgs(1);
|
||||
if(argc < 1) return moduleBaseThrow("Expected at least 1 argument");
|
||||
float_t (*m)[4] = (float_t (*)[4])moduleMatGet(callInfo);
|
||||
if(!m) return moduleBaseThrow("Mat4.mulVec3: invalid this");
|
||||
vec3 vin;
|
||||
@@ -78,7 +78,7 @@ moduleBaseFunction(moduleMatMulVec3) {
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleMatMulVec4) {
|
||||
moduleBaseRequireArgs(1);
|
||||
if(argc < 1) return moduleBaseThrow("Expected at least 1 argument");
|
||||
float_t (*m)[4] = (float_t (*)[4])moduleMatGet(callInfo);
|
||||
if(!m) return moduleBaseThrow("Mat4.mulVec4: invalid this");
|
||||
vec4 vin;
|
||||
@@ -91,7 +91,7 @@ moduleBaseFunction(moduleMatMulVec4) {
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleMatTranslate) {
|
||||
moduleBaseRequireArgs(1);
|
||||
if(argc < 1) return moduleBaseThrow("Expected at least 1 argument");
|
||||
float_t (*m)[4] = (float_t (*)[4])moduleMatGet(callInfo);
|
||||
if(!m) return moduleBaseThrow("Mat4.translate: invalid this");
|
||||
vec3 tv;
|
||||
@@ -105,7 +105,7 @@ moduleBaseFunction(moduleMatTranslate) {
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleMatScale) {
|
||||
moduleBaseRequireArgs(1);
|
||||
if(argc < 1) return moduleBaseThrow("Expected at least 1 argument");
|
||||
float_t (*m)[4] = (float_t (*)[4])moduleMatGet(callInfo);
|
||||
if(!m) return moduleBaseThrow("Mat4.scale: invalid this");
|
||||
vec3 sv;
|
||||
@@ -125,7 +125,7 @@ moduleBaseFunction(moduleMatStaticIdentity) {
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleMatStaticPerspective) {
|
||||
moduleBaseRequireArgs(4);
|
||||
if(argc < 4) return moduleBaseThrow("Expected at least 4 arguments");
|
||||
moduleBaseRequireNumber(0); moduleBaseRequireNumber(1);
|
||||
moduleBaseRequireNumber(2); moduleBaseRequireNumber(3);
|
||||
mat4 r;
|
||||
@@ -140,7 +140,7 @@ moduleBaseFunction(moduleMatStaticPerspective) {
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleMatStaticLookAt) {
|
||||
moduleBaseRequireArgs(3);
|
||||
if(argc < 3) return moduleBaseThrow("Expected at least 3 arguments");
|
||||
vec3 eye, center, up;
|
||||
if(!moduleVec3Check(args[0], eye)) {
|
||||
return moduleBaseThrow("Mat4.lookAt: eye must be a Vec3");
|
||||
|
||||
@@ -55,7 +55,7 @@ moduleBaseFunction(moduleVec2SetY) {
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleVec2Dot) {
|
||||
moduleBaseRequireArgs(1);
|
||||
if(argc < 1) return moduleBaseThrow("Expected at least 1 argument");
|
||||
float_t *a = moduleVec2Get(callInfo);
|
||||
if(!a) return moduleBaseThrow("Vec2.dot: invalid this");
|
||||
float_t *b = moduleVec2From(args[0]);
|
||||
@@ -92,7 +92,7 @@ moduleBaseFunction(moduleVec2Negate) {
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleVec2Add) {
|
||||
moduleBaseRequireArgs(1);
|
||||
if(argc < 1) return moduleBaseThrow("Expected at least 1 argument");
|
||||
float_t *a = moduleVec2Get(callInfo);
|
||||
if(!a) return moduleBaseThrow("Vec2.add: invalid this");
|
||||
float_t *b = moduleVec2From(args[0]);
|
||||
@@ -103,7 +103,7 @@ moduleBaseFunction(moduleVec2Add) {
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleVec2Sub) {
|
||||
moduleBaseRequireArgs(1);
|
||||
if(argc < 1) return moduleBaseThrow("Expected at least 1 argument");
|
||||
float_t *a = moduleVec2Get(callInfo);
|
||||
if(!a) return moduleBaseThrow("Vec2.sub: invalid this");
|
||||
float_t *b = moduleVec2From(args[0]);
|
||||
@@ -114,7 +114,11 @@ moduleBaseFunction(moduleVec2Sub) {
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleVec2Scale) {
|
||||
moduleBaseRequireArgs(1); moduleBaseRequireNumber(0);
|
||||
if(argc < 1) {
|
||||
return moduleBaseThrow("Expected at least 1 argument");
|
||||
}
|
||||
moduleBaseRequireNumber(0);
|
||||
|
||||
float_t *v = moduleVec2Get(callInfo);
|
||||
if(!v) return moduleBaseThrow("Vec2.scale: invalid this");
|
||||
vec2 r;
|
||||
@@ -123,7 +127,11 @@ moduleBaseFunction(moduleVec2Scale) {
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleVec2Lerp) {
|
||||
moduleBaseRequireArgs(2); moduleBaseRequireNumber(1);
|
||||
if(argc < 2) {
|
||||
return moduleBaseThrow("Expected at least 2 arguments");
|
||||
}
|
||||
moduleBaseRequireNumber(1);
|
||||
|
||||
float_t *a = moduleVec2Get(callInfo);
|
||||
if(!a) return moduleBaseThrow("Vec2.lerp: invalid this");
|
||||
float_t *b = moduleVec2From(args[0]);
|
||||
@@ -134,7 +142,7 @@ moduleBaseFunction(moduleVec2Lerp) {
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleVec2Distance) {
|
||||
moduleBaseRequireArgs(1);
|
||||
if(argc < 1) return moduleBaseThrow("Expected at least 1 argument");
|
||||
float_t *a = moduleVec2Get(callInfo);
|
||||
if(!a) return moduleBaseThrow("Vec2.distance: invalid this");
|
||||
float_t *b = moduleVec2From(args[0]);
|
||||
@@ -176,14 +184,14 @@ static void moduleVec2(void) {
|
||||
|
||||
scriptProtoDefineToString(&MODULE_VEC2_PROTO, moduleVec2ToString);
|
||||
|
||||
scriptProtoDefineFunc(&MODULE_VEC2_PROTO, "dot", moduleVec2Dot);
|
||||
scriptProtoDefineFunc(&MODULE_VEC2_PROTO, "length", moduleVec2Length);
|
||||
scriptProtoDefineFunc(&MODULE_VEC2_PROTO, "lengthSq", moduleVec2LengthSq);
|
||||
scriptProtoDefineFunc(&MODULE_VEC2_PROTO, "dot", moduleVec2Dot);
|
||||
scriptProtoDefineFunc(&MODULE_VEC2_PROTO, "length", moduleVec2Length);
|
||||
scriptProtoDefineFunc(&MODULE_VEC2_PROTO, "lengthSq", moduleVec2LengthSq);
|
||||
scriptProtoDefineFunc(&MODULE_VEC2_PROTO, "normalize", moduleVec2Normalize);
|
||||
scriptProtoDefineFunc(&MODULE_VEC2_PROTO, "negate", moduleVec2Negate);
|
||||
scriptProtoDefineFunc(&MODULE_VEC2_PROTO, "add", moduleVec2Add);
|
||||
scriptProtoDefineFunc(&MODULE_VEC2_PROTO, "sub", moduleVec2Sub);
|
||||
scriptProtoDefineFunc(&MODULE_VEC2_PROTO, "scale", moduleVec2Scale);
|
||||
scriptProtoDefineFunc(&MODULE_VEC2_PROTO, "lerp", moduleVec2Lerp);
|
||||
scriptProtoDefineFunc(&MODULE_VEC2_PROTO, "distance", moduleVec2Distance);
|
||||
scriptProtoDefineFunc(&MODULE_VEC2_PROTO, "negate", moduleVec2Negate);
|
||||
scriptProtoDefineFunc(&MODULE_VEC2_PROTO, "add", moduleVec2Add);
|
||||
scriptProtoDefineFunc(&MODULE_VEC2_PROTO, "sub", moduleVec2Sub);
|
||||
scriptProtoDefineFunc(&MODULE_VEC2_PROTO, "scale", moduleVec2Scale);
|
||||
scriptProtoDefineFunc(&MODULE_VEC2_PROTO, "lerp", moduleVec2Lerp);
|
||||
scriptProtoDefineFunc(&MODULE_VEC2_PROTO, "distance", moduleVec2Distance);
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ moduleBaseFunction(moduleVec3SetZ) {
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleVec3Dot) {
|
||||
moduleBaseRequireArgs(1);
|
||||
if(argc < 1) return moduleBaseThrow("Expected at least 1 argument");
|
||||
float_t *a = moduleVec3Get(callInfo);
|
||||
if(!a) return moduleBaseThrow("Vec3.dot: invalid this");
|
||||
float_t *b = moduleVec3From(args[0]);
|
||||
@@ -76,7 +76,7 @@ moduleBaseFunction(moduleVec3Dot) {
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleVec3Cross) {
|
||||
moduleBaseRequireArgs(1);
|
||||
if(argc < 1) return moduleBaseThrow("Expected at least 1 argument");
|
||||
float_t *a = moduleVec3Get(callInfo);
|
||||
if(!a) return moduleBaseThrow("Vec3.cross: invalid this");
|
||||
float_t *b = moduleVec3From(args[0]);
|
||||
@@ -115,7 +115,7 @@ moduleBaseFunction(moduleVec3Negate) {
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleVec3Add) {
|
||||
moduleBaseRequireArgs(1);
|
||||
if(argc < 1) return moduleBaseThrow("Expected at least 1 argument");
|
||||
float_t *a = moduleVec3Get(callInfo);
|
||||
if(!a) return moduleBaseThrow("Vec3.add: invalid this");
|
||||
float_t *b = moduleVec3From(args[0]);
|
||||
@@ -126,7 +126,7 @@ moduleBaseFunction(moduleVec3Add) {
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleVec3Sub) {
|
||||
moduleBaseRequireArgs(1);
|
||||
if(argc < 1) return moduleBaseThrow("Expected at least 1 argument");
|
||||
float_t *a = moduleVec3Get(callInfo);
|
||||
if(!a) return moduleBaseThrow("Vec3.sub: invalid this");
|
||||
float_t *b = moduleVec3From(args[0]);
|
||||
@@ -137,7 +137,11 @@ moduleBaseFunction(moduleVec3Sub) {
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleVec3Scale) {
|
||||
moduleBaseRequireArgs(1); moduleBaseRequireNumber(0);
|
||||
if(argc < 1) {
|
||||
return moduleBaseThrow("Expected at least 1 argument");
|
||||
}
|
||||
moduleBaseRequireNumber(0);
|
||||
|
||||
float_t *v = moduleVec3Get(callInfo);
|
||||
if(!v) return moduleBaseThrow("Vec3.scale: invalid this");
|
||||
vec3 r;
|
||||
@@ -146,7 +150,11 @@ moduleBaseFunction(moduleVec3Scale) {
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleVec3Lerp) {
|
||||
moduleBaseRequireArgs(2); moduleBaseRequireNumber(1);
|
||||
if(argc < 2) {
|
||||
return moduleBaseThrow("Expected at least 2 arguments");
|
||||
}
|
||||
moduleBaseRequireNumber(1);
|
||||
|
||||
float_t *a = moduleVec3Get(callInfo);
|
||||
if(!a) return moduleBaseThrow("Vec3.lerp: invalid this");
|
||||
float_t *b = moduleVec3From(args[0]);
|
||||
@@ -157,7 +165,7 @@ moduleBaseFunction(moduleVec3Lerp) {
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleVec3Distance) {
|
||||
moduleBaseRequireArgs(1);
|
||||
if(argc < 1) return moduleBaseThrow("Expected at least 1 argument");
|
||||
float_t *a = moduleVec3Get(callInfo);
|
||||
if(!a) return moduleBaseThrow("Vec3.distance: invalid this");
|
||||
float_t *b = moduleVec3From(args[0]);
|
||||
|
||||
@@ -121,7 +121,7 @@ moduleBaseFunction(moduleVec4SetV1) {
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleVec4Dot) {
|
||||
moduleBaseRequireArgs(1);
|
||||
if(argc < 1) return moduleBaseThrow("Expected at least 1 argument");
|
||||
float_t *a = moduleVec4Get(callInfo);
|
||||
if(!a) return moduleBaseThrow("Vec4.dot: invalid this");
|
||||
float_t *b = moduleVec4From(args[0]);
|
||||
@@ -158,7 +158,7 @@ moduleBaseFunction(moduleVec4Negate) {
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleVec4Add) {
|
||||
moduleBaseRequireArgs(1);
|
||||
if(argc < 1) return moduleBaseThrow("Expected at least 1 argument");
|
||||
float_t *a = moduleVec4Get(callInfo);
|
||||
if(!a) return moduleBaseThrow("Vec4.add: invalid this");
|
||||
float_t *b = moduleVec4From(args[0]);
|
||||
@@ -169,7 +169,7 @@ moduleBaseFunction(moduleVec4Add) {
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleVec4Sub) {
|
||||
moduleBaseRequireArgs(1);
|
||||
if(argc < 1) return moduleBaseThrow("Expected at least 1 argument");
|
||||
float_t *a = moduleVec4Get(callInfo);
|
||||
if(!a) return moduleBaseThrow("Vec4.sub: invalid this");
|
||||
float_t *b = moduleVec4From(args[0]);
|
||||
@@ -180,7 +180,11 @@ moduleBaseFunction(moduleVec4Sub) {
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleVec4Scale) {
|
||||
moduleBaseRequireArgs(1); moduleBaseRequireNumber(0);
|
||||
if(argc < 1) {
|
||||
return moduleBaseThrow("Expected at least 1 argument");
|
||||
}
|
||||
moduleBaseRequireNumber(0);
|
||||
|
||||
float_t *v = moduleVec4Get(callInfo);
|
||||
if(!v) return moduleBaseThrow("Vec4.scale: invalid this");
|
||||
vec4 r;
|
||||
@@ -189,7 +193,11 @@ moduleBaseFunction(moduleVec4Scale) {
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleVec4Lerp) {
|
||||
moduleBaseRequireArgs(2); moduleBaseRequireNumber(1);
|
||||
if(argc < 2) {
|
||||
return moduleBaseThrow("Expected at least 2 arguments");
|
||||
}
|
||||
moduleBaseRequireNumber(1);
|
||||
|
||||
float_t *a = moduleVec4Get(callInfo);
|
||||
if(!a) return moduleBaseThrow("Vec4.lerp: invalid this");
|
||||
float_t *b = moduleVec4From(args[0]);
|
||||
@@ -256,13 +264,13 @@ static void moduleVec4(void) {
|
||||
|
||||
scriptProtoDefineToString(&MODULE_VEC4_PROTO, moduleVec4ToString);
|
||||
|
||||
scriptProtoDefineFunc(&MODULE_VEC4_PROTO, "dot", moduleVec4Dot);
|
||||
scriptProtoDefineFunc(&MODULE_VEC4_PROTO, "length", moduleVec4Length);
|
||||
scriptProtoDefineFunc(&MODULE_VEC4_PROTO, "lengthSq", moduleVec4LengthSq);
|
||||
scriptProtoDefineFunc(&MODULE_VEC4_PROTO, "dot", moduleVec4Dot);
|
||||
scriptProtoDefineFunc(&MODULE_VEC4_PROTO, "length", moduleVec4Length);
|
||||
scriptProtoDefineFunc(&MODULE_VEC4_PROTO, "lengthSq", moduleVec4LengthSq);
|
||||
scriptProtoDefineFunc(&MODULE_VEC4_PROTO, "normalize", moduleVec4Normalize);
|
||||
scriptProtoDefineFunc(&MODULE_VEC4_PROTO, "negate", moduleVec4Negate);
|
||||
scriptProtoDefineFunc(&MODULE_VEC4_PROTO, "add", moduleVec4Add);
|
||||
scriptProtoDefineFunc(&MODULE_VEC4_PROTO, "sub", moduleVec4Sub);
|
||||
scriptProtoDefineFunc(&MODULE_VEC4_PROTO, "scale", moduleVec4Scale);
|
||||
scriptProtoDefineFunc(&MODULE_VEC4_PROTO, "lerp", moduleVec4Lerp);
|
||||
scriptProtoDefineFunc(&MODULE_VEC4_PROTO, "negate", moduleVec4Negate);
|
||||
scriptProtoDefineFunc(&MODULE_VEC4_PROTO, "add", moduleVec4Add);
|
||||
scriptProtoDefineFunc(&MODULE_VEC4_PROTO, "sub", moduleVec4Sub);
|
||||
scriptProtoDefineFunc(&MODULE_VEC4_PROTO, "scale", moduleVec4Scale);
|
||||
scriptProtoDefineFunc(&MODULE_VEC4_PROTO, "lerp", moduleVec4Lerp);
|
||||
}
|
||||
|
||||
@@ -13,21 +13,39 @@
|
||||
#include "script/module/moduleplatform.h"
|
||||
#include "script/module/time/moduletime.h"
|
||||
#include "script/module/display/modulecolor.h"
|
||||
#include "script/module/display/modulemesh.h"
|
||||
#include "script/module/display/modulescreen.h"
|
||||
#include "script/module/display/modulespritebatch.h"
|
||||
#include "script/module/display/moduletext.h"
|
||||
#include "script/module/animation/moduleeasing.h"
|
||||
#include "script/module/animation/moduleanimation.h"
|
||||
#include "script/module/scene/modulescene.h"
|
||||
#include "script/module/cutscene/modulecutscene.h"
|
||||
#include "script/module/console/moduleconsole.h"
|
||||
#include "script/module/engine/moduleengine.h"
|
||||
#include "script/module/item/moduleitem.h"
|
||||
#include "script/module/story/modulestory.h"
|
||||
#include "script/module/ui/moduletextbox.h"
|
||||
|
||||
static void moduleRegister(void) {
|
||||
moduleInclude();
|
||||
moduleMath();
|
||||
moduleMesh();
|
||||
moduleEntity();
|
||||
moduleInput();
|
||||
modulePlatform();
|
||||
moduleTime();
|
||||
moduleColor();
|
||||
moduleScreen();
|
||||
moduleSpriteBatch();
|
||||
moduleText();
|
||||
moduleEasing();
|
||||
moduleAnimation();
|
||||
moduleScene();
|
||||
moduleCutscene();
|
||||
moduleConsole();
|
||||
moduleEngine();
|
||||
moduleItem();
|
||||
moduleStory();
|
||||
moduleTextbox();
|
||||
}
|
||||
|
||||
@@ -213,14 +213,6 @@ static void moduleBaseCreateGlobalObject(
|
||||
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.
|
||||
*/
|
||||
|
||||
@@ -25,7 +25,7 @@ moduleBaseFunction(moduleSceneDefaultConstructor) {
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleSceneSet) {
|
||||
moduleBaseRequireArgs(1);
|
||||
if(argc < 1) return moduleBaseThrow("Expected at least 1 argument");
|
||||
moduleBaseRequireString(0);
|
||||
|
||||
char_t name[ASSET_FILE_PATH_MAX];
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
#pragma once
|
||||
#include "script/module/modulebase.h"
|
||||
#include "script/scriptmanager.h"
|
||||
|
||||
moduleBaseFunction(moduleIncludeInclude) {
|
||||
if(argc < 1 || !jerry_value_is_string(args[0])) {
|
||||
@@ -25,32 +26,9 @@ moduleBaseFunction(moduleIncludeInclude) {
|
||||
return moduleBaseThrow("include: filename must end with .js");
|
||||
}
|
||||
|
||||
char_t buffer[1024];
|
||||
stringCopy(buffer, filename, sizeof(buffer));
|
||||
|
||||
// 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);
|
||||
|
||||
jerry_value_t moduleVal = 0;
|
||||
errorret_t err = scriptInclude(filename, &moduleVal);
|
||||
if(err.code != ERROR_OK) {
|
||||
jerry_value_free(moduleVal);
|
||||
errorCatch(errorPrint(err));
|
||||
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
|
||||
);
|
||||
}
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
@@ -9,7 +9,6 @@
|
||||
#include "assert/assert.h"
|
||||
#include "asset/asset.h"
|
||||
#include "util/memory.h"
|
||||
#include "event/event.h"
|
||||
#include "asset/loader/script/assetscriptloader.h"
|
||||
#include "script/module/module.h"
|
||||
|
||||
@@ -78,13 +77,41 @@ errorret_t scriptManagerExecFile(
|
||||
return assetScriptLoad(fname, resultOut);
|
||||
}
|
||||
|
||||
errorret_t scriptManagerDispose(void) {
|
||||
for(uint8_t i = 0; i < SCRIPT_MANAGER_MAX_EVENT_SUBSCRIPTIONS; i++) {
|
||||
event_t *event = SCRIPT_MANAGER.subscribedEvents[i];
|
||||
if(event == NULL) continue;
|
||||
eventUnsubscribeScriptContext(event, &SCRIPT_MANAGER);
|
||||
errorret_t scriptInclude(const char_t *filename, jerry_value_t *out) {
|
||||
assertNotNull(filename, "Filename cannot be NULL");
|
||||
|
||||
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 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();
|
||||
errorOk();
|
||||
}
|
||||
|
||||
@@ -9,12 +9,10 @@
|
||||
#include "error/error.h"
|
||||
#include "scriptvalue.h"
|
||||
|
||||
typedef struct event_s event_t;
|
||||
|
||||
#define SCRIPT_MANAGER_MAX_EVENT_SUBSCRIPTIONS 64
|
||||
|
||||
typedef struct {
|
||||
event_t *subscribedEvents[SCRIPT_MANAGER_MAX_EVENT_SUBSCRIPTIONS];
|
||||
void* nothing;
|
||||
} scriptmanager_t;
|
||||
|
||||
extern scriptmanager_t SCRIPT_MANAGER;
|
||||
@@ -58,6 +56,18 @@ errorret_t scriptManagerExecFile(
|
||||
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.
|
||||
*
|
||||
|
||||
@@ -78,7 +78,9 @@ void scriptProtoDefineProp(
|
||||
}
|
||||
|
||||
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(key);
|
||||
jerry_value_free(desc.getter);
|
||||
@@ -111,7 +113,9 @@ void scriptProtoDefineStaticProp(
|
||||
assertStrLenMin(name, 1, "Property name must not be empty");
|
||||
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;
|
||||
memoryZero(&desc, sizeof(desc));
|
||||
@@ -145,7 +149,9 @@ void scriptProtoDefineStaticFunc(
|
||||
assertStrLenMin(name, 1, "Method name must not be empty");
|
||||
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 func = jerry_function_external(fn);
|
||||
jerry_object_set(target, key, func);
|
||||
|
||||
@@ -7,4 +7,8 @@
|
||||
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||
PUBLIC
|
||||
ui.c
|
||||
uifps.c
|
||||
uielement.c
|
||||
uiframe.c
|
||||
uitextbox.c
|
||||
)
|
||||
+18
-10
@@ -10,15 +10,19 @@
|
||||
#include "assert/assert.h"
|
||||
#include "display/spritebatch/spritebatch.h"
|
||||
#include "display/screen/screen.h"
|
||||
#include "ui/uielement.h"
|
||||
#include "log/log.h"
|
||||
|
||||
ui_t UI;
|
||||
|
||||
errorret_t uiInit(void) {
|
||||
memoryZero(&UI, sizeof(ui_t));
|
||||
|
||||
// cameraInitOrthographic(&UI.camera);
|
||||
// UI.camera.orthographic.left = 0;
|
||||
// UI.camera.orthographic.bottom = 0;
|
||||
uielement_t *element = &UI_ELEMENTS[0];
|
||||
while(element->type != UI_ELEMENT_TYPE_NULL) {
|
||||
errorChain(uiElementInit(element));
|
||||
element++;
|
||||
}
|
||||
|
||||
errorOk();
|
||||
}
|
||||
@@ -26,15 +30,19 @@ errorret_t uiInit(void) {
|
||||
void uiUpdate(void) {
|
||||
}
|
||||
|
||||
void uiRender(void) {
|
||||
// UI.camera.orthographic.right = SCREEN.width;
|
||||
// UI.camera.orthographic.top = SCREEN.height;
|
||||
errorret_t uiRender(void) {
|
||||
const uielement_t *element = &UI_ELEMENTS[0];
|
||||
while(element->type != UI_ELEMENT_TYPE_NULL) {
|
||||
errorChain(uiElementDraw(element));
|
||||
|
||||
// // cameraPushMatrix(&UI.camera);
|
||||
// spriteBatchClear();
|
||||
if(SPRITEBATCH.spriteCount > 0) {
|
||||
logDebug("Finished UI element but unflushed sprites remain.\n");
|
||||
}
|
||||
|
||||
// spriteBatchFlush();
|
||||
// // cameraPopMatrix();
|
||||
element++;
|
||||
}
|
||||
|
||||
errorOk();
|
||||
}
|
||||
|
||||
void uiDispose(void) {
|
||||
|
||||
+3
-1
@@ -26,8 +26,10 @@ void uiUpdate(void);
|
||||
|
||||
/**
|
||||
* Renders the UI system.
|
||||
*
|
||||
* @return Any error that occurs.
|
||||
*/
|
||||
void uiRender(void);
|
||||
errorret_t uiRender(void);
|
||||
|
||||
/**
|
||||
* Disposes of the UI system.
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
@@ -163,7 +163,12 @@ bool_t stringToI16(const char_t *str, int16_t *out) {
|
||||
char_t *endptr;
|
||||
errno = 0;
|
||||
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;
|
||||
}
|
||||
*out = (int16_t)result;
|
||||
|
||||
@@ -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_SetColorUpdate(GX_TRUE);
|
||||
|
||||
@@ -103,12 +99,36 @@ errorret_t displayUpdateDolphin(void) {
|
||||
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) {
|
||||
GX_DrawDone();
|
||||
|
||||
DISPLAY.whichFrameBuffer ^= 1;
|
||||
// GX_SetZMode(GX_TRUE, GX_LEQUAL, GX_TRUE);
|
||||
// GX_SetColorUpdate(GX_TRUE);
|
||||
GX_CopyDisp(DISPLAY.frameBuffer[DISPLAY.whichFrameBuffer], GX_TRUE);
|
||||
VIDEO_SetNextFramebuffer(DISPLAY.frameBuffer[DISPLAY.whichFrameBuffer]);
|
||||
VIDEO_Flush();
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
#pragma once
|
||||
#include "error/error.h"
|
||||
#include "display/displaystate.h"
|
||||
|
||||
#define DISPLAY_DOLPHIN_FIFO_SIZE (256*1024)
|
||||
|
||||
@@ -31,3 +32,8 @@ errorret_t displayUpdateDolphin(void);
|
||||
* Swaps the display buffers on Dolphin.
|
||||
*/
|
||||
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
Reference in New Issue
Block a user