item but as a file

This commit is contained in:
2026-07-11 11:44:34 -05:00
parent ca02ee0352
commit b08ad308e4
16 changed files with 236 additions and 162 deletions
+2
View File
@@ -10,6 +10,7 @@
#include "time/time.h"
#include "input/input.h"
#include "locale/localemanager.h"
#include "rpg/item/item.h"
#include "rpg/rpg.h"
#include "display/display.h"
#include "scene/scene.h"
@@ -39,6 +40,7 @@ errorret_t engineInit(const int32_t argc, const char_t **argv) {
errorChain(assetInit());
// errorChain(saveInit());
errorChain(localeManagerInit());
errorChain(itemInit());
errorChain(displayInit());
errorChain(uiInit());
errorChain(rpgInit());
+2 -2
View File
@@ -165,10 +165,10 @@ typedef struct cutscene_s {
#define CUTSCENE_SET_PAUSE(FLAGS) \
{ .type = CUTSCENE_ITEM_TYPE_SET_PAUSE, .setPause = (FLAGS) }
#define CUTSCENE_ITEM_GIVE(ITEM_ID, QUANTITY) \
#define CUTSCENE_ITEM_GIVE(ITEM_NAME, QUANTITY) \
{ \
.type = CUTSCENE_ITEM_TYPE_ITEM_GIVE, \
.itemGive = { .item = ITEM_ID, .quantity = QUANTITY } \
.itemGive = { .itemName = ITEM_NAME, .quantity = QUANTITY } \
}
// Runs all listed items simultaneously and waits until all are done.
@@ -6,6 +6,7 @@
*/
#include "rpg/cutscene/item/cutsceneitem.h"
#include "rpg/item/item.h"
#include "rpg/item/itemgive.h"
#include "ui/rpg/textbox/uitextboxmain.h"
@@ -13,7 +14,8 @@ void cutsceneItemGiveStart(
const cutsceneitem_t *item,
cutsceneitemdata_t *data
) {
itemGive(item->itemGive.item, item->itemGive.quantity);
itemid_t itemId = itemGetIdByName(item->itemGive.itemName);
itemGive(itemId, item->itemGive.quantity);
}
bool_t cutsceneItemGiveUpdate(
@@ -6,13 +6,13 @@
*/
#pragma once
#include "rpg/item/item.h"
#include "dusk.h"
typedef struct cutsceneitem_s cutsceneitem_t;
typedef union cutsceneitemdata_u cutsceneitemdata_t;
typedef struct {
itemid_t item;
const char_t *itemName;
uint8_t quantity;
} cutsceneitemgive_t;
+1 -1
View File
@@ -25,7 +25,7 @@ CUTSCENE(TEST_TWO, 0, DEFAULT,
// CUTSCENE_ENTITY_WALK_TO(CUTSCENE_ENTITY_INTERACT, 4, 4, 0),
// CUTSCENE_ENTITY_WALK_TO(CUTSCENE_ENTITY_INTERACTED, 8, 2, 0),
// ),
// CUTSCENE_ITEM_GIVE(ITEM_ID_POTATO, 3),
// CUTSCENE_ITEM_GIVE("POTATO", 3),
// CUTSCENE_ENTITY_REMOVE(CUTSCENE_ENTITY_INTERACT),
CUTSCENE_TEXT("Done."),
);
+1 -10
View File
@@ -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)
)
+14 -14
View File
@@ -11,69 +11,69 @@
backpack_t BACKPACK;
void backpackInit() {
for(uint8_t i = 0; i < ITEM_TYPE_COUNT; i++) {
for(uint32_t i = 0; i < ITEM_TYPE_COUNT_MAX; i++) {
inventoryInit(
&BACKPACK.inventories[i],
BACKPACK.storage[i],
ITEM_TYPE_COUNT_MAX
ITEM_TYPE_SLOT_COUNT_MAX
);
}
}
inventory_t *backpackGetInventory(const itemtype_t type) {
inventory_t *backpackGetInventory(const itemtypeid_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_TYPE_COUNT, "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");
assertTrue(item <= ITEM_COUNT, "Item ID out of range");
inventoryAdd(backpackGetInventory(ITEMS[item].type), 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");
assertTrue(item <= ITEM_COUNT, "Item ID out of range");
inventoryRemove(backpackGetInventory(ITEMS[item].type), 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");
assertTrue(item <= ITEM_COUNT, "Item ID out of range");
inventorySet(backpackGetInventory(ITEMS[item].type), 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");
assertTrue(item <= ITEM_COUNT, "Item ID out of range");
return inventoryGetCount(backpackGetInventory(ITEMS[item].type), 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");
assertTrue(item <= ITEM_COUNT, "Item ID out of range");
return inventoryItemExists(backpackGetInventory(ITEMS[item].type), item);
}
bool_t backpackIsFull(const itemtype_t type) {
bool_t backpackIsFull(const itemtypeid_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_TYPE_COUNT, "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");
assertTrue(item <= ITEM_COUNT, "Item ID out of range");
return inventoryItemFull(backpackGetInventory(ITEMS[item].type), item);
}
void backpackSort(
const itemtype_t type,
const itemtypeid_t type,
const inventorysort_t sortBy,
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_TYPE_COUNT, "Item type out of range");
inventorySort(backpackGetInventory(type), sortBy, reverse);
}
+7 -5
View File
@@ -8,9 +8,11 @@
#pragma once
#include "inventory.h"
#define ITEM_TYPE_SLOT_COUNT_MAX 64
typedef struct {
inventorystack_t storage[ITEM_TYPE_COUNT][ITEM_TYPE_COUNT_MAX];
inventory_t inventories[ITEM_TYPE_COUNT];
inventorystack_t storage[ITEM_TYPE_COUNT_MAX][ITEM_TYPE_SLOT_COUNT_MAX];
inventory_t inventories[ITEM_TYPE_COUNT_MAX];
} backpack_t;
extern backpack_t BACKPACK;
@@ -26,7 +28,7 @@ void backpackInit();
* @param type The item type.
* @returns Pointer to the inventory for that type.
*/
inventory_t *backpackGetInventory(const itemtype_t type);
inventory_t *backpackGetInventory(const itemtypeid_t type);
/**
* Adds a quantity of an item to the backpack.
@@ -73,7 +75,7 @@ bool_t backpackItemExists(const itemid_t item);
* @param type The item type to check.
* @returns true if the type's inventory is full.
*/
bool_t backpackIsFull(const itemtype_t type);
bool_t backpackIsFull(const itemtypeid_t type);
/**
* Checks if an item's stack is full in the backpack.
@@ -91,7 +93,7 @@ bool_t backpackItemFull(const itemid_t item);
* @param reverse Whether to sort in reverse order.
*/
void backpackSort(
const itemtype_t type,
const itemtypeid_t type,
const inventorysort_t sortBy,
const bool_t reverse
);
+4 -4
View File
@@ -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 itemtypeid_t typeA = ITEMS[stackA->item].type;
const itemtypeid_t typeB = ITEMS[stackB->item].type;
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 itemtypeid_t typeA = ITEMS[stackA->item].type;
const itemtypeid_t typeB = ITEMS[stackB->item].type;
if(typeA < typeB) return 1;
if(typeA > typeB) return -1;
return 0;
+132 -1
View File
@@ -7,8 +7,125 @@
#include "item.h"
#include "assert/assert.h"
#include "util/memory.h"
#include "util/string.h"
#include "asset/asset.h"
#include "asset/loader/assetentry.h"
#include "asset/loader/json/assetjsonloader.h"
#include "locale/localemanager.h"
#include "asset/loader/locale/assetlocaleloader.h"
#include "yyjson.h"
#define ITEM_JSON_PATH "item.json"
itemdef_t ITEMS[ITEM_COUNT_MAX];
uint32_t ITEM_COUNT;
itemtype_t ITEM_TYPES[ITEM_TYPE_COUNT_MAX];
uint32_t ITEM_TYPE_COUNT;
errorret_t itemInit(void) {
memoryZero(ITEMS, sizeof(ITEMS));
memoryZero(ITEM_TYPES, sizeof(ITEM_TYPES));
ITEM_COUNT = 0;
ITEM_TYPE_COUNT = 0;
assetentry_t *jsonEntry = assetLock(
ITEM_JSON_PATH, ASSET_LOADER_TYPE_JSON, NULL
);
errorret_t ret = assetRequireLoaded(jsonEntry);
if(errorIsNotOk(ret)) {
assetUnlockEntry(jsonEntry);
errorChain(ret);
}
yyjson_val *root = yyjson_doc_get_root(jsonEntry->data.json);
if(!yyjson_is_arr(root)) {
assetUnlockEntry(jsonEntry);
errorThrow("item.json root must be an array");
}
size_t idx, max;
yyjson_val *entry;
yyjson_arr_foreach(root, idx, max, entry) {
if(ITEM_COUNT >= ITEM_COUNT_MAX - 1) {
assetUnlockEntry(jsonEntry);
errorThrow(
"Too many items defined: exceeds ITEM_COUNT_MAX (%d)",
ITEM_COUNT_MAX
);
}
yyjson_val *idVal = yyjson_obj_get(entry, "id");
yyjson_val *typeVal = yyjson_obj_get(entry, "type");
yyjson_val *nameVal = yyjson_obj_get(entry, "name");
yyjson_val *weightVal = yyjson_obj_get(entry, "weight");
if(!idVal || !yyjson_is_str(idVal)) {
assetUnlockEntry(jsonEntry);
errorThrow("Item entry %zu missing 'id' string", idx);
}
if(!typeVal || !yyjson_is_str(typeVal)) {
assetUnlockEntry(jsonEntry);
errorThrow("Item entry %zu missing 'type' string", idx);
}
if(!nameVal || !yyjson_is_str(nameVal)) {
assetUnlockEntry(jsonEntry);
errorThrow("Item entry %zu missing 'name' string", idx);
}
const char_t *idStr = yyjson_get_str(idVal);
size_t idLen = yyjson_get_len(idVal);
const char_t *typeStr = yyjson_get_str(typeVal);
size_t typeLen = yyjson_get_len(typeVal);
const char_t *nameStr = yyjson_get_str(nameVal);
size_t nameLen = yyjson_get_len(nameVal);
if(idLen >= ITEM_STRING_MAX) {
assetUnlockEntry(jsonEntry);
errorThrow("Item id '%s' exceeds max length", idStr);
}
if(nameLen + 10 >= ITEM_STRING_MAX) {
assetUnlockEntry(jsonEntry);
errorThrow("Item name '%s' exceeds max length", nameStr);
}
if(typeLen >= ITEM_STRING_MAX) {
assetUnlockEntry(jsonEntry);
errorThrow("Item type '%s' exceeds max length", typeStr);
}
itemtypeid_t typeId = itemResolveType(typeStr, typeLen);
if(typeId == ITEM_TYPE_NULL) {
assetUnlockEntry(jsonEntry);
errorThrow(
"Too many item types defined: exceeds ITEM_TYPE_COUNT_MAX (%d)",
ITEM_TYPE_COUNT_MAX
);
}
ITEM_COUNT++;
itemid_t id = (itemid_t)ITEM_COUNT;
itemdef_t *def = &ITEMS[id];
def->id = id;
def->type = typeId;
def->weight = (weightVal && yyjson_is_num(weightVal)) ?
(float_t)yyjson_get_num(weightVal) : 0.0f;
memoryCopy(def->idName, idStr, idLen + 1);
stringFormat(def->name, ITEM_STRING_MAX - 1, "item.%s.name", nameStr);
}
assetUnlockEntry(jsonEntry);
errorOk();
}
itemid_t itemGetIdByName(const char_t *name) {
assertNotNull(name, "Item name cannot be NULL");
for(uint32_t i = 1; i <= ITEM_COUNT; i++) {
if(stringEquals(ITEMS[i].idName, name)) return (itemid_t)i;
}
return ITEM_ID_NULL;
}
errorret_t itemGetName(
const itemid_t item,
@@ -16,7 +133,7 @@ errorret_t itemGetName(
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");
assertTrue(item <= ITEM_COUNT, "Item ID out of range");
errorChain(assetLocaleGetString(
&LOCALE.entry->data.locale,
@@ -28,3 +145,17 @@ errorret_t itemGetName(
errorOk();
}
itemtypeid_t itemResolveType(const char_t *name, const size_t nameLen) {
for(uint32_t i = 1; i <= ITEM_TYPE_COUNT; i++) {
if(stringEquals(ITEM_TYPES[i].name, name)) return (itemtypeid_t)i;
}
if(ITEM_TYPE_COUNT >= ITEM_TYPE_COUNT_MAX - 1) return ITEM_TYPE_NULL;
ITEM_TYPE_COUNT++;
itemtypeid_t typeId = (itemtypeid_t)ITEM_TYPE_COUNT;
ITEM_TYPES[typeId].id = typeId;
memoryCopy(ITEM_TYPES[typeId].name, name, nameLen + 1);
return typeId;
}
+56 -1
View File
@@ -7,7 +7,62 @@
#pragma once
#include "error/error.h"
#include "rpg/item/itemdef.h"
#define ITEM_COUNT_MAX 256
#define ITEM_TYPE_COUNT_MAX 16
#define ITEM_STRING_MAX 48
typedef uint16_t itemid_t;
typedef uint8_t itemtypeid_t;
#define ITEM_ID_NULL ((itemid_t)0)
#define ITEM_TYPE_NULL ((itemtypeid_t)0)
typedef struct {
itemid_t id;
itemtypeid_t type;
float_t weight;
char_t idName[ITEM_STRING_MAX];
char_t name[ITEM_STRING_MAX];
} itemdef_t;
typedef struct {
itemtypeid_t id;
char_t name[ITEM_STRING_MAX];
} itemtype_t;
extern itemdef_t ITEMS[ITEM_COUNT_MAX];
extern uint32_t ITEM_COUNT;
extern itemtype_t ITEM_TYPES[ITEM_TYPE_COUNT_MAX];
extern uint32_t ITEM_TYPE_COUNT;
/**
* Loads assets/item.json and parses it into the ITEMS/ITEM_TYPES tables.
* Must be called once, before any other item/backpack function.
*
* @return Any error that occurs (missing/malformed JSON, or too many
* items/types defined for ITEM_COUNT_MAX/ITEM_TYPE_COUNT_MAX).
*/
errorret_t itemInit(void);
/**
* Looks up an item's numeric ID from its JSON "id" string.
*
* @param name The item's string ID (e.g. "POTION"), case-sensitive.
* @return The matching item ID, or ITEM_ID_NULL if not found.
*/
itemid_t itemGetIdByName(const char_t *name);
/**
* Resolves a type name string to its numeric type ID, registering it as
* a new type in ITEM_TYPES if not already known.
*
* @param name The type's string name (e.g. "MEDICINE").
* @param nameLen Length of name, excluding the null terminator.
* @return The resolved type ID, or ITEM_TYPE_NULL if ITEM_TYPE_COUNT_MAX
* would be exceeded.
*/
itemtypeid_t itemResolveType(const char_t *name, const size_t nameLen);
/**
* Gets the localized display name for an item.
+4 -4
View File
@@ -59,13 +59,13 @@ errorret_t rpgInit(void) {
assertTrue(itemEntIndex != 0xFF, "No available entity slots!.");
entity_t *itemEnt = &ENTITIES[itemEntIndex];
entityInit(itemEnt, ENTITY_TYPE_ITEM);
entityItemSet(itemEnt, ITEM_ID_POTION, 1);
entityItemSet(itemEnt, itemGetIdByName("POTION"), 1);
entityPositionSet(itemEnt, (worldpos_t){ 12, 2, 0 });
// TEST: Give the player a starting assortment of items.
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);
// TEST: Create a test map area.
uint8_t areaIndex = mapAreaAdd(
+7 -2
View File
@@ -22,7 +22,7 @@ void uiBackpackTabChanged(
const uint8_t index,
const uimenuitem_t *item
) {
const itemtype_t type = (itemtype_t)(index + 1);
const itemtypeid_t type = (itemtypeid_t)(index + 1);
const inventory_t *inventory = backpackGetInventory(type);
errorCatch(uiItemListSetItemStacks(
@@ -41,11 +41,16 @@ void uiBackpackTabSelected(
errorret_t uiBackpackInit(void) {
memoryZero(&UI_BACKPACK, sizeof(uibackpack_t));
assertTrue(
ITEM_TYPE_COUNT - 1 <= UI_BACKPACK_TAB_COUNT,
"Item type count exceeds UI_BACKPACK_TAB_COUNT"
);
MENU_BEGIN(
&UI_BACKPACK.tabsMenu, UI_BACKPACK.tabs,
uiBackpackTabSelected, NULL, uiBackpackTabChanged
);
for(uint8_t i = 0; i < UI_BACKPACK_TAB_COUNT; i++) {
for(uint32_t i = 0; i < ITEM_TYPE_COUNT - 1; i++) {
stringFormat(
UI_BACKPACK.tabLabels[i], UI_BACKPACK_TAB_LABEL_MAX - 1,
"Category %u", i + 1
+1 -1
View File
@@ -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_COUNT_MAX - 1)
#define UI_BACKPACK_TAB_LABEL_MAX 32
#define UI_BACKPACK_ITEM_LIST_COLUMNS 4
#define UI_BACKPACK_ITEM_LIST_ROWS 5
-114
View File
@@ -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))