Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a73f55beb0 | |||
| 0bd2491ab7 | |||
| 860c797c9c | |||
| 38c5080f9f | |||
| fae191d8fe | |||
| d83a953e2d | |||
| 2dcf0d0f0d | |||
| 6dee37d8c1 | |||
| 3bad03afb3 | |||
| 6a6d8448f7 | |||
| 189babd2cf | |||
| 988a0f2294 | |||
| 85bf455731 | |||
| 589e4224f3 | |||
| bd7fa154b4 | |||
| a292f1992b | |||
| 88ddf429b7 | |||
| 7a1f6662df |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+28
-43
@@ -10,51 +10,36 @@ msgid "ui.title"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Welcome"
|
"Welcome"
|
||||||
|
|
||||||
#: ui/user.c:22
|
#: src/dusk/ui/frame/settings/uisettings.c
|
||||||
msgid "ui.greeting"
|
msgid "ui.settings.tabs.general"
|
||||||
msgstr "Hello, %s!"
|
msgstr "General"
|
||||||
|
|
||||||
#: ui/files.c:40
|
#: src/dusk/ui/frame/settings/uisettings.c
|
||||||
msgid "ui.file_status"
|
msgid "ui.settings.tabs.input"
|
||||||
msgstr "%s has %d files."
|
msgstr "Input"
|
||||||
|
|
||||||
#: ui/cart.c:55
|
#: src/dusk/ui/frame/settings/uisettings.c
|
||||||
msgid "cart.item_count"
|
msgid "ui.settings.tabs.display"
|
||||||
msgid_plural "cart.item_count"
|
msgstr "Display"
|
||||||
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.audio"
|
||||||
"ui.multiline_help"
|
msgstr "Audio"
|
||||||
msgstr ""
|
|
||||||
"Line one of the help text.\n"
|
|
||||||
"Line two continues here.\n"
|
|
||||||
"Line three ends here."
|
|
||||||
|
|
||||||
#: ui/errors.c:90
|
msgid "ui.settings.input.deadzone"
|
||||||
msgid ""
|
msgstr "Deadzone"
|
||||||
"error.upload_failed.long"
|
|
||||||
msgstr ""
|
|
||||||
"Upload failed for file \"%s\".\n"
|
|
||||||
"Please try again later or contact support."
|
|
||||||
|
|
||||||
#: ui/messages.c:110
|
#: src/dusk/ui/frame/settings/uisettingsgeneral.c
|
||||||
msgid ""
|
msgid "ui.settings.general.language"
|
||||||
"user.invite_status"
|
msgstr "Language"
|
||||||
msgid_plural ""
|
|
||||||
"user.invite_status"
|
msgid "ui.settings.general.language_detail"
|
||||||
msgstr[0] ""
|
msgstr "Takes effect after restarting the application."
|
||||||
"%s invited %d user.\n"
|
|
||||||
"Please review the request."
|
#: src/dusk/ui/frame/settings/uisettings.c
|
||||||
msgstr[1] ""
|
msgid "ui.settings.apply"
|
||||||
"%s invited %d users (dual).\n"
|
msgstr "Apply"
|
||||||
"Please review the requests."
|
|
||||||
msgstr[2] ""
|
#: src/dusk/ui/frame/uiconfirm.c
|
||||||
"%s invited %d users (few).\n"
|
msgid "ui.confirm.discard_changes"
|
||||||
"Please review the requests."
|
msgstr "Discard unsaved changes?"
|
||||||
msgstr[3] ""
|
|
||||||
"%s invited %d users (many).\n"
|
|
||||||
"Please review the requests."
|
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: ExampleApp 1.0\n"
|
||||||
|
"Language: es\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Plural-Forms: nplurals=2; plural=(n==1 ? 0 : 1);\n"
|
||||||
|
|
||||||
|
#: ui/menu.c:10
|
||||||
|
msgid "ui.title"
|
||||||
|
msgstr ""
|
||||||
|
"Bienvenido"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/settings/uisettings.c
|
||||||
|
msgid "ui.settings.tabs.general"
|
||||||
|
msgstr "General"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/settings/uisettings.c
|
||||||
|
msgid "ui.settings.tabs.input"
|
||||||
|
msgstr "Entrada"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/settings/uisettings.c
|
||||||
|
msgid "ui.settings.tabs.display"
|
||||||
|
msgstr "Pantalla"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/settings/uisettings.c
|
||||||
|
msgid "ui.settings.tabs.audio"
|
||||||
|
msgstr "Audio"
|
||||||
|
|
||||||
|
msgid "ui.settings.input.deadzone"
|
||||||
|
msgstr "Deadzone"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/settings/uisettingsgeneral.c
|
||||||
|
msgid "ui.settings.general.language"
|
||||||
|
msgstr "Idioma"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/settings/uisettingsgeneral.c
|
||||||
|
msgid "ui.settings.general.language_detail"
|
||||||
|
msgstr "Se aplica después de reiniciar la aplicación."
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/settings/uisettings.c
|
||||||
|
msgid "ui.settings.apply"
|
||||||
|
msgstr "Aplicar"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/uiconfirm.c
|
||||||
|
msgid "ui.confirm.discard_changes"
|
||||||
|
msgstr "¿Descartar los cambios no guardados?"
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: ExampleApp 1.0\n"
|
||||||
|
"Language: ja\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Plural-Forms: nplurals=1; plural=(0);\n"
|
||||||
|
|
||||||
|
#: ui/menu.c:10
|
||||||
|
msgid "ui.title"
|
||||||
|
msgstr ""
|
||||||
|
"歓迎"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/settings/uisettings.c
|
||||||
|
msgid "ui.settings.tabs.general"
|
||||||
|
msgstr "一般"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/settings/uisettings.c
|
||||||
|
msgid "ui.settings.tabs.input"
|
||||||
|
msgstr "入力"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/settings/uisettings.c
|
||||||
|
msgid "ui.settings.tabs.display"
|
||||||
|
msgstr "表示"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/settings/uisettings.c
|
||||||
|
msgid "ui.settings.tabs.audio"
|
||||||
|
msgstr "オーディオ"
|
||||||
|
|
||||||
|
msgid "ui.settings.input.deadzone"
|
||||||
|
msgstr "デッドゾーン"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/settings/uisettingsgeneral.c
|
||||||
|
msgid "ui.settings.general.language"
|
||||||
|
msgstr "言語"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/settings/uisettingsgeneral.c
|
||||||
|
msgid "ui.settings.general.language_detail"
|
||||||
|
msgstr "アプリケーションを再起動すると適用されます。"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/settings/uisettings.c
|
||||||
|
msgid "ui.settings.apply"
|
||||||
|
msgstr "適用"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/uiconfirm.c
|
||||||
|
msgid "ui.confirm.discard_changes"
|
||||||
|
msgstr "未保存の変更を破棄しますか?"
|
||||||
Binary file not shown.
Binary file not shown.
@@ -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
|
||||||
|
|||||||
@@ -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 4
|
||||||
|
|
||||||
typedef struct assetloading_s assetloading_t;
|
typedef struct assetloading_s assetloading_t;
|
||||||
typedef struct assetentry_s assetentry_t;
|
typedef struct assetentry_s assetentry_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,6 +17,7 @@ input_t INPUT;
|
|||||||
|
|
||||||
errorret_t inputInit(void) {
|
errorret_t inputInit(void) {
|
||||||
memoryZero(&INPUT, sizeof(input_t));
|
memoryZero(&INPUT, sizeof(input_t));
|
||||||
|
INPUT.deadzone = INPUT_DEADZONE_DEFAULT;
|
||||||
|
|
||||||
for(uint8_t i = 0; i < INPUT_ACTION_COUNT; i++) {
|
for(uint8_t i = 0; i < INPUT_ACTION_COUNT; i++) {
|
||||||
INPUT.actions[i].action = (inputaction_t)i;
|
INPUT.actions[i].action = (inputaction_t)i;
|
||||||
|
|||||||
@@ -12,11 +12,15 @@
|
|||||||
|
|
||||||
#define INPUT_LISTENER_PRESSED_MAX 16
|
#define INPUT_LISTENER_PRESSED_MAX 16
|
||||||
#define INPUT_LISTENER_RELEASED_MAX INPUT_LISTENER_PRESSED_MAX
|
#define INPUT_LISTENER_RELEASED_MAX INPUT_LISTENER_PRESSED_MAX
|
||||||
|
#define INPUT_DEADZONE_DEFAULT 0.1f
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
inputactiondata_t actions[INPUT_ACTION_COUNT];
|
inputactiondata_t actions[INPUT_ACTION_COUNT];
|
||||||
|
|
||||||
inputplatform_t platform;
|
inputplatform_t platform;
|
||||||
|
|
||||||
|
/** User-configured gamepad axis deadzone (0.0f to 1.0f). */
|
||||||
|
float_t deadzone;
|
||||||
} input_t;
|
} input_t;
|
||||||
|
|
||||||
extern input_t INPUT;
|
extern input_t INPUT;
|
||||||
|
|||||||
@@ -16,4 +16,14 @@ typedef struct {
|
|||||||
static const localeinfo_t LOCALE_EN_US = {
|
static const localeinfo_t LOCALE_EN_US = {
|
||||||
.name = "en-US",
|
.name = "en-US",
|
||||||
.file = "locale/en_US.po",
|
.file = "locale/en_US.po",
|
||||||
|
};
|
||||||
|
|
||||||
|
static const localeinfo_t LOCALE_JP_JP = {
|
||||||
|
.name = "ja-JP",
|
||||||
|
.file = "locale/jp_JP.po",
|
||||||
|
};
|
||||||
|
|
||||||
|
static const localeinfo_t LOCALE_ES_MX = {
|
||||||
|
.name = "es-MX",
|
||||||
|
.file = "locale/es_MX.po",
|
||||||
};
|
};
|
||||||
@@ -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,12 @@
|
|||||||
|
# Copyright (c) 2026 Dominic Masters
|
||||||
|
#
|
||||||
|
# This software is released under the MIT License.
|
||||||
|
# https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
|
# Sources
|
||||||
|
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||||
|
PUBLIC
|
||||||
|
battle.c
|
||||||
|
battlefighter.c
|
||||||
|
party.c
|
||||||
|
)
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "battle.h"
|
||||||
|
#include "util/memory.h"
|
||||||
|
|
||||||
|
battle_t BATTLE;
|
||||||
|
|
||||||
|
void battleInit(void) {
|
||||||
|
memoryZero(&BATTLE, sizeof(battle_t));
|
||||||
|
for(uint8_t i = 0; i < BATTLE_FIGHTER_COUNT_MAX; i++) {
|
||||||
|
BATTLE.fighters[i].id = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t battleGetAvailableFighter(void) {
|
||||||
|
for(uint8_t i = 0; i < BATTLE_FIGHTER_COUNT_MAX; i++) {
|
||||||
|
if(BATTLE.fighters[i].status == BATTLE_FIGHTER_STATUS_NULL) return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0xFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
battlefighter_t *battleAddFighter(
|
||||||
|
const battlefighterteam_t team,
|
||||||
|
const battlefightercontroller_t controller,
|
||||||
|
const battlefighterstats_t stats,
|
||||||
|
const uint16_t healthMax,
|
||||||
|
const uint16_t mpMax
|
||||||
|
) {
|
||||||
|
const uint8_t index = battleGetAvailableFighter();
|
||||||
|
if(index == 0xFF) return NULL;
|
||||||
|
|
||||||
|
battlefighter_t *fighter = &BATTLE.fighters[index];
|
||||||
|
battleFighterInit(fighter, team, controller, stats, healthMax, mpMax);
|
||||||
|
return fighter;
|
||||||
|
}
|
||||||
|
|
||||||
|
void battleStart(void) {
|
||||||
|
BATTLE.active = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void battleDispose(void) {
|
||||||
|
battleInit();
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "battlefighter.h"
|
||||||
|
|
||||||
|
#define BATTLE_FIGHTER_COUNT_MAX 8
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
bool_t active;
|
||||||
|
battlefighter_t fighters[BATTLE_FIGHTER_COUNT_MAX];
|
||||||
|
} battle_t;
|
||||||
|
|
||||||
|
extern battle_t BATTLE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the battle system. Marks it as inactive with no fighters.
|
||||||
|
*/
|
||||||
|
void battleInit(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets an available (unused) fighter slot index.
|
||||||
|
*
|
||||||
|
* @return The index of an available fighter slot, or 0xFF if none are
|
||||||
|
* available.
|
||||||
|
*/
|
||||||
|
uint8_t battleGetAvailableFighter(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a fighter to the battle in the next available slot.
|
||||||
|
*
|
||||||
|
* @param team The team the fighter belongs to.
|
||||||
|
* @param controller Who makes decisions for the fighter.
|
||||||
|
* @param stats The fighter's base combat stats.
|
||||||
|
* @param healthMax The fighter's maximum health.
|
||||||
|
* @param mpMax The fighter's maximum mp.
|
||||||
|
* @return Pointer to the newly added fighter, or NULL if the battle is
|
||||||
|
* already full.
|
||||||
|
*/
|
||||||
|
battlefighter_t *battleAddFighter(
|
||||||
|
const battlefighterteam_t team,
|
||||||
|
const battlefightercontroller_t controller,
|
||||||
|
const battlefighterstats_t stats,
|
||||||
|
const uint16_t healthMax,
|
||||||
|
const uint16_t mpMax
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts a battle, marking it active. Any fighters already added via
|
||||||
|
* battleAddFighter remain in place.
|
||||||
|
*/
|
||||||
|
void battleStart(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disposes of the battle, clearing all fighters and marking it inactive.
|
||||||
|
*/
|
||||||
|
void battleDispose(void);
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "battlefighter.h"
|
||||||
|
#include "assert/assert.h"
|
||||||
|
#include "util/memory.h"
|
||||||
|
|
||||||
|
void battleFighterInit(
|
||||||
|
battlefighter_t *fighter,
|
||||||
|
const battlefighterteam_t team,
|
||||||
|
const battlefightercontroller_t controller,
|
||||||
|
const battlefighterstats_t stats,
|
||||||
|
const uint16_t healthMax,
|
||||||
|
const uint16_t mpMax
|
||||||
|
) {
|
||||||
|
assertNotNull(fighter, "Fighter pointer cannot be NULL");
|
||||||
|
assertTrue(team < BATTLE_FIGHTER_TEAM_COUNT, "Invalid fighter team");
|
||||||
|
assertTrue(
|
||||||
|
controller < BATTLE_FIGHTER_CONTROLLER_COUNT,
|
||||||
|
"Invalid fighter controller"
|
||||||
|
);
|
||||||
|
|
||||||
|
const uint8_t id = fighter->id;
|
||||||
|
memoryZero(fighter, sizeof(battlefighter_t));
|
||||||
|
fighter->id = id;
|
||||||
|
fighter->status = BATTLE_FIGHTER_STATUS_NORMAL;
|
||||||
|
fighter->team = team;
|
||||||
|
fighter->controller = controller;
|
||||||
|
fighter->stats = stats;
|
||||||
|
fighter->healthMax = healthMax;
|
||||||
|
fighter->health = healthMax;
|
||||||
|
fighter->mpMax = mpMax;
|
||||||
|
fighter->mp = mpMax;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool_t battleFighterIsAlive(const battlefighter_t *fighter) {
|
||||||
|
assertNotNull(fighter, "Fighter pointer cannot be NULL");
|
||||||
|
return fighter->status == BATTLE_FIGHTER_STATUS_NORMAL;
|
||||||
|
}
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "error/error.h"
|
||||||
|
|
||||||
|
// An empty status means the slot in BATTLE.fighters is unused.
|
||||||
|
typedef enum {
|
||||||
|
BATTLE_FIGHTER_STATUS_NULL,
|
||||||
|
|
||||||
|
BATTLE_FIGHTER_STATUS_NORMAL,
|
||||||
|
BATTLE_FIGHTER_STATUS_DEAD,
|
||||||
|
|
||||||
|
BATTLE_FIGHTER_STATUS_COUNT
|
||||||
|
} battlefighterstatus_t;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
BATTLE_FIGHTER_TEAM_ALLY,
|
||||||
|
BATTLE_FIGHTER_TEAM_ENEMY,
|
||||||
|
|
||||||
|
BATTLE_FIGHTER_TEAM_COUNT
|
||||||
|
} battlefighterteam_t;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
BATTLE_FIGHTER_CONTROLLER_PLAYER,
|
||||||
|
BATTLE_FIGHTER_CONTROLLER_AI,
|
||||||
|
|
||||||
|
BATTLE_FIGHTER_CONTROLLER_COUNT
|
||||||
|
} battlefightercontroller_t;
|
||||||
|
|
||||||
|
// Base combat stats, kept separate from the resource pools (health/mp) on
|
||||||
|
// battlefighter_t so that equipment/buffs can later modify them without
|
||||||
|
// touching current health/mp state.
|
||||||
|
typedef struct {
|
||||||
|
uint16_t attack;
|
||||||
|
uint16_t defense;
|
||||||
|
uint16_t magic;
|
||||||
|
uint16_t speed;
|
||||||
|
uint16_t luck;
|
||||||
|
} battlefighterstats_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint8_t id;
|
||||||
|
battlefighterstatus_t status;
|
||||||
|
battlefighterteam_t team;
|
||||||
|
battlefightercontroller_t controller;
|
||||||
|
|
||||||
|
uint16_t health;
|
||||||
|
uint16_t healthMax;
|
||||||
|
uint16_t mp;
|
||||||
|
uint16_t mpMax;
|
||||||
|
|
||||||
|
battlefighterstats_t stats;
|
||||||
|
} battlefighter_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes a battle fighter in place, filling health/mp to their maximum
|
||||||
|
* values and setting its status to normal.
|
||||||
|
*
|
||||||
|
* @param fighter Pointer to the fighter to initialize.
|
||||||
|
* @param team The team the fighter belongs to.
|
||||||
|
* @param controller Who makes decisions for the fighter.
|
||||||
|
* @param stats The fighter's base combat stats.
|
||||||
|
* @param healthMax The fighter's maximum health.
|
||||||
|
* @param mpMax The fighter's maximum mp.
|
||||||
|
*/
|
||||||
|
void battleFighterInit(
|
||||||
|
battlefighter_t *fighter,
|
||||||
|
const battlefighterteam_t team,
|
||||||
|
const battlefightercontroller_t controller,
|
||||||
|
const battlefighterstats_t stats,
|
||||||
|
const uint16_t healthMax,
|
||||||
|
const uint16_t mpMax
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the fighter is in a state where it can still act (i.e.
|
||||||
|
* is not dead).
|
||||||
|
*
|
||||||
|
* @param fighter Pointer to the fighter to check.
|
||||||
|
* @returns True if the fighter can act.
|
||||||
|
*/
|
||||||
|
bool_t battleFighterIsAlive(const battlefighter_t *fighter);
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "party.h"
|
||||||
|
#include "assert/assert.h"
|
||||||
|
#include "util/memory.h"
|
||||||
|
|
||||||
|
party_t PARTY;
|
||||||
|
|
||||||
|
void partyInit(void) {
|
||||||
|
memoryZero(&PARTY, sizeof(party_t));
|
||||||
|
for(uint8_t i = 0; i < PARTY_MEMBER_COUNT_MAX; i++) {
|
||||||
|
PARTY.members[i].id = i;
|
||||||
|
}
|
||||||
|
for(uint8_t i = 0; i < PARTY_ACTIVE_SIZE_MAX; i++) {
|
||||||
|
PARTY.order[i] = PARTY_ORDER_EMPTY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t partyGetAvailableMember(void) {
|
||||||
|
for(uint8_t i = 0; i < PARTY_MEMBER_COUNT_MAX; i++) {
|
||||||
|
if(PARTY.members[i].status == BATTLE_FIGHTER_STATUS_NULL) return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0xFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
battlefighter_t *partyAddMember(
|
||||||
|
const battlefighterstats_t stats,
|
||||||
|
const uint16_t healthMax,
|
||||||
|
const uint16_t mpMax
|
||||||
|
) {
|
||||||
|
const uint8_t index = partyGetAvailableMember();
|
||||||
|
if(index == 0xFF) return NULL;
|
||||||
|
|
||||||
|
battlefighter_t *member = &PARTY.members[index];
|
||||||
|
battleFighterInit(
|
||||||
|
member, BATTLE_FIGHTER_TEAM_ALLY, BATTLE_FIGHTER_CONTROLLER_PLAYER,
|
||||||
|
stats, healthMax, mpMax
|
||||||
|
);
|
||||||
|
|
||||||
|
for(uint8_t i = 0; i < PARTY_ACTIVE_SIZE_MAX; i++) {
|
||||||
|
if(PARTY.order[i] != PARTY_ORDER_EMPTY) continue;
|
||||||
|
PARTY.order[i] = index;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return member;
|
||||||
|
}
|
||||||
|
|
||||||
|
battlefighter_t *partyGetOrderMember(const uint8_t slot) {
|
||||||
|
assertTrue(slot < PARTY_ACTIVE_SIZE_MAX, "Invalid party order slot");
|
||||||
|
|
||||||
|
const uint8_t index = PARTY.order[slot];
|
||||||
|
if(index == PARTY_ORDER_EMPTY) return NULL;
|
||||||
|
return &PARTY.members[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
void partySetOrder(const uint8_t slot, const uint8_t memberIndex) {
|
||||||
|
assertTrue(slot < PARTY_ACTIVE_SIZE_MAX, "Invalid party order slot");
|
||||||
|
assertTrue(
|
||||||
|
memberIndex == PARTY_ORDER_EMPTY || memberIndex < PARTY_MEMBER_COUNT_MAX,
|
||||||
|
"Invalid party member index"
|
||||||
|
);
|
||||||
|
|
||||||
|
PARTY.order[slot] = memberIndex;
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "battlefighter.h"
|
||||||
|
|
||||||
|
#define PARTY_MEMBER_COUNT_MAX 4
|
||||||
|
#define PARTY_ACTIVE_SIZE_MAX 3
|
||||||
|
#define PARTY_ORDER_EMPTY 0xFF
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
battlefighter_t members[PARTY_MEMBER_COUNT_MAX];
|
||||||
|
|
||||||
|
// Maps an active battle slot to the roster member filling it, or
|
||||||
|
// PARTY_ORDER_EMPTY if the slot is unfilled. Only the first
|
||||||
|
// PARTY_ACTIVE_SIZE_MAX of the PARTY_MEMBER_COUNT_MAX roster members
|
||||||
|
// can be in the active lineup at once.
|
||||||
|
uint8_t order[PARTY_ACTIVE_SIZE_MAX];
|
||||||
|
} party_t;
|
||||||
|
|
||||||
|
extern party_t PARTY;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the party system with an empty roster and order.
|
||||||
|
*/
|
||||||
|
void partyInit(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets an available (unused) party member slot index.
|
||||||
|
*
|
||||||
|
* @return The index of an available slot, or 0xFF if the party is full.
|
||||||
|
*/
|
||||||
|
uint8_t partyGetAvailableMember(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a member to the party roster in the next available slot. Party
|
||||||
|
* members are always allies controlled by the player. If there is a
|
||||||
|
* free active order slot, the new member is placed into it.
|
||||||
|
*
|
||||||
|
* @param stats The member's base combat stats.
|
||||||
|
* @param healthMax The member's maximum health.
|
||||||
|
* @param mpMax The member's maximum mp.
|
||||||
|
* @return Pointer to the newly added party member, or NULL if the party is
|
||||||
|
* already full.
|
||||||
|
*/
|
||||||
|
battlefighter_t *partyAddMember(
|
||||||
|
const battlefighterstats_t stats,
|
||||||
|
const uint16_t healthMax,
|
||||||
|
const uint16_t mpMax
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the roster member currently occupying an active order slot.
|
||||||
|
*
|
||||||
|
* @param slot The active order slot to query.
|
||||||
|
* @return Pointer to the member in that slot, or NULL if the slot is
|
||||||
|
* empty.
|
||||||
|
*/
|
||||||
|
battlefighter_t *partyGetOrderMember(const uint8_t slot);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assigns a roster member to an active order slot, replacing whatever
|
||||||
|
* was there. Use PARTY_ORDER_EMPTY to clear a slot.
|
||||||
|
*
|
||||||
|
* @param slot The active order slot to assign.
|
||||||
|
* @param memberIndex The roster member index to place there, or
|
||||||
|
* PARTY_ORDER_EMPTY to clear the slot.
|
||||||
|
*/
|
||||||
|
void partySetOrder(const uint8_t slot, const uint8_t memberIndex);
|
||||||
@@ -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) \
|
||||||
@@ -70,6 +75,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, \
|
||||||
@@ -116,3 +150,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)
|
||||||
|
|||||||
@@ -25,10 +25,18 @@ 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.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 +62,9 @@ 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;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,25 +81,53 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
void cutsceneSystemDispose() {
|
void cutsceneSystemDispose() {
|
||||||
@@ -97,4 +136,7 @@ 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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,8 +10,15 @@
|
|||||||
|
|
||||||
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)
|
||||||
|
|
||||||
|
// 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 +26,16 @@ 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;
|
||||||
|
|
||||||
// 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 +67,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 +77,15 @@ 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);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Advance to the next item in the cutscene.
|
* Advance to the next item in the cutscene.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -12,4 +12,5 @@ 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)
|
||||||
|
|||||||
@@ -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).
|
||||||
|
|||||||
@@ -53,6 +53,30 @@ void cutsceneItemStart(const cutsceneitem_t *item, cutsceneitemdata_t *data) {
|
|||||||
cutsceneEntityRemoveStart(item, data);
|
cutsceneEntityRemoveStart(item, data);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case CUTSCENE_ITEM_TYPE_ENTITY_ADD:
|
||||||
|
cutsceneEntityAddStart(item, data);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CUTSCENE_ITEM_TYPE_ENTITY_TURN:
|
||||||
|
cutsceneEntityTurnStart(item, data);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CUTSCENE_ITEM_TYPE_ENTITY_WALK_TO_ENTITY:
|
||||||
|
cutsceneEntityWalkToEntityStart(item, data);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CUTSCENE_ITEM_TYPE_MAP_AREA_ADD:
|
||||||
|
cutsceneMapAreaAddStart(item, data);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CUTSCENE_ITEM_TYPE_MAP_AREA_REMOVE:
|
||||||
|
cutsceneMapAreaRemoveStart(item, data);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CUTSCENE_ITEM_TYPE_MAP_AREA_WAIT:
|
||||||
|
cutsceneMapAreaWaitStart(item, data);
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -90,6 +114,24 @@ bool_t cutsceneItemUpdate(const cutsceneitem_t *item, cutsceneitemdata_t *data)
|
|||||||
case CUTSCENE_ITEM_TYPE_ENTITY_REMOVE:
|
case CUTSCENE_ITEM_TYPE_ENTITY_REMOVE:
|
||||||
return cutsceneEntityRemoveUpdate(item, data);
|
return cutsceneEntityRemoveUpdate(item, data);
|
||||||
|
|
||||||
|
case CUTSCENE_ITEM_TYPE_ENTITY_ADD:
|
||||||
|
return cutsceneEntityAddUpdate(item, data);
|
||||||
|
|
||||||
|
case CUTSCENE_ITEM_TYPE_ENTITY_TURN:
|
||||||
|
return cutsceneEntityTurnUpdate(item, data);
|
||||||
|
|
||||||
|
case CUTSCENE_ITEM_TYPE_ENTITY_WALK_TO_ENTITY:
|
||||||
|
return cutsceneEntityWalkToEntityUpdate(item, data);
|
||||||
|
|
||||||
|
case CUTSCENE_ITEM_TYPE_MAP_AREA_ADD:
|
||||||
|
return cutsceneMapAreaAddUpdate(item, data);
|
||||||
|
|
||||||
|
case CUTSCENE_ITEM_TYPE_MAP_AREA_REMOVE:
|
||||||
|
return cutsceneMapAreaRemoveUpdate(item, data);
|
||||||
|
|
||||||
|
case CUTSCENE_ITEM_TYPE_MAP_AREA_WAIT:
|
||||||
|
return cutsceneMapAreaWaitUpdate(item, data);
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,9 +13,15 @@
|
|||||||
#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/cutscenefade.h"
|
#include "ui/cutscenefade.h"
|
||||||
#include "item/cutsceneitemgive.h"
|
#include "item/cutsceneitemgive.h"
|
||||||
|
#include "maparea/cutscenemapareaadd.h"
|
||||||
|
#include "maparea/cutscenemaparearemove.h"
|
||||||
|
#include "maparea/cutscenemapareawait.h"
|
||||||
|
|
||||||
typedef struct cutscene_s cutscene_t;
|
typedef struct cutscene_s cutscene_t;
|
||||||
|
|
||||||
@@ -31,7 +37,13 @@ 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
|
||||||
} cutsceneitemtype_t;
|
} cutsceneitemtype_t;
|
||||||
|
|
||||||
struct cutsceneitem_s {
|
struct cutsceneitem_s {
|
||||||
@@ -49,6 +61,12 @@ 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;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -56,6 +74,7 @@ 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;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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/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
|
||||||
|
);
|
||||||
@@ -9,17 +9,19 @@
|
|||||||
#include "rpg/cutscene/cutscene.h"
|
#include "rpg/cutscene/cutscene.h"
|
||||||
#include "rpg/cutscene/cutscenesystem.h"
|
#include "rpg/cutscene/cutscenesystem.h"
|
||||||
|
|
||||||
CUTSCENE(TEST_ONE, DEFAULT,
|
CUTSCENE(TEST_ONE, 0, DEFAULT,
|
||||||
CUTSCENE_TEXT("Test One."),
|
CUTSCENE_TEXT("Test One."),
|
||||||
);
|
);
|
||||||
|
|
||||||
CUTSCENE(TEST_TWO, DEFAULT,
|
CUTSCENE(TEST_TWO, 0, DEFAULT,
|
||||||
CUTSCENE_TEXT("Test Two."),
|
CUTSCENE_TEXT("Test Two."),
|
||||||
CUTSCENE_CONCURRENT(
|
CUTSCENE_ENTITY_ADD(ENTITY_TYPE_NPC, 4, 4, 0),
|
||||||
CUTSCENE_ENTITY_WALK_TO(CUTSCENE_ENTITY_INTERACT, 4, 4, 0),
|
CUTSCENE_ENTITY_WALK_TO(CUTSCENE_ENTITY_LAST_CREATED, 8, 2, 0),
|
||||||
CUTSCENE_ENTITY_WALK_TO(CUTSCENE_ENTITY_INTERACTED, 8, 2, 0),
|
// CUTSCENE_CONCURRENT(
|
||||||
),
|
// CUTSCENE_ENTITY_WALK_TO(CUTSCENE_ENTITY_INTERACT, 4, 4, 0),
|
||||||
CUTSCENE_ITEM_GIVE(ITEM_ID_POTATO, 3),
|
// CUTSCENE_ENTITY_WALK_TO(CUTSCENE_ENTITY_INTERACTED, 8, 2, 0),
|
||||||
CUTSCENE_ENTITY_REMOVE(CUTSCENE_ENTITY_INTERACT),
|
// ),
|
||||||
|
// CUTSCENE_ITEM_GIVE(ITEM_ID_POTATO, 3),
|
||||||
|
// CUTSCENE_ENTITY_REMOVE(CUTSCENE_ENTITY_INTERACT),
|
||||||
CUTSCENE_TEXT("Done."),
|
CUTSCENE_TEXT("Done."),
|
||||||
);
|
);
|
||||||
@@ -14,4 +14,6 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
|||||||
|
|
||||||
add_subdirectory(anim)
|
add_subdirectory(anim)
|
||||||
add_subdirectory(interact)
|
add_subdirectory(interact)
|
||||||
add_subdirectory(npc)
|
add_subdirectory(npc)
|
||||||
|
add_subdirectory(item)
|
||||||
|
add_subdirectory(global)
|
||||||
@@ -19,7 +19,7 @@ const entityanimcallback_t ENTITY_ANIM_CALLBACKS[ENTITY_ANIM_COUNT] = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
float_t entityAnimTileZOffset(const worldpos_t pos) {
|
float_t entityAnimTileZOffset(const worldpos_t pos) {
|
||||||
return tileIsRamp(mapGetTile(pos)) ? 0.5f : 0.0f;
|
return tileShapeIsRamp(mapGetTile(pos).shape) ? 0.5f : 0.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
void entityAnimUpdate(entity_t *entity) {
|
void entityAnimUpdate(entity_t *entity) {
|
||||||
@@ -32,7 +32,7 @@ void entityAnimUpdate(entity_t *entity) {
|
|||||||
) {
|
) {
|
||||||
entity->walkEndCooldown = ENTITY_ANIM_WALK_TURN_COOLDOWN;
|
entity->walkEndCooldown = ENTITY_ANIM_WALK_TURN_COOLDOWN;
|
||||||
}
|
}
|
||||||
entity->animation = ENTITY_ANIM_IDLE;
|
entity->animation = ENTITY_ANIM_IDLE;
|
||||||
entity->animTime = 0;
|
entity->animTime = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
#include "time/time.h"
|
#include "time/time.h"
|
||||||
#include "util/math.h"
|
#include "util/math.h"
|
||||||
#include "rpg/overworld/map.h"
|
#include "rpg/overworld/map.h"
|
||||||
|
#include "rpg/overworld/maparea.h"
|
||||||
#include "rpg/overworld/chunk.h"
|
#include "rpg/overworld/chunk.h"
|
||||||
#include "rpg/overworld/tile.h"
|
#include "rpg/overworld/tile.h"
|
||||||
|
|
||||||
@@ -27,6 +28,7 @@ void entityInit(entity_t *entity, const entitytype_t type) {
|
|||||||
|
|
||||||
memoryZero(entity, sizeof(entity_t));
|
memoryZero(entity, sizeof(entity_t));
|
||||||
entity->id = (uint8_t)(entity - ENTITIES);
|
entity->id = (uint8_t)(entity - ENTITIES);
|
||||||
|
entity->globalId = ENTITY_GLOBAL_ID_NULL;
|
||||||
entity->type = type;
|
entity->type = type;
|
||||||
entity->chunkIndex = 0xFF;
|
entity->chunkIndex = 0xFF;
|
||||||
|
|
||||||
@@ -60,6 +62,10 @@ bool_t entityCanRun(entity_t *entity) {
|
|||||||
return entity->animation == ENTITY_ANIM_IDLE;
|
return entity->animation == ENTITY_ANIM_IDLE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool_t entityCanUnload(entity_t *entity) {
|
||||||
|
return entity->globalId < ENTITY_GLOBAL_ID_START;
|
||||||
|
}
|
||||||
|
|
||||||
void entityTurn(entity_t *entity, const entitydir_t direction) {
|
void entityTurn(entity_t *entity, const entitydir_t direction) {
|
||||||
if(!entityCanTurn(entity)) return;
|
if(!entityCanTurn(entity)) return;
|
||||||
entity->direction = direction;
|
entity->direction = direction;
|
||||||
@@ -89,38 +95,38 @@ void entityWalk(entity_t *entity, const entitydir_t direction) {
|
|||||||
|
|
||||||
// Are we walking up a ramp?
|
// Are we walking up a ramp?
|
||||||
if(
|
if(
|
||||||
tileIsRamp(tileCurrent) &&
|
tileShapeIsRamp(tileCurrent.shape) &&
|
||||||
(
|
(
|
||||||
// Can only walk UP the direction the ramp faces.
|
// Can only walk UP the direction the ramp faces.
|
||||||
(direction+TILE_SHAPE_RAMP_NORTH) == tileCurrent ||
|
(direction+TILE_SHAPE_RAMP_NORTH) == tileCurrent.shape ||
|
||||||
// If diagonal ramp, can go up one of two ways only. Inner ramps
|
// If diagonal ramp, can go up one of two ways only. Inner ramps
|
||||||
// share the same allowed directions as their outer counterparts.
|
// share the same allowed directions as their outer counterparts.
|
||||||
(
|
(
|
||||||
(
|
(
|
||||||
(
|
(
|
||||||
tileCurrent == TILE_SHAPE_RAMP_SOUTHEAST ||
|
tileCurrent.shape == TILE_SHAPE_RAMP_SOUTHEAST ||
|
||||||
tileCurrent == TILE_SHAPE_RAMP_SOUTHEAST_INNER
|
tileCurrent.shape == TILE_SHAPE_RAMP_SOUTHEAST_INNER
|
||||||
) &&
|
) &&
|
||||||
(direction == ENTITY_DIR_SOUTH || direction == ENTITY_DIR_EAST)
|
(direction == ENTITY_DIR_SOUTH || direction == ENTITY_DIR_EAST)
|
||||||
) ||
|
) ||
|
||||||
(
|
(
|
||||||
(
|
(
|
||||||
tileCurrent == TILE_SHAPE_RAMP_SOUTHWEST ||
|
tileCurrent.shape == TILE_SHAPE_RAMP_SOUTHWEST ||
|
||||||
tileCurrent == TILE_SHAPE_RAMP_SOUTHWEST_INNER
|
tileCurrent.shape == TILE_SHAPE_RAMP_SOUTHWEST_INNER
|
||||||
) &&
|
) &&
|
||||||
(direction == ENTITY_DIR_SOUTH || direction == ENTITY_DIR_WEST)
|
(direction == ENTITY_DIR_SOUTH || direction == ENTITY_DIR_WEST)
|
||||||
) ||
|
) ||
|
||||||
(
|
(
|
||||||
(
|
(
|
||||||
tileCurrent == TILE_SHAPE_RAMP_NORTHEAST ||
|
tileCurrent.shape == TILE_SHAPE_RAMP_NORTHEAST ||
|
||||||
tileCurrent == TILE_SHAPE_RAMP_NORTHEAST_INNER
|
tileCurrent.shape == TILE_SHAPE_RAMP_NORTHEAST_INNER
|
||||||
) &&
|
) &&
|
||||||
(direction == ENTITY_DIR_NORTH || direction == ENTITY_DIR_EAST)
|
(direction == ENTITY_DIR_NORTH || direction == ENTITY_DIR_EAST)
|
||||||
) ||
|
) ||
|
||||||
(
|
(
|
||||||
(
|
(
|
||||||
tileCurrent == TILE_SHAPE_RAMP_NORTHWEST ||
|
tileCurrent.shape == TILE_SHAPE_RAMP_NORTHWEST ||
|
||||||
tileCurrent == TILE_SHAPE_RAMP_NORTHWEST_INNER
|
tileCurrent.shape == TILE_SHAPE_RAMP_NORTHWEST_INNER
|
||||||
) &&
|
) &&
|
||||||
(direction == ENTITY_DIR_NORTH || direction == ENTITY_DIR_WEST)
|
(direction == ENTITY_DIR_NORTH || direction == ENTITY_DIR_WEST)
|
||||||
)
|
)
|
||||||
@@ -129,55 +135,60 @@ void entityWalk(entity_t *entity, const entitydir_t direction) {
|
|||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
tile_t tileNewSaved = tileNew;
|
tile_t tileNewSaved = tileNew;
|
||||||
tileNew = TILE_SHAPE_NULL;
|
tileNew = TILE_NULL;
|
||||||
worldpos_t abovePos = newPos;
|
worldpos_t abovePos = newPos;
|
||||||
abovePos.z += 1;
|
abovePos.z += 1;
|
||||||
tile_t tileAbove = mapGetTile(abovePos);
|
tile_t tileAbove = mapGetTile(abovePos);
|
||||||
|
|
||||||
if(tileAbove != TILE_SHAPE_NULL && tileIsWalkable(tileAbove)) {
|
if(
|
||||||
|
tileAbove.shape != TILE_SHAPE_NULL &&
|
||||||
|
tileShapeIsWalkable(tileAbove.shape)
|
||||||
|
) {
|
||||||
raise = true;
|
raise = true;
|
||||||
} else {
|
} else {
|
||||||
tileNew = tileNewSaved;
|
tileNew = tileNewSaved;
|
||||||
}
|
}
|
||||||
} else if(tileNew == TILE_SHAPE_NULL && newPos.z > 0) {
|
} else if(tileNew.shape == TILE_SHAPE_NULL && newPos.z > 0) {
|
||||||
// Falling down?
|
// Falling down?
|
||||||
worldpos_t belowPos = newPos;
|
worldpos_t belowPos = newPos;
|
||||||
belowPos.z -= 1;
|
belowPos.z -= 1;
|
||||||
tile_t tileBelow = mapGetTile(belowPos);
|
tile_t tileBelow = mapGetTile(belowPos);
|
||||||
if(
|
if(
|
||||||
tileBelow != TILE_SHAPE_NULL &&
|
tileBelow.shape != TILE_SHAPE_NULL &&
|
||||||
tileIsRamp(tileBelow) &&
|
tileShapeIsRamp(tileBelow.shape) &&
|
||||||
(
|
(
|
||||||
// This handles regular cardinal ramps
|
// This handles regular cardinal ramps
|
||||||
(entityDirGetOpposite(direction)+TILE_SHAPE_RAMP_NORTH) == tileBelow ||
|
(
|
||||||
|
entityDirGetOpposite(direction)+TILE_SHAPE_RAMP_NORTH
|
||||||
|
) == tileBelow.shape ||
|
||||||
// This handles diagonal ramps. Inner ramps share the same
|
// This handles diagonal ramps. Inner ramps share the same
|
||||||
// allowed directions as their outer counterparts.
|
// allowed directions as their outer counterparts.
|
||||||
(
|
(
|
||||||
(
|
(
|
||||||
(
|
(
|
||||||
tileBelow == TILE_SHAPE_RAMP_SOUTHEAST ||
|
tileBelow.shape == TILE_SHAPE_RAMP_SOUTHEAST ||
|
||||||
tileBelow == TILE_SHAPE_RAMP_SOUTHEAST_INNER
|
tileBelow.shape == TILE_SHAPE_RAMP_SOUTHEAST_INNER
|
||||||
) &&
|
) &&
|
||||||
(direction == ENTITY_DIR_NORTH || direction == ENTITY_DIR_WEST)
|
(direction == ENTITY_DIR_NORTH || direction == ENTITY_DIR_WEST)
|
||||||
) ||
|
) ||
|
||||||
(
|
(
|
||||||
(
|
(
|
||||||
tileBelow == TILE_SHAPE_RAMP_SOUTHWEST ||
|
tileBelow.shape == TILE_SHAPE_RAMP_SOUTHWEST ||
|
||||||
tileBelow == TILE_SHAPE_RAMP_SOUTHWEST_INNER
|
tileBelow.shape == TILE_SHAPE_RAMP_SOUTHWEST_INNER
|
||||||
) &&
|
) &&
|
||||||
(direction == ENTITY_DIR_NORTH || direction == ENTITY_DIR_EAST)
|
(direction == ENTITY_DIR_NORTH || direction == ENTITY_DIR_EAST)
|
||||||
) ||
|
) ||
|
||||||
(
|
(
|
||||||
(
|
(
|
||||||
tileBelow == TILE_SHAPE_RAMP_NORTHEAST ||
|
tileBelow.shape == TILE_SHAPE_RAMP_NORTHEAST ||
|
||||||
tileBelow == TILE_SHAPE_RAMP_NORTHEAST_INNER
|
tileBelow.shape == TILE_SHAPE_RAMP_NORTHEAST_INNER
|
||||||
) &&
|
) &&
|
||||||
(direction == ENTITY_DIR_SOUTH || direction == ENTITY_DIR_WEST)
|
(direction == ENTITY_DIR_SOUTH || direction == ENTITY_DIR_WEST)
|
||||||
) ||
|
) ||
|
||||||
(
|
(
|
||||||
(
|
(
|
||||||
tileBelow == TILE_SHAPE_RAMP_NORTHWEST ||
|
tileBelow.shape == TILE_SHAPE_RAMP_NORTHWEST ||
|
||||||
tileBelow == TILE_SHAPE_RAMP_NORTHWEST_INNER
|
tileBelow.shape == TILE_SHAPE_RAMP_NORTHWEST_INNER
|
||||||
) &&
|
) &&
|
||||||
(direction == ENTITY_DIR_SOUTH || direction == ENTITY_DIR_EAST)
|
(direction == ENTITY_DIR_SOUTH || direction == ENTITY_DIR_EAST)
|
||||||
)
|
)
|
||||||
@@ -190,7 +201,7 @@ void entityWalk(entity_t *entity, const entitydir_t direction) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Can we walk here?
|
// Can we walk here?
|
||||||
if(!raise && !fall && !tileIsWalkable(tileNew)) return;// Blocked
|
if(!raise && !fall && !tileShapeIsWalkable(tileNew.shape)) return;// Blocked
|
||||||
|
|
||||||
// Raise/fall must be applied before checking for blocking entities,
|
// Raise/fall must be applied before checking for blocking entities,
|
||||||
// otherwise the check compares against the wrong z-level.
|
// otherwise the check compares against the wrong z-level.
|
||||||
@@ -213,6 +224,8 @@ void entityWalk(entity_t *entity, const entitydir_t direction) {
|
|||||||
entity->position = newPos;
|
entity->position = newPos;
|
||||||
entity->animation = ENTITY_ANIM_WALK;
|
entity->animation = ENTITY_ANIM_WALK;
|
||||||
entity->animTime = ENTITY_ANIM_WALK_DURATION;// TODO: Running vs walking
|
entity->animTime = ENTITY_ANIM_WALK_DURATION;// TODO: Running vs walking
|
||||||
|
entityUpdateChunk(entity);
|
||||||
|
mapAreaCheckEntity(entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
void entityRun(entity_t *entity, const entitydir_t direction) {
|
void entityRun(entity_t *entity, const entitydir_t direction) {
|
||||||
@@ -235,6 +248,17 @@ entity_t * entityGetAt(const worldpos_t position) {
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
entity_t * entityGetByGlobalId(const entityglobalid_t globalId) {
|
||||||
|
entity_t *ent = ENTITIES;
|
||||||
|
do {
|
||||||
|
if(ent->type == ENTITY_TYPE_NULL) continue;
|
||||||
|
if(ent->globalId != globalId) continue;
|
||||||
|
return ent;
|
||||||
|
} while(++ent, ent < &ENTITIES[ENTITY_COUNT]);
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
uint8_t entityGetAvailable() {
|
uint8_t entityGetAvailable() {
|
||||||
entity_t *ent = ENTITIES;
|
entity_t *ent = ENTITIES;
|
||||||
do {
|
do {
|
||||||
@@ -251,6 +275,7 @@ void entityPositionSet(entity_t *entity, const worldpos_t pos) {
|
|||||||
entity->animation = ENTITY_ANIM_IDLE;
|
entity->animation = ENTITY_ANIM_IDLE;
|
||||||
entity->animTime = 0;
|
entity->animTime = 0;
|
||||||
entity->walkEndCooldown = 0;
|
entity->walkEndCooldown = 0;
|
||||||
|
entityUpdateChunk(entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
void entitySetChunk(entity_t *entity, const uint8_t chunkIndex) {
|
void entitySetChunk(entity_t *entity, const uint8_t chunkIndex) {
|
||||||
@@ -279,4 +304,13 @@ void entitySetChunk(entity_t *entity, const uint8_t chunkIndex) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void entityUpdateChunk(entity_t *entity) {
|
||||||
|
assertNotNull(entity, "Entity pointer cannot be NULL");
|
||||||
|
|
||||||
|
chunkpos_t cp;
|
||||||
|
worldPosToChunkPos(&entity->position, &cp);
|
||||||
|
chunkindex_t ci = mapGetChunkIndexAt(cp);
|
||||||
|
if(ci != -1) entitySetChunk(entity, (uint8_t)ci);
|
||||||
}
|
}
|
||||||
@@ -14,8 +14,15 @@
|
|||||||
|
|
||||||
typedef struct map_s map_t;
|
typedef struct map_s map_t;
|
||||||
|
|
||||||
|
typedef uint16_t entityglobalid_t;
|
||||||
|
|
||||||
|
#define ENTITY_GLOBAL_ID_NULL 0
|
||||||
|
#define ENTITY_GLOBAL_ID_START 1
|
||||||
|
#define ENTITY_GLOBAL_ID_PLAYER 1
|
||||||
|
|
||||||
typedef struct entity_s {
|
typedef struct entity_s {
|
||||||
uint8_t id;
|
uint8_t id;
|
||||||
|
entityglobalid_t globalId;
|
||||||
entitytype_t type;
|
entitytype_t type;
|
||||||
entitytypedata_t data;
|
entitytypedata_t data;
|
||||||
|
|
||||||
@@ -75,6 +82,16 @@ bool_t entityCanWalk(entity_t *entity);
|
|||||||
*/
|
*/
|
||||||
bool_t entityCanRun(entity_t *entity);
|
bool_t entityCanRun(entity_t *entity);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the entity is allowed to be unloaded. By default this is
|
||||||
|
* true for entities whose global ID falls within the randomly assigned
|
||||||
|
* range below ENTITY_GLOBAL_ID_START.
|
||||||
|
*
|
||||||
|
* @param entity Pointer to the entity to check.
|
||||||
|
* @returns True if the entity can be unloaded.
|
||||||
|
*/
|
||||||
|
bool_t entityCanUnload(entity_t *entity);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Turn an entity to face a new direction.
|
* Turn an entity to face a new direction.
|
||||||
*
|
*
|
||||||
@@ -101,13 +118,21 @@ void entityRun(entity_t *entity, const entitydir_t direction);
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the entity at a specific world position.
|
* Gets the entity at a specific world position.
|
||||||
*
|
*
|
||||||
* @param map Pointer to the map to check.
|
* @param map Pointer to the map to check.
|
||||||
* @param pos The world position to check.
|
* @param pos The world position to check.
|
||||||
* @return Pointer to the entity at the position, or NULL if none.
|
* @return Pointer to the entity at the position, or NULL if none.
|
||||||
*/
|
*/
|
||||||
entity_t *entityGetAt(const worldpos_t pos);
|
entity_t *entityGetAt(const worldpos_t pos);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the entity with the given global ID, if one is currently loaded.
|
||||||
|
*
|
||||||
|
* @param globalId The global ID to search for.
|
||||||
|
* @return Pointer to the matching entity, or NULL if none is loaded.
|
||||||
|
*/
|
||||||
|
entity_t *entityGetByGlobalId(const entityglobalid_t globalId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets an available entity index.
|
* Gets an available entity index.
|
||||||
*
|
*
|
||||||
@@ -124,6 +149,15 @@ uint8_t entityGetAvailable();
|
|||||||
*/
|
*/
|
||||||
void entitySetChunk(entity_t *entity, const uint8_t chunkIndex);
|
void entitySetChunk(entity_t *entity, const uint8_t chunkIndex);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves the chunk that an entity's current position falls into and
|
||||||
|
* assigns the entity to it via entitySetChunk. Leaves the entity's chunk
|
||||||
|
* unchanged if its position doesn't fall within any loaded chunk.
|
||||||
|
*
|
||||||
|
* @param entity Pointer to the entity to update.
|
||||||
|
*/
|
||||||
|
void entityUpdateChunk(entity_t *entity);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Instantly moves an entity to a world position, resetting movement state.
|
* Instantly moves an entity to a world position, resetting movement state.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -24,7 +24,9 @@ bool_t entityPathStep(
|
|||||||
"Entity pointer is out of bounds"
|
"Entity pointer is out of bounds"
|
||||||
);
|
);
|
||||||
|
|
||||||
if(worldPosIsEqual(entity->position, target)) return true;
|
if(worldPosIsEqual(entity->position, target) && entityCanWalk(entity)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
if(!entityCanWalk(entity)) return false;
|
if(!entityCanWalk(entity)) return false;
|
||||||
|
|
||||||
entitydir_t dir;
|
entitydir_t dir;
|
||||||
|
|||||||
@@ -8,12 +8,14 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "rpg/entity/player.h"
|
#include "rpg/entity/player.h"
|
||||||
#include "npc/npc.h"
|
#include "npc/npc.h"
|
||||||
|
#include "item/entityitem.h"
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
ENTITY_TYPE_NULL,
|
ENTITY_TYPE_NULL,
|
||||||
|
|
||||||
ENTITY_TYPE_PLAYER,
|
ENTITY_TYPE_PLAYER,
|
||||||
ENTITY_TYPE_NPC,
|
ENTITY_TYPE_NPC,
|
||||||
|
ENTITY_TYPE_ITEM,
|
||||||
|
|
||||||
ENTITY_TYPE_COUNT
|
ENTITY_TYPE_COUNT
|
||||||
} entitytype_enum_t;
|
} entitytype_enum_t;
|
||||||
@@ -23,6 +25,7 @@ typedef uint8_t entitytype_t;
|
|||||||
typedef union {
|
typedef union {
|
||||||
player_t player;
|
player_t player;
|
||||||
npc_t npc;
|
npc_t npc;
|
||||||
|
entityitem_t item;
|
||||||
} entitytypedata_t;
|
} entitytypedata_t;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
@@ -50,5 +53,10 @@ static const entitycallback_t ENTITY_CALLBACKS[ENTITY_TYPE_COUNT] = {
|
|||||||
[ENTITY_TYPE_NPC] = {
|
[ENTITY_TYPE_NPC] = {
|
||||||
.init = npcInit,
|
.init = npcInit,
|
||||||
.movement = npcMovement,
|
.movement = npcMovement,
|
||||||
|
},
|
||||||
|
|
||||||
|
[ENTITY_TYPE_ITEM] = {
|
||||||
|
.init = entityItemInit,
|
||||||
|
.movement = entityItemMovement,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
# Copyright (c) 2026 Dominic Masters
|
||||||
|
#
|
||||||
|
# This software is released under the MIT License.
|
||||||
|
# https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
|
# Sources
|
||||||
|
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||||
|
PUBLIC
|
||||||
|
)
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "entityglobaldefs.h"
|
||||||
|
#include "entitygloballist.h"
|
||||||
|
|
||||||
|
#define ENTITY_GLOBAL_LIST_COUNT ( \
|
||||||
|
sizeof(ENTITY_GLOBAL_LIST) / \
|
||||||
|
sizeof(ENTITY_GLOBAL_LIST[0]) \
|
||||||
|
)
|
||||||
|
|
||||||
|
//EOF
|
||||||
@@ -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 "error/error.h"
|
||||||
|
#include "rpg/overworld/worldpos.h"
|
||||||
|
#include "rpg/entity/entity.h"
|
||||||
|
|
||||||
|
typedef struct entity_s entity_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
entity_t *entity;
|
||||||
|
worldpos_t position;
|
||||||
|
} entityglobalcreate_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback invoked to initialize a global entity.
|
||||||
|
*
|
||||||
|
* @param create Pointer to the entity/position being initialized.
|
||||||
|
* @returns An error code.
|
||||||
|
*/
|
||||||
|
typedef void (*entityglobalinitcallback_t)(
|
||||||
|
entityglobalcreate_t *create
|
||||||
|
);
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
entitytype_t type;
|
||||||
|
entityglobalinitcallback_t callback;
|
||||||
|
} entityglobaldef_t;
|
||||||
|
|
||||||
|
#define ENTITY_GLOBAL(id, entType, callbackFn) \
|
||||||
|
[id] = { .type = entType, .callback = callbackFn }
|
||||||
|
|
||||||
|
#define ENTITY_GLOBAL_CALLBACK(id) \
|
||||||
|
static void ENTTIYT_GLOBAL_CALLBACK_##id(entityglobalcreate_t *create)
|
||||||
|
|
||||||
|
#define ENTITY_GLOBAL_REF(id) \
|
||||||
|
ENTTIYT_GLOBAL_CALLBACK_##id
|
||||||
|
|
||||||
|
//EOF
|
||||||
@@ -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 "entityglobaldefs.h"
|
||||||
|
#include "rpg/cutscene/scene/testcutscene.h"
|
||||||
|
|
||||||
|
ENTITY_GLOBAL_CALLBACK(3) {
|
||||||
|
create->entity->data.npc.moveType = NPC_MOVE_TYPE_PATH;
|
||||||
|
npcPathAddNode(&create->entity->data.npc, (worldpos_t){ 4, 4, 0 });
|
||||||
|
npcPathAddNode(&create->entity->data.npc, (worldpos_t){ 10, 10, 1 });
|
||||||
|
npcPathAddNode(&create->entity->data.npc, (worldpos_t){ 4, 4, 0 });
|
||||||
|
npcPathAddNode(&create->entity->data.npc, (worldpos_t){ 10, 10, 1 });
|
||||||
|
|
||||||
|
create->entity->interact.type = ENTITY_INTERACT_CUTSCENE;
|
||||||
|
create->entity->interact.data.cutscene = CUTSCENE_REFERENCE(TEST_TWO);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const entityglobaldef_t ENTITY_GLOBAL_LIST[] = {
|
||||||
|
ENTITY_GLOBAL(ENTITY_GLOBAL_ID_NULL, ENTITY_TYPE_NULL, NULL),
|
||||||
|
ENTITY_GLOBAL(ENTITY_GLOBAL_ID_PLAYER, ENTITY_TYPE_PLAYER, NULL),
|
||||||
|
|
||||||
|
ENTITY_GLOBAL(3, ENTITY_TYPE_NPC, ENTITY_GLOBAL_REF(3)),
|
||||||
|
};
|
||||||
|
|
||||||
|
//EOF
|
||||||
@@ -40,6 +40,14 @@ void entityInteractWith(entity_t *player, entity_t *target) {
|
|||||||
// entityTurn(player, player->direction); // Redundant (for now)
|
// entityTurn(player, player->direction); // Redundant (for now)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case ENTITY_INTERACT_CALLBACK:
|
||||||
|
assertNotNull(
|
||||||
|
target->interact.data.callback,
|
||||||
|
"Interact callback pointer cannot be NULL"
|
||||||
|
);
|
||||||
|
target->interact.data.callback(player, target);
|
||||||
|
break;
|
||||||
|
|
||||||
case ENTITY_INTERACT_NULL:
|
case ENTITY_INTERACT_NULL:
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|||||||
@@ -7,9 +7,9 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "dusk.h"
|
#include "dusk.h"
|
||||||
#include "rpg/cutscene/cutscene.h"
|
|
||||||
|
|
||||||
typedef struct entity_s entity_t;
|
typedef struct entity_s entity_t;
|
||||||
|
typedef struct cutscene_s cutscene_t;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Describes the type of interaction an entity supports.
|
* Describes the type of interaction an entity supports.
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
# Copyright (c) 2026 Dominic Masters
|
||||||
|
#
|
||||||
|
# This software is released under the MIT License.
|
||||||
|
# https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
|
# Sources
|
||||||
|
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||||
|
PUBLIC
|
||||||
|
entityitem.c
|
||||||
|
)
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "entityitem.h"
|
||||||
|
#include "rpg/entity/entity.h"
|
||||||
|
#include "assert/assert.h"
|
||||||
|
#include "rpg/item/itemgive.h"
|
||||||
|
#include "ui/rpg/uitextboxmain.h"
|
||||||
|
|
||||||
|
void entityItemInit(entity_t *entity) {
|
||||||
|
assertNotNull(entity, "Entity pointer cannot be NULL");
|
||||||
|
entity->interact.type = ENTITY_INTERACT_CALLBACK;
|
||||||
|
entity->interact.data.callback = entityItemInteract;
|
||||||
|
}
|
||||||
|
|
||||||
|
void entityItemSet(
|
||||||
|
entity_t *entity,
|
||||||
|
const itemid_t item,
|
||||||
|
const uint8_t quantity
|
||||||
|
) {
|
||||||
|
assertNotNull(entity, "Entity pointer cannot be NULL");
|
||||||
|
entity->data.item.item = item;
|
||||||
|
entity->data.item.quantity = quantity;
|
||||||
|
}
|
||||||
|
|
||||||
|
void entityItemInteract(entity_t *player, entity_t *target) {
|
||||||
|
assertNotNull(player, "Player entity pointer cannot be NULL");
|
||||||
|
assertNotNull(target, "Target entity pointer cannot be NULL");
|
||||||
|
|
||||||
|
itemGive(target->data.item.item, target->data.item.quantity);
|
||||||
|
target->data.item.collected = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void entityItemMovement(entity_t *entity) {
|
||||||
|
assertNotNull(entity, "Entity pointer cannot be NULL");
|
||||||
|
if(!entity->data.item.collected) return;
|
||||||
|
if(uiTextboxMainIsActive()) return;
|
||||||
|
|
||||||
|
entity->type = ENTITY_TYPE_NULL;
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "rpg/item/item.h"
|
||||||
|
|
||||||
|
typedef struct entity_s entity_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
itemid_t item;
|
||||||
|
uint8_t quantity;
|
||||||
|
bool_t collected;
|
||||||
|
} entityitem_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes an item entity. Wires up the interact component so that
|
||||||
|
* interacting with the entity gives its item and removes it.
|
||||||
|
*
|
||||||
|
* @param entity Pointer to the entity structure to initialize.
|
||||||
|
*/
|
||||||
|
void entityItemInit(entity_t *entity);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the item and quantity represented by an item entity.
|
||||||
|
*
|
||||||
|
* @param entity Pointer to the entity structure.
|
||||||
|
* @param item The item ID to give when picked up.
|
||||||
|
* @param quantity The quantity to give when picked up.
|
||||||
|
*/
|
||||||
|
void entityItemSet(
|
||||||
|
entity_t *entity,
|
||||||
|
const itemid_t item,
|
||||||
|
const uint8_t quantity
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interact callback for item entities. Gives the entity's item to the
|
||||||
|
* player and shows the pickup message. The entity itself is removed once
|
||||||
|
* the message has been dismissed, via entityItemMovement.
|
||||||
|
*
|
||||||
|
* @param player Pointer to the player entity.
|
||||||
|
* @param target Pointer to the item entity being picked up.
|
||||||
|
*/
|
||||||
|
void entityItemInteract(entity_t *player, entity_t *target);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update callback for item entities. Removes the entity once its pickup
|
||||||
|
* message has been dismissed. No-op if the item hasn't been collected.
|
||||||
|
*
|
||||||
|
* @param entity Pointer to the entity structure to update.
|
||||||
|
*/
|
||||||
|
void entityItemMovement(entity_t *entity);
|
||||||
@@ -11,11 +11,12 @@
|
|||||||
#include "util/memory.h"
|
#include "util/memory.h"
|
||||||
#include "time/time.h"
|
#include "time/time.h"
|
||||||
#include "ui/focus/uifocus.h"
|
#include "ui/focus/uifocus.h"
|
||||||
#include "ui/frame/uisettings.h"
|
#include "ui/frame/game/uigamemenu.h"
|
||||||
#include "rpg/cutscene/cutscenesystem.h"
|
#include "rpg/cutscene/cutscenesystem.h"
|
||||||
|
|
||||||
void playerInit(entity_t *entity) {
|
void playerInit(entity_t *entity) {
|
||||||
assertNotNull(entity, "Entity pointer cannot be NULL");
|
assertNotNull(entity, "Entity pointer cannot be NULL");
|
||||||
|
entity->globalId = ENTITY_GLOBAL_ID_PLAYER;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool_t playerCanInteract(entity_t *entity) {
|
bool_t playerCanInteract(entity_t *entity) {
|
||||||
@@ -26,14 +27,14 @@ void playerInput(entity_t *entity) {
|
|||||||
assertNotNull(entity, "Entity pointer cannot be NULL");
|
assertNotNull(entity, "Entity pointer cannot be NULL");
|
||||||
if(CUTSCENE_SYSTEM.pause & CUTSCENE_PAUSE_PLAYER) return;
|
if(CUTSCENE_SYSTEM.pause & CUTSCENE_PAUSE_PLAYER) return;
|
||||||
|
|
||||||
// Toggle settings on pause
|
// Toggle game menu on pause
|
||||||
if(uiSettingsIsOpen() && inputPressed(INPUT_ACTION_PAUSE)) {
|
if(uiGameMenuIsOpen() && inputPressed(INPUT_ACTION_PAUSE)) {
|
||||||
uiSettingsClose();
|
uiGameMenuClose();
|
||||||
} else if(
|
} else if(
|
||||||
inputPressed(INPUT_ACTION_PAUSE) &&
|
inputPressed(INPUT_ACTION_PAUSE) &&
|
||||||
entity->animation == ENTITY_ANIM_IDLE
|
entity->animation == ENTITY_ANIM_IDLE
|
||||||
) {
|
) {
|
||||||
uiSettingsOpen();
|
uiGameMenuOpen();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Can player act?
|
// Can player act?
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "input/input.h"
|
#include "input/input.h"
|
||||||
|
#include "entitydir.h"
|
||||||
|
|
||||||
typedef struct entity_s entity_t;
|
typedef struct entity_s entity_t;
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
|||||||
PUBLIC
|
PUBLIC
|
||||||
inventory.c
|
inventory.c
|
||||||
backpack.c
|
backpack.c
|
||||||
|
itemgive.c
|
||||||
)
|
)
|
||||||
|
|
||||||
# Item Definitions
|
# Item Definitions
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "itemgive.h"
|
||||||
|
#include "rpg/item/backpack.h"
|
||||||
|
#include "ui/rpg/uitextboxmain.h"
|
||||||
|
#include "util/string.h"
|
||||||
|
|
||||||
|
void itemGive(const itemid_t item, const uint8_t quantity) {
|
||||||
|
backpackAdd(item, quantity);
|
||||||
|
|
||||||
|
char_t msg[ITEM_GIVE_MESSAGE_MAX_CHARS];
|
||||||
|
stringFormat(
|
||||||
|
msg,
|
||||||
|
ITEM_GIVE_MESSAGE_MAX_CHARS - 1,
|
||||||
|
"Received %s x%u",
|
||||||
|
ITEMS[item].name,
|
||||||
|
(uint32_t)quantity
|
||||||
|
);
|
||||||
|
uiTextboxMainSetText(msg);
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "rpg/item/item.h"
|
||||||
|
|
||||||
|
#define ITEM_GIVE_MESSAGE_MAX_CHARS 256
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a quantity of an item to the player's backpack and shows a
|
||||||
|
* "Received <item> x<quantity>" message in the main textbox.
|
||||||
|
*
|
||||||
|
* @param item The item ID to give.
|
||||||
|
* @param quantity The quantity to give.
|
||||||
|
*/
|
||||||
|
void itemGive(const itemid_t item, const uint8_t quantity);
|
||||||
@@ -8,7 +8,9 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
|||||||
PUBLIC
|
PUBLIC
|
||||||
chunk.c
|
chunk.c
|
||||||
map.c
|
map.c
|
||||||
|
maparea.c
|
||||||
worldpos.c
|
worldpos.c
|
||||||
tile.c
|
tile.c
|
||||||
|
tileshape.c
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -8,11 +8,7 @@
|
|||||||
#include "chunk.h"
|
#include "chunk.h"
|
||||||
|
|
||||||
uint32_t chunkGetTileIndex(const chunkpos_t position) {
|
uint32_t chunkGetTileIndex(const chunkpos_t position) {
|
||||||
return (
|
return (position.y * CHUNK_WIDTH) + position.x;
|
||||||
(position.z * CHUNK_WIDTH * CHUNK_HEIGHT) +
|
|
||||||
(position.y * CHUNK_WIDTH) +
|
|
||||||
position.x
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool_t chunkPositionIsEqual(const chunkpos_t a, const chunkpos_t b) {
|
bool_t chunkPositionIsEqual(const chunkpos_t a, const chunkpos_t b) {
|
||||||
|
|||||||
@@ -12,8 +12,8 @@
|
|||||||
#include "asset/loader/assetloader.h"
|
#include "asset/loader/assetloader.h"
|
||||||
#include "console/console.h"
|
#include "console/console.h"
|
||||||
#include "event/event.h"
|
#include "event/event.h"
|
||||||
#include "rpg/entity/entity.h"
|
|
||||||
#include "util/string.h"
|
#include "util/string.h"
|
||||||
|
#include "rpg/entity/global/entityglobal.h"
|
||||||
|
|
||||||
map_t MAP;
|
map_t MAP;
|
||||||
|
|
||||||
@@ -141,7 +141,7 @@ void mapChunkUnload(chunk_t *chunk) {
|
|||||||
for(uint8_t i = 0; i < CHUNK_ENTITY_COUNT_MAX; i++) {
|
for(uint8_t i = 0; i < CHUNK_ENTITY_COUNT_MAX; i++) {
|
||||||
if(chunk->entities[i] == 0xFF) continue;
|
if(chunk->entities[i] == 0xFF) continue;
|
||||||
entity_t *entity = &ENTITIES[chunk->entities[i]];
|
entity_t *entity = &ENTITIES[chunk->entities[i]];
|
||||||
if(entity->type == ENTITY_TYPE_PLAYER) {
|
if(!entityCanUnload(entity)) {
|
||||||
entitySetChunk(entity, 0xFF);
|
entitySetChunk(entity, 0xFF);
|
||||||
} else {
|
} else {
|
||||||
entity->type = ENTITY_TYPE_NULL;
|
entity->type = ENTITY_TYPE_NULL;
|
||||||
@@ -188,7 +188,10 @@ errorret_t mapChunkLoad(chunk_t *chunk) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if(!assetFileExists(name)) {
|
if(!assetFileExists(name)) {
|
||||||
memorySet(chunk->tiles, TILE_SHAPE_GROUND, sizeof(chunk->tiles));
|
for(uint32_t i = 0; i < CHUNK_TILE_COUNT; i++) {
|
||||||
|
// chunk->tiles[i] = (tile_t){ .shape = TILE_SHAPE_GROUND, .z = 0 };
|
||||||
|
chunk->tiles[i] = (tile_t){ .shape = TILE_SHAPE_GROUND };
|
||||||
|
}
|
||||||
errorOk();
|
errorOk();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -229,17 +232,82 @@ chunk_t *mapGetChunk(const uint8_t index) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
tile_t mapGetTile(const worldpos_t position) {
|
tile_t mapGetTile(const worldpos_t position) {
|
||||||
if(!mapIsLoaded()) return TILE_SHAPE_NULL;
|
if(!mapIsLoaded()) return TILE_NULL;
|
||||||
|
|
||||||
chunkpos_t chunkPos;
|
chunkpos_t chunkPos;
|
||||||
worldPosToChunkPos(&position, &chunkPos);
|
worldPosToChunkPos(&position, &chunkPos);
|
||||||
chunkindex_t chunkIndex = mapGetChunkIndexAt(chunkPos);
|
chunkindex_t chunkIndex = mapGetChunkIndexAt(chunkPos);
|
||||||
if(chunkIndex == -1) return TILE_SHAPE_NULL;
|
if(chunkIndex == -1) return TILE_NULL;
|
||||||
|
|
||||||
chunk_t *chunk = mapGetChunk(chunkIndex);
|
chunk_t *chunk = mapGetChunk(chunkIndex);
|
||||||
assertNotNull(chunk, "Chunk pointer cannot be NULL");
|
assertNotNull(chunk, "Chunk pointer cannot be NULL");
|
||||||
chunktileindex_t tileIndex = worldPosToChunkTileIndex(&position);
|
chunktileindex_t tileIndex = worldPosToChunkTileIndex(&position);
|
||||||
return chunk->tiles[tileIndex];
|
tile_t tile = chunk->tiles[tileIndex];
|
||||||
|
if(tile.z != worldPosToChunkLocalZ(&position)) return TILE_NULL;
|
||||||
|
return tile;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool_t mapGetWalkableZNear(
|
||||||
|
const worldunit_t x,
|
||||||
|
const worldunit_t y,
|
||||||
|
const worldunit_t nearZ,
|
||||||
|
worldunit_t *outZ
|
||||||
|
) {
|
||||||
|
assertNotNull(outZ, "Output Z pointer cannot be NULL");
|
||||||
|
|
||||||
|
const worldunit_t candidates[] = {
|
||||||
|
nearZ, (worldunit_t)(nearZ + 1), (worldunit_t)(nearZ - 1)
|
||||||
|
};
|
||||||
|
for(uint8_t i = 0; i < 3; i++) {
|
||||||
|
const worldpos_t pos = { x, y, candidates[i] };
|
||||||
|
if(!tileShapeIsWalkable(mapGetTile(pos).shape)) continue;
|
||||||
|
*outZ = candidates[i];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
entity_t * mapSpawnEntity(
|
||||||
|
const entityglobalid_t globalId,
|
||||||
|
const worldpos_t position
|
||||||
|
) {
|
||||||
|
assertTrue(
|
||||||
|
globalId > ENTITY_GLOBAL_ID_START,
|
||||||
|
"mapSpawnEntity requires a global ID greater than ENTITY_GLOBAL_ID_START"
|
||||||
|
);
|
||||||
|
assertTrue(
|
||||||
|
globalId < ENTITY_GLOBAL_LIST_COUNT,
|
||||||
|
"Global ID is out of range for entity global init callbacks"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Already spawned? Reuse the existing entity instead of making a
|
||||||
|
// duplicate - two entities must never share a global ID.
|
||||||
|
entity_t *existing = entityGetByGlobalId(globalId);
|
||||||
|
if(existing != NULL) return existing;
|
||||||
|
|
||||||
|
// See if there is a callback for this entity first.
|
||||||
|
const entityglobaldef_t *def = &ENTITY_GLOBAL_LIST[globalId];
|
||||||
|
assertNotNull(def, "No global entity definition for this ID");
|
||||||
|
assertNotNull(def->callback, "No callback registered for this global ID");
|
||||||
|
|
||||||
|
// Get available entity.
|
||||||
|
uint8_t index = entityGetAvailable();
|
||||||
|
assertTrue(index != 0xFF, "No available entity slots for mapSpawnEntity");
|
||||||
|
|
||||||
|
// Get the pointer and do the init.
|
||||||
|
entity_t *entity = &ENTITIES[index];
|
||||||
|
entityInit(entity, def->type);
|
||||||
|
entity->globalId = globalId;
|
||||||
|
entityPositionSet(entity, position);// Also assigns the entity's chunk.
|
||||||
|
|
||||||
|
// Invoke the callback to initialize the entity.
|
||||||
|
entityglobalcreate_t create = {
|
||||||
|
.entity = entity,
|
||||||
|
.position = position
|
||||||
|
};
|
||||||
|
def->callback(&create);
|
||||||
|
return entity;
|
||||||
}
|
}
|
||||||
|
|
||||||
void mapRebuildChunkOrder() {
|
void mapRebuildChunkOrder() {
|
||||||
@@ -273,7 +341,7 @@ void mapChunkLoadError(void *params, void *user) {
|
|||||||
);
|
);
|
||||||
assetUnlockEntry(chunk->dcfEntry);
|
assetUnlockEntry(chunk->dcfEntry);
|
||||||
chunk->dcfEntry = NULL;
|
chunk->dcfEntry = NULL;
|
||||||
memorySet(chunk->tiles, TILE_SHAPE_GROUND, sizeof(chunk->tiles));
|
memorySet(chunk->tiles, 0x00, sizeof(chunk->tiles));
|
||||||
}
|
}
|
||||||
|
|
||||||
void mapChunkLoaded(void *params, void *user) {
|
void mapChunkLoaded(void *params, void *user) {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "error/error.h"
|
#include "error/error.h"
|
||||||
#include "rpg/overworld/chunk.h"
|
#include "rpg/overworld/chunk.h"
|
||||||
|
#include "rpg/entity/entity.h"
|
||||||
|
|
||||||
#define MAP_FILE_PATH_MAX 128
|
#define MAP_FILE_PATH_MAX 128
|
||||||
|
|
||||||
@@ -36,18 +37,6 @@ errorret_t mapInit();
|
|||||||
*/
|
*/
|
||||||
bool_t mapIsLoaded();
|
bool_t mapIsLoaded();
|
||||||
|
|
||||||
/**
|
|
||||||
* Loads a map from the given file path.
|
|
||||||
*
|
|
||||||
* @param path The file path.
|
|
||||||
* @param position The initial chunk position.
|
|
||||||
* @return An error code.
|
|
||||||
*/
|
|
||||||
// errorret_t mapLoad(
|
|
||||||
// const char_t *path,
|
|
||||||
// const chunkpos_t position
|
|
||||||
// );
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the map.
|
* Updates the map.
|
||||||
*
|
*
|
||||||
@@ -128,8 +117,41 @@ chunk_t * mapGetChunk(const uint8_t chunkIndex);
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the tile at the given world position.
|
* Gets the tile at the given world position.
|
||||||
*
|
*
|
||||||
* @param position The world position.
|
* @param position The world position.
|
||||||
* @return The tile at that position, or TILE_NULL if the chunk is unloaded.
|
* @return The tile at that position, or TILE_NULL if the chunk is unloaded.
|
||||||
*/
|
*/
|
||||||
tile_t mapGetTile(const worldpos_t position);
|
tile_t mapGetTile(const worldpos_t position);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds the closest walkable Z layer to nearZ at the given X/Y. Checks
|
||||||
|
* nearZ first, then nearZ + 1, then nearZ - 1, since ramps only ever
|
||||||
|
* change height by one Z layer between adjacent tiles.
|
||||||
|
*
|
||||||
|
* @param x The world X coordinate to check.
|
||||||
|
* @param y The world Y coordinate to check.
|
||||||
|
* @param nearZ The reference Z layer to search outward from.
|
||||||
|
* @param outZ Output pointer, set to the resolved Z layer on success.
|
||||||
|
* @return true if a walkable tile was found, false otherwise.
|
||||||
|
*/
|
||||||
|
bool_t mapGetWalkableZNear(
|
||||||
|
const worldunit_t x,
|
||||||
|
const worldunit_t y,
|
||||||
|
const worldunit_t nearZ,
|
||||||
|
worldunit_t *outZ
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Spawns a global (persistent) entity into the world at the given position.
|
||||||
|
* Asserts globalId is greater than ENTITY_GLOBAL_ID_START - use entityInit
|
||||||
|
* directly for ephemeral, non-global entities.
|
||||||
|
*
|
||||||
|
* @param globalId The global entity ID to assign, must be greater than
|
||||||
|
* ENTITY_GLOBAL_ID_START.
|
||||||
|
* @param position The world position to spawn the entity at.
|
||||||
|
* @return Pointer to the spawned entity.
|
||||||
|
*/
|
||||||
|
entity_t * mapSpawnEntity(
|
||||||
|
const entityglobalid_t globalId,
|
||||||
|
const worldpos_t position
|
||||||
|
);
|
||||||
@@ -0,0 +1,150 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "maparea.h"
|
||||||
|
#include "assert/assert.h"
|
||||||
|
#include "util/math.h"
|
||||||
|
#include "util/memory.h"
|
||||||
|
#include "rpg/overworld/map.h"
|
||||||
|
|
||||||
|
maparea_t MAP_AREAS[MAP_AREA_COUNT_MAX];
|
||||||
|
|
||||||
|
void mapAreaInit(
|
||||||
|
maparea_t *area,
|
||||||
|
const worldpos_t min,
|
||||||
|
const worldpos_t max,
|
||||||
|
const mapareacallback_t callback,
|
||||||
|
const uint8_t notify,
|
||||||
|
const uint8_t trigger
|
||||||
|
) {
|
||||||
|
assertNotNull(area, "Map area pointer cannot be NULL");
|
||||||
|
assertNotNull(callback, "Map area callback cannot be NULL");
|
||||||
|
|
||||||
|
area->min.x = mathMin(min.x, max.x);
|
||||||
|
area->min.y = mathMin(min.y, max.y);
|
||||||
|
area->min.z = mathMin(min.z, max.z);
|
||||||
|
|
||||||
|
area->max.x = mathMax(min.x, max.x);
|
||||||
|
area->max.y = mathMax(min.y, max.y);
|
||||||
|
area->max.z = mathMax(min.z, max.z);
|
||||||
|
|
||||||
|
area->callback = callback;
|
||||||
|
area->notify = notify;
|
||||||
|
area->trigger = trigger;
|
||||||
|
area->triggerCount = 0;
|
||||||
|
|
||||||
|
memorySet(area->entities, 0, sizeof(area->entities));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool_t mapAreaIsInside(const maparea_t *area, const worldpos_t position) {
|
||||||
|
assertNotNull(area, "Map area pointer cannot be NULL");
|
||||||
|
|
||||||
|
return (
|
||||||
|
position.x >= area->min.x && position.x <= area->max.x &&
|
||||||
|
position.y >= area->min.y && position.y <= area->max.y &&
|
||||||
|
position.z >= area->min.z && position.z <= area->max.z
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool_t mapAreaIsChunkOverlappingOrInside(
|
||||||
|
const maparea_t *area,
|
||||||
|
const chunk_t *chunk
|
||||||
|
) {
|
||||||
|
assertNotNull(area, "Map area pointer cannot be NULL");
|
||||||
|
assertNotNull(chunk, "Chunk pointer cannot be NULL");
|
||||||
|
|
||||||
|
worldpos_t chunkMin, chunkMax;
|
||||||
|
chunkPosToWorldPos(&chunk->position, &chunkMin);
|
||||||
|
chunkMax.x = chunkMin.x + CHUNK_WIDTH - 1;
|
||||||
|
chunkMax.y = chunkMin.y + CHUNK_HEIGHT - 1;
|
||||||
|
chunkMax.z = chunkMin.z + CHUNK_DEPTH - 1;
|
||||||
|
|
||||||
|
return (
|
||||||
|
chunkMin.x <= area->max.x && area->min.x <= chunkMax.x &&
|
||||||
|
chunkMin.y <= area->max.y && area->min.y <= chunkMax.y &&
|
||||||
|
chunkMin.z <= area->max.z && area->min.z <= chunkMax.z
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool_t mapAreaCanUnload(const maparea_t *area) {
|
||||||
|
assertNotNull(area, "Map area pointer cannot be NULL");
|
||||||
|
|
||||||
|
for(chunkindex_t i = 0; i < MAP_LOADED_CHUNK_COUNT; i++) {
|
||||||
|
if(mapAreaIsChunkOverlappingOrInside(area, &MAP.chunks[i])) return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool_t mapAreaShouldNotify(const maparea_t *area, const entity_t *entity) {
|
||||||
|
assertNotNull(area, "Map area pointer cannot be NULL");
|
||||||
|
assertNotNull(entity, "Entity pointer cannot be NULL");
|
||||||
|
|
||||||
|
switch(entity->type) {
|
||||||
|
case ENTITY_TYPE_PLAYER:
|
||||||
|
return (area->notify & MAP_AREA_NOTIFY_PLAYER) != 0;
|
||||||
|
|
||||||
|
case ENTITY_TYPE_NPC:
|
||||||
|
return (area->notify & MAP_AREA_NOTIFY_NPC) != 0;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t mapAreaAdd(
|
||||||
|
const worldpos_t min,
|
||||||
|
const worldpos_t max,
|
||||||
|
const mapareacallback_t callback,
|
||||||
|
const uint8_t notify,
|
||||||
|
const uint8_t trigger
|
||||||
|
) {
|
||||||
|
for(uint8_t i = 0; i < MAP_AREA_COUNT_MAX; i++) {
|
||||||
|
if(MAP_AREAS[i].callback != NULL) continue;
|
||||||
|
mapAreaInit(&MAP_AREAS[i], min, max, callback, notify, trigger);
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
assertUnreachable("No available map area slots");
|
||||||
|
return 0xFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
void mapAreaRemove(const uint8_t id) {
|
||||||
|
assertTrue(id < MAP_AREA_COUNT_MAX, "Map area ID is out of range");
|
||||||
|
MAP_AREAS[id].callback = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void mapAreaCheckEntity(entity_t *entity) {
|
||||||
|
assertNotNull(entity, "Entity pointer cannot be NULL");
|
||||||
|
|
||||||
|
for(uint8_t i = 0; i < MAP_AREA_COUNT_MAX; i++) {
|
||||||
|
maparea_t *area = &MAP_AREAS[i];
|
||||||
|
if(area->callback == NULL) continue;
|
||||||
|
if(!mapAreaShouldNotify(area, entity)) continue;
|
||||||
|
|
||||||
|
bool_t wasInside = area->entities[entity->id] != 0;
|
||||||
|
bool_t isInside = mapAreaIsInside(area, entity->position);
|
||||||
|
uint8_t trigger = 0;
|
||||||
|
|
||||||
|
if(isInside && !wasInside) {
|
||||||
|
area->entities[entity->id] = 1;
|
||||||
|
trigger = MAP_TRIGGER_ENTER;
|
||||||
|
} else if(isInside && wasInside) {
|
||||||
|
trigger = MAP_TRIGGER_STEP;
|
||||||
|
} else if(!isInside && wasInside) {
|
||||||
|
area->entities[entity->id] = 0;
|
||||||
|
trigger = MAP_TRIGGER_EXIT;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(trigger == 0 || !(area->trigger & trigger)) continue;
|
||||||
|
area->triggerCount++;
|
||||||
|
area->callback(entity, trigger);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void mapAreaNoopCallback(entity_t *entity, const uint8_t trigger) {
|
||||||
|
}
|
||||||
@@ -0,0 +1,161 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "worldpos.h"
|
||||||
|
#include "rpg/entity/entity.h"
|
||||||
|
|
||||||
|
typedef struct chunk_s chunk_t;
|
||||||
|
typedef struct maparea_s maparea_t;
|
||||||
|
|
||||||
|
#define MAP_AREA_COUNT_MAX 32
|
||||||
|
|
||||||
|
#define MAP_AREA_NOTIFY_PLAYER (1 << 0)
|
||||||
|
#define MAP_AREA_NOTIFY_NPC (1 << 1)
|
||||||
|
#define MAP_AREA_NOTIFY_ALL (MAP_AREA_NOTIFY_PLAYER | MAP_AREA_NOTIFY_NPC)
|
||||||
|
|
||||||
|
#define MAP_TRIGGER_STEP (1 << 0)
|
||||||
|
#define MAP_TRIGGER_ENTER (1 << 1)
|
||||||
|
#define MAP_TRIGGER_EXIT (1 << 2)
|
||||||
|
#define MAP_TRIGGER_ALL (MAP_TRIGGER_STEP | MAP_TRIGGER_ENTER | MAP_TRIGGER_EXIT)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback invoked for a map area.
|
||||||
|
*
|
||||||
|
* @param entity Pointer to the entity associated with the callback.
|
||||||
|
* @param trigger Which MAP_TRIGGER_* condition invoked the callback.
|
||||||
|
*/
|
||||||
|
typedef void (*mapareacallback_t)(entity_t *entity, const uint8_t trigger);
|
||||||
|
|
||||||
|
typedef struct maparea_s {
|
||||||
|
worldpos_t min;
|
||||||
|
worldpos_t max;
|
||||||
|
mapareacallback_t callback;
|
||||||
|
uint8_t notify;
|
||||||
|
uint8_t trigger;
|
||||||
|
uint8_t entities[ENTITY_COUNT];
|
||||||
|
|
||||||
|
// Incremented every time this area's callback is invoked. Lets other
|
||||||
|
// systems (e.g. cutscenes) detect "has this area fired since I last
|
||||||
|
// checked" without needing to be the callback themselves.
|
||||||
|
uint32_t triggerCount;
|
||||||
|
} maparea_t;
|
||||||
|
|
||||||
|
extern maparea_t MAP_AREAS[MAP_AREA_COUNT_MAX];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes a map area with the given bounds, callback, notify flags
|
||||||
|
* and trigger flags.
|
||||||
|
*
|
||||||
|
* @param area Pointer to the map area to initialize.
|
||||||
|
* @param min The minimum world position of the area.
|
||||||
|
* @param max The maximum world position of the area.
|
||||||
|
* @param callback The callback to invoke for this area. Must not be NULL.
|
||||||
|
* @param notify Bitwise MAP_AREA_NOTIFY_* flags for which entity types
|
||||||
|
* should trigger the callback.
|
||||||
|
* @param trigger Bitwise MAP_TRIGGER_* flags for which conditions should
|
||||||
|
* invoke the callback.
|
||||||
|
*/
|
||||||
|
void mapAreaInit(
|
||||||
|
maparea_t *area,
|
||||||
|
const worldpos_t min,
|
||||||
|
const worldpos_t max,
|
||||||
|
const mapareacallback_t callback,
|
||||||
|
const uint8_t notify,
|
||||||
|
const uint8_t trigger
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether a world position falls within a map area's bounds
|
||||||
|
* (inclusive on all axes).
|
||||||
|
*
|
||||||
|
* @param area Pointer to the map area to check.
|
||||||
|
* @param position The world position to check.
|
||||||
|
* @returns true if the position is inside the area.
|
||||||
|
*/
|
||||||
|
bool_t mapAreaIsInside(const maparea_t *area, const worldpos_t position);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether a chunk's full footprint overlaps a map area's bounds,
|
||||||
|
* rather than just testing the chunk's origin corner.
|
||||||
|
*
|
||||||
|
* @param area Pointer to the map area to check.
|
||||||
|
* @param chunk Pointer to the chunk to check.
|
||||||
|
* @returns true if the chunk overlaps the area.
|
||||||
|
*/
|
||||||
|
bool_t mapAreaIsChunkOverlappingOrInside(
|
||||||
|
const maparea_t *area,
|
||||||
|
const chunk_t *chunk
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether a map area is safe to unload, i.e. no entity currently
|
||||||
|
* occupies a position within its bounds.
|
||||||
|
*
|
||||||
|
* @param area Pointer to the map area to check.
|
||||||
|
* @returns true if no entity is inside the area.
|
||||||
|
*/
|
||||||
|
bool_t mapAreaCanUnload(const maparea_t *area);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether an entity's type is included in a map area's notify flags.
|
||||||
|
*
|
||||||
|
* @param area Pointer to the map area to check.
|
||||||
|
* @param entity Pointer to the entity to check.
|
||||||
|
* @returns true if the entity's type should trigger this area's callback.
|
||||||
|
*/
|
||||||
|
bool_t mapAreaShouldNotify(const maparea_t *area, const entity_t *entity);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a map area to the global MAP_AREAS list, in the first free slot.
|
||||||
|
* A slot is considered free if its callback is NULL.
|
||||||
|
*
|
||||||
|
* @param min The minimum world position of the area.
|
||||||
|
* @param max The maximum world position of the area.
|
||||||
|
* @param callback The callback to invoke for this area. Must not be NULL.
|
||||||
|
* @param notify Bitwise MAP_AREA_NOTIFY_* flags for which entity types
|
||||||
|
* should trigger the callback.
|
||||||
|
* @param trigger Bitwise MAP_TRIGGER_* flags for which conditions should
|
||||||
|
* invoke the callback.
|
||||||
|
* @returns The ID of the newly added map area.
|
||||||
|
*/
|
||||||
|
uint8_t mapAreaAdd(
|
||||||
|
const worldpos_t min,
|
||||||
|
const worldpos_t max,
|
||||||
|
const mapareacallback_t callback,
|
||||||
|
const uint8_t notify,
|
||||||
|
const uint8_t trigger
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a map area from the global MAP_AREAS list, freeing its slot by
|
||||||
|
* clearing its callback to NULL.
|
||||||
|
*
|
||||||
|
* @param id The ID of the map area to remove.
|
||||||
|
*/
|
||||||
|
void mapAreaRemove(const uint8_t id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks every active map area against an entity's current position,
|
||||||
|
* for areas whose notify flags include the entity's type. Invokes the
|
||||||
|
* area's callback with MAP_TRIGGER_ENTER the frame the entity first
|
||||||
|
* becomes inside, MAP_TRIGGER_STEP on every subsequent frame it remains
|
||||||
|
* inside, and MAP_TRIGGER_EXIT the frame it leaves.
|
||||||
|
*
|
||||||
|
* @param entity Pointer to the entity to check.
|
||||||
|
*/
|
||||||
|
void mapAreaCheckEntity(entity_t *entity);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A map area callback that does nothing. Useful for areas whose only
|
||||||
|
* purpose is to be waited on (see CUTSCENE_MAP_AREA_WAIT), since the
|
||||||
|
* wait is driven by triggerCount rather than callback logic.
|
||||||
|
*
|
||||||
|
* @param entity Pointer to the entity associated with the callback.
|
||||||
|
* @param trigger Which MAP_TRIGGER_* condition invoked the callback.
|
||||||
|
*/
|
||||||
|
void mapAreaNoopCallback(entity_t *entity, const uint8_t trigger);
|
||||||
@@ -6,34 +6,3 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "tile.h"
|
#include "tile.h"
|
||||||
|
|
||||||
bool_t tileIsWalkable(const tile_t tile) {
|
|
||||||
switch(tile) {
|
|
||||||
case TILE_SHAPE_NULL:
|
|
||||||
return false;
|
|
||||||
|
|
||||||
default:
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool_t tileIsRamp(const tile_t tile) {
|
|
||||||
switch(tile) {
|
|
||||||
case TILE_SHAPE_RAMP_NORTH:
|
|
||||||
case TILE_SHAPE_RAMP_SOUTH:
|
|
||||||
case TILE_SHAPE_RAMP_EAST:
|
|
||||||
case TILE_SHAPE_RAMP_WEST:
|
|
||||||
case TILE_SHAPE_RAMP_NORTHEAST:
|
|
||||||
case TILE_SHAPE_RAMP_NORTHWEST:
|
|
||||||
case TILE_SHAPE_RAMP_SOUTHEAST:
|
|
||||||
case TILE_SHAPE_RAMP_SOUTHWEST:
|
|
||||||
case TILE_SHAPE_RAMP_NORTHEAST_INNER:
|
|
||||||
case TILE_SHAPE_RAMP_NORTHWEST_INNER:
|
|
||||||
case TILE_SHAPE_RAMP_SOUTHEAST_INNER:
|
|
||||||
case TILE_SHAPE_RAMP_SOUTHWEST_INNER:
|
|
||||||
return true;
|
|
||||||
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -6,41 +6,15 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "rpg/entity/entitydir.h"
|
#include "tileshape.h"
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
tileshape_t shape;
|
||||||
|
|
||||||
typedef enum {
|
// Local Z layer (0..CHUNK_DEPTH-1) this tile occupies within its chunk's
|
||||||
TILE_SHAPE_NULL = 0,
|
// depth slab. Used for entity navigation since chunk_t only stores one
|
||||||
|
// tile per X/Y column rather than one per X/Y/Z.
|
||||||
TILE_SHAPE_GROUND = 1,
|
uint8_t z;
|
||||||
TILE_SHAPE_RAMP_NORTH = 2,
|
|
||||||
TILE_SHAPE_RAMP_EAST = 3,
|
|
||||||
TILE_SHAPE_RAMP_SOUTH = 4,
|
|
||||||
TILE_SHAPE_RAMP_WEST = 5,
|
|
||||||
TILE_SHAPE_RAMP_NORTHEAST = 6,
|
|
||||||
TILE_SHAPE_RAMP_NORTHWEST = 7,
|
|
||||||
TILE_SHAPE_RAMP_SOUTHEAST = 8,
|
|
||||||
TILE_SHAPE_RAMP_SOUTHWEST = 9,
|
|
||||||
TILE_SHAPE_RAMP_NORTHEAST_INNER = 10,
|
|
||||||
TILE_SHAPE_RAMP_NORTHWEST_INNER = 11,
|
|
||||||
TILE_SHAPE_RAMP_SOUTHEAST_INNER = 12,
|
|
||||||
TILE_SHAPE_RAMP_SOUTHWEST_INNER = 13,
|
|
||||||
|
|
||||||
TILE_SHAPE_COUNT
|
|
||||||
} tile_t;
|
} tile_t;
|
||||||
|
|
||||||
/**
|
#define TILE_NULL ((tile_t){ .shape = TILE_SHAPE_NULL, .z = 0 })
|
||||||
* Returns whether or not the given tile is walkable.
|
|
||||||
*
|
|
||||||
* @param tile The tile to check.
|
|
||||||
* @return bool_t True if walkable, false if not.
|
|
||||||
*/
|
|
||||||
bool_t tileIsWalkable(const tile_t tile);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns whether or not the given tile is a ramp tile.
|
|
||||||
*
|
|
||||||
* @param tile The tile to check.
|
|
||||||
* @return bool_t True if ramp, false if not.
|
|
||||||
*/
|
|
||||||
bool_t tileIsRamp(const tile_t tile);
|
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "tileshape.h"
|
||||||
|
|
||||||
|
bool_t tileShapeIsWalkable(const tileshape_t shape) {
|
||||||
|
switch(shape) {
|
||||||
|
case TILE_SHAPE_NULL:
|
||||||
|
return false;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool_t tileShapeIsRamp(const tileshape_t shape) {
|
||||||
|
switch(shape) {
|
||||||
|
case TILE_SHAPE_RAMP_NORTH:
|
||||||
|
case TILE_SHAPE_RAMP_SOUTH:
|
||||||
|
case TILE_SHAPE_RAMP_EAST:
|
||||||
|
case TILE_SHAPE_RAMP_WEST:
|
||||||
|
case TILE_SHAPE_RAMP_NORTHEAST:
|
||||||
|
case TILE_SHAPE_RAMP_NORTHWEST:
|
||||||
|
case TILE_SHAPE_RAMP_SOUTHEAST:
|
||||||
|
case TILE_SHAPE_RAMP_SOUTHWEST:
|
||||||
|
case TILE_SHAPE_RAMP_NORTHEAST_INNER:
|
||||||
|
case TILE_SHAPE_RAMP_NORTHWEST_INNER:
|
||||||
|
case TILE_SHAPE_RAMP_SOUTHEAST_INNER:
|
||||||
|
case TILE_SHAPE_RAMP_SOUTHWEST_INNER:
|
||||||
|
return true;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
/**
|
||||||
|
* 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 enum {
|
||||||
|
TILE_SHAPE_NULL = 0,
|
||||||
|
|
||||||
|
TILE_SHAPE_GROUND = 1,
|
||||||
|
TILE_SHAPE_RAMP_NORTH = 2,
|
||||||
|
TILE_SHAPE_RAMP_EAST = 3,
|
||||||
|
TILE_SHAPE_RAMP_SOUTH = 4,
|
||||||
|
TILE_SHAPE_RAMP_WEST = 5,
|
||||||
|
TILE_SHAPE_RAMP_NORTHEAST = 6,
|
||||||
|
TILE_SHAPE_RAMP_NORTHWEST = 7,
|
||||||
|
TILE_SHAPE_RAMP_SOUTHEAST = 8,
|
||||||
|
TILE_SHAPE_RAMP_SOUTHWEST = 9,
|
||||||
|
TILE_SHAPE_RAMP_NORTHEAST_INNER = 10,
|
||||||
|
TILE_SHAPE_RAMP_NORTHWEST_INNER = 11,
|
||||||
|
TILE_SHAPE_RAMP_SOUTHEAST_INNER = 12,
|
||||||
|
TILE_SHAPE_RAMP_SOUTHWEST_INNER = 13,
|
||||||
|
|
||||||
|
TILE_SHAPE_COUNT
|
||||||
|
} tileshape_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether or not the given tile shape is a ramp.
|
||||||
|
*
|
||||||
|
* @param shape The tile shape to check.
|
||||||
|
* @return bool_t True if ramp, false if not.
|
||||||
|
*/
|
||||||
|
bool_t tileShapeIsRamp(const tileshape_t shape);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether or not the given tile shape is walkable.
|
||||||
|
*
|
||||||
|
* @param shape The tile shape to check.
|
||||||
|
* @return bool_t True if walkable, false if not.
|
||||||
|
*/
|
||||||
|
bool_t tileShapeIsWalkable(const tileshape_t shape);
|
||||||
@@ -47,7 +47,7 @@ void worldPosToChunkPos(const worldpos_t* worldPos, chunkpos_t* out) {
|
|||||||
chunktileindex_t worldPosToChunkTileIndex(const worldpos_t* worldPos) {
|
chunktileindex_t worldPosToChunkTileIndex(const worldpos_t* worldPos) {
|
||||||
assertNotNull(worldPos, "World position pointer cannot be NULL");
|
assertNotNull(worldPos, "World position pointer cannot be NULL");
|
||||||
|
|
||||||
uint8_t localX, localY, localZ;
|
uint8_t localX, localY;
|
||||||
if(worldPos->x < 0) {
|
if(worldPos->x < 0) {
|
||||||
localX = (uint8_t)(
|
localX = (uint8_t)(
|
||||||
(CHUNK_WIDTH - 1) - ((-worldPos->x - 1) % CHUNK_WIDTH)
|
(CHUNK_WIDTH - 1) - ((-worldPos->x - 1) % CHUNK_WIDTH)
|
||||||
@@ -64,18 +64,8 @@ chunktileindex_t worldPosToChunkTileIndex(const worldpos_t* worldPos) {
|
|||||||
localY = (uint8_t)(worldPos->y % CHUNK_HEIGHT);
|
localY = (uint8_t)(worldPos->y % CHUNK_HEIGHT);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(worldPos->z < 0) {
|
|
||||||
localZ = (uint8_t)(
|
|
||||||
(CHUNK_DEPTH - 1) - ((-worldPos->z - 1) % CHUNK_DEPTH)
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
localZ = (uint8_t)(worldPos->z % CHUNK_DEPTH);
|
|
||||||
}
|
|
||||||
|
|
||||||
chunktileindex_t chunkTileIndex = (chunktileindex_t)(
|
chunktileindex_t chunkTileIndex = (chunktileindex_t)(
|
||||||
(localZ * CHUNK_WIDTH * CHUNK_HEIGHT) +
|
(localY * CHUNK_WIDTH) + localX
|
||||||
(localY * CHUNK_WIDTH) +
|
|
||||||
localX
|
|
||||||
);
|
);
|
||||||
assertTrue(
|
assertTrue(
|
||||||
chunkTileIndex < CHUNK_TILE_COUNT,
|
chunkTileIndex < CHUNK_TILE_COUNT,
|
||||||
@@ -84,6 +74,17 @@ chunktileindex_t worldPosToChunkTileIndex(const worldpos_t* worldPos) {
|
|||||||
return chunkTileIndex;
|
return chunkTileIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint8_t worldPosToChunkLocalZ(const worldpos_t* worldPos) {
|
||||||
|
assertNotNull(worldPos, "World position pointer cannot be NULL");
|
||||||
|
|
||||||
|
if(worldPos->z < 0) {
|
||||||
|
return (uint8_t)(
|
||||||
|
(CHUNK_DEPTH - 1) - ((-worldPos->z - 1) % CHUNK_DEPTH)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (uint8_t)(worldPos->z % CHUNK_DEPTH);
|
||||||
|
}
|
||||||
|
|
||||||
chunkindex_t chunkPosToIndex(const chunkpos_t* pos) {
|
chunkindex_t chunkPosToIndex(const chunkpos_t* pos) {
|
||||||
assertNotNull(pos, "Chunk position pointer cannot be NULL");
|
assertNotNull(pos, "Chunk position pointer cannot be NULL");
|
||||||
|
|
||||||
|
|||||||
@@ -19,9 +19,11 @@
|
|||||||
|
|
||||||
#define CHUNK_WIDTH 16
|
#define CHUNK_WIDTH 16
|
||||||
#define CHUNK_HEIGHT 16
|
#define CHUNK_HEIGHT 16
|
||||||
#define CHUNK_DEPTH 4
|
#define CHUNK_DEPTH 8
|
||||||
|
|
||||||
#define CHUNK_TILE_COUNT (CHUNK_WIDTH * CHUNK_HEIGHT * CHUNK_DEPTH)
|
// Chunks store one tile per X/Y column, not one per X/Y/Z - each tile
|
||||||
|
// records its own local Z layer (see tile_t) instead.
|
||||||
|
#define CHUNK_TILE_COUNT (CHUNK_WIDTH * CHUNK_HEIGHT)
|
||||||
|
|
||||||
#define MAP_CHUNK_WIDTH 3
|
#define MAP_CHUNK_WIDTH 3
|
||||||
#define MAP_CHUNK_HEIGHT 3
|
#define MAP_CHUNK_HEIGHT 3
|
||||||
@@ -48,6 +50,10 @@ typedef uint32_t chunktileindex_t;
|
|||||||
typedef int32_t worldunits_t;
|
typedef int32_t worldunits_t;
|
||||||
typedef int32_t chunkunits_t;
|
typedef int32_t chunkunits_t;
|
||||||
|
|
||||||
|
typedef struct worldpos2d_s {
|
||||||
|
worldunit_t x, y;
|
||||||
|
} worldpos2d_t;
|
||||||
|
|
||||||
typedef struct worldpos_s {
|
typedef struct worldpos_s {
|
||||||
worldunit_t x, y, z;
|
worldunit_t x, y, z;
|
||||||
} worldpos_t;
|
} worldpos_t;
|
||||||
@@ -84,12 +90,21 @@ void worldPosToChunkPos(const worldpos_t* worldPos, chunkpos_t* out);
|
|||||||
/**
|
/**
|
||||||
* Converts a position in world-space to an index inside a chunk that the tile
|
* Converts a position in world-space to an index inside a chunk that the tile
|
||||||
* resides in.
|
* resides in.
|
||||||
*
|
*
|
||||||
* @param worldPos The world position.
|
* @param worldPos The world position.
|
||||||
* @return The tile index within the chunk.
|
* @return The tile index within the chunk.
|
||||||
*/
|
*/
|
||||||
chunktileindex_t worldPosToChunkTileIndex(const worldpos_t* worldPos);
|
chunktileindex_t worldPosToChunkTileIndex(const worldpos_t* worldPos);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a world-space Z coordinate to the local Z layer (0..CHUNK_DEPTH-1)
|
||||||
|
* within the chunk that owns it, for comparison against tile_t.z.
|
||||||
|
*
|
||||||
|
* @param worldPos The world position.
|
||||||
|
* @return The local Z layer within the owning chunk.
|
||||||
|
*/
|
||||||
|
uint8_t worldPosToChunkLocalZ(const worldpos_t* worldPos);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts a chunk position to a world position.
|
* Converts a chunk position to a world position.
|
||||||
*
|
*
|
||||||
|
|||||||
+29
-24
@@ -8,20 +8,30 @@
|
|||||||
#include "rpg.h"
|
#include "rpg.h"
|
||||||
#include "entity/entity.h"
|
#include "entity/entity.h"
|
||||||
#include "rpg/entity/npc/npcpath.h"
|
#include "rpg/entity/npc/npcpath.h"
|
||||||
|
#include "rpg/entity/item/entityitem.h"
|
||||||
#include "rpg/overworld/map.h"
|
#include "rpg/overworld/map.h"
|
||||||
|
#include "rpg/overworld/maparea.h"
|
||||||
#include "rpg/cutscene/cutscenesystem.h"
|
#include "rpg/cutscene/cutscenesystem.h"
|
||||||
#include "rpg/cutscene/scene/testcutscene.h"
|
#include "rpg/cutscene/scene/testcutscene.h"
|
||||||
#include "rpg/item/backpack.h"
|
#include "rpg/item/backpack.h"
|
||||||
|
#include "rpg/battle/party.h"
|
||||||
#include "time/time.h"
|
#include "time/time.h"
|
||||||
#include "rpgcamera.h"
|
#include "rpgcamera.h"
|
||||||
#include "util/memory.h"
|
#include "util/memory.h"
|
||||||
#include "util/string.h"
|
#include "util/string.h"
|
||||||
#include "assert/assert.h"
|
#include "assert/assert.h"
|
||||||
|
#include "console/console.h"
|
||||||
|
|
||||||
|
void rpgTestAreaCallback(entity_t *entity, const uint8_t trigger) {
|
||||||
|
consolePrint("rpgTestAreaCallback: trigger=%u", trigger);
|
||||||
|
}
|
||||||
|
|
||||||
errorret_t rpgInit(void) {
|
errorret_t rpgInit(void) {
|
||||||
memoryZero(ENTITIES, sizeof(ENTITIES));
|
memoryZero(ENTITIES, sizeof(ENTITIES));
|
||||||
|
memoryZero(MAP_AREAS, sizeof(MAP_AREAS));
|
||||||
|
|
||||||
backpackInit();
|
backpackInit();
|
||||||
|
partyInit();
|
||||||
cutsceneSystemInit();
|
cutsceneSystemInit();
|
||||||
|
|
||||||
errorChain(mapInit());
|
errorChain(mapInit());
|
||||||
@@ -35,34 +45,29 @@ errorret_t rpgInit(void) {
|
|||||||
assertTrue(entIndex != 0xFF, "No available entity slots!.");
|
assertTrue(entIndex != 0xFF, "No available entity slots!.");
|
||||||
entity_t *ent = &ENTITIES[entIndex];
|
entity_t *ent = &ENTITIES[entIndex];
|
||||||
entityInit(ent, ENTITY_TYPE_PLAYER);
|
entityInit(ent, ENTITY_TYPE_PLAYER);
|
||||||
entityPositionSet(ent, (worldpos_t){ 10, 2, 0 });
|
entityPositionSet(ent, (worldpos_t){ 10, 2, 0 });// Also assigns the chunk.
|
||||||
RPG_CAMERA.mode = RPG_CAMERA_MODE_FOLLOW_ENTITY;
|
RPG_CAMERA.mode = RPG_CAMERA_MODE_FOLLOW_ENTITY;
|
||||||
RPG_CAMERA.followEntity.followEntityId = ent->id;
|
RPG_CAMERA.followEntity.followEntityId = ent->id;
|
||||||
{
|
|
||||||
chunkpos_t cp;
|
|
||||||
worldPosToChunkPos(&ent->position, &cp);
|
|
||||||
chunkindex_t ci = mapGetChunkIndexAt(cp);
|
|
||||||
if(ci != -1) entitySetChunk(ent, (uint8_t)ci);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t npcIndex = entityGetAvailable();
|
mapSpawnEntity(3, (worldpos_t){ 8, 8, 1 });
|
||||||
entity_t *npc = &ENTITIES[npcIndex];
|
|
||||||
entityInit(npc, ENTITY_TYPE_NPC);
|
|
||||||
npcSetMoveType(npc, NPC_MOVE_TYPE_PATH);
|
|
||||||
entityPositionSet(npc, (worldpos_t){ 8, 8, 1 });
|
|
||||||
npc->interact.type = ENTITY_INTERACT_CUTSCENE;
|
|
||||||
npc->interact.data.cutscene = CUTSCENE_REFERENCE(TEST_TWO);
|
|
||||||
{
|
|
||||||
chunkpos_t cp;
|
|
||||||
worldPosToChunkPos(&npc->position, &cp);
|
|
||||||
chunkindex_t ci = mapGetChunkIndexAt(cp);
|
|
||||||
if(ci != -1) entitySetChunk(npc, (uint8_t)ci);
|
|
||||||
}
|
|
||||||
|
|
||||||
npcPathAddNode(&npc->data.npc, (worldpos_t){ 4, 4, 0 });
|
// TEST: Place an item entity.
|
||||||
npcPathAddNode(&npc->data.npc, (worldpos_t){ 10, 10, 1 });
|
uint8_t itemEntIndex = entityGetAvailable();
|
||||||
npcPathAddNode(&npc->data.npc, (worldpos_t){ 4, 4, 0 });
|
assertTrue(itemEntIndex != 0xFF, "No available entity slots!.");
|
||||||
npcPathAddNode(&npc->data.npc, (worldpos_t){ 10, 10, 1 });
|
entity_t *itemEnt = &ENTITIES[itemEntIndex];
|
||||||
|
entityInit(itemEnt, ENTITY_TYPE_ITEM);
|
||||||
|
entityItemSet(itemEnt, ITEM_ID_POTION, 1);
|
||||||
|
entityPositionSet(itemEnt, (worldpos_t){ 12, 2, 0 });
|
||||||
|
|
||||||
|
// TEST: Create a test map area.
|
||||||
|
uint8_t areaIndex = mapAreaAdd(
|
||||||
|
(worldpos_t){ 11, 3, 0 },
|
||||||
|
(worldpos_t){ 16, 9, 10 },
|
||||||
|
rpgTestAreaCallback,
|
||||||
|
MAP_AREA_NOTIFY_ALL,
|
||||||
|
MAP_TRIGGER_ENTER | MAP_TRIGGER_EXIT
|
||||||
|
);
|
||||||
|
assertTrue(areaIndex != 0xFF, "No available map area slots!.");
|
||||||
|
|
||||||
// All Good!
|
// All Good!
|
||||||
errorOk();
|
errorOk();
|
||||||
|
|||||||
+1
-1
@@ -28,7 +28,7 @@ errorret_t rpgUpdate(void);
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Dispose of the RPG subsystem.
|
* Dispose of the RPG subsystem.
|
||||||
*
|
*
|
||||||
* @return An error code.
|
* @return An error code.
|
||||||
*/
|
*/
|
||||||
errorret_t rpgDispose(void);
|
errorret_t rpgDispose(void);
|
||||||
@@ -31,6 +31,7 @@ uifocusitem_t * uiFocusPush(
|
|||||||
uifocusitemcallback_t selected,
|
uifocusitemcallback_t selected,
|
||||||
uifocusitemcallback_t changed,
|
uifocusitemcallback_t changed,
|
||||||
uifocusitemcallback_t closed,
|
uifocusitemcallback_t closed,
|
||||||
|
uifocusitemdirectioncallback_t direction,
|
||||||
void *user
|
void *user
|
||||||
) {
|
) {
|
||||||
assertTrue(
|
assertTrue(
|
||||||
@@ -47,6 +48,7 @@ uifocusitem_t * uiFocusPush(
|
|||||||
item->selected = selected;
|
item->selected = selected;
|
||||||
item->changed = changed;
|
item->changed = changed;
|
||||||
item->closed = closed;
|
item->closed = closed;
|
||||||
|
item->direction = direction;
|
||||||
item->user = user;
|
item->user = user;
|
||||||
UI_FOCUS.count++;
|
UI_FOCUS.count++;
|
||||||
UI_FOCUS.pushedThisTick = true;
|
UI_FOCUS.pushedThisTick = true;
|
||||||
@@ -118,6 +120,14 @@ void uiFocusMoveDirection(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void uiFocusHandleDirection(
|
||||||
|
uifocusitem_t *item,
|
||||||
|
const uifocusdirection_t dir
|
||||||
|
) {
|
||||||
|
if(item->direction != NULL && item->direction(item, dir)) return;
|
||||||
|
uiFocusMoveDirection(item, dir);
|
||||||
|
}
|
||||||
|
|
||||||
void uiFocusUpdate(void) {
|
void uiFocusUpdate(void) {
|
||||||
if(UI_FOCUS.count == 0) return;
|
if(UI_FOCUS.count == 0) return;
|
||||||
|
|
||||||
@@ -150,7 +160,7 @@ void uiFocusUpdate(void) {
|
|||||||
if(!inputPressed(m->action)) continue;
|
if(!inputPressed(m->action)) continue;
|
||||||
UI_FOCUS.direction = m->direction;
|
UI_FOCUS.direction = m->direction;
|
||||||
UI_FOCUS.timeHeld = 0.0f;
|
UI_FOCUS.timeHeld = 0.0f;
|
||||||
uiFocusMoveDirection(item, m->direction);
|
uiFocusHandleDirection(item, m->direction);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,5 +186,5 @@ void uiFocusUpdate(void) {
|
|||||||
if(UI_FOCUS.timeHeld < UI_FOCUS_HOLD_DELAY) return;
|
if(UI_FOCUS.timeHeld < UI_FOCUS_HOLD_DELAY) return;
|
||||||
if(UI_FOCUS.timeHeld < UI_FOCUS_HOLD_DELAY + UI_FOCUS_HOLD_REPEAT) return;
|
if(UI_FOCUS.timeHeld < UI_FOCUS_HOLD_DELAY + UI_FOCUS_HOLD_REPEAT) return;
|
||||||
UI_FOCUS.timeHeld = UI_FOCUS_HOLD_DELAY;
|
UI_FOCUS.timeHeld = UI_FOCUS_HOLD_DELAY;
|
||||||
uiFocusMoveDirection(item, UI_FOCUS.direction);
|
uiFocusHandleDirection(item, UI_FOCUS.direction);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,14 +22,6 @@
|
|||||||
*/
|
*/
|
||||||
#define UI_FOCUS_HOLD_REPEAT 0.1f
|
#define UI_FOCUS_HOLD_REPEAT 0.1f
|
||||||
|
|
||||||
typedef enum {
|
|
||||||
UI_FOCUS_DIRECTION_NONE,
|
|
||||||
UI_FOCUS_DIRECTION_UP,
|
|
||||||
UI_FOCUS_DIRECTION_DOWN,
|
|
||||||
UI_FOCUS_DIRECTION_LEFT,
|
|
||||||
UI_FOCUS_DIRECTION_RIGHT
|
|
||||||
} uifocusdirection_t;
|
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
inputaction_t action;
|
inputaction_t action;
|
||||||
uifocusdirection_t direction;
|
uifocusdirection_t direction;
|
||||||
@@ -72,6 +64,8 @@ void uiFocusInit(void);
|
|||||||
* @param selected Called when the user selects the focused cell.
|
* @param selected Called when the user selects the focused cell.
|
||||||
* @param changed Called when the focused cell position changes.
|
* @param changed Called when the focused cell position changes.
|
||||||
* @param closed Called when this focus item is popped.
|
* @param closed Called when this focus item is popped.
|
||||||
|
* @param direction Called on a direction press/hold before the default
|
||||||
|
* cell-to-cell movement is applied; may be NULL.
|
||||||
* @param user Arbitrary pointer stored on the item before changed fires.
|
* @param user Arbitrary pointer stored on the item before changed fires.
|
||||||
* @returns Pointer to the newly pushed focus item.
|
* @returns Pointer to the newly pushed focus item.
|
||||||
*/
|
*/
|
||||||
@@ -81,6 +75,7 @@ uifocusitem_t * uiFocusPush(
|
|||||||
uifocusitemcallback_t selected,
|
uifocusitemcallback_t selected,
|
||||||
uifocusitemcallback_t changed,
|
uifocusitemcallback_t changed,
|
||||||
uifocusitemcallback_t closed,
|
uifocusitemcallback_t closed,
|
||||||
|
uifocusitemdirectioncallback_t direction,
|
||||||
void *user
|
void *user
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -118,6 +113,19 @@ void uiFocusMoveDirection(
|
|||||||
const uifocusdirection_t dir
|
const uifocusdirection_t dir
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles a direction press/hold for the given item: gives the item's
|
||||||
|
* direction callback (if any) first refusal, falling back to the
|
||||||
|
* default cell-to-cell movement if it's unset or returns false.
|
||||||
|
*
|
||||||
|
* @param item The focus item to move.
|
||||||
|
* @param dir Direction that was pressed or held.
|
||||||
|
*/
|
||||||
|
void uiFocusHandleDirection(
|
||||||
|
uifocusitem_t *item,
|
||||||
|
const uifocusdirection_t dir
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the focus system. Handles first-press movement and
|
* Updates the focus system. Handles first-press movement and
|
||||||
* held-direction repeating. Called once per game tick.
|
* held-direction repeating. Called once per game tick.
|
||||||
|
|||||||
@@ -10,6 +10,14 @@
|
|||||||
|
|
||||||
typedef struct uifocusitem_s uifocusitem_t;
|
typedef struct uifocusitem_s uifocusitem_t;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
UI_FOCUS_DIRECTION_NONE,
|
||||||
|
UI_FOCUS_DIRECTION_UP,
|
||||||
|
UI_FOCUS_DIRECTION_DOWN,
|
||||||
|
UI_FOCUS_DIRECTION_LEFT,
|
||||||
|
UI_FOCUS_DIRECTION_RIGHT
|
||||||
|
} uifocusdirection_t;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Callback invoked when a focus item's selected cell changes.
|
* Callback invoked when a focus item's selected cell changes.
|
||||||
*
|
*
|
||||||
@@ -17,6 +25,21 @@ typedef struct uifocusitem_s uifocusitem_t;
|
|||||||
*/
|
*/
|
||||||
typedef bool_t (*uifocusitemcallback_t)(const uifocusitem_t *item);
|
typedef bool_t (*uifocusitemcallback_t)(const uifocusitem_t *item);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback invoked when a direction is pressed or held-repeated while
|
||||||
|
* this item is focused, before the default cell-to-cell movement is
|
||||||
|
* applied.
|
||||||
|
*
|
||||||
|
* @param item The focus item that is currently focused.
|
||||||
|
* @param direction The direction that was pressed.
|
||||||
|
* @returns True if the direction was fully handled and the default
|
||||||
|
* grid movement should be skipped; false to fall through to it.
|
||||||
|
*/
|
||||||
|
typedef bool_t (*uifocusitemdirectioncallback_t)(
|
||||||
|
const uifocusitem_t *item,
|
||||||
|
const uifocusdirection_t direction
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A single entry on the UI focus stack. Tracks the focused cell
|
* A single entry on the UI focus stack. Tracks the focused cell
|
||||||
* within a grid of cols x rows, and the current x/y position.
|
* within a grid of cols x rows, and the current x/y position.
|
||||||
@@ -29,5 +52,6 @@ struct uifocusitem_s {
|
|||||||
uifocusitemcallback_t selected;
|
uifocusitemcallback_t selected;
|
||||||
uifocusitemcallback_t changed;
|
uifocusitemcallback_t changed;
|
||||||
uifocusitemcallback_t closed;
|
uifocusitemcallback_t closed;
|
||||||
|
uifocusitemdirectioncallback_t direction;
|
||||||
void *user;
|
void *user;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,5 +6,8 @@
|
|||||||
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||||
PUBLIC
|
PUBLIC
|
||||||
uiframe.c
|
uiframe.c
|
||||||
uisettings.c
|
uiconfirm.c
|
||||||
)
|
)
|
||||||
|
|
||||||
|
add_subdirectory(game)
|
||||||
|
add_subdirectory(settings)
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
# Copyright (c) 2026 Dominic Masters
|
||||||
|
#
|
||||||
|
# This software is released under the MIT License.
|
||||||
|
# https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
|
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||||
|
PUBLIC
|
||||||
|
uigamemenu.c
|
||||||
|
)
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "uigamemenu.h"
|
||||||
|
#include "ui/frame/uiframe.h"
|
||||||
|
#include "ui/frame/settings/uisettings.h"
|
||||||
|
#include "util/memory.h"
|
||||||
|
#include "display/spritebatch/spritebatch.h"
|
||||||
|
#include "display/screen/screen.h"
|
||||||
|
#include "assert/assert.h"
|
||||||
|
|
||||||
|
#define UI_GAME_MENU_INDEX_CHARACTERS 0
|
||||||
|
#define UI_GAME_MENU_INDEX_SETTINGS 1
|
||||||
|
|
||||||
|
uigamemenu_t UI_GAME_MENU;
|
||||||
|
|
||||||
|
void uiGameMenuSelected(
|
||||||
|
const uimenu_t *menu,
|
||||||
|
const uint8_t index,
|
||||||
|
const uimenuitem_t *item
|
||||||
|
) {
|
||||||
|
if(index == UI_GAME_MENU_INDEX_SETTINGS) uiSettingsOpen();
|
||||||
|
}
|
||||||
|
|
||||||
|
errorret_t uiGameMenuInit(void) {
|
||||||
|
memoryZero(&UI_GAME_MENU, sizeof(uigamemenu_t));
|
||||||
|
|
||||||
|
MENU_BEGIN(
|
||||||
|
&UI_GAME_MENU.menu, UI_GAME_MENU.items, uiGameMenuSelected, NULL, NULL
|
||||||
|
);
|
||||||
|
MENU_BUTTON("Characters");
|
||||||
|
MENU_BUTTON("Settings");
|
||||||
|
|
||||||
|
MENU_END(UI_GAME_MENU.items, 1);
|
||||||
|
|
||||||
|
errorOk();
|
||||||
|
}
|
||||||
|
|
||||||
|
errorret_t uiGameMenuDraw(void) {
|
||||||
|
if(!uiMenuIsActive(&UI_GAME_MENU.menu)) errorOk();
|
||||||
|
|
||||||
|
const float_t width = UI_GAME_MENU_WIDTH;
|
||||||
|
const float_t height = (float_t)SCREEN.scanHeight;
|
||||||
|
const float_t x = (float_t)(SCREEN.scanX + SCREEN.scanWidth) - width;
|
||||||
|
const float_t y = (float_t)SCREEN.scanY;
|
||||||
|
|
||||||
|
errorChain(uiFrameDraw(x, y, width, height));
|
||||||
|
errorChain(uiMenuDraw(
|
||||||
|
&UI_GAME_MENU.menu,
|
||||||
|
x + UI_FRAME_START_X,
|
||||||
|
y + UI_FRAME_START_Y,
|
||||||
|
width - (UI_FRAME_START_X * 2),
|
||||||
|
height - (UI_FRAME_START_Y * 2)
|
||||||
|
));
|
||||||
|
|
||||||
|
errorChain(spriteBatchFlush());
|
||||||
|
errorOk();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool_t uiGameMenuIsOpen(void) {
|
||||||
|
return uiMenuIsActive(&UI_GAME_MENU.menu);
|
||||||
|
}
|
||||||
|
|
||||||
|
void uiGameMenuOpen() {
|
||||||
|
uiMenuOpen(&UI_GAME_MENU.menu);
|
||||||
|
}
|
||||||
|
|
||||||
|
void uiGameMenuClose() {
|
||||||
|
uiMenuClose(&UI_GAME_MENU.menu);
|
||||||
|
}
|
||||||
|
|
||||||
|
errorret_t uiGameMenuDispose(void) {
|
||||||
|
errorOk();
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "error/error.h"
|
||||||
|
#include "ui/widget/uimenu.h"
|
||||||
|
|
||||||
|
#define UI_GAME_MENU_ITEM_COUNT 2
|
||||||
|
#define UI_GAME_MENU_WIDTH 150.0f
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uimenu_t menu;
|
||||||
|
uimenuitem_t items[UI_GAME_MENU_ITEM_COUNT];
|
||||||
|
} uigamemenu_t;
|
||||||
|
|
||||||
|
extern uigamemenu_t UI_GAME_MENU;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the game menu panel.
|
||||||
|
*
|
||||||
|
* @return Any error that occurs.
|
||||||
|
*/
|
||||||
|
errorret_t uiGameMenuInit(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draws the game menu panel. No-op when not visible.
|
||||||
|
*
|
||||||
|
* @return Any error that occurs.
|
||||||
|
*/
|
||||||
|
errorret_t uiGameMenuDraw(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true when the game menu panel is currently open.
|
||||||
|
*
|
||||||
|
* @returns True if open.
|
||||||
|
*/
|
||||||
|
bool_t uiGameMenuIsOpen(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens the game menu panel. No-op when already open.
|
||||||
|
*/
|
||||||
|
void uiGameMenuOpen();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closes the game menu panel. No-op when already closed.
|
||||||
|
*/
|
||||||
|
void uiGameMenuClose();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disposes of the game menu panel.
|
||||||
|
*
|
||||||
|
* @return Any error that occurs.
|
||||||
|
*/
|
||||||
|
errorret_t uiGameMenuDispose(void);
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
# 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
|
||||||
|
uisettings.c
|
||||||
|
uisettingsgeneral.c
|
||||||
|
uisettingsinput.c
|
||||||
|
uisettingsdisplay.c
|
||||||
|
uisettingsaudio.c
|
||||||
|
)
|
||||||
@@ -0,0 +1,250 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "uisettings.h"
|
||||||
|
#include "uisettingsgeneral.h"
|
||||||
|
#include "uisettingsinput.h"
|
||||||
|
#include "uisettingsdisplay.h"
|
||||||
|
#include "uisettingsaudio.h"
|
||||||
|
#include "ui/frame/uiframe.h"
|
||||||
|
#include "ui/frame/uiconfirm.h"
|
||||||
|
#include "util/memory.h"
|
||||||
|
#include "display/spritebatch/spritebatch.h"
|
||||||
|
#include "display/text/text.h"
|
||||||
|
#include "display/screen/screen.h"
|
||||||
|
#include "assert/assert.h"
|
||||||
|
#include "locale/localemanager.h"
|
||||||
|
#include "asset/loader/locale/assetlocaleloader.h"
|
||||||
|
|
||||||
|
uisettings_t UI_SETTINGS;
|
||||||
|
|
||||||
|
uisettingstabdef_t UI_SETTINGS_TAB_DEFS[UI_SETTINGS_TAB_COUNT] = {
|
||||||
|
{
|
||||||
|
.localeId = "ui.settings.tabs.general",
|
||||||
|
.init = uiSettingsGeneralInit,
|
||||||
|
.menu = (uimenu_t *)&UI_SETTINGS.data,
|
||||||
|
.load = uiSettingsGeneralLoad,
|
||||||
|
.apply = uiSettingsGeneralApply,
|
||||||
|
.hasChanges = uiSettingsGeneralHasChanges
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
.localeId = "ui.settings.tabs.input",
|
||||||
|
.init = uiSettingsInputInit,
|
||||||
|
.menu = (uimenu_t *)&UI_SETTINGS.data,
|
||||||
|
.load = uiSettingsInputLoad,
|
||||||
|
.apply = uiSettingsInputApply,
|
||||||
|
.hasChanges = uiSettingsInputHasChanges
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
.localeId = "ui.settings.tabs.display",
|
||||||
|
.init = uiSettingsDisplayInit,
|
||||||
|
.menu = (uimenu_t *)&UI_SETTINGS.data,
|
||||||
|
.load = uiSettingsDisplayLoad,
|
||||||
|
.apply = uiSettingsDisplayApply,
|
||||||
|
.hasChanges = uiSettingsDisplayHasChanges
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
.localeId = "ui.settings.tabs.audio",
|
||||||
|
.init = uiSettingsAudioInit,
|
||||||
|
.menu = (uimenu_t *)&UI_SETTINGS.data,
|
||||||
|
.load = uiSettingsAudioLoad,
|
||||||
|
.apply = uiSettingsAudioApply,
|
||||||
|
.hasChanges = uiSettingsAudioHasChanges
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static uisettingstabdef_t UI_SETTINGS_TOP_DEF = {
|
||||||
|
.menu = &UI_SETTINGS.tabsMenu,
|
||||||
|
.load = uiSettingsLoad,
|
||||||
|
.hasChanges = uiSettingsHasChanges
|
||||||
|
};
|
||||||
|
|
||||||
|
static uisettingstabdef_t *UI_SETTINGS_PENDING_DISCARD = NULL;
|
||||||
|
static uisettingstabdef_t *UI_SETTINGS_ACTIVE_TAB = NULL;
|
||||||
|
|
||||||
|
errorret_t uiSettingsActivateTab(uisettingstabdef_t *def) {
|
||||||
|
if(def == UI_SETTINGS_ACTIVE_TAB) errorOk();
|
||||||
|
|
||||||
|
errorChain(def->init(&UI_SETTINGS.data));
|
||||||
|
def->menu->user = def;
|
||||||
|
def->load();
|
||||||
|
UI_SETTINGS_ACTIVE_TAB = def;
|
||||||
|
|
||||||
|
errorOk();
|
||||||
|
}
|
||||||
|
|
||||||
|
void uiSettingsTabChanged(
|
||||||
|
const uimenu_t *menu,
|
||||||
|
const uint8_t index,
|
||||||
|
const uimenuitem_t *item
|
||||||
|
) {
|
||||||
|
if(index >= UI_SETTINGS_TAB_COUNT) return;
|
||||||
|
errorCatch(uiSettingsActivateTab(&UI_SETTINGS_TAB_DEFS[index]));
|
||||||
|
}
|
||||||
|
|
||||||
|
void uiSettingsTabSelected(
|
||||||
|
const uimenu_t *menu,
|
||||||
|
const uint8_t index,
|
||||||
|
const uimenuitem_t *item
|
||||||
|
) {
|
||||||
|
if(index >= UI_SETTINGS_TAB_COUNT) return;
|
||||||
|
|
||||||
|
uisettingstabdef_t *def = &UI_SETTINGS_TAB_DEFS[index];
|
||||||
|
errorCatch(uiSettingsActivateTab(def));
|
||||||
|
uiMenuOpen(def->menu);
|
||||||
|
}
|
||||||
|
|
||||||
|
errorret_t uiSettingsInit(void) {
|
||||||
|
memoryZero(&UI_SETTINGS, sizeof(uisettings_t));
|
||||||
|
UI_SETTINGS_ACTIVE_TAB = NULL;
|
||||||
|
|
||||||
|
for(uint8_t i = 0; i < UI_SETTINGS_TAB_COUNT; i++) {
|
||||||
|
errorChain(assetLocaleGetString(
|
||||||
|
&LOCALE.entry->data.locale,
|
||||||
|
UI_SETTINGS_TAB_DEFS[i].localeId,
|
||||||
|
0,
|
||||||
|
UI_SETTINGS.tabLabels[i],
|
||||||
|
UI_SETTINGS_TAB_LABEL_MAX
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
MENU_BEGIN(
|
||||||
|
&UI_SETTINGS.tabsMenu, UI_SETTINGS.tabs, uiSettingsTabSelected,
|
||||||
|
uiSettingsDiscardMenuClosed, uiSettingsTabChanged
|
||||||
|
);
|
||||||
|
for(uint8_t i = 0; i < UI_SETTINGS_TAB_COUNT; i++) {
|
||||||
|
MENU_TAB(UI_SETTINGS.tabLabels[i]);
|
||||||
|
}
|
||||||
|
MENU_END(UI_SETTINGS.tabs, menuIndex);
|
||||||
|
|
||||||
|
UI_SETTINGS.tabsMenu.user = &UI_SETTINGS_TOP_DEF;
|
||||||
|
|
||||||
|
errorChain(assetLocaleGetString(
|
||||||
|
&LOCALE.entry->data.locale,
|
||||||
|
"ui.settings.apply",
|
||||||
|
0,
|
||||||
|
UI_SETTINGS.applyLabel,
|
||||||
|
UI_SETTINGS_APPLY_LABEL_MAX
|
||||||
|
));
|
||||||
|
|
||||||
|
errorChain(uiSettingsActivateTab(&UI_SETTINGS_TAB_DEFS[0]));
|
||||||
|
|
||||||
|
errorOk();
|
||||||
|
}
|
||||||
|
|
||||||
|
errorret_t uiSettingsDraw(void) {
|
||||||
|
if(!uiMenuIsActive(&UI_SETTINGS.tabsMenu)) errorOk();
|
||||||
|
|
||||||
|
const float_t width = SCREEN.width;
|
||||||
|
const float_t height = SCREEN.height;
|
||||||
|
const float_t x = (float_t)SCREEN.scanX +
|
||||||
|
((float_t)SCREEN.scanWidth - width) * 0.5f;
|
||||||
|
const float_t y = (float_t)SCREEN.scanY +
|
||||||
|
((float_t)SCREEN.scanHeight - height) * 0.5f;
|
||||||
|
|
||||||
|
errorChain(uiFrameDraw(x, y, width, height));
|
||||||
|
|
||||||
|
const float_t contentX = x + UI_FRAME_START_X;
|
||||||
|
const float_t contentY = y + UI_FRAME_START_Y;
|
||||||
|
const float_t contentWidth = width - (UI_FRAME_START_X * 2);
|
||||||
|
const float_t contentHeight = height - (UI_FRAME_START_Y * 2);
|
||||||
|
|
||||||
|
errorChain(uiMenuDraw(
|
||||||
|
&UI_SETTINGS.tabsMenu,
|
||||||
|
contentX,
|
||||||
|
contentY,
|
||||||
|
contentWidth,
|
||||||
|
contentHeight
|
||||||
|
));
|
||||||
|
|
||||||
|
const float_t tabsRowHeight = (float_t)FONT_DEFAULT.tileset->tileHeight;
|
||||||
|
const float_t pageY = contentY + tabsRowHeight + UI_FRAME_PADDING_Y;
|
||||||
|
const float_t pageHeight = contentHeight - tabsRowHeight - UI_FRAME_PADDING_Y;
|
||||||
|
|
||||||
|
if(UI_SETTINGS_ACTIVE_TAB != NULL) {
|
||||||
|
errorChain(uiMenuDraw(
|
||||||
|
UI_SETTINGS_ACTIVE_TAB->menu, contentX, pageY, contentWidth, pageHeight
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
errorChain(spriteBatchFlush());
|
||||||
|
errorOk();
|
||||||
|
}
|
||||||
|
|
||||||
|
errorret_t uiSettingsUpdate(void) {
|
||||||
|
if(!UI_SETTINGS_PENDING_DISCARD) errorOk();
|
||||||
|
|
||||||
|
uisettingstabdef_t *def = UI_SETTINGS_PENDING_DISCARD;
|
||||||
|
UI_SETTINGS_PENDING_DISCARD = NULL;
|
||||||
|
|
||||||
|
return uiSettingsDiscardUpdate(def);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool_t uiSettingsIsOpen(void) {
|
||||||
|
return uiMenuIsActive(&UI_SETTINGS.tabsMenu);
|
||||||
|
}
|
||||||
|
|
||||||
|
void uiSettingsOpen() {
|
||||||
|
uiSettingsLoad();
|
||||||
|
uiMenuOpen(&UI_SETTINGS.tabsMenu);
|
||||||
|
}
|
||||||
|
|
||||||
|
void uiSettingsClose() {
|
||||||
|
uiMenuClose(&UI_SETTINGS.tabsMenu);
|
||||||
|
}
|
||||||
|
|
||||||
|
errorret_t uiSettingsDispose(void) {
|
||||||
|
errorOk();
|
||||||
|
}
|
||||||
|
|
||||||
|
void uiSettingsLoad(void) {
|
||||||
|
if(UI_SETTINGS_ACTIVE_TAB != NULL) UI_SETTINGS_ACTIVE_TAB->load();
|
||||||
|
}
|
||||||
|
|
||||||
|
void uiSettingsApply(void) {
|
||||||
|
if(UI_SETTINGS_ACTIVE_TAB != NULL) UI_SETTINGS_ACTIVE_TAB->apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool_t uiSettingsHasChanges(void) {
|
||||||
|
return UI_SETTINGS_ACTIVE_TAB != NULL && UI_SETTINGS_ACTIVE_TAB->hasChanges();
|
||||||
|
}
|
||||||
|
|
||||||
|
void uiSettingsDiscardMenuClosed(const uimenu_t *menu) {
|
||||||
|
uisettingstabdef_t *def = (uisettingstabdef_t *)menu->user;
|
||||||
|
if(def->hasChanges()) UI_SETTINGS_PENDING_DISCARD = def;
|
||||||
|
}
|
||||||
|
|
||||||
|
errorret_t uiSettingsDiscardUpdate(uisettingstabdef_t *def) {
|
||||||
|
#define UI_SETTINGS_DISCARD_TEXT_MAX 128
|
||||||
|
char_t text[UI_SETTINGS_DISCARD_TEXT_MAX];
|
||||||
|
errorChain(assetLocaleGetString(
|
||||||
|
&LOCALE.entry->data.locale,
|
||||||
|
"ui.confirm.discard_changes",
|
||||||
|
0,
|
||||||
|
text,
|
||||||
|
UI_SETTINGS_DISCARD_TEXT_MAX
|
||||||
|
));
|
||||||
|
#undef UI_SETTINGS_DISCARD_TEXT_MAX
|
||||||
|
|
||||||
|
uiConfirmOpen(text, uiSettingsDiscardConfirmResult, def);
|
||||||
|
|
||||||
|
errorOk();
|
||||||
|
}
|
||||||
|
|
||||||
|
void uiSettingsDiscardConfirmResult(const bool_t result, void *user) {
|
||||||
|
uisettingstabdef_t *def = (uisettingstabdef_t *)user;
|
||||||
|
|
||||||
|
if(!result) {
|
||||||
|
uiMenuOpen(def->menu);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
def->load();
|
||||||
|
}
|
||||||
@@ -0,0 +1,133 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "error/error.h"
|
||||||
|
#include "ui/widget/uimenu.h"
|
||||||
|
#include "uisettingsdata.h"
|
||||||
|
|
||||||
|
#define UI_SETTINGS_TAB_COUNT 4
|
||||||
|
#define UI_SETTINGS_TAB_LABEL_MAX 32
|
||||||
|
#define UI_SETTINGS_APPLY_LABEL_MAX 32
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
const char_t *localeId;
|
||||||
|
errorret_t (*init)(uisettingsdata_t *data);
|
||||||
|
uimenu_t *menu;
|
||||||
|
void (*load)(void);
|
||||||
|
void (*apply)(void);
|
||||||
|
bool_t (*hasChanges)(void);
|
||||||
|
} uisettingstabdef_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uimenu_t tabsMenu;
|
||||||
|
uimenuitem_t tabs[UI_SETTINGS_TAB_COUNT];
|
||||||
|
char_t tabLabels[UI_SETTINGS_TAB_COUNT][UI_SETTINGS_TAB_LABEL_MAX];
|
||||||
|
char_t applyLabel[UI_SETTINGS_APPLY_LABEL_MAX];
|
||||||
|
uisettingsdata_t data;
|
||||||
|
} uisettings_t;
|
||||||
|
|
||||||
|
extern uisettings_t UI_SETTINGS;
|
||||||
|
extern uisettingstabdef_t UI_SETTINGS_TAB_DEFS[UI_SETTINGS_TAB_COUNT];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Menu closed callback that flags a pending discard-changes
|
||||||
|
* confirmation if the menu's settings page/panel reports unsaved
|
||||||
|
* changes. Pass this directly as a settings page menu's closed
|
||||||
|
* callback. The pending def is picked up by uiSettingsUpdate on the
|
||||||
|
* next tick.
|
||||||
|
*
|
||||||
|
* @param menu The menu that was just closed.
|
||||||
|
*/
|
||||||
|
void uiSettingsDiscardMenuClosed(const uimenu_t *menu);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows the discard-changes confirmation dialog for def. Called once
|
||||||
|
* by uiSettingsUpdate when uiSettingsDiscardMenuClosed has flagged a
|
||||||
|
* pending def.
|
||||||
|
*
|
||||||
|
* @param def The tab/panel def to show the confirmation for.
|
||||||
|
* @return Any error that occurs.
|
||||||
|
*/
|
||||||
|
errorret_t uiSettingsDiscardUpdate(uisettingstabdef_t *def);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback passed to uiConfirmOpen by uiSettingsDiscardUpdate. Reopens
|
||||||
|
* the menu if the user chose to keep editing, otherwise reloads the
|
||||||
|
* widgets back to the actual engine values.
|
||||||
|
*
|
||||||
|
* @param result True if the user confirmed discarding changes, false
|
||||||
|
* to keep editing.
|
||||||
|
* @param user The uisettingstabdef_t* this confirmation belongs to.
|
||||||
|
*/
|
||||||
|
void uiSettingsDiscardConfirmResult(const bool_t result, void *user);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the settings panel and all of its category pages.
|
||||||
|
*
|
||||||
|
* @return Any error that occurs.
|
||||||
|
*/
|
||||||
|
errorret_t uiSettingsInit(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draws the settings panel. No-op when not visible.
|
||||||
|
*
|
||||||
|
* @return Any error that occurs.
|
||||||
|
*/
|
||||||
|
errorret_t uiSettingsDraw(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the settings panel. Shows a discard-changes confirmation if
|
||||||
|
* the panel was just closed with unsaved changes.
|
||||||
|
*
|
||||||
|
* @return Any error that occurs.
|
||||||
|
*/
|
||||||
|
errorret_t uiSettingsUpdate(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true when the settings panel is currently open.
|
||||||
|
*
|
||||||
|
* @returns True if open.
|
||||||
|
*/
|
||||||
|
bool_t uiSettingsIsOpen(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens the settings panel. No-op when already open.
|
||||||
|
*/
|
||||||
|
void uiSettingsOpen();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closes the settings panel. No-op when already closed.
|
||||||
|
*/
|
||||||
|
void uiSettingsClose();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disposes of the settings panel.
|
||||||
|
*
|
||||||
|
* @return Any error that occurs.
|
||||||
|
*/
|
||||||
|
errorret_t uiSettingsDispose(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads every category page's widgets from the current engine settings.
|
||||||
|
* Called automatically by uiSettingsOpen.
|
||||||
|
*/
|
||||||
|
void uiSettingsLoad(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies every category page's widget values to the engine settings.
|
||||||
|
* Called automatically when a page's own Apply button is pressed.
|
||||||
|
*/
|
||||||
|
void uiSettingsApply(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether any category page has a widget value that differs
|
||||||
|
* from what was loaded.
|
||||||
|
*
|
||||||
|
* @returns True if there are unsaved changes.
|
||||||
|
*/
|
||||||
|
bool_t uiSettingsHasChanges(void);
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "uisettingsaudio.h"
|
||||||
|
#include "uisettings.h"
|
||||||
|
#include "assert/assert.h"
|
||||||
|
#include "util/memory.h"
|
||||||
|
|
||||||
|
void uiSettingsAudioSelected(
|
||||||
|
const uimenu_t *menu,
|
||||||
|
const uint8_t index,
|
||||||
|
const uimenuitem_t *item
|
||||||
|
) {
|
||||||
|
if(index != UI_SETTINGS_AUDIO_INDEX_APPLY) return;
|
||||||
|
uiSettingsAudioApply();
|
||||||
|
}
|
||||||
|
|
||||||
|
errorret_t uiSettingsAudioInit(uisettingsdata_t *data) {
|
||||||
|
uisettingsaudio_t *audio = &data->audio;
|
||||||
|
memoryZero(audio, sizeof(uisettingsaudio_t));
|
||||||
|
|
||||||
|
MENU_BEGIN(
|
||||||
|
&audio->menu, audio->items,
|
||||||
|
uiSettingsAudioSelected, uiSettingsDiscardMenuClosed, NULL
|
||||||
|
);
|
||||||
|
MENU_LABEL("No audio settings yet");
|
||||||
|
MENU_BUTTON(UI_SETTINGS.applyLabel);
|
||||||
|
|
||||||
|
MENU_END(audio->items, 1);
|
||||||
|
|
||||||
|
errorOk();
|
||||||
|
}
|
||||||
|
|
||||||
|
void uiSettingsAudioLoad(void) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void uiSettingsAudioApply(void) {
|
||||||
|
uiMenuClose(&UI_SETTINGS.data.audio.menu);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool_t uiSettingsAudioHasChanges(void) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "error/error.h"
|
||||||
|
#include "uisettingsdata.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the audio settings page into the shared settings data
|
||||||
|
* union.
|
||||||
|
*
|
||||||
|
* @param data The shared settings data union to initialize into.
|
||||||
|
* @return Any error that occurs.
|
||||||
|
*/
|
||||||
|
errorret_t uiSettingsAudioInit(uisettingsdata_t *data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the page's widgets from the current engine audio settings.
|
||||||
|
*/
|
||||||
|
void uiSettingsAudioLoad(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies the page's widget values to the engine audio settings.
|
||||||
|
*/
|
||||||
|
void uiSettingsAudioApply(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether any widget's value differs from what was loaded.
|
||||||
|
*
|
||||||
|
* @returns True if there are unsaved changes.
|
||||||
|
*/
|
||||||
|
bool_t uiSettingsAudioHasChanges(void);
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "error/error.h"
|
||||||
|
#include "ui/widget/uimenu.h"
|
||||||
|
|
||||||
|
#define UI_SETTINGS_GENERAL_ITEM_COUNT 3
|
||||||
|
#define UI_SETTINGS_GENERAL_INDEX_LANGUAGE 0
|
||||||
|
#define UI_SETTINGS_GENERAL_INDEX_LANGUAGE_DETAIL 1
|
||||||
|
#define UI_SETTINGS_GENERAL_INDEX_APPLY 2
|
||||||
|
#define UI_SETTINGS_GENERAL_LOCALE_COUNT 3
|
||||||
|
#define UI_SETTINGS_GENERAL_LABEL_MAX 32
|
||||||
|
#define UI_SETTINGS_GENERAL_INFO_MAX 64
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uimenu_t menu;
|
||||||
|
uimenuitem_t items[UI_SETTINGS_GENERAL_ITEM_COUNT];
|
||||||
|
const char_t *localeNames[UI_SETTINGS_GENERAL_LOCALE_COUNT];
|
||||||
|
char_t languageLabel[UI_SETTINGS_GENERAL_LABEL_MAX];
|
||||||
|
char_t labelInfo[UI_SETTINGS_GENERAL_INFO_MAX];
|
||||||
|
} uisettingsgeneral_t;
|
||||||
|
|
||||||
|
#define UI_SETTINGS_INPUT_ITEM_COUNT 2
|
||||||
|
#define UI_SETTINGS_INPUT_LABEL_MAX 32
|
||||||
|
#define UI_SETTINGS_INPUT_INDEX_DEADZONE 0
|
||||||
|
#define UI_SETTINGS_INPUT_INDEX_APPLY 1
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uimenu_t menu;
|
||||||
|
uimenuitem_t items[UI_SETTINGS_INPUT_ITEM_COUNT];
|
||||||
|
char_t deadzoneLabel[UI_SETTINGS_INPUT_LABEL_MAX];
|
||||||
|
} uisettingsinput_t;
|
||||||
|
|
||||||
|
#define UI_SETTINGS_DISPLAY_ITEM_COUNT 2
|
||||||
|
#define UI_SETTINGS_DISPLAY_INDEX_APPLY 1
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uimenu_t menu;
|
||||||
|
uimenuitem_t items[UI_SETTINGS_DISPLAY_ITEM_COUNT];
|
||||||
|
} uisettingsdisplay_t;
|
||||||
|
|
||||||
|
#define UI_SETTINGS_AUDIO_ITEM_COUNT 2
|
||||||
|
#define UI_SETTINGS_AUDIO_INDEX_APPLY 1
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uimenu_t menu;
|
||||||
|
uimenuitem_t items[UI_SETTINGS_AUDIO_ITEM_COUNT];
|
||||||
|
} uisettingsaudio_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shared, overlapping storage for every settings tab's page data. Only
|
||||||
|
* one member is ever live at a time -- the active tab's Init writes
|
||||||
|
* into it, overwriting whatever the previously active tab left there.
|
||||||
|
* This trades simultaneous access to every tab for a memory footprint
|
||||||
|
* of only the single largest page.
|
||||||
|
*/
|
||||||
|
typedef union {
|
||||||
|
uisettingsgeneral_t general;
|
||||||
|
uisettingsinput_t input;
|
||||||
|
uisettingsdisplay_t display;
|
||||||
|
uisettingsaudio_t audio;
|
||||||
|
} uisettingsdata_t;
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "uisettingsdisplay.h"
|
||||||
|
#include "uisettings.h"
|
||||||
|
#include "assert/assert.h"
|
||||||
|
#include "util/memory.h"
|
||||||
|
|
||||||
|
void uiSettingsDisplaySelected(
|
||||||
|
const uimenu_t *menu,
|
||||||
|
const uint8_t index,
|
||||||
|
const uimenuitem_t *item
|
||||||
|
) {
|
||||||
|
if(index != UI_SETTINGS_DISPLAY_INDEX_APPLY) return;
|
||||||
|
uiSettingsDisplayApply();
|
||||||
|
}
|
||||||
|
|
||||||
|
errorret_t uiSettingsDisplayInit(uisettingsdata_t *data) {
|
||||||
|
uisettingsdisplay_t *display = &data->display;
|
||||||
|
memoryZero(display, sizeof(uisettingsdisplay_t));
|
||||||
|
|
||||||
|
MENU_BEGIN(
|
||||||
|
&display->menu, display->items,
|
||||||
|
uiSettingsDisplaySelected, uiSettingsDiscardMenuClosed, NULL
|
||||||
|
);
|
||||||
|
MENU_LABEL("No display settings yet");
|
||||||
|
MENU_BUTTON(UI_SETTINGS.applyLabel);
|
||||||
|
|
||||||
|
MENU_END(display->items, 1);
|
||||||
|
|
||||||
|
errorOk();
|
||||||
|
}
|
||||||
|
|
||||||
|
void uiSettingsDisplayLoad(void) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void uiSettingsDisplayApply(void) {
|
||||||
|
uiMenuClose(&UI_SETTINGS.data.display.menu);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool_t uiSettingsDisplayHasChanges(void) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "error/error.h"
|
||||||
|
#include "uisettingsdata.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the display settings page into the shared settings data
|
||||||
|
* union.
|
||||||
|
*
|
||||||
|
* @param data The shared settings data union to initialize into.
|
||||||
|
* @return Any error that occurs.
|
||||||
|
*/
|
||||||
|
errorret_t uiSettingsDisplayInit(uisettingsdata_t *data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the page's widgets from the current engine display settings.
|
||||||
|
*/
|
||||||
|
void uiSettingsDisplayLoad(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies the page's widget values to the engine display settings.
|
||||||
|
*/
|
||||||
|
void uiSettingsDisplayApply(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether any widget's value differs from what was loaded.
|
||||||
|
*
|
||||||
|
* @returns True if there are unsaved changes.
|
||||||
|
*/
|
||||||
|
bool_t uiSettingsDisplayHasChanges(void);
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "uisettingsgeneral.h"
|
||||||
|
#include "uisettings.h"
|
||||||
|
#include "assert/assert.h"
|
||||||
|
#include "util/memory.h"
|
||||||
|
#include "util/string.h"
|
||||||
|
#include "locale/localemanager.h"
|
||||||
|
#include "locale/localeinfo.h"
|
||||||
|
#include "asset/loader/locale/assetlocaleloader.h"
|
||||||
|
|
||||||
|
static const localeinfo_t *const UI_SETTINGS_GENERAL_LOCALES[
|
||||||
|
UI_SETTINGS_GENERAL_LOCALE_COUNT
|
||||||
|
] = {
|
||||||
|
&LOCALE_EN_US,
|
||||||
|
&LOCALE_JP_JP,
|
||||||
|
&LOCALE_ES_MX
|
||||||
|
};
|
||||||
|
|
||||||
|
void uiSettingsGeneralSelected(
|
||||||
|
const uimenu_t *menu,
|
||||||
|
const uint8_t index,
|
||||||
|
const uimenuitem_t *item
|
||||||
|
) {
|
||||||
|
switch(index) {
|
||||||
|
case UI_SETTINGS_GENERAL_INDEX_APPLY:
|
||||||
|
uiSettingsGeneralApply();
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
errorret_t uiSettingsGeneralInit(uisettingsdata_t *data) {
|
||||||
|
uisettingsgeneral_t *general = &data->general;
|
||||||
|
memoryZero(general, sizeof(uisettingsgeneral_t));
|
||||||
|
|
||||||
|
for(uint8_t i = 0; i < UI_SETTINGS_GENERAL_LOCALE_COUNT; i++) {
|
||||||
|
general->localeNames[i] = UI_SETTINGS_GENERAL_LOCALES[i]->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
errorChain(assetLocaleGetString(
|
||||||
|
&LOCALE.entry->data.locale,
|
||||||
|
"ui.settings.general.language",
|
||||||
|
0,
|
||||||
|
general->languageLabel,
|
||||||
|
UI_SETTINGS_GENERAL_LABEL_MAX
|
||||||
|
));
|
||||||
|
errorChain(assetLocaleGetString(
|
||||||
|
&LOCALE.entry->data.locale,
|
||||||
|
"ui.settings.general.language_detail",
|
||||||
|
0,
|
||||||
|
general->labelInfo,
|
||||||
|
UI_SETTINGS_GENERAL_INFO_MAX
|
||||||
|
));
|
||||||
|
|
||||||
|
MENU_BEGIN(
|
||||||
|
&general->menu, general->items,
|
||||||
|
uiSettingsGeneralSelected, uiSettingsDiscardMenuClosed, NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
MENU_DROPDOWN(
|
||||||
|
general->languageLabel, general->localeNames,
|
||||||
|
UI_SETTINGS_GENERAL_LOCALE_COUNT, 0
|
||||||
|
);
|
||||||
|
MENU_LABEL(general->labelInfo);
|
||||||
|
|
||||||
|
MENU_BUTTON(UI_SETTINGS.applyLabel);
|
||||||
|
MENU_END(general->items, 1);
|
||||||
|
|
||||||
|
errorOk();
|
||||||
|
}
|
||||||
|
|
||||||
|
void uiSettingsGeneralLoad(void) {
|
||||||
|
uisettingsgeneral_t *general = &UI_SETTINGS.data.general;
|
||||||
|
|
||||||
|
uint8_t index = 0;
|
||||||
|
for(uint8_t i = 0; i < UI_SETTINGS_GENERAL_LOCALE_COUNT; i++) {
|
||||||
|
if(stringCompare(LOCALE.locale->file, UI_SETTINGS_GENERAL_LOCALES[i]->file) != 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
index = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
uiDropdownSetSelectedIndex(
|
||||||
|
&general->items[UI_SETTINGS_GENERAL_INDEX_LANGUAGE].dropdown, index
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void uiSettingsGeneralApply(void) {
|
||||||
|
uisettingsgeneral_t *general = &UI_SETTINGS.data.general;
|
||||||
|
|
||||||
|
uint8_t index = uiDropdownGetSelectedIndex(
|
||||||
|
&general->items[UI_SETTINGS_GENERAL_INDEX_LANGUAGE].dropdown
|
||||||
|
);
|
||||||
|
errorCatch(localeManagerSetLocale(UI_SETTINGS_GENERAL_LOCALES[index]));
|
||||||
|
|
||||||
|
uiMenuClose(&general->menu);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool_t uiSettingsGeneralHasChanges(void) {
|
||||||
|
uisettingsgeneral_t *general = &UI_SETTINGS.data.general;
|
||||||
|
|
||||||
|
uint8_t index = uiDropdownGetSelectedIndex(
|
||||||
|
&general->items[UI_SETTINGS_GENERAL_INDEX_LANGUAGE].dropdown
|
||||||
|
);
|
||||||
|
return stringCompare(
|
||||||
|
LOCALE.locale->file, UI_SETTINGS_GENERAL_LOCALES[index]->file
|
||||||
|
) != 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "error/error.h"
|
||||||
|
#include "uisettingsdata.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the general settings page into the shared settings data
|
||||||
|
* union.
|
||||||
|
*
|
||||||
|
* @param data The shared settings data union to initialize into.
|
||||||
|
* @return Any error that occurs.
|
||||||
|
*/
|
||||||
|
errorret_t uiSettingsGeneralInit(uisettingsdata_t *data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the page's widgets from the current engine general settings.
|
||||||
|
*/
|
||||||
|
void uiSettingsGeneralLoad(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies the page's widget values to the engine general settings.
|
||||||
|
*/
|
||||||
|
void uiSettingsGeneralApply(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether any widget's value differs from what was loaded.
|
||||||
|
*
|
||||||
|
* @returns True if there are unsaved changes.
|
||||||
|
*/
|
||||||
|
bool_t uiSettingsGeneralHasChanges(void);
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user