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

@@ -13,8 +13,8 @@ option(ENABLE_TESTS "Enable tests" ON)
# Set target system
if(NOT DEFINED DUSK_TARGET_SYSTEM)
# set(DUSK_TARGET_SYSTEM "linux")
set(DUSK_TARGET_SYSTEM "psp")
set(DUSK_TARGET_SYSTEM "linux")
# set(DUSK_TARGET_SYSTEM "psp")
endif()
# Prep cache

View File

@@ -7,7 +7,7 @@
#pragma once
#include "error/error.h"
#include "rpg/world/chunk.h"
#include "rpg/overworld/chunk.h"
typedef struct assetcustom_s assetcustom_t;

View File

@@ -7,7 +7,7 @@
#pragma once
#include "error/error.h"
#include "rpg/world/map.h"
#include "rpg/overworld/map.h"
#include "display/mesh/mesh.h"
/**

View File

@@ -9,7 +9,7 @@
#include "script/scriptcontext.h"
#include "debug/debug.h"
#include "assert/assert.h"
#include "rpg/world/map.h"
#include "rpg/overworld/map.h"
int moduleMapLoad(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");

View File

@@ -14,4 +14,4 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME}
# Subdirs
add_subdirectory(cutscene)
add_subdirectory(entity)
add_subdirectory(world)
add_subdirectory(overworld)

View File

@@ -11,7 +11,7 @@
#include "time/time.h"
#include "util/math.h"
#include "rpg/cutscene/cutscenemode.h"
#include "rpg/world/map.h"
#include "rpg/overworld/map.h"
entity_t ENTITIES[ENTITY_COUNT];

View File

@@ -6,7 +6,7 @@
*/
#pragma once
#include "rpg/world/worldpos.h"
#include "rpg/overworld/worldpos.h"
typedef enum {
ENTITY_DIR_UP = ENTITY_DIR_NORTH,

View File

@@ -6,7 +6,7 @@
*/
#pragma once
#include "rpg/world/tile.h"
#include "rpg/overworld/tile.h"
#include "worldpos.h"
#include "display/mesh/quad.h"

View File

@@ -6,7 +6,7 @@
*/
#pragma once
#include "rpg/world/chunk.h"
#include "rpg/overworld/chunk.h"
#define MAP_FILE_PATH_MAX 128

View File

@@ -7,7 +7,7 @@
#include "rpg.h"
#include "entity/entity.h"
#include "rpg/world/map.h"
#include "rpg/overworld/map.h"
#include "rpg/cutscene/cutscenesystem.h"
#include "time/time.h"
#include "rpgcamera.h"

View File

@@ -8,7 +8,7 @@
#include "rpgcamera.h"
#include "util/memory.h"
#include "rpg/entity/entity.h"
#include "rpg/world/map.h"
#include "rpg/overworld/map.h"
#include "assert/assert.h"
rpgcamera_t RPG_CAMERA;

View File

@@ -6,7 +6,7 @@
*/
#pragma once
#include "rpg/world/worldpos.h"
#include "rpg/overworld/worldpos.h"
#include "error/error.h"
typedef enum {

View File

@@ -0,0 +1,13 @@
# 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
scenemanager.c
)
# Subdirs
add_subdirectory(scene)

24
archive/scene/scene.h Normal file
View File

@@ -0,0 +1,24 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "dusk.h"
#include "error/error.h"
#include "display/color.h"
#define SCENE_FLAG_INITIALIZED (1 << 0)
typedef struct scenedata_s scenedata_t;
typedef struct {
const char_t *name;
errorret_t (*init)(scenedata_t *data);
void (*update)(scenedata_t *data);
void (*render)(scenedata_t *data);
void (*dispose)(scenedata_t *data);
uint8_t flags;
} scene_t;

View File

@@ -11,7 +11,7 @@
#include "assert/assert.h"
#include "asset/asset.h"
#include "rpg/entity/entity.h"
#include "rpg/world/map.h"
#include "rpg/overworld/map.h"
#include "display/screen.h"
#include "rpg/rpgcamera.h"
#include "util/memory.h"

View File

@@ -9,11 +9,4 @@
#include "scene/scene.h"
#include "scene/scene/scenetest.h"
#include "scene/scene/scenemap.h"
typedef struct scenedata_s {
union {
scenetest_t sceneTest;
scenemap_t sceneMap;
};
} scenedata_t;
#include "scene/scene/scenemap.h"

View File

@@ -15,7 +15,6 @@ typedef struct {
scene_t *current;
scene_t *scenes[SCENE_MANAGER_SCENE_COUNT_MAX];
uint8_t sceneCount;
scenedata_t sceneData;
} scenemanager_t;
extern scenemanager_t SCENE_MANAGER;

View File

@@ -1,7 +1,6 @@
module('platform')
module('input')
module('scene')
module('map')
-- Default Input bindings.
if PLATFORM == "psp" then
@@ -39,4 +38,4 @@ else
end
sceneSet('map')
mapLoad('map/testmap/testmap.dmf')
-- mapLoad('map/testmap/testmap.dmf')

View File

@@ -56,8 +56,9 @@ add_subdirectory(display)
add_subdirectory(engine)
add_subdirectory(error)
add_subdirectory(input)
add_subdirectory(item)
add_subdirectory(locale)
add_subdirectory(rpg)
# add_subdirectory(rpg)
add_subdirectory(scene)
add_subdirectory(script)
add_subdirectory(thread)

View File

@@ -9,8 +9,6 @@
#include "type/assetpaletteimage.h"
#include "type/assetalphaimage.h"
#include "type/assetlanguage.h"
#include "type/assetmap.h"
#include "type/assetchunk.h"
#include "type/assetscript.h"
#include <zip.h>
@@ -20,8 +18,6 @@ typedef enum {
ASSET_TYPE_PALETTE_IMAGE,
ASSET_TYPE_ALPHA_IMAGE,
ASSET_TYPE_LANGUAGE,
ASSET_TYPE_MAP,
ASSET_TYPE_CHUNK,
ASSET_TYPE_SCRIPT,
ASSET_TYPE_COUNT,
@@ -72,19 +68,6 @@ static const assettypedef_t ASSET_TYPE_DEFINITIONS[ASSET_TYPE_COUNT] = {
.custom = assetLanguageHandler
},
[ASSET_TYPE_MAP] = {
.header = "DMF",
.loadStrategy = ASSET_LOAD_STRAT_ENTIRE,
.dataSize = sizeof(1),
.entire = assetMapLoad
},
[ASSET_TYPE_CHUNK] = {
.header = "DCF",
.loadStrategy = ASSET_LOAD_STRAT_CUSTOM,
.custom = assetChunkLoad
},
[ASSET_TYPE_SCRIPT] = {
.header = "DSF",
.loadStrategy = ASSET_LOAD_STRAT_CUSTOM,

View File

@@ -9,7 +9,5 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME}
assetalphaimage.c
assetpaletteimage.c
assetlanguage.c
assetmap.c
assetchunk.c
assetscript.c
)

View File

@@ -45,7 +45,7 @@ typedef color4b_t color_t;
#define color4b(r, g, b, a) ((color4b_t){r, g, b, a})
#define color(r, g, b, a) ((color_t){r, g, b, a})
#define color_hex(hex) color( \
#define colorHex(hex) color( \
((hex >> 24) & 0xFF), \
((hex >> 16) & 0xFF), \
((hex >> 8) & 0xFF), \

View File

@@ -8,7 +8,7 @@
#include "display/display.h"
#include "engine/engine.h"
#include "display/framebuffer.h"
#include "scene/scenemanager.h"
#include "scene/scene.h"
#include "display/spritebatch.h"
#include "display/mesh/quad.h"
#include "display/screen.h"
@@ -120,7 +120,9 @@ errorret_t displayUpdate(void) {
FRAMEBUFFER_CLEAR_COLOR | FRAMEBUFFER_CLEAR_DEPTH,
COLOR_CORNFLOWER_BLUE
);
sceneManagerRender();
sceneRender();
// Render UI
uiRender();
// Finish up

View File

@@ -11,10 +11,10 @@
#include "input/input.h"
#include "locale/localemanager.h"
#include "display/display.h"
#include "scene/scenemanager.h"
#include "scene/scene.h"
#include "asset/asset.h"
#include "ui/ui.h"
#include "rpg/rpg.h"
// #include "rpg/rpg.h"
#include "script/scriptmanager.h"
#include "debug/debug.h"
@@ -35,8 +35,8 @@ errorret_t engineInit(const int32_t argc, const char_t **argv) {
errorChain(scriptManagerInit());
errorChain(displayInit());
errorChain(uiInit());
errorChain(rpgInit());
errorChain(sceneManagerInit());
// errorChain(rpgInit());
errorChain(sceneInit());
// Run the initial script.
scriptcontext_t ctx;
@@ -51,9 +51,9 @@ errorret_t engineUpdate(void) {
timeUpdate();
inputUpdate();
errorChain(rpgUpdate());
// errorChain(rpgUpdate());
uiUpdate();
sceneManagerUpdate();
sceneUpdate();
errorChain(displayUpdate());
if(inputPressed(INPUT_ACTION_RAGEQUIT)) ENGINE.running = false;
@@ -67,8 +67,8 @@ void engineExit(void) {
errorret_t engineDispose(void) {
localeManagerDispose();
sceneManagerDispose();
rpgDispose();
// sceneManagerDispose();
// rpgDispose();
uiDispose();
errorChain(displayDispose());
assetDispose();

11
src/item/CMakeLists.txt Normal file
View File

@@ -0,0 +1,11 @@
# 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
item.c
inventory.c
)

165
src/item/inventory.c Normal file
View File

@@ -0,0 +1,165 @@
/**
* Copyright (c) 2026 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,
inventorystack_t* storage,
uint8_t storageSize
) {
assertNotNull(inventory, "Inventory pointer is NULL.");
assertNotNull(storage, "Storage pointer is NULL.");
assertTrue(storageSize > 0, "Storage size must be greater than zero.");
inventory->storage = storage;
inventory->storageSize = storageSize;
// Zero item ids.
memoryZero(inventory->storage, sizeof(inventorystack_t) * storageSize);
}
bool_t inventoryItemExists(const inventory_t *inventory, const itemid_t item) {
assertNotNull(inventory, "Inventory pointer is NULL.");
assertNotNull(inventory->storage, "Storage pointer is NULL.");
assertTrue(inventory->storageSize > 0, "Storage too small.");
assertTrue(item != ITEM_ID_NULL, "Item ID cannot be ITEM_ID_NULL.");
inventorystack_t *stack = inventory->storage;
inventorystack_t *end = stack + inventory->storageSize;
do {
if(stack->item == ITEM_ID_NULL) break;
if(stack->item != item) continue;
assertTrue(stack->quantity > 0, "Item has quantity zero.");
return true;
} while(++stack < end);
return false;
}
void inventorySet(
inventory_t *inventory,
const itemid_t item,
const uint8_t quantity
) {
assertNotNull(inventory, "Inventory pointer is NULL.");
assertNotNull(inventory->storage, "Storage pointer is NULL.");
assertTrue(inventory->storageSize > 0, "Storage too small.");
assertTrue(item != ITEM_ID_NULL, "Item ID cannot be ITEM_ID_NULL.");
// If quantity 0, remove.
if(quantity == 0) return inventoryRemove(inventory, item);
// Search for existing stack.
inventorystack_t *stack = inventory->storage;
inventorystack_t *end = stack + inventory->storageSize;
do {
// Not in inventory yet, add as new stack.
if(stack->item == ITEM_ID_NULL) {
stack->item = item;
stack->quantity = quantity;
return;
}
// Not the stack we're looking for.
if(stack->item != item) continue;
// Update existing stack.
stack->quantity = quantity;
return;
} while(++stack < end);
// No space in the inventory.
assertUnreachable("Inventory is full, cannot set more items.");
}
void inventoryAdd(
inventory_t *inventory,
const itemid_t item,
const uint8_t quantity
) {
uint8_t current = inventoryGetCount(inventory, item);
uint16_t newQuantity = (uint16_t)current + (uint16_t)quantity;
assertTrue(
newQuantity <= UINT8_MAX,
"Cannot add item, would overflow maximum quantity."
);
inventorySet(inventory, item, (uint8_t)newQuantity);
}
void inventoryRemove(inventory_t *inventory, const itemid_t item) {
assertNotNull(inventory, "Inventory pointer is NULL.");
assertNotNull(inventory->storage, "Storage pointer is NULL.");
assertTrue(inventory->storageSize > 0, "Storage too small.");
assertTrue(item != ITEM_ID_NULL, "Item ID cannot be ITEM_ID_NULL.");
inventorystack_t *stack = inventory->storage;
inventorystack_t *end = stack + inventory->storageSize;
// Search for existing stack.
do {
// End of inventory, item not present.
if(stack->item == ITEM_ID_NULL) break;
// Not matching stack.
if(stack->item != item) continue;
// Match found, shift everything else down
memoryMove(stack, stack + 1, end - (stack + 1));
// Clear last stack.
inventorystack_t *last = end - 1;
last->item = ITEM_ID_NULL;
break;
} while(++stack < end);
}
uint8_t inventoryGetCount(const inventory_t *inventory, const itemid_t item) {
assertNotNull(inventory, "Inventory pointer is NULL.");
assertNotNull(inventory->storage, "Storage pointer is NULL.");
assertTrue(inventory->storageSize > 0, "Storage too small.");
assertTrue(item != ITEM_ID_NULL, "Item ID cannot be ITEM_ID_NULL.");
inventorystack_t *stack = inventory->storage;
inventorystack_t *end = stack + inventory->storageSize;
do {
// End of inventory, item not present.
if(stack->item == ITEM_ID_NULL) break;
// Not matching stack.
if(stack->item != item) continue;
// Match found, return quantity.
return stack->quantity;
} while(++stack < end);
return 0;
}
bool_t inventoryIsFull(const inventory_t *inventory) {
assertNotNull(inventory, "Inventory pointer is NULL.");
assertNotNull(inventory->storage, "Storage pointer is NULL.");
assertTrue(inventory->storageSize > 0, "Storage too small.");
inventorystack_t *stack = inventory->storage;
inventorystack_t *end = stack + inventory->storageSize;
do {
// Found empty stack, not full.
if(stack->item == ITEM_ID_NULL) return false;
} while(++stack < end);
return true;
}
bool_t inventoryItemFull(const inventory_t *inventory, const itemid_t item) {
return inventoryGetCount(inventory, item) == ITEM_STACK_QUANTITY_MAX;
}

103
src/item/inventory.h Normal file
View File

@@ -0,0 +1,103 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "item.h"
#define ITEM_STACK_QUANTITY_MAX UINT8_MAX
typedef struct {
itemid_t item;
uint8_t quantity;
} inventorystack_t;
typedef struct {
inventorystack_t *storage;
uint8_t storageSize;
} inventory_t;
/**
* Initializes an inventory.
*
* @param inventory The inventory to initialize.
* @param storage The storage array for the inventory.
* @param storageSize The size of the storage array.
*/
void inventoryInit(
inventory_t* inventory,
inventorystack_t* storage,
uint8_t storageSize
);
/**
* Checks if a specific item exists in the inventory (and has quantity > 0).
*
* @param inventory The inventory to check.
* @param item The item ID to check.
* @return true if the item exists, false otherwise.
*/
bool_t inventoryItemExists(const inventory_t *inventory, const itemid_t item);
/**
* Sets the quantity of a specific item in the inventory.
*
* @param inventory The inventory to modify.
* @param item The item ID to set.
* @param quantity The quantity to set.
*/
void inventorySet(
inventory_t *inventory,
const itemid_t item,
const uint8_t quantity
);
/**
* Adds a specific quantity of an item to the inventory.
*
* @param inventory The inventory to modify.
* @param item The item ID to add.
* @param quantity The quantity to add.
*/
void inventoryAdd(
inventory_t *inventory,
const itemid_t item,
const uint8_t quantity
);
/**
* Removes an item from the inventory.
*
* @param inventory The inventory to modify.
* @param item The item ID to remove.
*/
void inventoryRemove(inventory_t *inventory, const itemid_t item);
/**
* Gets the count of a specific item in the inventory.
*
* @param inventory The inventory to check.
* @param item The item ID to check.
* @return The count of the item in the inventory.
*/
uint8_t inventoryGetCount(const inventory_t *inventory, const itemid_t item);
/**
* Checks if the inventory is full.
*
* @param inventory The inventory to check.
* @return true if full, false otherwise.
*/
bool_t inventoryIsFull(const inventory_t *inventory);
/**
* Checks if a specific item stack is full in the inventory.
*
* @param inventory The inventory to check.
* @param item The item ID to check.
* @return true if the item stack is full, false otherwise.
*/
bool_t inventoryItemFull(const inventory_t *inventory, const itemid_t item);

19
src/item/item.c Normal file
View File

@@ -0,0 +1,19 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "item.h"
const item_t ITEMS[] = {
[ITEM_ID_NULL] = {
.type = ITEM_TYPE_NULL
},
// Potion
[ITEM_ID_POTION] = {
.type = ITEM_TYPE_MEDICINE
},
};

21
src/item/item.h Normal file
View File

@@ -0,0 +1,21 @@
/**
* Copyright (c) 2026 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;
} item_t;
typedef uint8_t itemid_t;
#define ITEM_ID_NULL 0
#define ITEM_ID_POTION 1
#define ITEM_ID_POTATO 2
extern const item_t ITEMS[];

19
src/item/itemtype.h Normal file
View File

@@ -0,0 +1,19 @@
/**
* Copyright (c) 2026 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,
ITEM_TYPE_MEDICINE,
ITEM_TYPE_INGREDIENT,
ITEM_TYPE_KEYITEM,
ITEM_TYPE_COUNT
} itemtype_t;

View File

@@ -6,8 +6,5 @@
# Sources
target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
scenemanager.c
)
# Subdirs
add_subdirectory(scene)
scene.c
)

84
src/scene/scene.c Normal file
View File

@@ -0,0 +1,84 @@
// Copyright (c) 2026 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
#include "scene.h"
#include "assert/assert.h"
#include "util/string.h"
const scene_t SCENES[] = {
};
uint_fast8_t SCENE_CURRENT = 0xFF;
errorret_t sceneInit(void) {
// Initialize the scene subsystem here
errorOk();
}
void sceneUpdate(void) {
const scene_t *current = sceneGetCurrent();
if(current && current->update) {
current->update();
}
}
void sceneRender(void) {
const scene_t *current = sceneGetCurrent();
if(current && current->render) {
current->render();
}
}
void sceneDispose(void) {
const scene_t *current = sceneGetCurrent();
if(current && current->dispose) {
current->dispose();
}
}
errorret_t sceneSet(const scene_t *scene) {
sceneDispose();
if(scene) {
SCENE_CURRENT = (uint_fast8_t)(scene - SCENES);
assertTrue(
SCENE_CURRENT < sizeof(SCENES) / sizeof(scene_t),
"Invalid scene index."
);
if(scene->init) {
errorret_t err = scene->init();
if(err.code != ERROR_OK) SCENE_CURRENT = 0xFF;
errorChain(err);
}
} else {
SCENE_CURRENT = 0xFF;
}
errorOk();
}
const scene_t* sceneGetCurrent(void) {
if(SCENE_CURRENT == 0xFF) return NULL;
assertTrue(
SCENE_CURRENT < sizeof(SCENES) / sizeof(scene_t),
"Invalid current scene index."
);
return &SCENES[SCENE_CURRENT];
}
const scene_t* sceneGetByName(const char_t *name) {
for(uint_fast8_t i = 0; i < sizeof(SCENES) / sizeof(scene_t); i++) {
if(stringCompare(SCENES[i].name, name) == 0) {
return &SCENES[i];
}
}
return NULL;
}

View File

@@ -1,24 +1,63 @@
/**
* Copyright (c) 2025 Dominic Masters
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "dusk.h"
#include "error/error.h"
#include "display/color.h"
#define SCENE_FLAG_INITIALIZED (1 << 0)
typedef struct scenedata_s scenedata_t;
typedef struct {
const char_t *name;
errorret_t (*init)(scenedata_t *data);
void (*update)(scenedata_t *data);
void (*render)(scenedata_t *data);
void (*dispose)(scenedata_t *data);
uint8_t flags;
} scene_t;
errorret_t (*init)(void);
void (*update)(void);
void (*render)(void);
void (*dispose)(void);
} scene_t;
extern const scene_t SCENES[];
extern uint_fast8_t SCENE_CURRENT;
/**
* Initialize the scene subsystem.
*/
errorret_t sceneInit(void);
/**
* Update the current scene.
*/
void sceneUpdate(void);
/**
* Render the current scene.
*/
void sceneRender(void);
/**
* Dispose of the scene subsystem.
*/
void sceneDispose(void);
/**
* Set the current scene.
*
* @param sceneIndex The index of the scene to set.
* @return An error code indicating success or failure.
*/
errorret_t sceneSet(const scene_t *scene);
/**
* Get the current scene.
*
* @return The current scene.
*/
const scene_t* sceneGetCurrent(void);
/**
* Get a scene by its name.
*
* @param name The name of the scene.
* @return The scene with the given name, or NULL if not found.
*/
const scene_t* sceneGetByName(const char_t *name);

View File

@@ -7,7 +7,7 @@
#pragma once
#include "script/scriptcontext.h"
#include "scene/scenemanager.h"
#include "scene/scene.h"
int moduleSceneSetScene(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
@@ -20,13 +20,13 @@ int moduleSceneSetScene(lua_State *L) {
return 0;
}
scene_t *scene = sceneManagerGetSceneByName(sceneName);
const scene_t *scene = sceneGetByName(sceneName);
if(scene == NULL) {
luaL_error(L, "Scene '%s' not found", sceneName);
return 0;
}
errorret_t err = sceneManagerSetScene(scene);
errorret_t err = sceneSet(scene);
if(err.code != ERROR_OK) {
luaL_error(L, "Failed to set scene '%s'", sceneName);
return 0;

View File

@@ -10,14 +10,12 @@
#include "script/module/moduleinput.h"
#include "script/module/moduleplatform.h"
#include "script/module/modulescene.h"
#include "script/module/modulemap.h"
const scriptmodule_t SCRIPT_MODULE_LIST[] = {
{ .name = "system", .callback = moduleSystem },
{ .name = "input", .callback = moduleInput },
{ .name = "platform", .callback = modulePlatform },
{ .name = "scene", .callback = moduleScene },
{ .name = "map", .callback = moduleMapSystem },
};
#define SCRIPT_MODULE_COUNT ( \

View File

@@ -26,6 +26,7 @@ void timeInit(void) {
TIME.dynamicTime = TIME_STEP;
TIME.dynamicDelta = TIME_STEP;
TIME.dynamicUpdate = false;
TIME.lastNonDynamic = TIME.dynamicTime;
#endif
}
@@ -44,8 +45,9 @@ void timeUpdate(void) {
assertTrue(TIME.dynamicDelta >= 0.0f, "Time delta is negative");
// Is within 1ms of a full step?
if(TIME.dynamicTime - TIME.time >= TIME_STEP * 0.999f) {
if(TIME.dynamicTime - TIME.lastNonDynamic >= TIME_STEP * 0.999f) {
TIME.dynamicUpdate = false;
TIME.lastNonDynamic = TIME.dynamicTime;
TIME.delta = TIME_STEP;
TIME.time += TIME_STEP;
}

View File

@@ -18,6 +18,7 @@ typedef struct {
float_t time;
#if TIME_FIXED == 0
float_t lastNonDynamic;
bool_t dynamicUpdate;
float_t dynamicDelta;
float_t dynamicTime;

View File

@@ -11,7 +11,7 @@
#include "util/memory.h"
#include "display/tileset/tileset_minogram.h"
#include "display/screen.h"
#include "ui/uitextbox.h"
// #include "ui/uitextbox.h"
ui_t UI;
@@ -33,7 +33,7 @@ void uiUpdate(void) {
UI.camera.orthographic.top = 0;
UI.camera.orthographic.bottom = SCREEN.height;
uiTextboxUpdate();
// uiTextboxUpdate();
}
void uiRender(void) {
@@ -42,7 +42,7 @@ void uiRender(void) {
// Render UI elements here
if(UI.fontTexture.width > 0) {
uiDebugRender(UI.fontTileset, &UI.fontTexture);
uiTextboxRender();
// uiTextboxRender();
}
cameraPopMatrix();
}

View File

@@ -11,7 +11,7 @@
#include "ui/uitext.h"
#include "display/screen.h"
#include "display/spritebatch.h"
#include "rpg/entity/entity.h"
// #include "rpg/entity/entity.h"
bool_t UI_DEBUG_DRAW = true;
@@ -67,32 +67,32 @@ void uiDebugRender(const tileset_t *tileset, texture_t *texture) {
// Player position
entity_t *player = NULL;
for(uint8_t i = 0; i < ENTITY_COUNT; i++) {
if(ENTITIES[i].type != ENTITY_TYPE_PLAYER) continue;
player = &ENTITIES[i];
break;
}
if(player == NULL) {
snprintf(buffer, sizeof(buffer), "Player: N/A");
} else {
snprintf(
buffer,
sizeof(buffer),
"%d,%d,%d/%d/%d",
player->position.x,
player->position.y,
player->position.z,
(int32_t)player->direction,
(int32_t)player->animation
);
}
uiTextMeasure(buffer, tileset, &w, &h);
uiTextDraw(
SCREEN.width - w, hOffset,
buffer, COLOR_GREEN, tileset, texture
);
hOffset += h;
// entity_t *player = NULL;
// for(uint8_t i = 0; i < ENTITY_COUNT; i++) {
// if(ENTITIES[i].type != ENTITY_TYPE_PLAYER) continue;
// player = &ENTITIES[i];
// break;
// }
// if(player == NULL) {
// snprintf(buffer, sizeof(buffer), "Player: N/A");
// } else {
// snprintf(
// buffer,
// sizeof(buffer),
// "%d,%d,%d/%d/%d",
// player->position.x,
// player->position.y,
// player->position.z,
// (int32_t)player->direction,
// (int32_t)player->animation
// );
// }
// uiTextMeasure(buffer, tileset, &w, &h);
// uiTextDraw(
// SCREEN.width - w, hOffset,
// buffer, COLOR_GREEN, tileset, texture
// );
// hOffset += h;
spriteBatchFlush();
}

View File

@@ -8,38 +8,38 @@
#include "uitextbox.h"
#include "ui/ui.h"
#include "ui/uitext.h"
#include "rpg/rpgtextbox.h"
// #include "rpg/rpgtextbox.h"
#include "display/screen.h"
#include "display/spritebatch.h"
#include "input/input.h"
void uiTextboxUpdate() {
if(!rpgTextboxIsVisible()) return;
// void uiTextboxUpdate() {
// if(!rpgTextboxIsVisible()) return;
if(inputPressed(INPUT_ACTION_ACCEPT)) {
rpgTextboxHide();
}
}
// if(inputPressed(INPUT_ACTION_ACCEPT)) {
// rpgTextboxHide();
// }
// }
void uiTextboxRender() {
if(!rpgTextboxIsVisible()) return;
// void uiTextboxRender() {
// if(!rpgTextboxIsVisible()) return;
const char_t *text = RPG_TEXTBOX.text;
int32_t textWidth, textHeight;
// const char_t *text = RPG_TEXTBOX.text;
// int32_t textWidth, textHeight;
uiTextMeasure(text, UI.fontTileset, &textWidth, &textHeight);
// uiTextMeasure(text, UI.fontTileset, &textWidth, &textHeight);
float_t y = 0;
if(RPG_TEXTBOX.position == RPG_TEXTBOX_POS_BOTTOM) {
y = SCREEN.height - (float_t)textHeight;
}
// float_t y = 0;
// if(RPG_TEXTBOX.position == RPG_TEXTBOX_POS_BOTTOM) {
// y = SCREEN.height - (float_t)textHeight;
// }
spriteBatchPush(
NULL,
0.0f, y,
(float_t)SCREEN.width, (float_t)(y + textHeight),
COLOR_BLACK,
0.0f, 0.0f, 1.0f, 1.0f
);
uiTextDraw(0, y, text, COLOR_RED, UI.fontTileset, &UI.fontTexture);
}
// spriteBatchPush(
// NULL,
// 0.0f, y,
// (float_t)SCREEN.width, (float_t)(y + textHeight),
// COLOR_BLACK,
// 0.0f, 0.0f, 1.0f, 1.0f
// );
// uiTextDraw(0, y, text, COLOR_RED, UI.fontTileset, &UI.fontTexture);
// }

View File

@@ -3,5 +3,8 @@
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
add_subdirectory(display)
# add_subdirectory(rpg)
add_subdirectory(item)
add_subdirectory(time)
add_subdirectory(util)

View File

@@ -0,0 +1,9 @@
# Copyright (c) 2026 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
include(dusktest)
# Tests
dusktest(test_color.c)

98
test/display/test_color.c Normal file
View File

@@ -0,0 +1,98 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "dusktest.h"
#include "display/color.h"
static void test_color3f_create(void **state) {
(void)state;
color3f_t color = color3f(0.1f, 0.2f, 0.3f);
assert_float_equal(color.r, 0.1f, 0.0001f);
assert_float_equal(color.g, 0.2f, 0.0001f);
assert_float_equal(color.b, 0.3f, 0.0001f);
}
static void test_color4f_create(void **state) {
(void)state;
color4f_t color = color4f(0.1f, 0.2f, 0.3f, 0.4f);
assert_float_equal(color.r, 0.1f, 0.0001f);
assert_float_equal(color.g, 0.2f, 0.0001f);
assert_float_equal(color.b, 0.3f, 0.0001f);
assert_float_equal(color.a, 0.4f, 0.0001f);
}
static void test_color3b_create(void **state) {
(void)state;
color3b_t color = color3b(10, 20, 30);
assert_int_equal(color.r, 10);
assert_int_equal(color.g, 20);
assert_int_equal(color.b, 30);
}
static void test_color4b_create(void **state) {
(void)state;
color4b_t color = color4b(10, 20, 30, 40);
assert_int_equal(color.r, 10);
assert_int_equal(color.g, 20);
assert_int_equal(color.b, 30);
assert_int_equal(color.a, 40);
}
static void test_color_create(void **state) {
(void)state;
color_t color = color(10, 20, 30, 40);
assert_int_equal(color.r, 10);
assert_int_equal(color.g, 20);
assert_int_equal(color.b, 30);
assert_int_equal(color.a, 40);
}
static void test_colorHex_create(void **state) {
(void)state;
color_t color = colorHex(0x11223344);
assert_int_equal(color.r, 0x11);
assert_int_equal(color.g, 0x22);
assert_int_equal(color.b, 0x33);
assert_int_equal(color.a, 0x44);
color = colorHex(0xFF00FF00);
assert_int_equal(color.r, 0xFF);
assert_int_equal(color.g, 0x00);
assert_int_equal(color.b, 0xFF);
assert_int_equal(color.a, 0x00);
color_t comp = color(255, 0, 255, 0);
assert_int_equal(color.r, comp.r);
assert_int_equal(color.g, comp.g);
assert_int_equal(color.b, comp.b);
assert_int_equal(color.a, comp.a);
color = colorHex(0xFFFFFFFF);
assert_int_equal(color.r, COLOR_WHITE.r);
assert_int_equal(color.g, COLOR_WHITE.g);
assert_int_equal(color.b, COLOR_WHITE.b);
assert_int_equal(color.a, COLOR_WHITE.a);
}
int main(int argc, char **argv) {
const struct CMUnitTest tests[] = {
cmocka_unit_test(test_color3f_create),
cmocka_unit_test(test_color4f_create),
cmocka_unit_test(test_color3b_create),
cmocka_unit_test(test_color4b_create),
cmocka_unit_test(test_color_create),
cmocka_unit_test(test_colorHex_create),
};
return cmocka_run_group_tests(tests, NULL, NULL);
}

9
test/item/CMakeLists.txt Normal file
View File

@@ -0,0 +1,9 @@
# Copyright (c) 2026 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
include(dusktest)
# Tests
dusktest(test_inventory.c)

341
test/item/test_inventory.c Normal file
View File

@@ -0,0 +1,341 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "dusktest.h"
#include "item/inventory.h"
static void test_inventoryInit(void **state) {
(void)state;
inventorystack_t storage[5];
inventory_t inventory;
inventoryInit(&inventory, storage, 5);
// Should setup struct
assert_non_null(inventory.storage);
assert_int_equal(inventory.storageSize, 5);
// Should zero the item ids.
for(size_t i = 0; i < inventory.storageSize; i++) {
assert_int_equal(inventory.storage[i].item, ITEM_ID_NULL);
assert_int_equal(inventory.storage[i].quantity, 0);
}
// Should fail when given NULL inventory pointer
expect_assert_failure(inventoryInit(NULL, storage, 5));
// Should fail when given NULL storage pointer
expect_assert_failure(inventoryInit(&inventory, NULL, 5));
// Should fail when given zero storage size
expect_assert_failure(inventoryInit(&inventory, storage, 0));
}
static void test_inventoryItemExists(void **state) {
(void)state;
inventorystack_t storage[5];
inventory_t inventory;
inventoryInit(&inventory, storage, 5);
// Initially should not exist
assert_false(inventoryItemExists(&inventory, ITEM_ID_POTION));
// Add item
inventorySet(&inventory, ITEM_ID_POTION, 3);
assert_true(inventoryItemExists(&inventory, ITEM_ID_POTION));
// Remove item
inventorySet(&inventory, ITEM_ID_POTION, 0);
assert_false(inventoryItemExists(&inventory, ITEM_ID_POTION));
// Should fail when given NULL inventory pointer
expect_assert_failure(inventoryItemExists(NULL, ITEM_ID_POTION));
// Should fail when given NULL storage pointer
inventory.storage = NULL;
expect_assert_failure(inventoryItemExists(&inventory, ITEM_ID_POTION));
inventory.storage = storage;
// Should fail when given zero storage size
inventory.storageSize = 0;
expect_assert_failure(inventoryItemExists(&inventory, ITEM_ID_POTION));
inventory.storageSize = 5;
// Should fail when given ITEM_ID_NULL
expect_assert_failure(inventoryItemExists(&inventory, ITEM_ID_NULL));
// Should fail if item has zero quantity somehow
inventorySet(&inventory, ITEM_ID_POTION, 3);
inventory.storage[0].quantity = 0;
expect_assert_failure(inventoryItemExists(&inventory, ITEM_ID_POTION));
}
static void test_inventorySet(void **state) {
(void)state;
inventorystack_t storage[5];
inventory_t inventory;
inventoryInit(&inventory, storage, 5);
// Set item quantity
inventorySet(&inventory, ITEM_ID_POTION, 4);
assert_int_equal(inventory.storage[0].item, ITEM_ID_POTION);
assert_int_equal(inventory.storage[0].quantity, 4);
// Update item quantity
inventorySet(&inventory, ITEM_ID_POTION, 2);
assert_int_equal(inventory.storage[0].item, ITEM_ID_POTION);
assert_int_equal(inventory.storage[0].quantity, 2);
// Remove item by setting quantity to 0
inventorySet(&inventory, ITEM_ID_POTION, 0);
assert_int_equal(inventory.storage[0].item, ITEM_ID_NULL);
// Setting multiple items should not cause issues
inventorySet(&inventory, ITEM_ID_POTION, 1);
inventorySet(&inventory, ITEM_ID_POTATO, 5);
assert_int_equal(inventory.storage[0].item, ITEM_ID_POTION);
assert_int_equal(inventory.storage[0].quantity, 1);
assert_int_equal(inventory.storage[1].item, ITEM_ID_POTATO);
assert_int_equal(inventory.storage[1].quantity, 5);
// Setting early item to 0 should shift others down
inventorySet(&inventory, ITEM_ID_POTION, 0);
assert_int_equal(inventory.storage[0].item, ITEM_ID_POTATO);
assert_int_equal(inventory.storage[0].quantity, 5);
assert_int_equal(inventory.storage[1].item, ITEM_ID_NULL);
// Setting non-existing item should add it
inventorySet(&inventory, ITEM_ID_POTION, 3);
assert_int_equal(inventory.storage[1].item, ITEM_ID_POTION);
assert_int_equal(inventory.storage[1].quantity, 3);
// Should fail when given NULL inventory pointer
expect_assert_failure(inventorySet(NULL, ITEM_ID_POTION, 3));
// Should fail when given NULL storage pointer
inventory.storage = NULL;
expect_assert_failure(inventorySet(&inventory, ITEM_ID_POTION, 3));
inventory.storage = storage;
// Should fail when given zero storage size
inventory.storageSize = 0;
expect_assert_failure(inventorySet(&inventory, ITEM_ID_POTION, 3));
inventory.storageSize = 5;
// Should fail when given ITEM_ID_NULL
expect_assert_failure(inventorySet(&inventory, ITEM_ID_NULL, 3));
}
static void test_inventoryAdd(void **state) {
(void)state;
inventorystack_t storage[5];
inventory_t inventory;
inventoryInit(&inventory, storage, 5);
// Add item quantity
inventoryAdd(&inventory, ITEM_ID_POTION, 4);
assert_int_equal(inventory.storage[0].item, ITEM_ID_POTION);
assert_int_equal(inventory.storage[0].quantity, 4);
// Add more to existing item
inventoryAdd(&inventory, ITEM_ID_POTION, 3);
assert_int_equal(inventory.storage[0].item, ITEM_ID_POTION);
assert_int_equal(inventory.storage[0].quantity, 7);
// Adding item that would overflow should assert
expect_assert_failure(inventoryAdd(&inventory, ITEM_ID_POTION, 250));
// Should fail when given NULL inventory pointer
expect_assert_failure(inventoryAdd(NULL, ITEM_ID_POTION, 3));
// Should fail when given NULL storage pointer
inventory.storage = NULL;
expect_assert_failure(inventoryAdd(&inventory, ITEM_ID_POTION, 3));
inventory.storage = storage;
// Should fail when given zero storage size
inventory.storageSize = 0;
expect_assert_failure(inventoryAdd(&inventory, ITEM_ID_POTION, 3));
inventory.storageSize = 5;
// Should fail when given ITEM_ID_NULL
expect_assert_failure(inventoryAdd(&inventory, ITEM_ID_NULL, 3));
}
static void test_inventoryRemove(void **state) {
(void)state;
inventorystack_t storage[5];
inventory_t inventory;
inventoryInit(&inventory, storage, 5);
// Add item and then remove it
inventorySet(&inventory, ITEM_ID_POTION, 5);
inventoryRemove(&inventory, ITEM_ID_POTION);
assert_int_equal(inventory.storage[0].item, ITEM_ID_NULL);
// Removing non-existing item should do nothing
inventoryRemove(&inventory, ITEM_ID_POTION);
assert_int_equal(inventory.storage[0].item, ITEM_ID_NULL);
// Should fail when given NULL inventory pointer
expect_assert_failure(inventoryRemove(NULL, ITEM_ID_POTION));
// Should fail when given NULL storage pointer
inventory.storage = NULL;
expect_assert_failure(inventoryRemove(&inventory, ITEM_ID_POTION));
inventory.storage = storage;
// Should fail when given zero storage size
inventory.storageSize = 0;
expect_assert_failure(inventoryRemove(&inventory, ITEM_ID_POTION));
inventory.storageSize = 5;
// Should fail when given ITEM_ID_NULL
expect_assert_failure(inventoryRemove(&inventory, ITEM_ID_NULL));
}
static void test_inventoryGetCount(void **state) {
(void)state;
inventorystack_t storage[5];
inventory_t inventory;
inventoryInit(&inventory, storage, 5);
// Initially should be zero
assert_int_equal(inventoryGetCount(&inventory, ITEM_ID_POTION), 0);
// Add item and check count
inventorySet(&inventory, ITEM_ID_POTION, 4);
assert_int_equal(inventoryGetCount(&inventory, ITEM_ID_POTION), 4);
// Update item quantity and check count
inventorySet(&inventory, ITEM_ID_POTION, 2);
assert_int_equal(inventoryGetCount(&inventory, ITEM_ID_POTION), 2);
// Remove item and check count
inventorySet(&inventory, ITEM_ID_POTION, 0);
assert_int_equal(inventoryGetCount(&inventory, ITEM_ID_POTION), 0);
// Should fail when given NULL inventory pointer
expect_assert_failure(inventoryGetCount(NULL, ITEM_ID_POTION));
// Should fail when given NULL storage pointer
inventory.storage = NULL;
expect_assert_failure(inventoryGetCount(&inventory, ITEM_ID_POTION));
inventory.storage = storage;
// Should fail when given zero storage size
inventory.storageSize = 0;
expect_assert_failure(inventoryGetCount(&inventory, ITEM_ID_POTION));
inventory.storageSize = 5;
// Should fail when given ITEM_ID_NULL
expect_assert_failure(inventoryGetCount(&inventory, ITEM_ID_NULL));
}
static void test_inventoryIsFull(void **state) {
(void)state;
inventorystack_t storage[2];
inventory_t inventory;
inventoryInit(&inventory, storage, 2);
// Initially should not be full
assert_false(inventoryIsFull(&inventory));
// Fill inventory
inventorySet(&inventory, ITEM_ID_POTION, 1);
inventorySet(&inventory, ITEM_ID_POTATO, 1);
assert_true(inventoryIsFull(&inventory));
// Remove one item
inventoryRemove(&inventory, ITEM_ID_POTION);
assert_false(inventoryIsFull(&inventory));
// Add one item back
inventorySet(&inventory, ITEM_ID_POTION, 1);
assert_true(inventoryIsFull(&inventory));
// Should fail when given NULL inventory pointer
expect_assert_failure(inventoryIsFull(NULL));
// Should fail when given NULL storage pointer
inventory.storage = NULL;
expect_assert_failure(inventoryIsFull(&inventory));
inventory.storage = storage;
// Should fail when given zero storage size
inventory.storageSize = 0;
expect_assert_failure(inventoryIsFull(&inventory));
inventory.storageSize = 2;
}
static void test_inventoryItemFull(void **state) {
(void)state;
inventorystack_t storage[2];
inventory_t inventory;
inventoryInit(&inventory, storage, 2);
// Initially should not be full
assert_false(inventoryItemFull(&inventory, ITEM_ID_POTION));
// Add some potions, but not too many.
inventorySet(&inventory, ITEM_ID_POTION, 100);
assert_false(inventoryItemFull(&inventory, ITEM_ID_POTION));
// Fill with potions
inventorySet(&inventory, ITEM_ID_POTION, ITEM_STACK_QUANTITY_MAX);
assert_true(inventoryItemFull(&inventory, ITEM_ID_POTION));
// Add potatoes, should not affect potions
inventorySet(&inventory, ITEM_ID_POTATO, 50);
assert_true(inventoryItemFull(&inventory, ITEM_ID_POTION));
assert_false(inventoryItemFull(&inventory, ITEM_ID_POTATO));
// Remove some potions
inventorySet(&inventory, ITEM_ID_POTION, 200);
assert_false(inventoryItemFull(&inventory, ITEM_ID_POTION));
// Fill with potatoes
inventorySet(&inventory, ITEM_ID_POTATO, ITEM_STACK_QUANTITY_MAX);
assert_false(inventoryItemFull(&inventory, ITEM_ID_POTION));
assert_true(inventoryItemFull(&inventory, ITEM_ID_POTATO));
// Should fail when given NULL inventory pointer
expect_assert_failure(inventoryItemFull(NULL, ITEM_ID_POTION));
// Should fail when given NULL storage pointer
inventory.storage = NULL;
expect_assert_failure(inventoryItemFull(&inventory, ITEM_ID_POTION));
inventory.storage = storage;
// Should fail when given zero storage size
inventory.storageSize = 0;
expect_assert_failure(inventoryItemFull(&inventory, ITEM_ID_POTION));
inventory.storageSize = 2;
}
int main(int argc, char** argv) {
const struct CMUnitTest tests[] = {
cmocka_unit_test(test_inventoryInit),
cmocka_unit_test(test_inventoryItemExists),
cmocka_unit_test(test_inventorySet),
cmocka_unit_test(test_inventoryAdd),
cmocka_unit_test(test_inventoryRemove),
cmocka_unit_test(test_inventoryGetCount),
cmocka_unit_test(test_inventoryIsFull),
cmocka_unit_test(test_inventoryItemFull),
};
return cmocka_run_group_tests(tests, NULL, NULL);
}

12
test/rpg/CMakeLists.txt Normal file
View File

@@ -0,0 +1,12 @@
# Copyright (c) 2026 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
include(dusktest)
# Tests
dusktest(test_rpg.c)
# Subdirs
add_subdirectory(overworld)

View File

@@ -0,0 +1,10 @@
# Copyright (c) 2026 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
include(dusktest)
# Tests
# Subdirs

16
test/rpg/test_rpg.c Normal file
View File

@@ -0,0 +1,16 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "dusktest.h"
int main(int argc, char** argv) {
const struct CMUnitTest tests[] = {
// Add RPG tests here in the future
};
return cmocka_run_group_tests(tests, NULL, NULL);
}

View File

@@ -100,17 +100,17 @@ static void test_timeUpdate(void **state) {
assert_float_equal(TIME.delta, TIME_STEP, 0.0001f);
assert_float_equal(TIME.time, TIME_STEP * 4, 0.0001f);
// Fifth test, despite a small time passing the game should be trying to catch
// and running extra ticks
// Fifth test, despite a small time passing the game should not start
// trying to run ahead
SDL_GETTICKS_TICKS = 104; // Simulate 4ms elapsed
SDL_GETTICKS_CALLED = false;
timeUpdate();
assert_true(SDL_GETTICKS_CALLED);
assert_false(TIME.dynamicUpdate);
assert_true(TIME.dynamicUpdate);
assert_float_equal(TIME.dynamicDelta, 0.004f, 0.0001f);
assert_float_equal(TIME.dynamicTime, TIME_STEP + 0.104f, 0.0001f);
assert_float_equal(TIME.delta, TIME_STEP, 0.0001f);
assert_float_equal(TIME.time, TIME_STEP * 5, 0.0001f);
assert_float_equal(TIME.time, TIME_STEP * 4, 0.0001f);
// Time can stand still if needed
SDL_GETTICKS_CALLED = false;