remove rpg
This commit is contained in:
@@ -63,7 +63,6 @@ add_subdirectory(engine)
|
||||
add_subdirectory(error)
|
||||
add_subdirectory(input)
|
||||
add_subdirectory(locale)
|
||||
add_subdirectory(rpg)
|
||||
add_subdirectory(scene)
|
||||
add_subdirectory(system)
|
||||
add_subdirectory(time)
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
# Copyright (c) 2025 Dominic Masters
|
||||
#
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
# Sources
|
||||
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||
PUBLIC
|
||||
rpg.c
|
||||
rpgcamera.c
|
||||
)
|
||||
|
||||
# Subdirs
|
||||
add_subdirectory(cutscene)
|
||||
add_subdirectory(entity)
|
||||
add_subdirectory(overworld)
|
||||
add_subdirectory(item)
|
||||
add_subdirectory(physics)
|
||||
@@ -1,13 +0,0 @@
|
||||
# Copyright (c) 2025 Dominic Masters
|
||||
#
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
# Sources
|
||||
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||
PUBLIC
|
||||
cutscenesystem.c
|
||||
)
|
||||
|
||||
# Subdirs
|
||||
add_subdirectory(item)
|
||||
@@ -1,232 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "rpg/cutscene/item/cutsceneitem.h"
|
||||
#include "rpg/cutscene/cutscenepause.h"
|
||||
|
||||
typedef struct cutscene_s {
|
||||
const cutsceneitem_t *items;
|
||||
uint8_t itemCount;
|
||||
cutscenepause_t pause;
|
||||
|
||||
// Size in bytes of this cutscene's custom user data, carved out of
|
||||
// CUTSCENE_SYSTEM.data while the cutscene is running.
|
||||
size_t dataSize;
|
||||
} cutscene_t;
|
||||
|
||||
#define CUTSCENE(NAME, SIZE, PAUSE_TYPE, ...) \
|
||||
static const cutsceneitem_t CUTSCENE_##NAME##_ITEMS[] = { __VA_ARGS__ }; \
|
||||
static const cutscene_t CUTSCENE_##NAME = { \
|
||||
.items = CUTSCENE_##NAME##_ITEMS, \
|
||||
.itemCount = sizeof(CUTSCENE_##NAME##_ITEMS) / sizeof(cutsceneitem_t), \
|
||||
.pause = CUTSCENE_PAUSE_##PAUSE_TYPE, \
|
||||
.dataSize = SIZE \
|
||||
};
|
||||
|
||||
#define CUTSCENE_REFERENCE(CUTSCENE) \
|
||||
&CUTSCENE_##CUTSCENE
|
||||
|
||||
#define CUTSCENE_TEXT(TEXT) \
|
||||
{ .type = CUTSCENE_ITEM_TYPE_TEXT, .text = { .text = TEXT } }
|
||||
|
||||
#define CUTSCENE_TEXT_MINI(TEXT, X, Y, Z, DURATION) \
|
||||
{ \
|
||||
.type = CUTSCENE_ITEM_TYPE_TEXT_MINI, \
|
||||
.textMini = { \
|
||||
.text = TEXT, \
|
||||
.position = { X, Y, Z }, \
|
||||
.duration = DURATION \
|
||||
} \
|
||||
}
|
||||
|
||||
#define CUTSCENE_TEXT_MINI_HIDE(INDEX) \
|
||||
{ \
|
||||
.type = CUTSCENE_ITEM_TYPE_TEXT_MINI_HIDE, \
|
||||
.textMiniHide = { .index = INDEX } \
|
||||
}
|
||||
|
||||
#define CUTSCENE_WAIT(WAIT) \
|
||||
{ .type = CUTSCENE_ITEM_TYPE_WAIT, .wait = WAIT }
|
||||
|
||||
#define CUTSCENE_CUTSCENE(CUTSCENE) \
|
||||
{ \
|
||||
.type = CUTSCENE_ITEM_TYPE_CUTSCENE, \
|
||||
.cutscene = CUTSCENE_REFERENCE(CUTSCENE) \
|
||||
}
|
||||
|
||||
#define CUTSCENE_CALLBACK(CALLBACK) \
|
||||
{ .type = CUTSCENE_ITEM_TYPE_CALLBACK, .callback = CALLBACK }
|
||||
|
||||
#define CUTSCENE_ENTITY_WALK_TO(ENTITY_INDEX, X, Y, Z) \
|
||||
{ \
|
||||
.type = CUTSCENE_ITEM_TYPE_ENTITY_WALK_TO, \
|
||||
.entityWalkTo = { \
|
||||
.entityIndex = ENTITY_INDEX, \
|
||||
.positions = (const worldpos_t[]){ { X, Y, Z } }, \
|
||||
.count = 1, \
|
||||
.walkAround = true \
|
||||
} \
|
||||
}
|
||||
|
||||
#define CUTSCENE_ENTITY_WALK_PATH(NAME, ENTITY_INDEX, ...) \
|
||||
static const worldpos_t CUTSCENE_##NAME##_POSITIONS[] = { __VA_ARGS__ }; \
|
||||
static const cutsceneitem_t CUTSCENE_##NAME = { \
|
||||
.type = CUTSCENE_ITEM_TYPE_ENTITY_WALK_TO, \
|
||||
.entityWalkTo = { \
|
||||
.entityIndex = ENTITY_INDEX, \
|
||||
.positions = CUTSCENE_##NAME##_POSITIONS, \
|
||||
.count = sizeof(CUTSCENE_##NAME##_POSITIONS) / sizeof(worldpos_t), \
|
||||
.walkAround = true \
|
||||
} \
|
||||
}
|
||||
|
||||
#define CUTSCENE_ENTITY_REMOVE(ENTITY_INDEX) \
|
||||
{ \
|
||||
.type = CUTSCENE_ITEM_TYPE_ENTITY_REMOVE, \
|
||||
.entityRemove = { .entityIndex = ENTITY_INDEX } \
|
||||
}
|
||||
|
||||
#define CUTSCENE_ENTITY_ADD(TYPE, X, Y, Z) \
|
||||
{ \
|
||||
.type = CUTSCENE_ITEM_TYPE_ENTITY_ADD, \
|
||||
.entityAdd = { .entityType = TYPE, .position = { X, Y, Z } } \
|
||||
}
|
||||
|
||||
#define CUTSCENE_ENTITY_TURN(ENTITY_INDEX, DIRECTION) \
|
||||
{ \
|
||||
.type = CUTSCENE_ITEM_TYPE_ENTITY_TURN, \
|
||||
.entityTurn = { .entityIndex = ENTITY_INDEX, .direction = DIRECTION } \
|
||||
}
|
||||
|
||||
// Walks ENTITY_INDEX to stand beside TARGET_ENTITY_INDEX, offset by
|
||||
// (OFFSET_X, OFFSET_Y) on the 2D plane. The destination Z is resolved
|
||||
// from nearby terrain each frame, so ramps between the two entities are
|
||||
// accounted for automatically.
|
||||
#define CUTSCENE_ENTITY_WALK_TO_ENTITY( \
|
||||
ENTITY_INDEX, TARGET_ENTITY_INDEX, OFFSET_X, OFFSET_Y \
|
||||
) \
|
||||
{ \
|
||||
.type = CUTSCENE_ITEM_TYPE_ENTITY_WALK_TO_ENTITY, \
|
||||
.entityWalkToEntity = { \
|
||||
.entityIndex = ENTITY_INDEX, \
|
||||
.targetEntityIndex = TARGET_ENTITY_INDEX, \
|
||||
.offsetX = OFFSET_X, \
|
||||
.offsetY = OFFSET_Y \
|
||||
} \
|
||||
}
|
||||
|
||||
#define CUTSCENE_ENTITY_TELEPORT(ENTITY_INDEX, X, Y, Z) \
|
||||
{ \
|
||||
.type = CUTSCENE_ITEM_TYPE_ENTITY_TELEPORT, \
|
||||
.entityTeleport = { .entityIndex = ENTITY_INDEX, .target = { X, Y, Z } } \
|
||||
}
|
||||
|
||||
#define CUTSCENE_FADE(FROM, TO, DURATION, EASING) \
|
||||
{ \
|
||||
.type = CUTSCENE_ITEM_TYPE_FADE, \
|
||||
.fade = { .from = FROM, .to = TO, .duration = DURATION, .easing = EASING } \
|
||||
}
|
||||
|
||||
#define CUTSCENE_FADE_TO_BLACK(DURATION) \
|
||||
CUTSCENE_FADE(COLOR_TRANSPARENT_BLACK, COLOR_BLACK, DURATION, EASING_LINEAR)
|
||||
|
||||
#define CUTSCENE_FADE_FROM_BLACK(DURATION) \
|
||||
CUTSCENE_FADE(COLOR_BLACK, COLOR_TRANSPARENT_BLACK, DURATION, EASING_LINEAR)
|
||||
|
||||
#define CUTSCENE_FADE_TO_WHITE(DURATION) \
|
||||
CUTSCENE_FADE(COLOR_TRANSPARENT_WHITE, COLOR_WHITE, DURATION, EASING_LINEAR)
|
||||
|
||||
#define CUTSCENE_FADE_FROM_WHITE(DURATION) \
|
||||
CUTSCENE_FADE(COLOR_WHITE, COLOR_TRANSPARENT_WHITE, DURATION, EASING_LINEAR)
|
||||
|
||||
#define CUTSCENE_EMOJI(ENTITY_INDEX, EMOJI_TYPE, DURATION) \
|
||||
{ \
|
||||
.type = CUTSCENE_ITEM_TYPE_EMOJI, \
|
||||
.emoji = { \
|
||||
.entityIndex = ENTITY_INDEX, \
|
||||
.emojiType = EMOJI_TYPE, \
|
||||
.duration = DURATION \
|
||||
} \
|
||||
}
|
||||
|
||||
// AMOUNT ranges 0 (no shake) to 4 (three tiles): 1 is half a tile, 2 is
|
||||
// a full tile, 3 is two tiles, and 4 is three tiles.
|
||||
#define CUTSCENE_SHAKE(AMOUNT, DURATION) \
|
||||
{ \
|
||||
.type = CUTSCENE_ITEM_TYPE_SHAKE, \
|
||||
.shake = { .amount = AMOUNT, .duration = DURATION } \
|
||||
}
|
||||
|
||||
#define CUTSCENE_SET_PAUSE(FLAGS) \
|
||||
{ .type = CUTSCENE_ITEM_TYPE_SET_PAUSE, .setPause = (FLAGS) }
|
||||
|
||||
#define CUTSCENE_ITEM_GIVE(ITEM_ID, QUANTITY) \
|
||||
{ \
|
||||
.type = CUTSCENE_ITEM_TYPE_ITEM_GIVE, \
|
||||
.itemGive = { .item = ITEM_ID, .quantity = QUANTITY } \
|
||||
}
|
||||
|
||||
// Runs all listed items simultaneously and waits until all are done.
|
||||
// Concurrent items cannot be nested inside another CUTSCENE_CONCURRENT.
|
||||
#define CUTSCENE_CONCURRENT(...) \
|
||||
{ \
|
||||
.type = CUTSCENE_ITEM_TYPE_CONCURRENT, \
|
||||
.concurrent = { \
|
||||
.items = (const cutsceneitem_t[]){ __VA_ARGS__ }, \
|
||||
.count = (uint8_t)( \
|
||||
sizeof((cutsceneitem_t[]){ __VA_ARGS__ }) / \
|
||||
sizeof(cutsceneitem_t) \
|
||||
) \
|
||||
} \
|
||||
}
|
||||
|
||||
#define CUTSCENE_MAP_AREA_ADD( \
|
||||
MIN_X, MIN_Y, MIN_Z, MAX_X, MAX_Y, MAX_Z, CALLBACK, NOTIFY, TRIGGER \
|
||||
) \
|
||||
{ \
|
||||
.type = CUTSCENE_ITEM_TYPE_MAP_AREA_ADD, \
|
||||
.mapAreaAdd = { \
|
||||
.min = { MIN_X, MIN_Y, MIN_Z }, \
|
||||
.max = { MAX_X, MAX_Y, MAX_Z }, \
|
||||
.callback = CALLBACK, \
|
||||
.notify = NOTIFY, \
|
||||
.trigger = TRIGGER \
|
||||
} \
|
||||
}
|
||||
|
||||
#define CUTSCENE_MAP_AREA_REMOVE(AREA_ID) \
|
||||
{ \
|
||||
.type = CUTSCENE_ITEM_TYPE_MAP_AREA_REMOVE, \
|
||||
.mapAreaRemove = { .areaId = AREA_ID } \
|
||||
}
|
||||
|
||||
// Waits until any one of the given map area IDs has its callback invoked.
|
||||
// Accepts CUTSCENE_AREA_LAST_CREATED in place of a literal area ID.
|
||||
#define CUTSCENE_MAP_AREA_WAIT(...) \
|
||||
{ \
|
||||
.type = CUTSCENE_ITEM_TYPE_MAP_AREA_WAIT, \
|
||||
.mapAreaWait = { \
|
||||
.areaIds = (const uint8_t[]){ __VA_ARGS__ }, \
|
||||
.count = (uint8_t)( \
|
||||
sizeof((const uint8_t[]){ __VA_ARGS__ }) / sizeof(uint8_t) \
|
||||
) \
|
||||
} \
|
||||
}
|
||||
|
||||
// Adds a map area, waits for it to be triggered once, then removes it
|
||||
// before the cutscene continues. Uses a no-op callback since the wait is
|
||||
// driven by the area's trigger count rather than callback logic.
|
||||
#define CUTSCENE_MAP_AREA_TRIGGER_ONCE( \
|
||||
MIN_X, MIN_Y, MIN_Z, MAX_X, MAX_Y, MAX_Z, NOTIFY, TRIGGER \
|
||||
) \
|
||||
CUTSCENE_MAP_AREA_ADD( \
|
||||
MIN_X, MIN_Y, MIN_Z, MAX_X, MAX_Y, MAX_Z, \
|
||||
mapAreaNoopCallback, NOTIFY, TRIGGER \
|
||||
), \
|
||||
CUTSCENE_MAP_AREA_WAIT(CUTSCENE_AREA_LAST_CREATED), \
|
||||
CUTSCENE_MAP_AREA_REMOVE(CUTSCENE_AREA_LAST_CREATED)
|
||||
@@ -1,24 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "dusk.h"
|
||||
|
||||
typedef uint8_t cutscenepause_t;
|
||||
|
||||
#define CUTSCENE_PAUSE_NONE ((cutscenepause_t)0)
|
||||
#define CUTSCENE_PAUSE_NPC ((cutscenepause_t)(1 << 0))
|
||||
#define CUTSCENE_PAUSE_PLAYER ((cutscenepause_t)(1 << 1))
|
||||
#define CUTSCENE_PAUSE_WORLD ((cutscenepause_t)(1 << 2))
|
||||
|
||||
#define CUTSCENE_PAUSE_DEFAULT ((cutscenepause_t)( \
|
||||
CUTSCENE_PAUSE_NPC | CUTSCENE_PAUSE_PLAYER \
|
||||
))
|
||||
|
||||
#define CUTSCENE_PAUSE_ALL ((cutscenepause_t)( \
|
||||
CUTSCENE_PAUSE_NPC | CUTSCENE_PAUSE_PLAYER | CUTSCENE_PAUSE_WORLD \
|
||||
))
|
||||
@@ -1,157 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "cutscenesystem.h"
|
||||
#include "rpg/entity/entity.h"
|
||||
#include "util/memory.h"
|
||||
#include "assert/assert.h"
|
||||
|
||||
cutscenesystem_t CUTSCENE_SYSTEM;
|
||||
|
||||
void cutsceneSystemInit() {
|
||||
memoryZero(&CUTSCENE_SYSTEM, sizeof(cutscenesystem_t));
|
||||
}
|
||||
|
||||
void cutsceneSystemStartCutscene(const cutscene_t *cutscene) {
|
||||
cutsceneSystemStartCutsceneWith(cutscene, NULL, NULL);
|
||||
}
|
||||
|
||||
void cutsceneSystemStartCutsceneWith(
|
||||
const cutscene_t *cutscene,
|
||||
entity_t *interact,
|
||||
entity_t *interacted
|
||||
) {
|
||||
assertTrue(
|
||||
cutscene->dataSize < CUTSCENE_SYSTEM_SIZE_MAX,
|
||||
"Cutscene data size exceeds CUTSCENE_SYSTEM_SIZE_MAX"
|
||||
);
|
||||
|
||||
CUTSCENE_SYSTEM.scene = cutscene;
|
||||
CUTSCENE_SYSTEM.pause = cutscene->pause;
|
||||
CUTSCENE_SYSTEM.entityInteract = interact;
|
||||
CUTSCENE_SYSTEM.entityInteracted = interacted;
|
||||
CUTSCENE_SYSTEM.entityLastCreated = NULL;
|
||||
CUTSCENE_SYSTEM.entityLastRef = NULL;
|
||||
CUTSCENE_SYSTEM.areaLastCreated = CUTSCENE_AREA_LAST_CREATED;
|
||||
CUTSCENE_SYSTEM.textMiniLastCreated = CUTSCENE_TEXT_MINI_LAST_CREATED;
|
||||
CUTSCENE_SYSTEM.currentItem = 0xFF;// Set to 0xFF so Next wraps to 0.
|
||||
cutsceneSystemNext();
|
||||
}
|
||||
|
||||
void cutsceneSystemUpdate() {
|
||||
if(CUTSCENE_SYSTEM.scene == NULL) return;
|
||||
|
||||
const cutsceneitem_t *item = cutsceneSystemGetCurrentItem();
|
||||
if(cutsceneItemUpdate(item, &CUTSCENE_SYSTEM.data)) cutsceneSystemNext();
|
||||
}
|
||||
|
||||
void cutsceneSystemNext() {
|
||||
if(CUTSCENE_SYSTEM.scene == NULL) return;
|
||||
|
||||
CUTSCENE_SYSTEM.currentItem++;
|
||||
|
||||
// End of the cutscene?
|
||||
if(
|
||||
CUTSCENE_SYSTEM.currentItem >= CUTSCENE_SYSTEM.scene->itemCount
|
||||
) {
|
||||
CUTSCENE_SYSTEM.scene = NULL;
|
||||
CUTSCENE_SYSTEM.currentItem = 0xFF;
|
||||
CUTSCENE_SYSTEM.pause = CUTSCENE_PAUSE_NONE;
|
||||
CUTSCENE_SYSTEM.entityInteract = NULL;
|
||||
CUTSCENE_SYSTEM.entityInteracted = NULL;
|
||||
CUTSCENE_SYSTEM.entityLastCreated = NULL;
|
||||
CUTSCENE_SYSTEM.entityLastRef = NULL;
|
||||
CUTSCENE_SYSTEM.areaLastCreated = CUTSCENE_AREA_LAST_CREATED;
|
||||
CUTSCENE_SYSTEM.textMiniLastCreated = CUTSCENE_TEXT_MINI_LAST_CREATED;
|
||||
return;
|
||||
}
|
||||
|
||||
// Start item.
|
||||
const cutsceneitem_t *item = cutsceneSystemGetCurrentItem();
|
||||
memset(&CUTSCENE_SYSTEM.data, 0, sizeof(CUTSCENE_SYSTEM.data));
|
||||
cutsceneItemStart(item, &CUTSCENE_SYSTEM.data);
|
||||
}
|
||||
|
||||
const cutsceneitem_t * cutsceneSystemGetCurrentItem() {
|
||||
if(CUTSCENE_SYSTEM.scene == NULL) return NULL;
|
||||
|
||||
return &CUTSCENE_SYSTEM.scene->items[CUTSCENE_SYSTEM.currentItem];
|
||||
}
|
||||
|
||||
entity_t * cutsceneSystemGetEntity(const uint8_t entityIndex) {
|
||||
entity_t *entity;
|
||||
|
||||
if(entityIndex == CUTSCENE_ENTITY_INTERACT) {
|
||||
assertNotNull(
|
||||
CUTSCENE_SYSTEM.entityInteract,
|
||||
"CUTSCENE_ENTITY_INTERACT used but no interact entity is set"
|
||||
);
|
||||
entity = CUTSCENE_SYSTEM.entityInteract;
|
||||
} else if(entityIndex == CUTSCENE_ENTITY_INTERACTED) {
|
||||
assertNotNull(
|
||||
CUTSCENE_SYSTEM.entityInteracted,
|
||||
"CUTSCENE_ENTITY_INTERACTED used but no interacted entity is set"
|
||||
);
|
||||
entity = CUTSCENE_SYSTEM.entityInteracted;
|
||||
} else if(entityIndex == CUTSCENE_ENTITY_LAST_CREATED) {
|
||||
assertNotNull(
|
||||
CUTSCENE_SYSTEM.entityLastCreated,
|
||||
"CUTSCENE_ENTITY_LAST_CREATED used but no entity has been created"
|
||||
);
|
||||
entity = CUTSCENE_SYSTEM.entityLastCreated;
|
||||
} else if(entityIndex == CUTSCENE_ENTITY_LAST_REF) {
|
||||
assertNotNull(
|
||||
CUTSCENE_SYSTEM.entityLastRef,
|
||||
"CUTSCENE_ENTITY_LAST_REF used but no entity has been referenced"
|
||||
);
|
||||
entity = CUTSCENE_SYSTEM.entityLastRef;
|
||||
} else {
|
||||
assertTrue(
|
||||
entityIndex < ENTITY_COUNT,
|
||||
"Entity index is out of range"
|
||||
);
|
||||
entity = &ENTITIES[entityIndex];
|
||||
}
|
||||
|
||||
CUTSCENE_SYSTEM.entityLastRef = entity;
|
||||
return entity;
|
||||
}
|
||||
|
||||
uint8_t cutsceneSystemGetAreaId(const uint8_t areaId) {
|
||||
if(areaId == CUTSCENE_AREA_LAST_CREATED) {
|
||||
assertTrue(
|
||||
CUTSCENE_SYSTEM.areaLastCreated != CUTSCENE_AREA_LAST_CREATED,
|
||||
"CUTSCENE_AREA_LAST_CREATED used but no map area has been created"
|
||||
);
|
||||
return CUTSCENE_SYSTEM.areaLastCreated;
|
||||
}
|
||||
return areaId;
|
||||
}
|
||||
|
||||
uint8_t cutsceneSystemGetTextMiniId(const uint8_t index) {
|
||||
if(index == CUTSCENE_TEXT_MINI_LAST_CREATED) {
|
||||
assertTrue(
|
||||
CUTSCENE_SYSTEM.textMiniLastCreated != CUTSCENE_TEXT_MINI_LAST_CREATED,
|
||||
"CUTSCENE_TEXT_MINI_LAST_CREATED used but no mini textbox has been "
|
||||
"shown"
|
||||
);
|
||||
return CUTSCENE_SYSTEM.textMiniLastCreated;
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
void cutsceneSystemDispose() {
|
||||
CUTSCENE_SYSTEM.scene = NULL;
|
||||
CUTSCENE_SYSTEM.currentItem = 0xFF;
|
||||
CUTSCENE_SYSTEM.pause = CUTSCENE_PAUSE_NONE;
|
||||
CUTSCENE_SYSTEM.entityInteract = NULL;
|
||||
CUTSCENE_SYSTEM.entityInteracted = NULL;
|
||||
CUTSCENE_SYSTEM.entityLastCreated = NULL;
|
||||
CUTSCENE_SYSTEM.entityLastRef = NULL;
|
||||
CUTSCENE_SYSTEM.areaLastCreated = CUTSCENE_AREA_LAST_CREATED;
|
||||
CUTSCENE_SYSTEM.textMiniLastCreated = CUTSCENE_TEXT_MINI_LAST_CREATED;
|
||||
}
|
||||
@@ -1,120 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "cutscene.h"
|
||||
|
||||
typedef struct entity_s entity_t;
|
||||
|
||||
#define CUTSCENE_ENTITY_INTERACT ((uint8_t)0xFE)
|
||||
#define CUTSCENE_ENTITY_INTERACTED ((uint8_t)0xFD)
|
||||
#define CUTSCENE_ENTITY_LAST_CREATED ((uint8_t)0xFC)
|
||||
#define CUTSCENE_ENTITY_LAST_REF ((uint8_t)0xFB)
|
||||
#define CUTSCENE_AREA_LAST_CREATED ((uint8_t)0xFF)
|
||||
#define CUTSCENE_TEXT_MINI_LAST_CREATED ((uint8_t)0xFA)
|
||||
|
||||
// Maximum number of bytes a running cutscene may request via
|
||||
// cutscene_t.dataSize.
|
||||
#define CUTSCENE_SYSTEM_SIZE_MAX 8192
|
||||
|
||||
typedef struct {
|
||||
const cutscene_t *scene;
|
||||
uint8_t currentItem;
|
||||
cutscenepause_t pause;
|
||||
entity_t *entityInteract;
|
||||
entity_t *entityInteracted;
|
||||
entity_t *entityLastCreated;
|
||||
entity_t *entityLastRef;
|
||||
uint8_t areaLastCreated;
|
||||
uint8_t textMiniLastCreated;
|
||||
|
||||
// Data (used by the current item).
|
||||
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;
|
||||
|
||||
extern cutscenesystem_t CUTSCENE_SYSTEM;
|
||||
|
||||
/**
|
||||
* Initialize the cutscene system.
|
||||
*/
|
||||
void cutsceneSystemInit();
|
||||
|
||||
/**
|
||||
* Start a cutscene with no bound entities.
|
||||
*
|
||||
* @param cutscene Pointer to the cutscene to start.
|
||||
*/
|
||||
void cutsceneSystemStartCutscene(const cutscene_t *cutscene);
|
||||
|
||||
/**
|
||||
* Start a cutscene with the two entities that triggered it.
|
||||
*
|
||||
* @param cutscene Pointer to the cutscene to start.
|
||||
* @param interact The entity that initiated the interaction (player).
|
||||
* @param interacted The entity that was interacted with (NPC).
|
||||
*/
|
||||
void cutsceneSystemStartCutsceneWith(
|
||||
const cutscene_t *cutscene,
|
||||
entity_t *interact,
|
||||
entity_t *interacted
|
||||
);
|
||||
|
||||
/**
|
||||
* Resolves a raw entity index (or sentinel) to an entity pointer.
|
||||
* Handles CUTSCENE_ENTITY_INTERACT, CUTSCENE_ENTITY_INTERACTED,
|
||||
* CUTSCENE_ENTITY_LAST_CREATED and CUTSCENE_ENTITY_LAST_REF.
|
||||
* Updates CUTSCENE_SYSTEM.entityLastRef to the resolved entity.
|
||||
* Asserts the resolved entity is within bounds.
|
||||
*
|
||||
* @param entityIndex Raw entity index or sentinel value.
|
||||
* @returns Pointer to the resolved entity.
|
||||
*/
|
||||
entity_t * cutsceneSystemGetEntity(const uint8_t entityIndex);
|
||||
|
||||
/**
|
||||
* Resolves a raw map area ID (or CUTSCENE_AREA_LAST_CREATED sentinel) to
|
||||
* a concrete map area ID.
|
||||
*
|
||||
* @param areaId Raw map area ID or sentinel value.
|
||||
* @returns The resolved map area ID.
|
||||
*/
|
||||
uint8_t cutsceneSystemGetAreaId(const uint8_t areaId);
|
||||
|
||||
/**
|
||||
* Resolves a raw mini textbox slot index (or CUTSCENE_TEXT_MINI_LAST_CREATED
|
||||
* sentinel) to a concrete UI_TEXTBOX_MINI_LIST slot index.
|
||||
*
|
||||
* @param index Raw slot index or sentinel value.
|
||||
* @returns The resolved slot index.
|
||||
*/
|
||||
uint8_t cutsceneSystemGetTextMiniId(const uint8_t index);
|
||||
|
||||
/**
|
||||
* Advance to the next item in the cutscene.
|
||||
*/
|
||||
void cutsceneSystemNext();
|
||||
|
||||
/**
|
||||
* Update the cutscene system for one frame.
|
||||
*/
|
||||
void cutsceneSystemUpdate();
|
||||
|
||||
/**
|
||||
* Get the current cutscene item.
|
||||
*
|
||||
* @return Pointer to the current cutscene item.
|
||||
*/
|
||||
const cutsceneitem_t * cutsceneSystemGetCurrentItem();
|
||||
|
||||
/**
|
||||
* Disposes of the cutscene system, stopping any active cutscene.
|
||||
*/
|
||||
void cutsceneSystemDispose();
|
||||
@@ -1,16 +0,0 @@
|
||||
# Copyright (c) 2025 Dominic Masters
|
||||
#
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||
PUBLIC
|
||||
cutsceneitem.c
|
||||
cutscenecallback.c
|
||||
)
|
||||
|
||||
add_subdirectory(control)
|
||||
add_subdirectory(entity)
|
||||
add_subdirectory(item)
|
||||
add_subdirectory(maparea)
|
||||
add_subdirectory(ui)
|
||||
@@ -1,11 +0,0 @@
|
||||
# Copyright (c) 2026 Dominic Masters
|
||||
#
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||
PUBLIC
|
||||
cutscenewait.c
|
||||
cutscenesetpause.c
|
||||
cutsceneconcurrent.c
|
||||
)
|
||||
@@ -1,46 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "rpg/cutscene/item/cutsceneitem.h"
|
||||
#include "assert/assert.h"
|
||||
|
||||
void cutsceneConcurrentStart(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
) {
|
||||
assertTrue(
|
||||
item->concurrent.count <= CUTSCENE_CONCURRENT_MAX,
|
||||
"Too many items in CUTSCENE_CONCURRENT"
|
||||
);
|
||||
for(uint8_t i = 0; i < item->concurrent.count; i++) {
|
||||
assertTrue(
|
||||
item->concurrent.items[i].type != CUTSCENE_ITEM_TYPE_CONCURRENT,
|
||||
"Concurrent items cannot be nested"
|
||||
);
|
||||
cutsceneItemStart(
|
||||
&item->concurrent.items[i],
|
||||
(cutsceneitemdata_t *)&data->concurrent.childData[i]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
bool_t cutsceneConcurrentUpdate(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
) {
|
||||
for(uint8_t i = 0; i < item->concurrent.count; i++) {
|
||||
if(data->concurrent.doneMask & (1u << i)) continue;
|
||||
if(cutsceneItemUpdate(
|
||||
&item->concurrent.items[i],
|
||||
(cutsceneitemdata_t *)&data->concurrent.childData[i]
|
||||
)) {
|
||||
data->concurrent.doneMask |= (uint8_t)(1u << i);
|
||||
}
|
||||
}
|
||||
uint8_t allDone = (uint8_t)((1u << item->concurrent.count) - 1u);
|
||||
return data->concurrent.doneMask == allDone;
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "cutscenewait.h"
|
||||
#include "rpg/cutscene/item/entity/cutsceneentitywalkto.h"
|
||||
|
||||
typedef struct cutsceneitem_s cutsceneitem_t;
|
||||
typedef union cutsceneitemdata_u cutsceneitemdata_t;
|
||||
|
||||
/** Maximum number of items that may run inside a CUTSCENE_CONCURRENT. */
|
||||
#define CUTSCENE_CONCURRENT_MAX 8
|
||||
|
||||
/**
|
||||
* Static (const) data for a concurrent cutscene item.
|
||||
*/
|
||||
typedef struct {
|
||||
const cutsceneitem_t *items;
|
||||
uint8_t count;
|
||||
} cutsceneconcurrent_t;
|
||||
|
||||
/**
|
||||
* Runtime data for one non-concurrent child item.
|
||||
* Concurrent items cannot be nested.
|
||||
*/
|
||||
typedef union {
|
||||
cutscenewaitdata_t wait;
|
||||
cutsceneentitywalktodata_t entityWalkTo;
|
||||
} cutsceneconcurrentchilddata_t;
|
||||
|
||||
/** Runtime data for a running concurrent item. */
|
||||
typedef struct {
|
||||
cutsceneconcurrentchilddata_t childData[CUTSCENE_CONCURRENT_MAX];
|
||||
uint8_t doneMask;
|
||||
} cutsceneconcurrentdata_t;
|
||||
|
||||
/**
|
||||
* Starts a concurrent item (starts all child items simultaneously).
|
||||
*
|
||||
* @param item The cutscene item.
|
||||
* @param data Runtime data storage.
|
||||
*/
|
||||
void cutsceneConcurrentStart(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
);
|
||||
|
||||
/**
|
||||
* Updates a concurrent item (ticks all unfinished children).
|
||||
*
|
||||
* @param item The cutscene item.
|
||||
* @param data Runtime data storage.
|
||||
* @returns true once every child has completed.
|
||||
*/
|
||||
bool_t cutsceneConcurrentUpdate(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
);
|
||||
@@ -1,23 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "rpg/cutscene/item/cutsceneitem.h"
|
||||
#include "rpg/cutscene/cutscenesystem.h"
|
||||
|
||||
void cutsceneSetPauseStart(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
) {
|
||||
CUTSCENE_SYSTEM.pause = item->setPause;
|
||||
}
|
||||
|
||||
bool_t cutsceneSetPauseUpdate(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "rpg/cutscene/cutscenepause.h"
|
||||
|
||||
typedef struct cutsceneitem_s cutsceneitem_t;
|
||||
typedef union cutsceneitemdata_u cutsceneitemdata_t;
|
||||
|
||||
/**
|
||||
* Starts a set-pause item (applies the new pause flags immediately).
|
||||
*
|
||||
* @param item The cutscene item.
|
||||
* @param data Runtime data storage.
|
||||
*/
|
||||
void cutsceneSetPauseStart(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
);
|
||||
|
||||
/**
|
||||
* Updates a set-pause item (always completes immediately).
|
||||
*
|
||||
* @param item The cutscene item.
|
||||
* @param data Runtime data storage.
|
||||
* @returns true always.
|
||||
*/
|
||||
bool_t cutsceneSetPauseUpdate(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
);
|
||||
@@ -1,24 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "rpg/cutscene/item/cutsceneitem.h"
|
||||
#include "time/time.h"
|
||||
|
||||
void cutsceneWaitStart(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
) {
|
||||
data->wait = item->wait;
|
||||
}
|
||||
|
||||
bool_t cutsceneWaitUpdate(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
) {
|
||||
data->wait -= TIME.delta;
|
||||
return data->wait <= 0;
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "dusk.h"
|
||||
|
||||
typedef struct cutsceneitem_s cutsceneitem_t;
|
||||
typedef union cutsceneitemdata_u cutsceneitemdata_t;
|
||||
|
||||
typedef float_t cutscenewait_t;
|
||||
typedef float_t cutscenewaitdata_t;
|
||||
|
||||
/**
|
||||
* Starts a wait item (stores the duration in data).
|
||||
*
|
||||
* @param item The cutscene item.
|
||||
* @param data Runtime data storage.
|
||||
*/
|
||||
void cutsceneWaitStart(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
);
|
||||
|
||||
/**
|
||||
* Updates a wait item.
|
||||
*
|
||||
* @param item The cutscene item.
|
||||
* @param data Runtime data storage.
|
||||
* @returns true when the wait has elapsed.
|
||||
*/
|
||||
bool_t cutsceneWaitUpdate(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
);
|
||||
@@ -1,23 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "cutsceneitem.h"
|
||||
#include "rpg/cutscene/cutscenesystem.h"
|
||||
|
||||
void cutsceneCallbackStart(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
) {
|
||||
if(item->callback != NULL) item->callback(CUTSCENE_SYSTEM.userData);
|
||||
}
|
||||
|
||||
bool_t cutsceneCallbackUpdate(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "dusk.h"
|
||||
|
||||
typedef struct cutsceneitem_s cutsceneitem_t;
|
||||
typedef union cutsceneitemdata_u cutsceneitemdata_t;
|
||||
|
||||
typedef void (*cutscenecallback_t)(void *userData);
|
||||
|
||||
/**
|
||||
* Starts a callback item (invokes the callback immediately).
|
||||
*
|
||||
* @param item The cutscene item.
|
||||
* @param data Runtime data storage.
|
||||
*/
|
||||
void cutsceneCallbackStart(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
);
|
||||
|
||||
/**
|
||||
* Updates a callback item (always completes immediately).
|
||||
*
|
||||
* @param item The cutscene item.
|
||||
* @param data Runtime data storage.
|
||||
* @returns true always.
|
||||
*/
|
||||
bool_t cutsceneCallbackUpdate(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
);
|
||||
@@ -1,147 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "rpg/cutscene/cutscenesystem.h"
|
||||
|
||||
cutsceneitemcallbacks_t CUTSCENE_ITEM_CALLBACKS[CUTSCENE_ITEM_TYPE_COUNT] = {
|
||||
[CUTSCENE_ITEM_TYPE_NULL] = { 0 },
|
||||
|
||||
[CUTSCENE_ITEM_TYPE_TEXT] = {
|
||||
.init = cutsceneTextStart,
|
||||
.update = cutsceneTextUpdate
|
||||
},
|
||||
|
||||
[CUTSCENE_ITEM_TYPE_TEXT_MINI] = {
|
||||
.init = cutsceneTextMiniStart,
|
||||
.update = cutsceneTextMiniUpdate
|
||||
},
|
||||
|
||||
[CUTSCENE_ITEM_TYPE_TEXT_MINI_HIDE] = {
|
||||
.init = cutsceneTextMiniHideStart,
|
||||
.update = cutsceneTextMiniHideUpdate
|
||||
},
|
||||
|
||||
[CUTSCENE_ITEM_TYPE_CALLBACK] = {
|
||||
.init = cutsceneCallbackStart,
|
||||
.update = cutsceneCallbackUpdate
|
||||
},
|
||||
|
||||
[CUTSCENE_ITEM_TYPE_WAIT] = {
|
||||
.init = cutsceneWaitStart,
|
||||
.update = cutsceneWaitUpdate
|
||||
},
|
||||
|
||||
[CUTSCENE_ITEM_TYPE_CUTSCENE] = {
|
||||
.init = cutsceneCutsceneStart,
|
||||
.update = cutsceneCutsceneUpdate
|
||||
},
|
||||
|
||||
[CUTSCENE_ITEM_TYPE_ENTITY_TELEPORT] = {
|
||||
.init = cutsceneEntityTeleportStart,
|
||||
.update = cutsceneEntityTeleportUpdate
|
||||
},
|
||||
|
||||
[CUTSCENE_ITEM_TYPE_ENTITY_WALK_TO] = {
|
||||
.init = cutsceneEntityWalkToStart,
|
||||
.update = cutsceneEntityWalkToUpdate
|
||||
},
|
||||
|
||||
[CUTSCENE_ITEM_TYPE_FADE] = {
|
||||
.init = cutsceneFadeStart,
|
||||
.update = cutsceneFadeUpdate
|
||||
},
|
||||
|
||||
[CUTSCENE_ITEM_TYPE_SET_PAUSE] = {
|
||||
.init = cutsceneSetPauseStart,
|
||||
.update = cutsceneSetPauseUpdate
|
||||
},
|
||||
|
||||
[CUTSCENE_ITEM_TYPE_CONCURRENT] = {
|
||||
.init = cutsceneConcurrentStart,
|
||||
.update = cutsceneConcurrentUpdate
|
||||
},
|
||||
|
||||
[CUTSCENE_ITEM_TYPE_ITEM_GIVE] = {
|
||||
.init = cutsceneItemGiveStart,
|
||||
.update = cutsceneItemGiveUpdate
|
||||
},
|
||||
|
||||
[CUTSCENE_ITEM_TYPE_ENTITY_REMOVE] = {
|
||||
.init = cutsceneEntityRemoveStart,
|
||||
.update = cutsceneEntityRemoveUpdate
|
||||
},
|
||||
|
||||
[CUTSCENE_ITEM_TYPE_ENTITY_ADD] = {
|
||||
.init = cutsceneEntityAddStart,
|
||||
.update = cutsceneEntityAddUpdate
|
||||
},
|
||||
|
||||
[CUTSCENE_ITEM_TYPE_ENTITY_TURN] = {
|
||||
.init = cutsceneEntityTurnStart,
|
||||
.update = cutsceneEntityTurnUpdate
|
||||
},
|
||||
|
||||
[CUTSCENE_ITEM_TYPE_ENTITY_WALK_TO_ENTITY] = {
|
||||
.init = cutsceneEntityWalkToEntityStart,
|
||||
.update = cutsceneEntityWalkToEntityUpdate
|
||||
},
|
||||
|
||||
[CUTSCENE_ITEM_TYPE_MAP_AREA_ADD] = {
|
||||
.init = cutsceneMapAreaAddStart,
|
||||
.update = cutsceneMapAreaAddUpdate
|
||||
},
|
||||
|
||||
[CUTSCENE_ITEM_TYPE_MAP_AREA_REMOVE] = {
|
||||
.init = cutsceneMapAreaRemoveStart,
|
||||
.update = cutsceneMapAreaRemoveUpdate
|
||||
},
|
||||
|
||||
[CUTSCENE_ITEM_TYPE_MAP_AREA_WAIT] = {
|
||||
.init = cutsceneMapAreaWaitStart,
|
||||
.update = cutsceneMapAreaWaitUpdate
|
||||
},
|
||||
|
||||
[CUTSCENE_ITEM_TYPE_EMOJI] = {
|
||||
.init = cutsceneEmojiStart,
|
||||
.update = cutsceneEmojiUpdate
|
||||
},
|
||||
|
||||
[CUTSCENE_ITEM_TYPE_SHAKE] = {
|
||||
.init = cutsceneShakeStart,
|
||||
.update = cutsceneShakeUpdate
|
||||
}
|
||||
};
|
||||
|
||||
void cutsceneItemStart(const cutsceneitem_t *item, cutsceneitemdata_t *data) {
|
||||
cutsceneiteminitcallback_t *init = CUTSCENE_ITEM_CALLBACKS[item->type].init;
|
||||
if(init != NULL) init(item, data);
|
||||
}
|
||||
|
||||
bool_t cutsceneItemUpdate(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
) {
|
||||
cutsceneitemupdatecallback_t *update =
|
||||
CUTSCENE_ITEM_CALLBACKS[item->type].update;
|
||||
if(update == NULL) return false;
|
||||
|
||||
return update(item, data);
|
||||
}
|
||||
|
||||
void cutsceneCutsceneStart(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
) {
|
||||
if(item->cutscene != NULL) cutsceneSystemStartCutscene(item->cutscene);
|
||||
}
|
||||
|
||||
bool_t cutsceneCutsceneUpdate(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
@@ -1,160 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "cutscenecallback.h"
|
||||
#include "control/cutscenewait.h"
|
||||
#include "control/cutscenesetpause.h"
|
||||
#include "control/cutsceneconcurrent.h"
|
||||
#include "entity/cutsceneentityteleport.h"
|
||||
#include "entity/cutsceneentitywalkto.h"
|
||||
#include "entity/cutsceneentityremove.h"
|
||||
#include "entity/cutsceneentityadd.h"
|
||||
#include "entity/cutsceneentityturn.h"
|
||||
#include "entity/cutsceneentitywalktoentity.h"
|
||||
#include "ui/cutscenetext.h"
|
||||
#include "ui/cutscenetextmini.h"
|
||||
#include "ui/cutscenetextminihide.h"
|
||||
#include "ui/cutscenefade.h"
|
||||
#include "ui/cutsceneemoji.h"
|
||||
#include "ui/cutsceneshake.h"
|
||||
#include "item/cutsceneitemgive.h"
|
||||
#include "maparea/cutscenemapareaadd.h"
|
||||
#include "maparea/cutscenemaparearemove.h"
|
||||
#include "maparea/cutscenemapareawait.h"
|
||||
|
||||
typedef struct cutscene_s cutscene_t;
|
||||
|
||||
typedef enum {
|
||||
CUTSCENE_ITEM_TYPE_NULL,
|
||||
|
||||
CUTSCENE_ITEM_TYPE_TEXT,
|
||||
CUTSCENE_ITEM_TYPE_TEXT_MINI,
|
||||
CUTSCENE_ITEM_TYPE_TEXT_MINI_HIDE,
|
||||
CUTSCENE_ITEM_TYPE_CALLBACK,
|
||||
CUTSCENE_ITEM_TYPE_WAIT,
|
||||
CUTSCENE_ITEM_TYPE_CUTSCENE,
|
||||
CUTSCENE_ITEM_TYPE_ENTITY_TELEPORT,
|
||||
CUTSCENE_ITEM_TYPE_ENTITY_WALK_TO,
|
||||
CUTSCENE_ITEM_TYPE_FADE,
|
||||
CUTSCENE_ITEM_TYPE_SET_PAUSE,
|
||||
CUTSCENE_ITEM_TYPE_CONCURRENT,
|
||||
CUTSCENE_ITEM_TYPE_ITEM_GIVE,
|
||||
CUTSCENE_ITEM_TYPE_ENTITY_REMOVE,
|
||||
CUTSCENE_ITEM_TYPE_ENTITY_ADD,
|
||||
CUTSCENE_ITEM_TYPE_ENTITY_TURN,
|
||||
CUTSCENE_ITEM_TYPE_ENTITY_WALK_TO_ENTITY,
|
||||
CUTSCENE_ITEM_TYPE_MAP_AREA_ADD,
|
||||
CUTSCENE_ITEM_TYPE_MAP_AREA_REMOVE,
|
||||
CUTSCENE_ITEM_TYPE_MAP_AREA_WAIT,
|
||||
CUTSCENE_ITEM_TYPE_EMOJI,
|
||||
CUTSCENE_ITEM_TYPE_SHAKE,
|
||||
|
||||
CUTSCENE_ITEM_TYPE_COUNT
|
||||
} cutsceneitemtype_t;
|
||||
|
||||
struct cutsceneitem_s {
|
||||
cutsceneitemtype_t type;
|
||||
|
||||
union {
|
||||
cutscenetext_t text;
|
||||
cutscenetextmini_t textMini;
|
||||
cutscenetextminihide_t textMiniHide;
|
||||
cutscenecallback_t callback;
|
||||
cutscenewait_t wait;
|
||||
const cutscene_t *cutscene;
|
||||
cutsceneentityteleport_t entityTeleport;
|
||||
cutsceneentitywalkto_t entityWalkTo;
|
||||
cutscenefade_t fade;
|
||||
cutscenepause_t setPause;
|
||||
cutsceneconcurrent_t concurrent;
|
||||
cutsceneitemgive_t itemGive;
|
||||
cutsceneentityremove_t entityRemove;
|
||||
cutsceneentityadd_t entityAdd;
|
||||
cutsceneentityturn_t entityTurn;
|
||||
cutsceneentitywalktoentity_t entityWalkToEntity;
|
||||
cutscenemapareaadd_t mapAreaAdd;
|
||||
cutscenemaparearemove_t mapAreaRemove;
|
||||
cutscenemapareawait_t mapAreaWait;
|
||||
cutsceneemoji_t emoji;
|
||||
cutsceneshake_t shake;
|
||||
};
|
||||
};
|
||||
|
||||
typedef union cutsceneitemdata_u {
|
||||
cutscenewaitdata_t wait;
|
||||
cutsceneentitywalktodata_t entityWalkTo;
|
||||
cutsceneconcurrentdata_t concurrent;
|
||||
cutscenemapareawaitdata_t mapAreaWait;
|
||||
} cutsceneitemdata_t;
|
||||
|
||||
typedef void (cutsceneiteminitcallback_t)(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
);
|
||||
|
||||
typedef bool_t (cutsceneitemupdatecallback_t)(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
);
|
||||
|
||||
typedef struct {
|
||||
cutsceneiteminitcallback_t *init;
|
||||
cutsceneitemupdatecallback_t *update;
|
||||
} cutsceneitemcallbacks_t;
|
||||
|
||||
extern cutsceneitemcallbacks_t
|
||||
CUTSCENE_ITEM_CALLBACKS[CUTSCENE_ITEM_TYPE_COUNT];
|
||||
|
||||
/**
|
||||
* Start the given cutscene item.
|
||||
*
|
||||
* @param item The cutscene item to start.
|
||||
* @param data Runtime data storage (pre-zeroed by caller).
|
||||
*/
|
||||
void cutsceneItemStart(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
);
|
||||
|
||||
/**
|
||||
* Tick the given cutscene item (one frame).
|
||||
*
|
||||
* @param item The cutscene item to tick.
|
||||
* @param data Runtime data storage.
|
||||
* @returns true if the item is complete and the cutscene should advance.
|
||||
*/
|
||||
bool_t cutsceneItemUpdate(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
);
|
||||
|
||||
/**
|
||||
* Starts a nested-cutscene item, handing control over to the
|
||||
* referenced cutscene.
|
||||
*
|
||||
* @param item The cutscene item.
|
||||
* @param data Runtime data storage.
|
||||
*/
|
||||
void cutsceneCutsceneStart(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
);
|
||||
|
||||
/**
|
||||
* Updates a nested-cutscene item. By the time this would run, control
|
||||
* has already moved on to the referenced cutscene, so this always
|
||||
* reports incomplete.
|
||||
*
|
||||
* @param item The cutscene item.
|
||||
* @param data Runtime data storage.
|
||||
* @returns false always.
|
||||
*/
|
||||
bool_t cutsceneCutsceneUpdate(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
);
|
||||
@@ -1,14 +0,0 @@
|
||||
# Copyright (c) 2026 Dominic Masters
|
||||
#
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||
PUBLIC
|
||||
cutsceneentityteleport.c
|
||||
cutsceneentitywalkto.c
|
||||
cutsceneentityremove.c
|
||||
cutsceneentityadd.c
|
||||
cutsceneentityturn.c
|
||||
cutsceneentitywalktoentity.c
|
||||
)
|
||||
@@ -1,33 +0,0 @@
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
/**
|
||||
* 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
|
||||
);
|
||||
@@ -1,25 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "rpg/cutscene/item/cutsceneitem.h"
|
||||
#include "rpg/cutscene/cutscenesystem.h"
|
||||
#include "rpg/entity/entity.h"
|
||||
|
||||
void cutsceneEntityRemoveStart(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
) {
|
||||
cutsceneSystemGetEntity(item->entityRemove.entityIndex)->type = \
|
||||
ENTITY_TYPE_NULL;
|
||||
}
|
||||
|
||||
bool_t cutsceneEntityRemoveUpdate(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "dusk.h"
|
||||
|
||||
typedef struct cutsceneitem_s cutsceneitem_t;
|
||||
typedef union cutsceneitemdata_u cutsceneitemdata_t;
|
||||
|
||||
typedef struct {
|
||||
uint8_t entityIndex;
|
||||
} cutsceneentityremove_t;
|
||||
|
||||
/**
|
||||
* Starts an entity remove step (removes the entity from the world immediately).
|
||||
*
|
||||
* @param item The cutscene item.
|
||||
* @param data Runtime data storage.
|
||||
*/
|
||||
void cutsceneEntityRemoveStart(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
);
|
||||
|
||||
/**
|
||||
* Updates an entity remove step (always completes immediately).
|
||||
*
|
||||
* @param item The cutscene item.
|
||||
* @param data Runtime data storage.
|
||||
* @returns true always.
|
||||
*/
|
||||
bool_t cutsceneEntityRemoveUpdate(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
);
|
||||
@@ -1,27 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "rpg/cutscene/item/cutsceneitem.h"
|
||||
#include "rpg/cutscene/cutscenesystem.h"
|
||||
#include "rpg/entity/entity.h"
|
||||
|
||||
void cutsceneEntityTeleportStart(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
) {
|
||||
entityPositionSet(
|
||||
cutsceneSystemGetEntity(item->entityTeleport.entityIndex),
|
||||
item->entityTeleport.target
|
||||
);
|
||||
}
|
||||
|
||||
bool_t cutsceneEntityTeleportUpdate(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "rpg/overworld/worldpos.h"
|
||||
|
||||
typedef struct cutsceneitem_s cutsceneitem_t;
|
||||
typedef union cutsceneitemdata_u cutsceneitemdata_t;
|
||||
|
||||
typedef struct {
|
||||
uint8_t entityIndex;
|
||||
worldpos_t target;
|
||||
} cutsceneentityteleport_t;
|
||||
|
||||
/**
|
||||
* Starts an entity teleport item (teleports the entity immediately).
|
||||
*
|
||||
* @param item The cutscene item.
|
||||
* @param data Runtime data storage.
|
||||
*/
|
||||
void cutsceneEntityTeleportStart(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
);
|
||||
|
||||
/**
|
||||
* Updates an entity teleport item (always completes immediately).
|
||||
*
|
||||
* @param item The cutscene item.
|
||||
* @param data Runtime data storage.
|
||||
* @returns true always.
|
||||
*/
|
||||
bool_t cutsceneEntityTeleportUpdate(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
);
|
||||
@@ -1,30 +0,0 @@
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
/**
|
||||
* 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
|
||||
);
|
||||
@@ -1,36 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "rpg/cutscene/item/cutsceneitem.h"
|
||||
#include "rpg/cutscene/cutscenesystem.h"
|
||||
#include "rpg/entity/entitypathstep.h"
|
||||
|
||||
void cutsceneEntityWalkToStart(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
) {
|
||||
data->entityWalkTo.currentIndex = 0;
|
||||
}
|
||||
|
||||
bool_t cutsceneEntityWalkToUpdate(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
) {
|
||||
uint8_t i = data->entityWalkTo.currentIndex;
|
||||
entity_t *e = cutsceneSystemGetEntity(item->entityWalkTo.entityIndex);
|
||||
if(!entityPathStep(
|
||||
e,
|
||||
item->entityWalkTo.positions[i],
|
||||
item->entityWalkTo.walkAround
|
||||
)) return false;
|
||||
i++;
|
||||
if(i < item->entityWalkTo.count) {
|
||||
data->entityWalkTo.currentIndex = i;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "rpg/overworld/worldpos.h"
|
||||
|
||||
typedef struct cutsceneitem_s cutsceneitem_t;
|
||||
typedef union cutsceneitemdata_u cutsceneitemdata_t;
|
||||
|
||||
typedef struct {
|
||||
uint8_t entityIndex;
|
||||
const worldpos_t *positions;
|
||||
uint8_t count;
|
||||
bool_t walkAround;
|
||||
} cutsceneentitywalkto_t;
|
||||
|
||||
typedef struct {
|
||||
uint8_t currentIndex;
|
||||
} cutsceneentitywalktodata_t;
|
||||
|
||||
/**
|
||||
* Starts an entity walk-to item (resets the waypoint index).
|
||||
*
|
||||
* @param item The cutscene item.
|
||||
* @param data Runtime data storage.
|
||||
*/
|
||||
void cutsceneEntityWalkToStart(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
);
|
||||
|
||||
/**
|
||||
* Updates an entity walk-to item (steps the entity toward the next waypoint).
|
||||
*
|
||||
* @param item The cutscene item.
|
||||
* @param data Runtime data storage.
|
||||
* @returns true once all waypoints have been reached.
|
||||
*/
|
||||
bool_t cutsceneEntityWalkToUpdate(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
);
|
||||
@@ -1,41 +0,0 @@
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
/**
|
||||
* 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
|
||||
);
|
||||
@@ -1,9 +0,0 @@
|
||||
# Copyright (c) 2026 Dominic Masters
|
||||
#
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||
PUBLIC
|
||||
cutsceneitemgive.c
|
||||
)
|
||||
@@ -1,24 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "rpg/cutscene/item/cutsceneitem.h"
|
||||
#include "rpg/item/itemgive.h"
|
||||
#include "ui/rpg/textbox/uitextboxmain.h"
|
||||
|
||||
void cutsceneItemGiveStart(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
) {
|
||||
itemGive(item->itemGive.item, item->itemGive.quantity);
|
||||
}
|
||||
|
||||
bool_t cutsceneItemGiveUpdate(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
) {
|
||||
return !uiTextboxMainIsActive();
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "rpg/item/item.h"
|
||||
|
||||
typedef struct cutsceneitem_s cutsceneitem_t;
|
||||
typedef union cutsceneitemdata_u cutsceneitemdata_t;
|
||||
|
||||
typedef struct {
|
||||
itemid_t item;
|
||||
uint8_t quantity;
|
||||
} cutsceneitemgive_t;
|
||||
|
||||
/**
|
||||
* Starts a give-item step (adds the item to the player's backpack immediately).
|
||||
*
|
||||
* @param item The cutscene item.
|
||||
* @param data Runtime data storage.
|
||||
*/
|
||||
void cutsceneItemGiveStart(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
);
|
||||
|
||||
/**
|
||||
* Updates a give-item step (always completes immediately).
|
||||
*
|
||||
* @param item The cutscene item.
|
||||
* @param data Runtime data storage.
|
||||
* @returns true always.
|
||||
*/
|
||||
bool_t cutsceneItemGiveUpdate(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
);
|
||||
@@ -1,11 +0,0 @@
|
||||
# Copyright (c) 2026 Dominic Masters
|
||||
#
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||
PUBLIC
|
||||
cutscenemapareaadd.c
|
||||
cutscenemaparearemove.c
|
||||
cutscenemapareawait.c
|
||||
)
|
||||
@@ -1,29 +0,0 @@
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
/**
|
||||
* 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
|
||||
);
|
||||
@@ -1,24 +0,0 @@
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "dusk.h"
|
||||
|
||||
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
|
||||
);
|
||||
@@ -1,40 +0,0 @@
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "dusk.h"
|
||||
|
||||
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
|
||||
);
|
||||
@@ -1,14 +0,0 @@
|
||||
# Copyright (c) 2026 Dominic Masters
|
||||
#
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||
PUBLIC
|
||||
cutscenetext.c
|
||||
cutscenetextmini.c
|
||||
cutscenetextminihide.c
|
||||
cutscenefade.c
|
||||
cutsceneemoji.c
|
||||
cutsceneshake.c
|
||||
)
|
||||
@@ -1,25 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "rpg/cutscene/item/cutsceneitem.h"
|
||||
#include "rpg/cutscene/cutscenesystem.h"
|
||||
#include "rpg/entity/entity.h"
|
||||
|
||||
void cutsceneEmojiStart(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
) {
|
||||
entity_t *entity = cutsceneSystemGetEntity(item->emoji.entityIndex);
|
||||
uiEmojiAdd(entity->id, item->emoji.duration, item->emoji.emojiType);
|
||||
}
|
||||
|
||||
bool_t cutsceneEmojiUpdate(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "dusk.h"
|
||||
#include "ui/rpg/uiemoji.h"
|
||||
|
||||
typedef struct cutsceneitem_s cutsceneitem_t;
|
||||
typedef union cutsceneitemdata_u cutsceneitemdata_t;
|
||||
|
||||
typedef struct {
|
||||
uint8_t entityIndex;
|
||||
float_t duration;
|
||||
uiemojitype_t emojiType;
|
||||
} cutsceneemoji_t;
|
||||
|
||||
/**
|
||||
* Starts an emoji step (shows an emoji above the entity for the given
|
||||
* duration, then completes immediately).
|
||||
*
|
||||
* @param item The cutscene item.
|
||||
* @param data Runtime data storage.
|
||||
*/
|
||||
void cutsceneEmojiStart(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
);
|
||||
|
||||
/**
|
||||
* Updates an emoji step (always completes immediately).
|
||||
*
|
||||
* @param item The cutscene item.
|
||||
* @param data Runtime data storage.
|
||||
* @returns true always.
|
||||
*/
|
||||
bool_t cutsceneEmojiUpdate(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
);
|
||||
@@ -1,32 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "rpg/cutscene/item/cutsceneitem.h"
|
||||
#include "ui/overlay/uifullbox.h"
|
||||
|
||||
void cutsceneFadeStart(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
) {
|
||||
uiFullboxTransition(
|
||||
&UI_FULLBOX_OVER,
|
||||
item->fade.from,
|
||||
item->fade.to,
|
||||
item->fade.duration,
|
||||
item->fade.easing
|
||||
);
|
||||
}
|
||||
|
||||
bool_t cutsceneFadeUpdate(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
) {
|
||||
return !(
|
||||
UI_FULLBOX_OVER.duration > 0.0f &&
|
||||
UI_FULLBOX_OVER.time < UI_FULLBOX_OVER.duration
|
||||
);
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "display/color.h"
|
||||
#include "animation/easing.h"
|
||||
|
||||
typedef struct cutsceneitem_s cutsceneitem_t;
|
||||
typedef union cutsceneitemdata_u cutsceneitemdata_t;
|
||||
|
||||
typedef struct {
|
||||
color_t from;
|
||||
color_t to;
|
||||
float_t duration;
|
||||
easingtype_t easing;
|
||||
} cutscenefade_t;
|
||||
|
||||
/**
|
||||
* Starts a fade item (begins the overlay transition).
|
||||
*
|
||||
* @param item The cutscene item.
|
||||
* @param data Runtime data storage.
|
||||
*/
|
||||
void cutsceneFadeStart(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
);
|
||||
|
||||
/**
|
||||
* Updates a fade item.
|
||||
*
|
||||
* @param item The cutscene item.
|
||||
* @param data Runtime data storage.
|
||||
* @returns true once the overlay transition has completed.
|
||||
*/
|
||||
bool_t cutsceneFadeUpdate(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
);
|
||||
@@ -1,23 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "rpg/cutscene/item/cutsceneitem.h"
|
||||
#include "rpg/rpgcamera.h"
|
||||
|
||||
void cutsceneShakeStart(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
) {
|
||||
rpgCameraShake(item->shake.amount, item->shake.duration);
|
||||
}
|
||||
|
||||
bool_t cutsceneShakeUpdate(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "dusk.h"
|
||||
|
||||
typedef struct cutsceneitem_s cutsceneitem_t;
|
||||
typedef union cutsceneitemdata_u cutsceneitemdata_t;
|
||||
|
||||
typedef struct {
|
||||
uint8_t amount;
|
||||
float_t duration;
|
||||
} cutsceneshake_t;
|
||||
|
||||
/**
|
||||
* Starts a camera shake item (kicks off the shake on the RPG camera).
|
||||
*
|
||||
* @param item The cutscene item.
|
||||
* @param data Runtime data storage.
|
||||
*/
|
||||
void cutsceneShakeStart(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
);
|
||||
|
||||
/**
|
||||
* Updates a camera shake item. The shake itself runs asynchronously on
|
||||
* the RPG camera, so this always completes immediately.
|
||||
*
|
||||
* @param item The cutscene item.
|
||||
* @param data Runtime data storage.
|
||||
* @returns true always.
|
||||
*/
|
||||
bool_t cutsceneShakeUpdate(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
);
|
||||
@@ -1,23 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "rpg/cutscene/item/cutsceneitem.h"
|
||||
#include "ui/rpg/textbox/uitextboxmain.h"
|
||||
|
||||
void cutsceneTextStart(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
) {
|
||||
uiTextboxMainSetText(item->text.text);
|
||||
}
|
||||
|
||||
bool_t cutsceneTextUpdate(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
) {
|
||||
return !uiTextboxMainIsActive();
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "dusk.h"
|
||||
|
||||
typedef struct cutsceneitem_s cutsceneitem_t;
|
||||
typedef union cutsceneitemdata_u cutsceneitemdata_t;
|
||||
|
||||
#define CUTSCENE_TEXT_MAX_CHARS 256
|
||||
|
||||
typedef struct {
|
||||
char_t text[CUTSCENE_TEXT_MAX_CHARS];
|
||||
} cutscenetext_t;
|
||||
|
||||
/**
|
||||
* Starts a text item (shows the textbox with the item's text).
|
||||
*
|
||||
* @param item The cutscene item.
|
||||
* @param data Runtime data storage.
|
||||
*/
|
||||
void cutsceneTextStart(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
);
|
||||
|
||||
/**
|
||||
* Updates a text item.
|
||||
*
|
||||
* @param item The cutscene item.
|
||||
* @param data Runtime data storage.
|
||||
* @returns true once the textbox has been dismissed.
|
||||
*/
|
||||
bool_t cutsceneTextUpdate(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
);
|
||||
@@ -1,33 +0,0 @@
|
||||
/**
|
||||
* 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 "ui/rpg/textbox/uitextboxminilist.h"
|
||||
|
||||
void cutsceneTextMiniStart(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
) {
|
||||
uint8_t index = uiTextboxMiniListGetNext();
|
||||
uiTextboxMiniShow(
|
||||
&UI_TEXTBOX_MINI_LIST[index],
|
||||
item->textMini.text,
|
||||
item->textMini.position,
|
||||
item->textMini.duration,
|
||||
NULL,
|
||||
NULL
|
||||
);
|
||||
CUTSCENE_SYSTEM.textMiniLastCreated = index;
|
||||
}
|
||||
|
||||
bool_t cutsceneTextMiniUpdate(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "dusk.h"
|
||||
|
||||
typedef struct cutsceneitem_s cutsceneitem_t;
|
||||
typedef union cutsceneitemdata_u cutsceneitemdata_t;
|
||||
|
||||
#define CUTSCENE_TEXT_MINI_MAX_CHARS 128
|
||||
|
||||
typedef struct {
|
||||
char_t text[CUTSCENE_TEXT_MINI_MAX_CHARS];
|
||||
vec3 position;
|
||||
float_t duration;
|
||||
} cutscenetextmini_t;
|
||||
|
||||
/**
|
||||
* Starts a mini text item (shows a mini textbox at the given world
|
||||
* position for the given duration, then completes immediately).
|
||||
*
|
||||
* @param item The cutscene item.
|
||||
* @param data Runtime data storage.
|
||||
*/
|
||||
void cutsceneTextMiniStart(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
);
|
||||
|
||||
/**
|
||||
* Updates a mini text item (always completes immediately).
|
||||
*
|
||||
* @param item The cutscene item.
|
||||
* @param data Runtime data storage.
|
||||
* @returns true always.
|
||||
*/
|
||||
bool_t cutsceneTextMiniUpdate(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
);
|
||||
@@ -1,25 +0,0 @@
|
||||
/**
|
||||
* 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 "ui/rpg/textbox/uitextboxminilist.h"
|
||||
|
||||
void cutsceneTextMiniHideStart(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
) {
|
||||
uint8_t index = cutsceneSystemGetTextMiniId(item->textMiniHide.index);
|
||||
uiTextboxMiniClose(&UI_TEXTBOX_MINI_LIST[index]);
|
||||
}
|
||||
|
||||
bool_t cutsceneTextMiniHideUpdate(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "dusk.h"
|
||||
|
||||
typedef struct cutsceneitem_s cutsceneitem_t;
|
||||
typedef union cutsceneitemdata_u cutsceneitemdata_t;
|
||||
|
||||
typedef struct {
|
||||
uint8_t index;
|
||||
} cutscenetextminihide_t;
|
||||
|
||||
/**
|
||||
* Starts a mini text hide step (closes the mini textbox immediately).
|
||||
*
|
||||
* @param item The cutscene item.
|
||||
* @param data Runtime data storage.
|
||||
*/
|
||||
void cutsceneTextMiniHideStart(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
);
|
||||
|
||||
/**
|
||||
* Updates a mini text hide step (always completes immediately).
|
||||
*
|
||||
* @param item The cutscene item.
|
||||
* @param data Runtime data storage.
|
||||
* @returns true always.
|
||||
*/
|
||||
bool_t cutsceneTextMiniHideUpdate(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
);
|
||||
@@ -1,31 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "rpg/cutscene/cutscene.h"
|
||||
#include "rpg/cutscene/cutscenesystem.h"
|
||||
|
||||
CUTSCENE(TEST_ONE, 0, DEFAULT,
|
||||
CUTSCENE_TEXT("Test One."),
|
||||
);
|
||||
|
||||
CUTSCENE(TEST_TWO, 0, DEFAULT,
|
||||
CUTSCENE_TEXT("Test Two."),
|
||||
CUTSCENE_ENTITY_ADD(ENTITY_TYPE_NPC, 4, 4, 0),
|
||||
CUTSCENE_TEXT_MINI("Hello!", 4, 4, 0, 3.0f),
|
||||
CUTSCENE_EMOJI(
|
||||
CUTSCENE_ENTITY_LAST_CREATED, UI_EMOJI_EXCLAMATION_MARK, 2.0f
|
||||
),
|
||||
CUTSCENE_ENTITY_WALK_TO(CUTSCENE_ENTITY_LAST_CREATED, 8, 2, 0),
|
||||
// CUTSCENE_CONCURRENT(
|
||||
// CUTSCENE_ENTITY_WALK_TO(CUTSCENE_ENTITY_INTERACT, 4, 4, 0),
|
||||
// CUTSCENE_ENTITY_WALK_TO(CUTSCENE_ENTITY_INTERACTED, 8, 2, 0),
|
||||
// ),
|
||||
// CUTSCENE_ITEM_GIVE(ITEM_ID_POTATO, 3),
|
||||
// CUTSCENE_ENTITY_REMOVE(CUTSCENE_ENTITY_INTERACT),
|
||||
CUTSCENE_TEXT("Done."),
|
||||
);
|
||||
@@ -1,18 +0,0 @@
|
||||
# Copyright (c) 2025 Dominic Masters
|
||||
#
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
# Sources
|
||||
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||
PUBLIC
|
||||
entity.c
|
||||
entitydir.c
|
||||
entitypathstep.c
|
||||
player.c
|
||||
)
|
||||
|
||||
add_subdirectory(interact)
|
||||
add_subdirectory(npc)
|
||||
add_subdirectory(item)
|
||||
add_subdirectory(global)
|
||||
@@ -1,244 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "entity.h"
|
||||
#include "assert/assert.h"
|
||||
#include "util/memory.h"
|
||||
#include "util/math.h"
|
||||
#include "time/time.h"
|
||||
#include "rpg/overworld/map.h"
|
||||
#include "rpg/overworld/maparea.h"
|
||||
#include "rpg/overworld/chunk.h"
|
||||
|
||||
entity_t ENTITIES[ENTITY_COUNT];
|
||||
physicsworld_t ENTITY_PHYSICS_WORLD;
|
||||
|
||||
void entityInit(entity_t *entity, const entitytype_t type) {
|
||||
assertNotNull(entity, "Entity pointer cannot be NULL");
|
||||
assertTrue(type < ENTITY_TYPE_COUNT, "Invalid entity type");
|
||||
assertTrue(type != ENTITY_TYPE_NULL, "Cannot have NULL entity type");
|
||||
assertTrue(
|
||||
entity >= ENTITIES && entity < ENTITIES + ENTITY_COUNT,
|
||||
"Entity pointer is out of bounds"
|
||||
);
|
||||
|
||||
memoryZero(entity, sizeof(entity_t));
|
||||
entity->id = (uint8_t)(entity - ENTITIES);
|
||||
entity->globalId = ENTITY_GLOBAL_ID_NULL;
|
||||
entity->type = type;
|
||||
entity->chunkIndex = 0xFF;
|
||||
|
||||
const vec3 extents = ENTITY_PHYSICS_EXTENTS_DEFAULT;
|
||||
physicsBodyInit(&entity->body, (vec3){ 0.0f, 0.0f, 0.0f }, extents);
|
||||
|
||||
if(ENTITY_CALLBACKS[type].init != NULL) ENTITY_CALLBACKS[type].init(entity);
|
||||
}
|
||||
|
||||
void entityUpdate(entity_t *entity) {
|
||||
assertNotNull(entity, "Entity pointer cannot be NULL");
|
||||
assertTrue(entity->type < ENTITY_TYPE_COUNT, "Invalid entity type");
|
||||
assertTrue(entity->type != ENTITY_TYPE_NULL, "Cannot have NULL entity type");
|
||||
|
||||
if(ENTITY_CALLBACKS[entity->type].movement != NULL) {
|
||||
ENTITY_CALLBACKS[entity->type].movement(entity);
|
||||
}
|
||||
|
||||
physicsbody_t *others[ENTITY_COUNT];
|
||||
uint32_t othersCount = 0;
|
||||
for(uint8_t i = 0; i < ENTITY_COUNT; i++) {
|
||||
if(ENTITIES[i].type == ENTITY_TYPE_NULL) continue;
|
||||
if(&ENTITIES[i] == entity) continue;
|
||||
others[othersCount++] = &ENTITIES[i].body;
|
||||
}
|
||||
|
||||
physicsWorldStep(
|
||||
&ENTITY_PHYSICS_WORLD, &entity->body, TIME.delta, others, othersCount
|
||||
);
|
||||
entitySyncFromPhysics(entity);
|
||||
}
|
||||
|
||||
bool_t entityCanUnload(entity_t *entity) {
|
||||
return entity->globalId < ENTITY_GLOBAL_ID_START;
|
||||
}
|
||||
|
||||
void entityMove(
|
||||
entity_t *entity, const vec2 direction, const bool_t running
|
||||
) {
|
||||
assertNotNull(entity, "Entity pointer cannot be NULL");
|
||||
|
||||
const float_t magSq =
|
||||
direction[0] * direction[0] + direction[1] * direction[1];
|
||||
if(magSq <= ENTITY_MOVE_DEADZONE * ENTITY_MOVE_DEADZONE) {
|
||||
entity->body.velocity[0] = 0.0f;
|
||||
entity->body.velocity[1] = 0.0f;
|
||||
entity->animation = ENTITY_ANIM_IDLE;
|
||||
return;
|
||||
}
|
||||
|
||||
const float_t mag = sqrtf(magSq);
|
||||
const float_t clampedMag = mathMin(mag, 1.0f);
|
||||
const float_t speed = running ? ENTITY_MOVE_RUN_SPEED : ENTITY_MOVE_WALK_SPEED;
|
||||
|
||||
entity->body.velocity[0] = (direction[0] / mag) * clampedMag * speed;
|
||||
entity->body.velocity[1] = (direction[1] / mag) * clampedMag * speed;
|
||||
entity->direction = entityDirFromVec2(direction);
|
||||
entity->animation = running ? ENTITY_ANIM_RUN : ENTITY_ANIM_WALK;
|
||||
}
|
||||
|
||||
void entityStop(entity_t *entity) {
|
||||
const vec2 zero = { 0.0f, 0.0f };
|
||||
entityMove(entity, zero, false);
|
||||
}
|
||||
|
||||
void entityTurn(entity_t *entity, const entitydir_t direction) {
|
||||
assertNotNull(entity, "Entity pointer cannot be NULL");
|
||||
entity->direction = direction;
|
||||
}
|
||||
|
||||
void entityWalk(entity_t *entity, const entitydir_t direction) {
|
||||
vec2 dirVec;
|
||||
entityDirToVec2(direction, dirVec);
|
||||
entityMove(entity, dirVec, false);
|
||||
}
|
||||
|
||||
void entityRun(entity_t *entity, const entitydir_t direction) {
|
||||
vec2 dirVec;
|
||||
entityDirToVec2(direction, dirVec);
|
||||
entityMove(entity, dirVec, true);
|
||||
}
|
||||
|
||||
entity_t * entityGetFacing(entity_t *entity, const float_t range) {
|
||||
assertNotNull(entity, "Entity pointer cannot be NULL");
|
||||
|
||||
vec2 dir;
|
||||
entityDirToVec2(entity->direction, dir);
|
||||
|
||||
vec3 min, max;
|
||||
physicsBodyGetBounds(&entity->body, min, max);
|
||||
|
||||
const vec3 probeMin = {
|
||||
min[0] + dir[0] * range, min[1] + dir[1] * range, min[2]
|
||||
};
|
||||
const vec3 probeMax = {
|
||||
max[0] + dir[0] * range, max[1] + dir[1] * range, max[2]
|
||||
};
|
||||
|
||||
entity_t *best = NULL;
|
||||
float_t bestDistSq = 0.0f;
|
||||
|
||||
entity_t *ent = ENTITIES;
|
||||
do {
|
||||
if(ent->type == ENTITY_TYPE_NULL) continue;
|
||||
if(ent == entity) continue;
|
||||
|
||||
vec3 oMin, oMax;
|
||||
physicsBodyGetBounds(&ent->body, oMin, oMax);
|
||||
|
||||
if(probeMin[0] >= oMax[0] || probeMax[0] <= oMin[0]) continue;
|
||||
if(probeMin[1] >= oMax[1] || probeMax[1] <= oMin[1]) continue;
|
||||
if(probeMin[2] >= oMax[2] || probeMax[2] <= oMin[2]) continue;
|
||||
|
||||
const float_t dx = ent->body.position[0] - entity->body.position[0];
|
||||
const float_t dy = ent->body.position[1] - entity->body.position[1];
|
||||
const float_t distSq = dx * dx + dy * dy;
|
||||
|
||||
if(best != NULL && distSq >= bestDistSq) continue;
|
||||
best = ent;
|
||||
bestDistSq = distSq;
|
||||
} while(++ent, ent < &ENTITIES[ENTITY_COUNT]);
|
||||
|
||||
return best;
|
||||
}
|
||||
|
||||
entity_t * entityGetByGlobalId(const entityglobalid_t globalId) {
|
||||
entity_t *ent = ENTITIES;
|
||||
do {
|
||||
if(ent->type == ENTITY_TYPE_NULL) continue;
|
||||
if(ent->globalId != globalId) continue;
|
||||
return ent;
|
||||
} while(++ent, ent < &ENTITIES[ENTITY_COUNT]);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
uint8_t entityGetAvailable() {
|
||||
entity_t *ent = ENTITIES;
|
||||
do {
|
||||
if(ent->type == ENTITY_TYPE_NULL) return ent - ENTITIES;
|
||||
} while(++ent, ent < &ENTITIES[ENTITY_COUNT]);
|
||||
|
||||
return 0xFF;
|
||||
}
|
||||
|
||||
void entityPositionSet(entity_t *entity, const worldpos_t pos) {
|
||||
assertNotNull(entity, "Entity pointer cannot be NULL");
|
||||
|
||||
const vec3 floatPos = {
|
||||
(float_t)pos.x, (float_t)pos.y, (float_t)pos.z
|
||||
};
|
||||
const vec3 extents = ENTITY_PHYSICS_EXTENTS_DEFAULT;
|
||||
physicsBodyInit(&entity->body, floatPos, extents);
|
||||
entity->animation = ENTITY_ANIM_IDLE;
|
||||
entitySyncFromPhysics(entity);
|
||||
}
|
||||
|
||||
void entitySetChunk(entity_t *entity, const uint8_t chunkIndex) {
|
||||
assertNotNull(entity, "Entity pointer cannot be NULL");
|
||||
|
||||
if(entity->chunkIndex != 0xFF) {
|
||||
chunk_t *old = mapGetChunk(entity->chunkIndex);
|
||||
if(old != NULL) {
|
||||
for(uint8_t i = 0; i < CHUNK_ENTITY_COUNT_MAX; i++) {
|
||||
if(old->entities[i] != entity->id) continue;
|
||||
old->entities[i] = 0xFF;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
entity->chunkIndex = chunkIndex;
|
||||
|
||||
if(chunkIndex != 0xFF) {
|
||||
chunk_t *next = mapGetChunk(chunkIndex);
|
||||
if(next != NULL) {
|
||||
bool_t inserted = false;
|
||||
for(uint8_t i = 0; i < CHUNK_ENTITY_COUNT_MAX; i++) {
|
||||
if(next->entities[i] != 0xFF) continue;
|
||||
next->entities[i] = entity->id;
|
||||
inserted = true;
|
||||
break;
|
||||
}
|
||||
assertTrue(inserted, "Chunk entity slot overflow");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 || ci == entity->chunkIndex) return;
|
||||
entitySetChunk(entity, (uint8_t)ci);
|
||||
}
|
||||
|
||||
void entitySyncFromPhysics(entity_t *entity) {
|
||||
assertNotNull(entity, "Entity pointer cannot be NULL");
|
||||
|
||||
entity->position = (worldpos_t){
|
||||
(worldunit_t)floorf(entity->body.position[0]),
|
||||
(worldunit_t)floorf(entity->body.position[1]),
|
||||
(worldunit_t)floorf(entity->body.position[2])
|
||||
};
|
||||
|
||||
glm_vec3_copy(entity->body.position, entity->renderPosition);
|
||||
entity->renderPosition[2] *= WORLD_LAYER_HEIGHT;
|
||||
|
||||
entityUpdateChunk(entity);
|
||||
mapAreaCheckEntity(entity);
|
||||
}
|
||||
@@ -1,213 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "entitydir.h"
|
||||
#include "interact/entityinteract.h"
|
||||
#include "entitytype.h"
|
||||
#include "npc/npc.h"
|
||||
#include "rpg/physics/physicsbody.h"
|
||||
#include "rpg/physics/physicsworld.h"
|
||||
|
||||
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
|
||||
|
||||
// Default collision box for every entity's physics body - matches the old
|
||||
// system's exact one-tile footprint.
|
||||
#define ENTITY_PHYSICS_EXTENTS_DEFAULT { 1.0f, 1.0f, 1.0f }
|
||||
|
||||
// Movement speeds, in grid units per second - chosen to match the feel of
|
||||
// the old fixed-duration one-tile-per-step system (12 ticks/tile walking,
|
||||
// 6 ticks/tile running, at DUSK_TIME_STEP = 16ms).
|
||||
#define ENTITY_MOVE_WALK_SPEED 5.2083f
|
||||
#define ENTITY_MOVE_RUN_SPEED 10.4167f
|
||||
|
||||
// Movement vectors below this magnitude are treated as no movement.
|
||||
#define ENTITY_MOVE_DEADZONE 0.1f
|
||||
|
||||
// How far in front of an entity entityGetFacing probes for a target.
|
||||
#define ENTITY_INTERACT_RANGE 1.0f
|
||||
|
||||
typedef enum {
|
||||
ENTITY_ANIM_IDLE,
|
||||
ENTITY_ANIM_WALK,
|
||||
ENTITY_ANIM_RUN,
|
||||
ENTITY_ANIM_COUNT
|
||||
} entityanim_t;
|
||||
|
||||
typedef struct entity_s {
|
||||
uint8_t id;
|
||||
entityglobalid_t globalId;
|
||||
entitytype_t type;
|
||||
entitytypedata_t data;
|
||||
|
||||
// Movement
|
||||
entitydir_t direction;
|
||||
physicsbody_t body;
|
||||
|
||||
// Derived each frame from body.position (floored) - kept for systems
|
||||
// that still assume an integer grid position (chunk membership, map
|
||||
// area triggers, entity-at-position queries).
|
||||
worldpos_t position;
|
||||
|
||||
// Derived each frame from body.position - mirrors the physics position
|
||||
// into render/world-float space (z scaled by WORLD_LAYER_HEIGHT).
|
||||
vec3 renderPosition;
|
||||
|
||||
entityanim_t animation;
|
||||
|
||||
entityinteract_t interact;
|
||||
|
||||
uint8_t chunkIndex;
|
||||
} entity_t;
|
||||
|
||||
extern entity_t ENTITIES[ENTITY_COUNT];
|
||||
|
||||
// Shared physics world every entity's body steps against.
|
||||
extern physicsworld_t ENTITY_PHYSICS_WORLD;
|
||||
|
||||
/**
|
||||
* Initializes an entity structure.
|
||||
*
|
||||
* @param entity Pointer to the entity structure to initialize.
|
||||
* @param type The type of the entity.
|
||||
*/
|
||||
void entityInit(entity_t *entity, const entitytype_t type);
|
||||
|
||||
/**
|
||||
* Updates an entity.
|
||||
*
|
||||
* @param entity Pointer to the entity structure to update.
|
||||
*/
|
||||
void entityUpdate(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);
|
||||
|
||||
/**
|
||||
* Moves an entity continuously in a direction, at walking or running
|
||||
* speed. Sets the entity's facing to the nearest cardinal direction that
|
||||
* matches the movement vector. Must be called every tick the entity
|
||||
* should keep moving - unlike the old tile-stepping system this does not
|
||||
* complete a move on its own; call entityStop to stop.
|
||||
*
|
||||
* @param entity Pointer to the entity to move.
|
||||
* @param direction Movement vector, magnitude 0-1 (values longer than 1
|
||||
* are clamped to 1, so diagonals aren't faster than cardinals).
|
||||
* @param running Whether to move at running speed instead of walking.
|
||||
*/
|
||||
void entityMove(entity_t *entity, const vec2 direction, const bool_t running);
|
||||
|
||||
/**
|
||||
* Stops an entity's horizontal movement (equivalent to
|
||||
* entityMove(entity, {0, 0}, false)).
|
||||
*
|
||||
* @param entity Pointer to the entity to stop.
|
||||
*/
|
||||
void entityStop(entity_t *entity);
|
||||
|
||||
/**
|
||||
* Turn an entity to face a new direction, instantly, without moving it.
|
||||
*
|
||||
* @param entity Pointer to the entity to turn.
|
||||
* @param direction The direction to face.
|
||||
*/
|
||||
void entityTurn(entity_t *entity, const entitydir_t direction);
|
||||
|
||||
/**
|
||||
* Makes an entity walk continuously in a cardinal direction, at walking
|
||||
* speed. Convenience wrapper over entityMove for callers that only think
|
||||
* in terms of the 4 cardinal directions. Must be called every tick the
|
||||
* entity should keep moving.
|
||||
*
|
||||
* @param entity Pointer to the entity to make walk.
|
||||
* @param direction The direction to walk in.
|
||||
*/
|
||||
void entityWalk(entity_t *entity, const entitydir_t direction);
|
||||
|
||||
/**
|
||||
* Makes an entity walk continuously in a cardinal direction, at running
|
||||
* speed. See entityWalk.
|
||||
*
|
||||
* @param entity Pointer to the entity to make run.
|
||||
* @param direction The direction to run in.
|
||||
*/
|
||||
void entityRun(entity_t *entity, const entitydir_t direction);
|
||||
|
||||
/**
|
||||
* Finds the closest other entity whose bounds overlap a probe box
|
||||
* projected out from the given entity's own bounds, along its current
|
||||
* facing direction. Used for interaction targeting - continuous-position
|
||||
* aware, unlike an exact tile match.
|
||||
*
|
||||
* @param entity Pointer to the entity to probe from.
|
||||
* @param range Distance, in grid units, to project the probe box.
|
||||
* @return Pointer to the closest overlapping entity, or NULL if none.
|
||||
*/
|
||||
entity_t *entityGetFacing(entity_t *entity, const float_t range);
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @return The index of an available entity, or 0xFF if none are available.
|
||||
*/
|
||||
uint8_t entityGetAvailable();
|
||||
|
||||
/**
|
||||
* Assigns an entity to a chunk, removing it from its current chunk first.
|
||||
* Pass 0xFF as chunkIndex to detach the entity from any chunk.
|
||||
*
|
||||
* @param entity Pointer to the entity.
|
||||
* @param chunkIndex Index of the chunk to assign to, or 0xFF for none.
|
||||
*/
|
||||
void entitySetChunk(entity_t *entity, const uint8_t chunkIndex);
|
||||
|
||||
/**
|
||||
* Resolves the chunk that an entity's current position falls into and
|
||||
* assigns the entity to it via entitySetChunk. Leaves the entity's chunk
|
||||
* unchanged if its position doesn't fall within any loaded chunk.
|
||||
*
|
||||
* @param entity Pointer to the entity to update.
|
||||
*/
|
||||
void entityUpdateChunk(entity_t *entity);
|
||||
|
||||
/**
|
||||
* Instantly moves an entity to a world position, resetting movement state.
|
||||
*
|
||||
* @param entity Pointer to the entity to move.
|
||||
* @param pos The world position to place the entity at.
|
||||
*/
|
||||
void entityPositionSet(entity_t *entity, const worldpos_t pos);
|
||||
|
||||
/**
|
||||
* Derives position and renderPosition from the entity's physics body, and
|
||||
* refreshes chunk membership and map area triggers to match. Called once
|
||||
* per entity per frame, after the physics step.
|
||||
*
|
||||
* @param entity Pointer to the entity to sync.
|
||||
*/
|
||||
void entitySyncFromPhysics(entity_t *entity);
|
||||
@@ -1,74 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "entitydir.h"
|
||||
#include "assert/assert.h"
|
||||
|
||||
entitydir_t entityDirGetOpposite(const entitydir_t dir) {
|
||||
switch(dir) {
|
||||
case ENTITY_DIR_NORTH: return ENTITY_DIR_SOUTH;
|
||||
case ENTITY_DIR_SOUTH: return ENTITY_DIR_NORTH;
|
||||
case ENTITY_DIR_EAST: return ENTITY_DIR_WEST;
|
||||
case ENTITY_DIR_WEST: return ENTITY_DIR_EAST;
|
||||
default: return dir;
|
||||
}
|
||||
}
|
||||
|
||||
void entityDirGetRelative(
|
||||
const entitydir_t from,
|
||||
worldunits_t *outX,
|
||||
worldunits_t *outY
|
||||
) {
|
||||
assertValidEntityDir(from, "Invalid direction provided");
|
||||
assertNotNull(outX, "Output X pointer cannot be NULL");
|
||||
assertNotNull(outY, "Output Y pointer cannot be NULL");
|
||||
|
||||
switch(from) {
|
||||
case ENTITY_DIR_NORTH:
|
||||
*outX = 0;
|
||||
*outY = 1;
|
||||
break;
|
||||
|
||||
case ENTITY_DIR_EAST:
|
||||
*outX = 1;
|
||||
*outY = 0;
|
||||
break;
|
||||
|
||||
case ENTITY_DIR_SOUTH:
|
||||
*outX = 0;
|
||||
*outY = -1;
|
||||
break;
|
||||
|
||||
case ENTITY_DIR_WEST:
|
||||
*outX = -1;
|
||||
*outY = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void entityDirToVec2(const entitydir_t dir, vec2 out) {
|
||||
assertValidEntityDir(dir, "Invalid direction provided");
|
||||
assertNotNull(out, "Output vector cannot be NULL");
|
||||
|
||||
worldunits_t relX, relY;
|
||||
entityDirGetRelative(dir, &relX, &relY);
|
||||
out[0] = (float_t)relX;
|
||||
out[1] = (float_t)relY;
|
||||
}
|
||||
|
||||
entitydir_t entityDirFromVec2(const vec2 direction) {
|
||||
assertNotNull(direction, "Direction vector cannot be NULL");
|
||||
assertTrue(
|
||||
direction[0] != 0.0f || direction[1] != 0.0f,
|
||||
"Direction vector cannot be zero"
|
||||
);
|
||||
|
||||
if(fabsf(direction[0]) > fabsf(direction[1])) {
|
||||
return direction[0] > 0.0f ? ENTITY_DIR_EAST : ENTITY_DIR_WEST;
|
||||
}
|
||||
return direction[1] > 0.0f ? ENTITY_DIR_NORTH : ENTITY_DIR_SOUTH;
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "rpg/overworld/worldpos.h"
|
||||
|
||||
typedef enum {
|
||||
ENTITY_DIR_NORTH,
|
||||
ENTITY_DIR_EAST,
|
||||
ENTITY_DIR_SOUTH,
|
||||
ENTITY_DIR_WEST,
|
||||
ENTITY_DIR_UP = ENTITY_DIR_NORTH,
|
||||
ENTITY_DIR_DOWN = ENTITY_DIR_SOUTH,
|
||||
ENTITY_DIR_LEFT = ENTITY_DIR_WEST,
|
||||
ENTITY_DIR_RIGHT = ENTITY_DIR_EAST,
|
||||
} entitydir_t;
|
||||
|
||||
/**
|
||||
* Gets the opposite direction of a given direction.
|
||||
*
|
||||
* @param dir The direction to get the opposite of.
|
||||
* @return entitydir_t The opposite direction.
|
||||
*/
|
||||
entitydir_t entityDirGetOpposite(const entitydir_t dir);
|
||||
|
||||
/**
|
||||
* Asserts a given direction is valid.
|
||||
*
|
||||
* @param dir The direction to validate.
|
||||
* @param msg The message to display if the assertion fails.
|
||||
*/
|
||||
#define assertValidEntityDir(dir, msg) \
|
||||
assertTrue( \
|
||||
(dir) == ENTITY_DIR_NORTH || \
|
||||
(dir) == ENTITY_DIR_EAST || \
|
||||
(dir) == ENTITY_DIR_SOUTH || \
|
||||
(dir) == ENTITY_DIR_WEST, \
|
||||
msg \
|
||||
)
|
||||
|
||||
/**
|
||||
* Gets the relative x and y offsets for a given direction.
|
||||
*
|
||||
* @param dir The direction to get offsets for.
|
||||
* @param relX Pointer to store the relative x offset.
|
||||
* @param relY Pointer to store the relative y offset.
|
||||
*/
|
||||
void entityDirGetRelative(
|
||||
const entitydir_t dir, worldunits_t *relX, worldunits_t *relY
|
||||
);
|
||||
|
||||
/**
|
||||
* Converts a cardinal direction to a unit 2D vector, using the same axis
|
||||
* convention as entityDirGetRelative (north = +y, east = +x).
|
||||
*
|
||||
* @param dir The direction to convert.
|
||||
* @param out Output unit vector for the direction.
|
||||
*/
|
||||
void entityDirToVec2(const entitydir_t dir, vec2 out);
|
||||
|
||||
/**
|
||||
* Quantizes a 2D direction vector to the nearest cardinal direction, by
|
||||
* comparing the magnitude of its x and y components.
|
||||
*
|
||||
* @param direction The direction vector to quantize. Must not be a zero
|
||||
* vector.
|
||||
* @return The nearest cardinal direction.
|
||||
*/
|
||||
entitydir_t entityDirFromVec2(const vec2 direction);
|
||||
@@ -1,71 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "entitypathstep.h"
|
||||
#include "entitydir.h"
|
||||
#include "assert/assert.h"
|
||||
|
||||
// Distance, in grid units, within which the entity is considered to have
|
||||
// arrived at its path target.
|
||||
#define ENTITY_PATH_ARRIVE_EPSILON 0.05f
|
||||
|
||||
bool_t entityPathStep(
|
||||
entity_t *entity,
|
||||
const worldpos_t target,
|
||||
bool_t walkAround
|
||||
) {
|
||||
assertNotNull(entity, "Entity must not be NULL");
|
||||
assertTrue(
|
||||
entity->type != ENTITY_TYPE_NULL,
|
||||
"Cannot path step a NULL entity type"
|
||||
);
|
||||
assertTrue(
|
||||
entity >= ENTITIES && entity < ENTITIES + ENTITY_COUNT,
|
||||
"Entity pointer is out of bounds"
|
||||
);
|
||||
|
||||
const float_t dx = (float_t)target.x - entity->body.position[0];
|
||||
const float_t dy = (float_t)target.y - entity->body.position[1];
|
||||
|
||||
if(
|
||||
fabsf(dx) <= ENTITY_PATH_ARRIVE_EPSILON &&
|
||||
fabsf(dy) <= ENTITY_PATH_ARRIVE_EPSILON
|
||||
) {
|
||||
entityStop(entity);
|
||||
return true;
|
||||
}
|
||||
|
||||
entitydir_t dir;
|
||||
bool_t horizontal;
|
||||
if(fabsf(dx) > fabsf(dy)) {
|
||||
dir = dx > 0.0f ? ENTITY_DIR_EAST : ENTITY_DIR_WEST;
|
||||
horizontal = true;
|
||||
} else {
|
||||
dir = dy > 0.0f ? ENTITY_DIR_NORTH : ENTITY_DIR_SOUTH;
|
||||
horizontal = false;
|
||||
}
|
||||
|
||||
// Was the entity trying to walk on this axis last tick, but ended up not
|
||||
// moving (velocity clamped by a collision)? If so, treat it as blocked.
|
||||
if(walkAround) {
|
||||
const uint8_t axis = horizontal ? 0 : 1;
|
||||
const bool_t blockedLastTick =
|
||||
entity->animation != ENTITY_ANIM_IDLE &&
|
||||
fabsf(entity->body.velocity[axis]) <= ENTITY_MOVE_DEADZONE;
|
||||
|
||||
if(blockedLastTick) {
|
||||
const entitydir_t alt = horizontal
|
||||
? (dy >= 0.0f ? ENTITY_DIR_NORTH : ENTITY_DIR_SOUTH)
|
||||
: (dx >= 0.0f ? ENTITY_DIR_EAST : ENTITY_DIR_WEST);
|
||||
entityWalk(entity, alt);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
entityWalk(entity, dir);
|
||||
return false;
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "entity.h"
|
||||
|
||||
/**
|
||||
* Continuously walks the entity toward target's X/Y position (Z is left to
|
||||
* gravity/ground collision, not deliberate path-stepping). Prefers closing
|
||||
* whichever of X/Y currently differs most. Must be called every tick until
|
||||
* it returns true. When walkAround is true and the last tick's movement on
|
||||
* the current axis was blocked (its velocity on that axis reads as zero
|
||||
* despite currently walking), tries a perpendicular direction instead.
|
||||
*
|
||||
* @param entity Pointer to the entity to move.
|
||||
* @param target The world position to move toward (Z is ignored).
|
||||
* @param walkAround Whether to try a perpendicular direction when blocked.
|
||||
* @returns true once the entity is within arrival distance of target.
|
||||
*/
|
||||
bool_t entityPathStep(
|
||||
entity_t *entity,
|
||||
const worldpos_t target,
|
||||
bool_t walkAround
|
||||
);
|
||||
@@ -1,62 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "rpg/entity/player.h"
|
||||
#include "npc/npc.h"
|
||||
#include "item/entityitem.h"
|
||||
|
||||
typedef enum {
|
||||
ENTITY_TYPE_NULL,
|
||||
|
||||
ENTITY_TYPE_PLAYER,
|
||||
ENTITY_TYPE_NPC,
|
||||
ENTITY_TYPE_ITEM,
|
||||
|
||||
ENTITY_TYPE_COUNT
|
||||
} entitytype_enum_t;
|
||||
|
||||
typedef uint8_t entitytype_t;
|
||||
|
||||
typedef union {
|
||||
player_t player;
|
||||
npc_t npc;
|
||||
entityitem_t item;
|
||||
} entitytypedata_t;
|
||||
|
||||
typedef struct {
|
||||
/**
|
||||
* Initialization callback for the entity type.
|
||||
* @param entity Pointer to the entity to initialize.
|
||||
*/
|
||||
void (*init)(entity_t *entity);
|
||||
|
||||
/**
|
||||
* Movement callback for the entity type. Gated by cutscene input.
|
||||
* @param entity Pointer to the entity to move.
|
||||
*/
|
||||
void (*movement)(entity_t *entity);
|
||||
} entitycallback_t;
|
||||
|
||||
static const entitycallback_t ENTITY_CALLBACKS[ENTITY_TYPE_COUNT] = {
|
||||
[ENTITY_TYPE_NULL] = { NULL },
|
||||
|
||||
[ENTITY_TYPE_PLAYER] = {
|
||||
.init = playerInit,
|
||||
.movement = playerInput
|
||||
},
|
||||
|
||||
[ENTITY_TYPE_NPC] = {
|
||||
.init = npcInit,
|
||||
.movement = npcMovement,
|
||||
},
|
||||
|
||||
[ENTITY_TYPE_ITEM] = {
|
||||
.init = entityItemInit,
|
||||
.movement = entityItemMovement,
|
||||
}
|
||||
};
|
||||
@@ -1,9 +0,0 @@
|
||||
# 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
|
||||
)
|
||||
@@ -1,17 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "entityglobaldefs.h"
|
||||
#include "entitygloballist.h"
|
||||
|
||||
#define ENTITY_GLOBAL_LIST_COUNT ( \
|
||||
sizeof(ENTITY_GLOBAL_LIST) / \
|
||||
sizeof(ENTITY_GLOBAL_LIST[0]) \
|
||||
)
|
||||
|
||||
//EOF
|
||||
@@ -1,44 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "error/error.h"
|
||||
#include "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
|
||||
@@ -1,30 +0,0 @@
|
||||
/**
|
||||
* 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
|
||||
@@ -1,9 +0,0 @@
|
||||
# Copyright (c) 2026 Dominic Masters
|
||||
#
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||
PUBLIC
|
||||
entityinteract.c
|
||||
)
|
||||
@@ -1,58 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "rpg/entity/entity.h"
|
||||
#include "assert/assert.h"
|
||||
#include "rpg/cutscene/cutscenesystem.h"
|
||||
#include "ui/rpg/textbox/uitextboxmain.h"
|
||||
|
||||
void entityInteractWith(entity_t *player, entity_t *target) {
|
||||
assertNotNull(player, "Player entity pointer cannot be NULL");
|
||||
assertNotNull(target, "Target entity pointer cannot be NULL");
|
||||
|
||||
switch(target->interact.type) {
|
||||
case ENTITY_INTERACT_CUTSCENE:
|
||||
assertNotNull(
|
||||
target->interact.data.cutscene,
|
||||
"Interact cutscene pointer cannot be NULL"
|
||||
);
|
||||
cutsceneSystemStartCutsceneWith(
|
||||
target->interact.data.cutscene,
|
||||
player,
|
||||
target
|
||||
);
|
||||
break;
|
||||
|
||||
case ENTITY_INTERACT_PRINT:
|
||||
uiTextboxMainSetText(target->interact.data.message);
|
||||
|
||||
// If NPC turn to face player.
|
||||
if(target->type == ENTITY_TYPE_NPC) {
|
||||
target->data.npc.interactState = NPC_INTERACT_STATE_CONVERSING;
|
||||
target->animation = ENTITY_ANIM_IDLE;
|
||||
entityTurn(target, entityDirGetOpposite(player->direction));
|
||||
}
|
||||
|
||||
// entityTurn(player, player->direction); // Redundant (for now)
|
||||
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:
|
||||
break;
|
||||
|
||||
default:
|
||||
assertUnreachable("Unknown entity interact type");
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "dusk.h"
|
||||
|
||||
typedef struct entity_s entity_t;
|
||||
typedef struct cutscene_s cutscene_t;
|
||||
|
||||
/**
|
||||
* Describes the type of interaction an entity supports.
|
||||
*/
|
||||
typedef enum {
|
||||
ENTITY_INTERACT_NULL = 0,
|
||||
|
||||
ENTITY_INTERACT_CUTSCENE,
|
||||
ENTITY_INTERACT_PRINT,
|
||||
ENTITY_INTERACT_CALLBACK,
|
||||
|
||||
ENTITY_INTERACT_COUNT
|
||||
} entityinteracttype_t;
|
||||
|
||||
/**
|
||||
* Per-type data for an entity's interact component.
|
||||
*/
|
||||
typedef union {
|
||||
const cutscene_t *cutscene;
|
||||
const char_t *message;
|
||||
void (*callback)(entity_t *player, entity_t *target);
|
||||
} entityinteractdata_t;
|
||||
|
||||
/**
|
||||
* Interact component attached to any entity that can be interacted with.
|
||||
* Set type to ENTITY_INTERACT_NULL to mark the entity as non-interactable.
|
||||
*/
|
||||
typedef struct {
|
||||
entityinteracttype_t type;
|
||||
entityinteractdata_t data;
|
||||
} entityinteract_t;
|
||||
|
||||
/**
|
||||
* Attempts to interact with the target entity on behalf of the player.
|
||||
* Dispatches via the interact component; falls back to the entity type
|
||||
* callback if no component is set.
|
||||
*
|
||||
* @param player Pointer to the player entity.
|
||||
* @param target Pointer to the entity to interact with.
|
||||
*/
|
||||
void entityInteractWith(entity_t *player, entity_t *target);
|
||||
@@ -1,10 +0,0 @@
|
||||
# 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
|
||||
)
|
||||
@@ -1,44 +0,0 @@
|
||||
/**
|
||||
* 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/textbox/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;
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
/**
|
||||
* 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);
|
||||
@@ -1,13 +0,0 @@
|
||||
# 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
|
||||
npc.c
|
||||
npcturn.c
|
||||
npcwalk.c
|
||||
npcpath.c
|
||||
)
|
||||
@@ -1,60 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "rpg/entity/entity.h"
|
||||
#include "assert/assert.h"
|
||||
#include "rpg/cutscene/cutscenesystem.h"
|
||||
|
||||
#include "rpg/cutscene/scene/testcutscene.h"
|
||||
|
||||
const npcmovecallback_t NPC_MOVE_CALLBACKS[NPC_MOVE_TYPE_COUNT] = {
|
||||
[NPC_MOVE_TYPE_NULL] = { 0 },
|
||||
|
||||
[NPC_MOVE_TYPE_RANDOM_TURN] = {
|
||||
npcRandomTurnInit,
|
||||
npcRandomTurnMovement
|
||||
},
|
||||
|
||||
[NPC_MOVE_TYPE_RANDOM_WALK] = {
|
||||
npcRandomWalkInit,
|
||||
npcRandomWalkMovement
|
||||
},
|
||||
|
||||
[NPC_MOVE_TYPE_RANDOM_TURN_AND_WALK] = {
|
||||
npcRandomTurnAndWalkInit,
|
||||
npcRandomTurnAndWalkMovement
|
||||
},
|
||||
|
||||
[NPC_MOVE_TYPE_PATH] = {
|
||||
npcPathInit,
|
||||
npcPathMovement
|
||||
},
|
||||
};
|
||||
|
||||
void npcInit(entity_t *entity) {
|
||||
assertNotNull(entity, "Entity pointer cannot be NULL");
|
||||
}
|
||||
|
||||
void npcSetMoveType(entity_t *entity, const npcmovetype_t moveType) {
|
||||
assertNotNull(entity, "Entity pointer cannot be NULL");
|
||||
npc_t *npc = &entity->data.npc;
|
||||
npc->moveType = moveType;
|
||||
if(NPC_MOVE_CALLBACKS[moveType].init != NULL) {
|
||||
NPC_MOVE_CALLBACKS[moveType].init(npc);
|
||||
}
|
||||
}
|
||||
|
||||
void npcMovement(entity_t *entity) {
|
||||
assertNotNull(entity, "Entity pointer cannot be NULL");
|
||||
if(CUTSCENE_SYSTEM.pause & CUTSCENE_PAUSE_NPC) return;
|
||||
|
||||
npc_t *npc = &entity->data.npc;
|
||||
if(npc->interactState != NPC_INTERACT_STATE_NONE) return;
|
||||
|
||||
const npcmovecallback_t *cb = &NPC_MOVE_CALLBACKS[npc->moveType];
|
||||
if(cb->movement != NULL) cb->movement(entity);
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "dusk.h"
|
||||
#include "npcturn.h"
|
||||
#include "npcwalk.h"
|
||||
#include "npcpath.h"
|
||||
|
||||
typedef struct entity_s entity_t;
|
||||
|
||||
typedef enum {
|
||||
NPC_INTERACT_STATE_NONE,
|
||||
NPC_INTERACT_STATE_CONVERSING,
|
||||
NPC_INTERACT_STATE_COUNT
|
||||
} npcinteractstate_t;
|
||||
|
||||
typedef enum {
|
||||
NPC_MOVE_TYPE_NULL,
|
||||
NPC_MOVE_TYPE_RANDOM_TURN,
|
||||
NPC_MOVE_TYPE_RANDOM_WALK,
|
||||
NPC_MOVE_TYPE_RANDOM_TURN_AND_WALK,
|
||||
NPC_MOVE_TYPE_PATH,
|
||||
NPC_MOVE_TYPE_COUNT
|
||||
} npcmovetype_t;
|
||||
|
||||
typedef union {
|
||||
npcrandomturn_t randomTurn;
|
||||
npcrandomwalk_t randomWalk;
|
||||
npcrandomturnandwalk_t randomTurnAndWalk;
|
||||
npcpath_t path;
|
||||
} npcmovedata_t;
|
||||
|
||||
typedef struct npc_s {
|
||||
npcinteractstate_t interactState;
|
||||
npcmovetype_t moveType;
|
||||
npcmovedata_t moveData;
|
||||
} npc_t;
|
||||
|
||||
typedef struct {
|
||||
/** Called once when the move type is set. */
|
||||
void (*init)(npc_t *npc);
|
||||
/** Called each movement tick. */
|
||||
void (*movement)(entity_t *entity);
|
||||
} npcmovecallback_t;
|
||||
|
||||
extern const npcmovecallback_t NPC_MOVE_CALLBACKS[NPC_MOVE_TYPE_COUNT];
|
||||
|
||||
/**
|
||||
* Initializes an NPC entity.
|
||||
*
|
||||
* @param entity Pointer to the entity structure to initialize.
|
||||
*/
|
||||
void npcInit(entity_t *entity);
|
||||
|
||||
/**
|
||||
* Sets the movement type for an NPC entity.
|
||||
*
|
||||
* @param entity Pointer to the entity structure.
|
||||
* @param moveType The movement type to set.
|
||||
*/
|
||||
void npcSetMoveType(entity_t *entity, const npcmovetype_t moveType);
|
||||
|
||||
/**
|
||||
* Movement callback for an NPC entity. Gated by cutscene input.
|
||||
*
|
||||
* @param entity Pointer to the entity structure to update.
|
||||
*/
|
||||
void npcMovement(entity_t *entity);
|
||||
|
||||
/**
|
||||
* Free movement callback for an NPC entity. Runs always-run move types
|
||||
* regardless of cutscene state.
|
||||
*
|
||||
* @param entity Pointer to the entity structure to update.
|
||||
*/
|
||||
void npcFreeMovement(entity_t *entity);
|
||||
@@ -1,37 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "npc.h"
|
||||
#include "rpg/entity/entity.h"
|
||||
#include "rpg/entity/entitypathstep.h"
|
||||
#include "rpg/overworld/worldpos.h"
|
||||
#include "assert/assert.h"
|
||||
|
||||
void npcPathInit(npc_t *npc) {
|
||||
npcpath_t *path = &npc->moveData.path;
|
||||
path->count = 0;
|
||||
path->index = 0;
|
||||
}
|
||||
|
||||
void npcPathAddNode(npc_t *npc, const worldpos_t pos) {
|
||||
assertNotNull(npc, "NPC must not be NULL");
|
||||
assertTrue(
|
||||
npc->moveData.path.count < NPC_PATH_COUNT_MAX,
|
||||
"NPC path is full"
|
||||
);
|
||||
npc->moveData.path.positions[npc->moveData.path.count++] = pos;
|
||||
}
|
||||
|
||||
void npcPathMovement(entity_t *entity) {
|
||||
npcpath_t *path = &entity->data.npc.moveData.path;
|
||||
if(path->count == 0) return;
|
||||
|
||||
const worldpos_t target = path->positions[path->index];
|
||||
if(entityPathStep(entity, target, false)) {
|
||||
path->index = (path->index + 1) % path->count;
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "dusk.h"
|
||||
#include "rpg/overworld/worldpos.h"
|
||||
|
||||
typedef struct npc_s npc_t;
|
||||
typedef struct entity_s entity_t;
|
||||
|
||||
/** Maximum number of waypoints in an NPC path. */
|
||||
#define NPC_PATH_COUNT_MAX 8
|
||||
|
||||
typedef struct {
|
||||
worldpos_t positions[NPC_PATH_COUNT_MAX];
|
||||
uint8_t count;
|
||||
uint8_t index;
|
||||
} npcpath_t;
|
||||
|
||||
/**
|
||||
* Initializes the path movement data for an NPC.
|
||||
*
|
||||
* @param npc Pointer to the NPC to initialize.
|
||||
*/
|
||||
void npcPathInit(npc_t *npc);
|
||||
|
||||
/**
|
||||
* Appends a waypoint to the NPC's path. Has no effect if the path is full.
|
||||
*
|
||||
* @param npc Pointer to the NPC.
|
||||
* @param pos The world position to append.
|
||||
*/
|
||||
void npcPathAddNode(npc_t *npc, const worldpos_t pos);
|
||||
|
||||
/**
|
||||
* Movement tick for an NPC following a path.
|
||||
*
|
||||
* @param entity Pointer to the entity to update.
|
||||
*/
|
||||
void npcPathMovement(entity_t *entity);
|
||||
@@ -1,27 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "npc.h"
|
||||
#include "rpg/entity/entity.h"
|
||||
#include "util/random.h"
|
||||
#include "time/time.h"
|
||||
#include <stdlib.h>
|
||||
|
||||
void npcRandomTurnInit(npc_t *npc) {
|
||||
npcrandomturn_t *turn = &npc->moveData.randomTurn;
|
||||
turn->frequencyMin = NPC_RANDOM_TURN_FREQUENCY_MIN_DEFAULT;
|
||||
turn->frequencyMax = NPC_RANDOM_TURN_FREQUENCY_MAX_DEFAULT;
|
||||
turn->timer = randomFloat(turn->frequencyMin, turn->frequencyMax);
|
||||
}
|
||||
|
||||
void npcRandomTurnMovement(entity_t *entity) {
|
||||
npcrandomturn_t *turn = &entity->data.npc.moveData.randomTurn;
|
||||
turn->timer -= TIME.delta;
|
||||
if(turn->timer > 0.0f) return;
|
||||
turn->timer = randomFloat(turn->frequencyMin, turn->frequencyMax);
|
||||
entityTurn(entity, (entitydir_t)(rand() % 4));
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "dusk.h"
|
||||
|
||||
typedef struct npc_s npc_t;
|
||||
typedef struct entity_s entity_t;
|
||||
|
||||
/** Default min/max seconds between NPC random-turn ticks. */
|
||||
#define NPC_RANDOM_TURN_FREQUENCY_MIN_DEFAULT 2.0f
|
||||
#define NPC_RANDOM_TURN_FREQUENCY_MAX_DEFAULT 4.0f
|
||||
|
||||
typedef struct {
|
||||
float_t frequencyMin;
|
||||
float_t frequencyMax;
|
||||
float_t timer;
|
||||
} npcrandomturn_t;
|
||||
|
||||
/**
|
||||
* Initializes the random-turn movement data for an NPC.
|
||||
*
|
||||
* @param npc Pointer to the NPC to initialize.
|
||||
*/
|
||||
void npcRandomTurnInit(npc_t *npc);
|
||||
|
||||
/**
|
||||
* Movement tick for an NPC using random-turn movement.
|
||||
*
|
||||
* @param entity Pointer to the entity to update.
|
||||
*/
|
||||
void npcRandomTurnMovement(entity_t *entity);
|
||||
@@ -1,71 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "npc.h"
|
||||
#include "rpg/entity/entity.h"
|
||||
#include "util/random.h"
|
||||
#include "time/time.h"
|
||||
#include <stdlib.h>
|
||||
|
||||
void npcRandomWalkInit(npc_t *npc) {
|
||||
npcrandomwalk_t *walk = &npc->moveData.randomWalk;
|
||||
walk->frequencyMin = NPC_RANDOM_WALK_FREQUENCY_MIN_DEFAULT;
|
||||
walk->frequencyMax = NPC_RANDOM_WALK_FREQUENCY_MAX_DEFAULT;
|
||||
walk->timer = randomFloat(walk->frequencyMin, walk->frequencyMax);
|
||||
walk->moveDuration = 0.0f;
|
||||
}
|
||||
|
||||
void npcRandomWalkMovement(entity_t *entity) {
|
||||
npcrandomwalk_t *walk = &entity->data.npc.moveData.randomWalk;
|
||||
|
||||
if(walk->moveDuration > 0.0f) {
|
||||
walk->moveDuration -= TIME.delta;
|
||||
entityWalk(entity, walk->direction);
|
||||
if(walk->moveDuration <= 0.0f) entityStop(entity);
|
||||
return;
|
||||
}
|
||||
|
||||
walk->timer -= TIME.delta;
|
||||
if(walk->timer > 0.0f) return;
|
||||
walk->timer = randomFloat(walk->frequencyMin, walk->frequencyMax);
|
||||
walk->direction = (entitydir_t)(rand() % 4);
|
||||
walk->moveDuration = NPC_RANDOM_WALK_MOVE_DURATION_DEFAULT;
|
||||
}
|
||||
|
||||
void npcRandomTurnAndWalkInit(npc_t *npc) {
|
||||
npcRandomTurnInit(npc);
|
||||
npcrandomturnandwalk_t *tw = &npc->moveData.randomTurnAndWalk;
|
||||
tw->walk.frequencyMin = NPC_RANDOM_WALK_FREQUENCY_MIN_DEFAULT;
|
||||
tw->walk.frequencyMax = NPC_RANDOM_WALK_FREQUENCY_MAX_DEFAULT;
|
||||
tw->walk.timer = randomFloat(tw->walk.frequencyMin, tw->walk.frequencyMax);
|
||||
tw->walk.moveDuration = 0.0f;
|
||||
}
|
||||
|
||||
void npcRandomTurnAndWalkMovement(entity_t *entity) {
|
||||
npcrandomturnandwalk_t *tw = &entity->data.npc.moveData.randomTurnAndWalk;
|
||||
|
||||
if(tw->walk.moveDuration > 0.0f) {
|
||||
tw->walk.moveDuration -= TIME.delta;
|
||||
entityWalk(entity, tw->walk.direction);
|
||||
if(tw->walk.moveDuration <= 0.0f) entityStop(entity);
|
||||
return;
|
||||
}
|
||||
|
||||
tw->turn.timer -= TIME.delta;
|
||||
if(tw->turn.timer <= 0.0f) {
|
||||
tw->turn.timer = randomFloat(tw->turn.frequencyMin, tw->turn.frequencyMax);
|
||||
entityTurn(entity, (entitydir_t)(rand() % 4));
|
||||
}
|
||||
|
||||
tw->walk.timer -= TIME.delta;
|
||||
if(tw->walk.timer <= 0.0f) {
|
||||
tw->walk.timer = randomFloat(tw->walk.frequencyMin, tw->walk.frequencyMax);
|
||||
tw->walk.direction = (entitydir_t)(rand() % 4);
|
||||
tw->walk.moveDuration = NPC_RANDOM_WALK_MOVE_DURATION_DEFAULT;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "dusk.h"
|
||||
#include "npcturn.h"
|
||||
#include "rpg/entity/entitydir.h"
|
||||
|
||||
/** Default min/max seconds between NPC random-walk ticks. */
|
||||
#define NPC_RANDOM_WALK_FREQUENCY_MIN_DEFAULT 2.0f
|
||||
#define NPC_RANDOM_WALK_FREQUENCY_MAX_DEFAULT 5.0f
|
||||
|
||||
/** How long a random-walk move lasts once triggered, roughly one tile's
|
||||
* worth of travel at walking speed. */
|
||||
#define NPC_RANDOM_WALK_MOVE_DURATION_DEFAULT 0.2f
|
||||
|
||||
typedef struct {
|
||||
float_t frequencyMin;
|
||||
float_t frequencyMax;
|
||||
float_t timer;
|
||||
|
||||
// In-progress move state - while moveDuration > 0, movement keeps
|
||||
// walking in direction every tick.
|
||||
float_t moveDuration;
|
||||
entitydir_t direction;
|
||||
} npcrandomwalk_t;
|
||||
|
||||
typedef struct {
|
||||
npcrandomturn_t turn;
|
||||
npcrandomwalk_t walk;
|
||||
} npcrandomturnandwalk_t;
|
||||
|
||||
/**
|
||||
* Initializes the random-walk movement data for an NPC.
|
||||
*
|
||||
* @param npc Pointer to the NPC to initialize.
|
||||
*/
|
||||
void npcRandomWalkInit(npc_t *npc);
|
||||
|
||||
/**
|
||||
* Movement tick for an NPC using random-walk movement.
|
||||
*
|
||||
* @param entity Pointer to the entity to update.
|
||||
*/
|
||||
void npcRandomWalkMovement(entity_t *entity);
|
||||
|
||||
/**
|
||||
* Initializes the random-turn-and-walk movement data for an NPC.
|
||||
*
|
||||
* @param npc Pointer to the NPC to initialize.
|
||||
*/
|
||||
void npcRandomTurnAndWalkInit(npc_t *npc);
|
||||
|
||||
/**
|
||||
* Movement tick for an NPC using random-turn-and-walk movement.
|
||||
*
|
||||
* @param entity Pointer to the entity to update.
|
||||
*/
|
||||
void npcRandomTurnAndWalkMovement(entity_t *entity);
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "entity.h"
|
||||
#include "assert/assert.h"
|
||||
#include "rpg/rpgcamera.h"
|
||||
#include "util/memory.h"
|
||||
#include "time/time.h"
|
||||
#include "ui/focus/uifocus.h"
|
||||
#include "ui/frame/game/uigamemenu.h"
|
||||
#include "rpg/cutscene/cutscenesystem.h"
|
||||
|
||||
void playerInit(entity_t *entity) {
|
||||
assertNotNull(entity, "Entity pointer cannot be NULL");
|
||||
entity->globalId = ENTITY_GLOBAL_ID_PLAYER;
|
||||
}
|
||||
|
||||
bool_t playerCanInteract(entity_t *entity) {
|
||||
return entity->animation == ENTITY_ANIM_IDLE;
|
||||
}
|
||||
|
||||
void playerInput(entity_t *entity) {
|
||||
assertNotNull(entity, "Entity pointer cannot be NULL");
|
||||
if(CUTSCENE_SYSTEM.pause & CUTSCENE_PAUSE_PLAYER) {
|
||||
entityStop(entity);
|
||||
return;
|
||||
}
|
||||
|
||||
// Toggle game menu on pause
|
||||
if(uiGameMenuIsOpen() && inputPressed(INPUT_ACTION_PAUSE)) {
|
||||
uiGameMenuClose();
|
||||
} else if(
|
||||
inputPressed(INPUT_ACTION_PAUSE) &&
|
||||
entity->animation == ENTITY_ANIM_IDLE
|
||||
) {
|
||||
uiGameMenuOpen();
|
||||
}
|
||||
|
||||
// Can player act?
|
||||
if(UI_FOCUS.count > 0) {
|
||||
entityStop(entity);
|
||||
return;
|
||||
}
|
||||
|
||||
// Analog/free-angle movement vector from the 4 directional actions -
|
||||
// already normalized so diagonals aren't faster than cardinals, and
|
||||
// preserves real analog magnitude on platforms that bind an actual
|
||||
// gamepad stick to these actions.
|
||||
vec2 moveDir;
|
||||
inputAngle2D(
|
||||
INPUT_ACTION_LEFT, INPUT_ACTION_RIGHT,
|
||||
INPUT_ACTION_DOWN, INPUT_ACTION_UP,
|
||||
moveDir
|
||||
);
|
||||
|
||||
entityMove(entity, moveDir, inputIsDown(INPUT_ACTION_CANCEL));
|
||||
|
||||
// Interaction
|
||||
if(inputPressed(INPUT_ACTION_ACCEPT) && playerCanInteract(entity)) {
|
||||
entity_t *target = entityGetFacing(entity, ENTITY_INTERACT_RANGE);
|
||||
if(target == NULL) return;
|
||||
entityInteractWith(entity, target);
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "input/input.h"
|
||||
#include "entitydir.h"
|
||||
|
||||
typedef struct entity_s entity_t;
|
||||
|
||||
typedef struct {
|
||||
void *nothing;
|
||||
} player_t;
|
||||
|
||||
/**
|
||||
* Initializes a player entity.
|
||||
*
|
||||
* @param entity Pointer to the entity structure to initialize.
|
||||
*/
|
||||
void playerInit(entity_t *entity);
|
||||
|
||||
/**
|
||||
* Returns true if the player entity is in a state where it can interact.
|
||||
*
|
||||
* @param entity Pointer to the player entity to check.
|
||||
* @returns True if the entity can interact.
|
||||
*/
|
||||
bool_t playerCanInteract(entity_t *entity);
|
||||
|
||||
/**
|
||||
* Handles movement logic for the player entity.
|
||||
*
|
||||
* @param entity Pointer to the player entity structure.
|
||||
*/
|
||||
void playerInput(entity_t *entity);
|
||||
@@ -1,22 +0,0 @@
|
||||
# Copyright (c) 2025 Dominic Masters
|
||||
#
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
# Sources
|
||||
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||
PUBLIC
|
||||
item.c
|
||||
inventory.c
|
||||
backpack.c
|
||||
itemgive.c
|
||||
)
|
||||
|
||||
# Item Definitions
|
||||
dusk_run_python(
|
||||
dusk_item_json_defs
|
||||
tools.item
|
||||
--json ${CMAKE_CURRENT_SOURCE_DIR}/item.json
|
||||
--output ${DUSK_GENERATED_HEADERS_DIR}/rpg/item/itemdef.h
|
||||
)
|
||||
add_dependencies(${DUSK_LIBRARY_TARGET_NAME} dusk_item_json_defs)
|
||||
@@ -1,79 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "backpack.h"
|
||||
#include "assert/assert.h"
|
||||
|
||||
backpack_t BACKPACK;
|
||||
|
||||
void backpackInit() {
|
||||
for(uint8_t i = 0; i < ITEM_TYPE_COUNT; i++) {
|
||||
inventoryInit(
|
||||
&BACKPACK.inventories[i],
|
||||
BACKPACK.storage[i],
|
||||
ITEM_TYPE_COUNT_MAX
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
inventory_t *backpackGetInventory(const itemtype_t type) {
|
||||
assertTrue(type > ITEM_TYPE_NULL, "Item type must not be null");
|
||||
assertTrue(type < ITEM_TYPE_COUNT, "Item type out of range");
|
||||
return &BACKPACK.inventories[type];
|
||||
}
|
||||
|
||||
void backpackAdd(const itemid_t item, const uint8_t quantity) {
|
||||
assertTrue(item > ITEM_ID_NULL, "Item ID must not be null");
|
||||
assertTrue(item < ITEM_ID_COUNT, "Item ID out of range");
|
||||
inventoryAdd(backpackGetInventory(ITEMS[item].type), item, quantity);
|
||||
}
|
||||
|
||||
void backpackRemove(const itemid_t item) {
|
||||
assertTrue(item > ITEM_ID_NULL, "Item ID must not be null");
|
||||
assertTrue(item < ITEM_ID_COUNT, "Item ID out of range");
|
||||
inventoryRemove(backpackGetInventory(ITEMS[item].type), item);
|
||||
}
|
||||
|
||||
void backpackSet(const itemid_t item, const uint8_t quantity) {
|
||||
assertTrue(item > ITEM_ID_NULL, "Item ID must not be null");
|
||||
assertTrue(item < ITEM_ID_COUNT, "Item ID out of range");
|
||||
inventorySet(backpackGetInventory(ITEMS[item].type), item, quantity);
|
||||
}
|
||||
|
||||
uint8_t backpackGetCount(const itemid_t item) {
|
||||
assertTrue(item > ITEM_ID_NULL, "Item ID must not be null");
|
||||
assertTrue(item < ITEM_ID_COUNT, "Item ID out of range");
|
||||
return inventoryGetCount(backpackGetInventory(ITEMS[item].type), item);
|
||||
}
|
||||
|
||||
bool_t backpackItemExists(const itemid_t item) {
|
||||
assertTrue(item > ITEM_ID_NULL, "Item ID must not be null");
|
||||
assertTrue(item < ITEM_ID_COUNT, "Item ID out of range");
|
||||
return inventoryItemExists(backpackGetInventory(ITEMS[item].type), item);
|
||||
}
|
||||
|
||||
bool_t backpackIsFull(const itemtype_t type) {
|
||||
assertTrue(type > ITEM_TYPE_NULL, "Item type must not be null");
|
||||
assertTrue(type < ITEM_TYPE_COUNT, "Item type out of range");
|
||||
return inventoryIsFull(backpackGetInventory(type));
|
||||
}
|
||||
|
||||
bool_t backpackItemFull(const itemid_t item) {
|
||||
assertTrue(item > ITEM_ID_NULL, "Item ID must not be null");
|
||||
assertTrue(item < ITEM_ID_COUNT, "Item ID out of range");
|
||||
return inventoryItemFull(backpackGetInventory(ITEMS[item].type), item);
|
||||
}
|
||||
|
||||
void backpackSort(
|
||||
const itemtype_t type,
|
||||
const inventorysort_t sortBy,
|
||||
const bool_t reverse
|
||||
) {
|
||||
assertTrue(type > ITEM_TYPE_NULL, "Item type must not be null");
|
||||
assertTrue(type < ITEM_TYPE_COUNT, "Item type out of range");
|
||||
inventorySort(backpackGetInventory(type), sortBy, reverse);
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "inventory.h"
|
||||
|
||||
typedef struct {
|
||||
inventorystack_t storage[ITEM_TYPE_COUNT][ITEM_TYPE_COUNT_MAX];
|
||||
inventory_t inventories[ITEM_TYPE_COUNT];
|
||||
} backpack_t;
|
||||
|
||||
extern backpack_t BACKPACK;
|
||||
|
||||
/**
|
||||
* Initializes the backpack inventory for the player.
|
||||
*/
|
||||
void backpackInit();
|
||||
|
||||
/**
|
||||
* Returns the inventory for a given item type.
|
||||
*
|
||||
* @param type The item type.
|
||||
* @returns Pointer to the inventory for that type.
|
||||
*/
|
||||
inventory_t *backpackGetInventory(const itemtype_t type);
|
||||
|
||||
/**
|
||||
* Adds a quantity of an item to the backpack.
|
||||
*
|
||||
* @param item The item ID to add.
|
||||
* @param quantity The quantity to add.
|
||||
*/
|
||||
void backpackAdd(const itemid_t item, const uint8_t quantity);
|
||||
|
||||
/**
|
||||
* Removes an item from the backpack.
|
||||
*
|
||||
* @param item The item ID to remove.
|
||||
*/
|
||||
void backpackRemove(const itemid_t item);
|
||||
|
||||
/**
|
||||
* Sets the quantity of an item in the backpack.
|
||||
*
|
||||
* @param item The item ID to set.
|
||||
* @param quantity The quantity to set.
|
||||
*/
|
||||
void backpackSet(const itemid_t item, const uint8_t quantity);
|
||||
|
||||
/**
|
||||
* Gets the quantity of an item in the backpack.
|
||||
*
|
||||
* @param item The item ID to check.
|
||||
* @returns The quantity held.
|
||||
*/
|
||||
uint8_t backpackGetCount(const itemid_t item);
|
||||
|
||||
/**
|
||||
* Checks if an item exists in the backpack (quantity > 0).
|
||||
*
|
||||
* @param item The item ID to check.
|
||||
* @returns true if the item exists.
|
||||
*/
|
||||
bool_t backpackItemExists(const itemid_t item);
|
||||
|
||||
/**
|
||||
* Checks if the inventory for a given type is full.
|
||||
*
|
||||
* @param type The item type to check.
|
||||
* @returns true if the type's inventory is full.
|
||||
*/
|
||||
bool_t backpackIsFull(const itemtype_t type);
|
||||
|
||||
/**
|
||||
* Checks if an item's stack is full in the backpack.
|
||||
*
|
||||
* @param item The item ID to check.
|
||||
* @returns true if the item stack is full.
|
||||
*/
|
||||
bool_t backpackItemFull(const itemid_t item);
|
||||
|
||||
/**
|
||||
* Sorts the inventory for a given item type.
|
||||
*
|
||||
* @param type The item type whose inventory to sort.
|
||||
* @param sortBy The sorting criteria.
|
||||
* @param reverse Whether to sort in reverse order.
|
||||
*/
|
||||
void backpackSort(
|
||||
const itemtype_t type,
|
||||
const inventorysort_t sortBy,
|
||||
const bool_t reverse
|
||||
);
|
||||
@@ -1,250 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "inventory.h"
|
||||
#include "util/memory.h"
|
||||
#include "util/sort.h"
|
||||
#include "assert/assert.h"
|
||||
|
||||
void inventoryInit(
|
||||
inventory_t* inventory,
|
||||
inventorystack_t* storage,
|
||||
uint8_t storageSize
|
||||
) {
|
||||
assertNotNull(inventory, "Inventory pointer is NULL.");
|
||||
assertNotNull(storage, "Storage pointer is NULL.");
|
||||
assertTrue(storageSize > 0, "Storage size must be greater than zero.");
|
||||
|
||||
inventory->storage = storage;
|
||||
inventory->storageSize = storageSize;
|
||||
|
||||
memoryZero(storage, sizeof(inventorystack_t) * storageSize);
|
||||
}
|
||||
|
||||
bool_t inventoryItemExists(const inventory_t *inventory, const itemid_t item) {
|
||||
assertNotNull(inventory, "Inventory pointer is NULL.");
|
||||
assertNotNull(inventory->storage, "Storage pointer is NULL.");
|
||||
assertTrue(inventory->storageSize > 0, "Storage too small.");
|
||||
assertTrue(item != ITEM_ID_NULL, "Item ID cannot be ITEM_ID_NULL.");
|
||||
|
||||
inventorystack_t *stack = inventory->storage;
|
||||
inventorystack_t *end = stack + inventory->storageSize;
|
||||
do {
|
||||
if(stack->item == ITEM_ID_NULL) break;
|
||||
if(stack->item != item) continue;
|
||||
assertTrue(stack->quantity > 0, "Item has quantity zero.");
|
||||
return true;
|
||||
} while(++stack < end);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void inventorySet(
|
||||
inventory_t *inventory,
|
||||
const itemid_t item,
|
||||
const uint8_t quantity
|
||||
) {
|
||||
assertNotNull(inventory, "Inventory pointer is NULL.");
|
||||
assertNotNull(inventory->storage, "Storage pointer is NULL.");
|
||||
assertTrue(inventory->storageSize > 0, "Storage too small.");
|
||||
assertTrue(item != ITEM_ID_NULL, "Item ID cannot be ITEM_ID_NULL.");
|
||||
|
||||
// If quantity 0, remove.
|
||||
if(quantity == 0) return inventoryRemove(inventory, item);
|
||||
|
||||
// Search for existing stack.
|
||||
inventorystack_t *stack = inventory->storage;
|
||||
inventorystack_t *end = stack + inventory->storageSize;
|
||||
do {
|
||||
// Not in inventory yet, add as new stack.
|
||||
if(stack->item == ITEM_ID_NULL) {
|
||||
stack->item = item;
|
||||
stack->quantity = quantity;
|
||||
return;
|
||||
}
|
||||
|
||||
// Not the stack we're looking for.
|
||||
if(stack->item != item) continue;
|
||||
|
||||
// Update existing stack.
|
||||
stack->quantity = quantity;
|
||||
return;
|
||||
} while(++stack < end);
|
||||
|
||||
// No space in the inventory.
|
||||
assertUnreachable("Inventory is full, cannot set more items.");
|
||||
}
|
||||
|
||||
void inventoryAdd(
|
||||
inventory_t *inventory,
|
||||
const itemid_t item,
|
||||
const uint8_t quantity
|
||||
) {
|
||||
uint8_t current = inventoryGetCount(inventory, item);
|
||||
uint16_t newQuantity = (uint16_t)current + (uint16_t)quantity;
|
||||
|
||||
assertTrue(
|
||||
newQuantity <= UINT8_MAX,
|
||||
"Cannot add item, would overflow maximum quantity."
|
||||
);
|
||||
|
||||
inventorySet(inventory, item, (uint8_t)newQuantity);
|
||||
}
|
||||
|
||||
void inventoryRemove(inventory_t *inventory, const itemid_t item) {
|
||||
assertNotNull(inventory, "Inventory pointer is NULL.");
|
||||
assertNotNull(inventory->storage, "Storage pointer is NULL.");
|
||||
assertTrue(inventory->storageSize > 0, "Storage too small.");
|
||||
assertTrue(item != ITEM_ID_NULL, "Item ID cannot be ITEM_ID_NULL.");
|
||||
|
||||
inventorystack_t *stack = inventory->storage;
|
||||
inventorystack_t *end = stack + inventory->storageSize;
|
||||
|
||||
// Search for existing stack.
|
||||
do {
|
||||
// End of inventory, item not present.
|
||||
if(stack->item == ITEM_ID_NULL) break;
|
||||
|
||||
// Not matching stack.
|
||||
if(stack->item != item) continue;
|
||||
|
||||
// Match found, shift everything else down
|
||||
memoryMove(
|
||||
stack,
|
||||
stack + 1,
|
||||
(end - (stack + 1)) * sizeof(inventorystack_t)
|
||||
);
|
||||
|
||||
// Clear last stack.
|
||||
inventorystack_t *last = end - 1;
|
||||
last->item = ITEM_ID_NULL;
|
||||
|
||||
break;
|
||||
} while(++stack < end);
|
||||
}
|
||||
|
||||
uint8_t inventoryGetCount(const inventory_t *inventory, const itemid_t item) {
|
||||
assertNotNull(inventory, "Inventory pointer is NULL.");
|
||||
assertNotNull(inventory->storage, "Storage pointer is NULL.");
|
||||
assertTrue(inventory->storageSize > 0, "Storage too small.");
|
||||
assertTrue(item != ITEM_ID_NULL, "Item ID cannot be ITEM_ID_NULL.");
|
||||
|
||||
inventorystack_t *stack = inventory->storage;
|
||||
inventorystack_t *end = stack + inventory->storageSize;
|
||||
do {
|
||||
// End of inventory, item not present.
|
||||
if(stack->item == ITEM_ID_NULL) break;
|
||||
|
||||
// Not matching stack.
|
||||
if(stack->item != item) continue;
|
||||
|
||||
// Match found, return quantity.
|
||||
return stack->quantity;
|
||||
} while(++stack < end);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool_t inventoryIsFull(const inventory_t *inventory) {
|
||||
assertNotNull(inventory, "Inventory pointer is NULL.");
|
||||
assertNotNull(inventory->storage, "Storage pointer is NULL.");
|
||||
assertTrue(inventory->storageSize > 0, "Storage too small.");
|
||||
|
||||
inventorystack_t *stack = inventory->storage;
|
||||
inventorystack_t *end = stack + inventory->storageSize;
|
||||
do {
|
||||
// Found empty stack, not full.
|
||||
if(stack->item == ITEM_ID_NULL) return false;
|
||||
} while(++stack < end);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool_t inventoryItemFull(const inventory_t *inventory, const itemid_t item) {
|
||||
return inventoryGetCount(inventory, item) == ITEM_STACK_QUANTITY_MAX;
|
||||
}
|
||||
|
||||
// Sorters
|
||||
int_t inventorySortById(const void *a, const void *b) {
|
||||
const inventorystack_t *stackA = (const inventorystack_t*)a;
|
||||
const inventorystack_t *stackB = (const inventorystack_t*)b;
|
||||
if(stackA->item < stackB->item) return -1;
|
||||
if(stackA->item > stackB->item) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int_t inventorySortByIdReverse(const void *a, const void *b) {
|
||||
const inventorystack_t *stackA = (const inventorystack_t*)a;
|
||||
const inventorystack_t *stackB = (const inventorystack_t*)b;
|
||||
if(stackA->item < stackB->item) return 1;
|
||||
if(stackA->item > stackB->item) return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int_t inventorySortByType(const void *a, const void *b) {
|
||||
const inventorystack_t *stackA = (const inventorystack_t*)a;
|
||||
const inventorystack_t *stackB = (const inventorystack_t*)b;
|
||||
const itemtype_t typeA = ITEMS[stackA->item].type;
|
||||
const itemtype_t typeB = ITEMS[stackB->item].type;
|
||||
if(typeA < typeB) return -1;
|
||||
if(typeA > typeB) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int_t inventorySortByTypeReverse(const void *a, const void *b) {
|
||||
const inventorystack_t *stackA = (const inventorystack_t*)a;
|
||||
const inventorystack_t *stackB = (const inventorystack_t*)b;
|
||||
const itemtype_t typeA = ITEMS[stackA->item].type;
|
||||
const itemtype_t typeB = ITEMS[stackB->item].type;
|
||||
if(typeA < typeB) return 1;
|
||||
if(typeA > typeB) return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void inventorySort(
|
||||
inventory_t *inventory,
|
||||
const inventorysort_t sortBy,
|
||||
const bool_t reverse
|
||||
) {
|
||||
assertNotNull(inventory, "Inventory pointer is NULL.");
|
||||
assertNotNull(inventory->storage, "Storage pointer is NULL.");
|
||||
assertTrue(inventory->storageSize > 0, "Storage too small.");
|
||||
assertTrue(sortBy < INVENTORY_SORT_COUNT, "Invalid sort type.");
|
||||
|
||||
// Get count of used stacks
|
||||
size_t count = 0;
|
||||
inventorystack_t *stack = inventory->storage;
|
||||
inventorystack_t *end = stack + inventory->storageSize;
|
||||
do {
|
||||
if(stack->item == ITEM_ID_NULL) break;
|
||||
count++;
|
||||
} while(++stack < end);
|
||||
|
||||
if(count == 0) return; // Nothing to sort
|
||||
|
||||
// Comparator
|
||||
sortcompare_t comparator = NULL;
|
||||
switch(sortBy) {
|
||||
case INVENTORY_SORT_BY_ID: {
|
||||
comparator = reverse ? inventorySortByIdReverse : inventorySortById;
|
||||
break;
|
||||
};
|
||||
|
||||
case INVENTORY_SORT_BY_TYPE: {
|
||||
comparator = reverse ? inventorySortByTypeReverse : inventorySortByType;
|
||||
break;
|
||||
};
|
||||
|
||||
default:
|
||||
assertUnreachable("Invalid sort type.");
|
||||
break;
|
||||
}
|
||||
|
||||
assertNotNull(comparator, "Comparator function is NULL.");
|
||||
|
||||
sort((void*)inventory->storage, count, sizeof(inventorystack_t), comparator);
|
||||
}
|
||||
@@ -1,123 +0,0 @@
|
||||
/**
|
||||
* 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_STACK_QUANTITY_MAX 99
|
||||
|
||||
typedef enum {
|
||||
INVENTORY_SORT_BY_ID,
|
||||
INVENTORY_SORT_BY_TYPE,
|
||||
|
||||
INVENTORY_SORT_COUNT
|
||||
} inventorysort_t;
|
||||
|
||||
typedef struct {
|
||||
itemid_t item;
|
||||
uint8_t quantity;
|
||||
} inventorystack_t;
|
||||
|
||||
typedef struct {
|
||||
inventorystack_t *storage;
|
||||
uint8_t storageSize;
|
||||
} inventory_t;
|
||||
|
||||
/**
|
||||
* Initializes an inventory.
|
||||
*
|
||||
* @param inventory The inventory to initialize.
|
||||
* @param storage The storage array for the inventory.
|
||||
* @param storageSize The size of the storage array.
|
||||
*/
|
||||
void inventoryInit(
|
||||
inventory_t* inventory,
|
||||
inventorystack_t* storage,
|
||||
uint8_t storageSize
|
||||
);
|
||||
|
||||
/**
|
||||
* Checks if a specific item exists in the inventory (and has quantity > 0).
|
||||
*
|
||||
* @param inventory The inventory to check.
|
||||
* @param item The item ID to check.
|
||||
* @return true if the item exists, false otherwise.
|
||||
*/
|
||||
bool_t inventoryItemExists(const inventory_t *inventory, const itemid_t item);
|
||||
|
||||
/**
|
||||
* Sets the quantity of a specific item in the inventory.
|
||||
*
|
||||
* @param inventory The inventory to modify.
|
||||
* @param item The item ID to set.
|
||||
* @param quantity The quantity to set.
|
||||
*/
|
||||
void inventorySet(
|
||||
inventory_t *inventory,
|
||||
const itemid_t item,
|
||||
const uint8_t quantity
|
||||
);
|
||||
|
||||
/**
|
||||
* Adds a specific quantity of an item to the inventory.
|
||||
*
|
||||
* @param inventory The inventory to modify.
|
||||
* @param item The item ID to add.
|
||||
* @param quantity The quantity to add.
|
||||
*/
|
||||
void inventoryAdd(
|
||||
inventory_t *inventory,
|
||||
const itemid_t item,
|
||||
const uint8_t quantity
|
||||
);
|
||||
|
||||
/**
|
||||
* Removes an item from the inventory.
|
||||
*
|
||||
* @param inventory The inventory to modify.
|
||||
* @param item The item ID to remove.
|
||||
*/
|
||||
void inventoryRemove(inventory_t *inventory, const itemid_t item);
|
||||
|
||||
/**
|
||||
* Gets the count of a specific item in the inventory.
|
||||
*
|
||||
* @param inventory The inventory to check.
|
||||
* @param item The item ID to check.
|
||||
* @return The count of the item in the inventory.
|
||||
*/
|
||||
uint8_t inventoryGetCount(const inventory_t *inventory, const itemid_t item);
|
||||
|
||||
/**
|
||||
* Checks if the inventory is full.
|
||||
*
|
||||
* @param inventory The inventory to check.
|
||||
* @return true if full, false otherwise.
|
||||
*/
|
||||
bool_t inventoryIsFull(const inventory_t *inventory);
|
||||
|
||||
/**
|
||||
* Checks if a specific item stack is full in the inventory.
|
||||
*
|
||||
* @param inventory The inventory to check.
|
||||
* @param item The item ID to check.
|
||||
* @return true if the item stack is full, false otherwise.
|
||||
*/
|
||||
bool_t inventoryItemFull(const inventory_t *inventory, const itemid_t item);
|
||||
|
||||
/**
|
||||
* Sorts the inventory based on the specified criteria.
|
||||
*
|
||||
* @param inventory The inventory to sort.
|
||||
* @param sortBy The sorting criteria.
|
||||
* @param reverse Whether to sort in reverse order.
|
||||
*/
|
||||
void inventorySort(
|
||||
inventory_t *inventory,
|
||||
const inventorysort_t sortBy,
|
||||
const bool_t reverse
|
||||
);
|
||||
@@ -1,30 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "item.h"
|
||||
#include "assert/assert.h"
|
||||
#include "locale/localemanager.h"
|
||||
#include "asset/loader/locale/assetlocaleloader.h"
|
||||
|
||||
errorret_t itemGetName(
|
||||
const itemid_t item,
|
||||
char_t *buffer,
|
||||
const size_t bufferSize
|
||||
) {
|
||||
assertTrue(item > ITEM_ID_NULL, "Item ID must not be null");
|
||||
assertTrue(item < ITEM_ID_COUNT, "Item ID out of range");
|
||||
|
||||
errorChain(assetLocaleGetString(
|
||||
&LOCALE.entry->data.locale,
|
||||
ITEMS[item].name,
|
||||
0,
|
||||
buffer,
|
||||
bufferSize
|
||||
));
|
||||
|
||||
errorOk();
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "error/error.h"
|
||||
#include "rpg/item/itemdef.h"
|
||||
|
||||
/**
|
||||
* Gets the localized display name for an item.
|
||||
*
|
||||
* @param item The item ID to look up. Must not be ITEM_ID_NULL.
|
||||
* @param buffer Buffer to write the localized name into.
|
||||
* @param bufferSize Size of the buffer.
|
||||
* @return Any error that occurs.
|
||||
*/
|
||||
errorret_t itemGetName(
|
||||
const itemid_t item,
|
||||
char_t *buffer,
|
||||
const size_t bufferSize
|
||||
);
|
||||
@@ -1,5 +0,0 @@
|
||||
[
|
||||
{ "id": "POTION", "type": "MEDICINE", "weight": 1.0, "name": "potion" },
|
||||
{ "id": "POTATO", "type": "FOOD", "weight": 0.5, "name": "potato" },
|
||||
{ "id": "APPLE", "type": "FOOD", "weight": 0.3, "name": "apple" }
|
||||
]
|
||||
@@ -1,32 +0,0 @@
|
||||
/**
|
||||
* 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 "rpg/item/item.h"
|
||||
#include "ui/rpg/textbox/uitextboxmain.h"
|
||||
#include "util/string.h"
|
||||
#include "error/error.h"
|
||||
|
||||
#define ITEM_GIVE_NAME_MAX_CHARS 32
|
||||
|
||||
void itemGive(const itemid_t item, const uint8_t quantity) {
|
||||
backpackAdd(item, quantity);
|
||||
|
||||
char_t name[ITEM_GIVE_NAME_MAX_CHARS];
|
||||
errorCatch(itemGetName(item, name, ITEM_GIVE_NAME_MAX_CHARS));
|
||||
|
||||
char_t msg[ITEM_GIVE_MESSAGE_MAX_CHARS];
|
||||
stringFormat(
|
||||
msg,
|
||||
ITEM_GIVE_MESSAGE_MAX_CHARS - 1,
|
||||
"Received %s x%u",
|
||||
name,
|
||||
(uint32_t)quantity
|
||||
);
|
||||
uiTextboxMainSetText(msg);
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
/**
|
||||
* 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);
|
||||
@@ -1,16 +0,0 @@
|
||||
# Copyright (c) 2025 Dominic Masters
|
||||
#
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
# Sources
|
||||
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||
PUBLIC
|
||||
chunk.c
|
||||
map.c
|
||||
maparea.c
|
||||
worldpos.c
|
||||
tile.c
|
||||
tileshape.c
|
||||
)
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "chunk.h"
|
||||
|
||||
uint32_t chunkGetTileIndex(const chunkpos_t position) {
|
||||
return (position.y * CHUNK_WIDTH) + position.x;
|
||||
}
|
||||
|
||||
bool_t chunkPositionIsEqual(const chunkpos_t a, const chunkpos_t b) {
|
||||
return (a.x == b.x) && (a.y == b.y) && (a.z == b.z);
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "rpg/overworld/tile.h"
|
||||
#include "worldpos.h"
|
||||
|
||||
#define CHUNK_MESH_COUNT_MAX 10
|
||||
#define CHUNK_MESH_NAME_MAX 64
|
||||
#define CHUNK_ENTITY_COUNT_MAX 10
|
||||
|
||||
typedef struct assetentry_s assetentry_t;
|
||||
|
||||
typedef struct chunk_s {
|
||||
chunkpos_t position;
|
||||
tile_t tiles[CHUNK_TILE_COUNT];
|
||||
|
||||
assetentry_t *dcfEntry;
|
||||
|
||||
uint8_t meshCount;
|
||||
char_t modelNames[CHUNK_MESH_COUNT_MAX][CHUNK_MESH_NAME_MAX];
|
||||
vec3 meshOffsets[CHUNK_MESH_COUNT_MAX];
|
||||
mat4 meshModels[CHUNK_MESH_COUNT_MAX];
|
||||
assetentry_t *modelEntries[CHUNK_MESH_COUNT_MAX];
|
||||
|
||||
uint8_t entities[CHUNK_ENTITY_COUNT_MAX];
|
||||
} chunk_t;
|
||||
|
||||
/**
|
||||
* Gets the tile index for a tile position within a chunk.
|
||||
*
|
||||
* @param position The position within the chunk.
|
||||
* @return The tile index within the chunk.
|
||||
*/
|
||||
uint32_t chunkGetTileIndex(const chunkpos_t position);
|
||||
|
||||
/**
|
||||
* Checks if two chunk positions are equal.
|
||||
*
|
||||
* @param a The first chunk position.
|
||||
* @param b The second chunk position.
|
||||
* @return true if equal, false otherwise.
|
||||
*/
|
||||
bool_t chunkPositionIsEqual(const chunkpos_t a, const chunkpos_t b);
|
||||
@@ -1,446 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "map.h"
|
||||
#include "util/memory.h"
|
||||
#include "assert/assert.h"
|
||||
#include "asset/asset.h"
|
||||
#include "asset/loader/assetloader.h"
|
||||
#include "console/console.h"
|
||||
#include "event/event.h"
|
||||
#include "util/string.h"
|
||||
#include "rpg/entity/global/entityglobal.h"
|
||||
|
||||
map_t MAP;
|
||||
|
||||
errorret_t mapInit() {
|
||||
memoryZero(&MAP, sizeof(map_t));
|
||||
MAP.loaded = true;
|
||||
|
||||
chunkindex_t i = 0;
|
||||
for(chunkunit_t z = 0; z < MAP_CHUNK_DEPTH; z++) {
|
||||
for(chunkunit_t y = 0; y < MAP_CHUNK_HEIGHT; y++) {
|
||||
for(chunkunit_t x = 0; x < MAP_CHUNK_WIDTH; x++) {
|
||||
chunk_t *chunk = &MAP.chunks[i++];
|
||||
chunk->position = (chunkpos_t){
|
||||
(chunkunit_t)x, (chunkunit_t)y, (chunkunit_t)z
|
||||
};
|
||||
errorChain(mapChunkLoad(chunk));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mapRebuildChunkOrder();
|
||||
errorOk();
|
||||
}
|
||||
|
||||
bool_t mapIsLoaded() {
|
||||
return MAP.loaded;
|
||||
}
|
||||
|
||||
errorret_t mapPositionSet(const chunkpos_t newPos) {
|
||||
if(!mapIsLoaded()) errorThrow("No map loaded");
|
||||
if(chunkPositionIsEqual(newPos, MAP.chunkPosition)) errorOk();
|
||||
|
||||
// Separate loaded chunks into "keep" and "free" buckets.
|
||||
chunkindex_t chunksFreed[MAP_CHUNK_COUNT];
|
||||
uint32_t freedCount = 0;
|
||||
|
||||
// Use a boolean grid so the inner load loop can check O(1).
|
||||
bool_t posLoaded[MAP_CHUNK_WIDTH][MAP_CHUNK_HEIGHT][MAP_CHUNK_DEPTH];
|
||||
memoryZero(posLoaded, sizeof(posLoaded));
|
||||
|
||||
for(chunkindex_t i = 0; i < MAP_CHUNK_COUNT; i++) {
|
||||
chunk_t *chunk = &MAP.chunks[i];
|
||||
chunkunit_t rx = chunk->position.x - newPos.x;
|
||||
chunkunit_t ry = chunk->position.y - newPos.y;
|
||||
chunkunit_t rz = chunk->position.z - newPos.z;
|
||||
if(
|
||||
rx >= 0 && rx < MAP_CHUNK_WIDTH &&
|
||||
ry >= 0 && ry < MAP_CHUNK_HEIGHT &&
|
||||
rz >= 0 && rz < MAP_CHUNK_DEPTH
|
||||
) {
|
||||
posLoaded[rx][ry][rz] = true;
|
||||
} else {
|
||||
mapChunkUnload(chunk);
|
||||
chunksFreed[freedCount++] = i;
|
||||
}
|
||||
}
|
||||
|
||||
for(chunkunit_t z = 0; z < MAP_CHUNK_DEPTH; z++) {
|
||||
for(chunkunit_t y = 0; y < MAP_CHUNK_HEIGHT; y++) {
|
||||
for(chunkunit_t x = 0; x < MAP_CHUNK_WIDTH; x++) {
|
||||
if(posLoaded[x][y][z]) continue;
|
||||
assertTrue(freedCount > 0, "No free chunk slot available.");
|
||||
chunk_t *chunk = &MAP.chunks[chunksFreed[--freedCount]];
|
||||
chunk->position = (chunkpos_t){
|
||||
newPos.x + (chunkunit_t)x,
|
||||
newPos.y + (chunkunit_t)y,
|
||||
newPos.z + (chunkunit_t)z
|
||||
};
|
||||
errorChain(mapChunkLoad(chunk));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MAP.chunkPosition = newPos;
|
||||
mapRebuildChunkOrder();
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t mapUpdate() {
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t mapDispose() {
|
||||
for(chunkindex_t i = 0; i < MAP_CHUNK_COUNT; i++) {
|
||||
mapChunkUnload(&MAP.chunks[i]);
|
||||
}
|
||||
errorOk();
|
||||
}
|
||||
|
||||
void mapChunkUnload(chunk_t *chunk) {
|
||||
mapChunkLoadQueueRemove(chunk);
|
||||
if(MAP.loadingChunk == chunk) MAP.loadingChunk = NULL;
|
||||
|
||||
for(uint8_t i = 0; i < CHUNK_ENTITY_COUNT_MAX; i++) {
|
||||
if(chunk->entities[i] == 0xFF) continue;
|
||||
entity_t *entity = &ENTITIES[chunk->entities[i]];
|
||||
if(!entityCanUnload(entity)) {
|
||||
entitySetChunk(entity, 0xFF);
|
||||
} else {
|
||||
entity->type = ENTITY_TYPE_NULL;
|
||||
}
|
||||
}
|
||||
|
||||
memorySet(chunk->entities, 0xFF, sizeof(chunk->entities));
|
||||
|
||||
if(chunk->dcfEntry != NULL) {
|
||||
eventUnsubscribe(&chunk->dcfEntry->onLoaded, mapChunkLoaded);
|
||||
eventUnsubscribe(&chunk->dcfEntry->onError, mapChunkLoadError);
|
||||
assetUnlockEntry(chunk->dcfEntry);
|
||||
chunk->dcfEntry = NULL;
|
||||
}
|
||||
|
||||
// modelEntries are borrowed pointers, not independently locked - the
|
||||
// chunk asset entry (released above) is what actually holds the ref on
|
||||
// each model, so nothing to unlock here, just drop our own copies.
|
||||
for(uint8_t m = 0; m < chunk->meshCount; m++) {
|
||||
chunk->modelEntries[m] = NULL;
|
||||
}
|
||||
chunk->meshCount = 0;
|
||||
}
|
||||
|
||||
errorret_t mapChunkLoad(chunk_t *chunk) {
|
||||
if(!mapIsLoaded()) errorThrow("No map loaded");
|
||||
|
||||
mapChunkLoadQueueRemove(chunk);
|
||||
if(MAP.loadingChunk == chunk) MAP.loadingChunk = NULL;
|
||||
|
||||
if(chunk->dcfEntry != NULL) {
|
||||
eventUnsubscribe(&chunk->dcfEntry->onLoaded, mapChunkLoaded);
|
||||
eventUnsubscribe(&chunk->dcfEntry->onError, mapChunkLoadError);
|
||||
assetUnlockEntry(chunk->dcfEntry);
|
||||
chunk->dcfEntry = NULL;
|
||||
}
|
||||
|
||||
memorySet(chunk->entities, 0xFF, sizeof(chunk->entities));
|
||||
chunk->meshCount = 0;
|
||||
|
||||
char_t name[64];
|
||||
stringFormat(
|
||||
name, sizeof(name),
|
||||
"chunks/%d_%d_%d.dcf",
|
||||
(int32_t)chunk->position.x,
|
||||
(int32_t)chunk->position.y,
|
||||
(int32_t)chunk->position.z
|
||||
);
|
||||
|
||||
if(!assetFileExists(name)) {
|
||||
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();
|
||||
}
|
||||
|
||||
// Placeholder-fill with flat ground while the real data streams in
|
||||
// asynchronously - entities standing on this chunk (subject to gravity)
|
||||
// would otherwise fall through the zeroed/empty tiles until
|
||||
// mapChunkLoaded() replaces them with the real data.
|
||||
for(uint32_t i = 0; i < CHUNK_TILE_COUNT; i++) {
|
||||
chunk->tiles[i] = (tile_t){ .shape = TILE_SHAPE_GROUND };
|
||||
}
|
||||
|
||||
assertTrue(
|
||||
MAP.loadQueueCount < MAP_CHUNK_COUNT,
|
||||
"Chunk load queue overflow"
|
||||
);
|
||||
MAP.loadQueue[MAP.loadQueueCount++] = chunk;
|
||||
mapChunkLoadNext();
|
||||
errorOk();
|
||||
}
|
||||
|
||||
void mapChunkLoadNext() {
|
||||
if(MAP.loadingChunk != NULL) return;
|
||||
if(MAP.loadQueueCount == 0) return;
|
||||
|
||||
chunk_t *chunk = MAP.loadQueue[0];
|
||||
for(uint32_t i = 1; i < MAP.loadQueueCount; i++) {
|
||||
MAP.loadQueue[i - 1] = MAP.loadQueue[i];
|
||||
}
|
||||
MAP.loadQueueCount--;
|
||||
MAP.loadingChunk = chunk;
|
||||
|
||||
char_t name[64];
|
||||
stringFormat(
|
||||
name, sizeof(name),
|
||||
"chunks/%d_%d_%d.dcf",
|
||||
(int32_t)chunk->position.x,
|
||||
(int32_t)chunk->position.y,
|
||||
(int32_t)chunk->position.z
|
||||
);
|
||||
|
||||
assetentry_t *entry = assetLock(name, ASSET_LOADER_TYPE_CHUNK, NULL);
|
||||
assertNotNull(entry, "Failed to get chunk asset entry");
|
||||
chunk->dcfEntry = entry;
|
||||
|
||||
// The entry may already be resident from an earlier load that hasn't been
|
||||
// reaped yet - in that case onLoaded/onError already fired once and never
|
||||
// will again, so handle the terminal state directly instead of waiting on
|
||||
// a subscription that would never trigger.
|
||||
if(entry->state == ASSET_ENTRY_STATE_LOADED) {
|
||||
mapChunkLoaded(entry, chunk);
|
||||
return;
|
||||
}
|
||||
if(entry->state == ASSET_ENTRY_STATE_ERROR) {
|
||||
mapChunkLoadError(entry, chunk);
|
||||
return;
|
||||
}
|
||||
|
||||
eventSubscribe(&entry->onLoaded, mapChunkLoaded, chunk);
|
||||
eventSubscribe(&entry->onError, mapChunkLoadError, chunk);
|
||||
}
|
||||
|
||||
void mapChunkLoadQueueRemove(chunk_t *chunk) {
|
||||
for(uint32_t i = 0; i < MAP.loadQueueCount; i++) {
|
||||
if(MAP.loadQueue[i] != chunk) continue;
|
||||
for(uint32_t j = i + 1; j < MAP.loadQueueCount; j++) {
|
||||
MAP.loadQueue[j - 1] = MAP.loadQueue[j];
|
||||
}
|
||||
MAP.loadQueueCount--;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
chunkindex_t mapGetChunkIndexAt(const chunkpos_t position) {
|
||||
if(!mapIsLoaded()) return -1;
|
||||
|
||||
chunkpos_t relPos = {
|
||||
position.x - MAP.chunkPosition.x,
|
||||
position.y - MAP.chunkPosition.y,
|
||||
position.z - MAP.chunkPosition.z
|
||||
};
|
||||
|
||||
if(
|
||||
relPos.x < 0 || relPos.y < 0 || relPos.z < 0 ||
|
||||
relPos.x >= MAP_CHUNK_WIDTH ||
|
||||
relPos.y >= MAP_CHUNK_HEIGHT ||
|
||||
relPos.z >= MAP_CHUNK_DEPTH
|
||||
) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return chunkPosToIndex(&relPos);
|
||||
}
|
||||
|
||||
chunk_t *mapGetChunk(const uint8_t index) {
|
||||
if(index >= MAP_CHUNK_COUNT) return NULL;
|
||||
if(!mapIsLoaded()) return NULL;
|
||||
return MAP.chunkOrder[index];
|
||||
}
|
||||
|
||||
tile_t mapGetTile(const worldpos_t position) {
|
||||
if(!mapIsLoaded()) return TILE_NULL;
|
||||
|
||||
chunkpos_t chunkPos;
|
||||
worldPosToChunkPos(&position, &chunkPos);
|
||||
chunkindex_t chunkIndex = mapGetChunkIndexAt(chunkPos);
|
||||
if(chunkIndex == -1) return TILE_NULL;
|
||||
|
||||
chunk_t *chunk = mapGetChunk(chunkIndex);
|
||||
assertNotNull(chunk, "Chunk pointer cannot be NULL");
|
||||
chunktileindex_t tileIndex = worldPosToChunkTileIndex(&position);
|
||||
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() {
|
||||
memoryZero(MAP.chunkOrder, sizeof(MAP.chunkOrder));
|
||||
for(chunkindex_t i = 0; i < MAP_CHUNK_COUNT; i++) {
|
||||
chunk_t *chunk = &MAP.chunks[i];
|
||||
const chunkpos_t rel = {
|
||||
chunk->position.x - MAP.chunkPosition.x,
|
||||
chunk->position.y - MAP.chunkPosition.y,
|
||||
chunk->position.z - MAP.chunkPosition.z
|
||||
};
|
||||
if(
|
||||
rel.x < 0 || rel.x >= MAP_CHUNK_WIDTH ||
|
||||
rel.y < 0 || rel.y >= MAP_CHUNK_HEIGHT ||
|
||||
rel.z < 0 || rel.z >= MAP_CHUNK_DEPTH
|
||||
) continue;
|
||||
MAP.chunkOrder[chunkPosToIndex(&rel)] = chunk;
|
||||
}
|
||||
}
|
||||
|
||||
void mapChunkLoadError(void *params, void *user) {
|
||||
assertNotNull(params, "mapChunkLoadError: params cannot be NULL");
|
||||
assertNotNull(user, "mapChunkLoadError: user cannot be NULL");
|
||||
assetentry_t *entry = (assetentry_t *)params;
|
||||
chunk_t *chunk = (chunk_t *)user;
|
||||
if(chunk->dcfEntry != entry) return;
|
||||
consolePrint(
|
||||
"Chunk load error: %d %d %d",
|
||||
(int32_t)chunk->position.x,
|
||||
(int32_t)chunk->position.y,
|
||||
(int32_t)chunk->position.z
|
||||
);
|
||||
eventUnsubscribe(&entry->onLoaded, mapChunkLoaded);
|
||||
eventUnsubscribe(&entry->onError, mapChunkLoadError);
|
||||
assetUnlockEntry(chunk->dcfEntry);
|
||||
chunk->dcfEntry = NULL;
|
||||
memorySet(chunk->tiles, 0x00, sizeof(chunk->tiles));
|
||||
|
||||
if(MAP.loadingChunk == chunk) MAP.loadingChunk = NULL;
|
||||
mapChunkLoadNext();
|
||||
}
|
||||
|
||||
void mapChunkLoaded(void *params, void *user) {
|
||||
assertNotNull(params, "mapChunkLoaded: params cannot be NULL");
|
||||
assertNotNull(user, "mapChunkLoaded: user cannot be NULL");
|
||||
assetentry_t *entry = (assetentry_t *)params;
|
||||
chunk_t *chunk = (chunk_t *)user;
|
||||
if(chunk->dcfEntry != entry) return;
|
||||
// consolePrint(
|
||||
// "Chunk loaded: %d %d %d",
|
||||
// (int32_t)chunk->position.x,
|
||||
// (int32_t)chunk->position.y,
|
||||
// (int32_t)chunk->position.z
|
||||
// );
|
||||
uint8_t meshCount = entry->data.chunk.meshCount;
|
||||
memoryCopy(
|
||||
chunk->tiles,
|
||||
entry->data.chunk.tiles,
|
||||
sizeof(chunk->tiles)
|
||||
);
|
||||
worldpos_t wp;
|
||||
chunkPosToWorldPos(&chunk->position, &wp);
|
||||
vec3 wpf = {
|
||||
(float_t)wp.x, (float_t)wp.y, (float_t)wp.z * WORLD_LAYER_HEIGHT
|
||||
};
|
||||
for(uint8_t m = 0; m < meshCount; m++) {
|
||||
stringCopy(
|
||||
chunk->modelNames[m],
|
||||
entry->data.chunk.modelNames[m],
|
||||
CHUNK_MESH_NAME_MAX
|
||||
);
|
||||
glm_vec3_copy(
|
||||
entry->data.chunk.meshOffsets[m],
|
||||
chunk->meshOffsets[m]
|
||||
);
|
||||
vec3 scaledOffset = {
|
||||
chunk->meshOffsets[m][0],
|
||||
chunk->meshOffsets[m][1],
|
||||
chunk->meshOffsets[m][2] * WORLD_LAYER_HEIGHT
|
||||
};
|
||||
vec3 pos;
|
||||
glm_vec3_add(wpf, scaledOffset, pos);
|
||||
glm_translate_make(chunk->meshModels[m], pos);
|
||||
// Borrow the pointer rather than stealing it - the chunk asset entry
|
||||
// keeps its own lock on each model (taken once while it loaded) and we
|
||||
// keep the chunk asset entry itself locked (see below), so the models
|
||||
// stay valid for as long as this chunk_t is using them. The entry may
|
||||
// now be reused by a later mapChunkLoad for a different chunk_t once we
|
||||
// eventually unlock it in mapChunkUnload, at which point its
|
||||
// modelEntries must still be intact for that next reuse to copy from.
|
||||
chunk->modelEntries[m] = entry->data.chunk.modelEntries[m];
|
||||
}
|
||||
eventUnsubscribe(&entry->onLoaded, mapChunkLoaded);
|
||||
eventUnsubscribe(&entry->onError, mapChunkLoadError);
|
||||
// Deliberately keep chunk->dcfEntry locked and set - it is what keeps the
|
||||
// chunk asset entry (and therefore its model locks) alive for as long as
|
||||
// this chunk_t is displaying it. Released in mapChunkUnload instead.
|
||||
chunk->meshCount = meshCount;
|
||||
|
||||
if(MAP.loadingChunk == chunk) MAP.loadingChunk = NULL;
|
||||
mapChunkLoadNext();
|
||||
}
|
||||
@@ -1,177 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "error/error.h"
|
||||
#include "rpg/overworld/chunk.h"
|
||||
#include "rpg/entity/entity.h"
|
||||
|
||||
#define MAP_FILE_PATH_MAX 128
|
||||
|
||||
typedef struct map_s {
|
||||
bool_t loaded;
|
||||
|
||||
chunk_t chunks[MAP_CHUNK_COUNT];
|
||||
chunk_t *chunkOrder[MAP_CHUNK_COUNT];
|
||||
chunkpos_t chunkPosition;
|
||||
|
||||
// Only one chunk may be mid-load (asset locked & awaiting onLoaded/
|
||||
// onError) at any given time - everything else waits here in FIFO order.
|
||||
chunk_t *loadQueue[MAP_CHUNK_COUNT];
|
||||
uint32_t loadQueueCount;
|
||||
chunk_t *loadingChunk;
|
||||
} map_t;
|
||||
|
||||
extern map_t MAP;
|
||||
|
||||
/**
|
||||
* Initializes the map.
|
||||
*
|
||||
* @return An error code.
|
||||
*/
|
||||
errorret_t mapInit();
|
||||
|
||||
/**
|
||||
* Checks if a map is loaded.
|
||||
*
|
||||
* @return true if a map is loaded, false otherwise.
|
||||
*/
|
||||
bool_t mapIsLoaded();
|
||||
|
||||
/**
|
||||
* Updates the map.
|
||||
*
|
||||
* @return An error code.
|
||||
*/
|
||||
errorret_t mapUpdate();
|
||||
|
||||
/**
|
||||
* Disposes of the map.
|
||||
*
|
||||
* @return An error code.
|
||||
*/
|
||||
errorret_t mapDispose();
|
||||
|
||||
/**
|
||||
* Sets the map position and updates chunks accordingly.
|
||||
*
|
||||
* @param newPos The new chunk position.
|
||||
* @return An error code.
|
||||
*/
|
||||
errorret_t mapPositionSet(const chunkpos_t newPos);
|
||||
|
||||
/**
|
||||
* Unloads a chunk.
|
||||
*
|
||||
* @param chunk The chunk to unload.
|
||||
*/
|
||||
void mapChunkUnload(chunk_t* chunk);
|
||||
|
||||
/**
|
||||
* Loads a chunk. Starts async loading without blocking.
|
||||
*
|
||||
* @param chunk The chunk to load.
|
||||
* @return An error code.
|
||||
*/
|
||||
errorret_t mapChunkLoad(chunk_t* chunk);
|
||||
|
||||
/**
|
||||
* Starts loading the next queued chunk, if no chunk is currently mid-load.
|
||||
* Called after mapChunkLoad enqueues a chunk, and again after the
|
||||
* currently-loading chunk finishes (or is unloaded) to advance the queue.
|
||||
*/
|
||||
void mapChunkLoadNext();
|
||||
|
||||
/**
|
||||
* Removes a chunk from the load queue if present. Used when a chunk is
|
||||
* re-queued or unloaded before its turn to load has come up.
|
||||
*
|
||||
* @param chunk The chunk to remove from the load queue.
|
||||
*/
|
||||
void mapChunkLoadQueueRemove(chunk_t *chunk);
|
||||
|
||||
/**
|
||||
* Callback invoked when a chunk DCF asset fails to load. Fills the
|
||||
* chunk tiles with TILE_SHAPE_GROUND as a fallback.
|
||||
* Always invoked on the main thread.
|
||||
*
|
||||
* @param params The failed assetentry_t.
|
||||
* @param user The chunk_t that owns the entry.
|
||||
*/
|
||||
void mapChunkLoadError(void *params, void *user);
|
||||
|
||||
/**
|
||||
* Callback invoked when a chunk DCF asset finishes loading.
|
||||
* Always invoked on the main thread.
|
||||
*
|
||||
* @param params The loaded assetentry_t.
|
||||
* @param user The chunk_t that owns the entry.
|
||||
*/
|
||||
void mapChunkLoaded(void *params, void *user);
|
||||
|
||||
/**
|
||||
* Rebuilds chunkOrder from the loaded chunks that fall within the
|
||||
* current render window. Called whenever chunkPosition changes.
|
||||
*/
|
||||
void mapRebuildChunkOrder();
|
||||
|
||||
/**
|
||||
* Gets the index of a chunk, within the world, at the given position.
|
||||
*
|
||||
* @param position The chunk position.
|
||||
* @return The index of the chunk, or -1 if out of bounds.
|
||||
*/
|
||||
chunkindex_t mapGetChunkIndexAt(const chunkpos_t position);
|
||||
|
||||
/**
|
||||
* Gets a chunk by its index.
|
||||
*
|
||||
* @param chunkIndex The index of the chunk.
|
||||
* @return A pointer to the chunk.
|
||||
*/
|
||||
chunk_t * mapGetChunk(const uint8_t chunkIndex);
|
||||
|
||||
/**
|
||||
* Gets the tile at the given world position.
|
||||
*
|
||||
* @param position The world position.
|
||||
* @return The tile at that position, or TILE_NULL if the chunk is unloaded.
|
||||
*/
|
||||
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
|
||||
);
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user