Add inventory.
All checks were successful
Build Dusk / run-tests (push) Successful in 2m12s
Build Dusk / build-linux (push) Successful in 1m49s
Build Dusk / build-psp (push) Successful in 1m52s

This commit is contained in:
2026-01-06 07:47:16 -06:00
parent 024ace1078
commit af5bf987c8
91 changed files with 1108 additions and 139 deletions

View File

@@ -0,0 +1,14 @@
# 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
entityanim.c
npc.c
player.c
entitydir.c
)

200
archive/rpg/entity/entity.c Normal file
View File

@@ -0,0 +1,200 @@
/**
* 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 "time/time.h"
#include "util/math.h"
#include "rpg/cutscene/cutscenemode.h"
#include "rpg/overworld/map.h"
entity_t ENTITIES[ENTITY_COUNT];
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->type = type;
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");
// What state is the entity in?
if(entity->animation != ENTITY_ANIM_IDLE) {
// Entity is mid animation, tick it (down).
entity->animTime -= TIME.delta;
if(entity->animTime <= 0) {
entity->animation = ENTITY_ANIM_IDLE;
entity->animTime = 0;
}
return;
}
// Movement code.
if(
cutsceneModeIsInputAllowed() &&
ENTITY_CALLBACKS[entity->type].movement != NULL
) {
ENTITY_CALLBACKS[entity->type].movement(entity);
}
}
void entityTurn(entity_t *entity, const entitydir_t direction) {
entity->direction = direction;
entity->animation = ENTITY_ANIM_TURN;
entity->animTime = ENTITY_ANIM_TURN_DURATION;
}
void entityWalk(entity_t *entity, const entitydir_t direction) {
// TODO: Animation, delay, etc.
entity->direction = direction;
// Where are we moving?
worldpos_t newPos = entity->position;
{
worldunits_t relX, relY;
entityDirGetRelative(direction, &relX, &relY);
newPos.x += relX;
newPos.y += relY;
}
// Get tile under foot
tile_t tileCurrent = mapGetTile(entity->position);
tile_t tileNew = mapGetTile(newPos);
bool_t fall = false;
bool_t raise = false;
// Are we walking up a ramp?
if(
tileIsRamp(tileCurrent) &&
(
// Can only walk UP the direction the ramp faces.
(direction+TILE_SHAPE_RAMP_SOUTH) == tileCurrent ||
// If diagonal ramp, can go up one of two ways only.
(
(
tileCurrent == TILE_SHAPE_RAMP_SOUTHEAST &&
(direction == ENTITY_DIR_SOUTH || direction == ENTITY_DIR_EAST)
) ||
(
tileCurrent == TILE_SHAPE_RAMP_SOUTHWEST &&
(direction == ENTITY_DIR_SOUTH || direction == ENTITY_DIR_WEST)
) ||
(
tileCurrent == TILE_SHAPE_RAMP_NORTHEAST &&
(direction == ENTITY_DIR_NORTH || direction == ENTITY_DIR_EAST)
) ||
(
tileCurrent == TILE_SHAPE_RAMP_NORTHWEST &&
(direction == ENTITY_DIR_NORTH || direction == ENTITY_DIR_WEST)
)
)
// Must be able to walk up.
)
) {
tileNew = TILE_SHAPE_NULL;// Force check for ramp above.
worldpos_t abovePos = newPos;
abovePos.z += 1;
tile_t tileAbove = mapGetTile(abovePos);
if(tileAbove != TILE_SHAPE_NULL && tileIsWalkable(tileAbove)) {
// We can go up the ramp.
raise = true;
}
} else if(tileNew == TILE_SHAPE_NULL && newPos.z > 0) {
// Falling down?
worldpos_t belowPos = newPos;
belowPos.z -= 1;
tile_t tileBelow = mapGetTile(belowPos);
if(
tileBelow != TILE_SHAPE_NULL &&
tileIsRamp(tileBelow) &&
(
// This handles regular cardinal ramps
(entityDirGetOpposite(direction)+TILE_SHAPE_RAMP_SOUTH) == tileBelow ||
// This handles diagonal ramps
(
(
tileBelow == TILE_SHAPE_RAMP_SOUTHEAST &&
(direction == ENTITY_DIR_NORTH || direction == ENTITY_DIR_WEST)
) ||
(
tileBelow == TILE_SHAPE_RAMP_SOUTHWEST &&
(direction == ENTITY_DIR_NORTH || direction == ENTITY_DIR_EAST)
) ||
(
tileBelow == TILE_SHAPE_RAMP_NORTHEAST &&
(direction == ENTITY_DIR_SOUTH || direction == ENTITY_DIR_WEST)
) ||
(
tileBelow == TILE_SHAPE_RAMP_NORTHWEST &&
(direction == ENTITY_DIR_SOUTH || direction == ENTITY_DIR_EAST)
)
)
)
) {
// We will fall to this tile.
fall = true;
}
}
// Can we walk here?
if(!raise && !fall && !tileIsWalkable(tileNew)) return;// Blocked
// Entity in way?
entity_t *other = ENTITIES;
do {
if(other == entity) continue;
if(other->type == ENTITY_TYPE_NULL) continue;
if(!worldPosIsEqual(other->position, newPos)) continue;
return;// Blocked
} while(++other, other < &ENTITIES[ENTITY_COUNT]);
entity->lastPosition = entity->position;
entity->position = newPos;
entity->animation = ENTITY_ANIM_WALK;
entity->animTime = ENTITY_ANIM_WALK_DURATION;// TODO: Running vs walking
if(raise) {
entity->position.z += 1;
} else if(fall) {
entity->position.z -= 1;
}
}
entity_t * entityGetAt(const worldpos_t position) {
entity_t *ent = ENTITIES;
do {
if(ent->type == ENTITY_TYPE_NULL) continue;
if(!worldPosIsEqual(ent->position, position)) 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;
}

View File

@@ -0,0 +1,77 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "entitydir.h"
#include "entityanim.h"
#include "entitytype.h"
#include "npc.h"
typedef struct map_s map_t;
typedef struct entity_s {
uint8_t id;
entitytype_t type;
entitytypedata_t data;
// Movement
entitydir_t direction;
worldpos_t position;
worldpos_t lastPosition;
entityanim_t animation;
float_t animTime;
} entity_t;
extern entity_t ENTITIES[ENTITY_COUNT];
/**
* 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);
/**
* Turn an entity to face a new direction.
*
* @param entity Pointer to the entity to turn.
* @param direction The direction to face.
*/
void entityTurn(entity_t *entity, const entitydir_t direction);
/**
* Make an entity walk in a direction.
*
* @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);
/**
* Gets the entity at a specific world position.
*
* @param map Pointer to the map to check.
* @param pos The world position to check.
* @return Pointer to the entity at the position, or NULL if none.
*/
entity_t *entityGetAt(const worldpos_t pos);
/**
* Gets an available entity index.
*
* @return The index of an available entity, or 0xFF if none are available.
*/
uint8_t entityGetAvailable();

View File

@@ -0,0 +1,9 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "entityanim.h"

View File

@@ -0,0 +1,18 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "dusk.h"
#define ENTITY_ANIM_TURN_DURATION 0.06f
#define ENTITY_ANIM_WALK_DURATION 0.1f
typedef enum {
ENTITY_ANIM_IDLE,
ENTITY_ANIM_TURN,
ENTITY_ANIM_WALK,
} entityanim_t;

View File

@@ -0,0 +1,51 @@
/**
* 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;
}
}

View File

@@ -0,0 +1,50 @@
/**
* 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_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
);

View File

@@ -0,0 +1,55 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "duskdefs.h"
#include "rpg/entity/player.h"
#include "npc.h"
typedef uint8_t entitytype_t;
typedef union {
player_t player;
npc_t npc;
} 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.
* @param entity Pointer to the entity to move.
*/
void (*movement)(entity_t *entity);
/**
* Interaction callback for the entity type.
* @param player Pointer to the player entity.
* @param entity Pointer to the entity to interact with.
* @return True if the entity handled the interaction, false otherwise.
*/
bool_t (*interact)(entity_t *player, 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,
.interact = npcInteract
}
};

31
archive/rpg/entity/npc.c Normal file
View File

@@ -0,0 +1,31 @@
/**
* 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;
};

37
archive/rpg/entity/npc.h Normal file
View File

@@ -0,0 +1,37 @@
/**
* 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);

View File

@@ -0,0 +1,54 @@
/**
* 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"
void playerInit(entity_t *entity) {
assertNotNull(entity, "Entity pointer cannot be NULL");
}
void playerInput(entity_t *entity) {
assertNotNull(entity, "Entity pointer cannot be NULL");
// Turn
const playerinputdirmap_t *dirMap = PLAYER_INPUT_DIR_MAP;
do {
if(!inputIsDown(dirMap->action)) continue;
if(entity->direction == dirMap->direction) continue;
return entityTurn(entity, dirMap->direction);
} while((++dirMap)->action != 0xFF);
// Walk
dirMap = PLAYER_INPUT_DIR_MAP;
do {
if(!inputIsDown(dirMap->action)) continue;
if(entity->direction != dirMap->direction) continue;
return entityWalk(entity, dirMap->direction);
} while((++dirMap)->action != 0xFF);
// Interaction
if(inputPressed(INPUT_ACTION_ACCEPT)) {
worldunit_t x, y, z;
{
worldunits_t relX, relY;
entityDirGetRelative(entity->direction, &relX, &relY);
x = entity->position.x + relX;
y = entity->position.y + relY;
z = entity->position.z;
}
entity_t *interact = entityGetAt((worldpos_t){ x, y, z });
if(interact != NULL && ENTITY_CALLBACKS[interact->type].interact != NULL) {
if(ENTITY_CALLBACKS[interact->type].interact(entity, interact)) return;
}
}
}

View File

@@ -0,0 +1,43 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "input/input.h"
typedef struct entity_s entity_t;
typedef struct {
void *nothing;
} player_t;
typedef struct {
inputaction_t action;
entitydir_t direction;
} playerinputdirmap_t;
static const playerinputdirmap_t PLAYER_INPUT_DIR_MAP[] = {
{ INPUT_ACTION_UP, ENTITY_DIR_NORTH },
{ INPUT_ACTION_DOWN, ENTITY_DIR_SOUTH },
{ INPUT_ACTION_LEFT, ENTITY_DIR_WEST },
{ INPUT_ACTION_RIGHT, ENTITY_DIR_EAST },
{ 0xFF, 0xFF }
};
/**
* Initializes a player entity.
*
* @param entity Pointer to the entity structure to initialize.
*/
void playerInit(entity_t *entity);
/**
* Handles movement logic for the player entity.
*
* @param entity Pointer to the player entity structure.
*/
void playerInput(entity_t *entity);