Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| afc68bccc6 | |||
| b4cdc4a64f | |||
| 3ad5afb81c | |||
| bed3f20118 | |||
| de67315178 | |||
| 8d5c0c7cad | |||
| a8271e01bd | |||
| 900b3f8558 | |||
| fdc4e056f9 | |||
| 7b98e40ccf | |||
| 01d89cf22c | |||
| 503a3c799a | |||
| 0b21388844 | |||
| 117bdf0c00 | |||
| 172dc5d37b |
@@ -4,36 +4,6 @@ on:
|
|||||||
tags:
|
tags:
|
||||||
- '*'
|
- '*'
|
||||||
jobs:
|
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:
|
build-linux:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
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.
@@ -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();
|
|
||||||
});
|
|
||||||
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,
|
10,
|
||||||
0
|
0
|
||||||
],
|
],
|
||||||
"type": 1,
|
"type": 4,
|
||||||
"tile": 0
|
"tile": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -1473,7 +1473,7 @@
|
|||||||
10,
|
10,
|
||||||
0
|
0
|
||||||
],
|
],
|
||||||
"type": 1,
|
"type": 4,
|
||||||
"tile": 0
|
"tile": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -1482,7 +1482,7 @@
|
|||||||
10,
|
10,
|
||||||
0
|
0
|
||||||
],
|
],
|
||||||
"type": 1,
|
"type": 4,
|
||||||
"tile": 0
|
"tile": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -1491,7 +1491,7 @@
|
|||||||
10,
|
10,
|
||||||
0
|
0
|
||||||
],
|
],
|
||||||
"type": 1,
|
"type": 4,
|
||||||
"tile": 0
|
"tile": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -1500,7 +1500,7 @@
|
|||||||
10,
|
10,
|
||||||
0
|
0
|
||||||
],
|
],
|
||||||
"type": 1,
|
"type": 4,
|
||||||
"tile": 0
|
"tile": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -1509,7 +1509,7 @@
|
|||||||
10,
|
10,
|
||||||
0
|
0
|
||||||
],
|
],
|
||||||
"type": 1,
|
"type": 4,
|
||||||
"tile": 0
|
"tile": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -2267,6 +2267,276 @@
|
|||||||
],
|
],
|
||||||
"type": 1,
|
"type": 1,
|
||||||
"tile": 0
|
"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": []
|
"meshes": []
|
||||||
|
|||||||
@@ -17,6 +17,10 @@ const ChunkTerrain = (() => {
|
|||||||
const CHUNK_DEPTH = 4;
|
const CHUNK_DEPTH = 4;
|
||||||
const TILE_COUNT = CHUNK_WIDTH * CHUNK_HEIGHT * CHUNK_DEPTH;
|
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_NULL = 0;
|
||||||
const TILE_SHAPE_GROUND = 1;
|
const TILE_SHAPE_GROUND = 1;
|
||||||
const TILE_SHAPE_RAMP_NORTH = 2;
|
const TILE_SHAPE_RAMP_NORTH = 2;
|
||||||
@@ -88,7 +92,53 @@ const ChunkTerrain = (() => {
|
|||||||
const v1 = (y + 1) / CHUNK_HEIGHT;
|
const v1 = (y + 1) / CHUNK_HEIGHT;
|
||||||
const [sw, se, ne, nw] = corners;
|
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({
|
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_NULL, TILE_SHAPE_GROUND,
|
||||||
TILE_SHAPE_RAMP_NORTH, TILE_SHAPE_RAMP_EAST,
|
TILE_SHAPE_RAMP_NORTH, TILE_SHAPE_RAMP_EAST,
|
||||||
TILE_SHAPE_RAMP_SOUTH, TILE_SHAPE_RAMP_WEST,
|
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_SOUTHEAST, TILE_SHAPE_RAMP_SOUTHWEST,
|
||||||
TILE_SHAPE_RAMP_NORTHEAST_INNER, TILE_SHAPE_RAMP_NORTHWEST_INNER,
|
TILE_SHAPE_RAMP_NORTHEAST_INNER, TILE_SHAPE_RAMP_NORTHWEST_INNER,
|
||||||
TILE_SHAPE_RAMP_SOUTHEAST_INNER, TILE_SHAPE_RAMP_SOUTHWEST_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-direction: column;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: 6px;
|
gap: 6px;
|
||||||
max-height: 512px;
|
max-height: 600px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tile-swatch {
|
.tile-swatch {
|
||||||
|
|||||||
@@ -41,16 +41,15 @@
|
|||||||
[ChunkTerrain.TILE_SHAPE_RAMP_SOUTHWEST_INNER]: { dx: -1, dy: 1 },
|
[ChunkTerrain.TILE_SHAPE_RAMP_SOUTHWEST_INNER]: { dx: -1, dy: 1 },
|
||||||
};
|
};
|
||||||
|
|
||||||
// Draws an arrow centered at (cx, cy) pointing toward the ramp's high
|
const INNER_CORNER_SHAPES = new Set([
|
||||||
// side, so ramp direction is visible at a glance on both the palette
|
ChunkTerrain.TILE_SHAPE_RAMP_NORTHEAST_INNER,
|
||||||
// swatches and the tile grid.
|
ChunkTerrain.TILE_SHAPE_RAMP_NORTHWEST_INNER,
|
||||||
function drawArrow(ctx, cx, cy, size, dx, dy) {
|
ChunkTerrain.TILE_SHAPE_RAMP_SOUTHEAST_INNER,
|
||||||
const mag = Math.hypot(dx, dy) || 1;
|
ChunkTerrain.TILE_SHAPE_RAMP_SOUTHWEST_INNER,
|
||||||
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;
|
|
||||||
|
|
||||||
|
// Draws a single-headed arrow from (tailX, tailY) to (tipX, tipY).
|
||||||
|
function drawArrowSegment(ctx, tailX, tailY, tipX, tipY, size) {
|
||||||
ctx.strokeStyle = "#ffffff";
|
ctx.strokeStyle = "#ffffff";
|
||||||
ctx.fillStyle = "#ffffff";
|
ctx.fillStyle = "#ffffff";
|
||||||
ctx.lineWidth = Math.max(1, size * 0.08);
|
ctx.lineWidth = Math.max(1, size * 0.08);
|
||||||
@@ -62,7 +61,7 @@
|
|||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
|
|
||||||
const headLen = size * 0.18;
|
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 leftAngle = angle + Math.PI * 0.8;
|
||||||
const rightAngle = angle - Math.PI * 0.8;
|
const rightAngle = angle - Math.PI * 0.8;
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
@@ -73,6 +72,54 @@
|
|||||||
ctx.fill();
|
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.
|
// Draws a small pencil icon centered in a `size`x`size` canvas.
|
||||||
function drawPencilIcon(ctx, size) {
|
function drawPencilIcon(ctx, size) {
|
||||||
ctx.save();
|
ctx.save();
|
||||||
@@ -146,6 +193,7 @@
|
|||||||
|
|
||||||
let mapRenderer = null;
|
let mapRenderer = null;
|
||||||
let terrainMesh = null;
|
let terrainMesh = null;
|
||||||
|
let gridLinesMesh = null;
|
||||||
let terrainTexturePromise = null;
|
let terrainTexturePromise = null;
|
||||||
let modelIndex = null;
|
let modelIndex = null;
|
||||||
let neighborChunks = [];
|
let neighborChunks = [];
|
||||||
@@ -262,6 +310,13 @@
|
|||||||
return modelIndex;
|
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
|
// Resolves a chunk mesh entry's bare filename (e.g. "house_5_3.dmf") to
|
||||||
// its model JSON by basename, mirroring find_model() in
|
// its model JSON by basename, mirroring find_model() in
|
||||||
// tools/asset/chunk/__main__.py - the chunk JSON only ever stores a bare
|
// tools/asset/chunk/__main__.py - the chunk JSON only ever stores a bare
|
||||||
@@ -330,7 +385,7 @@
|
|||||||
for(const m of neighborMeshes) {
|
for(const m of neighborMeshes) {
|
||||||
try {
|
try {
|
||||||
const resolved = await resolveModelForMeshFile(m.file);
|
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) {
|
} catch(e) {
|
||||||
console.warn("Failed to load neighbor mesh preview", m.file, e);
|
console.warn("Failed to load neighbor mesh preview", m.file, e);
|
||||||
}
|
}
|
||||||
@@ -426,13 +481,12 @@
|
|||||||
buildPalette();
|
buildPalette();
|
||||||
});
|
});
|
||||||
|
|
||||||
const dir = SHAPE_DIRECTIONS[s.type];
|
if(SHAPE_DIRECTIONS[s.type]) {
|
||||||
if(dir) {
|
|
||||||
const icon = document.createElement("canvas");
|
const icon = document.createElement("canvas");
|
||||||
icon.width = 32;
|
icon.width = 32;
|
||||||
icon.height = 32;
|
icon.height = 32;
|
||||||
icon.className = "tile-swatch-icon";
|
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);
|
btn.appendChild(icon);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -489,11 +543,10 @@
|
|||||||
const sy = (ChunkTerrain.CHUNK_HEIGHT - 1 - y) * cell;
|
const sy = (ChunkTerrain.CHUNK_HEIGHT - 1 - y) * cell;
|
||||||
ctx.fillStyle = SHAPE_COLORS[type] || "#15171b";
|
ctx.fillStyle = SHAPE_COLORS[type] || "#15171b";
|
||||||
ctx.fillRect(x * cell, sy, cell, cell);
|
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);
|
ctx.strokeRect(x * cell, sy, cell, cell);
|
||||||
|
|
||||||
const dir = SHAPE_DIRECTIONS[type];
|
drawShapeIcon(ctx, x * cell + cell / 2, sy + cell / 2, cell, type);
|
||||||
if(dir) drawArrow(ctx, x * cell + cell / 2, sy + cell / 2, cell, dir.dx, dir.dy);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -592,6 +645,9 @@
|
|||||||
function rebuildTerrainMesh() {
|
function rebuildTerrainMesh() {
|
||||||
const floats = ChunkTerrain.buildTerrainVerts(tiles);
|
const floats = ChunkTerrain.buildTerrainVerts(tiles);
|
||||||
terrainMesh = floats.length ? mapRenderer.createMesh(floats) : null;
|
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
|
// Keeps the preview canvas's backing-store resolution matched to its
|
||||||
@@ -619,7 +675,7 @@
|
|||||||
for(const m of meshes) {
|
for(const m of meshes) {
|
||||||
try {
|
try {
|
||||||
const resolved = await resolveModelForMeshFile(m.file);
|
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) {
|
} catch(e) {
|
||||||
console.warn("Failed to load mesh preview", m.file, e);
|
console.warn("Failed to load mesh preview", m.file, e);
|
||||||
}
|
}
|
||||||
@@ -637,13 +693,15 @@
|
|||||||
models: n.models,
|
models: n.models,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const zLevelHeight = currentZLevel * ChunkTerrain.WORLD_LAYER_HEIGHT;
|
||||||
mapRenderer.render({
|
mapRenderer.render({
|
||||||
target: [ChunkTerrain.CHUNK_WIDTH / 2, ChunkTerrain.CHUNK_HEIGHT / 2, currentZLevel],
|
target: [ChunkTerrain.CHUNK_WIDTH / 2, ChunkTerrain.CHUNK_HEIGHT / 2, zLevelHeight],
|
||||||
worldH: zoomWorldH,
|
worldH: zoomWorldH,
|
||||||
terrain: terrainMesh ? { mesh: terrainMesh, texture: terrainTexture } : null,
|
terrain: terrainMesh ? { mesh: terrainMesh, texture: terrainTexture } : null,
|
||||||
|
gridLines: gridLinesMesh ? { mesh: gridLinesMesh } : null,
|
||||||
models,
|
models,
|
||||||
neighbors,
|
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 = {
|
// scene = {
|
||||||
// target: [x, y, z], worldH: number,
|
// target: [x, y, z], worldH: number,
|
||||||
// terrain: { mesh, texture } | null,
|
// terrain: { mesh, texture } | null,
|
||||||
|
// gridLines: { mesh } | null,
|
||||||
// models: [{ mesh, texture, color: [r,g,b,a in 0..1], offset: [x,y,z] }],
|
// models: [{ mesh, texture, color: [r,g,b,a in 0..1], offset: [x,y,z] }],
|
||||||
// neighbors: [{
|
// neighbors: [{
|
||||||
// offset: [x,y,z],
|
// offset: [x,y,z],
|
||||||
@@ -248,6 +249,10 @@ const MapRenderer = (() => {
|
|||||||
drawMesh(scene.terrain.mesh, identity, scene.terrain.texture, [1, 1, 1, 1], true);
|
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) {
|
for(const model of scene.models) {
|
||||||
const modelMatrix = mat4Translation(new Float32Array(16), model.offset);
|
const modelMatrix = mat4Translation(new Float32Array(16), model.offset);
|
||||||
drawMesh(model.mesh, modelMatrix, model.texture, model.color, true);
|
drawMesh(model.mesh, modelMatrix, model.texture, model.color, true);
|
||||||
|
|||||||
@@ -506,6 +506,10 @@ errorret_t assetLocaleGetString(
|
|||||||
sizeof(lineBuffer)
|
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
|
// Skip blanks, comments, etc and start looking for msgid's
|
||||||
errorChain(assetLocaleLineSkipBlanks(&reader, lineBuffer));
|
errorChain(assetLocaleLineSkipBlanks(&reader, lineBuffer));
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
|||||||
PUBLIC
|
PUBLIC
|
||||||
rpg.c
|
rpg.c
|
||||||
rpgcamera.c
|
rpgcamera.c
|
||||||
rpgtextbox.c
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Subdirs
|
# Subdirs
|
||||||
|
|||||||
@@ -7,7 +7,6 @@
|
|||||||
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||||
PUBLIC
|
PUBLIC
|
||||||
cutscenesystem.c
|
cutscenesystem.c
|
||||||
cutscenemode.c
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Subdirs
|
# Subdirs
|
||||||
|
|||||||
@@ -7,8 +7,112 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "rpg/cutscene/item/cutsceneitem.h"
|
#include "rpg/cutscene/item/cutsceneitem.h"
|
||||||
|
#include "rpg/cutscene/cutscenepause.h"
|
||||||
|
|
||||||
typedef struct cutscene_s {
|
typedef struct cutscene_s {
|
||||||
const cutsceneitem_t *items;
|
const cutsceneitem_t *items;
|
||||||
uint8_t itemCount;
|
uint8_t itemCount;
|
||||||
} cutscene_t;
|
cutscenepause_t pause;
|
||||||
|
} cutscene_t;
|
||||||
|
|
||||||
|
#define CUTSCENE(NAME, 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 \
|
||||||
|
};
|
||||||
|
|
||||||
|
#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_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) \
|
||||||
|
) \
|
||||||
|
} \
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
|
* Copyright (c) 2025 Dominic Masters
|
||||||
*
|
*
|
||||||
* This software is released under the MIT License.
|
* This software is released under the MIT License.
|
||||||
* https://opensource.org/licenses/MIT
|
* https://opensource.org/licenses/MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "cutscenesystem.h"
|
#include "cutscenesystem.h"
|
||||||
|
#include "rpg/entity/entity.h"
|
||||||
#include "util/memory.h"
|
#include "util/memory.h"
|
||||||
|
#include "assert/assert.h"
|
||||||
|
|
||||||
cutscenesystem_t CUTSCENE_SYSTEM;
|
cutscenesystem_t CUTSCENE_SYSTEM;
|
||||||
|
|
||||||
@@ -15,9 +17,19 @@ void cutsceneSystemInit() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void cutsceneSystemStartCutscene(const cutscene_t *cutscene) {
|
void cutsceneSystemStartCutscene(const cutscene_t *cutscene) {
|
||||||
|
cutsceneSystemStartCutsceneWith(cutscene, NULL, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
void cutsceneSystemStartCutsceneWith(
|
||||||
|
const cutscene_t *cutscene,
|
||||||
|
entity_t *interact,
|
||||||
|
entity_t *interacted
|
||||||
|
) {
|
||||||
CUTSCENE_SYSTEM.scene = cutscene;
|
CUTSCENE_SYSTEM.scene = cutscene;
|
||||||
CUTSCENE_SYSTEM.mode = CUTSCENE_MODE_INITIAL;
|
CUTSCENE_SYSTEM.pause = cutscene->pause;
|
||||||
CUTSCENE_SYSTEM.currentItem = 0xFF;// Set to 0xFF so start wraps.
|
CUTSCENE_SYSTEM.entityInteract = interact;
|
||||||
|
CUTSCENE_SYSTEM.entityInteracted = interacted;
|
||||||
|
CUTSCENE_SYSTEM.currentItem = 0xFF;// Set to 0xFF so Next wraps to 0.
|
||||||
cutsceneSystemNext();
|
cutsceneSystemNext();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -25,7 +37,7 @@ void cutsceneSystemUpdate() {
|
|||||||
if(CUTSCENE_SYSTEM.scene == NULL) return;
|
if(CUTSCENE_SYSTEM.scene == NULL) return;
|
||||||
|
|
||||||
const cutsceneitem_t *item = cutsceneSystemGetCurrentItem();
|
const cutsceneitem_t *item = cutsceneSystemGetCurrentItem();
|
||||||
cutsceneItemUpdate(item, &CUTSCENE_SYSTEM.data);
|
if(cutsceneItemUpdate(item, &CUTSCENE_SYSTEM.data)) cutsceneSystemNext();
|
||||||
}
|
}
|
||||||
|
|
||||||
void cutsceneSystemNext() {
|
void cutsceneSystemNext() {
|
||||||
@@ -39,7 +51,9 @@ void cutsceneSystemNext() {
|
|||||||
) {
|
) {
|
||||||
CUTSCENE_SYSTEM.scene = NULL;
|
CUTSCENE_SYSTEM.scene = NULL;
|
||||||
CUTSCENE_SYSTEM.currentItem = 0xFF;
|
CUTSCENE_SYSTEM.currentItem = 0xFF;
|
||||||
CUTSCENE_SYSTEM.mode = CUTSCENE_MODE_NONE;
|
CUTSCENE_SYSTEM.pause = CUTSCENE_PAUSE_NONE;
|
||||||
|
CUTSCENE_SYSTEM.entityInteract = NULL;
|
||||||
|
CUTSCENE_SYSTEM.entityInteracted = NULL;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,8 +69,32 @@ const cutsceneitem_t * cutsceneSystemGetCurrentItem() {
|
|||||||
return &CUTSCENE_SYSTEM.scene->items[CUTSCENE_SYSTEM.currentItem];
|
return &CUTSCENE_SYSTEM.scene->items[CUTSCENE_SYSTEM.currentItem];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
entity_t * cutsceneSystemGetEntity(const uint8_t entityIndex) {
|
||||||
|
if(entityIndex == CUTSCENE_ENTITY_INTERACT) {
|
||||||
|
assertNotNull(
|
||||||
|
CUTSCENE_SYSTEM.entityInteract,
|
||||||
|
"CUTSCENE_ENTITY_INTERACT used but no interact entity is set"
|
||||||
|
);
|
||||||
|
return CUTSCENE_SYSTEM.entityInteract;
|
||||||
|
}
|
||||||
|
if(entityIndex == CUTSCENE_ENTITY_INTERACTED) {
|
||||||
|
assertNotNull(
|
||||||
|
CUTSCENE_SYSTEM.entityInteracted,
|
||||||
|
"CUTSCENE_ENTITY_INTERACTED used but no interacted entity is set"
|
||||||
|
);
|
||||||
|
return CUTSCENE_SYSTEM.entityInteracted;
|
||||||
|
}
|
||||||
|
assertTrue(
|
||||||
|
entityIndex < ENTITY_COUNT,
|
||||||
|
"Entity index is out of range"
|
||||||
|
);
|
||||||
|
return &ENTITIES[entityIndex];
|
||||||
|
}
|
||||||
|
|
||||||
void cutsceneSystemDispose() {
|
void cutsceneSystemDispose() {
|
||||||
CUTSCENE_SYSTEM.scene = NULL;
|
CUTSCENE_SYSTEM.scene = NULL;
|
||||||
CUTSCENE_SYSTEM.currentItem = 0xFF;
|
CUTSCENE_SYSTEM.currentItem = 0xFF;
|
||||||
CUTSCENE_SYSTEM.mode = CUTSCENE_MODE_NONE;
|
CUTSCENE_SYSTEM.pause = CUTSCENE_PAUSE_NONE;
|
||||||
}
|
CUTSCENE_SYSTEM.entityInteract = NULL;
|
||||||
|
CUTSCENE_SYSTEM.entityInteracted = NULL;
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,15 +7,21 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "cutscene.h"
|
#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)
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
const cutscene_t *scene;
|
const cutscene_t *scene;
|
||||||
uint8_t currentItem;
|
uint8_t currentItem;
|
||||||
|
cutscenepause_t pause;
|
||||||
|
entity_t *entityInteract;
|
||||||
|
entity_t *entityInteracted;
|
||||||
|
|
||||||
// Data (used by the current item).
|
// Data (used by the current item).
|
||||||
cutsceneitemdata_t data;
|
cutsceneitemdata_t data;
|
||||||
cutscenemode_t mode;
|
|
||||||
} cutscenesystem_t;
|
} cutscenesystem_t;
|
||||||
|
|
||||||
extern cutscenesystem_t CUTSCENE_SYSTEM;
|
extern cutscenesystem_t CUTSCENE_SYSTEM;
|
||||||
@@ -26,12 +32,35 @@ extern cutscenesystem_t CUTSCENE_SYSTEM;
|
|||||||
void cutsceneSystemInit();
|
void cutsceneSystemInit();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Start a cutscene.
|
* Start a cutscene with no bound entities.
|
||||||
*
|
*
|
||||||
* @param cutscene Pointer to the cutscene to start.
|
* @param cutscene Pointer to the cutscene to start.
|
||||||
*/
|
*/
|
||||||
void cutsceneSystemStartCutscene(const cutscene_t *cutscene);
|
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 and CUTSCENE_ENTITY_INTERACTED.
|
||||||
|
* 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);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Advance to the next item in the cutscene.
|
* Advance to the next item in the cutscene.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
# Copyright (c) 2025 Dominic Masters
|
# Copyright (c) 2025 Dominic Masters
|
||||||
#
|
#
|
||||||
# This software is released under the MIT License.
|
# This software is released under the MIT License.
|
||||||
# https://opensource.org/licenses/MIT
|
# https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
# Sources
|
|
||||||
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||||
PUBLIC
|
PUBLIC
|
||||||
cutsceneitem.c
|
cutsceneitem.c
|
||||||
cutsceneentitymove.c
|
cutscenecallback.c
|
||||||
)
|
)
|
||||||
|
|
||||||
|
add_subdirectory(control)
|
||||||
|
add_subdirectory(entity)
|
||||||
|
add_subdirectory(item)
|
||||||
|
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,22 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2025 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "cutsceneitem.h"
|
||||||
|
|
||||||
|
void cutsceneCallbackStart(
|
||||||
|
const cutsceneitem_t *item,
|
||||||
|
cutsceneitemdata_t *data
|
||||||
|
) {
|
||||||
|
if(item->callback != NULL) item->callback();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool_t cutsceneCallbackUpdate(
|
||||||
|
const cutsceneitem_t *item,
|
||||||
|
cutsceneitemdata_t *data
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* Copyright (c) 2025 Dominic Masters
|
* Copyright (c) 2025 Dominic Masters
|
||||||
*
|
*
|
||||||
* This software is released under the MIT License.
|
* This software is released under the MIT License.
|
||||||
* https://opensource.org/licenses/MIT
|
* https://opensource.org/licenses/MIT
|
||||||
*/
|
*/
|
||||||
@@ -8,4 +8,30 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "dusk.h"
|
#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);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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,56 @@
|
|||||||
/**
|
/**
|
||||||
* Copyright (c) 2025 Dominic Masters
|
* Copyright (c) 2025 Dominic Masters
|
||||||
*
|
*
|
||||||
* This software is released under the MIT License.
|
* This software is released under the MIT License.
|
||||||
* https://opensource.org/licenses/MIT
|
* https://opensource.org/licenses/MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "rpg/cutscene/cutscenesystem.h"
|
#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) {
|
void cutsceneItemStart(const cutsceneitem_t *item, cutsceneitemdata_t *data) {
|
||||||
switch(item->type) {
|
switch(item->type) {
|
||||||
case CUTSCENE_ITEM_TYPE_TEXT: {
|
case CUTSCENE_ITEM_TYPE_TEXT:
|
||||||
rpgTextboxShow(
|
cutsceneTextStart(item, data);
|
||||||
item->text.position,
|
|
||||||
item->text.text
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case CUTSCENE_ITEM_TYPE_WAIT:
|
|
||||||
data->wait = item->wait;
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case CUTSCENE_ITEM_TYPE_CALLBACK:
|
case CUTSCENE_ITEM_TYPE_CALLBACK:
|
||||||
if(item->callback != NULL) item->callback();
|
cutsceneCallbackStart(item, data);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CUTSCENE_ITEM_TYPE_WAIT:
|
||||||
|
cutsceneWaitStart(item, data);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case CUTSCENE_ITEM_TYPE_CUTSCENE:
|
case CUTSCENE_ITEM_TYPE_CUTSCENE:
|
||||||
if(item->cutscene != NULL) cutsceneSystemStartCutscene(item->cutscene);
|
if(item->cutscene != NULL) cutsceneSystemStartCutscene(item->cutscene);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case CUTSCENE_ITEM_TYPE_ENTITY_MOVE:
|
case CUTSCENE_ITEM_TYPE_ENTITY_TELEPORT:
|
||||||
cutsceneEntityMoveStart(item);
|
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;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@@ -41,23 +58,39 @@ 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) {
|
switch(item->type) {
|
||||||
case CUTSCENE_ITEM_TYPE_TEXT:
|
case CUTSCENE_ITEM_TYPE_TEXT:
|
||||||
if(rpgTextboxIsVisible()) return;
|
return cutsceneTextUpdate(item, data);
|
||||||
cutsceneSystemNext();
|
|
||||||
break;
|
case CUTSCENE_ITEM_TYPE_CALLBACK:
|
||||||
|
return cutsceneCallbackUpdate(item, data);
|
||||||
|
|
||||||
case CUTSCENE_ITEM_TYPE_WAIT:
|
case CUTSCENE_ITEM_TYPE_WAIT:
|
||||||
data->wait -= TIME.delta;
|
return cutsceneWaitUpdate(item, data);
|
||||||
if(data->wait <= 0) cutsceneSystemNext();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case CUTSCENE_ITEM_TYPE_ENTITY_MOVE:
|
case CUTSCENE_ITEM_TYPE_ENTITY_TELEPORT:
|
||||||
cutsceneEntityMoveUpdate(item);
|
return cutsceneEntityTeleportUpdate(item, data);
|
||||||
break;
|
|
||||||
|
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);
|
||||||
|
|
||||||
default:
|
default:
|
||||||
break;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,21 @@
|
|||||||
/**
|
/**
|
||||||
* Copyright (c) 2025 Dominic Masters
|
* Copyright (c) 2025 Dominic Masters
|
||||||
*
|
*
|
||||||
* This software is released under the MIT License.
|
* This software is released under the MIT License.
|
||||||
* https://opensource.org/licenses/MIT
|
* https://opensource.org/licenses/MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "cutscenewait.h"
|
|
||||||
#include "cutscenecallback.h"
|
#include "cutscenecallback.h"
|
||||||
#include "cutscenetext.h"
|
#include "control/cutscenewait.h"
|
||||||
#include "rpg/overworld/worldpos.h"
|
#include "control/cutscenesetpause.h"
|
||||||
|
#include "control/cutsceneconcurrent.h"
|
||||||
|
#include "entity/cutsceneentityteleport.h"
|
||||||
|
#include "entity/cutsceneentitywalkto.h"
|
||||||
|
#include "entity/cutsceneentityremove.h"
|
||||||
|
#include "ui/cutscenetext.h"
|
||||||
|
#include "ui/cutscenefade.h"
|
||||||
|
#include "item/cutsceneitemgive.h"
|
||||||
|
|
||||||
typedef struct cutscene_s cutscene_t;
|
typedef struct cutscene_s cutscene_t;
|
||||||
|
|
||||||
@@ -19,42 +25,58 @@ typedef enum {
|
|||||||
CUTSCENE_ITEM_TYPE_CALLBACK,
|
CUTSCENE_ITEM_TYPE_CALLBACK,
|
||||||
CUTSCENE_ITEM_TYPE_WAIT,
|
CUTSCENE_ITEM_TYPE_WAIT,
|
||||||
CUTSCENE_ITEM_TYPE_CUTSCENE,
|
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
|
||||||
} cutsceneitemtype_t;
|
} cutsceneitemtype_t;
|
||||||
|
|
||||||
typedef struct cutsceneitem_s {
|
struct cutsceneitem_s {
|
||||||
cutsceneitemtype_t type;
|
cutsceneitemtype_t type;
|
||||||
|
|
||||||
// Arguments/Data that will be used when this item is invoked.
|
|
||||||
union {
|
union {
|
||||||
cutscenetext_t text;
|
cutscenetext_t text;
|
||||||
cutscenecallback_t callback;
|
cutscenecallback_t callback;
|
||||||
cutscenewait_t wait;
|
cutscenewait_t wait;
|
||||||
const cutscene_t *cutscene;
|
const cutscene_t *cutscene;
|
||||||
struct {
|
cutsceneentityteleport_t entityTeleport;
|
||||||
uint8_t entityIndex;
|
cutsceneentitywalkto_t entityWalkTo;
|
||||||
worldpos_t target;
|
cutscenefade_t fade;
|
||||||
bool_t run;
|
cutscenepause_t setPause;
|
||||||
} entityMove;
|
cutsceneconcurrent_t concurrent;
|
||||||
|
cutsceneitemgive_t itemGive;
|
||||||
|
cutsceneentityremove_t entityRemove;
|
||||||
};
|
};
|
||||||
} cutsceneitem_t;
|
};
|
||||||
|
|
||||||
typedef union {
|
typedef union cutsceneitemdata_u {
|
||||||
cutscenewaitdata_t wait;
|
cutscenewaitdata_t wait;
|
||||||
|
cutsceneentitywalktodata_t entityWalkTo;
|
||||||
|
cutsceneconcurrentdata_t concurrent;
|
||||||
} cutsceneitemdata_t;
|
} cutsceneitemdata_t;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Start the given cutscene item.
|
* Start the given cutscene item.
|
||||||
*
|
*
|
||||||
* @param item The cutscene item to start.
|
* @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).
|
* Tick the given cutscene item (one frame).
|
||||||
*
|
*
|
||||||
* @param item The cutscene item to tick.
|
* @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,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
|
||||||
|
cutsceneentityteleport.c
|
||||||
|
cutsceneentitywalkto.c
|
||||||
|
cutsceneentityremove.c
|
||||||
|
)
|
||||||
@@ -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,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,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,35 @@
|
|||||||
|
/**
|
||||||
|
* 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/backpack.h"
|
||||||
|
#include "ui/rpg/uitextboxmain.h"
|
||||||
|
#include "util/string.h"
|
||||||
|
|
||||||
|
void cutsceneItemGiveStart(
|
||||||
|
const cutsceneitem_t *item,
|
||||||
|
cutsceneitemdata_t *data
|
||||||
|
) {
|
||||||
|
backpackAdd(item->itemGive.item, item->itemGive.quantity);
|
||||||
|
|
||||||
|
char_t msg[CUTSCENE_TEXT_MAX_CHARS];
|
||||||
|
stringFormat(
|
||||||
|
msg,
|
||||||
|
CUTSCENE_TEXT_MAX_CHARS - 1,
|
||||||
|
"Received %s x%u",
|
||||||
|
ITEMS[item->itemGive.item].name,
|
||||||
|
(uint32_t)item->itemGive.quantity
|
||||||
|
);
|
||||||
|
uiTextboxMainSetText(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
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,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,20 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
#include "rpg/cutscene/cutscene.h"
|
||||||
#include "rpg/cutscene/cutscenesystem.h"
|
#include "rpg/cutscene/cutscenesystem.h"
|
||||||
|
|
||||||
static const cutsceneitem_t TEST_CUTSCENE_ONE_ITEMS[] = {
|
CUTSCENE(TEST_ONE, DEFAULT,
|
||||||
{ .type = CUTSCENE_ITEM_TYPE_TEXT, .text = { .text = "This is a test cutscene.", .position = RPG_TEXTBOX_POS_BOTTOM } },
|
CUTSCENE_TEXT("Test One."),
|
||||||
{ .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 } },
|
|
||||||
};
|
|
||||||
|
|
||||||
static const cutscene_t TEST_CUTSCENE_ONE = {
|
CUTSCENE(TEST_TWO, DEFAULT,
|
||||||
.items = TEST_CUTSCENE_ONE_ITEMS,
|
CUTSCENE_TEXT("Test Two."),
|
||||||
.itemCount = sizeof(TEST_CUTSCENE_ONE_ITEMS) / sizeof(cutsceneitem_t)
|
CUTSCENE_CONCURRENT(
|
||||||
};
|
CUTSCENE_ENTITY_WALK_TO(CUTSCENE_ENTITY_INTERACT, 4, 4, 0),
|
||||||
|
CUTSCENE_ENTITY_WALK_TO(CUTSCENE_ENTITY_INTERACTED, 8, 2, 0),
|
||||||
static const cutsceneitem_t TEST_CUTSCENE_TWO_ITEMS[] = {
|
),
|
||||||
{ .type = CUTSCENE_ITEM_TYPE_WAIT, .wait = 1.0f },
|
CUTSCENE_ITEM_GIVE(ITEM_ID_POTATO, 3),
|
||||||
{ .type = CUTSCENE_ITEM_TYPE_CUTSCENE, .cutscene = &TEST_CUTSCENE_ONE },
|
CUTSCENE_ENTITY_REMOVE(CUTSCENE_ENTITY_INTERACT),
|
||||||
};
|
CUTSCENE_TEXT("Done."),
|
||||||
|
);
|
||||||
static const cutscene_t TEST_CUTSCENE = {
|
|
||||||
.items = TEST_CUTSCENE_TWO_ITEMS,
|
|
||||||
.itemCount = sizeof(TEST_CUTSCENE_TWO_ITEMS) / sizeof(cutsceneitem_t)
|
|
||||||
};
|
|
||||||
@@ -8,6 +8,7 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
|||||||
PUBLIC
|
PUBLIC
|
||||||
entity.c
|
entity.c
|
||||||
entitydir.c
|
entitydir.c
|
||||||
|
entitypathstep.c
|
||||||
player.c
|
player.c
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
void entityAnimIdleUpdate(entity_t *entity) {
|
void entityAnimIdleUpdate(entity_t *entity) {
|
||||||
entity->renderPosition[0] = (float_t)entity->position.x;
|
entity->renderPosition[0] = (float_t)entity->position.x;
|
||||||
entity->renderPosition[1] = (float_t)entity->position.y;
|
entity->renderPosition[1] = (float_t)entity->position.y;
|
||||||
entity->renderPosition[2] = (float_t)entity->position.z
|
entity->renderPosition[2] = (
|
||||||
+ entityAnimTileZOffset(entity->position);
|
(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 * (
|
entity->renderPosition[1] = (float_t)entity->lastPosition.y + t * (
|
||||||
(float_t)entity->position.y - (float_t)entity->lastPosition.y
|
(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) {
|
void entityAnimTurnUpdate(entity_t *entity) {
|
||||||
entity->renderPosition[0] = (float_t)entity->position.x;
|
entity->renderPosition[0] = (float_t)entity->position.x;
|
||||||
entity->renderPosition[1] = (float_t)entity->position.y;
|
entity->renderPosition[1] = (float_t)entity->position.y;
|
||||||
entity->renderPosition[2] = (float_t)entity->position.z
|
entity->renderPosition[2] = (
|
||||||
+ entityAnimTileZOffset(entity->position);
|
(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 * (
|
entity->renderPosition[1] = (float_t)entity->lastPosition.y + t * (
|
||||||
(float_t)entity->position.y - (float_t)entity->lastPosition.y
|
(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,7 +10,6 @@
|
|||||||
#include "util/memory.h"
|
#include "util/memory.h"
|
||||||
#include "time/time.h"
|
#include "time/time.h"
|
||||||
#include "util/math.h"
|
#include "util/math.h"
|
||||||
#include "rpg/cutscene/cutscenemode.h"
|
|
||||||
#include "rpg/overworld/map.h"
|
#include "rpg/overworld/map.h"
|
||||||
#include "rpg/overworld/chunk.h"
|
#include "rpg/overworld/chunk.h"
|
||||||
#include "rpg/overworld/tile.h"
|
#include "rpg/overworld/tile.h"
|
||||||
@@ -43,10 +42,7 @@ void entityUpdate(entity_t *entity) {
|
|||||||
entityAnimUpdate(entity);
|
entityAnimUpdate(entity);
|
||||||
|
|
||||||
// Movement code.
|
// Movement code.
|
||||||
if(
|
if(ENTITY_CALLBACKS[entity->type].movement != NULL) {
|
||||||
cutsceneModeIsInputAllowed() &&
|
|
||||||
ENTITY_CALLBACKS[entity->type].movement != NULL
|
|
||||||
) {
|
|
||||||
ENTITY_CALLBACKS[entity->type].movement(entity);
|
ENTITY_CALLBACKS[entity->type].movement(entity);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -196,6 +192,14 @@ void entityWalk(entity_t *entity, const entitydir_t direction) {
|
|||||||
// Can we walk here?
|
// Can we walk here?
|
||||||
if(!raise && !fall && !tileIsWalkable(tileNew)) return;// Blocked
|
if(!raise && !fall && !tileIsWalkable(tileNew)) 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 in way?
|
||||||
entity_t *other = ENTITIES;
|
entity_t *other = ENTITIES;
|
||||||
do {
|
do {
|
||||||
@@ -209,12 +213,6 @@ void entityWalk(entity_t *entity, const entitydir_t direction) {
|
|||||||
entity->position = newPos;
|
entity->position = newPos;
|
||||||
entity->animation = ENTITY_ANIM_WALK;
|
entity->animation = ENTITY_ANIM_WALK;
|
||||||
entity->animTime = ENTITY_ANIM_WALK_DURATION;// TODO: Running vs walking
|
entity->animTime = ENTITY_ANIM_WALK_DURATION;// TODO: Running vs walking
|
||||||
|
|
||||||
if(raise) {
|
|
||||||
entity->position.z += 1;
|
|
||||||
} else if(fall) {
|
|
||||||
entity->position.z -= 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void entityRun(entity_t *entity, const entitydir_t direction) {
|
void entityRun(entity_t *entity, const entitydir_t direction) {
|
||||||
@@ -246,6 +244,15 @@ uint8_t entityGetAvailable() {
|
|||||||
return 0xFF;
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
void entitySetChunk(entity_t *entity, const uint8_t chunkIndex) {
|
void entitySetChunk(entity_t *entity, const uint8_t chunkIndex) {
|
||||||
assertNotNull(entity, "Entity pointer cannot be NULL");
|
assertNotNull(entity, "Entity pointer cannot be NULL");
|
||||||
|
|
||||||
|
|||||||
@@ -122,4 +122,12 @@ uint8_t entityGetAvailable();
|
|||||||
* @param entity Pointer to the entity.
|
* @param entity Pointer to the entity.
|
||||||
* @param chunkIndex Index of the chunk to assign to, or 0xFF for none.
|
* @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);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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,57 @@
|
|||||||
|
/**
|
||||||
|
* 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)) 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
|
||||||
|
);
|
||||||
@@ -20,7 +20,11 @@ void entityInteractWith(entity_t *player, entity_t *target) {
|
|||||||
target->interact.data.cutscene,
|
target->interact.data.cutscene,
|
||||||
"Interact cutscene pointer cannot be NULL"
|
"Interact cutscene pointer cannot be NULL"
|
||||||
);
|
);
|
||||||
cutsceneSystemStartCutscene(target->interact.data.cutscene);
|
cutsceneSystemStartCutsceneWith(
|
||||||
|
target->interact.data.cutscene,
|
||||||
|
player,
|
||||||
|
target
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ENTITY_INTERACT_PRINT:
|
case ENTITY_INTERACT_PRINT:
|
||||||
|
|||||||
@@ -7,9 +7,9 @@
|
|||||||
|
|
||||||
#include "rpg/entity/entity.h"
|
#include "rpg/entity/entity.h"
|
||||||
#include "assert/assert.h"
|
#include "assert/assert.h"
|
||||||
|
#include "rpg/cutscene/cutscenesystem.h"
|
||||||
|
|
||||||
#include "rpg/cutscene/scene/testcutscene.h"
|
#include "rpg/cutscene/scene/testcutscene.h"
|
||||||
#include "rpg/rpgtextbox.h"
|
|
||||||
|
|
||||||
const npcmovecallback_t NPC_MOVE_CALLBACKS[NPC_MOVE_TYPE_COUNT] = {
|
const npcmovecallback_t NPC_MOVE_CALLBACKS[NPC_MOVE_TYPE_COUNT] = {
|
||||||
[NPC_MOVE_TYPE_NULL] = { 0 },
|
[NPC_MOVE_TYPE_NULL] = { 0 },
|
||||||
@@ -50,7 +50,8 @@ void npcSetMoveType(entity_t *entity, const npcmovetype_t moveType) {
|
|||||||
|
|
||||||
void npcMovement(entity_t *entity) {
|
void npcMovement(entity_t *entity) {
|
||||||
assertNotNull(entity, "Entity pointer cannot be NULL");
|
assertNotNull(entity, "Entity pointer cannot be NULL");
|
||||||
|
if(CUTSCENE_SYSTEM.pause & CUTSCENE_PAUSE_NPC) return;
|
||||||
|
|
||||||
npc_t *npc = &entity->data.npc;
|
npc_t *npc = &entity->data.npc;
|
||||||
if(npc->interactState != NPC_INTERACT_STATE_NONE) return;
|
if(npc->interactState != NPC_INTERACT_STATE_NONE) return;
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,9 @@
|
|||||||
|
|
||||||
#include "npc.h"
|
#include "npc.h"
|
||||||
#include "rpg/entity/entity.h"
|
#include "rpg/entity/entity.h"
|
||||||
|
#include "rpg/entity/entitypathstep.h"
|
||||||
#include "rpg/overworld/worldpos.h"
|
#include "rpg/overworld/worldpos.h"
|
||||||
|
#include "assert/assert.h"
|
||||||
|
|
||||||
void npcPathInit(npc_t *npc) {
|
void npcPathInit(npc_t *npc) {
|
||||||
npcpath_t *path = &npc->moveData.path;
|
npcpath_t *path = &npc->moveData.path;
|
||||||
@@ -15,6 +17,15 @@ void npcPathInit(npc_t *npc) {
|
|||||||
path->index = 0;
|
path->index = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void npcPathAddNode(npc_t *npc, const worldpos_t pos) {
|
||||||
|
assertNotNull(npc, "NPC must not be NULL");
|
||||||
|
assertTrue(
|
||||||
|
npc->moveData.path.count < NPC_PATH_COUNT_MAX,
|
||||||
|
"NPC path is full"
|
||||||
|
);
|
||||||
|
npc->moveData.path.positions[npc->moveData.path.count++] = pos;
|
||||||
|
}
|
||||||
|
|
||||||
void npcPathMovement(entity_t *entity) {
|
void npcPathMovement(entity_t *entity) {
|
||||||
npcpath_t *path = &entity->data.npc.moveData.path;
|
npcpath_t *path = &entity->data.npc.moveData.path;
|
||||||
if(path->count == 0) return;
|
if(path->count == 0) return;
|
||||||
@@ -22,23 +33,11 @@ void npcPathMovement(entity_t *entity) {
|
|||||||
|
|
||||||
// Advance past any waypoints already reached (including the current one).
|
// Advance past any waypoints already reached (including the current one).
|
||||||
worldpos_t *target = &path->positions[path->index];
|
worldpos_t *target = &path->positions[path->index];
|
||||||
if(worldPosIsEqual(entity->position, *target)) {
|
if(entityPathStep(entity, *target, false)) {
|
||||||
path->index = (path->index + 1) % path->count;
|
path->index = (path->index + 1) % path->count;
|
||||||
target = &path->positions[path->index];
|
target = &path->positions[path->index];
|
||||||
// New target is the same tile - nothing to do this tick
|
// New target is the same tile - nothing to do this tick
|
||||||
if(worldPosIsEqual(entity->position, *target)) return;
|
if(worldPosIsEqual(entity->position, *target)) return;
|
||||||
|
entityPathStep(entity, *target, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
entitydir_t dir;
|
|
||||||
worldpos_t pos = entity->position;
|
|
||||||
if(pos.x != target->x) {
|
|
||||||
dir = pos.x < target->x ? ENTITY_DIR_EAST : ENTITY_DIR_WEST;
|
|
||||||
} else if(pos.y != target->y) {
|
|
||||||
dir = pos.y < target->y ? ENTITY_DIR_NORTH : ENTITY_DIR_SOUTH;
|
|
||||||
} else {
|
|
||||||
// x and y match but z differs - step to correct z via ramp logic
|
|
||||||
dir = pos.z < target->z ? ENTITY_DIR_NORTH : ENTITY_DIR_SOUTH;
|
|
||||||
}
|
|
||||||
|
|
||||||
entityWalk(entity, dir);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,14 @@ typedef struct {
|
|||||||
*/
|
*/
|
||||||
void npcPathInit(npc_t *npc);
|
void npcPathInit(npc_t *npc);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Appends a waypoint to the NPC's path. Has no effect if the path is full.
|
||||||
|
*
|
||||||
|
* @param npc Pointer to the NPC.
|
||||||
|
* @param pos The world position to append.
|
||||||
|
*/
|
||||||
|
void npcPathAddNode(npc_t *npc, const worldpos_t pos);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Movement tick for an NPC following a path.
|
* Movement tick for an NPC following a path.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
#include "time/time.h"
|
#include "time/time.h"
|
||||||
#include "ui/focus/uifocus.h"
|
#include "ui/focus/uifocus.h"
|
||||||
#include "ui/frame/uisettings.h"
|
#include "ui/frame/uisettings.h"
|
||||||
|
#include "rpg/cutscene/cutscenesystem.h"
|
||||||
|
|
||||||
void playerInit(entity_t *entity) {
|
void playerInit(entity_t *entity) {
|
||||||
assertNotNull(entity, "Entity pointer cannot be NULL");
|
assertNotNull(entity, "Entity pointer cannot be NULL");
|
||||||
@@ -23,6 +24,7 @@ bool_t playerCanInteract(entity_t *entity) {
|
|||||||
|
|
||||||
void playerInput(entity_t *entity) {
|
void playerInput(entity_t *entity) {
|
||||||
assertNotNull(entity, "Entity pointer cannot be NULL");
|
assertNotNull(entity, "Entity pointer cannot be NULL");
|
||||||
|
if(CUTSCENE_SYSTEM.pause & CUTSCENE_PAUSE_PLAYER) return;
|
||||||
|
|
||||||
// Toggle settings on pause
|
// Toggle settings on pause
|
||||||
if(uiSettingsIsOpen() && inputPressed(INPUT_ACTION_PAUSE)) {
|
if(uiSettingsIsOpen() && inputPressed(INPUT_ACTION_PAUSE)) {
|
||||||
|
|||||||
@@ -22,8 +22,7 @@ void inventoryInit(
|
|||||||
inventory->storage = storage;
|
inventory->storage = storage;
|
||||||
inventory->storageSize = storageSize;
|
inventory->storageSize = storageSize;
|
||||||
|
|
||||||
// Zero item ids.
|
memoryZero(storage, sizeof(inventorystack_t) * storageSize);
|
||||||
memoryZero(inventory->storage, sizeof(inventorystack_t) * storageSize);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool_t inventoryItemExists(const inventory_t *inventory, const itemid_t item) {
|
bool_t inventoryItemExists(const inventory_t *inventory, const itemid_t item) {
|
||||||
|
|||||||
@@ -296,7 +296,9 @@ void mapChunkLoaded(void *params, void *user) {
|
|||||||
);
|
);
|
||||||
worldpos_t wp;
|
worldpos_t wp;
|
||||||
chunkPosToWorldPos(&chunk->position, &wp);
|
chunkPosToWorldPos(&chunk->position, &wp);
|
||||||
vec3 wpf = { (float_t)wp.x, (float_t)wp.y, (float_t)wp.z };
|
vec3 wpf = {
|
||||||
|
(float_t)wp.x, (float_t)wp.y, (float_t)wp.z * WORLD_LAYER_HEIGHT
|
||||||
|
};
|
||||||
for(uint8_t m = 0; m < meshCount; m++) {
|
for(uint8_t m = 0; m < meshCount; m++) {
|
||||||
stringCopy(
|
stringCopy(
|
||||||
chunk->modelNames[m],
|
chunk->modelNames[m],
|
||||||
@@ -307,8 +309,13 @@ void mapChunkLoaded(void *params, void *user) {
|
|||||||
entry->data.chunk.meshOffsets[m],
|
entry->data.chunk.meshOffsets[m],
|
||||||
chunk->meshOffsets[m]
|
chunk->meshOffsets[m]
|
||||||
);
|
);
|
||||||
|
vec3 scaledOffset = {
|
||||||
|
chunk->meshOffsets[m][0],
|
||||||
|
chunk->meshOffsets[m][1],
|
||||||
|
chunk->meshOffsets[m][2] * WORLD_LAYER_HEIGHT
|
||||||
|
};
|
||||||
vec3 pos;
|
vec3 pos;
|
||||||
glm_vec3_add(wpf, chunk->meshOffsets[m], pos);
|
glm_vec3_add(wpf, scaledOffset, pos);
|
||||||
glm_translate_make(chunk->meshModels[m], pos);
|
glm_translate_make(chunk->meshModels[m], pos);
|
||||||
chunk->modelEntries[m] = entry->data.chunk.modelEntries[m];
|
chunk->modelEntries[m] = entry->data.chunk.modelEntries[m];
|
||||||
entry->data.chunk.modelEntries[m] = NULL;
|
entry->data.chunk.modelEntries[m] = NULL;
|
||||||
|
|||||||
@@ -11,6 +11,12 @@
|
|||||||
|
|
||||||
#define TILE_SIZE_PIXELS 24
|
#define TILE_SIZE_PIXELS 24
|
||||||
|
|
||||||
|
// World-space Z distance of one Z-layer/story, in render/world-float
|
||||||
|
// space. A ramp rises this much over a horizontal run of 1 tile-width;
|
||||||
|
// chosen as 1/sqrt(2) so the ramp's slope, derived from a 45-45-90
|
||||||
|
// triangle with hypotenuse 1, matches the length of a flat tile edge.
|
||||||
|
#define WORLD_LAYER_HEIGHT 0.70710678f
|
||||||
|
|
||||||
#define CHUNK_WIDTH 16
|
#define CHUNK_WIDTH 16
|
||||||
#define CHUNK_HEIGHT 16
|
#define CHUNK_HEIGHT 16
|
||||||
#define CHUNK_DEPTH 4
|
#define CHUNK_DEPTH 4
|
||||||
|
|||||||
+12
-14
@@ -7,11 +7,13 @@
|
|||||||
|
|
||||||
#include "rpg.h"
|
#include "rpg.h"
|
||||||
#include "entity/entity.h"
|
#include "entity/entity.h"
|
||||||
|
#include "rpg/entity/npc/npcpath.h"
|
||||||
#include "rpg/overworld/map.h"
|
#include "rpg/overworld/map.h"
|
||||||
#include "rpg/cutscene/cutscenesystem.h"
|
#include "rpg/cutscene/cutscenesystem.h"
|
||||||
|
#include "rpg/cutscene/scene/testcutscene.h"
|
||||||
|
#include "rpg/item/backpack.h"
|
||||||
#include "time/time.h"
|
#include "time/time.h"
|
||||||
#include "rpgcamera.h"
|
#include "rpgcamera.h"
|
||||||
#include "rpgtextbox.h"
|
|
||||||
#include "util/memory.h"
|
#include "util/memory.h"
|
||||||
#include "util/string.h"
|
#include "util/string.h"
|
||||||
#include "assert/assert.h"
|
#include "assert/assert.h"
|
||||||
@@ -19,14 +21,12 @@
|
|||||||
errorret_t rpgInit(void) {
|
errorret_t rpgInit(void) {
|
||||||
memoryZero(ENTITIES, sizeof(ENTITIES));
|
memoryZero(ENTITIES, sizeof(ENTITIES));
|
||||||
|
|
||||||
// Init cutscene subsystem
|
backpackInit();
|
||||||
cutsceneSystemInit();
|
cutsceneSystemInit();
|
||||||
|
|
||||||
errorChain(mapInit());
|
errorChain(mapInit());
|
||||||
|
|
||||||
rpgCameraInit();
|
rpgCameraInit();
|
||||||
rpgTextboxInit();
|
|
||||||
|
|
||||||
// Init world
|
// Init world
|
||||||
errorChain(mapPositionSet((chunkpos_t){ 0, 0, 0 }));
|
errorChain(mapPositionSet((chunkpos_t){ 0, 0, 0 }));
|
||||||
|
|
||||||
@@ -35,7 +35,7 @@ errorret_t rpgInit(void) {
|
|||||||
assertTrue(entIndex != 0xFF, "No available entity slots!.");
|
assertTrue(entIndex != 0xFF, "No available entity slots!.");
|
||||||
entity_t *ent = &ENTITIES[entIndex];
|
entity_t *ent = &ENTITIES[entIndex];
|
||||||
entityInit(ent, ENTITY_TYPE_PLAYER);
|
entityInit(ent, ENTITY_TYPE_PLAYER);
|
||||||
ent->position = (worldpos_t){ 10, 2, 0 };
|
entityPositionSet(ent, (worldpos_t){ 10, 2, 0 });
|
||||||
RPG_CAMERA.mode = RPG_CAMERA_MODE_FOLLOW_ENTITY;
|
RPG_CAMERA.mode = RPG_CAMERA_MODE_FOLLOW_ENTITY;
|
||||||
RPG_CAMERA.followEntity.followEntityId = ent->id;
|
RPG_CAMERA.followEntity.followEntityId = ent->id;
|
||||||
{
|
{
|
||||||
@@ -49,9 +49,9 @@ errorret_t rpgInit(void) {
|
|||||||
entity_t *npc = &ENTITIES[npcIndex];
|
entity_t *npc = &ENTITIES[npcIndex];
|
||||||
entityInit(npc, ENTITY_TYPE_NPC);
|
entityInit(npc, ENTITY_TYPE_NPC);
|
||||||
npcSetMoveType(npc, NPC_MOVE_TYPE_PATH);
|
npcSetMoveType(npc, NPC_MOVE_TYPE_PATH);
|
||||||
npc->position = (worldpos_t){ 8, 8, 0 };
|
entityPositionSet(npc, (worldpos_t){ 8, 8, 1 });
|
||||||
npc->interact.type = ENTITY_INTERACT_PRINT;
|
npc->interact.type = ENTITY_INTERACT_CUTSCENE;
|
||||||
npc->interact.data.message = "hello world";
|
npc->interact.data.cutscene = CUTSCENE_REFERENCE(TEST_TWO);
|
||||||
{
|
{
|
||||||
chunkpos_t cp;
|
chunkpos_t cp;
|
||||||
worldPosToChunkPos(&npc->position, &cp);
|
worldPosToChunkPos(&npc->position, &cp);
|
||||||
@@ -59,12 +59,10 @@ errorret_t rpgInit(void) {
|
|||||||
if(ci != -1) entitySetChunk(npc, (uint8_t)ci);
|
if(ci != -1) entitySetChunk(npc, (uint8_t)ci);
|
||||||
}
|
}
|
||||||
|
|
||||||
// npcpath_t *path = &npc->data.npc.moveData.path;
|
npcPathAddNode(&npc->data.npc, (worldpos_t){ 4, 4, 0 });
|
||||||
// path->positions[0] = (worldpos_t){ 3, 3, 0 };
|
npcPathAddNode(&npc->data.npc, (worldpos_t){ 10, 10, 1 });
|
||||||
// path->positions[1] = (worldpos_t){ 10, 3, 0 };
|
npcPathAddNode(&npc->data.npc, (worldpos_t){ 4, 4, 0 });
|
||||||
// path->positions[2] = (worldpos_t){ 10, 10, 0 };
|
npcPathAddNode(&npc->data.npc, (worldpos_t){ 10, 10, 1 });
|
||||||
// path->positions[3] = (worldpos_t){ 3, 10, 0 };
|
|
||||||
// path->count = 4;
|
|
||||||
|
|
||||||
// All Good!
|
// All Good!
|
||||||
errorOk();
|
errorOk();
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ void rpgCameraUpdateProjection(void) {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
glm_perspective(
|
glm_perspective(
|
||||||
glm_rad(45.0f),
|
glm_rad(RPG_CAMERA_FOV),
|
||||||
SCREEN.aspect,
|
SCREEN.aspect,
|
||||||
0.1f,
|
0.1f,
|
||||||
100.0f,
|
100.0f,
|
||||||
@@ -65,7 +65,7 @@ errorret_t rpgCameraUpdate(void) {
|
|||||||
chunkpos_t chunkPos = {
|
chunkpos_t chunkPos = {
|
||||||
.x = (chunkunit_t)floorf(pos[0] / CHUNK_WIDTH),
|
.x = (chunkunit_t)floorf(pos[0] / CHUNK_WIDTH),
|
||||||
.y = (chunkunit_t)floorf(pos[1] / CHUNK_HEIGHT),
|
.y = (chunkunit_t)floorf(pos[1] / CHUNK_HEIGHT),
|
||||||
.z = (chunkunit_t)floorf(pos[2] / CHUNK_DEPTH)
|
.z = (chunkunit_t)floorf(pos[2] / WORLD_LAYER_HEIGHT / CHUNK_DEPTH)
|
||||||
};
|
};
|
||||||
|
|
||||||
errorChain(mapPositionSet((chunkpos_t){
|
errorChain(mapPositionSet((chunkpos_t){
|
||||||
|
|||||||
@@ -9,6 +9,8 @@
|
|||||||
#include "rpg/overworld/worldpos.h"
|
#include "rpg/overworld/worldpos.h"
|
||||||
#include "error/error.h"
|
#include "error/error.h"
|
||||||
|
|
||||||
|
#define RPG_CAMERA_FOV 35.0f
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
RPG_CAMERA_MODE_FREE,
|
RPG_CAMERA_MODE_FREE,
|
||||||
RPG_CAMERA_MODE_FOLLOW_ENTITY,
|
RPG_CAMERA_MODE_FOLLOW_ENTITY,
|
||||||
|
|||||||
@@ -1,39 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2025 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "rpgtextbox.h"
|
|
||||||
#include "util/memory.h"
|
|
||||||
#include "util/string.h"
|
|
||||||
#include "assert/assert.h"
|
|
||||||
|
|
||||||
rpgtextbox_t RPG_TEXTBOX;
|
|
||||||
|
|
||||||
void rpgTextboxInit() {
|
|
||||||
memoryZero(&RPG_TEXTBOX, sizeof(rpgtextbox_t));
|
|
||||||
}
|
|
||||||
|
|
||||||
void rpgTextboxShow(
|
|
||||||
const rpgtextboxpos_t position,
|
|
||||||
const char_t *text
|
|
||||||
) {
|
|
||||||
RPG_TEXTBOX.position = position;
|
|
||||||
RPG_TEXTBOX.visible = true;
|
|
||||||
|
|
||||||
stringCopy(
|
|
||||||
RPG_TEXTBOX.text,
|
|
||||||
text,
|
|
||||||
RPG_TEXTBOX_MAX_CHARS
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void rpgTextboxHide() {
|
|
||||||
RPG_TEXTBOX.visible = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool_t rpgTextboxIsVisible() {
|
|
||||||
return RPG_TEXTBOX.visible;
|
|
||||||
}
|
|
||||||
@@ -1,52 +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"
|
|
||||||
|
|
||||||
#define RPG_TEXTBOX_MAX_CHARS 256
|
|
||||||
|
|
||||||
typedef enum {
|
|
||||||
RPG_TEXTBOX_POS_TOP,
|
|
||||||
RPG_TEXTBOX_POS_BOTTOM,
|
|
||||||
} rpgtextboxpos_t;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
rpgtextboxpos_t position;
|
|
||||||
bool_t visible;
|
|
||||||
char_t text[RPG_TEXTBOX_MAX_CHARS];
|
|
||||||
} rpgtextbox_t;
|
|
||||||
|
|
||||||
extern rpgtextbox_t RPG_TEXTBOX;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes the RPG textbox.
|
|
||||||
*/
|
|
||||||
void rpgTextboxInit();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Shows the RPG textbox at a specified position.
|
|
||||||
*
|
|
||||||
* @param position The position to show the textbox at.
|
|
||||||
* @param text The text to display in the textbox (copied).
|
|
||||||
*/
|
|
||||||
void rpgTextboxShow(
|
|
||||||
const rpgtextboxpos_t position,
|
|
||||||
const char_t *text
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hides the RPG textbox.
|
|
||||||
*/
|
|
||||||
void rpgTextboxHide();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the RPG textbox is currently visible.
|
|
||||||
*
|
|
||||||
* @return true if the textbox is visible, false otherwise.
|
|
||||||
*/
|
|
||||||
bool_t rpgTextboxIsVisible();
|
|
||||||
@@ -59,7 +59,7 @@ errorret_t sceneOverworldRender(scenedata_t *sceneData) {
|
|||||||
));
|
));
|
||||||
|
|
||||||
// Camera Eye
|
// Camera Eye
|
||||||
float_t fov = glm_rad(45.0f);
|
float_t fov = glm_rad(RPG_CAMERA_FOV);
|
||||||
float_t pixelsPerUnit = TILE_SIZE_PIXELS;
|
float_t pixelsPerUnit = TILE_SIZE_PIXELS;
|
||||||
float_t worldH = (float_t)(SCREEN.height / SCREEN.scale3d) / pixelsPerUnit;
|
float_t worldH = (float_t)(SCREEN.height / SCREEN.scale3d) / pixelsPerUnit;
|
||||||
float_t z = (worldH * 0.5f) / tanf(fov * 0.5f);
|
float_t z = (worldH * 0.5f) / tanf(fov * 0.5f);
|
||||||
@@ -81,8 +81,15 @@ errorret_t sceneOverworldRender(scenedata_t *sceneData) {
|
|||||||
);
|
);
|
||||||
errorChain(shaderSetMatrix(&SHADER_UNLIT, SHADER_UNLIT_VIEW, eye));
|
errorChain(shaderSetMatrix(&SHADER_UNLIT, SHADER_UNLIT_VIEW, eye));
|
||||||
|
|
||||||
// Chunks
|
// Base terrain meshes, drawn with normal depth testing.
|
||||||
errorChain(sceneOverworldDrawChunks());
|
errorChain(sceneOverworldDrawChunksBase());
|
||||||
|
|
||||||
|
// Entities are drawn with depth testing fully disabled so sloped tiles
|
||||||
|
// (ramps) never clip them; entity-vs-entity overlap falls back to
|
||||||
|
// array draw order instead of true depth.
|
||||||
|
errorChain(displaySetState((displaystate_t){
|
||||||
|
.flags = DISPLAY_STATE_FLAG_CULL
|
||||||
|
}));
|
||||||
|
|
||||||
// Entities
|
// Entities
|
||||||
{
|
{
|
||||||
@@ -92,7 +99,7 @@ errorret_t sceneOverworldRender(scenedata_t *sceneData) {
|
|||||||
|
|
||||||
spritebatchsprite_t sprite;
|
spritebatchsprite_t sprite;
|
||||||
glm_vec3_copy(ent->renderPosition, sprite.min);
|
glm_vec3_copy(ent->renderPosition, sprite.min);
|
||||||
glm_vec3_add(sprite.min, (vec3){ 0, 0, 0.05f }, sprite.min);// Stop Fight
|
glm_vec3_add(sprite.min, (vec3){ 0, -0.05f, 0.05f }, sprite.min);// Stop Fight
|
||||||
glm_vec3_copy(sprite.min, sprite.max);
|
glm_vec3_copy(sprite.min, sprite.max);
|
||||||
glm_vec3_add(sprite.max, (vec3){ 1, 1, 0 }, sprite.max);
|
glm_vec3_add(sprite.max, (vec3){ 1, 1, 0 }, sprite.max);
|
||||||
glm_vec2_copy((vec2){ 0, 0 }, sprite.uvMin);
|
glm_vec2_copy((vec2){ 0, 0 }, sprite.uvMin);
|
||||||
@@ -115,16 +122,59 @@ errorret_t sceneOverworldRender(scenedata_t *sceneData) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Other chunk meshes (trees, buildings, etc), drawn last with normal
|
||||||
|
// depth testing so they correctly occlude entities standing beneath
|
||||||
|
// them.
|
||||||
|
errorChain(displaySetState((displaystate_t){
|
||||||
|
.flags = DISPLAY_STATE_FLAG_DEPTH_TEST | DISPLAY_STATE_FLAG_CULL
|
||||||
|
}));
|
||||||
|
errorChain(sceneOverworldDrawChunksProps());
|
||||||
|
|
||||||
errorOk();
|
errorOk();
|
||||||
}
|
}
|
||||||
|
|
||||||
errorret_t sceneOverworldDrawChunks() {
|
errorret_t sceneOverworldDrawChunksBase() {
|
||||||
for(chunkindex_t i = 0; i < MAP_CHUNK_COUNT; i++) {
|
for(chunkindex_t i = 0; i < MAP_CHUNK_COUNT; i++) {
|
||||||
chunk_t *chunk = MAP.chunkOrder[i];
|
chunk_t *chunk = MAP.chunkOrder[i];
|
||||||
if(chunk == NULL) continue;
|
if(chunk == NULL) continue;
|
||||||
if(chunk->meshCount == 0) continue;
|
if(chunk->meshCount == 0) continue;
|
||||||
|
if(chunk->modelEntries[0] == NULL) continue;
|
||||||
|
if(chunk->modelEntries[0]->state != ASSET_ENTRY_STATE_LOADED) continue;
|
||||||
|
|
||||||
for(uint8_t m = 0; m < chunk->meshCount; m++) {
|
assetmodeloutput_t *model = &chunk->modelEntries[0]->data.model;
|
||||||
|
if(model->meshEntry == NULL) continue;
|
||||||
|
|
||||||
|
texture_t *tex = model->texEntry != NULL
|
||||||
|
? &model->texEntry->data.texture
|
||||||
|
: NULL;
|
||||||
|
|
||||||
|
shadermaterial_t mat = {
|
||||||
|
.unlit = { .color = model->color, .texture = tex }
|
||||||
|
};
|
||||||
|
errorChain(shaderSetMatrix(
|
||||||
|
&SHADER_UNLIT, SHADER_UNLIT_MODEL, chunk->meshModels[0]
|
||||||
|
));
|
||||||
|
errorChain(shaderSetMaterial(&SHADER_UNLIT, &mat));
|
||||||
|
errorChain(meshDraw(
|
||||||
|
&model->meshEntry->data.mesh.mesh, 0, -1
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore identity model so subsequent renders (e.g. entities) are
|
||||||
|
// not affected by the last chunk transform.
|
||||||
|
mat4 identity;
|
||||||
|
glm_mat4_identity(identity);
|
||||||
|
errorChain(shaderSetMatrix(&SHADER_UNLIT, SHADER_UNLIT_MODEL, identity));
|
||||||
|
|
||||||
|
errorOk();
|
||||||
|
}
|
||||||
|
|
||||||
|
errorret_t sceneOverworldDrawChunksProps() {
|
||||||
|
for(chunkindex_t i = 0; i < MAP_CHUNK_COUNT; i++) {
|
||||||
|
chunk_t *chunk = MAP.chunkOrder[i];
|
||||||
|
if(chunk == NULL) continue;
|
||||||
|
|
||||||
|
for(uint8_t m = 1; m < chunk->meshCount; m++) {
|
||||||
if(chunk->modelEntries[m] == NULL) continue;
|
if(chunk->modelEntries[m] == NULL) continue;
|
||||||
if(chunk->modelEntries[m]->state != ASSET_ENTRY_STATE_LOADED) continue;
|
if(chunk->modelEntries[m]->state != ASSET_ENTRY_STATE_LOADED) continue;
|
||||||
|
|
||||||
@@ -148,8 +198,7 @@ errorret_t sceneOverworldDrawChunks() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restore identity model so subsequent renders (e.g. entities) are
|
// Restore identity model in case anything renders after props.
|
||||||
// not affected by the last chunk transform.
|
|
||||||
mat4 identity;
|
mat4 identity;
|
||||||
glm_mat4_identity(identity);
|
glm_mat4_identity(identity);
|
||||||
errorChain(shaderSetMatrix(&SHADER_UNLIT, SHADER_UNLIT_MODEL, identity));
|
errorChain(shaderSetMatrix(&SHADER_UNLIT, SHADER_UNLIT_MODEL, identity));
|
||||||
|
|||||||
@@ -29,12 +29,21 @@ errorret_t sceneOverworldInit(scenedata_t *sceneData);
|
|||||||
errorret_t sceneOverworldUpdate(scenedata_t *sceneData);
|
errorret_t sceneOverworldUpdate(scenedata_t *sceneData);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Draws all loaded chunks in two passes: base meshes first (shared texture,
|
* Draws the base (tile) mesh of every loaded chunk, with normal depth
|
||||||
* no binds between chunks), then each chunk's additional meshes.
|
* testing. Must be called before entities are rendered.
|
||||||
*
|
*
|
||||||
* @return An error if drawing failed, or errorOk() on success.
|
* @return An error if drawing failed, or errorOk() on success.
|
||||||
*/
|
*/
|
||||||
errorret_t sceneOverworldDrawChunks();
|
errorret_t sceneOverworldDrawChunksBase();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draws every loaded chunk's additional meshes (trees, buildings, etc),
|
||||||
|
* with normal depth testing so they correctly occlude entities standing
|
||||||
|
* beneath them. Must be called after entities are rendered.
|
||||||
|
*
|
||||||
|
* @return An error if drawing failed, or errorOk() on success.
|
||||||
|
*/
|
||||||
|
errorret_t sceneOverworldDrawChunksProps();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renders the overworld scene.
|
* Renders the overworld scene.
|
||||||
|
|||||||
@@ -6,8 +6,6 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#define DUSK_DISPLAY_SCALE_UI 2
|
|
||||||
#define DUSK_DISPLAY_SCALE_3D 2
|
|
||||||
#include "displaydolphin.h"
|
#include "displaydolphin.h"
|
||||||
|
|
||||||
#define displayPlatformInit displayInitDolphin
|
#define displayPlatformInit displayInitDolphin
|
||||||
|
|||||||
@@ -6,8 +6,6 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#define DUSK_DISPLAY_SCALE_UI 1
|
|
||||||
#define DUSK_DISPLAY_SCALE_3D 2
|
|
||||||
#include "display/displaysdl2.h"
|
#include "display/displaysdl2.h"
|
||||||
|
|
||||||
typedef displaysdl2_t displayplatform_t;
|
typedef displaysdl2_t displayplatform_t;
|
||||||
|
|||||||
+20
-26
@@ -133,21 +133,22 @@ static void test_update_entry_reaches_loaded(void **state) {
|
|||||||
assert_int_equal(memoryGetAllocatedCount(), 0);
|
assert_int_equal(memoryGetAllocatedCount(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void test_update_slot_occupied_after_first_update(void **state) {
|
static void test_update_slot_cleared_after_first_update(void **state) {
|
||||||
assetentry_t *entry = assetGetEntry("test.locale", ASSET_LOADER_TYPE_LOCALE, NULL);
|
assetentry_t *entry = assetGetEntry("test.locale", ASSET_LOADER_TYPE_LOCALE, NULL);
|
||||||
|
|
||||||
assetEntryLock(entry);
|
assetEntryLock(entry);
|
||||||
assetUpdate();
|
assetUpdate();
|
||||||
assert_int_equal(entry->state, ASSET_ENTRY_STATE_LOADED);
|
assert_int_equal(entry->state, ASSET_ENTRY_STATE_LOADED);
|
||||||
|
|
||||||
// Slot not cleared until the second update processes the LOADED case.
|
// A synchronous load clears its own loading slot in the same update call
|
||||||
assert_true(loading_slot_has_entry(entry));
|
// that completes it, so no second update is needed to free it.
|
||||||
|
assert_false(loading_slot_has_entry(entry));
|
||||||
assetEntryUnlock(entry);
|
assetEntryUnlock(entry);
|
||||||
|
|
||||||
assert_int_equal(memoryGetAllocatedCount(), 0);
|
assert_int_equal(memoryGetAllocatedCount(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void test_update_slot_cleared_after_second_update(void **state) {
|
static void test_update_slot_stays_cleared_on_further_updates(void **state) {
|
||||||
assetentry_t *entry = assetGetEntry("test.locale", ASSET_LOADER_TYPE_LOCALE, NULL);
|
assetentry_t *entry = assetGetEntry("test.locale", ASSET_LOADER_TYPE_LOCALE, NULL);
|
||||||
|
|
||||||
assetEntryLock(entry);
|
assetEntryLock(entry);
|
||||||
@@ -238,13 +239,17 @@ static void test_update_overflow_queues_entries(void **state) {
|
|||||||
entries[i] = assetGetEntry(name, ASSET_LOADER_TYPE_LOCALE, NULL);
|
entries[i] = assetGetEntry(name, ASSET_LOADER_TYPE_LOCALE, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lock first batch so the reaper won't collect them before we check.
|
// Lock every entry, including the overflow one, so the reaper won't
|
||||||
for(int i = 0; i < ASSET_LOADING_COUNT_MAX; i++) {
|
// collect any of them - a synchronous load can reach LOADED and free its
|
||||||
|
// slot within the same update call the reaper runs in.
|
||||||
|
for(int i = 0; i < TOTAL; i++) {
|
||||||
assetEntryLock(entries[i]);
|
assetEntryLock(entries[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update 1: fills all slots, first ASSET_LOADING_COUNT_MAX entries reach LOADED.
|
// Update 1: fills all slots, first ASSET_LOADING_COUNT_MAX entries reach
|
||||||
// The overflow entry has no slot yet and stays NOT_STARTED.
|
// LOADED and immediately free their slots. The overflow entry has no slot
|
||||||
|
// yet because dispatch ran before this update's turn began, so it stays
|
||||||
|
// NOT_STARTED.
|
||||||
errorret_t ret = assetUpdate();
|
errorret_t ret = assetUpdate();
|
||||||
assert_true(errorIsOk(ret));
|
assert_true(errorIsOk(ret));
|
||||||
for(int i = 0; i < ASSET_LOADING_COUNT_MAX; i++) {
|
for(int i = 0; i < ASSET_LOADING_COUNT_MAX; i++) {
|
||||||
@@ -252,26 +257,15 @@ static void test_update_overflow_queues_entries(void **state) {
|
|||||||
}
|
}
|
||||||
assert_int_equal(entries[ASSET_LOADING_COUNT_MAX]->state, ASSET_ENTRY_STATE_NOT_STARTED);
|
assert_int_equal(entries[ASSET_LOADING_COUNT_MAX]->state, ASSET_ENTRY_STATE_NOT_STARTED);
|
||||||
|
|
||||||
// Unlock first batch - the reaper can collect them in update 2.
|
// Update 2: the slots freed during update 1 are now available, so the
|
||||||
for(int i = 0; i < ASSET_LOADING_COUNT_MAX; i++) {
|
// overflow entry is dispatched and loaded in this pass.
|
||||||
assetEntryUnlock(entries[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update 2: LOADED slots are freed. Overflow entry still NOT_STARTED because
|
|
||||||
// the dispatch phase ran before the slots were cleared this turn.
|
|
||||||
ret = assetUpdate();
|
|
||||||
assert_true(errorIsOk(ret));
|
|
||||||
assert_int_equal(entries[ASSET_LOADING_COUNT_MAX]->state, ASSET_ENTRY_STATE_NOT_STARTED);
|
|
||||||
|
|
||||||
// Lock overflow entry so it stays LOADED after update 3.
|
|
||||||
assetEntryLock(entries[ASSET_LOADING_COUNT_MAX]);
|
|
||||||
|
|
||||||
// Update 3: now a slot is available; the overflow entry is dispatched and loaded.
|
|
||||||
ret = assetUpdate();
|
ret = assetUpdate();
|
||||||
assert_true(errorIsOk(ret));
|
assert_true(errorIsOk(ret));
|
||||||
assert_int_equal(entries[ASSET_LOADING_COUNT_MAX]->state, ASSET_ENTRY_STATE_LOADED);
|
assert_int_equal(entries[ASSET_LOADING_COUNT_MAX]->state, ASSET_ENTRY_STATE_LOADED);
|
||||||
|
|
||||||
assetEntryUnlock(entries[ASSET_LOADING_COUNT_MAX]);
|
for(int i = 0; i < TOTAL; i++) {
|
||||||
|
assetEntryUnlock(entries[i]);
|
||||||
|
}
|
||||||
|
|
||||||
assert_int_equal(memoryGetAllocatedCount(), 0);
|
assert_int_equal(memoryGetAllocatedCount(), 0);
|
||||||
}
|
}
|
||||||
@@ -492,8 +486,8 @@ int main(void) {
|
|||||||
// assetUpdate - state machine
|
// assetUpdate - state machine
|
||||||
cmocka_unit_test_setup_teardown(test_update_noop_on_empty_table, asset_setup, asset_teardown),
|
cmocka_unit_test_setup_teardown(test_update_noop_on_empty_table, asset_setup, asset_teardown),
|
||||||
cmocka_unit_test_setup_teardown(test_update_entry_reaches_loaded, asset_setup, asset_teardown),
|
cmocka_unit_test_setup_teardown(test_update_entry_reaches_loaded, asset_setup, asset_teardown),
|
||||||
cmocka_unit_test_setup_teardown(test_update_slot_occupied_after_first_update, asset_setup, asset_teardown),
|
cmocka_unit_test_setup_teardown(test_update_slot_cleared_after_first_update, asset_setup, asset_teardown),
|
||||||
cmocka_unit_test_setup_teardown(test_update_slot_cleared_after_second_update, asset_setup, asset_teardown),
|
cmocka_unit_test_setup_teardown(test_update_slot_stays_cleared_on_further_updates, asset_setup, asset_teardown),
|
||||||
cmocka_unit_test_setup_teardown(test_update_four_slots_fill_independently, asset_setup, asset_teardown),
|
cmocka_unit_test_setup_teardown(test_update_four_slots_fill_independently, asset_setup, asset_teardown),
|
||||||
cmocka_unit_test_setup_teardown(test_update_loaded_entry_not_redispatched, asset_setup, asset_teardown),
|
cmocka_unit_test_setup_teardown(test_update_loaded_entry_not_redispatched, asset_setup, asset_teardown),
|
||||||
cmocka_unit_test_setup_teardown(test_update_overflow_queues_entries, asset_setup, asset_teardown),
|
cmocka_unit_test_setup_teardown(test_update_overflow_queues_entries, asset_setup, asset_teardown),
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ Usage:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
import math
|
||||||
import os
|
import os
|
||||||
import struct
|
import struct
|
||||||
import sys
|
import sys
|
||||||
@@ -58,6 +59,10 @@ CHUNK_HEIGHT = 16
|
|||||||
CHUNK_DEPTH = 4
|
CHUNK_DEPTH = 4
|
||||||
CHUNK_TILE_COUNT = CHUNK_WIDTH * CHUNK_HEIGHT * CHUNK_DEPTH # 1024
|
CHUNK_TILE_COUNT = CHUNK_WIDTH * CHUNK_HEIGHT * CHUNK_DEPTH # 1024
|
||||||
|
|
||||||
|
# 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.
|
||||||
|
WORLD_LAYER_HEIGHT = 1.0 / math.sqrt(2)
|
||||||
|
|
||||||
CHUNK_MESH_COUNT_MAX = 10
|
CHUNK_MESH_COUNT_MAX = 10
|
||||||
CHUNK_MESH_NAME_MAX = 64
|
CHUNK_MESH_NAME_MAX = 64
|
||||||
|
|
||||||
@@ -231,7 +236,10 @@ def build_terrain_verts(tiles_bytes):
|
|||||||
sw, se, ne, nw = corners
|
sw, se, ne, nw = corners
|
||||||
buf += _tile_quad(
|
buf += _tile_quad(
|
||||||
u0, u1, v0, v1, fx, fy,
|
u0, u1, v0, v1, fx, fy,
|
||||||
fz + sw, fz + se, fz + ne, fz + nw
|
(fz + sw) * WORLD_LAYER_HEIGHT,
|
||||||
|
(fz + se) * WORLD_LAYER_HEIGHT,
|
||||||
|
(fz + ne) * WORLD_LAYER_HEIGHT,
|
||||||
|
(fz + nw) * WORLD_LAYER_HEIGHT
|
||||||
)
|
)
|
||||||
return bytes(buf)
|
return bytes(buf)
|
||||||
|
|
||||||
|
|||||||
+9
-14
@@ -30,23 +30,18 @@ with open(args.csv, newline="", encoding="utf-8") as f:
|
|||||||
item_types.append(item_type)
|
item_types.append(item_type)
|
||||||
rows[item_id] = row
|
rows[item_id] = row
|
||||||
|
|
||||||
# Assign enum values: types and IDs share a single counter so values never collide
|
# Assign enum values: types and IDs each start from 1 with NULL = 0.
|
||||||
count = 0
|
|
||||||
type_values = {}
|
type_values = {}
|
||||||
id_values = {}
|
type_count = 1
|
||||||
|
|
||||||
count += 1 # 0 = NULL
|
|
||||||
for t in item_types:
|
for t in item_types:
|
||||||
type_values[t] = count
|
type_values[t] = type_count
|
||||||
count += 1
|
type_count += 1
|
||||||
# ITEM_TYPE_COUNT = count; item IDs continue from here
|
|
||||||
type_count = count
|
|
||||||
|
|
||||||
count += 1 # type_count = ITEM_ID_NULL
|
id_values = {}
|
||||||
|
id_count = 1
|
||||||
for i in item_ids:
|
for i in item_ids:
|
||||||
id_values[i] = count
|
id_values[i] = id_count
|
||||||
count += 1
|
id_count += 1
|
||||||
id_count = count
|
|
||||||
|
|
||||||
# Count items per type
|
# Count items per type
|
||||||
type_item_counts = { t: 0 for t in item_types }
|
type_item_counts = { t: 0 for t in item_types }
|
||||||
@@ -68,7 +63,7 @@ out += [
|
|||||||
"} itemtype_t;",
|
"} itemtype_t;",
|
||||||
"",
|
"",
|
||||||
"typedef enum {",
|
"typedef enum {",
|
||||||
f" ITEM_ID_NULL = {type_count},",
|
" ITEM_ID_NULL = 0,",
|
||||||
]
|
]
|
||||||
for i in item_ids:
|
for i in item_ids:
|
||||||
out.append(f" {id_enum(i)} = {id_values[i]},")
|
out.append(f" {id_enum(i)} = {id_values[i]},")
|
||||||
|
|||||||
Reference in New Issue
Block a user