Getting shaders working with lua.

This commit is contained in:
2026-03-28 21:50:59 -05:00
parent cbb68a399d
commit dbb7e9f53c
49 changed files with 305 additions and 114 deletions
+16
View File
@@ -0,0 +1,16 @@
# Copyright (c) 2026 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
# Includes
target_include_directories(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
${CMAKE_CURRENT_LIST_DIR}
)
# Subdirs
add_subdirectory(item)
add_subdirectory(map)
add_subdirectory(story)
add_subdirectory(script)
+20
View File
@@ -0,0 +1,20 @@
# 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
inventory.c
backpack.c
)
# Item Definitions
dusk_run_python(
dusk_item_csv_defs
tools.item.csv
--csv ${CMAKE_CURRENT_SOURCE_DIR}/item.csv
--output ${DUSK_GENERATED_HEADERS_DIR}/item/item.h
)
add_dependencies(${DUSK_LIBRARY_TARGET_NAME} dusk_item_csv_defs)
+15
View File
@@ -0,0 +1,15 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "backpack.h"
inventorystack_t BACKPACK_STORAGE[BACKPACK_STORAGE_SIZE_MAX];
inventory_t BACKPACK;
void backpackInit() {
inventoryInit(&BACKPACK, BACKPACK_STORAGE, BACKPACK_STORAGE_SIZE_MAX);
}
+19
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 "inventory.h"
#define BACKPACK_STORAGE_SIZE_MAX 20
extern inventorystack_t BACKPACK_STORAGE[BACKPACK_STORAGE_SIZE_MAX];
extern inventory_t BACKPACK;
/**
* Initializes the backpack inventory for the player.
*/
void backpackInit();
+251
View File
@@ -0,0 +1,251 @@
/**
* 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 "util/sort.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)) * sizeof(inventorystack_t)
);
// 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;
}
// Sorters
int_t inventorySortById(const void *a, const void *b) {
const inventorystack_t *stackA = (const inventorystack_t*)a;
const inventorystack_t *stackB = (const inventorystack_t*)b;
if(stackA->item < stackB->item) return -1;
if(stackA->item > stackB->item) return 1;
return 0;
}
int_t inventorySortByIdReverse(const void *a, const void *b) {
const inventorystack_t *stackA = (const inventorystack_t*)a;
const inventorystack_t *stackB = (const inventorystack_t*)b;
if(stackA->item < stackB->item) return 1;
if(stackA->item > stackB->item) return -1;
return 0;
}
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;
if(typeA < typeB) return -1;
if(typeA > typeB) return 1;
return 0;
}
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;
if(typeA < typeB) return 1;
if(typeA > typeB) return -1;
return 0;
}
void inventorySort(
inventory_t *inventory,
const inventorysort_t sortBy,
const bool_t reverse
) {
assertNotNull(inventory, "Inventory pointer is NULL.");
assertNotNull(inventory->storage, "Storage pointer is NULL.");
assertTrue(inventory->storageSize > 0, "Storage too small.");
assertTrue(sortBy < INVENTORY_SORT_COUNT, "Invalid sort type.");
// Get count of used stacks
size_t count = 0;
inventorystack_t *stack = inventory->storage;
inventorystack_t *end = stack + inventory->storageSize;
do {
if(stack->item == ITEM_ID_NULL) break;
count++;
} while(++stack < end);
if(count == 0) return; // Nothing to sort
// Comparator
sortcompare_t comparator = NULL;
switch(sortBy) {
case INVENTORY_SORT_BY_ID: {
comparator = reverse ? inventorySortByIdReverse : inventorySortById;
break;
};
case INVENTORY_SORT_BY_TYPE: {
comparator = reverse ? inventorySortByTypeReverse : inventorySortByType;
break;
};
default:
assertUnreachable("Invalid sort type.");
break;
}
assertNotNull(comparator, "Comparator function is NULL.");
sort((void*)inventory->storage, count, sizeof(inventorystack_t), comparator);
}
+123
View File
@@ -0,0 +1,123 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "item/item.h"
#define ITEM_STACK_QUANTITY_MAX UINT8_MAX
typedef enum {
INVENTORY_SORT_BY_ID,
INVENTORY_SORT_BY_TYPE,
INVENTORY_SORT_COUNT
} inventorysort_t;
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);
/**
* Sorts the inventory based on the specified criteria.
*
* @param inventory The inventory to sort.
* @param sortBy The sorting criteria.
* @param reverse Whether to sort in reverse order.
*/
void inventorySort(
inventory_t *inventory,
const inventorysort_t sortBy,
const bool_t reverse
);
+4
View File
@@ -0,0 +1,4 @@
id,type,weight
POTION,MEDICINE,1.0
POTATO,FOOD,0.5
APPLE,FOOD,0.3
1 id type weight
2 POTION MEDICINE 1.0
3 POTATO FOOD 0.5
4 APPLE FOOD 0.3
+13
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
mapchunk.c
map.c
worldpos.c
maptile.c
)
+272
View File
@@ -0,0 +1,272 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "map.h"
#include "util/memory.h"
#include "assert/assert.h"
#include "asset/asset.h"
#include "display/texture/texture.h"
// #include "entity/entity.h"
#include "util/string.h"
#include "time/time.h"
map_t MAP;
errorret_t mapInit() {
memoryZero(&MAP, sizeof(map_t));
errorOk();
}
bool_t mapIsLoaded() {
return MAP.filePath[0] != '\0';
}
errorret_t mapLoad(const char_t *path, const chunkpos_t position) {
assertStrLenMin(path, 1, "Map file path cannot be empty");
assertStrLenMax(path, MAP_FILE_PATH_MAX - 1, "Map file path too long");
if(stringCompare(MAP.filePath, path) == 0) {
// Same map, no need to reload
errorChain(mapPositionSet(position));
errorOk();
}
chunkindex_t i;
// Unload all loaded chunks
if(mapIsLoaded()) {
for(i = 0; i < MAP_CHUNK_COUNT; i++) {
mapChunkUnload(&MAP.chunks[i]);
}
}
// Store the map file path
stringCopy(MAP.filePath, path, MAP_FILE_PATH_MAX);
// Determine directory path (it is dirname)
stringCopy(MAP.dirPath, path, MAP_FILE_PATH_MAX);
char_t *last = stringFindLastChar(MAP.dirPath, '/');
if(last == NULL) errorThrow("Invalid map file path");
// Store filename, sans extension
stringCopy(MAP.fileName, last + 1, MAP_FILE_PATH_MAX);
*last = '\0'; // Terminate to get directory path
last = stringFindLastChar(MAP.fileName, '.');
if(last == NULL) errorThrow("Map file name has no extension");
*last = '\0'; // Terminate to remove extension
// Load map itself
errorChain(assetLoad(MAP.filePath, &MAP));
// Reset map position
MAP.chunkPosition = position;
// Perform "initial load"
i = 0;
for(chunkunit_t z = 0; z < MAP_CHUNK_DEPTH; z++) {
for(chunkunit_t y = 0; y < MAP_CHUNK_HEIGHT; y++) {
for(chunkunit_t x = 0; x < MAP_CHUNK_WIDTH; x++) {
mapchunk_t *chunk = &MAP.chunks[i];
chunk->position.x = x + position.x;
chunk->position.y = y + position.y;
chunk->position.z = z + position.z;
MAP.chunkOrder[i] = chunk;
errorChain(mapChunkLoad(chunk));
i++;
}
}
}
errorOk();
}
errorret_t mapPositionSet(const chunkpos_t newPos) {
if(!mapIsLoaded()) errorThrow("No map loaded");
const chunkpos_t curPos = MAP.chunkPosition;
if(mapChunkPositionIsEqual(curPos, newPos)) {
errorOk();
}
// Determine which chunks remain loaded
chunkindex_t chunksRemaining[MAP_CHUNK_COUNT] = {0};
chunkindex_t chunksFreed[MAP_CHUNK_COUNT] = {0};
uint32_t remainingCount = 0;
uint32_t freedCount = 0;
for(chunkindex_t i = 0; i < MAP_CHUNK_COUNT; i++) {
// Will this chunk remain loaded?
mapchunk_t *chunk = &MAP.chunks[i];
if(
chunk->position.x >= newPos.x &&
chunk->position.x < newPos.x + MAP_CHUNK_WIDTH &&
chunk->position.y >= newPos.y &&
chunk->position.y < newPos.y + MAP_CHUNK_HEIGHT &&
chunk->position.z >= newPos.z &&
chunk->position.z < newPos.z + MAP_CHUNK_DEPTH
) {
// Stays loaded
chunksRemaining[remainingCount++] = i;
continue;
}
// Not remaining loaded
chunksFreed[freedCount++] = i;
}
// Unload the freed chunks
for(chunkindex_t i = 0; i < freedCount; i++) {
mapchunk_t *chunk = &MAP.chunks[chunksFreed[i]];
mapChunkUnload(chunk);
}
// This can probably be optimized later, for now we check each chunk and see
// if it needs loading or not, and update the chunk order
chunkindex_t orderIndex = 0;
for(chunkunit_t zOff = 0; zOff < MAP_CHUNK_DEPTH; zOff++) {
for(chunkunit_t yOff = 0; yOff < MAP_CHUNK_HEIGHT; yOff++) {
for(chunkunit_t xOff = 0; xOff < MAP_CHUNK_WIDTH; xOff++) {
const chunkpos_t newChunkPos = {
newPos.x + xOff, newPos.y + yOff, newPos.z + zOff
};
// Is this chunk already loaded (was not unloaded earlier)?
chunkindex_t chunkIndex = -1;
for(chunkindex_t i = 0; i < remainingCount; i++) {
mapchunk_t *chunk = &MAP.chunks[chunksRemaining[i]];
if(!mapChunkPositionIsEqual(chunk->position, newChunkPos)) continue;
chunkIndex = chunksRemaining[i];
break;
}
// Need to load this chunk
if(chunkIndex == -1) {
// Find a freed chunk to reuse
chunkIndex = chunksFreed[--freedCount];
mapchunk_t *chunk = &MAP.chunks[chunkIndex];
chunk->position = newChunkPos;
errorChain(mapChunkLoad(chunk));
}
MAP.chunkOrder[orderIndex++] = &MAP.chunks[chunkIndex];
}
}
}
// Update map position
MAP.chunkPosition = newPos;
errorOk();
}
void mapUpdate() {
#ifdef DUSK_TIME_DYNAMIC
if(!TIME.dynamicUpdate) return;
#endif
}
void mapRender() {
if(!mapIsLoaded()) return;
// textureBind(NULL);
for(chunkindex_t i = 0; i < MAP_CHUNK_COUNT; i++) {
mapChunkRender(&MAP.chunks[i]);
}
}
void mapDispose() {
for(chunkindex_t i = 0; i < MAP_CHUNK_COUNT; i++) {
mapChunkUnload(&MAP.chunks[i]);
}
}
void mapChunkUnload(mapchunk_t* chunk) {
// for(uint8_t i = 0; i < CHUNK_ENTITY_COUNT_MAX; i++) {
// if(chunk->entities[i] == 0xFF) break;
// entity_t *entity = &ENTITIES[chunk->entities[i]];
// entity->type = ENTITY_TYPE_NULL;
// }
for(uint8_t i = 0; i < chunk->meshCount; i++) {
// if(chunk->meshes[i].vertexCount == 0) continue;
// meshDispose(&chunk->meshes[i]);
}
}
errorret_t mapChunkLoad(mapchunk_t* chunk) {
if(!mapIsLoaded()) errorThrow("No map loaded");
char_t buffer[160];
// TODO: Can probably move this to asset load logic?
chunk->meshCount = 0;
memoryZero(chunk->meshes, sizeof(chunk->meshes));
memorySet(chunk->entities, 0xFF, sizeof(chunk->entities));
// Get chunk filepath.
snprintf(buffer, sizeof(buffer), "%s/chunks/%d_%d_%d.dmc",
MAP.dirPath,
chunk->position.x,
chunk->position.y,
chunk->position.z
);
// Chunk available?
if(!assetFileExists(buffer)) {
memoryZero(chunk->tiles, sizeof(chunk->tiles));
errorOk();
}
// Load.
errorChain(assetLoad(buffer, chunk));
errorOk();
}
chunkindex_t mapGetChunkIndexAt(const chunkpos_t position) {
if(!mapIsLoaded()) return -1;
chunkpos_t relPos = {
position.x - MAP.chunkPosition.x,
position.y - MAP.chunkPosition.y,
position.z - MAP.chunkPosition.z
};
if(
relPos.x < 0 || relPos.y < 0 || relPos.z < 0 ||
relPos.x >= MAP_CHUNK_WIDTH ||
relPos.y >= MAP_CHUNK_HEIGHT ||
relPos.z >= MAP_CHUNK_DEPTH
) {
return -1;
}
return chunkPosToIndex(&relPos);
}
mapchunk_t* mapGetChunk(const uint8_t index) {
if(index >= MAP_CHUNK_COUNT) return NULL;
if(!mapIsLoaded()) return NULL;
return MAP.chunkOrder[index];
}
maptile_t mapGetTile(const worldpos_t position) {
if(!mapIsLoaded()) return TILE_SHAPE_NULL;
chunkpos_t chunkPos;
worldPosToChunkPos(&position, &chunkPos);
chunkindex_t chunkIndex = mapGetChunkIndexAt(chunkPos);
if(chunkIndex == -1) return TILE_SHAPE_NULL;
mapchunk_t *chunk = mapGetChunk(chunkIndex);
assertNotNull(chunk, "Chunk pointer cannot be NULL");
chunktileindex_t tileIndex = worldPosToChunkTileIndex(&position);
return chunk->tiles[tileIndex];
}
+108
View File
@@ -0,0 +1,108 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "mapchunk.h"
#define MAP_FILE_PATH_MAX 128
typedef struct map_s {
char_t filePath[MAP_FILE_PATH_MAX];
char_t dirPath[MAP_FILE_PATH_MAX];
char_t fileName[MAP_FILE_PATH_MAX];
mapchunk_t chunks[MAP_CHUNK_COUNT];
mapchunk_t *chunkOrder[MAP_CHUNK_COUNT];
chunkpos_t chunkPosition;
} map_t;
extern map_t MAP;
/**
* Initializes the map.
*
* @return An error code.
*/
errorret_t mapInit();
/**
* Checks if a map is loaded.
*
* @return true if a map is loaded, false otherwise.
*/
bool_t mapIsLoaded();
/**
* Loads a map from the given file path.
*
* @param path The file path.
* @param position The initial chunk position.
* @return An error code.
*/
errorret_t mapLoad(const char_t *path, const chunkpos_t position);
/**
* Updates the map.
*/
void mapUpdate();
/**
* Renders the map.
*/
void mapRender();
/**
* Disposes of the map.
*/
void mapDispose();
/**
* Sets the map position and updates chunks accordingly.
*
* @param newPos The new chunk position.
* @return An error code.
*/
errorret_t mapPositionSet(const chunkpos_t newPos);
/**
* Unloads a chunk.
*
* @param chunk The chunk to unload.
*/
void mapChunkUnload(mapchunk_t* chunk);
/**
* Loads a chunk.
*
* @param chunk The chunk to load.
* @return An error code.
*/
errorret_t mapChunkLoad(mapchunk_t* chunk);
/**
* Gets the index of a chunk, within the world, at the given position.
*
* @param position The chunk position.
* @return The index of the chunk, or -1 if out of bounds.
*/
chunkindex_t mapGetChunkIndexAt(const chunkpos_t position);
/**
* Gets a chunk by its index.
*
* @param chunkIndex The index of the chunk.
* @return A pointer to the chunk.
*/
mapchunk_t * mapGetChunk(const uint8_t chunkIndex);
/**
* Gets the tile at the given world position.
*
* @param position The world position.
* @return The tile at that position, or TILE_NULL if the chunk is unloaded.
*/
maptile_t mapGetTile(const worldpos_t position);
+26
View File
@@ -0,0 +1,26 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "mapchunk.h"
uint32_t mapChunkGetTileindex(const chunkpos_t position) {
return (
(position.z * CHUNK_WIDTH * CHUNK_HEIGHT) +
(position.y * CHUNK_WIDTH) +
position.x
);
}
bool_t mapChunkPositionIsEqual(const chunkpos_t a, const chunkpos_t b) {
return (a.x == b.x) && (a.y == b.y) && (a.z == b.z);
}
void mapChunkRender(const mapchunk_t *chunk) {
for(uint8_t i = 0; i < chunk->meshCount; i++) {
meshDraw(&chunk->meshes[i], 0, -1);
}
}
+45
View File
@@ -0,0 +1,45 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "maptile.h"
#include "worldpos.h"
#include "display/mesh/quad.h"
typedef struct chunk_s {
chunkpos_t position;
maptile_t tiles[CHUNK_TILE_COUNT];
uint8_t meshCount;
meshvertex_t vertices[CHUNK_VERTEX_COUNT_MAX];
mesh_t meshes[CHUNK_MESH_COUNT_MAX];
uint8_t entities[CHUNK_ENTITY_COUNT_MAX];
} mapchunk_t;
/**
* Gets the tile index for a tile position within a chunk.
*
* @param position The position within the chunk.
* @return The tile index within the chunk.
*/
uint32_t mapChunkGetTileindex(const chunkpos_t position);
/**
* Checks if two chunk positions are equal.
*
* @param a The first chunk position.
* @param b The second chunk position.
* @return true if equal, false otherwise.
*/
bool_t mapChunkPositionIsEqual(const chunkpos_t a, const chunkpos_t b);
/**
* Renders the given map chunk.
*
* @param chunk The map chunk to render.
*/
void mapChunkRender(const mapchunk_t *chunk);
+35
View File
@@ -0,0 +1,35 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "maptile.h"
bool_t mapTileIsWalkable(const maptile_t tile) {
switch(tile) {
case TILE_SHAPE_NULL:
return false;
default:
return true;
}
}
bool_t mapTileIsRamp(const maptile_t tile) {
switch(tile) {
case TILE_SHAPE_RAMP_NORTH:
case TILE_SHAPE_RAMP_SOUTH:
case TILE_SHAPE_RAMP_EAST:
case TILE_SHAPE_RAMP_WEST:
case TILE_SHAPE_RAMP_NORTHEAST:
case TILE_SHAPE_RAMP_NORTHWEST:
case TILE_SHAPE_RAMP_SOUTHEAST:
case TILE_SHAPE_RAMP_SOUTHWEST:
return true;
default:
return false;
}
}
+28
View File
@@ -0,0 +1,28 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "duskdefs.h"
// #include "rpg/entity/entitydir.h"
typedef uint8_t maptile_t;
/**
* Returns whether or not the given tile is walkable.
*
* @param tile The tile to check.
* @return bool_t True if walkable, false if not.
*/
bool_t mapTileIsWalkable(const maptile_t tile);
/**
* Returns whether or not the given tile is a ramp tile.
*
* @param tile The tile to check.
* @return bool_t True if ramp, false if not.
*/
bool_t mapTileIsRamp(const maptile_t tile);
+97
View File
@@ -0,0 +1,97 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "worldpos.h"
#include "assert/assert.h"
bool_t worldPosIsEqual(const worldpos_t a, const worldpos_t b) {
return a.x == b.x && a.y == b.y && a.z == b.z;
}
void chunkPosToWorldPos(const chunkpos_t* chunkPos, worldpos_t* out) {
assertNotNull(chunkPos, "Chunk position pointer cannot be NULL");
assertNotNull(out, "Output world position pointer cannot be NULL");
out->x = (worldunit_t)(chunkPos->x * CHUNK_WIDTH);
out->y = (worldunit_t)(chunkPos->y * CHUNK_HEIGHT);
out->z = (worldunit_t)(chunkPos->z * CHUNK_DEPTH);
}
void worldPosToChunkPos(const worldpos_t* worldPos, chunkpos_t* out) {
assertNotNull(worldPos, "World position pointer cannot be NULL");
assertNotNull(out, "Output chunk position pointer cannot be NULL");
if(worldPos->x < 0) {
out->x = (chunkunit_t)((worldPos->x - (CHUNK_WIDTH - 1)) / CHUNK_WIDTH);
} else {
out->x = (chunkunit_t)(worldPos->x / CHUNK_WIDTH);
}
if(worldPos->y < 0) {
out->y = (chunkunit_t)((worldPos->y - (CHUNK_HEIGHT - 1)) / CHUNK_HEIGHT);
} else {
out->y = (chunkunit_t)(worldPos->y / CHUNK_HEIGHT);
}
if(worldPos->z < 0) {
out->z = (chunkunit_t)((worldPos->z - (CHUNK_DEPTH - 1)) / CHUNK_DEPTH);
} else {
out->z = (chunkunit_t)(worldPos->z / CHUNK_DEPTH);
}
}
chunktileindex_t worldPosToChunkTileIndex(const worldpos_t* worldPos) {
assertNotNull(worldPos, "World position pointer cannot be NULL");
uint8_t localX, localY, localZ;
if(worldPos->x < 0) {
localX = (uint8_t)(
(CHUNK_WIDTH - 1) - ((-worldPos->x - 1) % CHUNK_WIDTH)
);
} else {
localX = (uint8_t)(worldPos->x % CHUNK_WIDTH);
}
if(worldPos->y < 0) {
localY = (uint8_t)(
(CHUNK_HEIGHT - 1) - ((-worldPos->y - 1) % CHUNK_HEIGHT)
);
} else {
localY = (uint8_t)(worldPos->y % CHUNK_HEIGHT);
}
if(worldPos->z < 0) {
localZ = (uint8_t)(
(CHUNK_DEPTH - 1) - ((-worldPos->z - 1) % CHUNK_DEPTH)
);
} else {
localZ = (uint8_t)(worldPos->z % CHUNK_DEPTH);
}
chunktileindex_t chunkTileIndex = (chunktileindex_t)(
(localZ * CHUNK_WIDTH * CHUNK_HEIGHT) +
(localY * CHUNK_WIDTH) +
localX
);
assertTrue(
chunkTileIndex < CHUNK_TILE_COUNT,
"Calculated chunk tile index is out of bounds"
);
return chunkTileIndex;
}
chunkindex_t chunkPosToIndex(const chunkpos_t* pos) {
assertNotNull(pos, "Chunk position pointer cannot be NULL");
chunkindex_t chunkIndex = (chunkindex_t)(
(pos->z * MAP_CHUNK_WIDTH * MAP_CHUNK_HEIGHT) +
(pos->y * MAP_CHUNK_WIDTH) +
pos->x
);
return chunkIndex;
}
+75
View File
@@ -0,0 +1,75 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "dusk.h"
#include "duskdefs.h"
#define CHUNK_TILE_COUNT (CHUNK_WIDTH * CHUNK_HEIGHT * CHUNK_DEPTH)
#define MAP_CHUNK_WIDTH 3
#define MAP_CHUNK_HEIGHT 3
#define MAP_CHUNK_DEPTH 3
#define MAP_CHUNK_COUNT (MAP_CHUNK_WIDTH * MAP_CHUNK_HEIGHT * MAP_CHUNK_DEPTH)
typedef int16_t worldunit_t;
typedef int16_t chunkunit_t;
typedef int16_t chunkindex_t;
typedef uint32_t chunktileindex_t;
typedef int32_t worldunits_t;
typedef int32_t chunkunits_t;
typedef struct worldpos_s {
worldunit_t x, y, z;
} worldpos_t;
typedef struct chunkpos_t {
chunkunit_t x, y, z;
} chunkpos_t;
/**
* Compares two world positions for equality.
*
* @param a The first world position.
* @param b The second world position.
* @return true if equal, false otherwise.
*/
bool_t worldPosIsEqual(const worldpos_t a, const worldpos_t b);
/**
* Converts a world position to a chunk position.
*
* @param worldPos The world position.
* @param out The output chunk position.
*/
void chunkPosToWorldPos(const chunkpos_t* chunkPos, worldpos_t* out);
/**
* Converts a chunk position to a world position.
*
* @param worldPos The world position.
* @param out The output chunk position.
*/
void worldPosToChunkPos(const worldpos_t* worldPos, chunkpos_t* out);
/**
* Converts a position in world-space to an index inside a chunk that the tile
* resides in.
*
* @param worldPos The world position.
* @return The tile index within the chunk.
*/
chunktileindex_t worldPosToChunkTileIndex(const worldpos_t* worldPos);
/**
* Converts a chunk position to a world position.
*
* @param worldPos The world position.
* @param out The output chunk position.
*/
chunkindex_t chunkPosToIndex(const chunkpos_t* pos);
+7
View File
@@ -0,0 +1,7 @@
# Copyright (c) 2026 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
# Subdirs
add_subdirectory(module)
+9
View File
@@ -0,0 +1,9 @@
# Copyright (c) 2026 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
# Subdirectories
add_subdirectory(item)
add_subdirectory(map)
add_subdirectory(story)
@@ -0,0 +1,10 @@
# Copyright (c) 2026 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
# Sources
target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
moduleitem.c
)
+262
View File
@@ -0,0 +1,262 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "moduleitem.h"
#include "item/inventory.h"
#include "item/backpack.h"
#include "assert/assert.h"
void moduleItem(scriptcontext_t *context) {
assertNotNull(context, "Script context cannot be NULL");
// Set item information
scriptContextExec(context, ITEM_SCRIPT);
// Bind BACKPACK const pointer
lua_pushlightuserdata(context->luaState, &BACKPACK);
lua_setglobal(context->luaState, "BACKPACK");
// Bind Methods
lua_register(
context->luaState, "inventoryItemExists", moduleInventoryItemExists
);
lua_register(context->luaState, "inventoryAdd", moduleInventoryAdd);
lua_register(context->luaState, "inventorySet", moduleInventorySet);
lua_register(context->luaState, "inventoryRemove", moduleInventoryRemove);
lua_register(context->luaState, "inventoryGetCount", moduleInventoryGetCount);
lua_register(context->luaState, "inventoryIsFull", moduleInventoryIsFull);
lua_register(context->luaState, "inventoryItemFull", moduleInventoryItemFull);
lua_register(context->luaState, "inventorySort", moduleInventorySort);
}
int moduleInventoryItemExists(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
// Expect inventory pointer and item ID
if(!lua_islightuserdata(L, 1)) {
luaL_error(L, "inventoryItemExists: Expected inventory pointer as first argument");
return 0;
}
if(!lua_isnumber(L, 2)) {
luaL_error(L, "inventoryItemExists: Expected item ID as second argument");
return 0;
}
inventory_t *inventory = (inventory_t *)lua_touserdata(L, 1);
itemid_t item = (itemid_t)lua_tonumber(L, 2);
assertNotNull(inventory, "Inventory pointer cannot be NULL.");
// Error if item is ITEM_ID_NULL
if(item == ITEM_ID_NULL) {
luaL_error(L, "inventoryItemExists: Item ID cannot be ITEM_ID_NULL");
return 0;
}
bool_t hasItem = inventoryItemExists(inventory, item);
lua_pushboolean(L, hasItem);
return 1;
}
int moduleInventorySet(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
// Requires inventory pointer, item ID and quantity (uint8_t)
if(!lua_islightuserdata(L, 1)) {
luaL_error(L, "inventorySet: Expected inventory pointer as first argument");
return 0;
}
if(!lua_isnumber(L, 2)) {
luaL_error(L, "inventorySet: Expected item ID as second argument");
return 0;
}
if(!lua_isnumber(L, 3)) {
luaL_error(L, "inventorySet: Expected quantity as third argument");
return 0;
}
inventory_t *inventory = (inventory_t *)lua_touserdata(L, 1);
itemid_t item = (itemid_t)lua_tonumber(L, 2);
uint8_t quantity = (uint8_t)lua_tonumber(L, 3);
assertNotNull(inventory, "Inventory pointer cannot be NULL.");
inventorySet(inventory, item, quantity);
return 0;
}
int moduleInventoryAdd(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
// Requires inventory pointer, item ID and quantity (uint8_t)
if(!lua_islightuserdata(L, 1)) {
luaL_error(L, "inventoryAdd: Expected inventory pointer as first argument");
return 0;
}
if(!lua_isnumber(L, 2)) {
luaL_error(L, "inventoryAdd: Expected item ID as second argument");
return 0;
}
if(!lua_isnumber(L, 3)) {
luaL_error(L, "inventoryAdd: Expected quantity as third argument");
return 0;
}
inventory_t *inventory = (inventory_t *)lua_touserdata(L, 1);
itemid_t item = (itemid_t)lua_tonumber(L, 2);
uint8_t quantity = (uint8_t)lua_tonumber(L, 3);
assertNotNull(inventory, "Inventory pointer cannot be NULL.");
inventoryAdd(inventory, item, quantity);
return 0;
}
int moduleInventoryRemove(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
// Requires inventory pointer and item ID
if(!lua_islightuserdata(L, 1)) {
luaL_error(L, "inventoryRemove: Expected inventory pointer as first argument");
return 0;
}
if(!lua_isnumber(L, 2)) {
luaL_error(L, "inventoryRemove: Expected item ID as second argument");
return 0;
}
inventory_t *inventory = (inventory_t *)lua_touserdata(L, 1);
itemid_t item = (itemid_t)lua_tonumber(L, 2);
assertNotNull(inventory, "Inventory pointer cannot be NULL.");
// if there is a third argument (quantity), then we are actually doing a
// partial removal.
if(lua_gettop(L) >= 3) {
if(!lua_isnumber(L, 3)) {
luaL_error(L, "inventoryRemove: Expected quantity as third argument");
return 0;
}
uint8_t amount = (uint8_t)lua_tonumber(L, 3);
uint8_t currentQuantity = inventoryGetCount(inventory, item);
if(amount >= currentQuantity) {
// Remove entire stack
inventoryRemove(inventory, item);
return 0;
}
// Set new quantity
inventorySet(inventory, item, currentQuantity - amount);
return 0;
}
inventoryRemove(inventory, item);
return 0;
}
int moduleInventoryGetCount(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
// Requires inventory pointer and item ID
if(!lua_islightuserdata(L, 1)) {
luaL_error(L, "inventoryGetCount: Expected inventory pointer as first argument");
return 0;
}
if(!lua_isnumber(L, 2)) {
luaL_error(L, "inventoryGetCount: Expected item ID as second argument");
return 0;
}
inventory_t *inventory = (inventory_t *)lua_touserdata(L, 1);
itemid_t item = (itemid_t)lua_tonumber(L, 2);
assertNotNull(inventory, "Inventory pointer cannot be NULL.");
uint8_t count = inventoryGetCount(inventory, item);
lua_pushnumber(L, count);
return 1;
}
int moduleInventoryIsFull(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
// Requires inventory pointer
if(!lua_islightuserdata(L, 1)) {
luaL_error(L, "inventoryIsFull: Expected inventory pointer as first argument");
return 0;
}
inventory_t *inventory = (inventory_t *)lua_touserdata(L, 1);
assertNotNull(inventory, "Inventory pointer cannot be NULL.");
bool_t isFull = inventoryIsFull(inventory);
lua_pushboolean(L, isFull);
return 1;
}
int moduleInventoryItemFull(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
// Requires inventory pointer and item ID
if(!lua_islightuserdata(L, 1)) {
luaL_error(L, "inventoryItemFull: Expected inventory pointer as first argument");
return 0;
}
if(!lua_isnumber(L, 2)) {
luaL_error(L, "inventoryItemFull: Expected item ID as second argument");
return 0;
}
inventory_t *inventory = (inventory_t *)lua_touserdata(L, 1);
itemid_t item = (itemid_t)lua_tonumber(L, 2);
assertNotNull(inventory, "Inventory pointer cannot be NULL.");
bool_t isFull = inventoryItemFull(inventory, item);
lua_pushboolean(L, isFull);
return 1;
}
int moduleInventorySort(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
// Requires inventory pointer, sort type and reverse flag
if(!lua_islightuserdata(L, 1)) {
luaL_error(L, "inventorySort: Expected inventory pointer as first argument");
return 0;
}
if(!lua_isnumber(L, 2)) {
luaL_error(L, "inventorySort: Expected sort type as second argument");
return 0;
}
// Optional, reverse
bool_t reverse = false;
if(lua_gettop(L) >= 3) {
if(!lua_isboolean(L, 3)) {
luaL_error(L, "inventorySort: Expected reverse flag as third argument");
return 0;
}
reverse = (bool_t)lua_toboolean(L, 3);
}
inventory_t *inventory = (inventory_t *)lua_touserdata(L, 1);
inventorysort_t sortBy = (inventorysort_t)lua_tonumber(L, 2);
assertNotNull(inventory, "Inventory pointer cannot be NULL.");
inventorySort(inventory, sortBy, reverse);
return 0;
}
@@ -0,0 +1,80 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "script/scriptcontext.h"
/**
* Register item functions to the given script context.
*
* @param context The script context to register item functions to.
*/
void moduleItem(scriptcontext_t *context);
/**
* Script binding for checking if an item exists in an inventory.
*
* @param L The Lua state.
* @return Number of return values on the Lua stack.
*/
int moduleInventoryItemExists(lua_State *L);
/**
* Script binding for adding an item to an inventory.
*
* @param L The Lua state.
* @return Number of return values on the Lua stack.
*/
int moduleInventorySet(lua_State *L);
/**
* Script binding for setting the quantity of an item in an inventory.
*
* @param L The Lua state.
* @return Number of return values on the Lua stack.
*/
int moduleInventoryAdd(lua_State *L);
/**
* Script binding for removing an item from an inventory.
*
* @param L The Lua state.
* @return Number of return values on the Lua stack.
*/
int moduleInventoryRemove(lua_State *L);
/**
* Script binding for getting the count of an item in an inventory.
*
* @param L The Lua state.
* @return Number of return values on the Lua stack.
*/
int moduleInventoryGetCount(lua_State *L);
/**
* Script binding for checking if an inventory is full.
*
* @param L The Lua state.
* @return Number of return values on the Lua stack.
*/
int moduleInventoryIsFull(lua_State *L);
/**
* Script binding for checking if an item stack in an inventory is full.
*
* @param L The Lua state.
* @return Number of return values on the Lua stack.
*/
int moduleInventoryItemFull(lua_State *L);
/**
* Script binding for sorting an inventory.
*
* @param L The Lua state.
* @return Number of return values on the Lua stack.
*/
int moduleInventorySort(lua_State *L);
@@ -0,0 +1,10 @@
# Copyright (c) 2026 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
# Sources
target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
modulemap.c
)
+60
View File
@@ -0,0 +1,60 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "modulemap.h"
#include "assert/assert.h"
#include "map/map.h"
void moduleMap(scriptcontext_t *ctx) {
assertNotNull(ctx, "Script context cannot be NULL");
// Register map functions
lua_register(ctx->luaState, "mapIsLoaded", moduleMapIsLoaded);
lua_register(ctx->luaState, "mapGetTile", moduleMapGetTile);
lua_register(ctx->luaState, "mapRender", moduleMapRender);
lua_register(ctx->luaState, "mapLoad", moduleMapLoad);
}
int moduleMapIsLoaded(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL.");
lua_pushboolean(L, mapIsLoaded());
return 1;
}
int moduleMapGetTile(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL.");
return 1;
}
int moduleMapRender(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL.");
mapRender();
return 0;
}
int moduleMapLoad(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL.");
if(!lua_isstring(L, 1)) {
luaL_error(L, "Expected string as first argument (file path).");
return 0;
}
const char_t *path = lua_tostring(L, 1);
// Optional position.
chunkpos_t position = {0, 0, 0};
errorret_t err = mapLoad(path, position);
if(err.code != ERROR_OK) {
errorCatch(errorPrint(err));
luaL_error(L, "Failed to load map!");
return 0;
}
return 0;
}
+48
View File
@@ -0,0 +1,48 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "script/scriptcontext.h"
/**
* Register map functions to the given script context.
*
* @param context The script context to register map functions to.
*/
void moduleMap(scriptcontext_t *context);
/**
* Script function to check if a map is loaded.
*
* @param L The Lua state.
* @return Number of return values on the Lua stack.
*/
int moduleMapIsLoaded(lua_State *L);
/**
* Script function to get the tile at a given world position.
*
* @param L The Lua state.
* @return Number of return values on the Lua stack.
*/
int moduleMapGetTile(lua_State *L);
/**
* Script function to render the map.
*
* @param L The Lua state.
* @return Number of return values on the Lua stack.
*/
int moduleMapRender(lua_State *L);
/**
* Script function to load a map from a given file path.
*
* @param L The Lua state.
* @return Number of return values on the Lua stack.
*/
int moduleMapLoad(lua_State *L);
@@ -0,0 +1,10 @@
# Copyright (c) 2026 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
# Sources
target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
modulestoryflag.c
)
@@ -0,0 +1,109 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "modulestoryflag.h"
#include "assert/assert.h"
#include "story/storyflag.h"
void moduleStoryFlag(scriptcontext_t *context) {
assertNotNull(context, "Script context cannot be NULL");
lua_register(context->luaState, "storyFlagGet", moduleStoryFlagGet);
lua_register(context->luaState, "storyFlagSet", moduleStoryFlagSet);
lua_register(
context->luaState, "storyFlagIncrement", moduleStoryFlagIncrement
);
lua_register(
context->luaState, "storyFlagDecrement", moduleStoryFlagDecrement
);
}
int moduleStoryFlagGet(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
// Require story flag ID argument
if(!lua_isnumber(L, 1)) {
luaL_error(L, "Expected flag ID.");
return 0;
}
storyflag_t flag = (storyflag_t)lua_tonumber(L, 1);
if(flag <= STORY_FLAG_NULL || flag >= STORY_FLAG_COUNT) {
luaL_error(L, "Invalid flag ID %d", flag);
return 0;
}
storyflagvalue_t value = storyFlagGet(flag);
lua_pushnumber(L, value);
return 1;
}
int moduleStoryFlagSet(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
// Require story flag ID argument
if(!lua_isnumber(L, 1)) {
luaL_error(L, "Expected flag ID.");
return 0;
}
// Require story flag value argument
if(!lua_isnumber(L, 2)) {
luaL_error(L, "Expected flag value.");
return 0;
}
storyflag_t flag = (storyflag_t)lua_tonumber(L, 1);
if(flag <= STORY_FLAG_NULL || flag >= STORY_FLAG_COUNT) {
luaL_error(L, "Invalid flag ID %d", flag);
return 0;
}
storyflagvalue_t value = (storyflagvalue_t)lua_tonumber(L, 2);
storyFlagSet(flag, value);
return 0;
}
int moduleStoryFlagIncrement(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
// Require story flag ID argument
if(!lua_isnumber(L, 1)) {
luaL_error(L, "Expected flag ID.");
return 0;
}
storyflag_t flag = (storyflag_t)lua_tonumber(L, 1);
if(flag <= STORY_FLAG_NULL || flag >= STORY_FLAG_COUNT) {
luaL_error(L, "Invalid flag ID %d", flag);
return 0;
}
storyflagvalue_t value = storyFlagGet(flag);
storyFlagSet(flag, value + 1);
return 0;
}
int moduleStoryFlagDecrement(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
// Require story flag ID argument
if(!lua_isnumber(L, 1)) {
luaL_error(L, "Expected flag ID.");
return 0;
}
storyflag_t flag = (storyflag_t)lua_tonumber(L, 1);
if(flag <= STORY_FLAG_NULL || flag >= STORY_FLAG_COUNT) {
luaL_error(L, "Invalid flag ID %d", flag);
return 0;
}
storyflagvalue_t value = storyFlagGet(flag);
storyFlagSet(flag, value - 1);
return 0;
}
@@ -0,0 +1,48 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "script/scriptcontext.h"
/**
* Register story flag module functions to the given script context.
*
* @param context The script context to register story flag module functions to.
*/
void moduleStoryFlag(scriptcontext_t *context);
/**
* Script binding for getting the value of a story flag.
*
* @param L The Lua state.
* @return Number of return values on the Lua stack.
*/
int moduleStoryFlagGet(lua_State *L);
/**
* Script binding for setting the value of a story flag.
*
* @param L The Lua state.
* @return Number of return values on the Lua stack.
*/
int moduleStoryFlagSet(lua_State *L);
/**
* Script binding for incrementing a story flag.
*
* @param L The Lua state.
* @return Number of return values on the Lua stack.
*/
int moduleStoryFlagIncrement(lua_State *L);
/**
* Script binding for decrementing a story flag.
*
* @param L The Lua state.
* @return Number of return values on the Lua stack.
*/
int moduleStoryFlagDecrement(lua_State *L);
+16
View File
@@ -0,0 +1,16 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "script/module/item/moduleitem.h"
#include "script/module/story/modulestoryflag.h"
#include "script/module/map/modulemap.h"
#define SCRIPT_GAME_LIST \
{ .name = "item", .callback = moduleItem }, \
{ .name = "storyflag", .callback = moduleStoryFlag }, \
{ .name = "map", .callback = moduleMap },
+19
View File
@@ -0,0 +1,19 @@
# Copyright (c) 2026 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
# Sources
target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
storyflag.c
)
# Story Flag Definitions
dusk_run_python(
dusk_story_defs
tools.story.csv
--csv ${CMAKE_CURRENT_SOURCE_DIR}/storyflag.csv
--header-file ${DUSK_GENERATED_HEADERS_DIR}/story/storyflagvalue.h
)
add_dependencies(${DUSK_LIBRARY_TARGET_NAME} dusk_story_defs)
+14
View File
@@ -0,0 +1,14 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "storyflag.h"
#include "assert/assert.h"
void storyFlagSet(const storyflag_t flag, const storyflagvalue_t value) {
assertTrue(flag > STORY_FLAG_NULL && flag < STORY_FLAG_COUNT, "Bad Flag");
STORY_FLAG_VALUES[flag] = value;
}
+2
View File
@@ -0,0 +1,2 @@
id,description,initial
test,"Test flag for debugging purposes",1
1 id description initial
2 test Test flag for debugging purposes 1
+25
View File
@@ -0,0 +1,25 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "story/storyflagvalue.h"
/**
* Gets the value of a story flag.
*
* @param flag The story flag to get.
* @return The value of the story flag.
*/
#define storyFlagGet(flag) (STORY_FLAG_VALUES[(flag)])
/**
* Sets the value of a story flag.
*
* @param flag The story flag to set.
* @param value The value to set the story flag to.
*/
void storyFlagSet(const storyflag_t flag, const storyflagvalue_t value);
+11
View File
@@ -0,0 +1,11 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "dusk.h"
typedef uint8_t storyflagvalue_t;