Path finding (first pass)
This commit is contained in:
@@ -41,6 +41,32 @@ typedef struct cutscene_s {
|
||||
#define CUTSCENE_CALLBACK(CALLBACK) \
|
||||
{ .type = CUTSCENE_ITEM_TYPE_CALLBACK, .callback = CALLBACK }
|
||||
|
||||
// Single-destination walk: entity walks directly to (X, Y, Z).
|
||||
// walkAround=true: tries perpendicular directions when blocked by an entity.
|
||||
#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 \
|
||||
} \
|
||||
}
|
||||
|
||||
// Multi-waypoint walk: generates a named positions array then references it.
|
||||
#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_TELEPORT(ENTITY_INDEX, X, Y, Z) \
|
||||
{ \
|
||||
.type = CUTSCENE_ITEM_TYPE_ENTITY_TELEPORT, \
|
||||
@@ -64,3 +90,6 @@ typedef struct cutscene_s {
|
||||
|
||||
#define CUTSCENE_FADE_FROM_WHITE(DURATION) \
|
||||
CUTSCENE_FADE(COLOR_WHITE, COLOR_TRANSPARENT_WHITE, DURATION, EASING_LINEAR)
|
||||
|
||||
#define CUTSCENE_SET_PAUSE(FLAGS) \
|
||||
{ .type = CUTSCENE_ITEM_TYPE_SET_PAUSE, .setPause = (FLAGS) }
|
||||
|
||||
@@ -10,11 +10,15 @@
|
||||
|
||||
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))
|
||||
#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 \
|
||||
))
|
||||
|
||||
@@ -16,6 +16,7 @@ void cutsceneSystemInit() {
|
||||
|
||||
void cutsceneSystemStartCutscene(const cutscene_t *cutscene) {
|
||||
CUTSCENE_SYSTEM.scene = cutscene;
|
||||
CUTSCENE_SYSTEM.pause = cutscene->pause;
|
||||
CUTSCENE_SYSTEM.currentItem = 0xFF;// Set to 0xFF so start wraps.
|
||||
cutsceneSystemNext();
|
||||
}
|
||||
@@ -38,6 +39,7 @@ void cutsceneSystemNext() {
|
||||
) {
|
||||
CUTSCENE_SYSTEM.scene = NULL;
|
||||
CUTSCENE_SYSTEM.currentItem = 0xFF;
|
||||
CUTSCENE_SYSTEM.pause = CUTSCENE_PAUSE_NONE;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -56,4 +58,5 @@ const cutsceneitem_t * cutsceneSystemGetCurrentItem() {
|
||||
void cutsceneSystemDispose() {
|
||||
CUTSCENE_SYSTEM.scene = NULL;
|
||||
CUTSCENE_SYSTEM.currentItem = 0xFF;
|
||||
CUTSCENE_SYSTEM.pause = CUTSCENE_PAUSE_NONE;
|
||||
}
|
||||
@@ -11,6 +11,7 @@
|
||||
typedef struct {
|
||||
const cutscene_t *scene;
|
||||
uint8_t currentItem;
|
||||
cutscenepause_t pause;
|
||||
|
||||
// Data (used by the current item).
|
||||
cutsceneitemdata_t data;
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "time/time.h"
|
||||
#include "ui/rpg/uitextboxmain.h"
|
||||
#include "rpg/entity/entity.h"
|
||||
#include "rpg/entity/entitypathstep.h"
|
||||
|
||||
void cutsceneItemStart(const cutsceneitem_t *item, cutsceneitemdata_t *data) {
|
||||
switch(item->type) {
|
||||
@@ -37,6 +38,10 @@ void cutsceneItemStart(const cutsceneitem_t *item, cutsceneitemdata_t *data) {
|
||||
);
|
||||
break;
|
||||
|
||||
case CUTSCENE_ITEM_TYPE_ENTITY_WALK_TO:
|
||||
data->entityWalkTo.currentIndex = 0;
|
||||
break;
|
||||
|
||||
case CUTSCENE_ITEM_TYPE_FADE:
|
||||
uiFullboxTransition(
|
||||
&UI_FULLBOX_OVER,
|
||||
@@ -47,6 +52,10 @@ void cutsceneItemStart(const cutsceneitem_t *item, cutsceneitemdata_t *data) {
|
||||
);
|
||||
break;
|
||||
|
||||
case CUTSCENE_ITEM_TYPE_SET_PAUSE:
|
||||
CUTSCENE_SYSTEM.pause = item->setPause;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -68,6 +77,19 @@ void cutsceneItemUpdate(const cutsceneitem_t *item, cutsceneitemdata_t *data) {
|
||||
cutsceneSystemNext();
|
||||
break;
|
||||
|
||||
case CUTSCENE_ITEM_TYPE_ENTITY_WALK_TO: {
|
||||
uint8_t i = data->entityWalkTo.currentIndex;
|
||||
entity_t *e = &ENTITIES[item->entityWalkTo.entityIndex];
|
||||
if(!entityPathStep(e, item->entityWalkTo.positions[i], item->entityWalkTo.walkAround)) return;
|
||||
i++;
|
||||
if(i < item->entityWalkTo.count) {
|
||||
data->entityWalkTo.currentIndex = i;
|
||||
return;
|
||||
}
|
||||
cutsceneSystemNext();
|
||||
break;
|
||||
}
|
||||
|
||||
case CUTSCENE_ITEM_TYPE_FADE:
|
||||
if(
|
||||
UI_FULLBOX_OVER.duration > 0.0f &&
|
||||
@@ -76,6 +98,10 @@ void cutsceneItemUpdate(const cutsceneitem_t *item, cutsceneitemdata_t *data) {
|
||||
cutsceneSystemNext();
|
||||
break;
|
||||
|
||||
case CUTSCENE_ITEM_TYPE_SET_PAUSE:
|
||||
cutsceneSystemNext();
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "cutscenecallback.h"
|
||||
#include "cutscenetext.h"
|
||||
#include "cutscenefade.h"
|
||||
#include "rpg/cutscene/cutscenepause.h"
|
||||
#include "rpg/overworld/worldpos.h"
|
||||
|
||||
typedef struct cutscene_s cutscene_t;
|
||||
@@ -21,7 +22,9 @@ typedef enum {
|
||||
CUTSCENE_ITEM_TYPE_WAIT,
|
||||
CUTSCENE_ITEM_TYPE_CUTSCENE,
|
||||
CUTSCENE_ITEM_TYPE_ENTITY_TELEPORT,
|
||||
CUTSCENE_ITEM_TYPE_FADE
|
||||
CUTSCENE_ITEM_TYPE_ENTITY_WALK_TO,
|
||||
CUTSCENE_ITEM_TYPE_FADE,
|
||||
CUTSCENE_ITEM_TYPE_SET_PAUSE
|
||||
} cutsceneitemtype_t;
|
||||
|
||||
typedef struct cutsceneitem_s {
|
||||
@@ -37,12 +40,24 @@ typedef struct cutsceneitem_s {
|
||||
uint8_t entityIndex;
|
||||
worldpos_t target;
|
||||
} entityTeleport;
|
||||
struct {
|
||||
uint8_t entityIndex;
|
||||
const worldpos_t *positions;
|
||||
uint8_t count;
|
||||
bool_t walkAround;
|
||||
} entityWalkTo;
|
||||
cutscenefade_t fade;
|
||||
cutscenepause_t setPause;
|
||||
};
|
||||
} cutsceneitem_t;
|
||||
|
||||
typedef struct {
|
||||
uint8_t currentIndex;
|
||||
} cutsceneentitywalktodata_t;
|
||||
|
||||
typedef union {
|
||||
cutscenewaitdata_t wait;
|
||||
cutsceneentitywalktodata_t entityWalkTo;
|
||||
} cutsceneitemdata_t;
|
||||
|
||||
/**
|
||||
|
||||
@@ -15,8 +15,8 @@ CUTSCENE(TEST_ONE, DEFAULT,
|
||||
|
||||
CUTSCENE(TEST_TWO, DEFAULT,
|
||||
CUTSCENE_TEXT("Test Two."),
|
||||
CUTSCENE_FADE_TO_BLACK(1.0f),
|
||||
CUTSCENE_ENTITY_TELEPORT(0, 20, 0, 0),
|
||||
CUTSCENE_WAIT(1.0f),
|
||||
CUTSCENE_FADE_FROM_BLACK(1.0f),
|
||||
CUTSCENE_SET_PAUSE(CUTSCENE_PAUSE_NONE),
|
||||
CUTSCENE_WAIT(5.0f),
|
||||
CUTSCENE_TEXT("Hello world")
|
||||
// CUTSCENE_ENTITY_WALK_TO(0, 0, 0, 0),
|
||||
);
|
||||
@@ -8,6 +8,7 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||
PUBLIC
|
||||
entity.c
|
||||
entitydir.c
|
||||
entitypathstep.c
|
||||
player.c
|
||||
)
|
||||
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "entitypathstep.h"
|
||||
#include "entitydir.h"
|
||||
#include "assert/assert.h"
|
||||
|
||||
bool_t entityPathStep(
|
||||
entity_t *entity,
|
||||
const worldpos_t target,
|
||||
bool_t walkAround
|
||||
) {
|
||||
assertNotNull(entity, "Entity must not be NULL");
|
||||
assertTrue(
|
||||
entity->type != ENTITY_TYPE_NULL,
|
||||
"Cannot path step a NULL entity type"
|
||||
);
|
||||
assertTrue(
|
||||
entity >= ENTITIES && entity < ENTITIES + ENTITY_COUNT,
|
||||
"Entity pointer is out of bounds"
|
||||
);
|
||||
|
||||
if(worldPosIsEqual(entity->position, target)) return true;
|
||||
if(!entityCanWalk(entity)) return false;
|
||||
|
||||
entitydir_t dir;
|
||||
if(entity->position.x != target.x) {
|
||||
dir = entity->position.x < target.x ? ENTITY_DIR_EAST : ENTITY_DIR_WEST;
|
||||
} else if(entity->position.y != target.y) {
|
||||
dir = entity->position.y < target.y ? ENTITY_DIR_NORTH : ENTITY_DIR_SOUTH;
|
||||
} else {
|
||||
dir = entity->position.z < target.z ? ENTITY_DIR_NORTH : ENTITY_DIR_SOUTH;
|
||||
}
|
||||
|
||||
entityWalk(entity, dir);
|
||||
|
||||
if(walkAround && entity->animation == ENTITY_ANIM_IDLE) {
|
||||
// Primary direction blocked - try perpendicular axes.
|
||||
entitydir_t alt, altOpp;
|
||||
if(dir == ENTITY_DIR_EAST || dir == ENTITY_DIR_WEST) {
|
||||
alt = entity->position.y <= target.y
|
||||
? ENTITY_DIR_NORTH : ENTITY_DIR_SOUTH;
|
||||
} else {
|
||||
alt = entity->position.x <= target.x
|
||||
? ENTITY_DIR_EAST : ENTITY_DIR_WEST;
|
||||
}
|
||||
altOpp = entityDirGetOpposite(alt);
|
||||
entityWalk(entity, alt);
|
||||
if(entity->animation == ENTITY_ANIM_IDLE) entityWalk(entity, altOpp);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "entity.h"
|
||||
|
||||
/**
|
||||
* Attempts to walk the entity one tile toward target. Prefers resolving X
|
||||
* first, then Y, then Z. Does nothing if the entity cannot currently walk.
|
||||
* When walkAround is true and the preferred direction is blocked by an entity,
|
||||
* tries perpendicular directions to navigate around it.
|
||||
*
|
||||
* @param entity Pointer to the entity to move.
|
||||
* @param target The world position to move toward.
|
||||
* @param walkAround Whether to try perpendicular directions when blocked.
|
||||
* @returns true if the entity is already at target, false otherwise.
|
||||
*/
|
||||
bool_t entityPathStep(
|
||||
entity_t *entity,
|
||||
const worldpos_t target,
|
||||
bool_t walkAround
|
||||
);
|
||||
@@ -50,10 +50,7 @@ void npcSetMoveType(entity_t *entity, const npcmovetype_t moveType) {
|
||||
|
||||
void npcMovement(entity_t *entity) {
|
||||
assertNotNull(entity, "Entity pointer cannot be NULL");
|
||||
if(
|
||||
CUTSCENE_SYSTEM.scene != NULL &&
|
||||
(CUTSCENE_SYSTEM.scene->pause & CUTSCENE_PAUSE_NPC)
|
||||
) return;
|
||||
if(CUTSCENE_SYSTEM.pause & CUTSCENE_PAUSE_NPC) return;
|
||||
|
||||
npc_t *npc = &entity->data.npc;
|
||||
if(npc->interactState != NPC_INTERACT_STATE_NONE) return;
|
||||
|
||||
@@ -7,7 +7,9 @@
|
||||
|
||||
#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;
|
||||
@@ -15,6 +17,15 @@ void npcPathInit(npc_t *npc) {
|
||||
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;
|
||||
@@ -22,23 +33,11 @@ void npcPathMovement(entity_t *entity) {
|
||||
|
||||
// Advance past any waypoints already reached (including the current one).
|
||||
worldpos_t *target = &path->positions[path->index];
|
||||
if(worldPosIsEqual(entity->position, *target)) {
|
||||
if(entityPathStep(entity, *target, false)) {
|
||||
path->index = (path->index + 1) % path->count;
|
||||
target = &path->positions[path->index];
|
||||
// New target is the same tile - nothing to do this tick
|
||||
if(worldPosIsEqual(entity->position, *target)) return;
|
||||
entityPathStep(entity, *target, false);
|
||||
}
|
||||
|
||||
entitydir_t dir;
|
||||
worldpos_t pos = entity->position;
|
||||
if(pos.x != target->x) {
|
||||
dir = pos.x < target->x ? ENTITY_DIR_EAST : ENTITY_DIR_WEST;
|
||||
} else if(pos.y != target->y) {
|
||||
dir = pos.y < target->y ? ENTITY_DIR_NORTH : ENTITY_DIR_SOUTH;
|
||||
} else {
|
||||
// x and y match but z differs - step to correct z via ramp logic
|
||||
dir = pos.z < target->z ? ENTITY_DIR_NORTH : ENTITY_DIR_SOUTH;
|
||||
}
|
||||
|
||||
entityWalk(entity, dir);
|
||||
}
|
||||
|
||||
@@ -28,6 +28,14 @@ typedef struct {
|
||||
*/
|
||||
void npcPathInit(npc_t *npc);
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
|
||||
@@ -24,10 +24,7 @@ bool_t playerCanInteract(entity_t *entity) {
|
||||
|
||||
void playerInput(entity_t *entity) {
|
||||
assertNotNull(entity, "Entity pointer cannot be NULL");
|
||||
if(
|
||||
CUTSCENE_SYSTEM.scene != NULL &&
|
||||
(CUTSCENE_SYSTEM.scene->pause & CUTSCENE_PAUSE_PLAYER)
|
||||
) return;
|
||||
if(CUTSCENE_SYSTEM.pause & CUTSCENE_PAUSE_PLAYER) return;
|
||||
|
||||
// Toggle settings on pause
|
||||
if(uiSettingsIsOpen() && inputPressed(INPUT_ACTION_PAUSE)) {
|
||||
|
||||
+5
-6
@@ -7,6 +7,7 @@
|
||||
|
||||
#include "rpg.h"
|
||||
#include "entity/entity.h"
|
||||
#include "rpg/entity/npc/npcpath.h"
|
||||
#include "rpg/overworld/map.h"
|
||||
#include "rpg/cutscene/cutscenesystem.h"
|
||||
#include "rpg/cutscene/scene/testcutscene.h"
|
||||
@@ -57,12 +58,10 @@ errorret_t rpgInit(void) {
|
||||
if(ci != -1) entitySetChunk(npc, (uint8_t)ci);
|
||||
}
|
||||
|
||||
// npcpath_t *path = &npc->data.npc.moveData.path;
|
||||
// path->positions[0] = (worldpos_t){ 3, 3, 0 };
|
||||
// path->positions[1] = (worldpos_t){ 10, 3, 0 };
|
||||
// path->positions[2] = (worldpos_t){ 10, 10, 0 };
|
||||
// path->positions[3] = (worldpos_t){ 3, 10, 0 };
|
||||
// path->count = 4;
|
||||
npcPathAddNode(&npc->data.npc, (worldpos_t){ 4, 4, 0 });
|
||||
npcPathAddNode(&npc->data.npc, (worldpos_t){ 10, 10, 1 });
|
||||
npcPathAddNode(&npc->data.npc, (worldpos_t){ 4, 4, 0 });
|
||||
npcPathAddNode(&npc->data.npc, (worldpos_t){ 10, 10, 1 });
|
||||
|
||||
// All Good!
|
||||
errorOk();
|
||||
|
||||
Reference in New Issue
Block a user