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 # Set target system
if(NOT DEFINED DUSK_TARGET_SYSTEM) if(NOT DEFINED DUSK_TARGET_SYSTEM)
# set(DUSK_TARGET_SYSTEM "linux") set(DUSK_TARGET_SYSTEM "linux")
set(DUSK_TARGET_SYSTEM "psp") # set(DUSK_TARGET_SYSTEM "psp")
endif() endif()
# Prep cache # Prep cache

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -6,7 +6,7 @@
*/ */
#pragma once #pragma once
#include "rpg/world/worldpos.h" #include "rpg/overworld/worldpos.h"
#include "error/error.h" #include "error/error.h"
typedef enum { 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 "assert/assert.h"
#include "asset/asset.h" #include "asset/asset.h"
#include "rpg/entity/entity.h" #include "rpg/entity/entity.h"
#include "rpg/world/map.h" #include "rpg/overworld/map.h"
#include "display/screen.h" #include "display/screen.h"
#include "rpg/rpgcamera.h" #include "rpg/rpgcamera.h"
#include "util/memory.h" #include "util/memory.h"

View File

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

View File

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

View File

@@ -1,7 +1,6 @@
module('platform') module('platform')
module('input') module('input')
module('scene') module('scene')
module('map')
-- Default Input bindings. -- Default Input bindings.
if PLATFORM == "psp" then if PLATFORM == "psp" then
@@ -39,4 +38,4 @@ else
end end
sceneSet('map') 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(engine)
add_subdirectory(error) add_subdirectory(error)
add_subdirectory(input) add_subdirectory(input)
add_subdirectory(item)
add_subdirectory(locale) add_subdirectory(locale)
add_subdirectory(rpg) # add_subdirectory(rpg)
add_subdirectory(scene) add_subdirectory(scene)
add_subdirectory(script) add_subdirectory(script)
add_subdirectory(thread) add_subdirectory(thread)

View File

@@ -9,8 +9,6 @@
#include "type/assetpaletteimage.h" #include "type/assetpaletteimage.h"
#include "type/assetalphaimage.h" #include "type/assetalphaimage.h"
#include "type/assetlanguage.h" #include "type/assetlanguage.h"
#include "type/assetmap.h"
#include "type/assetchunk.h"
#include "type/assetscript.h" #include "type/assetscript.h"
#include <zip.h> #include <zip.h>
@@ -20,8 +18,6 @@ typedef enum {
ASSET_TYPE_PALETTE_IMAGE, ASSET_TYPE_PALETTE_IMAGE,
ASSET_TYPE_ALPHA_IMAGE, ASSET_TYPE_ALPHA_IMAGE,
ASSET_TYPE_LANGUAGE, ASSET_TYPE_LANGUAGE,
ASSET_TYPE_MAP,
ASSET_TYPE_CHUNK,
ASSET_TYPE_SCRIPT, ASSET_TYPE_SCRIPT,
ASSET_TYPE_COUNT, ASSET_TYPE_COUNT,
@@ -72,19 +68,6 @@ static const assettypedef_t ASSET_TYPE_DEFINITIONS[ASSET_TYPE_COUNT] = {
.custom = assetLanguageHandler .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] = { [ASSET_TYPE_SCRIPT] = {
.header = "DSF", .header = "DSF",
.loadStrategy = ASSET_LOAD_STRAT_CUSTOM, .loadStrategy = ASSET_LOAD_STRAT_CUSTOM,

View File

@@ -9,7 +9,5 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME}
assetalphaimage.c assetalphaimage.c
assetpaletteimage.c assetpaletteimage.c
assetlanguage.c assetlanguage.c
assetmap.c
assetchunk.c
assetscript.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 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(r, g, b, a) ((color_t){r, g, b, a})
#define color_hex(hex) color( \ #define colorHex(hex) color( \
((hex >> 24) & 0xFF), \ ((hex >> 24) & 0xFF), \
((hex >> 16) & 0xFF), \ ((hex >> 16) & 0xFF), \
((hex >> 8) & 0xFF), \ ((hex >> 8) & 0xFF), \

View File

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

View File

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

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. * This software is released under the MIT License.
* https://opensource.org/licenses/MIT * https://opensource.org/licenses/MIT
*/ */
#pragma once #pragma once
#include "dusk.h"
#include "error/error.h" #include "error/error.h"
#include "display/color.h"
#define SCENE_FLAG_INITIALIZED (1 << 0)
typedef struct scenedata_s scenedata_t;
typedef struct { typedef struct {
const char_t *name; const char_t *name;
errorret_t (*init)(scenedata_t *data); errorret_t (*init)(void);
void (*update)(scenedata_t *data); void (*update)(void);
void (*render)(scenedata_t *data); void (*render)(void);
void (*dispose)(scenedata_t *data); void (*dispose)(void);
uint8_t flags; } scene_t;
} 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 #pragma once
#include "script/scriptcontext.h" #include "script/scriptcontext.h"
#include "scene/scenemanager.h" #include "scene/scene.h"
int moduleSceneSetScene(lua_State *L) { int moduleSceneSetScene(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL"); assertNotNull(L, "Lua state cannot be NULL");
@@ -20,13 +20,13 @@ int moduleSceneSetScene(lua_State *L) {
return 0; return 0;
} }
scene_t *scene = sceneManagerGetSceneByName(sceneName); const scene_t *scene = sceneGetByName(sceneName);
if(scene == NULL) { if(scene == NULL) {
luaL_error(L, "Scene '%s' not found", sceneName); luaL_error(L, "Scene '%s' not found", sceneName);
return 0; return 0;
} }
errorret_t err = sceneManagerSetScene(scene); errorret_t err = sceneSet(scene);
if(err.code != ERROR_OK) { if(err.code != ERROR_OK) {
luaL_error(L, "Failed to set scene '%s'", sceneName); luaL_error(L, "Failed to set scene '%s'", sceneName);
return 0; return 0;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -3,5 +3,8 @@
# This software is released under the MIT License. # This software is released under the MIT License.
# https://opensource.org/licenses/MIT # https://opensource.org/licenses/MIT
add_subdirectory(display)
# add_subdirectory(rpg)
add_subdirectory(item)
add_subdirectory(time) add_subdirectory(time)
add_subdirectory(util) 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.delta, TIME_STEP, 0.0001f);
assert_float_equal(TIME.time, TIME_STEP * 4, 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 // Fifth test, despite a small time passing the game should not start
// and running extra ticks // trying to run ahead
SDL_GETTICKS_TICKS = 104; // Simulate 4ms elapsed SDL_GETTICKS_TICKS = 104; // Simulate 4ms elapsed
SDL_GETTICKS_CALLED = false; SDL_GETTICKS_CALLED = false;
timeUpdate(); timeUpdate();
assert_true(SDL_GETTICKS_CALLED); 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.dynamicDelta, 0.004f, 0.0001f);
assert_float_equal(TIME.dynamicTime, TIME_STEP + 0.104f, 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.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 // Time can stand still if needed
SDL_GETTICKS_CALLED = false; SDL_GETTICKS_CALLED = false;