Compare commits
33 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a73f55beb0 | |||
| 0bd2491ab7 | |||
| 860c797c9c | |||
| 38c5080f9f | |||
| fae191d8fe | |||
| d83a953e2d | |||
| 2dcf0d0f0d | |||
| 6dee37d8c1 | |||
| 3bad03afb3 | |||
| 6a6d8448f7 | |||
| 189babd2cf | |||
| 988a0f2294 | |||
| 85bf455731 | |||
| 589e4224f3 | |||
| bd7fa154b4 | |||
| a292f1992b | |||
| 88ddf429b7 | |||
| 7a1f6662df | |||
| afc68bccc6 | |||
| b4cdc4a64f | |||
| 3ad5afb81c | |||
| bed3f20118 | |||
| de67315178 | |||
| 8d5c0c7cad | |||
| a8271e01bd | |||
| 900b3f8558 | |||
| fdc4e056f9 | |||
| 7b98e40ccf | |||
| 01d89cf22c | |||
| 503a3c799a | |||
| 0b21388844 | |||
| 117bdf0c00 | |||
| 172dc5d37b |
@@ -4,36 +4,6 @@ on:
|
||||
tags:
|
||||
- '*'
|
||||
jobs:
|
||||
run-tests:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y \
|
||||
build-essential \
|
||||
cmake \
|
||||
python3 \
|
||||
python3-pip \
|
||||
python3-polib \
|
||||
python3-pil \
|
||||
libsdl2-dev \
|
||||
libgl1-mesa-dev \
|
||||
libzip-dev \
|
||||
python3-dotenv \
|
||||
python3-pyqt5 \
|
||||
python3-opengl \
|
||||
xz-utils \
|
||||
liblzma-dev \
|
||||
libbz2-dev \
|
||||
zlib1g-dev \
|
||||
git \
|
||||
libssl-dev
|
||||
- name: Run tests
|
||||
run: ./scripts/test-linux.sh
|
||||
|
||||
build-linux:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
name: Test Dusk
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
jobs:
|
||||
run-tests:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y \
|
||||
build-essential \
|
||||
cmake \
|
||||
python3 \
|
||||
python3-pip \
|
||||
python3-polib \
|
||||
python3-pil \
|
||||
libsdl2-dev \
|
||||
libgl1-mesa-dev \
|
||||
libzip-dev \
|
||||
python3-dotenv \
|
||||
python3-pyqt5 \
|
||||
python3-opengl \
|
||||
xz-utils \
|
||||
liblzma-dev \
|
||||
libbz2-dev \
|
||||
zlib1g-dev \
|
||||
git \
|
||||
libssl-dev
|
||||
- name: Run tests
|
||||
run: ./scripts/test-linux.sh
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,21 +0,0 @@
|
||||
// Copyright (c) 2026 Dominic Masters
|
||||
//
|
||||
// This software is released under the MIT License.
|
||||
// https://opensource.org/licenses/MIT
|
||||
|
||||
const platformNames = {
|
||||
[System.PLATFORM_LINUX]: 'Linux',
|
||||
[System.PLATFORM_KNULLI]: 'Knulli',
|
||||
[System.PLATFORM_PSP]: 'PSP',
|
||||
[System.PLATFORM_GAMECUBE]: 'GameCube',
|
||||
[System.PLATFORM_WII]: 'Wii',
|
||||
};
|
||||
|
||||
Console.print('Platform: ' + (platformNames[System.platform] || 'Unknown'));
|
||||
|
||||
UIFullboxOver.setColor(Color.BLACK);
|
||||
|
||||
requireAsync('testscene.js').then(Scene.set).catch(err => {
|
||||
Console.print('Error loading scene: ' + err);
|
||||
Engine.exit();
|
||||
});
|
||||
+28
-43
@@ -10,51 +10,36 @@ msgid "ui.title"
|
||||
msgstr ""
|
||||
"Welcome"
|
||||
|
||||
#: ui/user.c:22
|
||||
msgid "ui.greeting"
|
||||
msgstr "Hello, %s!"
|
||||
#: src/dusk/ui/frame/settings/uisettings.c
|
||||
msgid "ui.settings.tabs.general"
|
||||
msgstr "General"
|
||||
|
||||
#: ui/files.c:40
|
||||
msgid "ui.file_status"
|
||||
msgstr "%s has %d files."
|
||||
#: src/dusk/ui/frame/settings/uisettings.c
|
||||
msgid "ui.settings.tabs.input"
|
||||
msgstr "Input"
|
||||
|
||||
#: ui/cart.c:55
|
||||
msgid "cart.item_count"
|
||||
msgid_plural "cart.item_count"
|
||||
msgstr[0] "%d item"
|
||||
msgstr[1] "%d items (dual)"
|
||||
msgstr[2] "%d items (few)"
|
||||
msgstr[3] "%d items (many)"
|
||||
#: src/dusk/ui/frame/settings/uisettings.c
|
||||
msgid "ui.settings.tabs.display"
|
||||
msgstr "Display"
|
||||
|
||||
#: ui/notifications.c:71
|
||||
msgid ""
|
||||
"ui.multiline_help"
|
||||
msgstr ""
|
||||
"Line one of the help text.\n"
|
||||
"Line two continues here.\n"
|
||||
"Line three ends here."
|
||||
#: src/dusk/ui/frame/settings/uisettings.c
|
||||
msgid "ui.settings.tabs.audio"
|
||||
msgstr "Audio"
|
||||
|
||||
#: ui/errors.c:90
|
||||
msgid ""
|
||||
"error.upload_failed.long"
|
||||
msgstr ""
|
||||
"Upload failed for file \"%s\".\n"
|
||||
"Please try again later or contact support."
|
||||
msgid "ui.settings.input.deadzone"
|
||||
msgstr "Deadzone"
|
||||
|
||||
#: ui/messages.c:110
|
||||
msgid ""
|
||||
"user.invite_status"
|
||||
msgid_plural ""
|
||||
"user.invite_status"
|
||||
msgstr[0] ""
|
||||
"%s invited %d user.\n"
|
||||
"Please review the request."
|
||||
msgstr[1] ""
|
||||
"%s invited %d users (dual).\n"
|
||||
"Please review the requests."
|
||||
msgstr[2] ""
|
||||
"%s invited %d users (few).\n"
|
||||
"Please review the requests."
|
||||
msgstr[3] ""
|
||||
"%s invited %d users (many).\n"
|
||||
"Please review the requests."
|
||||
#: src/dusk/ui/frame/settings/uisettingsgeneral.c
|
||||
msgid "ui.settings.general.language"
|
||||
msgstr "Language"
|
||||
|
||||
msgid "ui.settings.general.language_detail"
|
||||
msgstr "Takes effect after restarting the application."
|
||||
|
||||
#: src/dusk/ui/frame/settings/uisettings.c
|
||||
msgid "ui.settings.apply"
|
||||
msgstr "Apply"
|
||||
|
||||
#: src/dusk/ui/frame/uiconfirm.c
|
||||
msgid "ui.confirm.discard_changes"
|
||||
msgstr "Discard unsaved changes?"
|
||||
@@ -0,0 +1,46 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: ExampleApp 1.0\n"
|
||||
"Language: es\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n==1 ? 0 : 1);\n"
|
||||
|
||||
#: ui/menu.c:10
|
||||
msgid "ui.title"
|
||||
msgstr ""
|
||||
"Bienvenido"
|
||||
|
||||
#: src/dusk/ui/frame/settings/uisettings.c
|
||||
msgid "ui.settings.tabs.general"
|
||||
msgstr "General"
|
||||
|
||||
#: src/dusk/ui/frame/settings/uisettings.c
|
||||
msgid "ui.settings.tabs.input"
|
||||
msgstr "Entrada"
|
||||
|
||||
#: src/dusk/ui/frame/settings/uisettings.c
|
||||
msgid "ui.settings.tabs.display"
|
||||
msgstr "Pantalla"
|
||||
|
||||
#: src/dusk/ui/frame/settings/uisettings.c
|
||||
msgid "ui.settings.tabs.audio"
|
||||
msgstr "Audio"
|
||||
|
||||
msgid "ui.settings.input.deadzone"
|
||||
msgstr "Deadzone"
|
||||
|
||||
#: src/dusk/ui/frame/settings/uisettingsgeneral.c
|
||||
msgid "ui.settings.general.language"
|
||||
msgstr "Idioma"
|
||||
|
||||
#: src/dusk/ui/frame/settings/uisettingsgeneral.c
|
||||
msgid "ui.settings.general.language_detail"
|
||||
msgstr "Se aplica después de reiniciar la aplicación."
|
||||
|
||||
#: src/dusk/ui/frame/settings/uisettings.c
|
||||
msgid "ui.settings.apply"
|
||||
msgstr "Aplicar"
|
||||
|
||||
#: src/dusk/ui/frame/uiconfirm.c
|
||||
msgid "ui.confirm.discard_changes"
|
||||
msgstr "¿Descartar los cambios no guardados?"
|
||||
@@ -0,0 +1,46 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: ExampleApp 1.0\n"
|
||||
"Language: ja\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Plural-Forms: nplurals=1; plural=(0);\n"
|
||||
|
||||
#: ui/menu.c:10
|
||||
msgid "ui.title"
|
||||
msgstr ""
|
||||
"歓迎"
|
||||
|
||||
#: src/dusk/ui/frame/settings/uisettings.c
|
||||
msgid "ui.settings.tabs.general"
|
||||
msgstr "一般"
|
||||
|
||||
#: src/dusk/ui/frame/settings/uisettings.c
|
||||
msgid "ui.settings.tabs.input"
|
||||
msgstr "入力"
|
||||
|
||||
#: src/dusk/ui/frame/settings/uisettings.c
|
||||
msgid "ui.settings.tabs.display"
|
||||
msgstr "表示"
|
||||
|
||||
#: src/dusk/ui/frame/settings/uisettings.c
|
||||
msgid "ui.settings.tabs.audio"
|
||||
msgstr "オーディオ"
|
||||
|
||||
msgid "ui.settings.input.deadzone"
|
||||
msgstr "デッドゾーン"
|
||||
|
||||
#: src/dusk/ui/frame/settings/uisettingsgeneral.c
|
||||
msgid "ui.settings.general.language"
|
||||
msgstr "言語"
|
||||
|
||||
#: src/dusk/ui/frame/settings/uisettingsgeneral.c
|
||||
msgid "ui.settings.general.language_detail"
|
||||
msgstr "アプリケーションを再起動すると適用されます。"
|
||||
|
||||
#: src/dusk/ui/frame/settings/uisettings.c
|
||||
msgid "ui.settings.apply"
|
||||
msgstr "適用"
|
||||
|
||||
#: src/dusk/ui/frame/uiconfirm.c
|
||||
msgid "ui.confirm.discard_changes"
|
||||
msgstr "未保存の変更を破棄しますか?"
|
||||
Binary file not shown.
Binary file not shown.
@@ -1,63 +0,0 @@
|
||||
// Copyright (c) 2026 Dominic Masters
|
||||
//
|
||||
// This software is released under the MIT License.
|
||||
// https://opensource.org/licenses/MIT
|
||||
|
||||
const PLAYER_SPEED = 5.0;
|
||||
// 1 world unit = 16 pixels.
|
||||
const PIXEL_SCALE = 1.0 / 16.0;
|
||||
// Player sprite is 32x32 px (test.png dimensions).
|
||||
const PLAYER_W = 32 * PIXEL_SCALE;
|
||||
const PLAYER_H = 32 * PIXEL_SCALE;
|
||||
|
||||
var player = {};
|
||||
|
||||
player.getAssets = () => {
|
||||
return [
|
||||
{ path: 'test.png', type: Asset.TYPE_TEXTURE, format: Texture.FORMAT_RGBA }
|
||||
];
|
||||
}
|
||||
|
||||
player.init = function(scene) {
|
||||
var texture = scene.assets.getAssetByPath('test.png');
|
||||
Console.print('Player init: got texture ' + texture);
|
||||
|
||||
_entity = Entity.create();
|
||||
_position = _entity.add(Component.POSITION);
|
||||
_physics = _entity.add(Component.PHYSICS);
|
||||
|
||||
_physics.bodyType = Physics.DYNAMIC;
|
||||
_physics.shape = Physics.SHAPE_CUBE;
|
||||
_physics.gravityScale = 1.0;
|
||||
|
||||
var r = _entity.add(Component.RENDERABLE);
|
||||
r.texture = texture.texture;
|
||||
r.type = Renderable.SPRITEBATCH;
|
||||
r.color = new Color(220, 80, 80);
|
||||
// Upright quad centered on X, bottom-aligned on Y.
|
||||
r.sprites = [[-PLAYER_W/2, 0, 0, PLAYER_W/2, PLAYER_H, 0, 0, 1, 1, 0]];
|
||||
|
||||
_position.localPosition = new Vec3(0, PLAYER_H, 0);
|
||||
};
|
||||
|
||||
player.getPosition = function() {
|
||||
return _position;
|
||||
};
|
||||
|
||||
player.update = function() {
|
||||
if(!_physics) return;
|
||||
var vx = Input.axis(INPUT_ACTION_LEFT, INPUT_ACTION_RIGHT) * PLAYER_SPEED;
|
||||
var vz = Input.axis(INPUT_ACTION_DOWN, INPUT_ACTION_UP) * PLAYER_SPEED;
|
||||
// Preserve vertical velocity so gravity and landing work correctly.
|
||||
var vy = _physics.velocity.y;
|
||||
_physics.velocity = new Vec3(vx, vy, vz);
|
||||
};
|
||||
|
||||
player.dispose = function() {
|
||||
Entity.dispose(_entity);
|
||||
_entity = null;
|
||||
_position = null;
|
||||
_physics = null;
|
||||
};
|
||||
|
||||
module.exports = player;
|
||||
@@ -1,42 +0,0 @@
|
||||
// Copyright (c) 2026 Dominic Masters
|
||||
//
|
||||
// This software is released under the MIT License.
|
||||
// https://opensource.org/licenses/MIT
|
||||
|
||||
var scene = {};
|
||||
|
||||
// Pokemon DS-style camera: ~34 degrees elevation (atan(6/9)).
|
||||
// CAM_HEIGHT / CAM_DIST ratio controls the tilt - keep it under 0.7 for
|
||||
// the characteristically shallow DS angle.
|
||||
const CAM_HEIGHT = 6;
|
||||
const CAM_DIST = 9;
|
||||
|
||||
scene.init = async function() {
|
||||
// Camera
|
||||
scene.cam = Entity.create();
|
||||
var camPos = scene.cam.add(Component.POSITION);
|
||||
var cam = scene.cam.add(Component.CAMERA);
|
||||
camPos.localPosition = new Vec3(3, 3, 3);
|
||||
camPos.lookAt(new Vec3(0, 0, 0));
|
||||
|
||||
// Floor - large flat slab, no texture needed.
|
||||
scene.floor = Entity.create();
|
||||
var floorPos = scene.floor.add(Component.POSITION);
|
||||
var floorR = scene.floor.add(Component.RENDERABLE);
|
||||
floorR.type = Renderable.SHADER_MATERIAL;
|
||||
floorR.color = Color.BLUE;
|
||||
// floorPos.localScale = new Vec3(16, 0.2, 16);
|
||||
// floorPos.localPosition = new Vec3(0, -0.1, 0);
|
||||
|
||||
await UIFullboxOver.transition(Color.BLACK, Color.TRANSPARENT, 1.0);
|
||||
};
|
||||
|
||||
scene.update = function() {
|
||||
};
|
||||
|
||||
scene.dispose = function() {
|
||||
Entity.dispose(scene.floor);
|
||||
Entity.dispose(scene.cam);
|
||||
};
|
||||
|
||||
module.exports = scene;
|
||||
@@ -1,6 +0,0 @@
|
||||
module = {
|
||||
render() {
|
||||
Text.draw(0, 0, "Hello World");
|
||||
SpriteBatch.flush();
|
||||
}
|
||||
};
|
||||
@@ -1464,7 +1464,7 @@
|
||||
10,
|
||||
0
|
||||
],
|
||||
"type": 1,
|
||||
"type": 4,
|
||||
"tile": 0
|
||||
},
|
||||
{
|
||||
@@ -1473,7 +1473,7 @@
|
||||
10,
|
||||
0
|
||||
],
|
||||
"type": 1,
|
||||
"type": 4,
|
||||
"tile": 0
|
||||
},
|
||||
{
|
||||
@@ -1482,7 +1482,7 @@
|
||||
10,
|
||||
0
|
||||
],
|
||||
"type": 1,
|
||||
"type": 4,
|
||||
"tile": 0
|
||||
},
|
||||
{
|
||||
@@ -1491,7 +1491,7 @@
|
||||
10,
|
||||
0
|
||||
],
|
||||
"type": 1,
|
||||
"type": 4,
|
||||
"tile": 0
|
||||
},
|
||||
{
|
||||
@@ -1500,7 +1500,7 @@
|
||||
10,
|
||||
0
|
||||
],
|
||||
"type": 1,
|
||||
"type": 4,
|
||||
"tile": 0
|
||||
},
|
||||
{
|
||||
@@ -1509,7 +1509,7 @@
|
||||
10,
|
||||
0
|
||||
],
|
||||
"type": 1,
|
||||
"type": 4,
|
||||
"tile": 0
|
||||
},
|
||||
{
|
||||
@@ -2267,6 +2267,276 @@
|
||||
],
|
||||
"type": 1,
|
||||
"tile": 0
|
||||
},
|
||||
{
|
||||
"pos": [
|
||||
6,
|
||||
9,
|
||||
1
|
||||
],
|
||||
"type": 4,
|
||||
"tile": 0
|
||||
},
|
||||
{
|
||||
"pos": [
|
||||
7,
|
||||
9,
|
||||
1
|
||||
],
|
||||
"type": 4,
|
||||
"tile": 0
|
||||
},
|
||||
{
|
||||
"pos": [
|
||||
8,
|
||||
9,
|
||||
1
|
||||
],
|
||||
"type": 4,
|
||||
"tile": 0
|
||||
},
|
||||
{
|
||||
"pos": [
|
||||
9,
|
||||
9,
|
||||
1
|
||||
],
|
||||
"type": 4,
|
||||
"tile": 0
|
||||
},
|
||||
{
|
||||
"pos": [
|
||||
10,
|
||||
9,
|
||||
1
|
||||
],
|
||||
"type": 4,
|
||||
"tile": 0
|
||||
},
|
||||
{
|
||||
"pos": [
|
||||
11,
|
||||
9,
|
||||
1
|
||||
],
|
||||
"type": 4,
|
||||
"tile": 0
|
||||
},
|
||||
{
|
||||
"pos": [
|
||||
6,
|
||||
8,
|
||||
2
|
||||
],
|
||||
"type": 4,
|
||||
"tile": 0
|
||||
},
|
||||
{
|
||||
"pos": [
|
||||
7,
|
||||
8,
|
||||
2
|
||||
],
|
||||
"type": 4,
|
||||
"tile": 0
|
||||
},
|
||||
{
|
||||
"pos": [
|
||||
8,
|
||||
8,
|
||||
2
|
||||
],
|
||||
"type": 4,
|
||||
"tile": 0
|
||||
},
|
||||
{
|
||||
"pos": [
|
||||
9,
|
||||
8,
|
||||
2
|
||||
],
|
||||
"type": 4,
|
||||
"tile": 0
|
||||
},
|
||||
{
|
||||
"pos": [
|
||||
10,
|
||||
8,
|
||||
2
|
||||
],
|
||||
"type": 4,
|
||||
"tile": 0
|
||||
},
|
||||
{
|
||||
"pos": [
|
||||
11,
|
||||
8,
|
||||
2
|
||||
],
|
||||
"type": 4,
|
||||
"tile": 0
|
||||
},
|
||||
{
|
||||
"pos": [
|
||||
6,
|
||||
5,
|
||||
3
|
||||
],
|
||||
"type": 1,
|
||||
"tile": 0
|
||||
},
|
||||
{
|
||||
"pos": [
|
||||
7,
|
||||
5,
|
||||
3
|
||||
],
|
||||
"type": 1,
|
||||
"tile": 0
|
||||
},
|
||||
{
|
||||
"pos": [
|
||||
8,
|
||||
5,
|
||||
3
|
||||
],
|
||||
"type": 1,
|
||||
"tile": 0
|
||||
},
|
||||
{
|
||||
"pos": [
|
||||
9,
|
||||
5,
|
||||
3
|
||||
],
|
||||
"type": 1,
|
||||
"tile": 0
|
||||
},
|
||||
{
|
||||
"pos": [
|
||||
10,
|
||||
5,
|
||||
3
|
||||
],
|
||||
"type": 1,
|
||||
"tile": 0
|
||||
},
|
||||
{
|
||||
"pos": [
|
||||
11,
|
||||
5,
|
||||
3
|
||||
],
|
||||
"type": 1,
|
||||
"tile": 0
|
||||
},
|
||||
{
|
||||
"pos": [
|
||||
6,
|
||||
6,
|
||||
3
|
||||
],
|
||||
"type": 1,
|
||||
"tile": 0
|
||||
},
|
||||
{
|
||||
"pos": [
|
||||
7,
|
||||
6,
|
||||
3
|
||||
],
|
||||
"type": 1,
|
||||
"tile": 0
|
||||
},
|
||||
{
|
||||
"pos": [
|
||||
8,
|
||||
6,
|
||||
3
|
||||
],
|
||||
"type": 1,
|
||||
"tile": 0
|
||||
},
|
||||
{
|
||||
"pos": [
|
||||
9,
|
||||
6,
|
||||
3
|
||||
],
|
||||
"type": 1,
|
||||
"tile": 0
|
||||
},
|
||||
{
|
||||
"pos": [
|
||||
10,
|
||||
6,
|
||||
3
|
||||
],
|
||||
"type": 1,
|
||||
"tile": 0
|
||||
},
|
||||
{
|
||||
"pos": [
|
||||
11,
|
||||
6,
|
||||
3
|
||||
],
|
||||
"type": 1,
|
||||
"tile": 0
|
||||
},
|
||||
{
|
||||
"pos": [
|
||||
6,
|
||||
7,
|
||||
3
|
||||
],
|
||||
"type": 1,
|
||||
"tile": 0
|
||||
},
|
||||
{
|
||||
"pos": [
|
||||
7,
|
||||
7,
|
||||
3
|
||||
],
|
||||
"type": 1,
|
||||
"tile": 0
|
||||
},
|
||||
{
|
||||
"pos": [
|
||||
8,
|
||||
7,
|
||||
3
|
||||
],
|
||||
"type": 1,
|
||||
"tile": 0
|
||||
},
|
||||
{
|
||||
"pos": [
|
||||
9,
|
||||
7,
|
||||
3
|
||||
],
|
||||
"type": 1,
|
||||
"tile": 0
|
||||
},
|
||||
{
|
||||
"pos": [
|
||||
10,
|
||||
7,
|
||||
3
|
||||
],
|
||||
"type": 1,
|
||||
"tile": 0
|
||||
},
|
||||
{
|
||||
"pos": [
|
||||
11,
|
||||
7,
|
||||
3
|
||||
],
|
||||
"type": 1,
|
||||
"tile": 0
|
||||
}
|
||||
],
|
||||
"meshes": []
|
||||
|
||||
@@ -36,7 +36,7 @@ target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
|
||||
# DUSK_OPENGL_LEGACY
|
||||
DUSK_LINUX
|
||||
DUSK_DISPLAY_SIZE_DYNAMIC
|
||||
DUSK_DISPLAY_WIDTH_DEFAULT=640
|
||||
DUSK_DISPLAY_WIDTH_DEFAULT=854
|
||||
DUSK_DISPLAY_HEIGHT_DEFAULT=480
|
||||
DUSK_DISPLAY_SCREEN_HEIGHT=240
|
||||
DUSK_INPUT_KEYBOARD
|
||||
|
||||
@@ -17,6 +17,10 @@ const ChunkTerrain = (() => {
|
||||
const CHUNK_DEPTH = 4;
|
||||
const TILE_COUNT = CHUNK_WIDTH * CHUNK_HEIGHT * CHUNK_DEPTH;
|
||||
|
||||
// World-space Z distance of one Z-layer/story, in world-float space.
|
||||
// Must match WORLD_LAYER_HEIGHT in src/dusk/rpg/overworld/worldpos.h.
|
||||
const WORLD_LAYER_HEIGHT = Math.SQRT1_2;
|
||||
|
||||
const TILE_SHAPE_NULL = 0;
|
||||
const TILE_SHAPE_GROUND = 1;
|
||||
const TILE_SHAPE_RAMP_NORTH = 2;
|
||||
@@ -88,7 +92,53 @@ const ChunkTerrain = (() => {
|
||||
const v1 = (y + 1) / CHUNK_HEIGHT;
|
||||
const [sw, se, ne, nw] = corners;
|
||||
|
||||
pushTileQuad(out, u0, u1, v0, v1, x, y, z + sw, z + se, z + ne, z + nw);
|
||||
pushTileQuad(
|
||||
out, u0, u1, v0, v1, x, y,
|
||||
(z + sw) * WORLD_LAYER_HEIGHT,
|
||||
(z + se) * WORLD_LAYER_HEIGHT,
|
||||
(z + ne) * WORLD_LAYER_HEIGHT,
|
||||
(z + nw) * WORLD_LAYER_HEIGHT
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
return new Float32Array(out);
|
||||
}
|
||||
|
||||
// Four edges (SW-SE, SE-NE, NE-NW, NW-SW) of a tile's quad as line
|
||||
// segments, lifted slightly above the terrain surface to avoid
|
||||
// z-fighting - same corner-height convention as pushTileQuad().
|
||||
function pushTileGridLines(out, fx, fy, swZ, seZ, neZ, nwZ) {
|
||||
const eps = 0.01;
|
||||
const sw = [fx, fy, swZ + eps];
|
||||
const se = [fx + 1, fy, seZ + eps];
|
||||
const ne = [fx + 1, fy + 1, neZ + eps];
|
||||
const nw = [fx, fy + 1, nwZ + eps];
|
||||
const edges = [sw, se, se, ne, ne, nw, nw, sw];
|
||||
for(const [x, y, z] of edges) out.push(0, 0, x, y, z);
|
||||
}
|
||||
|
||||
// Generate grid-outline line segments for every non-null tile, following
|
||||
// the same per-corner heights as buildTerrainVerts() so lines hug ramps
|
||||
// instead of cutting through them. Returns a Float32Array of interleaved
|
||||
// [u, v, x, y, z] vertices meant to be drawn with gl.LINES (pairs).
|
||||
function buildGridLines(tiles) {
|
||||
const out = [];
|
||||
for(let z = 0; z < CHUNK_DEPTH; z++) {
|
||||
for(let y = 0; y < CHUNK_HEIGHT; y++) {
|
||||
for(let x = 0; x < CHUNK_WIDTH; x++) {
|
||||
const type = tiles[tileIndex(x, y, z)] || TILE_SHAPE_NULL;
|
||||
const corners = RAMP_CORNERS[type];
|
||||
if(!corners) continue;
|
||||
|
||||
const [sw, se, ne, nw] = corners;
|
||||
pushTileGridLines(
|
||||
out, x, y,
|
||||
(z + sw) * WORLD_LAYER_HEIGHT,
|
||||
(z + se) * WORLD_LAYER_HEIGHT,
|
||||
(z + ne) * WORLD_LAYER_HEIGHT,
|
||||
(z + nw) * WORLD_LAYER_HEIGHT
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -96,7 +146,7 @@ const ChunkTerrain = (() => {
|
||||
}
|
||||
|
||||
return Object.freeze({
|
||||
CHUNK_WIDTH, CHUNK_HEIGHT, CHUNK_DEPTH, TILE_COUNT,
|
||||
CHUNK_WIDTH, CHUNK_HEIGHT, CHUNK_DEPTH, TILE_COUNT, WORLD_LAYER_HEIGHT,
|
||||
TILE_SHAPE_NULL, TILE_SHAPE_GROUND,
|
||||
TILE_SHAPE_RAMP_NORTH, TILE_SHAPE_RAMP_EAST,
|
||||
TILE_SHAPE_RAMP_SOUTH, TILE_SHAPE_RAMP_WEST,
|
||||
@@ -104,6 +154,6 @@ const ChunkTerrain = (() => {
|
||||
TILE_SHAPE_RAMP_SOUTHEAST, TILE_SHAPE_RAMP_SOUTHWEST,
|
||||
TILE_SHAPE_RAMP_NORTHEAST_INNER, TILE_SHAPE_RAMP_NORTHWEST_INNER,
|
||||
TILE_SHAPE_RAMP_SOUTHEAST_INNER, TILE_SHAPE_RAMP_SOUTHWEST_INNER,
|
||||
RAMP_CORNERS, tileIndex, buildTerrainVerts,
|
||||
RAMP_CORNERS, tileIndex, buildTerrainVerts, buildGridLines,
|
||||
});
|
||||
})();
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
flex-direction: column;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
max-height: 512px;
|
||||
max-height: 600px;
|
||||
}
|
||||
|
||||
.tile-swatch {
|
||||
|
||||
@@ -41,16 +41,15 @@
|
||||
[ChunkTerrain.TILE_SHAPE_RAMP_SOUTHWEST_INNER]: { dx: -1, dy: 1 },
|
||||
};
|
||||
|
||||
// Draws an arrow centered at (cx, cy) pointing toward the ramp's high
|
||||
// side, so ramp direction is visible at a glance on both the palette
|
||||
// swatches and the tile grid.
|
||||
function drawArrow(ctx, cx, cy, size, dx, dy) {
|
||||
const mag = Math.hypot(dx, dy) || 1;
|
||||
const ux = dx / mag, uy = dy / mag;
|
||||
const len = size * 0.32;
|
||||
const tipX = cx + ux * len, tipY = cy + uy * len;
|
||||
const tailX = cx - ux * len, tailY = cy - uy * len;
|
||||
const INNER_CORNER_SHAPES = new Set([
|
||||
ChunkTerrain.TILE_SHAPE_RAMP_NORTHEAST_INNER,
|
||||
ChunkTerrain.TILE_SHAPE_RAMP_NORTHWEST_INNER,
|
||||
ChunkTerrain.TILE_SHAPE_RAMP_SOUTHEAST_INNER,
|
||||
ChunkTerrain.TILE_SHAPE_RAMP_SOUTHWEST_INNER,
|
||||
]);
|
||||
|
||||
// Draws a single-headed arrow from (tailX, tailY) to (tipX, tipY).
|
||||
function drawArrowSegment(ctx, tailX, tailY, tipX, tipY, size) {
|
||||
ctx.strokeStyle = "#ffffff";
|
||||
ctx.fillStyle = "#ffffff";
|
||||
ctx.lineWidth = Math.max(1, size * 0.08);
|
||||
@@ -62,7 +61,7 @@
|
||||
ctx.stroke();
|
||||
|
||||
const headLen = size * 0.18;
|
||||
const angle = Math.atan2(uy, ux);
|
||||
const angle = Math.atan2(tipY - tailY, tipX - tailX);
|
||||
const leftAngle = angle + Math.PI * 0.8;
|
||||
const rightAngle = angle - Math.PI * 0.8;
|
||||
ctx.beginPath();
|
||||
@@ -73,6 +72,54 @@
|
||||
ctx.fill();
|
||||
}
|
||||
|
||||
// Draws an arrow centered at (cx, cy) pointing toward the ramp's high
|
||||
// side, so ramp direction is visible at a glance on both the palette
|
||||
// swatches and the tile grid. Used for cardinal ramps and outer-corner
|
||||
// ramps (a single raised corner).
|
||||
function drawArrow(ctx, cx, cy, size, dx, dy) {
|
||||
const mag = Math.hypot(dx, dy) || 1;
|
||||
const ux = dx / mag, uy = dy / mag;
|
||||
const len = size * 0.32;
|
||||
drawArrowSegment(ctx, cx - ux * len, cy - uy * len, cx + ux * len, cy + uy * len, size);
|
||||
}
|
||||
|
||||
// Draws a plain right-angle bracket - two line segments meeting at 90
|
||||
// degrees at the tile's corner, one running along each of the two edges
|
||||
// adjacent to it - so inner-corner ramps (three corners raised, one
|
||||
// dropped) read as "the adjacent sides meeting in a 90-degree corner",
|
||||
// distinct from the single diagonal arrow used for outer-corner ramps.
|
||||
function drawCornerBracket(ctx, cx, cy, size, dx, dy) {
|
||||
const cornerDist = size * 0.42;
|
||||
const armLen = size * 0.34;
|
||||
const cornerX = cx + dx * cornerDist;
|
||||
const cornerY = cy + dy * cornerDist;
|
||||
|
||||
ctx.strokeStyle = "#ffffff";
|
||||
ctx.lineWidth = Math.max(1, size * 0.08);
|
||||
ctx.lineCap = "round";
|
||||
ctx.lineJoin = "round";
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(cornerX - dx * armLen, cornerY);
|
||||
ctx.lineTo(cornerX, cornerY);
|
||||
ctx.lineTo(cornerX, cornerY - dy * armLen);
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
// Draws the direction icon for a ramp tile `type` in a `size`x`size` area
|
||||
// centered at (cx, cy): a diagonal arrow for cardinal/outer-corner ramps,
|
||||
// or a right-angle bracket for inner-corner ramps. No-op for shapes with
|
||||
// no direction (ground, erase).
|
||||
function drawShapeIcon(ctx, cx, cy, size, type) {
|
||||
const dir = SHAPE_DIRECTIONS[type];
|
||||
if(!dir) return;
|
||||
if(INNER_CORNER_SHAPES.has(type)) {
|
||||
drawCornerBracket(ctx, cx, cy, size, dir.dx, dir.dy);
|
||||
} else {
|
||||
drawArrow(ctx, cx, cy, size, dir.dx, dir.dy);
|
||||
}
|
||||
}
|
||||
|
||||
// Draws a small pencil icon centered in a `size`x`size` canvas.
|
||||
function drawPencilIcon(ctx, size) {
|
||||
ctx.save();
|
||||
@@ -146,6 +193,7 @@
|
||||
|
||||
let mapRenderer = null;
|
||||
let terrainMesh = null;
|
||||
let gridLinesMesh = null;
|
||||
let terrainTexturePromise = null;
|
||||
let modelIndex = null;
|
||||
let neighborChunks = [];
|
||||
@@ -262,6 +310,13 @@
|
||||
return modelIndex;
|
||||
}
|
||||
|
||||
// Scales a mesh's stored [x, y, z] offset for preview, matching the
|
||||
// Z scaling mapChunkLoaded() applies at runtime (chunk.meshOffsets[m][2]
|
||||
// * WORLD_LAYER_HEIGHT) - x/y stay 1:1 since only Z is height-scaled.
|
||||
function scaledMeshOffset(pos) {
|
||||
return [pos[0], pos[1], pos[2] * ChunkTerrain.WORLD_LAYER_HEIGHT];
|
||||
}
|
||||
|
||||
// Resolves a chunk mesh entry's bare filename (e.g. "house_5_3.dmf") to
|
||||
// its model JSON by basename, mirroring find_model() in
|
||||
// tools/asset/chunk/__main__.py - the chunk JSON only ever stores a bare
|
||||
@@ -330,7 +385,7 @@
|
||||
for(const m of neighborMeshes) {
|
||||
try {
|
||||
const resolved = await resolveModelForMeshFile(m.file);
|
||||
if(resolved) models.push({ ...resolved, offset: m.pos });
|
||||
if(resolved) models.push({ ...resolved, offset: scaledMeshOffset(m.pos) });
|
||||
} catch(e) {
|
||||
console.warn("Failed to load neighbor mesh preview", m.file, e);
|
||||
}
|
||||
@@ -426,13 +481,12 @@
|
||||
buildPalette();
|
||||
});
|
||||
|
||||
const dir = SHAPE_DIRECTIONS[s.type];
|
||||
if(dir) {
|
||||
if(SHAPE_DIRECTIONS[s.type]) {
|
||||
const icon = document.createElement("canvas");
|
||||
icon.width = 32;
|
||||
icon.height = 32;
|
||||
icon.className = "tile-swatch-icon";
|
||||
drawArrow(icon.getContext("2d"), 16, 16, 32, dir.dx, dir.dy);
|
||||
drawShapeIcon(icon.getContext("2d"), 16, 16, 32, s.type);
|
||||
btn.appendChild(icon);
|
||||
}
|
||||
|
||||
@@ -489,11 +543,10 @@
|
||||
const sy = (ChunkTerrain.CHUNK_HEIGHT - 1 - y) * cell;
|
||||
ctx.fillStyle = SHAPE_COLORS[type] || "#15171b";
|
||||
ctx.fillRect(x * cell, sy, cell, cell);
|
||||
ctx.strokeStyle = "rgba(255, 255, 255, 0.08)";
|
||||
ctx.strokeStyle = "rgba(255, 255, 255, 0.25)";
|
||||
ctx.strokeRect(x * cell, sy, cell, cell);
|
||||
|
||||
const dir = SHAPE_DIRECTIONS[type];
|
||||
if(dir) drawArrow(ctx, x * cell + cell / 2, sy + cell / 2, cell, dir.dx, dir.dy);
|
||||
drawShapeIcon(ctx, x * cell + cell / 2, sy + cell / 2, cell, type);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -592,6 +645,9 @@
|
||||
function rebuildTerrainMesh() {
|
||||
const floats = ChunkTerrain.buildTerrainVerts(tiles);
|
||||
terrainMesh = floats.length ? mapRenderer.createMesh(floats) : null;
|
||||
|
||||
const lineFloats = ChunkTerrain.buildGridLines(tiles);
|
||||
gridLinesMesh = lineFloats.length ? mapRenderer.createMesh(lineFloats) : null;
|
||||
}
|
||||
|
||||
// Keeps the preview canvas's backing-store resolution matched to its
|
||||
@@ -619,7 +675,7 @@
|
||||
for(const m of meshes) {
|
||||
try {
|
||||
const resolved = await resolveModelForMeshFile(m.file);
|
||||
if(resolved) models.push({ ...resolved, offset: m.pos });
|
||||
if(resolved) models.push({ ...resolved, offset: scaledMeshOffset(m.pos) });
|
||||
} catch(e) {
|
||||
console.warn("Failed to load mesh preview", m.file, e);
|
||||
}
|
||||
@@ -637,13 +693,15 @@
|
||||
models: n.models,
|
||||
}));
|
||||
|
||||
const zLevelHeight = currentZLevel * ChunkTerrain.WORLD_LAYER_HEIGHT;
|
||||
mapRenderer.render({
|
||||
target: [ChunkTerrain.CHUNK_WIDTH / 2, ChunkTerrain.CHUNK_HEIGHT / 2, currentZLevel],
|
||||
target: [ChunkTerrain.CHUNK_WIDTH / 2, ChunkTerrain.CHUNK_HEIGHT / 2, zLevelHeight],
|
||||
worldH: zoomWorldH,
|
||||
terrain: terrainMesh ? { mesh: terrainMesh, texture: terrainTexture } : null,
|
||||
gridLines: gridLinesMesh ? { mesh: gridLinesMesh } : null,
|
||||
models,
|
||||
neighbors,
|
||||
highlight: hoverTile ? { x: hoverTile.x, y: hoverTile.y, z: currentZLevel } : null,
|
||||
highlight: hoverTile ? { x: hoverTile.x, y: hoverTile.y, z: zLevelHeight } : null,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -221,6 +221,7 @@ const MapRenderer = (() => {
|
||||
// scene = {
|
||||
// target: [x, y, z], worldH: number,
|
||||
// terrain: { mesh, texture } | null,
|
||||
// gridLines: { mesh } | null,
|
||||
// models: [{ mesh, texture, color: [r,g,b,a in 0..1], offset: [x,y,z] }],
|
||||
// neighbors: [{
|
||||
// offset: [x,y,z],
|
||||
@@ -248,6 +249,10 @@ const MapRenderer = (() => {
|
||||
drawMesh(scene.terrain.mesh, identity, scene.terrain.texture, [1, 1, 1, 1], true);
|
||||
}
|
||||
|
||||
if(scene.gridLines) {
|
||||
drawMesh(scene.gridLines.mesh, identity, null, [0, 0, 0, 0.35], false, gl.LINES);
|
||||
}
|
||||
|
||||
for(const model of scene.models) {
|
||||
const modelMatrix = mat4Translation(new Float32Array(16), model.offset);
|
||||
drawMesh(model.mesh, modelMatrix, model.texture, model.color, true);
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
#include "asset/assetfile.h"
|
||||
#include "rpg/overworld/chunk.h"
|
||||
|
||||
#define ASSET_CHUNK_FILE_VERSION 3
|
||||
#define ASSET_CHUNK_FILE_VERSION 4
|
||||
|
||||
typedef struct assetloading_s assetloading_t;
|
||||
typedef struct assetentry_s assetentry_t;
|
||||
|
||||
@@ -506,12 +506,17 @@ errorret_t assetLocaleGetString(
|
||||
sizeof(lineBuffer)
|
||||
);
|
||||
|
||||
// Prime the reader with the first line before scanning; outBuffer holds
|
||||
// uninitialized memory until the first Next() call fills it.
|
||||
errorChain(assetFileLineReaderNext(&reader));
|
||||
|
||||
// Skip blanks, comments, etc and start looking for msgid's
|
||||
errorChain(assetLocaleLineSkipBlanks(&reader, lineBuffer));
|
||||
|
||||
while(!reader.eof) {
|
||||
while(true) {
|
||||
// Is this msgid?
|
||||
if(memoryCompare(lineBuffer, "msgid", 5) != 0) {
|
||||
if(reader.eof) break;
|
||||
errorChain(assetFileLineReaderNext(&reader));
|
||||
msgidBuffer[0] = '\0';
|
||||
continue;
|
||||
@@ -535,7 +540,7 @@ errorret_t assetLocaleGetString(
|
||||
}
|
||||
|
||||
// We are either going to see a msgstr or a msgid_plural
|
||||
while(!reader.eof) {
|
||||
while(true) {
|
||||
errorChain(assetLocaleLineSkipBlanks(&reader, lineBuffer));
|
||||
|
||||
// Is msgid_plural?
|
||||
|
||||
@@ -17,6 +17,7 @@ input_t INPUT;
|
||||
|
||||
errorret_t inputInit(void) {
|
||||
memoryZero(&INPUT, sizeof(input_t));
|
||||
INPUT.deadzone = INPUT_DEADZONE_DEFAULT;
|
||||
|
||||
for(uint8_t i = 0; i < INPUT_ACTION_COUNT; i++) {
|
||||
INPUT.actions[i].action = (inputaction_t)i;
|
||||
|
||||
@@ -12,11 +12,15 @@
|
||||
|
||||
#define INPUT_LISTENER_PRESSED_MAX 16
|
||||
#define INPUT_LISTENER_RELEASED_MAX INPUT_LISTENER_PRESSED_MAX
|
||||
#define INPUT_DEADZONE_DEFAULT 0.1f
|
||||
|
||||
typedef struct {
|
||||
inputactiondata_t actions[INPUT_ACTION_COUNT];
|
||||
|
||||
inputplatform_t platform;
|
||||
|
||||
/** User-configured gamepad axis deadzone (0.0f to 1.0f). */
|
||||
float_t deadzone;
|
||||
} input_t;
|
||||
|
||||
extern input_t INPUT;
|
||||
|
||||
@@ -16,4 +16,14 @@ typedef struct {
|
||||
static const localeinfo_t LOCALE_EN_US = {
|
||||
.name = "en-US",
|
||||
.file = "locale/en_US.po",
|
||||
};
|
||||
|
||||
static const localeinfo_t LOCALE_JP_JP = {
|
||||
.name = "ja-JP",
|
||||
.file = "locale/jp_JP.po",
|
||||
};
|
||||
|
||||
static const localeinfo_t LOCALE_ES_MX = {
|
||||
.name = "es-MX",
|
||||
.file = "locale/es_MX.po",
|
||||
};
|
||||
@@ -8,10 +8,10 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||
PUBLIC
|
||||
rpg.c
|
||||
rpgcamera.c
|
||||
rpgtextbox.c
|
||||
)
|
||||
|
||||
# Subdirs
|
||||
add_subdirectory(battle)
|
||||
add_subdirectory(cutscene)
|
||||
add_subdirectory(entity)
|
||||
add_subdirectory(overworld)
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
# 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
|
||||
battle.c
|
||||
battlefighter.c
|
||||
party.c
|
||||
)
|
||||
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "battle.h"
|
||||
#include "util/memory.h"
|
||||
|
||||
battle_t BATTLE;
|
||||
|
||||
void battleInit(void) {
|
||||
memoryZero(&BATTLE, sizeof(battle_t));
|
||||
for(uint8_t i = 0; i < BATTLE_FIGHTER_COUNT_MAX; i++) {
|
||||
BATTLE.fighters[i].id = i;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t battleGetAvailableFighter(void) {
|
||||
for(uint8_t i = 0; i < BATTLE_FIGHTER_COUNT_MAX; i++) {
|
||||
if(BATTLE.fighters[i].status == BATTLE_FIGHTER_STATUS_NULL) return i;
|
||||
}
|
||||
|
||||
return 0xFF;
|
||||
}
|
||||
|
||||
battlefighter_t *battleAddFighter(
|
||||
const battlefighterteam_t team,
|
||||
const battlefightercontroller_t controller,
|
||||
const battlefighterstats_t stats,
|
||||
const uint16_t healthMax,
|
||||
const uint16_t mpMax
|
||||
) {
|
||||
const uint8_t index = battleGetAvailableFighter();
|
||||
if(index == 0xFF) return NULL;
|
||||
|
||||
battlefighter_t *fighter = &BATTLE.fighters[index];
|
||||
battleFighterInit(fighter, team, controller, stats, healthMax, mpMax);
|
||||
return fighter;
|
||||
}
|
||||
|
||||
void battleStart(void) {
|
||||
BATTLE.active = true;
|
||||
}
|
||||
|
||||
void battleDispose(void) {
|
||||
battleInit();
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "battlefighter.h"
|
||||
|
||||
#define BATTLE_FIGHTER_COUNT_MAX 8
|
||||
|
||||
typedef struct {
|
||||
bool_t active;
|
||||
battlefighter_t fighters[BATTLE_FIGHTER_COUNT_MAX];
|
||||
} battle_t;
|
||||
|
||||
extern battle_t BATTLE;
|
||||
|
||||
/**
|
||||
* Initializes the battle system. Marks it as inactive with no fighters.
|
||||
*/
|
||||
void battleInit(void);
|
||||
|
||||
/**
|
||||
* Gets an available (unused) fighter slot index.
|
||||
*
|
||||
* @return The index of an available fighter slot, or 0xFF if none are
|
||||
* available.
|
||||
*/
|
||||
uint8_t battleGetAvailableFighter(void);
|
||||
|
||||
/**
|
||||
* Adds a fighter to the battle in the next available slot.
|
||||
*
|
||||
* @param team The team the fighter belongs to.
|
||||
* @param controller Who makes decisions for the fighter.
|
||||
* @param stats The fighter's base combat stats.
|
||||
* @param healthMax The fighter's maximum health.
|
||||
* @param mpMax The fighter's maximum mp.
|
||||
* @return Pointer to the newly added fighter, or NULL if the battle is
|
||||
* already full.
|
||||
*/
|
||||
battlefighter_t *battleAddFighter(
|
||||
const battlefighterteam_t team,
|
||||
const battlefightercontroller_t controller,
|
||||
const battlefighterstats_t stats,
|
||||
const uint16_t healthMax,
|
||||
const uint16_t mpMax
|
||||
);
|
||||
|
||||
/**
|
||||
* Starts a battle, marking it active. Any fighters already added via
|
||||
* battleAddFighter remain in place.
|
||||
*/
|
||||
void battleStart(void);
|
||||
|
||||
/**
|
||||
* Disposes of the battle, clearing all fighters and marking it inactive.
|
||||
*/
|
||||
void battleDispose(void);
|
||||
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "battlefighter.h"
|
||||
#include "assert/assert.h"
|
||||
#include "util/memory.h"
|
||||
|
||||
void battleFighterInit(
|
||||
battlefighter_t *fighter,
|
||||
const battlefighterteam_t team,
|
||||
const battlefightercontroller_t controller,
|
||||
const battlefighterstats_t stats,
|
||||
const uint16_t healthMax,
|
||||
const uint16_t mpMax
|
||||
) {
|
||||
assertNotNull(fighter, "Fighter pointer cannot be NULL");
|
||||
assertTrue(team < BATTLE_FIGHTER_TEAM_COUNT, "Invalid fighter team");
|
||||
assertTrue(
|
||||
controller < BATTLE_FIGHTER_CONTROLLER_COUNT,
|
||||
"Invalid fighter controller"
|
||||
);
|
||||
|
||||
const uint8_t id = fighter->id;
|
||||
memoryZero(fighter, sizeof(battlefighter_t));
|
||||
fighter->id = id;
|
||||
fighter->status = BATTLE_FIGHTER_STATUS_NORMAL;
|
||||
fighter->team = team;
|
||||
fighter->controller = controller;
|
||||
fighter->stats = stats;
|
||||
fighter->healthMax = healthMax;
|
||||
fighter->health = healthMax;
|
||||
fighter->mpMax = mpMax;
|
||||
fighter->mp = mpMax;
|
||||
}
|
||||
|
||||
bool_t battleFighterIsAlive(const battlefighter_t *fighter) {
|
||||
assertNotNull(fighter, "Fighter pointer cannot be NULL");
|
||||
return fighter->status == BATTLE_FIGHTER_STATUS_NORMAL;
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "error/error.h"
|
||||
|
||||
// An empty status means the slot in BATTLE.fighters is unused.
|
||||
typedef enum {
|
||||
BATTLE_FIGHTER_STATUS_NULL,
|
||||
|
||||
BATTLE_FIGHTER_STATUS_NORMAL,
|
||||
BATTLE_FIGHTER_STATUS_DEAD,
|
||||
|
||||
BATTLE_FIGHTER_STATUS_COUNT
|
||||
} battlefighterstatus_t;
|
||||
|
||||
typedef enum {
|
||||
BATTLE_FIGHTER_TEAM_ALLY,
|
||||
BATTLE_FIGHTER_TEAM_ENEMY,
|
||||
|
||||
BATTLE_FIGHTER_TEAM_COUNT
|
||||
} battlefighterteam_t;
|
||||
|
||||
typedef enum {
|
||||
BATTLE_FIGHTER_CONTROLLER_PLAYER,
|
||||
BATTLE_FIGHTER_CONTROLLER_AI,
|
||||
|
||||
BATTLE_FIGHTER_CONTROLLER_COUNT
|
||||
} battlefightercontroller_t;
|
||||
|
||||
// Base combat stats, kept separate from the resource pools (health/mp) on
|
||||
// battlefighter_t so that equipment/buffs can later modify them without
|
||||
// touching current health/mp state.
|
||||
typedef struct {
|
||||
uint16_t attack;
|
||||
uint16_t defense;
|
||||
uint16_t magic;
|
||||
uint16_t speed;
|
||||
uint16_t luck;
|
||||
} battlefighterstats_t;
|
||||
|
||||
typedef struct {
|
||||
uint8_t id;
|
||||
battlefighterstatus_t status;
|
||||
battlefighterteam_t team;
|
||||
battlefightercontroller_t controller;
|
||||
|
||||
uint16_t health;
|
||||
uint16_t healthMax;
|
||||
uint16_t mp;
|
||||
uint16_t mpMax;
|
||||
|
||||
battlefighterstats_t stats;
|
||||
} battlefighter_t;
|
||||
|
||||
/**
|
||||
* Initializes a battle fighter in place, filling health/mp to their maximum
|
||||
* values and setting its status to normal.
|
||||
*
|
||||
* @param fighter Pointer to the fighter to initialize.
|
||||
* @param team The team the fighter belongs to.
|
||||
* @param controller Who makes decisions for the fighter.
|
||||
* @param stats The fighter's base combat stats.
|
||||
* @param healthMax The fighter's maximum health.
|
||||
* @param mpMax The fighter's maximum mp.
|
||||
*/
|
||||
void battleFighterInit(
|
||||
battlefighter_t *fighter,
|
||||
const battlefighterteam_t team,
|
||||
const battlefightercontroller_t controller,
|
||||
const battlefighterstats_t stats,
|
||||
const uint16_t healthMax,
|
||||
const uint16_t mpMax
|
||||
);
|
||||
|
||||
/**
|
||||
* Returns true if the fighter is in a state where it can still act (i.e.
|
||||
* is not dead).
|
||||
*
|
||||
* @param fighter Pointer to the fighter to check.
|
||||
* @returns True if the fighter can act.
|
||||
*/
|
||||
bool_t battleFighterIsAlive(const battlefighter_t *fighter);
|
||||
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "party.h"
|
||||
#include "assert/assert.h"
|
||||
#include "util/memory.h"
|
||||
|
||||
party_t PARTY;
|
||||
|
||||
void partyInit(void) {
|
||||
memoryZero(&PARTY, sizeof(party_t));
|
||||
for(uint8_t i = 0; i < PARTY_MEMBER_COUNT_MAX; i++) {
|
||||
PARTY.members[i].id = i;
|
||||
}
|
||||
for(uint8_t i = 0; i < PARTY_ACTIVE_SIZE_MAX; i++) {
|
||||
PARTY.order[i] = PARTY_ORDER_EMPTY;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t partyGetAvailableMember(void) {
|
||||
for(uint8_t i = 0; i < PARTY_MEMBER_COUNT_MAX; i++) {
|
||||
if(PARTY.members[i].status == BATTLE_FIGHTER_STATUS_NULL) return i;
|
||||
}
|
||||
|
||||
return 0xFF;
|
||||
}
|
||||
|
||||
battlefighter_t *partyAddMember(
|
||||
const battlefighterstats_t stats,
|
||||
const uint16_t healthMax,
|
||||
const uint16_t mpMax
|
||||
) {
|
||||
const uint8_t index = partyGetAvailableMember();
|
||||
if(index == 0xFF) return NULL;
|
||||
|
||||
battlefighter_t *member = &PARTY.members[index];
|
||||
battleFighterInit(
|
||||
member, BATTLE_FIGHTER_TEAM_ALLY, BATTLE_FIGHTER_CONTROLLER_PLAYER,
|
||||
stats, healthMax, mpMax
|
||||
);
|
||||
|
||||
for(uint8_t i = 0; i < PARTY_ACTIVE_SIZE_MAX; i++) {
|
||||
if(PARTY.order[i] != PARTY_ORDER_EMPTY) continue;
|
||||
PARTY.order[i] = index;
|
||||
break;
|
||||
}
|
||||
|
||||
return member;
|
||||
}
|
||||
|
||||
battlefighter_t *partyGetOrderMember(const uint8_t slot) {
|
||||
assertTrue(slot < PARTY_ACTIVE_SIZE_MAX, "Invalid party order slot");
|
||||
|
||||
const uint8_t index = PARTY.order[slot];
|
||||
if(index == PARTY_ORDER_EMPTY) return NULL;
|
||||
return &PARTY.members[index];
|
||||
}
|
||||
|
||||
void partySetOrder(const uint8_t slot, const uint8_t memberIndex) {
|
||||
assertTrue(slot < PARTY_ACTIVE_SIZE_MAX, "Invalid party order slot");
|
||||
assertTrue(
|
||||
memberIndex == PARTY_ORDER_EMPTY || memberIndex < PARTY_MEMBER_COUNT_MAX,
|
||||
"Invalid party member index"
|
||||
);
|
||||
|
||||
PARTY.order[slot] = memberIndex;
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "battlefighter.h"
|
||||
|
||||
#define PARTY_MEMBER_COUNT_MAX 4
|
||||
#define PARTY_ACTIVE_SIZE_MAX 3
|
||||
#define PARTY_ORDER_EMPTY 0xFF
|
||||
|
||||
typedef struct {
|
||||
battlefighter_t members[PARTY_MEMBER_COUNT_MAX];
|
||||
|
||||
// Maps an active battle slot to the roster member filling it, or
|
||||
// PARTY_ORDER_EMPTY if the slot is unfilled. Only the first
|
||||
// PARTY_ACTIVE_SIZE_MAX of the PARTY_MEMBER_COUNT_MAX roster members
|
||||
// can be in the active lineup at once.
|
||||
uint8_t order[PARTY_ACTIVE_SIZE_MAX];
|
||||
} party_t;
|
||||
|
||||
extern party_t PARTY;
|
||||
|
||||
/**
|
||||
* Initializes the party system with an empty roster and order.
|
||||
*/
|
||||
void partyInit(void);
|
||||
|
||||
/**
|
||||
* Gets an available (unused) party member slot index.
|
||||
*
|
||||
* @return The index of an available slot, or 0xFF if the party is full.
|
||||
*/
|
||||
uint8_t partyGetAvailableMember(void);
|
||||
|
||||
/**
|
||||
* Adds a member to the party roster in the next available slot. Party
|
||||
* members are always allies controlled by the player. If there is a
|
||||
* free active order slot, the new member is placed into it.
|
||||
*
|
||||
* @param stats The member's base combat stats.
|
||||
* @param healthMax The member's maximum health.
|
||||
* @param mpMax The member's maximum mp.
|
||||
* @return Pointer to the newly added party member, or NULL if the party is
|
||||
* already full.
|
||||
*/
|
||||
battlefighter_t *partyAddMember(
|
||||
const battlefighterstats_t stats,
|
||||
const uint16_t healthMax,
|
||||
const uint16_t mpMax
|
||||
);
|
||||
|
||||
/**
|
||||
* Gets the roster member currently occupying an active order slot.
|
||||
*
|
||||
* @param slot The active order slot to query.
|
||||
* @return Pointer to the member in that slot, or NULL if the slot is
|
||||
* empty.
|
||||
*/
|
||||
battlefighter_t *partyGetOrderMember(const uint8_t slot);
|
||||
|
||||
/**
|
||||
* Assigns a roster member to an active order slot, replacing whatever
|
||||
* was there. Use PARTY_ORDER_EMPTY to clear a slot.
|
||||
*
|
||||
* @param slot The active order slot to assign.
|
||||
* @param memberIndex The roster member index to place there, or
|
||||
* PARTY_ORDER_EMPTY to clear the slot.
|
||||
*/
|
||||
void partySetOrder(const uint8_t slot, const uint8_t memberIndex);
|
||||
@@ -7,7 +7,6 @@
|
||||
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||
PUBLIC
|
||||
cutscenesystem.c
|
||||
cutscenemode.c
|
||||
)
|
||||
|
||||
# Subdirs
|
||||
|
||||
@@ -7,8 +7,192 @@
|
||||
|
||||
#pragma once
|
||||
#include "rpg/cutscene/item/cutsceneitem.h"
|
||||
#include "rpg/cutscene/cutscenepause.h"
|
||||
|
||||
typedef struct cutscene_s {
|
||||
const cutsceneitem_t *items;
|
||||
uint8_t itemCount;
|
||||
} cutscene_t;
|
||||
cutscenepause_t pause;
|
||||
|
||||
// Size in bytes of this cutscene's custom user data, carved out of
|
||||
// CUTSCENE_SYSTEM.data while the cutscene is running.
|
||||
size_t dataSize;
|
||||
} cutscene_t;
|
||||
|
||||
#define CUTSCENE(NAME, SIZE, PAUSE_TYPE, ...) \
|
||||
static const cutsceneitem_t CUTSCENE_##NAME##_ITEMS[] = { __VA_ARGS__ }; \
|
||||
static const cutscene_t CUTSCENE_##NAME = { \
|
||||
.items = CUTSCENE_##NAME##_ITEMS, \
|
||||
.itemCount = sizeof(CUTSCENE_##NAME##_ITEMS) / sizeof(cutsceneitem_t), \
|
||||
.pause = CUTSCENE_PAUSE_##PAUSE_TYPE, \
|
||||
.dataSize = SIZE \
|
||||
};
|
||||
|
||||
#define CUTSCENE_REFERENCE(CUTSCENE) \
|
||||
&CUTSCENE_##CUTSCENE
|
||||
|
||||
#define CUTSCENE_TEXT(TEXT) \
|
||||
{ .type = CUTSCENE_ITEM_TYPE_TEXT, .text = { .text = TEXT } }
|
||||
|
||||
#define CUTSCENE_WAIT(WAIT) \
|
||||
{ .type = CUTSCENE_ITEM_TYPE_WAIT, .wait = WAIT }
|
||||
|
||||
#define CUTSCENE_CUTSCENE(CUTSCENE) \
|
||||
{ \
|
||||
.type = CUTSCENE_ITEM_TYPE_CUTSCENE, \
|
||||
.cutscene = CUTSCENE_REFERENCE(CUTSCENE) \
|
||||
}
|
||||
|
||||
#define CUTSCENE_CALLBACK(CALLBACK) \
|
||||
{ .type = CUTSCENE_ITEM_TYPE_CALLBACK, .callback = CALLBACK }
|
||||
|
||||
#define CUTSCENE_ENTITY_WALK_TO(ENTITY_INDEX, X, Y, Z) \
|
||||
{ \
|
||||
.type = CUTSCENE_ITEM_TYPE_ENTITY_WALK_TO, \
|
||||
.entityWalkTo = { \
|
||||
.entityIndex = ENTITY_INDEX, \
|
||||
.positions = (const worldpos_t[]){ { X, Y, Z } }, \
|
||||
.count = 1, \
|
||||
.walkAround = true \
|
||||
} \
|
||||
}
|
||||
|
||||
#define CUTSCENE_ENTITY_WALK_PATH(NAME, ENTITY_INDEX, ...) \
|
||||
static const worldpos_t CUTSCENE_##NAME##_POSITIONS[] = { __VA_ARGS__ }; \
|
||||
static const cutsceneitem_t CUTSCENE_##NAME = { \
|
||||
.type = CUTSCENE_ITEM_TYPE_ENTITY_WALK_TO, \
|
||||
.entityWalkTo = { \
|
||||
.entityIndex = ENTITY_INDEX, \
|
||||
.positions = CUTSCENE_##NAME##_POSITIONS, \
|
||||
.count = sizeof(CUTSCENE_##NAME##_POSITIONS) / sizeof(worldpos_t), \
|
||||
.walkAround = true \
|
||||
} \
|
||||
}
|
||||
|
||||
#define CUTSCENE_ENTITY_REMOVE(ENTITY_INDEX) \
|
||||
{ \
|
||||
.type = CUTSCENE_ITEM_TYPE_ENTITY_REMOVE, \
|
||||
.entityRemove = { .entityIndex = ENTITY_INDEX } \
|
||||
}
|
||||
|
||||
#define CUTSCENE_ENTITY_ADD(TYPE, X, Y, Z) \
|
||||
{ \
|
||||
.type = CUTSCENE_ITEM_TYPE_ENTITY_ADD, \
|
||||
.entityAdd = { .entityType = TYPE, .position = { X, Y, Z } } \
|
||||
}
|
||||
|
||||
#define CUTSCENE_ENTITY_TURN(ENTITY_INDEX, DIRECTION) \
|
||||
{ \
|
||||
.type = CUTSCENE_ITEM_TYPE_ENTITY_TURN, \
|
||||
.entityTurn = { .entityIndex = ENTITY_INDEX, .direction = DIRECTION } \
|
||||
}
|
||||
|
||||
// Walks ENTITY_INDEX to stand beside TARGET_ENTITY_INDEX, offset by
|
||||
// (OFFSET_X, OFFSET_Y) on the 2D plane. The destination Z is resolved
|
||||
// from nearby terrain each frame, so ramps between the two entities are
|
||||
// accounted for automatically.
|
||||
#define CUTSCENE_ENTITY_WALK_TO_ENTITY( \
|
||||
ENTITY_INDEX, TARGET_ENTITY_INDEX, OFFSET_X, OFFSET_Y \
|
||||
) \
|
||||
{ \
|
||||
.type = CUTSCENE_ITEM_TYPE_ENTITY_WALK_TO_ENTITY, \
|
||||
.entityWalkToEntity = { \
|
||||
.entityIndex = ENTITY_INDEX, \
|
||||
.targetEntityIndex = TARGET_ENTITY_INDEX, \
|
||||
.offsetX = OFFSET_X, \
|
||||
.offsetY = OFFSET_Y \
|
||||
} \
|
||||
}
|
||||
|
||||
#define CUTSCENE_ENTITY_TELEPORT(ENTITY_INDEX, X, Y, Z) \
|
||||
{ \
|
||||
.type = CUTSCENE_ITEM_TYPE_ENTITY_TELEPORT, \
|
||||
.entityTeleport = { .entityIndex = ENTITY_INDEX, .target = { X, Y, Z } } \
|
||||
}
|
||||
|
||||
#define CUTSCENE_FADE(FROM, TO, DURATION, EASING) \
|
||||
{ \
|
||||
.type = CUTSCENE_ITEM_TYPE_FADE, \
|
||||
.fade = { .from = FROM, .to = TO, .duration = DURATION, .easing = EASING } \
|
||||
}
|
||||
|
||||
#define CUTSCENE_FADE_TO_BLACK(DURATION) \
|
||||
CUTSCENE_FADE(COLOR_TRANSPARENT_BLACK, COLOR_BLACK, DURATION, EASING_LINEAR)
|
||||
|
||||
#define CUTSCENE_FADE_FROM_BLACK(DURATION) \
|
||||
CUTSCENE_FADE(COLOR_BLACK, COLOR_TRANSPARENT_BLACK, DURATION, EASING_LINEAR)
|
||||
|
||||
#define CUTSCENE_FADE_TO_WHITE(DURATION) \
|
||||
CUTSCENE_FADE(COLOR_TRANSPARENT_WHITE, COLOR_WHITE, DURATION, EASING_LINEAR)
|
||||
|
||||
#define CUTSCENE_FADE_FROM_WHITE(DURATION) \
|
||||
CUTSCENE_FADE(COLOR_WHITE, COLOR_TRANSPARENT_WHITE, DURATION, EASING_LINEAR)
|
||||
|
||||
#define CUTSCENE_SET_PAUSE(FLAGS) \
|
||||
{ .type = CUTSCENE_ITEM_TYPE_SET_PAUSE, .setPause = (FLAGS) }
|
||||
|
||||
#define CUTSCENE_ITEM_GIVE(ITEM_ID, QUANTITY) \
|
||||
{ \
|
||||
.type = CUTSCENE_ITEM_TYPE_ITEM_GIVE, \
|
||||
.itemGive = { .item = ITEM_ID, .quantity = QUANTITY } \
|
||||
}
|
||||
|
||||
// Runs all listed items simultaneously and waits until all are done.
|
||||
// Concurrent items cannot be nested inside another CUTSCENE_CONCURRENT.
|
||||
#define CUTSCENE_CONCURRENT(...) \
|
||||
{ \
|
||||
.type = CUTSCENE_ITEM_TYPE_CONCURRENT, \
|
||||
.concurrent = { \
|
||||
.items = (const cutsceneitem_t[]){ __VA_ARGS__ }, \
|
||||
.count = (uint8_t)( \
|
||||
sizeof((cutsceneitem_t[]){ __VA_ARGS__ }) / \
|
||||
sizeof(cutsceneitem_t) \
|
||||
) \
|
||||
} \
|
||||
}
|
||||
|
||||
#define CUTSCENE_MAP_AREA_ADD( \
|
||||
MIN_X, MIN_Y, MIN_Z, MAX_X, MAX_Y, MAX_Z, CALLBACK, NOTIFY, TRIGGER \
|
||||
) \
|
||||
{ \
|
||||
.type = CUTSCENE_ITEM_TYPE_MAP_AREA_ADD, \
|
||||
.mapAreaAdd = { \
|
||||
.min = { MIN_X, MIN_Y, MIN_Z }, \
|
||||
.max = { MAX_X, MAX_Y, MAX_Z }, \
|
||||
.callback = CALLBACK, \
|
||||
.notify = NOTIFY, \
|
||||
.trigger = TRIGGER \
|
||||
} \
|
||||
}
|
||||
|
||||
#define CUTSCENE_MAP_AREA_REMOVE(AREA_ID) \
|
||||
{ \
|
||||
.type = CUTSCENE_ITEM_TYPE_MAP_AREA_REMOVE, \
|
||||
.mapAreaRemove = { .areaId = AREA_ID } \
|
||||
}
|
||||
|
||||
// Waits until any one of the given map area IDs has its callback invoked.
|
||||
// Accepts CUTSCENE_AREA_LAST_CREATED in place of a literal area ID.
|
||||
#define CUTSCENE_MAP_AREA_WAIT(...) \
|
||||
{ \
|
||||
.type = CUTSCENE_ITEM_TYPE_MAP_AREA_WAIT, \
|
||||
.mapAreaWait = { \
|
||||
.areaIds = (const uint8_t[]){ __VA_ARGS__ }, \
|
||||
.count = (uint8_t)( \
|
||||
sizeof((const uint8_t[]){ __VA_ARGS__ }) / sizeof(uint8_t) \
|
||||
) \
|
||||
} \
|
||||
}
|
||||
|
||||
// Adds a map area, waits for it to be triggered once, then removes it
|
||||
// before the cutscene continues. Uses a no-op callback since the wait is
|
||||
// driven by the area's trigger count rather than callback logic.
|
||||
#define CUTSCENE_MAP_AREA_TRIGGER_ONCE( \
|
||||
MIN_X, MIN_Y, MIN_Z, MAX_X, MAX_Y, MAX_Z, NOTIFY, TRIGGER \
|
||||
) \
|
||||
CUTSCENE_MAP_AREA_ADD( \
|
||||
MIN_X, MIN_Y, MIN_Z, MAX_X, MAX_Y, MAX_Z, \
|
||||
mapAreaNoopCallback, NOTIFY, TRIGGER \
|
||||
), \
|
||||
CUTSCENE_MAP_AREA_WAIT(CUTSCENE_AREA_LAST_CREATED), \
|
||||
CUTSCENE_MAP_AREA_REMOVE(CUTSCENE_AREA_LAST_CREATED)
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "rpg/cutscene/cutscenesystem.h"
|
||||
|
||||
bool_t cutsceneModeIsInputAllowed() {
|
||||
switch(CUTSCENE_SYSTEM.mode) {
|
||||
case CUTSCENE_MODE_FULL_FREEZE:
|
||||
case CUTSCENE_MODE_INPUT_FREEZE:
|
||||
return false;
|
||||
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "dusk.h"
|
||||
|
||||
typedef enum {
|
||||
CUTSCENE_MODE_NONE,
|
||||
CUTSCENE_MODE_FULL_FREEZE,
|
||||
CUTSCENE_MODE_INPUT_FREEZE,
|
||||
CUTSCENE_MODE_GAMEPLAY
|
||||
} cutscenemode_t;
|
||||
|
||||
// Default mode for all cutscenes.
|
||||
#define CUTSCENE_MODE_INITIAL CUTSCENE_MODE_INPUT_FREEZE
|
||||
|
||||
/**
|
||||
* Check if input is allowed in the current cutscene mode.
|
||||
*
|
||||
* @return true if input is allowed, false otherwise.
|
||||
*/
|
||||
bool_t cutsceneModeIsInputAllowed();
|
||||
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "dusk.h"
|
||||
|
||||
typedef uint8_t cutscenepause_t;
|
||||
|
||||
#define CUTSCENE_PAUSE_NONE ((cutscenepause_t)0)
|
||||
#define CUTSCENE_PAUSE_NPC ((cutscenepause_t)(1 << 0))
|
||||
#define CUTSCENE_PAUSE_PLAYER ((cutscenepause_t)(1 << 1))
|
||||
#define CUTSCENE_PAUSE_WORLD ((cutscenepause_t)(1 << 2))
|
||||
|
||||
#define CUTSCENE_PAUSE_DEFAULT ((cutscenepause_t)( \
|
||||
CUTSCENE_PAUSE_NPC | CUTSCENE_PAUSE_PLAYER \
|
||||
))
|
||||
|
||||
#define CUTSCENE_PAUSE_ALL ((cutscenepause_t)( \
|
||||
CUTSCENE_PAUSE_NPC | CUTSCENE_PAUSE_PLAYER | CUTSCENE_PAUSE_WORLD \
|
||||
))
|
||||
@@ -1,12 +1,14 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "cutscenesystem.h"
|
||||
#include "rpg/entity/entity.h"
|
||||
#include "util/memory.h"
|
||||
#include "assert/assert.h"
|
||||
|
||||
cutscenesystem_t CUTSCENE_SYSTEM;
|
||||
|
||||
@@ -15,9 +17,27 @@ void cutsceneSystemInit() {
|
||||
}
|
||||
|
||||
void cutsceneSystemStartCutscene(const cutscene_t *cutscene) {
|
||||
cutsceneSystemStartCutsceneWith(cutscene, NULL, NULL);
|
||||
}
|
||||
|
||||
void cutsceneSystemStartCutsceneWith(
|
||||
const cutscene_t *cutscene,
|
||||
entity_t *interact,
|
||||
entity_t *interacted
|
||||
) {
|
||||
assertTrue(
|
||||
cutscene->dataSize < CUTSCENE_SYSTEM_SIZE_MAX,
|
||||
"Cutscene data size exceeds CUTSCENE_SYSTEM_SIZE_MAX"
|
||||
);
|
||||
|
||||
CUTSCENE_SYSTEM.scene = cutscene;
|
||||
CUTSCENE_SYSTEM.mode = CUTSCENE_MODE_INITIAL;
|
||||
CUTSCENE_SYSTEM.currentItem = 0xFF;// Set to 0xFF so start wraps.
|
||||
CUTSCENE_SYSTEM.pause = cutscene->pause;
|
||||
CUTSCENE_SYSTEM.entityInteract = interact;
|
||||
CUTSCENE_SYSTEM.entityInteracted = interacted;
|
||||
CUTSCENE_SYSTEM.entityLastCreated = NULL;
|
||||
CUTSCENE_SYSTEM.entityLastRef = NULL;
|
||||
CUTSCENE_SYSTEM.areaLastCreated = CUTSCENE_AREA_LAST_CREATED;
|
||||
CUTSCENE_SYSTEM.currentItem = 0xFF;// Set to 0xFF so Next wraps to 0.
|
||||
cutsceneSystemNext();
|
||||
}
|
||||
|
||||
@@ -25,7 +45,7 @@ void cutsceneSystemUpdate() {
|
||||
if(CUTSCENE_SYSTEM.scene == NULL) return;
|
||||
|
||||
const cutsceneitem_t *item = cutsceneSystemGetCurrentItem();
|
||||
cutsceneItemUpdate(item, &CUTSCENE_SYSTEM.data);
|
||||
if(cutsceneItemUpdate(item, &CUTSCENE_SYSTEM.data)) cutsceneSystemNext();
|
||||
}
|
||||
|
||||
void cutsceneSystemNext() {
|
||||
@@ -39,7 +59,12 @@ void cutsceneSystemNext() {
|
||||
) {
|
||||
CUTSCENE_SYSTEM.scene = NULL;
|
||||
CUTSCENE_SYSTEM.currentItem = 0xFF;
|
||||
CUTSCENE_SYSTEM.mode = CUTSCENE_MODE_NONE;
|
||||
CUTSCENE_SYSTEM.pause = CUTSCENE_PAUSE_NONE;
|
||||
CUTSCENE_SYSTEM.entityInteract = NULL;
|
||||
CUTSCENE_SYSTEM.entityInteracted = NULL;
|
||||
CUTSCENE_SYSTEM.entityLastCreated = NULL;
|
||||
CUTSCENE_SYSTEM.entityLastRef = NULL;
|
||||
CUTSCENE_SYSTEM.areaLastCreated = CUTSCENE_AREA_LAST_CREATED;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -55,8 +80,63 @@ const cutsceneitem_t * cutsceneSystemGetCurrentItem() {
|
||||
return &CUTSCENE_SYSTEM.scene->items[CUTSCENE_SYSTEM.currentItem];
|
||||
}
|
||||
|
||||
entity_t * cutsceneSystemGetEntity(const uint8_t entityIndex) {
|
||||
entity_t *entity;
|
||||
|
||||
if(entityIndex == CUTSCENE_ENTITY_INTERACT) {
|
||||
assertNotNull(
|
||||
CUTSCENE_SYSTEM.entityInteract,
|
||||
"CUTSCENE_ENTITY_INTERACT used but no interact entity is set"
|
||||
);
|
||||
entity = CUTSCENE_SYSTEM.entityInteract;
|
||||
} else if(entityIndex == CUTSCENE_ENTITY_INTERACTED) {
|
||||
assertNotNull(
|
||||
CUTSCENE_SYSTEM.entityInteracted,
|
||||
"CUTSCENE_ENTITY_INTERACTED used but no interacted entity is set"
|
||||
);
|
||||
entity = CUTSCENE_SYSTEM.entityInteracted;
|
||||
} else if(entityIndex == CUTSCENE_ENTITY_LAST_CREATED) {
|
||||
assertNotNull(
|
||||
CUTSCENE_SYSTEM.entityLastCreated,
|
||||
"CUTSCENE_ENTITY_LAST_CREATED used but no entity has been created"
|
||||
);
|
||||
entity = CUTSCENE_SYSTEM.entityLastCreated;
|
||||
} else if(entityIndex == CUTSCENE_ENTITY_LAST_REF) {
|
||||
assertNotNull(
|
||||
CUTSCENE_SYSTEM.entityLastRef,
|
||||
"CUTSCENE_ENTITY_LAST_REF used but no entity has been referenced"
|
||||
);
|
||||
entity = CUTSCENE_SYSTEM.entityLastRef;
|
||||
} else {
|
||||
assertTrue(
|
||||
entityIndex < ENTITY_COUNT,
|
||||
"Entity index is out of range"
|
||||
);
|
||||
entity = &ENTITIES[entityIndex];
|
||||
}
|
||||
|
||||
CUTSCENE_SYSTEM.entityLastRef = entity;
|
||||
return entity;
|
||||
}
|
||||
|
||||
uint8_t cutsceneSystemGetAreaId(const uint8_t areaId) {
|
||||
if(areaId == CUTSCENE_AREA_LAST_CREATED) {
|
||||
assertTrue(
|
||||
CUTSCENE_SYSTEM.areaLastCreated != CUTSCENE_AREA_LAST_CREATED,
|
||||
"CUTSCENE_AREA_LAST_CREATED used but no map area has been created"
|
||||
);
|
||||
return CUTSCENE_SYSTEM.areaLastCreated;
|
||||
}
|
||||
return areaId;
|
||||
}
|
||||
|
||||
void cutsceneSystemDispose() {
|
||||
CUTSCENE_SYSTEM.scene = NULL;
|
||||
CUTSCENE_SYSTEM.currentItem = 0xFF;
|
||||
CUTSCENE_SYSTEM.mode = CUTSCENE_MODE_NONE;
|
||||
}
|
||||
CUTSCENE_SYSTEM.pause = CUTSCENE_PAUSE_NONE;
|
||||
CUTSCENE_SYSTEM.entityInteract = NULL;
|
||||
CUTSCENE_SYSTEM.entityInteracted = NULL;
|
||||
CUTSCENE_SYSTEM.entityLastCreated = NULL;
|
||||
CUTSCENE_SYSTEM.entityLastRef = NULL;
|
||||
CUTSCENE_SYSTEM.areaLastCreated = CUTSCENE_AREA_LAST_CREATED;
|
||||
}
|
||||
|
||||
@@ -7,15 +7,35 @@
|
||||
|
||||
#pragma once
|
||||
#include "cutscene.h"
|
||||
#include "cutscenemode.h"
|
||||
|
||||
typedef struct entity_s entity_t;
|
||||
|
||||
#define CUTSCENE_ENTITY_INTERACT ((uint8_t)0xFE)
|
||||
#define CUTSCENE_ENTITY_INTERACTED ((uint8_t)0xFD)
|
||||
#define CUTSCENE_ENTITY_LAST_CREATED ((uint8_t)0xFC)
|
||||
#define CUTSCENE_ENTITY_LAST_REF ((uint8_t)0xFB)
|
||||
#define CUTSCENE_AREA_LAST_CREATED ((uint8_t)0xFF)
|
||||
|
||||
// Maximum number of bytes a running cutscene may request via
|
||||
// cutscene_t.dataSize.
|
||||
#define CUTSCENE_SYSTEM_SIZE_MAX 8192
|
||||
|
||||
typedef struct {
|
||||
const cutscene_t *scene;
|
||||
uint8_t currentItem;
|
||||
cutscenepause_t pause;
|
||||
entity_t *entityInteract;
|
||||
entity_t *entityInteracted;
|
||||
entity_t *entityLastCreated;
|
||||
entity_t *entityLastRef;
|
||||
uint8_t areaLastCreated;
|
||||
|
||||
// Data (used by the current item).
|
||||
cutsceneitemdata_t data;
|
||||
cutscenemode_t mode;
|
||||
|
||||
// Custom user data for the running cutscene, sized per-scene by
|
||||
// cutscene_t.dataSize.
|
||||
uint8_t userData[CUTSCENE_SYSTEM_SIZE_MAX];
|
||||
} cutscenesystem_t;
|
||||
|
||||
extern cutscenesystem_t CUTSCENE_SYSTEM;
|
||||
@@ -26,12 +46,46 @@ extern cutscenesystem_t CUTSCENE_SYSTEM;
|
||||
void cutsceneSystemInit();
|
||||
|
||||
/**
|
||||
* Start a cutscene.
|
||||
*
|
||||
* Start a cutscene with no bound entities.
|
||||
*
|
||||
* @param cutscene Pointer to the cutscene to start.
|
||||
*/
|
||||
void cutsceneSystemStartCutscene(const cutscene_t *cutscene);
|
||||
|
||||
/**
|
||||
* Start a cutscene with the two entities that triggered it.
|
||||
*
|
||||
* @param cutscene Pointer to the cutscene to start.
|
||||
* @param interact The entity that initiated the interaction (player).
|
||||
* @param interacted The entity that was interacted with (NPC).
|
||||
*/
|
||||
void cutsceneSystemStartCutsceneWith(
|
||||
const cutscene_t *cutscene,
|
||||
entity_t *interact,
|
||||
entity_t *interacted
|
||||
);
|
||||
|
||||
/**
|
||||
* Resolves a raw entity index (or sentinel) to an entity pointer.
|
||||
* Handles CUTSCENE_ENTITY_INTERACT, CUTSCENE_ENTITY_INTERACTED,
|
||||
* CUTSCENE_ENTITY_LAST_CREATED and CUTSCENE_ENTITY_LAST_REF.
|
||||
* Updates CUTSCENE_SYSTEM.entityLastRef to the resolved entity.
|
||||
* Asserts the resolved entity is within bounds.
|
||||
*
|
||||
* @param entityIndex Raw entity index or sentinel value.
|
||||
* @returns Pointer to the resolved entity.
|
||||
*/
|
||||
entity_t * cutsceneSystemGetEntity(const uint8_t entityIndex);
|
||||
|
||||
/**
|
||||
* Resolves a raw map area ID (or CUTSCENE_AREA_LAST_CREATED sentinel) to
|
||||
* a concrete map area ID.
|
||||
*
|
||||
* @param areaId Raw map area ID or sentinel value.
|
||||
* @returns The resolved map area ID.
|
||||
*/
|
||||
uint8_t cutsceneSystemGetAreaId(const uint8_t areaId);
|
||||
|
||||
/**
|
||||
* Advance to the next item in the cutscene.
|
||||
*/
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
# Copyright (c) 2025 Dominic Masters
|
||||
#
|
||||
#
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
# Sources
|
||||
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||
PUBLIC
|
||||
cutsceneitem.c
|
||||
cutsceneentitymove.c
|
||||
)
|
||||
cutscenecallback.c
|
||||
)
|
||||
|
||||
add_subdirectory(control)
|
||||
add_subdirectory(entity)
|
||||
add_subdirectory(item)
|
||||
add_subdirectory(maparea)
|
||||
add_subdirectory(ui)
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
# 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
|
||||
cutscenewait.c
|
||||
cutscenesetpause.c
|
||||
cutsceneconcurrent.c
|
||||
)
|
||||
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "rpg/cutscene/item/cutsceneitem.h"
|
||||
#include "assert/assert.h"
|
||||
|
||||
void cutsceneConcurrentStart(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
) {
|
||||
assertTrue(
|
||||
item->concurrent.count <= CUTSCENE_CONCURRENT_MAX,
|
||||
"Too many items in CUTSCENE_CONCURRENT"
|
||||
);
|
||||
for(uint8_t i = 0; i < item->concurrent.count; i++) {
|
||||
assertTrue(
|
||||
item->concurrent.items[i].type != CUTSCENE_ITEM_TYPE_CONCURRENT,
|
||||
"Concurrent items cannot be nested"
|
||||
);
|
||||
cutsceneItemStart(
|
||||
&item->concurrent.items[i],
|
||||
(cutsceneitemdata_t *)&data->concurrent.childData[i]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
bool_t cutsceneConcurrentUpdate(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
) {
|
||||
for(uint8_t i = 0; i < item->concurrent.count; i++) {
|
||||
if(data->concurrent.doneMask & (1u << i)) continue;
|
||||
if(cutsceneItemUpdate(
|
||||
&item->concurrent.items[i],
|
||||
(cutsceneitemdata_t *)&data->concurrent.childData[i]
|
||||
)) {
|
||||
data->concurrent.doneMask |= (uint8_t)(1u << i);
|
||||
}
|
||||
}
|
||||
uint8_t allDone = (uint8_t)((1u << item->concurrent.count) - 1u);
|
||||
return data->concurrent.doneMask == allDone;
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "cutscenewait.h"
|
||||
#include "rpg/cutscene/item/entity/cutsceneentitywalkto.h"
|
||||
|
||||
typedef struct cutsceneitem_s cutsceneitem_t;
|
||||
typedef union cutsceneitemdata_u cutsceneitemdata_t;
|
||||
|
||||
/** Maximum number of items that may run inside a CUTSCENE_CONCURRENT. */
|
||||
#define CUTSCENE_CONCURRENT_MAX 8
|
||||
|
||||
/**
|
||||
* Static (const) data for a concurrent cutscene item.
|
||||
*/
|
||||
typedef struct {
|
||||
const cutsceneitem_t *items;
|
||||
uint8_t count;
|
||||
} cutsceneconcurrent_t;
|
||||
|
||||
/**
|
||||
* Runtime data for one non-concurrent child item.
|
||||
* Concurrent items cannot be nested.
|
||||
*/
|
||||
typedef union {
|
||||
cutscenewaitdata_t wait;
|
||||
cutsceneentitywalktodata_t entityWalkTo;
|
||||
} cutsceneconcurrentchilddata_t;
|
||||
|
||||
/** Runtime data for a running concurrent item. */
|
||||
typedef struct {
|
||||
cutsceneconcurrentchilddata_t childData[CUTSCENE_CONCURRENT_MAX];
|
||||
uint8_t doneMask;
|
||||
} cutsceneconcurrentdata_t;
|
||||
|
||||
/**
|
||||
* Starts a concurrent item (starts all child items simultaneously).
|
||||
*
|
||||
* @param item The cutscene item.
|
||||
* @param data Runtime data storage.
|
||||
*/
|
||||
void cutsceneConcurrentStart(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
);
|
||||
|
||||
/**
|
||||
* Updates a concurrent item (ticks all unfinished children).
|
||||
*
|
||||
* @param item The cutscene item.
|
||||
* @param data Runtime data storage.
|
||||
* @returns true once every child has completed.
|
||||
*/
|
||||
bool_t cutsceneConcurrentUpdate(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
);
|
||||
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "rpg/cutscene/item/cutsceneitem.h"
|
||||
#include "rpg/cutscene/cutscenesystem.h"
|
||||
|
||||
void cutsceneSetPauseStart(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
) {
|
||||
CUTSCENE_SYSTEM.pause = item->setPause;
|
||||
}
|
||||
|
||||
bool_t cutsceneSetPauseUpdate(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "rpg/cutscene/cutscenepause.h"
|
||||
|
||||
typedef struct cutsceneitem_s cutsceneitem_t;
|
||||
typedef union cutsceneitemdata_u cutsceneitemdata_t;
|
||||
|
||||
/**
|
||||
* Starts a set-pause item (applies the new pause flags immediately).
|
||||
*
|
||||
* @param item The cutscene item.
|
||||
* @param data Runtime data storage.
|
||||
*/
|
||||
void cutsceneSetPauseStart(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
);
|
||||
|
||||
/**
|
||||
* Updates a set-pause item (always completes immediately).
|
||||
*
|
||||
* @param item The cutscene item.
|
||||
* @param data Runtime data storage.
|
||||
* @returns true always.
|
||||
*/
|
||||
bool_t cutsceneSetPauseUpdate(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
);
|
||||
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "rpg/cutscene/item/cutsceneitem.h"
|
||||
#include "time/time.h"
|
||||
|
||||
void cutsceneWaitStart(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
) {
|
||||
data->wait = item->wait;
|
||||
}
|
||||
|
||||
bool_t cutsceneWaitUpdate(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
) {
|
||||
data->wait -= TIME.delta;
|
||||
return data->wait <= 0;
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* 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 cutsceneitem_s cutsceneitem_t;
|
||||
typedef union cutsceneitemdata_u cutsceneitemdata_t;
|
||||
|
||||
typedef float_t cutscenewait_t;
|
||||
typedef float_t cutscenewaitdata_t;
|
||||
|
||||
/**
|
||||
* Starts a wait item (stores the duration in data).
|
||||
*
|
||||
* @param item The cutscene item.
|
||||
* @param data Runtime data storage.
|
||||
*/
|
||||
void cutsceneWaitStart(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
);
|
||||
|
||||
/**
|
||||
* Updates a wait item.
|
||||
*
|
||||
* @param item The cutscene item.
|
||||
* @param data Runtime data storage.
|
||||
* @returns true when the wait has elapsed.
|
||||
*/
|
||||
bool_t cutsceneWaitUpdate(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
);
|
||||
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "cutsceneitem.h"
|
||||
#include "rpg/cutscene/cutscenesystem.h"
|
||||
|
||||
void cutsceneCallbackStart(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
) {
|
||||
if(item->callback != NULL) item->callback(CUTSCENE_SYSTEM.userData);
|
||||
}
|
||||
|
||||
bool_t cutsceneCallbackUpdate(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
@@ -8,4 +8,30 @@
|
||||
#pragma once
|
||||
#include "dusk.h"
|
||||
|
||||
typedef void (*cutscenecallback_t)(void);
|
||||
typedef struct cutsceneitem_s cutsceneitem_t;
|
||||
typedef union cutsceneitemdata_u cutsceneitemdata_t;
|
||||
|
||||
typedef void (*cutscenecallback_t)(void *userData);
|
||||
|
||||
/**
|
||||
* Starts a callback item (invokes the callback immediately).
|
||||
*
|
||||
* @param item The cutscene item.
|
||||
* @param data Runtime data storage.
|
||||
*/
|
||||
void cutsceneCallbackStart(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
);
|
||||
|
||||
/**
|
||||
* Updates a callback item (always completes immediately).
|
||||
*
|
||||
* @param item The cutscene item.
|
||||
* @param data Runtime data storage.
|
||||
* @returns true always.
|
||||
*/
|
||||
bool_t cutsceneCallbackUpdate(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
);
|
||||
@@ -1,39 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "cutsceneentitymove.h"
|
||||
#include "rpg/cutscene/cutscenesystem.h"
|
||||
#include "rpg/entity/entity.h"
|
||||
|
||||
void cutsceneEntityMoveStart(const cutsceneitem_t *item) {
|
||||
}
|
||||
|
||||
void cutsceneEntityMoveUpdate(const cutsceneitem_t *item) {
|
||||
entity_t *entity = &ENTITIES[item->entityMove.entityIndex];
|
||||
|
||||
if(worldPosIsEqual(entity->position, item->entityMove.target)) {
|
||||
cutsceneSystemNext();
|
||||
return;
|
||||
}
|
||||
|
||||
entitydir_t dir;
|
||||
if(entity->position.x != item->entityMove.target.x) {
|
||||
dir = entity->position.x < item->entityMove.target.x
|
||||
? ENTITY_DIR_EAST : ENTITY_DIR_WEST;
|
||||
} else {
|
||||
dir = entity->position.y < item->entityMove.target.y
|
||||
? ENTITY_DIR_SOUTH : ENTITY_DIR_NORTH;
|
||||
}
|
||||
|
||||
if(entityCanTurn(entity)) entityTurn(entity, dir);
|
||||
|
||||
if(item->entityMove.run) {
|
||||
if(entityCanRun(entity)) entityRun(entity, dir);
|
||||
} else {
|
||||
if(entityCanWalk(entity)) entityWalk(entity, dir);
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "cutsceneitem.h"
|
||||
|
||||
/**
|
||||
* Handles the start of an entity move cutscene item.
|
||||
*
|
||||
* @param item The cutscene item.
|
||||
*/
|
||||
void cutsceneEntityMoveStart(const cutsceneitem_t *item);
|
||||
|
||||
/**
|
||||
* Updates an entity move cutscene item, steering the entity one step
|
||||
* per frame toward the target and advancing the cutscene on arrival.
|
||||
*
|
||||
* @param item The cutscene item.
|
||||
*/
|
||||
void cutsceneEntityMoveUpdate(const cutsceneitem_t *item);
|
||||
@@ -1,39 +1,80 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "rpg/cutscene/cutscenesystem.h"
|
||||
#include "cutsceneentitymove.h"
|
||||
#include "input/input.h"
|
||||
#include "time/time.h"
|
||||
|
||||
void cutsceneItemStart(const cutsceneitem_t *item, cutsceneitemdata_t *data) {
|
||||
switch(item->type) {
|
||||
case CUTSCENE_ITEM_TYPE_TEXT: {
|
||||
rpgTextboxShow(
|
||||
item->text.position,
|
||||
item->text.text
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
case CUTSCENE_ITEM_TYPE_WAIT:
|
||||
data->wait = item->wait;
|
||||
case CUTSCENE_ITEM_TYPE_TEXT:
|
||||
cutsceneTextStart(item, data);
|
||||
break;
|
||||
|
||||
case CUTSCENE_ITEM_TYPE_CALLBACK:
|
||||
if(item->callback != NULL) item->callback();
|
||||
cutsceneCallbackStart(item, data);
|
||||
break;
|
||||
|
||||
case CUTSCENE_ITEM_TYPE_WAIT:
|
||||
cutsceneWaitStart(item, data);
|
||||
break;
|
||||
|
||||
case CUTSCENE_ITEM_TYPE_CUTSCENE:
|
||||
if(item->cutscene != NULL) cutsceneSystemStartCutscene(item->cutscene);
|
||||
break;
|
||||
|
||||
case CUTSCENE_ITEM_TYPE_ENTITY_MOVE:
|
||||
cutsceneEntityMoveStart(item);
|
||||
case CUTSCENE_ITEM_TYPE_ENTITY_TELEPORT:
|
||||
cutsceneEntityTeleportStart(item, data);
|
||||
break;
|
||||
|
||||
case CUTSCENE_ITEM_TYPE_ENTITY_WALK_TO:
|
||||
cutsceneEntityWalkToStart(item, data);
|
||||
break;
|
||||
|
||||
case CUTSCENE_ITEM_TYPE_FADE:
|
||||
cutsceneFadeStart(item, data);
|
||||
break;
|
||||
|
||||
case CUTSCENE_ITEM_TYPE_SET_PAUSE:
|
||||
cutsceneSetPauseStart(item, data);
|
||||
break;
|
||||
|
||||
case CUTSCENE_ITEM_TYPE_CONCURRENT:
|
||||
cutsceneConcurrentStart(item, data);
|
||||
break;
|
||||
|
||||
case CUTSCENE_ITEM_TYPE_ITEM_GIVE:
|
||||
cutsceneItemGiveStart(item, data);
|
||||
break;
|
||||
|
||||
case CUTSCENE_ITEM_TYPE_ENTITY_REMOVE:
|
||||
cutsceneEntityRemoveStart(item, data);
|
||||
break;
|
||||
|
||||
case CUTSCENE_ITEM_TYPE_ENTITY_ADD:
|
||||
cutsceneEntityAddStart(item, data);
|
||||
break;
|
||||
|
||||
case CUTSCENE_ITEM_TYPE_ENTITY_TURN:
|
||||
cutsceneEntityTurnStart(item, data);
|
||||
break;
|
||||
|
||||
case CUTSCENE_ITEM_TYPE_ENTITY_WALK_TO_ENTITY:
|
||||
cutsceneEntityWalkToEntityStart(item, data);
|
||||
break;
|
||||
|
||||
case CUTSCENE_ITEM_TYPE_MAP_AREA_ADD:
|
||||
cutsceneMapAreaAddStart(item, data);
|
||||
break;
|
||||
|
||||
case CUTSCENE_ITEM_TYPE_MAP_AREA_REMOVE:
|
||||
cutsceneMapAreaRemoveStart(item, data);
|
||||
break;
|
||||
|
||||
case CUTSCENE_ITEM_TYPE_MAP_AREA_WAIT:
|
||||
cutsceneMapAreaWaitStart(item, data);
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -41,23 +82,57 @@ void cutsceneItemStart(const cutsceneitem_t *item, cutsceneitemdata_t *data) {
|
||||
}
|
||||
}
|
||||
|
||||
void cutsceneItemUpdate(const cutsceneitem_t *item, cutsceneitemdata_t *data) {
|
||||
bool_t cutsceneItemUpdate(const cutsceneitem_t *item, cutsceneitemdata_t *data) {
|
||||
switch(item->type) {
|
||||
case CUTSCENE_ITEM_TYPE_TEXT:
|
||||
if(rpgTextboxIsVisible()) return;
|
||||
cutsceneSystemNext();
|
||||
break;
|
||||
return cutsceneTextUpdate(item, data);
|
||||
|
||||
case CUTSCENE_ITEM_TYPE_CALLBACK:
|
||||
return cutsceneCallbackUpdate(item, data);
|
||||
|
||||
case CUTSCENE_ITEM_TYPE_WAIT:
|
||||
data->wait -= TIME.delta;
|
||||
if(data->wait <= 0) cutsceneSystemNext();
|
||||
break;
|
||||
return cutsceneWaitUpdate(item, data);
|
||||
|
||||
case CUTSCENE_ITEM_TYPE_ENTITY_MOVE:
|
||||
cutsceneEntityMoveUpdate(item);
|
||||
break;
|
||||
case CUTSCENE_ITEM_TYPE_ENTITY_TELEPORT:
|
||||
return cutsceneEntityTeleportUpdate(item, data);
|
||||
|
||||
case CUTSCENE_ITEM_TYPE_ENTITY_WALK_TO:
|
||||
return cutsceneEntityWalkToUpdate(item, data);
|
||||
|
||||
case CUTSCENE_ITEM_TYPE_FADE:
|
||||
return cutsceneFadeUpdate(item, data);
|
||||
|
||||
case CUTSCENE_ITEM_TYPE_SET_PAUSE:
|
||||
return cutsceneSetPauseUpdate(item, data);
|
||||
|
||||
case CUTSCENE_ITEM_TYPE_CONCURRENT:
|
||||
return cutsceneConcurrentUpdate(item, data);
|
||||
|
||||
case CUTSCENE_ITEM_TYPE_ITEM_GIVE:
|
||||
return cutsceneItemGiveUpdate(item, data);
|
||||
|
||||
case CUTSCENE_ITEM_TYPE_ENTITY_REMOVE:
|
||||
return cutsceneEntityRemoveUpdate(item, data);
|
||||
|
||||
case CUTSCENE_ITEM_TYPE_ENTITY_ADD:
|
||||
return cutsceneEntityAddUpdate(item, data);
|
||||
|
||||
case CUTSCENE_ITEM_TYPE_ENTITY_TURN:
|
||||
return cutsceneEntityTurnUpdate(item, data);
|
||||
|
||||
case CUTSCENE_ITEM_TYPE_ENTITY_WALK_TO_ENTITY:
|
||||
return cutsceneEntityWalkToEntityUpdate(item, data);
|
||||
|
||||
case CUTSCENE_ITEM_TYPE_MAP_AREA_ADD:
|
||||
return cutsceneMapAreaAddUpdate(item, data);
|
||||
|
||||
case CUTSCENE_ITEM_TYPE_MAP_AREA_REMOVE:
|
||||
return cutsceneMapAreaRemoveUpdate(item, data);
|
||||
|
||||
case CUTSCENE_ITEM_TYPE_MAP_AREA_WAIT:
|
||||
return cutsceneMapAreaWaitUpdate(item, data);
|
||||
|
||||
default:
|
||||
break;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,27 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "cutscenewait.h"
|
||||
#include "cutscenecallback.h"
|
||||
#include "cutscenetext.h"
|
||||
#include "rpg/overworld/worldpos.h"
|
||||
#include "control/cutscenewait.h"
|
||||
#include "control/cutscenesetpause.h"
|
||||
#include "control/cutsceneconcurrent.h"
|
||||
#include "entity/cutsceneentityteleport.h"
|
||||
#include "entity/cutsceneentitywalkto.h"
|
||||
#include "entity/cutsceneentityremove.h"
|
||||
#include "entity/cutsceneentityadd.h"
|
||||
#include "entity/cutsceneentityturn.h"
|
||||
#include "entity/cutsceneentitywalktoentity.h"
|
||||
#include "ui/cutscenetext.h"
|
||||
#include "ui/cutscenefade.h"
|
||||
#include "item/cutsceneitemgive.h"
|
||||
#include "maparea/cutscenemapareaadd.h"
|
||||
#include "maparea/cutscenemaparearemove.h"
|
||||
#include "maparea/cutscenemapareawait.h"
|
||||
|
||||
typedef struct cutscene_s cutscene_t;
|
||||
|
||||
@@ -19,42 +31,71 @@ typedef enum {
|
||||
CUTSCENE_ITEM_TYPE_CALLBACK,
|
||||
CUTSCENE_ITEM_TYPE_WAIT,
|
||||
CUTSCENE_ITEM_TYPE_CUTSCENE,
|
||||
CUTSCENE_ITEM_TYPE_ENTITY_MOVE
|
||||
CUTSCENE_ITEM_TYPE_ENTITY_TELEPORT,
|
||||
CUTSCENE_ITEM_TYPE_ENTITY_WALK_TO,
|
||||
CUTSCENE_ITEM_TYPE_FADE,
|
||||
CUTSCENE_ITEM_TYPE_SET_PAUSE,
|
||||
CUTSCENE_ITEM_TYPE_CONCURRENT,
|
||||
CUTSCENE_ITEM_TYPE_ITEM_GIVE,
|
||||
CUTSCENE_ITEM_TYPE_ENTITY_REMOVE,
|
||||
CUTSCENE_ITEM_TYPE_ENTITY_ADD,
|
||||
CUTSCENE_ITEM_TYPE_ENTITY_TURN,
|
||||
CUTSCENE_ITEM_TYPE_ENTITY_WALK_TO_ENTITY,
|
||||
CUTSCENE_ITEM_TYPE_MAP_AREA_ADD,
|
||||
CUTSCENE_ITEM_TYPE_MAP_AREA_REMOVE,
|
||||
CUTSCENE_ITEM_TYPE_MAP_AREA_WAIT
|
||||
} cutsceneitemtype_t;
|
||||
|
||||
typedef struct cutsceneitem_s {
|
||||
struct cutsceneitem_s {
|
||||
cutsceneitemtype_t type;
|
||||
|
||||
// Arguments/Data that will be used when this item is invoked.
|
||||
union {
|
||||
cutscenetext_t text;
|
||||
cutscenecallback_t callback;
|
||||
cutscenewait_t wait;
|
||||
const cutscene_t *cutscene;
|
||||
struct {
|
||||
uint8_t entityIndex;
|
||||
worldpos_t target;
|
||||
bool_t run;
|
||||
} entityMove;
|
||||
cutsceneentityteleport_t entityTeleport;
|
||||
cutsceneentitywalkto_t entityWalkTo;
|
||||
cutscenefade_t fade;
|
||||
cutscenepause_t setPause;
|
||||
cutsceneconcurrent_t concurrent;
|
||||
cutsceneitemgive_t itemGive;
|
||||
cutsceneentityremove_t entityRemove;
|
||||
cutsceneentityadd_t entityAdd;
|
||||
cutsceneentityturn_t entityTurn;
|
||||
cutsceneentitywalktoentity_t entityWalkToEntity;
|
||||
cutscenemapareaadd_t mapAreaAdd;
|
||||
cutscenemaparearemove_t mapAreaRemove;
|
||||
cutscenemapareawait_t mapAreaWait;
|
||||
};
|
||||
} cutsceneitem_t;
|
||||
};
|
||||
|
||||
typedef union {
|
||||
typedef union cutsceneitemdata_u {
|
||||
cutscenewaitdata_t wait;
|
||||
cutsceneentitywalktodata_t entityWalkTo;
|
||||
cutsceneconcurrentdata_t concurrent;
|
||||
cutscenemapareawaitdata_t mapAreaWait;
|
||||
} cutsceneitemdata_t;
|
||||
|
||||
/**
|
||||
* Start the given cutscene item.
|
||||
*
|
||||
*
|
||||
* @param item The cutscene item to start.
|
||||
* @param data The cutscene item data storage.
|
||||
* @param data Runtime data storage (pre-zeroed by caller).
|
||||
*/
|
||||
void cutsceneItemStart(const cutsceneitem_t *item, cutsceneitemdata_t *data);
|
||||
void cutsceneItemStart(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
);
|
||||
|
||||
/**
|
||||
* Tick the given cutscene item (one frame).
|
||||
*
|
||||
*
|
||||
* @param item The cutscene item to tick.
|
||||
* @param data The cutscene item data storage.
|
||||
* @param data Runtime data storage.
|
||||
* @returns true if the item is complete and the cutscene should advance.
|
||||
*/
|
||||
void cutsceneItemUpdate(const cutsceneitem_t *item, cutsceneitemdata_t *data);
|
||||
bool_t cutsceneItemUpdate(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
);
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "rpg/rpgtextbox.h"
|
||||
|
||||
typedef struct {
|
||||
char_t text[RPG_TEXTBOX_MAX_CHARS];
|
||||
rpgtextboxpos_t position;
|
||||
} cutscenetext_t;
|
||||
@@ -1,12 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "dusk.h"
|
||||
|
||||
typedef float_t cutscenewait_t;
|
||||
typedef float_t cutscenewaitdata_t;
|
||||
@@ -0,0 +1,14 @@
|
||||
# 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
|
||||
cutsceneentityteleport.c
|
||||
cutsceneentitywalkto.c
|
||||
cutsceneentityremove.c
|
||||
cutsceneentityadd.c
|
||||
cutsceneentityturn.c
|
||||
cutsceneentitywalktoentity.c
|
||||
)
|
||||
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "rpg/cutscene/item/cutsceneitem.h"
|
||||
#include "rpg/cutscene/cutscenesystem.h"
|
||||
#include "rpg/entity/entity.h"
|
||||
#include "assert/assert.h"
|
||||
|
||||
void cutsceneEntityAddStart(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
) {
|
||||
uint8_t entIndex = entityGetAvailable();
|
||||
assertTrue(entIndex != 0xFF, "No available entity slots for CUTSCENE_ENTITY_ADD");
|
||||
|
||||
entity_t *entity = &ENTITIES[entIndex];
|
||||
entityInit(entity, item->entityAdd.entityType);
|
||||
entityPositionSet(entity, item->entityAdd.position);// Also assigns chunk.
|
||||
|
||||
CUTSCENE_SYSTEM.entityLastCreated = entity;
|
||||
CUTSCENE_SYSTEM.entityLastRef = entity;
|
||||
}
|
||||
|
||||
bool_t cutsceneEntityAddUpdate(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "rpg/entity/entitytype.h"
|
||||
#include "rpg/overworld/worldpos.h"
|
||||
|
||||
typedef struct cutsceneitem_s cutsceneitem_t;
|
||||
typedef union cutsceneitemdata_u cutsceneitemdata_t;
|
||||
|
||||
typedef struct {
|
||||
entitytype_t entityType;
|
||||
worldpos_t position;
|
||||
} cutsceneentityadd_t;
|
||||
|
||||
/**
|
||||
* Starts an entity add step (spawns the entity into the world immediately).
|
||||
*
|
||||
* @param item The cutscene item.
|
||||
* @param data Runtime data storage.
|
||||
*/
|
||||
void cutsceneEntityAddStart(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
);
|
||||
|
||||
/**
|
||||
* Updates an entity add step (always completes immediately).
|
||||
*
|
||||
* @param item The cutscene item.
|
||||
* @param data Runtime data storage.
|
||||
* @returns true always.
|
||||
*/
|
||||
bool_t cutsceneEntityAddUpdate(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
);
|
||||
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "rpg/cutscene/item/cutsceneitem.h"
|
||||
#include "rpg/cutscene/cutscenesystem.h"
|
||||
#include "rpg/entity/entity.h"
|
||||
|
||||
void cutsceneEntityRemoveStart(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
) {
|
||||
cutsceneSystemGetEntity(item->entityRemove.entityIndex)->type = \
|
||||
ENTITY_TYPE_NULL;
|
||||
}
|
||||
|
||||
bool_t cutsceneEntityRemoveUpdate(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* 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 cutsceneitem_s cutsceneitem_t;
|
||||
typedef union cutsceneitemdata_u cutsceneitemdata_t;
|
||||
|
||||
typedef struct {
|
||||
uint8_t entityIndex;
|
||||
} cutsceneentityremove_t;
|
||||
|
||||
/**
|
||||
* Starts an entity remove step (removes the entity from the world immediately).
|
||||
*
|
||||
* @param item The cutscene item.
|
||||
* @param data Runtime data storage.
|
||||
*/
|
||||
void cutsceneEntityRemoveStart(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
);
|
||||
|
||||
/**
|
||||
* Updates an entity remove step (always completes immediately).
|
||||
*
|
||||
* @param item The cutscene item.
|
||||
* @param data Runtime data storage.
|
||||
* @returns true always.
|
||||
*/
|
||||
bool_t cutsceneEntityRemoveUpdate(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
);
|
||||
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "rpg/cutscene/item/cutsceneitem.h"
|
||||
#include "rpg/cutscene/cutscenesystem.h"
|
||||
#include "rpg/entity/entity.h"
|
||||
|
||||
void cutsceneEntityTeleportStart(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
) {
|
||||
entityPositionSet(
|
||||
cutsceneSystemGetEntity(item->entityTeleport.entityIndex),
|
||||
item->entityTeleport.target
|
||||
);
|
||||
}
|
||||
|
||||
bool_t cutsceneEntityTeleportUpdate(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "rpg/overworld/worldpos.h"
|
||||
|
||||
typedef struct cutsceneitem_s cutsceneitem_t;
|
||||
typedef union cutsceneitemdata_u cutsceneitemdata_t;
|
||||
|
||||
typedef struct {
|
||||
uint8_t entityIndex;
|
||||
worldpos_t target;
|
||||
} cutsceneentityteleport_t;
|
||||
|
||||
/**
|
||||
* Starts an entity teleport item (teleports the entity immediately).
|
||||
*
|
||||
* @param item The cutscene item.
|
||||
* @param data Runtime data storage.
|
||||
*/
|
||||
void cutsceneEntityTeleportStart(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
);
|
||||
|
||||
/**
|
||||
* Updates an entity teleport item (always completes immediately).
|
||||
*
|
||||
* @param item The cutscene item.
|
||||
* @param data Runtime data storage.
|
||||
* @returns true always.
|
||||
*/
|
||||
bool_t cutsceneEntityTeleportUpdate(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
);
|
||||
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "rpg/cutscene/item/cutsceneitem.h"
|
||||
#include "rpg/cutscene/cutscenesystem.h"
|
||||
#include "rpg/entity/entity.h"
|
||||
|
||||
void cutsceneEntityTurnStart(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
) {
|
||||
}
|
||||
|
||||
bool_t cutsceneEntityTurnUpdate(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
) {
|
||||
entity_t *entity = cutsceneSystemGetEntity(item->entityTurn.entityIndex);
|
||||
if(
|
||||
entity->direction == item->entityTurn.direction &&
|
||||
entity->animation == ENTITY_ANIM_IDLE
|
||||
) return true;
|
||||
|
||||
entityTurn(entity, item->entityTurn.direction);
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "rpg/entity/entitydir.h"
|
||||
|
||||
typedef struct cutsceneitem_s cutsceneitem_t;
|
||||
typedef union cutsceneitemdata_u cutsceneitemdata_t;
|
||||
|
||||
typedef struct {
|
||||
uint8_t entityIndex;
|
||||
entitydir_t direction;
|
||||
} cutsceneentityturn_t;
|
||||
|
||||
/**
|
||||
* Starts an entity turn step. The turn itself is driven from Update, since
|
||||
* the entity may still be finishing a previous action.
|
||||
*
|
||||
* @param item The cutscene item.
|
||||
* @param data Runtime data storage.
|
||||
*/
|
||||
void cutsceneEntityTurnStart(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
);
|
||||
|
||||
/**
|
||||
* Updates an entity turn step, retrying entityTurn until it takes effect
|
||||
* and its animation completes.
|
||||
*
|
||||
* @param item The cutscene item.
|
||||
* @param data Runtime data storage.
|
||||
* @returns true once the entity is idle and facing the target direction.
|
||||
*/
|
||||
bool_t cutsceneEntityTurnUpdate(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
);
|
||||
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "rpg/cutscene/item/cutsceneitem.h"
|
||||
#include "rpg/cutscene/cutscenesystem.h"
|
||||
#include "rpg/entity/entitypathstep.h"
|
||||
|
||||
void cutsceneEntityWalkToStart(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
) {
|
||||
data->entityWalkTo.currentIndex = 0;
|
||||
}
|
||||
|
||||
bool_t cutsceneEntityWalkToUpdate(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
) {
|
||||
uint8_t i = data->entityWalkTo.currentIndex;
|
||||
entity_t *e = cutsceneSystemGetEntity(item->entityWalkTo.entityIndex);
|
||||
if(!entityPathStep(
|
||||
e,
|
||||
item->entityWalkTo.positions[i],
|
||||
item->entityWalkTo.walkAround
|
||||
)) return false;
|
||||
i++;
|
||||
if(i < item->entityWalkTo.count) {
|
||||
data->entityWalkTo.currentIndex = i;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "rpg/overworld/worldpos.h"
|
||||
|
||||
typedef struct cutsceneitem_s cutsceneitem_t;
|
||||
typedef union cutsceneitemdata_u cutsceneitemdata_t;
|
||||
|
||||
typedef struct {
|
||||
uint8_t entityIndex;
|
||||
const worldpos_t *positions;
|
||||
uint8_t count;
|
||||
bool_t walkAround;
|
||||
} cutsceneentitywalkto_t;
|
||||
|
||||
typedef struct {
|
||||
uint8_t currentIndex;
|
||||
} cutsceneentitywalktodata_t;
|
||||
|
||||
/**
|
||||
* Starts an entity walk-to item (resets the waypoint index).
|
||||
*
|
||||
* @param item The cutscene item.
|
||||
* @param data Runtime data storage.
|
||||
*/
|
||||
void cutsceneEntityWalkToStart(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
);
|
||||
|
||||
/**
|
||||
* Updates an entity walk-to item (steps the entity toward the next waypoint).
|
||||
*
|
||||
* @param item The cutscene item.
|
||||
* @param data Runtime data storage.
|
||||
* @returns true once all waypoints have been reached.
|
||||
*/
|
||||
bool_t cutsceneEntityWalkToUpdate(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
);
|
||||
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "rpg/cutscene/item/cutsceneitem.h"
|
||||
#include "rpg/cutscene/cutscenesystem.h"
|
||||
#include "rpg/entity/entity.h"
|
||||
#include "rpg/entity/entitypathstep.h"
|
||||
#include "rpg/overworld/map.h"
|
||||
|
||||
void cutsceneEntityWalkToEntityStart(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
) {
|
||||
}
|
||||
|
||||
bool_t cutsceneEntityWalkToEntityUpdate(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
) {
|
||||
entity_t *entity = cutsceneSystemGetEntity(
|
||||
item->entityWalkToEntity.entityIndex
|
||||
);
|
||||
entity_t *target = cutsceneSystemGetEntity(
|
||||
item->entityWalkToEntity.targetEntityIndex
|
||||
);
|
||||
|
||||
worldpos_t dest = {
|
||||
.x = (worldunit_t)(target->position.x + item->entityWalkToEntity.offsetX),
|
||||
.y = (worldunit_t)(target->position.y + item->entityWalkToEntity.offsetY),
|
||||
.z = target->position.z
|
||||
};
|
||||
|
||||
worldunit_t z;
|
||||
if(mapGetWalkableZNear(dest.x, dest.y, target->position.z, &z)) dest.z = z;
|
||||
|
||||
return entityPathStep(entity, dest, true);
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "rpg/overworld/worldpos.h"
|
||||
|
||||
typedef struct cutsceneitem_s cutsceneitem_t;
|
||||
typedef union cutsceneitemdata_u cutsceneitemdata_t;
|
||||
|
||||
typedef struct {
|
||||
uint8_t entityIndex;
|
||||
uint8_t targetEntityIndex;
|
||||
worldunit_t offsetX;
|
||||
worldunit_t offsetY;
|
||||
} cutsceneentitywalktoentity_t;
|
||||
|
||||
/**
|
||||
* Starts an entity walk-to-entity item. No setup is needed, the destination
|
||||
* is recomputed from the target's live position every Update.
|
||||
*
|
||||
* @param item The cutscene item.
|
||||
* @param data Runtime data storage.
|
||||
*/
|
||||
void cutsceneEntityWalkToEntityStart(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
);
|
||||
|
||||
/**
|
||||
* Updates an entity walk-to-entity item. Re-reads the target entity's
|
||||
* current position each frame, applies the X/Y offset, resolves the
|
||||
* destination Z from nearby terrain (to account for ramps), and steps
|
||||
* the entity toward it.
|
||||
*
|
||||
* @param item The cutscene item.
|
||||
* @param data Runtime data storage.
|
||||
* @returns true once the entity has reached the target's side.
|
||||
*/
|
||||
bool_t cutsceneEntityWalkToEntityUpdate(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
);
|
||||
@@ -0,0 +1,9 @@
|
||||
# Copyright (c) 2026 Dominic Masters
|
||||
#
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||
PUBLIC
|
||||
cutsceneitemgive.c
|
||||
)
|
||||
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "rpg/cutscene/item/cutsceneitem.h"
|
||||
#include "rpg/item/itemgive.h"
|
||||
#include "ui/rpg/uitextboxmain.h"
|
||||
|
||||
void cutsceneItemGiveStart(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
) {
|
||||
itemGive(item->itemGive.item, item->itemGive.quantity);
|
||||
}
|
||||
|
||||
bool_t cutsceneItemGiveUpdate(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
) {
|
||||
return !uiTextboxMainIsActive();
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "rpg/item/item.h"
|
||||
|
||||
typedef struct cutsceneitem_s cutsceneitem_t;
|
||||
typedef union cutsceneitemdata_u cutsceneitemdata_t;
|
||||
|
||||
typedef struct {
|
||||
itemid_t item;
|
||||
uint8_t quantity;
|
||||
} cutsceneitemgive_t;
|
||||
|
||||
/**
|
||||
* Starts a give-item step (adds the item to the player's backpack immediately).
|
||||
*
|
||||
* @param item The cutscene item.
|
||||
* @param data Runtime data storage.
|
||||
*/
|
||||
void cutsceneItemGiveStart(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
);
|
||||
|
||||
/**
|
||||
* Updates a give-item step (always completes immediately).
|
||||
*
|
||||
* @param item The cutscene item.
|
||||
* @param data Runtime data storage.
|
||||
* @returns true always.
|
||||
*/
|
||||
bool_t cutsceneItemGiveUpdate(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
);
|
||||
@@ -0,0 +1,11 @@
|
||||
# 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
|
||||
cutscenemapareaadd.c
|
||||
cutscenemaparearemove.c
|
||||
cutscenemapareawait.c
|
||||
)
|
||||
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "rpg/cutscene/item/cutsceneitem.h"
|
||||
#include "rpg/cutscene/cutscenesystem.h"
|
||||
|
||||
void cutsceneMapAreaAddStart(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
) {
|
||||
CUTSCENE_SYSTEM.areaLastCreated = mapAreaAdd(
|
||||
item->mapAreaAdd.min,
|
||||
item->mapAreaAdd.max,
|
||||
item->mapAreaAdd.callback,
|
||||
item->mapAreaAdd.notify,
|
||||
item->mapAreaAdd.trigger
|
||||
);
|
||||
}
|
||||
|
||||
bool_t cutsceneMapAreaAddUpdate(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "rpg/overworld/maparea.h"
|
||||
|
||||
typedef struct cutsceneitem_s cutsceneitem_t;
|
||||
typedef union cutsceneitemdata_u cutsceneitemdata_t;
|
||||
|
||||
typedef struct {
|
||||
worldpos_t min;
|
||||
worldpos_t max;
|
||||
mapareacallback_t callback;
|
||||
uint8_t notify;
|
||||
uint8_t trigger;
|
||||
} cutscenemapareaadd_t;
|
||||
|
||||
/**
|
||||
* Starts a map area add step (adds the area immediately, storing its ID
|
||||
* in CUTSCENE_SYSTEM.areaLastCreated).
|
||||
*
|
||||
* @param item The cutscene item.
|
||||
* @param data Runtime data storage.
|
||||
*/
|
||||
void cutsceneMapAreaAddStart(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
);
|
||||
|
||||
/**
|
||||
* Updates a map area add step (always completes immediately).
|
||||
*
|
||||
* @param item The cutscene item.
|
||||
* @param data Runtime data storage.
|
||||
* @returns true always.
|
||||
*/
|
||||
bool_t cutsceneMapAreaAddUpdate(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
);
|
||||
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "rpg/cutscene/item/cutsceneitem.h"
|
||||
#include "rpg/cutscene/cutscenesystem.h"
|
||||
#include "rpg/overworld/maparea.h"
|
||||
|
||||
void cutsceneMapAreaRemoveStart(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
) {
|
||||
mapAreaRemove(cutsceneSystemGetAreaId(item->mapAreaRemove.areaId));
|
||||
}
|
||||
|
||||
bool_t cutsceneMapAreaRemoveUpdate(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "dusk.h"
|
||||
|
||||
typedef struct cutsceneitem_s cutsceneitem_t;
|
||||
typedef union cutsceneitemdata_u cutsceneitemdata_t;
|
||||
|
||||
typedef struct {
|
||||
uint8_t areaId;
|
||||
} cutscenemaparearemove_t;
|
||||
|
||||
/**
|
||||
* Starts a map area remove step (removes the area immediately). Accepts
|
||||
* CUTSCENE_AREA_LAST_CREATED in place of a literal area ID.
|
||||
*
|
||||
* @param item The cutscene item.
|
||||
* @param data Runtime data storage.
|
||||
*/
|
||||
void cutsceneMapAreaRemoveStart(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
);
|
||||
|
||||
/**
|
||||
* Updates a map area remove step (always completes immediately).
|
||||
*
|
||||
* @param item The cutscene item.
|
||||
* @param data Runtime data storage.
|
||||
* @returns true always.
|
||||
*/
|
||||
bool_t cutsceneMapAreaRemoveUpdate(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
);
|
||||
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "rpg/cutscene/item/cutsceneitem.h"
|
||||
#include "rpg/cutscene/cutscenesystem.h"
|
||||
#include "rpg/overworld/maparea.h"
|
||||
#include "assert/assert.h"
|
||||
|
||||
void cutsceneMapAreaWaitStart(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
) {
|
||||
assertTrue(
|
||||
item->mapAreaWait.count <= CUTSCENE_MAP_AREA_WAIT_MAX,
|
||||
"Too many areas in CUTSCENE_MAP_AREA_WAIT"
|
||||
);
|
||||
|
||||
for(uint8_t i = 0; i < item->mapAreaWait.count; i++) {
|
||||
uint8_t areaId = cutsceneSystemGetAreaId(item->mapAreaWait.areaIds[i]);
|
||||
data->mapAreaWait.baseline[i] = MAP_AREAS[areaId].triggerCount;
|
||||
}
|
||||
}
|
||||
|
||||
bool_t cutsceneMapAreaWaitUpdate(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
) {
|
||||
for(uint8_t i = 0; i < item->mapAreaWait.count; i++) {
|
||||
uint8_t areaId = cutsceneSystemGetAreaId(item->mapAreaWait.areaIds[i]);
|
||||
if(MAP_AREAS[areaId].triggerCount != data->mapAreaWait.baseline[i]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "dusk.h"
|
||||
|
||||
typedef struct cutsceneitem_s cutsceneitem_t;
|
||||
typedef union cutsceneitemdata_u cutsceneitemdata_t;
|
||||
|
||||
/** Maximum number of areas a single CUTSCENE_MAP_AREA_WAIT may watch. */
|
||||
#define CUTSCENE_MAP_AREA_WAIT_MAX 4
|
||||
|
||||
typedef struct {
|
||||
const uint8_t *areaIds;
|
||||
uint8_t count;
|
||||
} cutscenemapareawait_t;
|
||||
|
||||
typedef struct {
|
||||
uint32_t baseline[CUTSCENE_MAP_AREA_WAIT_MAX];
|
||||
} cutscenemapareawaitdata_t;
|
||||
|
||||
/**
|
||||
* Starts a map area wait step, snapshotting each watched area's current
|
||||
* trigger count.
|
||||
*
|
||||
* @param item The cutscene item.
|
||||
* @param data Runtime data storage.
|
||||
*/
|
||||
void cutsceneMapAreaWaitStart(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
);
|
||||
|
||||
/**
|
||||
* Updates a map area wait step, completing once any watched area's
|
||||
* trigger count has changed since Start (i.e. its callback fired).
|
||||
*
|
||||
* @param item The cutscene item.
|
||||
* @param data Runtime data storage.
|
||||
* @returns true once any watched area has been triggered.
|
||||
*/
|
||||
bool_t cutsceneMapAreaWaitUpdate(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
);
|
||||
@@ -0,0 +1,10 @@
|
||||
# Copyright (c) 2026 Dominic Masters
|
||||
#
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||
PUBLIC
|
||||
cutscenetext.c
|
||||
cutscenefade.c
|
||||
)
|
||||
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "rpg/cutscene/item/cutsceneitem.h"
|
||||
#include "ui/overlay/uifullbox.h"
|
||||
|
||||
void cutsceneFadeStart(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
) {
|
||||
uiFullboxTransition(
|
||||
&UI_FULLBOX_OVER,
|
||||
item->fade.from,
|
||||
item->fade.to,
|
||||
item->fade.duration,
|
||||
item->fade.easing
|
||||
);
|
||||
}
|
||||
|
||||
bool_t cutsceneFadeUpdate(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
) {
|
||||
return !(
|
||||
UI_FULLBOX_OVER.duration > 0.0f &&
|
||||
UI_FULLBOX_OVER.time < UI_FULLBOX_OVER.duration
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "display/color.h"
|
||||
#include "animation/easing.h"
|
||||
|
||||
typedef struct cutsceneitem_s cutsceneitem_t;
|
||||
typedef union cutsceneitemdata_u cutsceneitemdata_t;
|
||||
|
||||
typedef struct {
|
||||
color_t from;
|
||||
color_t to;
|
||||
float_t duration;
|
||||
easingtype_t easing;
|
||||
} cutscenefade_t;
|
||||
|
||||
/**
|
||||
* Starts a fade item (begins the overlay transition).
|
||||
*
|
||||
* @param item The cutscene item.
|
||||
* @param data Runtime data storage.
|
||||
*/
|
||||
void cutsceneFadeStart(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
);
|
||||
|
||||
/**
|
||||
* Updates a fade item.
|
||||
*
|
||||
* @param item The cutscene item.
|
||||
* @param data Runtime data storage.
|
||||
* @returns true once the overlay transition has completed.
|
||||
*/
|
||||
bool_t cutsceneFadeUpdate(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
);
|
||||
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "rpg/cutscene/item/cutsceneitem.h"
|
||||
#include "ui/rpg/uitextboxmain.h"
|
||||
|
||||
void cutsceneTextStart(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
) {
|
||||
uiTextboxMainSetText(item->text.text);
|
||||
}
|
||||
|
||||
bool_t cutsceneTextUpdate(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
) {
|
||||
return !uiTextboxMainIsActive();
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* 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 cutsceneitem_s cutsceneitem_t;
|
||||
typedef union cutsceneitemdata_u cutsceneitemdata_t;
|
||||
|
||||
#define CUTSCENE_TEXT_MAX_CHARS 256
|
||||
|
||||
typedef struct {
|
||||
char_t text[CUTSCENE_TEXT_MAX_CHARS];
|
||||
} cutscenetext_t;
|
||||
|
||||
/**
|
||||
* Starts a text item (shows the textbox with the item's text).
|
||||
*
|
||||
* @param item The cutscene item.
|
||||
* @param data Runtime data storage.
|
||||
*/
|
||||
void cutsceneTextStart(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
);
|
||||
|
||||
/**
|
||||
* Updates a text item.
|
||||
*
|
||||
* @param item The cutscene item.
|
||||
* @param data Runtime data storage.
|
||||
* @returns true once the textbox has been dismissed.
|
||||
*/
|
||||
bool_t cutsceneTextUpdate(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
);
|
||||
@@ -6,25 +6,22 @@
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "rpg/cutscene/cutscene.h"
|
||||
#include "rpg/cutscene/cutscenesystem.h"
|
||||
|
||||
static const cutsceneitem_t TEST_CUTSCENE_ONE_ITEMS[] = {
|
||||
{ .type = CUTSCENE_ITEM_TYPE_TEXT, .text = { .text = "This is a test cutscene.", .position = RPG_TEXTBOX_POS_BOTTOM } },
|
||||
{ .type = CUTSCENE_ITEM_TYPE_WAIT, .wait = 2.0f },
|
||||
{ .type = CUTSCENE_ITEM_TYPE_TEXT, .text = { .text = "It has multiple lines of text.\nAnd waits in between.", .position = RPG_TEXTBOX_POS_TOP } },
|
||||
};
|
||||
CUTSCENE(TEST_ONE, 0, DEFAULT,
|
||||
CUTSCENE_TEXT("Test One."),
|
||||
);
|
||||
|
||||
static const cutscene_t TEST_CUTSCENE_ONE = {
|
||||
.items = TEST_CUTSCENE_ONE_ITEMS,
|
||||
.itemCount = sizeof(TEST_CUTSCENE_ONE_ITEMS) / sizeof(cutsceneitem_t)
|
||||
};
|
||||
|
||||
static const cutsceneitem_t TEST_CUTSCENE_TWO_ITEMS[] = {
|
||||
{ .type = CUTSCENE_ITEM_TYPE_WAIT, .wait = 1.0f },
|
||||
{ .type = CUTSCENE_ITEM_TYPE_CUTSCENE, .cutscene = &TEST_CUTSCENE_ONE },
|
||||
};
|
||||
|
||||
static const cutscene_t TEST_CUTSCENE = {
|
||||
.items = TEST_CUTSCENE_TWO_ITEMS,
|
||||
.itemCount = sizeof(TEST_CUTSCENE_TWO_ITEMS) / sizeof(cutsceneitem_t)
|
||||
};
|
||||
CUTSCENE(TEST_TWO, 0, DEFAULT,
|
||||
CUTSCENE_TEXT("Test Two."),
|
||||
CUTSCENE_ENTITY_ADD(ENTITY_TYPE_NPC, 4, 4, 0),
|
||||
CUTSCENE_ENTITY_WALK_TO(CUTSCENE_ENTITY_LAST_CREATED, 8, 2, 0),
|
||||
// CUTSCENE_CONCURRENT(
|
||||
// CUTSCENE_ENTITY_WALK_TO(CUTSCENE_ENTITY_INTERACT, 4, 4, 0),
|
||||
// CUTSCENE_ENTITY_WALK_TO(CUTSCENE_ENTITY_INTERACTED, 8, 2, 0),
|
||||
// ),
|
||||
// CUTSCENE_ITEM_GIVE(ITEM_ID_POTATO, 3),
|
||||
// CUTSCENE_ENTITY_REMOVE(CUTSCENE_ENTITY_INTERACT),
|
||||
CUTSCENE_TEXT("Done."),
|
||||
);
|
||||
@@ -8,9 +8,12 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||
PUBLIC
|
||||
entity.c
|
||||
entitydir.c
|
||||
entitypathstep.c
|
||||
player.c
|
||||
)
|
||||
|
||||
add_subdirectory(anim)
|
||||
add_subdirectory(interact)
|
||||
add_subdirectory(npc)
|
||||
add_subdirectory(npc)
|
||||
add_subdirectory(item)
|
||||
add_subdirectory(global)
|
||||
@@ -19,7 +19,7 @@ const entityanimcallback_t ENTITY_ANIM_CALLBACKS[ENTITY_ANIM_COUNT] = {
|
||||
};
|
||||
|
||||
float_t entityAnimTileZOffset(const worldpos_t pos) {
|
||||
return tileIsRamp(mapGetTile(pos)) ? 0.5f : 0.0f;
|
||||
return tileShapeIsRamp(mapGetTile(pos).shape) ? 0.5f : 0.0f;
|
||||
}
|
||||
|
||||
void entityAnimUpdate(entity_t *entity) {
|
||||
@@ -32,7 +32,7 @@ void entityAnimUpdate(entity_t *entity) {
|
||||
) {
|
||||
entity->walkEndCooldown = ENTITY_ANIM_WALK_TURN_COOLDOWN;
|
||||
}
|
||||
entity->animation = ENTITY_ANIM_IDLE;
|
||||
entity->animation = ENTITY_ANIM_IDLE;
|
||||
entity->animTime = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
void entityAnimIdleUpdate(entity_t *entity) {
|
||||
entity->renderPosition[0] = (float_t)entity->position.x;
|
||||
entity->renderPosition[1] = (float_t)entity->position.y;
|
||||
entity->renderPosition[2] = (float_t)entity->position.z
|
||||
+ entityAnimTileZOffset(entity->position);
|
||||
entity->renderPosition[2] = (
|
||||
(float_t)entity->position.z + entityAnimTileZOffset(entity->position)
|
||||
) * WORLD_LAYER_HEIGHT;
|
||||
}
|
||||
|
||||
@@ -20,5 +20,5 @@ void entityAnimRunUpdate(entity_t *entity) {
|
||||
entity->renderPosition[1] = (float_t)entity->lastPosition.y + t * (
|
||||
(float_t)entity->position.y - (float_t)entity->lastPosition.y
|
||||
);
|
||||
entity->renderPosition[2] = zFrom + t * (zTo - zFrom);
|
||||
entity->renderPosition[2] = (zFrom + t * (zTo - zFrom)) * WORLD_LAYER_HEIGHT;
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
void entityAnimTurnUpdate(entity_t *entity) {
|
||||
entity->renderPosition[0] = (float_t)entity->position.x;
|
||||
entity->renderPosition[1] = (float_t)entity->position.y;
|
||||
entity->renderPosition[2] = (float_t)entity->position.z
|
||||
+ entityAnimTileZOffset(entity->position);
|
||||
entity->renderPosition[2] = (
|
||||
(float_t)entity->position.z + entityAnimTileZOffset(entity->position)
|
||||
) * WORLD_LAYER_HEIGHT;
|
||||
}
|
||||
|
||||
@@ -20,5 +20,5 @@ void entityAnimWalkUpdate(entity_t *entity) {
|
||||
entity->renderPosition[1] = (float_t)entity->lastPosition.y + t * (
|
||||
(float_t)entity->position.y - (float_t)entity->lastPosition.y
|
||||
);
|
||||
entity->renderPosition[2] = zFrom + t * (zTo - zFrom);
|
||||
entity->renderPosition[2] = (zFrom + t * (zTo - zFrom)) * WORLD_LAYER_HEIGHT;
|
||||
}
|
||||
|
||||
@@ -10,8 +10,8 @@
|
||||
#include "util/memory.h"
|
||||
#include "time/time.h"
|
||||
#include "util/math.h"
|
||||
#include "rpg/cutscene/cutscenemode.h"
|
||||
#include "rpg/overworld/map.h"
|
||||
#include "rpg/overworld/maparea.h"
|
||||
#include "rpg/overworld/chunk.h"
|
||||
#include "rpg/overworld/tile.h"
|
||||
|
||||
@@ -28,6 +28,7 @@ void entityInit(entity_t *entity, const entitytype_t type) {
|
||||
|
||||
memoryZero(entity, sizeof(entity_t));
|
||||
entity->id = (uint8_t)(entity - ENTITIES);
|
||||
entity->globalId = ENTITY_GLOBAL_ID_NULL;
|
||||
entity->type = type;
|
||||
entity->chunkIndex = 0xFF;
|
||||
|
||||
@@ -43,10 +44,7 @@ void entityUpdate(entity_t *entity) {
|
||||
entityAnimUpdate(entity);
|
||||
|
||||
// Movement code.
|
||||
if(
|
||||
cutsceneModeIsInputAllowed() &&
|
||||
ENTITY_CALLBACKS[entity->type].movement != NULL
|
||||
) {
|
||||
if(ENTITY_CALLBACKS[entity->type].movement != NULL) {
|
||||
ENTITY_CALLBACKS[entity->type].movement(entity);
|
||||
}
|
||||
}
|
||||
@@ -64,6 +62,10 @@ bool_t entityCanRun(entity_t *entity) {
|
||||
return entity->animation == ENTITY_ANIM_IDLE;
|
||||
}
|
||||
|
||||
bool_t entityCanUnload(entity_t *entity) {
|
||||
return entity->globalId < ENTITY_GLOBAL_ID_START;
|
||||
}
|
||||
|
||||
void entityTurn(entity_t *entity, const entitydir_t direction) {
|
||||
if(!entityCanTurn(entity)) return;
|
||||
entity->direction = direction;
|
||||
@@ -93,38 +95,38 @@ void entityWalk(entity_t *entity, const entitydir_t direction) {
|
||||
|
||||
// Are we walking up a ramp?
|
||||
if(
|
||||
tileIsRamp(tileCurrent) &&
|
||||
tileShapeIsRamp(tileCurrent.shape) &&
|
||||
(
|
||||
// Can only walk UP the direction the ramp faces.
|
||||
(direction+TILE_SHAPE_RAMP_NORTH) == tileCurrent ||
|
||||
(direction+TILE_SHAPE_RAMP_NORTH) == tileCurrent.shape ||
|
||||
// If diagonal ramp, can go up one of two ways only. Inner ramps
|
||||
// share the same allowed directions as their outer counterparts.
|
||||
(
|
||||
(
|
||||
(
|
||||
tileCurrent == TILE_SHAPE_RAMP_SOUTHEAST ||
|
||||
tileCurrent == TILE_SHAPE_RAMP_SOUTHEAST_INNER
|
||||
tileCurrent.shape == TILE_SHAPE_RAMP_SOUTHEAST ||
|
||||
tileCurrent.shape == TILE_SHAPE_RAMP_SOUTHEAST_INNER
|
||||
) &&
|
||||
(direction == ENTITY_DIR_SOUTH || direction == ENTITY_DIR_EAST)
|
||||
) ||
|
||||
(
|
||||
(
|
||||
tileCurrent == TILE_SHAPE_RAMP_SOUTHWEST ||
|
||||
tileCurrent == TILE_SHAPE_RAMP_SOUTHWEST_INNER
|
||||
tileCurrent.shape == TILE_SHAPE_RAMP_SOUTHWEST ||
|
||||
tileCurrent.shape == TILE_SHAPE_RAMP_SOUTHWEST_INNER
|
||||
) &&
|
||||
(direction == ENTITY_DIR_SOUTH || direction == ENTITY_DIR_WEST)
|
||||
) ||
|
||||
(
|
||||
(
|
||||
tileCurrent == TILE_SHAPE_RAMP_NORTHEAST ||
|
||||
tileCurrent == TILE_SHAPE_RAMP_NORTHEAST_INNER
|
||||
tileCurrent.shape == TILE_SHAPE_RAMP_NORTHEAST ||
|
||||
tileCurrent.shape == TILE_SHAPE_RAMP_NORTHEAST_INNER
|
||||
) &&
|
||||
(direction == ENTITY_DIR_NORTH || direction == ENTITY_DIR_EAST)
|
||||
) ||
|
||||
(
|
||||
(
|
||||
tileCurrent == TILE_SHAPE_RAMP_NORTHWEST ||
|
||||
tileCurrent == TILE_SHAPE_RAMP_NORTHWEST_INNER
|
||||
tileCurrent.shape == TILE_SHAPE_RAMP_NORTHWEST ||
|
||||
tileCurrent.shape == TILE_SHAPE_RAMP_NORTHWEST_INNER
|
||||
) &&
|
||||
(direction == ENTITY_DIR_NORTH || direction == ENTITY_DIR_WEST)
|
||||
)
|
||||
@@ -133,55 +135,60 @@ void entityWalk(entity_t *entity, const entitydir_t direction) {
|
||||
)
|
||||
) {
|
||||
tile_t tileNewSaved = tileNew;
|
||||
tileNew = TILE_SHAPE_NULL;
|
||||
tileNew = TILE_NULL;
|
||||
worldpos_t abovePos = newPos;
|
||||
abovePos.z += 1;
|
||||
tile_t tileAbove = mapGetTile(abovePos);
|
||||
|
||||
if(tileAbove != TILE_SHAPE_NULL && tileIsWalkable(tileAbove)) {
|
||||
if(
|
||||
tileAbove.shape != TILE_SHAPE_NULL &&
|
||||
tileShapeIsWalkable(tileAbove.shape)
|
||||
) {
|
||||
raise = true;
|
||||
} else {
|
||||
tileNew = tileNewSaved;
|
||||
}
|
||||
} else if(tileNew == TILE_SHAPE_NULL && newPos.z > 0) {
|
||||
} else if(tileNew.shape == TILE_SHAPE_NULL && newPos.z > 0) {
|
||||
// Falling down?
|
||||
worldpos_t belowPos = newPos;
|
||||
belowPos.z -= 1;
|
||||
tile_t tileBelow = mapGetTile(belowPos);
|
||||
if(
|
||||
tileBelow != TILE_SHAPE_NULL &&
|
||||
tileIsRamp(tileBelow) &&
|
||||
tileBelow.shape != TILE_SHAPE_NULL &&
|
||||
tileShapeIsRamp(tileBelow.shape) &&
|
||||
(
|
||||
// This handles regular cardinal ramps
|
||||
(entityDirGetOpposite(direction)+TILE_SHAPE_RAMP_NORTH) == tileBelow ||
|
||||
(
|
||||
entityDirGetOpposite(direction)+TILE_SHAPE_RAMP_NORTH
|
||||
) == tileBelow.shape ||
|
||||
// This handles diagonal ramps. Inner ramps share the same
|
||||
// allowed directions as their outer counterparts.
|
||||
(
|
||||
(
|
||||
(
|
||||
tileBelow == TILE_SHAPE_RAMP_SOUTHEAST ||
|
||||
tileBelow == TILE_SHAPE_RAMP_SOUTHEAST_INNER
|
||||
tileBelow.shape == TILE_SHAPE_RAMP_SOUTHEAST ||
|
||||
tileBelow.shape == TILE_SHAPE_RAMP_SOUTHEAST_INNER
|
||||
) &&
|
||||
(direction == ENTITY_DIR_NORTH || direction == ENTITY_DIR_WEST)
|
||||
) ||
|
||||
(
|
||||
(
|
||||
tileBelow == TILE_SHAPE_RAMP_SOUTHWEST ||
|
||||
tileBelow == TILE_SHAPE_RAMP_SOUTHWEST_INNER
|
||||
tileBelow.shape == TILE_SHAPE_RAMP_SOUTHWEST ||
|
||||
tileBelow.shape == TILE_SHAPE_RAMP_SOUTHWEST_INNER
|
||||
) &&
|
||||
(direction == ENTITY_DIR_NORTH || direction == ENTITY_DIR_EAST)
|
||||
) ||
|
||||
(
|
||||
(
|
||||
tileBelow == TILE_SHAPE_RAMP_NORTHEAST ||
|
||||
tileBelow == TILE_SHAPE_RAMP_NORTHEAST_INNER
|
||||
tileBelow.shape == TILE_SHAPE_RAMP_NORTHEAST ||
|
||||
tileBelow.shape == TILE_SHAPE_RAMP_NORTHEAST_INNER
|
||||
) &&
|
||||
(direction == ENTITY_DIR_SOUTH || direction == ENTITY_DIR_WEST)
|
||||
) ||
|
||||
(
|
||||
(
|
||||
tileBelow == TILE_SHAPE_RAMP_NORTHWEST ||
|
||||
tileBelow == TILE_SHAPE_RAMP_NORTHWEST_INNER
|
||||
tileBelow.shape == TILE_SHAPE_RAMP_NORTHWEST ||
|
||||
tileBelow.shape == TILE_SHAPE_RAMP_NORTHWEST_INNER
|
||||
) &&
|
||||
(direction == ENTITY_DIR_SOUTH || direction == ENTITY_DIR_EAST)
|
||||
)
|
||||
@@ -194,7 +201,15 @@ void entityWalk(entity_t *entity, const entitydir_t direction) {
|
||||
}
|
||||
|
||||
// Can we walk here?
|
||||
if(!raise && !fall && !tileIsWalkable(tileNew)) return;// Blocked
|
||||
if(!raise && !fall && !tileShapeIsWalkable(tileNew.shape)) return;// Blocked
|
||||
|
||||
// Raise/fall must be applied before checking for blocking entities,
|
||||
// otherwise the check compares against the wrong z-level.
|
||||
if(raise) {
|
||||
newPos.z += 1;
|
||||
} else if(fall) {
|
||||
newPos.z -= 1;
|
||||
}
|
||||
|
||||
// Entity in way?
|
||||
entity_t *other = ENTITIES;
|
||||
@@ -209,12 +224,8 @@ void entityWalk(entity_t *entity, const entitydir_t direction) {
|
||||
entity->position = newPos;
|
||||
entity->animation = ENTITY_ANIM_WALK;
|
||||
entity->animTime = ENTITY_ANIM_WALK_DURATION;// TODO: Running vs walking
|
||||
|
||||
if(raise) {
|
||||
entity->position.z += 1;
|
||||
} else if(fall) {
|
||||
entity->position.z -= 1;
|
||||
}
|
||||
entityUpdateChunk(entity);
|
||||
mapAreaCheckEntity(entity);
|
||||
}
|
||||
|
||||
void entityRun(entity_t *entity, const entitydir_t direction) {
|
||||
@@ -237,6 +248,17 @@ entity_t * entityGetAt(const worldpos_t position) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
entity_t * entityGetByGlobalId(const entityglobalid_t globalId) {
|
||||
entity_t *ent = ENTITIES;
|
||||
do {
|
||||
if(ent->type == ENTITY_TYPE_NULL) continue;
|
||||
if(ent->globalId != globalId) continue;
|
||||
return ent;
|
||||
} while(++ent, ent < &ENTITIES[ENTITY_COUNT]);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
uint8_t entityGetAvailable() {
|
||||
entity_t *ent = ENTITIES;
|
||||
do {
|
||||
@@ -246,6 +268,16 @@ uint8_t entityGetAvailable() {
|
||||
return 0xFF;
|
||||
}
|
||||
|
||||
void entityPositionSet(entity_t *entity, const worldpos_t pos) {
|
||||
assertNotNull(entity, "Entity pointer cannot be NULL");
|
||||
entity->lastPosition = pos;
|
||||
entity->position = pos;
|
||||
entity->animation = ENTITY_ANIM_IDLE;
|
||||
entity->animTime = 0;
|
||||
entity->walkEndCooldown = 0;
|
||||
entityUpdateChunk(entity);
|
||||
}
|
||||
|
||||
void entitySetChunk(entity_t *entity, const uint8_t chunkIndex) {
|
||||
assertNotNull(entity, "Entity pointer cannot be NULL");
|
||||
|
||||
@@ -272,4 +304,13 @@ void entitySetChunk(entity_t *entity, const uint8_t chunkIndex) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void entityUpdateChunk(entity_t *entity) {
|
||||
assertNotNull(entity, "Entity pointer cannot be NULL");
|
||||
|
||||
chunkpos_t cp;
|
||||
worldPosToChunkPos(&entity->position, &cp);
|
||||
chunkindex_t ci = mapGetChunkIndexAt(cp);
|
||||
if(ci != -1) entitySetChunk(entity, (uint8_t)ci);
|
||||
}
|
||||
@@ -14,8 +14,15 @@
|
||||
|
||||
typedef struct map_s map_t;
|
||||
|
||||
typedef uint16_t entityglobalid_t;
|
||||
|
||||
#define ENTITY_GLOBAL_ID_NULL 0
|
||||
#define ENTITY_GLOBAL_ID_START 1
|
||||
#define ENTITY_GLOBAL_ID_PLAYER 1
|
||||
|
||||
typedef struct entity_s {
|
||||
uint8_t id;
|
||||
entityglobalid_t globalId;
|
||||
entitytype_t type;
|
||||
entitytypedata_t data;
|
||||
|
||||
@@ -75,6 +82,16 @@ bool_t entityCanWalk(entity_t *entity);
|
||||
*/
|
||||
bool_t entityCanRun(entity_t *entity);
|
||||
|
||||
/**
|
||||
* Returns true if the entity is allowed to be unloaded. By default this is
|
||||
* true for entities whose global ID falls within the randomly assigned
|
||||
* range below ENTITY_GLOBAL_ID_START.
|
||||
*
|
||||
* @param entity Pointer to the entity to check.
|
||||
* @returns True if the entity can be unloaded.
|
||||
*/
|
||||
bool_t entityCanUnload(entity_t *entity);
|
||||
|
||||
/**
|
||||
* Turn an entity to face a new direction.
|
||||
*
|
||||
@@ -101,13 +118,21 @@ void entityRun(entity_t *entity, const entitydir_t direction);
|
||||
|
||||
/**
|
||||
* Gets the entity at a specific world position.
|
||||
*
|
||||
*
|
||||
* @param map Pointer to the map to check.
|
||||
* @param pos The world position to check.
|
||||
* @return Pointer to the entity at the position, or NULL if none.
|
||||
*/
|
||||
entity_t *entityGetAt(const worldpos_t pos);
|
||||
|
||||
/**
|
||||
* Gets the entity with the given global ID, if one is currently loaded.
|
||||
*
|
||||
* @param globalId The global ID to search for.
|
||||
* @return Pointer to the matching entity, or NULL if none is loaded.
|
||||
*/
|
||||
entity_t *entityGetByGlobalId(const entityglobalid_t globalId);
|
||||
|
||||
/**
|
||||
* Gets an available entity index.
|
||||
*
|
||||
@@ -122,4 +147,21 @@ uint8_t entityGetAvailable();
|
||||
* @param entity Pointer to the entity.
|
||||
* @param chunkIndex Index of the chunk to assign to, or 0xFF for none.
|
||||
*/
|
||||
void entitySetChunk(entity_t *entity, const uint8_t chunkIndex);
|
||||
void entitySetChunk(entity_t *entity, const uint8_t chunkIndex);
|
||||
|
||||
/**
|
||||
* Resolves the chunk that an entity's current position falls into and
|
||||
* assigns the entity to it via entitySetChunk. Leaves the entity's chunk
|
||||
* unchanged if its position doesn't fall within any loaded chunk.
|
||||
*
|
||||
* @param entity Pointer to the entity to update.
|
||||
*/
|
||||
void entityUpdateChunk(entity_t *entity);
|
||||
|
||||
/**
|
||||
* Instantly moves an entity to a world position, resetting movement state.
|
||||
*
|
||||
* @param entity Pointer to the entity to move.
|
||||
* @param pos The world position to place the entity at.
|
||||
*/
|
||||
void entityPositionSet(entity_t *entity, const worldpos_t pos);
|
||||
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "entitypathstep.h"
|
||||
#include "entitydir.h"
|
||||
#include "assert/assert.h"
|
||||
|
||||
bool_t entityPathStep(
|
||||
entity_t *entity,
|
||||
const worldpos_t target,
|
||||
bool_t walkAround
|
||||
) {
|
||||
assertNotNull(entity, "Entity must not be NULL");
|
||||
assertTrue(
|
||||
entity->type != ENTITY_TYPE_NULL,
|
||||
"Cannot path step a NULL entity type"
|
||||
);
|
||||
assertTrue(
|
||||
entity >= ENTITIES && entity < ENTITIES + ENTITY_COUNT,
|
||||
"Entity pointer is out of bounds"
|
||||
);
|
||||
|
||||
if(worldPosIsEqual(entity->position, target) && entityCanWalk(entity)) {
|
||||
return true;
|
||||
}
|
||||
if(!entityCanWalk(entity)) return false;
|
||||
|
||||
entitydir_t dir;
|
||||
if(entity->position.x != target.x) {
|
||||
dir = entity->position.x < target.x ? ENTITY_DIR_EAST : ENTITY_DIR_WEST;
|
||||
} else if(entity->position.y != target.y) {
|
||||
dir = entity->position.y < target.y ? ENTITY_DIR_NORTH : ENTITY_DIR_SOUTH;
|
||||
} else {
|
||||
dir = entity->position.z < target.z ? ENTITY_DIR_NORTH : ENTITY_DIR_SOUTH;
|
||||
}
|
||||
|
||||
entityWalk(entity, dir);
|
||||
|
||||
if(walkAround && entity->animation == ENTITY_ANIM_IDLE) {
|
||||
// Primary direction blocked - try perpendicular axes.
|
||||
entitydir_t alt, altOpp;
|
||||
if(dir == ENTITY_DIR_EAST || dir == ENTITY_DIR_WEST) {
|
||||
alt = entity->position.y <= target.y
|
||||
? ENTITY_DIR_NORTH : ENTITY_DIR_SOUTH;
|
||||
} else {
|
||||
alt = entity->position.x <= target.x
|
||||
? ENTITY_DIR_EAST : ENTITY_DIR_WEST;
|
||||
}
|
||||
altOpp = entityDirGetOpposite(alt);
|
||||
entityWalk(entity, alt);
|
||||
if(entity->animation == ENTITY_ANIM_IDLE) entityWalk(entity, altOpp);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "entity.h"
|
||||
|
||||
/**
|
||||
* Attempts to walk the entity one tile toward target. Prefers resolving X
|
||||
* first, then Y, then Z. Does nothing if the entity cannot currently walk.
|
||||
* When walkAround is true and the preferred direction is blocked by an entity,
|
||||
* tries perpendicular directions to navigate around it.
|
||||
*
|
||||
* @param entity Pointer to the entity to move.
|
||||
* @param target The world position to move toward.
|
||||
* @param walkAround Whether to try perpendicular directions when blocked.
|
||||
* @returns true if the entity is already at target, false otherwise.
|
||||
*/
|
||||
bool_t entityPathStep(
|
||||
entity_t *entity,
|
||||
const worldpos_t target,
|
||||
bool_t walkAround
|
||||
);
|
||||
@@ -8,12 +8,14 @@
|
||||
#pragma once
|
||||
#include "rpg/entity/player.h"
|
||||
#include "npc/npc.h"
|
||||
#include "item/entityitem.h"
|
||||
|
||||
typedef enum {
|
||||
ENTITY_TYPE_NULL,
|
||||
|
||||
|
||||
ENTITY_TYPE_PLAYER,
|
||||
ENTITY_TYPE_NPC,
|
||||
ENTITY_TYPE_ITEM,
|
||||
|
||||
ENTITY_TYPE_COUNT
|
||||
} entitytype_enum_t;
|
||||
@@ -23,6 +25,7 @@ typedef uint8_t entitytype_t;
|
||||
typedef union {
|
||||
player_t player;
|
||||
npc_t npc;
|
||||
entityitem_t item;
|
||||
} entitytypedata_t;
|
||||
|
||||
typedef struct {
|
||||
@@ -50,5 +53,10 @@ static const entitycallback_t ENTITY_CALLBACKS[ENTITY_TYPE_COUNT] = {
|
||||
[ENTITY_TYPE_NPC] = {
|
||||
.init = npcInit,
|
||||
.movement = npcMovement,
|
||||
},
|
||||
|
||||
[ENTITY_TYPE_ITEM] = {
|
||||
.init = entityItemInit,
|
||||
.movement = entityItemMovement,
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,9 @@
|
||||
# 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
|
||||
)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user