From 109ddc9e1e4259bd908bae8a6afa0eafc0194858 Mon Sep 17 00:00:00 2001 From: Dominic Masters Date: Sun, 13 Sep 2026 11:36:50 -0500 Subject: [PATCH] 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 --- assets/items.jsonc | 7 ++ src/dusk/engine/engine.c | 2 +- src/dusk/rpg/item/CMakeLists.txt | 11 +-- src/dusk/rpg/item/backpack.c | 38 +++----- src/dusk/rpg/item/backpack.h | 4 +- src/dusk/rpg/item/inventory.c | 8 +- src/dusk/rpg/item/item.c | 79 +++++++++++++++- src/dusk/rpg/item/item.h | 69 +++++++++++++- src/dusk/rpg/item/item.json | 5 - src/dusk/rpg/rpg.c | 8 +- src/dusk/ui/screen/backpack/uibackpack.c | 3 +- src/dusk/ui/screen/backpack/uibackpack.h | 2 +- test/item/test_inventory.c | 32 ++++++- test/rpg/entity/test_entityitem.c | 8 +- tools/item.py | 114 ----------------------- 15 files changed, 216 insertions(+), 174 deletions(-) create mode 100644 assets/items.jsonc delete mode 100644 src/dusk/rpg/item/item.json delete mode 100644 tools/item.py diff --git a/assets/items.jsonc b/assets/items.jsonc new file mode 100644 index 00000000..05f02667 --- /dev/null +++ b/assets/items.jsonc @@ -0,0 +1,7 @@ +{ + "items": { + "potion": { "type": "MEDICINE" }, + "potato": { "type": "FOOD" }, + "apple": { "type": "FOOD" } + } +} diff --git a/src/dusk/engine/engine.c b/src/dusk/engine/engine.c index 2c17ccb5..f1420c4a 100644 --- a/src/dusk/engine/engine.c +++ b/src/dusk/engine/engine.c @@ -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 diff --git a/src/dusk/rpg/item/CMakeLists.txt b/src/dusk/rpg/item/CMakeLists.txt index cbd4416d..29e79fb0 100644 --- a/src/dusk/rpg/item/CMakeLists.txt +++ b/src/dusk/rpg/item/CMakeLists.txt @@ -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) \ No newline at end of file +) \ No newline at end of file diff --git a/src/dusk/rpg/item/backpack.c b/src/dusk/rpg/item/backpack.c index 03bd91ae..b668d0dd 100644 --- a/src/dusk/rpg/item/backpack.c +++ b/src/dusk/rpg/item/backpack.c @@ -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); -} \ No newline at end of file +} diff --git a/src/dusk/rpg/item/backpack.h b/src/dusk/rpg/item/backpack.h index 5812befa..a8ab4419 100644 --- a/src/dusk/rpg/item/backpack.h +++ b/src/dusk/rpg/item/backpack.h @@ -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; diff --git a/src/dusk/rpg/item/inventory.c b/src/dusk/rpg/item/inventory.c index b3579ccb..909b7296 100644 --- a/src/dusk/rpg/item/inventory.c +++ b/src/dusk/rpg/item/inventory.c @@ -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; diff --git a/src/dusk/rpg/item/item.c b/src/dusk/rpg/item/item.c index 010b6845..937e4302 100644 --- a/src/dusk/rpg/item/item.c +++ b/src/dusk/rpg/item/item.c @@ -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(); } diff --git a/src/dusk/rpg/item/item.h b/src/dusk/rpg/item/item.h index 1c1beda5..ccb42841 100644 --- a/src/dusk/rpg/item/item.h +++ b/src/dusk/rpg/item/item.h @@ -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. diff --git a/src/dusk/rpg/item/item.json b/src/dusk/rpg/item/item.json deleted file mode 100644 index 35aceb4d..00000000 --- a/src/dusk/rpg/item/item.json +++ /dev/null @@ -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" } -] diff --git a/src/dusk/rpg/rpg.c b/src/dusk/rpg/rpg.c index 9b0791d5..d2056e9b 100644 --- a/src/dusk/rpg/rpg.c +++ b/src/dusk/rpg/rpg.c @@ -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! diff --git a/src/dusk/ui/screen/backpack/uibackpack.c b/src/dusk/ui/screen/backpack/uibackpack.c index cc37e27a..269b8f0d 100644 --- a/src/dusk/ui/screen/backpack/uibackpack.c +++ b/src/dusk/ui/screen/backpack/uibackpack.c @@ -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 diff --git a/src/dusk/ui/screen/backpack/uibackpack.h b/src/dusk/ui/screen/backpack/uibackpack.h index 46088313..f48c076d 100644 --- a/src/dusk/ui/screen/backpack/uibackpack.h +++ b/src/dusk/ui/screen/backpack/uibackpack.h @@ -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 diff --git a/test/item/test_inventory.c b/test/item/test_inventory.c index bfe0d848..75d13302 100644 --- a/test/item/test_inventory.c +++ b/test/item/test_inventory.c @@ -7,9 +7,37 @@ #include "dusktest.h" #include "rpg/item/inventory.h" +#include "util/memory.h" + +static itemid_t ITEM_ID_POTION; +static itemid_t ITEM_ID_POTATO; +static itemid_t ITEM_ID_APPLE; + +// Registers three test items (POTION as MEDICINE, POTATO/APPLE as FOOD) +// directly into the global ITEM registry -- these tests exercise the +// inventory/backpack layer only, not the JSON-driven itemInit() pipeline. +static int_t itemFixtureSetup(void **state) { + memoryZero(&ITEM, sizeof(ITEM)); + ITEM.typeCount = ITEM_TYPE_NULL + 1; + ITEM.defCount = ITEM_ID_NULL + 1; + + const itemtype_t medicine = itemTypeGetOrCreate("MEDICINE"); + const itemtype_t food = itemTypeGetOrCreate("FOOD"); + + ITEM_ID_POTION = ITEM.defCount++; + ITEM.defs[ITEM_ID_POTION].type = medicine; + + ITEM_ID_POTATO = ITEM.defCount++; + ITEM.defs[ITEM_ID_POTATO].type = food; + + ITEM_ID_APPLE = ITEM.defCount++; + ITEM.defs[ITEM_ID_APPLE].type = food; + + return 0; +} static void test_inventoryInit(void **state) { - + inventorystack_t storage[5]; inventory_t inventory; @@ -401,5 +429,5 @@ int main(int argc, char** argv) { cmocka_unit_test(test_inventorySort), }; - return cmocka_run_group_tests(tests, NULL, NULL); + return cmocka_run_group_tests(tests, itemFixtureSetup, NULL); } \ No newline at end of file diff --git a/test/rpg/entity/test_entityitem.c b/test/rpg/entity/test_entityitem.c index 13b5ecbd..f4936ec2 100644 --- a/test/rpg/entity/test_entityitem.c +++ b/test/rpg/entity/test_entityitem.c @@ -10,6 +10,10 @@ #include "ui/rpg/textbox/uitextboxmain.h" #include "ui/focus/uifocus.h" +// An arbitrary, non-null itemid_t -- entityItemSet just stores/reports it +// verbatim, it doesn't need a real item registered in ITEM. +#define TEST_ITEM_ID ((itemid_t)1) + static entity_t *setUpItemEntity(void) { entityTestFixtureReset(); uiFocusInit(); @@ -32,8 +36,8 @@ static void test_entityItemSet(void **state) { entity_t *item = setUpItemEntity(); - entityItemSet(item, ITEM_ID_POTION, 3); - assert_int_equal(item->data.item.item, ITEM_ID_POTION); + entityItemSet(item, TEST_ITEM_ID, 3); + assert_int_equal(item->data.item.item, TEST_ITEM_ID); assert_int_equal(item->data.item.quantity, 3); assert_false(item->data.item.collected); } diff --git a/tools/item.py b/tools/item.py deleted file mode 100644 index f5d8b9c7..00000000 --- a/tools/item.py +++ /dev/null @@ -1,114 +0,0 @@ -import argparse -import json -import os - -parser = argparse.ArgumentParser(description="Item JSON to .h defines") -parser.add_argument("--json", required=True, help="Path to item JSON file") -parser.add_argument("--output", required=True, help="Path to output .h file") -args = parser.parse_args() - -def type_enum(name): - return "ITEM_TYPE_" + name.upper() - -def id_enum(name): - return "ITEM_ID_" + name.upper() - -# Load JSON -item_ids = [] -item_types = [] -rows = {} - -with open(args.json, encoding="utf-8") as f: - entries = json.load(f) - -if not all( - "id" in row and "type" in row and "name" in row for row in entries -): - raise ValueError("Each item must have 'id', 'type', and 'name' fields") - -for row in entries: - item_id, item_type = row["id"], row["type"] - if item_id not in item_ids: - item_ids.append(item_id) - if item_type not in item_types: - item_types.append(item_type) - rows[item_id] = row - -# Assign enum values: types and IDs each start from 1 with NULL = 0. -type_values = {} -type_count = 1 -for t in item_types: - type_values[t] = type_count - type_count += 1 - -id_values = {} -id_count = 1 -for i in item_ids: - id_values[i] = id_count - id_count += 1 - -# Count items per type -type_item_counts = { t: 0 for t in item_types } -for i in item_ids: - type_item_counts[rows[i]["type"]] += 1 - -# Build output -out = [ - "#pragma once", - '#include "dusk.h"', - "", - "typedef enum {", - " ITEM_TYPE_NULL = 0,", -] -for t in item_types: - out.append(f" {type_enum(t)} = {type_values[t]},") -out += [ - f" ITEM_TYPE_COUNT = {type_count}", - "} itemtype_t;", - "", - "typedef enum {", - " ITEM_ID_NULL = 0,", -] -for i in item_ids: - out.append(f" {id_enum(i)} = {id_values[i]},") -out += [ - f" ITEM_ID_COUNT = {id_count}", - "} itemid_t;", - "", - "typedef struct {", - " itemid_t id;", - " itemtype_t type;", - " const char_t *name;", - "} item_t;", - "", - "static const item_t ITEMS[] = {", -] -for i in item_ids: - row = rows[i] - out += [ - f" [{id_enum(i)}] = {{", - f" .id = {id_enum(i)},", - f" .type = {type_enum(row['type'])},", - f" .name = \"item.{row['name']}.name\",", - " },", - ] -out += [ - "};", - "", - "static const uint8_t ITEM_TYPE_COUNTS[] = {", -] -for t in item_types: - out.append(f" [{type_enum(t)}] = {type_item_counts[t]},") -out += [ - "};", - "", -] -max_type_count = max(type_item_counts.values()) if type_item_counts else 0 -out += [ - f"#define ITEM_TYPE_COUNT_MAX {max_type_count}", - "", -] - -os.makedirs(os.path.dirname(args.output), exist_ok=True) -with open(args.output, "w", encoding="utf-8") as f: - f.write("\n".join(out))