Switch item definitions to a JSON-driven runtime registry
Replaces the build-time item.json/tools.item.py code generator with a single item_t ITEM registry populated at runtime by parsing assets/items.jsonc, so items/types can grow without a rebuild. Adds itemGetDefById/itemGetTypeById/itemGetIdByName/itemTypeGetOrCreate, updates inventory/backpack to size off the new *_DEF_COUNT_MAX caps and look up types dynamically, and reorders engine init so rpgInit (which loads items) runs before uiInit (which needs the item type count to size the backpack's tabs). Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
@@ -47,8 +47,8 @@ errorret_t engineInit(const int32_t argc, const char_t **argv) {
|
||||
errorChain(localeManagerInit());
|
||||
errorChain(displayInit());
|
||||
errorChain(audioInit());
|
||||
errorChain(uiInit());
|
||||
errorChain(rpgInit());
|
||||
errorChain(uiInit());
|
||||
#ifdef DUSK_NETWORK
|
||||
errorChain(networkInit());
|
||||
#endif
|
||||
|
||||
@@ -10,13 +10,4 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||
inventory.c
|
||||
backpack.c
|
||||
itemgive.c
|
||||
)
|
||||
|
||||
# Item Definitions
|
||||
dusk_run_python(
|
||||
dusk_item_json_defs
|
||||
tools.item
|
||||
--json ${CMAKE_CURRENT_SOURCE_DIR}/item.json
|
||||
--output ${DUSK_GENERATED_HEADERS_DIR}/rpg/item/itemdef.h
|
||||
)
|
||||
add_dependencies(${DUSK_LIBRARY_TARGET_NAME} dusk_item_json_defs)
|
||||
)
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
@@ -11,61 +11,49 @@
|
||||
backpack_t BACKPACK;
|
||||
|
||||
void backpackInit() {
|
||||
for(uint8_t i = 0; i < ITEM_TYPE_COUNT; i++) {
|
||||
for(itemtype_t i = ITEM_TYPE_NULL + 1; i < ITEM.typeCount; i++) {
|
||||
inventoryInit(
|
||||
&BACKPACK.inventories[i],
|
||||
BACKPACK.storage[i],
|
||||
ITEM_TYPE_COUNT_MAX
|
||||
ITEM_DEF_COUNT_MAX
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
inventory_t *backpackGetInventory(const itemtype_t type) {
|
||||
assertTrue(type > ITEM_TYPE_NULL, "Item type must not be null");
|
||||
assertTrue(type < ITEM_TYPE_COUNT, "Item type out of range");
|
||||
assertTrue(type < ITEM.typeCount, "Item type out of range");
|
||||
return &BACKPACK.inventories[type];
|
||||
}
|
||||
|
||||
void backpackAdd(const itemid_t item, const uint8_t quantity) {
|
||||
assertTrue(item > ITEM_ID_NULL, "Item ID must not be null");
|
||||
assertTrue(item < ITEM_ID_COUNT, "Item ID out of range");
|
||||
inventoryAdd(backpackGetInventory(ITEMS[item].type), item, quantity);
|
||||
inventoryAdd(backpackGetInventory(itemGetTypeById(item)), item, quantity);
|
||||
}
|
||||
|
||||
void backpackRemove(const itemid_t item) {
|
||||
assertTrue(item > ITEM_ID_NULL, "Item ID must not be null");
|
||||
assertTrue(item < ITEM_ID_COUNT, "Item ID out of range");
|
||||
inventoryRemove(backpackGetInventory(ITEMS[item].type), item);
|
||||
inventoryRemove(backpackGetInventory(itemGetTypeById(item)), item);
|
||||
}
|
||||
|
||||
void backpackSet(const itemid_t item, const uint8_t quantity) {
|
||||
assertTrue(item > ITEM_ID_NULL, "Item ID must not be null");
|
||||
assertTrue(item < ITEM_ID_COUNT, "Item ID out of range");
|
||||
inventorySet(backpackGetInventory(ITEMS[item].type), item, quantity);
|
||||
inventorySet(backpackGetInventory(itemGetTypeById(item)), item, quantity);
|
||||
}
|
||||
|
||||
uint8_t backpackGetCount(const itemid_t item) {
|
||||
assertTrue(item > ITEM_ID_NULL, "Item ID must not be null");
|
||||
assertTrue(item < ITEM_ID_COUNT, "Item ID out of range");
|
||||
return inventoryGetCount(backpackGetInventory(ITEMS[item].type), item);
|
||||
return inventoryGetCount(backpackGetInventory(itemGetTypeById(item)), item);
|
||||
}
|
||||
|
||||
bool_t backpackItemExists(const itemid_t item) {
|
||||
assertTrue(item > ITEM_ID_NULL, "Item ID must not be null");
|
||||
assertTrue(item < ITEM_ID_COUNT, "Item ID out of range");
|
||||
return inventoryItemExists(backpackGetInventory(ITEMS[item].type), item);
|
||||
return inventoryItemExists(backpackGetInventory(itemGetTypeById(item)), item);
|
||||
}
|
||||
|
||||
bool_t backpackIsFull(const itemtype_t type) {
|
||||
assertTrue(type > ITEM_TYPE_NULL, "Item type must not be null");
|
||||
assertTrue(type < ITEM_TYPE_COUNT, "Item type out of range");
|
||||
assertTrue(type < ITEM.typeCount, "Item type out of range");
|
||||
return inventoryIsFull(backpackGetInventory(type));
|
||||
}
|
||||
|
||||
bool_t backpackItemFull(const itemid_t item) {
|
||||
assertTrue(item > ITEM_ID_NULL, "Item ID must not be null");
|
||||
assertTrue(item < ITEM_ID_COUNT, "Item ID out of range");
|
||||
return inventoryItemFull(backpackGetInventory(ITEMS[item].type), item);
|
||||
return inventoryItemFull(backpackGetInventory(itemGetTypeById(item)), item);
|
||||
}
|
||||
|
||||
void backpackSort(
|
||||
@@ -74,6 +62,6 @@ void backpackSort(
|
||||
const bool_t reverse
|
||||
) {
|
||||
assertTrue(type > ITEM_TYPE_NULL, "Item type must not be null");
|
||||
assertTrue(type < ITEM_TYPE_COUNT, "Item type out of range");
|
||||
assertTrue(type < ITEM.typeCount, "Item type out of range");
|
||||
inventorySort(backpackGetInventory(type), sortBy, reverse);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
#include "inventory.h"
|
||||
|
||||
typedef struct {
|
||||
inventorystack_t storage[ITEM_TYPE_COUNT][ITEM_TYPE_COUNT_MAX];
|
||||
inventory_t inventories[ITEM_TYPE_COUNT];
|
||||
inventorystack_t storage[ITEM_TYPE_DEF_COUNT_MAX][ITEM_DEF_COUNT_MAX];
|
||||
inventory_t inventories[ITEM_TYPE_DEF_COUNT_MAX];
|
||||
} backpack_t;
|
||||
|
||||
extern backpack_t BACKPACK;
|
||||
|
||||
@@ -188,8 +188,8 @@ int_t inventorySortByIdReverse(const void *a, const void *b) {
|
||||
int_t inventorySortByType(const void *a, const void *b) {
|
||||
const inventorystack_t *stackA = (const inventorystack_t*)a;
|
||||
const inventorystack_t *stackB = (const inventorystack_t*)b;
|
||||
const itemtype_t typeA = ITEMS[stackA->item].type;
|
||||
const itemtype_t typeB = ITEMS[stackB->item].type;
|
||||
const itemtype_t typeA = itemGetTypeById(stackA->item);
|
||||
const itemtype_t typeB = itemGetTypeById(stackB->item);
|
||||
if(typeA < typeB) return -1;
|
||||
if(typeA > typeB) return 1;
|
||||
return 0;
|
||||
@@ -198,8 +198,8 @@ int_t inventorySortByType(const void *a, const void *b) {
|
||||
int_t inventorySortByTypeReverse(const void *a, const void *b) {
|
||||
const inventorystack_t *stackA = (const inventorystack_t*)a;
|
||||
const inventorystack_t *stackB = (const inventorystack_t*)b;
|
||||
const itemtype_t typeA = ITEMS[stackA->item].type;
|
||||
const itemtype_t typeB = ITEMS[stackB->item].type;
|
||||
const itemtype_t typeA = itemGetTypeById(stackA->item);
|
||||
const itemtype_t typeB = itemGetTypeById(stackB->item);
|
||||
if(typeA < typeB) return 1;
|
||||
if(typeA > typeB) return -1;
|
||||
return 0;
|
||||
|
||||
@@ -8,17 +8,90 @@
|
||||
#include "item.h"
|
||||
#include "assert/assert.h"
|
||||
#include "locale/localemanager.h"
|
||||
#include "asset/asset.h"
|
||||
#include "asset/loader/locale/assetlocaleloader.h"
|
||||
#include "util/memory.h"
|
||||
#include "util/string.h"
|
||||
#include "yyjson.h"
|
||||
|
||||
item_t ITEM;
|
||||
|
||||
itemtype_t itemTypeGetOrCreate(const char_t *id) {
|
||||
for(itemtype_t type = ITEM_TYPE_NULL + 1; type < ITEM.typeCount; type++) {
|
||||
if(stringEquals(ITEM.types[type].id, id)) return type;
|
||||
}
|
||||
|
||||
assertTrue(
|
||||
ITEM.typeCount < ITEM_TYPE_DEF_COUNT_MAX, "Too many item types defined."
|
||||
);
|
||||
itemtype_t type = ITEM.typeCount++;
|
||||
stringCopy(ITEM.types[type].id, id, ITEM_TYPE_NAME_MAX);
|
||||
return type;
|
||||
}
|
||||
|
||||
errorret_t itemInit(void) {
|
||||
memoryZero(&ITEM, sizeof(ITEM));
|
||||
ITEM.typeCount = ITEM_TYPE_NULL + 1;
|
||||
ITEM.defCount = ITEM_ID_NULL + 1;
|
||||
|
||||
assetentry_t *entry = assetLock("items.jsonc", ASSET_LOADER_TYPE_JSON, NULL);
|
||||
errorret_t ret = assetRequireLoaded(entry);
|
||||
if(errorIsNotOk(ret)) {
|
||||
assetUnlockEntry(entry);
|
||||
errorChain(ret);
|
||||
}
|
||||
|
||||
yyjson_val *root = yyjson_doc_get_root(entry->data.json);
|
||||
yyjson_val *items = yyjson_obj_get(root, "items");
|
||||
|
||||
yyjson_obj_iter iter = yyjson_obj_iter_with(items);
|
||||
yyjson_val *key;
|
||||
while((key = yyjson_obj_iter_next(&iter))) {
|
||||
assertTrue(ITEM.defCount < ITEM_DEF_COUNT_MAX, "Too many item ids defined.");
|
||||
|
||||
yyjson_val *val = yyjson_obj_iter_get_val(key);
|
||||
const char_t *typeId = yyjson_get_str(yyjson_obj_get(val, "type"));
|
||||
|
||||
itemid_t id = ITEM.defCount++;
|
||||
stringCopy(ITEM.defs[id].id, yyjson_get_str(key), ITEM_ID_STRING_MAX);
|
||||
ITEM.defs[id].type = itemTypeGetOrCreate(typeId);
|
||||
}
|
||||
|
||||
assetUnlockEntry(entry);
|
||||
errorOk();
|
||||
}
|
||||
|
||||
const itemdef_t *itemGetDefById(const itemid_t id) {
|
||||
assertTrue(id > ITEM_ID_NULL, "Item ID must not be null");
|
||||
assertTrue(id < ITEM.defCount, "Item ID out of range");
|
||||
|
||||
return &ITEM.defs[id];
|
||||
}
|
||||
|
||||
itemtype_t itemGetTypeById(const itemid_t id) {
|
||||
return itemGetDefById(id)->type;
|
||||
}
|
||||
|
||||
itemid_t itemGetIdByName(const char_t *name) {
|
||||
assertNotNull(name, "name must not be NULL");
|
||||
|
||||
for(itemid_t id = ITEM_ID_NULL + 1; id < ITEM.defCount; id++) {
|
||||
if(stringEquals(ITEM.defs[id].id, name)) return id;
|
||||
}
|
||||
|
||||
return ITEM_ID_NULL;
|
||||
}
|
||||
|
||||
errorret_t itemGetName(
|
||||
const itemid_t item,
|
||||
char_t *buffer,
|
||||
const size_t bufferSize
|
||||
) {
|
||||
assertTrue(item > ITEM_ID_NULL, "Item ID must not be null");
|
||||
assertTrue(item < ITEM_ID_COUNT, "Item ID out of range");
|
||||
const itemdef_t *def = itemGetDefById(item);
|
||||
|
||||
errorChain(localeManagerGetString(ITEMS[item].name, buffer, bufferSize, 0));
|
||||
char_t key[ITEM_ID_STRING_MAX + 16];
|
||||
stringFormat(key, sizeof(key), "item.%s.name", def->id);
|
||||
errorChain(localeManagerGetString(key, buffer, bufferSize, 0));
|
||||
|
||||
errorOk();
|
||||
}
|
||||
|
||||
@@ -7,7 +7,74 @@
|
||||
|
||||
#pragma once
|
||||
#include "error/error.h"
|
||||
#include "rpg/item/itemdef.h"
|
||||
|
||||
#define ITEM_TYPE_NAME_MAX 12
|
||||
#define ITEM_TYPE_DEF_COUNT_MAX 32
|
||||
#define ITEM_ID_STRING_MAX 12
|
||||
#define ITEM_DEF_COUNT_MAX 64
|
||||
|
||||
typedef uint8_t itemtype_t;
|
||||
typedef uint16_t itemid_t;
|
||||
#define ITEM_TYPE_NULL ((itemtype_t)0)
|
||||
#define ITEM_ID_NULL ((itemid_t)0)
|
||||
|
||||
typedef struct {
|
||||
char_t id[ITEM_TYPE_NAME_MAX];
|
||||
} itemtypedef_t;
|
||||
|
||||
typedef struct {
|
||||
itemtype_t type;
|
||||
char_t id[ITEM_ID_STRING_MAX];
|
||||
} itemdef_t;
|
||||
|
||||
typedef struct {
|
||||
itemtypedef_t types[ITEM_TYPE_DEF_COUNT_MAX];
|
||||
itemtype_t typeCount;
|
||||
itemdef_t defs[ITEM_DEF_COUNT_MAX];
|
||||
itemid_t defCount;
|
||||
} item_t;
|
||||
|
||||
extern item_t ITEM;
|
||||
|
||||
/**
|
||||
* Initializes the item system.
|
||||
*
|
||||
* @return Any error that occurs.
|
||||
*/
|
||||
errorret_t itemInit(void);
|
||||
|
||||
/**
|
||||
* Finds the itemtype_t matching id among the types registered so far,
|
||||
* registering a new one if this is the first time it's been seen.
|
||||
*
|
||||
* @param id The type's identifier string (see itemtypedef_t.id).
|
||||
* @return The itemtype_t for id.
|
||||
*/
|
||||
itemtype_t itemTypeGetOrCreate(const char_t *id);
|
||||
|
||||
/**
|
||||
* Gets the itemdef_t for an item id.
|
||||
*
|
||||
* @param id The item ID to look up. Must not be ITEM_ID_NULL.
|
||||
* @return The itemdef_t for id.
|
||||
*/
|
||||
const itemdef_t *itemGetDefById(const itemid_t id);
|
||||
|
||||
/**
|
||||
* Gets the itemtype_t for an item id.
|
||||
*
|
||||
* @param id The item ID to look up. Must not be ITEM_ID_NULL.
|
||||
* @return The itemtype_t for id.
|
||||
*/
|
||||
itemtype_t itemGetTypeById(const itemid_t id);
|
||||
|
||||
/**
|
||||
* Finds the itemid_t whose id string matches name.
|
||||
*
|
||||
* @param name The item's identifier string (see itemdef_t.id).
|
||||
* @return The matching itemid_t, or ITEM_ID_NULL if none matched.
|
||||
*/
|
||||
itemid_t itemGetIdByName(const char_t *name);
|
||||
|
||||
/**
|
||||
* Gets the localized display name for an item.
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
[
|
||||
{ "id": "POTION", "type": "MEDICINE", "weight": 1.0, "name": "potion" },
|
||||
{ "id": "POTATO", "type": "FOOD", "weight": 0.5, "name": "potato" },
|
||||
{ "id": "APPLE", "type": "FOOD", "weight": 0.3, "name": "apple" }
|
||||
]
|
||||
+5
-3
@@ -10,6 +10,7 @@
|
||||
#include "rpg/overworld/map.h"
|
||||
#include "rpg/overworld/maparea.h"
|
||||
#include "rpg/cutscene/cutscenesystem.h"
|
||||
#include "rpg/item/item.h"
|
||||
#include "rpg/item/backpack.h"
|
||||
#include "rpg/battle/party.h"
|
||||
#include "save/save.h"
|
||||
@@ -27,6 +28,7 @@ errorret_t rpgInit(void) {
|
||||
memoryZero(ENTITIES, sizeof(ENTITIES));
|
||||
memoryZero(MAP_AREAS, sizeof(MAP_AREAS));
|
||||
|
||||
errorChain(itemInit());
|
||||
backpackInit();
|
||||
partyInit();
|
||||
cutsceneSystemInit();
|
||||
@@ -45,9 +47,9 @@ errorret_t rpgInit(void) {
|
||||
RPG_CAMERA.followEntity.followEntityId = ent->id;
|
||||
|
||||
// Starting inventory.
|
||||
backpackAdd(ITEM_ID_POTION, 5);
|
||||
backpackAdd(ITEM_ID_POTATO, 3);
|
||||
backpackAdd(ITEM_ID_APPLE, 8);
|
||||
backpackAdd(itemGetIdByName("potion"), 5);
|
||||
backpackAdd(itemGetIdByName("potato"), 3);
|
||||
backpackAdd(itemGetIdByName("apple"), 8);
|
||||
|
||||
|
||||
// All Good!
|
||||
|
||||
@@ -54,7 +54,8 @@ errorret_t uiBackpackInit(void) {
|
||||
&UI_BACKPACK.tabsMenu, UI_BACKPACK.tabs,
|
||||
uiBackpackTabSelected, NULL, uiBackpackTabChanged
|
||||
);
|
||||
for(uint8_t i = 0; i < UI_BACKPACK_TAB_COUNT; i++) {
|
||||
const uint8_t tabCount = ITEM.typeCount - 1;
|
||||
for(uint8_t i = 0; i < tabCount; i++) {
|
||||
stringFormat(
|
||||
UI_BACKPACK.tabLabels[i], UI_BACKPACK_TAB_LABEL_MAX - 1,
|
||||
UI_BACKPACK.categoryFormat, i + 1
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
#include "ui/widget/uiitemlist.h"
|
||||
#include "rpg/item/item.h"
|
||||
|
||||
#define UI_BACKPACK_TAB_COUNT (ITEM_TYPE_COUNT - 1)
|
||||
#define UI_BACKPACK_TAB_COUNT (ITEM_TYPE_DEF_COUNT_MAX - 1)
|
||||
#define UI_BACKPACK_TAB_LABEL_MAX 32
|
||||
#define UI_BACKPACK_CATEGORY_FORMAT_MAX 32
|
||||
#define UI_BACKPACK_ITEM_LIST_COLUMNS 4
|
||||
|
||||
Reference in New Issue
Block a user