Overworld render test.
This commit is contained in:
@@ -12,14 +12,15 @@
|
||||
#include "display/scene/scenemanager.h"
|
||||
#include "display/mesh/quad.h"
|
||||
#include "asset/assetmanager.h"
|
||||
#include "assert/assert.h"
|
||||
|
||||
camera_t SCENE_OVERWORLD_CAMERA;
|
||||
sceneoverworld_t SCENE_OVERWORLD;
|
||||
asset_t *testAsset;
|
||||
ref_t testAssetRef;
|
||||
|
||||
void sceneOverworldInit(void) {
|
||||
cameraInit(&SCENE_OVERWORLD_CAMERA);
|
||||
glm_vec3_copy((vec3){32.0f, 32.0f, 32.0f}, SCENE_OVERWORLD_CAMERA.lookat.position);
|
||||
cameraInit(&SCENE_OVERWORLD.camera);
|
||||
glm_vec3_copy((vec3){ 0.0f, 1.0f, 0.0f }, SCENE_OVERWORLD.camera.lookat.up);
|
||||
|
||||
scene_t *scene = &SCENE_MANAGER_SCENES[SCENE_TYPE_OVERWORLD];
|
||||
scene->flags |= SCENE_FLAG_ACTIVE | SCENE_FLAG_VISIBLE;
|
||||
@@ -31,24 +32,59 @@ void sceneOverworldUpdate(void) {
|
||||
}
|
||||
|
||||
void sceneOverworldRender(void) {
|
||||
cameraPushMatrix(&SCENE_OVERWORLD_CAMERA);
|
||||
const float_t camOffset = 12.0f;
|
||||
const float_t fbWidth = frameBufferGetWidth(FRAMEBUFFER_BOUND);
|
||||
const float_t fbHeight = frameBufferGetHeight(FRAMEBUFFER_BOUND);
|
||||
const float_t aspect = fbWidth / fbHeight;
|
||||
const float_t pixelPerfectOffset = tanf(
|
||||
(glm_rad(180) - SCENE_OVERWORLD.camera.perspective.fov) / 2.0f
|
||||
) * (fbHeight/ 2.0f);
|
||||
|
||||
glm_vec3_copy((vec3){
|
||||
-100.0f, -100.0f, 0.0f
|
||||
}, SCENE_OVERWORLD.camera.lookat.target);
|
||||
glm_vec3_copy((vec3){
|
||||
SCENE_OVERWORLD.camera.lookat.target[0],
|
||||
SCENE_OVERWORLD.camera.lookat.target[1] + camOffset,
|
||||
SCENE_OVERWORLD.camera.lookat.target[2] + pixelPerfectOffset
|
||||
}, SCENE_OVERWORLD.camera.lookat.position);
|
||||
|
||||
cameraPushMatrix(&SCENE_OVERWORLD.camera);
|
||||
|
||||
sceneOverworldRenderMap(&testMap);
|
||||
|
||||
spriteBatchFlush();
|
||||
cameraPopMatrix();
|
||||
}
|
||||
|
||||
void sceneOverworldRenderMap(const map_t *map) {
|
||||
assertNotNull(map, "Map pointer cannot be NULL");
|
||||
|
||||
// Draw base layer
|
||||
|
||||
// Draw entities
|
||||
entity_t *start = &map->entities[0];
|
||||
entity_t *end = &map->entities[map->entityCount];
|
||||
while(start < end) {
|
||||
// Render entity here.
|
||||
sceneOverworldRenderEntity(start);
|
||||
start++;
|
||||
}
|
||||
|
||||
// Draw overlay layer.
|
||||
// renderTextDraw(0.0f, 0.0f, "Hello World", 0xFF, 0xFF, 0xFF);
|
||||
}
|
||||
|
||||
// spriteBatchPush(
|
||||
// &testAsset->paletteImage.texture,
|
||||
// 0.0f, 0.0f, 12.0f, 12.0f,
|
||||
// 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
// 0.0f, 0.0f, 1.0f, 1.0f
|
||||
// );
|
||||
spriteBatchFlush();
|
||||
void sceneOverworldRenderEntity(const 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");
|
||||
|
||||
cameraPopMatrix();
|
||||
// For now, just draw a placeholder quad.
|
||||
spriteBatchPush(
|
||||
&testAsset->paletteImage.texture,
|
||||
entity->x, entity->y, entity->x + 32.0f, entity->y + 32.0f,
|
||||
COLOR_WHITE, 0.0f, 0.0f, 0.125f, 0.125f
|
||||
);
|
||||
}
|
||||
|
||||
void sceneOverworldDispose(void) {
|
||||
|
@@ -7,8 +7,13 @@
|
||||
|
||||
#pragma once
|
||||
#include "display/camera.h"
|
||||
#include "rpg/world/map.h"
|
||||
|
||||
extern camera_t SCENE_OVERWORLD_CAMERA;
|
||||
typedef struct {
|
||||
camera_t camera;
|
||||
} sceneoverworld_t;
|
||||
|
||||
extern sceneoverworld_t SCENE_OVERWORLD;
|
||||
|
||||
/**
|
||||
* Initialize the overworld scene.
|
||||
@@ -25,6 +30,20 @@ void sceneOverworldUpdate(void);
|
||||
*/
|
||||
void sceneOverworldRender(void);
|
||||
|
||||
/**
|
||||
* Render a map in the overworld scene.
|
||||
*
|
||||
* @param map Pointer to the map to render.
|
||||
*/
|
||||
void sceneOverworldRenderMap(const map_t *map);
|
||||
|
||||
/**
|
||||
* Render an entity in the overworld scene.
|
||||
*
|
||||
* @param entity Pointer to the entity to render.
|
||||
*/
|
||||
void sceneOverworldRenderEntity(const entity_t *entity);
|
||||
|
||||
/**
|
||||
* Dispose of the overworld scene.
|
||||
*/
|
||||
|
@@ -41,6 +41,8 @@ errorret_t engineUpdate(void) {
|
||||
inputUpdate();
|
||||
consoleUpdate();
|
||||
assetManagerUpdate();
|
||||
|
||||
rpgUpdate();
|
||||
errorChain(displayUpdate());
|
||||
|
||||
errorOk();
|
||||
|
@@ -108,6 +108,13 @@ bool_t inputReleased(const inputaction_t action) {
|
||||
return !inputIsDown(action) && inputWasDown(action);
|
||||
}
|
||||
|
||||
float_t inputAxis(const inputaction_t neg, const inputaction_t pos) {
|
||||
assertTrue(neg < INPUT_ACTION_COUNT, "Negative input action out of bounds");
|
||||
assertTrue(pos < INPUT_ACTION_COUNT, "Positive input action out of bounds");
|
||||
|
||||
return inputGetCurrentValue(pos) - inputGetCurrentValue(neg);
|
||||
}
|
||||
|
||||
void inputBind(const inputbutton_t button, const char_t *action) {
|
||||
assertNotNull(action, "Input action is null");
|
||||
assertStrLenMin(action, 1, "Input action is empty");
|
||||
|
@@ -80,6 +80,16 @@ bool_t inputPressed(const inputaction_t action);
|
||||
*/
|
||||
bool_t inputReleased(const inputaction_t action);
|
||||
|
||||
/**
|
||||
* Gets the value of an input axis, defined by two actions (negative and
|
||||
* positive).
|
||||
*
|
||||
* @param neg The action representing the negative direction of the axis.
|
||||
* @param pos The action representing the positive direction of the axis.
|
||||
* @return The current value of the axis (-1.0f to 1.0f).
|
||||
*/
|
||||
float_t inputAxis(const inputaction_t neg, const inputaction_t pos);
|
||||
|
||||
/**
|
||||
* Binds an input button to an action. Will first check if a matching action
|
||||
* exists, otherwise it will be treated as a command.
|
||||
@@ -87,4 +97,4 @@ bool_t inputReleased(const inputaction_t action);
|
||||
* @param button The input button to bind.
|
||||
* @param action The name of the input action or command to bind the button to.
|
||||
*/
|
||||
void inputBind(const inputbutton_t data, const char_t *action);
|
||||
void inputBind(const inputbutton_t data, const char_t *action);
|
||||
|
@@ -11,5 +11,4 @@ target_sources(${DUSK_TARGET_NAME}
|
||||
|
||||
# Subdirs
|
||||
add_subdirectory(entity)
|
||||
add_subdirectory(item)
|
||||
add_subdirectory(world)
|
@@ -6,8 +6,6 @@
|
||||
# Sources
|
||||
target_sources(${DUSK_TARGET_NAME}
|
||||
PRIVATE
|
||||
direction.c
|
||||
entity.c
|
||||
player.c
|
||||
npc.c
|
||||
)
|
@@ -1,53 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "direction.h"
|
||||
#include "assert/assert.h"
|
||||
|
||||
float_t directionToAngle(const direction_t dir) {
|
||||
switch(dir) {
|
||||
case DIRECTION_NORTH: return (M_PI_2);
|
||||
case DIRECTION_SOUTH: return -(M_PI_2);
|
||||
case DIRECTION_EAST: return 0;
|
||||
case DIRECTION_WEST: return (M_PI);
|
||||
default: return 0; // Should never happen
|
||||
}
|
||||
}
|
||||
|
||||
void directionGetCoordinates(
|
||||
const direction_t dir,
|
||||
int8_t *x, int8_t *y
|
||||
) {
|
||||
assertNotNull(x, "X coordinate pointer cannot be NULL");
|
||||
assertNotNull(y, "Y coordinate pointer cannot be NULL");
|
||||
|
||||
switch(dir) {
|
||||
case DIRECTION_NORTH:
|
||||
*x = 0;
|
||||
*y = -1;
|
||||
break;
|
||||
|
||||
case DIRECTION_SOUTH:
|
||||
*x = 0;
|
||||
*y = 1;
|
||||
break;
|
||||
|
||||
case DIRECTION_EAST:
|
||||
*x = 1;
|
||||
*y = 0;
|
||||
break;
|
||||
|
||||
case DIRECTION_WEST:
|
||||
*x = -1;
|
||||
*y = 0;
|
||||
break;
|
||||
|
||||
default:
|
||||
assertUnreachable("Invalid direction");
|
||||
break;
|
||||
}
|
||||
}
|
@@ -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 enum {
|
||||
DIRECTION_SOUTH = 0,
|
||||
DIRECTION_EAST = 1,
|
||||
DIRECTION_WEST = 2,
|
||||
DIRECTION_NORTH = 3,
|
||||
|
||||
DIRECTION_UP = DIRECTION_NORTH,
|
||||
DIRECTION_DOWN = DIRECTION_SOUTH,
|
||||
DIRECTION_LEFT = DIRECTION_WEST,
|
||||
DIRECTION_RIGHT = DIRECTION_EAST,
|
||||
} direction_t;
|
||||
|
||||
/**
|
||||
* Converts a direction to an angle in float_t format.
|
||||
*
|
||||
* @param dir The direction to convert.
|
||||
* @return The angle corresponding to the direction.
|
||||
*/
|
||||
float_t directionToAngle(const direction_t dir);
|
||||
|
||||
/**
|
||||
* Gets the relative coordinates for a given direction.
|
||||
*
|
||||
* @param dir The direction to get coordinates for.
|
||||
* @param x Pointer to store the x coordinate.
|
||||
* @param y Pointer to store the y coordinate.
|
||||
*/
|
||||
void directionGetCoordinates(
|
||||
const direction_t dir,
|
||||
int8_t *x, int8_t *y
|
||||
);
|
@@ -8,116 +8,30 @@
|
||||
#include "entity.h"
|
||||
#include "assert/assert.h"
|
||||
#include "util/memory.h"
|
||||
// #include "world/world.h"
|
||||
// #include "world/tiledata.h"
|
||||
#include "time.h"
|
||||
|
||||
entity_t ENTITIES[ENTITY_COUNT_MAX] = {0};
|
||||
entitycallback_t ENTITY_CALLBACKS[ENTITY_TYPE_COUNT] = {
|
||||
{ NULL, NULL }, // ENTITY_TYPE_NULL
|
||||
{ playerInit, playerUpdate }, // ENTITY_TYPE_PLAYER
|
||||
{ NULL, NULL }, // ENTITY_TYPE_NPC
|
||||
};
|
||||
|
||||
// entitycallback_t ENTITY_CALLBACKS[ENTITY_TYPE_COUNT] = {
|
||||
// {NULL}, // ENTITY_TYPE_NULL
|
||||
// {
|
||||
// .load = playerEntityLoad,
|
||||
// .update = playerEntityUpdate,
|
||||
// },
|
||||
// {
|
||||
// .load = npcLoad,
|
||||
// .update = npcUpdate,
|
||||
// .interact = npcInteract,
|
||||
// },
|
||||
// };
|
||||
|
||||
void entityInit(entity_t *entity) {
|
||||
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");
|
||||
assertNotNull(ENTITY_CALLBACKS[type].init, "Entity type has no init function");
|
||||
|
||||
memoryZero(entity, sizeof(entity_t));
|
||||
entity->type = type;
|
||||
|
||||
// entity->type = ENTITY_TYPE_NULL;
|
||||
// entity->x = 0;
|
||||
// entity->y = 0;
|
||||
// entity->dir = DIRECTION_SOUTH;
|
||||
// entity->id = 0;
|
||||
|
||||
// ENTITY_CALLBACKS[entity->type].load(entity, source);
|
||||
ENTITY_CALLBACKS[type].init(entity);
|
||||
}
|
||||
|
||||
void entityUpdate(entity_t *entity) {
|
||||
assertNotNull(entity, "Entity pointer cannot be NULL");
|
||||
assertTrue(entity->type != ENTITY_TYPE_NULL, "Entity type NULL");
|
||||
assertTrue(entity->type < ENTITY_TYPE_COUNT, "Entity type out of bounds");
|
||||
// assertNotNull(
|
||||
// ENTITY_CALLBACKS[entity->type].update,
|
||||
// "Entity type has no update callback"
|
||||
// );
|
||||
assertTrue(entity->type < ENTITY_TYPE_COUNT, "Invalid entity type");
|
||||
assertTrue(entity->type != ENTITY_TYPE_NULL, "Cannot have NULL entity type");
|
||||
assertNotNull(ENTITY_CALLBACKS[entity->type].update, "enttype lacks update");
|
||||
|
||||
// ENTITY_CALLBACKS[entity->type].update(entity);
|
||||
|
||||
if(entity->subX > 0) {
|
||||
entity->subX -= entity->moveSpeed;
|
||||
} else if(entity->subX < 0) {
|
||||
entity->subX += entity->moveSpeed;
|
||||
}
|
||||
|
||||
if(entity->subY > 0) {
|
||||
entity->subY -= entity->moveSpeed;
|
||||
} else if(entity->subY < 0) {
|
||||
entity->subY += entity->moveSpeed;
|
||||
}
|
||||
}
|
||||
|
||||
void entityMove(entity_t *entity, const uint8_t moveSpeed) {
|
||||
assertNotNull(entity, "Entity pointer cannot be NULL");
|
||||
assertTrue(entity->type != ENTITY_TYPE_NULL, "Entity type NULL");
|
||||
assertTrue(entity->type < ENTITY_TYPE_COUNT, "Entity type out of bounds");
|
||||
assertFalse(
|
||||
entityIsMoving(entity),
|
||||
"Entity is already moving, cannot move again"
|
||||
);
|
||||
|
||||
int8_t x = 0, y = 0;
|
||||
directionGetCoordinates(entity->dir, &x, &y);
|
||||
|
||||
// entity in way?
|
||||
entity_t *ent = entityGetAt(entity->x + x, entity->y + y);
|
||||
if(ent != NULL) return;
|
||||
|
||||
entity->x += x;
|
||||
entity->y += y;
|
||||
// entity->subX = TILE_WIDTH_HEIGHT * -x;
|
||||
// entity->subY = TILE_WIDTH_HEIGHT * -y;
|
||||
entity->moveSpeed = moveSpeed;
|
||||
}
|
||||
|
||||
void entityTurn(entity_t *entity, const direction_t dir) {
|
||||
assertNotNull(entity, "Entity pointer cannot be NULL");
|
||||
assertTrue(entity->type != ENTITY_TYPE_NULL, "Entity type NULL");
|
||||
assertTrue(entity->type < ENTITY_TYPE_COUNT, "Entity type out of bounds");
|
||||
assertTrue(
|
||||
dir >= DIRECTION_SOUTH && dir <= DIRECTION_NORTH, "Invalid direction"
|
||||
);
|
||||
assertFalse(
|
||||
entityIsMoving(entity), "Entity is already moving, cannot turn"
|
||||
);
|
||||
|
||||
entity->dir = dir;
|
||||
}
|
||||
|
||||
bool_t entityIsMoving(const entity_t *entity) {
|
||||
assertNotNull(entity, "Entity pointer cannot be NULL");
|
||||
assertTrue(entity->type != ENTITY_TYPE_NULL, "Entity type NULL");
|
||||
assertTrue(entity->type < ENTITY_TYPE_COUNT, "Entity type out of bounds");
|
||||
return entity->subX != 0 || entity->subY != 0;
|
||||
}
|
||||
|
||||
entity_t * entityGetAt(
|
||||
const int32_t tileX,
|
||||
const int32_t tileY
|
||||
) {
|
||||
entity_t *entity = ENTITIES;
|
||||
|
||||
do {
|
||||
if(entity->type == ENTITY_TYPE_NULL) continue;
|
||||
if(entity->x == tileX && entity->y == tileY) return entity;
|
||||
} while((entity++) < &ENTITIES[ENTITY_COUNT_MAX - 1]);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
ENTITY_CALLBACKS[entity->type].update(entity);
|
||||
}
|
@@ -6,94 +6,51 @@
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "direction.h"
|
||||
// #include "direction.h"
|
||||
#include "rpg/entity/player.h"
|
||||
#include "npc.h"
|
||||
// #include "npc.h"
|
||||
|
||||
#define ENTITY_COUNT_MAX 32
|
||||
#define ENTITY_TURN_DURATION 0.075f // Duration for turning in seconds
|
||||
#define ENTITY_MOVE_DURATION 0.1f // Duration for moving 1 tile, in seconds.
|
||||
#define ENTITY_PLAYER_INDEX 0
|
||||
// #define ENTITY_TURN_DURATION 0.075f // Duration for turning in seconds
|
||||
// #define ENTITY_MOVE_DURATION 0.1f // Duration for moving 1 tile, in seconds.
|
||||
// #define ENTITY_PLAYER_INDEX 0
|
||||
|
||||
typedef struct {
|
||||
void (*init)(entity_t *entity);
|
||||
void (*update)(entity_t *entity);
|
||||
} entitycallback_t;
|
||||
|
||||
typedef enum {
|
||||
ENTITY_TYPE_NULL = 0,
|
||||
ENTITY_TYPE_PLAYER = 1,
|
||||
ENTITY_TYPE_NPC = 2,
|
||||
ENTITY_TYPE_NULL,
|
||||
ENTITY_TYPE_PLAYER,
|
||||
ENTITY_TYPE_NPC,
|
||||
|
||||
ENTITY_TYPE_COUNT
|
||||
} entitytype_t;
|
||||
#define ENTITY_TYPE_COUNT 3
|
||||
|
||||
typedef struct _entity_t {
|
||||
uint32_t id;// Completely unique ID for this entity.
|
||||
int32_t x, y;
|
||||
int8_t subX, subY;
|
||||
uint8_t moveSpeed;
|
||||
|
||||
typedef struct entity_s {
|
||||
// uint32_t id;// Completely unique ID for this entity.
|
||||
float_t x, y;
|
||||
entitytype_t type;
|
||||
direction_t dir;
|
||||
// direction_t dir;
|
||||
|
||||
union {
|
||||
npc_t npc;
|
||||
playerentity_t player;
|
||||
player_t player;
|
||||
};
|
||||
} entity_t;
|
||||
|
||||
// typedef struct {
|
||||
// void (*load) (entity_t *entity, const entity_t *source);
|
||||
// void (*update) (entity_t *entity);
|
||||
// void (*interact)(entity_t *player, entity_t *self);
|
||||
// } entitycallback_t;
|
||||
|
||||
extern entity_t ENTITIES[ENTITY_COUNT_MAX];
|
||||
// extern entitycallback_t ENTITY_CALLBACKS[ENTITY_TYPE_COUNT];
|
||||
extern entitycallback_t ENTITY_CALLBACKS[ENTITY_TYPE_COUNT];
|
||||
|
||||
/**
|
||||
* Loads an entity from the generated entity data.
|
||||
* Initializes an entity structure.
|
||||
*
|
||||
* @param entity Pointer to the entity to initialize.
|
||||
* @param source Pointer to the source entity data.
|
||||
* @param entity Pointer to the entity structure to initialize.
|
||||
* @param type The type of the entity.
|
||||
*/
|
||||
void entityInit(entity_t *entity);
|
||||
void entityInit(entity_t *entity, const entitytype_t type);
|
||||
|
||||
/**
|
||||
* Updates the entity's state.
|
||||
* Updates an entity.
|
||||
*
|
||||
* @param entity Pointer to the entity to update.
|
||||
* @param entity Pointer to the entity structure to update.
|
||||
*/
|
||||
void entityUpdate(entity_t *entity);
|
||||
|
||||
/**
|
||||
* Moves the entity by the specified x and y offsets.
|
||||
*
|
||||
* @param entity Pointer to the entity to move.
|
||||
* @param moveSpeed The speed at which to move the entity.
|
||||
*/
|
||||
void entityMove(entity_t *entity, const uint8_t moveSpeed);
|
||||
|
||||
/**
|
||||
* Turns the entity to face the specified direction.
|
||||
*
|
||||
* @param entity Pointer to the entity to turn.
|
||||
* @param dir The direction to turn the entity to.
|
||||
*/
|
||||
void entityTurn(entity_t *entity, const direction_t dir);
|
||||
|
||||
/**
|
||||
* Returns whether or not an entity is currently moving.
|
||||
*
|
||||
* @param entity Pointer to the entity to check.
|
||||
* @return True if the entity is moving, false otherwise.
|
||||
*/
|
||||
bool_t entityIsMoving(const entity_t *entity);
|
||||
|
||||
/**
|
||||
* Gets the entity at the specified tile coordinates.
|
||||
*
|
||||
* @param tileX The x coordinate of the tile to get the entity from.
|
||||
* @param tileY The y coordinate of the tile to get the entity from.
|
||||
* @return Pointer to the entity at the specified coordinates, or NULL if no
|
||||
* entity exists there.
|
||||
*/
|
||||
entity_t *entityGetAt(
|
||||
const int32_t tileX,
|
||||
const int32_t tileY
|
||||
);
|
||||
void entityUpdate(entity_t *entity);
|
@@ -1,45 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "entity.h"
|
||||
// #include "ui/uitextbox.h"
|
||||
// #include "locale/language.h"
|
||||
#include "assert/assert.h"
|
||||
|
||||
void npcLoad(entity_t *entity, const entity_t *source) {
|
||||
assertNotNull(entity, "Entity pointer cannot be NULL");
|
||||
assertNotNull(source, "Source entity pointer cannot be NULL");
|
||||
assertTrue(source->type == ENTITY_TYPE_NPC, "Source entity type must be NPC");
|
||||
|
||||
entity->npc = source->npc;
|
||||
}
|
||||
|
||||
void npcUpdate(entity_t *entity) {
|
||||
}
|
||||
|
||||
void npcInteract(entity_t *player, entity_t *self) {
|
||||
assertTrue(self->type == ENTITY_TYPE_NPC, "Entity must be of type NPC");
|
||||
|
||||
switch(self->npc.interactType) {
|
||||
case NPC_INTERACT_TYPE_NONE:
|
||||
break;
|
||||
|
||||
case NPC_INTERACT_TYPE_TEXT:
|
||||
// uiTextboxSetText(languageGet(self->npc.text));
|
||||
break;
|
||||
|
||||
case NPC_INTERACT_TYPE_CONVO:
|
||||
break;
|
||||
|
||||
case NPC_INTERACT_TYPE_EVENT:
|
||||
// eventSetActive(self->npc.eventData);
|
||||
break;
|
||||
|
||||
default:
|
||||
assertUnreachable("Unknown NPC interaction type");
|
||||
}
|
||||
}
|
@@ -1,44 +0,0 @@
|
||||
|
||||
#pragma once
|
||||
#include "dusk.h"
|
||||
|
||||
typedef struct _entity_t entity_t;
|
||||
|
||||
typedef enum {
|
||||
NPC_INTERACT_TYPE_NONE = 0,
|
||||
NPC_INTERACT_TYPE_TEXT = 1,
|
||||
NPC_INTERACT_TYPE_CONVO = 2,
|
||||
NPC_INTERACT_TYPE_EVENT = 3,
|
||||
} npcinteracttype_t;
|
||||
|
||||
typedef struct {
|
||||
npcinteracttype_t interactType;
|
||||
|
||||
union {
|
||||
const char_t* text;
|
||||
// const eventdata_t *eventData;
|
||||
};
|
||||
} npc_t;
|
||||
|
||||
/**
|
||||
* Initializes the NPC entity.
|
||||
*
|
||||
* @param entity The entity to initialize.
|
||||
* @param source The source entity to copy data from.
|
||||
*/
|
||||
void npcLoad(entity_t *entity, const entity_t *source);
|
||||
|
||||
/**
|
||||
* Updates the NPC entity.
|
||||
*
|
||||
* @param entity The entity to update.
|
||||
*/
|
||||
void npcUpdate(entity_t *entity);
|
||||
|
||||
/**
|
||||
* Handles interaction between the player and the NPC.
|
||||
*
|
||||
* @param player The player entity interacting with the NPC.
|
||||
* @param self The NPC entity being interacted with.
|
||||
*/
|
||||
void npcInteract(entity_t *player, entity_t *self);
|
@@ -7,81 +7,23 @@
|
||||
|
||||
#include "entity.h"
|
||||
#include "assert/assert.h"
|
||||
#include "time/time.h"
|
||||
#include "input/input.h"
|
||||
// #include "display/render.h"
|
||||
// #include "world/world.h"
|
||||
|
||||
// #include "ui/uitextbox.h"
|
||||
|
||||
inventory_t PLAYER_INVENTORY;
|
||||
|
||||
void playerInit() {
|
||||
entity_t *ent = &ENTITIES[ENTITY_PLAYER_INDEX];
|
||||
entityInit(ent);
|
||||
inventoryInit(&PLAYER_INVENTORY, INVENTORY_SIZE_MAX);
|
||||
void playerInit(entity_t *entity) {
|
||||
assertNotNull(entity, "Entity pointer cannot be NULL");
|
||||
}
|
||||
|
||||
void playerEntityLoad(entity_t *entity, const entity_t *source) {
|
||||
void playerUpdate(entity_t *entity) {
|
||||
assertNotNull(entity, "Entity pointer cannot be NULL");
|
||||
assertNotNull(source, "Source entity pointer cannot be NULL");
|
||||
assertTrue(entity->type == ENTITY_TYPE_PLAYER, "Entity type must be PLAYER");
|
||||
assertTrue(source->type == entity->type, "Source/Entity type mismatch");
|
||||
}
|
||||
|
||||
void playerEntityUpdate(entity_t *entity) {
|
||||
assertNotNull(entity, "Entity pointer cannot be NULL");
|
||||
assertTrue(entity->type == ENTITY_TYPE_PLAYER, "Entity type must be PLAYER");
|
||||
|
||||
// TODO: make this just a method somewhere.
|
||||
// if(UI_TEXTBOX.visible) return;
|
||||
if(entityIsMoving(entity)) return;
|
||||
|
||||
// const uint8_t moveSpeed = inputIsDown(INPUT_BIND_CANCEL) ? PLAYER_SPEED_RUN : PLAYER_SPEED_WALK;
|
||||
const uint8_t moveSpeed = PLAYER_SPEED_WALK;
|
||||
|
||||
// if(inputIsDown(INPUT_BIND_UP)) {
|
||||
// if(entity->dir != DIRECTION_NORTH) {
|
||||
// entityTurn(entity, DIRECTION_NORTH);
|
||||
// return;
|
||||
// }
|
||||
// entityMove(entity, moveSpeed);
|
||||
// return;
|
||||
|
||||
// } else if(inputIsDown(INPUT_BIND_DOWN)) {
|
||||
// if(entity->dir != DIRECTION_SOUTH) {
|
||||
// entityTurn(entity, DIRECTION_SOUTH);
|
||||
// return;
|
||||
// }
|
||||
|
||||
// entityMove(entity, moveSpeed);
|
||||
// return;
|
||||
// } else if(inputIsDown(INPUT_BIND_LEFT)) {
|
||||
// if(entity->dir != DIRECTION_WEST) {
|
||||
// entityTurn(entity, DIRECTION_WEST);
|
||||
// return;
|
||||
// }
|
||||
// entityMove(entity, moveSpeed);
|
||||
// return;
|
||||
|
||||
// } else if(inputIsDown(INPUT_BIND_RIGHT)) {
|
||||
// if(entity->dir != DIRECTION_EAST) {
|
||||
// entityTurn(entity, DIRECTION_EAST);
|
||||
// return;
|
||||
// }
|
||||
|
||||
// entityMove(entity, moveSpeed);
|
||||
// return;
|
||||
// }
|
||||
|
||||
// Interact
|
||||
// if(inputPressed(INPUT_BIND_ACTION)) {
|
||||
// int8_t x, y;
|
||||
// directionGetCoordinates(entity->dir, &x, &y);
|
||||
// entity_t *ent = entityGetAt(entity->x + x, entity->y + y);
|
||||
|
||||
// // if(ent != NULL && ENTITY_CALLBACKS[ent->type].interact != NULL) {
|
||||
// // assertTrue(ent->type < ENTITY_TYPE_COUNT, "Entity type out of bounds");
|
||||
// // ENTITY_CALLBACKS[ent->type].interact(entity, ent);
|
||||
// // }
|
||||
// }
|
||||
// testing only
|
||||
float_t move = TIME.delta * 128.0f; // tiles per second
|
||||
vec2 dir = {
|
||||
inputAxis(INPUT_ACTION_LEFT, INPUT_ACTION_RIGHT),
|
||||
inputAxis(INPUT_ACTION_UP, INPUT_ACTION_DOWN)
|
||||
};
|
||||
glm_vec2_normalize(dir);
|
||||
entity->x += move * dir[0];
|
||||
entity->y -= move * dir[1];
|
||||
}
|
@@ -7,37 +7,23 @@
|
||||
|
||||
#pragma once
|
||||
#include "dusk.h"
|
||||
#include "rpg/item/inventory.h"
|
||||
|
||||
#define PLAYER_SPEED_WALK 1
|
||||
#define PLAYER_SPEED_RUN 2
|
||||
|
||||
typedef struct _entity_t entity_t;
|
||||
typedef struct entity_s entity_t;
|
||||
|
||||
typedef struct {
|
||||
uint32_t nothing;
|
||||
} playerentity_t;
|
||||
|
||||
#define PLAYER_ENTITY_ID (UINT32_MAX-1)
|
||||
|
||||
extern inventory_t PLAYER_INVENTORY;
|
||||
void *nothing;
|
||||
} player_t;
|
||||
|
||||
/**
|
||||
* Initializes the player and all player-related entities.
|
||||
*/
|
||||
void playerInit(void);
|
||||
|
||||
/**
|
||||
* Loads the player entity.
|
||||
* Initializes a player entity.
|
||||
*
|
||||
* @param entity The entity to initialize.
|
||||
* @param source The source entity to copy data from.
|
||||
* @param entity Pointer to the entity structure to initialize.
|
||||
*/
|
||||
void playerEntityLoad(entity_t *entity, const entity_t *source);
|
||||
void playerInit(entity_t *entity);
|
||||
|
||||
/**
|
||||
* Updates the player entity.
|
||||
* Updates a player entity.
|
||||
*
|
||||
* @param entity The entity to update.
|
||||
* @param entity Pointer to the entity structure to update.
|
||||
*/
|
||||
void playerEntityUpdate(entity_t *entity);
|
||||
void playerUpdate(entity_t *entity);
|
@@ -1,10 +0,0 @@
|
||||
# Copyright (c) 2025 Dominic Masters
|
||||
#
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
# Sources
|
||||
target_sources(${DUSK_TARGET_NAME}
|
||||
PRIVATE
|
||||
inventory.c
|
||||
)
|
@@ -1,68 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "inventory.h"
|
||||
#include "util/memory.h"
|
||||
#include "assert/assert.h"
|
||||
|
||||
void inventoryInit(inventory_t *inventory, const uint8_t size) {
|
||||
assertNotNull(inventory, "inventory must not be NULL");
|
||||
assertTrue(size <= INVENTORY_SIZE_MAX, "size exceeding INVENTORY_SIZE_MAX");
|
||||
assertTrue(size > 0, "size must be greater than 0");
|
||||
|
||||
memoryZero(inventory, sizeof(inventory_t));
|
||||
inventory->size = size;
|
||||
}
|
||||
|
||||
uint8_t inventoryItemIndexByType(
|
||||
const inventory_t *inventory,
|
||||
const itemtype_t type
|
||||
) {
|
||||
assertNotNull(inventory, "inventory must not be NULL");
|
||||
assertTrue(type > ITEM_TYPE_NULL, "type must be greater than ITEM_TYPE_NULL");
|
||||
|
||||
uint8_t item = inventory->itemCount;
|
||||
while(item--) {
|
||||
if(inventory->items[item].type == type) return item;
|
||||
}
|
||||
return INVENTORY_SIZE_MAX;
|
||||
}
|
||||
|
||||
uint8_t inventoryItemCount(
|
||||
const inventory_t *inventory,
|
||||
const itemtype_t type
|
||||
) {
|
||||
assertNotNull(inventory, "inventory must not be NULL");
|
||||
assertTrue(type > ITEM_TYPE_NULL, "type must be greater than ITEM_TYPE_NULL");
|
||||
|
||||
const uint8_t index = inventoryItemIndexByType(inventory, type);
|
||||
if(index == INVENTORY_SIZE_MAX) return 0;
|
||||
return inventory->items[index].count;
|
||||
}
|
||||
|
||||
void inventoryItemSet(
|
||||
inventory_t *inventory,
|
||||
const itemtype_t type,
|
||||
const uint8_t count
|
||||
) {
|
||||
assertNotNull(inventory, "inventory must not be NULL");
|
||||
assertTrue(type > ITEM_TYPE_NULL, "type must be greater than ITEM_TYPE_NULL");
|
||||
assertTrue(count > 0, "count must be greater than 0");
|
||||
|
||||
const uint8_t index = inventoryItemIndexByType(inventory, type);
|
||||
|
||||
if(index == INVENTORY_SIZE_MAX) {
|
||||
// Item does not exist, add it
|
||||
assertTrue(inventory->itemCount < inventory->size, "inventory is full");
|
||||
inventory->items[inventory->itemCount].type = type;
|
||||
inventory->items[inventory->itemCount].count = count;
|
||||
inventory->itemCount++;
|
||||
} else {
|
||||
// Item exists, update the count
|
||||
inventory->items[index].count = count;
|
||||
}
|
||||
}
|
@@ -1,63 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "itemstack.h"
|
||||
|
||||
#define INVENTORY_SIZE_MAX UINT8_MAX
|
||||
|
||||
typedef struct {
|
||||
itemstack_t items[INVENTORY_SIZE_MAX];
|
||||
uint8_t itemCount;
|
||||
uint8_t size;
|
||||
} inventory_t;
|
||||
|
||||
/**
|
||||
* Initializes an inventory with a specified size.
|
||||
*
|
||||
* @param inventory Pointer to the inventory to initialize.
|
||||
* @param size The size of the inventory (maximum is INVENTORY_SIZE_MAX).
|
||||
*/
|
||||
void inventoryInit(inventory_t *inventory, const uint8_t size);
|
||||
|
||||
/**
|
||||
* Finds the index of the item of a specified type in the inventory.
|
||||
*
|
||||
* @param inventory Pointer to the inventory to search.
|
||||
* @param type The type of item to find.
|
||||
* @return The index of the item, or INVENTORY_SIZE_MAX if not found.
|
||||
*/
|
||||
uint8_t inventoryItemIndexByType(
|
||||
const inventory_t *inventory,
|
||||
const itemtype_t type
|
||||
);
|
||||
|
||||
/**
|
||||
* Gets the count of items of a specified type in the inventory.
|
||||
*
|
||||
* @param inventory Pointer to the inventory to check.
|
||||
* @param type The type of item to count.
|
||||
* @return The count of items of the specified type.
|
||||
*/
|
||||
uint8_t inventoryItemCount(
|
||||
const inventory_t *inventory,
|
||||
const itemtype_t type
|
||||
);
|
||||
|
||||
/**
|
||||
* Sets the count of items of a specified type in the inventory.
|
||||
* If the item does not exist, it will be added.
|
||||
*
|
||||
* @param inventory Pointer to the inventory to modify.
|
||||
* @param type The type of item to set.
|
||||
* @param count The count of items to set.
|
||||
*/
|
||||
void inventoryItemSet(
|
||||
inventory_t *inventory,
|
||||
const itemtype_t type,
|
||||
const uint8_t count
|
||||
);
|
@@ -1,14 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "itemtype.h"
|
||||
|
||||
typedef struct {
|
||||
itemtype_t type;
|
||||
uint8_t count;
|
||||
} itemstack_t;
|
@@ -1,24 +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 enum {
|
||||
ITEM_TYPE_NULL = 0,
|
||||
|
||||
// MEDICINE
|
||||
ITEM_TYPE_POTION,
|
||||
|
||||
// INGREDIENTS
|
||||
ITEM_TYPE_ONION,
|
||||
ITEM_TYPE_SWEET_POTATO,
|
||||
ITEM_TYPE_CARROT,
|
||||
|
||||
// COOKED FOOD
|
||||
ITEM_TYPE_BAKED_SWEET_POTATO,
|
||||
} itemtype_t;
|
@@ -6,8 +6,18 @@
|
||||
*/
|
||||
|
||||
#include "rpg.h"
|
||||
#include "rpg/entity/player.h"
|
||||
|
||||
#include "rpg/world/map.h"
|
||||
|
||||
map_t testMap;
|
||||
|
||||
void rpgInit() {
|
||||
|
||||
mapInit(&testMap);
|
||||
|
||||
entity_t *ent = mapEntityAdd(&testMap);
|
||||
entityInit(ent, ENTITY_TYPE_PLAYER);
|
||||
}
|
||||
|
||||
void rpgUpdate() {
|
||||
mapUpdate(&testMap);
|
||||
}
|
@@ -10,4 +10,9 @@
|
||||
/**
|
||||
* Initializes the RPG subsystem.
|
||||
*/
|
||||
void rpgInit();
|
||||
void rpgInit();
|
||||
|
||||
/**
|
||||
* Updates the RPG subsystem.
|
||||
*/
|
||||
void rpgUpdate();
|
@@ -1,11 +1,10 @@
|
||||
# Copyright (c) 2025 Dominic Masters
|
||||
#
|
||||
#
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
# Sources
|
||||
target_sources(${DUSK_TARGET_NAME}
|
||||
PRIVATE
|
||||
chunk.c
|
||||
overworld.c
|
||||
map.c
|
||||
)
|
@@ -1,321 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "chunk.h"
|
||||
#include "util/memory.h"
|
||||
#include "assert/assert.h"
|
||||
#include "rpg/world/world.h"
|
||||
|
||||
void renderChunkUpdated(chunk_t *chunk);
|
||||
|
||||
chunkmap_t CHUNK_MAP;
|
||||
|
||||
void chunkMapInit() {
|
||||
memoryZero(&CHUNK_MAP, sizeof(chunkmap_t));
|
||||
|
||||
// Load default chunks, YX order.
|
||||
uint16_t i = 0;
|
||||
chunk_t *chunk;
|
||||
for(uint8_t y = 0; y < CHUNK_MAP_HEIGHT; y++) {
|
||||
for(uint8_t x = 0; x < CHUNK_MAP_WIDTH; x++) {
|
||||
assertTrue(i < CHUNK_MAP_COUNT, "Chunk index out of bounds");
|
||||
|
||||
chunk = CHUNK_MAP.chunks + i;
|
||||
CHUNK_MAP.chunkOrder[i] = chunk;
|
||||
|
||||
chunkLoad(chunk, x, y);
|
||||
assertTrue(
|
||||
chunk->x == x && chunk->y == y,
|
||||
"Chunk coordinates do not match expected values"
|
||||
);
|
||||
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void chunkMapShift(const int16_t x, const int16_t y) {
|
||||
if(x == 0 && y == 0) assertUnreachable("ChunkMapShift called with no shift");
|
||||
|
||||
chunk_t *newChunkOrder[CHUNK_MAP_COUNT];
|
||||
chunk_t *unloadedChunks[CHUNK_MAP_COUNT];
|
||||
chunk_t *chunk;
|
||||
uint8_t i, j;
|
||||
uint16_t
|
||||
/** New Map Coordinates */
|
||||
newX, newY,
|
||||
newChunkX, newChunkY
|
||||
;
|
||||
|
||||
// Calculate the new map coordinates
|
||||
newX = CHUNK_MAP.topLeftX + x;
|
||||
newY = CHUNK_MAP.topLeftY + y;
|
||||
|
||||
// Zero the new chunk order
|
||||
memoryZero(newChunkOrder, sizeof(newChunkOrder));
|
||||
|
||||
// For each chunk...
|
||||
j = 0;
|
||||
chunk = CHUNK_MAP.chunks;
|
||||
do {
|
||||
// Is this chunk still going to be within the map bounds?
|
||||
if(
|
||||
chunk->x < newX || chunk->y < newY ||
|
||||
chunk->x >= newX + CHUNK_MAP_WIDTH ||
|
||||
chunk->y >= newY + CHUNK_MAP_HEIGHT
|
||||
) {
|
||||
// No, it's not, let's unload it and make it available for reuse.
|
||||
chunkUnload(chunk);
|
||||
assertTrue(
|
||||
j < CHUNK_MAP_COUNT,
|
||||
"Unloaded chunk index out of bounds"
|
||||
);
|
||||
unloadedChunks[j++] = chunk;
|
||||
chunk++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Yes it is still valid, determine the new index that it will be at
|
||||
i = (chunk->y - newY) * CHUNK_MAP_WIDTH + (chunk->x - newX);
|
||||
assertTrue(
|
||||
i < CHUNK_MAP_COUNT,
|
||||
"Chunk index out of bounds after shifting"
|
||||
);
|
||||
assertNull(
|
||||
newChunkOrder[i],
|
||||
"New chunk order index is already occupied"
|
||||
);
|
||||
|
||||
// Set the new chunk order
|
||||
newChunkOrder[i] = chunk;
|
||||
chunk++;
|
||||
} while(chunk < CHUNK_MAP.chunks + CHUNK_MAP_COUNT);
|
||||
|
||||
// Now check the new chunk order list for missing chunks.
|
||||
i = 0;
|
||||
do {
|
||||
assertTrue(
|
||||
i < CHUNK_MAP_COUNT,
|
||||
"New chunk order index out of bounds after shifting"
|
||||
);
|
||||
|
||||
// Is this chunk loaded still?
|
||||
chunk = newChunkOrder[i];
|
||||
if(chunk != NULL) {
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Determine the new chunk coordinates.
|
||||
newChunkX = i % CHUNK_MAP_WIDTH + newX;
|
||||
newChunkY = i / CHUNK_MAP_WIDTH + newY;
|
||||
assertTrue(
|
||||
newChunkX >= newX && newChunkX < newX + CHUNK_MAP_WIDTH,
|
||||
"New chunk X coordinate out of bounds after shifting"
|
||||
);
|
||||
assertTrue(
|
||||
newChunkY >= newY && newChunkY < newY + CHUNK_MAP_HEIGHT,
|
||||
"New chunk Y coordinate out of bounds after shifting"
|
||||
);
|
||||
|
||||
// Pop a chunk from the unloaded chunks list.
|
||||
assertTrue(j > 0, "No unloaded chunks available to reuse");
|
||||
chunk = unloadedChunks[--j];
|
||||
assertNotNull(chunk, "Unloaded chunk pointer is null");
|
||||
|
||||
// Load the chunk at the new coordinates.
|
||||
chunkLoad(chunk, newChunkX, newChunkY);
|
||||
assertTrue(
|
||||
chunk->x == newChunkX && chunk->y == newChunkY,
|
||||
"Chunk coordinates do not match expected values after shifting"
|
||||
);
|
||||
|
||||
// Set it in order.
|
||||
newChunkOrder[i] = chunk;
|
||||
i++;
|
||||
} while(i < CHUNK_MAP_COUNT);
|
||||
|
||||
// Update Absolutes.
|
||||
CHUNK_MAP.topLeftX = newX;
|
||||
CHUNK_MAP.topLeftY = newY;
|
||||
|
||||
// Update the chunk order.
|
||||
memoryCopy(
|
||||
CHUNK_MAP.chunkOrder,
|
||||
newChunkOrder,
|
||||
sizeof(CHUNK_MAP.chunkOrder)
|
||||
);
|
||||
}
|
||||
|
||||
void chunkMapSetPosition(const uint16_t x, const uint16_t y) {
|
||||
if(x == CHUNK_MAP.topLeftX && y == CHUNK_MAP.topLeftY) {
|
||||
return;
|
||||
}
|
||||
|
||||
int16_t shiftX = x - CHUNK_MAP.topLeftX;
|
||||
int16_t shiftY = y - CHUNK_MAP.topLeftY;
|
||||
|
||||
// Are we shifting the entire map?
|
||||
if(
|
||||
shiftX >= CHUNK_MAP_WIDTH || shiftX < -CHUNK_MAP_WIDTH ||
|
||||
shiftY >= CHUNK_MAP_HEIGHT || shiftY < -CHUNK_MAP_HEIGHT
|
||||
) {
|
||||
printf("Shifting chunk map to new position (%u, %u)\n", x, y);
|
||||
}
|
||||
|
||||
// Shift the chunk map by the specified offsets.
|
||||
chunkMapShift(shiftX, shiftY);
|
||||
}
|
||||
|
||||
chunk_t * chunkGetChunkAt(const uint16_t chunkX, const uint16_t chunkY) {
|
||||
assertTrue(
|
||||
chunkX < WORLD_WIDTH && chunkY < WORLD_HEIGHT,
|
||||
"Chunk coordinates out of bounds"
|
||||
);
|
||||
|
||||
chunk_t *chunk = CHUNK_MAP.chunks;
|
||||
do {
|
||||
if(chunk->x == chunkX && chunk->y == chunkY) return chunk;
|
||||
chunk++;
|
||||
} while(chunk < CHUNK_MAP.chunks + CHUNK_MAP_COUNT);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void chunkLoad(chunk_t *chunk, const uint16_t x, const uint16_t y) {
|
||||
assertNotNull(chunk, "Chunk pointer is null");
|
||||
|
||||
// Zero out the chunk data.
|
||||
memoryZero(chunk, sizeof(chunk_t));
|
||||
|
||||
// Set the chunk coordinates.
|
||||
chunk->x = x;
|
||||
chunk->y = y;
|
||||
|
||||
// Only load data if the chunk is within bounds.
|
||||
if(x >= WORLD_WIDTH || y >= WORLD_HEIGHT) {
|
||||
memorySet(chunk->tilesBase, 0, sizeof(chunk->tilesBase));
|
||||
memorySet(chunk->tilesBaseOverlay, 0, sizeof(chunk->tilesBaseOverlay));
|
||||
return;
|
||||
}
|
||||
|
||||
// Is chunk data defined?
|
||||
const chunkdata_t *chunkData = WORLD_CHUNKS[y * WORLD_WIDTH + x];
|
||||
if(chunkData == NULL) {
|
||||
memorySet(chunk->tilesBase, 0, sizeof(chunk->tilesBase));
|
||||
memorySet(chunk->tilesBaseOverlay, 0, sizeof(chunk->tilesBaseOverlay));
|
||||
return;
|
||||
}
|
||||
|
||||
// Load tile data into chunk
|
||||
// printf("Loading chunk at (%u, %u)\n", x, y);
|
||||
memoryCopy(
|
||||
chunk->tilesBase,
|
||||
chunkData->layerBase,
|
||||
sizeof(chunk->tilesBase)
|
||||
);
|
||||
memoryCopy(
|
||||
chunk->tilesBaseOverlay,
|
||||
chunkData->layerBaseOverlay,
|
||||
sizeof(chunk->tilesBaseOverlay)
|
||||
);
|
||||
|
||||
// Load chunk entities
|
||||
const entity_t *data;
|
||||
entity_t *entity;
|
||||
data = chunkData->entities;
|
||||
while(data < chunkData->entities + CHUNK_ENTITY_COUNT_MAX) {
|
||||
if(data->type == ENTITY_TYPE_NULL) break;
|
||||
|
||||
// Store that this chunk owns this entity ID.
|
||||
chunk->entityIDs[chunk->entityCount++] = data->id;
|
||||
|
||||
// Check entity isn't loaded (still).
|
||||
entity = ENTITIES;
|
||||
do {
|
||||
if(entity->type != ENTITY_TYPE_NULL && entity->id == data->id) break;
|
||||
entity++;
|
||||
} while(entity < ENTITIES + ENTITY_COUNT_MAX);
|
||||
|
||||
if(entity != ENTITIES + ENTITY_COUNT_MAX) {
|
||||
// Entity is already loaded, skip it.
|
||||
printf("Entity ID %u already loaded, skipping...\n", data->id);
|
||||
data++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Find an empty entity slot.
|
||||
entity = ENTITIES;
|
||||
while(true) {
|
||||
assertTrue(
|
||||
entity < ENTITIES + ENTITY_COUNT_MAX,
|
||||
"Out of available entities"
|
||||
);
|
||||
|
||||
if(entity->type == ENTITY_TYPE_NULL) break;
|
||||
entity++;
|
||||
};
|
||||
|
||||
// Load this entity.
|
||||
// entityLoad(entity, data);
|
||||
data++;
|
||||
}
|
||||
|
||||
// Allow the rendering platform to know this chunk is loaded.
|
||||
// renderChunkUpdated(chunk);
|
||||
}
|
||||
|
||||
void chunkUnload(chunk_t *chunk) {
|
||||
uint8_t i;
|
||||
entity_t *entity;
|
||||
uint32_t id;
|
||||
assertNotNull(chunk, "Chunk pointer is null");
|
||||
|
||||
// Iterate over each entity this chunk owns.
|
||||
i = 0;
|
||||
while(i < chunk->entityCount) {
|
||||
id = chunk->entityIDs[i++];
|
||||
|
||||
// Now, do we need to unload this entity?
|
||||
bool_t shouldUnload = false;
|
||||
|
||||
// Now, find the entity loaded with this ID. It should be impossible for
|
||||
// this entity to be unloaded (but may change in future).
|
||||
entity = ENTITIES;
|
||||
do {
|
||||
if(entity->type != ENTITY_TYPE_NULL && entity->id == id) break;
|
||||
entity++;
|
||||
} while(entity < ENTITIES + ENTITY_COUNT_MAX);
|
||||
|
||||
assertTrue(
|
||||
entity < ENTITIES + ENTITY_COUNT_MAX,
|
||||
"Entity ID not found in ENTITIES array, cannot unload"
|
||||
);
|
||||
|
||||
// If the entity is still within our chunk bounds, it's getting unloaded
|
||||
if(
|
||||
floorf(entity->x) >= chunk->x * CHUNK_WIDTH * TILE_WIDTH_HEIGHT &&
|
||||
ceilf(entity->x) < (chunk->x + 1) * CHUNK_WIDTH * TILE_WIDTH_HEIGHT &&
|
||||
floorf(entity->y) >= chunk->y * CHUNK_HEIGHT * TILE_WIDTH_HEIGHT &&
|
||||
ceilf(entity->y) < (chunk->y + 1) * CHUNK_HEIGHT * TILE_WIDTH_HEIGHT
|
||||
) {
|
||||
shouldUnload = true;
|
||||
} else {
|
||||
assertUnreachable(
|
||||
"Entity has left its chunk bounds, we should not be unloading it but "
|
||||
"I have yet to implement that properly. It will need to self-manage "
|
||||
"its own unloading somehow, and also not be in a null chunk "
|
||||
"when it does so."
|
||||
);
|
||||
}
|
||||
|
||||
// This entity is still in use, leave it loaded.
|
||||
if(!shouldUnload) continue;
|
||||
|
||||
// NULL the entity type, effectively unloading it.
|
||||
entity->type = ENTITY_TYPE_NULL;
|
||||
}
|
||||
}
|
@@ -1,84 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "tile.h"
|
||||
#include "display/display.h"
|
||||
|
||||
#define CHUNK_WIDTH 8
|
||||
#define CHUNK_HEIGHT 8
|
||||
#define CHUNK_TILE_COUNT (CHUNK_WIDTH * CHUNK_HEIGHT)
|
||||
#define CHUNK_ENTITY_COUNT_MAX 8
|
||||
|
||||
#define CHUNK_MAP_WIDTH (((DISPLAY_WIDTH / TILE_WIDTH_HEIGHT)/CHUNK_WIDTH)+2)
|
||||
#define CHUNK_MAP_HEIGHT (((DISPLAY_HEIGHT / TILE_WIDTH_HEIGHT)/CHUNK_HEIGHT)+2)
|
||||
#define CHUNK_MAP_COUNT (CHUNK_MAP_WIDTH * CHUNK_MAP_HEIGHT)
|
||||
|
||||
typedef struct {
|
||||
uint16_t x, y;
|
||||
tile_t tilesBase[CHUNK_TILE_COUNT];
|
||||
tile_t tilesBaseOverlay[CHUNK_TILE_COUNT];
|
||||
uint32_t entityIDs[CHUNK_ENTITY_COUNT_MAX];
|
||||
uint8_t entityCount;
|
||||
} chunk_t;
|
||||
|
||||
typedef struct {
|
||||
chunk_t chunks[CHUNK_MAP_COUNT];
|
||||
chunk_t *chunkOrder[CHUNK_MAP_COUNT];
|
||||
|
||||
uint16_t topLeftX;
|
||||
uint16_t topLeftY;
|
||||
} chunkmap_t;
|
||||
|
||||
extern chunkmap_t CHUNK_MAP;
|
||||
|
||||
/**
|
||||
* Initializes the chunk map.
|
||||
*/
|
||||
void chunkMapInit();
|
||||
|
||||
/**
|
||||
* Shifts the chunk map by the specified x and y offsets.
|
||||
*
|
||||
* @param x The x offset to shift the chunk map.
|
||||
* @param y The y offset to shift the chunk map.
|
||||
*/
|
||||
void chunkMapShift(const int16_t x, const int16_t y);
|
||||
|
||||
/**
|
||||
* Sets the position of the chunk map to the specified coordinates.
|
||||
*
|
||||
* @param x The x coordinate of the top-left chunk.
|
||||
* @param y The y coordinate of the top-left chunk.
|
||||
*/
|
||||
void chunkMapSetPosition(const uint16_t x, const uint16_t y);
|
||||
|
||||
/**
|
||||
* Gets the chunk at the specified chunk coordinates.
|
||||
*
|
||||
* @param chunkX The x coordinate of the chunk.
|
||||
* @param chunkY The y coordinate of the chunk.
|
||||
* @return A pointer to the chunk at the specified chunk coordinates, or NULL if
|
||||
* no chunk exists at those coordinates.
|
||||
*/
|
||||
chunk_t * chunkGetChunkAt(const uint16_t chunkX, const uint16_t chunkY);
|
||||
|
||||
/**
|
||||
* Loads a chunk at the specified coordinates.
|
||||
*
|
||||
* @param chunk The chunk to load.
|
||||
* @param x The x coordinate of the chunk.
|
||||
* @param y The y coordinate of the chunk.
|
||||
*/
|
||||
void chunkLoad(chunk_t *chunk, const uint16_t x, const uint16_t y);
|
||||
|
||||
/**
|
||||
* Unloads a chunk (that is currently loaded).
|
||||
*
|
||||
* @param chunk The chunk to unload.
|
||||
*/
|
||||
void chunkUnload(chunk_t *chunk);
|
@@ -1,16 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "chunk.h"
|
||||
#include "rpg/entity/entity.h"
|
||||
|
||||
typedef struct {
|
||||
uint8_t layerBase[CHUNK_TILE_COUNT];
|
||||
uint8_t layerBaseOverlay[CHUNK_TILE_COUNT];
|
||||
entity_t entities[CHUNK_ENTITY_COUNT_MAX];
|
||||
} chunkdata_t;
|
33
src/rpg/world/map.c
Normal file
33
src/rpg/world/map.c
Normal file
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* 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"
|
||||
|
||||
void mapInit(map_t *map) {
|
||||
assertNotNull(map, "Map cannot be NULL");
|
||||
memoryZero(map, sizeof(map_t));
|
||||
}
|
||||
|
||||
void mapUpdate(map_t *map) {
|
||||
assertNotNull(map, "Map cannot be NULL");
|
||||
|
||||
entity_t *start = &map->entities[0];
|
||||
entity_t *end = &map->entities[map->entityCount];
|
||||
while(start < end) {
|
||||
entityUpdate(start++);
|
||||
}
|
||||
}
|
||||
|
||||
entity_t * mapEntityAdd(map_t *map) {
|
||||
assertNotNull(map, "Map cannot be NULL");
|
||||
assertTrue(map->entityCount < MAP_ENTITY_COUNT_MAX, "Map entities full");
|
||||
|
||||
entity_t *entity = &map->entities[map->entityCount++];
|
||||
return entity;
|
||||
}
|
40
src/rpg/world/map.h
Normal file
40
src/rpg/world/map.h
Normal file
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "rpg/entity/entity.h"
|
||||
|
||||
#define MAP_ENTITY_COUNT_MAX 32
|
||||
|
||||
typedef struct {
|
||||
entity_t entities[MAP_ENTITY_COUNT_MAX];
|
||||
uint8_t entityCount;
|
||||
} map_t;
|
||||
|
||||
extern map_t testMap;
|
||||
|
||||
/**
|
||||
* Initializes a map structure.
|
||||
*
|
||||
* @param map Pointer to the map structure to initialize.
|
||||
*/
|
||||
void mapInit(map_t *map);
|
||||
|
||||
/**
|
||||
* Updates the map and its entities.
|
||||
*
|
||||
* @param map Pointer to the map structure to update.
|
||||
*/
|
||||
void mapUpdate(map_t *map);
|
||||
|
||||
/**
|
||||
* Adds (but does not initialize) an entity on the map.
|
||||
*
|
||||
* @param map Pointer to the map structure.
|
||||
* @return Pointer to the added entity.
|
||||
*/
|
||||
entity_t * mapEntityAdd(map_t *map);
|
@@ -1,72 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "overworld.h"
|
||||
#include "chunk.h"
|
||||
#include "display/display.h"
|
||||
#include "assert/assert.h"
|
||||
#include "rpg/entity/entity.h"
|
||||
|
||||
uint32_t OVERWORLD_CAMERA_X;
|
||||
uint32_t OVERWORLD_CAMERA_Y;
|
||||
overworldcameratype_t OVERWORLD_CAMERA_TYPE;
|
||||
|
||||
void overworldInit(void) {
|
||||
playerInit();
|
||||
chunkMapInit();
|
||||
|
||||
OVERWORLD_CAMERA_X = 0;
|
||||
OVERWORLD_CAMERA_Y = 0;
|
||||
OVERWORLD_CAMERA_TYPE = OVERWORLD_CAMERA_TYPE_CENTERED_POSITION;
|
||||
}
|
||||
|
||||
void overworldUpdate() {
|
||||
entity_t *entity;
|
||||
|
||||
assertTrue(
|
||||
OVERWORLD_CAMERA_X < OVERWORLD_CAMERA_LIMIT_X,
|
||||
"Camera position limit (just because I haven't tested properly)"
|
||||
);
|
||||
assertTrue(
|
||||
OVERWORLD_CAMERA_Y < OVERWORLD_CAMERA_LIMIT_Y,
|
||||
"Camera position limit (just because I haven't tested properly)"
|
||||
);
|
||||
|
||||
entity = ENTITIES;
|
||||
do {
|
||||
entityUpdate(entity++);
|
||||
} while(entity->type != ENTITY_TYPE_NULL);
|
||||
|
||||
// Testing, follow player
|
||||
entity = &ENTITIES[0]; // Player entity
|
||||
assertTrue(
|
||||
entity->type == ENTITY_TYPE_PLAYER,
|
||||
"First entity must be player"
|
||||
);
|
||||
OVERWORLD_CAMERA_X = entity->x * TILE_WIDTH_HEIGHT + entity->subX;
|
||||
OVERWORLD_CAMERA_Y = entity->y * TILE_WIDTH_HEIGHT + entity->subY;
|
||||
|
||||
uint16_t x, y;
|
||||
uint16_t halfWidth, halfHeight;
|
||||
halfWidth = ((CHUNK_MAP_WIDTH - 1) * CHUNK_WIDTH * TILE_WIDTH_HEIGHT) / 2;
|
||||
halfHeight = ((CHUNK_MAP_HEIGHT - 1) * CHUNK_HEIGHT * TILE_WIDTH_HEIGHT) / 2;
|
||||
|
||||
// Calculate the chunk map position based on the camera position.
|
||||
if(OVERWORLD_CAMERA_X < halfWidth) {
|
||||
x = 0;
|
||||
} else {
|
||||
x = (OVERWORLD_CAMERA_X - halfWidth) / (CHUNK_WIDTH*TILE_WIDTH_HEIGHT);
|
||||
}
|
||||
|
||||
if(OVERWORLD_CAMERA_Y < halfHeight) {
|
||||
y = 0;
|
||||
} else {
|
||||
y = (OVERWORLD_CAMERA_Y - halfHeight) / (CHUNK_HEIGHT*TILE_WIDTH_HEIGHT);
|
||||
}
|
||||
|
||||
chunkMapSetPosition(x, y);
|
||||
}
|
@@ -1,30 +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 enum {
|
||||
OVERWORLD_CAMERA_TYPE_CENTERED_POSITION,
|
||||
} overworldcameratype_t;
|
||||
|
||||
extern uint32_t OVERWORLD_CAMERA_X;
|
||||
extern uint32_t OVERWORLD_CAMERA_Y;
|
||||
extern overworldcameratype_t OVERWORLD_CAMERA_TYPE;
|
||||
|
||||
#define OVERWORLD_CAMERA_LIMIT_X (UINT32_MAX / 4)
|
||||
#define OVERWORLD_CAMERA_LIMIT_Y (UINT32_MAX / 4)
|
||||
|
||||
/**
|
||||
* Initializes the overworld.
|
||||
*/
|
||||
void overworldInit(void);
|
||||
|
||||
/**
|
||||
* Updates the overworld.
|
||||
*/
|
||||
void overworldUpdate(void);
|
@@ -1,26 +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"
|
||||
|
||||
#define TILE_WIDTH_HEIGHT 16
|
||||
|
||||
typedef uint8_t tile_t;
|
||||
|
||||
typedef enum {
|
||||
TILE_SOLID_NONE = 0,
|
||||
TILE_SOLID_FULL = 1,
|
||||
TILE_SOLID_TRIANGLE_TOP_RIGHT = 2,
|
||||
TILE_SOLID_TRIANGLE_TOP_LEFT = 3,
|
||||
TILE_SOLID_TRIANGLE_BOTTOM_RIGHT = 4,
|
||||
TILE_SOLID_TRIANGLE_BOTTOM_LEFT = 5,
|
||||
} tilesolidtype_t;
|
||||
|
||||
typedef struct {
|
||||
tilesolidtype_t solidType;
|
||||
} tilemetadata_t;
|
@@ -1,16 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "rpg/world/chunkdata.h"
|
||||
|
||||
#define WORLD_WIDTH 1
|
||||
#define WORLD_HEIGHT 1
|
||||
|
||||
static const chunkdata_t *WORLD_CHUNKS[WORLD_HEIGHT * WORLD_WIDTH] = {
|
||||
NULL
|
||||
};
|
Reference in New Issue
Block a user