Basic NPC loading (half done)

This commit is contained in:
2025-06-13 17:07:28 -05:00
parent 49989e0272
commit 9288c01887
25 changed files with 644 additions and 26 deletions

View File

@ -6,19 +6,13 @@
find_package(Python3 COMPONENTS Interpreter REQUIRED)
# Custom command to generate all header files
add_custom_command(
OUTPUT ${DUSK_GENERATED_HEADERS_DIR}/world/world.h
add_custom_target(DUSK_CHUNKS
# OUTPUT ${DUSK_GENERATED_HEADERS_DIR}/world/world.h
COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/chunks.py --output ${DUSK_GENERATED_HEADERS_DIR}
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/chunks.py
COMMENT "Generating chunk header files"
VERBATIM
)
# Custom target to generate headers before build
add_custom_target(DUSK_CHUNKS
DEPENDS
${DUSK_GENERATED_HEADERS_DIR}/world/world.h
)
# Ensure headers are generated before compiling main
add_dependencies(${DUSK_TARGET_NAME} DUSK_CHUNKS)

View File

@ -7,6 +7,11 @@ import json
# Constants that are defined in the C code
CHUNK_WIDTH = 8
CHUNK_HEIGHT = 8
CHUNK_ENTITY_COUNT_MAX = 8
ENTITY_TYPE = {
"npc": "ENTITY_TYPE_NPC",
}
# Check if the script is run with the correct arguments
parser = argparse.ArgumentParser(description="Generate chunk header files")
@ -38,8 +43,7 @@ now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
worldWidth = 0
worldHeight = 0
chunksDone = []
entityIdNext = 1
# For each chunk file
for chunkFile in os.listdir(chunks_dir):
@ -109,6 +113,8 @@ for chunkFile in os.listdir(chunks_dir):
print(f"Error: Chunk file '{chunkFile}' has invalid 'overlayLayer' row length.")
exit(1)
# Read in entities
entities = data['chunk'].get('entities', [])
# Now we generate a chunk header file
chunk_header_path = os.path.join(chunksDir, f"chunk_{x}_{y}.h")
@ -116,22 +122,51 @@ for chunkFile in os.listdir(chunks_dir):
f.write(f"// Generated chunk header for chunk at position ({x}, {y})\n")
f.write(f"// Generated at {now}\n")
f.write("#pragma once\n")
f.write("#include \"dusk.h\"\n\n")
f.write("#include \"world/chunkdata.h\"\n\n")
f.write(f"static const uint8_t CHUNK_{x}_{y}_LAYER_BASE[] = {{\n")
f.write(f"static const chunkdata_t CHUNK_{x}_{y} = {{\n")
f.write(f" .layerBase = {{\n")
for row in baseLayer:
f.write(" ")
f.write(" ")
for column in row:
f.write(f"0x{column:02x}, ")
f.write("\n")
f.write("};\n\n")
f.write(" },\n\n")
f.write(f"static const uint8_t CHUNK_{x}_{y}_LAYER_OVERLAY[] = {{\n")
f.write(f" .layerOverlay = {{\n")
for row in overlayLayer:
f.write(" ")
f.write(" ")
for column in row:
f.write(f"0x{column:02x}, ")
f.write("\n")
f.write(" },\n\n")
f.write(f" .entities = {{\n")
for entity in entities:
if 'id' in entity:
entityId = entity['id']
else:
entityId = entityIdNext
entityIdNext += 1
if 'type' not in entity:
print(f"Error: Entity in chunk ({x}, {y}) does not have 'type' key.")
exit(1)
if 'x' not in entity or 'y' not in entity:
print(f"Error: Entity in chunk ({x}, {y}) does not have 'x' or 'y' key.")
exit(1)
f.write(" {\n")
f.write(f" .id = {entityId},\n")
f.write(f" .type = {ENTITY_TYPE.get(entity['type'], 'ENTITY_TYPE_UNKNOWN')},\n"),
f.write(f" .x = {entity['x']},\n")
f.write(f" .y = {entity['y']},\n")
f.write(f" }},\n")
pass
f.write(" },\n\n")
f.write("};\n\n")
pass
@ -152,12 +187,12 @@ with open(header_path, 'w') as f:
f.write("\n")
f.write(f"#define WORLD_WIDTH {worldWidth}\n")
f.write(f"#define WORLD_HEIGHT {worldHeight}\n\n")
f.write(f"static const uint8_t* WORLD_CHUNKS_BASE[] = {{\n")
f.write(f"static const chunkdata_t* WORLD_CHUNKS[] = {{\n")
for i in range(worldHeight):
f.write(" ")
for j in range(worldWidth):
if (j, i) in chunksDone:
f.write(f"CHUNK_{j}_{i}_LAYER_BASE, ")
f.write(f"&CHUNK_{j}_{i}, ")
else:
f.write("NULL, ")
f.write("\n")

View File

@ -1,6 +1,6 @@
{
"chunk": {
"position": [ 1, 1 ],
"position": [ 0, 0 ],
"baseLayer": [
[ 1, 1, 1, 1, 1, 1, 1, 1 ],
[ 1, 1, 1, 1, 1, 1, 1, 1 ],
@ -22,6 +22,11 @@
[ 1, 1, 1, 1, 1, 1, 1, 1 ]
],
"entities": [
{
"type": "npc",
"x": "3",
"y": "3"
}
],
"triggers": [
]

View File

@ -18,10 +18,12 @@ target_include_directories(${DUSK_TARGET_NAME}
target_sources(${DUSK_TARGET_NAME}
PRIVATE
main.c
input.c
)
# Subdirs
add_subdirectory(assert)
add_subdirectory(display)
add_subdirectory(entity)
add_subdirectory(util)
add_subdirectory(world)

View File

@ -5,4 +5,6 @@
* https://opensource.org/licenses/MIT
*/
#include "display/render.h"
#include "display/render.h"
uint8_t RENDER_FRAME = 0;

View File

@ -11,6 +11,8 @@
#define RENDER_WIDTH 320
#define RENDER_HEIGHT 240
extern uint8_t RENDER_FRAME;
/**
* Initializes the rendering system.
*/

View File

@ -11,6 +11,7 @@
#include <stdbool.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
typedef bool bool_t;
typedef int int_t;

View File

@ -0,0 +1,12 @@
# 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
entity.c
player.c
npc.c
)

42
src/dusk/entity/entity.c Normal file
View File

@ -0,0 +1,42 @@
/**
* 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"
entity_t ENTITIES[ENTITY_COUNT_MAX] = {0};
entitycallback_t ENTITY_CALLBACKS[ENTITY_TYPE_COUNT] = {
{NULL}, // ENTITY_TYPE_NULL
{playerNPCInit, playerNPCUpdate}, // ENTITY_TYPE_PLAYER
{npcInit, npcUpdate} // ENTITY_TYPE_NPC
};
void entityInit(entity_t *entity, const entitytype_t type) {
assertNotNull(entity, "Entity pointer cannot be NULL");
assertTrue(type != ENTITY_TYPE_NULL, "Entity type NULL");
assertTrue(type < ENTITY_TYPE_COUNT, "Entity type out of bounds");
assertNotNull(
ENTITY_CALLBACKS[type].init,
"Entity type has no init callback"
);
entity->type = type;
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"
);
ENTITY_CALLBACKS[entity->type].update(entity);
}

66
src/dusk/entity/entity.h Normal file
View File

@ -0,0 +1,66 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "player.h"
#include "npc.h"
#define ENTITY_COUNT_MAX 32
typedef enum {
ENTITY_DIR_NORTH = 0,
ENTITY_DIR_SOUTH = 1,
ENTITY_DIR_WEST = 2,
ENTITY_DIR_EAST = 3,
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;
typedef enum {
ENTITY_TYPE_NULL = 0,
ENTITY_TYPE_PLAYER = 1,
ENTITY_TYPE_NPC = 2,
} entitytype_t;
#define ENTITY_TYPE_COUNT 3
typedef struct _entity_t {
uint32_t id;// Completely unique ID for this entity.
float_t x, y;
entitytype_t type;
entitydir_t dir;
union {
npc_t npc;
playerentity_t player;
};
} entity_t;
typedef struct {
void (*init) (entity_t *entity);
void (*update) (entity_t *entity);
} entitycallback_t;
extern entity_t ENTITIES[ENTITY_COUNT_MAX];
extern entitycallback_t ENTITY_CALLBACKS[ENTITY_TYPE_COUNT];
/**
* Initializes an entity with the given type.
*
* @param entity Pointer to the entity to initialize.
* @param type The type of the entity to initialize.
*/
void entityInit(entity_t *entity, const entitytype_t type);
/**
* Updates the entity's state.
*
* @param entity Pointer to the entity to update.
*/
void entityUpdate(entity_t *entity);

16
src/dusk/entity/npc.c Normal file
View File

@ -0,0 +1,16 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "npc.h"
void npcInit(entity_t *entity) {
}
void npcUpdate(entity_t *entity) {
}

23
src/dusk/entity/npc.h Normal file
View File

@ -0,0 +1,23 @@
#pragma once
#include "dusk.h"
typedef struct _entity_t entity_t;
typedef struct {
uint32_t nothing;
} npc_t;
/**
* Initializes the NPC entity.
*
* @param entity The entity to initialize.
*/
void npcInit(entity_t *entity);
/**
* Updates the NPC entity.
*
* @param entity The entity to update.
*/
void npcUpdate(entity_t *entity);

77
src/dusk/entity/player.c Normal file
View File

@ -0,0 +1,77 @@
/**
* 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 "input.h"
#include "display/render.h"
void playerInit() {
entity_t *ent = &ENTITIES[0];
entityInit(ent, ENTITY_TYPE_PLAYER);
ent->id = PLAYER_ENTITY_ID;
ent->x = 32;
ent->y = 32;
}
void playerNPCInit(entity_t *entity) {
assertNotNull(entity, "Entity pointer cannot be NULL");
assertTrue(entity->type == ENTITY_TYPE_PLAYER, "Entity type must be PLAYER");
}
void playerNPCUpdate(entity_t *entity) {
assertNotNull(entity, "Entity pointer cannot be NULL");
assertTrue(entity->type == ENTITY_TYPE_PLAYER, "Entity type must be PLAYER");
if(inputIsDown(INPUT_BIND_UP)) {
if(inputIsDown(INPUT_BIND_LEFT)) {
entity->x -= PLAYER_MOVE_SPEED_XY;
entity->y -= PLAYER_MOVE_SPEED_XY;
if(entity->dir != ENTITY_DIR_NORTH && entity->dir != ENTITY_DIR_WEST) {
entity->dir = ENTITY_DIR_NORTH;
}
} else if(inputIsDown(INPUT_BIND_RIGHT)) {
entity->x += PLAYER_MOVE_SPEED_XY;
entity->y -= PLAYER_MOVE_SPEED_XY;
if(entity->dir != ENTITY_DIR_NORTH && entity->dir != ENTITY_DIR_EAST) {
entity->dir = ENTITY_DIR_NORTH;
}
} else {
entity->y -= PLAYER_MOVE_SPEED;
entity->dir = ENTITY_DIR_NORTH;
}
} else if(inputIsDown(INPUT_BIND_DOWN)) {
if(inputIsDown(INPUT_BIND_LEFT)) {
entity->x -= PLAYER_MOVE_SPEED_XY;
entity->y += PLAYER_MOVE_SPEED_XY;
if(entity->dir != ENTITY_DIR_SOUTH && entity->dir != ENTITY_DIR_WEST) {
entity->dir = ENTITY_DIR_SOUTH;
}
} else if(inputIsDown(INPUT_BIND_RIGHT)) {
entity->x += PLAYER_MOVE_SPEED_XY;
entity->y += PLAYER_MOVE_SPEED_XY;
if(entity->dir != ENTITY_DIR_SOUTH && entity->dir != ENTITY_DIR_EAST) {
entity->dir = ENTITY_DIR_SOUTH;
}
} else {
entity->y += PLAYER_MOVE_SPEED;
entity->dir = ENTITY_DIR_SOUTH;
}
} else if(inputIsDown(INPUT_BIND_LEFT)) {
entity->x -= PLAYER_MOVE_SPEED;
entity->dir = ENTITY_DIR_WEST;
} else if(inputIsDown(INPUT_BIND_RIGHT)) {
entity->x += PLAYER_MOVE_SPEED;
entity->dir = ENTITY_DIR_EAST;
}
}

View File

@ -6,4 +6,33 @@
*/
#pragma once
#include "dusk.h"
#include "dusk.h"
typedef struct _entity_t entity_t;
typedef struct {
uint32_t nothing;
} playerentity_t;
#define PLAYER_ENTITY_ID (UINT32_MAX-1)
#define PLAYER_MOVE_SPEED 1.0f
#define PLAYER_MOVE_SPEED_XY (PLAYER_MOVE_SPEED * 0.70710678118f)
/**
* Initializes the player and all player-related entities.
*/
void playerInit(void);
/**
* Initializes the player entity.
*
* @param entity The entity to initialize.
*/
void playerNPCInit(entity_t *entity);
/**
* Updates the player entity.
*
* @param entity The entity to update.
*/
void playerNPCUpdate(entity_t *entity);

39
src/dusk/input.c Normal file
View File

@ -0,0 +1,39 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "input.h"
#include "assert/assert.h"
#include "util/memory.h"
input_t INPUT;
void inputInit(void) {
memoryZero(&INPUT, sizeof(input_t));
}
void inputUpdate(void) {
INPUT.previous = INPUT.current;
INPUT.current = inputStateGet();
}
bool_t inputIsDown(const uint8_t bind) {
assertTrue(bind < INPUT_BIND_COUNT, "Input bind out of bounds");
return (INPUT.current & bind) != 0;
}
bool_t inputWasDown(const uint8_t bind) {
assertTrue(bind < INPUT_BIND_COUNT, "Input bind out of bounds");
return (INPUT.previous & bind) != 0;
}
bool_t inputPressed(const uint8_t bind) {
return inputIsDown(bind) && !inputWasDown(bind);
}
bool_t inputReleased(const uint8_t bind) {
return !inputIsDown(bind) && inputWasDown(bind);
}

74
src/dusk/input.h Normal file
View File

@ -0,0 +1,74 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "dusk.h"
#define INPUT_BIND_UP (1 << 0)
#define INPUT_BIND_DOWN (1 << 1)
#define INPUT_BIND_LEFT (1 << 2)
#define INPUT_BIND_RIGHT (1 << 3)
#define INPUT_BIND_ACTION (1 << 4)
#define INPUT_BIND_CANCEL (1 << 5)
#define INPUT_BIND_COUNT (INPUT_BIND_CANCEL + 1)
typedef struct {
uint8_t current;
uint8_t previous;
} input_t;
extern input_t INPUT;
/**
* Initialize the input system.
*/
void inputInit(void);
/**
* Updates the input state.
*/
void inputUpdate(void);
/**
* Gets the current input state as a bitmask.
*
* @return The current input state as a bitmask.
*/
uint8_t inputStateGet(void);
/**
* Checks if a specific input bind is currently pressed.
*
* @param bind The input bind to check.
* @return true if the bind is currently pressed, false otherwise.
*/
bool_t inputIsDown(const uint8_t bind);
/**
* Checks if a specific input bind was pressed in the last update.
*
* @param bind The input bind to check.
* @return true if the bind was pressed in the last update, false otherwise.
*/
bool_t inputWasDown(const uint8_t bind);
/**
* Checks if a specific input bind was down this frame but not in the the
* previous frame.
*
* @param bind The input bind to check.
* @return true if the bind is currently pressed, false otherwise.
*/
bool_t inputPressed(const uint8_t bind);
/**
* Checks if a specific input bind was released this frame.
*
* @param bind The input bind to check.
* @return true if the bind was released this frame, false otherwise.
*/
bool_t inputReleased(const uint8_t bind);

View File

@ -10,19 +10,25 @@
#include "world/chunk.h"
#include "display/scene.h"
#include "world/overworld.h"
#include "input.h"
// Press F5 to compile and run the compiled game.gb in the Emulicous Emulator/Debugger
void main(void) {
renderInit();
inputInit();
overworldInit();
SCENE_CURRENT = SCENE_OVERWORLD;
while(!renderShouldExit()) {
RENDER_FRAME++;
renderDraw();
overworldUpdate();
// Update input for next frame.
inputUpdate();
}
renderDispose();

View File

@ -149,7 +149,6 @@ void chunkMapShift(const int16_t x, const int16_t y) {
);
}
void chunkMapSetPosition(const uint16_t x, const uint16_t y) {
if(x == CHUNK_MAP.topLeftX && y == CHUNK_MAP.topLeftY) {
return;
@ -181,18 +180,63 @@ void chunkLoad(chunk_t *chunk, const uint16_t x, const uint16_t y) {
return;
}
const uint8_t *chunkLayerBase = WORLD_CHUNKS_BASE[y * WORLD_WIDTH + x];
if(chunkLayerBase == NULL) {
const chunkdata_t *chunkData = WORLD_CHUNKS[y * WORLD_WIDTH + x];
if(chunkData == NULL) {
memorySet(chunk->tiles, 0, sizeof(chunk->tiles));
return;
}
// Load tile data into chunk
printf("Loading chunk at (%u, %u)\n", x, y);
memoryCopy(
chunk->tiles,
chunkLayerBase,
chunkData->layerBase,
sizeof(chunk->tiles)
);
// Load chunk entities
const chunkentity_t *data;
entity_t *entity;
data = chunkData->entities;
while(data < chunkData->entities + CHUNK_ENTITY_COUNT_MAX) {
if(data->type == ENTITY_TYPE_NULL) break;
// Check entity isn't loaded (still).
entity = ENTITIES;
do {
if(entity->id == data->id) break;
entity++;
} while(entity < ENTITIES + ENTITY_COUNT_MAX);
if(entity != ENTITIES + ENTITY_COUNT_MAX) {
// Entity is already loaded, skip it.
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++;
};
// Initialize this entity.
entityInit(entity, data->type);
entity->id = data->id;
// Positions are chunk-relative.
entity->x = (chunk->x * CHUNK_WIDTH * TILE_WIDTH) + data->x;
entity->y = (chunk->y * CHUNK_HEIGHT * TILE_HEIGHT) + data->y;
// Next entity
data++;
}
}
void chunkUnload(chunk_t *chunk) {

View File

@ -15,10 +15,13 @@
#define CHUNK_WIDTH 8
#define CHUNK_HEIGHT 8
#define CHUNK_TILE_COUNT (CHUNK_WIDTH * CHUNK_HEIGHT)
#define CHUNK_ENTITY_COUNT_MAX 8
typedef struct {
uint16_t x, y;
tile_t tiles[CHUNK_TILE_COUNT];
// int32_t entityIDs[CHUNK_ENTITY_COUNT_MAX];
// uint8_t entityCount;
} chunk_t;
typedef struct {

View File

@ -0,0 +1,23 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "chunk.h"
#include "entity/entity.h"
typedef struct {
uint32_t id;
entitytype_t type;
uint8_t x, y;
entitydir_t dir;
} chunkentity_t;
typedef struct {
uint8_t layerBase[CHUNK_TILE_COUNT];
uint8_t layerOverlay[CHUNK_TILE_COUNT];
chunkentity_t entities[CHUNK_ENTITY_COUNT_MAX];
} chunkdata_t;

View File

@ -9,19 +9,24 @@
#include "chunk.h"
#include "display/render.h"
#include "assert/assert.h"
#include "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)"
@ -30,6 +35,11 @@ void overworldUpdate() {
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);
uint16_t x, y;
if(OVERWORLD_CAMERA_X < RENDER_WIDTH / 2) {

View File

@ -27,6 +27,7 @@ target_include_directories(${DUSK_TARGET_NAME}
# Sources
target_sources(${DUSK_TARGET_NAME}
PRIVATE
input.c
)
# Subdirs

View File

@ -9,6 +9,7 @@
#include "world/chunk.h"
#include "world/overworld.h"
#include "display/render.h"
#include "assert/assert.h"
Camera2D DRAW_OVERWORLD_CAMERA = { 0 };
@ -36,6 +37,7 @@ void drawOverworldDraw(void) {
BeginMode2D(DRAW_OVERWORLD_CAMERA);
// Bottom layer
chunk_t *chunk = CHUNK_MAP.chunks;
do {
DrawRectangle(
@ -48,5 +50,62 @@ void drawOverworldDraw(void) {
chunk++;
} while(chunk < CHUNK_MAP.chunks + CHUNK_MAP_COUNT);
// Entities
entity_t *entity = ENTITIES;
do {
drawOverworldDrawEntity(entity++);
} while(entity->type != ENTITY_TYPE_NULL);
EndMode2D();
}
void drawOverworldDrawEntity(const entity_t *entity) {
assertNotNull(entity, "Entity pointer cannot be NULL");
uint32_t x = (uint32_t)floorf(entity->x);
uint32_t y = (uint32_t)floorf(entity->y);
DrawRectangle(
x,
y,
TILE_WIDTH,
TILE_HEIGHT,
(entity->type == ENTITY_TYPE_PLAYER) ? BLUE : YELLOW
);
switch(entity->dir) {
case ENTITY_DIR_NORTH:
DrawTriangle(
(Vector2){ x + TILE_WIDTH / 2, y },
(Vector2){ x, y + TILE_HEIGHT },
(Vector2){ x + TILE_WIDTH, y + TILE_HEIGHT },
WHITE
);
break;
case ENTITY_DIR_SOUTH:
DrawTriangle(
(Vector2){ x + TILE_WIDTH / 2, y + TILE_HEIGHT },
(Vector2){ x + TILE_WIDTH, y },
(Vector2){ x, y },
WHITE
);
break;
case ENTITY_DIR_WEST:
DrawTriangle(
(Vector2){ x, y + TILE_HEIGHT / 2 },
(Vector2){ x + TILE_WIDTH, y + TILE_HEIGHT },
(Vector2){ x + TILE_WIDTH, y },
WHITE
);
break;
case ENTITY_DIR_EAST:
DrawTriangle(
(Vector2){ x + TILE_WIDTH, y + TILE_HEIGHT / 2 },
(Vector2){ x, y },
(Vector2){ x, y + TILE_HEIGHT },
WHITE
);
break;
}
}

View File

@ -7,6 +7,7 @@
#pragma once
#include "duskraylib.h"
#include "entity/entity.h"
/**
* Initializes the overworld drawing system.
@ -16,4 +17,11 @@ void drawOverworldInit(void);
/**
* Renders the overworld, including map and characters.
*/
void drawOverworldDraw(void);
void drawOverworldDraw(void);
/**
* Renders a specific entity in the overworld.
*
* @param entity The entity to render.
*/
void drawOverworldDrawEntity(const entity_t *entity);

45
src/duskraylib/input.c Normal file
View File

@ -0,0 +1,45 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "duskraylib.h"
#include "input.h"
typedef struct {
int32_t key;
uint8_t bind;
} inputmap_t;
inputmap_t INPUT_MAP[] = {
{ KEY_UP, INPUT_BIND_UP },
{ KEY_W, INPUT_BIND_UP },
{ KEY_DOWN, INPUT_BIND_DOWN },
{ KEY_S, INPUT_BIND_DOWN },
{ KEY_LEFT, INPUT_BIND_LEFT },
{ KEY_A, INPUT_BIND_LEFT },
{ KEY_RIGHT, INPUT_BIND_RIGHT },
{ KEY_D, INPUT_BIND_RIGHT },
{ KEY_SPACE, INPUT_BIND_ACTION },
{ KEY_E, INPUT_BIND_ACTION },
{ KEY_ENTER, INPUT_BIND_ACTION },
{ KEY_ESCAPE, INPUT_BIND_CANCEL },
{ KEY_Q, INPUT_BIND_CANCEL },
{ KEY_BACKSPACE, INPUT_BIND_CANCEL },
{ 0, 0 }
};
uint8_t inputStateGet() {
uint8_t state = 0;
inputmap_t *map = INPUT_MAP;
do {
if(IsKeyDown(map->key)) state |= map->bind;
map++;
} while(map->key != 0);
return state;
}