2 Commits

Author SHA1 Message Date
YourWishes ca6cbf41c1 Lua test. 2026-04-14 18:50:04 -05:00
YourWishes 2a9667feca Module updating 2026-04-14 16:36:50 -05:00
310 changed files with 6143 additions and 10617 deletions
-16
View File
@@ -53,22 +53,6 @@ jobs:
path: ./git-artifcats/Dusk
if-no-files-found: error
# build-vita:
# runs-on: ubuntu-latest
# steps:
# - name: Checkout repository
# uses: actions/checkout@v6
# - name: Set up Docker
# uses: docker/setup-docker-action@v5
# - name: Build Vita
# run: ./scripts/build-vita-docker.sh
# - name: Upload Vita binary
# uses: actions/upload-artifact@v6
# with:
# name: dusk-vita
# path: build-vita/Dusk.vpk
# if-no-files-found: error
build-knulli:
runs-on: ubuntu-latest
steps:
+1 -3
View File
@@ -4,13 +4,11 @@
# https://opensource.org/licenses/MIT
# Setup
cmake_minimum_required(VERSION 3.13)
cmake_minimum_required(VERSION 3.18)
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_policy(SET CMP0079 NEW)
# set(FETCHCONTENT_UPDATES_DISCONNECTED ON)
option(DUSK_BUILD_TESTS "Enable tests" OFF)
+13
View File
@@ -0,0 +1,13 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "dusk.h"
typedef struct {
void *nothing;
} inventory_t;
-35
View File
@@ -1,35 +0,0 @@
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;
-25
View File
@@ -1,25 +0,0 @@
var OverworldEntity = include('entities/OverworldEntity.js');
function CubeEntity() {
OverworldEntity.call(this);
this.add(MESH);
this.add(MATERIAL);
this.cubeMesh = Mesh.createCube();
this.mesh.mesh = this.cubeMesh;
}
CubeEntity.prototype = Object.create(OverworldEntity.prototype);
CubeEntity.prototype.constructor = CubeEntity;
CubeEntity.prototype.update = function() {
OverworldEntity.prototype.update.call(this);
var speed = 3.0;
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.color = Color.rainbow();
};
module = CubeEntity;
-21
View File
@@ -1,21 +0,0 @@
function OverworldEntity() {
Entity.call(this);
this.add(POSITION);
}
OverworldEntity.prototype = Object.create(Entity.prototype);
OverworldEntity.prototype.constructor = OverworldEntity;
OverworldEntity.prototype.update = function() {
// var speed = 3.0;
// 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;
}
OverworldEntity.prototype.dispose = function() {
// Nothing to dispose
}
module = OverworldEntity;
+14
View File
@@ -0,0 +1,14 @@
module('entity')
module('entityposition')
module('entitymaterial')
module('glm')
module('color')
-- Position
local posComp = entityAddComponent(ENTITY_ID, COMPONENT_TYPE_POSITION)
entityPositionSetPosition(ENTITY_ID, posComp, vec3(1, 2, 3))
-- Material
local matComp = entityAddComponent(ENTITY_ID, COMPONENT_TYPE_MATERIAL)
local material = entityMaterialGetShaderMaterial(ENTITY_ID, matComp)
material.unlit.color = colorBlue()
-77
View File
@@ -1,77 +0,0 @@
Console.visible = true;
// Default input bindings.
if (typeof PSP !== 'undefined') {
Input.bind("up", INPUT_ACTION_UP);
Input.bind("down", INPUT_ACTION_DOWN);
Input.bind("left", INPUT_ACTION_LEFT);
Input.bind("right", INPUT_ACTION_RIGHT);
Input.bind("accept", INPUT_ACTION_ACCEPT);
Input.bind("cancel", INPUT_ACTION_CANCEL);
Input.bind("select", INPUT_ACTION_RAGEQUIT);
Input.bind("lstick_up", INPUT_ACTION_UP);
Input.bind("lstick_down", INPUT_ACTION_DOWN);
Input.bind("lstick_left", INPUT_ACTION_LEFT);
Input.bind("lstick_right", INPUT_ACTION_RIGHT);
Input.bind("triangle", INPUT_ACTION_CONSOLE);
} else if (typeof DOLPHIN !== 'undefined') {
Input.bind("up", INPUT_ACTION_UP);
Input.bind("down", INPUT_ACTION_DOWN);
Input.bind("left", INPUT_ACTION_LEFT);
Input.bind("right", INPUT_ACTION_RIGHT);
Input.bind("b", INPUT_ACTION_CANCEL);
Input.bind("a", INPUT_ACTION_ACCEPT);
Input.bind("z", INPUT_ACTION_CONSOLE);
Input.bind("lstick_up", INPUT_ACTION_UP);
Input.bind("lstick_down", INPUT_ACTION_DOWN);
Input.bind("lstick_left", INPUT_ACTION_LEFT);
Input.bind("lstick_right", INPUT_ACTION_RIGHT);
} else if (typeof LINUX !== 'undefined') {
if (typeof INPUT_KEYBOARD !== 'undefined') {
Input.bind("w", INPUT_ACTION_UP);
Input.bind("s", INPUT_ACTION_DOWN);
Input.bind("a", INPUT_ACTION_LEFT);
Input.bind("d", INPUT_ACTION_RIGHT);
Input.bind("left", INPUT_ACTION_LEFT);
Input.bind("right", INPUT_ACTION_RIGHT);
Input.bind("up", INPUT_ACTION_UP);
Input.bind("down", INPUT_ACTION_DOWN);
Input.bind("enter", INPUT_ACTION_ACCEPT);
Input.bind("e", INPUT_ACTION_ACCEPT);
Input.bind("q", INPUT_ACTION_CANCEL);
Input.bind("escape", INPUT_ACTION_RAGEQUIT);
Input.bind("`", INPUT_ACTION_CONSOLE);
}
if (typeof INPUT_GAMEPAD !== 'undefined') {
Input.bind("gamepad_up", INPUT_ACTION_UP);
Input.bind("gamepad_down", INPUT_ACTION_DOWN);
Input.bind("gamepad_left", INPUT_ACTION_LEFT);
Input.bind("gamepad_right", INPUT_ACTION_RIGHT);
Input.bind("gamepad_a", INPUT_ACTION_ACCEPT);
Input.bind("gamepad_b", INPUT_ACTION_CANCEL);
Input.bind("gamepad_back", INPUT_ACTION_RAGEQUIT);
Input.bind("gamepad_lstick_up", INPUT_ACTION_UP);
Input.bind("gamepad_lstick_down", INPUT_ACTION_DOWN);
Input.bind("gamepad_lstick_left", INPUT_ACTION_LEFT);
Input.bind("gamepad_lstick_right", INPUT_ACTION_RIGHT);
}
if (typeof INPUT_POINTER !== 'undefined') {
Input.bind("mouse_x", INPUT_ACTION_POINTERX);
Input.bind("mouse_y", INPUT_ACTION_POINTERY);
}
} else {
print("Unknown platform, no default input bindings set.");
}
Scene.set('scenes/cube.js');
+76
View File
@@ -0,0 +1,76 @@
module('input')
module('platform')
module('scene')
module('locale')
-- Default Input bindings.
if PSP then
inputBind("up", INPUT_ACTION_UP)
inputBind("down", INPUT_ACTION_DOWN)
inputBind("left", INPUT_ACTION_LEFT)
inputBind("right", INPUT_ACTION_RIGHT)
inputBind("circle", INPUT_ACTION_CANCEL)
inputBind("cross", INPUT_ACTION_ACCEPT)
inputBind("select", INPUT_ACTION_RAGEQUIT)
inputBind("lstick_up", INPUT_ACTION_UP)
inputBind("lstick_down", INPUT_ACTION_DOWN)
inputBind("lstick_left", INPUT_ACTION_LEFT)
inputBind("lstick_right", INPUT_ACTION_RIGHT)
elseif DOLPHIN then
inputBind("up", INPUT_ACTION_UP)
inputBind("down", INPUT_ACTION_DOWN)
inputBind("left", INPUT_ACTION_LEFT)
inputBind("right", INPUT_ACTION_RIGHT)
inputBind("b", INPUT_ACTION_CANCEL)
inputBind("a", INPUT_ACTION_ACCEPT)
inputBind("z", INPUT_ACTION_RAGEQUIT)
inputBind("lstick_up", INPUT_ACTION_UP)
inputBind("lstick_down", INPUT_ACTION_DOWN)
inputBind("lstick_left", INPUT_ACTION_LEFT)
inputBind("lstick_right", INPUT_ACTION_RIGHT)
elseif LINUX then
if INPUT_KEYBOARD then
inputBind("w", INPUT_ACTION_UP)
inputBind("s", INPUT_ACTION_DOWN)
inputBind("a", INPUT_ACTION_LEFT)
inputBind("d", INPUT_ACTION_RIGHT)
inputBind("left", INPUT_ACTION_LEFT)
inputBind("right", INPUT_ACTION_RIGHT)
inputBind("up", INPUT_ACTION_UP)
inputBind("down", INPUT_ACTION_DOWN)
inputBind("enter", INPUT_ACTION_ACCEPT)
inputBind("e", INPUT_ACTION_ACCEPT)
inputBind("q", INPUT_ACTION_CANCEL)
inputBind("escape", INPUT_ACTION_RAGEQUIT)
end
if INPUT_GAMEPAD then
inputBind("gamepad_up", INPUT_ACTION_UP)
inputBind("gamepad_down", INPUT_ACTION_DOWN)
inputBind("gamepad_left", INPUT_ACTION_LEFT)
inputBind("gamepad_right", INPUT_ACTION_RIGHT)
inputBind("gamepad_a", INPUT_ACTION_ACCEPT)
inputBind("gamepad_b", INPUT_ACTION_CANCEL)
inputBind("gamepad_back", INPUT_ACTION_RAGEQUIT)
inputBind("gamepad_lstick_up", INPUT_ACTION_UP)
inputBind("gamepad_lstick_down", INPUT_ACTION_DOWN)
inputBind("gamepad_lstick_left", INPUT_ACTION_LEFT)
inputBind("gamepad_lstick_right", INPUT_ACTION_RIGHT)
end
if INPUT_POINTER then
inputBind("mouse_x", INPUT_ACTION_POINTERX)
inputBind("mouse_y", INPUT_ACTION_POINTERY)
end
else
print("Unknown platform, no default input bindings set.")
end
-50
View File
@@ -1,50 +0,0 @@
var CubeEntity = include('entities/CubeEntity.js');
var MoveCubeCutscene = include('cutscenes/MoveCubeCutscene.js');
function CubeScene() {
this.cam = new Entity();
this.cam.add(POSITION);
this.cam.add(CAMERA);
this.cam.position.position = new Vec3(3, 3, 3);
this.cam.position.lookAt(new Vec3(0, 0, 0));
this.cube = new CubeEntity();
this.spriteEnt = new Entity();
this.spriteEnt.add(POSITION);
this.spriteEnt.position.position = new Vec3(16, 16, 0);
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() {
if(this.inputEnabled) {
this.cube.update();
}
};
CubeScene.prototype.dispose = function() {
Cutscene.stop();
this.cam.dispose();
this.cube.dispose();
this.spriteEnt.dispose();
};
module = CubeScene;
-6
View File
@@ -1,6 +0,0 @@
module = {
render() {
Text.draw(0, 0, "Hello World");
SpriteBatch.flush();
}
};
-96
View File
@@ -1,96 +0,0 @@
# Copyright (c) 2026 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
# Turn things off we don't need
set(JERRY_CMDLINE OFF CACHE BOOL "" FORCE)
set(JERRY_EXT ON CACHE BOOL "" FORCE)
set(JERRY_DEBUGGER OFF CACHE BOOL "" FORCE)
set(JERRY_BUILTIN_DATE OFF CACHE BOOL "" FORCE)
set(ENABLE_LTO OFF CACHE BOOL "" FORCE)
# Fetch Jerry
include(FetchContent)
FetchContent_Declare(
jerryscript
GIT_REPOSITORY https://git.wish.moe/YourWishes/jerryscript
GIT_TAG float32-fix
)
FetchContent_MakeAvailable(jerryscript)
# Mark found
set(jerryscript_FOUND ON)
# Define targets
if(TARGET jerryscript-core)
set(JERRY_CORE_TARGET jerryscript-core)
elseif(TARGET jerry-core)
set(JERRY_CORE_TARGET jerry-core)
endif()
if(TARGET jerryscript-ext)
set(JERRY_EXT_TARGET jerryscript-ext)
elseif(TARGET jerry-ext)
set(JERRY_EXT_TARGET jerry-ext)
endif()
if(TARGET jerryscript-port-default)
set(JERRY_PORT_TARGET jerryscript-port-default)
elseif(TARGET jerry-port-default)
set(JERRY_PORT_TARGET jerry-port-default)
elseif(TARGET jerryscript-port)
set(JERRY_PORT_TARGET jerryscript-port)
elseif(TARGET jerry-port)
set(JERRY_PORT_TARGET jerry-port)
endif()
if(NOT JERRY_CORE_TARGET)
message(FATAL_ERROR "JerryScript core target not found")
endif()
if(NOT JERRY_EXT_TARGET)
message(FATAL_ERROR "JerryScript ext target not found")
endif()
if(NOT JERRY_PORT_TARGET)
message(FATAL_ERROR "JerryScript port target not found")
endif()
foreach(tgt IN ITEMS
${JERRY_CORE_TARGET}
${JERRY_EXT_TARGET}
${JERRY_PORT_TARGET}
)
if(TARGET ${tgt})
set_property(TARGET ${tgt} PROPERTY INTERPROCEDURAL_OPTIMIZATION OFF)
target_compile_definitions(${JERRY_CORE_TARGET} PRIVATE
JERRY_NUMBER_TYPE_FLOAT64=0
JERRY_BUILTIN_DATE=0
)
endif()
endforeach()
# Export include dirs through the targets
target_include_directories(${JERRY_CORE_TARGET} INTERFACE
${jerryscript_SOURCE_DIR}/jerry-core/include
)
target_include_directories(${JERRY_EXT_TARGET} INTERFACE
${jerryscript_SOURCE_DIR}/jerry-ext/include
)
target_include_directories(${JERRY_PORT_TARGET} INTERFACE
${jerryscript_SOURCE_DIR}/jerry-port/default/include
)
# Suppress JerryScript-only warning
if(CMAKE_C_COMPILER_ID MATCHES "GNU|Clang")
target_compile_options(${JERRY_CORE_TARGET} PRIVATE
-Wno-error
)
endif()
add_library(jerryscript::core ALIAS ${JERRY_CORE_TARGET})
add_library(jerryscript::ext ALIAS ${JERRY_EXT_TARGET})
add_library(jerryscript::port ALIAS ${JERRY_PORT_TARGET})
+22 -1
View File
@@ -4,7 +4,6 @@ target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
DUSK_INPUT_GAMEPAD
DUSK_DISPLAY_WIDTH=640
DUSK_DISPLAY_HEIGHT=480
DUSK_THREAD_PTHREAD
)
# Custom compiler flags
@@ -22,9 +21,31 @@ set(CGLM_SHARED OFF CACHE BOOL "Build cglm shared" FORCE)
set(CGLM_STATIC ON CACHE BOOL "Build cglm static" FORCE)
find_package(cglm REQUIRED)
# Compile lua
include(FetchContent)
FetchContent_Declare(
liblua
URL https://www.lua.org/ftp/lua-5.5.0.tar.gz
)
FetchContent_MakeAvailable(liblua)
set(LUA_SRC_DIR "${liblua_SOURCE_DIR}/src")
set(LUA_C_FILES
lapi.c lauxlib.c lbaselib.c lcode.c lcorolib.c lctype.c ldblib.c ldebug.c
ldo.c ldump.c lfunc.c lgc.c linit.c liolib.c llex.c lmathlib.c lmem.c
loadlib.c lobject.c lopcodes.c loslib.c lparser.c lstate.c lstring.c
lstrlib.c ltable.c ltablib.c ltm.c lundump.c lutf8lib.c lvm.c lzio.c
)
list(TRANSFORM LUA_C_FILES PREPEND "${LUA_SRC_DIR}/")
add_library(liblua STATIC ${LUA_C_FILES})
target_include_directories(liblua PUBLIC "${LUA_SRC_DIR}")
target_compile_definitions(liblua PRIVATE LUA_USE_C89)
add_library(lua::lua ALIAS liblua)
set(Lua_FOUND TRUE CACHE BOOL "Lua found" FORCE)
# Link libraries
target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PRIVATE
cglm
liblua
m
fat
PkgConfig::zip
+1 -6
View File
@@ -2,9 +2,4 @@ include(cmake/targets/dolphin.cmake)
target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
DUSK_GAMECUBE
)
# Link libraries
target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PRIVATE
# bba
)
)
+1 -6
View File
@@ -1,7 +1,6 @@
# Find link platform-specific libraries
find_package(SDL2 REQUIRED)
find_package(OpenGL REQUIRED)
# find_package(CURL REQUIRED)
# Setup endianess at compile time to optimize.
include(TestBigEndian)
@@ -17,20 +16,18 @@ else()
endif()
# Link required libraries.
target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
SDL2
pthread
OpenGL::GL
GL
m
# CURL::libcurl
)
# Define platform-specific macros.
target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
DUSK_SDL2
DUSK_OPENGL
DUSK_CONSOLE_POSIX
# DUSK_OPENGL_LEGACY
DUSK_LINUX
DUSK_DISPLAY_SIZE_DYNAMIC
@@ -41,6 +38,4 @@ target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
DUSK_INPUT_POINTER
DUSK_INPUT_GAMEPAD
DUSK_TIME_DYNAMIC
DUSK_NETWORK_IPV6
DUSK_THREAD_PTHREAD
)
+3 -26
View File
@@ -1,19 +1,6 @@
# Fixes some problems building JerryScript
set(CMAKE_AR "$ENV{PSPDEV}/bin/psp-ar" CACHE FILEPATH "" FORCE)
set(CMAKE_RANLIB "$ENV{PSPDEV}/bin/psp-ranlib" CACHE FILEPATH "" FORCE)
set(CMAKE_C_COMPILER_AR "$ENV{PSPDEV}/bin/psp-ar" CACHE FILEPATH "" FORCE)
set(CMAKE_C_COMPILER_RANLIB "$ENV{PSPDEV}/bin/psp-ranlib" CACHE FILEPATH "" FORCE)
set(CMAKE_C_ARCHIVE_CREATE "$ENV{PSPDEV}/bin/psp-ar qc <TARGET> <LINK_FLAGS> <OBJECTS>")
set(CMAKE_C_ARCHIVE_APPEND "$ENV{PSPDEV}/bin/psp-ar q <TARGET> <LINK_FLAGS> <OBJECTS>")
set(CMAKE_C_ARCHIVE_FINISH "$ENV{PSPDEV}/bin/psp-ranlib <TARGET>")
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION OFF CACHE BOOL "" FORCE)
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION_C OFF CACHE BOOL "" FORCE)
set(JERRY_LTO OFF CACHE BOOL "" FORCE)
find_package(jerryscript REQUIRED)
find_package(SDL2 REQUIRED)
find_package(OpenGL REQUIRED)
target_link_libraries(${DUSK_BINARY_TARGET_NAME} PUBLIC
target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
${SDL2_LIBRARIES}
SDL2
pthread
@@ -37,22 +24,13 @@ target_link_libraries(${DUSK_BINARY_TARGET_NAME} PUBLIC
pspvfpu
pspvram
psphprm
pspnet
pspnet_inet
pspnet_apctl
psphttp
pspssl
jerryscript::core
jerryscript::ext
jerryscript::port
)
target_include_directories(${DUSK_BINARY_TARGET_NAME} PRIVATE
target_include_directories(${DUSK_LIBRARY_TARGET_NAME} PRIVATE
${SDL2_INCLUDE_DIRS}
)
target_compile_definitions(${DUSK_BINARY_TARGET_NAME} PUBLIC
target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
DUSK_SDL2
DUSK_OPENGL
DUSK_PSP
@@ -61,7 +39,6 @@ target_compile_definitions(${DUSK_BINARY_TARGET_NAME} PUBLIC
DUSK_OPENGL_LEGACY
DUSK_DISPLAY_WIDTH=480
DUSK_DISPLAY_HEIGHT=272
DUSK_THREAD_PTHREAD
)
# Postbuild, create .pbp file for PSP.
-88
View File
@@ -1,88 +0,0 @@
# Copyright (c) 2026 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
if(NOT DEFINED ENV{VITASDK})
message(FATAL_ERROR "VITASDK environment variable is not set.")
endif()
include("$ENV{VITASDK}/share/vita.cmake" REQUIRED)
set(VITA_APP_NAME "Dusk")
set(VITA_TITLEID "DUSK00001")
set(VITA_VERSION "01.00")
find_package(SDL2 REQUIRED)
# Custom flags for cglm
set(CGLM_SHARED OFF CACHE BOOL "Build cglm shared" FORCE)
set(CGLM_STATIC ON CACHE BOOL "Build cglm static" FORCE)
find_package(cglm REQUIRED)
# Link libraries
target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
${SDL2_LIBRARIES}
cglm
SDL2
SDL2main
zip
bz2
z
zstd
crypto
lzma
m
pthread
stdc++
vitaGL
mathneon
vitashark
kubridge_stub
SceAppMgr_stub
SceAudio_stub
SceCtrl_stub
SceCommonDialog_stub
SceDisplay_stub
SceKernelDmacMgr_stub
SceGxm_stub
SceShaccCg_stub
SceSysmodule_stub
ScePower_stub
SceTouch_stub
SceVshBridge_stub
SceIofilemgr_stub
SceShaccCgExt
libtaihen_stub.a
# SceKernel_stub
SceAppUtil_stub
SceHid_stub
SceRtc_stub
)
target_include_directories(${DUSK_LIBRARY_TARGET_NAME} PRIVATE
${SDL2_INCLUDE_DIRS}
)
target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
DUSK_SDL2
DUSK_OPENGL
DUSK_VITA
DUSK_INPUT_GAMEPAD
DUSK_PLATFORM_ENDIAN_LITTLE
DUSK_OPENGL_LEGACY
DUSK_DISPLAY_WIDTH=960
DUSK_DISPLAY_HEIGHT=544
)
# Post-build: create SELF from the ELF binary (UNSAFE = homebrew, no signing)
vita_create_self(${DUSK_BINARY_TARGET_NAME}.self ${DUSK_BINARY_TARGET_NAME} UNSAFE)
# Post-build: package SELF + assets into a .vpk installable on the Vita
vita_create_vpk(${DUSK_BINARY_TARGET_NAME}.vpk ${VITA_TITLEID} ${DUSK_BINARY_TARGET_NAME}.self
VERSION ${VITA_VERSION}
NAME ${VITA_APP_NAME}
FILE ${DUSK_ASSETS_ZIP} dusk.dsk
)
+1 -1
View File
@@ -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 \
-13
View File
@@ -1,13 +0,0 @@
FROM vitasdk/vitasdk:latest
WORKDIR /workdir
# Install vitaGL and its dependencies (vitashark, SceShaccCg) via vdpm
RUN which vdpm
# Install Python (needed for Dusk code generation tools)
RUN apk add --no-cache \
python3 \
py3-pip \
py3-dotenv
VOLUME ["/workdir"]
-3
View File
@@ -1,3 +0,0 @@
#!/bin/bash
docker build -t dusk-vita -f docker/vita/Dockerfile .
docker run --rm -v $(pwd):/workdir dusk-vita /bin/bash -c "./scripts/build-vita.sh"
-13
View File
@@ -1,13 +0,0 @@
#!/bin/bash
if [ -z "$VITASDK" ]; then
echo "VITASDK environment variable is not set. Please set it to the path of your VitaSDK installation."
exit 1
fi
mkdir -p build-vita
cd build-vita
cmake \
-DCMAKE_TOOLCHAIN_FILE=$VITASDK/share/vita.toolchain.cmake \
-DDUSK_TARGET_SYSTEM=vita \
..
make -j$(nproc)
+2 -6
View File
@@ -4,6 +4,7 @@
# 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)
@@ -15,12 +16,7 @@ elseif(DUSK_TARGET_SYSTEM STREQUAL "psp")
add_subdirectory(dusksdl2)
add_subdirectory(duskgl)
elseif(DUSK_TARGET_SYSTEM STREQUAL "vita")
add_subdirectory(duskvita)
add_subdirectory(dusksdl2)
add_subdirectory(duskgl)
elseif(DUSK_TARGET_SYSTEM STREQUAL "wii" OR DUSK_TARGET_SYSTEM STREQUAL "gamecube")
elseif(DUSK_TARGET_SYSTEM STREQUAL "gamecube" OR DUSK_TARGET_SYSTEM STREQUAL "wii")
add_subdirectory(duskdolphin)
endif()
+18 -16
View File
@@ -32,13 +32,18 @@ if(NOT yyjson_FOUND)
endif()
endif()
if(NOT jerryscript_FOUND)
find_package(jerryscript REQUIRED)
target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
jerryscript::core
jerryscript::ext
jerryscript::port
)
if(NOT Lua_FOUND)
find_package(Lua REQUIRED)
if(Lua_FOUND AND NOT TARGET Lua::Lua)
add_library(Lua::Lua INTERFACE IMPORTED)
set_target_properties(
Lua::Lua
PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES "${LUA_INCLUDE_DIR}"
INTERFACE_LINK_LIBRARIES "${LUA_LIBRARIES}"
)
endif()
target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PUBLIC Lua::Lua)
endif()
# Includes
@@ -54,26 +59,23 @@ 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(display)
add_subdirectory(engine)
add_subdirectory(entity)
add_subdirectory(error)
add_subdirectory(event)
add_subdirectory(input)
add_subdirectory(locale)
add_subdirectory(physics)
add_subdirectory(scene)
add_subdirectory(script)
add_subdirectory(system)
add_subdirectory(time)
add_subdirectory(ui)
add_subdirectory(network)
add_subdirectory(util)
add_subdirectory(thread)
# if(DUSK_TARGET_SYSTEM STREQUAL "linux" OR DUSK_TARGET_SYSTEM STREQUAL "psp")
# add_subdirectory(thread)
# endif()
-75
View File
@@ -1,75 +0,0 @@
// Copyright (c) 2026 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
#include "animation.h"
#include "assert/assert.h"
#include "util/memory.h"
#include <math.h>
void animationInit(animation_t *anim) {
memoryZero(anim, sizeof(animation_t));
}
void animationAddKeyframe(
animation_t *anim,
float_t time,
float_t value,
easingtype_t easing
) {
assertTrue(
anim->keyframeCount < ANIMATION_KEYFRAME_COUNT_MAX,
"Keyframe count exceeds ANIMATION_KEYFRAME_COUNT_MAX"
);
uint8_t i = anim->keyframeCount++;
anim->keyframes[i].time = time;
anim->keyframes[i].value = value;
anim->keyframes[i].easing = easing;
if(time > anim->duration) anim->duration = time;
}
float_t animationGetValue(const animation_t *anim) {
if(anim->keyframeCount == 0) return 0.0f;
uint8_t last = anim->keyframeCount - 1;
if(anim->keyframeCount == 1) return anim->keyframes[0].value;
if(anim->time <= anim->keyframes[0].time) return anim->keyframes[0].value;
if(anim->time >= anim->keyframes[last].time) {
return anim->keyframes[last].value;
}
for(uint8_t i = 0; i < last; i++) {
const keyframe_t *a = &anim->keyframes[i];
const keyframe_t *b = &anim->keyframes[i + 1];
if(anim->time < a->time || anim->time >= b->time) continue;
float_t t = (anim->time - a->time) / (b->time - a->time);
t = easingApply(a->easing, t);
return a->value + (b->value - a->value) * t;
}
return anim->keyframes[last].value;
}
float_t animationUpdate(animation_t *anim, float_t delta) {
if(anim->complete && !anim->loop) return animationGetValue(anim);
anim->time += delta;
if(anim->duration > 0.0f && anim->time >= anim->duration) {
if(anim->loop) {
anim->time = fmodf(anim->time, anim->duration);
} else {
anim->time = anim->duration;
anim->complete = true;
}
}
return animationGetValue(anim);
}
void animationReset(animation_t *anim) {
anim->time = 0.0f;
anim->complete = false;
}
-63
View File
@@ -1,63 +0,0 @@
// Copyright (c) 2026 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
#pragma once
#include "keyframe.h"
#include "error/error.h"
typedef struct {
keyframe_t keyframes[ANIMATION_KEYFRAME_COUNT_MAX];
uint8_t keyframeCount;
float_t time;
float_t duration;
bool_t loop;
bool_t complete;
} animation_t;
/**
* Zeroes an animation struct.
*
* @param anim The animation to initialize.
*/
void animationInit(animation_t *anim);
/**
* Appends a keyframe. Keyframes must be added in ascending time order.
*
* @param anim The animation to modify.
* @param time Time in seconds for this keyframe.
* @param value Value at this keyframe.
* @param easing Easing applied to the segment after this keyframe.
*/
void animationAddKeyframe(
animation_t *anim,
float_t time,
float_t value,
easingtype_t easing
);
/**
* Advances the animation by delta seconds and returns the current value.
*
* @param anim The animation to update.
* @param delta Seconds elapsed since last update.
* @return Interpolated value at the new time.
*/
float_t animationUpdate(animation_t *anim, float_t delta);
/**
* Returns the interpolated value at the current time without advancing.
*
* @param anim The animation to read.
* @return Interpolated value.
*/
float_t animationGetValue(const animation_t *anim);
/**
* Resets the animation to the beginning.
*
* @param anim The animation to reset.
*/
void animationReset(animation_t *anim);
-106
View File
@@ -1,106 +0,0 @@
// Copyright (c) 2026 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
#include "easing.h"
#include "assert/assert.h"
#include <math.h>
#define EASING_PI 3.14159265358979323846f
#define EASING_C1 1.70158f
#define EASING_C2 (EASING_C1 * 1.525f)
#define EASING_C3 (EASING_C1 + 1.0f)
float_t easingLinear(float_t t) {
return t;
}
float_t easingInSine(float_t t) {
return 1.0f - cosf(t * EASING_PI * 0.5f);
}
float_t easingOutSine(float_t t) {
return sinf(t * EASING_PI * 0.5f);
}
float_t easingInOutSine(float_t t) {
return -(cosf(EASING_PI * t) - 1.0f) * 0.5f;
}
float_t easingInQuad(float_t t) {
return t * t;
}
float_t easingOutQuad(float_t t) {
float_t u = 1.0f - t;
return 1.0f - u * u;
}
float_t easingInOutQuad(float_t t) {
if(t < 0.5f) return 2.0f * t * t;
float_t u = -2.0f * t + 2.0f;
return 1.0f - u * u * 0.5f;
}
float_t easingInCubic(float_t t) {
return t * t * t;
}
float_t easingOutCubic(float_t t) {
float_t u = 1.0f - t;
return 1.0f - u * u * u;
}
float_t easingInOutCubic(float_t t) {
if(t < 0.5f) return 4.0f * t * t * t;
float_t u = -2.0f * t + 2.0f;
return 1.0f - u * u * u * 0.5f;
}
float_t easingInQuart(float_t t) {
return t * t * t * t;
}
float_t easingOutQuart(float_t t) {
float_t u = 1.0f - t;
return 1.0f - u * u * u * u;
}
float_t easingInOutQuart(float_t t) {
if(t < 0.5f) return 8.0f * t * t * t * t;
float_t u = -2.0f * t + 2.0f;
return 1.0f - u * u * u * u * 0.5f;
}
float_t easingInBack(float_t t) {
return EASING_C3 * t * t * t - EASING_C1 * t * t;
}
float_t easingOutBack(float_t t) {
float_t u = t - 1.0f;
return 1.0f + EASING_C3 * u * u * u + EASING_C1 * u * u;
}
float_t easingInOutBack(float_t t) {
if(t < 0.5f) {
float_t u = 2.0f * t;
return u * u * ((EASING_C2 + 1.0f) * u - EASING_C2) * 0.5f;
}
float_t u = 2.0f * t - 2.0f;
return (u * u * ((EASING_C2 + 1.0f) * u + EASING_C2) + 2.0f) * 0.5f;
}
const easingfn_t EASING_FUNCTIONS[EASING_COUNT] = {
easingLinear,
easingInSine, easingOutSine, easingInOutSine,
easingInQuad, easingOutQuad, easingInOutQuad,
easingInCubic, easingOutCubic, easingInOutCubic,
easingInQuart, easingOutQuart, easingInOutQuart,
easingInBack, easingOutBack, easingInOutBack,
};
float_t easingApply(easingtype_t type, float_t t) {
assertTrue(type < EASING_COUNT, "Invalid easing type");
return EASING_FUNCTIONS[type](t);
}
-49
View File
@@ -1,49 +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"
#define EASING_LINEAR 0
#define EASING_IN_SINE 1
#define EASING_OUT_SINE 2
#define EASING_IN_OUT_SINE 3
#define EASING_IN_QUAD 4
#define EASING_OUT_QUAD 5
#define EASING_IN_OUT_QUAD 6
#define EASING_IN_CUBIC 7
#define EASING_OUT_CUBIC 8
#define EASING_IN_OUT_CUBIC 9
#define EASING_IN_QUART 10
#define EASING_OUT_QUART 11
#define EASING_IN_OUT_QUART 12
#define EASING_IN_BACK 13
#define EASING_OUT_BACK 14
#define EASING_IN_OUT_BACK 15
#define EASING_COUNT 16
typedef uint8_t easingtype_t;
typedef float_t (*easingfn_t)(float_t t);
extern const easingfn_t EASING_FUNCTIONS[EASING_COUNT];
float_t easingApply(easingtype_t type, float_t t);
float_t easingLinear(float_t t);
float_t easingInSine(float_t t);
float_t easingOutSine(float_t t);
float_t easingInOutSine(float_t t);
float_t easingInQuad(float_t t);
float_t easingOutQuad(float_t t);
float_t easingInOutQuad(float_t t);
float_t easingInCubic(float_t t);
float_t easingOutCubic(float_t t);
float_t easingInOutCubic(float_t t);
float_t easingInQuart(float_t t);
float_t easingOutQuart(float_t t);
float_t easingInOutQuart(float_t t);
float_t easingInBack(float_t t);
float_t easingOutBack(float_t t);
float_t easingInOutBack(float_t t);
-15
View File
@@ -1,15 +0,0 @@
// 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;
+2 -2
View File
@@ -25,7 +25,7 @@ errorret_t assetInit(void) {
}
bool_t assetFileExists(const char_t *filename) {
assertStrLenMax(filename, ASSET_FILE_PATH_MAX, "Filename too long.");
assertStrLenMax(filename, FILENAME_MAX, "Filename too long.");
zip_int64_t idx = zip_name_locate(ASSET.zip, filename, 0);
if(idx < 0) return false;
@@ -38,7 +38,7 @@ errorret_t assetLoad(
void *params,
void *output
) {
assertStrLenMax(filename, ASSET_FILE_PATH_MAX, "Filename too long.");
assertStrLenMax(filename, FILENAME_MAX, "Filename too long.");
assertNotNull(output, "Output pointer cannot be NULL.");
assertNotNull(loader, "Asset file loader cannot be NULL.");
+4 -14
View File
@@ -146,9 +146,7 @@ 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;
@@ -179,16 +177,11 @@ 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.");
@@ -291,10 +284,7 @@ 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--;
}
-2
View File
@@ -9,8 +9,6 @@
#include "error/error.h"
#include <zip.h>
#define ASSET_FILE_PATH_MAX FILENAME_MAX
typedef struct assetfile_s assetfile_t;
typedef errorret_t (*assetfileloader_t)(assetfile_t *file);
@@ -152,7 +152,7 @@ errorret_t assetLocaleParseHeader(
if(pluralIndex >= localeFile->pluralStateCount - 1) {
errorThrow(
"Too many plural conditions. Expected %d clauses for nplurals=%d.",
"Too many plural conditions. Expected %d conditional clauses for nplurals=%d.",
localeFile->pluralStateCount - 1,
localeFile->pluralStateCount
);
@@ -1,92 +1,82 @@
/**
* Copyright (c) 2026 Dominic Masters
*
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "assetscriptloader.h"
#include "assert/assert.h"
#include <stdlib.h>
#include <zip.h>
errorret_t assetScriptLoader(assetfile_t *file) {
assertNotNull(file, "Asset file cannot be NULL");
assertNull(file->zipFile, "Asset file zip handle must be NULL before open");
assertNull(file->zipFile, "Asset file zip handle must be NULL");
assertNotNull(file->output, "Asset file output cannot be NULL");
assetscript_t *script = (assetscript_t *)file->output;
// Open the asset for buffering
errorChain(assetFileOpen(file));
// Accumulate full source into a dynamically grown buffer.
size_t srcLen = 0;
size_t capacity = ASSET_SCRIPT_CHUNK_SIZE;
char_t *src = (char_t *)malloc(capacity + 1);
if(!src) {
assetFileClose(file);
errorThrow("Out of memory reading script: %s", file->filename);
// Request loading
if(!lua_load(
script->ctx->luaState,
assetScriptReader,
file,
file->filename,
NULL
) == LUA_OK) {
const char_t *strErr = lua_tostring(script->ctx->luaState, -1);
lua_pop(script->ctx->luaState, 1);
errorThrow("Failed to load Lua script: %s", strErr);
}
while(1) {
if(srcLen + ASSET_SCRIPT_CHUNK_SIZE > capacity) {
capacity = srcLen + ASSET_SCRIPT_CHUNK_SIZE;
char_t *tmp = (char_t *)realloc(src, capacity + 1);
if(!tmp) {
free(src);
assetFileClose(file);
errorThrow("Out of memory reading script: %s", file->filename);
}
src = tmp;
}
zip_int64_t n = zip_fread(
file->zipFile, src + srcLen, ASSET_SCRIPT_CHUNK_SIZE
);
if(n <= 0) break;
srcLen += (size_t)n;
}
src[srcLen] = '\0';
errorret_t closeRet = assetFileClose(file);
jerry_value_t result = jerry_eval(
(const jerry_char_t *)src,
srcLen,
JERRY_PARSE_NO_OPTS
);
free(src);
if(jerry_value_is_exception(result)) {
jerry_value_t errVal = jerry_exception_value(result, 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(result);
errorThrow("Script error in '%s': %s", file->filename, buf);
// Now loaded, exec
if(lua_pcall(script->ctx->luaState, 0, LUA_MULTRET, 0) != LUA_OK) {
const char_t *strErr = lua_tostring(script->ctx->luaState, -1);
lua_pop(script->ctx->luaState, 1);
errorThrow("Failed to execute Lua script: %s", strErr);
}
if(script->resultOut != NULL) {
*(script->resultOut) = result;
} else {
jerry_value_free(result);
}
return closeRet;
// Close the file
return assetFileClose(file);
}
errorret_t assetScriptLoad(
const char_t *path,
jerry_value_t *resultOut
) {
errorret_t assetScriptLoad(const char_t *path, scriptcontext_t *ctx) {
assertNotNull(path, "Script path cannot be NULL");
assertNotNull(ctx, "Script context cannot be NULL");
assetscript_t script;
script.ctx = ctx;
assetscript_t scriptData;
scriptData.resultOut = resultOut;
return assetLoad(path, assetScriptLoader, NULL, &scriptData);
return assetLoad(
path,
assetScriptLoader,
NULL,
&script
);
}
const char_t * assetScriptReader(lua_State* L, void* data, size_t* size) {
assetfile_t *file = (assetfile_t*)data;
assertNotNull(file, "Script asset file cannot be NULL");
assertNotNull(file->zipFile, "Script asset zip handle cannot be NULL");
assertNotNull(file->output, "Script asset output cannot be NULL");
assetscript_t *script = (assetscript_t *)file->output;
assertNotNull(script, "Script asset output cannot be NULL");
zip_int64_t read = zip_fread(
file->zipFile,
script->buffer,
sizeof(script->buffer)
);
if(read < 0) {
*size = 0;
return NULL;
}
*size = (size_t)read;
return script->buffer;
}
@@ -1,23 +1,28 @@
/**
* Copyright (c) 2026 Dominic Masters
*
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "asset/asset.h"
#include "script/scriptcontext.h"
#define ASSET_SCRIPT_CHUNK_SIZE 1024
#define ASSET_SCRIPT_BUFFER_SIZE 1024
typedef struct {
jerry_value_t *resultOut;
void *nothing;
} assetscriptloaderparams_t;
typedef struct {
scriptcontext_t *ctx;
char_t buffer[ASSET_SCRIPT_BUFFER_SIZE];
} assetscript_t;
/**
* Handler for script assets. Reads the full source, evaluates it with
* JerryScript, and optionally stores the result.
*
* Handler for script assets.
*
* @param file Asset file to load the script from.
* @return Any error that occurs during loading.
*/
@@ -25,14 +30,19 @@ errorret_t assetScriptLoader(assetfile_t *file);
/**
* Loads a script from the specified path.
*
* @param path Path to the script asset.
* @param resultOut Optional out-parameter for the script return value.
* Caller must call jerry_value_free() if non-NULL.
* Pass NULL to discard the return value.
*
* @param path Path to the script asset.
* @param ctx Script context to load the script into.
* @return Any error that occurs during loading.
*/
errorret_t assetScriptLoad(
const char_t *path,
jerry_value_t *resultOut
);
errorret_t assetScriptLoad(const char_t *path, scriptcontext_t *ctx);
/**
* Reader function for Lua to read script data from the asset.
*
* @param L Lua state.
* @param data Pointer to the scriptcontext_t structure.
* @param size Pointer to store the size of the read data.
* @return Pointer to the read data buffer.
*/
const char_t * assetScriptReader(lua_State* L, void* data, size_t* size);
-192
View File
@@ -1,192 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "console.h"
#include "assert/assert.h"
#include "util/memory.h"
#include "util/string.h"
#include "input/input.h"
#include "log/log.h"
#include "engine/engine.h"
#include "script/scriptmanager.h"
#include "display/shader/shaderunlit.h"
#include "display/text/text.h"
#include "display/spritebatch/spritebatch.h"
console_t CONSOLE;
void consoleInit(void) {
memoryZero(&CONSOLE, sizeof(console_t));
#ifdef DUSK_CONSOLE_POSIX
threadInit(&CONSOLE.thread, consoleInputThread);
threadMutexInit(&CONSOLE.execMutex);
threadMutexInit(&CONSOLE.printMutex);
threadStartRequest(&CONSOLE.thread);
#endif
}
void consolePrint(const char_t *message, ...) {
char_t buffer[CONSOLE_LINE_MAX];
va_list args;
va_start(args, message);
int32_t len = stringFormatVA(buffer, CONSOLE_LINE_MAX, message, args);
va_end(args);
#ifdef DUSK_CONSOLE_POSIX
threadMutexLock(&CONSOLE.printMutex);
#endif
memoryMove(
CONSOLE.line[0],
CONSOLE.line[1],
(CONSOLE_HISTORY_MAX - 1) * CONSOLE_LINE_MAX
);
memoryCopy(CONSOLE.line[CONSOLE_HISTORY_MAX - 1], buffer, len + 1);
#ifdef DUSK_CONSOLE_POSIX
threadMutexUnlock(&CONSOLE.printMutex);
#endif
logDebug("%s\n", buffer);
}
void consoleExec(const char_t *line) {
assertNotNull(line, "line must not be NULL");
#ifdef DUSK_CONSOLE_POSIX
threadMutexLock(&CONSOLE.execMutex);
#endif
assertTrue(
CONSOLE.execBufferCount < CONSOLE_EXEC_BUFFER_MAX,
"Console exec buffer is full"
);
stringCopy(
CONSOLE.execBuffer[CONSOLE.execBufferCount],
line,
CONSOLE_LINE_MAX
);
CONSOLE.execBufferCount++;
#ifdef DUSK_CONSOLE_POSIX
threadMutexUnlock(&CONSOLE.execMutex);
#endif
}
void consoleUpdate(void) {
#ifdef DUSK_TIME_DYNAMIC
if(TIME.dynamicUpdate) return;
#endif
if(inputPressed(INPUT_ACTION_CONSOLE)) {
CONSOLE.visible = !CONSOLE.visible;
}
if(CONSOLE.execBufferCount == 0) return;
#ifdef DUSK_CONSOLE_POSIX
threadMutexLock(&CONSOLE.execMutex);
#endif
char_t execBuffer[CONSOLE_EXEC_BUFFER_MAX][CONSOLE_LINE_MAX];
uint32_t execBufferCount = CONSOLE.execBufferCount;
memoryCopy(execBuffer, CONSOLE.execBuffer, sizeof(execBuffer));
CONSOLE.execBufferCount = 0;
#ifdef DUSK_CONSOLE_POSIX
threadMutexUnlock(&CONSOLE.execMutex);
#endif
for(uint32_t i = 0; i < execBufferCount; i++) {
jerry_value_t result = 0;
errorret_t err = scriptManagerExec(execBuffer[i], &result);
if(err.code != ERROR_OK) {
consolePrint("Error: %s", err.state->message);
errorCatch(err);
} else if(!jerry_value_is_undefined(result)) {
jerry_value_t strVal = jerry_value_to_string(result);
char_t buf[CONSOLE_LINE_MAX];
jerry_size_t len = jerry_string_to_buffer(
strVal, JERRY_ENCODING_UTF8, (jerry_char_t*)buf, sizeof(buf) - 1
);
buf[len] = '\0';
jerry_value_free(strVal);
consolePrint("%s", buf);
}
if(result != 0) jerry_value_free(result);
}
}
errorret_t consoleDraw(void) {
if(!CONSOLE.visible) errorOk();
for(uint32_t i = 0; i < CONSOLE_HISTORY_MAX; i++) {
errorChain(textDraw(
0, FONT_TILESET_DEFAULT.tileHeight * i,
CONSOLE.line[i],
COLOR_WHITE,
&FONT_TILESET_DEFAULT,
&FONT_TEXTURE_DEFAULT
));
}
return spriteBatchFlush();
}
void consoleDispose(void) {
#ifdef DUSK_CONSOLE_POSIX
threadStop(&CONSOLE.thread);
threadMutexDispose(&CONSOLE.execMutex);
threadMutexDispose(&CONSOLE.printMutex);
#endif
}
#ifdef DUSK_CONSOLE_POSIX
void consoleInputThread(thread_t *thread) {
assertNotNull(thread, "Thread cannot be NULL.");
char_t line[CONSOLE_LINE_MAX];
struct pollfd pfd = {
.fd = STDIN_FILENO,
.events = POLLIN
};
while(!threadShouldStop(thread) && ENGINE.running) {
int32_t rc = poll(&pfd, 1, CONSOLE_POSIX_POLL_RATE);
if(rc == 0) continue;
if(rc < 0) {
if(errno == EINTR) continue;
assertUnreachable("poll() failed with unexpected error.");
}
if(pfd.revents & (POLLERR | POLLHUP | POLLNVAL)) break;
if(!(pfd.revents & POLLIN)) {
pfd.revents = 0;
continue;
}
if(!fgets(line, CONSOLE_LINE_MAX, stdin)) {
if(feof(stdin)) break;
clearerr(stdin);
continue;
}
size_t len = strlen(line);
while(len && (line[len - 1] == '\n' || line[len - 1] == '\r')) {
line[--len] = '\0';
}
if(len > 0) consoleExec(line);
pfd.revents = 0;
}
}
#endif
-79
View File
@@ -1,79 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "consoledefs.h"
#include "error/error.h"
#include "dusk.h"
#ifdef DUSK_CONSOLE_POSIX
#include "thread/thread.h"
#include <poll.h>
#include <unistd.h>
#define CONSOLE_POSIX_POLL_RATE 75
#endif
typedef struct {
char_t line[CONSOLE_HISTORY_MAX][CONSOLE_LINE_MAX];
bool_t visible;
char_t execBuffer[CONSOLE_EXEC_BUFFER_MAX][CONSOLE_LINE_MAX];
uint32_t execBufferCount;
#ifdef DUSK_CONSOLE_POSIX
thread_t thread;
threadmutex_t execMutex;
threadmutex_t printMutex;
#endif
} console_t;
extern console_t CONSOLE;
/**
* Initializes the console.
*/
void consoleInit(void);
/**
* Prints a message to the console history.
*
* @param message The message to print (printf-style).
*/
void consolePrint(const char_t *message, ...);
/**
* Queues a JS string for execution on the main thread. Thread-safe.
*
* @param line The JS source line to execute.
*/
void consoleExec(const char_t *line);
/**
* Processes pending queued script lines. Call once per frame from main thread.
*/
void consoleUpdate(void);
/**
* Renders the console history to the screen (UI space).
*
* @return The error return value.
*/
errorret_t consoleDraw(void);
/**
* Disposes of the console.
*/
void consoleDispose(void);
#ifdef DUSK_CONSOLE_POSIX
/**
* Input thread handler for POSIX stdin.
*
* @param thread The thread that is running.
*/
void consoleInputThread(thread_t *thread);
#endif
-12
View File
@@ -1,12 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#define CONSOLE_LINE_MAX 512
#define CONSOLE_HISTORY_MAX 16
#define CONSOLE_EXEC_BUFFER_MAX 32
-9
View File
@@ -1,9 +0,0 @@
# Copyright (c) 2026 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
cutscene.c
)
-187
View File
@@ -1,187 +0,0 @@
// Copyright (c) 2026 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
#include "cutscene.h"
#include "assert/assert.h"
#include "util/memory.h"
#include "script/module/modulebase.h"
#include "script/module/cutscene/modulecutscene.h"
#include "console/console.h"
#include "time/time.h"
cutscene_t CUTSCENE;
static errorret_t cutsceneCallMethod(const char_t *method) {
if(CUTSCENE.scriptRef == CUTSCENE_SCRIPT_REF_NONE) errorOk();
jerry_value_t key = jerry_string_sz(method);
jerry_value_t fn = jerry_object_get(CUTSCENE.scriptRef, key);
jerry_value_free(key);
if(!jerry_value_is_function(fn)) {
jerry_value_free(fn);
errorOk();
}
jerry_value_t result = jerry_call(fn, CUTSCENE.scriptRef, NULL, 0);
jerry_value_free(fn);
if(jerry_value_is_exception(result)) {
cutsceneevent_t *event = &CUTSCENE.events[CUTSCENE.eventCurrent];
char_t errMsg[512];
moduleBaseExceptionMessage(result, errMsg, sizeof(errMsg));
jerry_value_free(result);
errorThrow(
"Cutscene event '%s' %s failed: %s",
event->script.script, method, errMsg
);
}
jerry_value_free(result);
errorOk();
}
static errorret_t cutsceneEventStart(void) {
cutsceneevent_t *event = &CUTSCENE.events[CUTSCENE.eventCurrent];
if(event->type == CUTSCENE_EVENT_TYPE_NATIVE) {
if(event->native.onStart) errorChain(event->native.onStart());
} else if(event->type == CUTSCENE_EVENT_TYPE_SCRIPT) {
jerry_value_t eventClass = CUTSCENE_SCRIPT_REF_NONE;
errorChain(scriptManagerExecFile(event->script.script, &eventClass));
if(!jerry_value_is_function(eventClass)) {
if(eventClass != CUTSCENE_SCRIPT_REF_NONE) jerry_value_free(eventClass);
errorThrow(
"Cutscene event '%s' must export a constructor function",
event->script.script
);
}
jerry_value_t eventObj = jerry_construct(eventClass, NULL, 0);
jerry_value_free(eventClass);
if(jerry_value_is_exception(eventObj)) {
char_t errMsg[512];
moduleBaseExceptionMessage(eventObj, errMsg, sizeof(errMsg));
jerry_value_free(eventObj);
errorThrow(
"Cutscene event '%s' constructor threw: %s",
event->script.script, errMsg
);
}
CUTSCENE.scriptRef = eventObj;
errorChain(cutsceneCallMethod("onStart"));
}
errorOk();
}
static errorret_t cutsceneEventEnd(void) {
cutsceneevent_t *event = &CUTSCENE.events[CUTSCENE.eventCurrent];
if(event->type == CUTSCENE_EVENT_TYPE_NATIVE) {
if(event->native.onEnd) errorChain(event->native.onEnd());
} else if(event->type == CUTSCENE_EVENT_TYPE_SCRIPT) {
errorret_t err = cutsceneCallMethod("onEnd");
if(CUTSCENE.scriptRef != CUTSCENE_SCRIPT_REF_NONE) {
jerry_value_free(CUTSCENE.scriptRef);
CUTSCENE.scriptRef = CUTSCENE_SCRIPT_REF_NONE;
}
errorChain(err);
}
errorOk();
}
errorret_t cutsceneInit(void) {
memoryZero(&CUTSCENE, sizeof(cutscene_t));
CUTSCENE.scriptRef = CUTSCENE_SCRIPT_REF_NONE;
CUTSCENE.activeRef = CUTSCENE_SCRIPT_REF_NONE;
consolePrint("Cutscene init");
errorOk();
}
errorret_t cutsceneUpdate(void) {
#ifdef DUSK_TIME_DYNAMIC
if(TIME.dynamicUpdate) {
errorOk();
}
#endif
errorChain(moduleCutsceneUpdate());
if(!CUTSCENE.active) errorOk();
cutsceneevent_t *event = &CUTSCENE.events[CUTSCENE.eventCurrent];
if(event->type == CUTSCENE_EVENT_TYPE_NATIVE) {
if(event->native.onUpdate) errorChain(event->native.onUpdate());
} else if(event->type == CUTSCENE_EVENT_TYPE_SCRIPT) {
errorChain(cutsceneCallMethod("onUpdate"));
}
errorOk();
}
errorret_t cutscenePlay(
const cutsceneevent_t *events,
const uint8_t eventCount
) {
assertNotNull(events, "Events cannot be null");
assertTrue(eventCount > 0, "Event count must be greater than zero");
assertTrue(
eventCount <= CUTSCENE_EVENT_COUNT_MAX,
"Event count exceeds CUTSCENE_EVENT_COUNT_MAX"
);
if(CUTSCENE.active) {
errorChain(cutsceneStop());
}
memoryCopy(CUTSCENE.events, events, sizeof(cutsceneevent_t) * eventCount);
CUTSCENE.eventCount = eventCount;
CUTSCENE.eventCurrent = 0;
CUTSCENE.active = true;
errorChain(cutsceneEventStart());
consolePrint("Cutscene play");
errorOk();
}
errorret_t cutsceneAdvance(void) {
if(!CUTSCENE.active) errorOk();
errorChain(cutsceneEventEnd());
CUTSCENE.eventCurrent++;
if(CUTSCENE.eventCurrent >= CUTSCENE.eventCount) {
CUTSCENE.active = false;
errorOk();
}
errorChain(cutsceneEventStart());
consolePrint("Cutscene advance");
errorOk();
}
errorret_t cutsceneStop(void) {
if(!CUTSCENE.active) errorOk();
errorChain(cutsceneEventEnd());
CUTSCENE.active = false;
consolePrint("Cutscene stop");
errorOk();
}
errorret_t cutsceneDispose(void) {
errorChain(cutsceneStop());
errorOk();
}
bool_t cutsceneIsActive(void) {
return CUTSCENE.active;
}
-105
View File
@@ -1,105 +0,0 @@
// 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);
+6 -9
View File
@@ -25,6 +25,8 @@
#include "display/shader/shaderunlit.h"
#include "time/time.h"
#include "script/module/display/moduleshader.h"
display_t DISPLAY = { 0 };
errorret_t displayInit(void) {
@@ -33,7 +35,6 @@ 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 }
@@ -63,7 +64,7 @@ errorret_t displayInit(void) {
glm_perspective(
glm_rad(45.0f),
SCREEN.aspect,
(float_t)SCREEN.width / (float_t)SCREEN.height,
0.1f,
100.0f,
proj
@@ -100,6 +101,9 @@ errorret_t displayUpdate(void) {
errorChain(sceneRender());
// Render UI
// uiRender();
// Finish up
screenUnbind();
screenRender();
@@ -111,13 +115,6 @@ 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());
-11
View File
@@ -40,26 +40,15 @@ 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);
-17
View File
@@ -1,17 +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"
#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;
@@ -40,17 +40,6 @@ uint32_t frameBufferGetHeight(const framebuffer_t *framebuffer) {
return frameBufferPlatformGetHeight(framebuffer);
}
float_t frameBufferGetAspect(const framebuffer_t *framebuffer) {
#ifdef frameBufferPlatformGetAspect
return frameBufferPlatformGetAspect(framebuffer);
#endif
uint32_t width = frameBufferGetWidth(framebuffer);
uint32_t height = frameBufferGetHeight(framebuffer);
if(height == 0) return 1.0f; // Avoid divide by zero, just return 1:1 aspect.
return (float_t)width / (float_t)height;
}
void frameBufferClear(const uint8_t flags, const color_t color) {
frameBufferPlatformClear(flags, color);
}
@@ -58,16 +58,6 @@ uint32_t frameBufferGetWidth(const framebuffer_t *framebuffer);
*/
uint32_t frameBufferGetHeight(const framebuffer_t *framebuffer);
/**
* Returns the aspect ratio of the framebuffer. This is ALMOST always just
* the width / height, however some platforms may choose to override this if
* they have stretched styled back buffers, e.g. 640x480 stretched.
*
* @param framebuffer The framebuffer to get the aspect ratio of.
* @return The aspect ratio of the framebuffer.
*/
float_t frameBufferGetAspect(const framebuffer_t *framebuffer);
/**
* Binds the framebuffer for rendering, or the backbuffer if the framebuffer
* provided is NULL.
+4 -12
View File
@@ -119,12 +119,8 @@ 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;
@@ -156,12 +152,8 @@ 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;
+1 -1
View File
@@ -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.
* @param vertices The vertex array to buffer into (must hold CUBE_VERTEX_COUNT).
* @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.
+2 -6
View File
@@ -34,14 +34,10 @@ 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) {
+13 -7
View File
@@ -9,6 +9,12 @@
#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
@@ -29,13 +35,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.
* @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 (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.
*/
void triPrismBuffer(
meshvertex_t *vertices,
+4 -4
View File
@@ -52,7 +52,7 @@ errorret_t screenBind() {
// Screen mode backbuffer uses the full display size
SCREEN.width = frameBufferGetWidth(FRAMEBUFFER_BOUND);
SCREEN.height = frameBufferGetHeight(FRAMEBUFFER_BOUND);
SCREEN.aspect = frameBufferGetAspect(FRAMEBUFFER_BOUND);
SCREEN.aspect = (float_t)SCREEN.width / (float_t)SCREEN.height;
// No needd for a framebuffer.
#ifdef DUSK_DISPLAY_SIZE_DYNAMIC
@@ -100,7 +100,8 @@ errorret_t screenBind() {
int32_t fbWidth, fbHeight;
fbWidth = frameBufferGetWidth(FRAMEBUFFER_BOUND);
fbHeight = frameBufferGetHeight(FRAMEBUFFER_BOUND);
float_t currentAspect = frameBufferGetAspect(FRAMEBUFFER_BOUND);
float_t currentAspect = (float_t)fbWidth / (float_t)fbHeight;
if(currentAspect == SCREEN.aspectRatio.ratio) {
// No need to use framebuffer.
SCREEN.width = fbWidth;
@@ -128,14 +129,13 @@ errorret_t screenBind() {
if(SCREEN.framebufferReady) {
// Is current framebuffer the correct size?
int32_t curFbWidth, curFbHeight;
float_t curFbAspect = frameBufferGetAspect(&SCREEN.framebuffer);
curFbWidth = frameBufferGetWidth(&SCREEN.framebuffer);
curFbHeight = frameBufferGetHeight(&SCREEN.framebuffer);
if(curFbWidth == newFbWidth && curFbHeight == newFbHeight) {
// Correct size, nothing to do.
SCREEN.width = newFbWidth;
SCREEN.height = newFbHeight;
SCREEN.aspect = curFbAspect;
SCREEN.aspect = (float_t)SCREEN.width / (float_t)SCREEN.height;
errorChain(frameBufferBind(&SCREEN.framebuffer));
errorOk();
}
+2 -2
View File
@@ -8,9 +8,9 @@
#pragma once
#include "display/mesh/quad.h"
#define SPRITEBATCH_SPRITES_MAX 256
#define SPRITEBATCH_SPRITES_MAX 32
#define SPRITEBATCH_VERTEX_COUNT (SPRITEBATCH_SPRITES_MAX * QUAD_VERTEX_COUNT)
#define SPRITEBATCH_FLUSH_COUNT 8
#define SPRITEBATCH_FLUSH_COUNT 4
#define SPRITEBATCH_SPRITES_MAX_PER_FLUSH (\
SPRITEBATCH_SPRITES_MAX / SPRITEBATCH_FLUSH_COUNT \
)
+8 -11
View File
@@ -13,19 +13,19 @@
#include "asset/loader/display/assettilesetloader.h"
#include "display/shader/shaderunlit.h"
texture_t FONT_TEXTURE_DEFAULT;
tileset_t FONT_TILESET_DEFAULT;
texture_t DEFAULT_FONT_TEXTURE;
tileset_t DEFAULT_FONT_TILESET;
errorret_t textInit(void) {
errorChain(assetTextureLoad(
"ui/minogram.png", &FONT_TEXTURE_DEFAULT, TEXTURE_FORMAT_RGBA
"ui/minogram.png", &DEFAULT_FONT_TEXTURE, TEXTURE_FORMAT_RGBA
));
errorChain(assetTilesetLoad("ui/minogram.dtf", &FONT_TILESET_DEFAULT));
errorChain(assetTilesetLoad("ui/minogram.dtf", &DEFAULT_FONT_TILESET));
errorOk();
}
errorret_t textDispose(void) {
errorChain(textureDispose(&FONT_TEXTURE_DEFAULT));
errorChain(textureDispose(&DEFAULT_FONT_TEXTURE));
errorOk();
}
@@ -70,7 +70,9 @@ errorret_t textDraw(
const float_t x,
const float_t y,
const char_t *text,
const color_t color,
#if MESH_ENABLE_COLOR
const color_t color,
#endif
const tileset_t *tileset,
texture_t *texture
) {
@@ -81,11 +83,6 @@ errorret_t textDraw(
errorChain(shaderSetTexture(&SHADER_UNLIT, SHADER_UNLIT_TEXTURE, texture));
#if MESH_ENABLE_COLOR
#else
errorChain(shaderSetColor(&SHADER_UNLIT, SHADER_UNLIT_COLOR, color));
#endif
// errorChain(spriteBatchPush(
// // texture,
// posX, posY,
+5 -3
View File
@@ -12,8 +12,8 @@
#define TEXT_CHAR_START '!'
extern texture_t FONT_TEXTURE_DEFAULT;
extern tileset_t FONT_TILESET_DEFAULT;
extern texture_t DEFAULT_FONT_TEXTURE;
extern tileset_t DEFAULT_FONT_TILESET;
/**
* Initializes the text system.
@@ -66,7 +66,9 @@ errorret_t textDraw(
const float_t x,
const float_t y,
const char_t *text,
const color_t color,
#if MESH_ENABLE_COLOR
const color_t color,
#endif
const tileset_t *tileset,
texture_t *texture
);
-1
View File
@@ -21,7 +21,6 @@
#include <cglm/cglm.h>
#include <cglm/types.h>
#include <cglm/vec2.h>
#include <jerryscript.h>
#include "duskplatform.h"
+61 -39
View File
@@ -12,32 +12,23 @@
#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();
return epoch.time * 1000.0;
}
int32_t jerry_port_local_tza(double unix_ms) {
(void) unix_ms;
return 0;
}
#include "display/mesh/cube.h"
#include "display/mesh/plane.h"
engine_t ENGINE;
/* Kept module-level only because engineUpdate needs them for the reset. */
static entityid_t phBoxEnt;
static componentid_t phBoxPhys;
errorret_t engineInit(const int32_t argc, const char_t **argv) {
memoryZero(&ENGINE, sizeof(engine_t));
ENGINE.running = true;
@@ -45,49 +36,84 @@ errorret_t engineInit(const int32_t argc, const char_t **argv) {
ENGINE.argv = argv;
// Init systems. Order is important.
errorChain(systemInit());
timeInit();
consoleInit();
errorChain(inputInit());
errorChain(assetInit());
errorChain(localeManagerInit());
errorChain(scriptManagerInit());
errorChain(displayInit());
errorChain(uiInit());
errorChain(uiTextboxInit());
errorChain(cutsceneInit());
errorChain(sceneInit());
entityManagerInit();
backpackInit();
physicsManagerInit();
errorChain(networkInit());
errorChain(gameInit());
/* ---- Camera ---- */
entityid_t cam = entityManagerAdd();
componentid_t camPos = entityAddComponent(cam, COMPONENT_TYPE_POSITION);
float_t distance = 6.0f;
entityPositionLookAt(
cam, camPos,
(vec3){ 0.0f, 1.0f, 0.0f },
(vec3){ 0.0f, 1.0f, 0.0f },
(vec3){ distance, distance, distance }
);
componentid_t camCam = entityAddComponent(cam, COMPONENT_TYPE_CAMERA);
entityCameraSetZFar(cam, camCam, distance * 6.0f);
/* ---- Static floor (visual + physics) ---- */
entityid_t floorEnt = entityManagerAdd();
componentid_t floorPos = entityAddComponent(floorEnt, COMPONENT_TYPE_POSITION);
componentid_t floorMesh = entityAddComponent(floorEnt, COMPONENT_TYPE_MESH);
componentid_t floorMat = entityAddComponent(floorEnt, COMPONENT_TYPE_MATERIAL);
componentid_t floorPhys = entityAddComponent(floorEnt, COMPONENT_TYPE_PHYSICS);
entityPositionSetPosition(floorEnt, floorPos, (vec3){ -5.0f, 0.0f, -5.0f });
entityPositionSetScale(floorEnt, floorPos, (vec3){ 10.0f, 1.0f, 10.0f });
entityMeshSetMesh(floorEnt, floorMesh, &PLANE_MESH_SIMPLE);
entityMaterialGetShaderMaterial(floorEnt, floorMat)->unlit.color = COLOR_GREEN;
entityphysics_t *floorPhysData = entityPhysicsGet(floorEnt, floorPhys);
floorPhysData->type = PHYSICS_BODY_STATIC;
floorPhysData->shape.type = PHYSICS_SHAPE_PLANE;
floorPhysData->shape.data.plane.normal[0] = 0.0f;
floorPhysData->shape.data.plane.normal[1] = 1.0f;
floorPhysData->shape.data.plane.normal[2] = 0.0f;
floorPhysData->shape.data.plane.distance = 0.0f;
// Test Box
phBoxEnt = entityManagerAdd();
componentid_t boxMesh = entityAddComponent(phBoxEnt, COMPONENT_TYPE_MESH);
// componentid_t boxMat = entityAddComponent(phBoxEnt, COMPONENT_TYPE_MATERIAL);
entityMeshSetMesh(phBoxEnt, boxMesh, &CUBE_MESH_SIMPLE);
// entityMaterialGetShaderMaterial(phBoxEnt, boxMat)->unlit.color = COLOR_RED;
componentid_t boxScript = entityAddComponent(phBoxEnt, COMPONENT_TYPE_SCRIPT);
errorChain(entityScriptExecAsset(phBoxEnt, boxScript, "entity/test.lua"));
/* Run the init script. */
consolePrint("Engine initialized");
errorChain(scriptManagerExecFile("init.js", NULL));
scriptcontext_t ctx;
errorChain(scriptContextInit(&ctx));
errorChain(scriptContextExecFile(&ctx, "init.lua"));
scriptContextDispose(&ctx);
errorOk();
}
errorret_t engineUpdate(void) {
// Order here is important.
errorChain(networkUpdate());
timeUpdate();
inputUpdate();
consoleUpdate();
uiUpdate();
errorChain(uiTextboxUpdate());
errorChain(sceneUpdate());
/* Step physics: positions are updated directly on POSITION components. */
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();
}
@@ -96,16 +122,12 @@ void engineExit(void) {
}
errorret_t engineDispose(void) {
uiTextboxDispose();
cutsceneDispose();
sceneDispose();
errorChain(networkDispose());
errorChain(gameDispose());
entityManagerDispose();
localeManagerDispose();
uiDispose();
consoleDispose();
errorChain(displayDispose());
errorChain(assetDispose());
errorOk();
}
+1 -2
View File
@@ -35,5 +35,4 @@ errorret_t engineUpdate(void);
/**
* Shuts down the engine.
*/
errorret_t engineDispose(void);
errorret_t engineDispose(void);
+3 -9
View File
@@ -12,14 +12,8 @@
componentdefinition_t COMPONENT_DEFINITIONS[] = {
[COMPONENT_TYPE_NULL] = { 0 },
#define X(enm, type, field, iMethod, dMethod) \
[COMPONENT_TYPE_##enm] = { \
.enumName = #enm, \
.name = #field, \
.init = iMethod, \
.dispose = dMethod \
},
#define X(enumName, type, field, iMethod, dMethod) \
[COMPONENT_TYPE_##enumName] = { .init = iMethod, .dispose = dMethod },
#include "componentlist.h"
#undef X
@@ -101,7 +95,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(
-2
View File
@@ -20,8 +20,6 @@ typedef union {
} componentdata_t;
typedef struct {
const char_t *enumName;
const char_t *name;
void (*init)(const entityid_t, const componentid_t);
void (*dispose)(const entityid_t, const componentid_t);
} componentdefinition_t;
@@ -6,8 +6,6 @@
*/
#include "entity/entitymanager.h"
#include "entity/entity.h"
#include "entity/component/display/entityposition.h"
#include "display/framebuffer/framebuffer.h"
#include "display/screen/screen.h"
@@ -21,6 +19,101 @@ void entityCameraInit(const entityid_t ent, const componentid_t comp) {
cam->perspective.fov = glm_rad(45.0f);
}
float_t entityCameraGetZNear(const entityid_t ent, const componentid_t comp) {
entitycamera_t *cam = (entitycamera_t *)componentGetData(
ent, comp, COMPONENT_TYPE_CAMERA
);
return cam->nearClip;
}
void entityCameraSetZNear(
const entityid_t ent,
const componentid_t comp,
const float_t zNear
) {
entitycamera_t *cam = (entitycamera_t *)componentGetData(
ent, comp, COMPONENT_TYPE_CAMERA
);
cam->nearClip = zNear;
}
float_t entityCameraGetZFar(const entityid_t ent, const componentid_t comp) {
entitycamera_t *cam = (entitycamera_t *)componentGetData(
ent, comp, COMPONENT_TYPE_CAMERA
);
return cam->farClip;
}
void entityCameraSetZFar(
const entityid_t ent,
const componentid_t comp,
const float_t zFar
) {
entitycamera_t *cam = (entitycamera_t *)componentGetData(
ent, comp, COMPONENT_TYPE_CAMERA
);
cam->farClip = zFar;
}
entitycameraprojectiontype_t entityCameraGetProjType(
const entityid_t ent,
const componentid_t comp
) {
entitycamera_t *cam = (entitycamera_t *)componentGetData(
ent, comp, COMPONENT_TYPE_CAMERA
);
return cam->projType;
}
void entityCameraSetProjType(
const entityid_t ent,
const componentid_t comp,
const entitycameraprojectiontype_t type
) {
entitycamera_t *cam = (entitycamera_t *)componentGetData(
ent, comp, COMPONENT_TYPE_CAMERA
);
cam->projType = type;
}
float_t entityCameraGetFov(
const entityid_t ent,
const componentid_t comp
) {
entitycamera_t *cam = (entitycamera_t *)componentGetData(
ent, comp, COMPONENT_TYPE_CAMERA
);
return cam->perspective.fov;
}
void entityCameraSetFov(
const entityid_t ent,
const componentid_t comp,
const float_t fov
) {
entitycamera_t *cam = (entitycamera_t *)componentGetData(
ent, comp, COMPONENT_TYPE_CAMERA
);
cam->perspective.fov = fov;
}
void entityCameraSetOrthographic(
const entityid_t ent,
const componentid_t comp,
const float_t left,
const float_t right,
const float_t top,
const float_t bottom
) {
entitycamera_t *cam = (entitycamera_t *)componentGetData(
ent, comp, COMPONENT_TYPE_CAMERA
);
cam->orthographic.left = left;
cam->orthographic.right = right;
cam->orthographic.top = top;
cam->orthographic.bottom = bottom;
}
void entityCameraGetProjection(
const entityid_t ent,
const componentid_t comp,
@@ -58,39 +151,4 @@ void entityCameraGetProjection(
out
);
}
}
entityid_t entityCameraGetCurrent(void) {
entityid_t camEnts[ENTITY_COUNT_MAX];
componentid_t camComps[ENTITY_COUNT_MAX];
entityid_t count = componentGetEntitiesWithComponent(
COMPONENT_TYPE_CAMERA, camEnts, camComps
);
if(count == 0) return ENTITY_COUNT_MAX;
return camEnts[0];
}
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]}
float_t fx = -pos->transform[0][2];
float_t fz = -pos->transform[2][2];
float_t len = sqrtf(fx * fx + fz * fz);
if(len > 1e-6f) { fx /= len; fz /= len; }
out[0] = fx;
out[1] = fz;
}
void entityCameraGetRight(const entityid_t entityId, vec2 out) {
componentid_t posComp = entityGetComponent(entityId, COMPONENT_TYPE_POSITION);
entityposition_t *pos = entityPositionGet(entityId, posComp);
// View matrix column layout: right = {M[0][0], M[1][0], M[2][0]}
float_t rx = pos->transform[0][0];
float_t rz = pos->transform[2][0];
float_t len = sqrtf(rx * rx + rz * rz);
if(len > 1e-6f) { rx /= len; rz /= len; }
out[0] = rx;
out[1] = rz;
}
+102 -13
View File
@@ -55,25 +55,114 @@ void entityCameraGetProjection(
);
/**
* Returns the entity ID of the first active camera, or ENTITY_COUNT_MAX if
* none are active.
* Gets the near clip distance of a camera.
*
* @param ent The entity ID.
* @param comp The component ID.
* @return The near clip distance.
*/
entityid_t entityCameraGetCurrent(void);
float_t entityCameraGetZNear(const entityid_t ent, const componentid_t comp);
/**
* Gets the camera's horizontal forward direction (XZ plane) from its position
* component. Automatically finds the position component on the entity.
* Sets the near clip distance of a camera.
*
* @param entityId The camera entity ID.
* @param out Output vec2: {forwardX, forwardZ} normalized.
* @param ent The entity ID.
* @param comp The component ID.
* @param zNear The near clip distance.
*/
void entityCameraGetForward(const entityid_t entityId, vec2 out);
void entityCameraSetZNear(
const entityid_t ent,
const componentid_t comp,
const float_t zNear
);
/**
* Gets the camera's horizontal right direction (XZ plane) from its position
* component. Automatically finds the position component on the entity.
* Gets the far clip distance of a camera.
*
* @param entityId The camera entity ID.
* @param out Output vec2: {rightX, rightZ} normalized.
* @param ent The entity ID.
* @param comp The component ID.
* @return The far clip distance.
*/
void entityCameraGetRight(const entityid_t entityId, vec2 out);
float_t entityCameraGetZFar(const entityid_t ent, const componentid_t comp);
/**
* Sets the far clip distance of a camera.
*
* @param ent The entity ID.
* @param comp The component ID.
* @param zFar The far clip distance.
*/
void entityCameraSetZFar(
const entityid_t ent,
const componentid_t comp,
const float_t zFar
);
/**
* Gets the projection type of a camera.
*
* @param ent The entity ID.
* @param comp The component ID.
* @return The projection type.
*/
entitycameraprojectiontype_t entityCameraGetProjType(
const entityid_t ent,
const componentid_t comp
);
/**
* Sets the projection type of a camera.
*
* @param ent The entity ID.
* @param comp The component ID.
* @param type The projection type.
*/
void entityCameraSetProjType(
const entityid_t ent,
const componentid_t comp,
const entitycameraprojectiontype_t type
);
/**
* Gets the field of view (in radians) of a perspective camera.
*
* @param ent The entity ID.
* @param comp The component ID.
* @return The field of view in radians.
*/
float_t entityCameraGetFov(
const entityid_t ent,
const componentid_t comp
);
/**
* Sets the field of view (in radians) of a perspective camera.
*
* @param ent The entity ID.
* @param comp The component ID.
* @param fov The field of view in radians.
*/
void entityCameraSetFov(
const entityid_t ent,
const componentid_t comp,
const float_t fov
);
/**
* Sets the orthographic projection bounds of a camera.
*
* @param ent The entity ID.
* @param comp The component ID.
* @param left Left bound.
* @param right Right bound.
* @param top Top bound.
* @param bottom Bottom bound.
*/
void entityCameraSetOrthographic(
const entityid_t ent,
const componentid_t comp,
const float_t left,
const float_t right,
const float_t top,
const float_t bottom
);
@@ -48,15 +48,4 @@ void entityMaterialSetShader(
entityId, componentId, COMPONENT_TYPE_MATERIAL
);
mat->shader = shader;
}
void entityMaterialSetColor(
const entityid_t entityId,
const componentid_t componentId,
const color_t color
) {
entitymaterial_t *mat = componentGetData(
entityId, componentId, COMPONENT_TYPE_MATERIAL
);
mat->material.unlit.color = color;
}
@@ -60,17 +60,4 @@ void entityMaterialSetShader(
const entityid_t entityId,
const componentid_t componentId,
shader_t *shader
);
/**
* Sets the unlit color for the given entity and component.
*
* @param entityId The entity ID.
* @param componentId The component ID.
* @param color The color to set.
*/
void entityMaterialSetColor(
const entityid_t entityId,
const componentid_t componentId,
const color_t color
);
@@ -5,7 +5,6 @@
* https://opensource.org/licenses/MIT
*/
#include "entitymesh.h"
#include "entity/entitymanager.h"
void entityMeshInit(
@@ -37,10 +36,4 @@ void entityMeshSetMesh(
entityId, componentId, COMPONENT_TYPE_MESH
);
comp->mesh = mesh;
}
void entityMeshDispose(
const entityid_t entityId,
const componentid_t componentId
) {
}
}
+2 -13
View File
@@ -41,21 +41,10 @@ mesh_t * entityMeshGetMesh(
*
* @param entityId The entity ID.
* @param componentId The component ID.
* @param mesh A pointer to the mesh to associate.
* @param mesh A pointer to the mesh to associate with the entity mesh component.
*/
void entityMeshSetMesh(
const entityid_t entityId,
const componentid_t componentId,
mesh_t *mesh
);
/**
* Disposes the entity mesh component.
*
* @param entityId The entity ID.
* @param componentId The component ID.
*/
void entityMeshDispose(
const entityid_t entityId,
const componentid_t componentId
);
);
@@ -100,6 +100,15 @@ bool_t entityPhysicsIsOnGround(
return phys->onGround;
}
physicsbodytype_t entityPhysicsGetBodyType(
const entityid_t entityId,
const componentid_t componentId
) {
entityphysics_t *phys = entityPhysicsGet(entityId, componentId);
assertNotNull(phys, "Failed to get physics component data");
return phys->type;
}
void entityPhysicsSetBodyType(
const entityid_t entityId,
const componentid_t componentId,
@@ -110,13 +119,23 @@ void entityPhysicsSetBodyType(
phys->type = type;
}
physicsbodytype_t entityPhysicsGetBodyType(
float_t entityPhysicsGetGravityScale(
const entityid_t entityId,
const componentid_t componentId
) {
entityphysics_t *phys = entityPhysicsGet(entityId, componentId);
assertNotNull(phys, "Failed to get physics component data");
return phys->type;
return phys->gravityScale;
}
void entityPhysicsSetGravityScale(
const entityid_t entityId,
const componentid_t componentId,
const float_t scale
) {
entityphysics_t *phys = entityPhysicsGet(entityId, componentId);
assertNotNull(phys, "Failed to get physics component data");
phys->gravityScale = scale;
}
void entityPhysicsDispose(
@@ -122,12 +122,24 @@ bool_t entityPhysicsIsOnGround(
const componentid_t componentId
);
/**
* Gets the body type of the entity's physics body.
*
* @param entityId The entity ID.
* @param componentId The component ID.
* @return The body type (static, dynamic, kinematic).
*/
physicsbodytype_t entityPhysicsGetBodyType(
const entityid_t entityId,
const componentid_t componentId
);
/**
* Sets the body type of the entity's physics body.
*
* @param entityId The entity ID.
* @param componentId The component ID.
* @param type The body type to set.
* @param type The new body type.
*/
void entityPhysicsSetBodyType(
const entityid_t entityId,
@@ -136,17 +148,30 @@ void entityPhysicsSetBodyType(
);
/**
* Gets the body type of the entity's physics body.
* Gets the gravity scale of the entity's physics body.
*
* @param entityId The entity ID.
* @param componentId The component ID.
* @return The body type of the physics body.
* @return The gravity scale factor.
*/
physicsbodytype_t entityPhysicsGetBodyType(
float_t entityPhysicsGetGravityScale(
const entityid_t entityId,
const componentid_t componentId
);
/**
* Sets the gravity scale of the entity's physics body.
*
* @param entityId The entity ID.
* @param componentId The component ID.
* @param scale The new gravity scale factor.
*/
void entityPhysicsSetGravityScale(
const entityid_t entityId,
const componentid_t componentId,
const float_t scale
);
/**
* Releases the body slot back to PHYSICS_WORLD. Called automatically when
* the component is disposed via the component system.
@@ -1,7 +1,10 @@
# 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
entityscript.c
)
@@ -0,0 +1,61 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "entityscript.h"
#include "entity/component.h"
#include "assert/assert.h"
void entityScriptInit(
const entityid_t entityId,
const componentid_t componentId
) {
entityscript_t *script = (entityscript_t*)componentGetData(
entityId, componentId, COMPONENT_TYPE_SCRIPT
);
scriptContextInit(&script->scriptContext);
// Define script globals.
char_t strScript[64];
snprintf(strScript, sizeof(strScript), "ENTITY_ID = %d\n", entityId);
errorret_t ret = scriptContextExec(&script->scriptContext, strScript);
if(ret.code != ERROR_OK) {
errorCatch(errorPrint(ret));
assertUnreachable("Failed to set up script globals");
}
}
errorret_t entityScriptExec(
const entityid_t entityId,
const componentid_t componentId,
const char_t *script
) {
entityscript_t *entityScript = (entityscript_t*)componentGetData(
entityId, componentId, COMPONENT_TYPE_SCRIPT
);
return scriptContextExec(&entityScript->scriptContext, script);
}
errorret_t entityScriptExecAsset(
const entityid_t entityId,
const componentid_t componentId,
const char_t *assetName
) {
entityscript_t *entityScript = (entityscript_t*)componentGetData(
entityId, componentId, COMPONENT_TYPE_SCRIPT
);
return scriptContextExecFile(&entityScript->scriptContext, assetName);
}
void entityScriptDispose(
const entityid_t entityId,
const componentid_t componentId
) {
entityscript_t *script = (entityscript_t*)componentGetData(
entityId, componentId, COMPONENT_TYPE_SCRIPT
);
scriptContextDispose(&script->scriptContext);
}
@@ -0,0 +1,64 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "entity/entitybase.h"
#include "script/scriptcontext.h"
typedef struct {
scriptcontext_t scriptContext;
} entityscript_t;
/**
* Initializes the script entity component.
*
* @param entityId The entity ID.
* @param componentId The component ID.
*/
void entityScriptInit(
const entityid_t entityId,
const componentid_t componentId
);
/**
* Executes a script on the entity's script component.
*
* @param entityId The entity ID.
* @param componentId The component ID.
* @param script The script to execute.
* @return The error return value.
*/
errorret_t entityScriptExec(
const entityid_t entityId,
const componentid_t componentId,
const char_t *script
);
/**
* Executes a script from an asset on the entity's script component.
*
* @param entityId The entity ID.
* @param componentId The component ID.
* @param assetName The name of the script asset to execute.
* @return The error return value.
*/
errorret_t entityScriptExecAsset(
const entityid_t entityId,
const componentid_t componentId,
const char_t *assetName
);
/**
* Disposes of the script entity component.
*
* @param entityId The entity ID.
* @param componentId The component ID.
*/
void entityScriptDispose(
const entityid_t entityId,
const componentid_t componentId
);
+4 -8
View File
@@ -10,15 +10,11 @@
#include "entity/component/display/entitymesh.h"
#include "entity/component/display/entitymaterial.h"
#include "entity/component/physics/entityphysics.h"
// Name (Uppercase)
// Structure
// Field name (lowercase)
// Init function (optional)
// Dispose function (optional)
#include "entity/component/script/entityscript.h"
X(POSITION, entityposition_t, position, entityPositionInit, NULL)
X(CAMERA, entitycamera_t, camera, entityCameraInit, NULL)
X(MESH, entitymesh_t, mesh, entityMeshInit, entityMeshDispose)
X(MESH, entitymesh_t, mesh, entityMeshInit, NULL)
X(MATERIAL, entitymaterial_t, material, entityMaterialInit, NULL)
X(PHYSICS, entityphysics_t, physics, entityPhysicsInit, entityPhysicsDispose)
X(PHYSICS, entityphysics_t, physics, entityPhysicsInit, entityPhysicsDispose)
X(SCRIPT, entityscript_t, script, entityScriptInit, entityScriptDispose)
+2 -2
View File
@@ -8,8 +8,8 @@
#pragma once
#include "dusk.h"
#define ENTITY_COUNT_MAX 20
#define ENTITY_COMPONENT_COUNT_MAX 8
#define ENTITY_COUNT_MAX 64
#define ENTITY_COMPONENT_COUNT_MAX 16
typedef uint8_t entityid_t;
typedef uint8_t componentid_t;
+2 -3
View File
@@ -8,7 +8,6 @@
#include "entitymanager.h"
#include "assert/assert.h"
#include "util/memory.h"
#include "console/console.h"
entitymanager_t ENTITY_MANAGER;
@@ -19,8 +18,8 @@ void entityManagerInit(void) {
sizeof(entityid_t) * COMPONENT_TYPE_COUNT * ENTITY_COUNT_MAX
);
consolePrint(
"Entity Manager size: %zu bytes (%.2f KB)",
printf(
"Entity Manager size is currently: %zu bytes (%.2f KB)\n",
sizeof(entitymanager_t),
sizeof(entitymanager_t) / 1024.0f
);
+1 -3
View File
@@ -83,9 +83,7 @@ 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,10 +1,10 @@
# 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
log.c
)
event.c
)
+252
View File
@@ -0,0 +1,252 @@
/**
* 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"
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.luaFunctionRef != LUA_NOREF,
"Script event listener function reference is invalid"
);
} else {
assertUnreachable("Unknown event listener type");
}
// Gen a new ID
eventsub_t id = event->nextId++;
// Did we wrap?
assertTrue(event->nextId != 0, "Event subscription ID overflow");
// Append listener
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
) {
eventSubscribeUser(
event,
EVENT_TYPE_C,
(eventuserdata_t){ .c = { .callback = callback, .user = (void *)user } }
);
}
eventsub_t eventSubscribeScriptContext(
event_t *event,
scriptcontext_t *context,
const int functionIndex
) {
assertNotNull(context, "Script context cannot be NULL");
assertTrue(
lua_isfunction(context->luaState, functionIndex),
"Expected function at given index"
);
// Create a reference to the function
lua_pushvalue(context->luaState, functionIndex);
int funcRef = luaL_ref(context->luaState, LUA_REGISTRYINDEX);
eventscript_t scriptUser = {
.context = context,
.luaFunctionRef = funcRef
};
// Note to the context that it is now a part of this event
bool_t alreadySubbed = false;
uint8_t i;
i = 0;
do {
if(context->subscribedEvents[i] != event) {
i++;
continue;
}
if(context->subscribedEvents[i] == NULL) break;
alreadySubbed = true;
break;
} while(i < SCRIPT_CONTEXT_MAX_EVENT_SUBSCRIPTIONS);
if(!alreadySubbed) {
i = 0;
do {
if(context->subscribedEvents[i] != NULL) {
i++;
continue;
}
context->subscribedEvents[i] = event;
break;
} while(i < SCRIPT_CONTEXT_MAX_EVENT_SUBSCRIPTIONS);
assertTrue(
i < SCRIPT_CONTEXT_MAX_EVENT_SUBSCRIPTIONS,
"Script context has reached maximum event subscriptions"
);
}
return eventSubscribeUser(
event,
EVENT_TYPE_SCRIPT,
(eventuserdata_t){ .script = scriptUser }
);
}
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;
// Find listener
uint16_t index = 0;
do {
if(event->listenerArray[index].id == id) {
// Found it, remove by swapping with last and reducing count
event->listenerArray[index] = event->listenerArray[--event->listenerCount];
return;
}
index++;
} while(index < event->listenerCount);
// Did we find it?
if(index == event->listenerCount) return;
// Shift remaining listeners down (if any)
if(index < event->listenerCount - 1) {
memoryMove(
&event->listenerArray[index],
&event->listenerArray[index + 1],
sizeof(eventlistener_t) * (event->listenerCount - index - 1)
);
}
event->listenerCount--;
}
void eventUnsubscribeScriptContext(event_t *event, const scriptcontext_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;
}
// This listener belongs to the context and will need to go away. This will
// in turn decrement the listener count so we don't increment i here.
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;
uint16_t i = 0;
eventdata_t data ={
.event = event,
.eventParams = eventParams,
};
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) {
// Call Lua function
lua_State *L = listener->user.script.context->luaState;
assertNotNull(L, "Lua state in event listener cannot be NULL");
// Push function
lua_rawgeti(L, LUA_REGISTRYINDEX, listener->user.script.luaFunctionRef);
if(eventParams != NULL && metatableName != NULL) {
lua_getmetatable(L, -1);
luaL_getmetatable(L, metatableName);
assertTrue(
lua_rawequal(L, -1, -2),
"Event parameter metatable does not match expected type"
);
}
// Call function with 1 arg, 0 return values
if(lua_pcall(L, 1, 0, 0) != LUA_OK) {
const char_t *strErr = lua_tostring(L, -1);
lua_pop(L, 1);
// Log error but continue
printf("Error invoking Lua event listener: %s\n", strErr);
}
} else {
assertUnreachable("Unknown event listener type");
}
i++;
} while(i < event->listenerCount);
event->isInvoking = false;
}
+121
View File
@@ -0,0 +1,121 @@
/**
* 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,
scriptcontext_t *context,
const int functionIndex
);
/**
* 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 scriptcontext_t *ctx);
/**
* Invoke an event, calling all subscribed listeners. Optionally provide event
* parameters, and if desired pass a metatable name. Event listeners will be
* able to read event params, and if metatable name is provided the script
* listeners will lookup metatable information matching that name to access the
* params as a structure within the script.
*
* @param event The event to invoke.
* @param eventParams Parameters to pass to the event listeners.
* @param metatableName Metatable name. See details.
*/
void eventInvoke(
event_t *event,
const void *eventParams,
const char_t *metatableName
);
+14
View File
@@ -0,0 +1,14 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "dusk.h"
typedef struct eventdata_s eventdata_t;
typedef struct eventc_s eventc_t;
typedef void (*eventcallback_t)(eventdata_t *data, eventc_t user);
+30
View File
@@ -0,0 +1,30 @@
/**
* 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/scriptcontext.h"
typedef enum {
EVENT_TYPE_C = 0,
EVENT_TYPE_SCRIPT = 1
} eventtype_t;
typedef struct {
scriptcontext_t *context;
int luaFunctionRef;
} 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;
+37 -14
View File
@@ -27,6 +27,13 @@ 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();
}
@@ -87,6 +94,28 @@ 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) {
@@ -142,16 +171,6 @@ float_t inputAxis(const inputaction_t neg, const inputaction_t pos) {
return inputGetCurrentValue(pos) - inputGetCurrentValue(neg);
}
void inputAxis2D(
const inputaction_t negX, const inputaction_t posX,
const inputaction_t negY, const inputaction_t posY,
vec2 result
) {
assertNotNull(result, "Result vector cannot be null");
result[0] = inputAxis(negX, posX);
result[1] = inputAxis(negY, posY);
}
void inputBind(const inputbutton_t button, const inputaction_t act) {
assertTrue(
act < INPUT_ACTION_COUNT,
@@ -160,15 +179,19 @@ void inputBind(const inputbutton_t button, const inputaction_t act) {
assertTrue(act != INPUT_ACTION_NULL, "Cannot bind to NULL action");
// Get the button data for this button.
inputbuttondata_t *data = inputButtonGetData(button);
assertNotNull(data, "Input button not found");
inputbuttondata_t *data = INPUT_BUTTON_DATA;
do {
if(memoryCompare(&data->button, &button, sizeof(inputbutton_t)) == 0) {
break;
}
data++;
} while(data->name != NULL);
assertNotNull(data->name, "Input button not found");
// Bind the action.
data->action = act;
}
float_t inputDeadzone(const float_t rawValue, const float_t deadzone) {
if(rawValue < deadzone) return 0.0f;
return (rawValue - deadzone) / (1.0f - deadzone);
+1 -2
View File
@@ -5,7 +5,6 @@ LEFT,
RIGHT,
ACCEPT,
CANCEL,
RAGEQUIT,
CONSOLE,
RAGEQUIT
POINTERX,
POINTERY,
1 id id,
5 RIGHT RIGHT,
6 ACCEPT ACCEPT,
7 CANCEL CANCEL,
8 RAGEQUIT RAGEQUIT
CONSOLE
9 POINTERX POINTERX,
10 POINTERY POINTERY,
+10 -16
View File
@@ -9,13 +9,23 @@
#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;
@@ -110,22 +120,6 @@ bool_t inputReleased(const inputaction_t action);
*/
float_t inputAxis(const inputaction_t neg, const inputaction_t pos);
/**
* Gets the values of a 2D input axis, defined by two pairs of actions (negative
* and positive for each axis).
*
* @param negX The action representing the negative direction of the X axis.
* @param posX The action representing the positive direction of the X axis.
* @param negY The action representing the negative direction of the Y axis.
* @param posY The action representing the positive direction of the Y axis.
* @param result A vec2 to store the resulting axis values (-1.0f to 1.0f).
*/
void inputAxis2D(
const inputaction_t negX, const inputaction_t posX,
const inputaction_t negY, const inputaction_t posY,
vec2 result
);
/**
* Binds an input button to an action.
*
+9 -9
View File
@@ -9,14 +9,14 @@
#include "assert/assert.h"
#include "util/string.h"
inputaction_t inputActionGetByName(const char_t *name) {
assertNotNull(name, "name must not be NULL");
// inputaction_t inputActionGetByName(const char_t *name) {
// assertNotNull(name, "name must not be NULL");
for(inputaction_t i = 0; i < INPUT_ACTION_COUNT; i++) {
if(INPUT_ACTION_IDS[i] == NULL) continue;
if(stringCompareInsensitive(INPUT_ACTION_IDS[i], name) != 0) continue;
return i;
}
// for(inputaction_t i = 0; i < INPUT_ACTION_COUNT; i++) {
// if(INPUT_ACTION_IDS[i] == NULL) continue;
// if(stringCompareInsensitive(INPUT_ACTION_IDS[i], name) != 0) continue;
// return i;
// }
return INPUT_ACTION_COUNT;
}
// return INPUT_ACTION_COUNT;
// }
+1 -1
View File
@@ -26,4 +26,4 @@ typedef struct {
* @param name The name of the input action.
* @return The input action, or INPUT_ACTION_COUNT if not found.
*/
inputaction_t inputActionGetByName(const char_t *name);
// inputaction_t inputActionGetByName(const char_t *name);
-13
View File
@@ -9,7 +9,6 @@
#include "input.h"
#include "assert/assert.h"
#include "util/string.h"
#include "util/memory.h"
inputbutton_t inputButtonGetByName(const char_t *name) {
assertNotNull(name, "name must not be NULL");
@@ -27,16 +26,4 @@ inputbutton_t inputButtonGetByName(const char_t *name) {
float_t inputButtonGetValue(const inputbutton_t button) {
return inputButtonGetValuePlatform(button);
}
inputbuttondata_t * inputButtonGetData(const inputbutton_t button) {
inputbuttondata_t *data = INPUT_BUTTON_DATA;
do {
if(memoryCompare(&data->button, &button, sizeof(inputbutton_t)) == 0) {
return data;
}
data++;
} while(data->name != NULL);
return NULL;
}
+1 -9
View File
@@ -96,12 +96,4 @@ inputbutton_t inputButtonGetByName(const char_t *name);
* @param button The input button.
* @return The current value of the input button (0.0f to 1.0f).
*/
float_t inputButtonGetValue(const inputbutton_t button);
/**
* Gets the button data for a specific input button.
*
* @param button The input button to get the data for.
* @return The button data, or NULL if not found.
*/
inputbuttondata_t * inputButtonGetData(const inputbutton_t button);
float_t inputButtonGetValue(const inputbutton_t button);
-116
View File
@@ -1,116 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "network.h"
#include "util/memory.h"
#include "assert/assert.h"
#include "log/log.h"
network_t NETWORK;
errorret_t networkInit() {
memoryZero(&NETWORK, sizeof(network_t));
NETWORK.errorState.code = ERROR_OK;
NETWORK.onDisconnect = NULL;
return networkPlatformInit();
}
errorret_t networkUpdate() {
errorChain(networkPlatformUpdate());
if(NETWORK.state == NETWORK_STATE_CONNECTED && !networkIsConnected()) {
NETWORK.state = NETWORK_STATE_DISCONNECTED;
if(NETWORK.onDisconnect) {
errorret_t ret;
if(NETWORK.errorState.code == ERROR_OK) {
ret = errorThrowImpl(
&NETWORK.errorState,
ERROR_NOT_OK,
__FILE__, __func__, __LINE__,
"Network connection lost"
);
} else {
ret.code = NETWORK.errorState.code;
ret.state = &NETWORK.errorState;
}
NETWORK.onDisconnect(ret, NETWORK.disconnectUser);
}
}
errorOk();
}
bool_t networkIsConnected() {
return networkPlatformIsConnected();
}
void networkRequestConnection(
void (*onConnected)(void *user),
void (*onFailed)(errorret_t error, void *user),
void (*onDisconnect)(errorret_t error, void *user),
void *user
) {
assertNotNull(onConnected, "onConnected callback must not be null");
assertNotNull(onFailed, "onFailed callback must not be null");
assertNotNull(onDisconnect, "onDisconnect callback must not be null");
NETWORK.state = NETWORK_STATE_CONNECTING;
NETWORK.onDisconnect = onDisconnect;
NETWORK.disconnectUser = user;
#ifndef networkPlatformRequestConnection
// This is a platform cannot be requested to go online, this would basically
// be for platforms like Linux or Windows where the OS is responsible for
// maintaining the network connection.
if(networkIsConnected()) {
NETWORK.state = NETWORK_STATE_CONNECTED;
onConnected(user);
} else {
errorret_t ret = errorThrowImpl(
&NETWORK.errorState,
ERROR_NOT_OK,
__FILE__, __func__, __LINE__,
"No network connection available"
);
onFailed(ret, user);
}
#else
networkPlatformRequestConnection(onConnected, onFailed, onDisconnect, user);
#endif
}
void networkRequestDisconnection(
void (*onComplete)(void *user),
void *user
) {
assertNotNull(onComplete, "onComplete callback must not be null");
NETWORK.state = NETWORK_STATE_DISCONNECTING;
#ifndef networkPlatformRequestDisconnection
NETWORK.state = NETWORK_STATE_DISCONNECTED;
onComplete(user);
#else
networkPlatformRequestDisconnection(onComplete, user);
#endif
}
void networkDisconnectedDuringDispose(void *u) {
logDebug("Network disconnected during dispose\n");
}
errorret_t networkDispose() {
if(NETWORK.state == NETWORK_STATE_CONNECTED) {
networkRequestDisconnection(networkDisconnectedDuringDispose, NULL);
}
errorChain(networkPlatformDispose());
errorOk();
}
-126
View File
@@ -1,126 +0,0 @@
/**
* 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 "network/networkplatform.h"
#ifndef networkPlatformInit
#error "networkPlatformInit must be defined"
#endif
#ifndef networkPlatformUpdate
#error "networkPlatformUpdate must be defined"
#endif
#ifndef networkPlatformDispose
#error "networkPlatformDispose must be defined"
#endif
#ifndef networkPlatformIsConnected
#error "networkPlatformIsConnected must be defined"
#endif
typedef enum {
NETWORK_STATE_DISCONNECTED,
NETWORK_STATE_CONNECTING,
NETWORK_STATE_CONNECTED,
NETWORK_STATE_DISCONNECTING,
} networkstate_t;
typedef struct {
networkplatform_t platform;
errorstate_t errorState;
networkstate_t state;
void (*onDisconnect)(errorret_t error, void *user);
void *disconnectUser;
} network_t;
extern network_t NETWORK;
/**
* Initializes the network system. This will NOT connect to the network.
*
* @return An error code indicating success or failure.
*/
errorret_t networkInit();
/**
* Updates the network manager, dispatching any completed async request
* callbacks on the main thread.
*
* @return An error code indicating success or failure.
*/
errorret_t networkUpdate();
/**
* Disposes of the network manager. This will NOT disconnect from the network.
*
* @return An error code indicating success or failure.
*/
errorret_t networkDispose();
/**
* Returns true if the system is connected to AN network, this doesn't mean that
* requests will succeed, doesn't mean there's internet, just that we could
* possibly make network requests. If this is false, this usually means
* something like;
* - A network cable is not connnected
* - No Wi-Fi Connection has been established
* - No IP Address has been assigned
*
* That kinda stuff.
*
* In future I will probably have REASONS for why it's not connected, for
* example;
* - On PSP you need to "request" network access
* - On GameCube, network settings need to be defined, including DHCP, etc.
* - On Windows, this may require additional permissions
*
* @return True if some network connection is detected.
*/
bool_t networkIsConnected();
/**
* See networkIsConnected for a bit more info, but this is for some
* platforms (mainly PSP) to request the system to start doing networking.
*
* You should only call this once and assume that it is "pending" until either
* onComplete or onFailed is invoked. If you call this twice it is undefined
* behavior.
*
* onDisconnect must be provided, and is called whenever the network is lost
* after a successful connection. This will NOT be called if disconnect is
* manually triggered, but WILL if an error occurs, or a network stack bug, etc.
*
* @param onConnected Callback to invoke when the network is connected.
* @param onFailed Callback to invoke if the network connection fails.
* @param onDisconnect Called after a successful connection, when disconnected.
* @param user User data to pass to the callbacks.
*/
void networkRequestConnection(
void (*onConnected)(void *user),
void (*onFailed)(errorret_t error, void *user),
void (*onDisconnect)(errorret_t error, void *user),
void *user
);
/**
* Requests the system to disconnect from the network. This is basically just
* for PSP, but I guess it could be used on other platforms in future if they
* have some kind of "network connection mode" that needs to be exited.
*
* You should only call this once and assume that it is "pending" until
* onComplete is invoked. If you call this twice it is undefined behavior.
*
* If it fails, you still get onComplete called, but may fail if you try to
* reconnect later unfortunately.
*
* @param onComplete Callback to invoke when the network is disconnected.
* @param user User data to pass to the callback.
*/
void networkRequestDisconnection(
void (*onComplete)(void *user),
void *user
);
-24
View File
@@ -1,24 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "network.h"
#include "networkinfo.h"
#include "network/networkplatform.h"
#include "assert/assert.h"
#ifndef networkPlatformGetInfo
#error "networkPlatformGetInfo must be defined"
#endif
networkinfo_t networkGetInfo() {
assertTrue(
NETWORK.state == NETWORK_STATE_CONNECTED,
"networkGetInfo called when not connected"
);
return networkPlatformGetInfo();
}
-51
View File
@@ -1,51 +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"
#define NETWORK_INFO_IPV4_DNS_COUNT_MAX 2
#define NETWORK_INFO_IPV4_OCTET_COUNT 4
#define NETWORK_INFO_IPV6_OCTET_COUNT 16
#define NETWORK_INFO_IPV6_DNS_COUNT_MAX 2
#define NETWORK_INFO_FORMAT_IPV4 "%u.%u.%u.%u"
#define NETWORK_INFO_FORMAT_IPV6 \
"%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x"
typedef enum {
NETWORK_TYPE_IPV4,
#ifdef DUSK_NETWORK_IPV6
NETWORK_TYPE_IPV6,
#endif
} networktype_t;
typedef struct {
uint8_t ip[NETWORK_INFO_IPV4_OCTET_COUNT];
} networkinfoipv4_t;
#ifdef DUSK_NETWORK_IPV6
typedef struct {
uint8_t ip[NETWORK_INFO_IPV6_OCTET_COUNT];
} networkinfoipv6_t;
#endif
typedef struct {
networktype_t type;
union {
networkinfoipv4_t ipv4;
#ifdef DUSK_NETWORK_IPV6
networkinfoipv6_t ipv6;
#endif
};
} networkinfo_t;
/**
* Returns the network information for the currently active network connection.
*
* @return Network information for the currently active network connection.
*/
networkinfo_t networkGetInfo();
+1 -1
View File
@@ -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];
+60 -164
View File
@@ -1,5 +1,5 @@
// Copyright (c) 2026 Dominic Masters
//
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
@@ -11,19 +11,13 @@
#include "display/screen/screen.h"
#include "entity/entitymanager.h"
#include "display/shader/shaderunlit.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"
#include "display/mesh/cube.h"
scene_t SCENE;
errorret_t sceneInit(void) {
memoryZero(&SCENE, sizeof(scene_t));
SCENE.scriptRef = SCENE_SCRIPT_REF_NONE;
errorOk();
}
@@ -33,36 +27,31 @@ errorret_t sceneUpdate(void) {
errorOk();
}
#endif
if(stringCompare(SCENE.sceneNext, SCENE.sceneCurrent) != 0) {
errorChain(sceneSetImmediate(SCENE.sceneNext));
}
if(SCENE.sceneActive) {
errorChain(moduleSceneCall("update"));
}
errorOk();
}
dusktimeepoch_t LAST;
errorret_t sceneRender(void) {
// Get Cameras
entityid_t camEnts[ENTITY_COUNT_MAX];
componentid_t camComps[ENTITY_COUNT_MAX];
entityid_t camCount = componentGetEntitiesWithComponent(
COMPONENT_TYPE_CAMERA, camEnts, camComps
);
if(camCount == 0) errorOk();
shader_t *shaderCurrent = NULL;
// Get meshes
entityid_t meshEnts[ENTITY_COUNT_MAX];
componentid_t meshComps[ENTITY_COUNT_MAX];
entityid_t meshCount = componentGetEntitiesWithComponent(
COMPONENT_TYPE_MESH, meshEnts, meshComps
);
if(meshCount == 0) errorOk();
// Prep Matricies
mat4 view, proj, model;
errorChain(displaySetState((displaystate_t){
// .flags = DISPLAY_STATE_FLAG_CULL | DISPLAY_STATE_FLAG_DEPTH_TEST
.flags = 0
}));
errorChain(shaderBind(&SHADER_UNLIT));
// For each camera
for(entityid_t camIndex = 0; camIndex < camCount; camIndex++) {
entityid_t camEnt = camEnts[camIndex];
componentid_t camComp = camComps[camIndex];
@@ -75,152 +64,59 @@ errorret_t sceneRender(void) {
entityCameraGetProjection(camEnt, camComp, proj);
entityPositionGetTransform(camEnt, camPos, view);
// 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(
entityId, COMPONENT_TYPE_MATERIAL
);
if(matComp != 0xFF) {
// Yes, get the mesh
componentid_t meshComp = entityGetComponent(
entityId, COMPONENT_TYPE_MESH
);
if(meshComp == 0xFF) {
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;
}
// For each mesh.
for(entityid_t meshIndex = 0; meshIndex < meshCount; meshIndex++) {
entityid_t meshEnt = meshEnts[meshIndex];
// Yes, get the material and shader.
shadermaterial_t *material = entityMaterialGetShaderMaterial(
entityId, matComp
);
shader_t *shader = entityMaterialGetShader(entityId, matComp);
if(shader == NULL) {
logError("Entity with material but no shader found\n");
continue;
}
// Get the transform.
componentid_t meshPos = entityGetComponent(
entityId, COMPONENT_TYPE_POSITION
);
if(meshPos == 0xFF) {
glm_mat4_identity(model);
} else {
entityPositionGetTransform(entityId, meshPos, model);
}
// Render the mesh.
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));
componentid_t meshComp = meshComps[meshIndex];
mesh_t *mesh = entityMeshGetMesh(meshEnt, meshComp);
if(mesh == NULL) {
continue;
}
// No, in future there may be other renderable types.
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));
}
}
// 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();
}
errorret_t sceneSetImmediate(const char_t *scene) {
if(scene != SCENE.sceneNext) {
stringCopy(
SCENE.sceneNext,
scene == NULL ? "" : scene,
ASSET_FILE_PATH_MAX
);
}
if(SCENE.sceneActive) {
errorChain(moduleSceneCall("dispose"));
SCENE.sceneActive = false;
}
moduleCutsceneReset();
moduleSceneReset();
stringCopy(
SCENE.sceneCurrent,
scene == NULL ? "" : scene,
ASSET_FILE_PATH_MAX
);
if(scene != NULL) {
jerry_value_t sceneClass = SCENE_SCRIPT_REF_NONE;
errorChain(scriptManagerExecFile(scene, &sceneClass));
if(!jerry_value_is_function(sceneClass)) {
if(sceneClass != SCENE_SCRIPT_REF_NONE) jerry_value_free(sceneClass);
errorThrow("Scene '%s' must export a constructor function", scene);
}
jerry_value_t sceneObj = jerry_construct(sceneClass, NULL, 0);
jerry_value_free(sceneClass);
if(jerry_value_is_exception(sceneObj)) {
char_t errMsg[512];
moduleBaseExceptionMessage(sceneObj, errMsg, sizeof(errMsg));
jerry_value_free(sceneObj);
errorThrow("Scene '%s' constructor threw: %s", scene, errMsg);
}
SCENE.scriptRef = sceneObj;
SCENE.sceneActive = true;
}
errorOk();
}
void sceneSet(const char_t *scene) {
stringCopy(
SCENE.sceneNext,
scene == NULL ? "" : scene,
ASSET_FILE_PATH_MAX
);
}
errorret_t sceneDispose(void) {
errorChain(moduleSceneCall("dispose"));
errorret_t sceneSet(const char_t *script) {
errorOk();
}
void sceneDispose(void) {
}
+18 -37
View File
@@ -1,67 +1,48 @@
/**
* 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 "asset/assetfile.h"
#define SCENE_EVENT_UPDATE_MAX 16
#include "error/error.h"
typedef struct {
bool_t sceneActive;
jerry_value_t scriptRef;
char_t sceneCurrent[ASSET_FILE_PATH_MAX];
char_t sceneNext[ASSET_FILE_PATH_MAX];
void *nothing;
} scene_t;
extern scene_t SCENE;
/** Sentinel value meaning no scene script is loaded. */
#define SCENE_SCRIPT_REF_NONE ((jerry_value_t)0)
/**
* Initializes the scene manager.
*
* @return Any error state that happened.
* Initialize the scene subsystem.
*
* @return The error return value.
*/
errorret_t sceneInit(void);
/**
* Ticks the scene manager; may call the scene's update method.
*
* @return Any error state that happened.
* Update the current scene.
*
* @return The error return value.
*/
errorret_t sceneUpdate(void);
/**
* Renders the scene.
*
* @return Any error state that happened.
* Render the current scene.
*
* @return The error return value.
*/
errorret_t sceneRender(void);
/**
* Immediately switches scenes, disposing the current one first.
*
* @param scene Scene to switch to (asset file path).
* @return Any error state that happened.
* Set the current scene by script name.
*
* @param script The script name of the scene to set.
*/
errorret_t sceneSetImmediate(const char_t *scene);
errorret_t sceneSet(const char_t *script);
/**
* Requests a scene change on the next safe opportunity.
*
* @param scene Which scene to set.
* Dispose of the scene subsystem.
*/
void sceneSet(const char_t *scene);
/**
* Disposes of the current scene.
*
* @return Any error state that happened.
*/
errorret_t sceneDispose(void);
void sceneDispose(void);
+4 -2
View File
@@ -7,7 +7,9 @@
target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
scriptmanager.c
scriptproto.c
scriptcontext.c
scriptmodule.c
)
# Subdirectories
# Subdirectories
add_subdirectory(module)
+15
View File
@@ -0,0 +1,15 @@
# Copyright (c) 2026 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
# Subdirectories
add_subdirectory(display)
add_subdirectory(event)
add_subdirectory(input)
add_subdirectory(locale)
add_subdirectory(system)
add_subdirectory(scene)
add_subdirectory(time)
add_subdirectory(ui)
add_subdirectory(entity)
@@ -1,293 +0,0 @@
// 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
);
}
@@ -1,70 +0,0 @@
// 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
}
@@ -1,67 +0,0 @@
/**
* 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 "console/console.h"
static scriptproto_t MODULE_CONSOLE_PROTO;
moduleBaseFunction(moduleConsolePrint) {
char_t buf[512];
char_t msg[4096];
size_t msgLen = 0;
for(jerry_length_t i = 0; i < argc; ++i) {
jerry_value_t strVal = jerry_value_to_string(args[i]);
moduleBaseToString(strVal, buf, sizeof(buf));
jerry_value_free(strVal);
size_t partLen = strlen(buf);
if(msgLen + partLen + 1 < sizeof(msg)) {
stringCopy(msg + msgLen, buf, sizeof(msg) - msgLen);
msgLen += partLen;
}
if(i + 1 < argc && msgLen + 1 < sizeof(msg)) {
msg[msgLen++] = '\t';
msg[msgLen] = '\0';
}
}
consolePrint("%s", msg);
return jerry_undefined();
}
moduleBaseFunction(moduleConsoleGetVisible) {
return jerry_boolean(CONSOLE.visible);
}
moduleBaseFunction(moduleConsoleSetVisible) {
if(argc < 1) return moduleBaseThrow("Expected at least 1 argument");
if(!jerry_value_is_boolean(args[0])) {
return moduleBaseThrow("Console.visible: expected boolean");
}
CONSOLE.visible = jerry_value_is_true(args[0]);
return jerry_undefined();
}
static void moduleConsole(void) {
scriptProtoInit(
&MODULE_CONSOLE_PROTO, "Console",
sizeof(uint8_t), NULL
);
scriptProtoDefineStaticFunc(
&MODULE_CONSOLE_PROTO, "print", moduleConsolePrint
);
scriptProtoDefineStaticProp(
&MODULE_CONSOLE_PROTO, "visible",
moduleConsoleGetVisible, moduleConsoleSetVisible
);
}
@@ -1,204 +0,0 @@
// 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
);
}
@@ -0,0 +1,18 @@
# 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
moduleglm.c
modulespritebatch.c
moduleglm.c
modulecolor.c
moduletext.c
modulescreen.c
moduletileset.c
moduletexture.c
moduleshader.c
)
@@ -0,0 +1,186 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "modulecolor.h"
#include "display/color.h"
#include "assert/assert.h"
#include "util/string.h"
#include "time/time.h"
void moduleColor(scriptcontext_t *context) {
assertNotNull(context, "Context cannot be NULL.");
if(luaL_newmetatable(context->luaState, "color_mt")) {
// Metatable is new, set __index and __newindex
lua_pushstring(context->luaState, "__index");
lua_pushcfunction(context->luaState, moduleColorIndex);
lua_settable(context->luaState, -3);
lua_pushstring(context->luaState, "__newindex");
lua_pushcfunction(context->luaState, moduleColorNewIndex);
lua_settable(context->luaState, -3);
lua_pushstring(context->luaState, "__tostring");
lua_pushcfunction(context->luaState, moduleColorToString);
lua_settable(context->luaState, -3);
}
lua_pop(context->luaState, 1);
lua_register(context->luaState, "color", moduleColorFuncColor);
lua_register(context->luaState, "colorRainbow", moduleColorRainbow);
scriptContextExec(context, COLOR_SCRIPT);
}
int moduleColorFuncColor(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL.");
scriptcontext_t *context = *(scriptcontext_t **)lua_getextraspace(L);
assertNotNull(context, "Script context cannot be NULL.");
// Needs 4 channel uint8_t
if(
!lua_isnumber(L, 1) || !lua_isnumber(L, 2) ||
!lua_isnumber(L, 3) || !lua_isnumber(L, 4)
) {
return luaL_error(L, "color(r, g, b, a) requires four number arguments.");
}
colorchannel8_t r = (colorchannel8_t)lua_tonumber(L, 1);
colorchannel8_t g = (colorchannel8_t)lua_tonumber(L, 2);
colorchannel8_t b = (colorchannel8_t)lua_tonumber(L, 3);
colorchannel8_t a = (colorchannel8_t)lua_tonumber(L, 4);
// Create color_t as lua memory, and push metatable
color_t *color = (color_t *)lua_newuserdata(L, sizeof(color_t));
luaL_getmetatable(L, "color_mt");
lua_setmetatable(L, -2);
// Initial values.
color->r = r;
color->g = g;
color->b = b;
color->a = a;
return 1;
}
int moduleColorIndex(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL.");
const char_t *key = lua_tostring(L, 2);
assertStrLenMin(key, 1, "Key cannot be empty.");
const color_t *color = (const color_t*)luaL_checkudata(L, 1, "color_mt");
assertNotNull(color, "Color struct cannot be NULL.");
if(stringCompare(key, "r") == 0) {
lua_pushnumber(L, color->r);
return 1;
}
if(stringCompare(key, "g") == 0) {
lua_pushnumber(L, color->g);
return 1;
}
if(stringCompare(key, "b") == 0) {
lua_pushnumber(L, color->b);
return 1;
}
if(stringCompare(key, "a") == 0) {
lua_pushnumber(L, color->a);
return 1;
}
lua_pushnil(L);
return 1;
}
int moduleColorNewIndex(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL.");
const char_t *key = lua_tostring(L, 2);
assertStrLenMin(key, 1, "Key cannot be empty.");
color_t *color = (color_t*)luaL_checkudata(L, 1, "color_mt");
assertNotNull(color, "Color struct cannot be NULL.");
if(stringCompare(key, "r") == 0) {
if(!lua_isnumber(L, 3)) {
return luaL_error(L, "color channel r must be a number.");
}
color->r = (colorchannel8_t)lua_tonumber(L, 3);
return 0;
} else if(stringCompare(key, "g") == 0) {
if(!lua_isnumber(L, 3)) {
return luaL_error(L, "color channel g must be a number.");
}
color->g = (colorchannel8_t)lua_tonumber(L, 3);
return 0;
} else if(stringCompare(key, "b") == 0) {
if(!lua_isnumber(L, 3)) {
return luaL_error(L, "color channel b must be a number.");
}
color->b = (colorchannel8_t)lua_tonumber(L, 3);
return 0;
} else if(stringCompare(key, "a") == 0) {
if(!lua_isnumber(L, 3)) {
return luaL_error(L, "color channel a must be a number.");
}
color->a = (colorchannel8_t)lua_tonumber(L, 3);
return 0;
}
return 0;
}
int moduleColorToString(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL.");
const color_t *color = (const color_t*)luaL_checkudata(L, 1, "color_mt");
assertNotNull(color, "Color struct cannot be NULL.");
lua_pushfstring(
L, "color(r=%d, g=%d, b=%d, a=%d)",
color->r, color->g, color->b, color->a
);
return 1;
}
int moduleColorRainbow(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL.");
// Allow time offset
float_t t = TIME.time;
if(lua_gettop(L) >= 1) {
if(!lua_isnumber(L, 1)) {
return luaL_error(L, "Rainbow time offset must be a number.");
}
t += (float_t)lua_tonumber(L, 1);
}
// Allow speed multiplier
if(lua_gettop(L) >= 2) {
if(!lua_isnumber(L, 2)) {
return luaL_error(L, "Rainbow speed multiplier must be a number.");
}
t *= (float_t)lua_tonumber(L, 2);
}
// Generate rainbow based on time.
color_t *color = (color_t *)lua_newuserdata(L, sizeof(color_t));
color->r = (colorchannel8_t)((sinf(t) + 1.0f) * 0.5f * 255.0f);
color->g = (colorchannel8_t)((sinf(t + 2.0f) + 1.0f) * 0.5f * 255.0f);
color->b = (colorchannel8_t)((sinf(t + 4.0f) + 1.0f) * 0.5f * 255.0f);
color->a = 255;
// Set metatable
luaL_getmetatable(L, "color_mt");
lua_setmetatable(L, -2);
return 1;
}

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