Compare commits
64 Commits
alpha-0.0.6
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| e3f10e0926 | |||
| d7223d7387 | |||
| 45331c2a60 | |||
| 33f50a2c69 | |||
| 6c8e4d5cbd | |||
| 3b7215876a | |||
| c74f5890bd | |||
| fbd3c71ba7 | |||
| 128f9ab9d4 | |||
| 36fb359aa2 | |||
| 1bd73d69fe | |||
| fb48285143 | |||
| 3de50b8370 | |||
| e61cbe25b7 | |||
| 8395830be6 | |||
| 6e4ec2b9d8 | |||
| f501bb8e28 | |||
| d07cd3397d | |||
| e008fb108a | |||
| 9aaffff7a8 | |||
| 7357b4a5df | |||
| 1ddc298a74 | |||
| e2a9442aa6 | |||
| aa0180571e | |||
| 7f7be39230 | |||
| 4d95415232 | |||
| 2cbd80a004 | |||
| 9abf8101da | |||
| 24badd06a5 | |||
| 7a03ef8eaf | |||
| f3ea507313 | |||
| 4b0388a0e1 | |||
| a84137b5ff | |||
| ca02ee0352 | |||
| fbaa54145e | |||
| 28754ffbf2 | |||
| 470c0eba7a | |||
| 7098dcec43 | |||
| 07137f57af | |||
| 8b7491a3d3 | |||
| 8cfa8ddfeb | |||
| ef284a15a1 | |||
| 3723921573 | |||
| 195399635e | |||
| 46e2a924d3 | |||
| b693ea4102 | |||
| a73f55beb0 | |||
| 0bd2491ab7 | |||
| 860c797c9c | |||
| 38c5080f9f | |||
| fae191d8fe | |||
| d83a953e2d | |||
| 2dcf0d0f0d | |||
| 6dee37d8c1 | |||
| 3bad03afb3 | |||
| 6a6d8448f7 | |||
| 189babd2cf | |||
| 988a0f2294 | |||
| 85bf455731 | |||
| 589e4224f3 | |||
| bd7fa154b4 | |||
| a292f1992b | |||
| 88ddf429b7 | |||
| 7a1f6662df |
@@ -1,455 +0,0 @@
|
|||||||
# Dusk — Claude Code rules
|
|
||||||
|
|
||||||
## File headers
|
|
||||||
Every C, H, and JS file starts with:
|
|
||||||
|
|
||||||
```c
|
|
||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
```
|
|
||||||
|
|
||||||
JS files use `//` comment style instead.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## C conventions
|
|
||||||
|
|
||||||
### Types
|
|
||||||
Always use the project-defined aliases instead of bare C primitives:
|
|
||||||
|
|
||||||
| Use | Not |
|
|
||||||
|-----------|--------------|
|
|
||||||
| `bool_t` | `bool` |
|
|
||||||
| `int_t` | `int` |
|
|
||||||
| `float_t` | `float` |
|
|
||||||
| `char_t` | `char` |
|
|
||||||
|
|
||||||
Use `uint8_t`, `uint16_t`, `int32_t`, etc. for fixed-width integers.
|
|
||||||
All struct and enum types end in `_t` (`animation_t`, `errorret_t`, …).
|
|
||||||
|
|
||||||
### Naming
|
|
||||||
- **Functions** — snake_case, prefixed with their module:
|
|
||||||
`assetLock()`, `entityPositionInit()`, `moduleAssetBatchCtor()`
|
|
||||||
- **Struct fields** — camelCase: `keyframeCount`, `localPosition`
|
|
||||||
- **Macros / constants** — UPPER_SNAKE_CASE:
|
|
||||||
`ENTITY_ID_INVALID`, `ERROR_OK`, `COMPONENT_TYPE_COUNT`
|
|
||||||
- **Files** — snake_case matching the primary type: `entityposition.c`,
|
|
||||||
`moduleassetbatch.c`
|
|
||||||
|
|
||||||
### Header files (`.h`)
|
|
||||||
- Use `#pragma once` — no include guards.
|
|
||||||
- Declare every public function, `#define`, and `extern` global.
|
|
||||||
- Write a JSDoc block (`/** … */`) above every declaration explaining
|
|
||||||
purpose, `@param`s, and `@returns`.
|
|
||||||
- Only include headers that the `.h` file itself strictly requires for
|
|
||||||
the types it exposes. Move everything else to the `.c` file.
|
|
||||||
Do not use forward declarations as a workaround — use the real
|
|
||||||
include in the `.c` file instead.
|
|
||||||
|
|
||||||
### Implementation files (`.c`)
|
|
||||||
- Contain function bodies only; no declarations.
|
|
||||||
- Pull in whatever additional includes the implementation needs.
|
|
||||||
- Do not use `static` or `inline` on **functions**. Every function,
|
|
||||||
including internal helpers, must be declared in the matching `.h` and
|
|
||||||
defined in the `.c` file. Internal helpers belong near the bottom of
|
|
||||||
the `.c` file, not at the top with a `static` qualifier.
|
|
||||||
`static` and `inline` on functions are only appropriate when the
|
|
||||||
function body is written directly inside a `.h` file.
|
|
||||||
`static` on **variables** (file-scope state) is fine and expected.
|
|
||||||
|
|
||||||
### Formatting
|
|
||||||
- Hard-wrap all lines at **80 characters**.
|
|
||||||
|
|
||||||
### Error handling
|
|
||||||
Return `errorret_t` from fallible functions. Use these macros:
|
|
||||||
|
|
||||||
```c
|
|
||||||
errorOk(); // return success
|
|
||||||
errorThrow("msg %d", val); // return failure with message
|
|
||||||
errorChain(someCall()); // propagate failure, continue on success
|
|
||||||
errorIsOk(ret) / errorIsNotOk(ret) // test a result
|
|
||||||
errorCatch(ret); // handle + free an error
|
|
||||||
```
|
|
||||||
|
|
||||||
Never return raw error codes or use `errno` for in-engine errors.
|
|
||||||
|
|
||||||
### Memory
|
|
||||||
Use the project allocator — never raw `malloc`/`free`:
|
|
||||||
|
|
||||||
```c
|
|
||||||
memoryAllocate(size) // allocate
|
|
||||||
memoryFree(ptr) // free
|
|
||||||
memoryZero(dest, size) // zero a block
|
|
||||||
memoryCopy(dest, src, size) // copy
|
|
||||||
```
|
|
||||||
|
|
||||||
### Asserts
|
|
||||||
Prefer specific assert macros over bare `assert()`:
|
|
||||||
|
|
||||||
```c
|
|
||||||
assertNotNull(ptr, "msg");
|
|
||||||
assertTrue(cond, "msg");
|
|
||||||
assertFalse(cond, "msg");
|
|
||||||
assertUnreachable("msg");
|
|
||||||
assertIsMainThread("msg");
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Build system
|
|
||||||
Each subdirectory has its own `CMakeLists.txt` that adds sources with:
|
|
||||||
|
|
||||||
```cmake
|
|
||||||
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
|
||||||
PUBLIC
|
|
||||||
myfile.c
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
Never add source files to the root `CMakeLists.txt` directly.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Platform support
|
|
||||||
|
|
||||||
### Targets
|
|
||||||
Set `DUSK_TARGET_SYSTEM` at CMake configure time to select a platform:
|
|
||||||
|
|
||||||
| `DUSK_TARGET_SYSTEM` | Macro defined | Platform |
|
|
||||||
|----------------------|-------------------|------------------|
|
|
||||||
| `linux` | `DUSK_LINUX` | Linux desktop |
|
|
||||||
| `knulli` | `DUSK_KNULLI` | Knulli (handheld)|
|
|
||||||
| `psp` | `DUSK_PSP` | Sony PSP |
|
|
||||||
| `vita` | `DUSK_VITA` | PlayStation Vita |
|
|
||||||
| `gamecube` | `DUSK_GAMECUBE` | Nintendo GameCube|
|
|
||||||
| `wii` | `DUSK_WII` | Nintendo Wii |
|
|
||||||
|
|
||||||
### Layer structure
|
|
||||||
```
|
|
||||||
src/dusk/ core, platform-agnostic game logic
|
|
||||||
src/duskgl/ OpenGL abstraction (Linux, Knulli, PSP, Vita)
|
|
||||||
src/dusksdl2/ SDL2 window + input (Linux, Knulli, PSP, Vita)
|
|
||||||
src/dusklinux/ Linux + Knulli platform impl
|
|
||||||
src/duskpsp/ PSP platform impl
|
|
||||||
src/duskvita/ Vita platform impl
|
|
||||||
src/duskdolphin/ GameCube / Wii platform impl (no SDL2/OpenGL)
|
|
||||||
```
|
|
||||||
|
|
||||||
Dolphin is the only target that bypasses SDL2 and OpenGL entirely —
|
|
||||||
it uses native GameCube/Wii rendering and input APIs.
|
|
||||||
|
|
||||||
### Platform guards
|
|
||||||
Use the compile-time macros for platform-specific code:
|
|
||||||
|
|
||||||
```c
|
|
||||||
#ifdef DUSK_PSP
|
|
||||||
// PSP-only path
|
|
||||||
#elif defined(DUSK_GAMECUBE) || defined(DUSK_WII)
|
|
||||||
// GameCube / Wii path
|
|
||||||
#else
|
|
||||||
// Generic / Linux fallback
|
|
||||||
#endif
|
|
||||||
```
|
|
||||||
|
|
||||||
Additional capability macros set per-target:
|
|
||||||
`DUSK_SDL2`, `DUSK_OPENGL`, `DUSK_OPENGL_ES`, `DUSK_OPENGL_LEGACY`,
|
|
||||||
`DUSK_INPUT_GAMEPAD`, `DUSK_INPUT_KEYBOARD`, `DUSK_INPUT_POINTER`,
|
|
||||||
`DUSK_PLATFORM_ENDIAN_BIG` / `DUSK_PLATFORM_ENDIAN_LITTLE`.
|
|
||||||
|
|
||||||
### Abstraction pattern
|
|
||||||
Platform-specific implementations are wired in via `#define` macros in
|
|
||||||
each platform's `displayplatform.h` / `inputplatform.h` etc., which
|
|
||||||
the core calls through. Functions that a platform does not support are
|
|
||||||
simply left undefined — the core guards calls with `#ifdef`.
|
|
||||||
|
|
||||||
### Adding platform-specific code
|
|
||||||
- Put it under `src/dusk<platform>/` in the matching subsystem folder.
|
|
||||||
- Gate any core call-site with the appropriate `#ifdef DUSK_<PLATFORM>`
|
|
||||||
or capability macro.
|
|
||||||
- Keep the `src/dusk/` core free of platform ifdefs — delegate through
|
|
||||||
the platform header macros instead.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Adding a new asset loader type
|
|
||||||
1. Add an enum value to `assetloadertype_t` (before `_COUNT`) in
|
|
||||||
`src/dusk/asset/loader/assetloader.h`.
|
|
||||||
2. Add fields to the input/loading/output unions in `assetloader.h`.
|
|
||||||
3. Implement `assetXxxLoaderSync`, `assetXxxLoaderAsync`, and
|
|
||||||
`assetXxxDispose` in a new `src/dusk/asset/loader/xxx/` directory.
|
|
||||||
4. Register the three callbacks in `ASSET_LOADER_CALLBACKS[]` in
|
|
||||||
`src/dusk/asset/loader/assetloader.c`.
|
|
||||||
5. If user-facing, create a JS module (see below) and a `.d.ts` file.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Adding a new entity component
|
|
||||||
1. Create `src/dusk/entity/component/<category>/entityMyComp.h/.c` with
|
|
||||||
struct `entityMyComp_t`, `entityMyCompInit()`, and optionally
|
|
||||||
`entityMyCompDispose()`.
|
|
||||||
2. Add the include to `src/dusk/entity/componentlist.h` header block.
|
|
||||||
3. Add a row to `src/dusk/entity/componentlist.h`:
|
|
||||||
```c
|
|
||||||
X(MYCOMP, entityMyComp_t, myComp, entityMyCompInit, NULL, NULL)
|
|
||||||
```
|
|
||||||
This auto-generates the enum, union field, and definition entry.
|
|
||||||
4. If JS-facing, create the script module and `.d.ts` (see below).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Adding a new script (JS) module
|
|
||||||
1. Create `src/dusk/script/module/<category>/moduleMyMod.h/.c`.
|
|
||||||
- Declare `extern scriptproto_t MODULE_MYMOD_PROTO;` in the header.
|
|
||||||
- Use `moduleBaseFunction(name)` to define JS-callable functions.
|
|
||||||
- Register props/funcs in `moduleMyModInit()` with
|
|
||||||
`scriptProtoDefineProp` / `scriptProtoDefineFunc` /
|
|
||||||
`scriptProtoDefineStaticFunc`.
|
|
||||||
2. `#include` the header in
|
|
||||||
`src/dusk/script/module/modulelist.c` and call
|
|
||||||
`moduleMyModInit()` in `moduleListInit()` (and `Dispose` in
|
|
||||||
`moduleListDispose()`).
|
|
||||||
3. For component modules also register in
|
|
||||||
`src/dusk/script/module/entity/component/modulecomponentlist.c`
|
|
||||||
so `entity.add()` returns the typed wrapper.
|
|
||||||
4. Create `types/<category>/mymod.d.ts` and add a
|
|
||||||
`/// <reference path="..." />` line to `types/index.d.ts`.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Script module type declarations
|
|
||||||
Whenever a `src/dusk/script/module/**/*.c` file is created or modified,
|
|
||||||
check whether the corresponding `types/**/*.d.ts` needs updating and
|
|
||||||
apply any changes before finishing the task.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## JavaScript (asset scripts)
|
|
||||||
- Use `var` for module-level state; `const` for values that never
|
|
||||||
change.
|
|
||||||
- Always use semicolons.
|
|
||||||
- Scene objects are plain objects (`var scene = {}`) with assigned
|
|
||||||
methods.
|
|
||||||
- Export via `module.exports = scene`.
|
|
||||||
- Async scene init should use `async function` and `await`.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Coding style
|
|
||||||
|
|
||||||
### ASCII only
|
|
||||||
Source files (`.c`, `.h`, `.js`) must contain only ASCII characters (U+0000–U+007F).
|
|
||||||
Non-ASCII characters are banned even in comments and string literals.
|
|
||||||
Use ASCII-only substitutes instead:
|
|
||||||
- `--` or `-` instead of `—` (em dash)
|
|
||||||
- `->` instead of `→` (arrow)
|
|
||||||
- `x` or `*` instead of `×` (multiplication)
|
|
||||||
|
|
||||||
Only non-script asset files (e.g. `.po` locale files) may contain non-ASCII text.
|
|
||||||
|
|
||||||
### Indentation
|
|
||||||
2 spaces. No tabs.
|
|
||||||
|
|
||||||
### Keyword and operator spacing
|
|
||||||
No space between a keyword or function name and its opening parenthesis:
|
|
||||||
|
|
||||||
```c
|
|
||||||
if(!ptr) return;
|
|
||||||
for(uint8_t i = 0; i < count; i++) {
|
|
||||||
while(entry->state != DONE) {
|
|
||||||
switch(type) {
|
|
||||||
sizeof(assetbatch_t)
|
|
||||||
memoryZero(ptr, size)
|
|
||||||
```
|
|
||||||
|
|
||||||
Spaces around all binary operators and after every comma:
|
|
||||||
|
|
||||||
```c
|
|
||||||
pos->flags |= ENTITY_POSITION_FLAG_WORLD_DIRTY;
|
|
||||||
(size_t)end - (size_t)start
|
|
||||||
foo(a, b, c)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Braces
|
|
||||||
Opening brace on the **same line** as the statement (K&R style) for all
|
|
||||||
constructs — functions, `if`, `else`, `for`, `while`, `switch`:
|
|
||||||
|
|
||||||
```c
|
|
||||||
void assetEntryLock(assetentry_t *entry) {
|
|
||||||
...
|
|
||||||
}
|
|
||||||
|
|
||||||
if(dirty) {
|
|
||||||
...
|
|
||||||
} else {
|
|
||||||
...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Guard returns
|
|
||||||
Short guards go on one line with no braces:
|
|
||||||
|
|
||||||
```c
|
|
||||||
if(!ptr) return;
|
|
||||||
if(!b || !b->batch) return jerry_undefined();
|
|
||||||
if(!(flags & DIRTY)) return;
|
|
||||||
```
|
|
||||||
|
|
||||||
### Blank lines
|
|
||||||
- One blank line between functions; no blank line at the start or end of
|
|
||||||
a function body.
|
|
||||||
- One blank line between logical blocks inside a function body.
|
|
||||||
- No trailing blank lines at the end of a file.
|
|
||||||
|
|
||||||
### Pointer placement
|
|
||||||
`*` is attached to the variable name, not the type:
|
|
||||||
|
|
||||||
```c
|
|
||||||
assetentry_t *entry
|
|
||||||
const char_t *name
|
|
||||||
void *ptr
|
|
||||||
uint8_t *d = (uint8_t *)dest;
|
|
||||||
```
|
|
||||||
|
|
||||||
### Casts
|
|
||||||
Space between cast and operand:
|
|
||||||
|
|
||||||
```c
|
|
||||||
(assetbatch_t *)user
|
|
||||||
(uint8_t *)dest
|
|
||||||
(textureformat_t)v
|
|
||||||
```
|
|
||||||
|
|
||||||
### Return
|
|
||||||
No parentheses around the return value:
|
|
||||||
|
|
||||||
```c
|
|
||||||
return ptr;
|
|
||||||
return MEMORY_POINTERS_IN_USE;
|
|
||||||
```
|
|
||||||
|
|
||||||
### switch / case
|
|
||||||
`case` indented 2 spaces from `switch`; body indented 2 more from `case`:
|
|
||||||
|
|
||||||
```c
|
|
||||||
switch(type) {
|
|
||||||
case ASSET_LOADER_TYPE_TEXTURE:
|
|
||||||
descs[i].input.texture = (textureformat_t)v;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Multi-line function signatures
|
|
||||||
When parameters don't fit on one line, put each on its own line indented
|
|
||||||
2 spaces; the closing `) {` (definition) or `);` (declaration) goes on
|
|
||||||
its own line at column 0:
|
|
||||||
|
|
||||||
```c
|
|
||||||
void assetEntryInit(
|
|
||||||
assetentry_t *entry,
|
|
||||||
const char_t *name,
|
|
||||||
const assetloadertype_t type,
|
|
||||||
assetloaderinput_t *input
|
|
||||||
) {
|
|
||||||
|
|
||||||
errorret_t memoryCompare(
|
|
||||||
const void *a,
|
|
||||||
const void *b,
|
|
||||||
const size_t size
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
### Structs and enums
|
|
||||||
Anonymous inner struct or enum with a `typedef`, `_t` suffix, closing
|
|
||||||
brace and name on the same line:
|
|
||||||
|
|
||||||
```c
|
|
||||||
typedef struct {
|
|
||||||
errorcode_t code;
|
|
||||||
char_t *message;
|
|
||||||
} errorstate_t;
|
|
||||||
|
|
||||||
typedef enum {
|
|
||||||
ASSET_LOADER_TYPE_NULL,
|
|
||||||
ASSET_LOADER_TYPE_COUNT
|
|
||||||
} assetloadertype_t;
|
|
||||||
```
|
|
||||||
|
|
||||||
### Designated initialisers
|
|
||||||
Spaces inside braces; `.field = value`:
|
|
||||||
|
|
||||||
```c
|
|
||||||
jsassetentry_t e = { .entry = entry };
|
|
||||||
assetbatchloadedpend_t init = { .batch = batch };
|
|
||||||
```
|
|
||||||
|
|
||||||
### Ternary operator
|
|
||||||
Spaces around `?` and `:`:
|
|
||||||
|
|
||||||
```c
|
|
||||||
const float val = psx > 0.0f ? pt[0][0] / psx : 0.0f;
|
|
||||||
```
|
|
||||||
|
|
||||||
### const placement
|
|
||||||
`const` before the type, `*` attached to the variable:
|
|
||||||
|
|
||||||
```c
|
|
||||||
const char_t *name
|
|
||||||
const void *src
|
|
||||||
const size_t size
|
|
||||||
```
|
|
||||||
|
|
||||||
### Comments in `.c` files
|
|
||||||
- Do not use section dividers (`/* ---- ... ---- */`). Just let the
|
|
||||||
functions follow one another with a single blank line between them.
|
|
||||||
- Multi-line explanatory comments inside function bodies use `//` lines:
|
|
||||||
```c
|
|
||||||
// Script modules are freed; orphaned JS wrapper objects now get GC'd
|
|
||||||
// so their finalizers fire before assetDispose() checks ref counts.
|
|
||||||
jerry_heap_gc(JERRY_GC_PRESSURE_HIGH);
|
|
||||||
```
|
|
||||||
- Do not use `/* */` for inline or inline-block comments inside `.c`
|
|
||||||
function bodies.
|
|
||||||
|
|
||||||
### Comments in `.h` files
|
|
||||||
Every public declaration gets a Javadoc block (`/** … */`) with
|
|
||||||
`@param` and `@returns` where relevant. Keep it on the lines immediately
|
|
||||||
above the declaration with no blank line in between.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Color system
|
|
||||||
|
|
||||||
Colors are defined in `src/dusk/display/color.csv` and code-generated
|
|
||||||
into a `color.h` header by `tools/color/csv/__main__.py`.
|
|
||||||
|
|
||||||
Each row in the CSV has `name,r,g,b,a` with channel values in `[0.0, 1.0]`.
|
|
||||||
The script emits four `#define` variants per color plus a bare alias:
|
|
||||||
|
|
||||||
```
|
|
||||||
COLOR_<NAME>_4B color4b(r8, g8, b8, a8) // default alias target
|
|
||||||
COLOR_<NAME>_3B color3b(r8, g8, b8)
|
|
||||||
COLOR_<NAME>_3F color3f(rf, gf, bf)
|
|
||||||
COLOR_<NAME>_4F color4f(rf, gf, bf, af)
|
|
||||||
COLOR_<NAME> COLOR_<NAME>_4B
|
|
||||||
```
|
|
||||||
|
|
||||||
`color_t` is `color4b_t` (four `uint8_t` channels).
|
|
||||||
|
|
||||||
To add a new color, append a row to `color.csv` and rebuild — do not
|
|
||||||
hand-edit the generated header.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Tests
|
|
||||||
- Tests live in `test/` mirroring `src/dusk/` structure.
|
|
||||||
- Use cmocka; include `dusktest.h`.
|
|
||||||
- Test functions: `static void test_something(void **state)`.
|
|
||||||
- After each test, assert `memoryGetAllocatedCount() == 0` to catch
|
|
||||||
leaks.
|
|
||||||
- Build with `-DDUSK_BUILD_TESTS=ON`.
|
|
||||||
@@ -13,6 +13,7 @@ cmake_policy(SET CMP0079 NEW)
|
|||||||
# set(FETCHCONTENT_UPDATES_DISCONNECTED ON)
|
# set(FETCHCONTENT_UPDATES_DISCONNECTED ON)
|
||||||
|
|
||||||
option(DUSK_BUILD_TESTS "Enable tests" OFF)
|
option(DUSK_BUILD_TESTS "Enable tests" OFF)
|
||||||
|
option(DUSK_NETWORK "Enable network support" ON)
|
||||||
|
|
||||||
set(DUSK_GAME_NAME "Dusk" CACHE STRING "Game display name")
|
set(DUSK_GAME_NAME "Dusk" CACHE STRING "Game display name")
|
||||||
set(DUSK_GAME_AUTHOR "YourWishes" CACHE STRING "Game author / coder")
|
set(DUSK_GAME_AUTHOR "YourWishes" CACHE STRING "Game author / coder")
|
||||||
@@ -90,6 +91,12 @@ target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME}
|
|||||||
DUSK_VERSION="${DUSK_VERSION}"
|
DUSK_VERSION="${DUSK_VERSION}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if(DUSK_NETWORK)
|
||||||
|
target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
|
||||||
|
DUSK_NETWORK
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
# Toolchains
|
# Toolchains
|
||||||
include(cmake/targets/${DUSK_TARGET_SYSTEM}.cmake)
|
include(cmake/targets/${DUSK_TARGET_SYSTEM}.cmake)
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+176
-43
@@ -10,51 +10,184 @@ msgid "ui.title"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Welcome"
|
"Welcome"
|
||||||
|
|
||||||
#: ui/user.c:22
|
msgid "save.linux.mkdirp_failed"
|
||||||
msgid "ui.greeting"
|
msgstr "Failed to create save directory, check the disk is not full or write-protected."
|
||||||
msgstr "Hello, %s!"
|
|
||||||
|
|
||||||
#: ui/files.c:40
|
#: src/dusk/ui/frame/settings/uisettings.c
|
||||||
msgid "ui.file_status"
|
msgid "ui.settings.tabs.general"
|
||||||
msgstr "%s has %d files."
|
msgstr "General"
|
||||||
|
|
||||||
#: ui/cart.c:55
|
#: src/dusk/ui/frame/settings/uisettings.c
|
||||||
msgid "cart.item_count"
|
msgid "ui.settings.tabs.input"
|
||||||
msgid_plural "cart.item_count"
|
msgstr "Input"
|
||||||
msgstr[0] "%d item"
|
|
||||||
msgstr[1] "%d items (dual)"
|
|
||||||
msgstr[2] "%d items (few)"
|
|
||||||
msgstr[3] "%d items (many)"
|
|
||||||
|
|
||||||
#: ui/notifications.c:71
|
#: src/dusk/ui/frame/settings/uisettings.c
|
||||||
msgid ""
|
msgid "ui.settings.tabs.display"
|
||||||
"ui.multiline_help"
|
msgstr "Display"
|
||||||
msgstr ""
|
|
||||||
"Line one of the help text.\n"
|
|
||||||
"Line two continues here.\n"
|
|
||||||
"Line three ends here."
|
|
||||||
|
|
||||||
#: ui/errors.c:90
|
#: src/dusk/ui/frame/settings/uisettings.c
|
||||||
msgid ""
|
msgid "ui.settings.tabs.audio"
|
||||||
"error.upload_failed.long"
|
msgstr "Audio"
|
||||||
msgstr ""
|
|
||||||
"Upload failed for file \"%s\".\n"
|
|
||||||
"Please try again later or contact support."
|
|
||||||
|
|
||||||
#: ui/messages.c:110
|
msgid "ui.settings.input.deadzone"
|
||||||
msgid ""
|
msgstr "Deadzone"
|
||||||
"user.invite_status"
|
|
||||||
msgid_plural ""
|
#: src/dusk/ui/frame/settings/uisettingsgeneral.c
|
||||||
"user.invite_status"
|
msgid "ui.settings.general.language"
|
||||||
msgstr[0] ""
|
msgstr "Language"
|
||||||
"%s invited %d user.\n"
|
|
||||||
"Please review the request."
|
msgid "ui.settings.general.language_detail"
|
||||||
msgstr[1] ""
|
msgstr "Takes effect after restarting the application."
|
||||||
"%s invited %d users (dual).\n"
|
|
||||||
"Please review the requests."
|
#: src/dusk/ui/frame/settings/uisettings.c
|
||||||
msgstr[2] ""
|
msgid "ui.settings.apply"
|
||||||
"%s invited %d users (few).\n"
|
msgstr "Apply"
|
||||||
"Please review the requests."
|
|
||||||
msgstr[3] ""
|
#: src/dusk/ui/frame/uiconfirm.c
|
||||||
"%s invited %d users (many).\n"
|
msgid "ui.confirm.discard_changes"
|
||||||
"Please review the requests."
|
msgstr "Discard unsaved changes?"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/game/uigamemenu.c
|
||||||
|
msgid "ui.game_menu.characters"
|
||||||
|
msgstr "Characters"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/game/uigamemenu.c
|
||||||
|
msgid "ui.game_menu.items"
|
||||||
|
msgstr "Items"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/game/uigamemenu.c
|
||||||
|
msgid "ui.game_menu.settings"
|
||||||
|
msgstr "Settings"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/game/uigamemenu.c
|
||||||
|
msgid "ui.game_menu.save"
|
||||||
|
msgstr "Save"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/game/uigamemenu.c
|
||||||
|
msgid "ui.game_menu.save_success"
|
||||||
|
msgstr "Game saved."
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/game/uigamemenu.c
|
||||||
|
msgid "ui.game_menu.save_cancelled"
|
||||||
|
msgstr "Save cancelled."
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/game/uigamemenu.c
|
||||||
|
msgid "ui.game_menu.save_unavailable"
|
||||||
|
msgstr "Can't save - no save device found."
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/game/uigamemenu.c
|
||||||
|
msgid "ui.game_menu.save_temporary"
|
||||||
|
msgstr "This session is temporary - no save device was found, so saving is disabled."
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/game/uigamemenu.c
|
||||||
|
msgid "ui.game_menu.save_create_confirm"
|
||||||
|
msgstr "No save data found. Create a new save?"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/game/uigamemenu.c
|
||||||
|
msgid "ui.game_menu.save_failed_format"
|
||||||
|
msgstr "Save failed: %s"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/game/uigamemenu.c
|
||||||
|
msgid "ui.game_menu.save_check_failed_format"
|
||||||
|
msgstr "Can't save: %s"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/initial/uiinitialnocard.c
|
||||||
|
msgid "ui.initial.no_card.message"
|
||||||
|
msgstr "No save device found. You can continue, but\nprogress will not be saved."
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/initial/uiinitialnocard.c
|
||||||
|
msgid "ui.initial.no_card.retry"
|
||||||
|
msgstr "Retry"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/initial/uiinitialnocard.c
|
||||||
|
msgid "ui.initial.no_card.continue"
|
||||||
|
msgstr "Continue Anyway"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/initial/uiinitialcreatesave.c
|
||||||
|
msgid "ui.initial.create_save.message"
|
||||||
|
msgstr "No save data found. Create a new save?"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/initial/uiinitialcreatesave.c
|
||||||
|
msgid "ui.initial.create_save.yes"
|
||||||
|
msgstr "Yes"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/initial/uiinitialcreatesave.c
|
||||||
|
msgid "ui.initial.create_save.no"
|
||||||
|
msgstr "No"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/mainmenu/uimainmenu.c
|
||||||
|
msgid "ui.main_menu.new_game"
|
||||||
|
msgstr "New Game"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/mainmenu/uimainmenu.c
|
||||||
|
msgid "ui.main_menu.load_game"
|
||||||
|
msgstr "Load Game"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/mainmenu/uimainmenu.c
|
||||||
|
msgid "ui.main_menu.options"
|
||||||
|
msgstr "Options"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/mainmenu/uimainmenu.c
|
||||||
|
msgid "ui.main_menu.quit"
|
||||||
|
msgstr "Quit Game"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/uiconfirm.c
|
||||||
|
msgid "ui.confirm.confirm"
|
||||||
|
msgstr "Confirm"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/uiconfirm.c
|
||||||
|
msgid "ui.confirm.cancel"
|
||||||
|
msgstr "Cancel"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/battle/uibattlemenu.c
|
||||||
|
msgid "ui.battle.menu.attack"
|
||||||
|
msgstr "Attack"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/battle/uibattlemenu.c
|
||||||
|
msgid "ui.battle.menu.flee"
|
||||||
|
msgstr "Flee"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/battle/uibattlemenu.c
|
||||||
|
msgid "ui.battle.menu.target_format"
|
||||||
|
msgstr "Enemy %u (%u/%u HP)"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/battle/uibattlehud.c
|
||||||
|
msgid "ui.battle.hud.hp_format"
|
||||||
|
msgstr "HP %u/%u"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/battle/uibattlehud.c
|
||||||
|
msgid "ui.battle.hud.mp_format"
|
||||||
|
msgstr "MP %u/%u"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/settings/uisettingsaudio.c
|
||||||
|
msgid "ui.settings.audio.placeholder"
|
||||||
|
msgstr "No audio settings yet"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/settings/uisettingsdisplay.c
|
||||||
|
msgid "ui.settings.display.placeholder"
|
||||||
|
msgstr "No display settings yet"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/settings/uisettingsinput.c
|
||||||
|
msgid "ui.settings.input.placeholder"
|
||||||
|
msgstr "No input settings yet"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/backpack/uibackpack.c
|
||||||
|
msgid "ui.backpack.category_format"
|
||||||
|
msgstr "Category %u"
|
||||||
|
|
||||||
|
#: src/dusk/ui/overlay/uiloading.c
|
||||||
|
msgid "ui.loading.text"
|
||||||
|
msgstr "loading"
|
||||||
|
|
||||||
|
#: src/dusk/ui/overlay/uiautosave.c
|
||||||
|
msgid "ui.autosave.saving"
|
||||||
|
msgstr "SAVING"
|
||||||
|
|
||||||
|
msgid "item.potion.name"
|
||||||
|
msgstr "Potion"
|
||||||
|
|
||||||
|
msgid "item.potato.name"
|
||||||
|
msgstr "Potato"
|
||||||
|
|
||||||
|
msgid "item.apple.name"
|
||||||
|
msgstr "Apple"
|
||||||
@@ -0,0 +1,194 @@
|
|||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: ExampleApp 1.0\n"
|
||||||
|
"Language: es\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Plural-Forms: nplurals=2; plural=(n==1 ? 0 : 1);\n"
|
||||||
|
|
||||||
|
#: ui/menu.c:10
|
||||||
|
msgid "ui.title"
|
||||||
|
msgstr ""
|
||||||
|
"Bienvenido"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/settings/uisettings.c
|
||||||
|
msgid "ui.settings.tabs.general"
|
||||||
|
msgstr "General"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/settings/uisettings.c
|
||||||
|
msgid "ui.settings.tabs.input"
|
||||||
|
msgstr "Entrada"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/settings/uisettings.c
|
||||||
|
msgid "ui.settings.tabs.display"
|
||||||
|
msgstr "Pantalla"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/settings/uisettings.c
|
||||||
|
msgid "ui.settings.tabs.audio"
|
||||||
|
msgstr "Audio"
|
||||||
|
|
||||||
|
msgid "ui.settings.input.deadzone"
|
||||||
|
msgstr "Deadzone"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/settings/uisettingsgeneral.c
|
||||||
|
msgid "ui.settings.general.language"
|
||||||
|
msgstr "Idioma"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/settings/uisettingsgeneral.c
|
||||||
|
msgid "ui.settings.general.language_detail"
|
||||||
|
msgstr "Se aplica después de reiniciar la aplicación."
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/settings/uisettings.c
|
||||||
|
msgid "ui.settings.apply"
|
||||||
|
msgstr "Aplicar"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/uiconfirm.c
|
||||||
|
msgid "ui.confirm.discard_changes"
|
||||||
|
msgstr "¿Descartar los cambios no guardados?"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/game/uigamemenu.c
|
||||||
|
msgid "ui.game_menu.characters"
|
||||||
|
msgstr "Personajes"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/game/uigamemenu.c
|
||||||
|
msgid "ui.game_menu.items"
|
||||||
|
msgstr "Objetos"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/game/uigamemenu.c
|
||||||
|
msgid "ui.game_menu.settings"
|
||||||
|
msgstr "Configuración"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/game/uigamemenu.c
|
||||||
|
msgid "ui.game_menu.save"
|
||||||
|
msgstr "Guardar"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/game/uigamemenu.c
|
||||||
|
msgid "ui.game_menu.save_success"
|
||||||
|
msgstr "Partida guardada."
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/game/uigamemenu.c
|
||||||
|
msgid "ui.game_menu.save_cancelled"
|
||||||
|
msgstr "Guardado cancelado."
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/game/uigamemenu.c
|
||||||
|
msgid "ui.game_menu.save_unavailable"
|
||||||
|
msgstr "No se puede guardar: no se encontró ningún dispositivo de guardado."
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/game/uigamemenu.c
|
||||||
|
msgid "ui.game_menu.save_temporary"
|
||||||
|
msgstr "Esta sesión es temporal - no se encontró ningún dispositivo de guardado, por lo que guardar está deshabilitado."
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/game/uigamemenu.c
|
||||||
|
msgid "ui.game_menu.save_create_confirm"
|
||||||
|
msgstr "No se encontraron datos guardados. ¿Crear una partida nueva?"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/game/uigamemenu.c
|
||||||
|
msgid "ui.game_menu.save_failed_format"
|
||||||
|
msgstr "Error al guardar: %s"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/game/uigamemenu.c
|
||||||
|
msgid "ui.game_menu.save_check_failed_format"
|
||||||
|
msgstr "No se puede guardar: %s"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/initial/uiinitialnocard.c
|
||||||
|
msgid "ui.initial.no_card.message"
|
||||||
|
msgstr "No se encontró ningún dispositivo de guardado. Puedes continuar, pero\nel progreso no se guardará."
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/initial/uiinitialnocard.c
|
||||||
|
msgid "ui.initial.no_card.retry"
|
||||||
|
msgstr "Reintentar"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/initial/uiinitialnocard.c
|
||||||
|
msgid "ui.initial.no_card.continue"
|
||||||
|
msgstr "Continuar de todos modos"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/initial/uiinitialcreatesave.c
|
||||||
|
msgid "ui.initial.create_save.message"
|
||||||
|
msgstr "No se encontraron datos guardados. ¿Crear una partida nueva?"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/initial/uiinitialcreatesave.c
|
||||||
|
msgid "ui.initial.create_save.yes"
|
||||||
|
msgstr "Sí"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/initial/uiinitialcreatesave.c
|
||||||
|
msgid "ui.initial.create_save.no"
|
||||||
|
msgstr "No"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/mainmenu/uimainmenu.c
|
||||||
|
msgid "ui.main_menu.new_game"
|
||||||
|
msgstr "Nueva Partida"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/mainmenu/uimainmenu.c
|
||||||
|
msgid "ui.main_menu.load_game"
|
||||||
|
msgstr "Cargar Partida"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/mainmenu/uimainmenu.c
|
||||||
|
msgid "ui.main_menu.options"
|
||||||
|
msgstr "Opciones"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/mainmenu/uimainmenu.c
|
||||||
|
msgid "ui.main_menu.quit"
|
||||||
|
msgstr "Salir del Juego"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/uiconfirm.c
|
||||||
|
msgid "ui.confirm.confirm"
|
||||||
|
msgstr "Confirmar"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/uiconfirm.c
|
||||||
|
msgid "ui.confirm.cancel"
|
||||||
|
msgstr "Cancelar"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/battle/uibattlemenu.c
|
||||||
|
msgid "ui.battle.menu.attack"
|
||||||
|
msgstr "Atacar"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/battle/uibattlemenu.c
|
||||||
|
msgid "ui.battle.menu.flee"
|
||||||
|
msgstr "Huir"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/battle/uibattlemenu.c
|
||||||
|
msgid "ui.battle.menu.target_format"
|
||||||
|
msgstr "Enemigo %u (%u/%u PS)"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/battle/uibattlehud.c
|
||||||
|
msgid "ui.battle.hud.hp_format"
|
||||||
|
msgstr "PS %u/%u"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/battle/uibattlehud.c
|
||||||
|
msgid "ui.battle.hud.mp_format"
|
||||||
|
msgstr "PM %u/%u"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/settings/uisettingsaudio.c
|
||||||
|
msgid "ui.settings.audio.placeholder"
|
||||||
|
msgstr "Aún no hay opciones de audio"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/settings/uisettingsdisplay.c
|
||||||
|
msgid "ui.settings.display.placeholder"
|
||||||
|
msgstr "Aún no hay opciones de pantalla"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/settings/uisettingsinput.c
|
||||||
|
msgid "ui.settings.input.placeholder"
|
||||||
|
msgstr "Aún no hay opciones de entrada"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/backpack/uibackpack.c
|
||||||
|
msgid "ui.backpack.category_format"
|
||||||
|
msgstr "Categoría %u"
|
||||||
|
|
||||||
|
#: src/dusk/ui/overlay/uiloading.c
|
||||||
|
msgid "ui.loading.text"
|
||||||
|
msgstr "cargando"
|
||||||
|
|
||||||
|
#: src/dusk/ui/overlay/uiautosave.c
|
||||||
|
msgid "ui.autosave.saving"
|
||||||
|
msgstr "GUARDANDO"
|
||||||
|
|
||||||
|
#: src/dusk/rpg/item/item.json
|
||||||
|
msgid "item.potion.name"
|
||||||
|
msgstr "Poción"
|
||||||
|
|
||||||
|
#: src/dusk/rpg/item/item.json
|
||||||
|
msgid "item.potato.name"
|
||||||
|
msgstr "Papa"
|
||||||
|
|
||||||
|
#: src/dusk/rpg/item/item.json
|
||||||
|
msgid "item.apple.name"
|
||||||
|
msgstr "Manzana"
|
||||||
@@ -0,0 +1,194 @@
|
|||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: ExampleApp 1.0\n"
|
||||||
|
"Language: ja\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Plural-Forms: nplurals=1; plural=(0);\n"
|
||||||
|
|
||||||
|
#: ui/menu.c:10
|
||||||
|
msgid "ui.title"
|
||||||
|
msgstr ""
|
||||||
|
"歓迎"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/settings/uisettings.c
|
||||||
|
msgid "ui.settings.tabs.general"
|
||||||
|
msgstr "一般"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/settings/uisettings.c
|
||||||
|
msgid "ui.settings.tabs.input"
|
||||||
|
msgstr "入力"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/settings/uisettings.c
|
||||||
|
msgid "ui.settings.tabs.display"
|
||||||
|
msgstr "表示"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/settings/uisettings.c
|
||||||
|
msgid "ui.settings.tabs.audio"
|
||||||
|
msgstr "オーディオ"
|
||||||
|
|
||||||
|
msgid "ui.settings.input.deadzone"
|
||||||
|
msgstr "デッドゾーン"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/settings/uisettingsgeneral.c
|
||||||
|
msgid "ui.settings.general.language"
|
||||||
|
msgstr "言語"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/settings/uisettingsgeneral.c
|
||||||
|
msgid "ui.settings.general.language_detail"
|
||||||
|
msgstr "アプリケーションを再起動すると適用されます。"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/settings/uisettings.c
|
||||||
|
msgid "ui.settings.apply"
|
||||||
|
msgstr "適用"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/uiconfirm.c
|
||||||
|
msgid "ui.confirm.discard_changes"
|
||||||
|
msgstr "未保存の変更を破棄しますか?"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/game/uigamemenu.c
|
||||||
|
msgid "ui.game_menu.characters"
|
||||||
|
msgstr "キャラクター"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/game/uigamemenu.c
|
||||||
|
msgid "ui.game_menu.items"
|
||||||
|
msgstr "アイテム"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/game/uigamemenu.c
|
||||||
|
msgid "ui.game_menu.settings"
|
||||||
|
msgstr "設定"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/game/uigamemenu.c
|
||||||
|
msgid "ui.game_menu.save"
|
||||||
|
msgstr "セーブ"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/game/uigamemenu.c
|
||||||
|
msgid "ui.game_menu.save_success"
|
||||||
|
msgstr "セーブしました。"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/game/uigamemenu.c
|
||||||
|
msgid "ui.game_menu.save_cancelled"
|
||||||
|
msgstr "セーブをキャンセルしました。"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/game/uigamemenu.c
|
||||||
|
msgid "ui.game_menu.save_unavailable"
|
||||||
|
msgstr "セーブできません - セーブデバイスが見つかりません。"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/game/uigamemenu.c
|
||||||
|
msgid "ui.game_menu.save_temporary"
|
||||||
|
msgstr "このセッションは一時的です - セーブデバイスが見つからなかったため、セーブは無効になっています。"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/game/uigamemenu.c
|
||||||
|
msgid "ui.game_menu.save_create_confirm"
|
||||||
|
msgstr "セーブデータが見つかりません。新しいセーブを作成しますか?"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/game/uigamemenu.c
|
||||||
|
msgid "ui.game_menu.save_failed_format"
|
||||||
|
msgstr "セーブに失敗しました: %s"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/game/uigamemenu.c
|
||||||
|
msgid "ui.game_menu.save_check_failed_format"
|
||||||
|
msgstr "セーブできません: %s"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/initial/uiinitialnocard.c
|
||||||
|
msgid "ui.initial.no_card.message"
|
||||||
|
msgstr "セーブデバイスが見つかりません。続行できますが、\n進行状況は保存されません。"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/initial/uiinitialnocard.c
|
||||||
|
msgid "ui.initial.no_card.retry"
|
||||||
|
msgstr "再試行"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/initial/uiinitialnocard.c
|
||||||
|
msgid "ui.initial.no_card.continue"
|
||||||
|
msgstr "続行する"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/initial/uiinitialcreatesave.c
|
||||||
|
msgid "ui.initial.create_save.message"
|
||||||
|
msgstr "セーブデータが見つかりません。新しいセーブを作成しますか?"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/initial/uiinitialcreatesave.c
|
||||||
|
msgid "ui.initial.create_save.yes"
|
||||||
|
msgstr "はい"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/initial/uiinitialcreatesave.c
|
||||||
|
msgid "ui.initial.create_save.no"
|
||||||
|
msgstr "いいえ"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/mainmenu/uimainmenu.c
|
||||||
|
msgid "ui.main_menu.new_game"
|
||||||
|
msgstr "ニューゲーム"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/mainmenu/uimainmenu.c
|
||||||
|
msgid "ui.main_menu.load_game"
|
||||||
|
msgstr "ロードゲーム"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/mainmenu/uimainmenu.c
|
||||||
|
msgid "ui.main_menu.options"
|
||||||
|
msgstr "オプション"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/mainmenu/uimainmenu.c
|
||||||
|
msgid "ui.main_menu.quit"
|
||||||
|
msgstr "ゲームを終了"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/uiconfirm.c
|
||||||
|
msgid "ui.confirm.confirm"
|
||||||
|
msgstr "確認"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/uiconfirm.c
|
||||||
|
msgid "ui.confirm.cancel"
|
||||||
|
msgstr "キャンセル"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/battle/uibattlemenu.c
|
||||||
|
msgid "ui.battle.menu.attack"
|
||||||
|
msgstr "攻撃"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/battle/uibattlemenu.c
|
||||||
|
msgid "ui.battle.menu.flee"
|
||||||
|
msgstr "逃げる"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/battle/uibattlemenu.c
|
||||||
|
msgid "ui.battle.menu.target_format"
|
||||||
|
msgstr "敵%u (%u/%u HP)"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/battle/uibattlehud.c
|
||||||
|
msgid "ui.battle.hud.hp_format"
|
||||||
|
msgstr "HP %u/%u"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/battle/uibattlehud.c
|
||||||
|
msgid "ui.battle.hud.mp_format"
|
||||||
|
msgstr "MP %u/%u"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/settings/uisettingsaudio.c
|
||||||
|
msgid "ui.settings.audio.placeholder"
|
||||||
|
msgstr "オーディオ設定はまだありません"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/settings/uisettingsdisplay.c
|
||||||
|
msgid "ui.settings.display.placeholder"
|
||||||
|
msgstr "表示設定はまだありません"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/settings/uisettingsinput.c
|
||||||
|
msgid "ui.settings.input.placeholder"
|
||||||
|
msgstr "入力設定はまだありません"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/backpack/uibackpack.c
|
||||||
|
msgid "ui.backpack.category_format"
|
||||||
|
msgstr "カテゴリー%u"
|
||||||
|
|
||||||
|
#: src/dusk/ui/overlay/uiloading.c
|
||||||
|
msgid "ui.loading.text"
|
||||||
|
msgstr "読み込み中"
|
||||||
|
|
||||||
|
#: src/dusk/ui/overlay/uiautosave.c
|
||||||
|
msgid "ui.autosave.saving"
|
||||||
|
msgstr "保存中"
|
||||||
|
|
||||||
|
#: src/dusk/rpg/item/item.json
|
||||||
|
msgid "item.potion.name"
|
||||||
|
msgstr "ポーション"
|
||||||
|
|
||||||
|
#: src/dusk/rpg/item/item.json
|
||||||
|
msgid "item.potato.name"
|
||||||
|
msgstr "ジャガイモ"
|
||||||
|
|
||||||
|
#: src/dusk/rpg/item/item.json
|
||||||
|
msgid "item.apple.name"
|
||||||
|
msgstr "リンゴ"
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 1.2 KiB |
@@ -2179,5 +2179,27 @@
|
|||||||
0
|
0
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"entities": [
|
||||||
|
{
|
||||||
|
"type": "global",
|
||||||
|
"globalId": 3,
|
||||||
|
"pos": [8, 8, 1]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "item",
|
||||||
|
"itemId": 1,
|
||||||
|
"quantity": 1,
|
||||||
|
"pos": [12, 2, 0]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"areas": [
|
||||||
|
{
|
||||||
|
"min": [11, 3, 0],
|
||||||
|
"max": [16, 9, 10],
|
||||||
|
"callbackId": 1,
|
||||||
|
"notify": 3,
|
||||||
|
"trigger": 6
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -16,6 +16,9 @@ target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
|
|||||||
DOL=1
|
DOL=1
|
||||||
ISO=2
|
ISO=2
|
||||||
DUSK_DOLPHIN_BUILD_TYPE=${DUSK_DOLPHIN_BUILD_TYPE}
|
DUSK_DOLPHIN_BUILD_TYPE=${DUSK_DOLPHIN_BUILD_TYPE}
|
||||||
|
# GameCube/Wii PowerPC is always big-endian; declare it at compile time
|
||||||
|
# like every other target instead of relying on endian.h's runtime probe.
|
||||||
|
DUSK_PLATFORM_ENDIAN_BIG
|
||||||
)
|
)
|
||||||
|
|
||||||
# Custom compiler flags
|
# Custom compiler flags
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
|
|||||||
# DUSK_OPENGL_LEGACY
|
# DUSK_OPENGL_LEGACY
|
||||||
DUSK_LINUX
|
DUSK_LINUX
|
||||||
DUSK_DISPLAY_SIZE_DYNAMIC
|
DUSK_DISPLAY_SIZE_DYNAMIC
|
||||||
DUSK_DISPLAY_WIDTH_DEFAULT=640
|
DUSK_DISPLAY_WIDTH_DEFAULT=854
|
||||||
DUSK_DISPLAY_HEIGHT_DEFAULT=480
|
DUSK_DISPLAY_HEIGHT_DEFAULT=480
|
||||||
DUSK_DISPLAY_SCREEN_HEIGHT=240
|
DUSK_DISPLAY_SCREEN_HEIGHT=240
|
||||||
DUSK_INPUT_KEYBOARD
|
DUSK_INPUT_KEYBOARD
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ target_compile_definitions(${DUSK_BINARY_TARGET_NAME} PUBLIC
|
|||||||
DUSK_DISPLAY_HEIGHT=272
|
DUSK_DISPLAY_HEIGHT=272
|
||||||
DUSK_THREAD_PTHREAD
|
DUSK_THREAD_PTHREAD
|
||||||
DUSK_TIME_DYNAMIC
|
DUSK_TIME_DYNAMIC
|
||||||
|
DUSK_DISPLAY_OVERSCAN=6
|
||||||
)
|
)
|
||||||
|
|
||||||
if(NOT CMAKE_BUILD_TYPE STREQUAL "Debug")
|
if(NOT CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||||
|
|||||||
@@ -4,6 +4,17 @@ target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
|
|||||||
DUSK_WII
|
DUSK_WII
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Wii save storage method - see src/duskdolphin/save/savedeviceplatform.h.
|
||||||
|
set(DUSK_SAVE_WII_METHOD "NAND" CACHE STRING
|
||||||
|
"Wii save storage: NAND (internal storage via ISFS), CARD (GameCube-\
|
||||||
|
compatible memory card emulation), or SD (SD card via libfat)"
|
||||||
|
)
|
||||||
|
set_property(CACHE DUSK_SAVE_WII_METHOD PROPERTY STRINGS "NAND" "CARD" "SD")
|
||||||
|
|
||||||
|
target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
|
||||||
|
DUSK_SAVE_WII_METHOD_${DUSK_SAVE_WII_METHOD}
|
||||||
|
)
|
||||||
|
|
||||||
# Generate Homebrew Channel meta.xml from project identity variables
|
# Generate Homebrew Channel meta.xml from project identity variables
|
||||||
string(TIMESTAMP DUSK_BUILD_DATE "%Y%m%d000000" UTC)
|
string(TIMESTAMP DUSK_BUILD_DATE "%Y%m%d000000" UTC)
|
||||||
configure_file(
|
configure_file(
|
||||||
|
|||||||
@@ -5,6 +5,10 @@
|
|||||||
|
|
||||||
add_subdirectory(dusk)
|
add_subdirectory(dusk)
|
||||||
|
|
||||||
|
if(DUSK_NETWORK)
|
||||||
|
add_subdirectory(dusknetwork)
|
||||||
|
endif()
|
||||||
|
|
||||||
if(DUSK_TARGET_SYSTEM STREQUAL "linux" OR DUSK_TARGET_SYSTEM STREQUAL "knulli")
|
if(DUSK_TARGET_SYSTEM STREQUAL "linux" OR DUSK_TARGET_SYSTEM STREQUAL "knulli")
|
||||||
add_subdirectory(dusklinux)
|
add_subdirectory(dusklinux)
|
||||||
add_subdirectory(dusksdl2)
|
add_subdirectory(dusksdl2)
|
||||||
|
|||||||
@@ -68,7 +68,6 @@ add_subdirectory(scene)
|
|||||||
add_subdirectory(system)
|
add_subdirectory(system)
|
||||||
add_subdirectory(time)
|
add_subdirectory(time)
|
||||||
add_subdirectory(ui)
|
add_subdirectory(ui)
|
||||||
add_subdirectory(network)
|
|
||||||
add_subdirectory(save)
|
add_subdirectory(save)
|
||||||
add_subdirectory(util)
|
add_subdirectory(util)
|
||||||
add_subdirectory(thread)
|
add_subdirectory(thread)
|
||||||
@@ -7,4 +7,5 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
|||||||
PUBLIC
|
PUBLIC
|
||||||
easing.c
|
easing.c
|
||||||
animation.c
|
animation.c
|
||||||
|
keyframe.c
|
||||||
)
|
)
|
||||||
|
|||||||
+105
-25
@@ -11,42 +11,122 @@
|
|||||||
void animationInit(
|
void animationInit(
|
||||||
animation_t *anim,
|
animation_t *anim,
|
||||||
keyframe_t *keyframes,
|
keyframe_t *keyframes,
|
||||||
uint16_t keyframeCount
|
uint16_t *keyframeCounts,
|
||||||
|
const uint16_t layerCount
|
||||||
) {
|
) {
|
||||||
assertNotNull(anim, "Animation pointer cannot be null.");
|
assertNotNull(anim, "Animation pointer cannot be null.");
|
||||||
assertNotNull(keyframes, "Keyframes pointer cannot be null.");
|
assertNotNull(keyframes, "Keyframes pointer cannot be null.");
|
||||||
assertTrue(keyframeCount > 0, "Keyframe count must be more than 0.");
|
assertNotNull(keyframeCounts, "Keyframe counts pointer cannot be null.");
|
||||||
|
assertTrue(layerCount > 0, "Layer count must be greater than zero.");
|
||||||
|
|
||||||
|
memoryZero(anim, sizeof(animation_t));
|
||||||
anim->keyframes = keyframes;
|
anim->keyframes = keyframes;
|
||||||
anim->keyframeCount = keyframeCount;
|
anim->keyframeCounts = keyframeCounts;
|
||||||
|
anim->layerCount = layerCount;
|
||||||
|
|
||||||
|
// Determine duration
|
||||||
|
float_t duration = 0.0f;
|
||||||
|
for(uint16_t layer = 0; layer < layerCount; layer++) {
|
||||||
|
uint16_t keyframeCount = keyframeCounts[layer];
|
||||||
|
assertTrue(keyframeCount > 0, "Keyframe count invalid.");
|
||||||
|
keyframe_t *layerKeyframes = keyframes + layer * keyframeCount;
|
||||||
|
|
||||||
|
#ifdef DUSK_ASSERTIONS
|
||||||
|
// Check that the keyframes are sorted by time.
|
||||||
|
for(uint16_t i = 1; i < keyframeCount; i++) {
|
||||||
|
assertTrue(
|
||||||
|
layerKeyframes[i].time >= layerKeyframes[i - 1].time,
|
||||||
|
"Keyframes must be sorted by time."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
keyframe_t *lastKeyframe = layerKeyframes + keyframeCount - 1;
|
||||||
|
duration = mathMax(duration, lastKeyframe->time);
|
||||||
|
}
|
||||||
|
assertTrue(duration > 0, "Animation duration must be greater than 0.");
|
||||||
|
anim->duration = duration;
|
||||||
}
|
}
|
||||||
|
|
||||||
float_t animationGetValue(animation_t *anim, const float_t time) {
|
float_t animationGetLayerValue(const animation_t *anim, const uint16_t layer) {
|
||||||
assertNotNull(anim, "Animation pointer cannot be null.");
|
assertNotNull(anim, "Animation pointer cannot be null.");
|
||||||
assertNotNull(anim->keyframes, "Keyframes pointer cannot be null.");
|
assertTrue(layer < anim->layerCount, "Layer index out of bounds.");
|
||||||
assertTrue(anim->keyframeCount > 0, "Keyframe count invalid.");
|
|
||||||
assertTrue(time >= 0, "Time must be non-negative.");
|
|
||||||
|
|
||||||
keyframe_t *start;
|
|
||||||
keyframe_t *end;
|
|
||||||
keyframe_t *last = anim->keyframes + anim->keyframeCount - 1;
|
|
||||||
keyframe_t *current = anim->keyframes;
|
|
||||||
start = current;
|
|
||||||
|
|
||||||
do {
|
uint16_t keyframeCount = anim->keyframeCounts[layer];
|
||||||
if(current->time > time) {
|
keyframe_t *layerKeyframes = anim->keyframes + layer * keyframeCount;
|
||||||
end = current;
|
return keyframeGetValue(layerKeyframes, keyframeCount, anim->time);
|
||||||
break;
|
}
|
||||||
|
|
||||||
|
void animationUpdate(
|
||||||
|
animation_t *anim,
|
||||||
|
const float_t deltaTime
|
||||||
|
) {
|
||||||
|
assertNotNull(anim, "Animation pointer cannot be null.");
|
||||||
|
assertTrue(deltaTime >= 0, "Delta time must be non-negative.");
|
||||||
|
|
||||||
|
bool_t justCompleted = false;
|
||||||
|
|
||||||
|
if(!(anim->flags & ANIMATION_FLAG_INTERNAL_COMPLETED)) {
|
||||||
|
bool_t loop = (anim->flags & ANIMATION_FLAG_LOOP) != 0;
|
||||||
|
bool_t pingpong = (anim->flags & ANIMATION_FLAG_PINGPONG) != 0;
|
||||||
|
assertFalse(
|
||||||
|
loop && pingpong,
|
||||||
|
"Cannot set both ANIMATION_FLAG_LOOP and ANIMATION_FLAG_PINGPONG."
|
||||||
|
);
|
||||||
|
|
||||||
|
bool_t backward;
|
||||||
|
if(pingpong) {
|
||||||
|
backward = (anim->flags & ANIMATION_FLAG_INTERNAL_PINGPONG_BACKWARD) != 0;
|
||||||
|
} else {
|
||||||
|
backward = (anim->flags & ANIMATION_FLAG_REVERSE) != 0;
|
||||||
}
|
}
|
||||||
start = current;
|
|
||||||
current++;
|
|
||||||
|
|
||||||
if(current > last) {
|
// Resolve boundary crossings one at a time, so a single large deltaTime
|
||||||
end = start;
|
// can correctly loop/pingpong across multiple boundaries in one call.
|
||||||
break;
|
float_t remaining = deltaTime;
|
||||||
|
while(remaining > 0.0f) {
|
||||||
|
float_t toBoundary = (
|
||||||
|
backward ? anim->time : (anim->duration - anim->time)
|
||||||
|
);
|
||||||
|
if(remaining < toBoundary) {
|
||||||
|
anim->time += backward ? -remaining : remaining;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
remaining -= toBoundary;
|
||||||
|
anim->time = backward ? 0.0f : anim->duration;
|
||||||
|
|
||||||
|
bool_t stopHere;
|
||||||
|
if(backward) {
|
||||||
|
stopHere = (anim->flags & ANIMATION_FLAG_STOP_BEGINNING) != 0;
|
||||||
|
} else {
|
||||||
|
stopHere = (anim->flags & ANIMATION_FLAG_STOP_END) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(stopHere) {
|
||||||
|
justCompleted = true;
|
||||||
|
break;
|
||||||
|
} else if(pingpong) {
|
||||||
|
backward = !backward;
|
||||||
|
if(backward) anim->flags |= ANIMATION_FLAG_INTERNAL_PINGPONG_BACKWARD;
|
||||||
|
else anim->flags &= ~ANIMATION_FLAG_INTERNAL_PINGPONG_BACKWARD;
|
||||||
|
} else if(loop) {
|
||||||
|
anim->time = backward ? anim->duration : 0.0f;
|
||||||
|
if(anim->onLoop) anim->onLoop(anim->user);
|
||||||
|
} else {
|
||||||
|
justCompleted = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} while(true);
|
|
||||||
|
|
||||||
float_t t = (time - start->time) / (end->time - start->time);
|
if(justCompleted) anim->flags |= ANIMATION_FLAG_INTERNAL_COMPLETED;
|
||||||
return mathLerp(start->value, end->value, easingApply(start->easing, t));
|
}
|
||||||
|
|
||||||
|
// Call onUpdate for each layer.
|
||||||
|
for(uint16_t layer = 0; layer < anim->layerCount; layer++) {
|
||||||
|
float_t value = animationGetLayerValue(anim, layer);
|
||||||
|
if(anim->onUpdate) anim->onUpdate(layer, value, anim->user);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(justCompleted && anim->onComplete) anim->onComplete(anim->user);
|
||||||
}
|
}
|
||||||
@@ -6,29 +6,89 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "keyframe.h"
|
#include "keyframe.h"
|
||||||
|
|
||||||
|
#define ANIMATION_FLAG_LOOP (1 << 0)
|
||||||
|
#define ANIMATION_FLAG_REVERSE (1 << 1)
|
||||||
|
#define ANIMATION_FLAG_PINGPONG (1 << 2)
|
||||||
|
#define ANIMATION_FLAG_STOP_BEGINNING (1 << 3)
|
||||||
|
#define ANIMATION_FLAG_STOP_END (1 << 4)
|
||||||
|
|
||||||
|
// Internal - tracks which direction a pingponging animation is currently
|
||||||
|
// travelling. Do not set this manually, it is managed by animationUpdate().
|
||||||
|
#define ANIMATION_FLAG_INTERNAL_PINGPONG_BACKWARD (1 << 7)
|
||||||
|
|
||||||
|
// Internal - set once the animation has stopped advancing (see
|
||||||
|
// animationUpdate()). Do not set this manually. There is currently no way to
|
||||||
|
// restart a completed animation short of clearing this bit and resetting
|
||||||
|
// anim->time by hand.
|
||||||
|
#define ANIMATION_FLAG_INTERNAL_COMPLETED (1 << 6)
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
keyframe_t *keyframes;
|
keyframe_t *keyframes;
|
||||||
uint16_t keyframeCount;
|
uint16_t *keyframeCounts;
|
||||||
|
uint16_t layerCount;
|
||||||
|
float_t time;
|
||||||
|
float_t duration;
|
||||||
|
uint8_t flags;
|
||||||
|
|
||||||
|
void *user;
|
||||||
|
void (*onUpdate)(const uint16_t layer, const float_t value, void *user);
|
||||||
|
void (*onComplete)(void *user);
|
||||||
|
void (*onLoop)(void *user);
|
||||||
} animation_t;
|
} animation_t;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes an animation.
|
* Initializes an animation with the given keyframes and layer count.
|
||||||
*
|
*
|
||||||
* @param anim The animation to initialize.
|
* @param anim Pointer to the animation to initialize.
|
||||||
* @param keyframes The keyframes to use for the animation.
|
* @param keyframes Pointer to the array of keyframes for each layer.
|
||||||
* @param keyframeCount The number of keyframes in the animation.
|
* @param keyframeCount Number of keyframes in each layer.
|
||||||
|
* @param layerCount Number of layers in the animation.
|
||||||
*/
|
*/
|
||||||
void animationInit(
|
void animationInit(
|
||||||
animation_t *anim,
|
animation_t *anim,
|
||||||
keyframe_t *keyframes,
|
keyframe_t *keyframes,
|
||||||
uint16_t keyframeCount
|
uint16_t *keyframeCounts,
|
||||||
|
const uint16_t layerCount
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the value of the animation at a given time.
|
* Sets the current time of the animation, clamping it to the valid range.
|
||||||
|
* This will call the onUpdate callback but none of the other callbacks.
|
||||||
*
|
*
|
||||||
* @param anim The animation to get the value from.
|
* @param anim Pointer to the animation to set the time for.
|
||||||
* @param time The time at which to get the value, in seconds.
|
* @param time The new time to set for the animation.
|
||||||
* @return The value of the animation at the given time.
|
|
||||||
*/
|
*/
|
||||||
float_t animationGetValue(animation_t *anim, const float_t time);
|
void animationSetTime(animation_t *anim, const float_t time);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the current value of a specific layer in the animation based on the
|
||||||
|
* current animation time.
|
||||||
|
*/
|
||||||
|
float_t animationGetLayerValue(const animation_t *anim, const uint16_t layer);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the animation state based on the elapsed time. Advances anim->time
|
||||||
|
* by deltaTime (or against it, if ANIMATION_FLAG_REVERSE is set), then
|
||||||
|
* resolves whatever happens when it reaches the 0 or duration boundary:
|
||||||
|
*
|
||||||
|
* - ANIMATION_FLAG_PINGPONG: reflects off the boundary and continues playing
|
||||||
|
* in the opposite direction, forever, unless stopped (see below).
|
||||||
|
* - ANIMATION_FLAG_LOOP: wraps back around to the other boundary and keeps
|
||||||
|
* playing in the same direction, forever, unless stopped (see below).
|
||||||
|
* - ANIMATION_FLAG_STOP_BEGINNING / ANIMATION_FLAG_STOP_END: when the
|
||||||
|
* animation reaches that specific boundary, it clamps there and stops
|
||||||
|
* (firing onComplete) instead of looping/pingponging past it.
|
||||||
|
* - If none of the above apply at a boundary, the animation clamps there and
|
||||||
|
* stops, firing onComplete.
|
||||||
|
*
|
||||||
|
* onUpdate is called for every layer on every call. onLoop is called each
|
||||||
|
* time a loop wraps around. onComplete is called at most once, the moment
|
||||||
|
* the animation stops advancing.
|
||||||
|
*
|
||||||
|
* @param anim Pointer to the animation to update.
|
||||||
|
* @param deltaTime Time elapsed since the last update (in seconds).
|
||||||
|
*/
|
||||||
|
void animationUpdate(
|
||||||
|
animation_t *anim,
|
||||||
|
const float_t deltaTime
|
||||||
|
);
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "keyframe.h"
|
||||||
|
#include "assert/assert.h"
|
||||||
|
#include "util/math.h"
|
||||||
|
|
||||||
|
float_t keyframeGetValue(
|
||||||
|
const keyframe_t *keyframes,
|
||||||
|
const uint32_t keyframeCount,
|
||||||
|
const float_t time
|
||||||
|
) {
|
||||||
|
assertNotNull(keyframes, "Keyframes pointer cannot be null.");
|
||||||
|
assertTrue(keyframeCount > 0, "Keyframe count must be more than 0.");
|
||||||
|
assertTrue(time >= 0, "Time must be non-negative.");
|
||||||
|
#ifdef DUSK_ASSERTIONS
|
||||||
|
// Checks that the keyframes are sorted by time.
|
||||||
|
for(uint32_t i = 1; i < keyframeCount; i++) {
|
||||||
|
assertTrue(
|
||||||
|
keyframes[i].time >= keyframes[i - 1].time,
|
||||||
|
"Keyframes must be sorted by time."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
keyframe_t *last = (keyframe_t *)(keyframes + keyframeCount - 1);
|
||||||
|
if(time >= last->time) return last->value;
|
||||||
|
|
||||||
|
// Since time < last->time (checked above), current is guaranteed to stop
|
||||||
|
// at or before reaching last, so no separate end-of-array check is needed.
|
||||||
|
keyframe_t *current = (keyframe_t *)keyframes;
|
||||||
|
keyframe_t *start = current;
|
||||||
|
while(current->time <= time) {
|
||||||
|
start = current;
|
||||||
|
current++;
|
||||||
|
}
|
||||||
|
keyframe_t *end = current;
|
||||||
|
|
||||||
|
float_t t = (time - start->time) / (end->time - start->time);
|
||||||
|
return mathLerp(start->value, end->value, easingApply(start->easing, t));
|
||||||
|
}
|
||||||
@@ -10,4 +10,18 @@ typedef struct {
|
|||||||
float_t time;
|
float_t time;
|
||||||
float_t value;
|
float_t value;
|
||||||
easingtype_t easing;
|
easingtype_t easing;
|
||||||
} keyframe_t;
|
} keyframe_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the value of a keyframe at a given time.
|
||||||
|
*
|
||||||
|
* @param keyframes The keyframes to get the value from.
|
||||||
|
* @param keyframeCount The number of keyframes in the array.
|
||||||
|
* @param time The time at which to get the value, in seconds.
|
||||||
|
* @return The value of the keyframe at the given time.
|
||||||
|
*/
|
||||||
|
float_t keyframeGetValue(
|
||||||
|
const keyframe_t *keyframes,
|
||||||
|
const uint32_t keyframeCount,
|
||||||
|
const float_t time
|
||||||
|
);
|
||||||
@@ -22,6 +22,8 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifndef DUSK_ASSERTIONS_FAKED
|
#ifndef DUSK_ASSERTIONS_FAKED
|
||||||
|
#define DUSK_ASSERTIONS 1
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the assert system. Must be the very first call in engine
|
* Initializes the assert system. Must be the very first call in engine
|
||||||
* startup.
|
* startup.
|
||||||
|
|||||||
+48
-57
@@ -59,20 +59,28 @@ assetentry_t * assetGetEntry(
|
|||||||
entry++;
|
entry++;
|
||||||
} while(entry < ASSET.entries + ASSET_ENTRY_COUNT_MAX);
|
} while(entry < ASSET.entries + ASSET_ENTRY_COUNT_MAX);
|
||||||
|
|
||||||
// We did not find one existing, Find first available slot.
|
// We did not find one existing. Find first available slot, reaping
|
||||||
entry = ASSET.entries;
|
// zero-ref entries to make room if none are immediately available.
|
||||||
do {
|
bool_t reaped = false;
|
||||||
if(entry->type != ASSET_LOADER_TYPE_NULL) {
|
for(;;) {
|
||||||
entry++;
|
entry = ASSET.entries;
|
||||||
continue;
|
do {
|
||||||
}
|
if(entry->type != ASSET_LOADER_TYPE_NULL) {
|
||||||
|
entry++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if(entry->state == ASSET_ENTRY_STATE_NOT_STARTED) {
|
if(entry->state == ASSET_ENTRY_STATE_NOT_STARTED) {
|
||||||
assetEntryInit(entry, name, type, input);
|
assetEntryInit(entry, name, type, input);
|
||||||
return entry;
|
return entry;
|
||||||
}
|
}
|
||||||
entry++;
|
entry++;
|
||||||
} while(entry < ASSET.entries + ASSET_ENTRY_COUNT_MAX);
|
} while(entry < ASSET.entries + ASSET_ENTRY_COUNT_MAX);
|
||||||
|
|
||||||
|
if(reaped) break;
|
||||||
|
reaped = true;
|
||||||
|
errorCatch(assetReapUnused());
|
||||||
|
}
|
||||||
|
|
||||||
assertUnreachable("No available asset entry slots.");
|
assertUnreachable("No available asset entry slots.");
|
||||||
return NULL;
|
return NULL;
|
||||||
@@ -191,6 +199,32 @@ void assetUnlockEntry(assetentry_t *entry) {
|
|||||||
assetEntryUnlock(entry);
|
assetEntryUnlock(entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
errorret_t assetReapUnused(void) {
|
||||||
|
assertIsMainThread("assetReapUnused must be called from the main thread.");
|
||||||
|
|
||||||
|
// Repeatedly find and dispose zero-ref LOADED entries until none remain.
|
||||||
|
// This handles dependency chains where an entry (e.g. a model) holds refs
|
||||||
|
// on child entries (mesh, texture): dispose parents first so child ref
|
||||||
|
// counts drop to zero, then pick up the children on the next pass. Without
|
||||||
|
// this, a forward-only scan fails when a shared child entry appears before
|
||||||
|
// a parent that still holds a ref to it.
|
||||||
|
bool_t any;
|
||||||
|
do {
|
||||||
|
any = false;
|
||||||
|
assetentry_t *entry = ASSET.entries;
|
||||||
|
do {
|
||||||
|
if(entry->type == ASSET_LOADER_TYPE_NULL) { entry++; continue; }
|
||||||
|
if(entry->state != ASSET_ENTRY_STATE_LOADED) { entry++; continue; }
|
||||||
|
if(entry->refs.count > 0) { entry++; continue; }
|
||||||
|
errorChain(assetEntryDispose(entry));
|
||||||
|
any = true;
|
||||||
|
entry++;
|
||||||
|
} while(entry < ASSET.entries + ASSET_ENTRY_COUNT_MAX);
|
||||||
|
} while(any);
|
||||||
|
|
||||||
|
errorOk();
|
||||||
|
}
|
||||||
|
|
||||||
errorret_t assetUpdate(void) {
|
errorret_t assetUpdate(void) {
|
||||||
assertIsMainThread("assetUpdate must be called from the main thread.");
|
assertIsMainThread("assetUpdate must be called from the main thread.");
|
||||||
|
|
||||||
@@ -324,30 +358,6 @@ errorret_t assetUpdate(void) {
|
|||||||
}
|
}
|
||||||
} while(loading < ASSET.loading + ASSET_LOADING_COUNT_MAX);
|
} while(loading < ASSET.loading + ASSET_LOADING_COUNT_MAX);
|
||||||
|
|
||||||
|
|
||||||
// Reap unused entries.
|
|
||||||
entry = ASSET.entries;
|
|
||||||
do {
|
|
||||||
if(entry->state != ASSET_ENTRY_STATE_LOADED) {
|
|
||||||
entry++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(entry->type == ASSET_LOADER_TYPE_NULL) {
|
|
||||||
entry++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(entry->refs.count > 0) {
|
|
||||||
entry++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// consolePrint("Reaping asset %s", entry->name);
|
|
||||||
errorChain(assetEntryDispose(entry));
|
|
||||||
entry++;
|
|
||||||
} while(entry < ASSET.entries + ASSET_ENTRY_COUNT_MAX);
|
|
||||||
|
|
||||||
errorOk();
|
errorOk();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -413,26 +423,7 @@ errorret_t assetDispose(void) {
|
|||||||
assertIsMainThread("Must be called from the main thread.");
|
assertIsMainThread("Must be called from the main thread.");
|
||||||
threadStop(&ASSET.loadThread);
|
threadStop(&ASSET.loadThread);
|
||||||
|
|
||||||
// Drain-dispose: repeatedly find and dispose zero-ref LOADED entries
|
errorChain(assetReapUnused());
|
||||||
// until none remain. This handles dependency chains where an entry
|
|
||||||
// (e.g. a model) holds refs on child entries (mesh, texture): dispose
|
|
||||||
// parents first so child ref counts drop to zero, then pick up the
|
|
||||||
// children on the next pass. Without this, a forward-only scan fails
|
|
||||||
// when a shared child entry appears before a parent that still holds a
|
|
||||||
// ref to it.
|
|
||||||
bool_t any;
|
|
||||||
do {
|
|
||||||
any = false;
|
|
||||||
assetentry_t *e = ASSET.entries;
|
|
||||||
do {
|
|
||||||
if(e->type == ASSET_LOADER_TYPE_NULL) { e++; continue; }
|
|
||||||
if(e->state != ASSET_ENTRY_STATE_LOADED) { e++; continue; }
|
|
||||||
if(e->refs.count > 0) { e++; continue; }
|
|
||||||
errorChain(assetEntryDispose(e));
|
|
||||||
any = true;
|
|
||||||
e++;
|
|
||||||
} while(e < ASSET.entries + ASSET_ENTRY_COUNT_MAX);
|
|
||||||
} while(any);
|
|
||||||
|
|
||||||
// Cleanup zip file.
|
// Cleanup zip file.
|
||||||
if(ASSET.zip != NULL) {
|
if(ASSET.zip != NULL) {
|
||||||
|
|||||||
+12
-2
@@ -23,8 +23,8 @@
|
|||||||
#define ASSET_FILE_NAME "dusk.dsk"
|
#define ASSET_FILE_NAME "dusk.dsk"
|
||||||
#define ASSET_HEADER_SIZE 3
|
#define ASSET_HEADER_SIZE 3
|
||||||
|
|
||||||
#define ASSET_LOADING_COUNT_MAX 20
|
#define ASSET_LOADING_COUNT_MAX 10
|
||||||
#define ASSET_ENTRY_COUNT_MAX 128
|
#define ASSET_ENTRY_COUNT_MAX 64
|
||||||
|
|
||||||
typedef struct asset_s {
|
typedef struct asset_s {
|
||||||
zip_t *zip;
|
zip_t *zip;
|
||||||
@@ -112,6 +112,16 @@ void assetUnlock(const char_t *name);
|
|||||||
*/
|
*/
|
||||||
void assetUnlockEntry(assetentry_t *entry);
|
void assetUnlockEntry(assetentry_t *entry);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Frees every currently unreferenced (zero-ref) loaded asset entry. Repeats
|
||||||
|
* until a full pass frees nothing further, since disposing a parent entry
|
||||||
|
* (e.g. a model) may drop a child entry's (e.g. a mesh) ref count to zero,
|
||||||
|
* making it eligible for reaping too.
|
||||||
|
*
|
||||||
|
* @return An error code if any entry could not be disposed properly.
|
||||||
|
*/
|
||||||
|
errorret_t assetReapUnused(void);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Requires an asset entry to be loaded. This will block until the asset entry
|
* Requires an asset entry to be loaded. This will block until the asset entry
|
||||||
* is fully loaded.
|
* is fully loaded.
|
||||||
|
|||||||
@@ -14,6 +14,26 @@
|
|||||||
#include "asset/loader/assetloader.h"
|
#include "asset/loader/assetloader.h"
|
||||||
#include "asset/asset.h"
|
#include "asset/asset.h"
|
||||||
|
|
||||||
|
// Reads a little-endian int16 from a potentially-unaligned offset into a
|
||||||
|
// worldunit_t, advancing *offset past it.
|
||||||
|
static worldunit_t assetChunkReadWorldUnit(
|
||||||
|
const uint8_t *data,
|
||||||
|
size_t *offset
|
||||||
|
) {
|
||||||
|
int16_t value;
|
||||||
|
memoryCopy(&value, data + *offset, sizeof(int16_t));
|
||||||
|
*offset += sizeof(int16_t);
|
||||||
|
return (worldunit_t)endianLittleToHost16((uint16_t)value);
|
||||||
|
}
|
||||||
|
|
||||||
|
static worldpos_t assetChunkReadWorldPos(const uint8_t *data, size_t *offset) {
|
||||||
|
worldpos_t pos;
|
||||||
|
pos.x = assetChunkReadWorldUnit(data, offset);
|
||||||
|
pos.y = assetChunkReadWorldUnit(data, offset);
|
||||||
|
pos.z = assetChunkReadWorldUnit(data, offset);
|
||||||
|
return pos;
|
||||||
|
}
|
||||||
|
|
||||||
errorret_t assetChunkLoaderAsync(assetloading_t *loading) {
|
errorret_t assetChunkLoaderAsync(assetloading_t *loading) {
|
||||||
assertNotNull(loading, "Loading cannot be NULL");
|
assertNotNull(loading, "Loading cannot be NULL");
|
||||||
assertNotMainThread("Should be called from an async thread.");
|
assertNotMainThread("Should be called from an async thread.");
|
||||||
@@ -111,9 +131,15 @@ errorret_t assetChunkLoaderSync(assetloading_t *loading) {
|
|||||||
size_t offset = 8;
|
size_t offset = 8;
|
||||||
|
|
||||||
size_t tileSize = CHUNK_TILE_COUNT * sizeof(tile_t);
|
size_t tileSize = CHUNK_TILE_COUNT * sizeof(tile_t);
|
||||||
|
out->tiles = memoryAllocate(tileSize);
|
||||||
memoryCopy(out->tiles, data + offset, tileSize);
|
memoryCopy(out->tiles, data + offset, tileSize);
|
||||||
offset += tileSize;
|
offset += tileSize;
|
||||||
|
|
||||||
|
for(size_t t = 0; t < CHUNK_TILE_COUNT; t++) {
|
||||||
|
uint32_t *shape = (uint32_t *)&out->tiles[t].shape;
|
||||||
|
*shape = endianLittleToHost32(*shape);
|
||||||
|
}
|
||||||
|
|
||||||
out->meshCount = data[offset];
|
out->meshCount = data[offset];
|
||||||
offset += sizeof(uint8_t);
|
offset += sizeof(uint8_t);
|
||||||
assertTrue(
|
assertTrue(
|
||||||
@@ -135,6 +161,65 @@ errorret_t assetChunkLoaderSync(assetloading_t *loading) {
|
|||||||
|
|
||||||
memoryCopy(out->meshOffsets[m], data + offset, sizeof(vec3));
|
memoryCopy(out->meshOffsets[m], data + offset, sizeof(vec3));
|
||||||
offset += sizeof(vec3);
|
offset += sizeof(vec3);
|
||||||
|
out->meshOffsets[m][0] = endianLittleToHostFloat(out->meshOffsets[m][0]);
|
||||||
|
out->meshOffsets[m][1] = endianLittleToHostFloat(out->meshOffsets[m][1]);
|
||||||
|
out->meshOffsets[m][2] = endianLittleToHostFloat(out->meshOffsets[m][2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
out->entitySpawnCount = data[offset];
|
||||||
|
offset += sizeof(uint8_t);
|
||||||
|
assertTrue(
|
||||||
|
out->entitySpawnCount <= CHUNK_ENTITY_SPAWN_COUNT_MAX,
|
||||||
|
"Chunk entity spawn count exceeds maximum."
|
||||||
|
);
|
||||||
|
|
||||||
|
for(uint8_t s = 0; s < out->entitySpawnCount; s++) {
|
||||||
|
chunkentityspawn_t *spawn = &out->entitySpawns[s];
|
||||||
|
spawn->kind = (chunkentityspawnkind_t)data[offset];
|
||||||
|
offset += sizeof(uint8_t);
|
||||||
|
|
||||||
|
uint16_t a;
|
||||||
|
memoryCopy(&a, data + offset, sizeof(uint16_t));
|
||||||
|
a = endianLittleToHost16(a);
|
||||||
|
offset += sizeof(uint16_t);
|
||||||
|
|
||||||
|
uint8_t b = data[offset];
|
||||||
|
offset += sizeof(uint8_t);
|
||||||
|
|
||||||
|
if(spawn->kind == CHUNK_ENTITY_SPAWN_KIND_ITEM) {
|
||||||
|
spawn->globalId = 0;
|
||||||
|
spawn->itemId = a;
|
||||||
|
spawn->itemQuantity = b;
|
||||||
|
} else {
|
||||||
|
spawn->globalId = a;
|
||||||
|
spawn->itemId = 0;
|
||||||
|
spawn->itemQuantity = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
spawn->position = assetChunkReadWorldPos(data, &offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
out->areaSpawnCount = data[offset];
|
||||||
|
offset += sizeof(uint8_t);
|
||||||
|
assertTrue(
|
||||||
|
out->areaSpawnCount <= CHUNK_AREA_COUNT_MAX,
|
||||||
|
"Chunk area spawn count exceeds maximum."
|
||||||
|
);
|
||||||
|
|
||||||
|
for(uint8_t s = 0; s < out->areaSpawnCount; s++) {
|
||||||
|
chunkareaspawn_t *area = &out->areaSpawns[s];
|
||||||
|
area->min = assetChunkReadWorldPos(data, &offset);
|
||||||
|
area->max = assetChunkReadWorldPos(data, &offset);
|
||||||
|
|
||||||
|
uint16_t callbackId;
|
||||||
|
memoryCopy(&callbackId, data + offset, sizeof(uint16_t));
|
||||||
|
area->callbackId = endianLittleToHost16(callbackId);
|
||||||
|
offset += sizeof(uint16_t);
|
||||||
|
|
||||||
|
area->notify = data[offset];
|
||||||
|
offset += sizeof(uint8_t);
|
||||||
|
area->trigger = data[offset];
|
||||||
|
offset += sizeof(uint8_t);
|
||||||
}
|
}
|
||||||
|
|
||||||
memoryFree(data);
|
memoryFree(data);
|
||||||
@@ -157,6 +242,12 @@ errorret_t assetChunkDispose(assetentry_t *entry) {
|
|||||||
assertIsMainThread("Must be called from the main thread.");
|
assertIsMainThread("Must be called from the main thread.");
|
||||||
|
|
||||||
assetchunkoutput_t *out = &entry->data.chunk;
|
assetchunkoutput_t *out = &entry->data.chunk;
|
||||||
|
|
||||||
|
if(out->tiles != NULL) {
|
||||||
|
memoryFree(out->tiles);
|
||||||
|
out->tiles = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
for(uint8_t m = 0; m < out->meshCount; m++) {
|
for(uint8_t m = 0; m < out->meshCount; m++) {
|
||||||
if(out->modelEntries[m] == NULL) continue;
|
if(out->modelEntries[m] == NULL) continue;
|
||||||
assetUnlockEntry(out->modelEntries[m]);
|
assetUnlockEntry(out->modelEntries[m]);
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
#include "asset/assetfile.h"
|
#include "asset/assetfile.h"
|
||||||
#include "rpg/overworld/chunk.h"
|
#include "rpg/overworld/chunk.h"
|
||||||
|
|
||||||
#define ASSET_CHUNK_FILE_VERSION 3
|
#define ASSET_CHUNK_FILE_VERSION 5
|
||||||
|
|
||||||
typedef struct assetloading_s assetloading_t;
|
typedef struct assetloading_s assetloading_t;
|
||||||
typedef struct assetentry_s assetentry_t;
|
typedef struct assetentry_s assetentry_t;
|
||||||
@@ -33,12 +33,39 @@ typedef struct {
|
|||||||
uint8_t modelIndex;
|
uint8_t modelIndex;
|
||||||
} assetchunkloaderloading_t;
|
} assetchunkloaderloading_t;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
CHUNK_ENTITY_SPAWN_KIND_GLOBAL,
|
||||||
|
CHUNK_ENTITY_SPAWN_KIND_ITEM
|
||||||
|
} chunkentityspawnkind_t;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
tile_t tiles[CHUNK_TILE_COUNT];
|
chunkentityspawnkind_t kind;
|
||||||
|
uint16_t globalId; // Valid when kind == CHUNK_ENTITY_SPAWN_KIND_GLOBAL.
|
||||||
|
uint16_t itemId; // Valid when kind == CHUNK_ENTITY_SPAWN_KIND_ITEM.
|
||||||
|
uint8_t itemQuantity; // Valid when kind == CHUNK_ENTITY_SPAWN_KIND_ITEM.
|
||||||
|
worldpos_t position;
|
||||||
|
} chunkentityspawn_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
worldpos_t min;
|
||||||
|
worldpos_t max;
|
||||||
|
uint16_t callbackId; // Index into MAP_AREA_CALLBACK_LIST.
|
||||||
|
uint8_t notify;
|
||||||
|
uint8_t trigger;
|
||||||
|
} chunkareaspawn_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
tile_t *tiles;
|
||||||
uint8_t meshCount;
|
uint8_t meshCount;
|
||||||
char_t modelNames[CHUNK_MESH_COUNT_MAX][CHUNK_MESH_NAME_MAX];
|
char_t modelNames[CHUNK_MESH_COUNT_MAX][CHUNK_MESH_NAME_MAX];
|
||||||
vec3 meshOffsets[CHUNK_MESH_COUNT_MAX];
|
vec3 meshOffsets[CHUNK_MESH_COUNT_MAX];
|
||||||
assetentry_t *modelEntries[CHUNK_MESH_COUNT_MAX];
|
assetentry_t *modelEntries[CHUNK_MESH_COUNT_MAX];
|
||||||
|
|
||||||
|
uint8_t entitySpawnCount;
|
||||||
|
chunkentityspawn_t entitySpawns[CHUNK_ENTITY_SPAWN_COUNT_MAX];
|
||||||
|
|
||||||
|
uint8_t areaSpawnCount;
|
||||||
|
chunkareaspawn_t areaSpawns[CHUNK_AREA_COUNT_MAX];
|
||||||
} assetchunkoutput_t;
|
} assetchunkoutput_t;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -48,8 +48,20 @@ errorret_t assetMeshLoaderAsync(assetloading_t *loading) {
|
|||||||
uint32_t vertCount = endianLittleToHost32(*(uint32_t *)(raw + 8));
|
uint32_t vertCount = endianLittleToHost32(*(uint32_t *)(raw + 8));
|
||||||
meshvertex_t *vertices = NULL;
|
meshvertex_t *vertices = NULL;
|
||||||
if(vertCount > 0) {
|
if(vertCount > 0) {
|
||||||
vertices = memoryAllocate(vertCount * sizeof(meshvertex_t));
|
// 32-byte (cache-line) aligned: GX_SetArray + DCFlushRange on Dolphin
|
||||||
|
// require this for the DMA'd vertex data to actually reach the GPU
|
||||||
|
// coherently. Static compiled-in vertex arrays happen to get this from
|
||||||
|
// the linker; a plain memoryAllocate here would not.
|
||||||
|
vertices = memoryAlign(32, vertCount * sizeof(meshvertex_t));
|
||||||
memoryCopy(vertices, raw + 12, vertCount * sizeof(meshvertex_t));
|
memoryCopy(vertices, raw + 12, vertCount * sizeof(meshvertex_t));
|
||||||
|
|
||||||
|
for(uint32_t v = 0; v < vertCount; v++) {
|
||||||
|
vertices[v].uv[0] = endianLittleToHostFloat(vertices[v].uv[0]);
|
||||||
|
vertices[v].uv[1] = endianLittleToHostFloat(vertices[v].uv[1]);
|
||||||
|
vertices[v].pos[0] = endianLittleToHostFloat(vertices[v].pos[0]);
|
||||||
|
vertices[v].pos[1] = endianLittleToHostFloat(vertices[v].pos[1]);
|
||||||
|
vertices[v].pos[2] = endianLittleToHostFloat(vertices[v].pos[2]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
memoryFree(raw);
|
memoryFree(raw);
|
||||||
|
|
||||||
@@ -103,18 +115,22 @@ errorret_t assetMeshLoaderSync(assetloading_t *loading) {
|
|||||||
out->vertices = NULL;
|
out->vertices = NULL;
|
||||||
errorChain(ret);
|
errorChain(ret);
|
||||||
}
|
}
|
||||||
|
out->meshInitialized = true;
|
||||||
|
|
||||||
ret = meshFlush(&out->mesh, 0, (int32_t)vertCount);
|
ret = meshFlush(&out->mesh, 0, (int32_t)vertCount);
|
||||||
if(errorIsNotOk(ret)) {
|
if(errorIsNotOk(ret)) {
|
||||||
loading->entry->state = ASSET_ENTRY_STATE_ERROR;
|
loading->entry->state = ASSET_ENTRY_STATE_ERROR;
|
||||||
meshDispose(&out->mesh);
|
meshDispose(&out->mesh);
|
||||||
|
out->meshInitialized = false;
|
||||||
memoryFree(out->vertices);
|
memoryFree(out->vertices);
|
||||||
out->vertices = NULL;
|
out->vertices = NULL;
|
||||||
errorChain(ret);
|
errorChain(ret);
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifndef DUSK_OPENGL_LEGACY
|
#if defined(DUSK_OPENGL) && !defined(DUSK_OPENGL_LEGACY)
|
||||||
// VBO owns the data now; CPU copy is no longer needed.
|
// VBO owns the data now; CPU copy is no longer needed. The platform
|
||||||
|
// mesh object itself still needs meshDispose later - tracked via
|
||||||
|
// out->meshInitialized, independent of the CPU buffer's lifetime.
|
||||||
memoryFree(out->vertices);
|
memoryFree(out->vertices);
|
||||||
out->vertices = NULL;
|
out->vertices = NULL;
|
||||||
#endif
|
#endif
|
||||||
@@ -129,8 +145,11 @@ errorret_t assetMeshDispose(assetentry_t *entry) {
|
|||||||
assertIsMainThread("Must be called from the main thread.");
|
assertIsMainThread("Must be called from the main thread.");
|
||||||
|
|
||||||
assetmeshoutput_t *out = &entry->data.mesh;
|
assetmeshoutput_t *out = &entry->data.mesh;
|
||||||
if(out->vertices != NULL) {
|
if(out->meshInitialized) {
|
||||||
errorChain(meshDispose(&out->mesh));
|
errorChain(meshDispose(&out->mesh));
|
||||||
|
out->meshInitialized = false;
|
||||||
|
}
|
||||||
|
if(out->vertices != NULL) {
|
||||||
memoryFree(out->vertices);
|
memoryFree(out->vertices);
|
||||||
out->vertices = NULL;
|
out->vertices = NULL;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ typedef struct {
|
|||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
mesh_t mesh;
|
mesh_t mesh;
|
||||||
|
bool_t meshInitialized;
|
||||||
meshvertex_t *vertices;
|
meshvertex_t *vertices;
|
||||||
} assetmeshoutput_t;
|
} assetmeshoutput_t;
|
||||||
|
|
||||||
|
|||||||
@@ -513,9 +513,10 @@ errorret_t assetLocaleGetString(
|
|||||||
// 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));
|
||||||
|
|
||||||
while(!reader.eof) {
|
while(true) {
|
||||||
// Is this msgid?
|
// Is this msgid?
|
||||||
if(memoryCompare(lineBuffer, "msgid", 5) != 0) {
|
if(memoryCompare(lineBuffer, "msgid", 5) != 0) {
|
||||||
|
if(reader.eof) break;
|
||||||
errorChain(assetFileLineReaderNext(&reader));
|
errorChain(assetFileLineReaderNext(&reader));
|
||||||
msgidBuffer[0] = '\0';
|
msgidBuffer[0] = '\0';
|
||||||
continue;
|
continue;
|
||||||
@@ -539,7 +540,7 @@ errorret_t assetLocaleGetString(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// We are either going to see a msgstr or a msgid_plural
|
// We are either going to see a msgstr or a msgid_plural
|
||||||
while(!reader.eof) {
|
while(true) {
|
||||||
errorChain(assetLocaleLineSkipBlanks(&reader, lineBuffer));
|
errorChain(assetLocaleLineSkipBlanks(&reader, lineBuffer));
|
||||||
|
|
||||||
// Is msgid_plural?
|
// Is msgid_plural?
|
||||||
|
|||||||
@@ -17,11 +17,8 @@ console_t CONSOLE;
|
|||||||
|
|
||||||
void consoleInit(void) {
|
void consoleInit(void) {
|
||||||
memoryZero(&CONSOLE, sizeof(console_t));
|
memoryZero(&CONSOLE, sizeof(console_t));
|
||||||
CONSOLE.visible = true;
|
CONSOLE.visible = false;
|
||||||
|
threadMutexInit(&CONSOLE.printMutex);
|
||||||
#ifdef DUSK_CONSOLE_POSIX
|
|
||||||
threadMutexInit(&CONSOLE.printMutex);
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void consolePrint(const char_t *message, ...) {
|
void consolePrint(const char_t *message, ...) {
|
||||||
@@ -32,20 +29,14 @@ void consolePrint(const char_t *message, ...) {
|
|||||||
int32_t len = stringFormatVA(buffer, CONSOLE_LINE_MAX, message, args);
|
int32_t len = stringFormatVA(buffer, CONSOLE_LINE_MAX, message, args);
|
||||||
va_end(args);
|
va_end(args);
|
||||||
|
|
||||||
#ifdef DUSK_CONSOLE_POSIX
|
threadMutexLock(&CONSOLE.printMutex);
|
||||||
threadMutexLock(&CONSOLE.printMutex);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
memoryMove(
|
memoryMove(
|
||||||
CONSOLE.line[0],
|
CONSOLE.line[0],
|
||||||
CONSOLE.line[1],
|
CONSOLE.line[1],
|
||||||
(CONSOLE_HISTORY_MAX - 1) * CONSOLE_LINE_MAX
|
(CONSOLE_HISTORY_MAX - 1) * CONSOLE_LINE_MAX
|
||||||
);
|
);
|
||||||
memoryCopy(CONSOLE.line[CONSOLE_HISTORY_MAX - 1], buffer, len + 1);
|
memoryCopy(CONSOLE.line[CONSOLE_HISTORY_MAX - 1], buffer, len + 1);
|
||||||
|
threadMutexUnlock(&CONSOLE.printMutex);
|
||||||
#ifdef DUSK_CONSOLE_POSIX
|
|
||||||
threadMutexUnlock(&CONSOLE.printMutex);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
logDebug("%s\n", buffer);
|
logDebug("%s\n", buffer);
|
||||||
}
|
}
|
||||||
@@ -61,7 +52,5 @@ void consoleUpdate(void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void consoleDispose(void) {
|
void consoleDispose(void) {
|
||||||
#ifdef DUSK_CONSOLE_POSIX
|
threadMutexDispose(&CONSOLE.printMutex);
|
||||||
threadMutexDispose(&CONSOLE.printMutex);
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
@@ -6,24 +6,18 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "consoledefs.h"
|
|
||||||
#include "error/error.h"
|
#include "error/error.h"
|
||||||
#include "dusk.h"
|
#include "dusk.h"
|
||||||
|
#include "thread/thread.h"
|
||||||
|
|
||||||
#ifdef DUSK_CONSOLE_POSIX
|
#define CONSOLE_LINE_MAX 512
|
||||||
#include "thread/thread.h"
|
#define CONSOLE_HISTORY_MAX 16
|
||||||
#include <poll.h>
|
#define CONSOLE_EXEC_BUFFER_MAX 32
|
||||||
#include <unistd.h>
|
|
||||||
#define CONSOLE_POSIX_POLL_RATE 75
|
|
||||||
#endif
|
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
char_t line[CONSOLE_HISTORY_MAX][CONSOLE_LINE_MAX];
|
char_t line[CONSOLE_HISTORY_MAX][CONSOLE_LINE_MAX];
|
||||||
bool_t visible;
|
bool_t visible;
|
||||||
|
threadmutex_t printMutex;
|
||||||
#ifdef DUSK_CONSOLE_POSIX
|
|
||||||
threadmutex_t printMutex;
|
|
||||||
#endif
|
|
||||||
} console_t;
|
} console_t;
|
||||||
|
|
||||||
extern console_t CONSOLE;
|
extern console_t CONSOLE;
|
||||||
|
|||||||
@@ -33,13 +33,17 @@ errorret_t displayInit(void) {
|
|||||||
#ifdef displayPlatformInit
|
#ifdef displayPlatformInit
|
||||||
errorChain(displayPlatformInit());
|
errorChain(displayPlatformInit());
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// Set initial state
|
||||||
errorChain(displaySetState((displaystate_t){ .flags = 0 }));
|
errorChain(displaySetState((displaystate_t){ .flags = 0 }));
|
||||||
|
|
||||||
|
// Init the fixed textures
|
||||||
errorChain(textureInit(
|
errorChain(textureInit(
|
||||||
&TEXTURE_WHITE, 4, 4,
|
&TEXTURE_WHITE, TEXTURE_FIXED_WIDTH, TEXTURE_FIXED_HEIGHT,
|
||||||
TEXTURE_FORMAT_RGBA, (texturedata_t){ .rgbaColors = TEXTURE_WHITE_PIXELS }
|
TEXTURE_FORMAT_RGBA, (texturedata_t){ .rgbaColors = TEXTURE_WHITE_PIXELS }
|
||||||
));
|
));
|
||||||
errorChain(textureInit(
|
errorChain(textureInit(
|
||||||
&TEXTURE_TEST, 4, 4,
|
&TEXTURE_TEST, TEXTURE_FIXED_WIDTH, TEXTURE_FIXED_HEIGHT,
|
||||||
TEXTURE_FORMAT_RGBA, (texturedata_t){ .rgbaColors = TEXTURE_TEST_PIXELS }
|
TEXTURE_FORMAT_RGBA, (texturedata_t){ .rgbaColors = TEXTURE_TEST_PIXELS }
|
||||||
));
|
));
|
||||||
|
|
||||||
@@ -50,14 +54,14 @@ errorret_t displayInit(void) {
|
|||||||
errorChain(planeInit());
|
errorChain(planeInit());
|
||||||
errorChain(capsuleInit());
|
errorChain(capsuleInit());
|
||||||
errorChain(triPrismInit());
|
errorChain(triPrismInit());
|
||||||
|
|
||||||
|
// Init the subsystems
|
||||||
errorChain(frameBufferInitBackBuffer());
|
errorChain(frameBufferInitBackBuffer());
|
||||||
errorChain(spriteBatchInit());
|
errorChain(spriteBatchInit());
|
||||||
errorChain(textInit());
|
errorChain(textInit());
|
||||||
errorChain(screenInit());
|
errorChain(screenInit());
|
||||||
|
|
||||||
// Setup initial shader with default values
|
// Setup initial shader with default values
|
||||||
|
|
||||||
errorChain(shaderListInit());
|
errorChain(shaderListInit());
|
||||||
|
|
||||||
errorOk();
|
errorOk();
|
||||||
|
|||||||
@@ -7,4 +7,5 @@
|
|||||||
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||||
PUBLIC
|
PUBLIC
|
||||||
text.c
|
text.c
|
||||||
|
font.c
|
||||||
)
|
)
|
||||||
@@ -0,0 +1,168 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "font.h"
|
||||||
|
#include "util/memory.h"
|
||||||
|
#include "util/math.h"
|
||||||
|
#include "display/color.h"
|
||||||
|
|
||||||
|
font_t FONT_DEFAULT;
|
||||||
|
static texture_t FONT_DEFAULT_TEXTURE;
|
||||||
|
static tileset_t FONT_DEFAULT_TILESET;
|
||||||
|
|
||||||
|
const uint8_t FONT_DEFAULT_GLYPHS[FONT_DEFAULT_TILE_COUNT][
|
||||||
|
FONT_DEFAULT_TILE_HEIGHT
|
||||||
|
] = {
|
||||||
|
{ 0x00, 0x10, 0x10, 0x10, 0x10, 0x10, 0x00, 0x10, 0x00, 0x00 }, // !
|
||||||
|
{ 0x00, 0x14, 0x14, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // "
|
||||||
|
{ 0x00, 0x14, 0x14, 0x3E, 0x14, 0x3E, 0x14, 0x14, 0x00, 0x00 }, // #
|
||||||
|
{ 0x00, 0x08, 0x1E, 0x28, 0x1C, 0x0A, 0x3C, 0x08, 0x00, 0x00 }, // $
|
||||||
|
{ 0x00, 0x00, 0x22, 0x24, 0x08, 0x12, 0x22, 0x00, 0x00, 0x00 }, // %
|
||||||
|
{ 0x00, 0x08, 0x14, 0x14, 0x1A, 0x24, 0x24, 0x1A, 0x00, 0x00 }, // &
|
||||||
|
{ 0x00, 0x20, 0x20, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // '
|
||||||
|
{ 0x00, 0x04, 0x08, 0x08, 0x08, 0x08, 0x08, 0x04, 0x00, 0x00 }, // (
|
||||||
|
{ 0x00, 0x10, 0x08, 0x08, 0x08, 0x08, 0x08, 0x10, 0x00, 0x00 }, // )
|
||||||
|
{ 0x00, 0x00, 0x08, 0x2A, 0x1C, 0x2A, 0x08, 0x00, 0x00, 0x00 }, // *
|
||||||
|
{ 0x00, 0x00, 0x08, 0x08, 0x3E, 0x08, 0x08, 0x00, 0x00, 0x00 }, // +
|
||||||
|
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x10, 0x20, 0x00 }, // ,
|
||||||
|
{ 0x00, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x00, 0x00 }, // -
|
||||||
|
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x10, 0x00, 0x00 }, // .
|
||||||
|
{ 0x00, 0x04, 0x04, 0x08, 0x08, 0x08, 0x10, 0x10, 0x00, 0x00 }, // /
|
||||||
|
{ 0x00, 0x1C, 0x22, 0x26, 0x2A, 0x32, 0x22, 0x1C, 0x00, 0x00 }, // 0
|
||||||
|
{ 0x00, 0x08, 0x18, 0x08, 0x08, 0x08, 0x08, 0x3E, 0x00, 0x00 }, // 1
|
||||||
|
{ 0x00, 0x1C, 0x22, 0x02, 0x04, 0x08, 0x10, 0x3E, 0x00, 0x00 }, // 2
|
||||||
|
{ 0x00, 0x1C, 0x22, 0x02, 0x0C, 0x02, 0x22, 0x1C, 0x00, 0x00 }, // 3
|
||||||
|
{ 0x00, 0x22, 0x22, 0x22, 0x3E, 0x02, 0x02, 0x02, 0x00, 0x00 }, // 4
|
||||||
|
{ 0x00, 0x3E, 0x20, 0x20, 0x3C, 0x02, 0x22, 0x1C, 0x00, 0x00 }, // 5
|
||||||
|
{ 0x00, 0x1C, 0x20, 0x20, 0x3C, 0x22, 0x22, 0x1C, 0x00, 0x00 }, // 6
|
||||||
|
{ 0x00, 0x3E, 0x02, 0x02, 0x04, 0x08, 0x08, 0x08, 0x00, 0x00 }, // 7
|
||||||
|
{ 0x00, 0x1C, 0x22, 0x22, 0x1C, 0x22, 0x22, 0x1C, 0x00, 0x00 }, // 8
|
||||||
|
{ 0x00, 0x1C, 0x22, 0x22, 0x1E, 0x02, 0x22, 0x1C, 0x00, 0x00 }, // 9
|
||||||
|
{ 0x00, 0x00, 0x10, 0x10, 0x00, 0x10, 0x10, 0x00, 0x00, 0x00 }, // :
|
||||||
|
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // ;
|
||||||
|
{ 0x00, 0x04, 0x08, 0x10, 0x20, 0x10, 0x08, 0x04, 0x00, 0x00 }, // <
|
||||||
|
{ 0x00, 0x00, 0x00, 0x3E, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x00 }, // =
|
||||||
|
{ 0x00, 0x10, 0x08, 0x04, 0x02, 0x04, 0x08, 0x10, 0x00, 0x00 }, // >
|
||||||
|
{ 0x00, 0x1C, 0x22, 0x02, 0x04, 0x08, 0x00, 0x08, 0x00, 0x00 }, // ?
|
||||||
|
{ 0x00, 0x1C, 0x26, 0x2A, 0x2A, 0x26, 0x20, 0x1C, 0x00, 0x00 }, // @
|
||||||
|
{ 0x00, 0x1C, 0x22, 0x22, 0x22, 0x3E, 0x22, 0x22, 0x00, 0x00 }, // A
|
||||||
|
{ 0x00, 0x3C, 0x22, 0x22, 0x3C, 0x22, 0x22, 0x3C, 0x00, 0x00 }, // B
|
||||||
|
{ 0x00, 0x1C, 0x22, 0x20, 0x20, 0x20, 0x22, 0x1C, 0x00, 0x00 }, // C
|
||||||
|
{ 0x00, 0x3C, 0x22, 0x22, 0x22, 0x22, 0x22, 0x3C, 0x00, 0x00 }, // D
|
||||||
|
{ 0x00, 0x3E, 0x20, 0x20, 0x3C, 0x20, 0x20, 0x3E, 0x00, 0x00 }, // E
|
||||||
|
{ 0x00, 0x3E, 0x20, 0x20, 0x3C, 0x20, 0x20, 0x20, 0x00, 0x00 }, // F
|
||||||
|
{ 0x00, 0x1C, 0x22, 0x20, 0x2E, 0x22, 0x22, 0x1C, 0x00, 0x00 }, // G
|
||||||
|
{ 0x00, 0x22, 0x22, 0x22, 0x3E, 0x22, 0x22, 0x22, 0x00, 0x00 }, // H
|
||||||
|
{ 0x00, 0x3E, 0x08, 0x08, 0x08, 0x08, 0x08, 0x3E, 0x00, 0x00 }, // I
|
||||||
|
{ 0x00, 0x02, 0x02, 0x02, 0x02, 0x22, 0x22, 0x1C, 0x00, 0x00 }, // J
|
||||||
|
{ 0x00, 0x22, 0x24, 0x28, 0x30, 0x28, 0x24, 0x22, 0x00, 0x00 }, // K
|
||||||
|
{ 0x00, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3E, 0x00, 0x00 }, // L
|
||||||
|
{ 0x00, 0x22, 0x36, 0x2A, 0x22, 0x22, 0x22, 0x22, 0x00, 0x00 }, // M
|
||||||
|
{ 0x00, 0x22, 0x22, 0x32, 0x2A, 0x26, 0x22, 0x22, 0x00, 0x00 }, // N
|
||||||
|
{ 0x00, 0x1C, 0x22, 0x22, 0x22, 0x22, 0x22, 0x1C, 0x00, 0x00 }, // O
|
||||||
|
{ 0x00, 0x3C, 0x22, 0x22, 0x3C, 0x20, 0x20, 0x20, 0x00, 0x00 }, // P
|
||||||
|
{ 0x00, 0x1C, 0x22, 0x22, 0x22, 0x22, 0x22, 0x1C, 0x06, 0x00 }, // Q
|
||||||
|
{ 0x00, 0x3C, 0x22, 0x22, 0x3C, 0x22, 0x22, 0x22, 0x00, 0x00 }, // R
|
||||||
|
{ 0x00, 0x1C, 0x22, 0x20, 0x1C, 0x02, 0x22, 0x1C, 0x00, 0x00 }, // S
|
||||||
|
{ 0x00, 0x3E, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x00, 0x00 }, // T
|
||||||
|
{ 0x00, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x1C, 0x00, 0x00 }, // U
|
||||||
|
{ 0x00, 0x22, 0x22, 0x22, 0x22, 0x14, 0x14, 0x08, 0x00, 0x00 }, // V
|
||||||
|
{ 0x00, 0x22, 0x22, 0x22, 0x22, 0x2A, 0x36, 0x22, 0x00, 0x00 }, // W
|
||||||
|
{ 0x00, 0x22, 0x22, 0x14, 0x08, 0x14, 0x22, 0x22, 0x00, 0x00 }, // X
|
||||||
|
{ 0x00, 0x22, 0x22, 0x14, 0x08, 0x08, 0x08, 0x08, 0x00, 0x00 }, // Y
|
||||||
|
{ 0x00, 0x3E, 0x02, 0x04, 0x08, 0x10, 0x20, 0x3E, 0x00, 0x00 }, // Z
|
||||||
|
{ 0x00, 0x0C, 0x08, 0x08, 0x08, 0x08, 0x08, 0x0C, 0x00, 0x00 }, // [
|
||||||
|
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // backslash (not drawn in source font)
|
||||||
|
{ 0x00, 0x18, 0x08, 0x08, 0x08, 0x08, 0x08, 0x18, 0x00, 0x00 }, // ]
|
||||||
|
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // ^ (not drawn in source font)
|
||||||
|
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // _ (not drawn in source font)
|
||||||
|
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // ` (not drawn in source font)
|
||||||
|
{ 0x00, 0x00, 0x00, 0x1E, 0x22, 0x22, 0x22, 0x1E, 0x00, 0x00 }, // a
|
||||||
|
{ 0x00, 0x20, 0x20, 0x3C, 0x22, 0x22, 0x22, 0x3C, 0x00, 0x00 }, // b
|
||||||
|
{ 0x00, 0x00, 0x00, 0x1C, 0x22, 0x20, 0x22, 0x1C, 0x00, 0x00 }, // c
|
||||||
|
{ 0x00, 0x02, 0x02, 0x1E, 0x22, 0x22, 0x22, 0x1E, 0x00, 0x00 }, // d
|
||||||
|
{ 0x00, 0x00, 0x00, 0x1C, 0x22, 0x3E, 0x20, 0x1C, 0x00, 0x00 }, // e
|
||||||
|
{ 0x00, 0x0C, 0x12, 0x10, 0x3C, 0x10, 0x10, 0x10, 0x00, 0x00 }, // f
|
||||||
|
{ 0x00, 0x00, 0x00, 0x1E, 0x22, 0x22, 0x22, 0x1E, 0x02, 0x1C }, // g
|
||||||
|
{ 0x00, 0x20, 0x20, 0x3C, 0x22, 0x22, 0x22, 0x22, 0x00, 0x00 }, // h
|
||||||
|
{ 0x00, 0x08, 0x00, 0x18, 0x08, 0x08, 0x08, 0x3E, 0x00, 0x00 }, // i
|
||||||
|
{ 0x00, 0x02, 0x00, 0x06, 0x02, 0x02, 0x02, 0x02, 0x22, 0x1C }, // j
|
||||||
|
{ 0x00, 0x20, 0x20, 0x22, 0x24, 0x38, 0x24, 0x22, 0x00, 0x00 }, // k
|
||||||
|
{ 0x00, 0x30, 0x10, 0x10, 0x10, 0x10, 0x10, 0x0E, 0x00, 0x00 }, // l
|
||||||
|
{ 0x00, 0x00, 0x00, 0x3C, 0x2A, 0x2A, 0x2A, 0x2A, 0x00, 0x00 }, // m
|
||||||
|
{ 0x00, 0x00, 0x00, 0x3C, 0x22, 0x22, 0x22, 0x22, 0x00, 0x00 }, // n
|
||||||
|
{ 0x00, 0x00, 0x00, 0x1C, 0x22, 0x22, 0x22, 0x1C, 0x00, 0x00 }, // o
|
||||||
|
{ 0x00, 0x00, 0x00, 0x3C, 0x22, 0x22, 0x22, 0x3C, 0x20, 0x20 }, // p
|
||||||
|
{ 0x00, 0x00, 0x00, 0x1E, 0x22, 0x22, 0x22, 0x1E, 0x02, 0x02 }, // q
|
||||||
|
{ 0x00, 0x00, 0x00, 0x2C, 0x32, 0x20, 0x20, 0x20, 0x00, 0x00 }, // r
|
||||||
|
{ 0x00, 0x00, 0x00, 0x1E, 0x20, 0x1C, 0x02, 0x3C, 0x00, 0x00 }, // s
|
||||||
|
{ 0x00, 0x10, 0x10, 0x3C, 0x10, 0x10, 0x10, 0x0E, 0x00, 0x00 }, // t
|
||||||
|
{ 0x00, 0x00, 0x00, 0x22, 0x22, 0x22, 0x22, 0x1E, 0x00, 0x00 }, // u
|
||||||
|
{ 0x00, 0x00, 0x00, 0x22, 0x22, 0x22, 0x14, 0x08, 0x00, 0x00 }, // v
|
||||||
|
{ 0x00, 0x00, 0x00, 0x22, 0x22, 0x2A, 0x2A, 0x14, 0x00, 0x00 }, // w
|
||||||
|
{ 0x00, 0x00, 0x00, 0x22, 0x14, 0x08, 0x14, 0x22, 0x00, 0x00 }, // x
|
||||||
|
{ 0x00, 0x00, 0x00, 0x22, 0x22, 0x22, 0x22, 0x1E, 0x02, 0x1C }, // y
|
||||||
|
{ 0x00, 0x00, 0x00, 0x3E, 0x04, 0x08, 0x10, 0x3E, 0x00, 0x00 }, // z
|
||||||
|
{ 0x00, 0x04, 0x08, 0x08, 0x10, 0x08, 0x08, 0x04, 0x00, 0x00 }, // {
|
||||||
|
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // | (not drawn in source font)
|
||||||
|
{ 0x00, 0x10, 0x08, 0x08, 0x04, 0x08, 0x08, 0x10, 0x00, 0x00 }, // }
|
||||||
|
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // ~ (not drawn in source font)
|
||||||
|
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // unused trailing tile
|
||||||
|
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // unused trailing tile
|
||||||
|
};
|
||||||
|
|
||||||
|
errorret_t fontDefaultInit(void) {
|
||||||
|
const int32_t width = (int32_t)mathNextPowTwo(
|
||||||
|
FONT_DEFAULT_COLUMNS * FONT_DEFAULT_TILE_WIDTH
|
||||||
|
);
|
||||||
|
const int32_t height = (int32_t)mathNextPowTwo(
|
||||||
|
FONT_DEFAULT_ROWS * FONT_DEFAULT_TILE_HEIGHT
|
||||||
|
);
|
||||||
|
|
||||||
|
color_t *pixels = memoryAllocate(sizeof(color_t) * width * height);
|
||||||
|
memoryZero(pixels, sizeof(color_t) * width * height);
|
||||||
|
|
||||||
|
for(uint16_t i = 0; i < FONT_DEFAULT_TILE_COUNT; i++) {
|
||||||
|
const uint16_t tileX = (i % FONT_DEFAULT_COLUMNS) * FONT_DEFAULT_TILE_WIDTH;
|
||||||
|
const uint16_t tileY = (i / FONT_DEFAULT_COLUMNS) * FONT_DEFAULT_TILE_HEIGHT;
|
||||||
|
|
||||||
|
for(uint8_t row = 0; row < FONT_DEFAULT_TILE_HEIGHT; row++) {
|
||||||
|
const uint8_t bits = FONT_DEFAULT_GLYPHS[i][row];
|
||||||
|
|
||||||
|
for(uint8_t col = 0; col < FONT_DEFAULT_TILE_WIDTH; col++) {
|
||||||
|
if(!((bits >> (FONT_DEFAULT_TILE_WIDTH - 1 - col)) & 1)) continue;
|
||||||
|
pixels[((tileY + row) * width) + (tileX + col)] = COLOR_WHITE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FONT_DEFAULT_TILESET.tileWidth = FONT_DEFAULT_TILE_WIDTH;
|
||||||
|
FONT_DEFAULT_TILESET.tileHeight = FONT_DEFAULT_TILE_HEIGHT;
|
||||||
|
FONT_DEFAULT_TILESET.columns = FONT_DEFAULT_COLUMNS;
|
||||||
|
FONT_DEFAULT_TILESET.rows = FONT_DEFAULT_ROWS;
|
||||||
|
FONT_DEFAULT_TILESET.tileCount = FONT_DEFAULT_TILE_COUNT;
|
||||||
|
FONT_DEFAULT_TILESET.uv[0] = (float_t)FONT_DEFAULT_TILE_WIDTH / (float_t)width;
|
||||||
|
FONT_DEFAULT_TILESET.uv[1] = (float_t)FONT_DEFAULT_TILE_HEIGHT / (float_t)height;
|
||||||
|
|
||||||
|
const texturedata_t data = { .rgbaColors = pixels };
|
||||||
|
errorret_t textureResult = textureInit(
|
||||||
|
&FONT_DEFAULT_TEXTURE, width, height, TEXTURE_FORMAT_RGBA, data
|
||||||
|
);
|
||||||
|
memoryFree(pixels);
|
||||||
|
errorChain(textureResult);
|
||||||
|
|
||||||
|
FONT_DEFAULT.texture = &FONT_DEFAULT_TEXTURE;
|
||||||
|
FONT_DEFAULT.tileset = &FONT_DEFAULT_TILESET;
|
||||||
|
errorOk();
|
||||||
|
}
|
||||||
|
|
||||||
|
errorret_t fontDefaultDispose(void) {
|
||||||
|
errorChain(textureDispose(&FONT_DEFAULT_TEXTURE));
|
||||||
|
FONT_DEFAULT.texture = NULL;
|
||||||
|
FONT_DEFAULT.tileset = NULL;
|
||||||
|
errorOk();
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
#include "error/error.h"
|
||||||
#include "display/texture/texture.h"
|
#include "display/texture/texture.h"
|
||||||
#include "display/texture/tileset.h"
|
#include "display/texture/tileset.h"
|
||||||
|
|
||||||
@@ -13,3 +14,51 @@ typedef struct {
|
|||||||
texture_t *texture;
|
texture_t *texture;
|
||||||
tileset_t *tileset;
|
tileset_t *tileset;
|
||||||
} font_t;
|
} font_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pixel width/height of a single default-font glyph tile.
|
||||||
|
*/
|
||||||
|
#define FONT_DEFAULT_TILE_WIDTH 6
|
||||||
|
#define FONT_DEFAULT_TILE_HEIGHT 10
|
||||||
|
|
||||||
|
/** Grid layout of the generated default-font texture, in tiles. */
|
||||||
|
#define FONT_DEFAULT_COLUMNS 16
|
||||||
|
#define FONT_DEFAULT_ROWS 6
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Number of glyphs defined in FONT_DEFAULT_GLYPHS (FONT_DEFAULT_COLUMNS *
|
||||||
|
* FONT_DEFAULT_ROWS), covering the printable ASCII range starting at
|
||||||
|
* TEXT_CHAR_START ('!') plus a couple of unused trailing tiles.
|
||||||
|
*/
|
||||||
|
#define FONT_DEFAULT_TILE_COUNT (FONT_DEFAULT_COLUMNS * FONT_DEFAULT_ROWS)
|
||||||
|
|
||||||
|
extern font_t FONT_DEFAULT;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hard coded bitmap data for the built-in default font. Indexed
|
||||||
|
* [glyph][row], where glyph 0 corresponds to TEXT_CHAR_START ('!') and
|
||||||
|
* glyphs run consecutively through the printable ASCII range. Each row
|
||||||
|
* byte holds FONT_DEFAULT_TILE_WIDTH bit flags, one per pixel column:
|
||||||
|
* bit (FONT_DEFAULT_TILE_WIDTH - 1) is the leftmost pixel and bit 0 is
|
||||||
|
* the rightmost; 1 means the pixel is set, 0 means it is not.
|
||||||
|
*/
|
||||||
|
extern const uint8_t FONT_DEFAULT_GLYPHS[FONT_DEFAULT_TILE_COUNT][
|
||||||
|
FONT_DEFAULT_TILE_HEIGHT
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds the default font's texture + tileset directly from
|
||||||
|
* FONT_DEFAULT_GLYPHS, without going through the asset system - so the
|
||||||
|
* engine always has a usable font to render with regardless of whether
|
||||||
|
* asset loading (e.g. the packed .dsk archive) succeeds.
|
||||||
|
*
|
||||||
|
* @return Either an error or success result.
|
||||||
|
*/
|
||||||
|
errorret_t fontDefaultInit(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disposes of the default font created by fontDefaultInit().
|
||||||
|
*
|
||||||
|
* @return Either an error or success result.
|
||||||
|
*/
|
||||||
|
errorret_t fontDefaultDispose(void);
|
||||||
|
|||||||
@@ -9,34 +9,15 @@
|
|||||||
#include "assert/assert.h"
|
#include "assert/assert.h"
|
||||||
#include "util/memory.h"
|
#include "util/memory.h"
|
||||||
#include "display/spritebatch/spritebatch.h"
|
#include "display/spritebatch/spritebatch.h"
|
||||||
#include "asset/asset.h"
|
|
||||||
#include "asset/loader/display/assettextureloader.h"
|
|
||||||
#include "asset/loader/display/assettilesetloader.h"
|
|
||||||
#include "display/shader/shaderunlit.h"
|
#include "display/shader/shaderunlit.h"
|
||||||
|
|
||||||
font_t FONT_DEFAULT;
|
|
||||||
|
|
||||||
errorret_t textInit(void) {
|
errorret_t textInit(void) {
|
||||||
assetloaderinput_t input = { .texture = TEXTURE_FORMAT_RGBA };
|
errorChain(fontDefaultInit());
|
||||||
assetentry_t *entryTexture = assetLock(
|
|
||||||
"ui/minogram.png", ASSET_LOADER_TYPE_TEXTURE, &input
|
|
||||||
);
|
|
||||||
assetentry_t *entryTileset = assetLock(
|
|
||||||
"ui/minogram.dtf", ASSET_LOADER_TYPE_TILESET, NULL
|
|
||||||
);
|
|
||||||
errorChain(assetRequireLoaded(entryTexture));
|
|
||||||
errorChain(assetRequireLoaded(entryTileset));
|
|
||||||
|
|
||||||
FONT_DEFAULT.texture = &entryTexture->data.texture;
|
|
||||||
FONT_DEFAULT.tileset = &entryTileset->data.tileset;
|
|
||||||
errorOk();
|
errorOk();
|
||||||
}
|
}
|
||||||
|
|
||||||
errorret_t textDispose(void) {
|
errorret_t textDispose(void) {
|
||||||
FONT_DEFAULT.texture = NULL;
|
errorChain(fontDefaultDispose());
|
||||||
FONT_DEFAULT.tileset = NULL;
|
|
||||||
assetUnlock("ui/minogram.png");
|
|
||||||
assetUnlock("ui/minogram.dtf");
|
|
||||||
errorOk();
|
errorOk();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,8 +12,6 @@
|
|||||||
|
|
||||||
#define TEXT_CHAR_START '!'
|
#define TEXT_CHAR_START '!'
|
||||||
|
|
||||||
extern font_t FONT_DEFAULT;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the text system.
|
* Initializes the text system.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
#include "display/display.h"
|
#include "display/display.h"
|
||||||
|
|
||||||
texture_t TEXTURE_WHITE;
|
texture_t TEXTURE_WHITE;
|
||||||
color_t TEXTURE_WHITE_PIXELS[4*4] = {
|
color_t TEXTURE_WHITE_PIXELS[TEXTURE_FIXED_WIDTH * TEXTURE_FIXED_HEIGHT] = {
|
||||||
COLOR_WHITE, COLOR_WHITE, COLOR_WHITE, COLOR_WHITE,
|
COLOR_WHITE, COLOR_WHITE, COLOR_WHITE, COLOR_WHITE,
|
||||||
COLOR_WHITE, COLOR_WHITE, COLOR_WHITE, COLOR_WHITE,
|
COLOR_WHITE, COLOR_WHITE, COLOR_WHITE, COLOR_WHITE,
|
||||||
COLOR_WHITE, COLOR_WHITE, COLOR_WHITE, COLOR_WHITE,
|
COLOR_WHITE, COLOR_WHITE, COLOR_WHITE, COLOR_WHITE,
|
||||||
@@ -20,7 +20,7 @@ color_t TEXTURE_WHITE_PIXELS[4*4] = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
texture_t TEXTURE_TEST;
|
texture_t TEXTURE_TEST;
|
||||||
color_t TEXTURE_TEST_PIXELS[4*4] = {
|
color_t TEXTURE_TEST_PIXELS[TEXTURE_FIXED_WIDTH * TEXTURE_FIXED_HEIGHT] = {
|
||||||
COLOR_BLACK, COLOR_MAGENTA, COLOR_BLACK, COLOR_MAGENTA,
|
COLOR_BLACK, COLOR_MAGENTA, COLOR_BLACK, COLOR_MAGENTA,
|
||||||
COLOR_MAGENTA, COLOR_BLACK, COLOR_MAGENTA, COLOR_BLACK,
|
COLOR_MAGENTA, COLOR_BLACK, COLOR_MAGENTA, COLOR_BLACK,
|
||||||
COLOR_BLACK, COLOR_MAGENTA, COLOR_BLACK, COLOR_MAGENTA,
|
COLOR_BLACK, COLOR_MAGENTA, COLOR_BLACK, COLOR_MAGENTA,
|
||||||
|
|||||||
@@ -17,6 +17,9 @@
|
|||||||
#error "textureDisposePlatform should not be defined."
|
#error "textureDisposePlatform should not be defined."
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#define TEXTURE_FIXED_WIDTH 4
|
||||||
|
#define TEXTURE_FIXED_HEIGHT 4
|
||||||
|
|
||||||
typedef textureformatplatform_t textureformat_t;
|
typedef textureformatplatform_t textureformat_t;
|
||||||
typedef textureplatform_t texture_t;
|
typedef textureplatform_t texture_t;
|
||||||
|
|
||||||
@@ -29,9 +32,9 @@ typedef union texturedata_u {
|
|||||||
} texturedata_t;
|
} texturedata_t;
|
||||||
|
|
||||||
extern texture_t TEXTURE_WHITE;
|
extern texture_t TEXTURE_WHITE;
|
||||||
extern color_t TEXTURE_WHITE_PIXELS[4*4];
|
extern color_t TEXTURE_WHITE_PIXELS[TEXTURE_FIXED_WIDTH * TEXTURE_FIXED_HEIGHT];
|
||||||
extern texture_t TEXTURE_TEST;
|
extern texture_t TEXTURE_TEST;
|
||||||
extern color_t TEXTURE_TEST_PIXELS[4*4];
|
extern color_t TEXTURE_TEST_PIXELS[TEXTURE_FIXED_WIDTH * TEXTURE_FIXED_HEIGHT];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes a texture.
|
* Initializes a texture.
|
||||||
|
|||||||
@@ -16,10 +16,12 @@
|
|||||||
#include "asset/asset.h"
|
#include "asset/asset.h"
|
||||||
#include "ui/ui.h"
|
#include "ui/ui.h"
|
||||||
#include "assert/assert.h"
|
#include "assert/assert.h"
|
||||||
#include "network/network.h"
|
#ifdef DUSK_NETWORK
|
||||||
|
#include "network/network.h"
|
||||||
|
#endif
|
||||||
#include "system/system.h"
|
#include "system/system.h"
|
||||||
#include "console/console.h"
|
#include "console/console.h"
|
||||||
#include "save/save.h"
|
#include "save/savemanager.h"\
|
||||||
|
|
||||||
engine_t ENGINE;
|
engine_t ENGINE;
|
||||||
|
|
||||||
@@ -37,12 +39,14 @@ errorret_t engineInit(const int32_t argc, const char_t **argv) {
|
|||||||
errorChain(systemInit());
|
errorChain(systemInit());
|
||||||
errorChain(inputInit());
|
errorChain(inputInit());
|
||||||
errorChain(assetInit());
|
errorChain(assetInit());
|
||||||
// errorChain(saveInit());
|
errorChain(saveManagerInit());
|
||||||
errorChain(localeManagerInit());
|
errorChain(localeManagerInit());
|
||||||
errorChain(displayInit());
|
errorChain(displayInit());
|
||||||
errorChain(uiInit());
|
errorChain(uiInit());
|
||||||
errorChain(rpgInit());
|
errorChain(rpgInit());
|
||||||
errorChain(networkInit());
|
#ifdef DUSK_NETWORK
|
||||||
|
errorChain(networkInit());
|
||||||
|
#endif
|
||||||
errorChain(sceneInit());
|
errorChain(sceneInit());
|
||||||
|
|
||||||
consolePrint("Engine initialized");
|
consolePrint("Engine initialized");
|
||||||
@@ -53,15 +57,17 @@ errorret_t engineInit(const int32_t argc, const char_t **argv) {
|
|||||||
consolePrint("Assertions real");
|
consolePrint("Assertions real");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
sceneSet(SCENE_TYPE_OVERWORLD);
|
sceneSet(SCENE_TYPE_INITIAL);
|
||||||
|
|
||||||
|
|
||||||
errorOk();
|
errorOk();
|
||||||
}
|
}
|
||||||
|
|
||||||
errorret_t engineUpdate(void) {
|
errorret_t engineUpdate(void) {
|
||||||
// Order here is important.
|
// Order here is important.
|
||||||
errorChain(networkUpdate());
|
#ifdef DUSK_NETWORK
|
||||||
|
errorChain(networkUpdate());
|
||||||
|
#endif
|
||||||
|
errorChain(saveManagerUpdate());
|
||||||
timeUpdate();
|
timeUpdate();
|
||||||
inputUpdate();
|
inputUpdate();
|
||||||
consoleUpdate();
|
consoleUpdate();
|
||||||
@@ -82,13 +88,15 @@ void engineExit(void) {
|
|||||||
|
|
||||||
errorret_t engineDispose(void) {
|
errorret_t engineDispose(void) {
|
||||||
errorChain(sceneDispose());
|
errorChain(sceneDispose());
|
||||||
errorChain(networkDispose());
|
#ifdef DUSK_NETWORK
|
||||||
|
errorChain(networkDispose());
|
||||||
|
#endif
|
||||||
errorChain(rpgDispose());
|
errorChain(rpgDispose());
|
||||||
localeManagerDispose();
|
localeManagerDispose();
|
||||||
errorChain(uiDispose());
|
errorChain(uiDispose());
|
||||||
consoleDispose();
|
consoleDispose();
|
||||||
errorChain(displayDispose());
|
errorChain(displayDispose());
|
||||||
// errorChain(saveDispose());
|
errorChain(saveManagerDispose());
|
||||||
errorChain(assetDispose());
|
errorChain(assetDispose());
|
||||||
|
|
||||||
errorOk();
|
errorOk();
|
||||||
|
|||||||
@@ -22,20 +22,6 @@ errorret_t inputInit(void) {
|
|||||||
INPUT.actions[i].action = (inputaction_t)i;
|
INPUT.actions[i].action = (inputaction_t)i;
|
||||||
INPUT.actions[i].lastValue = 0.0f;
|
INPUT.actions[i].lastValue = 0.0f;
|
||||||
INPUT.actions[i].currentValue = 0.0f;
|
INPUT.actions[i].currentValue = 0.0f;
|
||||||
|
|
||||||
eventInit(
|
|
||||||
&INPUT.actions[i].onPressed,
|
|
||||||
INPUT.actions[i].onPressedCallbacks,
|
|
||||||
INPUT.actions[i].onPressedUsers,
|
|
||||||
INPUT_ACTION_CALLBACK_COUNT_MAX
|
|
||||||
);
|
|
||||||
|
|
||||||
eventInit(
|
|
||||||
&INPUT.actions[i].onReleased,
|
|
||||||
INPUT.actions[i].onReleasedCallbacks,
|
|
||||||
INPUT.actions[i].onReleasedUsers,
|
|
||||||
INPUT_ACTION_CALLBACK_COUNT_MAX
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef inputInitPlatform
|
#ifdef inputInitPlatform
|
||||||
@@ -106,8 +92,6 @@ void inputUpdate(void) {
|
|||||||
inputactiondata_t *act = &INPUT.actions[i];
|
inputactiondata_t *act = &INPUT.actions[i];
|
||||||
bool_t isDown = act->currentValue > 0.0f;
|
bool_t isDown = act->currentValue > 0.0f;
|
||||||
bool_t wasDown = act->lastValue > 0.0f;
|
bool_t wasDown = act->lastValue > 0.0f;
|
||||||
if(isDown && !wasDown) eventInvoke(&act->onPressed, act);
|
|
||||||
if(!isDown && wasDown) eventInvoke(&act->onReleased, act);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,12 +10,8 @@
|
|||||||
#include "inputbutton.h"
|
#include "inputbutton.h"
|
||||||
#include "inputaction.h"
|
#include "inputaction.h"
|
||||||
|
|
||||||
#define INPUT_LISTENER_PRESSED_MAX 16
|
|
||||||
#define INPUT_LISTENER_RELEASED_MAX INPUT_LISTENER_PRESSED_MAX
|
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
inputactiondata_t actions[INPUT_ACTION_COUNT];
|
inputactiondata_t actions[INPUT_ACTION_COUNT];
|
||||||
|
|
||||||
inputplatform_t platform;
|
inputplatform_t platform;
|
||||||
} input_t;
|
} input_t;
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "time/time.h"
|
#include "time/time.h"
|
||||||
#include "input/inputactiondefs.h"
|
#include "input/inputactiondefs.h"
|
||||||
#include "event/event.h"
|
|
||||||
|
|
||||||
#define INPUT_ACTION_CALLBACK_COUNT_MAX 4
|
#define INPUT_ACTION_CALLBACK_COUNT_MAX 4
|
||||||
|
|
||||||
@@ -21,13 +20,6 @@ typedef struct {
|
|||||||
float_t lastDynamicValue;
|
float_t lastDynamicValue;
|
||||||
float_t currentDynamicValue;
|
float_t currentDynamicValue;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
eventcallback_t onPressedCallbacks[INPUT_ACTION_CALLBACK_COUNT_MAX];
|
|
||||||
void *onPressedUsers[INPUT_ACTION_CALLBACK_COUNT_MAX];
|
|
||||||
event_t onPressed;
|
|
||||||
eventcallback_t onReleasedCallbacks[INPUT_ACTION_CALLBACK_COUNT_MAX];
|
|
||||||
void *onReleasedUsers[INPUT_ACTION_CALLBACK_COUNT_MAX];
|
|
||||||
event_t onReleased;
|
|
||||||
} inputactiondata_t;
|
} inputactiondata_t;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -13,7 +13,31 @@ typedef struct {
|
|||||||
const char_t *file;
|
const char_t *file;
|
||||||
} localeinfo_t;
|
} localeinfo_t;
|
||||||
|
|
||||||
static const localeinfo_t LOCALE_EN_US = {
|
static const localeinfo_t LOCALE_INFO_EN_US = {
|
||||||
.name = "en-US",
|
.name = "en-US",
|
||||||
.file = "locale/en_US.po",
|
.file = "locale/en_US.po",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static const localeinfo_t LOCALE_INFO_JP_JP = {
|
||||||
|
.name = "ja-JP",
|
||||||
|
.file = "locale/jp_JP.po",
|
||||||
|
};
|
||||||
|
|
||||||
|
static const localeinfo_t LOCALE_INFO_ES_MX = {
|
||||||
|
.name = "es-MX",
|
||||||
|
.file = "locale/es_MX.po",
|
||||||
|
};
|
||||||
|
|
||||||
|
static const localeinfo_t * const LOCALE_INFO_LIST[] = {
|
||||||
|
&LOCALE_INFO_EN_US,
|
||||||
|
&LOCALE_INFO_JP_JP,
|
||||||
|
&LOCALE_INFO_ES_MX
|
||||||
|
};
|
||||||
|
|
||||||
|
#define LOCALE_INFO_LIST_COUNT ( \
|
||||||
|
sizeof(LOCALE_INFO_LIST) / sizeof(LOCALE_INFO_LIST[0]) \
|
||||||
|
)
|
||||||
|
|
||||||
|
#define LOCALE_DEFAULT LOCALE_INFO_EN_US
|
||||||
|
|
||||||
|
// EOF
|
||||||
|
|||||||
@@ -7,13 +7,22 @@
|
|||||||
|
|
||||||
#include "localemanager.h"
|
#include "localemanager.h"
|
||||||
#include "util/memory.h"
|
#include "util/memory.h"
|
||||||
|
#include "util/string.h"
|
||||||
#include "assert/assert.h"
|
#include "assert/assert.h"
|
||||||
|
#include "ui/ui.h"
|
||||||
|
#include "system/system.h"
|
||||||
|
#include "console/console.h"
|
||||||
|
|
||||||
localemanager_t LOCALE;
|
localemanager_t LOCALE;
|
||||||
|
|
||||||
errorret_t localeManagerInit() {
|
errorret_t localeManagerInit() {
|
||||||
memoryZero(&LOCALE, sizeof(localemanager_t));
|
memoryZero(&LOCALE, sizeof(localemanager_t));
|
||||||
errorChain(localeManagerSetLocale(&LOCALE_EN_US));
|
|
||||||
|
// TODO: Set locale based on system locale.
|
||||||
|
const localeinfo_t *locale = systemGetLocale();
|
||||||
|
errorChain(localeManagerSetLocale(locale));
|
||||||
|
consolePrint("Locale set to: %s", locale->name);
|
||||||
|
|
||||||
errorOk();
|
errorOk();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30,6 +39,9 @@ errorret_t localeManagerSetLocale(const localeinfo_t *locale) {
|
|||||||
assetEntryLock(LOCALE.entry);
|
assetEntryLock(LOCALE.entry);
|
||||||
errorChain(assetRequireLoaded(LOCALE.entry));
|
errorChain(assetRequireLoaded(LOCALE.entry));
|
||||||
|
|
||||||
|
// TODO : Trigger UI update.
|
||||||
|
errorChain(uiUpdateTranslations());
|
||||||
|
|
||||||
errorOk();
|
errorOk();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "error/error.h"
|
#include "error/error.h"
|
||||||
#include "localemanager.h"
|
#include "locale/localemanager.h"
|
||||||
#include "locale/localeinfo.h"
|
#include "locale/localeinfo.h"
|
||||||
#include "asset/asset.h"
|
#include "asset/asset.h"
|
||||||
|
|
||||||
@@ -20,14 +20,14 @@ extern localemanager_t LOCALE;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize the locale system.
|
* Initialize the locale system.
|
||||||
*
|
*
|
||||||
* @return An error code if a failure occurs.
|
* @return An error code if a failure occurs.
|
||||||
*/
|
*/
|
||||||
errorret_t localeManagerInit();
|
errorret_t localeManagerInit();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the current locale.
|
* Set the current locale.
|
||||||
*
|
*
|
||||||
* @param locale The locale to set.
|
* @param locale The locale to set.
|
||||||
* @return An error code if a failure occurs.
|
* @return An error code if a failure occurs.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Subdirs
|
# Subdirs
|
||||||
|
add_subdirectory(battle)
|
||||||
add_subdirectory(cutscene)
|
add_subdirectory(cutscene)
|
||||||
add_subdirectory(entity)
|
add_subdirectory(entity)
|
||||||
add_subdirectory(overworld)
|
add_subdirectory(overworld)
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
# Copyright (c) 2026 Dominic Masters
|
||||||
|
#
|
||||||
|
# This software is released under the MIT License.
|
||||||
|
# https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
|
# Sources
|
||||||
|
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||||
|
PUBLIC
|
||||||
|
battle.c
|
||||||
|
battlefighter.c
|
||||||
|
party.c
|
||||||
|
)
|
||||||
|
|
||||||
|
# Subdirs
|
||||||
|
add_subdirectory(testbattle)
|
||||||
@@ -0,0 +1,326 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "battle.h"
|
||||||
|
#include "assert/assert.h"
|
||||||
|
#include "util/memory.h"
|
||||||
|
#include "rpg/cutscene/cutscenesystem.h"
|
||||||
|
|
||||||
|
battle_t BATTLE;
|
||||||
|
|
||||||
|
void battleInit(void) {
|
||||||
|
memoryZero(&BATTLE, sizeof(battle_t));
|
||||||
|
for(uint8_t i = 0; i < BATTLE_FIGHTER_COUNT_MAX; i++) {
|
||||||
|
BATTLE.fighters[i].id = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t battleGetAvailableFighter(void) {
|
||||||
|
for(uint8_t i = 0; i < BATTLE_FIGHTER_COUNT_MAX; i++) {
|
||||||
|
if(BATTLE.fighters[i].status == BATTLE_FIGHTER_STATUS_NULL) return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0xFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
battlefighter_t *battleAddFighter(
|
||||||
|
const battlefighterteam_t team,
|
||||||
|
const battlefightercontroller_t controller,
|
||||||
|
const battlefighterstats_t stats,
|
||||||
|
const uint16_t healthMax,
|
||||||
|
const uint16_t mpMax
|
||||||
|
) {
|
||||||
|
const uint8_t index = battleGetAvailableFighter();
|
||||||
|
if(index == 0xFF) return NULL;
|
||||||
|
|
||||||
|
battlefighter_t *fighter = &BATTLE.fighters[index];
|
||||||
|
battleFighterInit(fighter, team, controller, stats, healthMax, mpMax);
|
||||||
|
return fighter;
|
||||||
|
}
|
||||||
|
|
||||||
|
void battleStart(
|
||||||
|
const battleencountertype_t encounterType,
|
||||||
|
const bool_t fleeAvailable
|
||||||
|
) {
|
||||||
|
assertTrue(encounterType < BATTLE_ENCOUNTER_COUNT, "Invalid encounter type");
|
||||||
|
|
||||||
|
BATTLE.encounterType = encounterType;
|
||||||
|
BATTLE.fleeAvailable = fleeAvailable;
|
||||||
|
BATTLE.result = BATTLE_RESULT_NONE;
|
||||||
|
BATTLE.round = 1;
|
||||||
|
|
||||||
|
battleSetState(BATTLE_STATE_OPENING);
|
||||||
|
}
|
||||||
|
|
||||||
|
void battleDispose(void) {
|
||||||
|
battleInit();
|
||||||
|
}
|
||||||
|
|
||||||
|
battlefighter_t *battleGetCurrentFighter(void) {
|
||||||
|
if(BATTLE.state != BATTLE_STATE_PLAYER_SELECTION) return NULL;
|
||||||
|
if(BATTLE.selectionIndex >= BATTLE.executionCount) return NULL;
|
||||||
|
return &BATTLE.fighters[BATTLE.executionOrder[BATTLE.selectionIndex]];
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t battleGetAliveCount(const battlefighterteam_t team) {
|
||||||
|
uint8_t count = 0;
|
||||||
|
for(uint8_t i = 0; i < BATTLE_FIGHTER_COUNT_MAX; i++) {
|
||||||
|
if(BATTLE.fighters[i].team != team) continue;
|
||||||
|
if(!battleFighterIsAlive(&BATTLE.fighters[i])) continue;
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
void battleResolveAttack(
|
||||||
|
battlefighter_t *attacker,
|
||||||
|
battlefighter_t *defender
|
||||||
|
) {
|
||||||
|
assertNotNull(attacker, "Attacker cannot be NULL");
|
||||||
|
assertNotNull(defender, "Defender cannot be NULL");
|
||||||
|
|
||||||
|
const int32_t rawDamage =
|
||||||
|
(int32_t)attacker->stats.attack - (int32_t)defender->stats.defense;
|
||||||
|
const uint16_t damage = rawDamage > 0 ? (uint16_t)rawDamage : 1;
|
||||||
|
|
||||||
|
defender->health = damage >= defender->health ? 0 : defender->health - damage;
|
||||||
|
if(defender->health == 0) defender->status = BATTLE_FIGHTER_STATUS_DEAD;
|
||||||
|
}
|
||||||
|
|
||||||
|
battleresult_t battleCheckResult(void) {
|
||||||
|
if(BATTLE.result != BATTLE_RESULT_NONE) return BATTLE.result;
|
||||||
|
|
||||||
|
if(battleGetAliveCount(BATTLE_FIGHTER_TEAM_ALLY) == 0) {
|
||||||
|
battleSetResult(BATTLE_RESULT_LOSS);
|
||||||
|
} else if(battleGetAliveCount(BATTLE_FIGHTER_TEAM_ENEMY) == 0) {
|
||||||
|
battleSetResult(BATTLE_RESULT_WIN);
|
||||||
|
}
|
||||||
|
|
||||||
|
return BATTLE.result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void battleQueueAction(
|
||||||
|
const uint8_t fighterIndex,
|
||||||
|
const battleactiontype_t type,
|
||||||
|
const uint8_t targetIndex
|
||||||
|
) {
|
||||||
|
battleaction_t *action = &BATTLE.actions[fighterIndex];
|
||||||
|
action->type = type;
|
||||||
|
action->targetIndex = targetIndex;
|
||||||
|
|
||||||
|
if(BATTLE.onActionDecided != NULL) {
|
||||||
|
BATTLE.onActionDecided(&BATTLE.fighters[fighterIndex], action);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void battlePlayerAttack(const uint8_t targetIndex) {
|
||||||
|
battlefighter_t *fighter = battleGetCurrentFighter();
|
||||||
|
if(fighter == NULL) return;
|
||||||
|
if(targetIndex >= BATTLE_FIGHTER_COUNT_MAX) return;
|
||||||
|
if(!battleFighterIsAlive(&BATTLE.fighters[targetIndex])) return;
|
||||||
|
|
||||||
|
battleQueueAction(fighter->id, BATTLE_ACTION_ATTACK, targetIndex);
|
||||||
|
BATTLE.selectionIndex++;
|
||||||
|
battleAdvanceSelection();
|
||||||
|
}
|
||||||
|
|
||||||
|
void battlePlayerFlee(void) {
|
||||||
|
battlefighter_t *fighter = battleGetCurrentFighter();
|
||||||
|
if(fighter == NULL) return;
|
||||||
|
if(!BATTLE.fleeAvailable) return;
|
||||||
|
|
||||||
|
battleSetResult(BATTLE_RESULT_FLED);
|
||||||
|
}
|
||||||
|
|
||||||
|
void battleUpdate(void) {
|
||||||
|
if(BATTLE.state == BATTLE_STATE_NONE) return;
|
||||||
|
if(BATTLE.state == BATTLE_STATE_ENDED) return;
|
||||||
|
if(CUTSCENE_SYSTEM.pause & CUTSCENE_PAUSE_BATTLE) return;
|
||||||
|
|
||||||
|
switch(BATTLE.state) {
|
||||||
|
case BATTLE_STATE_OPENING:
|
||||||
|
battleSetState(BATTLE_STATE_PRE_ROUND);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case BATTLE_STATE_PRE_ROUND:
|
||||||
|
battleUpdatePreRound();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case BATTLE_STATE_AI_SELECTION:
|
||||||
|
battleUpdateAiSelection();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case BATTLE_STATE_MOVES_EXECUTING:
|
||||||
|
battleUpdateMovesExecuting();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case BATTLE_STATE_POST_ROUND:
|
||||||
|
battleUpdatePostRound();
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
// BATTLE_STATE_PLAYER_SELECTION: waits on battlePlayerAttack/Flee.
|
||||||
|
// BATTLE_STATE_NONE/ENDED: handled above.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void battleBuildExecutionOrder(const bool_t applyEncounterBias) {
|
||||||
|
BATTLE.executionCount = 0;
|
||||||
|
for(uint8_t i = 0; i < BATTLE_FIGHTER_COUNT_MAX; i++) {
|
||||||
|
if(!battleFighterIsAlive(&BATTLE.fighters[i])) continue;
|
||||||
|
BATTLE.executionOrder[BATTLE.executionCount++] = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insertion sort by speed descending -- fine for BATTLE_FIGHTER_COUNT_MAX.
|
||||||
|
for(uint8_t i = 1; i < BATTLE.executionCount; i++) {
|
||||||
|
const uint8_t key = BATTLE.executionOrder[i];
|
||||||
|
const uint16_t keySpeed = BATTLE.fighters[key].stats.speed;
|
||||||
|
|
||||||
|
int8_t j = (int8_t)i - 1;
|
||||||
|
while(
|
||||||
|
j >= 0 &&
|
||||||
|
BATTLE.fighters[BATTLE.executionOrder[j]].stats.speed < keySpeed
|
||||||
|
) {
|
||||||
|
BATTLE.executionOrder[j + 1] = BATTLE.executionOrder[j];
|
||||||
|
j--;
|
||||||
|
}
|
||||||
|
BATTLE.executionOrder[j + 1] = key;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!applyEncounterBias) return;
|
||||||
|
|
||||||
|
if(BATTLE.encounterType == BATTLE_ENCOUNTER_PLAYER_ADVANTAGE) {
|
||||||
|
battleMoveTeamFirst(BATTLE_FIGHTER_TEAM_ALLY);
|
||||||
|
} else if(BATTLE.encounterType == BATTLE_ENCOUNTER_BACK_ATTACK) {
|
||||||
|
battleMoveTeamFirst(BATTLE_FIGHTER_TEAM_ENEMY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void battleMoveTeamFirst(const battlefighterteam_t team) {
|
||||||
|
uint8_t sorted[BATTLE_FIGHTER_COUNT_MAX];
|
||||||
|
uint8_t count = 0;
|
||||||
|
|
||||||
|
for(uint8_t i = 0; i < BATTLE.executionCount; i++) {
|
||||||
|
if(BATTLE.fighters[BATTLE.executionOrder[i]].team != team) continue;
|
||||||
|
sorted[count++] = BATTLE.executionOrder[i];
|
||||||
|
}
|
||||||
|
for(uint8_t i = 0; i < BATTLE.executionCount; i++) {
|
||||||
|
if(BATTLE.fighters[BATTLE.executionOrder[i]].team == team) continue;
|
||||||
|
sorted[count++] = BATTLE.executionOrder[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
memoryCopy(BATTLE.executionOrder, sorted, sizeof(uint8_t) * BATTLE.executionCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
battlefighter_t *battleAIChooseTarget(const battlefighter_t *fighter) {
|
||||||
|
const battlefighterteam_t enemyTeam =
|
||||||
|
fighter->team == BATTLE_FIGHTER_TEAM_ALLY ?
|
||||||
|
BATTLE_FIGHTER_TEAM_ENEMY : BATTLE_FIGHTER_TEAM_ALLY;
|
||||||
|
|
||||||
|
battlefighter_t *weakest = NULL;
|
||||||
|
for(uint8_t i = 0; i < BATTLE_FIGHTER_COUNT_MAX; i++) {
|
||||||
|
battlefighter_t *candidate = &BATTLE.fighters[i];
|
||||||
|
if(candidate->team != enemyTeam) continue;
|
||||||
|
if(!battleFighterIsAlive(candidate)) continue;
|
||||||
|
if(weakest == NULL || candidate->health < weakest->health) {
|
||||||
|
weakest = candidate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return weakest;
|
||||||
|
}
|
||||||
|
|
||||||
|
void battleSetState(const battlestate_t next) {
|
||||||
|
const battlestate_t previous = BATTLE.state;
|
||||||
|
BATTLE.state = next;
|
||||||
|
if(BATTLE.onStateChanged != NULL) BATTLE.onStateChanged(previous, next);
|
||||||
|
}
|
||||||
|
|
||||||
|
void battleSetResult(const battleresult_t result) {
|
||||||
|
BATTLE.result = result;
|
||||||
|
battleSetState(BATTLE_STATE_ENDED);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool_t battleFighterNeedsDecision(
|
||||||
|
const uint8_t fighterIndex,
|
||||||
|
const battlefightercontroller_t controller
|
||||||
|
) {
|
||||||
|
return battleFighterIsAlive(&BATTLE.fighters[fighterIndex])
|
||||||
|
&& BATTLE.fighters[fighterIndex].controller == controller
|
||||||
|
&& BATTLE.actions[fighterIndex].type == BATTLE_ACTION_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
void battleAdvanceSelection(void) {
|
||||||
|
while(BATTLE.selectionIndex < BATTLE.executionCount) {
|
||||||
|
const uint8_t fighterIndex = BATTLE.executionOrder[BATTLE.selectionIndex];
|
||||||
|
if(
|
||||||
|
battleFighterNeedsDecision(fighterIndex, BATTLE_FIGHTER_CONTROLLER_PLAYER)
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
BATTLE.selectionIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
battleSetState(BATTLE_STATE_AI_SELECTION);
|
||||||
|
}
|
||||||
|
|
||||||
|
void battleUpdatePreRound(void) {
|
||||||
|
// No need to clear BATTLE.actions here: every living fighter's action is
|
||||||
|
// unconditionally reset to BATTLE_ACTION_NONE as it's processed in
|
||||||
|
// battleUpdateMovesExecuting, and round 1 starts pre-zeroed by
|
||||||
|
// battleInit(). Clearing it here would also wipe out any action a
|
||||||
|
// cutscene force-queued while parked at BATTLE_STATE_PRE_ROUND.
|
||||||
|
battleBuildExecutionOrder(BATTLE.round == 1);
|
||||||
|
|
||||||
|
BATTLE.selectionIndex = 0;
|
||||||
|
battleSetState(BATTLE_STATE_PLAYER_SELECTION);
|
||||||
|
battleAdvanceSelection();
|
||||||
|
}
|
||||||
|
|
||||||
|
void battleUpdateAiSelection(void) {
|
||||||
|
for(uint8_t i = 0; i < BATTLE.executionCount; i++) {
|
||||||
|
const uint8_t fighterIndex = BATTLE.executionOrder[i];
|
||||||
|
if(
|
||||||
|
!battleFighterNeedsDecision(fighterIndex, BATTLE_FIGHTER_CONTROLLER_AI)
|
||||||
|
) continue;
|
||||||
|
|
||||||
|
battlefighter_t *target =
|
||||||
|
battleAIChooseTarget(&BATTLE.fighters[fighterIndex]);
|
||||||
|
if(target == NULL) continue;
|
||||||
|
|
||||||
|
battleQueueAction(fighterIndex, BATTLE_ACTION_ATTACK, target->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
BATTLE.executionIndex = 0;
|
||||||
|
battleSetState(BATTLE_STATE_MOVES_EXECUTING);
|
||||||
|
}
|
||||||
|
|
||||||
|
void battleUpdateMovesExecuting(void) {
|
||||||
|
if(BATTLE.executionIndex >= BATTLE.executionCount) {
|
||||||
|
battleSetState(BATTLE_STATE_POST_ROUND);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint8_t fighterIndex = BATTLE.executionOrder[BATTLE.executionIndex++];
|
||||||
|
battlefighter_t *fighter = &BATTLE.fighters[fighterIndex];
|
||||||
|
if(!battleFighterIsAlive(fighter)) return;
|
||||||
|
|
||||||
|
battleaction_t *action = &BATTLE.actions[fighterIndex];
|
||||||
|
if(action->type == BATTLE_ACTION_ATTACK) {
|
||||||
|
battlefighter_t *target = &BATTLE.fighters[action->targetIndex];
|
||||||
|
if(battleFighterIsAlive(target)) battleResolveAttack(fighter, target);
|
||||||
|
}
|
||||||
|
action->type = BATTLE_ACTION_NONE;
|
||||||
|
|
||||||
|
battleCheckResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
void battleUpdatePostRound(void) {
|
||||||
|
BATTLE.round++;
|
||||||
|
battleSetState(BATTLE_STATE_PRE_ROUND);
|
||||||
|
}
|
||||||
@@ -0,0 +1,313 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "battlefighter.h"
|
||||||
|
|
||||||
|
#define BATTLE_FIGHTER_COUNT_MAX 8
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
BATTLE_ENCOUNTER_REGULAR,
|
||||||
|
BATTLE_ENCOUNTER_PLAYER_ADVANTAGE,
|
||||||
|
BATTLE_ENCOUNTER_BACK_ATTACK,
|
||||||
|
|
||||||
|
BATTLE_ENCOUNTER_COUNT
|
||||||
|
} battleencountertype_t;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
BATTLE_RESULT_NONE,
|
||||||
|
BATTLE_RESULT_WIN,
|
||||||
|
BATTLE_RESULT_LOSS,
|
||||||
|
BATTLE_RESULT_FLED,
|
||||||
|
|
||||||
|
BATTLE_RESULT_COUNT
|
||||||
|
} battleresult_t;
|
||||||
|
|
||||||
|
// Where BATTLE currently is within a round. A cutscene can pause progression
|
||||||
|
// (CUTSCENE_PAUSE_BATTLE) and use CUTSCENE_BATTLE_WAIT_STATE to synchronize
|
||||||
|
// with any of these, or CUTSCENE_BATTLE_FORCE_ACTION to decide a fighter's
|
||||||
|
// action ahead of PLAYER_SELECTION/AI_SELECTION reaching them.
|
||||||
|
typedef enum {
|
||||||
|
BATTLE_STATE_NONE, // Battle inactive.
|
||||||
|
|
||||||
|
BATTLE_STATE_OPENING, // Entered once by battleStart().
|
||||||
|
BATTLE_STATE_PRE_ROUND, // Rebuilds execution order, clears the action queue.
|
||||||
|
BATTLE_STATE_PLAYER_SELECTION, // Waits on battlePlayerAttack/Flee.
|
||||||
|
BATTLE_STATE_AI_SELECTION, // Auto-queues every undecided AI fighter.
|
||||||
|
BATTLE_STATE_MOVES_EXECUTING, // Resolves one queued action per update.
|
||||||
|
BATTLE_STATE_POST_ROUND, // Round wrap-up; loops back to PRE_ROUND.
|
||||||
|
|
||||||
|
BATTLE_STATE_ENDED, // Terminal for WIN/LOSS/FLED alike -- see BATTLE.result.
|
||||||
|
|
||||||
|
BATTLE_STATE_COUNT
|
||||||
|
} battlestate_t;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
BATTLE_ACTION_NONE, // No action decided yet for this fighter this round.
|
||||||
|
BATTLE_ACTION_ATTACK,
|
||||||
|
|
||||||
|
BATTLE_ACTION_COUNT
|
||||||
|
} battleactiontype_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
battleactiontype_t type;
|
||||||
|
uint8_t targetIndex; // Meaningful for BATTLE_ACTION_ATTACK.
|
||||||
|
} battleaction_t;
|
||||||
|
|
||||||
|
typedef void (*battlestatechangedcallback_t)(
|
||||||
|
const battlestate_t previous,
|
||||||
|
const battlestate_t next
|
||||||
|
);
|
||||||
|
|
||||||
|
typedef void (*battleactiondecidedcallback_t)(
|
||||||
|
const battlefighter_t *fighter,
|
||||||
|
const battleaction_t *action
|
||||||
|
);
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
battlestate_t state;
|
||||||
|
battlefighter_t fighters[BATTLE_FIGHTER_COUNT_MAX];
|
||||||
|
battleaction_t actions[BATTLE_FIGHTER_COUNT_MAX];
|
||||||
|
|
||||||
|
battleencountertype_t encounterType;
|
||||||
|
bool_t fleeAvailable;
|
||||||
|
battleresult_t result;
|
||||||
|
|
||||||
|
// Fighter indices (into fighters[]), sorted for the current round.
|
||||||
|
uint8_t executionOrder[BATTLE_FIGHTER_COUNT_MAX];
|
||||||
|
uint8_t executionCount;
|
||||||
|
uint8_t executionIndex;
|
||||||
|
uint16_t round;
|
||||||
|
|
||||||
|
// Cursor into executionOrder used by BATTLE_STATE_PLAYER_SELECTION to find
|
||||||
|
// the next player-controlled fighter that still needs a decision.
|
||||||
|
uint8_t selectionIndex;
|
||||||
|
|
||||||
|
battlestatechangedcallback_t onStateChanged;
|
||||||
|
battleactiondecidedcallback_t onActionDecided;
|
||||||
|
} battle_t;
|
||||||
|
|
||||||
|
extern battle_t BATTLE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the battle system. Marks it as inactive with no fighters.
|
||||||
|
*/
|
||||||
|
void battleInit(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets an available (unused) fighter slot index.
|
||||||
|
*
|
||||||
|
* @return The index of an available fighter slot, or 0xFF if none are
|
||||||
|
* available.
|
||||||
|
*/
|
||||||
|
uint8_t battleGetAvailableFighter(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a fighter to the battle in the next available slot.
|
||||||
|
*
|
||||||
|
* @param team The team the fighter belongs to.
|
||||||
|
* @param controller Who makes decisions for the fighter.
|
||||||
|
* @param stats The fighter's base combat stats.
|
||||||
|
* @param healthMax The fighter's maximum health.
|
||||||
|
* @param mpMax The fighter's maximum mp.
|
||||||
|
* @return Pointer to the newly added fighter, or NULL if the battle is
|
||||||
|
* already full.
|
||||||
|
*/
|
||||||
|
battlefighter_t *battleAddFighter(
|
||||||
|
const battlefighterteam_t team,
|
||||||
|
const battlefightercontroller_t controller,
|
||||||
|
const battlefighterstats_t stats,
|
||||||
|
const uint16_t healthMax,
|
||||||
|
const uint16_t mpMax
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts the battle: enters BATTLE_STATE_OPENING and marks the battle
|
||||||
|
* active. Call once every fighter has been added via battleAddFighter.
|
||||||
|
*
|
||||||
|
* @param encounterType Determines the opening round's execution order.
|
||||||
|
* @param fleeAvailable Whether the party may attempt to flee this battle.
|
||||||
|
*/
|
||||||
|
void battleStart(
|
||||||
|
const battleencountertype_t encounterType,
|
||||||
|
const bool_t fleeAvailable
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disposes of the battle, clearing all fighters and marking it inactive.
|
||||||
|
*/
|
||||||
|
void battleDispose(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the fighter currently awaiting a player decision.
|
||||||
|
*
|
||||||
|
* @return Pointer to the fighter awaiting a decision, or NULL if the battle
|
||||||
|
* isn't in BATTLE_STATE_PLAYER_SELECTION or every player-controlled
|
||||||
|
* fighter has already decided.
|
||||||
|
*/
|
||||||
|
battlefighter_t *battleGetCurrentFighter(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the number of living fighters on a team.
|
||||||
|
*
|
||||||
|
* @param team The team to count.
|
||||||
|
* @return Count of living fighters on that team.
|
||||||
|
*/
|
||||||
|
uint8_t battleGetAliveCount(const battlefighterteam_t team);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves a physical attack from attacker onto defender: damage is the
|
||||||
|
* attacker's attack stat minus the defender's defense stat (minimum 1),
|
||||||
|
* subtracted from the defender's health. The defender is marked dead
|
||||||
|
* once health reaches 0.
|
||||||
|
*
|
||||||
|
* @param attacker The attacking fighter.
|
||||||
|
* @param defender The defending fighter.
|
||||||
|
*/
|
||||||
|
void battleResolveAttack(
|
||||||
|
battlefighter_t *attacker,
|
||||||
|
battlefighter_t *defender
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether the battle has been won or lost, transitioning to
|
||||||
|
* BATTLE_STATE_ENDED and updating BATTLE.result if so. Does nothing if a
|
||||||
|
* result has already been set (e.g. by a successful flee).
|
||||||
|
*
|
||||||
|
* @return The battle's current result.
|
||||||
|
*/
|
||||||
|
battleresult_t battleCheckResult(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Queues an action for a fighter to perform once BATTLE_STATE_MOVES_EXECUTING
|
||||||
|
* reaches them this round, overwriting any action already queued for that
|
||||||
|
* fighter. Fires BATTLE.onActionDecided.
|
||||||
|
*
|
||||||
|
* @param fighterIndex Index into BATTLE.fighters of the deciding fighter.
|
||||||
|
* @param type The type of action to perform.
|
||||||
|
* @param targetIndex Index into BATTLE.fighters of the target, meaningful
|
||||||
|
* for BATTLE_ACTION_ATTACK.
|
||||||
|
*/
|
||||||
|
void battleQueueAction(
|
||||||
|
const uint8_t fighterIndex,
|
||||||
|
const battleactiontype_t type,
|
||||||
|
const uint8_t targetIndex
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Submits the currently-selecting fighter's attack against a target, if the
|
||||||
|
* battle is in BATTLE_STATE_PLAYER_SELECTION and awaiting a decision.
|
||||||
|
* Queues the action and advances the selection cursor.
|
||||||
|
*
|
||||||
|
* @param targetIndex Index into BATTLE.fighters of the target.
|
||||||
|
*/
|
||||||
|
void battlePlayerAttack(const uint8_t targetIndex);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Submits a flee attempt for the currently-selecting fighter, if the battle
|
||||||
|
* is in BATTLE_STATE_PLAYER_SELECTION and fleeing is available for this
|
||||||
|
* battle. Always succeeds, ending the battle with BATTLE_RESULT_FLED.
|
||||||
|
*/
|
||||||
|
void battlePlayerFlee(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the battle simulation for one frame, dispatching on BATTLE.state.
|
||||||
|
* No-op if the battle isn't active, has already ended, or
|
||||||
|
* CUTSCENE_PAUSE_BATTLE is set.
|
||||||
|
*/
|
||||||
|
void battleUpdate(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rebuilds BATTLE.executionOrder/executionCount from every currently living
|
||||||
|
* fighter, sorted by speed descending.
|
||||||
|
*
|
||||||
|
* @param applyEncounterBias If true, reorders the freshly speed-sorted
|
||||||
|
* queue so BATTLE.encounterType's favoured team goes first (used only
|
||||||
|
* for the opening round).
|
||||||
|
*/
|
||||||
|
void battleBuildExecutionOrder(const bool_t applyEncounterBias);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stably partitions BATTLE.executionOrder so every fighter on the given team
|
||||||
|
* comes first, preserving each side's relative (speed-sorted) order.
|
||||||
|
*
|
||||||
|
* @param team The team to move to the front of the execution order.
|
||||||
|
*/
|
||||||
|
void battleMoveTeamFirst(const battlefighterteam_t team);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Picks an AI target for fighter: the lowest-health living fighter on
|
||||||
|
* the opposing team.
|
||||||
|
*
|
||||||
|
* @param fighter The AI-controlled fighter choosing a target.
|
||||||
|
* @return The chosen target, or NULL if the opposing team has no
|
||||||
|
* living fighters.
|
||||||
|
*/
|
||||||
|
battlefighter_t *battleAIChooseTarget(const battlefighter_t *fighter);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets BATTLE.state and fires BATTLE.onStateChanged with the previous and
|
||||||
|
* new state.
|
||||||
|
*
|
||||||
|
* @param next The state to transition to.
|
||||||
|
*/
|
||||||
|
void battleSetState(const battlestate_t next);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets BATTLE.result and transitions to BATTLE_STATE_ENDED.
|
||||||
|
*
|
||||||
|
* @param result The result to end the battle with.
|
||||||
|
*/
|
||||||
|
void battleSetResult(const battleresult_t result);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether a fighter is a live, undecided candidate for the given
|
||||||
|
* controller -- i.e. whether PLAYER_SELECTION or AI_SELECTION should still
|
||||||
|
* be deciding an action for it this round.
|
||||||
|
*
|
||||||
|
* @param fighterIndex Index into BATTLE.fighters to check.
|
||||||
|
* @param controller The controller PLAYER_SELECTION/AI_SELECTION is
|
||||||
|
* currently deciding for.
|
||||||
|
* @return True if the fighter is alive, matches controller, and has no
|
||||||
|
* action queued yet.
|
||||||
|
*/
|
||||||
|
bool_t battleFighterNeedsDecision(
|
||||||
|
const uint8_t fighterIndex,
|
||||||
|
const battlefightercontroller_t controller
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Advances BATTLE.selectionIndex to the next player-controlled fighter that
|
||||||
|
* still needs a decision, or transitions to BATTLE_STATE_AI_SELECTION once
|
||||||
|
* none remain.
|
||||||
|
*/
|
||||||
|
void battleAdvanceSelection(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles BATTLE_STATE_PRE_ROUND: rebuilds the execution order and moves on
|
||||||
|
* to BATTLE_STATE_PLAYER_SELECTION, positioning the selection cursor.
|
||||||
|
*/
|
||||||
|
void battleUpdatePreRound(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles BATTLE_STATE_AI_SELECTION: queues an attack for every undecided
|
||||||
|
* AI-controlled fighter, then moves on to BATTLE_STATE_MOVES_EXECUTING.
|
||||||
|
*/
|
||||||
|
void battleUpdateAiSelection(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles BATTLE_STATE_MOVES_EXECUTING: resolves one queued action from
|
||||||
|
* BATTLE.executionOrder per call, or transitions to BATTLE_STATE_POST_ROUND
|
||||||
|
* once every fighter this round has been processed.
|
||||||
|
*/
|
||||||
|
void battleUpdateMovesExecuting(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles BATTLE_STATE_POST_ROUND: advances BATTLE.round and transitions
|
||||||
|
* back to BATTLE_STATE_PRE_ROUND.
|
||||||
|
*/
|
||||||
|
void battleUpdatePostRound(void);
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "battlefighter.h"
|
||||||
|
#include "assert/assert.h"
|
||||||
|
#include "util/memory.h"
|
||||||
|
|
||||||
|
void battleFighterInit(
|
||||||
|
battlefighter_t *fighter,
|
||||||
|
const battlefighterteam_t team,
|
||||||
|
const battlefightercontroller_t controller,
|
||||||
|
const battlefighterstats_t stats,
|
||||||
|
const uint16_t healthMax,
|
||||||
|
const uint16_t mpMax
|
||||||
|
) {
|
||||||
|
assertNotNull(fighter, "Fighter pointer cannot be NULL");
|
||||||
|
assertTrue(team < BATTLE_FIGHTER_TEAM_COUNT, "Invalid fighter team");
|
||||||
|
assertTrue(
|
||||||
|
controller < BATTLE_FIGHTER_CONTROLLER_COUNT,
|
||||||
|
"Invalid fighter controller"
|
||||||
|
);
|
||||||
|
|
||||||
|
const uint8_t id = fighter->id;
|
||||||
|
memoryZero(fighter, sizeof(battlefighter_t));
|
||||||
|
fighter->id = id;
|
||||||
|
fighter->status = BATTLE_FIGHTER_STATUS_NORMAL;
|
||||||
|
fighter->team = team;
|
||||||
|
fighter->controller = controller;
|
||||||
|
fighter->stats = stats;
|
||||||
|
fighter->healthMax = healthMax;
|
||||||
|
fighter->health = healthMax;
|
||||||
|
fighter->mpMax = mpMax;
|
||||||
|
fighter->mp = mpMax;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool_t battleFighterIsAlive(const battlefighter_t *fighter) {
|
||||||
|
assertNotNull(fighter, "Fighter pointer cannot be NULL");
|
||||||
|
return fighter->status == BATTLE_FIGHTER_STATUS_NORMAL;
|
||||||
|
}
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "error/error.h"
|
||||||
|
|
||||||
|
// An empty status means the slot in BATTLE.fighters is unused.
|
||||||
|
typedef enum {
|
||||||
|
BATTLE_FIGHTER_STATUS_NULL,
|
||||||
|
|
||||||
|
BATTLE_FIGHTER_STATUS_NORMAL,
|
||||||
|
BATTLE_FIGHTER_STATUS_DEAD,
|
||||||
|
|
||||||
|
BATTLE_FIGHTER_STATUS_COUNT
|
||||||
|
} battlefighterstatus_t;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
BATTLE_FIGHTER_TEAM_ALLY,
|
||||||
|
BATTLE_FIGHTER_TEAM_ENEMY,
|
||||||
|
|
||||||
|
BATTLE_FIGHTER_TEAM_COUNT
|
||||||
|
} battlefighterteam_t;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
BATTLE_FIGHTER_CONTROLLER_PLAYER,
|
||||||
|
BATTLE_FIGHTER_CONTROLLER_AI,
|
||||||
|
|
||||||
|
BATTLE_FIGHTER_CONTROLLER_COUNT
|
||||||
|
} battlefightercontroller_t;
|
||||||
|
|
||||||
|
// Base combat stats, kept separate from the resource pools (health/mp) on
|
||||||
|
// battlefighter_t so that equipment/buffs can later modify them without
|
||||||
|
// touching current health/mp state.
|
||||||
|
typedef struct {
|
||||||
|
uint16_t attack;
|
||||||
|
uint16_t defense;
|
||||||
|
uint16_t magic;
|
||||||
|
uint16_t speed;
|
||||||
|
uint16_t luck;
|
||||||
|
} battlefighterstats_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint8_t id;
|
||||||
|
battlefighterstatus_t status;
|
||||||
|
battlefighterteam_t team;
|
||||||
|
battlefightercontroller_t controller;
|
||||||
|
|
||||||
|
uint16_t health;
|
||||||
|
uint16_t healthMax;
|
||||||
|
uint16_t mp;
|
||||||
|
uint16_t mpMax;
|
||||||
|
|
||||||
|
battlefighterstats_t stats;
|
||||||
|
} battlefighter_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes a battle fighter in place, filling health/mp to their maximum
|
||||||
|
* values and setting its status to normal.
|
||||||
|
*
|
||||||
|
* @param fighter Pointer to the fighter to initialize.
|
||||||
|
* @param team The team the fighter belongs to.
|
||||||
|
* @param controller Who makes decisions for the fighter.
|
||||||
|
* @param stats The fighter's base combat stats.
|
||||||
|
* @param healthMax The fighter's maximum health.
|
||||||
|
* @param mpMax The fighter's maximum mp.
|
||||||
|
*/
|
||||||
|
void battleFighterInit(
|
||||||
|
battlefighter_t *fighter,
|
||||||
|
const battlefighterteam_t team,
|
||||||
|
const battlefightercontroller_t controller,
|
||||||
|
const battlefighterstats_t stats,
|
||||||
|
const uint16_t healthMax,
|
||||||
|
const uint16_t mpMax
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the fighter is in a state where it can still act (i.e.
|
||||||
|
* is not dead).
|
||||||
|
*
|
||||||
|
* @param fighter Pointer to the fighter to check.
|
||||||
|
* @returns True if the fighter can act.
|
||||||
|
*/
|
||||||
|
bool_t battleFighterIsAlive(const battlefighter_t *fighter);
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "party.h"
|
||||||
|
#include "assert/assert.h"
|
||||||
|
#include "util/memory.h"
|
||||||
|
|
||||||
|
party_t PARTY;
|
||||||
|
|
||||||
|
void partyInit(void) {
|
||||||
|
memoryZero(&PARTY, sizeof(party_t));
|
||||||
|
for(uint8_t i = 0; i < PARTY_MEMBER_COUNT_MAX; i++) {
|
||||||
|
PARTY.members[i].id = i;
|
||||||
|
}
|
||||||
|
for(uint8_t i = 0; i < PARTY_ACTIVE_SIZE_MAX; i++) {
|
||||||
|
PARTY.order[i] = PARTY_ORDER_EMPTY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t partyGetAvailableMember(void) {
|
||||||
|
for(uint8_t i = 0; i < PARTY_MEMBER_COUNT_MAX; i++) {
|
||||||
|
if(PARTY.members[i].status == BATTLE_FIGHTER_STATUS_NULL) return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0xFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
battlefighter_t *partyAddMember(
|
||||||
|
const battlefighterstats_t stats,
|
||||||
|
const uint16_t healthMax,
|
||||||
|
const uint16_t mpMax
|
||||||
|
) {
|
||||||
|
const uint8_t index = partyGetAvailableMember();
|
||||||
|
if(index == 0xFF) return NULL;
|
||||||
|
|
||||||
|
battlefighter_t *member = &PARTY.members[index];
|
||||||
|
battleFighterInit(
|
||||||
|
member, BATTLE_FIGHTER_TEAM_ALLY, BATTLE_FIGHTER_CONTROLLER_PLAYER,
|
||||||
|
stats, healthMax, mpMax
|
||||||
|
);
|
||||||
|
|
||||||
|
for(uint8_t i = 0; i < PARTY_ACTIVE_SIZE_MAX; i++) {
|
||||||
|
if(PARTY.order[i] != PARTY_ORDER_EMPTY) continue;
|
||||||
|
PARTY.order[i] = index;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return member;
|
||||||
|
}
|
||||||
|
|
||||||
|
battlefighter_t *partyGetOrderMember(const uint8_t slot) {
|
||||||
|
assertTrue(slot < PARTY_ACTIVE_SIZE_MAX, "Invalid party order slot");
|
||||||
|
|
||||||
|
const uint8_t index = PARTY.order[slot];
|
||||||
|
if(index == PARTY_ORDER_EMPTY) return NULL;
|
||||||
|
return &PARTY.members[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
void partySetOrder(const uint8_t slot, const uint8_t memberIndex) {
|
||||||
|
assertTrue(slot < PARTY_ACTIVE_SIZE_MAX, "Invalid party order slot");
|
||||||
|
assertTrue(
|
||||||
|
memberIndex == PARTY_ORDER_EMPTY || memberIndex < PARTY_MEMBER_COUNT_MAX,
|
||||||
|
"Invalid party member index"
|
||||||
|
);
|
||||||
|
|
||||||
|
PARTY.order[slot] = memberIndex;
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "battlefighter.h"
|
||||||
|
|
||||||
|
#define PARTY_MEMBER_COUNT_MAX 4
|
||||||
|
#define PARTY_ACTIVE_SIZE_MAX 3
|
||||||
|
#define PARTY_ORDER_EMPTY 0xFF
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
battlefighter_t members[PARTY_MEMBER_COUNT_MAX];
|
||||||
|
|
||||||
|
// Maps an active battle slot to the roster member filling it, or
|
||||||
|
// PARTY_ORDER_EMPTY if the slot is unfilled. Only the first
|
||||||
|
// PARTY_ACTIVE_SIZE_MAX of the PARTY_MEMBER_COUNT_MAX roster members
|
||||||
|
// can be in the active lineup at once.
|
||||||
|
uint8_t order[PARTY_ACTIVE_SIZE_MAX];
|
||||||
|
} party_t;
|
||||||
|
|
||||||
|
extern party_t PARTY;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the party system with an empty roster and order.
|
||||||
|
*/
|
||||||
|
void partyInit(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets an available (unused) party member slot index.
|
||||||
|
*
|
||||||
|
* @return The index of an available slot, or 0xFF if the party is full.
|
||||||
|
*/
|
||||||
|
uint8_t partyGetAvailableMember(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a member to the party roster in the next available slot. Party
|
||||||
|
* members are always allies controlled by the player. If there is a
|
||||||
|
* free active order slot, the new member is placed into it.
|
||||||
|
*
|
||||||
|
* @param stats The member's base combat stats.
|
||||||
|
* @param healthMax The member's maximum health.
|
||||||
|
* @param mpMax The member's maximum mp.
|
||||||
|
* @return Pointer to the newly added party member, or NULL if the party is
|
||||||
|
* already full.
|
||||||
|
*/
|
||||||
|
battlefighter_t *partyAddMember(
|
||||||
|
const battlefighterstats_t stats,
|
||||||
|
const uint16_t healthMax,
|
||||||
|
const uint16_t mpMax
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the roster member currently occupying an active order slot.
|
||||||
|
*
|
||||||
|
* @param slot The active order slot to query.
|
||||||
|
* @return Pointer to the member in that slot, or NULL if the slot is
|
||||||
|
* empty.
|
||||||
|
*/
|
||||||
|
battlefighter_t *partyGetOrderMember(const uint8_t slot);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assigns a roster member to an active order slot, replacing whatever
|
||||||
|
* was there. Use PARTY_ORDER_EMPTY to clear a slot.
|
||||||
|
*
|
||||||
|
* @param slot The active order slot to assign.
|
||||||
|
* @param memberIndex The roster member index to place there, or
|
||||||
|
* PARTY_ORDER_EMPTY to clear the slot.
|
||||||
|
*/
|
||||||
|
void partySetOrder(const uint8_t slot, const uint8_t memberIndex);
|
||||||
@@ -6,6 +6,5 @@
|
|||||||
# Sources
|
# Sources
|
||||||
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||||
PUBLIC
|
PUBLIC
|
||||||
savevita.c
|
testbattle.c
|
||||||
savestreamvita.c
|
|
||||||
)
|
)
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "testbattle.h"
|
||||||
|
#include "rpg/battle/battle.h"
|
||||||
|
#include "scene/scene.h"
|
||||||
|
|
||||||
|
void testBattleStart(void) {
|
||||||
|
battleInit();
|
||||||
|
|
||||||
|
const battlefighterstats_t allyOneStats =
|
||||||
|
{ .attack = 10, .defense = 5, .magic = 0, .speed = 10, .luck = 0 };
|
||||||
|
const battlefighterstats_t allyTwoStats =
|
||||||
|
{ .attack = 8, .defense = 4, .magic = 0, .speed = 8, .luck = 0 };
|
||||||
|
const battlefighterstats_t enemyOneStats =
|
||||||
|
{ .attack = 6, .defense = 3, .magic = 0, .speed = 6, .luck = 0 };
|
||||||
|
const battlefighterstats_t enemyTwoStats =
|
||||||
|
{ .attack = 7, .defense = 3, .magic = 0, .speed = 5, .luck = 0 };
|
||||||
|
|
||||||
|
battleAddFighter(
|
||||||
|
BATTLE_FIGHTER_TEAM_ALLY, BATTLE_FIGHTER_CONTROLLER_PLAYER,
|
||||||
|
allyOneStats, 30, 10
|
||||||
|
);
|
||||||
|
battleAddFighter(
|
||||||
|
BATTLE_FIGHTER_TEAM_ALLY, BATTLE_FIGHTER_CONTROLLER_PLAYER,
|
||||||
|
allyTwoStats, 25, 10
|
||||||
|
);
|
||||||
|
battleAddFighter(
|
||||||
|
BATTLE_FIGHTER_TEAM_ENEMY, BATTLE_FIGHTER_CONTROLLER_AI,
|
||||||
|
enemyOneStats, 20, 5
|
||||||
|
);
|
||||||
|
battleAddFighter(
|
||||||
|
BATTLE_FIGHTER_TEAM_ENEMY, BATTLE_FIGHTER_CONTROLLER_AI,
|
||||||
|
enemyTwoStats, 20, 5
|
||||||
|
);
|
||||||
|
|
||||||
|
battleStart(BATTLE_ENCOUNTER_REGULAR, true);
|
||||||
|
sceneSet(SCENE_TYPE_BATTLE);
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "error/error.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TEMPORARY test hook: sets up a hardcoded mock battle and switches to the
|
||||||
|
* battle scene, so the battle scene (camera/fighters/HUD) can be seen and
|
||||||
|
* played without a real encounter trigger yet.
|
||||||
|
*/
|
||||||
|
void testBattleStart(void);
|
||||||
@@ -13,14 +13,19 @@ typedef struct cutscene_s {
|
|||||||
const cutsceneitem_t *items;
|
const cutsceneitem_t *items;
|
||||||
uint8_t itemCount;
|
uint8_t itemCount;
|
||||||
cutscenepause_t pause;
|
cutscenepause_t pause;
|
||||||
|
|
||||||
|
// Size in bytes of this cutscene's custom user data, carved out of
|
||||||
|
// CUTSCENE_SYSTEM.data while the cutscene is running.
|
||||||
|
size_t dataSize;
|
||||||
} cutscene_t;
|
} cutscene_t;
|
||||||
|
|
||||||
#define CUTSCENE(NAME, PAUSE_TYPE, ...) \
|
#define CUTSCENE(NAME, SIZE, PAUSE_TYPE, ...) \
|
||||||
static const cutsceneitem_t CUTSCENE_##NAME##_ITEMS[] = { __VA_ARGS__ }; \
|
static const cutsceneitem_t CUTSCENE_##NAME##_ITEMS[] = { __VA_ARGS__ }; \
|
||||||
static const cutscene_t CUTSCENE_##NAME = { \
|
static const cutscene_t CUTSCENE_##NAME = { \
|
||||||
.items = CUTSCENE_##NAME##_ITEMS, \
|
.items = CUTSCENE_##NAME##_ITEMS, \
|
||||||
.itemCount = sizeof(CUTSCENE_##NAME##_ITEMS) / sizeof(cutsceneitem_t), \
|
.itemCount = sizeof(CUTSCENE_##NAME##_ITEMS) / sizeof(cutsceneitem_t), \
|
||||||
.pause = CUTSCENE_PAUSE_##PAUSE_TYPE \
|
.pause = CUTSCENE_PAUSE_##PAUSE_TYPE, \
|
||||||
|
.dataSize = SIZE \
|
||||||
};
|
};
|
||||||
|
|
||||||
#define CUTSCENE_REFERENCE(CUTSCENE) \
|
#define CUTSCENE_REFERENCE(CUTSCENE) \
|
||||||
@@ -29,6 +34,22 @@ typedef struct cutscene_s {
|
|||||||
#define CUTSCENE_TEXT(TEXT) \
|
#define CUTSCENE_TEXT(TEXT) \
|
||||||
{ .type = CUTSCENE_ITEM_TYPE_TEXT, .text = { .text = TEXT } }
|
{ .type = CUTSCENE_ITEM_TYPE_TEXT, .text = { .text = TEXT } }
|
||||||
|
|
||||||
|
#define CUTSCENE_TEXT_MINI(TEXT, X, Y, Z, DURATION) \
|
||||||
|
{ \
|
||||||
|
.type = CUTSCENE_ITEM_TYPE_TEXT_MINI, \
|
||||||
|
.textMini = { \
|
||||||
|
.text = TEXT, \
|
||||||
|
.position = { X, Y, Z }, \
|
||||||
|
.duration = DURATION \
|
||||||
|
} \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define CUTSCENE_TEXT_MINI_HIDE(INDEX) \
|
||||||
|
{ \
|
||||||
|
.type = CUTSCENE_ITEM_TYPE_TEXT_MINI_HIDE, \
|
||||||
|
.textMiniHide = { .index = INDEX } \
|
||||||
|
}
|
||||||
|
|
||||||
#define CUTSCENE_WAIT(WAIT) \
|
#define CUTSCENE_WAIT(WAIT) \
|
||||||
{ .type = CUTSCENE_ITEM_TYPE_WAIT, .wait = WAIT }
|
{ .type = CUTSCENE_ITEM_TYPE_WAIT, .wait = WAIT }
|
||||||
|
|
||||||
@@ -70,6 +91,35 @@ typedef struct cutscene_s {
|
|||||||
.entityRemove = { .entityIndex = ENTITY_INDEX } \
|
.entityRemove = { .entityIndex = ENTITY_INDEX } \
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#define CUTSCENE_ENTITY_ADD(TYPE, X, Y, Z) \
|
||||||
|
{ \
|
||||||
|
.type = CUTSCENE_ITEM_TYPE_ENTITY_ADD, \
|
||||||
|
.entityAdd = { .entityType = TYPE, .position = { X, Y, Z } } \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define CUTSCENE_ENTITY_TURN(ENTITY_INDEX, DIRECTION) \
|
||||||
|
{ \
|
||||||
|
.type = CUTSCENE_ITEM_TYPE_ENTITY_TURN, \
|
||||||
|
.entityTurn = { .entityIndex = ENTITY_INDEX, .direction = DIRECTION } \
|
||||||
|
}
|
||||||
|
|
||||||
|
// Walks ENTITY_INDEX to stand beside TARGET_ENTITY_INDEX, offset by
|
||||||
|
// (OFFSET_X, OFFSET_Y) on the 2D plane. The destination Z is resolved
|
||||||
|
// from nearby terrain each frame, so ramps between the two entities are
|
||||||
|
// accounted for automatically.
|
||||||
|
#define CUTSCENE_ENTITY_WALK_TO_ENTITY( \
|
||||||
|
ENTITY_INDEX, TARGET_ENTITY_INDEX, OFFSET_X, OFFSET_Y \
|
||||||
|
) \
|
||||||
|
{ \
|
||||||
|
.type = CUTSCENE_ITEM_TYPE_ENTITY_WALK_TO_ENTITY, \
|
||||||
|
.entityWalkToEntity = { \
|
||||||
|
.entityIndex = ENTITY_INDEX, \
|
||||||
|
.targetEntityIndex = TARGET_ENTITY_INDEX, \
|
||||||
|
.offsetX = OFFSET_X, \
|
||||||
|
.offsetY = OFFSET_Y \
|
||||||
|
} \
|
||||||
|
}
|
||||||
|
|
||||||
#define CUTSCENE_ENTITY_TELEPORT(ENTITY_INDEX, X, Y, Z) \
|
#define CUTSCENE_ENTITY_TELEPORT(ENTITY_INDEX, X, Y, Z) \
|
||||||
{ \
|
{ \
|
||||||
.type = CUTSCENE_ITEM_TYPE_ENTITY_TELEPORT, \
|
.type = CUTSCENE_ITEM_TYPE_ENTITY_TELEPORT, \
|
||||||
@@ -94,6 +144,46 @@ typedef struct cutscene_s {
|
|||||||
#define CUTSCENE_FADE_FROM_WHITE(DURATION) \
|
#define CUTSCENE_FADE_FROM_WHITE(DURATION) \
|
||||||
CUTSCENE_FADE(COLOR_WHITE, COLOR_TRANSPARENT_WHITE, DURATION, EASING_LINEAR)
|
CUTSCENE_FADE(COLOR_WHITE, COLOR_TRANSPARENT_WHITE, DURATION, EASING_LINEAR)
|
||||||
|
|
||||||
|
#define CUTSCENE_EMOJI(ENTITY_INDEX, EMOJI_TYPE, DURATION) \
|
||||||
|
{ \
|
||||||
|
.type = CUTSCENE_ITEM_TYPE_EMOJI, \
|
||||||
|
.emoji = { \
|
||||||
|
.entityIndex = ENTITY_INDEX, \
|
||||||
|
.emojiType = EMOJI_TYPE, \
|
||||||
|
.duration = DURATION \
|
||||||
|
} \
|
||||||
|
}
|
||||||
|
|
||||||
|
// AMOUNT ranges 0 (no shake) to 4 (three tiles): 1 is half a tile, 2 is
|
||||||
|
// a full tile, 3 is two tiles, and 4 is three tiles.
|
||||||
|
#define CUTSCENE_SHAKE(AMOUNT, DURATION) \
|
||||||
|
{ \
|
||||||
|
.type = CUTSCENE_ITEM_TYPE_SHAKE, \
|
||||||
|
.shake = { .amount = AMOUNT, .duration = DURATION } \
|
||||||
|
}
|
||||||
|
|
||||||
|
// Waits until BATTLE.state reaches STATE. Put this BEFORE
|
||||||
|
// CUTSCENE_SET_PAUSE(CUTSCENE_PAUSE_BATTLE), not after -- pausing first
|
||||||
|
// freezes BATTLE.state wherever it already is, so it would never reach
|
||||||
|
// STATE on its own to satisfy the wait. Waiting unpaused, then pausing the
|
||||||
|
// moment it's satisfied, catches the battle right at STATE before it can
|
||||||
|
// advance further.
|
||||||
|
#define CUTSCENE_BATTLE_WAIT_STATE(STATE) \
|
||||||
|
{ \
|
||||||
|
.type = CUTSCENE_ITEM_TYPE_BATTLE_WAIT_STATE, \
|
||||||
|
.battleWaitState = { .state = STATE } \
|
||||||
|
}
|
||||||
|
|
||||||
|
// Immediately queues an attack for FIGHTER_INDEX against TARGET_INDEX,
|
||||||
|
// bypassing normal player/AI selection for that fighter this round.
|
||||||
|
#define CUTSCENE_BATTLE_FORCE_ACTION(FIGHTER_INDEX, TARGET_INDEX) \
|
||||||
|
{ \
|
||||||
|
.type = CUTSCENE_ITEM_TYPE_BATTLE_FORCE_ACTION, \
|
||||||
|
.battleForceAction = { \
|
||||||
|
.fighterIndex = FIGHTER_INDEX, .targetIndex = TARGET_INDEX \
|
||||||
|
} \
|
||||||
|
}
|
||||||
|
|
||||||
#define CUTSCENE_SET_PAUSE(FLAGS) \
|
#define CUTSCENE_SET_PAUSE(FLAGS) \
|
||||||
{ .type = CUTSCENE_ITEM_TYPE_SET_PAUSE, .setPause = (FLAGS) }
|
{ .type = CUTSCENE_ITEM_TYPE_SET_PAUSE, .setPause = (FLAGS) }
|
||||||
|
|
||||||
@@ -116,3 +206,49 @@ typedef struct cutscene_s {
|
|||||||
) \
|
) \
|
||||||
} \
|
} \
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#define CUTSCENE_MAP_AREA_ADD( \
|
||||||
|
MIN_X, MIN_Y, MIN_Z, MAX_X, MAX_Y, MAX_Z, CALLBACK, NOTIFY, TRIGGER \
|
||||||
|
) \
|
||||||
|
{ \
|
||||||
|
.type = CUTSCENE_ITEM_TYPE_MAP_AREA_ADD, \
|
||||||
|
.mapAreaAdd = { \
|
||||||
|
.min = { MIN_X, MIN_Y, MIN_Z }, \
|
||||||
|
.max = { MAX_X, MAX_Y, MAX_Z }, \
|
||||||
|
.callback = CALLBACK, \
|
||||||
|
.notify = NOTIFY, \
|
||||||
|
.trigger = TRIGGER \
|
||||||
|
} \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define CUTSCENE_MAP_AREA_REMOVE(AREA_ID) \
|
||||||
|
{ \
|
||||||
|
.type = CUTSCENE_ITEM_TYPE_MAP_AREA_REMOVE, \
|
||||||
|
.mapAreaRemove = { .areaId = AREA_ID } \
|
||||||
|
}
|
||||||
|
|
||||||
|
// Waits until any one of the given map area IDs has its callback invoked.
|
||||||
|
// Accepts CUTSCENE_AREA_LAST_CREATED in place of a literal area ID.
|
||||||
|
#define CUTSCENE_MAP_AREA_WAIT(...) \
|
||||||
|
{ \
|
||||||
|
.type = CUTSCENE_ITEM_TYPE_MAP_AREA_WAIT, \
|
||||||
|
.mapAreaWait = { \
|
||||||
|
.areaIds = (const uint8_t[]){ __VA_ARGS__ }, \
|
||||||
|
.count = (uint8_t)( \
|
||||||
|
sizeof((const uint8_t[]){ __VA_ARGS__ }) / sizeof(uint8_t) \
|
||||||
|
) \
|
||||||
|
} \
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adds a map area, waits for it to be triggered once, then removes it
|
||||||
|
// before the cutscene continues. Uses a no-op callback since the wait is
|
||||||
|
// driven by the area's trigger count rather than callback logic.
|
||||||
|
#define CUTSCENE_MAP_AREA_TRIGGER_ONCE( \
|
||||||
|
MIN_X, MIN_Y, MIN_Z, MAX_X, MAX_Y, MAX_Z, NOTIFY, TRIGGER \
|
||||||
|
) \
|
||||||
|
CUTSCENE_MAP_AREA_ADD( \
|
||||||
|
MIN_X, MIN_Y, MIN_Z, MAX_X, MAX_Y, MAX_Z, \
|
||||||
|
mapAreaNoopCallback, NOTIFY, TRIGGER \
|
||||||
|
), \
|
||||||
|
CUTSCENE_MAP_AREA_WAIT(CUTSCENE_AREA_LAST_CREATED), \
|
||||||
|
CUTSCENE_MAP_AREA_REMOVE(CUTSCENE_AREA_LAST_CREATED)
|
||||||
|
|||||||
@@ -14,11 +14,13 @@ typedef uint8_t cutscenepause_t;
|
|||||||
#define CUTSCENE_PAUSE_NPC ((cutscenepause_t)(1 << 0))
|
#define CUTSCENE_PAUSE_NPC ((cutscenepause_t)(1 << 0))
|
||||||
#define CUTSCENE_PAUSE_PLAYER ((cutscenepause_t)(1 << 1))
|
#define CUTSCENE_PAUSE_PLAYER ((cutscenepause_t)(1 << 1))
|
||||||
#define CUTSCENE_PAUSE_WORLD ((cutscenepause_t)(1 << 2))
|
#define CUTSCENE_PAUSE_WORLD ((cutscenepause_t)(1 << 2))
|
||||||
|
#define CUTSCENE_PAUSE_BATTLE ((cutscenepause_t)(1 << 3))
|
||||||
|
|
||||||
#define CUTSCENE_PAUSE_DEFAULT ((cutscenepause_t)( \
|
#define CUTSCENE_PAUSE_DEFAULT ((cutscenepause_t)( \
|
||||||
CUTSCENE_PAUSE_NPC | CUTSCENE_PAUSE_PLAYER \
|
CUTSCENE_PAUSE_NPC | CUTSCENE_PAUSE_PLAYER \
|
||||||
))
|
))
|
||||||
|
|
||||||
#define CUTSCENE_PAUSE_ALL ((cutscenepause_t)( \
|
#define CUTSCENE_PAUSE_ALL ((cutscenepause_t)( \
|
||||||
CUTSCENE_PAUSE_NPC | CUTSCENE_PAUSE_PLAYER | CUTSCENE_PAUSE_WORLD \
|
CUTSCENE_PAUSE_NPC | CUTSCENE_PAUSE_PLAYER | CUTSCENE_PAUSE_WORLD | \
|
||||||
|
CUTSCENE_PAUSE_BATTLE \
|
||||||
))
|
))
|
||||||
|
|||||||
@@ -25,10 +25,19 @@ void cutsceneSystemStartCutsceneWith(
|
|||||||
entity_t *interact,
|
entity_t *interact,
|
||||||
entity_t *interacted
|
entity_t *interacted
|
||||||
) {
|
) {
|
||||||
|
assertTrue(
|
||||||
|
cutscene->dataSize < CUTSCENE_SYSTEM_SIZE_MAX,
|
||||||
|
"Cutscene data size exceeds CUTSCENE_SYSTEM_SIZE_MAX"
|
||||||
|
);
|
||||||
|
|
||||||
CUTSCENE_SYSTEM.scene = cutscene;
|
CUTSCENE_SYSTEM.scene = cutscene;
|
||||||
CUTSCENE_SYSTEM.pause = cutscene->pause;
|
CUTSCENE_SYSTEM.pause = cutscene->pause;
|
||||||
CUTSCENE_SYSTEM.entityInteract = interact;
|
CUTSCENE_SYSTEM.entityInteract = interact;
|
||||||
CUTSCENE_SYSTEM.entityInteracted = interacted;
|
CUTSCENE_SYSTEM.entityInteracted = interacted;
|
||||||
|
CUTSCENE_SYSTEM.entityLastCreated = NULL;
|
||||||
|
CUTSCENE_SYSTEM.entityLastRef = NULL;
|
||||||
|
CUTSCENE_SYSTEM.areaLastCreated = CUTSCENE_AREA_LAST_CREATED;
|
||||||
|
CUTSCENE_SYSTEM.textMiniLastCreated = CUTSCENE_TEXT_MINI_LAST_CREATED;
|
||||||
CUTSCENE_SYSTEM.currentItem = 0xFF;// Set to 0xFF so Next wraps to 0.
|
CUTSCENE_SYSTEM.currentItem = 0xFF;// Set to 0xFF so Next wraps to 0.
|
||||||
cutsceneSystemNext();
|
cutsceneSystemNext();
|
||||||
}
|
}
|
||||||
@@ -54,6 +63,10 @@ void cutsceneSystemNext() {
|
|||||||
CUTSCENE_SYSTEM.pause = CUTSCENE_PAUSE_NONE;
|
CUTSCENE_SYSTEM.pause = CUTSCENE_PAUSE_NONE;
|
||||||
CUTSCENE_SYSTEM.entityInteract = NULL;
|
CUTSCENE_SYSTEM.entityInteract = NULL;
|
||||||
CUTSCENE_SYSTEM.entityInteracted = NULL;
|
CUTSCENE_SYSTEM.entityInteracted = NULL;
|
||||||
|
CUTSCENE_SYSTEM.entityLastCreated = NULL;
|
||||||
|
CUTSCENE_SYSTEM.entityLastRef = NULL;
|
||||||
|
CUTSCENE_SYSTEM.areaLastCreated = CUTSCENE_AREA_LAST_CREATED;
|
||||||
|
CUTSCENE_SYSTEM.textMiniLastCreated = CUTSCENE_TEXT_MINI_LAST_CREATED;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,25 +83,65 @@ const cutsceneitem_t * cutsceneSystemGetCurrentItem() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
entity_t * cutsceneSystemGetEntity(const uint8_t entityIndex) {
|
entity_t * cutsceneSystemGetEntity(const uint8_t entityIndex) {
|
||||||
|
entity_t *entity;
|
||||||
|
|
||||||
if(entityIndex == CUTSCENE_ENTITY_INTERACT) {
|
if(entityIndex == CUTSCENE_ENTITY_INTERACT) {
|
||||||
assertNotNull(
|
assertNotNull(
|
||||||
CUTSCENE_SYSTEM.entityInteract,
|
CUTSCENE_SYSTEM.entityInteract,
|
||||||
"CUTSCENE_ENTITY_INTERACT used but no interact entity is set"
|
"CUTSCENE_ENTITY_INTERACT used but no interact entity is set"
|
||||||
);
|
);
|
||||||
return CUTSCENE_SYSTEM.entityInteract;
|
entity = CUTSCENE_SYSTEM.entityInteract;
|
||||||
}
|
} else if(entityIndex == CUTSCENE_ENTITY_INTERACTED) {
|
||||||
if(entityIndex == CUTSCENE_ENTITY_INTERACTED) {
|
|
||||||
assertNotNull(
|
assertNotNull(
|
||||||
CUTSCENE_SYSTEM.entityInteracted,
|
CUTSCENE_SYSTEM.entityInteracted,
|
||||||
"CUTSCENE_ENTITY_INTERACTED used but no interacted entity is set"
|
"CUTSCENE_ENTITY_INTERACTED used but no interacted entity is set"
|
||||||
);
|
);
|
||||||
return CUTSCENE_SYSTEM.entityInteracted;
|
entity = CUTSCENE_SYSTEM.entityInteracted;
|
||||||
|
} else if(entityIndex == CUTSCENE_ENTITY_LAST_CREATED) {
|
||||||
|
assertNotNull(
|
||||||
|
CUTSCENE_SYSTEM.entityLastCreated,
|
||||||
|
"CUTSCENE_ENTITY_LAST_CREATED used but no entity has been created"
|
||||||
|
);
|
||||||
|
entity = CUTSCENE_SYSTEM.entityLastCreated;
|
||||||
|
} else if(entityIndex == CUTSCENE_ENTITY_LAST_REF) {
|
||||||
|
assertNotNull(
|
||||||
|
CUTSCENE_SYSTEM.entityLastRef,
|
||||||
|
"CUTSCENE_ENTITY_LAST_REF used but no entity has been referenced"
|
||||||
|
);
|
||||||
|
entity = CUTSCENE_SYSTEM.entityLastRef;
|
||||||
|
} else {
|
||||||
|
assertTrue(
|
||||||
|
entityIndex < ENTITY_COUNT,
|
||||||
|
"Entity index is out of range"
|
||||||
|
);
|
||||||
|
entity = &ENTITIES[entityIndex];
|
||||||
}
|
}
|
||||||
assertTrue(
|
|
||||||
entityIndex < ENTITY_COUNT,
|
CUTSCENE_SYSTEM.entityLastRef = entity;
|
||||||
"Entity index is out of range"
|
return entity;
|
||||||
);
|
}
|
||||||
return &ENTITIES[entityIndex];
|
|
||||||
|
uint8_t cutsceneSystemGetAreaId(const uint8_t areaId) {
|
||||||
|
if(areaId == CUTSCENE_AREA_LAST_CREATED) {
|
||||||
|
assertTrue(
|
||||||
|
CUTSCENE_SYSTEM.areaLastCreated != CUTSCENE_AREA_LAST_CREATED,
|
||||||
|
"CUTSCENE_AREA_LAST_CREATED used but no map area has been created"
|
||||||
|
);
|
||||||
|
return CUTSCENE_SYSTEM.areaLastCreated;
|
||||||
|
}
|
||||||
|
return areaId;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t cutsceneSystemGetTextMiniId(const uint8_t index) {
|
||||||
|
if(index == CUTSCENE_TEXT_MINI_LAST_CREATED) {
|
||||||
|
assertTrue(
|
||||||
|
CUTSCENE_SYSTEM.textMiniLastCreated != CUTSCENE_TEXT_MINI_LAST_CREATED,
|
||||||
|
"CUTSCENE_TEXT_MINI_LAST_CREATED used but no mini textbox has been "
|
||||||
|
"shown"
|
||||||
|
);
|
||||||
|
return CUTSCENE_SYSTEM.textMiniLastCreated;
|
||||||
|
}
|
||||||
|
return index;
|
||||||
}
|
}
|
||||||
|
|
||||||
void cutsceneSystemDispose() {
|
void cutsceneSystemDispose() {
|
||||||
@@ -97,4 +150,8 @@ void cutsceneSystemDispose() {
|
|||||||
CUTSCENE_SYSTEM.pause = CUTSCENE_PAUSE_NONE;
|
CUTSCENE_SYSTEM.pause = CUTSCENE_PAUSE_NONE;
|
||||||
CUTSCENE_SYSTEM.entityInteract = NULL;
|
CUTSCENE_SYSTEM.entityInteract = NULL;
|
||||||
CUTSCENE_SYSTEM.entityInteracted = NULL;
|
CUTSCENE_SYSTEM.entityInteracted = NULL;
|
||||||
|
CUTSCENE_SYSTEM.entityLastCreated = NULL;
|
||||||
|
CUTSCENE_SYSTEM.entityLastRef = NULL;
|
||||||
|
CUTSCENE_SYSTEM.areaLastCreated = CUTSCENE_AREA_LAST_CREATED;
|
||||||
|
CUTSCENE_SYSTEM.textMiniLastCreated = CUTSCENE_TEXT_MINI_LAST_CREATED;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,8 +10,16 @@
|
|||||||
|
|
||||||
typedef struct entity_s entity_t;
|
typedef struct entity_s entity_t;
|
||||||
|
|
||||||
#define CUTSCENE_ENTITY_INTERACT ((uint8_t)0xFE)
|
#define CUTSCENE_ENTITY_INTERACT ((uint8_t)0xFE)
|
||||||
#define CUTSCENE_ENTITY_INTERACTED ((uint8_t)0xFD)
|
#define CUTSCENE_ENTITY_INTERACTED ((uint8_t)0xFD)
|
||||||
|
#define CUTSCENE_ENTITY_LAST_CREATED ((uint8_t)0xFC)
|
||||||
|
#define CUTSCENE_ENTITY_LAST_REF ((uint8_t)0xFB)
|
||||||
|
#define CUTSCENE_AREA_LAST_CREATED ((uint8_t)0xFF)
|
||||||
|
#define CUTSCENE_TEXT_MINI_LAST_CREATED ((uint8_t)0xFA)
|
||||||
|
|
||||||
|
// Maximum number of bytes a running cutscene may request via
|
||||||
|
// cutscene_t.dataSize.
|
||||||
|
#define CUTSCENE_SYSTEM_SIZE_MAX 8192
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
const cutscene_t *scene;
|
const cutscene_t *scene;
|
||||||
@@ -19,9 +27,17 @@ typedef struct {
|
|||||||
cutscenepause_t pause;
|
cutscenepause_t pause;
|
||||||
entity_t *entityInteract;
|
entity_t *entityInteract;
|
||||||
entity_t *entityInteracted;
|
entity_t *entityInteracted;
|
||||||
|
entity_t *entityLastCreated;
|
||||||
|
entity_t *entityLastRef;
|
||||||
|
uint8_t areaLastCreated;
|
||||||
|
uint8_t textMiniLastCreated;
|
||||||
|
|
||||||
// Data (used by the current item).
|
// Data (used by the current item).
|
||||||
cutsceneitemdata_t data;
|
cutsceneitemdata_t data;
|
||||||
|
|
||||||
|
// Custom user data for the running cutscene, sized per-scene by
|
||||||
|
// cutscene_t.dataSize.
|
||||||
|
uint8_t userData[CUTSCENE_SYSTEM_SIZE_MAX];
|
||||||
} cutscenesystem_t;
|
} cutscenesystem_t;
|
||||||
|
|
||||||
extern cutscenesystem_t CUTSCENE_SYSTEM;
|
extern cutscenesystem_t CUTSCENE_SYSTEM;
|
||||||
@@ -53,7 +69,9 @@ void cutsceneSystemStartCutsceneWith(
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolves a raw entity index (or sentinel) to an entity pointer.
|
* Resolves a raw entity index (or sentinel) to an entity pointer.
|
||||||
* Handles CUTSCENE_ENTITY_INTERACT and CUTSCENE_ENTITY_INTERACTED.
|
* Handles CUTSCENE_ENTITY_INTERACT, CUTSCENE_ENTITY_INTERACTED,
|
||||||
|
* CUTSCENE_ENTITY_LAST_CREATED and CUTSCENE_ENTITY_LAST_REF.
|
||||||
|
* Updates CUTSCENE_SYSTEM.entityLastRef to the resolved entity.
|
||||||
* Asserts the resolved entity is within bounds.
|
* Asserts the resolved entity is within bounds.
|
||||||
*
|
*
|
||||||
* @param entityIndex Raw entity index or sentinel value.
|
* @param entityIndex Raw entity index or sentinel value.
|
||||||
@@ -61,6 +79,24 @@ void cutsceneSystemStartCutsceneWith(
|
|||||||
*/
|
*/
|
||||||
entity_t * cutsceneSystemGetEntity(const uint8_t entityIndex);
|
entity_t * cutsceneSystemGetEntity(const uint8_t entityIndex);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves a raw map area ID (or CUTSCENE_AREA_LAST_CREATED sentinel) to
|
||||||
|
* a concrete map area ID.
|
||||||
|
*
|
||||||
|
* @param areaId Raw map area ID or sentinel value.
|
||||||
|
* @returns The resolved map area ID.
|
||||||
|
*/
|
||||||
|
uint8_t cutsceneSystemGetAreaId(const uint8_t areaId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves a raw mini textbox slot index (or CUTSCENE_TEXT_MINI_LAST_CREATED
|
||||||
|
* sentinel) to a concrete UI_TEXTBOX_MINI_LIST slot index.
|
||||||
|
*
|
||||||
|
* @param index Raw slot index or sentinel value.
|
||||||
|
* @returns The resolved slot index.
|
||||||
|
*/
|
||||||
|
uint8_t cutsceneSystemGetTextMiniId(const uint8_t index);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Advance to the next item in the cutscene.
|
* Advance to the next item in the cutscene.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -12,4 +12,6 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
|||||||
add_subdirectory(control)
|
add_subdirectory(control)
|
||||||
add_subdirectory(entity)
|
add_subdirectory(entity)
|
||||||
add_subdirectory(item)
|
add_subdirectory(item)
|
||||||
|
add_subdirectory(maparea)
|
||||||
add_subdirectory(ui)
|
add_subdirectory(ui)
|
||||||
|
add_subdirectory(battle)
|
||||||
|
|||||||
@@ -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
|
||||||
|
cutscenestartbattle.c
|
||||||
|
cutscenebattlewaitstate.c
|
||||||
|
cutscenebattleforceaction.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"
|
||||||
|
|
||||||
|
void cutsceneBattleForceActionStart(
|
||||||
|
const cutsceneitem_t *item,
|
||||||
|
cutsceneitemdata_t *data
|
||||||
|
) {
|
||||||
|
battleQueueAction(
|
||||||
|
item->battleForceAction.fighterIndex, BATTLE_ACTION_ATTACK,
|
||||||
|
item->battleForceAction.targetIndex
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool_t cutsceneBattleForceActionUpdate(
|
||||||
|
const cutsceneitem_t *item,
|
||||||
|
cutsceneitemdata_t *data
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "rpg/battle/battle.h"
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint8_t fighterIndex;
|
||||||
|
uint8_t targetIndex;
|
||||||
|
} cutscenebattleforceaction_t;
|
||||||
|
|
||||||
|
typedef struct cutsceneitem_s cutsceneitem_t;
|
||||||
|
typedef union cutsceneitemdata_u cutsceneitemdata_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts a battle force-action step: immediately queues an attack for the
|
||||||
|
* given fighter against the given target, bypassing normal player/AI
|
||||||
|
* selection for that fighter this round.
|
||||||
|
*
|
||||||
|
* @param item The cutscene item.
|
||||||
|
* @param data Runtime data storage.
|
||||||
|
*/
|
||||||
|
void cutsceneBattleForceActionStart(
|
||||||
|
const cutsceneitem_t *item,
|
||||||
|
cutsceneitemdata_t *data
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates a battle force-action step (always completes immediately).
|
||||||
|
*
|
||||||
|
* @param item The cutscene item.
|
||||||
|
* @param data Runtime data storage.
|
||||||
|
* @returns true always.
|
||||||
|
*/
|
||||||
|
bool_t cutsceneBattleForceActionUpdate(
|
||||||
|
const cutsceneitem_t *item,
|
||||||
|
cutsceneitemdata_t *data
|
||||||
|
);
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "rpg/cutscene/item/cutsceneitem.h"
|
||||||
|
|
||||||
|
bool_t cutsceneBattleWaitStateUpdate(
|
||||||
|
const cutsceneitem_t *item,
|
||||||
|
cutsceneitemdata_t *data
|
||||||
|
) {
|
||||||
|
return BATTLE.state == item->battleWaitState.state;
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "rpg/battle/battle.h"
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
battlestate_t state;
|
||||||
|
} cutscenebattlewaitstate_t;
|
||||||
|
|
||||||
|
typedef struct cutsceneitem_s cutsceneitem_t;
|
||||||
|
typedef union cutsceneitemdata_u cutsceneitemdata_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates a battle wait-state step, completing once BATTLE.state reaches
|
||||||
|
* the watched state. Has no Start callback -- there's nothing to do until
|
||||||
|
* the state is actually reached.
|
||||||
|
*
|
||||||
|
* @param item The cutscene item.
|
||||||
|
* @param data Runtime data storage.
|
||||||
|
* @returns true once BATTLE.state equals the watched state.
|
||||||
|
*/
|
||||||
|
bool_t cutsceneBattleWaitStateUpdate(
|
||||||
|
const cutsceneitem_t *item,
|
||||||
|
cutsceneitemdata_t *data
|
||||||
|
);
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
/**
|
||||||
|
* 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/battle/party.h"
|
||||||
|
#include "scene/scene.h"
|
||||||
|
|
||||||
|
void cutsceneStartBattleStart(
|
||||||
|
const cutsceneitem_t *item,
|
||||||
|
cutsceneitemdata_t *data
|
||||||
|
) {
|
||||||
|
const cutscenestartbattle_t *config = &item->startBattle;
|
||||||
|
|
||||||
|
battleInit();
|
||||||
|
|
||||||
|
for(uint8_t i = 0; i < PARTY_ACTIVE_SIZE_MAX; i++) {
|
||||||
|
battlefighter_t *member = partyGetOrderMember(i);
|
||||||
|
if(member == NULL) continue;
|
||||||
|
|
||||||
|
battlefighter_t *fighter = battleAddFighter(
|
||||||
|
BATTLE_FIGHTER_TEAM_ALLY, BATTLE_FIGHTER_CONTROLLER_PLAYER,
|
||||||
|
member->stats, member->healthMax, member->mpMax
|
||||||
|
);
|
||||||
|
if(fighter == NULL) continue;
|
||||||
|
|
||||||
|
fighter->health = member->health;
|
||||||
|
fighter->mp = member->mp;
|
||||||
|
fighter->status = member->status;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(uint8_t i = 0; i < config->enemyCount; i++) {
|
||||||
|
const cutscenestartbattleenemy_t *enemy = &config->enemies[i];
|
||||||
|
battleAddFighter(
|
||||||
|
BATTLE_FIGHTER_TEAM_ENEMY, BATTLE_FIGHTER_CONTROLLER_AI,
|
||||||
|
enemy->stats, enemy->healthMax, enemy->mpMax
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
battleStart(config->encounterType, config->fleeAvailable);
|
||||||
|
sceneSet(SCENE_TYPE_BATTLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool_t cutsceneStartBattleUpdate(
|
||||||
|
const cutsceneitem_t *item,
|
||||||
|
cutsceneitemdata_t *data
|
||||||
|
) {
|
||||||
|
if(BATTLE.result == BATTLE_RESULT_NONE) return false;
|
||||||
|
|
||||||
|
// Sync ally HP/MP back to the persistent party roster. Relies on
|
||||||
|
// ally fighters having been added to BATTLE.fighters in the same
|
||||||
|
// order partyGetOrderMember() iterates, starting at index 0 (see
|
||||||
|
// cutsceneStartBattleStart).
|
||||||
|
uint8_t allySlot = 0;
|
||||||
|
for(uint8_t i = 0; i < PARTY_ACTIVE_SIZE_MAX; i++) {
|
||||||
|
battlefighter_t *member = partyGetOrderMember(i);
|
||||||
|
if(member == NULL) continue;
|
||||||
|
|
||||||
|
battlefighter_t *fighter = &BATTLE.fighters[allySlot++];
|
||||||
|
member->health = fighter->health;
|
||||||
|
member->mp = fighter->mp;
|
||||||
|
member->status = fighter->status;
|
||||||
|
}
|
||||||
|
|
||||||
|
sceneSet(SCENE_TYPE_OVERWORLD);
|
||||||
|
battleDispose();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "rpg/battle/battle.h"
|
||||||
|
|
||||||
|
#define CUTSCENE_START_BATTLE_ENEMY_COUNT_MAX 4
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
battlefighterstats_t stats;
|
||||||
|
uint16_t healthMax;
|
||||||
|
uint16_t mpMax;
|
||||||
|
} cutscenestartbattleenemy_t;
|
||||||
|
|
||||||
|
typedef struct cutsceneitem_s cutsceneitem_t;
|
||||||
|
typedef union cutsceneitemdata_u cutsceneitemdata_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
battleencountertype_t encounterType;
|
||||||
|
bool_t fleeAvailable;
|
||||||
|
uint8_t enemyCount;
|
||||||
|
cutscenestartbattleenemy_t enemies[CUTSCENE_START_BATTLE_ENEMY_COUNT_MAX];
|
||||||
|
} cutscenestartbattle_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts a battle: seeds BATTLE with the party's active order members
|
||||||
|
* and the item's configured enemies, then switches to the battle
|
||||||
|
* scene.
|
||||||
|
*
|
||||||
|
* @param item The cutscene item.
|
||||||
|
* @param data Runtime data storage.
|
||||||
|
*/
|
||||||
|
void cutsceneStartBattleStart(
|
||||||
|
const cutsceneitem_t *item,
|
||||||
|
cutsceneitemdata_t *data
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Waits for the battle to produce a result, syncs ally HP/MP back to
|
||||||
|
* the party roster, then returns to the overworld scene.
|
||||||
|
*
|
||||||
|
* @param item The cutscene item.
|
||||||
|
* @param data Runtime data storage.
|
||||||
|
* @returns true once the battle has ended.
|
||||||
|
*/
|
||||||
|
bool_t cutsceneStartBattleUpdate(
|
||||||
|
const cutsceneitem_t *item,
|
||||||
|
cutsceneitemdata_t *data
|
||||||
|
);
|
||||||
@@ -6,12 +6,13 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "cutsceneitem.h"
|
#include "cutsceneitem.h"
|
||||||
|
#include "rpg/cutscene/cutscenesystem.h"
|
||||||
|
|
||||||
void cutsceneCallbackStart(
|
void cutsceneCallbackStart(
|
||||||
const cutsceneitem_t *item,
|
const cutsceneitem_t *item,
|
||||||
cutsceneitemdata_t *data
|
cutsceneitemdata_t *data
|
||||||
) {
|
) {
|
||||||
if(item->callback != NULL) item->callback();
|
if(item->callback != NULL) item->callback(CUTSCENE_SYSTEM.userData);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool_t cutsceneCallbackUpdate(
|
bool_t cutsceneCallbackUpdate(
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
typedef struct cutsceneitem_s cutsceneitem_t;
|
typedef struct cutsceneitem_s cutsceneitem_t;
|
||||||
typedef union cutsceneitemdata_u cutsceneitemdata_t;
|
typedef union cutsceneitemdata_u cutsceneitemdata_t;
|
||||||
|
|
||||||
typedef void (*cutscenecallback_t)(void);
|
typedef void (*cutscenecallback_t)(void *userData);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Starts a callback item (invokes the callback immediately).
|
* Starts a callback item (invokes the callback immediately).
|
||||||
|
|||||||
@@ -7,90 +7,155 @@
|
|||||||
|
|
||||||
#include "rpg/cutscene/cutscenesystem.h"
|
#include "rpg/cutscene/cutscenesystem.h"
|
||||||
|
|
||||||
|
cutsceneitemcallbacks_t CUTSCENE_ITEM_CALLBACKS[CUTSCENE_ITEM_TYPE_COUNT] = {
|
||||||
|
[CUTSCENE_ITEM_TYPE_NULL] = { 0 },
|
||||||
|
|
||||||
|
[CUTSCENE_ITEM_TYPE_TEXT] = {
|
||||||
|
.init = cutsceneTextStart,
|
||||||
|
.update = cutsceneTextUpdate
|
||||||
|
},
|
||||||
|
|
||||||
|
[CUTSCENE_ITEM_TYPE_TEXT_MINI] = {
|
||||||
|
.init = cutsceneTextMiniStart,
|
||||||
|
.update = cutsceneTextMiniUpdate
|
||||||
|
},
|
||||||
|
|
||||||
|
[CUTSCENE_ITEM_TYPE_TEXT_MINI_HIDE] = {
|
||||||
|
.init = cutsceneTextMiniHideStart,
|
||||||
|
.update = cutsceneTextMiniHideUpdate
|
||||||
|
},
|
||||||
|
|
||||||
|
[CUTSCENE_ITEM_TYPE_CALLBACK] = {
|
||||||
|
.init = cutsceneCallbackStart,
|
||||||
|
.update = cutsceneCallbackUpdate
|
||||||
|
},
|
||||||
|
|
||||||
|
[CUTSCENE_ITEM_TYPE_WAIT] = {
|
||||||
|
.init = cutsceneWaitStart,
|
||||||
|
.update = cutsceneWaitUpdate
|
||||||
|
},
|
||||||
|
|
||||||
|
[CUTSCENE_ITEM_TYPE_CUTSCENE] = {
|
||||||
|
.init = cutsceneCutsceneStart,
|
||||||
|
.update = cutsceneCutsceneUpdate
|
||||||
|
},
|
||||||
|
|
||||||
|
[CUTSCENE_ITEM_TYPE_ENTITY_TELEPORT] = {
|
||||||
|
.init = cutsceneEntityTeleportStart,
|
||||||
|
.update = cutsceneEntityTeleportUpdate
|
||||||
|
},
|
||||||
|
|
||||||
|
[CUTSCENE_ITEM_TYPE_ENTITY_WALK_TO] = {
|
||||||
|
.init = cutsceneEntityWalkToStart,
|
||||||
|
.update = cutsceneEntityWalkToUpdate
|
||||||
|
},
|
||||||
|
|
||||||
|
[CUTSCENE_ITEM_TYPE_FADE] = {
|
||||||
|
.init = cutsceneFadeStart,
|
||||||
|
.update = cutsceneFadeUpdate
|
||||||
|
},
|
||||||
|
|
||||||
|
[CUTSCENE_ITEM_TYPE_SET_PAUSE] = {
|
||||||
|
.init = cutsceneSetPauseStart,
|
||||||
|
.update = cutsceneSetPauseUpdate
|
||||||
|
},
|
||||||
|
|
||||||
|
[CUTSCENE_ITEM_TYPE_CONCURRENT] = {
|
||||||
|
.init = cutsceneConcurrentStart,
|
||||||
|
.update = cutsceneConcurrentUpdate
|
||||||
|
},
|
||||||
|
|
||||||
|
[CUTSCENE_ITEM_TYPE_ITEM_GIVE] = {
|
||||||
|
.init = cutsceneItemGiveStart,
|
||||||
|
.update = cutsceneItemGiveUpdate
|
||||||
|
},
|
||||||
|
|
||||||
|
[CUTSCENE_ITEM_TYPE_ENTITY_REMOVE] = {
|
||||||
|
.init = cutsceneEntityRemoveStart,
|
||||||
|
.update = cutsceneEntityRemoveUpdate
|
||||||
|
},
|
||||||
|
|
||||||
|
[CUTSCENE_ITEM_TYPE_ENTITY_ADD] = {
|
||||||
|
.init = cutsceneEntityAddStart,
|
||||||
|
.update = cutsceneEntityAddUpdate
|
||||||
|
},
|
||||||
|
|
||||||
|
[CUTSCENE_ITEM_TYPE_ENTITY_TURN] = {
|
||||||
|
.init = cutsceneEntityTurnStart,
|
||||||
|
.update = cutsceneEntityTurnUpdate
|
||||||
|
},
|
||||||
|
|
||||||
|
[CUTSCENE_ITEM_TYPE_ENTITY_WALK_TO_ENTITY] = {
|
||||||
|
.init = cutsceneEntityWalkToEntityStart,
|
||||||
|
.update = cutsceneEntityWalkToEntityUpdate
|
||||||
|
},
|
||||||
|
|
||||||
|
[CUTSCENE_ITEM_TYPE_MAP_AREA_ADD] = {
|
||||||
|
.init = cutsceneMapAreaAddStart,
|
||||||
|
.update = cutsceneMapAreaAddUpdate
|
||||||
|
},
|
||||||
|
|
||||||
|
[CUTSCENE_ITEM_TYPE_MAP_AREA_REMOVE] = {
|
||||||
|
.init = cutsceneMapAreaRemoveStart,
|
||||||
|
.update = cutsceneMapAreaRemoveUpdate
|
||||||
|
},
|
||||||
|
|
||||||
|
[CUTSCENE_ITEM_TYPE_MAP_AREA_WAIT] = {
|
||||||
|
.init = cutsceneMapAreaWaitStart,
|
||||||
|
.update = cutsceneMapAreaWaitUpdate
|
||||||
|
},
|
||||||
|
|
||||||
|
[CUTSCENE_ITEM_TYPE_START_BATTLE] = {
|
||||||
|
.init = cutsceneStartBattleStart,
|
||||||
|
.update = cutsceneStartBattleUpdate
|
||||||
|
},
|
||||||
|
|
||||||
|
[CUTSCENE_ITEM_TYPE_EMOJI] = {
|
||||||
|
.init = cutsceneEmojiStart,
|
||||||
|
.update = cutsceneEmojiUpdate
|
||||||
|
},
|
||||||
|
|
||||||
|
[CUTSCENE_ITEM_TYPE_SHAKE] = {
|
||||||
|
.init = cutsceneShakeStart,
|
||||||
|
.update = cutsceneShakeUpdate
|
||||||
|
},
|
||||||
|
|
||||||
|
[CUTSCENE_ITEM_TYPE_BATTLE_WAIT_STATE] = {
|
||||||
|
.update = cutsceneBattleWaitStateUpdate
|
||||||
|
},
|
||||||
|
|
||||||
|
[CUTSCENE_ITEM_TYPE_BATTLE_FORCE_ACTION] = {
|
||||||
|
.init = cutsceneBattleForceActionStart,
|
||||||
|
.update = cutsceneBattleForceActionUpdate
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
void cutsceneItemStart(const cutsceneitem_t *item, cutsceneitemdata_t *data) {
|
void cutsceneItemStart(const cutsceneitem_t *item, cutsceneitemdata_t *data) {
|
||||||
switch(item->type) {
|
cutsceneiteminitcallback_t *init = CUTSCENE_ITEM_CALLBACKS[item->type].init;
|
||||||
case CUTSCENE_ITEM_TYPE_TEXT:
|
if(init != NULL) init(item, data);
|
||||||
cutsceneTextStart(item, data);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case CUTSCENE_ITEM_TYPE_CALLBACK:
|
|
||||||
cutsceneCallbackStart(item, data);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case CUTSCENE_ITEM_TYPE_WAIT:
|
|
||||||
cutsceneWaitStart(item, data);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case CUTSCENE_ITEM_TYPE_CUTSCENE:
|
|
||||||
if(item->cutscene != NULL) cutsceneSystemStartCutscene(item->cutscene);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case CUTSCENE_ITEM_TYPE_ENTITY_TELEPORT:
|
|
||||||
cutsceneEntityTeleportStart(item, data);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case CUTSCENE_ITEM_TYPE_ENTITY_WALK_TO:
|
|
||||||
cutsceneEntityWalkToStart(item, data);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case CUTSCENE_ITEM_TYPE_FADE:
|
|
||||||
cutsceneFadeStart(item, data);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case CUTSCENE_ITEM_TYPE_SET_PAUSE:
|
|
||||||
cutsceneSetPauseStart(item, data);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case CUTSCENE_ITEM_TYPE_CONCURRENT:
|
|
||||||
cutsceneConcurrentStart(item, data);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case CUTSCENE_ITEM_TYPE_ITEM_GIVE:
|
|
||||||
cutsceneItemGiveStart(item, data);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case CUTSCENE_ITEM_TYPE_ENTITY_REMOVE:
|
|
||||||
cutsceneEntityRemoveStart(item, data);
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool_t cutsceneItemUpdate(const cutsceneitem_t *item, cutsceneitemdata_t *data) {
|
bool_t cutsceneItemUpdate(
|
||||||
switch(item->type) {
|
const cutsceneitem_t *item,
|
||||||
case CUTSCENE_ITEM_TYPE_TEXT:
|
cutsceneitemdata_t *data
|
||||||
return cutsceneTextUpdate(item, data);
|
) {
|
||||||
|
cutsceneitemupdatecallback_t *update =
|
||||||
|
CUTSCENE_ITEM_CALLBACKS[item->type].update;
|
||||||
|
if(update == NULL) return false;
|
||||||
|
|
||||||
case CUTSCENE_ITEM_TYPE_CALLBACK:
|
return update(item, data);
|
||||||
return cutsceneCallbackUpdate(item, data);
|
}
|
||||||
|
|
||||||
case CUTSCENE_ITEM_TYPE_WAIT:
|
void cutsceneCutsceneStart(
|
||||||
return cutsceneWaitUpdate(item, data);
|
const cutsceneitem_t *item,
|
||||||
|
cutsceneitemdata_t *data
|
||||||
case CUTSCENE_ITEM_TYPE_ENTITY_TELEPORT:
|
) {
|
||||||
return cutsceneEntityTeleportUpdate(item, data);
|
if(item->cutscene != NULL) cutsceneSystemStartCutscene(item->cutscene);
|
||||||
|
}
|
||||||
case CUTSCENE_ITEM_TYPE_ENTITY_WALK_TO:
|
|
||||||
return cutsceneEntityWalkToUpdate(item, data);
|
bool_t cutsceneCutsceneUpdate(
|
||||||
|
const cutsceneitem_t *item,
|
||||||
case CUTSCENE_ITEM_TYPE_FADE:
|
cutsceneitemdata_t *data
|
||||||
return cutsceneFadeUpdate(item, data);
|
) {
|
||||||
|
return false;
|
||||||
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:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,15 +13,31 @@
|
|||||||
#include "entity/cutsceneentityteleport.h"
|
#include "entity/cutsceneentityteleport.h"
|
||||||
#include "entity/cutsceneentitywalkto.h"
|
#include "entity/cutsceneentitywalkto.h"
|
||||||
#include "entity/cutsceneentityremove.h"
|
#include "entity/cutsceneentityremove.h"
|
||||||
|
#include "entity/cutsceneentityadd.h"
|
||||||
|
#include "entity/cutsceneentityturn.h"
|
||||||
|
#include "entity/cutsceneentitywalktoentity.h"
|
||||||
#include "ui/cutscenetext.h"
|
#include "ui/cutscenetext.h"
|
||||||
|
#include "ui/cutscenetextmini.h"
|
||||||
|
#include "ui/cutscenetextminihide.h"
|
||||||
#include "ui/cutscenefade.h"
|
#include "ui/cutscenefade.h"
|
||||||
|
#include "ui/cutsceneemoji.h"
|
||||||
|
#include "ui/cutsceneshake.h"
|
||||||
#include "item/cutsceneitemgive.h"
|
#include "item/cutsceneitemgive.h"
|
||||||
|
#include "maparea/cutscenemapareaadd.h"
|
||||||
|
#include "maparea/cutscenemaparearemove.h"
|
||||||
|
#include "maparea/cutscenemapareawait.h"
|
||||||
|
#include "battle/cutscenestartbattle.h"
|
||||||
|
#include "battle/cutscenebattlewaitstate.h"
|
||||||
|
#include "battle/cutscenebattleforceaction.h"
|
||||||
|
|
||||||
typedef struct cutscene_s cutscene_t;
|
typedef struct cutscene_s cutscene_t;
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
CUTSCENE_ITEM_TYPE_NULL,
|
CUTSCENE_ITEM_TYPE_NULL,
|
||||||
|
|
||||||
CUTSCENE_ITEM_TYPE_TEXT,
|
CUTSCENE_ITEM_TYPE_TEXT,
|
||||||
|
CUTSCENE_ITEM_TYPE_TEXT_MINI,
|
||||||
|
CUTSCENE_ITEM_TYPE_TEXT_MINI_HIDE,
|
||||||
CUTSCENE_ITEM_TYPE_CALLBACK,
|
CUTSCENE_ITEM_TYPE_CALLBACK,
|
||||||
CUTSCENE_ITEM_TYPE_WAIT,
|
CUTSCENE_ITEM_TYPE_WAIT,
|
||||||
CUTSCENE_ITEM_TYPE_CUTSCENE,
|
CUTSCENE_ITEM_TYPE_CUTSCENE,
|
||||||
@@ -31,7 +47,20 @@ typedef enum {
|
|||||||
CUTSCENE_ITEM_TYPE_SET_PAUSE,
|
CUTSCENE_ITEM_TYPE_SET_PAUSE,
|
||||||
CUTSCENE_ITEM_TYPE_CONCURRENT,
|
CUTSCENE_ITEM_TYPE_CONCURRENT,
|
||||||
CUTSCENE_ITEM_TYPE_ITEM_GIVE,
|
CUTSCENE_ITEM_TYPE_ITEM_GIVE,
|
||||||
CUTSCENE_ITEM_TYPE_ENTITY_REMOVE
|
CUTSCENE_ITEM_TYPE_ENTITY_REMOVE,
|
||||||
|
CUTSCENE_ITEM_TYPE_ENTITY_ADD,
|
||||||
|
CUTSCENE_ITEM_TYPE_ENTITY_TURN,
|
||||||
|
CUTSCENE_ITEM_TYPE_ENTITY_WALK_TO_ENTITY,
|
||||||
|
CUTSCENE_ITEM_TYPE_MAP_AREA_ADD,
|
||||||
|
CUTSCENE_ITEM_TYPE_MAP_AREA_REMOVE,
|
||||||
|
CUTSCENE_ITEM_TYPE_MAP_AREA_WAIT,
|
||||||
|
CUTSCENE_ITEM_TYPE_START_BATTLE,
|
||||||
|
CUTSCENE_ITEM_TYPE_EMOJI,
|
||||||
|
CUTSCENE_ITEM_TYPE_SHAKE,
|
||||||
|
CUTSCENE_ITEM_TYPE_BATTLE_WAIT_STATE,
|
||||||
|
CUTSCENE_ITEM_TYPE_BATTLE_FORCE_ACTION,
|
||||||
|
|
||||||
|
CUTSCENE_ITEM_TYPE_COUNT
|
||||||
} cutsceneitemtype_t;
|
} cutsceneitemtype_t;
|
||||||
|
|
||||||
struct cutsceneitem_s {
|
struct cutsceneitem_s {
|
||||||
@@ -39,6 +68,8 @@ struct cutsceneitem_s {
|
|||||||
|
|
||||||
union {
|
union {
|
||||||
cutscenetext_t text;
|
cutscenetext_t text;
|
||||||
|
cutscenetextmini_t textMini;
|
||||||
|
cutscenetextminihide_t textMiniHide;
|
||||||
cutscenecallback_t callback;
|
cutscenecallback_t callback;
|
||||||
cutscenewait_t wait;
|
cutscenewait_t wait;
|
||||||
const cutscene_t *cutscene;
|
const cutscene_t *cutscene;
|
||||||
@@ -49,6 +80,17 @@ struct cutsceneitem_s {
|
|||||||
cutsceneconcurrent_t concurrent;
|
cutsceneconcurrent_t concurrent;
|
||||||
cutsceneitemgive_t itemGive;
|
cutsceneitemgive_t itemGive;
|
||||||
cutsceneentityremove_t entityRemove;
|
cutsceneentityremove_t entityRemove;
|
||||||
|
cutsceneentityadd_t entityAdd;
|
||||||
|
cutsceneentityturn_t entityTurn;
|
||||||
|
cutsceneentitywalktoentity_t entityWalkToEntity;
|
||||||
|
cutscenemapareaadd_t mapAreaAdd;
|
||||||
|
cutscenemaparearemove_t mapAreaRemove;
|
||||||
|
cutscenemapareawait_t mapAreaWait;
|
||||||
|
cutscenestartbattle_t startBattle;
|
||||||
|
cutsceneemoji_t emoji;
|
||||||
|
cutsceneshake_t shake;
|
||||||
|
cutscenebattlewaitstate_t battleWaitState;
|
||||||
|
cutscenebattleforceaction_t battleForceAction;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -56,8 +98,27 @@ typedef union cutsceneitemdata_u {
|
|||||||
cutscenewaitdata_t wait;
|
cutscenewaitdata_t wait;
|
||||||
cutsceneentitywalktodata_t entityWalkTo;
|
cutsceneentitywalktodata_t entityWalkTo;
|
||||||
cutsceneconcurrentdata_t concurrent;
|
cutsceneconcurrentdata_t concurrent;
|
||||||
|
cutscenemapareawaitdata_t mapAreaWait;
|
||||||
} cutsceneitemdata_t;
|
} cutsceneitemdata_t;
|
||||||
|
|
||||||
|
typedef void (cutsceneiteminitcallback_t)(
|
||||||
|
const cutsceneitem_t *item,
|
||||||
|
cutsceneitemdata_t *data
|
||||||
|
);
|
||||||
|
|
||||||
|
typedef bool_t (cutsceneitemupdatecallback_t)(
|
||||||
|
const cutsceneitem_t *item,
|
||||||
|
cutsceneitemdata_t *data
|
||||||
|
);
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
cutsceneiteminitcallback_t *init;
|
||||||
|
cutsceneitemupdatecallback_t *update;
|
||||||
|
} cutsceneitemcallbacks_t;
|
||||||
|
|
||||||
|
extern cutsceneitemcallbacks_t
|
||||||
|
CUTSCENE_ITEM_CALLBACKS[CUTSCENE_ITEM_TYPE_COUNT];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Start the given cutscene item.
|
* Start the given cutscene item.
|
||||||
*
|
*
|
||||||
@@ -80,3 +141,29 @@ bool_t cutsceneItemUpdate(
|
|||||||
const cutsceneitem_t *item,
|
const cutsceneitem_t *item,
|
||||||
cutsceneitemdata_t *data
|
cutsceneitemdata_t *data
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts a nested-cutscene item, handing control over to the
|
||||||
|
* referenced cutscene.
|
||||||
|
*
|
||||||
|
* @param item The cutscene item.
|
||||||
|
* @param data Runtime data storage.
|
||||||
|
*/
|
||||||
|
void cutsceneCutsceneStart(
|
||||||
|
const cutsceneitem_t *item,
|
||||||
|
cutsceneitemdata_t *data
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates a nested-cutscene item. By the time this would run, control
|
||||||
|
* has already moved on to the referenced cutscene, so this always
|
||||||
|
* reports incomplete.
|
||||||
|
*
|
||||||
|
* @param item The cutscene item.
|
||||||
|
* @param data Runtime data storage.
|
||||||
|
* @returns false always.
|
||||||
|
*/
|
||||||
|
bool_t cutsceneCutsceneUpdate(
|
||||||
|
const cutsceneitem_t *item,
|
||||||
|
cutsceneitemdata_t *data
|
||||||
|
);
|
||||||
|
|||||||
@@ -8,4 +8,7 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
|||||||
cutsceneentityteleport.c
|
cutsceneentityteleport.c
|
||||||
cutsceneentitywalkto.c
|
cutsceneentitywalkto.c
|
||||||
cutsceneentityremove.c
|
cutsceneentityremove.c
|
||||||
|
cutsceneentityadd.c
|
||||||
|
cutsceneentityturn.c
|
||||||
|
cutsceneentitywalktoentity.c
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "rpg/cutscene/item/cutsceneitem.h"
|
||||||
|
#include "rpg/cutscene/cutscenesystem.h"
|
||||||
|
#include "rpg/entity/entity.h"
|
||||||
|
#include "assert/assert.h"
|
||||||
|
|
||||||
|
void cutsceneEntityAddStart(
|
||||||
|
const cutsceneitem_t *item,
|
||||||
|
cutsceneitemdata_t *data
|
||||||
|
) {
|
||||||
|
uint8_t entIndex = entityGetAvailable();
|
||||||
|
assertTrue(entIndex != 0xFF, "No available entity slots for CUTSCENE_ENTITY_ADD");
|
||||||
|
|
||||||
|
entity_t *entity = &ENTITIES[entIndex];
|
||||||
|
entityInit(entity, item->entityAdd.entityType);
|
||||||
|
entityPositionSet(entity, item->entityAdd.position);// Also assigns chunk.
|
||||||
|
|
||||||
|
CUTSCENE_SYSTEM.entityLastCreated = entity;
|
||||||
|
CUTSCENE_SYSTEM.entityLastRef = entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool_t cutsceneEntityAddUpdate(
|
||||||
|
const cutsceneitem_t *item,
|
||||||
|
cutsceneitemdata_t *data
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "rpg/entity/entitytype.h"
|
||||||
|
#include "rpg/overworld/worldpos.h"
|
||||||
|
|
||||||
|
typedef struct cutsceneitem_s cutsceneitem_t;
|
||||||
|
typedef union cutsceneitemdata_u cutsceneitemdata_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
entitytype_t entityType;
|
||||||
|
worldpos_t position;
|
||||||
|
} cutsceneentityadd_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts an entity add step (spawns the entity into the world immediately).
|
||||||
|
*
|
||||||
|
* @param item The cutscene item.
|
||||||
|
* @param data Runtime data storage.
|
||||||
|
*/
|
||||||
|
void cutsceneEntityAddStart(
|
||||||
|
const cutsceneitem_t *item,
|
||||||
|
cutsceneitemdata_t *data
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates an entity add step (always completes immediately).
|
||||||
|
*
|
||||||
|
* @param item The cutscene item.
|
||||||
|
* @param data Runtime data storage.
|
||||||
|
* @returns true always.
|
||||||
|
*/
|
||||||
|
bool_t cutsceneEntityAddUpdate(
|
||||||
|
const cutsceneitem_t *item,
|
||||||
|
cutsceneitemdata_t *data
|
||||||
|
);
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "rpg/cutscene/item/cutsceneitem.h"
|
||||||
|
#include "rpg/cutscene/cutscenesystem.h"
|
||||||
|
#include "rpg/entity/entity.h"
|
||||||
|
|
||||||
|
void cutsceneEntityTurnStart(
|
||||||
|
const cutsceneitem_t *item,
|
||||||
|
cutsceneitemdata_t *data
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
bool_t cutsceneEntityTurnUpdate(
|
||||||
|
const cutsceneitem_t *item,
|
||||||
|
cutsceneitemdata_t *data
|
||||||
|
) {
|
||||||
|
entity_t *entity = cutsceneSystemGetEntity(item->entityTurn.entityIndex);
|
||||||
|
if(
|
||||||
|
entity->direction == item->entityTurn.direction &&
|
||||||
|
entity->animation == ENTITY_ANIM_IDLE
|
||||||
|
) return true;
|
||||||
|
|
||||||
|
entityTurn(entity, item->entityTurn.direction);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "rpg/entity/entitydir.h"
|
||||||
|
|
||||||
|
typedef struct cutsceneitem_s cutsceneitem_t;
|
||||||
|
typedef union cutsceneitemdata_u cutsceneitemdata_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint8_t entityIndex;
|
||||||
|
entitydir_t direction;
|
||||||
|
} cutsceneentityturn_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts an entity turn step. The turn itself is driven from Update, since
|
||||||
|
* the entity may still be finishing a previous action.
|
||||||
|
*
|
||||||
|
* @param item The cutscene item.
|
||||||
|
* @param data Runtime data storage.
|
||||||
|
*/
|
||||||
|
void cutsceneEntityTurnStart(
|
||||||
|
const cutsceneitem_t *item,
|
||||||
|
cutsceneitemdata_t *data
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates an entity turn step, retrying entityTurn until it takes effect
|
||||||
|
* and its animation completes.
|
||||||
|
*
|
||||||
|
* @param item The cutscene item.
|
||||||
|
* @param data Runtime data storage.
|
||||||
|
* @returns true once the entity is idle and facing the target direction.
|
||||||
|
*/
|
||||||
|
bool_t cutsceneEntityTurnUpdate(
|
||||||
|
const cutsceneitem_t *item,
|
||||||
|
cutsceneitemdata_t *data
|
||||||
|
);
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "rpg/cutscene/item/cutsceneitem.h"
|
||||||
|
#include "rpg/cutscene/cutscenesystem.h"
|
||||||
|
#include "rpg/entity/entity.h"
|
||||||
|
#include "rpg/entity/entitypathstep.h"
|
||||||
|
#include "rpg/overworld/map.h"
|
||||||
|
|
||||||
|
void cutsceneEntityWalkToEntityStart(
|
||||||
|
const cutsceneitem_t *item,
|
||||||
|
cutsceneitemdata_t *data
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
bool_t cutsceneEntityWalkToEntityUpdate(
|
||||||
|
const cutsceneitem_t *item,
|
||||||
|
cutsceneitemdata_t *data
|
||||||
|
) {
|
||||||
|
entity_t *entity = cutsceneSystemGetEntity(
|
||||||
|
item->entityWalkToEntity.entityIndex
|
||||||
|
);
|
||||||
|
entity_t *target = cutsceneSystemGetEntity(
|
||||||
|
item->entityWalkToEntity.targetEntityIndex
|
||||||
|
);
|
||||||
|
|
||||||
|
worldpos_t dest = {
|
||||||
|
.x = (worldunit_t)(target->position.x + item->entityWalkToEntity.offsetX),
|
||||||
|
.y = (worldunit_t)(target->position.y + item->entityWalkToEntity.offsetY),
|
||||||
|
.z = target->position.z
|
||||||
|
};
|
||||||
|
|
||||||
|
worldunit_t z;
|
||||||
|
if(mapGetWalkableZNear(dest.x, dest.y, target->position.z, &z)) dest.z = z;
|
||||||
|
|
||||||
|
return entityPathStep(entity, dest, true);
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "rpg/overworld/worldpos.h"
|
||||||
|
|
||||||
|
typedef struct cutsceneitem_s cutsceneitem_t;
|
||||||
|
typedef union cutsceneitemdata_u cutsceneitemdata_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint8_t entityIndex;
|
||||||
|
uint8_t targetEntityIndex;
|
||||||
|
worldunit_t offsetX;
|
||||||
|
worldunit_t offsetY;
|
||||||
|
} cutsceneentitywalktoentity_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts an entity walk-to-entity item. No setup is needed, the destination
|
||||||
|
* is recomputed from the target's live position every Update.
|
||||||
|
*
|
||||||
|
* @param item The cutscene item.
|
||||||
|
* @param data Runtime data storage.
|
||||||
|
*/
|
||||||
|
void cutsceneEntityWalkToEntityStart(
|
||||||
|
const cutsceneitem_t *item,
|
||||||
|
cutsceneitemdata_t *data
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates an entity walk-to-entity item. Re-reads the target entity's
|
||||||
|
* current position each frame, applies the X/Y offset, resolves the
|
||||||
|
* destination Z from nearby terrain (to account for ramps), and steps
|
||||||
|
* the entity toward it.
|
||||||
|
*
|
||||||
|
* @param item The cutscene item.
|
||||||
|
* @param data Runtime data storage.
|
||||||
|
* @returns true once the entity has reached the target's side.
|
||||||
|
*/
|
||||||
|
bool_t cutsceneEntityWalkToEntityUpdate(
|
||||||
|
const cutsceneitem_t *item,
|
||||||
|
cutsceneitemdata_t *data
|
||||||
|
);
|
||||||
@@ -6,25 +6,14 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "rpg/cutscene/item/cutsceneitem.h"
|
#include "rpg/cutscene/item/cutsceneitem.h"
|
||||||
#include "rpg/item/backpack.h"
|
#include "rpg/item/itemgive.h"
|
||||||
#include "ui/rpg/uitextboxmain.h"
|
#include "ui/rpg/textbox/uitextboxmain.h"
|
||||||
#include "util/string.h"
|
|
||||||
|
|
||||||
void cutsceneItemGiveStart(
|
void cutsceneItemGiveStart(
|
||||||
const cutsceneitem_t *item,
|
const cutsceneitem_t *item,
|
||||||
cutsceneitemdata_t *data
|
cutsceneitemdata_t *data
|
||||||
) {
|
) {
|
||||||
backpackAdd(item->itemGive.item, item->itemGive.quantity);
|
itemGive(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(
|
bool_t cutsceneItemGiveUpdate(
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# Copyright (c) 2026 Dominic Masters
|
||||||
|
#
|
||||||
|
# This software is released under the MIT License.
|
||||||
|
# https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
|
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||||
|
PUBLIC
|
||||||
|
cutscenemapareaadd.c
|
||||||
|
cutscenemaparearemove.c
|
||||||
|
cutscenemapareawait.c
|
||||||
|
)
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "rpg/cutscene/item/cutsceneitem.h"
|
||||||
|
#include "rpg/cutscene/cutscenesystem.h"
|
||||||
|
|
||||||
|
void cutsceneMapAreaAddStart(
|
||||||
|
const cutsceneitem_t *item,
|
||||||
|
cutsceneitemdata_t *data
|
||||||
|
) {
|
||||||
|
CUTSCENE_SYSTEM.areaLastCreated = mapAreaAdd(
|
||||||
|
item->mapAreaAdd.min,
|
||||||
|
item->mapAreaAdd.max,
|
||||||
|
item->mapAreaAdd.callback,
|
||||||
|
item->mapAreaAdd.notify,
|
||||||
|
item->mapAreaAdd.trigger
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool_t cutsceneMapAreaAddUpdate(
|
||||||
|
const cutsceneitem_t *item,
|
||||||
|
cutsceneitemdata_t *data
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "rpg/overworld/maparea.h"
|
||||||
|
|
||||||
|
typedef struct cutsceneitem_s cutsceneitem_t;
|
||||||
|
typedef union cutsceneitemdata_u cutsceneitemdata_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
worldpos_t min;
|
||||||
|
worldpos_t max;
|
||||||
|
mapareacallback_t callback;
|
||||||
|
uint8_t notify;
|
||||||
|
uint8_t trigger;
|
||||||
|
} cutscenemapareaadd_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts a map area add step (adds the area immediately, storing its ID
|
||||||
|
* in CUTSCENE_SYSTEM.areaLastCreated).
|
||||||
|
*
|
||||||
|
* @param item The cutscene item.
|
||||||
|
* @param data Runtime data storage.
|
||||||
|
*/
|
||||||
|
void cutsceneMapAreaAddStart(
|
||||||
|
const cutsceneitem_t *item,
|
||||||
|
cutsceneitemdata_t *data
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates a map area add step (always completes immediately).
|
||||||
|
*
|
||||||
|
* @param item The cutscene item.
|
||||||
|
* @param data Runtime data storage.
|
||||||
|
* @returns true always.
|
||||||
|
*/
|
||||||
|
bool_t cutsceneMapAreaAddUpdate(
|
||||||
|
const cutsceneitem_t *item,
|
||||||
|
cutsceneitemdata_t *data
|
||||||
|
);
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "rpg/cutscene/item/cutsceneitem.h"
|
||||||
|
#include "rpg/cutscene/cutscenesystem.h"
|
||||||
|
#include "rpg/overworld/maparea.h"
|
||||||
|
|
||||||
|
void cutsceneMapAreaRemoveStart(
|
||||||
|
const cutsceneitem_t *item,
|
||||||
|
cutsceneitemdata_t *data
|
||||||
|
) {
|
||||||
|
mapAreaRemove(cutsceneSystemGetAreaId(item->mapAreaRemove.areaId));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool_t cutsceneMapAreaRemoveUpdate(
|
||||||
|
const cutsceneitem_t *item,
|
||||||
|
cutsceneitemdata_t *data
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "dusk.h"
|
||||||
|
|
||||||
|
typedef struct cutsceneitem_s cutsceneitem_t;
|
||||||
|
typedef union cutsceneitemdata_u cutsceneitemdata_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint8_t areaId;
|
||||||
|
} cutscenemaparearemove_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts a map area remove step (removes the area immediately). Accepts
|
||||||
|
* CUTSCENE_AREA_LAST_CREATED in place of a literal area ID.
|
||||||
|
*
|
||||||
|
* @param item The cutscene item.
|
||||||
|
* @param data Runtime data storage.
|
||||||
|
*/
|
||||||
|
void cutsceneMapAreaRemoveStart(
|
||||||
|
const cutsceneitem_t *item,
|
||||||
|
cutsceneitemdata_t *data
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates a map area remove step (always completes immediately).
|
||||||
|
*
|
||||||
|
* @param item The cutscene item.
|
||||||
|
* @param data Runtime data storage.
|
||||||
|
* @returns true always.
|
||||||
|
*/
|
||||||
|
bool_t cutsceneMapAreaRemoveUpdate(
|
||||||
|
const cutsceneitem_t *item,
|
||||||
|
cutsceneitemdata_t *data
|
||||||
|
);
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "rpg/cutscene/item/cutsceneitem.h"
|
||||||
|
#include "rpg/cutscene/cutscenesystem.h"
|
||||||
|
#include "rpg/overworld/maparea.h"
|
||||||
|
#include "assert/assert.h"
|
||||||
|
|
||||||
|
void cutsceneMapAreaWaitStart(
|
||||||
|
const cutsceneitem_t *item,
|
||||||
|
cutsceneitemdata_t *data
|
||||||
|
) {
|
||||||
|
assertTrue(
|
||||||
|
item->mapAreaWait.count <= CUTSCENE_MAP_AREA_WAIT_MAX,
|
||||||
|
"Too many areas in CUTSCENE_MAP_AREA_WAIT"
|
||||||
|
);
|
||||||
|
|
||||||
|
for(uint8_t i = 0; i < item->mapAreaWait.count; i++) {
|
||||||
|
uint8_t areaId = cutsceneSystemGetAreaId(item->mapAreaWait.areaIds[i]);
|
||||||
|
data->mapAreaWait.baseline[i] = MAP_AREAS[areaId].triggerCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool_t cutsceneMapAreaWaitUpdate(
|
||||||
|
const cutsceneitem_t *item,
|
||||||
|
cutsceneitemdata_t *data
|
||||||
|
) {
|
||||||
|
for(uint8_t i = 0; i < item->mapAreaWait.count; i++) {
|
||||||
|
uint8_t areaId = cutsceneSystemGetAreaId(item->mapAreaWait.areaIds[i]);
|
||||||
|
if(MAP_AREAS[areaId].triggerCount != data->mapAreaWait.baseline[i]) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "dusk.h"
|
||||||
|
|
||||||
|
typedef struct cutsceneitem_s cutsceneitem_t;
|
||||||
|
typedef union cutsceneitemdata_u cutsceneitemdata_t;
|
||||||
|
|
||||||
|
/** Maximum number of areas a single CUTSCENE_MAP_AREA_WAIT may watch. */
|
||||||
|
#define CUTSCENE_MAP_AREA_WAIT_MAX 4
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
const uint8_t *areaIds;
|
||||||
|
uint8_t count;
|
||||||
|
} cutscenemapareawait_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint32_t baseline[CUTSCENE_MAP_AREA_WAIT_MAX];
|
||||||
|
} cutscenemapareawaitdata_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts a map area wait step, snapshotting each watched area's current
|
||||||
|
* trigger count.
|
||||||
|
*
|
||||||
|
* @param item The cutscene item.
|
||||||
|
* @param data Runtime data storage.
|
||||||
|
*/
|
||||||
|
void cutsceneMapAreaWaitStart(
|
||||||
|
const cutsceneitem_t *item,
|
||||||
|
cutsceneitemdata_t *data
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates a map area wait step, completing once any watched area's
|
||||||
|
* trigger count has changed since Start (i.e. its callback fired).
|
||||||
|
*
|
||||||
|
* @param item The cutscene item.
|
||||||
|
* @param data Runtime data storage.
|
||||||
|
* @returns true once any watched area has been triggered.
|
||||||
|
*/
|
||||||
|
bool_t cutsceneMapAreaWaitUpdate(
|
||||||
|
const cutsceneitem_t *item,
|
||||||
|
cutsceneitemdata_t *data
|
||||||
|
);
|
||||||
@@ -6,5 +6,9 @@
|
|||||||
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||||
PUBLIC
|
PUBLIC
|
||||||
cutscenetext.c
|
cutscenetext.c
|
||||||
|
cutscenetextmini.c
|
||||||
|
cutscenetextminihide.c
|
||||||
cutscenefade.c
|
cutscenefade.c
|
||||||
|
cutsceneemoji.c
|
||||||
|
cutsceneshake.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 cutsceneEmojiStart(
|
||||||
|
const cutsceneitem_t *item,
|
||||||
|
cutsceneitemdata_t *data
|
||||||
|
) {
|
||||||
|
entity_t *entity = cutsceneSystemGetEntity(item->emoji.entityIndex);
|
||||||
|
uiEmojiAdd(entity->id, item->emoji.duration, item->emoji.emojiType);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool_t cutsceneEmojiUpdate(
|
||||||
|
const cutsceneitem_t *item,
|
||||||
|
cutsceneitemdata_t *data
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
@@ -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 "dusk.h"
|
||||||
|
#include "ui/rpg/uiemoji.h"
|
||||||
|
|
||||||
|
typedef struct cutsceneitem_s cutsceneitem_t;
|
||||||
|
typedef union cutsceneitemdata_u cutsceneitemdata_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint8_t entityIndex;
|
||||||
|
float_t duration;
|
||||||
|
uiemojitype_t emojiType;
|
||||||
|
} cutsceneemoji_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts an emoji step (shows an emoji above the entity for the given
|
||||||
|
* duration, then completes immediately).
|
||||||
|
*
|
||||||
|
* @param item The cutscene item.
|
||||||
|
* @param data Runtime data storage.
|
||||||
|
*/
|
||||||
|
void cutsceneEmojiStart(
|
||||||
|
const cutsceneitem_t *item,
|
||||||
|
cutsceneitemdata_t *data
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates an emoji step (always completes immediately).
|
||||||
|
*
|
||||||
|
* @param item The cutscene item.
|
||||||
|
* @param data Runtime data storage.
|
||||||
|
* @returns true always.
|
||||||
|
*/
|
||||||
|
bool_t cutsceneEmojiUpdate(
|
||||||
|
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/rpgcamera.h"
|
||||||
|
|
||||||
|
void cutsceneShakeStart(
|
||||||
|
const cutsceneitem_t *item,
|
||||||
|
cutsceneitemdata_t *data
|
||||||
|
) {
|
||||||
|
rpgCameraShake(item->shake.amount, item->shake.duration);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool_t cutsceneShakeUpdate(
|
||||||
|
const cutsceneitem_t *item,
|
||||||
|
cutsceneitemdata_t *data
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "dusk.h"
|
||||||
|
|
||||||
|
typedef struct cutsceneitem_s cutsceneitem_t;
|
||||||
|
typedef union cutsceneitemdata_u cutsceneitemdata_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint8_t amount;
|
||||||
|
float_t duration;
|
||||||
|
} cutsceneshake_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts a camera shake item (kicks off the shake on the RPG camera).
|
||||||
|
*
|
||||||
|
* @param item The cutscene item.
|
||||||
|
* @param data Runtime data storage.
|
||||||
|
*/
|
||||||
|
void cutsceneShakeStart(
|
||||||
|
const cutsceneitem_t *item,
|
||||||
|
cutsceneitemdata_t *data
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates a camera shake item. The shake itself runs asynchronously on
|
||||||
|
* the RPG camera, so this always completes immediately.
|
||||||
|
*
|
||||||
|
* @param item The cutscene item.
|
||||||
|
* @param data Runtime data storage.
|
||||||
|
* @returns true always.
|
||||||
|
*/
|
||||||
|
bool_t cutsceneShakeUpdate(
|
||||||
|
const cutsceneitem_t *item,
|
||||||
|
cutsceneitemdata_t *data
|
||||||
|
);
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "rpg/cutscene/item/cutsceneitem.h"
|
#include "rpg/cutscene/item/cutsceneitem.h"
|
||||||
#include "ui/rpg/uitextboxmain.h"
|
#include "ui/rpg/textbox/uitextboxmain.h"
|
||||||
|
|
||||||
void cutsceneTextStart(
|
void cutsceneTextStart(
|
||||||
const cutsceneitem_t *item,
|
const cutsceneitem_t *item,
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user