NPC movements
This commit is contained in:
@@ -423,6 +423,29 @@ above the declaration with no blank line in between.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Color system
|
||||||
|
|
||||||
|
Colors are defined in `src/dusk/display/color.csv` and code-generated
|
||||||
|
into a `color.h` header by `tools/color/csv/__main__.py`.
|
||||||
|
|
||||||
|
Each row in the CSV has `name,r,g,b,a` with channel values in `[0.0, 1.0]`.
|
||||||
|
The script emits four `#define` variants per color plus a bare alias:
|
||||||
|
|
||||||
|
```
|
||||||
|
COLOR_<NAME>_4B color4b(r8, g8, b8, a8) // default alias target
|
||||||
|
COLOR_<NAME>_3B color3b(r8, g8, b8)
|
||||||
|
COLOR_<NAME>_3F color3f(rf, gf, bf)
|
||||||
|
COLOR_<NAME>_4F color4f(rf, gf, bf, af)
|
||||||
|
COLOR_<NAME> COLOR_<NAME>_4B
|
||||||
|
```
|
||||||
|
|
||||||
|
`color_t` is `color4b_t` (four `uint8_t` channels).
|
||||||
|
|
||||||
|
To add a new color, append a row to `color.csv` and rebuild — do not
|
||||||
|
hand-edit the generated header.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Tests
|
## Tests
|
||||||
- Tests live in `test/` mirroring `src/dusk/` structure.
|
- Tests live in `test/` mirroring `src/dusk/` structure.
|
||||||
- Use cmocka; include `dusktest.h`.
|
- Use cmocka; include `dusktest.h`.
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
|||||||
entityanim.c
|
entityanim.c
|
||||||
entitydir.c
|
entitydir.c
|
||||||
entityinteract.c
|
entityinteract.c
|
||||||
npc.c
|
|
||||||
player.c
|
player.c
|
||||||
)
|
)
|
||||||
|
|
||||||
|
add_subdirectory(npc)
|
||||||
@@ -41,6 +41,9 @@ void entityUpdate(entity_t *entity) {
|
|||||||
entityAnimUpdate(entity);
|
entityAnimUpdate(entity);
|
||||||
|
|
||||||
// Movement code.
|
// Movement code.
|
||||||
|
if(ENTITY_CALLBACKS[entity->type].freeMovement != NULL) {
|
||||||
|
ENTITY_CALLBACKS[entity->type].freeMovement(entity);
|
||||||
|
}
|
||||||
if(
|
if(
|
||||||
cutsceneModeIsInputAllowed() &&
|
cutsceneModeIsInputAllowed() &&
|
||||||
ENTITY_CALLBACKS[entity->type].movement != NULL
|
ENTITY_CALLBACKS[entity->type].movement != NULL
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
#include "entityanim.h"
|
#include "entityanim.h"
|
||||||
#include "entityinteract.h"
|
#include "entityinteract.h"
|
||||||
#include "entitytype.h"
|
#include "entitytype.h"
|
||||||
#include "npc.h"
|
#include "npc/npc.h"
|
||||||
|
|
||||||
typedef struct map_s map_t;
|
typedef struct map_s map_t;
|
||||||
|
|
||||||
|
|||||||
@@ -9,8 +9,8 @@
|
|||||||
#include "time/time.h"
|
#include "time/time.h"
|
||||||
|
|
||||||
#define ENTITY_ANIM_TURN_DURATION TIME_TICKS_TO_TIME(2)
|
#define ENTITY_ANIM_TURN_DURATION TIME_TICKS_TO_TIME(2)
|
||||||
#define ENTITY_ANIM_WALK_DURATION TIME_TICKS_TO_TIME(6)
|
#define ENTITY_ANIM_WALK_DURATION TIME_TICKS_TO_TIME(10)
|
||||||
#define ENTITY_ANIM_RUN_DURATION TIME_TICKS_TO_TIME(3)
|
#define ENTITY_ANIM_RUN_DURATION TIME_TICKS_TO_TIME(6)
|
||||||
|
|
||||||
typedef struct entity_s entity_t;
|
typedef struct entity_s entity_t;
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "rpg/entity/player.h"
|
#include "rpg/entity/player.h"
|
||||||
#include "npc.h"
|
#include "npc/npc.h"
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
ENTITY_TYPE_NULL,
|
ENTITY_TYPE_NULL,
|
||||||
@@ -33,11 +33,17 @@ typedef struct {
|
|||||||
void (*init)(entity_t *entity);
|
void (*init)(entity_t *entity);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Movement callback for the entity type.
|
* Movement callback for the entity type. Gated by cutscene input.
|
||||||
* @param entity Pointer to the entity to move.
|
* @param entity Pointer to the entity to move.
|
||||||
*/
|
*/
|
||||||
void (*movement)(entity_t *entity);
|
void (*movement)(entity_t *entity);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Free movement callback. Always runs regardless of cutscene state.
|
||||||
|
* @param entity Pointer to the entity to move.
|
||||||
|
*/
|
||||||
|
void (*freeMovement)(entity_t *entity);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interaction callback for the entity type.
|
* Interaction callback for the entity type.
|
||||||
* @param player Pointer to the player entity.
|
* @param player Pointer to the player entity.
|
||||||
@@ -58,6 +64,7 @@ static const entitycallback_t ENTITY_CALLBACKS[ENTITY_TYPE_COUNT] = {
|
|||||||
[ENTITY_TYPE_NPC] = {
|
[ENTITY_TYPE_NPC] = {
|
||||||
.init = npcInit,
|
.init = npcInit,
|
||||||
.movement = npcMovement,
|
.movement = npcMovement,
|
||||||
|
.freeMovement = npcFreeMovement,
|
||||||
.interact = npcInteract
|
.interact = npcInteract
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -1,31 +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/cutscene/scene/testcutscene.h"
|
|
||||||
#include "rpg/rpgtextbox.h"
|
|
||||||
|
|
||||||
void npcInit(entity_t *entity) {
|
|
||||||
assertNotNull(entity, "Entity pointer cannot be NULL");
|
|
||||||
}
|
|
||||||
|
|
||||||
void npcMovement(entity_t *entity) {
|
|
||||||
assertNotNull(entity, "Entity pointer cannot be NULL");
|
|
||||||
}
|
|
||||||
|
|
||||||
bool_t npcInteract(entity_t *player, entity_t *npc) {
|
|
||||||
assertNotNull(player, "Player entity pointer cannot be NULL");
|
|
||||||
assertNotNull(npc, "NPC entity pointer cannot be NULL");
|
|
||||||
|
|
||||||
cutsceneSystemStartCutscene(&TEST_CUTSCENE);
|
|
||||||
|
|
||||||
// rpgTextboxShow(RPG_TEXTBOX_POS_BOTTOM, "Hello World!");
|
|
||||||
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
@@ -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 entity_s entity_t;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
void *nothing;
|
|
||||||
} npc_t;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes an NPC entity.
|
|
||||||
*
|
|
||||||
* @param entity Pointer to the entity structure to initialize.
|
|
||||||
*/
|
|
||||||
void npcInit(entity_t *entity);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates an NPC entity.
|
|
||||||
*
|
|
||||||
* @param entity Pointer to the entity structure to update.
|
|
||||||
*/
|
|
||||||
void npcMovement(entity_t *entity);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles interaction with an NPC entity.
|
|
||||||
*
|
|
||||||
* @param player Pointer to the player entity.
|
|
||||||
* @param npc Pointer to the NPC entity.
|
|
||||||
*/
|
|
||||||
bool_t npcInteract(entity_t *player, entity_t *npc);
|
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
# 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
|
||||||
|
)
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
/**
|
||||||
|
* 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/scene/testcutscene.h"
|
||||||
|
#include "rpg/rpgtextbox.h"
|
||||||
|
|
||||||
|
const npcmovecallback_t NPC_MOVE_CALLBACKS[NPC_MOVE_TYPE_COUNT] = {
|
||||||
|
[NPC_MOVE_TYPE_NULL] = { NULL, NULL },
|
||||||
|
[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, true },
|
||||||
|
};
|
||||||
|
|
||||||
|
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");
|
||||||
|
npc_t *npc = &entity->data.npc;
|
||||||
|
const npcmovecallback_t *cb = &NPC_MOVE_CALLBACKS[npc->moveType];
|
||||||
|
if(!cb->alwaysRun && cb->movement != NULL) cb->movement(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
void npcFreeMovement(entity_t *entity) {
|
||||||
|
assertNotNull(entity, "Entity pointer cannot be NULL");
|
||||||
|
npc_t *npc = &entity->data.npc;
|
||||||
|
const npcmovecallback_t *cb = &NPC_MOVE_CALLBACKS[npc->moveType];
|
||||||
|
if(cb->alwaysRun && cb->movement != NULL) cb->movement(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool_t npcInteract(entity_t *player, entity_t *npc) {
|
||||||
|
assertNotNull(player, "Player entity pointer cannot be NULL");
|
||||||
|
assertNotNull(npc, "NPC entity pointer cannot be NULL");
|
||||||
|
|
||||||
|
cutsceneSystemStartCutscene(&TEST_CUTSCENE);
|
||||||
|
// rpgTextboxShow(RPG_TEXTBOX_POS_BOTTOM, "Hello World!");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
/**
|
||||||
|
* 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_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 {
|
||||||
|
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);
|
||||||
|
/** True if movement runs regardless of cutscene state. */
|
||||||
|
bool_t alwaysRun;
|
||||||
|
} 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);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles interaction with an NPC entity.
|
||||||
|
*
|
||||||
|
* @param player Pointer to the player entity.
|
||||||
|
* @param npc Pointer to the NPC entity.
|
||||||
|
*/
|
||||||
|
bool_t npcInteract(entity_t *player, entity_t *npc);
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
/**
|
||||||
|
* 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"
|
||||||
|
|
||||||
|
void npcPathInit(npc_t *npc) {
|
||||||
|
npcpath_t *path = &npc->moveData.path;
|
||||||
|
path->count = 0;
|
||||||
|
path->index = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void npcPathMovement(entity_t *entity) {
|
||||||
|
npcpath_t *path = &entity->data.npc.moveData.path;
|
||||||
|
if(path->count == 0) return;
|
||||||
|
if(!entityCanWalk(entity)) return;
|
||||||
|
|
||||||
|
worldpos_t *target = &path->positions[path->index];
|
||||||
|
|
||||||
|
// At this waypoint - advance to the next
|
||||||
|
if(
|
||||||
|
entity->position.x == target->x &&
|
||||||
|
entity->position.y == target->y
|
||||||
|
) {
|
||||||
|
path->index = (path->index + 1) % path->count;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
entitydir_t dir;
|
||||||
|
if(entity->position.x != target->x) {
|
||||||
|
dir = entity->position.x < target->x ? ENTITY_DIR_EAST : ENTITY_DIR_WEST;
|
||||||
|
} else {
|
||||||
|
dir = entity->position.y < target->y ? ENTITY_DIR_SOUTH : ENTITY_DIR_NORTH;
|
||||||
|
}
|
||||||
|
|
||||||
|
entityWalk(entity, dir);
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "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);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Movement tick for an NPC following a path.
|
||||||
|
*
|
||||||
|
* @param entity Pointer to the entity to update.
|
||||||
|
*/
|
||||||
|
void npcPathMovement(entity_t *entity);
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
if(entityCanTurn(entity)) entityTurn(entity, (entitydir_t)(rand() % 4));
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "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);
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
void npcRandomWalkMovement(entity_t *entity) {
|
||||||
|
npcrandomwalk_t *walk = &entity->data.npc.moveData.randomWalk;
|
||||||
|
walk->timer -= TIME.delta;
|
||||||
|
if(walk->timer > 0.0f) return;
|
||||||
|
walk->timer = randomFloat(walk->frequencyMin, walk->frequencyMax);
|
||||||
|
if(entityCanWalk(entity)) entityWalk(entity, (entitydir_t)(rand() % 4));
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
void npcRandomTurnAndWalkMovement(entity_t *entity) {
|
||||||
|
npcrandomturnandwalk_t *tw = &entity->data.npc.moveData.randomTurnAndWalk;
|
||||||
|
|
||||||
|
tw->turn.timer -= TIME.delta;
|
||||||
|
if(tw->turn.timer <= 0.0f) {
|
||||||
|
tw->turn.timer = randomFloat(tw->turn.frequencyMin, tw->turn.frequencyMax);
|
||||||
|
if(entityCanTurn(entity)) 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);
|
||||||
|
if(entityCanWalk(entity)) entityWalk(entity, (entitydir_t)(rand() % 4));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
/**
|
||||||
|
* 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"
|
||||||
|
|
||||||
|
/** 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
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
float_t frequencyMin;
|
||||||
|
float_t frequencyMax;
|
||||||
|
float_t timer;
|
||||||
|
} 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);
|
||||||
|
|
||||||
@@ -41,10 +41,18 @@ errorret_t rpgInit(void) {
|
|||||||
uint8_t npcIndex = entityGetAvailable();
|
uint8_t npcIndex = entityGetAvailable();
|
||||||
entity_t *npc = &ENTITIES[npcIndex];
|
entity_t *npc = &ENTITIES[npcIndex];
|
||||||
entityInit(npc, ENTITY_TYPE_NPC);
|
entityInit(npc, ENTITY_TYPE_NPC);
|
||||||
|
npcSetMoveType(npc, NPC_MOVE_TYPE_PATH);
|
||||||
npc->position = (worldpos_t){ 3, 3, 0 };
|
npc->position = (worldpos_t){ 3, 3, 0 };
|
||||||
npc->interact.type = ENTITY_INTERACT_PRINT;
|
npc->interact.type = ENTITY_INTERACT_PRINT;
|
||||||
npc->interact.data.message = "hello world";
|
npc->interact.data.message = "hello world";
|
||||||
|
|
||||||
|
npcpath_t *path = &npc->data.npc.moveData.path;
|
||||||
|
path->positions[0] = (worldpos_t){ 3, 3, 0 };
|
||||||
|
path->positions[1] = (worldpos_t){ 5, 3, 0 };
|
||||||
|
path->positions[2] = (worldpos_t){ 5, 5, 0 };
|
||||||
|
path->positions[3] = (worldpos_t){ 3, 5, 0 };
|
||||||
|
path->count = 4;
|
||||||
|
|
||||||
// All Good!
|
// All Good!
|
||||||
errorOk();
|
errorOk();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -109,35 +109,30 @@ errorret_t sceneOverworldRender(scenedata_t *sceneData) {
|
|||||||
|
|
||||||
// Entities
|
// Entities
|
||||||
{
|
{
|
||||||
uint8_t spriteCount = 0;
|
|
||||||
spritebatchsprite_t sprites[ENTITY_COUNT];
|
|
||||||
for(uint8_t i = 0; i < ENTITY_COUNT; i++) {
|
for(uint8_t i = 0; i < ENTITY_COUNT; i++) {
|
||||||
entity_t *ent = &ENTITIES[i];
|
entity_t *ent = &ENTITIES[i];
|
||||||
if(ent->type == ENTITY_TYPE_NULL) continue;
|
if(ent->type == ENTITY_TYPE_NULL) continue;
|
||||||
|
|
||||||
glm_vec3_copy(ent->renderPosition, sprites[spriteCount].min);
|
spritebatchsprite_t sprite;
|
||||||
glm_vec3_copy(ent->renderPosition, sprites[spriteCount].max);
|
glm_vec3_copy(ent->renderPosition, sprite.min);
|
||||||
glm_vec3_add(
|
glm_vec3_copy(ent->renderPosition, sprite.max);
|
||||||
sprites[spriteCount].max,
|
glm_vec3_add(sprite.max, (vec3){ 1, 1, 0 }, sprite.max);
|
||||||
(vec3){ 1, 1, 0 },
|
glm_vec2_copy((vec2){ 0, 0 }, sprite.uvMin);
|
||||||
sprites[spriteCount].max
|
glm_vec2_copy((vec2){ 1, 1 }, sprite.uvMax);
|
||||||
);
|
|
||||||
|
|
||||||
glm_vec2_copy((vec2){ 0, 0 }, sprites[spriteCount].uvMin);
|
color_t color;
|
||||||
glm_vec2_copy((vec2){ 1, 1 }, sprites[spriteCount].uvMax);
|
switch(ent->direction) {
|
||||||
|
case ENTITY_DIR_NORTH: color = COLOR_YELLOW; break;
|
||||||
|
case ENTITY_DIR_EAST: color = COLOR_RED; break;
|
||||||
|
case ENTITY_DIR_SOUTH: color = COLOR_GREEN; break;
|
||||||
|
case ENTITY_DIR_WEST: color = COLOR_BLUE; break;
|
||||||
|
default: color = COLOR_CYAN; break;
|
||||||
|
}
|
||||||
|
|
||||||
spriteCount++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(spriteCount) {
|
|
||||||
shadermaterial_t material = {
|
shadermaterial_t material = {
|
||||||
.unlit = {
|
.unlit = { .color = color, .texture = NULL }
|
||||||
.color = COLOR_CYAN,
|
|
||||||
.texture = NULL
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
// material.unlit.texture = &TEXTURE_TEST;
|
spriteBatchBuffer(&sprite, 1, &SHADER_UNLIT, material);
|
||||||
spriteBatchBuffer(sprites, spriteCount, &SHADER_UNLIT, material);
|
|
||||||
spriteBatchFlush();
|
spriteBatchFlush();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
|||||||
crypt.c
|
crypt.c
|
||||||
endian.c
|
endian.c
|
||||||
memory.c
|
memory.c
|
||||||
|
random.c
|
||||||
string.c
|
string.c
|
||||||
math.c
|
math.c
|
||||||
sort.c
|
sort.c
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "random.h"
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
float_t randomFloat(const float_t min, const float_t max) {
|
||||||
|
return min + ((float_t)rand() / (float_t)RAND_MAX) * (max - min);
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "dusk.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a random float between min (inclusive) and max (exclusive).
|
||||||
|
*
|
||||||
|
* @param min Lower bound.
|
||||||
|
* @param max Upper bound.
|
||||||
|
* @returns A random float in [min, max).
|
||||||
|
*/
|
||||||
|
float_t randomFloat(const float_t min, const float_t max);
|
||||||
Reference in New Issue
Block a user